Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions graphify/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3898,6 +3898,14 @@ def _progress(idx: int, total: int, _result: dict) -> None:
# first means semantic node attributes win on collision (richer labels
# for symbols also referenced in docs). Hyperedges only come from the
# semantic side.
# Everything in sem_result is semantic tier - fresh results and cache
# hits alike - so say so before the merge (#2843): an unstamped item
# is classified by the shape of its source_location, and a doc node
# located at 'L12' would read as AST and be deleted by the next
# incremental rebuild. Fresh results already carry the stamp
# (llm._stamp_semantic_origin); cache entries from before it do not.
from graphify.llm import _stamp_semantic_origin as _stamp_sem
_stamp_sem(sem_result)
merged: dict = {
"nodes": list(ast_result.get("nodes", [])) + list(sem_result.get("nodes", [])) + list(pg_result.get("nodes", [])) + list(cargo_result.get("nodes", [])),
"edges": list(ast_result.get("edges", [])) + list(sem_result.get("edges", [])) + list(pg_result.get("edges", [])) + list(cargo_result.get("edges", [])),
Expand Down
19 changes: 19 additions & 0 deletions graphify/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2794,6 +2794,7 @@ def _out_of_scope(item: dict) -> bool:
if p.resolve() not in {c.resolve() for c in covered}
)
merged["uncovered_files"] = [str(p) for p in uncovered]
_stamp_semantic_origin(merged)
if uncovered:
shown = ", ".join(p.name for p in uncovered[:5])
more = f" (+{len(uncovered) - 5} more)" if len(uncovered) > 5 else ""
Expand All @@ -2806,6 +2807,24 @@ def _out_of_scope(item: dict) -> bool:
return merged


def _stamp_semantic_origin(result: dict) -> dict:
"""Mark every node and edge of a semantic extraction ``_origin: "semantic"``.

build._is_ast_tier reads ``_origin`` when present and otherwise guesses
from the shape of ``source_location``: ``L<line>`` means AST. A backend
or subagent that reports line numbers for a document's sections made
those nodes read as AST, and the next incremental rebuild deleted them
with the re-extracted code file they seemed to belong to (#2843).
extract() stamps its items ``ast``; this is the semantic counterpart.
Existing stamps are kept (``setdefault``); returns ``result``.
"""
for key in ("nodes", "edges"):
for item in result.get(key, []) or []:
if isinstance(item, dict):
item.setdefault("_origin", "semantic")
return result


def _merge_into(merged: dict, result: dict) -> None:
"""Append a chunk result into the running merged accumulator."""
merged["nodes"].extend(result.get("nodes", []))
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-aider.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,18 @@ from pathlib import Path
ast = json.loads(Path('.graphify_ast.json').read_text())
sem = json.loads(Path('.graphify_semantic.json').read_text())

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-amp.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-claw.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-copilot.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-devin.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,18 @@ from graphify.semantic_cleanup import sanitize_semantic_fragment
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text())
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text())

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-droid.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-kilo.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-kiro.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-opencode.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-pi.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-trae.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-vscode.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill-windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding="utf-8"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding="utf-8"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
12 changes: 12 additions & 0 deletions graphify/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,18 @@ from pathlib import Path
ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding=\"utf-8\"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding=\"utf-8\"))

# Stamp tier provenance on the semantic side. build._is_ast_tier reads
# _origin when present and otherwise guesses from the SHAPE of
# source_location ('L<line>' means AST). A subagent that writes 'L12' for a
# doc section therefore made that node read as AST, and the next
# `graphify update` deleted it along with the re-extracted code file it
# seemed to belong to (#2843). extract() stamps its own items 'ast'; the
# semantic side must say so explicitly.
for n in sem['nodes']:
n.setdefault('_origin', 'semantic')
for e in sem['edges']:
e.setdefault('_origin', 'semantic')

# Merge: AST nodes first, semantic nodes deduplicated by id
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
Expand Down
Loading
Loading