@javierkaiser/opencode-indexing 0.2.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,106 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.1] - 2026-10-16
9
+
10
+ ### Documentation
11
+ - The README's code-graph section is no longer a roadmap. It said the plugin
12
+ "deliberately stays on the semantic side" and offered `indexing_graph_*` tools
13
+ as a future major version — both untrue once codegraph became a companion.
14
+ Rewritten around the two indexes: what each costs to keep fresh, what the
15
+ benchmark does and does not measure, how to move from one to the other, and an
16
+ explicit note that the graph tools are withdrawn rather than pending. The
17
+ install-time detail moved to *Install*, where `--with-codegraph` already
18
+ lives, instead of being restated here.
19
+
20
+ ### Fixed
21
+ - **The installer named the wrong package.** `install.mjs` and the skip message
22
+ in `src/codegraph.ts` both said `codegraph`, which on npm is an unrelated 2024
23
+ library by a different author with no binary at all. `npm install -g codegraph`
24
+ exits 0, puts nothing on `PATH`, and the step then reports success — the worst
25
+ shape of failure for an installer. The real package is
26
+ `@colbymchenry/codegraph`, which is what the CLI already on this machine came
27
+ from. Both call sites now use the scoped name, and a test reads `install.mjs`
28
+ and asserts the unscoped install never appears.
29
+ - **The PowerShell wrapper could not pass `--with-codegraph`.** `install.mjs`
30
+ implemented the flag and `install.sh` forwards every argument, but
31
+ `install.ps1` declared only five switches, so on Windows the recommended
32
+ install was impossible through the wrapper. The switch exists now, and both
33
+ wrappers record the preferred `npx` form.
34
+ - The prompt-footer skill now tells the model that codegraph exists. It listed
35
+ `Grep` as the answer for exact symbols and never mentioned the graph tools at
36
+ all, so an assistant had no way to know the structural half was available —
37
+ the same class of problem as an indicator that renders nothing: the capability
38
+ was there and invisible. The guidance is conditional on the MCP server being
39
+ present, because it is optional.
40
+ - `THIRD_PARTY_NOTICES.md` was missing four packages it distributes:
41
+ `solid-js`, `@opentui/core`, `@opentui/solid` (shipped since 0.2.4) and
42
+ `apache-arrow`, which had never been listed. Licenses read from the installed
43
+ manifests, not from memory. CodeGraph gets its own section: not bundled, MIT
44
+ per its manifest, and the license text is linked rather than reproduced
45
+ because the package ships no `LICENSE` file and declares no author, so there
46
+ is no copyright line this project can verify.
47
+ - *How it works* and *Where each index lives* now show codegraph. The diagram
48
+ had one index and the table had two; there are three.
49
+ - `indexing_search` and `indexing_status` descriptions mention the companion.
50
+
51
+ ### CI
52
+ - The release workflow now creates the GitHub Release itself, attaching the
53
+ tarball that was actually published and taking its notes from this file. It
54
+ also stopped hiding its own failures: the download ran under `--silent`
55
+ inside a `pipefail` pipeline, so a registry hiccup failed the step with no
56
+ output at all to explain it — which is how three versions shipped without a
57
+ release page.
58
+ - Dependabot groups only what can move together. The old type-based groups
59
+ paired dependencies that contradict each other (`apache-arrow` against
60
+ `@lancedb/lancedb`'s peer ceiling, `@opencode/plugin` against the pinned
61
+ `@opentui` packages), and `npm ci` failed with ERESOLVE before any test ran.
62
+
63
+ ## [0.3.0] - 2026-10-16
64
+
65
+ ### Added
66
+ - **`indexing_build` and `indexing_import` now also start codegraph** for the
67
+ workspace, so the semantic index and the code graph are built by the same
68
+ action instead of one silently going stale. `init` the first time a folder is
69
+ seen, `sync` afterwards — the second is what covers the case where codegraph's
70
+ daemon has already shut down, which it does after five minutes without
71
+ clients.
72
+
73
+ **codegraph is a companion, not a dependency.** Nothing about this plugin
74
+ requires it: with the CLI absent, `available()` returns false, nothing is
75
+ spawned, no build fails, and no line is added to the output. That path is
76
+ covered by a test that hides codegraph from `PATH` and runs the real spawner,
77
+ not a fake — an injected runner would have proved the logic while leaving the
78
+ wiring unchecked.
79
+
80
+ - `codegraph` setting, on by default (`/indexing-config set codegraph=false` to
81
+ turn it off). Read as `!== false`, because the key is in no config file anyone
82
+ has and a default that required it to be present would never apply.
83
+
84
+ - One line in `indexing_status` saying whether codegraph is indexed for the
85
+ folder. Read from the filesystem, so it costs no process — status runs on
86
+ every search through the footer.
87
+
88
+ ### Changed
89
+ - The build summary reports the codegraph command it started, and always in the
90
+ past tense of a *launch*, never of a success: the process is detached, so at
91
+ the moment the tool answers, codegraph has only begun. Anything that goes
92
+ wrong afterwards lands in `.codegraph/daemon.log`, which is where the message
93
+ points.
94
+
95
+ ### Notes for review
96
+ - A dangerous workspace root — the home directory or a drive root — is refused
97
+ rather than indexed. codegraph has `-f` for exactly that case, and passing it
98
+ because a tool call said so would index the whole machine.
99
+ - Launches are rate-limited per workspace to one per 15 seconds, because
100
+ codegraph runs its own daemon with a file watcher and two `sync` invocations
101
+ seconds apart contend for the same SQLite database.
102
+ - `.codegraph/` was already excluded from the scanner (every dot-directory that
103
+ is not explicitly allowed is skipped), and there is now a test pinning it:
104
+ codegraph writes a multi-megabyte SQLite database plus a WAL there, and
105
+ letting the scanner walk it would embed another indexer's storage into this
106
+ one, for money, forever.
107
+
8
108
  ## [0.2.4] - 2026-10-16
9
109
 
10
110
  ### Fixed
@@ -237,7 +337,12 @@ First public release.
237
337
  - Cross-platform installer (`install.mjs`, plus `install.sh` / `install.ps1`
238
338
  wrappers).
239
339
 
240
- [Unreleased]: https://github.com/Javierkaiser/opencode-indexing/compare/v0.2.0...HEAD
340
+ [Unreleased]: https://github.com/Javierkaiser/opencode-indexing/compare/v0.3.1...HEAD
341
+ [0.3.1]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.3.1
342
+ [0.3.0]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.3.0
343
+ [0.2.4]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.2.4
344
+ [0.2.3]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.2.3
345
+ [0.2.1]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.2.1
241
346
  [0.2.0]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.2.0
242
347
  [0.1.1]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.1.1
243
348
  [0.1.0]: https://github.com/Javierkaiser/opencode-indexing/releases/tag/v0.1.0
package/README.md CHANGED
@@ -55,6 +55,36 @@ npx @javierkaiser/opencode-indexing --with-codegraph
55
55
  | `--no-restart` | Do not restart the service |
56
56
  | `--with-codegraph` | Install and register codegraph as a companion MCP (opt-in, `--package` only) |
57
57
 
58
+ ### The codegraph companion
59
+
60
+ [CodeGraph](https://github.com/colbymchenry/codegraph) is a separate tool — a
61
+ local code-graph indexer exposed as an MCP server — and this plugin drives it
62
+ rather than reimplementing it. If it is not installed yet:
63
+
64
+ ```bash
65
+ npm install -g @colbymchenry/codegraph
66
+ ```
67
+
68
+ The package is scoped, and that matters: `codegraph` unscoped on npm is an
69
+ unrelated 2024 library with no CLI, so installing that name exits 0 and leaves
70
+ nothing on `PATH`.
71
+
72
+ Installing it is only half of it. `indexing_build` and `indexing_import` also
73
+ start the CLI for the workspace, so both indexes are built by the same action:
74
+ `codegraph init --yes` the first time a folder is seen, `codegraph sync`
75
+ afterwards. codegraph is a **companion, not a dependency** — with the CLI absent
76
+ nothing is spawned, no build fails, and no line is added to the output. Turn it
77
+ off with `/indexing-config set codegraph=false`, and see
78
+ [Semantic search and code graphs](#semantic-search-and-code-graphs) for why the
79
+ two belong together.
80
+
81
+ The launch is detached and reported as started, never as finished: on a large
82
+ monorepo `init` runs for minutes, and a tool call that blocked that long would
83
+ be worse than one that returns. Anything that fails afterwards lands in
84
+ `.codegraph/daemon.log`. A home directory or a drive root is refused rather than
85
+ indexed, and launches are limited to one per 15 seconds per workspace because
86
+ codegraph's own daemon already watches files.
87
+
58
88
  ### With the OpenCode CLI
59
89
 
60
90
  ```bash
@@ -82,7 +112,7 @@ The other six tools only read. OpenCode's base policy already allows everything
82
112
  write them out only if you want to tighten the set.
83
113
 
84
114
  `install.sh` and `install.ps1` are thin wrappers over it; PowerShell also accepts
85
- `-Local`, `-Package`, `-Spec`, `-SkipDeps` and `-NoRestart`.
115
+ `-Local`, `-Package`, `-Spec`, `-SkipDeps`, `-NoRestart` and `-WithCodegraph`.
86
116
 
87
117
  `--package` deletes any copied plugin it finds: both sources define the same
88
118
  plugin id and the config directory is scanned automatically, so leaving both
@@ -108,12 +138,17 @@ resolve from the config file containing the entry:
108
138
  ```
109
139
  OpenCode ──► indexing_search ──┬──► Kilo's index (read-only) Qdrant ws-* · Kilo LanceDB
110
140
  └──► own index (read/write) own LanceDB (default) · Qdrant oc-*
141
+
142
+ OpenCode ──► codegraph_* ───────► codegraph's graph (its own) .codegraph/codegraph.db
143
+ ▲
144
+ └── indexing_build / indexing_import keep it in step
111
145
  ```
112
146
 
113
147
  - **Read-only reuse**: the plugin computes the same collection name Kilo uses (`ws-` + sha256 of the workspace path, first 16 hex chars), validates the embedding profile stored in the metadata point, and queries with an embedding from the *same* model (e.g. Mistral `codestral-embed-2505`, 1536d).
114
148
  - **Import without re-embedding**: `indexing_build` (or `indexing_import`) can copy vectors + chunks from an existing Kilo index — both its Qdrant collections and its local LanceDB database — at **zero embedding cost**, then incrementally index only the delta.
115
149
  - **Own index**: manifest-based incremental indexer (per-file sha256) writing to `oc-*` collections or an embedded LanceDB database, using the same chunk payload shape as Kilo (`filePath`, `fileHash`, `codeChunk`, `startLine`, `endLine`, `segmentHash`, `pathSegments`).
116
150
  - **Merge**: results from Kilo + own are merged; own (fresher) hits win on overlap; ghost hits (deleted files) are filtered out.
151
+ - **Companion graph**: the same build starts codegraph for the workspace, which keeps its own index in `.codegraph/` and serves it over its own MCP server. This plugin never reads or writes that database — see [Semantic search and code graphs](#semantic-search-and-code-graphs).
117
152
 
118
153
  ## Tools (namespace `indexing`)
119
154
 
@@ -155,6 +190,7 @@ Settable keys (case-insensitive):
155
190
  | `ollamaurl` | Ollama base URL |
156
191
  | `openaicompatibleurl` / `openaicompatiblekey` | OpenAI-compatible endpoint |
157
192
  | `importfromkilo` | `true` to import from Kilo when building, `false` to start from scratch |
193
+ | `codegraph` | `false` to stop also running the `codegraph` CLI on build. On by default; a no-op when codegraph is not installed |
158
194
  | `autorefresh` | Refresh the own index automatically on search |
159
195
  | `enabled` | Master switch. `false` pauses writing and refreshing; search keeps reading Kilo's index |
160
196
  | `searchmaxresults` | Result cap per search |
@@ -400,12 +436,13 @@ Word about data safety: Kilo's indexes (`ws-*` collections and its LanceDB direc
400
436
  ### Where each index lives
401
437
 
402
438
  The own index and Kilo's index are independent, and each one is chosen on its
403
- own:
439
+ own. codegraph keeps a third, and it is the one this plugin never writes to:
404
440
 
405
441
  | Index | Default | Configured by |
406
442
  | --- | --- | --- |
407
443
  | Own index | embedded LanceDB, under `~/.local/state/opencode-indexing/lancedb` | `vectorStore` |
408
444
  | Kilo index (read-only) | whatever `kilo.jsonc` says | Kilo itself |
445
+ | codegraph's graph | `.codegraph/` inside the workspace | codegraph itself |
409
446
 
410
447
  Kilo's `vectorStore` used to decide where this plugin's own index was written,
411
448
  because both were read from the same setting chain. That is no longer the case:
@@ -439,20 +476,31 @@ that. Set vectorStore=qdrant explicitly in
439
476
  Set `vectorStore=qdrant` in `~/.config/opencode/indexing.json` (or run
440
477
  `/indexing-config set vectorstore=qdrant`) to use a Qdrant server instead.
441
478
 
442
- ## Semantic search vs. code graphs (roadmap)
479
+ ## Semantic search and code graphs
480
+
481
+ Two indexes, not two options. This plugin builds and searches the semantic one;
482
+ it also keeps codegraph's code graph in step for the same folder, so a single
483
+ `indexing_build` leaves both current. Neither replaces the other, and this is not
484
+ a fork of codegraph — see [The codegraph companion](#the-codegraph-companion)
485
+ for the install side.
443
486
 
444
- `opencode-indexing` implements **semantic (vector) search**. It is worth being
445
- explicit about how it compares to **code-graph** tooling (call graphs, symbol
446
- graphs, SCIP-style indexes), because they answer different questions and are
447
- complementary:
487
+ ### The two indexes
448
488
 
449
- | | Semantic index (this plugin) | Code graph |
489
+ | | Semantic index (this plugin) | Code graph (codegraph) |
450
490
  | --- | --- | --- |
451
491
  | Question it answers | "Where is the code that does something like X?" | "Who calls X? What breaks if I change it? Path from A to B?" |
452
492
  | Signal | Meaning (embeddings over chunks) | Structure (symbols, `CALLS`, `DEFINES`, `IMPORTS` edges) |
453
493
  | Strengths | Natural language, concepts without known identifiers, cross-language | Exact structural facts, multi-hop traversals, no similarity false positives |
454
494
  | Weaknesses | "Similar" is not "relevant"; no notion of connections | Cannot find a concept with no known symbol; fragile on dynamic code |
455
- | Cost profile | Embedding API + vector DB | Local parsing + small graph store |
495
+ | What it costs to stay fresh | **Paid**: the embedding API, re-run when the model changes | **Free**: local parsing and a small graph store, re-run on every edit |
496
+
497
+ That last row is what shapes the setup. A semantic index is re-embedded only when
498
+ the model changes, and that is money; a graph is synced whenever a file is
499
+ touched, and that is CPU. Two cadences for two economies — which is the reason
500
+ to keep both pointed at one folder rather than at two projects that quietly
501
+ drift apart.
502
+
503
+ ### Measuring the difference
456
504
 
457
505
  We measured this rather than assuming it. [`bench/`](./bench) runs the same
458
506
  real questions about code through four methods — word matching, symbol-name
@@ -471,23 +519,43 @@ What it found:
471
519
  better on both: more files found on one codebase, a better position on the
472
520
  other.
473
521
 
474
- One caveat that matters for reading that: the benchmark measures `codegraph
475
- query`, the one subcommand that matches names. It does **not** measure what a
476
- graph is actually good for — who calls what, what breaks if you change it,
477
- paths between two places. Those are the graph's real strength and they are not
478
- in the numbers above.
522
+ One caveat, and it is larger than it looks. The benchmark measures
523
+ `codegraph query`, the one subcommand that matches **names**. It does **not**
524
+ measure what a graph is actually good for — who calls what, what breaks if you
525
+ change it, paths between two places. So the fusion above is *semantic + name
526
+ matching*, **not** *semantic + graph traversal*. What would actually justify
527
+ reaching for the graph is not in those numbers, and this section does not claim
528
+ it is.
529
+
530
+ ### Using them together
531
+
532
+ The division of labour is the one the wider field converges on: use embeddings
533
+ to find the entry node **by intent**, then walk the graph for **exact
534
+ relationships**.
479
535
 
480
- The broader consensus points the same way: use embeddings to find the entry node
481
- by intent, then walk the graph for exact relationships. Cursor reported ~12.5%
482
- retrieval-accuracy improvement combining semantic search with grep; Sourcegraph
483
- layers semantic search over a SCIP graph; Aider uses tree-sitter + PageRank with
484
- no embeddings at all for repo maps.
536
+ 1. `indexing_search` — "where is the retry logic for failed webhooks". Finds the
537
+ neighbourhood from meaning alone, with no identifier to know in advance.
538
+ 2. `codegraph_explore` — the same area, returning the verbatim source of the
539
+ relevant symbols, the call paths between them, and what depends on them.
540
+ 3. `codegraph_impact` / `codegraph_callers` — once the question becomes "what
541
+ breaks if I change this", the graph answers exactly, and a similarity search
542
+ cannot.
485
543
 
486
- This plugin deliberately stays on the semantic side and coexists with grep/read
487
- (see the tool descriptions and the bundled skill). A code-graph mode (tree-sitter
488
- symbol/edge extraction into SQLite, exposed as `indexing_graph_*` tools that
489
- chain after `indexing_search`) is a candidate for a future major version; the
490
- tree-sitter infrastructure is already in place. Independently, the installer can itself install and register codegraph as a companion MCP (`--with-codegraph`, opt-in, published-package mode only).
544
+ Both are live in the same session, so an assistant moves between them without
545
+ being asked to.
546
+
547
+ ### Why there are no graph tools in this plugin
548
+
549
+ There are no `indexing_graph_*` tools here, and that is a decision rather than a
550
+ gap. An earlier version of this section proposed adding tree-sitter symbol and
551
+ edge extraction into SQLite as a candidate for a future major version. Once
552
+ codegraph was available as a companion, that plan would have reimplemented
553
+ something another tool already does better — in a second store, behind a second
554
+ indexer that has to be kept alive and fresh.
555
+
556
+ The plugin stays on the semantic side and coexists with grep/read (see the tool
557
+ descriptions and the bundled skill). What it adds for the structural half is to
558
+ keep it current, not to duplicate it.
491
559
 
492
560
  ## Development
493
561
 
@@ -505,7 +573,9 @@ node test/cli.ts build --vector-store lancedb --root .
505
573
 
506
574
  ## Acknowledgements
507
575
 
508
- This plugin interoperates with the open-source [Kilo Code](https://github.com/Kilo-Org/kilocode) indexing format (MIT License, Copyright (c) 2026 Kilo Code). The line-based chunker and the tree-sitter queries are ports of Kilo Code's implementation. It also builds on [opencode](https://github.com/anomalyco/opencode) (MIT), [LanceDB](https://github.com/lancedb/lancedb) (Apache-2.0), [web-tree-sitter](https://github.com/tree-sitter/tree-sitter) (MIT), [tree-sitter-wasms](https://github.com/Gregoor/tree-sitter-wasms) (Unlicense) and [ignore](https://github.com/kaelzhang/node-ignore) (MIT).
576
+ This plugin interoperates with the open-source [Kilo Code](https://github.com/Kilo-Org/kilocode) indexing format (MIT License, Copyright (c) 2026 Kilo Code). The line-based chunker and the tree-sitter queries are ports of Kilo Code's implementation. It also builds on [opencode](https://github.com/anomalyco/opencode) (MIT), [LanceDB](https://github.com/lancedb/lancedb) (Apache-2.0), [Apache Arrow](https://github.com/apache/arrow) (Apache-2.0), [web-tree-sitter](https://github.com/tree-sitter/tree-sitter) (MIT), [tree-sitter-wasms](https://github.com/Gregoor/tree-sitter-wasms) (Unlicense), [ignore](https://github.com/kaelzhang/node-ignore) (MIT), and — for the terminal UI — [SolidJS](https://github.com/solidjs/solid) (MIT) and [OpenTUI](https://github.com/sst/opentui) (MIT).
577
+
578
+ The companion code graph is [CodeGraph](https://github.com/colbymchenry/codegraph) (MIT), a separate project this one installs and drives rather than bundles. Its npm package is `@colbymchenry/codegraph`; the unscoped `codegraph` on npm is a different, unrelated project.
509
579
 
510
580
  Kilo Code is a trademark of its owners; this project is **unofficial and not affiliated** with Kilo Code or the OpenCode project.
511
581
 
@@ -56,10 +56,35 @@ SOFTWARE.
56
56
  | Package | License | Copyright |
57
57
  | --- | --- | --- |
58
58
  | [`@lancedb/lancedb`](https://github.com/lancedb/lancedb) | Apache-2.0 | LanceDB, Inc. |
59
+ | [`apache-arrow`](https://github.com/apache/arrow) | Apache-2.0 | The Apache Software Foundation |
59
60
  | [`web-tree-sitter`](https://github.com/tree-sitter/tree-sitter) | MIT | Tree-sitter contributors |
60
61
  | [`tree-sitter-wasms`](https://github.com/Gregoor/tree-sitter-wasms) | Unlicense | Gregor (packaging); grammars by their respective authors |
61
62
  | [`ignore`](https://github.com/kaelzhang/node-ignore) | MIT | Kael Zhang and contributors |
63
+ | [`solid-js`](https://github.com/solidjs/solid) | MIT | Ryan Carniato and contributors |
64
+ | [`@opentui/core`](https://github.com/sst/opentui) | MIT | SST and OpenTUI contributors |
65
+ | [`@opentui/solid`](https://github.com/sst/opentui) | MIT | SST and OpenTUI contributors |
66
+
67
+ `solid-js`, `@opentui/core` and `@opentui/solid` are what the terminal UI is
68
+ rendered with. They are optional dependencies: without them the prompt-footer
69
+ indicator and the settings page are absent, and everything else works.
62
70
 
63
71
  Tree-sitter grammar `.wasm` files bundled by `tree-sitter-wasms` are distributed
64
72
  under their upstream licenses (mostly MIT or Apache-2.0); see the
65
73
  `tree-sitter-wasms` package for details.
74
+
75
+ ## CodeGraph (github.com/colbymchenry/codegraph)
76
+
77
+ Not bundled and not required. CodeGraph is a separate program that this plugin
78
+ can install, register as an MCP server and keep in step; it is listed here
79
+ because the plugin drives it and points at it by name.
80
+
81
+ License: **MIT**, as declared in the package manifest
82
+ (`@colbymchenry/codegraph@1.6.2`).
83
+
84
+ The published package carries no `LICENSE` file of its own and declares no
85
+ author, so the authoritative license text is the one in the upstream repository:
86
+ <https://github.com/colbymchenry/codegraph>. It is not reproduced here rather
87
+ than reproduced with a copyright holder this project cannot verify.
88
+
89
+ The npm package is `@colbymchenry/codegraph`. The unscoped `codegraph` on npm is
90
+ an unrelated project and is not what this plugin installs.
package/index.ts CHANGED
@@ -79,6 +79,24 @@ plugin's own incremental index (Qdrant or LanceDB backend).
79
79
  - Verifying whether code exists that must be fresh: the index may lag behind
80
80
  recent edits; check \`indexing_status\` (freshness) or use Grep for certainty.
81
81
 
82
+ ## If codegraph is also installed
83
+
84
+ When the codegraph MCP server is present, its tools are available alongside this
85
+ one, and together they answer more than either can alone:
86
+
87
+ - \`codegraph_explore\` — the area around a finding, with the verbatim source of
88
+ the relevant symbols, the call paths between them, and what depends on them.
89
+ - \`codegraph_callers\` / \`codegraph_impact\` — "who calls this" and "what breaks
90
+ if I change it". Exact, structural answers that a similarity search cannot
91
+ give.
92
+ - \`indexing_search\` still goes first when the intent is known and the
93
+ identifier is not: it finds the entry node by meaning, and the graph then
94
+ explains it.
95
+
96
+ The two indexes are kept current together, so neither is stale relative to the
97
+ other. If those tools are not available, everything above still applies and
98
+ Grep remains the right fallback.
99
+
82
100
  ## Configuration
83
101
 
84
102
  \`indexing_config\` reports the effective settings (backend, model, credentials —
package/install.mjs CHANGED
@@ -330,6 +330,15 @@ function hasCodegraphBin() {
330
330
  }
331
331
  }
332
332
 
333
+ /**
334
+ * The npm package that provides the `codegraph` CLI.
335
+ *
336
+ * Scoped, and it has to be. The unscoped `codegraph` on npm is an unrelated
337
+ * 2024 library with no binary, so installing that name succeeds and leaves
338
+ * nothing on PATH — a silent no-op that looks like a working install.
339
+ */
340
+ const CODEGRAPH_PACKAGE = "@colbymchenry/codegraph"
341
+
333
342
  /**
334
343
  * Install codegraph and register its MCP with OpenCode.
335
344
  *
@@ -342,16 +351,17 @@ function installCodegraph() {
342
351
  log(" installing codegraph as a companion MCP server...")
343
352
 
344
353
  if (!hasCodegraphBin()) {
345
- log(" codegraph CLI missing; running npm install -g codegraph...")
354
+ log(` codegraph CLI missing; running npm install -g ${CODEGRAPH_PACKAGE}...`)
346
355
  const npm = isWindows ? "npm.cmd" : "npm"
347
- // --omit=... is not needed here: codegraph stands on its own, and a global
348
- // install is what its own documentation assumes.
349
- const installed = spawnSync(npm, ["install", "-g", "codegraph"], {
356
+ // Scoped on purpose. `codegraph` on the unscoped registry is an unrelated
357
+ // 2024 library with no CLI at all, and installing it would look successful
358
+ // while leaving nothing on PATH to register.
359
+ const installed = spawnSync(npm, ["install", "-g", CODEGRAPH_PACKAGE], {
350
360
  stdio: "inherit",
351
361
  shell: isWindows,
352
362
  })
353
363
  if (installed.error || installed.status !== 0) {
354
- warn(" could not install codegraph (npm install -g codegraph failed); skipping MCP registration")
364
+ warn(` could not install codegraph (npm install -g ${CODEGRAPH_PACKAGE} failed); skipping MCP registration`)
355
365
  return
356
366
  }
357
367
  }
package/install.ps1 CHANGED
@@ -2,14 +2,19 @@
2
2
  # Thin wrapper: the cross-platform logic lives in install.mjs, so Windows, Linux
3
3
  # and macOS all run the same code instead of drifting copies.
4
4
  #
5
+ # The preferred form from npm is:
6
+ # npx @javierkaiser/opencode-indexing --with-codegraph
7
+ # This wrapper forwards to the same install.mjs, so it accepts the same flags.
8
+ #
5
9
  # Usage:
6
- # powershell -ExecutionPolicy Bypass -File install.ps1 [-Local|-Package] [-Spec <spec>] [-SkipDeps] [-NoRestart]
10
+ # powershell -ExecutionPolicy Bypass -File install.ps1 [-Local|-Package] [-Spec <spec>] [-SkipDeps] [-NoRestart] [-WithCodegraph]
7
11
  param(
8
12
  [switch]$Local,
9
13
  [switch]$Package,
10
14
  [string]$Spec,
11
15
  [switch]$SkipDeps,
12
- [switch]$NoRestart
16
+ [switch]$NoRestart,
17
+ [switch]$WithCodegraph
13
18
  )
14
19
 
15
20
  $ErrorActionPreference = "Stop"
@@ -20,6 +25,7 @@ if ($Package) { $forward += "--package" }
20
25
  if ($Spec) { $forward += $Spec }
21
26
  if ($SkipDeps) { $forward += "--skip-deps" }
22
27
  if ($NoRestart) { $forward += "--no-restart" }
28
+ if ($WithCodegraph) { $forward += "--with-codegraph" }
23
29
 
24
30
  if (Get-Command node -ErrorAction SilentlyContinue) {
25
31
  & node $script @forward
package/install.sh CHANGED
@@ -2,8 +2,12 @@
2
2
  # Installs opencode-indexing into the global OpenCode config directory.
3
3
  # Thin wrapper: the cross-platform logic lives in install.mjs.
4
4
  #
5
+ # The preferred form from npm is:
6
+ # npx @javierkaiser/opencode-indexing --with-codegraph
7
+ # This wrapper forwards to the same install.mjs, so it accepts the same flags.
8
+ #
5
9
  # Usage:
6
- # sh install.sh [--local|--package] [spec] [--skip-deps] [--no-restart]
10
+ # sh install.sh [--local|--package] [spec] [--skip-deps] [--no-restart] [--with-codegraph]
7
11
  set -eu
8
12
 
9
13
  dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@javierkaiser/opencode-indexing",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "description": "OpenCode plugin: semantic code search that reuses Kilo Code indexes (Qdrant/LanceDB) and maintains its own independent index",
5
5
  "license": "MIT",
6
6
  "author": "Carlos Javier Lust <javierkaiser@gmail.com>",
@@ -0,0 +1,265 @@
1
+ import * as fs from "node:fs"
2
+ import * as path from "node:path"
3
+
4
+ import { assessScope } from "./scope.ts"
5
+
6
+ /**
7
+ * Companion integration with CodeGraph, the code-graph indexer.
8
+ *
9
+ * Two indexes, two signals: this plugin answers "where is the code that does
10
+ * something like X", codegraph answers "who calls X, what breaks if I change
11
+ * it". They are complementary, and indexing only one leaves the other stale.
12
+ *
13
+ * The decision this module makes is narrow on purpose. It never searches, never
14
+ * parses, never reads codegraph's database: it either launches `codegraph init`
15
+ * or `codegraph sync` and reports what it launched. Everything it knows about
16
+ * codegraph comes from running its CLI.
17
+ */
18
+
19
+ /** Directory codegraph writes its index into. Its presence is the "is it indexed" answer. */
20
+ const CODEGRAPH_DIR = ".codegraph"
21
+
22
+ /**
23
+ * The npm package that provides the `codegraph` CLI.
24
+ *
25
+ * Scoped, and it has to be: the unscoped `codegraph` on npm is an unrelated
26
+ * 2024 library with no binary. Installing that name exits 0 and leaves nothing
27
+ * on PATH, which is the worst shape of failure for an installer step — it looks
28
+ * like it worked.
29
+ */
30
+ const CODEGRAPH_PACKAGE = "@colbymchenry/codegraph"
31
+
32
+ /**
33
+ * Minimum seconds between launches for one workspace.
34
+ *
35
+ * codegraph is not the only writer of `.codegraph/`: it runs a daemon with a
36
+ * file watcher that auto-syncs on change. Two `sync` invocations seconds apart
37
+ * contend for the same database, so the second one is skipped rather than
38
+ * allowed to collide. Also the cheap defence against a user hammering build.
39
+ */
40
+ const MIN_LAUNCH_INTERVAL_MS = 15_000
41
+
42
+ export interface CodegraphLaunch {
43
+ /** `init` on a folder that was never indexed, `sync` otherwise. */
44
+ action: "init" | "sync"
45
+ /** Command as run, for the message the user sees. */
46
+ command: string
47
+ /** Why it ran, or why it did not. Always present. */
48
+ detail: string
49
+ }
50
+
51
+ export type CodegraphOutcome =
52
+ | { kind: "launched"; launch: CodegraphLaunch }
53
+ /** Nothing was started; `reason` is user-facing and complete on its own. */
54
+ | { kind: "skipped"; reason: string }
55
+ /** Could not start at all. Never fatal: the semantic index does not depend on this. */
56
+ | { kind: "failed"; reason: string }
57
+
58
+ /**
59
+ * The seam that keeps the test suite away from real processes.
60
+ *
61
+ * `src/` had no `child_process` use before this, so there is no precedent to
62
+ * follow. Defaulting to the real spawner and injecting in tests keeps the module
63
+ * honest without making every test shell out.
64
+ */
65
+ export interface CodegraphRunner {
66
+ /** Resolves true when the `codegraph` CLI answers. Never throws. */
67
+ available(): Promise<boolean>
68
+ /**
69
+ * Starts the command and returns immediately.
70
+ *
71
+ * Deliberately not awaited to completion: `init` on a large monorepo can run
72
+ * for minutes, and a tool call that blocks that long is worse than one that
73
+ * finishes. The caller therefore reports "started", never "succeeded".
74
+ */
75
+ launch(args: string[], cwd: string): Promise<void>
76
+ }
77
+
78
+ /** Monotonic-enough clock, injectable so tests do not have to wait 15 seconds. */
79
+ export interface CodegraphClock {
80
+ now(): number
81
+ }
82
+
83
+ const realClock: CodegraphClock = { now: () => Date.now() }
84
+
85
+ /** Last launch per workspace, so the interval gate survives across calls. */
86
+ const lastLaunch = new Map<string, number>()
87
+
88
+ /** Whether `.codegraph/` exists. Sync rather than async: it runs on every build. */
89
+ export function isCodegraphInitialized(root: string): boolean {
90
+ try {
91
+ return fs.existsSync(path.join(root, CODEGRAPH_DIR))
92
+ } catch {
93
+ return false
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Spawn options shared by both commands.
99
+ *
100
+ * `shell` is true on Windows and false elsewhere, and it is not a stylistic
101
+ * choice: npm installs its executables as `codegraph.cmd` / `codegraph.ps1`
102
+ * shims, which `spawn` cannot resolve on its own — it reports ENOENT for a
103
+ * binary that is installed and working. Verified by running both shapes.
104
+ *
105
+ * The arguments are fixed literals chosen here, never user input, so there is
106
+ * no path for quoting to matter; `shell` is what buys Windows compatibility.
107
+ */
108
+ function spawnOptions(cwd?: string): Record<string, unknown> {
109
+ return {
110
+ cwd,
111
+ stdio: "ignore",
112
+ windowsHide: true,
113
+ shell: process.platform === "win32",
114
+ }
115
+ }
116
+
117
+ /**
118
+ * The CLI runner.
119
+ *
120
+ * `version` is the cheapest command that proves both existence and runnability;
121
+ * `init --help` would do too but prints help. stdio is ignored rather than
122
+ * inherited so codegraph's output cannot corrupt the TUI, and `--yes` on init is
123
+ * what keeps it from blocking on a prompt nobody can answer.
124
+ */
125
+ export const realRunner: CodegraphRunner = {
126
+ async available() {
127
+ const { spawn } = await import("node:child_process")
128
+ return new Promise<boolean>((resolve) => {
129
+ try {
130
+ const child = spawn("codegraph", ["version"], spawnOptions())
131
+ child.once("error", () => resolve(false))
132
+ child.once("close", (code) => resolve(code === 0))
133
+ } catch {
134
+ resolve(false)
135
+ }
136
+ })
137
+ },
138
+ async launch(args, cwd) {
139
+ const { spawn } = await import("node:child_process")
140
+ await new Promise<void>((resolve) => {
141
+ let child: ReturnType<typeof spawn>
142
+ try {
143
+ child = spawn("codegraph", args, {
144
+ ...spawnOptions(cwd),
145
+ // Detached so the index keeps building after the service restarts.
146
+ // Without this a restart mid-init orphans a half-written database.
147
+ // Not on Windows: a detached shell process is not unref-able the same
148
+ // way, and the host holds the handle anyway.
149
+ detached: process.platform !== "win32",
150
+ })
151
+ // Nothing is awaited on purpose: see the interface note. `unref` only
152
+ // exists on non-Windows, where detached was requested at all.
153
+ child.unref()
154
+ } catch {
155
+ resolve()
156
+ return
157
+ }
158
+ child.once("error", () => resolve())
159
+ child.once("spawn", () => resolve())
160
+ })
161
+ },
162
+ }
163
+
164
+ export interface CodegraphOptions {
165
+ root: string
166
+ enabled: boolean
167
+ runner?: CodegraphRunner
168
+ clock?: CodegraphClock
169
+ }
170
+
171
+ /**
172
+ * Launch codegraph for a workspace, if it makes sense to.
173
+ *
174
+ * Returns what happened; the caller decides how to say it. Never throws: a
175
+ * companion indexer must not be able to fail the semantic one.
176
+ */
177
+ export async function indexWithCodegraph(options: CodegraphOptions): Promise<CodegraphOutcome> {
178
+ const { root, enabled } = options
179
+ if (!enabled) return { kind: "skipped", reason: "codegraph indexing is turned off (set codegraph=true)" }
180
+
181
+ const runner = options.runner ?? realRunner
182
+ const clock = options.clock ?? realClock
183
+
184
+ // A home directory or a drive root is reported rather than indexed. codegraph
185
+ // has `-f` for exactly this case; passing it here would index the user's whole
186
+ // machine because a tool call said so.
187
+ const scope = assessScope(root)
188
+ if (scope.level === "warn") {
189
+ return { kind: "skipped", reason: `${root} is ${scope.reason}; codegraph indexing was not started` }
190
+ }
191
+
192
+ const last = lastLaunch.get(root)
193
+ const now = clock.now()
194
+ if (last !== undefined && now - last < MIN_LAUNCH_INTERVAL_MS) {
195
+ return { kind: "skipped", reason: "codegraph was launched for this workspace moments ago" }
196
+ }
197
+
198
+ // "Not installed" arrives as either a false or a throw: spawn() reports a
199
+ // missing binary as ENOENT on the error event. Both mean the same ordinary
200
+ // thing, so both are a skip. A throw here is not a failure of this plugin —
201
+ // it is the absence of an optional companion, which is the case the whole
202
+ // design has to get right.
203
+ let available: boolean
204
+ try {
205
+ available = await runner.available()
206
+ } catch {
207
+ available = false
208
+ }
209
+ if (!available) {
210
+ return { kind: "skipped", reason: `the codegraph CLI is not installed (npm i -g ${CODEGRAPH_PACKAGE})` }
211
+ }
212
+
213
+ const initialized = isCodegraphInitialized(root)
214
+ const args = initialized ? ["sync"] : ["init", "--yes"]
215
+ const action = initialized ? "sync" : "init"
216
+
217
+ // Recorded before launching, so a failed launch still rate-limits: otherwise
218
+ // a broken CLI would be spawned again on every single build.
219
+ lastLaunch.set(root, now)
220
+
221
+ try {
222
+ await runner.launch(args, root)
223
+ } catch (error) {
224
+ return { kind: "failed", reason: `could not start \`codegraph ${args.join(" ")}\`: ${message(error)}` }
225
+ }
226
+
227
+ return {
228
+ kind: "launched",
229
+ launch: {
230
+ action,
231
+ command: `codegraph ${args.join(" ")}`,
232
+ detail: initialized
233
+ ? "syncing changes since the last index"
234
+ : "building the first index for this folder",
235
+ },
236
+ }
237
+ }
238
+
239
+ /** Test seam: forget the rate-limit state. */
240
+ export function resetCodegraphLaunchState(): void {
241
+ lastLaunch.clear()
242
+ }
243
+
244
+ /**
245
+ * One line for `indexing_status`, or "" when there is nothing worth saying.
246
+ *
247
+ * Deliberately does not run the CLI: status is called on every search via the
248
+ * footer, and spawning a process to decorate a status line is not a trade worth
249
+ * making. The directory's existence is the whole answer.
250
+ */
251
+ export function codegraphStatusLine(root: string): string {
252
+ try {
253
+ if (!fs.existsSync(root)) return ""
254
+ const initialized = isCodegraphInitialized(root)
255
+ return initialized
256
+ ? "codegraph: indexed (synced on the next indexing_build)"
257
+ : "codegraph: not indexed (initialised on the next indexing_build)"
258
+ } catch {
259
+ return ""
260
+ }
261
+ }
262
+
263
+ function message(error: unknown): string {
264
+ return error instanceof Error ? error.message : String(error)
265
+ }
package/src/config.ts CHANGED
@@ -38,6 +38,8 @@ export interface PluginOptionsInput {
38
38
  /** Master switch: when false the own index is neither written nor refreshed. */
39
39
  enabled?: boolean
40
40
  importFromKilo?: boolean
41
+ /** Also launch the codegraph CLI on build. Absent means on; see IndexingSettings. */
42
+ codegraph?: boolean
41
43
  /** Disable reading Kilo's config file entirely. */
42
44
  ignoreKiloConfig?: boolean
43
45
  homeDir?: string
@@ -283,6 +285,11 @@ export function resolveSettings(options: PluginOptionsInput): IndexingSettings {
283
285
  // but nothing is written and nothing is auto-refreshed. Toggled from the TUI
284
286
  // footer indicator or `/indexing-config set enabled false`.
285
287
  const enabled = options.enabled ?? file?.enabled !== false
288
+ // Companion indexer. On by default, and a no-op when codegraph is not
289
+ // installed, so enabling it costs nothing for someone who does not use it.
290
+ // `!== false` rather than `=== true`: the key is absent from most configs and
291
+ // absent has to mean "on" or the default would never apply.
292
+ const codegraph = options.codegraph ?? file?.codegraph !== false
286
293
 
287
294
  return {
288
295
  provider,
@@ -306,6 +313,7 @@ export function resolveSettings(options: PluginOptionsInput): IndexingSettings {
306
313
  autoRefresh,
307
314
  importFromKilo,
308
315
  enabled,
316
+ codegraph,
309
317
  credentials,
310
318
  warnings,
311
319
  }
package/src/scanner.ts CHANGED
@@ -59,8 +59,14 @@ export const DEFAULT_IGNORE_GLOBS: readonly string[] = [
59
59
 
60
60
  const DEFAULT_MAX_FILE_SIZE_BYTES = 1024 * 1024
61
61
 
62
- /** Dot-directories that are still walked because they hold indexable config. */
63
- const ALLOWED_DOT_DIRS: ReadonlySet<string> = new Set([
62
+ /**
63
+ * Dot-directories that are still walked because they hold indexable config.
64
+ *
65
+ * Exported for the test that pins `.codegraph` out of it: codegraph's SQLite
66
+ * database sits in that directory, and letting the scanner walk it would embed
67
+ * another indexer's storage into this one.
68
+ */
69
+ export const ALLOWED_DOT_DIRS: ReadonlySet<string> = new Set([
64
70
  ".github",
65
71
  ".vscode",
66
72
  ".gitlab",
@@ -265,6 +265,14 @@ const KEY_SPECS: Record<string, KeySpec> = {
265
265
  toPatch: (value) => ({ enabled: Boolean(value) }),
266
266
  display: (settings) => (settings.enabled === false ? "false" : "true"),
267
267
  },
268
+ codegraph: {
269
+ group: "behavior",
270
+ kind: "boolean",
271
+ description: "Also run the codegraph CLI on build (companion code graph; no-op when codegraph is not installed)",
272
+ toPatch: (value) => ({ codegraph: Boolean(value) }),
273
+ // `!== false`, matching how the value resolves: absent means on.
274
+ display: (settings) => (settings.codegraph === false ? "false" : "true"),
275
+ },
268
276
  searchmaxresults: {
269
277
  group: "search",
270
278
  kind: "integer",
package/src/settings.ts CHANGED
@@ -39,6 +39,8 @@ export interface SettingsFile {
39
39
  autoRefresh?: boolean
40
40
  /** Master switch: when false the own index is neither written nor refreshed. */
41
41
  enabled?: boolean
42
+ /** Also launch the codegraph CLI on build. Absent means on; a no-op if not installed. */
43
+ codegraph?: boolean
42
44
  searchMinScore?: number | null
43
45
  searchMaxResults?: number
44
46
  embeddingBatchSize?: number
package/src/tools.ts CHANGED
@@ -5,6 +5,7 @@ import { indexStatusLine } from "./index-state.ts"
5
5
  import { discoverKiloSources, importFromKilo, type KiloSourceInfo } from "./import.ts"
6
6
  import { createOwnStore, createSettingsQdrant } from "./store-factory.ts"
7
7
  import { assessScope } from "./scope.ts"
8
+ import { codegraphStatusLine, indexWithCodegraph, type CodegraphOutcome } from "./codegraph.ts"
8
9
  import type { IndexingSettings, KVStore } from "./types.ts"
9
10
  import type { Qdrant } from "./qdrant.ts"
10
11
 
@@ -72,7 +73,8 @@ Use this tool early for open-ended exploration when you know the intent but not
72
73
  Notes:
73
74
  - The index combines Kilo Code's index (when present and profile-compatible) with the plugin's own index, which can be backed by Qdrant or LanceDB.
74
75
  - If the index reports stale/incomplete, results may be incomplete — indexed files might not include recent edits.
75
- - Pass \`path\` to limit results to a subdirectory, relative to the workspace root.`
76
+ - Pass \`path\` to limit results to a subdirectory, relative to the workspace root.
77
+ - This finds code by meaning. For structure — who calls a symbol, what depends on it, what breaks if it changes — use the codegraph tools when that MCP server is available; a similarity search cannot answer those.`
76
78
 
77
79
  const STATUS_DESCRIPTION = `Report the state of the project's semantic code index.
78
80
 
@@ -81,6 +83,7 @@ Shows:
81
83
  - Kilo Code index sources available for import (Qdrant collection and/or local LanceDB database), with embedding profile and compatibility.
82
84
  - The plugin's own index (points, completion, embedding profile, last run).
83
85
  - Freshness: number of workspace files that were never indexed, possibly changed, or deleted since the last run.
86
+ - The codegraph companion's state for this folder, when that setting is on: whether it is indexed, and that the next build will sync it.
84
87
  - A recommended next action (import/build/refresh).`
85
88
 
86
89
  const REFRESH_DESCRIPTION = `Incrementally update the plugin's own code index for this workspace.
@@ -95,6 +98,11 @@ Behavior:
95
98
  - \`rebuild: true\` drops the own index first and re-imports/re-indexes everything.
96
99
  - \`skipImport: true\` forces indexing from scratch even when a Kilo index exists.
97
100
 
101
+ If the codegraph CLI is installed, it is also started for this folder in the
102
+ background (\`init\` the first time, \`sync\` afterwards) so the companion code
103
+ graph stays in step. It never blocks this tool and never fails it: without
104
+ codegraph this build is identical.
105
+
98
106
  This can take a while for large workspaces and may consume embedding API credits (except when importing).`
99
107
 
100
108
  const IMPORT_DESCRIPTION = `Import an existing Kilo Code index into the plugin's own index WITHOUT re-embedding.
@@ -103,7 +111,9 @@ Vectors and chunks are copied as-is (zero embedding API cost), which requires a
103
111
  - Kilo's Qdrant collection (\`ws-*\`).
104
112
  - Kilo's local LanceDB database for this workspace, when present.
105
113
 
106
- Use indexing_status to list detected sources. If profiles do not match, rebuild with the matching model instead.`
114
+ Use indexing_status to list detected sources. If profiles do not match, rebuild with the matching model instead.
115
+
116
+ If the codegraph CLI is installed, it is also started for this folder in the background, so the companion code graph covers the files this import brought in. Without codegraph this import is identical.`
107
117
 
108
118
  const jsonSchema = (properties: Record<string, unknown>, required: string[] = []): Record<string, unknown> => ({
109
119
  type: "object",
@@ -242,6 +252,13 @@ export function createTools(deps: ToolDeps): ToolDefinition[] {
242
252
  `Freshness: ${status.freshness.total} files scanned, ${status.freshness.added} new, ${status.freshness.maybeStale} possibly changed, ${status.freshness.deleted} deleted.`,
243
253
  )
244
254
  }
255
+ // Only when the companion indexer is actually in play: saying
256
+ // "codegraph: not indexed" to someone who never asked for codegraph is
257
+ // noise. Read from the filesystem, so this costs no process.
258
+ if (settings.codegraph !== false) {
259
+ const codegraph = codegraphStatusLine(root)
260
+ if (codegraph) lines.push(codegraph)
261
+ }
245
262
  lines.push(`Recommendation: ${status.recommendation}`)
246
263
  if (status.warnings.length > 0) lines.push(`Warnings: ${status.warnings.join("; ")}`)
247
264
  return { output: lines.join("\n"), metadata: status as unknown as Record<string, unknown> }
@@ -298,9 +315,9 @@ export function createTools(deps: ToolDeps): ToolDefinition[] {
298
315
  { mode: "build", rebuild: input.rebuild === true, skipImport: input.skipImport === true },
299
316
  )
300
317
  const summary = formatReport("build", report)
301
- const warning = scopeWarning(root)
318
+ const lines = [scopeWarning(root), summary, await codegraphLine(root, settings)].filter(Boolean)
302
319
  return {
303
- output: warning ? `${warning}\n${summary}` : summary,
320
+ output: lines.join("\n"),
304
321
  metadata: report as unknown as Record<string, unknown>,
305
322
  }
306
323
  },
@@ -368,14 +385,38 @@ export function createTools(deps: ToolDeps): ToolDefinition[] {
368
385
  const lines = [
369
386
  `Imported ${report.imported} chunks from ${report.source.kind} ${report.source.name} in ${(report.durationMs / 1000).toFixed(1)}s.`,
370
387
  `Batches: ${report.batches}, skipped: ${report.skipped}. Embedding API calls: 0.`,
388
+ await codegraphLine(root, settings),
371
389
  ]
372
390
  if (report.errors.length > 0) lines.push(`Errors: ${report.errors.join("; ")}`)
373
- return { output: lines.join("\n"), metadata: report as unknown as Record<string, unknown> }
391
+ return { output: lines.filter(Boolean).join("\n"), metadata: report as unknown as Record<string, unknown> }
374
392
  },
375
393
  },
376
394
  ]
377
395
  }
378
396
 
397
+ /**
398
+ * One line about the companion indexer, for the write tools.
399
+ *
400
+ * Never throws and never reports success: the launch is detached, so by the time
401
+ * this returns the CLI has only started. Anything that went wrong afterwards
402
+ * lands in `.codegraph/daemon.log`, which is where the message points.
403
+ */
404
+ async function codegraphLine(root: string, settings: IndexingSettings): Promise<string> {
405
+ let outcome: CodegraphOutcome
406
+ try {
407
+ outcome = await indexWithCodegraph({ root, enabled: settings.codegraph !== false })
408
+ } catch (error) {
409
+ return `Codegraph: not indexed (${error instanceof Error ? error.message : String(error)})`
410
+ }
411
+ if (outcome.kind === "launched") {
412
+ return `Codegraph: \`${outcome.launch.command}\` started in the background — ${outcome.launch.detail}.`
413
+ }
414
+ // A skip while the CLI is absent is the common case and not worth a line on
415
+ // every build; the other two say something the user may want to act on.
416
+ if (outcome.reason.startsWith("the codegraph CLI is not installed")) return ""
417
+ return `Codegraph: not indexed (${outcome.reason})`
418
+ }
419
+
379
420
  function makeProgress(context: { progress?: (m: unknown) => Promise<void> }): (update: {
380
421
  phase: string
381
422
  processed: number
package/src/types.ts CHANGED
@@ -96,6 +96,16 @@ export interface IndexingSettings extends EmbeddingProfile {
96
96
  importFromKilo: boolean
97
97
  /** Master switch: when false nothing is written to nor refreshed in the own index. */
98
98
  enabled: boolean
99
+ /**
100
+ * Also launch the codegraph CLI when the workspace is indexed.
101
+ *
102
+ * Optional so that every fixture, every caller and every consumer that builds
103
+ * settings by hand keeps compiling: a companion that must be threaded through
104
+ * eight test doubles to exist is a dependency, not a companion. Callers read
105
+ * it as `settings.codegraph !== false`, which treats absent as "try it" —
106
+ * and "try it" is a no-op when the CLI is not installed.
107
+ */
108
+ codegraph?: boolean
99
109
  credentials: ProviderCredentials
100
110
  /** Warnings produced while resolving configuration. */
101
111
  warnings: string[]