Restructures the rsl-siege-manager evaluation to match upstream's
worked/karpathy-repos/ convention before opening against safishamsi/graphify:
- Move evaluations/rsl-siege-manager/ -> worked/rsl-siege-manager/
- Flatten graphify-out-with-tests/ artifacts to the case directory root
(matches karpathy-repos layout: README.md, review.md, GRAPH_REPORT.md,
graph.html, graph.json, manifest.json at top level).
- Drop the codebase-only artifact set; keep only the tests-included run as
the more realistic default (most users won't pre-configure .graphifyignore).
- Rename runbook.md -> README.md (corpus description + reproduction steps).
- Rename results.md -> review.md (matches karpathy-repos), and reframe from
"do not adopt" verdict to neutral findings + suggested follow-ups.
- Drop evaluations/README.md fork-only index.
- Revert .gitignore evaluations/ exception block — no longer needed now that
artifacts live at worked/rsl-siege-manager/ (no graphify-out/ in path).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the rsl-siege-manager evaluation under a new `evaluations/` convention:
- `evaluations/README.md` — directory index for future evaluations.
- `evaluations/rsl-siege-manager/runbook.md` — methodology used (relocated
from siege-web's `docs/experiments/graphify-dry-run.md`).
- `evaluations/rsl-siege-manager/results.md` — decision log with quoted
findings and acid-test scoring. Verdict: do not adopt.
- `evaluations/rsl-siege-manager/graphify-out/` and `…-with-tests/` — raw
artifacts (GRAPH_REPORT.md, graph.html, graph.json, manifest.json) so
quoted findings stay auditable. AST cache excluded.
Also extends `.gitignore` with a scoped exception so the rule that hides
fresh `graphify-out/` from a developer's working checkout still applies,
while committed evaluation artifacts under `evaluations/` are preserved.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
In a linked worktree .git is a file not a directory, so root/.git/hooks
fails with NotADirectoryError. Fall back to git rev-parse --path-format=absolute
--git-path hooks which resolves the correct hooks dir from any worktree.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- skill-windows.md: add graphify-out/ prefix to 5 paths in Step B3 that
were being read/written without prefix (lines 244,245,373,387,388,407,413)
causing semantic cache to always appear empty on Windows (#862)
- detect.py: gate converted office/Google Workspace sidecar paths through
_is_ignored() before appending to files list so .graphifyignore rules
apply to graphify-out/converted/ sidecars (#861)
- detect.py: split manifest entries from {mtime,hash} to
{mtime,ast_hash,semantic_hash}; save_manifest() accepts kind="ast"|
"semantic"|"both"; detect_incremental() accepts kind= and checks the
right hash field so graphify update (kind=ast) no longer prevents
graphify extract from re-running semantic extraction (#857)
- watch.py: all three save_manifest() calls use kind="ast" (AST-only rebuild)
- __main__.py: both extract save_manifest() calls use kind="both"
- backwards compatible: legacy {mtime,hash} entries treated as ast_hash only
775 tests passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Several host agents (Claude Code in particular) misread the current
phrasing and prompt the user for `ANTHROPIC_API_KEY` when no Gemini
key is set, instead of falling through to subagent dispatch. The
Gemini tip stays as-is; this just adds an explicit one-line callout
that no other provider keys are consulted, with `ANTHROPIC_API_KEY`
called out by name since that is the specific misread we hit in the
wild.
No behavior change — pure documentation.
The rebuild lock file accumulated concatenated PIDs across post-commit
rebuilds without a separator, and was never removed when the rebuild
finished. Two practical consequences for users:
1. Downstream tooling that polls for `.rebuild.lock` to disappear before
doing post-rebuild work (publish scripts copying graph.html to a web
root, etc.) blocked forever / until its own timeout.
2. The accumulated digit string could not be parsed by humans or tooling
to find the owning PID.
The `_rebuild_lock` context manager now:
- Opens the lock file with `a+` so a non-acquiring caller does not
truncate the existing holder's PID.
- After flock acquisition, truncates and writes a single `<pid>\n` line
so external readers can `kill -0 $(cat .rebuild.lock)` to check
liveness.
- Unlinks the lock file in the finally block (only when *we* held the
lock), restoring the "signal-by-absence" convention users rely on.
Four regression tests added under `tests/test_watch.py` covering the
PID-with-newline payload, post-release unlink, no-accumulation across
sequential acquisitions, and the non-blocking-caller-does-not-clobber
invariant.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Astro files have a `---...---` TypeScript frontmatter block at the top
containing nearly all imports, followed by an HTML-with-expressions
template body, and optionally `<script>` blocks for client-side JS.
Feeding the whole file to the JS tree-sitter parser produces a top-level
ERROR node because the template isn't valid JS, so `import_statement`
nodes are never reached.
Mirrors `extract_svelte` (#713) — same regex-rescue strategy:
- Dynamic `import('...')` pass over the full source.
- Static `import X from '...'` pass over the frontmatter block plus any
`<script>` blocks.
Both static and dynamic imports honor tsconfig.json path aliases and
resolve relative paths through `_resolve_js_module_path` so node ids
match the targets that `_extract_generic` produces for sibling files.
Wired in:
- `detect.py:27` — `.astro` added to `CODE_EXTENSIONS`.
- `extract.py:5492` — `".astro": extract_astro` in the dispatch table.
Tests in `tests/test_astro_extraction.py` cover: extension registration,
frontmatter static imports, dynamic import in frontmatter, client-side
`<script>` block imports, frontmatter-less pages (must not crash), and
tsconfig.json path-alias resolution.
- skill.md + skill-windows.md: add encoding="utf-8" to all read_text()/write_text()
calls and ensure_ascii=False to json.dumps — bare calls defaulted to system
codepage on Chinese-locale Windows, mojibaking non-ASCII content (#832)
- skill.md + skill-windows.md: prefer uv tool install --upgrade graphifyy over
pip in the Step 1 install fallback — pip installs to the wrong env when
graphify was installed via uv tool (#831)
- serve.py + __main__.py: replace flat substring scoring in _score_nodes with
three-tier precedence (exact 1000 / prefix 100 / substring 1); _find_node
returns results ordered exact→prefix→substring; both path CLI and MCP now
emit a clear error when src and tgt resolve to the same node (#828)
- cache.py: normalize path key via .as_posix().lower() in file_hash so Windows
junction/case variants hash identically; mirror abs-path normalization from
save_semantic_cache into check_semantic_cache so relative source_file paths
resolve the same way on both sides (#826)
- __main__.py: add /graphify skill trigger line to _AGENTS_MD_SECTION — affects
all 7 AGENTS.md platforms (OpenCode, Codex, Aider, Trae, Hermes, Claw, Droid)
so typing /graphify actually invokes the skill tool (#827)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a pre-cluster topology comparison fast path in update rebuilds so unchanged graphs short-circuit before clustering and report generation, preventing residual run-to-run community-count drift.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use safe JSON serialization fallbacks for deterministic sort keys in clustering and graph canonicalization, and skip invalid community IDs with a stderr warning instead of raising during update rebuilds.
Co-authored-by: Cursor <cursoragent@cursor.com>
Make `graphify update` idempotent by skipping output rewrites when graph/report content is unchanged, add `update --no-cluster`, and preserve community IDs across runs via overlap-based remapping with deterministic partition inputs.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Universal -h/--help/-? guard after cmd dispatch: any help flag anywhere in
argv stops execution and prints "Run 'graphify --help'" instead of triggering
the subcommand — cursor/kiro/gemini install --help no longer silently installs;
benchmark --help no longer crashes with FileNotFoundError (#821)
- --version / -v / version subcommand: print graphify {__version__} and exit (#818)
- GRAPHIFY_OLLAMA_NUM_CTX=<invalid> now falls through to auto-derived num_ctx
instead of hardcoding 131072 (the cap that causes OOM on constrained VRAM);
pinned num_ctx < estimated input now triggers an explicit truncation warning
with a suggested --token-budget correction (#820)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extract/_make_id + build/_normalize_id: use NFKC normalization and casefold
so composed/decomposed Unicode forms produce the same ID; collapse consecutive
underscores; both functions are now byte-for-byte equivalent (#811)
- dedup: use explicit key-presence check instead of `or` for source/from
fallback; pop stale from/to keys so they don't leak into graph.json attrs (#803)
- skill --update: use build_merge() to avoid NetworkX round-trip direction flip;
fix dict merge ordering so explicit source/target win; pull hyperedges from
G.graph (merged) not new_extraction only (#801)
- skill subagents: inject absolute CHUNK_PATH so Write tool doesn't lose chunk
files to undefined cwd (#808)
- __main__: skip skill version check during hook-check (runs on every editor
tool use, must be silent); move warning to stderr
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#796: add edge_data()/edge_datas() helpers in build.py that tolerate
MultiGraph/MultiDiGraph; replace all G.edges[u,v] 2-tuple call sites in
__main__.py, serve.py, wiki.py, export.py, analyze.py, benchmark.py;
fix same pattern in 10 skill file inline heredocs
#795: all 12 skill files now short-circuit on /graphify --help or -h
and print the Usage block without running any pipeline steps
#792 (hollow response): add _response_is_hollow() predicate in llm.py;
when Ollama (or any backend) returns empty/null/whitespace content or a
parsed result with no nodes/edges, rewrite finish_reason="length" so
_extract_with_adaptive_retry bisects the chunk instead of silently
dropping it; applied to _call_openai_compat, _call_claude, _call_bedrock
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#791: per-repo fcntl flock in _rebuild_code prevents concurrent hook rebuilds
from exhausting memory; changed_paths wired through so only modified files are
re-extracted; stale nodes evicted on deletion; SIGALRM watchdog with
GRAPHIFY_REBUILD_TIMEOUT; Darwin-aware RLIMIT_DATA memory cap
#792: remove hard 8-worker cap (GRAPHIFY_MAX_WORKERS env var); add
--max-workers, --token-budget, --max-concurrency, --api-timeout CLI flags
to graphify extract; fix ollama API key gate for loopback URLs; explicit
timeout on OpenAI client (GRAPHIFY_API_TIMEOUT, default 600s); per-chunk
progress prints during extraction
#453 + #785: rename .agent -> .agents throughout antigravity install/uninstall;
add trigger:always_on YAML frontmatter to _ANTIGRAVITY_RULES so Antigravity
recognises the rules file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tree-sitter-pascal is not on PyPI so extract_pascal() fell back to an
empty error result for all users. _extract_pascal_regex() now handles
unit/program/library headers, uses clauses, class/interface declarations
with inheritance, forward method decls, qualified impl headers, balanced
begin/end body extraction, and intra-file calls edges. All 15 previously
skipped pascal_required tests now run unconditionally and pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(extract): add Pascal/Delphi language support
Adds full AST extraction for Pascal and Delphi source files using
tree-sitter-pascal (https://github.com/Isopod/tree-sitter-pascal).
Supported file extensions: .pas, .pp, .dpr, .dpk, .inc
Extracted nodes:
- File node (the .pas file itself)
- unit / program / library declarations
- class, interface, and helper type declarations
- procedure and function implementations
Extracted edges:
- file --contains--> module
- module --imports--> dependency (via uses clause, resolved to path-based IDs)
- class --inherits--> base class / interface
- class/module --contains/method--> procedure or function
- procedure --calls--> procedure (in-file call resolution)
Key design: uses clause targets are resolved to path-based node IDs by
scanning all Pascal files under the project root (_pascal_project_root +
_pascal_resolve_unit helpers). This avoids dangling import edges that
result from resolving bare unit names like "SysUtils" to IDs that never
match any file node.
Bare procedure calls (e.g. `Reset;` without parentheses) are detected
by inspecting statement nodes whose sole named child is an identifier,
in addition to the standard exprCall nodes used for calls with arguments.
Requires: pip install tree-sitter-pascal
(https://github.com/Isopod/tree-sitter-pascal)
If not installed, extract_pascal returns {"nodes":[], "edges":[], "error": ...}
so the rest of the pipeline is unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(extract): add Lazarus .lfm and .lpk file support
Adds two new extractors for Lazarus IDE-specific file formats:
extract_lazarus_form() — .lfm (Lazarus Form files)
.lfm files are text-based UI component trees. The extractor parses
`object Name: TClassName ... end` blocks to build a containment graph
of form components, and captures `OnXxx = HandlerName` event bindings
as `references` edges (context: "event") linking each component to
its handler procedure.
extract_lazarus_package() — .lpk (Lazarus Package files)
.lpk files are XML package definitions. The extractor reads the
package name, required package dependencies (→ imports edges), and
listed unit files (→ contains edges). Unit names are resolved to
path-based node IDs via _pascal_resolve_unit so they connect to the
same nodes produced by extract_pascal on .pas files.
Both extensions added to CODE_EXTENSIONS in detect.py and to _DISPATCH.
13 new tests in test_pascal.py cover both extractors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(pascal): fix dangling inherits-edge targets and capture all base classes
The declType/typeref handler built inherits edge targets with
_make_id(_read(child)) — just the bare class name. But class nodes
use _make_id(stem, type_name), so targets never matched, making the
entire class hierarchy invisible in the graph.
Add _pascal_class_stem_cache and _pascal_resolve_class(): strips the
conventional T/I prefix, locates the defining file by stem lookup
(same cache mechanism as _pascal_resolve_unit), and returns the
correct _make_id(file_stem, class_name) ID. RTL/unresolvable bases
(e.g. TObject) fall back to _make_id(bare_name) with an explicit
stub node, following the same pattern as the Python extractor.
Also remove the `break` that stopped after the first typeref, so
all parents are captured (e.g. class(TBase, IInterface)).
Extend test_pascal_no_dangling_edges to also assert that within-file
edge targets (contains, method, inherits, calls) resolve to real nodes.
* feat(extract): add Delphi .dfm form file support
Adds extract_delphi_form() for Delphi Form files (.dfm), which use the
same `object Name: TClassName ... end` text syntax as Lazarus .lfm files.
Binary .dfm files (FF 0A magic header) are skipped gracefully with an
informative error message so the pipeline is unaffected. Text .dfm files
are parsed identically to .lfm: component containment (`contains` edges)
and event handler references (`references`, context "event").
Adds .dfm to _DISPATCH and CODE_EXTENSIONS.
10 new tests in test_pascal.py, including a regression test for the
binary-format detection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add .lpr extension and fix removesuffix in Pascal extractor
Address review feedback from @safishamsi:
- Add .lpr (Lazarus program file, identical syntax to .dpr) to _DISPATCH
in extract.py and CODE_EXTENSIONS in detect.py so Lazarus project entry
points are indexed. Completes the promised Lazarus IDE support.
- Replace rstrip("()") with removesuffix("()") in the call-resolution
dict comprehension for precise suffix removal (rstrip strips individual
characters, not the literal string "()").
- Add .lpr assertions to test_pascal_dispatch_registered and
test_pascal_detect_extensions_registered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Simeon Bodurov <simeon.bodurov@speedy.bg>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>