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.
- package/README.md +136 -36
- package/bin/kj-rag-mcp.js +75 -0
- package/docs/README.es.md +4 -4
- package/package.json +22 -16
- package/packages/hu-board/package.json +5 -2
- package/scripts/git-hooks/post-merge +7 -0
- package/src/audit/basal-cost.js +17 -12
- package/src/audit/deterministic-summary.js +20 -0
- package/src/audit/injection-findings.js +105 -0
- package/src/checks/hardware.js +150 -0
- package/src/checks/node.js +9 -10
- package/src/cli/register-meta.js +57 -1
- package/src/cli/register-pipeline.js +1 -0
- package/src/commands/doctor.js +2 -0
- package/src/commands/init.js +41 -1
- package/src/commands/rag-mcp.js +18 -0
- package/src/commands/rag.js +82 -8
- package/src/commands/run.js +8 -0
- package/src/commands/telemetry.js +70 -0
- package/src/config/defaults.js +11 -1
- package/src/lang/chunk-go.js +94 -0
- package/src/lang/chunk-java.js +90 -0
- package/src/lang/chunk-python.js +88 -0
- package/src/lang/chunk-rust.js +102 -0
- package/src/lang/go.js +14 -0
- package/src/lang/java.js +16 -0
- package/src/lang/js.js +8 -0
- package/src/lang/python.js +14 -0
- package/src/lang/registry.js +79 -0
- package/src/lang/rust.js +14 -0
- package/src/lang/tree-sitter-loader.js +49 -0
- package/src/onboarder/collectors/framework-multilang.js +64 -0
- package/src/onboarder/collectors/index.js +18 -1
- package/src/orchestrator/stages/coder-stage.js +3 -0
- package/src/orchestrator/stages/reviewer-stage.js +11 -0
- package/src/rag/auto-update.js +50 -0
- package/src/rag/chunker.js +7 -3
- package/src/rag/eval.js +61 -0
- package/src/rag/indexer.js +81 -10
- package/src/rag/retriever.js +57 -5
- package/src/rag/vec-store.js +69 -3
- package/src/rag/watcher.js +6 -4
- package/src/roles/audit-role.js +9 -1
- package/src/sonar/scanner.js +51 -16
- package/src/utils/manifest-sniff.js +82 -0
- package/src/utils/quota-simulator.js +56 -0
- package/src/utils/telemetry.js +34 -12
- package/templates/roles/onboarder.md +10 -1
- package/vendor/tree-sitter-grammars/README.md +21 -0
- package/vendor/tree-sitter-grammars/go.wasm +0 -0
- package/vendor/tree-sitter-grammars/java.wasm +0 -0
- package/vendor/tree-sitter-grammars/python.wasm +0 -0
- 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%
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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
|
-
###
|
|
223
|
+
### Companion: `kj-tail` (monitor from a separate terminal)
|
|
172
224
|
|
|
173
|
-
|
|
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.
|
|
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 (
|
|
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
|
-
**
|
|
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
|
-
##
|
|
369
|
+
## Recommended companions
|
|
312
370
|
|
|
313
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
380
|
+
## Why vanilla JavaScript?
|
|
318
381
|
|
|
319
|
-
|
|
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 ~
|
|
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
|
-
##
|
|
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
|
|
341
|
-
version, OS,
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
**
|
|
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 ~
|
|
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": "
|
|
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": ">=
|
|
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.
|
|
76
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
77
|
-
"better-sqlite3": "^
|
|
78
|
-
"chokidar": "^
|
|
79
|
-
"commander": "^
|
|
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.
|
|
85
|
-
"knip": "^6.
|
|
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.
|
|
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.
|
|
98
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
93
99
|
"esbuild": "^0.28.0",
|
|
94
|
-
"eslint": "^10.
|
|
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.
|
|
98
|
-
"lint-staged": "^
|
|
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.
|
|
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": "^
|
|
14
|
-
"chokidar": "^
|
|
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
|
package/src/audit/basal-cost.js
CHANGED
|
@@ -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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
235
|
-
const
|
|
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"}`, ""];
|