karajan-code 2.7.4 → 2.9.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 +1 -1
- package/package.json +24 -4
- package/scripts/diet-baseline.mjs +170 -0
- package/scripts/regen-arch-stats.sh +71 -0
- package/src/agents/availability.js +15 -9
- package/src/agents/claude-agent.js +143 -45
- package/src/agents/model-registry.js +1 -1
- package/src/audit/agent-readiness.js +240 -0
- package/src/audit/basal-cost.js +92 -9
- package/src/audit/deterministic-summary.js +200 -0
- package/src/audit/osv-findings.js +162 -0
- package/src/audit/semgrep-findings.js +117 -0
- package/src/audit/sonar-findings.js +81 -0
- package/src/audit/webperf-input.js +90 -0
- package/src/bootstrap.js +26 -1
- package/src/brain/decision-tracker.js +3 -1
- package/src/brain/decisor.js +1 -1
- package/src/canvas/parse-md.js +238 -0
- package/src/canvas/schema.js +101 -0
- package/src/canvas/to-flat.js +85 -0
- package/src/canvas/validate.js +163 -0
- package/src/checks/binaries.js +3 -3
- package/src/checks/ci.js +2 -2
- package/src/checks/config-files.js +6 -6
- package/src/checks/dir-setup.js +4 -0
- package/src/checks/mcp-health.js +6 -0
- package/src/checks/ports.js +91 -29
- package/src/checks/rtk.js +1 -1
- package/src/checks/skills.js +5 -1
- package/src/checks/sonar.js +2 -2
- package/src/checks/system.js +3 -3
- package/src/checks/tokens.js +1 -1
- package/src/checks/types.js +0 -12
- package/src/cli/_shared.js +30 -0
- package/src/cli/register-meta.js +218 -0
- package/src/cli/register-pipeline.js +212 -0
- package/src/cli/register-plan.js +152 -0
- package/src/cli/register-roles-skills.js +38 -0
- package/src/cli/register-sonar.js +28 -0
- package/src/cli.js +83 -468
- package/src/commands/agents.js +18 -6
- package/src/commands/architect.js +29 -2
- package/src/commands/audit.js +265 -30
- package/src/commands/board.js +160 -30
- package/src/commands/clean.js +83 -0
- package/src/commands/code.js +7 -2
- package/src/commands/discover.js +7 -2
- package/src/commands/doctor.js +1 -1
- package/src/commands/init.js +2 -2
- package/src/commands/plan/_shared.js +40 -0
- package/src/commands/plan/add-hu.js +21 -0
- package/src/commands/plan/delete.js +9 -0
- package/src/commands/plan/generate.js +284 -0
- package/src/commands/plan/index.js +14 -0
- package/src/commands/plan/list.js +21 -0
- package/src/commands/plan/ready.js +46 -0
- package/src/commands/plan/remove-hu.js +17 -0
- package/src/commands/plan/show.js +81 -0
- package/src/commands/plan/validate.js +20 -0
- package/src/commands/plan.js +14 -291
- package/src/commands/researcher.js +7 -2
- package/src/commands/resume.js +24 -1
- package/src/commands/review.js +7 -2
- package/src/commands/run.js +52 -1
- package/src/commands/status.js +1 -1
- package/src/commands/sync.js +75 -0
- package/src/commands/triage.js +7 -2
- package/src/commands/undo.js +1 -1
- package/src/config/defaults.js +213 -0
- package/src/config/loader.js +137 -0
- package/src/config/overrides.js +199 -0
- package/src/config/role-resolver.js +159 -0
- package/src/config/test-harness.js +78 -0
- package/src/config.js +30 -632
- package/src/git/automation.js +2 -2
- package/src/guards/output-guard.js +0 -2
- package/src/hu/acceptance-runner.js +52 -10
- package/src/hu/store.js +11 -0
- package/src/mcp/handlers/direct-handlers.js +2 -5
- package/src/mcp/handlers/management-handlers.js +48 -4
- package/src/mcp/handlers/run-handler.js +9 -8
- package/src/mcp/preflight.js +32 -13
- package/src/mcp/server-handlers.js +4 -3
- package/src/mcp/suggest-handler.js +3 -2
- package/src/mcp/tools.js +15 -0
- package/src/orchestrator/agent-fallback.js +1 -1
- package/src/orchestrator/ci-integration.js +11 -11
- package/src/orchestrator/config-init.js +25 -7
- package/src/orchestrator/direct-actions.js +7 -3
- package/src/orchestrator/drivers/error-recovery.js +287 -0
- package/src/orchestrator/drivers/init-context.js +222 -0
- package/src/orchestrator/drivers/iteration-loop.js +301 -0
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
- package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
- package/src/orchestrator/drivers/post-loop.js +370 -0
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
- package/src/orchestrator/drivers/pre-loop.js +431 -0
- package/src/orchestrator/drivers/run-hu-batch.js +367 -0
- package/src/orchestrator/feedback-queue.js +0 -1
- package/src/orchestrator/flow-control.js +4 -3
- package/src/orchestrator/flow-runner.js +121 -2077
- package/src/orchestrator/fs-leak-detector.js +156 -0
- package/src/orchestrator/hu-sub-pipeline.js +197 -9
- package/src/orchestrator/integrations.js +81 -0
- package/src/orchestrator/post-loop-stages.js +35 -8
- package/src/orchestrator/preflight-checks.js +20 -7
- package/src/orchestrator/repair/repair-detector.js +117 -0
- package/src/orchestrator/repair/repair-runner.js +142 -0
- package/src/orchestrator/reviewer-fallback.js +1 -1
- package/src/orchestrator/role-output-compressor.js +0 -1
- package/src/orchestrator/session-journal.js +4 -41
- package/src/orchestrator/solomon-escalation.js +3 -2
- package/src/orchestrator/solomon-rules.js +7 -3
- package/src/orchestrator/stages/architect-stage.js +12 -11
- package/src/orchestrator/stages/coder-stage.js +56 -7
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
- package/src/orchestrator/stages/planner-stage.js +1 -1
- package/src/orchestrator/stages/research-stage.js +1 -1
- package/src/orchestrator/stages/reviewer-stage.js +7 -7
- package/src/orchestrator/stages/sonar-stage.js +94 -10
- package/src/orchestrator/stages/stage-classes.js +84 -0
- package/src/orchestrator/stages/stage-executor.js +1 -1
- package/src/orchestrator/stages/triage-stage.js +1 -1
- package/src/orchestrator/standby.js +3 -2
- package/src/orchestrator/verification-gate.js +20 -12
- package/src/plan/adr-generator.js +170 -0
- package/src/plan/adr-loader.js +60 -0
- package/src/plan/plan-executor.js +19 -1
- package/src/plan/plan-hu-ops.js +170 -1
- package/src/plan/plan-reviewer.js +199 -0
- package/src/plan/plan-schema.js +69 -1
- package/src/plan/plan-store.js +40 -17
- package/src/plan/spec-detector.js +61 -0
- package/src/plan/tests-synthesizer.js +116 -0
- package/src/planning-game/client.js +3 -28
- package/src/planning-game/decomposition.js +1 -1
- package/src/planning-game/pipeline-adapter.js +37 -6
- package/src/prompts/audit.js +206 -6
- package/src/prompts/coder.js +172 -1
- package/src/prompts/hu-reviewer.js +0 -1
- package/src/prompts/planner.js +81 -4
- package/src/prompts/rtk-snippet.js +1 -1
- package/src/review/diff-generator.js +35 -22
- package/src/review/snapshot-diff.js +1 -1
- package/src/roles/audit-role.js +125 -13
- package/src/roles/coder-role.js +25 -3
- package/src/roles/planner-role.js +42 -1
- package/src/roles/repairer-role.js +181 -0
- package/src/roles/tester-role.js +70 -4
- package/src/session/mutators.js +304 -0
- package/src/session/runtime-overrides.js +59 -0
- package/src/{session-store.js → session/store.js} +2 -2
- package/src/skills/semantic-detector.js +10 -5
- package/src/skills/skill-detector.js +1 -1
- package/src/sonar/api.js +1 -1
- package/src/sonar/cloud-scanner.js +0 -1
- package/src/sonar/credentials.js +14 -6
- package/src/sonar/discovery.js +123 -0
- package/src/sonar/manager.js +18 -0
- package/src/sync/detect-drift.js +113 -0
- package/src/tracker-bootstrap.js +44 -0
- package/src/utils/agent-detect.js +15 -11
- package/src/utils/board-prompt-bridge.js +109 -0
- package/src/utils/cli-progress.js +161 -0
- package/src/utils/cwd-confirm.js +39 -0
- package/src/utils/derive-project-name-from-cwd.js +119 -0
- package/src/utils/display/event-handlers.js +1 -1
- package/src/utils/display/header.js +1 -1
- package/src/utils/display/session-summary.js +3 -3
- package/src/utils/display/solomon.js +1 -1
- package/src/utils/garbage-collector.js +396 -0
- package/src/utils/git.js +16 -4
- package/src/utils/locale.js +1 -1
- package/src/utils/paths.js +21 -1
- package/src/utils/process.js +1 -1
- package/src/utils/prompt-project-name.js +35 -0
- package/src/utils/role-assigner.js +1 -1
- package/src/utils/run-context.js +75 -0
- package/src/utils/run-log.js +12 -0
- package/src/utils/task-file.js +1 -1
- package/src/utils/telemetry.js +13 -1
- package/src/webperf/devtools-detect.js +1 -1
- package/templates/coder-rules.md +13 -0
- package/templates/roles/coder.md +34 -0
- package/templates/roles/repairer.md +94 -0
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
15
|
-
import { addCheckpoint
|
|
15
|
+
import { addCheckpoint } from "../session/store.js";
|
|
16
|
+
import { appendPgCommits } from "../session/mutators.js";
|
|
16
17
|
import { getLang } from "../utils/messages.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -28,7 +29,7 @@ import { getLang } from "../utils/messages.js";
|
|
|
28
29
|
* @param {string|null} opts.pgProject - Planning Game project ID
|
|
29
30
|
* @returns {{ pgCard: object|null }} The fetched PG card (or null)
|
|
30
31
|
*/
|
|
31
|
-
export async function initPgAdapter({ session, config, logger, pgTaskId, pgProject }) {
|
|
32
|
+
export async function initPgAdapter({ session: _session, config, logger, pgTaskId, pgProject }) {
|
|
32
33
|
if (!pgTaskId || !pgProject || config.planning_game?.enabled === false) {
|
|
33
34
|
return { pgCard: null };
|
|
34
35
|
}
|
|
@@ -41,7 +42,7 @@ export async function initPgAdapter({ session, config, logger, pgTaskId, pgProje
|
|
|
41
42
|
* Handle PG task decomposition after triage.
|
|
42
43
|
* Creates subtask cards in PG when triage recommends decomposition.
|
|
43
44
|
*/
|
|
44
|
-
|
|
45
|
+
async function handlePgDecomposition({ triageResult, pgTaskId, pgProject, config, askQuestion, emitter, eventBase, session, stageResults, logger }) {
|
|
45
46
|
const shouldDecompose = triageResult.stageResult?.shouldDecompose
|
|
46
47
|
&& triageResult.stageResult.subtasks?.length > 1
|
|
47
48
|
&& pgTaskId
|
|
@@ -183,13 +184,12 @@ export function buildHuStoriesFromPgCard(pgCard) {
|
|
|
183
184
|
*/
|
|
184
185
|
export function accumulateCommit(session, commitInfo) {
|
|
185
186
|
if (!session || !commitInfo?.hash) return;
|
|
186
|
-
|
|
187
|
-
session.pg_commits.push({
|
|
187
|
+
appendPgCommits(session, [{
|
|
188
188
|
hash: commitInfo.hash,
|
|
189
189
|
message: commitInfo.message || "",
|
|
190
190
|
date: commitInfo.date || new Date().toISOString(),
|
|
191
191
|
author: commitInfo.author || "Karajan"
|
|
192
|
-
});
|
|
192
|
+
}]);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// --- Internal helpers ---
|
|
@@ -218,3 +218,34 @@ async function markPgCardInProgress({ pgTaskId, pgProject, config, logger }) {
|
|
|
218
218
|
return null;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* TSK-0339: integration adapter shape — the orchestrator consumes tracker
|
|
224
|
+
* hooks via this neutral interface, not via direct imports. `registerPgAdapter`
|
|
225
|
+
* is called once at bootstrap when `config.planning_game.enabled` is true,
|
|
226
|
+
* wiring these hooks into `src/orchestrator/integrations.js` under the
|
|
227
|
+
* "tracker" slot.
|
|
228
|
+
*
|
|
229
|
+
* Adding a new tracker (GitHub Projects, Jira, Linear, …) means creating
|
|
230
|
+
* a sibling `src/adapters/<name>/adapter.js` with the same hook surface,
|
|
231
|
+
* nothing else in the orchestrator changes.
|
|
232
|
+
*/
|
|
233
|
+
export const planningGameAdapter = {
|
|
234
|
+
name: "planning-game",
|
|
235
|
+
async onSessionStart(opts) { return initPgAdapter(opts); },
|
|
236
|
+
onCommit(session, commit) { accumulateCommit(session, commit); },
|
|
237
|
+
async onSessionApproved(opts) { return markPgCardToValidate(opts); },
|
|
238
|
+
buildHuStoriesFromCard(card) { return buildHuStoriesFromPgCard(card); },
|
|
239
|
+
async handleDecomposition(opts) { return handlePgDecomposition(opts); },
|
|
240
|
+
async createAdrsFromTradeoffs({ tradeoffs, pgTaskId, pgProject, taskTitle }) {
|
|
241
|
+
// Lazy-loaded to avoid pulling createArchitectADRs into every import.
|
|
242
|
+
const { createArchitectADRs } = await import("./architect-adrs.js");
|
|
243
|
+
const pgClient = await import("./client.js");
|
|
244
|
+
return createArchitectADRs({ tradeoffs, pgTaskId, pgProject, taskTitle, mcpClient: pgClient });
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export async function registerPgAdapter() {
|
|
249
|
+
const { registerIntegration } = await import("../orchestrator/integrations.js");
|
|
250
|
+
registerIntegration("tracker", planningGameAdapter);
|
|
251
|
+
}
|
package/src/prompts/audit.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { extractFirstJson } from "../utils/json-extract.js";
|
|
2
|
+
import { groupIssuesBySeverity } from "../audit/sonar-findings.js";
|
|
3
|
+
import { groupVulnerabilitiesBySeverity } from "../audit/osv-findings.js";
|
|
4
|
+
import { groupFindingsBySeverity as groupSemgrepBySeverity } from "../audit/semgrep-findings.js";
|
|
5
|
+
import { formatCwvVerdict } from "../audit/webperf-input.js";
|
|
6
|
+
|
|
7
|
+
const MAX_SONAR_ISSUES_IN_PROMPT = 50;
|
|
8
|
+
const MAX_OSV_VULNS_IN_PROMPT = 50;
|
|
9
|
+
const MAX_SEMGREP_FINDINGS_IN_PROMPT = 50;
|
|
2
10
|
|
|
3
11
|
const SUBAGENT_PREAMBLE = [
|
|
4
12
|
"IMPORTANT: You are running as a Karajan sub-agent.",
|
|
@@ -7,14 +15,20 @@ const SUBAGENT_PREAMBLE = [
|
|
|
7
15
|
"DO NOT modify any files — this is a read-only analysis. Only use: Read, Grep, Glob, Bash (for analysis commands like wc, find, git log, du, npm ls)."
|
|
8
16
|
].join(" ");
|
|
9
17
|
|
|
10
|
-
export const AUDIT_DIMENSIONS = ["security", "codeQuality", "performance", "architecture", "testing"];
|
|
18
|
+
export const AUDIT_DIMENSIONS = ["security", "codeQuality", "performance", "architecture", "testing", "accessibility"];
|
|
19
|
+
|
|
20
|
+
// Dimensions that only apply to frontend / fullstack projects. Auto-dropped
|
|
21
|
+
// from the default dimension list when stack detection knows the project
|
|
22
|
+
// is backend-only — preserves activation when the user explicitly opts in
|
|
23
|
+
// via --dimensions=accessibility (KJC-TSK-0359).
|
|
24
|
+
const FRONTEND_ONLY_DIMENSIONS = new Set(["accessibility"]);
|
|
11
25
|
|
|
12
26
|
const VALID_HEALTH = new Set(["good", "fair", "poor", "critical"]);
|
|
13
27
|
const VALID_SCORES = new Set(["A", "B", "C", "D", "F"]);
|
|
14
28
|
const VALID_SEVERITIES = new Set(["critical", "high", "medium", "low"]);
|
|
15
29
|
const VALID_IMPACT = new Set(["high", "medium", "low"]);
|
|
16
30
|
|
|
17
|
-
export function buildAuditPrompt({ task, instructions, dimensions = null, context = null, basalCost = null, growthDelta = null }) {
|
|
31
|
+
export function buildAuditPrompt({ task, instructions, dimensions = null, context = null, basalCost = null, growthDelta = null, stack = null, sonarFindings = null, webperf = null, osvFindings = null, semgrepFindings = null }) {
|
|
18
32
|
const sections = [SUBAGENT_PREAMBLE];
|
|
19
33
|
|
|
20
34
|
if (instructions) {
|
|
@@ -27,9 +41,46 @@ export function buildAuditPrompt({ task, instructions, dimensions = null, contex
|
|
|
27
41
|
"DO NOT modify any files. This is a READ-ONLY analysis."
|
|
28
42
|
);
|
|
29
43
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
// Stack hint — when detected, tells the LLM what kind of project this is
|
|
45
|
+
// so it can filter out irrelevant heuristics (e.g. don't flag bundle size
|
|
46
|
+
// in an Express-only API; don't flag N+1 queries in a static Astro site).
|
|
47
|
+
// Detected by src/utils/stack-detect.js — KJC-TSK-0358.
|
|
48
|
+
if (stack && (stack.frameworks?.length > 0 || stack.language || stack.isFrontend || stack.isBackend)) {
|
|
49
|
+
const stackLines = ["## Project Stack"];
|
|
50
|
+
if (stack.language) stackLines.push(`- Language: ${stack.language}`);
|
|
51
|
+
if (stack.frameworks?.length) stackLines.push(`- Frameworks: ${stack.frameworks.join(", ")}`);
|
|
52
|
+
const tier = stack.isFullstack
|
|
53
|
+
? "fullstack (frontend + backend)"
|
|
54
|
+
: stack.isFrontend
|
|
55
|
+
? "frontend-only"
|
|
56
|
+
: stack.isBackend
|
|
57
|
+
? "backend-only"
|
|
58
|
+
: "unknown";
|
|
59
|
+
stackLines.push(`- Tier: ${tier}`);
|
|
60
|
+
if (stack.isFullstack) {
|
|
61
|
+
stackLines.push("Apply BOTH backend heuristics (queries, sync I/O, caching) AND frontend heuristics (bundle size, lazy loading, render-blocking) where each layer applies.");
|
|
62
|
+
} else if (stack.isFrontend) {
|
|
63
|
+
stackLines.push("This project is frontend-only. SKIP findings about N+1 queries, sync file I/O in request handlers, missing pagination on list endpoints, and other backend-specific patterns. Focus on bundle size, lazy loading, render-blocking resources, image optimisation, and accessibility hints.");
|
|
64
|
+
} else if (stack.isBackend) {
|
|
65
|
+
stackLines.push("This project is backend-only. SKIP findings about bundle size, lazy loading, render-blocking resources, image optimisation, and frontend accessibility. Focus on queries, sync I/O, pagination, caching, auth/authz, and dependency hygiene.");
|
|
66
|
+
}
|
|
67
|
+
sections.push(stackLines.join("\n"));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// When dimensions are explicit, honour them verbatim — even frontend-only
|
|
71
|
+
// dimensions on a backend project (the user knows what they want).
|
|
72
|
+
// When dimensions are absent, default to all but drop frontend-only
|
|
73
|
+
// dimensions if the stack is provably backend-only. Unknown stack falls
|
|
74
|
+
// back to the full list (no false negatives — better an extra section
|
|
75
|
+
// than a missing one).
|
|
76
|
+
let activeDimensions;
|
|
77
|
+
if (dimensions) {
|
|
78
|
+
activeDimensions = dimensions.filter(d => AUDIT_DIMENSIONS.includes(d));
|
|
79
|
+
} else if (stack && stack.isBackend && !stack.isFrontend) {
|
|
80
|
+
activeDimensions = AUDIT_DIMENSIONS.filter(d => !FRONTEND_ONLY_DIMENSIONS.has(d));
|
|
81
|
+
} else {
|
|
82
|
+
activeDimensions = AUDIT_DIMENSIONS;
|
|
83
|
+
}
|
|
33
84
|
|
|
34
85
|
if (activeDimensions.includes("security")) {
|
|
35
86
|
sections.push(
|
|
@@ -105,9 +156,32 @@ export function buildAuditPrompt({ task, instructions, dimensions = null, contex
|
|
|
105
156
|
);
|
|
106
157
|
}
|
|
107
158
|
|
|
159
|
+
if (activeDimensions.includes("accessibility")) {
|
|
160
|
+
sections.push(
|
|
161
|
+
"## Accessibility Analysis (WCAG 2.x)",
|
|
162
|
+
[
|
|
163
|
+
"- Missing alt text on <img> tags (decorative images need empty alt=\"\", not absent)",
|
|
164
|
+
"- Form inputs without associated <label> or aria-label",
|
|
165
|
+
"- Heading hierarchy violations (h1 → h3 skipping h2; multiple h1 per page)",
|
|
166
|
+
"- Buttons / links containing only icons without aria-label or visually-hidden text",
|
|
167
|
+
"- Interactive elements built from <div>/<span> without role + keyboard handlers",
|
|
168
|
+
"- ARIA roles misused (presentation on focusable elements, redundant on native semantics)",
|
|
169
|
+
"- Focus management gaps (modals without focus trap, no visible :focus styles, tabindex misuse)",
|
|
170
|
+
"- Colour-only signalling (status indicated by red/green only, no icon or text)",
|
|
171
|
+
"- Static contrast — flag suspicious low-contrast colour pairs in CSS variables / token files",
|
|
172
|
+
"- Missing lang attribute on <html>, missing skip-to-content link, missing landmarks"
|
|
173
|
+
].join("\n"),
|
|
174
|
+
"Skip findings about colour contrast that require runtime rendering (e.g. computed styles); flag the suspect CSS values and recommend an axe-core/Lighthouse runtime check."
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// JSON schema — keep the per-dimension entries in sync with AUDIT_DIMENSIONS.
|
|
179
|
+
const schemaDimensions = AUDIT_DIMENSIONS
|
|
180
|
+
.map(d => `"${d}":{"score":"A|B|C|D|F","findings":[]}`)
|
|
181
|
+
.join(",");
|
|
108
182
|
sections.push(
|
|
109
183
|
"Return a single valid JSON object and nothing else.",
|
|
110
|
-
|
|
184
|
+
`JSON schema: {"ok":true,"result":{"summary":{"overallHealth":"good|fair|poor|critical","totalFindings":number,"critical":number,"high":number,"medium":number,"low":number},"dimensions":{${schemaDimensions}},"topRecommendations":[{"priority":number,"dimension":string,"action":string,"impact":"high|medium|low","effort":"high|medium|low"}]},"summary":string}`,
|
|
111
185
|
'Each finding: {"severity":"critical|high|medium|low","file":string,"line":number,"rule":string,"description":string,"recommendation":string}',
|
|
112
186
|
`Only include dimensions you were asked to analyze: ${activeDimensions.join(", ")}`
|
|
113
187
|
);
|
|
@@ -146,6 +220,132 @@ export function buildAuditPrompt({ task, instructions, dimensions = null, contex
|
|
|
146
220
|
sections.push(lines.join("\n"));
|
|
147
221
|
}
|
|
148
222
|
|
|
223
|
+
// Sonar findings — KJC-TSK-0361. Deterministic input from a tool the
|
|
224
|
+
// codebase already runs (kj run + SonarStage). The audit reads existing
|
|
225
|
+
// open issues + quality-gate state instead of re-scanning. Capped at
|
|
226
|
+
// MAX_SONAR_ISSUES_IN_PROMPT total entries (50) across all severities to
|
|
227
|
+
// bound prompt size; the LLM is told the truncated count so it can
|
|
228
|
+
// prioritise.
|
|
229
|
+
if (sonarFindings?.available && (sonarFindings.total > 0 || sonarFindings.qualityGate)) {
|
|
230
|
+
const lines = ["## SonarQube Findings"];
|
|
231
|
+
if (sonarFindings.qualityGate?.status) {
|
|
232
|
+
lines.push(`- Quality gate: ${sonarFindings.qualityGate.status}`);
|
|
233
|
+
}
|
|
234
|
+
if (sonarFindings.total > 0) {
|
|
235
|
+
lines.push(`- Total open issues: ${sonarFindings.total}`);
|
|
236
|
+
const groups = groupIssuesBySeverity(sonarFindings.issues);
|
|
237
|
+
let remaining = MAX_SONAR_ISSUES_IN_PROMPT;
|
|
238
|
+
for (const [severity, issues] of Object.entries(groups)) {
|
|
239
|
+
if (issues.length === 0) continue;
|
|
240
|
+
lines.push("");
|
|
241
|
+
lines.push(`### ${severity} (${issues.length})`);
|
|
242
|
+
const slice = issues.slice(0, Math.max(0, remaining));
|
|
243
|
+
for (const issue of slice) {
|
|
244
|
+
const loc = issue.component ? `${issue.component}${issue.line ? `:${issue.line}` : ""}` : "";
|
|
245
|
+
const rule = issue.rule ? ` [${issue.rule}]` : "";
|
|
246
|
+
lines.push(` - ${loc}${rule} ${issue.message || ""}`.trim());
|
|
247
|
+
}
|
|
248
|
+
if (issues.length > slice.length) {
|
|
249
|
+
lines.push(` - ... and ${issues.length - slice.length} more in ${severity}`);
|
|
250
|
+
}
|
|
251
|
+
remaining -= slice.length;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
lines.push("");
|
|
255
|
+
lines.push("Cross-reference these with your own findings. When your finding overlaps a Sonar rule, mention the rule ID in the `rule` field — that's a high-confidence match. Findings the LLM raises that Sonar can't see (architecture, naming, API design) are still valuable; mark them with rule names like \"AUDIT-ARCH-N\".");
|
|
256
|
+
sections.push(lines.join("\n"));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// WebPerf — KJC-TSK-0360. Two modes: a real CWV measurement when one is
|
|
260
|
+
// surfaced via config.webperf.lastResult (future `kj webperf` command)
|
|
261
|
+
// OR static frontend-perf hints for the LLM to look at the source code
|
|
262
|
+
// when no live measurement is available. Backend-only projects skip the
|
|
263
|
+
// section entirely (collectWebPerfInput returns available:false).
|
|
264
|
+
if (webperf?.available) {
|
|
265
|
+
const lines = ["## WebPerf — Frontend Performance"];
|
|
266
|
+
if (webperf.mode === "cwv-result") {
|
|
267
|
+
lines.push("Live Core Web Vitals measurement available — incorporate the verdict below into the performance dimension findings:");
|
|
268
|
+
lines.push("");
|
|
269
|
+
lines.push(formatCwvVerdict(webperf));
|
|
270
|
+
lines.push("");
|
|
271
|
+
lines.push("If a metric is `poor`, recommend a concrete code change (preconnect, lazy-load, image format, critical CSS, defer script). Cite the offending file or pattern when you can find it in the source.");
|
|
272
|
+
} else {
|
|
273
|
+
lines.push("No live CWV measurement was provided. Audit the source for these static frontend-perf patterns and include findings under the `performance` dimension:");
|
|
274
|
+
lines.push("");
|
|
275
|
+
lines.push("- Render-blocking `<link rel=\"stylesheet\">` or `<script>` without `defer`/`async` in `<head>`");
|
|
276
|
+
lines.push("- Large JavaScript bundles imported eagerly that could be `await import(...)` or split by route");
|
|
277
|
+
lines.push("- Non-modern image formats (only .jpg/.png) where webp/avif are widely supported");
|
|
278
|
+
lines.push("- `<img>` without `width`/`height` attributes (causes CLS)");
|
|
279
|
+
lines.push("- `<img>` without `loading=\"lazy\"` for off-screen images, or videos auto-playing without `preload=\"metadata\"`");
|
|
280
|
+
lines.push("- Web fonts loaded without `font-display: swap` or without `<link rel=\"preconnect\">` to the font origin");
|
|
281
|
+
lines.push("- Whole-library imports for one helper (e.g. `import _ from 'lodash'` instead of `lodash/get`)");
|
|
282
|
+
lines.push("- Missing critical CSS extraction or above-the-fold inlining strategy in build config");
|
|
283
|
+
lines.push("- Large third-party scripts (analytics, A/B, chat widgets) without defer + facade pattern");
|
|
284
|
+
lines.push("- Unbounded list rendering without virtualisation for very long collections");
|
|
285
|
+
}
|
|
286
|
+
sections.push(lines.join("\n"));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// OSV vulnerabilities — KJC-TSK-0365. Deterministic CVE/GHSA findings
|
|
290
|
+
// from osv-scanner across the dependency manifest. Capped at
|
|
291
|
+
// MAX_OSV_VULNS_IN_PROMPT entries; the LLM is told the truncated count.
|
|
292
|
+
// Findings fold into the `security` dimension with the upstream
|
|
293
|
+
// identifier (CVE-/GHSA-) as the rule.
|
|
294
|
+
if (osvFindings?.available && (osvFindings.total > 0)) {
|
|
295
|
+
const lines = ["## OSV Vulnerabilities"];
|
|
296
|
+
lines.push(`- Total open vulnerabilities in dependencies: ${osvFindings.total}`);
|
|
297
|
+
const groups = groupVulnerabilitiesBySeverity(osvFindings.vulnerabilities);
|
|
298
|
+
let remaining = MAX_OSV_VULNS_IN_PROMPT;
|
|
299
|
+
for (const [severity, vulns] of Object.entries(groups)) {
|
|
300
|
+
if (vulns.length === 0) continue;
|
|
301
|
+
lines.push("");
|
|
302
|
+
lines.push(`### ${severity} (${vulns.length})`);
|
|
303
|
+
const slice = vulns.slice(0, Math.max(0, remaining));
|
|
304
|
+
for (const vuln of slice) {
|
|
305
|
+
const where = vuln.package ? `${vuln.package}@${vuln.version || "?"}` : "(unknown package)";
|
|
306
|
+
const fixed = vuln.fixed ? ` → fixed in ${vuln.fixed}` : "";
|
|
307
|
+
lines.push(` - ${vuln.id} ${where}${fixed}: ${vuln.summary || ""}`.trim());
|
|
308
|
+
}
|
|
309
|
+
if (vulns.length > slice.length) {
|
|
310
|
+
lines.push(` - ... and ${vulns.length - slice.length} more in ${severity}`);
|
|
311
|
+
}
|
|
312
|
+
remaining -= slice.length;
|
|
313
|
+
}
|
|
314
|
+
lines.push("");
|
|
315
|
+
lines.push("These CVE/GHSA findings are GROUND TRUTH — incorporate them into the `security` dimension. Use the OSV id (CVE-XXXX-XXXX or GHSA-xxxx-xxxx-xxxx) as the `rule` field. Recommend the fixed version when available.");
|
|
316
|
+
sections.push(lines.join("\n"));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Semgrep SAST findings — KJC-TSK-0366. Static analysis catches what
|
|
320
|
+
// sonar/CVE-DBs miss: SQL/Cmd injection, XSS, hardcoded secrets,
|
|
321
|
+
// taint flow, language-specific anti-patterns. Capped at
|
|
322
|
+
// MAX_SEMGREP_FINDINGS_IN_PROMPT entries; LLM is told these are
|
|
323
|
+
// deterministic and asked to use rule IDs as-is in findings.
|
|
324
|
+
if (semgrepFindings?.available && semgrepFindings.total > 0) {
|
|
325
|
+
const lines = ["## Semgrep SAST Findings"];
|
|
326
|
+
lines.push(`- Total findings: ${semgrepFindings.total}`);
|
|
327
|
+
const groups = groupSemgrepBySeverity(semgrepFindings.findings);
|
|
328
|
+
let remaining = MAX_SEMGREP_FINDINGS_IN_PROMPT;
|
|
329
|
+
for (const [severity, findings] of Object.entries(groups)) {
|
|
330
|
+
if (findings.length === 0) continue;
|
|
331
|
+
lines.push("");
|
|
332
|
+
lines.push(`### ${severity} (${findings.length})`);
|
|
333
|
+
const slice = findings.slice(0, Math.max(0, remaining));
|
|
334
|
+
for (const f of slice) {
|
|
335
|
+
const loc = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : "";
|
|
336
|
+
const cwe = f.cwe ? ` (${Array.isArray(f.cwe) ? f.cwe.join(", ") : f.cwe})` : "";
|
|
337
|
+
lines.push(` - ${loc} [${f.rule}]${cwe}: ${f.message || ""}`.trim());
|
|
338
|
+
}
|
|
339
|
+
if (findings.length > slice.length) {
|
|
340
|
+
lines.push(` - ... and ${findings.length - slice.length} more in ${severity}`);
|
|
341
|
+
}
|
|
342
|
+
remaining -= slice.length;
|
|
343
|
+
}
|
|
344
|
+
lines.push("");
|
|
345
|
+
lines.push("These SAST findings are GROUND TRUTH — fold ERROR-severity ones into the `security` dimension as critical/high findings. Use the semgrep rule id (e.g. javascript.express.security.audit.xss.direct-response-write) verbatim in the `rule` field; that lets the developer look it up in the semgrep registry.");
|
|
346
|
+
sections.push(lines.join("\n"));
|
|
347
|
+
}
|
|
348
|
+
|
|
149
349
|
if (context) {
|
|
150
350
|
sections.push(`## Context\n${context}`);
|
|
151
351
|
}
|
package/src/prompts/coder.js
CHANGED
|
@@ -33,8 +33,155 @@ const SERENA_INSTRUCTIONS = [
|
|
|
33
33
|
"Fall back to reading files only when Serena tools are not sufficient."
|
|
34
34
|
].join("\n");
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Render structured acceptance_tests (v2.7.5) as a Markdown section the
|
|
38
|
+
* coder reads as its contract. Legacy plain-string entries and
|
|
39
|
+
* structured { type, content, file? } objects are both accepted.
|
|
40
|
+
*
|
|
41
|
+
* The layout is intentional:
|
|
42
|
+
* - Gherkin blocks preserve their newlines so the coder sees the
|
|
43
|
+
* Given/When/Then scenarios as-is.
|
|
44
|
+
* - Shell blocks are fenced so the coder can copy-paste to verify.
|
|
45
|
+
* - The header says "MUST pass" because this is the gate the tester
|
|
46
|
+
* role runs (phase 3) and refuses to approve without.
|
|
47
|
+
*
|
|
48
|
+
* @param {Array} tests
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function renderAcceptanceTestsSection(tests) {
|
|
52
|
+
if (!Array.isArray(tests) || tests.length === 0) return "";
|
|
53
|
+
const lines = [
|
|
54
|
+
"## Acceptance Tests — MUST pass before the HU can be approved",
|
|
55
|
+
"",
|
|
56
|
+
"These are the concrete contract for this task. The tester stage will execute them as the pass/fail gate — if any one fails, the HU is rejected. Write your implementation so each test passes.",
|
|
57
|
+
"",
|
|
58
|
+
];
|
|
59
|
+
tests.forEach((entry, i) => {
|
|
60
|
+
// Normalise: accept legacy strings and structured objects.
|
|
61
|
+
let type = "shell";
|
|
62
|
+
let content;
|
|
63
|
+
let file = null;
|
|
64
|
+
if (typeof entry === "string") {
|
|
65
|
+
content = entry;
|
|
66
|
+
} else if (entry && typeof entry === "object") {
|
|
67
|
+
type = entry.type === "gherkin" ? "gherkin" : "shell";
|
|
68
|
+
content = typeof entry.content === "string" ? entry.content : JSON.stringify(entry);
|
|
69
|
+
file = typeof entry.file === "string" && entry.file.trim() ? entry.file.trim() : null;
|
|
70
|
+
} else {
|
|
71
|
+
content = String(entry ?? "");
|
|
72
|
+
}
|
|
73
|
+
const heading = file
|
|
74
|
+
? `### Test ${i + 1} · \`${type}\` · target: \`${file}\``
|
|
75
|
+
: `### Test ${i + 1} · \`${type}\``;
|
|
76
|
+
lines.push(heading);
|
|
77
|
+
if (type === "gherkin") {
|
|
78
|
+
lines.push("```gherkin", content, "```", "");
|
|
79
|
+
} else {
|
|
80
|
+
lines.push("```bash", content, "```", "");
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
lines.push(
|
|
84
|
+
"If a test is ambiguous, implement what makes it pass AND add the clarifying assertion to the PR description — do NOT silently rewrite the test."
|
|
85
|
+
);
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Render the active ADRs (PR F) as a "MUST respect" preamble. Each
|
|
91
|
+
* ADR is shown verbatim — they're already in Nygard format
|
|
92
|
+
* (Context · Decision · Consequences) and the coder benefits from
|
|
93
|
+
* the full text, not a summary. Keeps the prompt size sane: caps at
|
|
94
|
+
* 8 ADRs (the planner shouldn't realistically emit more for one
|
|
95
|
+
* plan, but bound it just in case).
|
|
96
|
+
*/
|
|
97
|
+
function renderAdrSection(adrs) {
|
|
98
|
+
if (!Array.isArray(adrs) || adrs.length === 0) return "";
|
|
99
|
+
const capped = adrs.slice(0, 8);
|
|
100
|
+
const lines = [
|
|
101
|
+
"## Architecture Decision Records (active)",
|
|
102
|
+
"",
|
|
103
|
+
"These ADRs were committed by the architect role. Your implementation MUST respect them. If the SPEC asks for something incompatible with one of these, surface the conflict in your PR description rather than silently overriding the ADR.",
|
|
104
|
+
"",
|
|
105
|
+
];
|
|
106
|
+
for (const adr of capped) {
|
|
107
|
+
lines.push(adr.markdown.trim(), "", "---", "");
|
|
108
|
+
}
|
|
109
|
+
if (adrs.length > capped.length) {
|
|
110
|
+
lines.push(`_(…${adrs.length - capped.length} more ADRs not shown — see ~/.kj/plans/<slug>/<planId>/adrs/.)_`, "");
|
|
111
|
+
}
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* SPEC-section pointer (PR F). One short line. Lets the coder put
|
|
117
|
+
* "implements §X.Y of SPEC.md" in the PR description for traceability.
|
|
118
|
+
*/
|
|
119
|
+
function renderSpecSectionLine(specSection) {
|
|
120
|
+
if (!specSection) return "";
|
|
121
|
+
return [
|
|
122
|
+
"## SPEC reference",
|
|
123
|
+
"",
|
|
124
|
+
`This HU implements **§${specSection}** of the source SPEC. Cite this section in your PR description so the reviewer can trace coverage end-to-end.`,
|
|
125
|
+
].join("\n");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Plan-reviewer findings relevant to this HU (PR F). The reviewer's
|
|
130
|
+
* output is plan-wide; we filter to entries that mention the
|
|
131
|
+
* current HU id so the coder gets only what's actionable for them.
|
|
132
|
+
*/
|
|
133
|
+
function renderReviewerFindingsSection(findings, huId) {
|
|
134
|
+
if (!findings || !huId) return "";
|
|
135
|
+
const items = [];
|
|
136
|
+
for (const md of findings.missing_dependencies || []) {
|
|
137
|
+
if (md.from === huId) items.push(`- This HU should depend on **${md.on}** — ${md.rationale}`);
|
|
138
|
+
else if (md.on === huId) items.push(`- HU **${md.from}** should depend on this one — ${md.rationale}`);
|
|
139
|
+
}
|
|
140
|
+
for (const ov of findings.scope_overlaps || []) {
|
|
141
|
+
if (Array.isArray(ov.between) && ov.between.includes(huId)) {
|
|
142
|
+
const others = ov.between.filter((id) => id !== huId);
|
|
143
|
+
items.push(`- Scope overlap with **${others.join(", ")}** — ${ov.rationale}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const oi of findings.order_issues || []) {
|
|
147
|
+
if (Array.isArray(oi.hus) && oi.hus.includes(huId)) {
|
|
148
|
+
items.push(`- Order issue (${oi.issue}): ${oi.rationale}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (items.length === 0) return "";
|
|
152
|
+
return [
|
|
153
|
+
"## Reviewer findings about this HU",
|
|
154
|
+
"",
|
|
155
|
+
"The plan reviewer flagged the following — consider them while implementing:",
|
|
156
|
+
"",
|
|
157
|
+
...items,
|
|
158
|
+
].join("\n");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function buildCoderPrompt({ task, reviewerFeedback = null, sonarSummary = null, coderRules = null, methodology = "tdd", serenaEnabled = false, rtkAvailable = false, deferredContext = null, productContext = null, domainContext = null, plan = null, projectDir = null, language = "en", provider = null, acceptanceTests = null, adrs = null, specSection = null, reviewerFindings = null, huId = null }) {
|
|
37
162
|
const langInstruction = getLanguageInstruction(language);
|
|
163
|
+
// KJC-BUG-0032 (PR-I): hard rule about file paths. Real bug: a HU
|
|
164
|
+
// titled "Initialize project skeleton: create assistant/ directory…"
|
|
165
|
+
// had Claude run `cd /home/manu/assistant && pnpm init …` and 36 MB
|
|
166
|
+
// of code landed in $HOME instead of inside projectDir. Reinforce
|
|
167
|
+
// the constraint at the prompt level (soft fix) AND enforce it
|
|
168
|
+
// post-coder via fs-leak-detector.js (hard fix).
|
|
169
|
+
const projectDirRule = projectDir
|
|
170
|
+
? [
|
|
171
|
+
"## CRITICAL — Project directory boundary",
|
|
172
|
+
`The project root is **${projectDir}**. EVERY file you create, write, edit, mkdir or cd to MUST stay inside this directory.`,
|
|
173
|
+
"",
|
|
174
|
+
"Forbidden, regardless of what the task says:",
|
|
175
|
+
"- Absolute paths outside the project root (e.g. `/home/USER/something`, `/tmp/x`, `/etc/y`).",
|
|
176
|
+
"- `cd` to any directory outside the project root inside Bash commands.",
|
|
177
|
+
"- Creating sibling top-level directories under `$HOME`.",
|
|
178
|
+
"",
|
|
179
|
+
"If the task or HU title mentions a directory like `assistant/` or `app/`, treat it as RELATIVE to the project root. NEVER as an absolute path under $HOME.",
|
|
180
|
+
"",
|
|
181
|
+
"Karajan runs a deterministic post-coder guard that detects writes outside the project root and ABORTS the HU. If you ignore this rule the run will fail and your work will be discarded."
|
|
182
|
+
].join("\n")
|
|
183
|
+
: null;
|
|
184
|
+
|
|
38
185
|
const sections = [
|
|
39
186
|
serenaEnabled ? SUBAGENT_PREAMBLE_SERENA : SUBAGENT_PREAMBLE,
|
|
40
187
|
...(langInstruction ? [langInstruction] : []),
|
|
@@ -47,6 +194,7 @@ export async function buildCoderPrompt({ task, reviewerFeedback = null, sonarSum
|
|
|
47
194
|
"No console.log — use a structured logger. No 'any' types — use JSDoc annotations.",
|
|
48
195
|
SUBPROCESS_CONSTRAINTS
|
|
49
196
|
];
|
|
197
|
+
if (projectDirRule) sections.push(projectDirRule);
|
|
50
198
|
|
|
51
199
|
if (serenaEnabled) {
|
|
52
200
|
sections.push(SERENA_INSTRUCTIONS);
|
|
@@ -69,6 +217,29 @@ export async function buildCoderPrompt({ task, reviewerFeedback = null, sonarSum
|
|
|
69
217
|
sections.push(`## Implementation Plan (from planner)\nFollow these steps:\n${plan}`);
|
|
70
218
|
}
|
|
71
219
|
|
|
220
|
+
// PR F context injections — order matters:
|
|
221
|
+
// 1. ADRs first (they constrain HOW you implement),
|
|
222
|
+
// 2. SPEC reference (what section we're satisfying),
|
|
223
|
+
// 3. Reviewer findings about THIS HU (warnings the coder needs
|
|
224
|
+
// to consider while writing code),
|
|
225
|
+
// 4. Acceptance tests (the gate the implementation must pass).
|
|
226
|
+
// All four sit BEFORE coder rules / TDD policy so the policy
|
|
227
|
+
// frames them, not the other way around.
|
|
228
|
+
const adrSection = renderAdrSection(adrs);
|
|
229
|
+
if (adrSection) sections.push(adrSection);
|
|
230
|
+
|
|
231
|
+
const specSectionLine = renderSpecSectionLine(specSection);
|
|
232
|
+
if (specSectionLine) sections.push(specSectionLine);
|
|
233
|
+
|
|
234
|
+
const reviewerSection = renderReviewerFindingsSection(reviewerFindings, huId);
|
|
235
|
+
if (reviewerSection) sections.push(reviewerSection);
|
|
236
|
+
|
|
237
|
+
// Tests-first contract: the gate the implementation must pass.
|
|
238
|
+
const testsSection = renderAcceptanceTestsSection(acceptanceTests);
|
|
239
|
+
if (testsSection) {
|
|
240
|
+
sections.push(testsSection);
|
|
241
|
+
}
|
|
242
|
+
|
|
72
243
|
if (coderRules) {
|
|
73
244
|
sections.push(`Coder rules (MUST follow):\n${coderRules}`);
|
|
74
245
|
}
|
package/src/prompts/planner.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { detectSpecSections } from "../plan/spec-detector.js";
|
|
2
|
+
|
|
1
3
|
function extractStepText(line) {
|
|
2
4
|
const numberedStep = /^\d+[).:-]\s*(.+)$/.exec(line);
|
|
3
5
|
if (numberedStep) return numberedStep[1].trim();
|
|
@@ -64,7 +66,68 @@ function formatArchitectContext(architectContext) {
|
|
|
64
66
|
return lines.length > 1 ? lines.join("\n") : null;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Render the SPEC-section guidance block for the planner.
|
|
71
|
+
*
|
|
72
|
+
* Two modes:
|
|
73
|
+
* • the task contains numbered headings (`## 1.`, `### 2.1`, `§5`) →
|
|
74
|
+
* spec_section becomes a HARD requirement and the detected
|
|
75
|
+
* sections are echoed back as a checklist. LLMs (Sonnet, Codex)
|
|
76
|
+
* consistently skipped the soft "preferred" wording, so the only
|
|
77
|
+
* reliable lever is to (a) escalate the rule to MUST and (b) hand
|
|
78
|
+
* them the literal list of section refs they're allowed to cite.
|
|
79
|
+
* • no numbered headings detected → spec_section stays optional
|
|
80
|
+
* and the prompt simply explains the field for completeness.
|
|
81
|
+
*/
|
|
82
|
+
function renderSpecSectionRules(specInfo) {
|
|
83
|
+
if (!specInfo.hasSpec) {
|
|
84
|
+
return [
|
|
85
|
+
"### `spec_section` (optional)",
|
|
86
|
+
"The task does not look like a structured SPEC, so this field may be `null`. If you do cite a section, use a short ref like `\"5.3\"` or `\"§5 Initial Scope\"`.",
|
|
87
|
+
].join("\n");
|
|
88
|
+
}
|
|
89
|
+
const checklist = specInfo.sectionList
|
|
90
|
+
.map(({ section, title }) => ` - "${section}" — ${title}`)
|
|
91
|
+
.join("\n");
|
|
92
|
+
return [
|
|
93
|
+
"### `spec_section` (REQUIRED — the task is a structured SPEC)",
|
|
94
|
+
`Detected ${specInfo.sections.length} numbered SPEC heading(s) in the task. Every \`step\` MUST set \`spec_section\` to one of the refs below — string, exactly as listed. NEVER \`null\`, NEVER omitted, NEVER a free-form paraphrase.`,
|
|
95
|
+
"",
|
|
96
|
+
"Allowed values:",
|
|
97
|
+
checklist,
|
|
98
|
+
"",
|
|
99
|
+
"If a step covers multiple sections, pick the most specific one (e.g. prefer `\"2.1\"` over `\"2\"`). If a step does not map to any section, refactor the step so it does — the SPEC is the source of truth for what work belongs in this plan.",
|
|
100
|
+
].join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Render the acceptance_tests guidance block — same contract as
|
|
105
|
+
* planner-role.js (the orchestrator path) so both entry points
|
|
106
|
+
* produce comparable plans.
|
|
107
|
+
*/
|
|
108
|
+
function renderAcceptanceTestsRules() {
|
|
109
|
+
return [
|
|
110
|
+
"### `acceptance_tests` (REQUIRED — at least one test per step)",
|
|
111
|
+
"Each step MUST include 2-4 executable tests. Mix `gherkin` (observable behaviour) and `shell` (concrete commands that exit 0).",
|
|
112
|
+
"",
|
|
113
|
+
"Shape:",
|
|
114
|
+
"```json",
|
|
115
|
+
'"acceptance_tests": [',
|
|
116
|
+
' { "type": "gherkin", "content": "Given <ctx>\\nWhen <action>\\nThen <outcome>" },',
|
|
117
|
+
' { "type": "shell", "content": "<bash command that exits 0 on success>" }',
|
|
118
|
+
"]",
|
|
119
|
+
"```",
|
|
120
|
+
"",
|
|
121
|
+
"Rules:",
|
|
122
|
+
"- Tests are written BEFORE the step is implemented — they should fail until the step is done, pass afterwards.",
|
|
123
|
+
"- Cover at least one happy path and one edge case / failure mode.",
|
|
124
|
+
"- Do NOT use the placeholder `\"npx vitest run\"` — every test must assert something specific to the step.",
|
|
125
|
+
].join("\n");
|
|
126
|
+
}
|
|
127
|
+
|
|
67
128
|
export function buildPlannerPrompt({ task, context, architectContext, productContext = null, domainContext = null }) {
|
|
129
|
+
const specInfo = detectSpecSections(task);
|
|
130
|
+
|
|
68
131
|
const parts = [
|
|
69
132
|
"You are an expert software architect. Create an implementation plan for the following task.",
|
|
70
133
|
"",
|
|
@@ -93,10 +156,24 @@ export function buildPlannerPrompt({ task, context, architectContext, productCon
|
|
|
93
156
|
parts.push(
|
|
94
157
|
"## Output format",
|
|
95
158
|
"Respond with a JSON object containing:",
|
|
96
|
-
"- `approach`: A concise paragraph describing the overall strategy",
|
|
97
|
-
"- `steps`: An array of objects
|
|
98
|
-
"- `risks`: An array of strings describing potential risks or challenges",
|
|
99
|
-
"- `outOfScope`: An array of strings listing what is explicitly NOT included",
|
|
159
|
+
"- `approach`: A concise paragraph describing the overall strategy.",
|
|
160
|
+
"- `steps`: An array of step objects (see shape below). Each step = one commit.",
|
|
161
|
+
"- `risks`: An array of strings describing potential risks or challenges.",
|
|
162
|
+
"- `outOfScope`: An array of strings listing what is explicitly NOT included.",
|
|
163
|
+
"",
|
|
164
|
+
"Each step in `steps` has this shape:",
|
|
165
|
+
"```json",
|
|
166
|
+
"{",
|
|
167
|
+
' "description": "<what this step achieves, one sentence>",',
|
|
168
|
+
' "commit": "<conventional commit message>",',
|
|
169
|
+
' "spec_section": "<see rules below>",',
|
|
170
|
+
' "acceptance_tests": [ /* see rules below */ ]',
|
|
171
|
+
"}",
|
|
172
|
+
"```",
|
|
173
|
+
"",
|
|
174
|
+
renderSpecSectionRules(specInfo),
|
|
175
|
+
"",
|
|
176
|
+
renderAcceptanceTestsRules(),
|
|
100
177
|
"",
|
|
101
178
|
"Respond ONLY with valid JSON, no markdown fences or extra text."
|
|
102
179
|
);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* RTK (Rust Token Killer) prompt instruction snippet.
|
|
3
3
|
* Injected into coder/reviewer prompts when RTK is detected.
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
const RTK_INSTRUCTIONS = [
|
|
6
6
|
"## Token Optimization (RTK detected)",
|
|
7
7
|
"RTK is installed. Prefix ALL Bash tool calls with `rtk` to reduce token usage:",
|
|
8
8
|
"- Use `rtk git status` instead of `git status`",
|