diff --git a/CHANGELOG.md b/CHANGELOG.md index 4626cd7..e8bb075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu ## Unreleased +- Fix: the skill runbooks now write a portable `manifest.json`. Step 9 (full build) and the `--update` reference called `save_manifest(...)` without `root=`, so manifest keys were stored as absolute paths; cloning or moving the repo then broke `graphify --update` — every cached file missed and the whole corpus re-extracted. All four runbook call sites (the lean-core `skill.md`, the Aider/Devin monoliths, and the shared `--update` reference) now pass `root='INPUT_PATH'`, relativizing keys to the scan root to match the native `graphify update` path. The monolith change is registered as a new sanctioned change-class in the round-trip guard (#1417). - Fix: hyperedge `source_file` is now relativized to the scan root like nodes and edges. `build_from_json(root=...)` relativized `source_file` on `nodes[]` and `links[]`, but stored `graph.hyperedges[]` verbatim, so a semantic subagent's absolute path (e.g. `/Users/.../CLAUDE.md`) leaked into `graph.json`. The fix lives in `build_from_json` (not `to_json`, which has no `root` to relativize against) and mirrors the existing node/edge handling (#1418). - Fix: the `GRAPHIFY_OUT` override is now honoured everywhere instead of a hardcoded `"graphify-out"` literal. The name is consolidated into a single `graphify.paths` module (was duplicated across `__main__`, `cache`, and `watch`); `security.validate_graph_path`'s `base=None` discovery + fallback, `callflow_html`'s project-root resolution, and the post-commit/post-checkout hook bodies (which now read the env var at hook-run time) all use it. Previously a renamed output dir validated against the wrong base or made the hook miss `.graphify_root` (#1423). diff --git a/graphify/skill-aider.md b/graphify/skill-aider.md index 7cea232..c2872e3 100644 --- a/graphify/skill-aider.md +++ b/graphify/skill-aider.md @@ -674,7 +674,7 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('.graphify_detect.json').read_text()) -save_manifest(detect['files']) +save_manifest(detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('.graphify_extract.json').read_text()) diff --git a/graphify/skill-amp.md b/graphify/skill-amp.md index 8cea9f3..411b315 100644 --- a/graphify/skill-amp.md +++ b/graphify/skill-amp.md @@ -522,7 +522,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -553,6 +556,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index 3df00d5..8e6c100 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index ee64830..a2b77e6 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -522,7 +522,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -553,6 +556,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-copilot.md b/graphify/skill-copilot.md index 3df00d5..8e6c100 100644 --- a/graphify/skill-copilot.md +++ b/graphify/skill-copilot.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-devin.md b/graphify/skill-devin.md index 6cbb55d..4e72cf1 100644 --- a/graphify/skill-devin.md +++ b/graphify/skill-devin.md @@ -792,7 +792,7 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text()) -save_manifest(detect['files']) +save_manifest(detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index 38f2781..40178e8 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -522,7 +522,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -553,6 +556,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index 6ccb172..8e54ec8 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-kiro.md b/graphify/skill-kiro.md index 3df00d5..8e6c100 100644 --- a/graphify/skill-kiro.md +++ b/graphify/skill-kiro.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index aa2e9a1..373473b 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -517,7 +517,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -548,6 +551,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-pi.md b/graphify/skill-pi.md index 3df00d5..8e6c100 100644 --- a/graphify/skill-pi.md +++ b/graphify/skill-pi.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index 2c0a574..8ed6ca2 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -523,7 +523,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -554,6 +557,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-vscode.md b/graphify/skill-vscode.md index 2166d08..e854363 100644 --- a/graphify/skill-vscode.md +++ b/graphify/skill-vscode.md @@ -521,7 +521,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -552,6 +555,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index 64d2cbc..0c7a534 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -547,7 +547,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -578,6 +581,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skill.md b/graphify/skill.md index 3df00d5..8e6c100 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/graphify/skills/amp/references/update.md b/graphify/skills/amp/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/amp/references/update.md +++ b/graphify/skills/amp/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/claude/references/update.md b/graphify/skills/claude/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/claude/references/update.md +++ b/graphify/skills/claude/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/claw/references/update.md b/graphify/skills/claw/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/claw/references/update.md +++ b/graphify/skills/claw/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/codex/references/update.md b/graphify/skills/codex/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/codex/references/update.md +++ b/graphify/skills/codex/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/copilot/references/update.md b/graphify/skills/copilot/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/copilot/references/update.md +++ b/graphify/skills/copilot/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/droid/references/update.md b/graphify/skills/droid/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/droid/references/update.md +++ b/graphify/skills/droid/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/kilo/references/update.md b/graphify/skills/kilo/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/kilo/references/update.md +++ b/graphify/skills/kilo/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/kiro/references/update.md b/graphify/skills/kiro/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/kiro/references/update.md +++ b/graphify/skills/kiro/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/opencode/references/update.md b/graphify/skills/opencode/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/opencode/references/update.md +++ b/graphify/skills/opencode/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/pi/references/update.md b/graphify/skills/pi/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/pi/references/update.md +++ b/graphify/skills/pi/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/trae/references/update.md b/graphify/skills/trae/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/trae/references/update.md +++ b/graphify/skills/trae/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/vscode/references/update.md b/graphify/skills/vscode/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/vscode/references/update.md +++ b/graphify/skills/vscode/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/windows/references/update.md b/graphify/skills/windows/references/update.md index b00917c..fa26121 100644 --- a/graphify/skills/windows/references/update.md +++ b/graphify/skills/windows/references/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tests/test_skillgen.py b/tests/test_skillgen.py index 08a875c..95755b3 100644 --- a/tests/test_skillgen.py +++ b/tests/test_skillgen.py @@ -503,6 +503,32 @@ def test_monoliths_carry_the_1392_runbook_fixes(): assert "if not wrote:" in body +def test_generated_runbooks_pass_root_to_save_manifest(): + """#1417: every save_manifest call in a shipped runbook threads root=. + + Without root=, save_manifest stores absolute path keys, so a clone or move + breaks --update (every cached file misses and the whole corpus re-extracts). + The full-build (skill.md / monoliths) and the --update reference all relativize + the manifest to the scan root via root='INPUT_PATH'. This guards the actual + shipped artifacts; --check keeps them in sync with the fragments. + """ + targets = [ + REPO_ROOT / "graphify" / "skill.md", + REPO_ROOT / "graphify" / "skill-aider.md", + REPO_ROOT / "graphify" / "skill-devin.md", + ] + targets += sorted((REPO_ROOT / "graphify" / "skills").glob("*/references/update.md")) + checked = 0 + for path in targets: + for ln in path.read_text(encoding="utf-8").splitlines(): + if "save_manifest(" in ln and "import" not in ln: + checked += 1 + assert "root=" in ln, ( + f"{path.relative_to(REPO_ROOT)}: save_manifest without root= (#1417): {ln.strip()!r}" + ) + assert checked >= 4, f"expected save_manifest calls across the runbooks, found {checked}" + + def test_devin_keeps_its_multi_field_frontmatter(): """devin renders inline, so its 4+-field frontmatter is preserved verbatim.""" platforms = gen.load_platforms() diff --git a/tools/skillgen/expected/graphify__skill-aider.md b/tools/skillgen/expected/graphify__skill-aider.md index 7cea232..c2872e3 100644 --- a/tools/skillgen/expected/graphify__skill-aider.md +++ b/tools/skillgen/expected/graphify__skill-aider.md @@ -674,7 +674,7 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('.graphify_detect.json').read_text()) -save_manifest(detect['files']) +save_manifest(detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('.graphify_extract.json').read_text()) diff --git a/tools/skillgen/expected/graphify__skill-amp.md b/tools/skillgen/expected/graphify__skill-amp.md index 8cea9f3..411b315 100644 --- a/tools/skillgen/expected/graphify__skill-amp.md +++ b/tools/skillgen/expected/graphify__skill-amp.md @@ -522,7 +522,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -553,6 +556,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-claw.md b/tools/skillgen/expected/graphify__skill-claw.md index 3df00d5..8e6c100 100644 --- a/tools/skillgen/expected/graphify__skill-claw.md +++ b/tools/skillgen/expected/graphify__skill-claw.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-codex.md b/tools/skillgen/expected/graphify__skill-codex.md index ee64830..a2b77e6 100644 --- a/tools/skillgen/expected/graphify__skill-codex.md +++ b/tools/skillgen/expected/graphify__skill-codex.md @@ -522,7 +522,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -553,6 +556,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-copilot.md b/tools/skillgen/expected/graphify__skill-copilot.md index 3df00d5..8e6c100 100644 --- a/tools/skillgen/expected/graphify__skill-copilot.md +++ b/tools/skillgen/expected/graphify__skill-copilot.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-devin.md b/tools/skillgen/expected/graphify__skill-devin.md index 6cbb55d..4e72cf1 100644 --- a/tools/skillgen/expected/graphify__skill-devin.md +++ b/tools/skillgen/expected/graphify__skill-devin.md @@ -792,7 +792,7 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text()) -save_manifest(detect['files']) +save_manifest(detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) diff --git a/tools/skillgen/expected/graphify__skill-droid.md b/tools/skillgen/expected/graphify__skill-droid.md index 38f2781..40178e8 100644 --- a/tools/skillgen/expected/graphify__skill-droid.md +++ b/tools/skillgen/expected/graphify__skill-droid.md @@ -522,7 +522,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -553,6 +556,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index 6ccb172..8e54ec8 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index 3df00d5..8e6c100 100644 --- a/tools/skillgen/expected/graphify__skill-kiro.md +++ b/tools/skillgen/expected/graphify__skill-kiro.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-opencode.md b/tools/skillgen/expected/graphify__skill-opencode.md index aa2e9a1..373473b 100644 --- a/tools/skillgen/expected/graphify__skill-opencode.md +++ b/tools/skillgen/expected/graphify__skill-opencode.md @@ -517,7 +517,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -548,6 +551,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-pi.md b/tools/skillgen/expected/graphify__skill-pi.md index 3df00d5..8e6c100 100644 --- a/tools/skillgen/expected/graphify__skill-pi.md +++ b/tools/skillgen/expected/graphify__skill-pi.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-trae.md b/tools/skillgen/expected/graphify__skill-trae.md index 2c0a574..8ed6ca2 100644 --- a/tools/skillgen/expected/graphify__skill-trae.md +++ b/tools/skillgen/expected/graphify__skill-trae.md @@ -523,7 +523,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -554,6 +557,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-vscode.md b/tools/skillgen/expected/graphify__skill-vscode.md index 2166d08..e854363 100644 --- a/tools/skillgen/expected/graphify__skill-vscode.md +++ b/tools/skillgen/expected/graphify__skill-vscode.md @@ -521,7 +521,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -552,6 +555,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill-windows.md b/tools/skillgen/expected/graphify__skill-windows.md index 64d2cbc..0c7a534 100644 --- a/tools/skillgen/expected/graphify__skill-windows.md +++ b/tools/skillgen/expected/graphify__skill-windows.md @@ -547,7 +547,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -578,6 +581,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skill.md b/tools/skillgen/expected/graphify__skill.md index 3df00d5..8e6c100 100644 --- a/tools/skillgen/expected/graphify__skill.md +++ b/tools/skillgen/expected/graphify__skill.md @@ -525,7 +525,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -556,6 +559,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/expected/graphify__skills__amp__references__update.md b/tools/skillgen/expected/graphify__skills__amp__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__amp__references__update.md +++ b/tools/skillgen/expected/graphify__skills__amp__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__claude__references__update.md b/tools/skillgen/expected/graphify__skills__claude__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__claude__references__update.md +++ b/tools/skillgen/expected/graphify__skills__claude__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__claw__references__update.md b/tools/skillgen/expected/graphify__skills__claw__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__claw__references__update.md +++ b/tools/skillgen/expected/graphify__skills__claw__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__codex__references__update.md b/tools/skillgen/expected/graphify__skills__codex__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__codex__references__update.md +++ b/tools/skillgen/expected/graphify__skills__codex__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__copilot__references__update.md b/tools/skillgen/expected/graphify__skills__copilot__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__copilot__references__update.md +++ b/tools/skillgen/expected/graphify__skills__copilot__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__droid__references__update.md b/tools/skillgen/expected/graphify__skills__droid__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__droid__references__update.md +++ b/tools/skillgen/expected/graphify__skills__droid__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__kilo__references__update.md b/tools/skillgen/expected/graphify__skills__kilo__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__kilo__references__update.md +++ b/tools/skillgen/expected/graphify__skills__kilo__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__kiro__references__update.md b/tools/skillgen/expected/graphify__skills__kiro__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__kiro__references__update.md +++ b/tools/skillgen/expected/graphify__skills__kiro__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__opencode__references__update.md b/tools/skillgen/expected/graphify__skills__opencode__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__opencode__references__update.md +++ b/tools/skillgen/expected/graphify__skills__opencode__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__pi__references__update.md b/tools/skillgen/expected/graphify__skills__pi__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__pi__references__update.md +++ b/tools/skillgen/expected/graphify__skills__pi__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__trae__references__update.md b/tools/skillgen/expected/graphify__skills__trae__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__trae__references__update.md +++ b/tools/skillgen/expected/graphify__skills__trae__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__vscode__references__update.md b/tools/skillgen/expected/graphify__skills__vscode__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__vscode__references__update.md +++ b/tools/skillgen/expected/graphify__skills__vscode__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__windows__references__update.md b/tools/skillgen/expected/graphify__skills__windows__references__update.md index b00917c..fa26121 100644 --- a/tools/skillgen/expected/graphify__skills__windows__references__update.md +++ b/tools/skillgen/expected/graphify__skills__windows__references__update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/fragments/core/aider.md b/tools/skillgen/fragments/core/aider.md index 7cea232..c2872e3 100644 --- a/tools/skillgen/fragments/core/aider.md +++ b/tools/skillgen/fragments/core/aider.md @@ -674,7 +674,7 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('.graphify_detect.json').read_text()) -save_manifest(detect['files']) +save_manifest(detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('.graphify_extract.json').read_text()) diff --git a/tools/skillgen/fragments/core/core.md b/tools/skillgen/fragments/core/core.md index 3a06445..b4e127b 100644 --- a/tools/skillgen/fragments/core/core.md +++ b/tools/skillgen/fragments/core/core.md @@ -460,7 +460,10 @@ from graphify.detect import save_manifest detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) # In --update mode, 'all_files' carries the full corpus; 'files' is the changed # subset. Full-rebuild mode populates only 'files', so the fallback handles that. -save_manifest(detect.get('all_files') or detect['files']) +# root= relativizes the manifest keys to the scan root (same base as the build), +# so the on-disk manifest is portable across clones/machines and a later --update +# matches cached files instead of missing every one (#1417). +save_manifest(detect.get('all_files') or detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) @@ -491,6 +494,8 @@ find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` +Replace INPUT_PATH with the actual path (same value used in Steps 4-5) so the manifest is relativized to the scan root. + Tell the user (omit the obsidian line unless --obsidian was given): ``` Graph complete. Outputs in PATH_TO_DIR/graphify-out/ diff --git a/tools/skillgen/fragments/core/devin.md b/tools/skillgen/fragments/core/devin.md index 6cbb55d..4e72cf1 100644 --- a/tools/skillgen/fragments/core/devin.md +++ b/tools/skillgen/fragments/core/devin.md @@ -792,7 +792,7 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text()) -save_manifest(detect['files']) +save_manifest(detect['files'], root='INPUT_PATH') # Update cumulative cost tracker extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) diff --git a/tools/skillgen/fragments/references/shared/update.md b/tools/skillgen/fragments/references/shared/update.md index b00917c..fa26121 100644 --- a/tools/skillgen/fragments/references/shared/update.md +++ b/tools/skillgen/fragments/references/shared/update.md @@ -139,7 +139,10 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). -save_manifest(incremental['files']) +# root= matches the build_merge call above so the manifest keys stay relative to +# the scan root — portable across clones/machines, so --update keeps matching +# cached files instead of missing every one after a move (#1417). +save_manifest(incremental['files'], root='INPUT_PATH') print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/gen.py b/tools/skillgen/gen.py index 4b6ab2d..1304442 100644 --- a/tools/skillgen/gen.py +++ b/tools/skillgen/gen.py @@ -798,6 +798,21 @@ def _is_zero_node_guard_fix_line(line: str) -> bool: ) +def _is_manifest_root_fix_line(line: str) -> bool: + """Whether a line is part of the manifest-portability fix (#1417). + + The monolith Step 9 called ``save_manifest(detect['files'])`` with no + ``root=``, so the manifest stored absolute path keys and a clone or move + broke ``--update`` — every cached file missed and the whole corpus + re-extracted. The call now threads ``root='INPUT_PATH'`` so keys are + relativized to the scan root, matching the native ``graphify update`` path. + Both the old bare call (removed) and the new rooted call (added) match here; + the ``import`` guard avoids matching the ``from graphify.detect import + save_manifest`` line. + """ + return "save_manifest(" in line and "import" not in line + + # Every line that may differ between a rendered monolith and its pristine v8 # baseline. Each predicate documents one sanctioned change-class; a blank line is # allowed because the multi-line fix blocks insert spacing. Anything else failing @@ -810,6 +825,7 @@ _SANCTIONED_MONOLITH_DIFFS = ( _is_content_scope_fix_line, _is_cache_unlink_fix_line, _is_zero_node_guard_fix_line, + _is_manifest_root_fix_line, )