From 9c2e0fcfa2cc05796bdda503f3c4e4e2e5f6ec82 Mon Sep 17 00:00:00 2001 From: safishamsi Date: Mon, 20 Jul 2026 13:30:26 +0100 Subject: [PATCH] fix(skillgen): stamp only produced-output files in the update runbook (#2015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --update runbook's Step 9 stamped the entire detected corpus into the manifest, so a semantic file (doc/paper/image) whose chunk failed or was omitted was recorded as done and never re-queued on the next update — losing its content permanently. The runbook now builds the manifest the way the library extract path does: cli._stamped_manifest_files stamps only files that actually produced nodes/edges/hyperedges, dispatched-but-empty files have their stale hash cleared, and scan_corpus drops newly-excluded in-root rows. Applied to the Claude, Aider, and Devin skill bodies and the shared update reference; all 134 artifacts regenerated. gen.py gains a sanctioned monolith- diff predicate for the new stamping lines. --- graphify/skill-agents.md | 26 +++++++++++++++-- graphify/skill-aider.md | 13 +++++++-- graphify/skill-amp.md | 26 +++++++++++++++-- graphify/skill-claw.md | 26 +++++++++++++++-- graphify/skill-codex.md | 26 +++++++++++++++-- graphify/skill-copilot.md | 26 +++++++++++++++-- graphify/skill-devin.md | 13 +++++++-- graphify/skill-droid.md | 26 +++++++++++++++-- graphify/skill-kilo.md | 26 +++++++++++++++-- graphify/skill-kiro.md | 26 +++++++++++++++-- graphify/skill-opencode.md | 26 +++++++++++++++-- graphify/skill-pi.md | 26 +++++++++++++++-- graphify/skill-trae.md | 26 +++++++++++++++-- graphify/skill-vscode.md | 26 +++++++++++++++-- graphify/skill-windows.md | 26 +++++++++++++++-- graphify/skill.md | 26 +++++++++++++++-- graphify/skills/agents/references/update.md | 20 ++++++++++++- graphify/skills/amp/references/update.md | 20 ++++++++++++- graphify/skills/claude/references/update.md | 20 ++++++++++++- graphify/skills/claw/references/update.md | 20 ++++++++++++- graphify/skills/codex/references/update.md | 20 ++++++++++++- graphify/skills/copilot/references/update.md | 20 ++++++++++++- graphify/skills/droid/references/update.md | 20 ++++++++++++- graphify/skills/kilo/references/update.md | 20 ++++++++++++- graphify/skills/kiro/references/update.md | 20 ++++++++++++- graphify/skills/opencode/references/update.md | 20 ++++++++++++- graphify/skills/pi/references/update.md | 20 ++++++++++++- graphify/skills/trae/references/update.md | 20 ++++++++++++- graphify/skills/vscode/references/update.md | 20 ++++++++++++- graphify/skills/windows/references/update.md | 20 ++++++++++++- .../expected/graphify__skill-agents.md | 26 +++++++++++++++-- .../expected/graphify__skill-aider.md | 13 +++++++-- .../skillgen/expected/graphify__skill-amp.md | 26 +++++++++++++++-- .../skillgen/expected/graphify__skill-claw.md | 26 +++++++++++++++-- .../expected/graphify__skill-codex.md | 26 +++++++++++++++-- .../expected/graphify__skill-copilot.md | 26 +++++++++++++++-- .../expected/graphify__skill-devin.md | 13 +++++++-- .../expected/graphify__skill-droid.md | 26 +++++++++++++++-- .../skillgen/expected/graphify__skill-kilo.md | 26 +++++++++++++++-- .../skillgen/expected/graphify__skill-kiro.md | 26 +++++++++++++++-- .../expected/graphify__skill-opencode.md | 26 +++++++++++++++-- tools/skillgen/expected/graphify__skill-pi.md | 26 +++++++++++++++-- .../skillgen/expected/graphify__skill-trae.md | 26 +++++++++++++++-- .../expected/graphify__skill-vscode.md | 26 +++++++++++++++-- .../expected/graphify__skill-windows.md | 26 +++++++++++++++-- tools/skillgen/expected/graphify__skill.md | 26 +++++++++++++++-- ...ify__skills__agents__references__update.md | 20 ++++++++++++- ...aphify__skills__amp__references__update.md | 20 ++++++++++++- ...ify__skills__claude__references__update.md | 20 ++++++++++++- ...phify__skills__claw__references__update.md | 20 ++++++++++++- ...hify__skills__codex__references__update.md | 20 ++++++++++++- ...fy__skills__copilot__references__update.md | 20 ++++++++++++- ...hify__skills__droid__references__update.md | 20 ++++++++++++- ...phify__skills__kilo__references__update.md | 20 ++++++++++++- ...phify__skills__kiro__references__update.md | 20 ++++++++++++- ...y__skills__opencode__references__update.md | 20 ++++++++++++- ...raphify__skills__pi__references__update.md | 20 ++++++++++++- ...phify__skills__trae__references__update.md | 20 ++++++++++++- ...ify__skills__vscode__references__update.md | 20 ++++++++++++- ...fy__skills__windows__references__update.md | 20 ++++++++++++- tools/skillgen/fragments/core/aider.md | 13 +++++++-- tools/skillgen/fragments/core/core.md | 26 +++++++++++++++-- tools/skillgen/fragments/core/devin.md | 13 +++++++-- .../fragments/references/shared/update.md | 20 ++++++++++++- tools/skillgen/gen.py | 29 +++++++++++++++++++ 65 files changed, 1342 insertions(+), 99 deletions(-) diff --git a/graphify/skill-agents.md b/graphify/skill-agents.md index 19d5bea4e..20e288319 100644 --- a/graphify/skill-agents.md +++ b/graphify/skill-agents.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-aider.md b/graphify/skill-aider.md index 8db8f73f2..8d13335f0 100644 --- a/graphify/skill-aider.md +++ b/graphify/skill-aider.md @@ -677,10 +677,19 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('.graphify_detect.json').read_text()) -save_manifest(detect['files'], root='INPUT_PATH') +extract = json.loads(Path('.graphify_extract.json').read_text()) +# Stamp only semantic files that produced output so a failed chunk is re-queued next run, not lost (#2015). +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('.graphify_extract.json').read_text()) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-amp.md b/graphify/skill-amp.md index 19d5bea4e..20e288319 100644 --- a/graphify/skill-amp.md +++ b/graphify/skill-amp.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index bf9dd3431..977bb9da0 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index 04eb5705b..44613eaae 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-copilot.md b/graphify/skill-copilot.md index bf9dd3431..977bb9da0 100644 --- a/graphify/skill-copilot.md +++ b/graphify/skill-copilot.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-devin.md b/graphify/skill-devin.md index c444a852d..0866e4ae0 100644 --- a/graphify/skill-devin.md +++ b/graphify/skill-devin.md @@ -795,10 +795,19 @@ 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'], root='INPUT_PATH') +extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) +# Stamp only semantic files that produced output so a failed chunk is re-queued next run, not lost (#2015). +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index ffa94995d..56388a2a8 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index 96dfc2e85..b3d77a820 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-kiro.md b/graphify/skill-kiro.md index bf9dd3431..977bb9da0 100644 --- a/graphify/skill-kiro.md +++ b/graphify/skill-kiro.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index 6613434e8..87f283180 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -546,15 +546,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-pi.md b/graphify/skill-pi.md index bf9dd3431..977bb9da0 100644 --- a/graphify/skill-pi.md +++ b/graphify/skill-pi.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index 407f091b6..aff8cc0f5 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -552,15 +552,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-vscode.md b/graphify/skill-vscode.md index f7e7f8442..f80f9f369 100644 --- a/graphify/skill-vscode.md +++ b/graphify/skill-vscode.md @@ -550,15 +550,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index 9c7ab7420..325d749d9 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -576,15 +576,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skill.md b/graphify/skill.md index bf9dd3431..977bb9da0 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/graphify/skills/agents/references/update.md b/graphify/skills/agents/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/agents/references/update.md +++ b/graphify/skills/agents/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/amp/references/update.md b/graphify/skills/amp/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/amp/references/update.md +++ b/graphify/skills/amp/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/claude/references/update.md b/graphify/skills/claude/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/claude/references/update.md +++ b/graphify/skills/claude/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/claw/references/update.md b/graphify/skills/claw/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/claw/references/update.md +++ b/graphify/skills/claw/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/codex/references/update.md b/graphify/skills/codex/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/codex/references/update.md +++ b/graphify/skills/codex/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/copilot/references/update.md b/graphify/skills/copilot/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/copilot/references/update.md +++ b/graphify/skills/copilot/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/droid/references/update.md b/graphify/skills/droid/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/droid/references/update.md +++ b/graphify/skills/droid/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/kilo/references/update.md b/graphify/skills/kilo/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/kilo/references/update.md +++ b/graphify/skills/kilo/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/kiro/references/update.md b/graphify/skills/kiro/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/kiro/references/update.md +++ b/graphify/skills/kiro/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/opencode/references/update.md b/graphify/skills/opencode/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/opencode/references/update.md +++ b/graphify/skills/opencode/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/pi/references/update.md b/graphify/skills/pi/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/pi/references/update.md +++ b/graphify/skills/pi/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/trae/references/update.md b/graphify/skills/trae/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/trae/references/update.md +++ b/graphify/skills/trae/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/vscode/references/update.md b/graphify/skills/vscode/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/vscode/references/update.md +++ b/graphify/skills/vscode/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/graphify/skills/windows/references/update.md b/graphify/skills/windows/references/update.md index fa2612180..3632fd412 100644 --- a/graphify/skills/windows/references/update.md +++ b/graphify/skills/windows/references/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skill-agents.md b/tools/skillgen/expected/graphify__skill-agents.md index 19d5bea4e..20e288319 100644 --- a/tools/skillgen/expected/graphify__skill-agents.md +++ b/tools/skillgen/expected/graphify__skill-agents.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-aider.md b/tools/skillgen/expected/graphify__skill-aider.md index 8db8f73f2..8d13335f0 100644 --- a/tools/skillgen/expected/graphify__skill-aider.md +++ b/tools/skillgen/expected/graphify__skill-aider.md @@ -677,10 +677,19 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('.graphify_detect.json').read_text()) -save_manifest(detect['files'], root='INPUT_PATH') +extract = json.loads(Path('.graphify_extract.json').read_text()) +# Stamp only semantic files that produced output so a failed chunk is re-queued next run, not lost (#2015). +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('.graphify_extract.json').read_text()) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-amp.md b/tools/skillgen/expected/graphify__skill-amp.md index 19d5bea4e..20e288319 100644 --- a/tools/skillgen/expected/graphify__skill-amp.md +++ b/tools/skillgen/expected/graphify__skill-amp.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-claw.md b/tools/skillgen/expected/graphify__skill-claw.md index bf9dd3431..977bb9da0 100644 --- a/tools/skillgen/expected/graphify__skill-claw.md +++ b/tools/skillgen/expected/graphify__skill-claw.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-codex.md b/tools/skillgen/expected/graphify__skill-codex.md index 04eb5705b..44613eaae 100644 --- a/tools/skillgen/expected/graphify__skill-codex.md +++ b/tools/skillgen/expected/graphify__skill-codex.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-copilot.md b/tools/skillgen/expected/graphify__skill-copilot.md index bf9dd3431..977bb9da0 100644 --- a/tools/skillgen/expected/graphify__skill-copilot.md +++ b/tools/skillgen/expected/graphify__skill-copilot.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-devin.md b/tools/skillgen/expected/graphify__skill-devin.md index c444a852d..0866e4ae0 100644 --- a/tools/skillgen/expected/graphify__skill-devin.md +++ b/tools/skillgen/expected/graphify__skill-devin.md @@ -795,10 +795,19 @@ 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'], root='INPUT_PATH') +extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) +# Stamp only semantic files that produced output so a failed chunk is re-queued next run, not lost (#2015). +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-droid.md b/tools/skillgen/expected/graphify__skill-droid.md index ffa94995d..56388a2a8 100644 --- a/tools/skillgen/expected/graphify__skill-droid.md +++ b/tools/skillgen/expected/graphify__skill-droid.md @@ -551,15 +551,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index 96dfc2e85..b3d77a820 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index bf9dd3431..977bb9da0 100644 --- a/tools/skillgen/expected/graphify__skill-kiro.md +++ b/tools/skillgen/expected/graphify__skill-kiro.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-opencode.md b/tools/skillgen/expected/graphify__skill-opencode.md index 6613434e8..87f283180 100644 --- a/tools/skillgen/expected/graphify__skill-opencode.md +++ b/tools/skillgen/expected/graphify__skill-opencode.md @@ -546,15 +546,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-pi.md b/tools/skillgen/expected/graphify__skill-pi.md index bf9dd3431..977bb9da0 100644 --- a/tools/skillgen/expected/graphify__skill-pi.md +++ b/tools/skillgen/expected/graphify__skill-pi.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-trae.md b/tools/skillgen/expected/graphify__skill-trae.md index 407f091b6..aff8cc0f5 100644 --- a/tools/skillgen/expected/graphify__skill-trae.md +++ b/tools/skillgen/expected/graphify__skill-trae.md @@ -552,15 +552,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-vscode.md b/tools/skillgen/expected/graphify__skill-vscode.md index f7e7f8442..f80f9f369 100644 --- a/tools/skillgen/expected/graphify__skill-vscode.md +++ b/tools/skillgen/expected/graphify__skill-vscode.md @@ -550,15 +550,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill-windows.md b/tools/skillgen/expected/graphify__skill-windows.md index 9c7ab7420..325d749d9 100644 --- a/tools/skillgen/expected/graphify__skill-windows.md +++ b/tools/skillgen/expected/graphify__skill-windows.md @@ -576,15 +576,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skill.md b/tools/skillgen/expected/graphify__skill.md index bf9dd3431..977bb9da0 100644 --- a/tools/skillgen/expected/graphify__skill.md +++ b/tools/skillgen/expected/graphify__skill.md @@ -554,15 +554,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/expected/graphify__skills__agents__references__update.md b/tools/skillgen/expected/graphify__skills__agents__references__update.md index fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__agents__references__update.md +++ b/tools/skillgen/expected/graphify__skills__agents__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/expected/graphify__skills__amp__references__update.md b/tools/skillgen/expected/graphify__skills__amp__references__update.md index fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__amp__references__update.md +++ b/tools/skillgen/expected/graphify__skills__amp__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__claude__references__update.md +++ b/tools/skillgen/expected/graphify__skills__claude__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__claw__references__update.md +++ b/tools/skillgen/expected/graphify__skills__claw__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__codex__references__update.md +++ b/tools/skillgen/expected/graphify__skills__codex__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__copilot__references__update.md +++ b/tools/skillgen/expected/graphify__skills__copilot__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__droid__references__update.md +++ b/tools/skillgen/expected/graphify__skills__droid__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__kilo__references__update.md +++ b/tools/skillgen/expected/graphify__skills__kilo__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__kiro__references__update.md +++ b/tools/skillgen/expected/graphify__skills__kiro__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__opencode__references__update.md +++ b/tools/skillgen/expected/graphify__skills__opencode__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__pi__references__update.md +++ b/tools/skillgen/expected/graphify__skills__pi__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__trae__references__update.md +++ b/tools/skillgen/expected/graphify__skills__trae__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__vscode__references__update.md +++ b/tools/skillgen/expected/graphify__skills__vscode__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) 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 fa2612180..3632fd412 100644 --- a/tools/skillgen/expected/graphify__skills__windows__references__update.md +++ b/tools/skillgen/expected/graphify__skills__windows__references__update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/fragments/core/aider.md b/tools/skillgen/fragments/core/aider.md index 8db8f73f2..8d13335f0 100644 --- a/tools/skillgen/fragments/core/aider.md +++ b/tools/skillgen/fragments/core/aider.md @@ -677,10 +677,19 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('.graphify_detect.json').read_text()) -save_manifest(detect['files'], root='INPUT_PATH') +extract = json.loads(Path('.graphify_extract.json').read_text()) +# Stamp only semantic files that produced output so a failed chunk is re-queued next run, not lost (#2015). +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('.graphify_extract.json').read_text()) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/fragments/core/core.md b/tools/skillgen/fragments/core/core.md index cf8f03062..53076a5a9 100644 --- a/tools/skillgen/fragments/core/core.md +++ b/tools/skillgen/fragments/core/core.md @@ -489,15 +489,37 @@ from graphify.detect import save_manifest # Save manifest for --update detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\")) +extract = json.loads(Path('graphify-out/.graphify_extract.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. # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output: +# a detected file whose chunk failed or was omitted must stay unstamped so the +# next --update re-queues it, otherwise it is marked done and its content is lost +# forever (#2015). This mirrors the library extract path exactly +# (cli._stamped_manifest_files + clear_semantic + scan_corpus); do not stamp the +# raw corpus. Code files are always stamped (AST is deterministic); only semantic +# types are gated on output. +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +# Files dispatched this run (the changed subset) but NOT stamped above still carry +# a stale semantic_hash from a prior run; clear it so detect_incremental re-queues +# them instead of reading them as unchanged (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus (not the stamp-filtered subset) so in-root +# files newly excluded since last run are dropped rather than masquerading as +# deletions; untouched files' prior rows are still preserved (#1908). +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/fragments/core/devin.md b/tools/skillgen/fragments/core/devin.md index c444a852d..0866e4ae0 100644 --- a/tools/skillgen/fragments/core/devin.md +++ b/tools/skillgen/fragments/core/devin.md @@ -795,10 +795,19 @@ 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'], root='INPUT_PATH') +extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) +# Stamp only semantic files that produced output so a failed chunk is re-queued next run, not lost (#2015). +from graphify.cli import _stamped_manifest_files +_corpus = detect.get('all_files') or detect['files'] +_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH')) +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in detect['files'].items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +_scan = {f for fl in _corpus.values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) # Update cumulative cost tracker -extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text()) input_tok = extract.get('input_tokens', 0) output_tok = extract.get('output_tokens', 0) diff --git a/tools/skillgen/fragments/references/shared/update.md b/tools/skillgen/fragments/references/shared/update.md index fa2612180..3632fd412 100644 --- a/tools/skillgen/fragments/references/shared/update.md +++ b/tools/skillgen/fragments/references/shared/update.md @@ -142,7 +142,25 @@ print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"]) # 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') +# +# Only stamp semantic files (docs/papers/images) that ACTUALLY produced output +# THIS run (new_extraction is this run's fresh extraction, read above before the +# merge overwrote the file): a changed doc whose chunk failed must stay unstamped +# so the next --update re-queues it, otherwise it is marked done and its content +# is lost forever (#2015). Mirrors the library extract path +# (cli._stamped_manifest_files + clear_semantic + scan_corpus). +from graphify.cli import _stamped_manifest_files +_manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) +# Changed semantic files dispatched this run but NOT stamped had their chunk fail +# or be omitted; clear any stale semantic_hash so they are re-queued (#1948). +_sem_types = ('document', 'paper', 'image') +_dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} +_stamped = {f for fl in _manifest_files.values() for f in fl} +_cleared = _dispatched - _stamped +# scan_corpus = the RAW full corpus so in-root files newly excluded since last run +# are dropped rather than masquerading as deletions; untouched rows preserved (#1908). +_scan = {f for fl in incremental['files'].values() for f in fl} +save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') " ``` diff --git a/tools/skillgen/gen.py b/tools/skillgen/gen.py index 93e403f27..1412b9196 100644 --- a/tools/skillgen/gen.py +++ b/tools/skillgen/gen.py @@ -851,6 +851,34 @@ def _is_manifest_root_fix_line(line: str) -> bool: return "save_manifest(" in line and "import" not in line +def _is_manifest_stamp_fix_line(line: str) -> bool: + """Whether a line is part of the manifest over-stamping fix (#2015). + + Step 9 stamped the whole detected corpus, so a semantic file whose chunk + failed (or was omitted) was marked done and never re-queued on the next + ``--update`` — its content lost forever. The manifest is now built with + ``cli._stamped_manifest_files`` (only files that actually produced output) + plus ``clear_semantic``/``scan_corpus``, mirroring the native + ``graphify extract`` path. The rooted ``save_manifest`` call itself is + covered by ``_is_manifest_root_fix_line``; these are the added helper import + and derivation lines, plus the single ``#2015`` explanatory comment. + """ + stripped = line.strip() + return ( + "_stamped_manifest_files" in stripped + or stripped.startswith(( + "_corpus =", + "_manifest_files =", + "_sem_types =", + "_dispatched =", + "_stamped =", + "_cleared =", + "_scan =", + )) + or (stripped.startswith("#") and "#2015" in stripped) + ) + + def _is_no_api_key_fix_line(line: str) -> bool: """Whether a line is part of the "no API key required" clarity (#1461). @@ -931,6 +959,7 @@ _SANCTIONED_MONOLITH_DIFFS = ( _is_cache_unlink_fix_line, _is_zero_node_guard_fix_line, _is_manifest_root_fix_line, + _is_manifest_stamp_fix_line, _is_no_api_key_fix_line, _is_shebang_allowlist_fix_line, _is_obsidian_usage_comment_line,