karajan-code 2.33.0 → 3.0.0

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.
Files changed (53) hide show
  1. package/README.md +136 -36
  2. package/bin/kj-rag-mcp.js +75 -0
  3. package/docs/README.es.md +4 -4
  4. package/package.json +22 -16
  5. package/packages/hu-board/package.json +5 -2
  6. package/scripts/git-hooks/post-merge +7 -0
  7. package/src/audit/basal-cost.js +17 -12
  8. package/src/audit/deterministic-summary.js +20 -0
  9. package/src/audit/injection-findings.js +105 -0
  10. package/src/checks/hardware.js +150 -0
  11. package/src/checks/node.js +9 -10
  12. package/src/cli/register-meta.js +57 -1
  13. package/src/cli/register-pipeline.js +1 -0
  14. package/src/commands/doctor.js +2 -0
  15. package/src/commands/init.js +41 -1
  16. package/src/commands/rag-mcp.js +18 -0
  17. package/src/commands/rag.js +82 -8
  18. package/src/commands/run.js +8 -0
  19. package/src/commands/telemetry.js +70 -0
  20. package/src/config/defaults.js +11 -1
  21. package/src/lang/chunk-go.js +94 -0
  22. package/src/lang/chunk-java.js +90 -0
  23. package/src/lang/chunk-python.js +88 -0
  24. package/src/lang/chunk-rust.js +102 -0
  25. package/src/lang/go.js +14 -0
  26. package/src/lang/java.js +16 -0
  27. package/src/lang/js.js +8 -0
  28. package/src/lang/python.js +14 -0
  29. package/src/lang/registry.js +79 -0
  30. package/src/lang/rust.js +14 -0
  31. package/src/lang/tree-sitter-loader.js +49 -0
  32. package/src/onboarder/collectors/framework-multilang.js +64 -0
  33. package/src/onboarder/collectors/index.js +18 -1
  34. package/src/orchestrator/stages/coder-stage.js +3 -0
  35. package/src/orchestrator/stages/reviewer-stage.js +11 -0
  36. package/src/rag/auto-update.js +50 -0
  37. package/src/rag/chunker.js +7 -3
  38. package/src/rag/eval.js +61 -0
  39. package/src/rag/indexer.js +81 -10
  40. package/src/rag/retriever.js +57 -5
  41. package/src/rag/vec-store.js +69 -3
  42. package/src/rag/watcher.js +6 -4
  43. package/src/roles/audit-role.js +9 -1
  44. package/src/sonar/scanner.js +51 -16
  45. package/src/utils/manifest-sniff.js +82 -0
  46. package/src/utils/quota-simulator.js +56 -0
  47. package/src/utils/telemetry.js +34 -12
  48. package/templates/roles/onboarder.md +10 -1
  49. package/vendor/tree-sitter-grammars/README.md +21 -0
  50. package/vendor/tree-sitter-grammars/go.wasm +0 -0
  51. package/vendor/tree-sitter-grammars/java.wasm +0 -0
  52. package/vendor/tree-sitter-grammars/python.wasm +0 -0
  53. package/vendor/tree-sitter-grammars/rust.wasm +0 -0
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  <a href="https://www.npmjs.com/package/karajan-code"><img src="https://img.shields.io/npm/dw/karajan-code.svg" alt="npm downloads"></a>
14
14
  <a href="https://github.com/manufosela/karajan-code/actions"><img src="https://github.com/manufosela/karajan-code/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
15
15
  <a href="https://www.gnu.org/licenses/agpl-3.0"><img src="https://img.shields.io/badge/license-AGPL--3.0-blue.svg" alt="License"></a>
16
- <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node.js"></a>
16
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg" alt="Node.js"></a>
17
17
  <a href="https://github.com/manufosela/homebrew-tap"><img src="https://img.shields.io/badge/homebrew-tap-orange.svg" alt="Homebrew"></a>
18
18
  </p>
19
19
 
@@ -23,6 +23,8 @@
23
23
 
24
24
  ---
25
25
 
26
+ > **⚠ v3.0.0 — BREAKING:** Node 22+ required. Karajan v3 drops Node 20 (EOL 2026-04-30) and aligns with the Active LTS line. Three deps forced the bump: `lint-staged 17`, `commander 15`, and `better-sqlite3 12.10+` (Node 20 prebuilds gone). Migration: `nvm install 22 && nvm use 22`. No public API changes if you were already on Node 22. Full notes in [CHANGELOG.md](CHANGELOG.md).
27
+
26
28
  You describe what you want to build. Karajan orchestrates multiple AI agents to plan it, implement it, test it, review it with SonarQube, and iterate. No babysitting required.
27
29
 
28
30
  ## What is Karajan?
@@ -60,7 +62,6 @@ While Genkit, Mastra, LangChain and Vercel AI SDK call `/v1/messages`, Karajan o
60
62
  | Calls provider HTTP API (`/v1/messages`, etc.) | ❌ Delegates to CLIs | ✅ |
61
63
  | Orchestrates existing AI CLIs (claude, codex, gemini, aider, opencode) as subprocesses | ✅ | ❌ |
62
64
  | Depends on cloud infrastructure | ❌ Fully local | ⚠️ Varies |
63
- | Vanilla JS (no TypeScript required) | ✅ | ⚠️ TS-first |
64
65
  | Token billing | **Uses your existing CLI subscriptions** | Pay per API call |
65
66
 
66
67
  Two technical facts worth keeping straight:
@@ -105,28 +106,79 @@ docker run --rm -v $(pwd):/workspace karajan-code kj --version
105
106
  ```
106
107
 
107
108
  **Python**:
109
+
110
+ The Python wrapper is a thin shim over the `kj` binary, useful when your stack lives in `pyproject.toml` / `requirements.txt` and you want to invoke Karajan from a venv.
111
+
108
112
  ```bash
109
- cd wrappers/python && pip install .
113
+ # Option A — once published to PyPI (recommended)
114
+ pip install karajan-code
115
+
116
+ # Option B — install from the cloned repo (dev)
117
+ git clone https://github.com/manufosela/karajan-code.git
118
+ cd karajan-code/wrappers/python
119
+ pip install .
110
120
  ```
111
121
 
122
+ Both options expect the `kj` binary to be on your `PATH` (via one of the methods above) — the Python wrapper does not vendor it.
123
+
112
124
  That's it. `kj init` auto-detects your installed agents and installs RTK for token optimization.
113
125
 
114
126
  ### Optional scanners for `kj audit` + `kj webperf`
115
127
 
116
- Karajan auto-skips any scanner that isn't installed. Add the ones that match your projects:
128
+ None of these are required. Karajan auto-skips any scanner that isn't installed, so the pipeline runs fine with zero of them. Add the ones that match your projects to get more signal:
117
129
 
118
- | Tool | Install | What you get |
119
- |------|---------|--------------|
120
- | **SonarQube** | `docker compose -f ~/sonarqube/docker-compose.yml up -d` | Code quality + security rules with line-precision in `kj audit` |
121
- | **OSV-Scanner** | `go install github.com/google/osv-scanner@latest` | Dependency CVE coverage broader than `npm audit` |
122
- | **Semgrep** | `pipx install semgrep` | SAST: XSS, SQLi, taint flow, secrets — equivalent to `snyk code`, free for OSS |
123
- | **Lighthouse** | `npm install -g lighthouse` | Core Web Vitals + opportunities for `kj webperf` (auto-feeds `kj audit`) |
130
+ | Tool | Scope | Install | Installable from `kj init`? | What you get |
131
+ |------|-------|---------|-----------------------------|--------------|
132
+ | **SonarQube** | Any stack | `docker compose -f ~/sonarqube/docker-compose.yml up -d` | ✅ Yes (wizard configures Docker container + token) | Code quality + security rules with line-precision in `kj audit` |
133
+ | **OSV-Scanner** | Any stack | `go install github.com/google/osv-scanner@latest` | ❌ No (install manually) | Dependency CVE coverage broader than `npm audit` |
134
+ | **Semgrep** | Any stack | `pipx install semgrep` | ❌ No (install manually) | SAST: XSS, SQLi, taint flow, secrets — equivalent to `snyk code`, free for OSS |
135
+ | **Lighthouse** | **Frontend only** | `npm install -g lighthouse` | ❌ No (install manually) | Core Web Vitals + opportunities for `kj webperf` (auto-feeds `kj audit`) |
124
136
 
125
137
  Skip any per-run with `--no-sonar`, `--no-osv`, `--no-semgrep`. See [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md#optional-scanners--kj-audit--kj-webperf) for full table.
126
138
 
127
- ## Three ways to use Karajan
139
+ ## Footprint & hardware requirements
140
+
141
+ Karajan ships as **layers you opt into**. The base CLI is tiny (~5 MB). Heavier pieces — Ollama for local RAG, SonarQube for static analysis, the `qmd` model cache for global semantic search — are only pulled if you ask for them. None of them are pulled during `npm install`.
142
+
143
+ ### What lives where
144
+
145
+ | Layer | Size | When you pay it | Notes |
146
+ | --- | --- | --- | --- |
147
+ | `karajan-code` npm tarball | 5.2 MB / 555 files | Always | The CLI itself |
148
+ | `node_modules/` (global) | ~208 MB | After `npm install -g karajan-code` | Standard for any Node global; SEA binary skips it |
149
+ | `~/.karajan/` (state) | ~40 MB typical | After first `kj run` | Sessions, plans, audit history, HU board DB. `kj clean` prunes it |
150
+ | Ollama Docker image | 6.55 GB | If you use the **local** RAG embedder (default since v2.26) | Auto-pulled in the background on first `kj rag index` (~5 min) |
151
+ | Ollama embedding model | ~260 MB | First `kj rag index` | `nomic-embed-text` by default; lives inside the Ollama container |
152
+ | SonarQube Docker image | 1.47 GB | If you enable Sonar in `kj init` | Optional; `kj audit` runs fine without it |
153
+ | `qmd` model cache | ~2.2 GB | If you use `qmd query` for global semantic search | Local LLMs for query expansion + rerank; entirely off-box otherwise |
154
+
155
+ ### Three install profiles
156
+
157
+ | Profile | Disk | What's enabled | Trade-off |
158
+ | --- | --- | --- | --- |
159
+ | **Minimum** | ~250 MB | `kj`, audits, reviews, RAG with **cloud embedder** (OpenAI / Voyage / Cohere / Mistral) | Needs an API key; no offline RAG |
160
+ | **Recommended** | ~8.5 GB | + Ollama (local RAG, no API key) + SonarQube | Offline-capable; needs Docker running |
161
+ | **Full house** | ~11 GB | + `qmd` (semantic search across personal docs/memory) | All features local; biggest footprint |
162
+
163
+ ### Hardware
164
+
165
+ | Profile | CPU | RAM | Free disk | Notes |
166
+ | --- | --- | --- | --- | --- |
167
+ | **Minimum** | 2 cores | 4 GB | 1 GB | Cloud embedder only; pipeline peaks ~1 GB RAM |
168
+ | **Recommended** | 4–8 cores | 8–12 GB | 10–15 GB | Docker + Ollama running in background |
169
+ | **Ideal** | 16+ cores | 32+ GB | 50+ GB | Multiple Docker stacks, large repos, big audit history |
170
+
171
+ ### Operational notes
172
+
173
+ - **No GPU required.** Embeddings are CPU-only by default; cloud embedders push the compute off-box entirely.
174
+ - **Pipeline RAM peak**: 1–2 GB per `kj run` (one orchestrator + one coder + one reviewer subprocess at a time).
175
+ - **SQLite is in-process.** No separate DB daemon. `~/.karajan/` is a folder, not a service.
176
+ - **First-run Ollama pull**: ~5 min in the background; you can keep working — `kj` falls back to a cloud embedder if Ollama isn't ready yet.
177
+ - **Runtime**: Node.js ≥ 22.22.1 (Active LTS). v3.0.0 dropped Node 20 support. Use `kj doctor` to verify your environment.
178
+
179
+ ## Two ways to use Karajan
128
180
 
129
- Karajan installs **three commands**: `kj`, `kj-tail`, and `karajan-mcp`.
181
+ Karajan installs **three commands**: `kj` (the CLI), `karajan-mcp` (the MCP server) and `kj-tail` (a monitoring companion). There are two *ways to use it* — CLI or MCP — and `kj-tail` is the monitor you keep open in a second terminal while either mode is running.
130
182
 
131
183
  ### 1. CLI: direct from terminal
132
184
 
@@ -164,13 +216,13 @@ This is the primary use case. Karajan runs as an MCP server inside Claude Code,
164
216
  You → Claude Code → kj_run (via MCP) → triage → coder → sonar → reviewer → tester → security
165
217
  ```
166
218
 
167
- The MCP server auto-registers during `npm install`. Your AI agent sees 23 tools (`kj_run`, `kj_code`, `kj_review`, etc.) and uses them as needed.
219
+ The MCP server auto-registers during `npm install`. Your AI agent sees 27 tools (`kj_run`, `kj_code`, `kj_review`, etc.) and uses them as needed.
168
220
 
169
221
  **The problem**: when Karajan runs inside an AI agent, you lose visibility. The agent shows you the final result, but not the pipeline stages, iterations, or Solomon decisions happening in real time.
170
222
 
171
- ### 3. kj-tail: monitor from a separate terminal
223
+ ### Companion: `kj-tail` (monitor from a separate terminal)
172
224
 
173
- **This is the companion tool.** Open a second terminal in the **same project directory** where your AI agent is working, and run:
225
+ `kj-tail` is **not a third way to run Karajan** — it's a read-only monitor for whichever of the two ways above you're using. Open a second terminal in the **same project directory** where the pipeline is running, and run:
174
226
 
175
227
  ```bash
176
228
  kj-tail
@@ -221,7 +273,13 @@ iteration: coder → refactorer? → guard(output) → guard(perf) → sonar?
221
273
  post-loop: tester? → security? → perf? → impeccable? → audit?
222
274
  ```
223
275
 
224
- **24 stages** across three phases: 18 AI-agent-backed roles (table below), 6 deterministic stages without an LLM call (`intent`, `skills`, `acceptance`, `guard(output)`, `guard(perf)`, `tdd`). Two extra classes (`commiter`, `repairer`) are post-approval / internal helpers, not standalone pipeline stages. Each AI role is executed by the agent you choose:
276
+ **24 stages max** across three phases: 18 AI-agent-backed roles (table below), 6 deterministic stages without an LLM call (`intent`, `skills`, `acceptance`, `guard(output)`, `guard(perf)`, `tdd`). Two extra classes (`commiter`, `repairer`) are post-approval / internal helpers, not standalone pipeline stages.
277
+
278
+ > **Karajan is multi-language.** The 24 figure is the upper bound for a frontend project. On a **backend / library / CLI** task, three stages are skipped automatically because they have nothing to assess: `impeccable` (UI/UX audit), `perf` (Core Web Vitals via Lighthouse) and `guard(perf)` (frontend anti-pattern check). That leaves a typical pipeline at **~21 stages** for backend / systems / CLI / data work, with the same triage rules trimming further on trivial tasks. Stage selection is auto-detected from the repo (presence of frontend frameworks, `index.html`, `package.json` browser fields, etc.) — no flag required.
279
+ >
280
+ > The **minimum useful pipeline** on a `trivial` task is roughly: `intent → triage → coder → guard(output) → tdd → reviewer → brain` (~7 stages). Triage decides.
281
+
282
+ Each AI role is executed by the agent you choose:
225
283
 
226
284
  | Role | What it does | Default |
227
285
  |------|-------------|---------|
@@ -263,7 +321,7 @@ post-loop: tester? → security? → perf? → impeccable? → audit?
263
321
 
264
322
  Mix and match. Use Claude as coder and Codex as reviewer. Karajan auto-detects installed agents during `kj init`.
265
323
 
266
- ## MCP server (23 tools)
324
+ ## MCP server (27 tools)
267
325
 
268
326
  After `npm install -g karajan-code`, the MCP server auto-registers in Claude and Codex. Manual config if needed:
269
327
 
@@ -275,7 +333,7 @@ After `npm install -g karajan-code`, the MCP server auto-registers in Claude and
275
333
  # command = "karajan-mcp"
276
334
  ```
277
335
 
278
- **24 tools** available: `kj_run`, `kj_code`, `kj_review`, `kj_plan`, `kj_board`, `kj_audit`, `kj_scan`, `kj_doctor`, `kj_config`, `kj_report`, `kj_resume`, `kj_roles`, `kj_agents`, `kj_preflight`, `kj_status`, `kj_init`, `kj_discover`, `kj_triage`, `kj_researcher`, `kj_architect`, `kj_impeccable`, `kj_hu`, `kj_skills`, `kj_suggest`.
336
+ **27 tools** available: `kj_run`, `kj_code`, `kj_review`, `kj_plan`, `kj_board`, `kj_audit`, `kj_scan`, `kj_doctor`, `kj_config`, `kj_report`, `kj_resume`, `kj_roles`, `kj_agents`, `kj_preflight`, `kj_status`, `kj_init`, `kj_discover`, `kj_triage`, `kj_researcher`, `kj_architect`, `kj_hu`, `kj_skills`, `kj_suggest`, `kj_undo`, `kj_clean`, `kj_rag_query`, `kj_rag_index`.
279
337
 
280
338
  Use `kj-tail` in a separate terminal to see what the pipeline is doing in real time (see [Three ways to use Karajan](#three-ways-to-use-karajan)).
281
339
 
@@ -308,20 +366,20 @@ Karajan auto-detects and auto-configures everything it can:
308
366
 
309
367
  No per-project configuration required. If you want to customize, config is layered: session > project > global.
310
368
 
311
- ## Why vanilla JavaScript?
369
+ ## Recommended companions
312
370
 
313
- Not nostalgia, not stubbornness. I've been using JavaScript since 1997, when Brendan Eich created it in a week and changed the lives of everyone building for the web. I know its guts, its bugs, its quirks. And I know that whoever truly understands JS turns those bugs into features. TypeScript exists so that developers used to strongly-typed languages don't panic when they see JS. I respect that. But I don't need it. Tests are my type safety. JSDoc and a good IDE are my intellisense. And not having a compiler between the code and me is what lets me ship 57 releases in 45 days without fear.
371
+ None of these are required. Karajan runs fine on its own. They're tools that, when present, Karajan can take advantage of — or that help *you* work better around Karajan.
314
372
 
315
- [Why vanilla JavaScript: the long version](docs/why-vanilla-js.md)
373
+ | Tool | Invoked by | Why |
374
+ |------|-----------|-----|
375
+ | [**RTK**](https://github.com/rtk-ai/rtk) | Karajan (auto, on Bash outputs) | Reduces token consumption by 60-90% on Bash command outputs |
376
+ | [**QMD**](https://github.com/manufosela/qmd) | You (CLI / MCP), complementary to RAG | Semantic search engine over Markdown corpora — works alongside `kj rag query` when you want a richer index over your own docs |
377
+ | [**GitHub MCP**](https://github.com/github/github-mcp-server) | Your AI agent (via MCP) | Create PRs, manage issues directly from the agent |
378
+ | [**Chrome DevTools MCP**](https://github.com/ChromeDevTools/chrome-devtools-mcp) | Your AI agent (via MCP) | Verify UI changes visually after frontend modifications |
316
379
 
317
- ## Recommended companions
380
+ ## Why vanilla JavaScript?
318
381
 
319
- | Tool | Why |
320
- |------|-----|
321
- | [**RTK**](https://github.com/rtk-ai/rtk) | Reduces token consumption by 60-90% on Bash command outputs |
322
- | [**Planning Game MCP**](https://github.com/manufosela/planning-game-xp-mcp) | Agile project management (tasks, sprints, estimation), XP-native |
323
- | [**GitHub MCP**](https://github.com/github/github-mcp-server) | Create PRs, manage issues directly from the agent |
324
- | [**Chrome DevTools MCP**](https://github.com/ChromeDevTools/chrome-devtools-mcp) | Verify UI changes visually after frontend modifications |
382
+ Tests are the type safety; JSDoc + a good IDE are the intellisense; no compiler in the loop is what makes shipping fast. The long version is anecdotal and lives in [docs/why-vanilla-js.md](docs/why-vanilla-js.md).
325
383
 
326
384
  ## Contributing
327
385
 
@@ -329,22 +387,63 @@ Not nostalgia, not stubbornness. I've been using JavaScript since 1997, when Bre
329
387
  git clone https://github.com/manufosela/karajan-code.git
330
388
  cd karajan-code
331
389
  npm install
332
- npm test # Run ~2599 tests with Vitest
390
+ npm test # Run ~5 368 tests across 482 files with Vitest
333
391
  npm run validate # Lint + test
334
392
  ```
335
393
 
336
394
  Issues and pull requests welcome. If something doesn't work as documented, [open an issue](https://github.com/manufosela/karajan-code/issues). That's the most useful contribution at this stage.
337
395
 
338
- ## Telemetry
396
+ ## Privacy & telemetry
397
+
398
+ **Telemetry is OFF by default.** The `kj init` wizard asks once, in your OS
399
+ language, with a plain-text "yes / no" prompt:
339
400
 
340
- Karajan collects anonymous usage statistics to improve the tool:
341
- version, OS, command used, pipeline duration and success rate.
342
- No code, task descriptions, or personal data is ever sent.
401
+ > *Help improve Karajan by sending anonymous telemetry to the project?*
402
+ > *(version, OS, commands, pipeline duration — no code, no tasks, no personal data)*
343
403
 
344
- Opt out: set `telemetry: false` in `~/.karajan/kj.config.yml`
404
+ Answer "yes" once and `telemetry: true` is persisted to `~/.karajan/kj.config.yml`.
405
+ Anything else — undefined, missing key, `false`, fresh install with no wizard run —
406
+ keeps it off. There is no hidden default-on path.
407
+
408
+ ### What gets sent (when enabled)
409
+
410
+ Three event types, anonymous, no userID / email / IP collected by Karajan itself:
411
+
412
+ | Event | When | Payload |
413
+ |---|---|---|
414
+ | `install` | first `kj init` | `version`, `os`, `node`, `ts` |
415
+ | `cli_command` | each `kj <subcommand>` | `version`, `os`, `node`, `ts`, `command` |
416
+ | `pipeline_complete` | end of `kj run` | `version`, `os`, `node`, `ts`, `mode`, `agent`, `duration_s`, `success`, `taskType` |
417
+
418
+ Endpoint: `https://karajan-code.web.app/api/telemetry` (POST, 3 s timeout, fire-and-forget).
419
+ Implementation: [`src/utils/telemetry.js`](src/utils/telemetry.js).
420
+
421
+ ### Flip it later
422
+
423
+ Open `~/.karajan/kj.config.yml`, set `telemetry: true` (opt in) or `telemetry: false` (opt out),
424
+ save. Or re-run `kj init` and answer the prompt again. Set `KJ_DEBUG=1` to see telemetry
425
+ errors on stderr (otherwise failures are silent — telemetry never blocks the pipeline).
345
426
 
346
427
  ## Recent releases
347
428
 
429
+ > **v2.34.0 released** — Minor. Two epics in one window. **KJC-PCS-0052 "Multi-language RAG"** — Python, Rust, Go and Java join JS/TS as first-class citizens of the local RAG index (AST chunkers via `web-tree-sitter` + multi-lang watcher + audit/onboarder multi-stack manifest sniffer), so a polyglot repo gets the same semantic retrieval surface as a single-language one. **KJC-PCS-0053 "RAG Quality & Observability"** — `kj rag eval` ships a frozen golden-query harness with `recall@k + MRR` (CI-gated via `--min-recall`), content-hash dedup skips redundant embeddings, optional MMR diversification returns *different* chunks instead of five near-duplicates, and `docs/RAG.md` gains a six-questions deep-dive documenting the system end-to-end. `kj rag index --since <ref>` + a post-merge git hook keep the index in sync with `git pull` / merge without manual intervention.
430
+ >
431
+ > - **`vec_store_meta` + `kj rag index --since`** (PR #882, KJC-TSK-0455 PR1): per-project last-indexed-commit stamp; `--since auto` resolves it; explicit refs honoured; missing baseline falls back to a full index with a friendly warning. Idempotent.
432
+ > - **Post-merge hook + pre-run drift check** (PR #883, KJC-TSK-0455 PR2): new `kj rag install-hooks` writes `.git/hooks/post-merge` so `git pull` auto-triggers a delta re-index. Pre-run drift check emits a one-line hint when `HEAD` is ahead of the last indexed SHA by more than N files.
433
+ > - **Language registry + Python canary** (PR #884, KJC-TSK-0474): new `src/lang/registry.js` with `adapterForPath(file)`; the indexer + watcher both route through it. Python lands as the first non-JS adapter to exercise the registry shape from day one.
434
+ > - **Python AST chunker** (PR #886, KJC-TSK-0478) and **Rust AST chunker** (PR #888, KJC-TSK-0479): web-tree-sitter walkers + regex fallbacks, grammars vendored under `vendor/tree-sitter-grammars/*.wasm` so SEA binaries stay self-contained.
435
+ > - **Indexer prepares Python / Rust grammars** (PR #889, KJC-TSK-0480): `prepareAdapters()` awaits the grammar loads before walking so the first file doesn't pay the cold-load tax mid-batch.
436
+ > - **Go AST chunker** (PR #890, KJC-TSK-0481): tree-sitter walker + regex fallback. Method receivers rendered explicitly so retrieval surfaces method-vs-function disambiguation without re-parsing.
437
+ > - **Multi-stack manifest sniffer for `basal-cost`** (PR #891, KJC-TSK-0476): `kj audit` now reads Python (`pyproject.toml`, `requirements*.txt`), Rust (`Cargo.toml`), Go (`go.mod`) and Java (`pom.xml`, `build.gradle*`) manifests alongside `package.json`. Cost estimates no longer assume JS/TS.
438
+ > - **Multi-stack frameworks in audit bundle** (PR #892, KJC-TSK-0477): the audit bundle JSON gains a `stacks` field listing detected language stacks + framework hints (Django, Flask, Actix, Tokio, Gin, Echo, Spring, Quarkus...). Onboarder + audit role render polyglot reality.
439
+ > - **Java AST chunker** (PR #893, KJC-TSK-0486): two-level walker (class / interface / enum / record → method / constructor) + regex fallback. Inner classes flattened with `Outer$Inner` symbols.
440
+ > - **`docs/RAG.md` six-questions deep-dive** (PR #894, KJC-TSK-0485): per-language chunker table, six-provider embedder matrix, hybrid + rerank + metadata filter, update strategy table, validation tiers. Excluded from the shrink-budget gate.
441
+ > - **Watcher derived from language registry** (PR #895, KJC-TSK-0482): `kj watch` matcher no longer hard-codes JS/TS. Python / Rust / Go / Java sources trigger live re-index.
442
+ > - **Content-hash skip-on-match** (PR #896, KJC-TSK-0484 PR-A): each chunk gains a `sha256` column; identical bodies skip the Ollama call. Re-indexing identical files now costs zero embeddings.
443
+ > - **MMR diversification in retriever** (PR #898, KJC-TSK-0484 PR-B): optional `rag.search.mmr` over the top-N candidates; lambda (`rag.search.mmrLambda`, default 0.5) trades pure cosine relevance for diversity. Off by default.
444
+ > - **Retrieval-quality harness** (PR #899, KJC-TSK-0483 PR-A): pure `runEval(queries, runQuery, { topK, ks })` scoring recall@k (binary) + MRR. `tests/rag/golden-queries.json` ships 20 entries covering the public surface of `src/rag/`.
445
+ > - **`kj rag eval` CLI + baseline docs** (PR #900, KJC-TSK-0483 PR-B): subcommand wires the harness to the indexed corpus, emits aggregate + per-query report (human or `--json`), and sets `process.exitCode = 1` when `--min-recall <n>` is set and aggregate falls below — drop-in CI gate after `kj rag index`.
446
+
348
447
  > **v2.33.0 released** — Minor. **AI Harness Scorecard golden metric (KJC-PCS-0051, Plan B)** — every `kj audit` now boots a Docker one-shot of `addyosmani/ai-harness-scorecard`, gets a deterministic 0–100 score + A–F grade, persists it to a per-project `audit-history.db`, and on the next run renders the delta vs the previous baseline plus an optional Unicode-bar trend sparkline. One golden number for "how AI-friendly is this repo today vs last week," zero LLM tokens spent.
349
448
  >
350
449
  > - **Harness Docker one-shot** (PR #877, KJC-TSK-0470): `src/audit/harness-runner.js` runs `docker run --rm -v <repo>:/repo addyosmani/ai-harness-scorecard analyze`, parses the verdict, auto-skips on missing Docker. `--no-harness` opts out.
@@ -515,7 +614,7 @@ Opt out: set `telemetry: false` in `~/.karajan/kj.config.yml`
515
614
  >
516
615
  > KJC-BUG-0058 (PR #798, reported by [@aitormf](https://github.com/aitormf)): a session that stopped during Sonar would re-run the full pre-loop on `kj resume <id>` — HU-reviewer, intent, discover, triage, domainCurator, **researcher, architect, planner** all from scratch — doubling token cost and breaking the value-prop of the command. Root cause: `resumeFlow` (flow-runner.js:280) called `runFlow` without rehydrating stage state, and the session never persisted stage outputs in the first place. **Fix**: two new mutators in `src/session/mutators.js` (`setStageResult` mirrors into `stage_results[name]` + `stages_completed[]`; `setStageBundle` adds `stage_bundles[name]` for cross-stage context like `researchContext`, `architectContext`, `plannedTask`). Two closures inside `runPreLoopStages` (`persistStage` + `resumeSkip`) wrap every cacheable stage. `init-context.js` rehydrates `ctx.stageResults` from the loaded session before invoking the pre-loop. Triage is *not* skipped on resume — it produces `roleOverrides` the Brain decisor depends on and is cheap to re-run.
517
616
  >
518
- > KJC-BUG-0060 (PR #797, reported by mjfosela during the v2.19.3 release): `git checkout main` reported `[ahead 27]` of origin/main. Every commit was titled `initial commit`, authored by `manufosela@gmail.com`, and had the **exact same tree as its parent** — completely empty. The reflog held **2 495 such SHAs** accumulated since April 2026. None ever reached origin/main (gh push / CI would have rejected them) so runtime impact was zero, but the local history was noisy and on every release it looked like a sync loss. Root cause: `src/orchestrator/config-init.js::autoInit()` guarded with `!(await exists(projectDir/.git))`, which fails two ways: (a) dogfooding kj on karajan-code itself from a subdir → `exists()` returns false → `git init` re-initializes the *parent's* `.git/` (idempotent, harmless) → `git commit --allow-empty` then resolves upward and lands an empty commit on the parent's main; (b) transient FS hicks (EACCES/ENOENT) flip `exists()` to a false negative. **Fix**: switch the static FS probe for `git rev-parse --is-inside-work-tree`, which performs the same upward search git would use for the commit itself — guard cannot disagree with the operation it guards. And drop the `git commit --allow-empty -m "initial commit"` step entirely: no downstream stage needs a root commit; the 2 495 zombies never broke anything, the seed was decorative and turned out to be the actual user-visible symptom.
617
+ > KJC-BUG-0060 (PR #797, reported by @manufosela during the v2.19.3 release): `git checkout main` reported `[ahead 27]` of origin/main. Every commit was titled `initial commit`, authored by the local git identity, and had the **exact same tree as its parent** — completely empty. The reflog held **2 495 such SHAs** accumulated since April 2026. None ever reached origin/main (gh push / CI would have rejected them) so runtime impact was zero, but the local history was noisy and on every release it looked like a sync loss. Root cause: `src/orchestrator/config-init.js::autoInit()` guarded with `!(await exists(projectDir/.git))`, which fails two ways: (a) dogfooding kj on karajan-code itself from a subdir → `exists()` returns false → `git init` re-initializes the *parent's* `.git/` (idempotent, harmless) → `git commit --allow-empty` then resolves upward and lands an empty commit on the parent's main; (b) transient FS hicks (EACCES/ENOENT) flip `exists()` to a false negative. **Fix**: switch the static FS probe for `git rev-parse --is-inside-work-tree`, which performs the same upward search git would use for the commit itself — guard cannot disagree with the operation it guards. And drop the `git commit --allow-empty -m "initial commit"` step entirely: no downstream stage needs a root commit; the 2 495 zombies never broke anything, the seed was decorative and turned out to be the actual user-visible symptom.
519
618
  >
520
619
  > **If your `kj resume` re-runs researcher/architect, or your `git status` shows mysterious `[ahead N]` after dogfooding kj on a kj-linked source tree, upgrade to v2.19.4.**
521
620
  >
@@ -575,5 +674,6 @@ Built by [@manufosela](https://github.com/manufosela). Head of Engineering at Ge
575
674
 
576
675
  ### Contributors
577
676
 
578
- - [@aitormf](https://github.com/aitormf) — OpenCode agent (5th built-in agent)
677
+ - [@aitormf](https://github.com/aitormf) — OpenCode agent (5th built-in agent), early-bug reporter on resume/standby flow
678
+ - [@jorgecasar](https://github.com/jorgecasar) — Model registry, display refactor, valibot config proposals; multiple issue triages
579
679
  - [@reiaguilera](https://github.com/reiaguilera) — Beta testing, feature proposals, and quality feedback
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * kj-rag-mcp — standalone MCP server exposing only RAG tools.
4
+ *
5
+ * For external IDEs (Cursor, Windsurf, Continue, Zed) that want semantic
6
+ * search over a Karajan-indexed project without pulling in the full 27-tool
7
+ * orchestrator surface. Re-uses handleRagQuery / handleRagIndex from the
8
+ * main server, so behavior stays in sync with `karajan-mcp`.
9
+ */
10
+ import path from "node:path";
11
+ import { readFileSync } from "node:fs";
12
+ import { fileURLToPath } from "node:url";
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
16
+ import { handleRagQuery, handleRagIndex } from "../src/mcp/handlers/rag-handler.js";
17
+ import { responseText, enrichedFailPayload } from "../src/mcp/shared-helpers.js";
18
+ import { setupOrphanGuard } from "../src/mcp/orphan-guard.js";
19
+
20
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
21
+ const PKG_PATH = path.resolve(MODULE_DIR, "../package.json");
22
+ const VERSION = JSON.parse(readFileSync(PKG_PATH, "utf8")).version;
23
+
24
+ const RAG_TOOLS = [
25
+ {
26
+ name: "kj_rag_query",
27
+ description: "Semantic search across the indexed Karajan plans, onboarding briefs and (optionally) project source code. Returns the top-K nearest chunks with metadata so the agent can quote prior decisions or locate where a concept was last implemented. Empty until `kj_rag_index` has run at least once.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ text: { type: "string", description: "Natural-language query." },
32
+ topK: { type: "number", description: "Max hits to return. Default 5." },
33
+ scope: { type: "string", description: "'plans' | 'code' | 'onboarding' | 'all' (default)." },
34
+ projectDir: { type: "string", description: "Absolute path to the project directory." }
35
+ },
36
+ required: ["text"]
37
+ }
38
+ },
39
+ {
40
+ name: "kj_rag_index",
41
+ description: "(Re)index this project's Karajan-managed assets into the local vector store. Idempotent. Pass `withSources: true` to also index project source files. Required as a one-time bootstrap before `kj_rag_query` returns anything.",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ withSources: { type: "boolean", description: "Also index source files under projectDir. Default false." },
46
+ projectDir: { type: "string", description: "Absolute path to the project directory." }
47
+ }
48
+ }
49
+ }
50
+ ];
51
+
52
+ const mcpServer = new McpServer(
53
+ { name: "kj-rag-mcp", version: VERSION },
54
+ { capabilities: { tools: {}, logging: {} } }
55
+ );
56
+ const server = mcpServer.server;
57
+
58
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: RAG_TOOLS }));
59
+
60
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
61
+ const name = request.params?.name;
62
+ const args = request.params?.arguments || {};
63
+ try {
64
+ if (name === "kj_rag_query") return responseText(await handleRagQuery(args, server));
65
+ if (name === "kj_rag_index") return responseText(await handleRagIndex(args, server));
66
+ return responseText({ ok: false, error: `Unknown tool: ${name}` });
67
+ } catch (error) {
68
+ return responseText(enrichedFailPayload(error, name));
69
+ }
70
+ });
71
+
72
+ setupOrphanGuard();
73
+
74
+ const transport = new StdioServerTransport();
75
+ await mcpServer.connect(transport);
package/docs/README.es.md CHANGED
@@ -111,7 +111,7 @@ Este es el caso de uso principal. Karajan corre como servidor MCP dentro de Clau
111
111
  Tu → Claude Code → kj_run (via MCP) → triage → coder → sonar → reviewer → tester → security
112
112
  ```
113
113
 
114
- El servidor MCP se auto-registra durante `npm install`. Tu agente de IA ve 23 herramientas (`kj_run`, `kj_code`, `kj_review`, `kj_hu`, etc.) y las usa segun necesite.
114
+ El servidor MCP se auto-registra durante `npm install`. Tu agente de IA ve 27 herramientas (`kj_run`, `kj_code`, `kj_review`, `kj_hu`, etc.) y las usa segun necesite.
115
115
 
116
116
  **El problema**: cuando Karajan corre dentro de un agente de IA, pierdes visibilidad. El agente te muestra el resultado final, pero no las etapas del pipeline, iteraciones o decisiones de Solomon en tiempo real.
117
117
 
@@ -188,7 +188,7 @@ post-loop: tester? → security? → perf? → impeccable? → audit?
188
188
 
189
189
  Mezcla y combina. Usa Claude como coder y Codex como reviewer. Karajan auto-detecta agentes instalados durante `kj init`.
190
190
 
191
- ## Servidor MCP (23 herramientas)
191
+ ## Servidor MCP (27 herramientas)
192
192
 
193
193
  Tras `npm install -g karajan-code`, el servidor MCP se auto-registra en Claude y Codex. Config manual si es necesario:
194
194
 
@@ -200,7 +200,7 @@ Tras `npm install -g karajan-code`, el servidor MCP se auto-registra en Claude y
200
200
  # command = "karajan-mcp"
201
201
  ```
202
202
 
203
- **23 herramientas** disponibles: `kj_run`, `kj_code`, `kj_review`, `kj_plan`, `kj_audit`, `kj_scan`, `kj_doctor`, `kj_config`, `kj_report`, `kj_resume`, `kj_roles`, `kj_agents`, `kj_preflight`, `kj_status`, `kj_init`, `kj_discover`, `kj_triage`, `kj_researcher`, `kj_architect`, `kj_impeccable`, `kj_hu`, `kj_skills`, `kj_suggest`.
203
+ **27 herramientas** disponibles: `kj_run`, `kj_code`, `kj_review`, `kj_plan`, `kj_board`, `kj_audit`, `kj_scan`, `kj_doctor`, `kj_config`, `kj_report`, `kj_resume`, `kj_roles`, `kj_agents`, `kj_preflight`, `kj_status`, `kj_init`, `kj_discover`, `kj_triage`, `kj_researcher`, `kj_architect`, `kj_hu`, `kj_skills`, `kj_suggest`, `kj_undo`, `kj_clean`, `kj_rag_query`, `kj_rag_index`.
204
204
 
205
205
  Usa `kj-tail` en un terminal separado para ver lo que el pipeline esta haciendo en tiempo real (ver [Tres formas de usar Karajan](#tres-formas-de-usar-karajan)).
206
206
 
@@ -254,7 +254,7 @@ No es nostalgia ni cabezoneria. Es que llevo usando JavaScript desde 1997, cuand
254
254
  git clone https://github.com/manufosela/karajan-code.git
255
255
  cd karajan-code
256
256
  npm install
257
- npm test # Ejecutar ~2599 tests con Vitest
257
+ npm test # Ejecutar ~5 368 tests en 482 ficheros con Vitest
258
258
  npm run validate # Lint + test
259
259
  ```
260
260
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "2.33.0",
3
+ "version": "3.0.0",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -24,7 +24,7 @@
24
24
  "cli"
25
25
  ],
26
26
  "engines": {
27
- "node": ">=20.19.0"
27
+ "node": ">=22.22.1"
28
28
  },
29
29
  "imports": {
30
30
  "#utils/*": "./src/utils/*",
@@ -37,6 +37,7 @@
37
37
  "bin/",
38
38
  "templates/",
39
39
  "scripts/",
40
+ "vendor/tree-sitter-grammars/",
40
41
  "packages/hu-board/src/",
41
42
  "packages/hu-board/public/",
42
43
  "packages/hu-board/package.json",
@@ -48,7 +49,8 @@
48
49
  "bin": {
49
50
  "kj": "bin/kj.js",
50
51
  "kj-tail": "bin/kj-tail",
51
- "karajan-mcp": "bin/karajan-mcp.js"
52
+ "karajan-mcp": "bin/karajan-mcp.js",
53
+ "kj-rag-mcp": "bin/kj-rag-mcp.js"
52
54
  },
53
55
  "scripts": {
54
56
  "postinstall": "node scripts/postinstall.js",
@@ -71,34 +73,38 @@
71
73
  "lint-staged": {
72
74
  "src/**/*.js": "eslint --max-warnings=999"
73
75
  },
76
+ "overrides": {
77
+ "fast-uri": "^3.1.2"
78
+ },
74
79
  "dependencies": {
75
- "@babel/parser": "^7.29.3",
76
- "@modelcontextprotocol/sdk": "^1.26.0",
77
- "better-sqlite3": "^11.0.0",
78
- "chokidar": "^4.0.0",
79
- "commander": "^14.0.0",
80
+ "@babel/parser": "^7.29.7",
81
+ "@modelcontextprotocol/sdk": "^1.29.0",
82
+ "better-sqlite3": "^12.10.0",
83
+ "chokidar": "^5.0.0",
84
+ "commander": "^15.0.0",
80
85
  "execa": "^9.6.0",
81
86
  "express": "^5.1.0",
82
87
  "express-rate-limit": "^8.5.0",
83
88
  "helmet": "^8.1.0",
84
- "js-yaml": "^4.1.0",
85
- "knip": "^6.14.1",
89
+ "js-yaml": "^4.2.0",
90
+ "knip": "^6.15.0",
86
91
  "madge": "^8.0.0",
87
92
  "sqlite-vec": "^0.1.9",
88
- "valibot": "^1.3.1"
93
+ "valibot": "^1.4.1",
94
+ "web-tree-sitter": "^0.26.9"
89
95
  },
90
96
  "devDependencies": {
91
97
  "@eslint/js": "^10.0.1",
92
- "@vitest/coverage-v8": "^4.0.18",
98
+ "@vitest/coverage-v8": "^4.1.8",
93
99
  "esbuild": "^0.28.0",
94
- "eslint": "^10.2.1",
100
+ "eslint": "^10.4.1",
95
101
  "eslint-plugin-import-x": "^4.16.2",
96
102
  "eslint-plugin-security": "^4.0.0",
97
- "globals": "^17.5.0",
98
- "lint-staged": "^16.4.0",
103
+ "globals": "^17.6.0",
104
+ "lint-staged": "^17.0.7",
99
105
  "postject": "^1.0.0-alpha.6",
100
106
  "prettier": "^3.4.2",
101
107
  "simple-git-hooks": "^2.13.1",
102
- "vitest": "^4.0.0"
108
+ "vitest": "^4.1.8"
103
109
  }
104
110
  }
@@ -4,14 +4,17 @@
4
4
  "description": "HU Story Board dashboard for Karajan Code",
5
5
  "type": "module",
6
6
  "private": true,
7
+ "engines": {
8
+ "node": ">=22.22.1"
9
+ },
7
10
  "scripts": {
8
11
  "start": "node src/server.js",
9
12
  "dev": "node --watch src/server.js",
10
13
  "test": "vitest run"
11
14
  },
12
15
  "dependencies": {
13
- "better-sqlite3": "^11.0.0",
14
- "chokidar": "^4.0.0",
16
+ "better-sqlite3": "^12.10.0",
17
+ "chokidar": "^5.0.0",
15
18
  "express": "^5.1.0",
16
19
  "express-rate-limit": "^8.5.0",
17
20
  "helmet": "^8.1.0"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # KJC-TSK-0455 — Auto-refresh the local RAG index after a merge so retrieval
3
+ # never serves stale code. Installed by `kj rag install-hooks`. The marker
4
+ # in this comment is how the installer tells kj-managed hooks from user hooks.
5
+ set -euo pipefail
6
+ if ! command -v kj >/dev/null 2>&1; then exit 0; fi
7
+ kj rag index --since auto >/dev/null 2>&1 || true
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
5
5
  import { getKarajanHome } from "../utils/paths.js";
6
+ import { sniffManifests, summariseStack } from "../utils/manifest-sniff.js";
6
7
 
7
8
  const execFileAsync = promisify(execFile);
8
9
 
@@ -59,16 +60,16 @@ async function countLines(filePath) {
59
60
  }
60
61
  }
61
62
 
62
- async function countDependencies(projectDir) {
63
- const pkgPath = path.join(projectDir, "package.json");
64
- try {
65
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
66
- const deps = Object.keys(pkg.dependencies || {});
67
- const devDeps = Object.keys(pkg.devDependencies || {});
68
- return { dependencies: deps.length, devDependencies: devDeps.length, total: deps.length + devDeps.length };
69
- } catch {
70
- return { dependencies: 0, devDependencies: 0, total: 0 };
63
+ function countDependenciesFromStack(stack) {
64
+ // Suma deps + devDeps de TODOS los manifests detectados (multi-stack).
65
+ // Si no hay manifests, devuelve ceros — coherente con repos sin paquete.
66
+ let deps = 0;
67
+ let devDeps = 0;
68
+ for (const m of stack.manifests) {
69
+ deps += m.deps;
70
+ devDeps += m.devDeps;
71
71
  }
72
+ return { dependencies: deps, devDependencies: devDeps, total: deps + devDeps };
72
73
  }
73
74
 
74
75
  function runDepcheck(projectDir) {
@@ -231,8 +232,11 @@ export async function measureBasalCost(projectDir) {
231
232
  totalLines += await countLines(f);
232
233
  }
233
234
 
234
- const depInfo = await countDependencies(projectDir);
235
- const depcheck = await runDepcheck(projectDir);
235
+ const stack = summariseStack(await sniffManifests(projectDir));
236
+ const depInfo = countDependenciesFromStack(stack);
237
+ // depcheck solo aplica a Node; en stacks puros no-Node lo saltamos.
238
+ const hasNode = stack.manifests.some((m) => m.stack === "node");
239
+ const depcheck = hasNode ? await runDepcheck(projectDir) : { unused: [], note: "depcheck skipped (no package.json)" };
236
240
  const deadExports = await findDeadExports(sourceFiles);
237
241
 
238
242
  return {
@@ -240,7 +244,8 @@ export async function measureBasalCost(projectDir) {
240
244
  totalFiles: sourceFiles.length,
241
245
  dependencies: depInfo,
242
246
  unusedDependencies: depcheck,
243
- deadExports
247
+ deadExports,
248
+ stack
244
249
  };
245
250
  }
246
251
 
@@ -14,6 +14,7 @@ import { groupVulnerabilitiesBySeverity } from "./osv-findings.js";
14
14
  import { groupFindingsBySeverity as groupSemgrepBySeverity } from "./semgrep-findings.js";
15
15
  import { groupCyclesBySeverity } from "./circular-deps.js";
16
16
  import { groupDeadExportsBySeverity } from "./dead-exports.js";
17
+ import { groupInjectionBySeverity } from "./injection-findings.js";
17
18
 
18
19
  const MAX_SAMPLE_DEAD_EXPORTS = 10;
19
20
  const MAX_SAMPLE_SONAR_PER_SEVERITY = 5;
@@ -21,6 +22,7 @@ const MAX_SAMPLE_OSV_PER_SEVERITY = 5;
21
22
  const MAX_SAMPLE_SEMGREP_PER_SEVERITY = 5;
22
23
  const MAX_SAMPLE_CYCLES_PER_SEVERITY = 5;
23
24
  const MAX_SAMPLE_KNIP_PER_SEVERITY = 5;
25
+ const MAX_SAMPLE_INJECTION_PER_SEVERITY = 5;
24
26
 
25
27
  /**
26
28
  * @param {{basalCost?: object, growthDelta?: object, stack?: object, sonarFindings?: object, webperf?: object, osvFindings?: object}} ctx
@@ -38,11 +40,29 @@ export function formatDeterministicSummary(ctx) {
38
40
  if (ctx.semgrepFindings) lines.push(...formatSemgrepBlock(ctx.semgrepFindings));
39
41
  if (ctx.circularDeps) lines.push(...formatCircularDepsBlock(ctx.circularDeps));
40
42
  if (ctx.deadExports) lines.push(...formatDeadExportsBlock(ctx.deadExports));
43
+ if (ctx.injectionFindings) lines.push(...formatInjectionBlock(ctx.injectionFindings));
41
44
  if (ctx.webperf) lines.push(...formatWebperfBlock(ctx.webperf));
42
45
 
43
46
  return lines.join("\n");
44
47
  }
45
48
 
49
+ function formatInjectionBlock(inj) {
50
+ if (!inj.available) return ["### Prompt-injection scan", `- Status: not available — ${inj.reason || "scan failed"}`, ""];
51
+ const lines = ["### Prompt-injection scan (specs / domain / onboarding)", `- Files scanned: ${inj.scanned ?? 0}`, `- Findings: ${inj.total ?? 0}`];
52
+ if ((inj.total ?? 0) > 0) {
53
+ for (const [severity, items] of Object.entries(groupInjectionBySeverity(inj.findings || []))) {
54
+ if (items.length === 0) continue;
55
+ lines.push(` - ${severity} (${items.length}):`);
56
+ for (const f of items.slice(0, MAX_SAMPLE_INJECTION_PER_SEVERITY)) {
57
+ lines.push(` - ${f.file || "(unknown)"}${f.line ? `:${f.line}` : ""} [${f.type}] ${f.pattern}`);
58
+ }
59
+ if (items.length > MAX_SAMPLE_INJECTION_PER_SEVERITY) lines.push(` - ... and ${items.length - MAX_SAMPLE_INJECTION_PER_SEVERITY} more in ${severity}`);
60
+ }
61
+ }
62
+ lines.push("");
63
+ return lines;
64
+ }
65
+
46
66
  function formatCircularDepsBlock(circularDeps) {
47
67
  if (!circularDeps.available) {
48
68
  return ["### Circular Dependencies (architecture)", `- Status: not available — ${circularDeps.reason || "madge not available"}`, ""];