karajan-code 2.23.0 → 2.25.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 +33 -0
- package/package.json +1 -1
- package/src/commands/init.js +1 -1
- package/src/orchestrator/drivers/pre-loop.js +30 -0
- package/src/orchestrator/stages/rag-context-stage.js +60 -0
- package/src/orchestrator/stages/rag-preload-decisor.js +58 -0
- package/templates/skills/kj-rag-query.md +50 -0
package/README.md
CHANGED
|
@@ -345,6 +345,39 @@ Opt out: set `telemetry: false` in `~/.karajan/kj.config.yml`
|
|
|
345
345
|
|
|
346
346
|
## Recent releases
|
|
347
347
|
|
|
348
|
+
> **v2.25.0 released** — Minor. **RAG Camino B + Camino D** (KJC-PCS-0049). Closes the consumer-surface plan. Skills hosts can now invoke RAG via `/kj-rag-query` without MCP, and the pre-loop retrieval stage from v2.24.0 only fires when triage signals make it worthwhile.
|
|
349
|
+
>
|
|
350
|
+
> Camino B — `/kj-rag-query <text> [--scope <s>] [--top-k <n>]` slash command shipped by `kj init` to `.claude/commands/`. Thin wrapper over `kj rag query`; passes flags through, surfaces `empty:true` as a one-line hint, renders hits as background context rather than raw JSON. For Claude Code / Cursor instances loaded without MCP.
|
|
351
|
+
>
|
|
352
|
+
> Camino D — Brain decisor heuristic in `src/orchestrator/stages/rag-preload-decisor.js`. New `config.rag.preload.policy`: `always` (v2.24.0 behaviour, kept for back-compat), `never` (benchmarking), `auto` (default). In `auto` mode, retrieval fires when triage decomposes, level ∈ {complex, high, epic}, task body ≥ 200 chars, or `config.rag.preload.brownfield` is set. Otherwise the stage persists `{ skipped: true, reason: 'auto:low-value' }` and the pipeline pays no retrieval cost on trivial tasks.
|
|
353
|
+
>
|
|
354
|
+
> **Coming in v2.26.0+**: chokidar watcher for live re-indexing, AST source chunker (tree-sitter / `@babel/parser`), BM25 + cosine hybrid scoring.
|
|
355
|
+
>
|
|
356
|
+
> **v2.24.0 released** — Minor. **RAG Camino C — pre-loop auto-retrieval** (KJC-PCS-0049). After v2.23.0 taught the agents that `kj_rag_query` exists, Karajan now injects prior context for them automatically: a new pre-loop stage queries the vec store with the task description and prepends the top-K chunks to the task before any LLM call.
|
|
357
|
+
>
|
|
358
|
+
> Opt-in: `config.rag.preload.enabled = false` by default. Five guards before the retrieval fires (`disabled`, `no-task`, empty corpus, no hits, error). The stage never throws — best-effort enrichment, opt-out by default, opt-back-out on failure. When all guards pass, the task receives an extra block:
|
|
359
|
+
>
|
|
360
|
+
> ```markdown
|
|
361
|
+
> ## Prior context from RAG (auto-retrieved, top N)
|
|
362
|
+
>
|
|
363
|
+
> ### [plan · AUTH-1] /path/to/plan.json
|
|
364
|
+
>
|
|
365
|
+
> <chunk text, truncated 600 chars>
|
|
366
|
+
> ```
|
|
367
|
+
>
|
|
368
|
+
> Because `task` flows through `runPlanningPhases` to researcher / architect / planner via parameter passing, **one mutation feeds six consumers** — no per-stage prompt wrapper, no fan-out through `pipelineFlags`. Triage runs first because its `roleOverrides` gate the rest of the pipeline; the RAG stage runs right after, before `domainCurator`.
|
|
369
|
+
>
|
|
370
|
+
> Workflow:
|
|
371
|
+
>
|
|
372
|
+
> ```bash
|
|
373
|
+
> kj onboard
|
|
374
|
+
> kj rag index
|
|
375
|
+
> yq -i '.rag.preload.enabled = true' ~/.karajan/kj.config.yml
|
|
376
|
+
> kj run task.md # researcher/architect/planner/coder all see prior context
|
|
377
|
+
> ```
|
|
378
|
+
>
|
|
379
|
+
> **Coming in v2.25.0+**: Camino B (`/kj-rag-query` slash command for Skills hosts), Camino D (Brain decisor heuristic deciding **when** to pre-fetch based on task complexity — refinement on top of C), chokidar watcher for live re-indexing, AST source chunker, BM25 + cosine hybrid scoring.
|
|
380
|
+
>
|
|
348
381
|
> **v2.23.0 released** — Minor. **RAG exposed to agents and humans alike** (KJC-PCS-0049, Steps 7+8+Camino A). After v2.22.0's CLI MVP, the corpus is now reachable from three more places.
|
|
349
382
|
>
|
|
350
383
|
> 1. **MCP tools** `kj_rag_query` + `kj_rag_index` (PR #815). Any MCP-connected agent — Claude Desktop, Cursor, Claude Code, Karajan's own roles — can call them. Tool count 25 → 27. Empty store responds `empty: true` so agents have a deterministic recovery signal.
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -544,7 +544,7 @@ async function installSkills(logger, interactive) {
|
|
|
544
544
|
|
|
545
545
|
if (installed > 0) {
|
|
546
546
|
logger.info(`Installed ${installed} Karajan skill(s) in .claude/commands/`);
|
|
547
|
-
logger.info("Available as slash commands: /kj-run, /kj-code, /kj-review, /kj-test, /kj-security, /kj-discover, /kj-architect, /kj-sonar");
|
|
547
|
+
logger.info("Available as slash commands: /kj-run, /kj-code, /kj-review, /kj-test, /kj-security, /kj-discover, /kj-architect, /kj-sonar, /kj-rag-query");
|
|
548
548
|
} else {
|
|
549
549
|
logger.info("All skills already installed.");
|
|
550
550
|
}
|
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
runResearcherStage, runArchitectStage, runPlannerStage,
|
|
41
41
|
runDiscoverStage, runHuReviewerStage,
|
|
42
42
|
} from "../pre-loop-stages.js";
|
|
43
|
+
import { runRagContextStage } from "../stages/rag-context-stage.js";
|
|
44
|
+
import { shouldPreloadRag } from "../stages/rag-preload-decisor.js";
|
|
43
45
|
// TSK-0336: triage goes through the StageRegistry / StageExecutor contract
|
|
44
46
|
// so canRun / execute / onFailure are actually exercised in production.
|
|
45
47
|
// The registry is a singleton with TriageStage / CoderStage / ReviewerStage
|
|
@@ -123,6 +125,34 @@ export async function runPreLoopStages({ config, logger, emitter, eventBase, ses
|
|
|
123
125
|
applyTriageOverrides(pipelineFlags, triageResult.roleOverrides);
|
|
124
126
|
await persistStage("triage", triageResult.stageResult);
|
|
125
127
|
|
|
128
|
+
// --- RAG preload (KJC-PCS-0049 Camino C, opt-in) ---
|
|
129
|
+
// Runs only when config.rag.preload.enabled === true. Queries the local
|
|
130
|
+
// vector store with the task description, prepends the top-K chunks to
|
|
131
|
+
// the task as a markdown block so researcher/architect/planner/coder
|
|
132
|
+
// all see prior context without having to call kj_rag_query themselves.
|
|
133
|
+
// Empty store / network / etc. degrade to no-op via the stage itself.
|
|
134
|
+
if (!resumeSkip("ragPreload")) {
|
|
135
|
+
// KJC-TSK-0434 (Camino D): Brain decisor heuristic — skip retrieval
|
|
136
|
+
// when triage + task shape say it would be low-value. Defaults to
|
|
137
|
+
// policy=auto; `always`/`never` are escape hatches.
|
|
138
|
+
const decision = shouldPreloadRag({ triage: triageResult.stageResult, task, config });
|
|
139
|
+
if (!decision.pull) {
|
|
140
|
+
await persistStage("ragPreload", { skipped: true, reason: decision.reason });
|
|
141
|
+
} else {
|
|
142
|
+
const ragRes = await runRagContextStage({ config, logger, emitter, eventBase, task });
|
|
143
|
+
if (!ragRes.skipped && ragRes.contextBlock) {
|
|
144
|
+
task = `${task}\n\n${ragRes.contextBlock}`;
|
|
145
|
+
await persistStage("ragPreload", {
|
|
146
|
+
hits: ragRes.hits.length,
|
|
147
|
+
scope: config?.rag?.preload?.scope || "all",
|
|
148
|
+
decisor: decision.reason,
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
await persistStage("ragPreload", { skipped: true, reason: ragRes.reason, decisor: decision.reason });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
126
156
|
// --- Brain decisor (opt-in, intent-driven routing) ---
|
|
127
157
|
//
|
|
128
158
|
// When enabled, translates triage output + task text + CLI overrides into a
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// KJC-PCS-0049 Camino C — pre-loop RAG retrieval stage.
|
|
2
|
+
// Runs between triage and domainCurator. When `config.rag.preload.enabled`
|
|
3
|
+
// is true and the corpus has chunks, queries the retriever with the
|
|
4
|
+
// task text and returns a markdown block ready to prepend to the task.
|
|
5
|
+
// Failures degrade gracefully (warn + continue); the rest of the pipeline
|
|
6
|
+
// never sees an exception from this stage.
|
|
7
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
8
|
+
import { openVecStore, countChunks } from "../../rag/vec-store.js";
|
|
9
|
+
import { OllamaEmbedder } from "../../rag/embedder.js";
|
|
10
|
+
import { query } from "../../rag/retriever.js";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_TOP_K = 5;
|
|
13
|
+
const DEFAULT_SCOPE = "all";
|
|
14
|
+
const MAX_CHUNK_CHARS = 600;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs the RAG preload stage. Returns:
|
|
18
|
+
* { skipped: true, reason } — when disabled / empty / error
|
|
19
|
+
* { skipped: false, contextBlock, hits } — markdown block + raw hits
|
|
20
|
+
*
|
|
21
|
+
* The caller decides whether to mutate `task` with the contextBlock. We
|
|
22
|
+
* keep that responsibility at the call site so this module stays pure.
|
|
23
|
+
*/
|
|
24
|
+
export async function runRagContextStage({ config, logger, emitter, eventBase, task }) {
|
|
25
|
+
const preload = config?.rag?.preload;
|
|
26
|
+
if (!preload?.enabled) return { skipped: true, reason: "disabled" };
|
|
27
|
+
if (typeof task !== "string" || task.length === 0) return { skipped: true, reason: "no-task" };
|
|
28
|
+
try {
|
|
29
|
+
const dim = config?.rag?.embedder?.dim || 768;
|
|
30
|
+
const db = openVecStore({ dim });
|
|
31
|
+
try {
|
|
32
|
+
if (countChunks(db) === 0) {
|
|
33
|
+
logger?.info?.("[rag-preload] corpus is empty, skipping (run `kj rag index` to seed)");
|
|
34
|
+
return { skipped: true, reason: "empty" };
|
|
35
|
+
}
|
|
36
|
+
const topK = preload.topK || DEFAULT_TOP_K;
|
|
37
|
+
const scope = preload.scope || DEFAULT_SCOPE;
|
|
38
|
+
const cfg = config?.rag?.embedder || {};
|
|
39
|
+
const embedder = new OllamaEmbedder({ url: cfg.url, model: cfg.model, dim });
|
|
40
|
+
const hits = await query(db, embedder, task, { topK, scope });
|
|
41
|
+
if (hits.length === 0) return { skipped: true, reason: "no-hits" };
|
|
42
|
+
const lines = hits.map((h) => {
|
|
43
|
+
const label = h.metadata?.hu_id || h.metadata?.symbol || h.metadata?.headingPath?.join(" > ") || "block";
|
|
44
|
+
const text = h.text.length > MAX_CHUNK_CHARS ? `${h.text.slice(0, MAX_CHUNK_CHARS)}…` : h.text;
|
|
45
|
+
return `### [${h.kind} · ${label}] ${h.source}\n\n${text}`;
|
|
46
|
+
});
|
|
47
|
+
const contextBlock = `## Prior context from RAG (auto-retrieved, top ${hits.length})\n\n${lines.join("\n\n")}`;
|
|
48
|
+
emitProgress(emitter, makeEvent("rag-preload:hits", { ...eventBase, stage: "rag-preload" }, {
|
|
49
|
+
message: `RAG preload: injected ${hits.length} chunk(s) into the task context`,
|
|
50
|
+
detail: { topK, scope, hits: hits.length },
|
|
51
|
+
}));
|
|
52
|
+
return { skipped: false, contextBlock, hits };
|
|
53
|
+
} finally {
|
|
54
|
+
db.close();
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
logger?.warn?.(`[rag-preload] failed (non-blocking): ${err.message}`);
|
|
58
|
+
return { skipped: true, reason: "error", error: err.message };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// KJC-TSK-0434 — RAG Camino D: Brain decisor heuristic for retrieval.
|
|
2
|
+
// Decides WHETHER to pay the cost of running the RAG preload stage based
|
|
3
|
+
// on signals from triage + task shape, instead of pre-fetching every run.
|
|
4
|
+
//
|
|
5
|
+
// Returns `{ pull: boolean, reason: string }`. The pre-loop driver
|
|
6
|
+
// skips `runRagContextStage` when `pull === false`.
|
|
7
|
+
//
|
|
8
|
+
// Policies (config.rag.preload.policy):
|
|
9
|
+
// - "always" : pull regardless (back-compat with v2.24.0 behaviour)
|
|
10
|
+
// - "never" : never pull (useful for benchmarking, debugging)
|
|
11
|
+
// - "auto" : heuristic — pull only when triage signals complexity,
|
|
12
|
+
// decomposition, or when the task body is long enough
|
|
13
|
+
// to suggest non-trivial scope. Default.
|
|
14
|
+
|
|
15
|
+
const DEFAULT_POLICY = "auto";
|
|
16
|
+
const COMPLEX_LEVELS = new Set(["complex", "high", "epic"]);
|
|
17
|
+
const LONG_TASK_CHARS = 200;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} params
|
|
21
|
+
* @param {object} [params.triage] - triage stage result (may be null)
|
|
22
|
+
* @param {string} [params.task] - the user task text
|
|
23
|
+
* @param {object} [params.config] - full karajan config
|
|
24
|
+
* @returns {{ pull: boolean, reason: string }}
|
|
25
|
+
*/
|
|
26
|
+
export function shouldPreloadRag({ triage, task, config } = {}) {
|
|
27
|
+
const policy = config?.rag?.preload?.policy || DEFAULT_POLICY;
|
|
28
|
+
|
|
29
|
+
if (policy === "always") return { pull: true, reason: "policy:always" };
|
|
30
|
+
if (policy === "never") return { pull: false, reason: "policy:never" };
|
|
31
|
+
|
|
32
|
+
// policy === "auto" — apply heuristic.
|
|
33
|
+
// Brownfield is an explicit user signal — if onboarding produced a
|
|
34
|
+
// brief and config marks the project as brownfield, prior context is
|
|
35
|
+
// almost always worth pulling.
|
|
36
|
+
if (config?.rag?.preload?.brownfield === true) {
|
|
37
|
+
return { pull: true, reason: "auto:brownfield" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Triage said this is big enough to decompose → multi-HU run benefits
|
|
41
|
+
// from prior context fed to researcher/architect/planner.
|
|
42
|
+
if (triage?.shouldDecompose === true) {
|
|
43
|
+
return { pull: true, reason: "auto:decompose" };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Triage classified the task as complex / high-effort.
|
|
47
|
+
if (triage?.level && COMPLEX_LEVELS.has(String(triage.level).toLowerCase())) {
|
|
48
|
+
return { pull: true, reason: "auto:complex" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Long task body usually means a brief with context, motivation and
|
|
52
|
+
// non-trivial scope — worth checking the RAG.
|
|
53
|
+
if (typeof task === "string" && task.length >= LONG_TASK_CHARS) {
|
|
54
|
+
return { pull: true, reason: "auto:long-task" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { pull: false, reason: "auto:low-value" };
|
|
58
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# kj-rag-query — Query Project RAG index
|
|
2
|
+
|
|
3
|
+
Query the Project RAG (vector store + embedder) and return the top-K most relevant chunks of the indexed project: plans, onboarding briefs, and (optionally) source code.
|
|
4
|
+
|
|
5
|
+
Use this when you need prior context about the project before deciding, refactoring, planning, or coding — without scanning the filesystem manually.
|
|
6
|
+
|
|
7
|
+
## Your task
|
|
8
|
+
|
|
9
|
+
$ARGUMENTS
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
This skill is a thin wrapper over the Karajan CLI:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
kj rag query "<text>" [--scope <all|plans|onboarding|code>] [--top-k <n>] [--json]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Parse `$ARGUMENTS` as the natural-language query plus optional flags:
|
|
20
|
+
|
|
21
|
+
- `--scope <s>`: restrict retrieval to one source bucket. Default: `all`.
|
|
22
|
+
- `plans` → indexed plan JSON files (HU titles, scope, blocked_by).
|
|
23
|
+
- `onboarding` → onboarding briefs from `~/.karajan/onboarding/`.
|
|
24
|
+
- `code` → indexed source files (only present if user ran `kj rag index --with-sources`).
|
|
25
|
+
- `--top-k <n>`: number of chunks to return. Default: 3. Max recommended: 10.
|
|
26
|
+
- `--json`: emit raw JSON (use when piping into another tool).
|
|
27
|
+
|
|
28
|
+
If `$ARGUMENTS` is empty, print a one-line usage hint and stop.
|
|
29
|
+
|
|
30
|
+
## Behavior contract
|
|
31
|
+
|
|
32
|
+
1. **Empty store**: if the CLI replies with `empty: true`, do NOT block the conversation. Tell the user once: "RAG index is empty — run `kj rag index` to seed it" and continue without prior context.
|
|
33
|
+
2. **Zero hits**: if the store has chunks but no match scored above threshold, say "no relevant prior context" and continue.
|
|
34
|
+
3. **Hits returned**: render each as a short block — `[score] source — snippet` — and use them as background context for the user's actual question. Do NOT dump raw JSON unless `--json` was passed.
|
|
35
|
+
4. **Never re-validate flags**. Pass `--scope`/`--top-k` straight to the CLI; the CLI is the source of truth.
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
User: `/kj-rag-query how does the retriever rank chunks`
|
|
40
|
+
→ `kj rag query "how does the retriever rank chunks"`
|
|
41
|
+
|
|
42
|
+
User: `/kj-rag-query auth flow --scope plans --top-k 5`
|
|
43
|
+
→ `kj rag query "auth flow" --scope plans --top-k 5`
|
|
44
|
+
|
|
45
|
+
User: `/kj-rag-query --scope onboarding architecture`
|
|
46
|
+
→ `kj rag query "architecture" --scope onboarding`
|
|
47
|
+
|
|
48
|
+
## Why this exists (Camino B)
|
|
49
|
+
|
|
50
|
+
Hosts that load Karajan as Skills (Claude Code without MCP, Cursor without MCP) cannot reach the `kj_rag_query` MCP tool. This skill bridges that gap with the same retrieval contract.
|