karajan-code 1.51.0 → 1.53.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 +24 -4
- package/docs/README.es.md +24 -1
- package/package.json +1 -1
- package/src/cli.js +1 -0
- package/src/commands/plan.js +24 -1
- package/src/guards/intent-guard.js +17 -0
- package/src/guards/policy-resolver.js +2 -1
- package/src/mcp/handlers/direct-handlers.js +89 -2
- package/src/mcp/tools.js +1 -0
- package/src/orchestrator.js +39 -1
- package/src/plan/plan-store.js +116 -0
- package/src/prompts/triage.js +2 -1
- package/src/review/tdd-policy.js +1 -1
- package/src/skills/skill-detector.js +9 -0
- package/templates/skills/kj-csv-transform.md +40 -0
- package/templates/skills/kj-data-report.md +44 -0
- package/templates/skills/kj-sql-analysis.md +40 -0
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
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
16
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node.js"></a>
|
|
17
|
+
<a href="https://github.com/manufosela/homebrew-tap"><img src="https://img.shields.io/badge/homebrew-tap-orange.svg" alt="Homebrew"></a>
|
|
17
18
|
</p>
|
|
18
19
|
|
|
19
20
|
<p align="center">
|
|
@@ -52,19 +53,33 @@ If Claude Code is a smart pair programmer, Karajan is the CI/CD pipeline for AI-
|
|
|
52
53
|
|
|
53
54
|
## Install
|
|
54
55
|
|
|
55
|
-
**
|
|
56
|
+
**npm** (recommended):
|
|
57
|
+
```bash
|
|
58
|
+
npm install -g karajan-code
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Homebrew** (macOS):
|
|
62
|
+
```bash
|
|
63
|
+
brew tap manufosela/tap
|
|
64
|
+
brew install karajan-code
|
|
65
|
+
```
|
|
56
66
|
|
|
67
|
+
**One-liner** (detects OS, installs via npm):
|
|
57
68
|
```bash
|
|
58
69
|
curl -fsSL https://raw.githubusercontent.com/manufosela/karajan-code/main/scripts/install-kj.sh | sh
|
|
59
70
|
```
|
|
60
71
|
|
|
61
|
-
**
|
|
72
|
+
**Docker** (no Node.js needed):
|
|
73
|
+
```bash
|
|
74
|
+
docker run --rm -v $(pwd):/workspace karajan-code kj --version
|
|
75
|
+
```
|
|
62
76
|
|
|
77
|
+
**Python**:
|
|
63
78
|
```bash
|
|
64
|
-
|
|
79
|
+
cd wrappers/python && pip install .
|
|
65
80
|
```
|
|
66
81
|
|
|
67
|
-
That's it.
|
|
82
|
+
That's it. `kj init` auto-detects your installed agents and installs RTK for token optimization.
|
|
68
83
|
|
|
69
84
|
## Three ways to use Karajan
|
|
70
85
|
|
|
@@ -260,3 +275,8 @@ Issues and pull requests welcome. If something doesn't work as documented, [open
|
|
|
260
275
|
---
|
|
261
276
|
|
|
262
277
|
Built by [@manufosela](https://github.com/manufosela). Head of Engineering at Geniova Technologies, co-organizer of NodeJS Madrid, author of [Liderazgo Afectivo](https://www.liderazgoafectivo.com). 90+ npm packages published.
|
|
278
|
+
|
|
279
|
+
### Contributors
|
|
280
|
+
|
|
281
|
+
- [@aitormf](https://github.com/aitormf) — OpenCode agent (5th built-in agent)
|
|
282
|
+
- [@reiaguilera](https://github.com/reiaguilera) — Beta testing, feature proposals, and quality feedback
|
package/docs/README.es.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
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
16
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node.js"></a>
|
|
17
|
+
<a href="https://github.com/manufosela/homebrew-tap"><img src="https://img.shields.io/badge/homebrew-tap-orange.svg" alt="Homebrew"></a>
|
|
17
18
|
</p>
|
|
18
19
|
|
|
19
20
|
<p align="center">
|
|
@@ -52,11 +53,28 @@ Si Claude Code es un programador de pares inteligente, Karajan es el pipeline CI
|
|
|
52
53
|
|
|
53
54
|
## Instalacion
|
|
54
55
|
|
|
56
|
+
**npm** (recomendado):
|
|
55
57
|
```bash
|
|
56
58
|
npm install -g karajan-code
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
**Homebrew** (macOS):
|
|
62
|
+
```bash
|
|
63
|
+
brew tap manufosela/tap
|
|
64
|
+
brew install karajan-code
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**One-liner** (detecta SO, instala via npm):
|
|
68
|
+
```bash
|
|
69
|
+
curl -fsSL https://raw.githubusercontent.com/manufosela/karajan-code/main/scripts/install-kj.sh | sh
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Docker** (sin Node.js):
|
|
73
|
+
```bash
|
|
74
|
+
docker run --rm -v $(pwd):/workspace karajan-code kj --version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`kj init` auto-detecta tus agentes instalados e instala RTK para optimizacion de tokens.
|
|
60
78
|
|
|
61
79
|
## Tres formas de usar Karajan
|
|
62
80
|
|
|
@@ -230,3 +248,8 @@ Issues y pull requests bienvenidos. Si algo no funciona como esta documentado, [
|
|
|
230
248
|
---
|
|
231
249
|
|
|
232
250
|
Construido por [@manufosela](https://github.com/manufosela). Head of Engineering en Geniova Technologies, co-organizador de NodeJS Madrid, autor de [Liderazgo Afectivo](https://www.liderazgoafectivo.com). 90+ paquetes npm publicados.
|
|
251
|
+
|
|
252
|
+
### Contributors
|
|
253
|
+
|
|
254
|
+
- [@aitormf](https://github.com/aitormf) — Agente OpenCode (5o agente built-in)
|
|
255
|
+
- [@reiaguilera](https://github.com/reiaguilera) — Beta testing, propuestas de mejora y feedback de calidad
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -86,6 +86,7 @@ program
|
|
|
86
86
|
.option("--enable-triage")
|
|
87
87
|
.option("--enable-discover")
|
|
88
88
|
.option("--enable-architect")
|
|
89
|
+
.option("--plan <planId>", "Plan ID from kj plan. Loads persisted plan context and skips researcher/architect/planner.")
|
|
89
90
|
.option("--enable-hu-reviewer")
|
|
90
91
|
.option("--hu-file <path>", "YAML file with HU stories to certify before coding")
|
|
91
92
|
.option("--enable-serena")
|
package/src/commands/plan.js
CHANGED
|
@@ -60,8 +60,26 @@ export async function planCommand({ task, config, logger, json, context }) {
|
|
|
60
60
|
|
|
61
61
|
const parsed = parseMaybeJsonString(result.output);
|
|
62
62
|
|
|
63
|
+
// Persist plan
|
|
64
|
+
let planId = null;
|
|
65
|
+
try {
|
|
66
|
+
const { savePlan } = await import("../plan/plan-store.js");
|
|
67
|
+
const projectDir = config.projectDir || process.cwd();
|
|
68
|
+
planId = await savePlan(projectDir, {
|
|
69
|
+
task,
|
|
70
|
+
researchContext: null,
|
|
71
|
+
architectContext: null,
|
|
72
|
+
plan: parsed || result.output,
|
|
73
|
+
raw: result.output
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger.warn(`Plan persistence failed: ${err.message}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
63
79
|
if (json) {
|
|
64
|
-
|
|
80
|
+
const jsonOutput = parsed || result.output;
|
|
81
|
+
const outputObj = typeof jsonOutput === "object" ? { ...jsonOutput, planId } : { plan: jsonOutput, planId };
|
|
82
|
+
console.log(JSON.stringify(outputObj, null, 2));
|
|
65
83
|
return;
|
|
66
84
|
}
|
|
67
85
|
|
|
@@ -70,4 +88,9 @@ export async function planCommand({ task, config, logger, json, context }) {
|
|
|
70
88
|
} else {
|
|
71
89
|
console.log(result.output);
|
|
72
90
|
}
|
|
91
|
+
|
|
92
|
+
if (planId) {
|
|
93
|
+
console.log(`\nPlan saved: ${planId}`);
|
|
94
|
+
console.log(`Use it with: kj run --plan ${planId} "<task>"`);
|
|
95
|
+
}
|
|
73
96
|
}
|
|
@@ -6,6 +6,23 @@ import { VALID_TASK_TYPES } from "./policy-resolver.js";
|
|
|
6
6
|
* Evaluated top-down; first match with confidence >= threshold wins.
|
|
7
7
|
*/
|
|
8
8
|
const INTENT_PATTERNS = [
|
|
9
|
+
// No-code tasks (data analysis, document generation, SQL queries, CSV transforms)
|
|
10
|
+
// Placed before audit so specific multi-word patterns win over single-word "analizar"/"analyze"
|
|
11
|
+
{
|
|
12
|
+
id: "no-code",
|
|
13
|
+
keywords: [
|
|
14
|
+
"generate report", "create document", "analyze data", "transform csv",
|
|
15
|
+
"sql query", "sql queries", "generate queries", "data analysis", "process data",
|
|
16
|
+
"export to", "import from", "convert data", "parse file",
|
|
17
|
+
"generar informe", "crear documento", "analizar datos", "transformar csv",
|
|
18
|
+
"consulta sql", "generar consultas", "analisis de datos", "procesar datos",
|
|
19
|
+
"exportar a", "importar de", "convertir datos", "parsear fichero",
|
|
20
|
+
],
|
|
21
|
+
taskType: "no-code",
|
|
22
|
+
level: "simple",
|
|
23
|
+
confidence: 0.9,
|
|
24
|
+
message: "No-code task detected — TDD and SonarQube disabled, coder generates non-code output",
|
|
25
|
+
},
|
|
9
26
|
// Audit / analysis tasks (no code changes, read-only analysis)
|
|
10
27
|
{
|
|
11
28
|
id: "audit",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VALID_TASK_TYPES = new Set(["sw", "infra", "doc", "add-tests", "refactor", "audit", "analysis"]);
|
|
1
|
+
export const VALID_TASK_TYPES = new Set(["sw", "infra", "doc", "add-tests", "refactor", "audit", "analysis", "no-code"]);
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_POLICIES = {
|
|
4
4
|
sw: { tdd: true, sonar: true, reviewer: true, testsRequired: true },
|
|
@@ -8,6 +8,7 @@ export const DEFAULT_POLICIES = {
|
|
|
8
8
|
refactor: { tdd: true, sonar: true, reviewer: true, testsRequired: false },
|
|
9
9
|
audit: { tdd: false, sonar: false, reviewer: false, testsRequired: false, coderRequired: false },
|
|
10
10
|
analysis: { tdd: false, sonar: false, reviewer: false, testsRequired: false, coderRequired: false },
|
|
11
|
+
"no-code": { tdd: false, sonar: false, reviewer: true, testsRequired: false, coderRequired: true },
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
const FALLBACK_TYPE = "sw";
|
|
@@ -70,6 +70,76 @@ export async function handlePlanDirect(a, server, extra) {
|
|
|
70
70
|
);
|
|
71
71
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
72
72
|
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
73
|
+
|
|
74
|
+
// --- Phase 1: Researcher (codebase context) ---
|
|
75
|
+
let researchContext = null;
|
|
76
|
+
try {
|
|
77
|
+
const { ResearcherRole } = await import("../../roles/researcher-role.js");
|
|
78
|
+
const researcherRole = resolveRole(config, "researcher");
|
|
79
|
+
const researcherOnOutput = ({ stream, line }) => {
|
|
80
|
+
emitter.emit("progress", { type: "agent:output", stage: "researcher", message: line, detail: { stream, agent: researcherRole.provider } });
|
|
81
|
+
};
|
|
82
|
+
const researcherStall = createStallDetector({
|
|
83
|
+
onOutput: researcherOnOutput, emitter, eventBase, stage: "researcher", provider: researcherRole.provider
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
sendTrackerLog(server, "researcher", "running", researcherRole.provider);
|
|
87
|
+
runLog.logText(`[researcher] agent launched for plan context...`);
|
|
88
|
+
const researcher = new ResearcherRole({ config, logger, emitter });
|
|
89
|
+
await researcher.init({ task: a.task });
|
|
90
|
+
let researchOutput;
|
|
91
|
+
try {
|
|
92
|
+
researchOutput = await researcher.run({ task: a.task, onOutput: researcherStall.onOutput });
|
|
93
|
+
} finally {
|
|
94
|
+
researcherStall.stop();
|
|
95
|
+
}
|
|
96
|
+
if (researchOutput.ok) {
|
|
97
|
+
researchContext = researchOutput.result;
|
|
98
|
+
sendTrackerLog(server, "researcher", "done");
|
|
99
|
+
runLog.logText(`[researcher] completed — context available`);
|
|
100
|
+
} else {
|
|
101
|
+
sendTrackerLog(server, "researcher", "failed");
|
|
102
|
+
runLog.logText(`[researcher] failed: ${researchOutput.summary || "unknown"}`);
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
logger.warn(`Researcher phase skipped: ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --- Phase 2: Architect (solution design) ---
|
|
109
|
+
let architectContext = null;
|
|
110
|
+
try {
|
|
111
|
+
const { ArchitectRole } = await import("../../roles/architect-role.js");
|
|
112
|
+
const architectRole = resolveRole(config, "architect");
|
|
113
|
+
const architectOnOutput = ({ stream, line }) => {
|
|
114
|
+
emitter.emit("progress", { type: "agent:output", stage: "architect", message: line, detail: { stream, agent: architectRole.provider } });
|
|
115
|
+
};
|
|
116
|
+
const architectStall = createStallDetector({
|
|
117
|
+
onOutput: architectOnOutput, emitter, eventBase, stage: "architect", provider: architectRole.provider
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
sendTrackerLog(server, "architect", "running", architectRole.provider);
|
|
121
|
+
runLog.logText(`[architect] agent launched for plan context...`);
|
|
122
|
+
const architect = new ArchitectRole({ config, logger, emitter });
|
|
123
|
+
await architect.init({ task: a.task });
|
|
124
|
+
let architectOutput;
|
|
125
|
+
try {
|
|
126
|
+
architectOutput = await architect.run({ task: a.task, researchContext, onOutput: architectStall.onOutput });
|
|
127
|
+
} finally {
|
|
128
|
+
architectStall.stop();
|
|
129
|
+
}
|
|
130
|
+
if (architectOutput.ok) {
|
|
131
|
+
architectContext = architectOutput.result;
|
|
132
|
+
sendTrackerLog(server, "architect", "done");
|
|
133
|
+
runLog.logText(`[architect] completed — context available`);
|
|
134
|
+
} else {
|
|
135
|
+
sendTrackerLog(server, "architect", "failed");
|
|
136
|
+
runLog.logText(`[architect] failed: ${architectOutput.summary || "unknown"}`);
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
logger.warn(`Architect phase skipped: ${err.message}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- Phase 3: Planner (with research + architecture context) ---
|
|
73
143
|
const onOutput = ({ stream, line }) => {
|
|
74
144
|
emitter.emit("progress", { type: "agent:output", stage: "planner", message: line, detail: { stream, agent: plannerRole.provider } });
|
|
75
145
|
};
|
|
@@ -78,7 +148,7 @@ export async function handlePlanDirect(a, server, extra) {
|
|
|
78
148
|
});
|
|
79
149
|
|
|
80
150
|
const planner = createAgent(plannerRole.provider, config, logger);
|
|
81
|
-
const prompt = buildPlannerPrompt({ task: a.task, context: a.context });
|
|
151
|
+
const prompt = buildPlannerPrompt({ task: a.task, context: a.context, architectContext });
|
|
82
152
|
sendTrackerLog(server, "planner", "running", plannerRole.provider);
|
|
83
153
|
runLog.logText(`[planner] agent launched, waiting for response...`);
|
|
84
154
|
let result;
|
|
@@ -111,7 +181,24 @@ export async function handlePlanDirect(a, server, extra) {
|
|
|
111
181
|
|
|
112
182
|
sendTrackerLog(server, "planner", "done");
|
|
113
183
|
const parsed = parseMaybeJsonString(result.output);
|
|
114
|
-
|
|
184
|
+
|
|
185
|
+
// --- Persist plan ---
|
|
186
|
+
let planId = null;
|
|
187
|
+
try {
|
|
188
|
+
const { savePlan } = await import("../../plan/plan-store.js");
|
|
189
|
+
planId = await savePlan(projectDir, {
|
|
190
|
+
task: a.task,
|
|
191
|
+
researchContext,
|
|
192
|
+
architectContext,
|
|
193
|
+
plan: parsed || result.output,
|
|
194
|
+
raw: result.output
|
|
195
|
+
});
|
|
196
|
+
runLog.logText(`[kj_plan] plan persisted — planId=${planId}`);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
logger.warn(`Plan persistence failed (non-blocking): ${err.message}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { ok: true, plan: parsed || result.output, raw: result.output, planId, researchContext, architectContext };
|
|
115
202
|
}
|
|
116
203
|
|
|
117
204
|
export async function handleCodeDirect(a, server, extra) {
|
package/src/mcp/tools.js
CHANGED
|
@@ -53,6 +53,7 @@ export const tools = [
|
|
|
53
53
|
required: ["task"],
|
|
54
54
|
properties: {
|
|
55
55
|
task: { type: "string", description: "Task description for the coder (can include a Planning Game card ID like KJC-TSK-0042)" },
|
|
56
|
+
plan: { type: "string", description: "Plan ID from kj_plan. Loads persisted plan context and skips researcher/architect/planner stages." },
|
|
56
57
|
projectDir: { type: "string", description: "Absolute path to the project directory. Required when KJ MCP server runs from a different directory than the target project." },
|
|
57
58
|
pgTask: { type: "string", description: "Planning Game card ID (e.g., KJC-TSK-0042). If provided, fetches full card details as task context and updates card status on completion." },
|
|
58
59
|
pgProject: { type: "string", description: "Planning Game project ID (e.g., 'Karajan Code'). Required when pgTask is used." },
|
package/src/orchestrator.js
CHANGED
|
@@ -984,6 +984,44 @@ async function runPreLoopStages({ config, logger, emitter, eventBase, session, f
|
|
|
984
984
|
pipelineFlags.securityEnabled = false;
|
|
985
985
|
}
|
|
986
986
|
|
|
987
|
+
// --- Plan injection: skip researcher/architect/planner if a persisted plan is loaded ---
|
|
988
|
+
if (flags.plan) {
|
|
989
|
+
try {
|
|
990
|
+
const { loadPlan } = await import("./plan/plan-store.js");
|
|
991
|
+
const projectDir = updatedConfig.projectDir || process.cwd();
|
|
992
|
+
const loadedPlan = await loadPlan(projectDir, flags.plan);
|
|
993
|
+
if (loadedPlan) {
|
|
994
|
+
logger.info(`Loaded persisted plan: ${flags.plan}`);
|
|
995
|
+
emitProgress(emitter, makeEvent("plan:loaded", { ...eventBase, stage: "plan" }, {
|
|
996
|
+
message: `Plan loaded from kj_plan: ${flags.plan}`,
|
|
997
|
+
detail: { planId: flags.plan, task: loadedPlan.task, createdAt: loadedPlan.createdAt }
|
|
998
|
+
}));
|
|
999
|
+
stageResults.researcher = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
|
|
1000
|
+
stageResults.architect = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
|
|
1001
|
+
stageResults.planner = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
|
|
1002
|
+
// Inject contexts into session for downstream stages
|
|
1003
|
+
session.research_context = loadedPlan.researchContext || null;
|
|
1004
|
+
session.architect_context = loadedPlan.architectContext || null;
|
|
1005
|
+
session.loaded_plan = loadedPlan.plan || null;
|
|
1006
|
+
await saveSession(session);
|
|
1007
|
+
|
|
1008
|
+
// Build the planned task from the plan steps
|
|
1009
|
+
const plan = loadedPlan.plan;
|
|
1010
|
+
let plannedTask = task;
|
|
1011
|
+
if (plan && typeof plan === "object" && plan.steps) {
|
|
1012
|
+
const stepList = plan.steps.map((s, idx) => `${idx + 1}. ${s.description || s}`).join("\n");
|
|
1013
|
+
plannedTask = `${task}\n\n## Implementation Plan\n${plan.approach || ""}\n\n## Steps\n${stepList}`;
|
|
1014
|
+
} else if (typeof plan === "string") {
|
|
1015
|
+
plannedTask = `${task}\n\n## Implementation Plan\n${plan}`;
|
|
1016
|
+
}
|
|
1017
|
+
return { plannedTask, updatedConfig };
|
|
1018
|
+
}
|
|
1019
|
+
logger.warn(`Plan ${flags.plan} not found — falling back to normal pipeline`);
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
logger.warn(`Plan loading failed: ${err.message} — falling back to normal pipeline`);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
987
1025
|
// --- Researcher → Planner ---
|
|
988
1026
|
const { plannedTask } = await runPlanningPhases({ config: updatedConfig, logger, emitter, eventBase, session, stageResults, pipelineFlags, coderRole, trackBudget, task, askQuestion });
|
|
989
1027
|
|
|
@@ -1085,7 +1123,7 @@ async function runQualityGateStages({ config, logger, emitter, eventBase, sessio
|
|
|
1085
1123
|
if (tddResult.action === "pause") return { action: "return", result: tddResult.result };
|
|
1086
1124
|
if (tddResult.action === "continue") return { action: "continue" };
|
|
1087
1125
|
|
|
1088
|
-
const skipSonarForTaskType = new Set(["infra", "doc"]);
|
|
1126
|
+
const skipSonarForTaskType = new Set(["infra", "doc", "no-code"]);
|
|
1089
1127
|
const effectiveTaskType = session.resolved_policies?.taskType || null;
|
|
1090
1128
|
if (config.sonarqube.enabled && !skipSonarForTaskType.has(effectiveTaskType)) {
|
|
1091
1129
|
const sonarResult = await runSonarStage({
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan persistence — save, load, list plans generated by kj_plan.
|
|
3
|
+
* Plans are stored at ~/.kj/plans/<projectSlug>/<planId>.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
|
|
10
|
+
function getKjHome() {
|
|
11
|
+
return process.env.KJ_HOME || path.join(os.homedir(), ".kj");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function projectSlug(projectDir) {
|
|
15
|
+
return projectDir
|
|
16
|
+
.replace(/^\//, "")
|
|
17
|
+
.replace(/[/\\]/g, "_")
|
|
18
|
+
.replace(/[^a-zA-Z0-9_.-]/g, "")
|
|
19
|
+
.slice(0, 120);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function plansDir(projectDir) {
|
|
23
|
+
return path.join(getKjHome(), "plans", projectSlug(projectDir));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function generatePlanId() {
|
|
27
|
+
const now = new Date();
|
|
28
|
+
const ts = now.toISOString().replace(/[-:T]/g, "").replace(/\.\d+Z$/, "");
|
|
29
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
30
|
+
return `plan-${ts}-${rand}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Save a plan result to disk.
|
|
35
|
+
* @param {string} projectDir - Absolute path to the project
|
|
36
|
+
* @param {object} planResult - { task, researchContext, architectContext, plan, raw }
|
|
37
|
+
* @returns {Promise<string>} planId
|
|
38
|
+
*/
|
|
39
|
+
export async function savePlan(projectDir, planResult) {
|
|
40
|
+
const dir = plansDir(projectDir);
|
|
41
|
+
await fs.mkdir(dir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
const planId = generatePlanId();
|
|
44
|
+
const record = {
|
|
45
|
+
planId,
|
|
46
|
+
task: planResult.task || null,
|
|
47
|
+
researchContext: planResult.researchContext || null,
|
|
48
|
+
architectContext: planResult.architectContext || null,
|
|
49
|
+
plan: planResult.plan || null,
|
|
50
|
+
raw: planResult.raw || null,
|
|
51
|
+
createdAt: new Date().toISOString()
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const filePath = path.join(dir, `${planId}.json`);
|
|
55
|
+
await fs.writeFile(filePath, JSON.stringify(record, null, 2), "utf8");
|
|
56
|
+
return planId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Load a saved plan by ID.
|
|
61
|
+
* @param {string} projectDir
|
|
62
|
+
* @param {string} planId
|
|
63
|
+
* @returns {Promise<object|null>} { planId, task, researchContext, architectContext, plan, createdAt } or null
|
|
64
|
+
*/
|
|
65
|
+
export async function loadPlan(projectDir, planId) {
|
|
66
|
+
const filePath = path.join(plansDir(projectDir), `${planId}.json`);
|
|
67
|
+
try {
|
|
68
|
+
const data = await fs.readFile(filePath, "utf8");
|
|
69
|
+
return JSON.parse(data);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* List all plans for a project, sorted by creation time (newest first).
|
|
77
|
+
* @param {string} projectDir
|
|
78
|
+
* @returns {Promise<Array<{planId: string, task: string, createdAt: string}>>}
|
|
79
|
+
*/
|
|
80
|
+
export async function listPlans(projectDir) {
|
|
81
|
+
const dir = plansDir(projectDir);
|
|
82
|
+
let files;
|
|
83
|
+
try {
|
|
84
|
+
files = await fs.readdir(dir);
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const plans = [];
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
if (!file.endsWith(".json")) continue;
|
|
92
|
+
try {
|
|
93
|
+
const data = await fs.readFile(path.join(dir, file), "utf8");
|
|
94
|
+
const record = JSON.parse(data);
|
|
95
|
+
plans.push({
|
|
96
|
+
planId: record.planId,
|
|
97
|
+
task: record.task,
|
|
98
|
+
createdAt: record.createdAt
|
|
99
|
+
});
|
|
100
|
+
} catch { /* skip corrupt files */ }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
plans.sort((a, b) => (b.createdAt || "").localeCompare(a.createdAt || ""));
|
|
104
|
+
return plans;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the most recent plan for a project.
|
|
109
|
+
* @param {string} projectDir
|
|
110
|
+
* @returns {Promise<object|null>}
|
|
111
|
+
*/
|
|
112
|
+
export async function getLatestPlan(projectDir) {
|
|
113
|
+
const all = await listPlans(projectDir);
|
|
114
|
+
if (all.length === 0) return null;
|
|
115
|
+
return loadPlan(projectDir, all[0].planId);
|
|
116
|
+
}
|
package/src/prompts/triage.js
CHANGED
|
@@ -40,13 +40,14 @@ export function buildTriagePrompt({ task, instructions, availableRoles }) {
|
|
|
40
40
|
"- **architect**: Enable when creating new modules/apps, changing data models or APIs, medium/complex tasks, or when design approach is ambiguous. Disable for simple fixes, doc-only, or CSS-only changes.",
|
|
41
41
|
"- **hu-reviewer**: Enable for medium/complex tasks that benefit from story decomposition and certification. Disable for trivial/simple tasks, doc-only, or CSS-only changes.",
|
|
42
42
|
"- **audit/analysis taskType**: Set taskType to \"audit\" or \"analysis\" for tasks that only require reading/inspecting code without making changes (e.g., audit, review, analyze, check, verify, inspect, scan, auditar, revisar, analizar, comprobar, verificar, inspeccionar). The coder and reviewer will be skipped entirely for these tasks.",
|
|
43
|
+
"- **no-code taskType**: Use no-code when the task is about data analysis, document generation, SQL queries, CSV transformation, report generation, or any non-programming task. The coder will generate output (not source code), and TDD/SonarQube will be disabled. The reviewer still runs to validate logic/correctness.",
|
|
43
44
|
"",
|
|
44
45
|
"Note: coder is ALWAYS active for coding tasks — you don't need to decide on it. However, for audit/analysis taskTypes the coder is skipped."
|
|
45
46
|
].join("\n"),
|
|
46
47
|
"Classify the task complexity, determine its taskType, recommend only the necessary pipeline roles, and assess whether the task should be decomposed into smaller subtasks.",
|
|
47
48
|
"Keep the reasoning short and practical.",
|
|
48
49
|
"Return a single valid JSON object and nothing else.",
|
|
49
|
-
'JSON schema: {"level":"trivial|simple|medium|complex","roles":["planner|researcher|refactorer|reviewer|tester|security|architect|hu-reviewer"],"taskType":"sw|infra|doc|add-tests|refactor|audit|analysis","reasoning":string,"shouldDecompose":boolean,"subtasks":string[]}',
|
|
50
|
+
'JSON schema: {"level":"trivial|simple|medium|complex","roles":["planner|researcher|refactorer|reviewer|tester|security|architect|hu-reviewer"],"taskType":"sw|infra|doc|add-tests|refactor|audit|analysis|no-code","reasoning":string,"shouldDecompose":boolean,"subtasks":string[]}',
|
|
50
51
|
`## Task\n${task}`
|
|
51
52
|
);
|
|
52
53
|
|
package/src/review/tdd-policy.js
CHANGED
|
@@ -19,7 +19,7 @@ function isSourceFile(file, extensions = []) {
|
|
|
19
19
|
return extensions.some((ext) => file.endsWith(ext));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const SKIP_TDD_TASK_TYPES = new Set(["doc", "infra"]);
|
|
22
|
+
const SKIP_TDD_TASK_TYPES = new Set(["doc", "infra", "no-code"]);
|
|
23
23
|
|
|
24
24
|
const DEFAULT_TEST_PATTERNS = ["/tests/", "/__tests__/", ".test.", ".spec.", "/src/test/", "/test/", "Test.java", "Test.kt", "test_", "_test.py", "_test.go", "Test.cs", "_spec.rb", "Test.php", "_test.dart", "Tests.swift"];
|
|
25
25
|
const DEFAULT_SOURCE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".kt", ".rb", ".php", ".cs", ".rs", ".swift", ".dart"];
|
|
@@ -67,6 +67,15 @@ const TASK_TEXT_PATTERNS = [
|
|
|
67
67
|
{ pattern: /\bflutter\b/i, skill: "flutter" },
|
|
68
68
|
{ pattern: /\bswift\b/i, skill: "swift" },
|
|
69
69
|
{ pattern: /\bphp\b/i, skill: "php" },
|
|
70
|
+
{ pattern: /\bSQL\b/i, skill: "sql-analysis" },
|
|
71
|
+
{ pattern: /\bquer(?:y|ies)\b/i, skill: "sql-analysis" },
|
|
72
|
+
{ pattern: /\bdatabase\b/i, skill: "sql-analysis" },
|
|
73
|
+
{ pattern: /\bCSV\b/i, skill: "csv-transform" },
|
|
74
|
+
{ pattern: /\btransform\b/i, skill: "csv-transform" },
|
|
75
|
+
{ pattern: /\bparse\b/i, skill: "csv-transform" },
|
|
76
|
+
{ pattern: /\breport\b/i, skill: "data-report" },
|
|
77
|
+
{ pattern: /\banalysis\b/i, skill: "data-report" },
|
|
78
|
+
{ pattern: /\bfindings\b/i, skill: "data-report" },
|
|
70
79
|
];
|
|
71
80
|
|
|
72
81
|
/**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# kj-csv-transform — CSV & Data Transformation
|
|
2
|
+
|
|
3
|
+
Transform, validate, and process CSV and tabular data files.
|
|
4
|
+
|
|
5
|
+
## Your task
|
|
6
|
+
|
|
7
|
+
$ARGUMENTS
|
|
8
|
+
|
|
9
|
+
## Coder instructions
|
|
10
|
+
|
|
11
|
+
When working with CSV or tabular data:
|
|
12
|
+
|
|
13
|
+
1. **Auto-detect delimiter** — do not assume comma. Detect the actual delimiter (comma, semicolon, tab, pipe) by inspecting the first few lines. Use libraries that support delimiter sniffing when available.
|
|
14
|
+
2. **Handle encoding** — detect and handle file encoding (UTF-8, UTF-8 BOM, Latin-1/ISO-8859-1, Windows-1252). Convert to UTF-8 for processing.
|
|
15
|
+
3. **Validate data types per column** — after parsing, verify that each column contains the expected data type (string, number, date, boolean). Flag rows with type mismatches.
|
|
16
|
+
4. **Handle missing values explicitly** — define a strategy for missing/empty values (keep as null, fill with default, skip row). Never silently drop rows with missing data.
|
|
17
|
+
5. **Preserve row count** — track input vs output row counts. Log any rows filtered, merged, or split so the transformation is auditable.
|
|
18
|
+
6. **Quote fields correctly** — when writing CSV output, properly escape fields containing delimiters, quotes, or newlines.
|
|
19
|
+
7. **Header normalization** — trim whitespace from headers, handle duplicate column names, and normalize casing if needed.
|
|
20
|
+
8. **Large file support** — for files that may be large, use streaming/chunked processing instead of loading everything into memory.
|
|
21
|
+
|
|
22
|
+
## Reviewer checklist
|
|
23
|
+
|
|
24
|
+
When reviewing CSV/data transformation code, check for:
|
|
25
|
+
|
|
26
|
+
- [ ] **Data integrity** — input row count matches expected output row count (accounting for intentional filters)?
|
|
27
|
+
- [ ] **No silent data loss** — missing values are handled explicitly, not silently dropped?
|
|
28
|
+
- [ ] **Correct transformations** — type conversions, date parsing, and numeric formatting are accurate?
|
|
29
|
+
- [ ] **Encoding handled** — non-ASCII characters preserved correctly through the pipeline?
|
|
30
|
+
- [ ] **Delimiter consistency** — detection works for the actual file, not just assumed comma?
|
|
31
|
+
- [ ] **Edge cases** — empty files, single-row files, files with only headers, fields with embedded newlines?
|
|
32
|
+
- [ ] **Memory safety** — large files processed via streams, not loaded entirely into memory?
|
|
33
|
+
- [ ] **Output format** — output CSV is well-formed (proper quoting, consistent line endings)?
|
|
34
|
+
|
|
35
|
+
## Output
|
|
36
|
+
|
|
37
|
+
Provide the transformation code with:
|
|
38
|
+
- Clear input/output format documentation
|
|
39
|
+
- Row count summary (input rows, output rows, filtered rows, error rows)
|
|
40
|
+
- Sample of transformed data for verification
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# kj-data-report — Data Report Generation
|
|
2
|
+
|
|
3
|
+
Generate structured data reports with clear findings, methodology, and recommendations.
|
|
4
|
+
|
|
5
|
+
## Your task
|
|
6
|
+
|
|
7
|
+
$ARGUMENTS
|
|
8
|
+
|
|
9
|
+
## Coder instructions
|
|
10
|
+
|
|
11
|
+
When generating data reports:
|
|
12
|
+
|
|
13
|
+
1. **Structure the report** — follow this format:
|
|
14
|
+
- **Executive summary** — 2-3 sentences with the key takeaway
|
|
15
|
+
- **Methodology** — data sources, time range, filters applied, tools used
|
|
16
|
+
- **Findings** — organized by theme or priority, each with supporting data
|
|
17
|
+
- **Recommendations** — actionable next steps based on the findings
|
|
18
|
+
2. **Include data tables** — present data in tables with clear headers, units, and formatting. Align numbers right, text left.
|
|
19
|
+
3. **Highlight anomalies and outliers** — flag data points that deviate significantly from the norm. Quantify the deviation (e.g., "3x above average", "lowest in 12 months").
|
|
20
|
+
4. **Quantify everything** — use percentages, counts, averages, medians, and trends. Avoid vague language like "significant increase" without numbers.
|
|
21
|
+
5. **Show comparisons** — include period-over-period comparisons (MoM, YoY) and benchmarks where available.
|
|
22
|
+
6. **Cite data sources** — reference the specific dataset, query, or file that produced each finding.
|
|
23
|
+
7. **Separate facts from interpretation** — clearly distinguish what the data shows from what you conclude.
|
|
24
|
+
8. **Include limitations** — note any data gaps, sample size issues, or caveats that affect conclusions.
|
|
25
|
+
|
|
26
|
+
## Reviewer checklist
|
|
27
|
+
|
|
28
|
+
When reviewing data reports, check for:
|
|
29
|
+
|
|
30
|
+
- [ ] **Conclusions match data** — every claim is supported by the data presented? No logical leaps?
|
|
31
|
+
- [ ] **No cherry-picking** — all relevant data included, not just data that supports a narrative?
|
|
32
|
+
- [ ] **Methodology is sound** — data sources are appropriate, filters are justified, time ranges are sensible?
|
|
33
|
+
- [ ] **Numbers add up** — percentages sum correctly, counts are consistent across sections?
|
|
34
|
+
- [ ] **Anomalies explained** — outliers are acknowledged and investigated, not ignored?
|
|
35
|
+
- [ ] **Actionable recommendations** — recommendations are specific, feasible, and tied to findings?
|
|
36
|
+
- [ ] **Clear formatting** — tables are readable, units are specified, dates are unambiguous?
|
|
37
|
+
- [ ] **Limitations disclosed** — caveats and data quality issues are transparently noted?
|
|
38
|
+
|
|
39
|
+
## Output
|
|
40
|
+
|
|
41
|
+
Deliver the report in the structured format above, with:
|
|
42
|
+
- All data tables properly formatted
|
|
43
|
+
- Key metrics highlighted
|
|
44
|
+
- Clear separation between data, analysis, and recommendations
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# kj-sql-analysis — SQL Analysis & Generation
|
|
2
|
+
|
|
3
|
+
Generate and review SQL queries with a focus on correctness, performance, and security.
|
|
4
|
+
|
|
5
|
+
## Your task
|
|
6
|
+
|
|
7
|
+
$ARGUMENTS
|
|
8
|
+
|
|
9
|
+
## Coder instructions
|
|
10
|
+
|
|
11
|
+
When writing or modifying SQL queries:
|
|
12
|
+
|
|
13
|
+
1. **Parameterized queries only** — never concatenate user input into SQL strings. Use prepared statements or parameterized queries (`$1`, `?`, `:param`) to prevent injection.
|
|
14
|
+
2. **Comment complex logic** — add inline comments explaining non-obvious JOINs, subqueries, CTEs, and window functions.
|
|
15
|
+
3. **Validate against schema** — if a schema or migration files are available, verify that table names, column names, and types match the actual schema.
|
|
16
|
+
4. **Prefer explicit column lists** — avoid `SELECT *`; list the columns you need.
|
|
17
|
+
5. **Use aliases consistently** — when joining multiple tables, alias every table and prefix all column references.
|
|
18
|
+
6. **Index-aware queries** — structure WHERE clauses and JOINs to leverage existing indexes. Avoid wrapping indexed columns in functions.
|
|
19
|
+
7. **Limit result sets** — add `LIMIT` / `TOP` for queries that could return unbounded rows unless the full result is explicitly needed.
|
|
20
|
+
8. **Handle NULLs explicitly** — use `COALESCE`, `IS NULL`, or `IS NOT NULL` where NULL values could affect logic.
|
|
21
|
+
|
|
22
|
+
## Reviewer checklist
|
|
23
|
+
|
|
24
|
+
When reviewing SQL-related code, check for:
|
|
25
|
+
|
|
26
|
+
- [ ] **Injection risk** — any string concatenation or template literals building SQL?
|
|
27
|
+
- [ ] **N+1 queries** — loops executing individual queries instead of a single batch/join?
|
|
28
|
+
- [ ] **Missing indexes** — queries filtering or joining on columns without indexes?
|
|
29
|
+
- [ ] **Correct JOINs** — LEFT vs INNER vs CROSS used intentionally? Accidental cartesian products?
|
|
30
|
+
- [ ] **Transaction safety** — multi-statement operations wrapped in transactions where needed?
|
|
31
|
+
- [ ] **Schema alignment** — column names, types, and constraints match the current schema?
|
|
32
|
+
- [ ] **Performance** — unnecessary subqueries that could be CTEs or JOINs? Missing LIMIT on large tables?
|
|
33
|
+
- [ ] **NULL handling** — aggregations, comparisons, and JOINs account for NULL values?
|
|
34
|
+
|
|
35
|
+
## Output
|
|
36
|
+
|
|
37
|
+
Provide the SQL queries with:
|
|
38
|
+
- Inline comments for complex sections
|
|
39
|
+
- Explanation of the query plan and expected performance characteristics
|
|
40
|
+
- Any schema changes needed (new indexes, columns, tables)
|