fix: resolve OLLAMA_HOST env var alongside OLLAMA_BASE_URL (#1940)#1966
Open
smz202000 wants to merge 1145 commits into
Open
fix: resolve OLLAMA_HOST env var alongside OLLAMA_BASE_URL (#1940)#1966smz202000 wants to merge 1145 commits into
smz202000 wants to merge 1145 commits into
Conversation
… routing (Graphify-Labs#1556, Graphify-Labs#1547) A class declared in a header (Foo.h/@interface) and defined in its impl (Foo.cpp/Foo.m/@implementation) fragmented into two nodes: _file_stem drops the extension so Foo.h and Foo.cpp share a node id, which _disambiguate_colliding_node_ids then split apart by path — and the two "defs" tripped every resolver's single-definition god-node guard, cascading into missing .h<->.m/.cpp linkage and cross-file/cross-language edges. - Routing: a `.h` using `#import` now routes to extract_objc (Graphify-Labs#1556 bridging headers — extract_c drops `#import` as a preproc_call), and a `.h` with C++-only signals (class/namespace/template/::/access-specifiers) routes to extract_cpp (Graphify-Labs#1547 — the C grammar has no class_specifier, so a C++ header previously yielded a junk node and lost every method). ObjC sniff keeps priority; a plain C header still routes to extract_c. - Merge: a new _merge_decl_def_classes post-pass collapses the header/impl id-collision onto the header (declaration) variant, modeled on _merge_swift_extensions, gated so it fires ONLY for a clean sibling header/impl pair (same dir, same base stem, exactly one header) — two same-named classes in different directories have different stems and never collide, so they are never merged (god-node guard verified). C++ method definitions retain their `Foo::` qualifier so a `Foo::bar` def keys onto the header declaration (one method node, not two); free functions keep their bare-name ids. Result: one canonical class node per .h/.m or .h/.cpp pair with methods unified, which unblocks the existing member-call resolvers (verified Swift->ObjC calls and Swift `extension` folding now resolve). Strict improvement over v8 (which produced junk/fragmented nodes here, verified). Still open as follow-ups: cross-file C++ #include edge resolution and a C++/ObjC cross-file member-call resolver (a pre-existing gap, not a regression). Reported by @JabberYQ (Graphify-Labs#1556) and @c0dezer019 (Graphify-Labs#1547). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aphify-Labs#1547, Graphify-Labs#1556) Connects paired classes across files: Main.cpp's `Foo f; f.bar()` now resolves to Foo::bar, and ObjC `Foo *f = [[Foo alloc] init]; [f doThing]` to Foo's doThing — the "connect with other classes" goal of Graphify-Labs#1547/Graphify-Labs#1556. Design grounded in prior-art research (ctags qualified-name matching, Doxygen's name-keyed false-edge failure modes, PAIGE's receiver-type approach, Clang USR): resolve by RECEIVER TYPE, never bare name, and skip when the type can't be inferred rather than guess (a false call edge / god-node is worse than a missing one). Mirrors the existing Swift/Python/Ruby/TS member-call resolvers. - C++ extractor now captures the member-call receiver (field_expression / qualified_identifier / pointer access) and builds a per-file type table from local declarations (`Foo f;`, `Foo* f;`, `Foo *f = ...;`); emits raw_calls. - ObjC extractor emits raw_calls for message sends with the receiver + selector and a type table from `Foo *f = ...;` locals (existing in-file selector / alloc-init / dot-syntax / @selector matching preserved). - New _resolve_cpp_member_calls / _resolve_objc_member_calls, registered for their suffixes. Receiver tiers: `Foo::bar()` / capitalized ObjC receiver and this/self/super (enclosing class) -> EXTRACTED; local-var-typed -> INFERRED. Single-definition god-node guard (skip unless exactly one type def matches); the just-shipped decl/def class merge makes a paired class one def so the guard resolves it. Verified: a.run() -> A::run only (not a same-named B::run); an uninferable receiver with run() in two classes emits zero edges (no fan-out); ObjC [f doThing] -> Foo only. - build.py: the cross-language INFERRED-call prune treated .h/.cpp/.m as different families and dropped header/impl interop calls; unified the C family (.c .h .cc .cpp .hpp .cxx .hh .hxx .cu .cuh .metal .m .mm) so a .cpp/.m call to a .h-declared method survives. Still open (tracked on Graphify-Labs#1547/Graphify-Labs#1556): the file-level `#include` edge can stay uncanonicalized when the project root isn't symlink-resolved (the extract() id-remap `continue`s on a /var-vs-/private/var mismatch) — the class connection above is robust to it; include-reachability candidate narrowing and ObjC dynamic-dispatch/id-typed receivers also deferred (expected low ObjC recall, per the research). Reported by @c0dezer019 (Graphify-Labs#1547) and @JabberYQ (Graphify-Labs#1556). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bs#1562) Extends the C# type-reference resolver (Graphify-Labs#1466) to be namespace-aware, advancing the Graphify-Labs#1318 shadow-node umbrella for C#: - The namespace is folded into the C# node id (_make_id(stem, namespace, name)), so two same-named types in different namespaces in one file no longer collapse — replacing Graphify-Labs#1466's detect-and-skip workaround for multi-namespace files. - Lexical per-block using-scope: a `using` applies only where it is in scope (file-level, or the enclosing namespace block via a scope chain), so sibling namespace blocks no longer share each other's usings. - Qualified references (`Namespace.Type`) resolve via in-scope aliases (`using Q = X.Y`) then exact known namespaces; generics are stripped. Preserves (and tightens) the refuse-rather-than-guess discipline: a bare reference resolves only when exactly one in-scope namespace provides the type; an ambiguous reference (e.g. `using A; using B;` both defining `Widget`) resolves to nothing rather than fanning out. Verified: `using A` -> A.Widget only; ambiguous -> no edge; qualified `B.Widget` -> B.Widget regardless of usings; sibling-block using-scope isolated; no dangling edges or fan-out. Reconciled onto current v8 (the PR predated the C++/ObjC member-call resolvers); full suite green, the C++/ObjC resolution coexists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cross-file member-call resolution for C++/ObjC (Graphify-Labs#1547/Graphify-Labs#1556) and namespace-aware C# type resolution (Graphify-Labs#1562), the work-memory overlay (Graphify-Labs#1441), test-mock call-graph fix (Graphify-Labs#1553), hyperedge member-key aliases (Graphify-Labs#1561), plus the TS/JS/ObjC resolution fixes (Graphify-Labs#1316/Graphify-Labs#1544/Graphify-Labs#1552/Graphify-Labs#1475). See CHANGELOG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hify-Labs#1565) `graphify affected` (blast radius) was blind to a function passed BY NAME as a call argument — `executor.submit(fn)`, `Thread(target=fn)`, `map(fn, xs)`, callbacks — so those callers were silently dropped from the affected set (an under-counting blast radius reads complete while missing exactly where regressions hide). Capture them as a distinct `indirect_call` relation (INFERRED, context "argument"), kept separate from `calls` so strict call-graph queries stay precise, and add it to DEFAULT_AFFECTED_RELATIONS. Hardened over the original PR against the name-collision false edge (the Doxygen #3748 trap): emit only when the argument identifier resolves to a callable definition AND is not shadowed by a parameter or local binding in the enclosing function. So `def via(pool, handler): pool.submit(handler)` (handler is the param, not the module function) and `process(config)` where `config` is local data emit no edge, while a genuine module function passed by name still resolves. Dedup-safe against existing direct `calls` edges. Python only; dict-literal / getattr-by-string / decorator dispatch deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Graphify-Labs#1565 captured a function passed by name as a call argument (executor.submit(fn), Thread(target=fn), map(fn, xs)) only when the callback was defined in the SAME file — it resolved through the in-file label map. But the dominant real-world shape is cross-module: the callback is imported (`from .handlers import on_event; pool.submit(on_event)`), so the same-file map can't see it and the edge was dropped — exactly the caller a blast-radius query must not miss. When the argument identifier isn't defined in-file (and isn't shadowed by a param/local — that guard already ran), emit an `indirect` raw_call and let the existing cross-file resolution pass handle it, branching to a distinct indirect_call/INFERRED edge instead of calls. It rides the same single-definition god-node guard and import-evidence disambiguation as direct calls (parity: when a direct call resolves, so does the indirect one; when the name is ambiguous, both bail). Two added safeguards: the target must be a real callable def (per-file callable_nids unioned across the corpus) so an imported data constant can never become a dispatch target, and an existing direct `calls` edge for the pair pre-empts it. Smoke-verified: imported single-def callback resolves; ambiguous name bails (same as direct); imported data constant rejected; direct+indirect to the same target keeps only the direct edge; cross-file keyword target=cb resolves. Full suite 2710 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fy-Labs#1566) Slice 1 of Graphify-Labs#1566. A function referenced as a VALUE in a dict/list/set/ tuple literal — the registry/route-table idiom (`ROUTES = {"create": create_user}`, `HOOKS = [on_start, on_stop]`) — is an indirect dependency that blast-radius must see, but the call-argument capture (Graphify-Labs#1565) didn't cover it. Emit an indirect_call edge for each callable value: - module-level tables attribute to the file node; function-scoped tables attribute to the enclosing function. Same-file and cross-file (an imported handler in a table routes through the cross-file resolver). - dict KEYS are excluded (only values are references); non-callable values (a number, a string) never resolve; a name shadowed by a param/local or rebound at module scope is the local value, not the function. Refactors the resolve-and-emit logic shared by the argument and table paths into one guarded helper (_emit_python_indirect_ref) and threads the edge `context` ("argument" | "collection") through the cross-file pass. Adds _python_module_bound_names for the module-scope shadow set. 7 new tests (module dict/list, function-scoped, dict-keys-excluded, non-callable-value, module-reassign shadow, cross-file table). Full suite 2717 passed. Python only; assignment/return refs + other languages remain on Graphify-Labs#1566. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…slice 5)
Brings the indirect_call model to JavaScript and TypeScript, the next-
biggest callback-passing ecosystem. Now captured:
- callbacks passed by name as call arguments: `arr.map(fn)`, `setTimeout(fn)`,
`el.addEventListener("x", fn)` — function-scoped, attributed to the caller.
- module-level callback registration (idiomatic in JS, unlike Python):
Express routes `app.get("/", handler)`, event wiring `emitter.on("e", h)`,
timers — attributed to the file.
- object/array dispatch tables: `const ROUTES = { create: handler }`,
`const HOOKS = [onStart, onStop]`, object shorthand `{ handler }`.
Arrow-const functions (`const cb = () => {}`) are registered as callable
targets (threaded callable_def_nids + local_bound_names through
_js_extra_walk). Guards mirror Python: shadowing by param/local/module
reassignment is rejected (JS module shadow set excludes function-valued
declarators so arrow-consts stay resolvable), object KEYS and non-callable
values are excluded, inline arrows/function expressions are direct defs not
references, single-definition god-node guard cross-file.
Two cross-file fixes the JS import model exposed:
- a name that resolves to an import-surfaced FOREIGN symbol (JS named
imports map the real node into the importing file's label map) is now
deferred to the cross-file resolver instead of being mistaken for a local
non-callable; the global callable_nids guard still rejects imported data.
- indirect_call dedup is now call-aware: a benign `imports` edge from a file
to the symbol it imports no longer suppresses the indirect_call to it
(only a real calls/indirect_call edge does).
Shared the resolve-and-emit helper across Python and JS/TS. 9 new tests
(func-arg, module object/array, Express-style registration, inline-arrow
negative, param-shadow, key/data exclusion, shorthand, cross-file import,
TS typed params). Full suite 2726 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) _check_skill_version advised "Run 'graphify install' to update" on ANY version mismatch. But `install` writes the package's OWN bundled skill and re-stamps .graphify_version, so when the skill on disk is NEWER than the running package, following that advice silently DOWNGRADES the skill to silence the warning. The docstring even said "warn if the skill is from an OLDER version" but the code never checked direction. Compare versions numerically (new _version_tuple, so 0.10 > 0.9 and a malformed stamp degrades instead of raising). When the skill is newer than the package, recommend upgrading the package (uv tool upgrade / pip install -U) instead of install; the older-skill case is unchanged. Hit in practice by a stale `uv tool` CLI and by contributors whose dev checkout stamped a newer skill. Reported by @TPAteeq. 4 tests (numeric ordering, both mismatch directions, silent-when-equal). Full suite 2730 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y-Labs#1566 slice 2) A function bound to a name (cb = handler) or returned from a factory (def make(): return handler) is a real reference, but indirect_call only covered call arguments and dispatch tables, so `affected` still dropped these callers. Emit indirect_call (context "assignment"/"return", INFERRED) for the value-side identifiers of a Python assignment RHS and a return, at function scope (owner = enclosing function) and module scope (owner = file node). Reuses the shared _emit_indirect_ref guard. Scans the VALUE side only -- the assignment target is a new local binding, not a reference -- so the existing param/local shadow guard still rejects the false edges Graphify-Labs#1565 fixed. Negatives covered: param-shadow, local-shadow, non-callable emit nothing. Full suite green; ruff clean.
…1569) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…raphify-Labs#1574, Graphify-Labs#1571) build_merge backs `graphify --update`. Two incremental-update data bugs: Graphify-Labs#1574 — it read only nodes+edges from the existing graph.json, never hyperedges, and build() only sees the new chunks' hyperedges. So every --update collapsed the graph's hyperedge set (the highest-signal semantic groupings) down to just the re-extracted files'. Now existing hyperedges are carried forward via attach_hyperedges (id-dedup): re-extracted files' prior hyperedges are replaced by their new version (by source_file), deleted files' are dropped via the prune set, and unchanged/global ones are preserved. This mirrors what watch.py already did. Graphify-Labs#1571 — when a caller omits `root`, absolute prune_sources (from detect_incremental) never relativized to the stored relative source_file keys, so deleted files' nodes survived as ghosts and accumulated across runs. Added _infer_merge_root: fall back to the committed graphify-out/.graphify_root marker, else the output dir's parent. This root now drives BOTH the prune set and the replace-per-source normalization, so both work without an explicit root. The CLI --update path and all shipped runbooks already pass root; this hardens the library for any other caller. 5 tests: hyperedge preservation (unchanged/global kept, re-extracted replaced, with and without root), deleted-file hyperedge prune, and root-less prune via both the grandparent and .graphify_root-marker fallbacks. Full suite 2742. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hify-Labs#1566 slice 3) Reflective dispatch by string literal — getattr(obj, "handler") — resolves the attribute name to a callable def and emits it under indirect_call (context "getattr", INFERRED) at both function and module scope, so `graphify affected handler` now covers getattr call sites. The name is a STRING, not an identifier: it names an attribute and is never shadowed by a param/local, so it resolves without the identifier shadow guard — the inverse of the Graphify-Labs#1565/Graphify-Labs#1566 identifier paths. A dynamic name (a variable, f-string, concatenation, or any expression) is not statically resolvable and emits nothing; obj.getattr(...) (a method, not the builtin) and the 1-arg form are ignored. Refactors the shared resolve-and-emit core out of _emit_indirect_ref into _emit_indirect_by_name so the getattr path reuses it (callable-target-only, cross-file deferral, dedup) without duplicating the guard; the identifier wrapper is behavior-preserving. Full suite green.
…n LLM)
Community labels defaulted to "Community N" whenever no LLM backend was configured,
making the report + suggested questions unreadable ("why does log_action connect
Community 70 to Community 129?"). Add `label_communities_by_hub`: name each community
after its highest-degree member — the structural hub — so the report reads "log_action"
/ "auth" with zero LLM cost. Ties break by node id for run-to-run stability; a community
with no members in the graph keeps the "Community N" placeholder.
Wired as the default base at both label-building sites — the label/standalone path in
__main__ and the watch/detect rebuild in watch.py. A configured LLM naming pass still
runs and overrides these with richer names; its no-backend placeholder fallback is
guarded so it can't clobber the hub labels.
…bels (Graphify-Labs#1576) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…to-called deduplicate_by_label is never wired into build(); the active dedup path is deduplicate_entities (imported and called in build). Its docstring claimed "Called in build() automatically," which was never true. Correct it to say the helper is dormant/unused and to warn that it merges by label alone with no file_type guard, so it must not be enabled for code nodes — same-label symbols from different files/packages (e.g. two Account types) would collapse, the cross-file conflation deduplicate_entities deliberately avoids for code (Graphify-Labs#1205). Docstring only; no behavior change. The function is unused and superseded, so it could reasonably be deleted instead — left in place here, flagged for your call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrap unguarded json.loads() in build_merge(), load_graph(), and _read_json_file() so corrupted graph.json files produce an actionable RuntimeError instead of an unhelpful JSONDecodeError traceback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ata loss (Graphify-Labs#1504) When two LLM extraction chunks each process a file with the same name in different directories, they independently generate the same node IDs and deduplicate_entities() silently drops one node (first-writer-wins). The data loss had no indication in any log, counter, or output. Adds a stderr WARNING when a duplicate ID comes from a different source_file, telling the user which files collided and recommending the per-subfolder extract + merge-graphs workflow to avoid it.
`class Dog < Animal` exposes the base in the `superclass` field, but the inheritance handler in `_extract_generic` had branches for java/kotlin/c#/scala/cpp/php/swift/python and none for Ruby, so every Ruby `inherits` edge was silently dropped (contains/methods/calls unaffected). Add a Ruby branch that reads the `superclass` field, handling both a bare `constant` (`< Animal`) and a `scope_resolution` (`< Foo::Bar` -> Bar). Adds a subclass to the Ruby fixture and a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tree-sitter-groovy exposes inheritance via the same `superclass` and `interfaces`/`type_list` fields as tree-sitter-java, but the inheritance- emitting block in `_extract_generic` was gated on `ts_module == "tree_sitter_java"`. Groovy was the only class-based JVM language in the file with no inheritance handler, so every Groovy `extends`/`implements` was silently dropped (contains/methods/imports/calls were unaffected). Widen the gate to include `tree_sitter_groovy`; the existing `_emit_java_parent_type` path handles the identical node shapes verbatim. Adds a base class + interface to the Groovy fixture and two regression tests (extends -> inherits, implements -> implements). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bs#1537) Graphify-Labs#1537 shipped with a manual test checklist only. Add automated tests that corrupt a graph.json and assert the actionable RuntimeError at all three load paths (build_merge, affected.load_graph, diagnostics._read_json_file) plus a happy-path guard. Also record the six merged small fixes in the changelog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Labs#1571 follow-up) Rigorous smoke testing surfaced an edge case the canonical-tmp unit tests couldn't reach: when the scan root is under a symlink (macOS /var -> /private/var, a symlinked home or git worktree), the absolute prune path and the resolved root differ by prefix, so _norm_source_file's lexical relative_to fails and the prune/replace match silently misses — deleted files' ghost nodes survive. Latent in the pre-existing Graphify-Labs#1007 path too, now that build_merge resolves the root. Fix: when lexical relative_to fails, retry with both sides fully resolved. Only the failure path resolves, so the common lexical match stays filesystem-free (no per-node stat on the hot replace-per-source loop). Adds a symlinked-root prune regression test (POSIX-only). Full suite 2768, and the full end-to-end smoke battery (indirect_call all contexts, JS, Ruby/Groovy inherits, hyperedge preservation, symlinked-root ghost prune, corrupt-json errors, dedup collision warning) is green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
indirect_call dispatch arc (call args + cross-file, dispatch tables, assignment/return, getattr, JS/TS), two incremental-update data fixes (hyperedge preservation + ghost-node prune, incl. symlinked-root hardening), direction-aware skill-version warning, deterministic hub community labels, Ruby/Groovy inheritance edges, corrupt-graph.json error handling, cross-chunk collision warning, Windows hook worker limit. Built wheel validated in a clean venv: CLI reports 0.9.4, import resolves to the installed package, new-feature smoke battery green, and a real `graphify extract` produces indirect_call + inherits edges end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…9.4 regression) Local install-testing of 0.9.4 surfaced that `graphify extract .` dropped every cross-file indirect_call edge — the headline feature, broken on the primary code path — while the extract() API worked. Root cause: the cross-file callable-target guard unioned per-file `callable_nids` (pre-remap ids), but extract() rewrites node ids afterward (id_remap / prefix sym_remap / _disambiguate_colliding_node_ids). When the scan root relativizes ids (cache_root == project root, which the CLI passes), the guard set went stale and `tgt not in callable_nids` rejected every remapped target. In-file indirect edges survived (emitted with consistently-remapped endpoints), which masked it — only cross-file dropped. Fix: mark callable defs with a `_callable` attribute on the node dict instead of exporting an id list. A marker rides through every id remap; callable_nids is rebuilt from the final (post-remap) nodes right before the pass that uses it, and the marker is stripped before output (like origin_file). Regression test extracts with cache_root == project root (the CLI shape) and asserts the cross-file edge survives and _callable never ships to graph.json. Full suite 2769. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reported from a real run: after re-scoping a repo (5807->3710 nodes), the 228-community re-cluster kept run-1's 300 saved labels, so cids now covering a different community wore the wrong (LLM) names — silently. cluster-only reused .graphify_labels.json wholesale, and the overlap-based cid remap grabs a prior cid on any overlap, inheriting a stale name. Fix: write a per-community membership signature (sha256 of sorted member ids) beside the labels. On reuse, keep a saved label only when the community's signature is unchanged; a changed community is renamed by its deterministic hub (correct-by-construction) with a warning to run `graphify label` for fresh LLM names. For label files predating the signature, fall back to a community- count check (a differing count means a different clustering -> don't trust cid labels). Unchanged graphs reuse labels silently — no false warnings. Verified: stale legacy labels (42) on a 12-community graph -> warned + hub- renamed all + sig written; rerun on the unchanged graph -> silent reuse, labels stable. Unit tests for the signature (deterministic, order-independent, changes on membership change). Full suite 2771. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`alias Foo.{Bar, Baz}` (and the same import/require/use brace form) emitted
NO imports edges. tree-sitter-elixir represents it as a `dot` node holding the
base alias plus a trailing `tuple` of member aliases, but the import handler
only matched a bare `alias` child, so every multi-alias import was silently
dropped.
Add `_get_alias_modules`, which expands the brace form to `Foo.Bar`,
`Foo.Baz`, … while leaving the single form (`alias Foo.Bar`) unchanged. Adds a
fixture line + regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Function calls (`y = f(x)`) were silently dropped — only `subroutine_call` (`call sub(...)`) was handled in walk_calls. tree-sitter-fortran represents a function invocation as a `call_expression`, which had no branch, so every function-to-function call produced no edge. Handle `call_expression`. Because Fortran uses the same `name(...)` syntax for array indexing, the callee is resolved against procedures defined in the file (`target_nid in seen_ids`) before emitting — so array accesses like `arr(i)` cannot fabricate spurious `calls` edges. Adds a function + caller to the fixture and a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enum variant payload types were silently dropped — `struct_item` and
`trait_item` had type-reference handlers but `enum_item` had none, so the
variant field types were never traversed.
Add an `enum_item` branch that walks
`enum_variant_list -> enum_variant -> ordered_field_declaration_list`
(tuple variants, `Click(Logger)`) and `field_declaration_list` (struct
variants, `Resize { size: Dim }`), emitting a `references` edge from the enum
to each field type. Reuses the same type collection as the struct path. Adds
an enum to the fixture and a regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…abs#1577, Graphify-Labs#1578, Graphify-Labs#1579) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bs#1897 Graphify-Labs#1902 Graphify-Labs#1907 Graphify-Labs#1896 Graphify-Labs#1900 Graphify-Labs#1901 Graphify-Labs#1906) Bumps to 0.9.17 (unreleased) and records the batch implemented via Fable subagents in isolated worktrees, integrated onto v8: out-of-scope node drop, manifest stamping, merge-driver registration, hooksPath configparser fix, obsidian prune, multilingual query stopwords, .skill classification, and the postgres package-name hint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot misreported as deleted (Graphify-Labs#1908 Graphify-Labs#1909) Two coupled excluded-vs-deleted fixes: Graphify-Labs#1909 — incremental extract's prune set was derived from the manifest alone (manifest - corpus), so a file that became excluded without ever being manifest-listed (every pre-Graphify-Labs#1897 graph) kept its stale nodes in graph.json forever. The prune set is now also derived from the existing graph's own node source_files reconciled against the post-exclude detect corpus (_stale_graph_sources), restricted to in-root paths; out-of-root --include/symlinked entries and remote (://) sources are never pruned. Relative source_files are anchored against both the scan root and the --out root (the Graphify-Labs#555/Graphify-Labs#1899 relativized form). The --no-cluster incremental early exit never runs build_merge, so an exclusion-only change now prunes the raw graph.json in place instead. Graphify-Labs#1908 — save_manifest retained any prior row whose file still existed on disk, so an excluded-but-alive file survived as a permanent phantom that detect_incremental reported as deleted on every run. Full-scan callers (extract's saves, watch._rebuild_code's saves) now pass the RAW detect corpus via a new scan_corpus parameter and in-root rows outside it are dropped; the corpus is deliberately not the Graphify-Labs#933 stamp-filtered files dict, so failed-chunk/omitted-doc rows and --code-only doc rows survive. Subset saves (changed_paths hooks, Graphify-Labs#917) keep the seeding default. detect_incremental now splits manifest rows that left the scan into deleted_files (gone from disk) and excluded_files (alive but out of scan), mirroring the watch-side Graphify-Labs#1795 distinction, and the extract summaries report the two separately. Ordering matters: extract's cleanup of newly-excluded nodes previously worked only through the Graphify-Labs#1908 conflation, so the graph-source prune lands together with the manifest split to avoid regressing Graphify-Labs#1909.
…des (Graphify-Labs#1910) tree-sitter-sql parses PL/pgSQL CREATE FUNCTION statements (OUT/INOUT params, tagged dollar quotes, PERFORM/:= body statements) as ERROR nodes, and the dispatch loop had no branch for them, so the functions were silently dropped from the graph. Handle ERROR nodes inside walk() (they can nest inside a merged create_function during multi-statement error recovery) and dispatch top-level ERROR statements to it. The branch regex-scans the raw node text for every CREATE [OR REPLACE] FUNCTION/PROCEDURE, mirroring the existing fb_proc_or_trigger and has_error fallbacks, with a name class that keeps schema-qualified names (exposed.important_function) whole. The PL/pgSQL body is not scanned for FROM/JOIN references to avoid junk reads_from targets, and node ids match the clean create_function branch so seen_ids dedups consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…phify-Labs#1910 in unreleased 0.9.17 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.cjs was half-registered: the language maps in build.py and extract.py already routed it to the JS grammar, but it was missing from CODE_EXTENSIONS, the extractor _DISPATCH, _LANG_FAMILY, and the JS resolution/cache suffix sets — so .cjs sources (Electron main/preload scripts, CommonJS escape hatches in "type": "module" packages) were silently skipped during a build: classified as non-code and never handed to the JS extractor. Add .cjs alongside .mjs in the six lists that gate/route JS files (detect.py, extract.py _DISPATCH, analyze.py _LANG_FAMILY, extractors/models.py cache-bypass, extractors/resolution.py resolve exts, cli.py _HOOK_SOURCE_EXTS), with regression locks mirroring the .mts/.cts fix (Graphify-Labs#1607). Real-world impact: an Electron app whose ~2000-line main.cjs backend was invisible went from 201 to 294 nodes and 256 to 441 edges on rebuild. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…odes (Graphify-Labs#1915) _rebuild_code fed Markdown/doc files (.md/.mdx/.qmd/.skill) into the AST quick-scan while _reconcile_existing_graph preserved the same docs' semantic (LLM) nodes, so every semantic-backed doc was represented twice (heading nodes + concept nodes) and the graph bloated ~4x vs the CLI `graphify . --update` path. Semantic now supersedes AST per doc source: before choosing extract_targets, read the existing graph's source identities that carry semantic doc nodes (non-_origin=="ast", gated on file_type=="document" so pre-Graphify-Labs#1865 marker-less graphs aren't misread) and exclude those docs from the quick-scan in both the full-rebuild and changed_paths branches. They stay in code_files, so the Graphify-Labs#1795 fail-closed corpus check and the shrink accounting still cover them — a previously-bloated graph self-heals on the next full rebuild (the AST ownership rule drops the stale heading nodes) without the shrink guard refusing the smaller write. On incremental rebuilds the excluded docs never enter rebuilt/node-evicted identities, mirroring Graphify-Labs#1865's tier-scoped edge rule at the node level, so a doc's semantic nodes and edges survive. Docs with no semantic layer (and brand-new docs) keep the no-LLM quick-scan structure from #09b33b7.
… cache (Graphify-Labs#1916) save_semantic_cache groups nodes/edges/hyperedges by their own source_file and the write loop skips a group whose path is a ghost (not .is_file(), silently) or out-of-scope per the Graphify-Labs#1757 guard — but an edge or hyperedge in an ALLOWED group that references a node id from a skipped group was still written verbatim, so on replay (check_semantic_cache) it dangled forever. The Graphify-Labs#1895 filter does not cover this: it cleans the in-memory merged result, while the checkpoint writes the cache BEFORE it runs and replay bypasses it entirely. Fix at the cache layer (the authoritative write path): before the write loop, compute the node ids belonging to groups that will be skipped — mirroring BOTH skip branches — minus ids also defined in a group that will be written (duplicate-attribution nodes must not be over-pruned). Each written group then drops edges whose source/target is a skipped id and hyperedges whose member list intersects them (whole-hyperedge drop, mirroring Graphify-Labs#1895). Pruning runs on the incoming result only, so with merge_existing=True (the llm.py checkpoint path) the prior cached entry's valid edges survive the union untouched. Everything is gated on allowed_source_files being provided, so unscoped callers stay byte-identical. Complementary hardening in build_from_json: hyperedges were copied into G.graph["hyperedges"] verbatim without member validation, so a dangling hyperedge reached graph.json even from a live (non-cache) extraction. Members are now remapped via the same normalization the pairwise-edge loop uses and pruned when they still don't resolve; a hyperedge with no surviving member is dropped whole with a stderr warning. (Single-member hyperedges are legal in this codebase — per-file flows in the Graphify-Labs#1574 tests — so the drop threshold is zero survivors, not two.) Tests: scoped save with an edge to an out-of-scope real file, the same with a ghost source_file, whole-hyperedge drop, unscoped save preserved byte-identically (raw cache entry compared), merge_existing keeping the prior slice's valid edges, and build_from_json pruning a dangling hyperedge member / dropping an all-dangling hyperedge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ce (Graphify-Labs#1894) `graphify extract --mode deep` over a warm tree was a silent no-op, for three stacked reasons: 1. The semantic cache ignored mode: deep runs were served standard-mode entries (and vice versa). check_semantic_cache/save_semantic_cache now take `mode` (default None, byte-identical when omitted so older installed callers keep working) and map it to a namespaced kind — cache/semantic/ for None, cache/semantic-{mode}/ otherwise. The per-chunk checkpoint in llm.extract_corpus_parallel and the extract / cache-check consumers thread the run's mode through (cache-check grows --mode/--deep). cached_files, clear_cache, and prune_semantic_cache sweep BOTH namespaces; prune uses the same live-hash set for both (liveness is content-based, mode-independent) so semantic-deep/ can't regrow the Graphify-Labs#1527 unbounded-orphan problem and inherits the files_by_type-derived exclusion gating for free. 2. extract had no --force — the flag was silently swallowed by the parser's unknown-arg fallthrough. It is now real (plus GRAPHIFY_FORCE env parity with `update`): force disables the incremental gate so detection is a full scan and skips the semantic cache READ so every semantic file re-dispatches, while the post-run save and manifest stamping still happen. 3. The incremental gate dispatched zero files on a warm unchanged tree before the cache was ever consulted, so namespacing alone couldn't fix the repro. In deep+incremental runs the semantic pass now widens to the full live doc/paper/image set from detect_incremental's files_by_type (already exclusion-filtered, Graphify-Labs#1908/Graphify-Labs#1909) and lets the mode-namespaced cache decide hits/misses, with a loud count line so the first deep run's full re-dispatch is visible. Skill-side threading of mode is deliberately deferred to PR-2; mode defaults keep generated skills byte-compatible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…raphify-Labs#1915 Graphify-Labs#1912 in unreleased 0.9.17 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add _score_query() producing combined ranking + per-term singleton winners in a single graph traversal. _pick_seeds consumes precomputed winners instead of rescoring each term via _score_nodes. Behavior byte-identical to the legacy T+1 path. Supersedes the rescoring loop from Graphify-Labs#1596 while preserving its per-term coverage guarantee; folds in coverage scaling from Graphify-Labs#1724 and multiplicity penalty from Graphify-Labs#1832. Orthogonal to the trigram prefilter from Graphify-Labs#1431. Benchmark (full sweep in Graphify-Labs#1889): 1k-100k nodes, 1-10 terms: 1.43x-2.43x median speedup Single-term: no regression (1.70x-1.89x improvement) Traversals: T+1 -> 1 regardless of term count Tests: 3221 passed, 3 skipped. Ruff clean. Graphify graph updated. Refs Graphify-Labs#1889
…score_query API; bench newline PR Graphify-Labs#1918 replaced _pick_seeds(terms=) with best_seed_by_term= from _score_query. Update the Graphify-Labs#1900 German-stopword seed test to the new single-traversal API and add the missing trailing newline in the (manual, non-collected) query-scoring benchmark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…raphify-Labs#1917) An id whose canonical stem contains its legacy stem as a prefix (parent dir name == file stem, e.g. .claude/CLAUDE.md) re-matched the legacy branch every build and grew another stem segment, defeating the same_topology/no_change short-circuits and churning graph.json + clustering on every zero-delta update. Skip an id that already carries its canonical stem (mirrors graph_has_legacy_ids); genuine legacy migration still applies. Also records the Graphify-Labs#1889/Graphify-Labs#1918 query-scoring perf work in the changelog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e diagnostic (Graphify-Labs#1925 Graphify-Labs#1920 Graphify-Labs#1923 Graphify-Labs#1922) - Graphify-Labs#1925: a missing manifest.json no longer degrades `extract --code-only` into a full scan that discards the committed semantic layer. An existing graph.json is a sufficient incremental baseline (detect_incremental treats an absent manifest as "all new / none deleted"), so out-of-scope doc/paper/ image nodes are preserved while genuinely deleted sources still evict. - Graphify-Labs#1920: _stamped_manifest_files now counts hyperedge output, so a doc whose only chunk output is a hyperedge is stamped instead of re-extracted forever. - Graphify-Labs#1923: new namespace/use-aware PHP resolver (mirrors the Java resolver, runs before the unique-name rewire) so App\Models\Page and an imported Filament\Pages\Page stay distinct — no more false inherits/imports edge. - Graphify-Labs#1922: detect() records ignored files/dirs in a new `ignored` diagnostic field (the nested-ignore scoping bug itself shipped in 0.9.16 / Graphify-Labs#1873). Regression tests added for each; full suite 3325 passed, 3 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Graphify-Labs#948) Stamp the 0.9.17 release date and add a belated acknowledgement that Amp (ampcode.com) platform support, which shipped earlier in v8, was contributed by @zuwasi in Graphify-Labs#948. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…routines can't drop references edges (Graphify-Labs#1854) A C-language routine's body from information_schema.routines is just the C symbol name, so its reconstructed stub (CREATE FUNCTION ... AS $gfx$ name $gfx$ LANGUAGE c) is unparseable by tree-sitter-sql, and the parser's error recovery consumes the statements that follow. With FK ALTER TABLEs emitted last, every references edge was silently lost on any DB with a common extension installed (uuid-ossp, pgcrypto, pg_trgm, fuzzystrmatch, ...). Emit the FK block before the routine DDL. FK statements only reference tables, which are emitted first, so the order is always safe. On a real DB with 35 tables / 41 FKs / 41 extension routines: 41/41 references edges vs 1/41 before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s#1939) The semantic cache keyed entries on sha256(file content + path) alone, with no component for the extraction prompt that produced them. After an upgrade that changed the prompt, every unchanged file was a cache hit and replayed the older prompt's extraction: the run exited 0, cost.json looked cheap, and the graph silently carried two prompt generations side by side. The reporter saw 506 of 512 docs replay an older vintage on a rebuild they expected to be cold; deleting the whole cache was the only workaround. output, and invalidating them on every release would re-bill extraction for unchanged files. Fingerprinting the prompt itself keeps both properties: entries survive releases that don't touch the prompt, and invalidate only when it actually changed. Semantic entries now live under cache/semantic/p{fingerprint}/, mirroring the AST cache's v{version}/ layout. Both extraction paths pass their prompt: the Python/CLI path from llm.py's _EXTRACTION_SYSTEM (shared by every backend), and the skill path via a new prompt_file argument in Step B0/B3 naming the references/extraction-spec.md the subagents were handed. The fingerprint normalizes line endings so a CRLF checkout isn't mistaken for a new prompt. Pre-existing entries predate fingerprinting and have unknowable vintage, so they are still served rather than re-billing a whole corpus on upgrade — but check_semantic_cache now warns with the count, turning "no signal at all" into a visible one. merge_existing refuses to fuse such an entry into a current-vintage write, which would mix two prompts inside one entry and then attest the result to a prompt that produced half of it. Old-fingerprint entries are pruned by liveness only, never swept wholesale the way stale AST versions are: two hosts with different prompts (verbose vs compact extraction-spec) can share one graphify-out/, and a wholesale sweep would have each run delete the other's entries and re-bill on every alternation. prune/clear/cached_files glob recursively so fingerprinted entries can't become unprunable orphans (the Graphify-Labs#1527 failure mode). The two monolith skills (aider, devin) inline their prompt instead of shipping a spec sidecar and stay on the unfingerprinted path for now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`graphify merge-chunks` concatenates agent-written `.graphify_chunk_*.json` files with only a JSON-decode guard, so an oversized payload or a crafted node/edge id (e.g. `../../etc/passwd`) flowed straight into the merged graph. Route each chunk through `load_validated_semantic_fragment`, which stats the file size BEFORE reading it (a multi-GB chunk can't blow up memory), parses the JSON, and validates the byte/count caps + the node/edge id charset that blocks path traversal (Graphify-Labs#825). An invalid chunk is skipped with a warning (filter semantics; never abort). Left OUT of build_from_json/load_graph_json on purpose: those must keep loading valid pre-existing graphs. Also relax two over-strict checks in the shared validator that would otherwise silently drop whole legitimate chunks (a relaxation — never a new rejection): - file_type is no longer gated: build coerces any value via _FILE_TYPE_SYNONYMS (unknown -> "concept", Graphify-Labs#840), so synonyms like "markdown"/"tool" the loader maps must not fail validation. - the id charset now allows Unicode word chars (build's normalize_id preserves CJK/Cyrillic/accented-Latin ids); the explicit path-separator/".." check still blocks directory escape. Corrects the stale module docstring (the validator serves the devin skill path and merge-chunks, not skill-opencode/codex).
…Graphify-Labs#1953) An untrusted chunk with a non-numeric input_tokens/output_tokens would abort the whole merge with a TypeError after other chunks had already merged. Coerce to 0 so a bad token field can't defeat the per-chunk validation guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A full `graphify extract` writes the final graph with `to_json(..., force=True)`, which bypasses the Graphify-Labs#479 shrink guard. That is correct for a clean build that legitimately shrinks (dedup collapse, deleted code), but when this run's extraction was incomplete — an AST pass crashed, or some semantic chunks failed — forcing the write lets a partial graph silently overwrite a good complete one. The build now tracks incompleteness (AST-pass failure, semantic-pass crash, or succeeded < total chunks) and falls back to the shrink guard (force=False) on an incomplete run, so a smaller partial graph is refused rather than written. It exits non-zero before the manifest is written, so the manifest is never stamped for a graph we declined to write and the next run re-attempts. `--allow-partial` restores force=True to override intentionally. Note: the `--no-cluster` raw-dump path writes graph.json directly and has no shrink guard; this change covers the normal clustered build path only.
…uster path The clustered write is guarded by to_json's Graphify-Labs#479 shrink check, but the `--no-cluster` raw-dump path writes graph.json directly and had no guard, so an incomplete `--no-cluster` build could still overwrite a larger complete graph with a partial one — the residual gap noted in the original change. Add `existing_graph_node_count` in graphify.export (mirrors to_json's guard and respects the graph-size cap) and, on the raw path, refuse the write with exit 1 before the manifest when the build was incomplete and the new graph has fewer nodes than the existing one — unless --allow-partial is passed. Both write paths now enforce the same guarantee.
…rors (follow-up to Graphify-Labs#1951) Two gaps the review found in the incomplete-build shrink guard: - existing_graph_node_count() returned None ("proceed") on a present-but- unparseable graph.json, so the --no-cluster path could clobber a complete graph whose file was corrupt/mid-write. It now returns a MALFORMED_GRAPH sentinel and the caller fails closed, matching to_json's Graphify-Labs#479 handling. - A walk that couldn't fully enumerate the corpus (permission-denied subtree, I/O error) is now treated as an incomplete extraction: detect()/ detect_incremental() already record walk_errors; the extract path consumes them so a walk-truncated graph can't force-overwrite a complete one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
graph.json (the clustered `to_json` write and the `--no-cluster`/merge raw dumps) and manifest.json were written with a direct `open()`/`write_text`, so a crash, kill, or disk-full mid-write left a truncated, unparseable file that the next load or `detect_incremental` then failed on. Add `write_text_atomic`/`write_json_atomic` in graphify.paths (temp file in the same directory + `os.replace`; JSON is streamed into the temp, not materialized as one string) and route the graph.json writers (export.to_json, cli._prune_graph_json_sources, the merge driver) plus detect.save_manifest through them. The helper preserves the destination's mode (an atomic replace never tightens 0644 to mkstemp's 0600), writes through a symlinked destination (shared-output setups), and falls back to copy-then-delete on a Windows os.replace lock — matching graphify.cache's existing atomic writer. On failure the previous file is left intact and the temp removed. Not a power-loss durability guarantee (no fsync, consistent with the rest of the codebase).
…c helper (follow-up to Graphify-Labs#1952) The atomic-write PR left several writers on the old truncate-then-write path. Route them through write_json_atomic so a crash mid-write can't corrupt them: - the --no-cluster raw graph.json dump (a core graph.json writer) - merge-graphs / merge-chunks / merge-semantic output - .graphify_analysis.json and .graphify_labels.json sidecars - global_graph.py's global-graph.json and global-manifest.json write_json_atomic gains an ensure_ascii flag so the raw-UTF-8 writers (labels, merge outputs) keep byte-for-byte output. Adds tests for the Windows PermissionError copy fallback and ensure_ascii=False. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…as complete A chunk whose LLM response is truncated (`finish_reason="length"`) and can't be recovered by splitting, or that hits the adaptive-retry depth cap, returns a partial node set. Today that set is checkpointed and written to the content-hash semantic cache + manifest-stamped as complete, so the incomplete nodes are served forever until the file content changes or `--force`. Truncated give-up results are now tagged with an internal `_partial` marker. `save_semantic_cache` stamps the affected file's entry `partial: True` (detected from the marker or an explicit `partial_source_files` arg), and `load_cached` treats a partial entry as a cache MISS, so the file re-dispatches and retries. The file is also left unstamped in the manifest (like a failed chunk, Graphify-Labs#933) so detect_incremental re-queues it on the next incremental run — not only on a full / `--force` / content-change run. A file sliced across chunks accumulates via a partial-aware `merge_existing` peek (`load_cached(allow_partial=True)`) so a truncated slice is never dropped or silently promoted to complete. Self-heals: a later complete extraction overwrites the same key with a non-partial entry. The marker is stripped after the final save so it never leaks into graph.json.
…Graphify-Labs#1950) The _partial item-marker approach couldn't fire on the most common truncation shape: a mid-JSON cut parses to zero items, so a sliced document whose second slice truncated empty was still stamped complete (the PR's own headline case). - The adaptive-retry give-up sites now record the chunk's own source files in a result-level _partial_files list, independent of parsed items; it propagates through _merge_two / the recursion merges / _merge_into so it reaches both the per-chunk checkpoint and the run-level manifest stamp. - _partial_source_files unions _partial_files with the item markers. - save_semantic_cache seeds an empty group for a named partial file with no items so its entry is stamped partial, and carries a partial prev entry's flag forward so a later clean slice merging over it can't re-promote it to complete. - the CLI final save now passes partial_source_files (computed before the save) so an empty-parse file isn't written back as a complete entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The semantic (LLM) extraction runs on documents/papers/images; code files are handled by the deterministic AST engine and never reach the model. A node the model tags file_type="code" is therefore a symbol it surfaced from within a document (a name in a fenced code block, an API referenced in a paper), and it enters graph.json today with no check that the symbol actually appears in the source the model read. `_out_of_scope` (Graphify-Labs#1895) only rejects nodes attributed to a file that was NOT dispatched, so a fabricated symbol on a dispatched file passes. `extract_files_direct` now verifies every file_type=="code" node whose source_file was dispatched in the call: if no identifier from its label occurs (case-insensitive substring) in that file's source bytes, the node's confidence is downgraded to "UNVERIFIED" (never dropped) and the count is reported to stderr. Concept/document nodes, nodes without a source_file, nodes attributed to undispatched files, and labels with no checkable identifier are left untouched. Best-effort; never aborts extraction; one unreadable file is skipped, not fatal.
…follow-up to Graphify-Labs#1949) The PR flagged unverifiable code nodes as confidence="UNVERIFIED", but node confidence is read by nothing and UNVERIFIED isn't in the validated (edge-only) confidence vocabulary {EXTRACTED,INFERRED,AMBIGUOUS} — a dead, colliding field. - Move the flag to a dedicated verification="unverified" node field, off the confidence key. A node the model already hedged (INFERRED/AMBIGUOUS) is left untouched, as before. - Also check the node id's identifiers (not just the label): ids carry the verbatim symbol, cutting false flags on prettified labels. - Skip the (potentially PDF-re-extracting) source read entirely when a result has no code-typed node with a source_file. - Wire a consumer: diagnose_extraction now counts and reports unverified nodes, so the persisted flag is actually surfaced. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ter fixes in the 0.9.18 changelog Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Labs#1940) Graphify's ollama backend previously only recognised the custom OLLAMA_BASE_URL variable for configuring the Ollama API endpoint. Ollama itself has always used OLLAMA_HOST for server configuration -- users who had already set OLLAMA_HOST for their Ollama setup needed to set a separate, Graphify-specific variable. Add _ollama_base_url() helper that resolves the Ollama API base URL with the following precedence: 1. OLLAMA_BASE_URL (graphify-specific, backward compatible) 2. OLLAMA_HOST (standard Ollama env var, auto-constructs /v1 path) 3. Default http://localhost:11434/v1 Update detect_backend(), extract_files_direct(), call_llm(), and cli.py validation paths to use the consolidated helper. OLLAMA_HOST values with an explicit scheme (https://...) are honoured; plain host:port values default to http://.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1940