diff --git a/graphify/affected.py b/graphify/affected.py index 109eaa95..10c63187 100644 --- a/graphify/affected.py +++ b/graphify/affected.py @@ -145,6 +145,9 @@ def load_graph(path: Path) -> nx.Graph: from networkx.readwrite import json_graph raw = json.loads(path.read_text(encoding="utf-8")) + # Force directed so stored caller→callee direction survives the round-trip; + # mirrors serve.py and __main__.py (#1174). + raw = {**raw, "directed": True} try: return json_graph.node_link_graph(raw, edges="links") except TypeError: diff --git a/graphify/detect.py b/graphify/detect.py index 921661d2..2eff3f80 100644 --- a/graphify/detect.py +++ b/graphify/detect.py @@ -1347,6 +1347,12 @@ def detect_incremental( changed = True else: stored_mtime = stored.get("mtime") + # Schema-drift guard (#1163): tolerate a nested {mtime: ...} + # dict or any non-numeric value without crashing. + if isinstance(stored_mtime, dict): + stored_mtime = stored_mtime.get("mtime") + if not isinstance(stored_mtime, (int, float)): + stored_mtime = None if stored_mtime is None or current_mtime != stored_mtime: # mtime bumped — verify with content hash before re-extracting changed = _md5_file(Path(f)) != stored_hash diff --git a/graphify/hooks.py b/graphify/hooks.py index d704eea2..958465da 100644 --- a/graphify/hooks.py +++ b/graphify/hooks.py @@ -98,7 +98,13 @@ try: signal.signal(signal.SIGALRM, lambda *_: (_ for _ in ()).throw(TimeoutError(f'graphify rebuild exceeded {_timeout}s'))) signal.alarm(_timeout) _force = os.environ.get('GRAPHIFY_FORCE', '').lower() in ('1', 'true', 'yes') - _rebuild_code(Path('.'), changed_paths=changed, force=_force) + _root = Path('.') + _saved = Path('graphify-out/.graphify_root') + if _saved.exists(): + _txt = _saved.read_text(encoding='utf-8').strip() + if _txt: + _root = Path(_txt) + _rebuild_code(_root, changed_paths=changed, force=_force) except TimeoutError as exc: print(f'[graphify hook] {exc}') sys.exit(1) @@ -121,7 +127,13 @@ try: # post-checkout: branch switch can touch arbitrary files; full rebuild path # (no changed_paths) is correct here. The flock inside _rebuild_code still # prevents pile-ups when commit + checkout fire back-to-back. - _rebuild_code(Path('.'), force=_force) + _root = Path('.') + _saved = Path('graphify-out/.graphify_root') + if _saved.exists(): + _txt = _saved.read_text(encoding='utf-8').strip() + if _txt: + _root = Path(_txt) + _rebuild_code(_root, force=_force) except TimeoutError as exc: print(f'[graphify] {exc}') sys.exit(1) diff --git a/graphify/skill-aider.md b/graphify/skill-aider.md index aad4ff95..5154559d 100644 --- a/graphify/skill-aider.md +++ b/graphify/skill-aider.md @@ -684,7 +684,7 @@ cost_path.write_text(json.dumps(cost, indent=2)) print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json .graphify_chunk_*.json +rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json; find . -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-amp.md b/graphify/skill-amp.md index 9cad76fc..7f58bb80 100644 --- a/graphify/skill-amp.md +++ b/graphify/skill-amp.md @@ -520,7 +520,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index bad0a0d5..4d59af1e 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index b9a70f06..e395cf73 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -520,7 +520,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-copilot.md b/graphify/skill-copilot.md index bad0a0d5..4d59af1e 100644 --- a/graphify/skill-copilot.md +++ b/graphify/skill-copilot.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-devin.md b/graphify/skill-devin.md index 7013159a..6e466128 100644 --- a/graphify/skill-devin.md +++ b/graphify/skill-devin.md @@ -801,7 +801,7 @@ cost_path.write_text(json.dumps(cost, indent=2)) print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_labels.json graphify-out/.graphify_chunk_*.json graphify-out/.graphify_incremental.json graphify-out/.graphify_transcripts.json graphify-out/.graphify_old.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_labels.json graphify-out/.graphify_incremental.json graphify-out/.graphify_transcripts.json graphify-out/.graphify_old.json; find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index 6c965c67..1001fea3 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -520,7 +520,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index 8bc6d536..15b47082 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-kiro.md b/graphify/skill-kiro.md index 796ad8ca..06d8195d 100644 --- a/graphify/skill-kiro.md +++ b/graphify/skill-kiro.md @@ -522,7 +522,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index 6c4f2086..064bbcd0 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -515,7 +515,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-pi.md b/graphify/skill-pi.md index 796ad8ca..06d8195d 100644 --- a/graphify/skill-pi.md +++ b/graphify/skill-pi.md @@ -522,7 +522,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index 8ba7c66f..9c8f927b 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -521,7 +521,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-vscode.md b/graphify/skill-vscode.md index 7be667ca..dc33c0df 100644 --- a/graphify/skill-vscode.md +++ b/graphify/skill-vscode.md @@ -519,7 +519,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index a15480dc..f64d0e83 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -545,7 +545,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/graphify/skill.md b/graphify/skill.md index 4e9827b1..efde04e4 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tests/test_affected_cli.py b/tests/test_affected_cli.py index 65a3be8c..f2a4e554 100644 --- a/tests/test_affected_cli.py +++ b/tests/test_affected_cli.py @@ -58,3 +58,37 @@ def test_affected_cli_relation_filter_limits_reverse_traversal(monkeypatch, tmp_ assert "Relations: calls" in out assert "X()" in out assert "__init__.py" not in out + + +def test_affected_cli_forces_directed_on_undirected_graph(monkeypatch, tmp_path, capsys): + """A graph persisted with directed=false must still recover caller->callee + direction (#1174): affected on the callee returns the caller, not the callee + or nothing. Without forcing directed=True, node_link_graph builds an + undirected Graph, predecessors() collapses, and the reverse traversal breaks. + """ + graph = nx.DiGraph() + graph.add_node("A", label="caller_fn", source_file="a.py", source_location="L1") + graph.add_node("B", label="callee_fn", source_file="b.py", source_location="L2") + graph.add_edge("A", "B", relation="calls", context="call", confidence="EXTRACTED") + + data = json_graph.node_link_data(graph, edges="links") + # Persist as undirected on disk to reproduce the bug condition. + data["directed"] = False + graph_path = tmp_path / "graph.json" + graph_path.write_text(json.dumps(data), encoding="utf-8") + + monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None) + monkeypatch.setattr( + mainmod.sys, + "argv", + ["graphify", "affected", "B", "--relation", "calls", "--graph", str(graph_path)], + ) + + mainmod.main() + + out = capsys.readouterr().out + # A (the caller) is affected by a change to B (the callee). + assert "caller_fn" in out + assert "calls" in out + # B is the query node, not an affected node, and the result is not empty. + assert "No affected nodes found." not in out diff --git a/tests/test_detect.py b/tests/test_detect.py index 395a9d4d..00efc8fd 100644 --- a/tests/test_detect.py +++ b/tests/test_detect.py @@ -298,6 +298,43 @@ def test_detect_incremental_propagates_follow_symlinks(tmp_path, monkeypatch): assert second["new_total"] == 0 +def test_detect_incremental_survives_dict_valued_mtime(tmp_path, monkeypatch): + """A schema-drifted manifest whose entry stores mtime as a nested dict + (instead of a float) must not crash detect_incremental (#1163). The guard + coerces the bad mtime to None so the file is re-verified by content hash and + treated as new, rather than blowing up on the int/float comparison. + """ + import json + + monkeypatch.chdir(tmp_path) + + src = tmp_path / "mod.py" + src.write_text("def f():\n return 1\n", encoding="utf-8") + + manifest_dir = tmp_path / "graphify-out" + manifest_dir.mkdir() + manifest_path = str(manifest_dir / "manifest.json") + + # Drifted entry: a non-empty ast_hash (so the dict branch reaches the mtime + # comparison) with mtime stored as a dict rather than a float. Absolute key + # so it matches detect's absolute file paths without re-anchoring. + drifted = { + str(src.resolve()): { + "mtime": {"mtime": 123.0}, + "ast_hash": "deadbeef" * 4, + "semantic_hash": "cafebabe" * 4, + } + } + Path(manifest_path).write_text(json.dumps(drifted), encoding="utf-8") + + # Must not raise (pre-fix: TypeError comparing float and dict). + result = detect_incremental(tmp_path, manifest_path) + + # The drifted file is re-classified as new rather than silently skipped. + assert any("mod.py" in f for f in result["new_files"]["code"]) + assert not any("mod.py" in f for f in result["unchanged_files"]["code"]) + + def test_classify_video_extensions(): """Video and audio file extensions should classify as VIDEO.""" from graphify.detect import FileType diff --git a/tests/test_hooks.py b/tests/test_hooks.py index c4032718..30b958d5 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -295,6 +295,28 @@ def test_rebuild_bodies_are_shell_quote_safe(): assert "'''" not in body # would terminate the launcher's _src literal +@pytest.mark.parametrize( + "name,body", + [("post-commit", _REBUILD_BODY_COMMIT), ("post-checkout", _REBUILD_BODY_CHECKOUT)], +) +def test_rebuild_bodies_read_graphify_root(name, body): + """The rebuild must honour the persisted scan root rather than hardcoding the + repo top (#1173). Both bodies read graphify-out/.graphify_root and pass the + recovered root to _rebuild_code instead of the bare Path('.').""" + assert "graphify-out/.graphify_root" in body, f"{name} ignores .graphify_root (#1173)" + # The recovered root is what gets rebuilt, not a hardcoded cwd. + assert "_rebuild_code(_root" in body, f"{name} does not pass the recovered root" + # Quote-safe inside the shell-double-quoted launcher: single quotes only. + assert "read_text(encoding='utf-8')" in body, f"{name} root read is not single-quoted" + + +def test_rebuild_bodies_with_graphify_root_are_valid_python(): + """The .graphify_root snippet must parse so a quoting slip can't ship a hook + that crashes the moment git fires it (#1173).""" + for body in (_REBUILD_BODY_COMMIT, _REBUILD_BODY_CHECKOUT): + ast.parse(body) + + def test_detached_launch_targets_graphify_python(): """The launcher must run via the resolved $GRAPHIFY_PYTHON, not a bare `python`, so it uses the same interpreter the detection block selected.""" diff --git a/tests/test_skillgen.py b/tests/test_skillgen.py index 47494801..512b68f2 100644 --- a/tests/test_skillgen.py +++ b/tests/test_skillgen.py @@ -455,12 +455,13 @@ def test_monolith_roundtrip_passes_for_aider_and_devin(): assert problems == [], f"[{key}]\n" + "\n".join(problems) -def test_monoliths_change_only_the_enum_and_the_description(): - """The rendered monolith differs from v8 on exactly the enum + description lines. +def test_monoliths_change_only_the_enum_description_and_chunk_cleanup(): + """The rendered monolith differs from v8 on exactly the allowed lines. - Two changes are now in play for the monoliths: the file_type enum unified to - the six-value superset (the prose guidance line + the schema line) and the - frontmatter description unified across all platforms. Nothing else may differ. + Three changes are now in play for the monoliths: the file_type enum unified to + the six-value superset (the prose guidance line + the schema line), the + frontmatter description unified across all platforms, and the shell-agnostic + chunk-cleanup rewrite (#1172). Nothing else may differ. """ platforms = gen.load_platforms() for key in ("aider", "devin"): @@ -468,11 +469,12 @@ def test_monoliths_change_only_the_enum_and_the_description(): original = gen._normalise(gen._git_show(platforms[key].roundtrip_ref)).splitlines() assert len(rendered) == len(original), f"[{key}] line count changed" diff_idx = [i for i, (r, o) in enumerate(zip(rendered, original)) if r != o] - # Exactly three lines change: the prose enum guidance, the schema line, - # and the frontmatter description. - assert len(diff_idx) == 3, f"[{key}] expected 3 changed lines, got {len(diff_idx)}" + # Exactly four lines change: the prose enum guidance, the schema line, + # the frontmatter description, and the chunk-cleanup rewrite. + assert len(diff_idx) == 4, f"[{key}] expected 4 changed lines, got {len(diff_idx)}" enum_changes = 0 desc_changes = 0 + cleanup_changes = 0 for i in diff_idx: line = rendered[i] if gen.ENUM_VALUES in line or gen.ENUM_PROSE in line: @@ -482,12 +484,18 @@ def test_monoliths_change_only_the_enum_and_the_description(): assert UNIFIED_DESCRIPTION in line, ( f"[{key}] description line is not the unified text: {line!r}" ) + elif gen._is_chunk_cleanup_line(line): + cleanup_changes += 1 + # The unmatched-glob abort is fixed: the rm no longer carries the + # bare chunk glob, and a find ... -delete sweeps the chunks. + assert ".graphify_chunk_*.json" not in line.split("find", 1)[0] else: raise AssertionError( - f"[{key}] changed line {i} is neither enum nor description: {line!r}" + f"[{key}] changed line {i} is none of enum/description/cleanup: {line!r}" ) assert enum_changes == 2, f"[{key}] expected 2 enum line changes, got {enum_changes}" assert desc_changes == 1, f"[{key}] expected 1 description change, got {desc_changes}" + assert cleanup_changes == 1, f"[{key}] expected 1 cleanup change, got {cleanup_changes}" # The six-value superset replaced the five-value enum in both files. assert any(gen.ENUM_VALUES in line for line in rendered) diff --git a/tools/skillgen/expected/graphify__skill-aider.md b/tools/skillgen/expected/graphify__skill-aider.md index aad4ff95..5154559d 100644 --- a/tools/skillgen/expected/graphify__skill-aider.md +++ b/tools/skillgen/expected/graphify__skill-aider.md @@ -684,7 +684,7 @@ cost_path.write_text(json.dumps(cost, indent=2)) print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json .graphify_chunk_*.json +rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json; find . -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-amp.md b/tools/skillgen/expected/graphify__skill-amp.md index 9cad76fc..7f58bb80 100644 --- a/tools/skillgen/expected/graphify__skill-amp.md +++ b/tools/skillgen/expected/graphify__skill-amp.md @@ -520,7 +520,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-claw.md b/tools/skillgen/expected/graphify__skill-claw.md index bad0a0d5..4d59af1e 100644 --- a/tools/skillgen/expected/graphify__skill-claw.md +++ b/tools/skillgen/expected/graphify__skill-claw.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-codex.md b/tools/skillgen/expected/graphify__skill-codex.md index b9a70f06..e395cf73 100644 --- a/tools/skillgen/expected/graphify__skill-codex.md +++ b/tools/skillgen/expected/graphify__skill-codex.md @@ -520,7 +520,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-copilot.md b/tools/skillgen/expected/graphify__skill-copilot.md index bad0a0d5..4d59af1e 100644 --- a/tools/skillgen/expected/graphify__skill-copilot.md +++ b/tools/skillgen/expected/graphify__skill-copilot.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-devin.md b/tools/skillgen/expected/graphify__skill-devin.md index 7013159a..6e466128 100644 --- a/tools/skillgen/expected/graphify__skill-devin.md +++ b/tools/skillgen/expected/graphify__skill-devin.md @@ -801,7 +801,7 @@ cost_path.write_text(json.dumps(cost, indent=2)) print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_labels.json graphify-out/.graphify_chunk_*.json graphify-out/.graphify_incremental.json graphify-out/.graphify_transcripts.json graphify-out/.graphify_old.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_labels.json graphify-out/.graphify_incremental.json graphify-out/.graphify_transcripts.json graphify-out/.graphify_old.json; find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-droid.md b/tools/skillgen/expected/graphify__skill-droid.md index 6c965c67..1001fea3 100644 --- a/tools/skillgen/expected/graphify__skill-droid.md +++ b/tools/skillgen/expected/graphify__skill-droid.md @@ -520,7 +520,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index 8bc6d536..15b47082 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index 796ad8ca..06d8195d 100644 --- a/tools/skillgen/expected/graphify__skill-kiro.md +++ b/tools/skillgen/expected/graphify__skill-kiro.md @@ -522,7 +522,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-opencode.md b/tools/skillgen/expected/graphify__skill-opencode.md index 6c4f2086..064bbcd0 100644 --- a/tools/skillgen/expected/graphify__skill-opencode.md +++ b/tools/skillgen/expected/graphify__skill-opencode.md @@ -515,7 +515,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-pi.md b/tools/skillgen/expected/graphify__skill-pi.md index 796ad8ca..06d8195d 100644 --- a/tools/skillgen/expected/graphify__skill-pi.md +++ b/tools/skillgen/expected/graphify__skill-pi.md @@ -522,7 +522,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-trae.md b/tools/skillgen/expected/graphify__skill-trae.md index 8ba7c66f..9c8f927b 100644 --- a/tools/skillgen/expected/graphify__skill-trae.md +++ b/tools/skillgen/expected/graphify__skill-trae.md @@ -521,7 +521,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-vscode.md b/tools/skillgen/expected/graphify__skill-vscode.md index 7be667ca..dc33c0df 100644 --- a/tools/skillgen/expected/graphify__skill-vscode.md +++ b/tools/skillgen/expected/graphify__skill-vscode.md @@ -519,7 +519,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill-windows.md b/tools/skillgen/expected/graphify__skill-windows.md index a15480dc..f64d0e83 100644 --- a/tools/skillgen/expected/graphify__skill-windows.md +++ b/tools/skillgen/expected/graphify__skill-windows.md @@ -545,7 +545,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/expected/graphify__skill.md b/tools/skillgen/expected/graphify__skill.md index 4e9827b1..efde04e4 100644 --- a/tools/skillgen/expected/graphify__skill.md +++ b/tools/skillgen/expected/graphify__skill.md @@ -523,7 +523,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/fragments/core/aider.md b/tools/skillgen/fragments/core/aider.md index aad4ff95..5154559d 100644 --- a/tools/skillgen/fragments/core/aider.md +++ b/tools/skillgen/fragments/core/aider.md @@ -684,7 +684,7 @@ cost_path.write_text(json.dumps(cost, indent=2)) print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json .graphify_chunk_*.json +rm -f .graphify_detect.json .graphify_extract.json .graphify_ast.json .graphify_semantic.json .graphify_analysis.json .graphify_labels.json; find . -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/fragments/core/core.md b/tools/skillgen/fragments/core/core.md index 4a56635e..a3a77921 100644 --- a/tools/skillgen/fragments/core/core.md +++ b/tools/skillgen/fragments/core/core.md @@ -457,7 +457,8 @@ cost_path.write_text(json.dumps(cost, indent=2, ensure_ascii=False), encoding=\" print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_chunk_*.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json +find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/fragments/core/devin.md b/tools/skillgen/fragments/core/devin.md index 7013159a..6e466128 100644 --- a/tools/skillgen/fragments/core/devin.md +++ b/tools/skillgen/fragments/core/devin.md @@ -801,7 +801,7 @@ cost_path.write_text(json.dumps(cost, indent=2)) print(f'This run: {input_tok:,} input tokens, {output_tok:,} output tokens') print(f'All time: {cost[\"total_input_tokens\"]:,} input, {cost[\"total_output_tokens\"]:,} output ({len(cost[\"runs\"])} runs)') " -rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_labels.json graphify-out/.graphify_chunk_*.json graphify-out/.graphify_incremental.json graphify-out/.graphify_transcripts.json graphify-out/.graphify_old.json +rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_extract.json graphify-out/.graphify_ast.json graphify-out/.graphify_semantic.json graphify-out/.graphify_analysis.json graphify-out/.graphify_labels.json graphify-out/.graphify_incremental.json graphify-out/.graphify_transcripts.json graphify-out/.graphify_old.json; find graphify-out -maxdepth 1 -name '.graphify_chunk_*.json' -delete 2>/dev/null rm -f graphify-out/.needs_update 2>/dev/null || true ``` diff --git a/tools/skillgen/gen.py b/tools/skillgen/gen.py index 7e9c80aa..c67334b2 100644 --- a/tools/skillgen/gen.py +++ b/tools/skillgen/gen.py @@ -705,6 +705,19 @@ def _is_frontmatter_description_line(line: str) -> bool: return line.lstrip().startswith("description:") +def _is_chunk_cleanup_line(line: str) -> bool: + """Whether a line is the Step 9 chunk-file cleanup ``rm -f`` command. + + The bare glob ``.graphify_chunk_*.json`` in the v8 cleanup line aborts the + whole ``rm`` under fish/zsh when no chunk files exist (no-match is a hard + error there, unlike bash). The fix (graphify #1172) drops the glob from the + ``rm`` and deletes the chunk files with ``find ... -delete`` instead. That + rewrite touches the single cleanup line in place (no line added or removed), + so it joins the enum and description unifications as an allowed monolith diff. + """ + return line.lstrip().startswith("rm -f") and "find " in line and "-name '.graphify_chunk_" in line + + def monolith_roundtrip(platform: Platform) -> list[str]: """Assert a monolith renders diff-clean vs its v8 blob modulo allowed changes. @@ -736,8 +749,9 @@ def monolith_roundtrip(platform: Platform) -> list[str]: for i, (r, o) in enumerate(zip(rendered_lines, original_lines), start=1): if r == o: continue - # The permitted diffs are the enum unification and the unified description. - if _is_enum_line(r) or _is_frontmatter_description_line(r): + # The permitted diffs are the enum unification, the unified description, + # and the shell-agnostic chunk-cleanup rewrite (#1172). + if _is_enum_line(r) or _is_frontmatter_description_line(r) or _is_chunk_cleanup_line(r): continue problems.append( f"[{platform.key}] line {i} differs and is not an enum or description unification:\n"