Commit Graph
2537 Commits
Author SHA1 Message Date
nothariharan aa8ca7370b docs: document GRAPHIFY_CLAUDE_CLI_MODEL (#2861)
The claude-cli backend defaults to Opus, so community naming can burn
Opus quota silently. Document the env var in the README environment
section and mention it in the label/cluster-only --model help.

(cherry picked from commit f6d8a9b6b0)
2026-10-23 18:20:05 +01:00
nothariharan e8b1bb571f docs(readme): list the optional [watch] extra (#1203)
graphify watch needs watchdog, but the README extras table omitted the
[watch] extra, so users following the install docs hit 'watchdog not
installed'. Add the row alongside the other extras.

(cherry picked from commit ff015b9a74)
2026-10-23 18:20:04 +01:00
nothariharan 7174d3b972 docs(readme): document labeling batch limits, --missing-only, and html node limit (#3859)
- README: add a compact 'Labeling and rendering limits' section covering
  --batch-size vs context window, --missing-only, .graphify_labels.json.sig,
  export html --node-limit / GRAPHIFY_VIZ_NODE_LIMIT, and the Leiden->Louvain
  fallback.
- CLI help: state the ~16k default sizing for --batch-size and the 5000
  default for export html --node-limit.

Docs/help strings only; no behavior change.

(cherry picked from commit 4bbb93789b)
2026-10-23 18:20:02 +01:00
safishamsiandClaude Opus 4.8 b111c5cea9 docs(changelog): fold the batch into the still-unreleased 0.9.95 (now 21 PRs)
0.9.95 was gated but never shipped, so the follow-on batch belongs in it,
not a separate 0.9.96. pyproject stays at 0.9.95.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:20:00 +01:00
safishamsiandClaude Opus 4.8 2a0cacb60c docs(changelog): open 0.9.96 with the install-security, type-alias, csharp-overload, tree, and other fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:16:39 +01:00
safishamsiandClaude Opus 4.8 18e52c4bb8 build(cache): bump schema to 25 for the type-alias, C# overload, and package.json-link extraction changes
Covers #4578/#4579/#4580/#4581 (Python/Go/C++/Rust aliases + Rust union),
#4591 (#4584, C# overload nodes), and #4572 (#4569, package.json dep link).
Each PR shipped a stale 20->21 bump against an older base; this is the single
reconciled bump on top of HEAD's 24.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:41 +01:00
Deepjyoti SarmahandClaude Opus 4.8 e76019466d fix(json_config): link a package.json dependency's manifest entry to its ref node (#4569, #4572)
Applied from PR #4572 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:25 +01:00
Mpasha17andClaude Opus 4.8 87151e40ac fix(csharp): give each method overload its own node and bind calls by argument count (#4584, #4591)
Applied from PR #4591 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:24 +01:00
madhusudanpatnaikandClaude Opus 4.8 14514b37f6 fix(rust): extract type aliases and union items as type nodes (Refs #4285, #4581)
Applied from PR #4581 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:22 +01:00
madhusudanpatnaikandClaude Opus 4.8 3bab26798e fix(cpp): extract namespace-scope using/typedef aliases as type nodes (#4580)
Applied from PR #4580 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:21 +01:00
madhusudanpatnaikandClaude Opus 4.8 8cfda7f1ce fix(go): extract type A = B aliases as type nodes (#4579)
Applied from PR #4579 (authorship preserved); stale cache-schema bump dropped for one combined bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:20 +01:00
madhusudanpatnaikandClaude Opus 4.8 291248ef49 fix(python): extract explicit type aliases as type nodes (#4578)
Applied from PR #4578 (authorship preserved); the PR's stale cache-schema
bump is dropped in favour of one combined bump for the alias cluster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 18:15:06 +01:00
Neal 44079cbd9b fix(detect): skip binary media word counting
Co-authored-by: Codex <noreply@openai.com>
(cherry picked from commit 62663244fb)
2026-10-23 18:12:48 +01:00
omsenjalia 73876cbdc7 fix(cache): read and write AST cache entries via extended-length paths on Windows
Under a deep root, the cache entry
<root>/graphify-out/cache/ast/v<ver>-s<schema>-g<grammars>/<64 hex>.json,
and mkstemp's even longer <hash>.<random>.tmp temp name, overrun Windows
MAX_PATH. save_cached raised FileNotFoundError and `graphify extract`
failed with "AST extraction failed" (e.g. any project under the default
%TEMP% of a long user name).

Move detect._os_path (the \?\ helper from #1655) to paths.os_long_path
so cache.py can share it, and use it for the entry and temp file in
save_cached and load_cached. detect._os_path stays as an alias. Keys are
unaffected: _normalize_path still strips the prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit abf9c9f6ab)
2026-10-23 18:12:47 +01:00
AromalBiju1 cbcd8dd601 fix(tree): don't count untagged nodes as a repo when choosing merged layout
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit a751df5f89)
2026-10-23 18:12:46 +01:00
AromalBiju1 97f18cb7b0 fix(tree): group merged graphs by repo tag so repos get their own roots
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 873795a05b)
2026-10-23 18:12:46 +01:00
m.fotohi 8a19705383 fix(install): use unique temp paths to prevent symlink attacks during skill install
(cherry picked from commit b2d3ffec7a)
2026-10-23 18:12:46 +01:00
safishamsiandClaude Opus 4.8 53d3d80346 fix(deps): declare pyyaml as a base dependency
The 0.9.95 YAML structural extractor (#4706) imports PyYAML, but it was
only a transitive dependency, so a fresh `pip install graphifyy` produced
zero nodes from .yaml/.yml files (the extractor degrades gracefully but
silently). manifest_ingest and markdown also use it via guarded imports.
Declare it like tree-sitter-json so the feature works out of the box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:16:07 +01:00
safishamsiandClaude Opus 4.8 ae2b453b7d chore: bump version to 0.9.95
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:14:52 +01:00
safishamsiandClaude Opus 4.8 4f99094579 docs(changelog): 0.9.95 — add the security, affected, build, cross-repo, hooks, objc, and sql fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:13:31 +01:00
safishamsiandClaude Opus 4.8 3b213769f0 fix: bump cache schema to 24 (#4705, #4677), clean recovered SQL labels, add security tests
- Bump _AST_CACHE_SCHEMA 23 -> 24: #4705 (ObjC NS_CLOSED_ENUM) and #4677 (SQL
  CREATE TABLE ERROR-node recovery) both change extraction output, and neither
  PR included the bump, so a warm ObjC/SQL cache would serve stale results.
- #4677: display the recovered table label through _clean_ident (consistent
  with the routine/trigger recoveries) while keeping the id keyed off the raw
  name so FK edges still resolve to it.
- Add regression tests the #4708/#4709/#4710 security PRs shipped without
  (multicast/unspecified SSRF block, Obsidian tag injection, Google shortcut
  frontmatter escaping).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 16:12:55 +01:00
08raviyadav e69cc9d89e Recover CREATE TABLE statements dropped into ERROR nodes by unsupported column defaults (fixes #4613)
(cherry picked from commit d496e9113e)
2026-10-23 16:11:21 +01:00
Jason Daniel 8a29ab62cb feat(objc): extract NS_CLOSED_ENUM declarations as enums (#4401)
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit c0d1001005)
2026-10-23 16:11:20 +01:00
Dextheking1 fedc4e82ed google_workspace: use full YAML scalar escaping for shortcut frontmatter
(cherry picked from commit 9907005e93)
2026-10-23 16:11:11 +01:00
Dextheking1 bf48d2b83e export: sanitize obsidian frontmatter tag values
(cherry picked from commit 420cc5c2be)
2026-10-23 16:11:10 +01:00
Dextheking1 02f62e769d security: block multicast and unspecified IPs in _ip_is_blocked
(cherry picked from commit cd96fbea26)
2026-10-23 16:11:09 +01:00
dherreraj 5c3c47ebea fix(hooks): run the rebuild from the checkout's top level (#3258)
The rebuild bodies read graphify-out/ and its .graphify_root relative to
the cwd, but git does not always start hooks at the top level. With
GIT_DIR exported and no GIT_WORK_TREE (a nested git call from another
hook or tool), git takes the caller's cwd as the work tree and
`git rev-parse --show-toplevel` echoes that subdirectory. A stray
graphify-out/ left there by an earlier run then roots the rebuild at the
subdirectory; for .claude/worktrees/ that walks every linked worktree.
The #3265 guard rejects a `..` marker, but its fallback is the same cwd.

Add a shared _TOPLEVEL_ANCHOR snippet right after _WORKTREE_GUARD in all
four hooks. In the standard layout it cds to the parent of the absolute
common .git dir; with GIT_WORK_TREE or a separate git dir it keeps git's
own --show-toplevel. GIT_DIR is rewritten to its absolute form so later
git calls still resolve. The block already runs in a subshell, so the cd
does not leak into chained hooks.

(cherry picked from commit e6193ab816)
2026-10-23 16:11:08 +01:00
xiongjianxu d709f67941 fix(cross-repo): bind ObjC split decl/impl across repos to its definition
An ObjC class whose @interface(.h) and @implementation(.m) live in
different repositories surfaces as two same-named candidate type nodes
that _merge_decl_def_classes folds within a repo but cannot across the
boundary (its id embeds the directory, #1556). The cross-repo member-call
pass then tripped the single-definition guard (len(candidates) != 1) and
dropped the call edge even though exactly one candidate DEFINES the
selector.

Scoped to lang == "objc": when more than one same-named candidate
survives, union their members and bind the selector to the sole candidate
that DEFINES it (a method node with definition_file set, or a .m/.mm
source). Two candidates that both define it are two unrelated classes and
stay ambiguous, so the guard still catches the collision it was meant to.
Other languages, the C# resolver path, and the single-candidate path are
unchanged.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit a82a375c2d)
2026-10-23 16:11:07 +01:00
nothariharan 7598c5dcee fix(build): resolve a raw graph's import endpoints through the alias index (#4670)
`extract --no-cluster` and the clustered path disagreed on where a
framework-style `#import <Kit/ToolEvent.h>` lands. The clustered path
re-pointed the edge at the header's real file node (`kit_event_toolevent`);
the raw path minted an external stub for the header's bare basename
(`toolevent`) and left the edge on it, so the two write paths of one
extractor produced different graphs for the same corpus.

The raw path only had a normalization map. Normalization cannot bridge
`toolevent` and `kit_event_toolevent` - they are not the same string
modulo case and punctuation - so re-pointing was impossible. The
clustered path resolves it through the pre-migration alias index, which
maps a file's old-stem spellings onto its canonical id (#1504/#1509).

Move that index into a shared `_alias_index` helper and use it in both
paths, so the clustered and the raw write path resolve an endpoint the
same way. The raw path now re-points endpoints BEFORE it mints stubs, so
an import that names a real file is no longer frozen onto a stub minted
for its unresolved spelling; only a genuinely external import
(`<Foundation/Foundation.h>`) still mints one (#2873).

An alias is committed only when exactly one node claims it, so two files
sharing a bare stem stay ambiguous and the import keeps its stub rather
than binding to whichever same-stem file was seen first.

Tests: two unit tests over `finalize_raw_graph_endpoints` (an old-stem
alias resolves to the real file node; an ambiguous alias stays
unresolved) and one end-to-end test that `extract --no-cluster` and the
clustered path agree on the import target. The new tests fail without
this change.

(cherry picked from commit acbec5f5c3)
2026-10-23 16:11:06 +01:00
Deepjyoti-Sarmah fb90065380 test: affected <path> on a shared basename with a second L1 node (#4698)
Pins the resolver against the node shapes #2032 and the rationale pass
produce, plus end-to-end runs of the issue's repro and the comment
control that was never broken.

(cherry picked from commit 3b04706956)
2026-10-23 16:11:06 +01:00
Deepjyoti-Sarmah 7aae85ec6e fix: resolve affected <path> for disambiguated file labels (#4698)
A path query matches every node in the file, so resolution ends in
_prefer_file_node. Two of its passes compare the label to the bare
basename, which #2032's disambiguation rewrote to a unique path suffix
(alpha/hooks.py); the third pass trusts L1 uniqueness, which a module
docstring (a rationale node on line 1) or a one-line top-level
definition also occupies.

Accept a label that names its own source_file, using the
_is_file_node_label predicate build/analyze already share. Runs before
the L1 fallback, so extra L1 nodes no longer decide the result.

(cherry picked from commit b80626ed3b)
2026-10-23 16:11:06 +01:00
safishamsiandClaude Opus 4.8 d67b5a0faf docs(changelog): 0.9.95 — add the YAML structural extraction feature (#4706)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 15:55:13 +01:00
safishamsiandClaude Opus 4.8 1834b3e703 fix(yaml): handle multi-document streams, gate data YAML, bump cache schema (#4706)
Three fixes on top of #4706's YAML extractor:
- Multi-document YAML (`---` separators) returned zero nodes because it used
  yaml.safe_load, which accepts only one document; k8s manifests, Helm output,
  and CI pipelines (the headline use cases) were silently empty. Use
  safe_load_all and give each document a distinct node (labelled by kind+name
  for k8s) so two docs in one file do not merge their keys.
- Add a _is_config_yaml gate mirroring extract_json's _is_config_json: a
  top-level data array, an OpenAPI/Swagger spec, a JSON-schema-in-YAML, and a
  flat all-scalar bundle (i18n) are skipped as data instead of minting up to
  500 orphan nodes. A mapping with any nested value, a recognized config name,
  or a .github/workflows path is still walked.
- Bump _AST_CACHE_SCHEMA 22 -> 23 (the PR reclassifies .yaml/.yml and adds a
  new extractor, so cached YAML must re-extract).
- Tests: multi-doc, data/OpenAPI/array skip, the safe_load ACE guard, and caps.
- Clearer missing-PyYAML hint (pip install pyyaml).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 15:54:58 +01:00
nothariharan 9c5d56f28f test(watch): use the markdown language alias for the document override
The rcfile language table maps 'markdown' -> .md; 'md' is not a recognized
alias, so the override was ignored and the watch test never saw a document.

(cherry picked from commit c1890f376a)
2026-10-23 15:51:09 +01:00
nothariharan e4d8235378 test(yaml): add structural extraction coverage and update document tests (#2214)
- Add tests/test_yaml_extraction.py (classification, key nodes, contains edges,
  stable ids, scalar/empty documents).
- Drop .yaml/.yml from the oversized-document slicing parametrization: they are
  code now, not documents sent to the semantic pass.
- Point the language-override lifecycle test at .md so it still exercises a
  document override (yaml is no longer a document).

(cherry picked from commit b69b32e2f1)
2026-10-23 15:51:09 +01:00
nothariharan 6a21696caa fix(detect/extract): structurally extract .yaml/.yml config files (#2214)
YAML files were classified as documents, so code-only / AST-only runs
(graphify update) produced zero nodes from them. Move .yaml/.yml into the
code path like .json: add a PyYAML-based structural extractor (extract_yaml)
registered in _DISPATCH, add the suffixes to CODE_EXTENSIONS, drop them from
DOC_EXTENSIONS and the splittable-text set.

Deterministic ids derived from the file path; top-level and nested keys become
nodes with contains edges, capped at 500 pairs / depth 6 to avoid exploding on
data-heavy YAML.

(cherry picked from commit 2e5565a684)
2026-10-23 15:51:09 +01:00
safishamsiandClaude Opus 4.8 d4f3cf7f51 docs(changelog): open 0.9.95 with the module-import over-promotion fix (#4707)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-23 15:39:02 +01:00
nothariharan a8ce86f02e test(extract): pin #2945 module-import over-promotion regression
Adds a regression test that fails on the pre-fix resolver: a caller that
imports a module for a different symbol and calls a same-named function must
keep the edge INFERRED, never EXTRACTED.

(cherry picked from commit 7d41be8891)
2026-10-23 15:38:48 +01:00
nothariharan ac4b5e33dd fix(extract): don't promote module-imported same-named calls to EXTRACTED (#2945)
When an ambiguous bare call resolves to a candidate solely because the
caller imported the candidate's source module (not the symbol itself), the
edge was promoted to EXTRACTED, falsely certifying a same-named symbol that
was never actually imported. Keep the resolution (for graph connectivity) but
mark it INFERRED. Symbol-level (from X import Y) imports still promote to
EXTRACTED.

(cherry picked from commit e73ca4e4a7)
2026-10-23 15:38:48 +01:00
safishamsiandClaude Opus 4.8 c588b72d93 chore: bump version to 0.9.94
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.9.94
2026-10-22 17:06:40 +01:00
safishamsiandClaude Opus 4.8 c3dad319ee docs(changelog): 0.9.94 — add the SQL, markdown, mcp, cross-repo, and ObjC fixes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-22 13:50:44 +01:00
Deepjyoti-Sarmah b8d9f12385 fix: fold SQL table stubs across files and bind schema-qualified names
The stub rewire skipped dotted labels, but a SQL table is schema.name,
so dotted labels from .sql files now count as definitions. SQL stubs
that share a normalized name and bind to nothing fold into one node
(the smallest id, so file order doesn't matter). .sql joins the
language families, so a SQL read no longer binds to a same-named
definition in another language.

(cherry picked from commit 55bc540bbe)
2026-10-22 13:49:38 +01:00
Deepjyoti-Sarmah 8e8a7c3808 test: one node per SQL table across files
A table no file defines, read from N files, becomes N per-file stub
nodes, and a schema-qualified read never binds to its definition in
another file. A SQL read of `Orders` also binds to a Python class
Orders. Guards cover same-file binding and different schemas.

(cherry picked from commit 1dfc710497)
2026-10-22 13:49:38 +01:00
Deepjyoti-Sarmah 9ce1404719 fix(sql): end the statement at GO batch separators
GO is a client-tool command, not SQL, and tree-sitter-sql has no rule
for it. Before parsing, each GO [n] line found on the masked text
(comments and strings blanked) becomes `;` plus spaces, so byte
offsets and line numbers don't move. A go line after a comma, or
before a comma or `)`, is a column and stays.

AST cache schema 20 -> 21 so cached .sql extractions re-run.

(cherry picked from commit 2293c31d79)
2026-10-22 13:49:33 +01:00
Deepjyoti-Sarmah 01715accd5 test(sql): GO batch separators without a semicolon
A GO line with no `;` before it is read as an identifier, so the next
statement lands in an ERROR node and its table is lost. Guards cover
`;`-terminated batches, GO inside a block comment, a column named go,
and GO after a keyword line such as SET NOCOUNT ON.

(cherry picked from commit f00ecc42c9)
2026-10-22 13:48:29 +01:00
Deepjyoti-Sarmah 518bd3f066 fix(sql): strip T-SQL brackets and read routine bodies (#4635)
tree-sitter-sql has no T-SQL bracket identifiers and no create_procedure
node, so bracket names were mangled and procedures only got a node from
the regex recovery, never an edge. Labels and lookup keys now drop the
brackets (ids are unchanged). Each recovered routine's body, up to the
next CREATE/ALTER, a GO line or EOF, is scanned on the masked text:
FROM/JOIN give reads_from, and INSERT/UPDATE/DELETE/MERGE/SELECT INTO
give writes_to. Alias updates resolve to their table; CTEs, variables
and temp tables are skipped.

AST cache schema 20 -> 21 so cached .sql extractions re-run.

(cherry picked from commit 70f521dba9)
2026-10-22 13:48:14 +01:00
Deepjyoti-Sarmah 608722208d test(sql): T-SQL bracket names, procedure reads and writes (#4635)
Bracket names like [dbo].[Orders] come out as the label `dbo].[Orders`,
stored procedures get no reads_from edges in any dialect, and
INSERT/UPDATE/DELETE/MERGE make no edge. Guards cover aliases, CTEs,
temp tables, variables, comments, strings and body boundaries.

(cherry picked from commit a2a666c6d7)
2026-10-22 13:47:26 +01:00
safishamsiandClaude Opus 4.8 7a0b031088 docs(serve): correct _resolve_single_node docstring to name explain (#4618)
The landed docstring listed only the get_node/get_neighbors MCP tools,
but the CLI `explain` command also routes node lookup through this
resolver (cli.py), so a substring-only near-miss now refuses there too.
Name explain in the docstring so the shared surface is accurate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-22 13:47:19 +01:00
nothariharan c6d4c25135 fix(mcp): a substring near-miss is no longer the answer (#3917)
`get_node` / `get_neighbors` share `_resolve_single_node`, which takes the
first node of the winning tier. The substring tier matches a query anywhere
inside a label, so `get_node(label="MailController")` on a codebase with no
`MailController` returned `EmailController` as if it were the exact symbol,
with no hint that the name does not exist.

The resolver now splits the two fuzzy tiers by intent. A prefix match still
resolves, because the caller started typing the real name (`email` ->
`EmailController`). A substring-only match is a search hit, not the symbol
the caller named, so it returns `No exact match for '<query>'` followed by
up to five closest names with their source location, plus a `path::symbol`
retry example. `get_neighbors` inherits the same answer through the shared
resolver, so both tools report one lookup the same way (ADR-0001).

Exact, source-exact and prefix resolution, the ambiguity refusals, the
dotted/Python and `path::symbol` forms and scoring are unchanged. The other
near-miss case in the issue (`get_node(label="edit")` with several `.edit()`
methods) already refused as Ambiguous under ADR-0001; tests cover both.

Tests: the issue's MailController repro, prefix shorthand, exact-over-substring
precedence, the five-candidate cap.

(cherry picked from commit d3875f4f1c)
2026-10-22 13:47:04 +01:00
xiongjianxu 775ddb9d5a fix(cross-repo): point a cross-repo call edge at its call site, not the caller's header
A cross-repo `calls` edge took its `source_file` from the caller node
(an ObjC method node is minted at its `@interface` in the `.h`) while its
`source_location` is the call-site line, recorded in the `.m`. Pairing the
two pointed `source_file:source_location` into the wrong file — often past
the end of a short header.

The park payload now carries the call-site file alongside the line, and
`link_cross_repo_member_calls` prefers it, falling back to the caller
node's file for pre-#3152 graphs. The parked path is relativized in the
same extract-time pass that relativizes node source_files, so it never
leaks the absolute checkout path into graph.json.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 3281d42492)
2026-10-22 13:46:54 +01:00