Commit Graph

566 Commits

Author SHA1 Message Date
Safi cddf47d3a0 chore: commit uv.lock (regenerated for 0.8.22 with tree-sitter-dm)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:38:59 +01:00
Brian K 244a266c06 chore: switch CI to uv, remove uv.lock from gitignore (#885)
* chore: declare pytest as a uv dev dependency

The contributing guide currently tells contributors `pip install pytest`
as a separate step, and CI does the same. Move pytest into PEP 735
`[dependency-groups]` so it's declared in pyproject.toml and `uv sync`
installs it by default (no `--with` workaround, no separate install
line). Update CI to use astral-sh/setup-uv + `uv sync` + `uv run pytest`,
and refresh the Contributing section of the README to match.

`[dependency-groups]` is the right home (vs `[project.optional-dependencies]`)
because pytest is dev-only and shouldn't appear in the published wheel's
optional features list alongside things like `pdf` or `mcp`.

* remove uv.lock from gitignore
2026-05-28 14:38:31 +01:00
Safi a1706ff7fd docs: add --mode deep to graphify extract CLI reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:30:36 +01:00
Safi 43bf3c2371 release 0.8.22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.8.22
2026-05-28 00:26:31 +01:00
Safi cfc945a507 fix: cap zlib decompression in extract_dmi, add size guard in extract_dmm
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:08:55 +01:00
Safi dacbdb539a feat: BYOND DreamMaker support, --mode deep flag, changelog fixes (#884, #1030)
- Feat: extract_dm (tree-sitter-dm), extract_dmi (PNG icon states),
  extract_dmm (tile dict uses edges), extract_dmf (window/elem hierarchy)
  for .dm .dme .dmi .dmm .dmf; 26 tests, fixtures, pyproject.toml dep
- Feat: graphify extract --mode deep flag; deep_mode threaded through all
  four LLM backends via extract_corpus_parallel
- Fix: CHANGELOG 0.8.21 entries for #1050, #1046, #1047 that were missing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:55:12 +01:00
Aadi c09fbef401 fix: remap hyperedges in community-aggregated meta-graph view (#1006)
When a graph exceeds the viz node limit, to_html() builds a
community-aggregated meta-graph and recursively calls itself.
The recursive call never carried hyperedges onto the meta-graph,
so graph.html always emitted const hyperedges = [] even when
graph.json contained plenty.

This fix remaps hyperedge node references from semantic node IDs
to community IDs before the recursive call, so hyperedge regions
render correctly in the aggregated view. Hyperedges that collapse
to fewer than 2 distinct communities are dropped (they wouldn't
render as a polygon anyway).

Fixes #1005
2026-05-27 22:39:01 +01:00
Safi 9f73400cbc fix: memory-dir gitignore leak, Pass 2 dedup cross-file identical merge, decorated method node ID mismatch (#1047, #1046, #1050)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 22:30:08 +01:00
Safi 740382af51 docs: add Amp platform, .svh extension, fix opencode uninstall in README 2026-05-27 13:04:50 +01:00
Safi 80301a06bf fix: OpenCode project path, hook loop guard, deterministic output, Amp platform, punctuation search, builtin god-node filter, .svh Verilog (#1040, #1018, #1037, #948, #994, #916, #1042)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.8.21
2026-05-27 12:39:22 +01:00
Aleksandr Tarutin a54a542b6c fix: make graph output deterministic (stop graphify-out churn) (#1010)
graphify-out regenerates differently on every `graphify update` even
when no source changed, so the committed graph is perpetually dirty and
the post-commit/post-checkout hooks fight every commit. Two independent
nondeterminism sources, each fixed here:

1. Edge direction flips. build.py builds an undirected graph and stores
   direction in _src/_tgt; collapsing two edges onto the same node pair
   is last-write-wins, and unstable edge iteration order flips them
   run-to-run. Fixed by sorting edges by (source, target, relation)
   before the add loop.

2. Clustering churn. The networkx Louvain fallback iterates string-keyed
   sets whose order is randomized per-process by PYTHONHASHSEED, so
   community assignments differ run-to-run even with seed=42. Fixed by
   exporting PYTHONHASHSEED=0 in the generated post-commit and
   post-checkout hook scripts.

With both fixes, `graphify update` is idempotent: rebuilding an
already-converged graphify-out reproduces graph.json and GRAPH_REPORT.md
byte-for-byte.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 12:20:31 +01:00
Safi d1d5751fa4 fix(watch): evict stale nodes in full re-extraction path when changed_paths is None (#1007)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:21:55 +01:00
Safi b07f0eb6a7 bump version to 0.8.20 v0.8.20 2026-05-26 20:34:22 +01:00
Safi ad3f3b2d9e harden XML parsing against billion-laughs DoS in extract_csproj and extract_lpk
stdlib ET does not cap entity expansion — a crafted .csproj or .lpk with nested
internal entities can exhaust memory. Pre-screen input bytes for <!DOCTYPE and
<!ENTITY before parsing (legitimate MSBuild/Lazarus files never contain these).
Also adds the missing 2 MiB size cap to extract_lpk (csproj already had one).
No new dependencies required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:32:14 +01:00
Safi e5313e73b2 add MCP config files to README file types table 2026-05-26 20:24:14 +01:00
Alexey Z baaab5f2a9 fix(extract_dart): use _file_stem instead of str(path) for child node IDs to prevent machine-specific absolute paths in graph.json (#999) 2026-05-26 20:21:27 +01:00
Safi 2c01a89b28 feat: MCP config extractor (.mcp.json, claude_desktop_config.json, mcp.json)
Adds graphify/mcp_ingest.py — extracts MCP server configurations into the
knowledge graph. Captures server nodes, NuGet/npm/pip package refs, commands,
env var requirements, and inter-server edges. Dispatched by filename before
the suffix lookup so generic .json extraction is unaffected. Env values are
discarded to prevent secret leakage. File size capped at 1 MiB. 29 tests.

Fixes: server_count budget now checked after validity guard so invalid entries
don't consume capacity; removed misleading uv run docstring example.

Co-Authored-By: adityachaudhary99 <adityachaudhary99@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:16:41 +01:00
Leon GyeongMan Baek 9abaa77c62 fix(cli): apply remap_communities_to_previous in cluster-only path (#1028)
cluster-only re-runs Leiden clustering and then re-applies the existing
.graphify_labels.json by raw cid index, which causes labels to attach to
clusters whose members are unrelated to the label's original meaning
whenever the graph has changed between labeling and re-clustering.

Mirror the safety net already present in watch.py:_rebuild_code added in
#822 for the watch/update paths.

Adds a regression test that fails without the fix (label cids become
orphaned from graph.json community attributes after re-clustering).

Refs: #1027

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:12:02 +01:00
Safi 3f8efaebc2 fix graphify update ghost nodes: as_posix() and relativize existing graph before eviction
Three issues in _rebuild_code (watch.py):
1. _relativize_source_files was called on result after eviction list was built,
   so existing nodes with absolute source_file were never normalized before comparison
2. deleted_paths and evict_sources used str() (backslashes on Windows) while
   graph.json stores forward-slash paths via _norm_source_file
3. _relativize_source_files itself used str() instead of as_posix()

Also fix extract.py source_file relativization to use as_posix(). Closes #1007.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:05:31 +01:00
Safi eef623a54a improve #1007 fix: use _norm_source_file with resolve() for symlink safety
Replace inlined path normalisation with _norm_source_file (the same function
that builds node source_file keys) so prune_set and node attrs are normalised
identically. resolve() on root handles symlinked scan roots. Keep both raw and
normalised forms in prune_set so nodes with absolute source_file also match.
Add edge pruning and Windows backslash path tests per Opus review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:59:13 +01:00
Safi a26f24ef7b fix stale nodes persisting after file deletion when manifest uses absolute paths
prune_set in build_merge now includes relative-path variants of each deleted file
so manifest absolute paths (e.g. /home/user/corpus/module_b/utils.py) match graph
node source_file values (e.g. module_b/utils.py) regardless of OS or run context.
Fixes #1007.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:53:36 +01:00
Safi d9debdf39a bump version to 0.8.19 v0.8.19 2026-05-26 12:52:04 +01:00
Safi 903fa9c141 add chinese extra to README optional extras table 2026-05-26 12:39:16 +01:00
Safi 8bcfffdf62 add .NET project file support (.sln, .csproj, .fsproj, .vbproj, .razor, .cshtml)
Adds extract_sln, extract_csproj, and extract_razor extractors. Captures NuGet
package refs, project-to-project dependencies, target frameworks, SDK attribute,
@using/@inject/@inherits/@model directives, Blazor component refs, and @code
methods. Resolves relative project paths to absolute paths so sln/csproj nodes
link correctly when the graph is assembled. Closes #515.

Co-Authored-By: aksrathore <aksrathore@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 12:27:28 +01:00
Candy c7a05d67a1 Clarify Chinese query segmentation scope (#1026)
Keep Graphify query segmentation focused on Chinese terms: rename the CJK helpers and extra to Chinese scope, cache the optional jieba import at module load, and keep a bigram fallback when jieba is unavailable.

Constraint: Reviewer asked either to broaden Hiragana/Katakana/Hangul support or rename CJK helpers; user chose Chinese-only because Japanese segmentation accuracy is uncertain.

Rejected: Broaden to Japanese and Korean segmentation | jieba is Chinese-oriented and the user explicitly limited scope to Chinese.

Confidence: high

Scope-risk: narrow

Directive: Do not label this path as CJK unless Hiragana/Katakana/Hangul segmentation is intentionally supported and tested.

Tested: uv run --with pytest pytest tests/test_serve.py tests/test_query_cli.py tests/test_benchmark.py

Tested: uv run --with pytest --with jieba pytest tests/test_serve.py -k "chinese or non_chinese"

Tested: graphify update .

Not-tested: Full test suite.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-26 12:24:04 +01:00
Safi 68863a72fe Remove unused _DEVIN_RULES_MARKER constant 2026-05-26 09:19:09 +01:00
vishal-gandhi 065a621fa6 feat: add Devin CLI support (graphify devin install/uninstall) (#1020)
* feat: add Devin CLI support

- Add 'devin' to _PLATFORM_CONFIG with skill-devin.md
- Override _platform_skill_destination for devin:
  user scope -> ~/.config/devin/skills/graphify/SKILL.md
  project scope -> .devin/skills/graphify/SKILL.md
- Add _DEVIN_RULES + _devin_rules_install/_uninstall:
  writes .windsurf/rules/graphify.md (always-on context)
  Devin reads .windsurf/rules/*.md the same way Windsurf IDE does
- Add graphify devin install [--project] / uninstall subcommand
- Create graphify/skill-devin.md with Devin YAML frontmatter
  (name, argument-hint, model, allowed-tools, triggers)
- Add 21 tests in tests/test_devin.py covering user/project scope
- Register _DEVIN_RULES in test_install_strings.py policy checks
- Update README.md and pyproject.toml to list Devin CLI

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* expand skill-devin.md to full feature parity with skill-pi.md

Rebuilds skill-devin.md from ~267 lines to 1176 lines, matching the
depth of other mature platform skills (pi, droid, codex).

Added:
- Parallel Part A + B dispatch guidance (AST alongside semantic subagents)
- Full subagent extraction prompt with rationale, semantic similarity,
  hyperedge, and confidence_score rules
- Extraction cache check (Step B0) and cache save/load flow
- --mode deep, --svg, --graphml, --neo4j/--neo4j-push, --mcp, --watch
- /graphify path, /graphify explain, /graphify add, /graphify query
- BFS vs DFS traversal with --dfs and --budget flags
- Token reduction benchmark step
- --update (incremental) and --cluster-only pipelines with graph diff
- Video/audio transcription (Step 2.5) with Whisper domain-hint strategy
- git commit hook section
- Always-on Devin rules section referencing `graphify devin install`

Updated test_devin.py: corrected python_c_syntax assertion to match
the interpreter-detection pattern used across all mature skills.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: vishal-gandhi <ivishalgandhi@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-26 09:17:18 +01:00
msotnikov 336652779b Fix: support array form of tsconfig extends (TypeScript 5.0) (#1017)
TS 5.0 allows `"extends": ["./a", "./b"]`. _read_tsconfig_aliases called
`extends.startswith("@")` directly, so an array raised
`AttributeError: 'list' object has no attribute 'startswith'`. _safe_extract
caught it and skipped the WHOLE file — and since alias resolution fires on any
path-aliased import, every TS file using `@/`-style imports in a repo with
array-extends tsconfig was dropped, yielding an empty graph.

Normalize `extends` to a list (str -> [str], list kept, else []), process
entries in order (later overrides earlier, current config's paths override all
parents per TS semantics), and skip scoped npm configs (`@...`) per entry as
before.

Adds regression test test_tsconfig_array_extends_alias_resolves_existing_ts_file.
2026-05-26 09:17:14 +01:00
Safi 4e80d8621f Fix wiki TypeError on null source_file and skip nested worktrees/ dirs (#1016, #1023)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 09:06:43 +01:00
Safi 3efae3827f Rate-limit backup_if_protected to one folder per day via content hash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 09:14:20 +01:00
Safi 98100f3b35 Bump version to 0.8.18 v0.8.18 2026-05-24 20:41:31 +01:00
Safi a4a615da97 Fix Ukrainian README typo, Unicode vocab regex, add migration note for Java extends->inherits
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:40:49 +01:00
eugene-krivosheyev 238702b697 Constrained query expansion (#998)
Add constrained query expansion step to /graphify query skill

## Problem

`graphify query` matches via case-folded substring + IDF — no stemming, no synonyms, no cross-language match. When the user's question uses different vocabulary than the graph labels (Slavic → English, "handlers" → "handler", "обработчик" → "handler"), the literal matcher returns 0
hits and the LLM consumer either gets empty subgraph or improvises an ungrounded keyword list from training memory (e.g. expanding "auth" to `{passport, sso, saml, oauth, jwt, scim, …}` regardless of whether those tokens exist in the corpus).

## Fix

Adds a `Step 0 — Constrained query expansion` block to the skill's `/graphify query` section. The LLM consumer extracts vocabulary from graph labels (CamelCase/snake_case split, length-filtered) and is instructed to pick **only** tokens present in that vocabulary, explicitly forbidden from inventing terms.

Effects:
- Bounded improvisation — fantom tokens (terms not in corpus) cannot be expanded, even when LLM "knows" they're related to the intent.
- Honest negative signal — if vocab is poor on a query's topic,  expansion returns [] and the LLM tells the user, instead of  fabricating a search.
- Auditability — selected tokens are printed to the user, and saved into `save-result` for the next --update to graph as Q&A nodes.

## Scope

Patches the canonical `graphify/skill.md`. The 11 host-variant skills (skill-codex.md, skill-aider.md, …) follow the same query-section contract but inline Python rather than calling `graphify query` CLI; those need a parallel patch with the inline form. Happy to follow up in a separate PR after review on the canonical patch.

## Test

On a graph built from the graphify repo itself (1284 nodes, 1454 vocab tokens), an unconstrained expansion of "укрупненная архитектура аутентификации" yields {auth, oauth, jwt, saml, sso, ldap, scim, mfa, 2fa, pin, passport, session, login, token} — of which 11/15 are absent
from the corpus. Constrained expansion against the actual vocab yields {credential, security, token, signature, user, architecture, component, module, overview} — 9 tokens, 0 fantom. Same retrieval, dramatically higher precision.
2026-05-24 20:38:13 +01:00
TheFedaikin ab4e5424ca feat: add cross-language semantic contexts for Python, JS/TS, C#, and Java (#996) 2026-05-24 20:38:10 +01:00
Ivan Dzhulynskyi 32effb10a9 docs: update Ukrainian README translation to v8 (#995) 2026-05-24 20:38:08 +01:00
mohamadkattan12 d778e2c36b fix(cli): reconstruct communities from per-node attribute when sidecar missing (#1001)
`graphify export html|obsidian|wiki|svg|graphml|neo4j` reads `communities`
exclusively from `.graphify_analysis.json` (set to `{}` if missing). The
post-commit / watch rebuild path doesn't regenerate that sidecar — only
graph.json + GRAPH_REPORT.md. Several skill workflows also delete temp
files at the end of `graphify extract`. In both cases the per-node
`community` attribute (`to_json` writes it on every node) is intact, but
the CLI ignores it.

Observed failure: `graphify export html` on a graph that exceeds the
viz node limit prints

    Graph has 64703 nodes (above 5000 limit). Building aggregated community view...
    Single community - aggregated view not useful. Skipping graph.html.

even though the same graph.json has 2,026 distinct `community` values
on its nodes — `to_html` just received an empty `communities` dict and
the aggregator collapsed to a single meta-node.

Fix: when the analysis sidecar is absent (or its `communities` field is
empty), reconstruct the `cid -> [node_ids]` mapping from the per-node
attribute in graph.json. The sidecar remains the canonical source of
truth when present; the reconstruction is a strict fallback. Every
downstream subcommand (`html`, `obsidian`, `wiki`, `svg`, `graphml`,
`neo4j`) sees the same shape it always did, just populated from the
graph itself instead of an externally-cached sidecar.

Tests added (tests/test_cli_export.py):
- `test_export_html_falls_back_to_node_community_attribute` —
  delete the sidecar, run export html, confirm `graph.html` exists
  and the "Single community" bail-out path does NOT fire.
- `test_export_html_fallback_recovers_multiple_communities` —
  stronger guarantee that the reconstructed community count equals
  what the sidecar would have provided (no silent data loss).
- `test_export_html_no_community_data_at_all_still_succeeds` —
  hand-build a graph.json with no per-node `community` attribute
  (older `to_json` versions, manually-constructed graphs); the
  command must still exit cleanly rather than crash.

All 26 tests in test_cli_export.py pass; ruff clean on both files.
2026-05-24 20:35:21 +01:00
mohamadkattan12 6fba4e4594 fix(watch): bypass shrink-guard when caller declared explicit deletions (#1000)
The post-commit hook passes `git diff --name-only HEAD~1 HEAD` as
changed_paths to `_rebuild_code`. That list includes deletions, and
`_rebuild_code` correctly identifies them (lines 352-367 in watch.py)
and evicts the stale nodes from the preserved set. The rebuilt graph
is intentionally smaller.

`_check_shrink` then refuses to overwrite the existing graph.json
because it only sees the node-count delta, not the cause. The guard
fires with "Refusing to overwrite — you may be missing chunk files
from a previous session. Pass --force to override."

Result: every commit that deletes a tracked file silently leaves stale
nodes in graph.json. The user must either pass --force (which also
disables the guard for legitimate failure modes) or manually re-run
`graphify update . --force` after delete-heavy commits.

Fix: thread a `had_explicit_deletions` flag from `_rebuild_code` into
`_check_shrink`. When the caller has declared the deletions, the
smaller graph is the expected outcome and the guard is skipped. The
guard remains intact for SILENT shrinkage — its actual purpose —
from failed semantic chunks or corrupted runs.

The fix is opt-in by design: callers that don't pass `changed_paths`
(e.g. the post-checkout full rebuild path) keep the old conservative
behavior. Only paths that explicitly track deletions get the bypass.

Tests added (tests/test_watch.py):
- `test_check_shrink_blocks_silent_shrink` — pre-existing behavior intact
- `test_check_shrink_allows_force_override` — pre-existing behavior intact
- `test_check_shrink_allows_explicit_deletions` — new: deletion bypass
- `test_check_shrink_allows_no_existing_data` — first-run case
- `test_check_shrink_allows_growth` — sanity
- `test_check_shrink_unlinks_tmp_on_refuse` — cleanup on refusal
- `test_check_shrink_keeps_tmp_when_deletions_declared` — no spurious unlink
- `test_rebuild_code_prunes_deleted_file_nodes` — end-to-end probe of the
  exact scenario the post-commit hook triggers (git init, build, delete
  one file, re-run with the deleted path in changed_paths, verify the
  graph shrinks and the surviving file's nodes are preserved)

All 8 new tests pass; full test_watch.py + test_build.py + test_export.py
(74 tests) pass with no regressions.
2026-05-24 20:35:19 +01:00
Safi 73c3c33ac7 Bump version to 0.8.17 v0.8.17 2026-05-23 23:36:56 +01:00
Safi 4dce16f475 Fix case-sensitive call resolution and cross-language phantom calls (#993, #991)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:35:25 +01:00
Safi 71b4e57b67 fix Husky 9 hook path, skill.md INPUT_PATH literal, per-worker exception isolation
- hooks.py: add _user_hooks_dir() to target .husky/ instead of .husky/_ on Husky 9 repos (#987)
- skill.md: replace unsubstituted 'INPUT_PATH' literal with '.' in generate() calls (#986)
- extract.py: wrap future.result() per-future so a single worker failure prints a warning instead of falling back to full sequential re-extraction (#943)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:17:40 +01:00
Safi 461e346f3a fix Windows cp1252 crash, str.parent crash, mcp error message, god_nodes relative import
- Replace non-ASCII → with -> in __main__.py print statements (#992)
- Coerce paths to Path objects at extract() entry to fix str.parent crash (#988)
- Fix serve.py ImportError to recommend graphifyy[mcp] extra (#967)
- Change relative import in _tool_god_nodes to absolute to fix script invocation (#966)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:14:01 +01:00
Safi 990ac706d8 bump version to 0.8.16
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.8.16
2026-05-22 18:58:31 +01:00
balloon72 b3474924c2 feat(install): add project-scoped skill installs (#931)
* feat(install): add project-scoped skill installs

* fix(install): cover project-scoped antigravity install

* test(install): cover project-scoped platform subcommands

---------

Co-authored-by: hanmo1 <hanmo1@lenovo.com>
2026-05-22 14:38:35 +01:00
Jon Attree 3238b32677 Exit non-zero when all semantic-extraction chunks fail (#889)
If `graphify extract --backend claude` runs without the `anthropic`
package installed (pip install graphifyy doesn't pull it in), every
semantic chunk fails inside extract_corpus_parallel. The per-chunk
errors print to stderr but the function returns the empty merged
accumulator anyway, so extract proceeds to write an AST-only graph.json
and exit 0. CI that checks exit status sees success even though the
requested semantic pass produced no nodes.

Track per-chunk success via the existing on_chunk_done callback, which
only fires after a chunk succeeds. If fresh extraction was requested
(uncached_paths non-empty) and zero chunks completed, abort before the
merge/cluster/write phase with exit 1 and a message naming the backend.

The same shape covers other backends with optional SDK deps (openai,
google-generativeai). Cached-only runs are unaffected: uncached_paths
is empty and the guard does not fire.

Tests in tests/test_extract_cli.py simulate the all-failed and
one-succeeded paths by patching extract_corpus_parallel directly.
2026-05-22 14:38:32 +01:00
yyautumn330 52d75bd988 fix: add .ets (ArkTS) extension to CODE_EXTENSIONS (#926)
ArkTS (.ets) is the primary language for HarmonyOS/OpenHarmony
application development, used by projects like SceneBoard. The
tree-sitter TypeScript parser already handles .ets files for AST
extraction — the detect module just wasn't recognizing them.

Without this, `detect()` silently skips all .ets source files,
missing ~90% of code in OpenHarmony codebases.

Co-authored-by: Autumn <autumn@AutumndeMacBook-Pro.local>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 14:38:15 +01:00
Sherbaev 38cebd321f docs: add Uzbek (uz-UZ) README translation (#982)
Adds docs/translations/README.uz-UZ.md and inserts an entry for
🇺🇿 Oʻzbekcha into the language navigation bar of README.md and all
27 existing translations.

Co-authored-by: Javokhir Sherbaev <javokhir.sherbaev@noveogroup.com>
2026-05-22 14:38:07 +01:00
szgnewGh 86109e9f27 fix: CJK/Unicode labels silently skipped in _norm/_norm_label dedup (follow-up to #811) (#937) 2026-05-22 14:26:27 +01:00
Safi 6efd06c54d add YC S26 badge to README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 14:17:02 +01:00
Safi ff14ad5245 bump version to 0.8.15
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 14:04:54 +01:00
deXterbed 1494874e25 feat: track JS/TS barrel re-exports as explicit graph edges
- Add 'export_statement' to import_types for JS/TS/TSX configs
- Extend _import_js to detect 'export { X } from ./mod' re-exports
- Emit 're_exports' edges linking barrel files to source symbols
- Preserve walk-through for 'export function/const' declarations
- Add 're_exports' to clean_edges allowlist for cross-file edges

Tested on a 976-file Next.js codebase: detects 162 re_exports edges
and 5760 symbol-level imports (previously 0 for both).
2026-05-22 13:28:35 +01:00