karajan-code 2.8.0 → 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 +1 -1
- 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/canvas/schema.js +1 -1
- package/src/checks/binaries.js +2 -2
- 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 +8 -0
- package/src/checks/rtk.js +1 -1
- package/src/checks/skills.js +5 -1
- package/src/checks/sonar.js +1 -1
- package/src/checks/system.js +2 -2
- package/src/checks/tokens.js +1 -1
- package/src/checks/types.js +0 -12
- package/src/cli/register-meta.js +15 -1
- package/src/commands/audit.js +239 -28
- package/src/commands/doctor.js +1 -1
- package/src/config/test-harness.js +0 -1
- package/src/orchestrator/ci-integration.js +1 -1
- package/src/orchestrator/direct-actions.js +1 -1
- package/src/orchestrator/drivers/error-recovery.js +2 -2
- package/src/orchestrator/drivers/iteration-loop.js +3 -13
- package/src/orchestrator/drivers/pre-loop.js +1 -4
- package/src/orchestrator/feedback-queue.js +0 -1
- package/src/orchestrator/integrations.js +0 -7
- package/src/orchestrator/role-output-compressor.js +0 -1
- package/src/orchestrator/session-journal.js +4 -41
- package/src/orchestrator/stages/stage-classes.js +0 -2
- package/src/plan/plan-store.js +0 -14
- package/src/planning-game/client.js +0 -14
- package/src/planning-game/pipeline-adapter.js +1 -1
- package/src/prompts/audit.js +206 -6
- package/src/prompts/rtk-snippet.js +1 -1
- package/src/roles/audit-role.js +125 -13
- package/src/sonar/api.js +1 -1
- 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/role-assigner.js +1 -1
package/src/commands/audit.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import readline from "node:readline";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
3
6
|
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
4
7
|
import { resolveRole } from "../config.js";
|
|
5
|
-
import {
|
|
8
|
+
import { AUDIT_DIMENSIONS } from "../prompts/audit.js";
|
|
9
|
+
import { AuditRole } from "../roles/audit-role.js";
|
|
6
10
|
import { withCliRunLog } from "../utils/cli-run-log.js";
|
|
7
11
|
import { createCliProgressReporter } from "../utils/cli-progress.js";
|
|
8
12
|
import { runAgentReadiness, formatAgentReadinessReport } from "../audit/agent-readiness.js";
|
|
13
|
+
import { formatDeterministicSummary } from "../audit/deterministic-summary.js";
|
|
14
|
+
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Prompt the user (TTY only) to decide whether to continue with the LLM
|
|
19
|
+
* phase after deterministic findings have been printed. KJC-TSK-0364.
|
|
20
|
+
*
|
|
21
|
+
* @returns {Promise<boolean>} true to continue with LLM, false to skip
|
|
22
|
+
*/
|
|
23
|
+
function promptContinueWithLlm({ promptFn = null } = {}) {
|
|
24
|
+
if (typeof promptFn === "function") return promptFn();
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
if (!process.stdin.isTTY) { resolve(true); return; } // CI / piped input → continue
|
|
27
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
+
rl.question("Continue with LLM analysis? [y/N] ", (answer) => {
|
|
29
|
+
rl.close();
|
|
30
|
+
resolve(/^y(es)?$/i.test((answer || "").trim()));
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
9
34
|
|
|
10
35
|
function formatFindings(findings) {
|
|
11
36
|
const lines = [];
|
|
@@ -45,10 +70,11 @@ const DIMENSION_LABELS = {
|
|
|
45
70
|
codeQuality: "Code Quality",
|
|
46
71
|
performance: "Performance",
|
|
47
72
|
architecture: "Architecture",
|
|
48
|
-
testing: "Testing"
|
|
73
|
+
testing: "Testing",
|
|
74
|
+
accessibility: "Accessibility (WCAG 2.x)"
|
|
49
75
|
};
|
|
50
76
|
|
|
51
|
-
function formatAudit(parsed) {
|
|
77
|
+
function formatAudit(parsed, usage = null) {
|
|
52
78
|
const lines = [];
|
|
53
79
|
lines.push("## Codebase Health Report");
|
|
54
80
|
lines.push(`**Overall Health:** ${parsed.summary.overallHealth}`);
|
|
@@ -66,10 +92,119 @@ function formatAudit(parsed) {
|
|
|
66
92
|
}
|
|
67
93
|
|
|
68
94
|
if (parsed.textSummary) lines.push(`---\n${parsed.textSummary}`);
|
|
95
|
+
|
|
96
|
+
const usageBlock = formatUsageSummary(usage);
|
|
97
|
+
if (usageBlock) {
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push(usageBlock);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Format the LLM token + cost summary that closes every audit report.
|
|
107
|
+
* KJC-TSK-0363: makes the LLM cost visible (was silently discarded
|
|
108
|
+
* pre-patch — auditCommand never read roleResult.usage).
|
|
109
|
+
*
|
|
110
|
+
* @param {object|null} usage - shape produced by AuditRole's extractUsage
|
|
111
|
+
* @returns {string|null} markdown block, or null when there's nothing useful to show
|
|
112
|
+
*/
|
|
113
|
+
function formatUsageSummary(usage) {
|
|
114
|
+
if (!usage) return null;
|
|
115
|
+
const lines = ["## LLM Usage"];
|
|
116
|
+
lines.push(`- Provider: ${usage.provider}${usage.model ? ` (${usage.model})` : ""}`);
|
|
117
|
+
if (typeof usage.durationMs === "number") {
|
|
118
|
+
lines.push(`- Duration: ${(usage.durationMs / 1000).toFixed(1)}s`);
|
|
119
|
+
}
|
|
120
|
+
if (!usage.available) {
|
|
121
|
+
lines.push(`- Tokens: usage data not available — ${usage.reason || "provider does not report it"}`);
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
// Force en-US locale so the comma grouping is deterministic across CI
|
|
125
|
+
// machines (Node's default Intl.NumberFormat depends on the runtime
|
|
126
|
+
// locale; without this the rendering would look like "1234" on
|
|
127
|
+
// locale-less Node and "1,234" on a US-locale workstation).
|
|
128
|
+
const fmt = (n) => n.toLocaleString("en-US");
|
|
129
|
+
lines.push(`- Tokens: ${fmt(usage.total_tokens)} total (${fmt(usage.tokens_in)} in, ${fmt(usage.tokens_out)} out)`);
|
|
130
|
+
if (typeof usage.cost_usd === "number") {
|
|
131
|
+
lines.push(`- Estimated cost: $${usage.cost_usd.toFixed(4)} USD`);
|
|
132
|
+
}
|
|
69
133
|
return lines.join("\n");
|
|
70
134
|
}
|
|
71
135
|
|
|
72
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Resolve where to write the audit report on disk, given a flag value, an
|
|
138
|
+
* env override, and the desired format. Returns null if no write target
|
|
139
|
+
* was requested. Creates parent directories on demand.
|
|
140
|
+
*
|
|
141
|
+
* Resolution rules (KJC-TSK-0362):
|
|
142
|
+
* - flag undefined + no env → null (legacy behaviour, stdout only)
|
|
143
|
+
* - flag is a directory → write `audit-<ISO>.{md,json}` inside it
|
|
144
|
+
* - flag is a non-directory path → use it verbatim (extension drives format
|
|
145
|
+
* when --json/--md not explicit)
|
|
146
|
+
* - flag is undefined but env $KJ_AUDIT_REPORT_DIR is set → treat env as dir
|
|
147
|
+
*/
|
|
148
|
+
async function resolveReportFilePath(flagValue, isJson) {
|
|
149
|
+
const envDir = process.env.KJ_AUDIT_REPORT_DIR;
|
|
150
|
+
const target = flagValue || (envDir ? envDir : null);
|
|
151
|
+
if (!target) return null;
|
|
152
|
+
|
|
153
|
+
const ext = isJson ? "json" : "md";
|
|
154
|
+
let stats = null;
|
|
155
|
+
try { stats = await fs.stat(target); } catch { /* not a directory yet */ }
|
|
156
|
+
const isDir = stats?.isDirectory() || target.endsWith(path.sep) || target === envDir;
|
|
157
|
+
|
|
158
|
+
let resolved;
|
|
159
|
+
if (isDir) {
|
|
160
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
161
|
+
resolved = path.resolve(target, `audit-${stamp}.${ext}`);
|
|
162
|
+
} else {
|
|
163
|
+
resolved = path.resolve(target);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await fs.mkdir(path.dirname(resolved), { recursive: true });
|
|
167
|
+
return resolved;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Build the markdown header that prefixes a persisted report — captures
|
|
172
|
+
* timestamp + repo state + invocation flags so the file is reproducible.
|
|
173
|
+
*/
|
|
174
|
+
async function buildReportHeader(projectDir, invocation) {
|
|
175
|
+
const lines = ["# Karajan Audit Report", ""];
|
|
176
|
+
lines.push(`- **Generated:** ${new Date().toISOString()}`);
|
|
177
|
+
lines.push(`- **Project:** ${projectDir || process.cwd()}`);
|
|
178
|
+
|
|
179
|
+
// Best-effort git info — silent if outside a repo.
|
|
180
|
+
try {
|
|
181
|
+
const branch = (await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: projectDir, timeout: 2000 })).stdout.trim();
|
|
182
|
+
const commit = (await execFileAsync("git", ["rev-parse", "--short", "HEAD"], { cwd: projectDir, timeout: 2000 })).stdout.trim();
|
|
183
|
+
lines.push(`- **Branch:** ${branch}`);
|
|
184
|
+
lines.push(`- **Commit:** ${commit}`);
|
|
185
|
+
} catch { /* not a git repo or git missing */ }
|
|
186
|
+
|
|
187
|
+
if (invocation) lines.push(`- **Invocation:** \`${invocation}\``);
|
|
188
|
+
lines.push("");
|
|
189
|
+
lines.push("---");
|
|
190
|
+
lines.push("");
|
|
191
|
+
return lines.join("\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function describeInvocation({ task, dimensions, noSonar, noOsv, noSemgrep, json, deterministicOnly, yes }) {
|
|
195
|
+
const parts = ["kj audit"];
|
|
196
|
+
if (task && task !== "Analyze the full codebase") parts.push(JSON.stringify(task));
|
|
197
|
+
if (dimensions && dimensions !== "all") parts.push(`--dimensions=${dimensions}`);
|
|
198
|
+
if (noSonar) parts.push("--no-sonar");
|
|
199
|
+
if (noOsv) parts.push("--no-osv");
|
|
200
|
+
if (noSemgrep) parts.push("--no-semgrep");
|
|
201
|
+
if (deterministicOnly) parts.push("--deterministic-only");
|
|
202
|
+
if (yes) parts.push("--yes");
|
|
203
|
+
if (json) parts.push("--json");
|
|
204
|
+
return parts.join(" ");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function auditCommand({ task, config, logger, dimensions, json, agentReadiness, path: pathArg, noSonar = false, noOsv = false, noSemgrep = false, reportFile = null, deterministicOnly = false, yes = false, promptFn = null }) {
|
|
73
208
|
// --agent-readiness is a STANDALONE, deterministic, LLM-free audit
|
|
74
209
|
// dimension. It scores any third-party repo for AI-agent readability
|
|
75
210
|
// (llms.txt presence, page token budgets, robots allowlist, etc.).
|
|
@@ -88,44 +223,120 @@ export async function auditCommand({ task, config, logger, dimensions, json, age
|
|
|
88
223
|
}
|
|
89
224
|
|
|
90
225
|
return withCliRunLog("audit", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
91
|
-
const
|
|
92
|
-
await assertAgentsAvailable([
|
|
93
|
-
logger.info(`Audit (${
|
|
94
|
-
runLog.logText(`[audit] provider=${
|
|
226
|
+
const auditRoleConfig = resolveRole(config, "audit");
|
|
227
|
+
await assertAgentsAvailable([auditRoleConfig.provider]);
|
|
228
|
+
logger.info(`Audit (${auditRoleConfig.provider}) starting...`);
|
|
229
|
+
runLog.logText(`[audit] provider=${auditRoleConfig.provider} dimensions=${dimensions || "all"} deterministicOnly=${deterministicOnly}`);
|
|
95
230
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
231
|
+
// Two-phase audit (KJC-TSK-0364) — collect deterministic findings
|
|
232
|
+
// first; show them; ask the user (when TTY + not --yes + not
|
|
233
|
+
// --deterministic-only) whether to spend tokens on the LLM analysis.
|
|
234
|
+
// CI / piped input / --yes / --deterministic-only never prompts.
|
|
235
|
+
const role = new AuditRole({ config, logger });
|
|
236
|
+
const roleInput = {
|
|
237
|
+
task: task || "Analyze the full codebase",
|
|
238
|
+
dimensions: dimensions || null,
|
|
239
|
+
noSonar,
|
|
240
|
+
noOsv,
|
|
241
|
+
noSemgrep,
|
|
242
|
+
};
|
|
243
|
+
const deterministicCtx = await role.collectDeterministic(roleInput);
|
|
244
|
+
const deterministicMd = formatDeterministicSummary(deterministicCtx);
|
|
100
245
|
|
|
101
|
-
|
|
246
|
+
// --json suppresses the interactive flow regardless: it would mangle
|
|
247
|
+
// a script's stdout. JSON consumers always get the full audit unless
|
|
248
|
+
// they explicitly pass deterministicOnly.
|
|
249
|
+
const reportPath = await resolveReportFilePath(reportFile, json);
|
|
250
|
+
let runLlm = !deterministicOnly;
|
|
251
|
+
if (runLlm && !json && !yes) {
|
|
252
|
+
console.log(deterministicMd);
|
|
253
|
+
console.log("");
|
|
254
|
+
const ok = await promptContinueWithLlm({ promptFn });
|
|
255
|
+
runLlm = ok;
|
|
256
|
+
if (!ok) logger.info("LLM analysis skipped (deterministic-only mode).");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!runLlm) {
|
|
260
|
+
// Deterministic-only path. Produce the report from what we already
|
|
261
|
+
// have, persist it (md or json), and exit. Zero tokens spent.
|
|
262
|
+
const stdoutContent = json
|
|
263
|
+
? JSON.stringify({ deterministic: deterministicCtx, mode: "deterministic-only" }, null, 2)
|
|
264
|
+
: (deterministicOnly ? deterministicMd : ""); // already printed above when interactive
|
|
265
|
+
if (json || deterministicOnly) console.log(stdoutContent);
|
|
266
|
+
|
|
267
|
+
if (reportPath) {
|
|
268
|
+
let payload;
|
|
269
|
+
if (reportPath.endsWith(".json")) {
|
|
270
|
+
payload = JSON.stringify({ deterministic: deterministicCtx, mode: "deterministic-only" }, null, 2);
|
|
271
|
+
} else {
|
|
272
|
+
const header = await buildReportHeader(config?.projectDir, describeInvocation({ task, dimensions, noSonar, noOsv, noSemgrep, json, deterministicOnly, yes }));
|
|
273
|
+
payload = header + deterministicMd + "\n";
|
|
274
|
+
}
|
|
275
|
+
await fs.writeFile(reportPath, payload, "utf8");
|
|
276
|
+
runLog.logText(`[audit] report written (deterministic-only) → ${reportPath}`);
|
|
277
|
+
logger.info(`Audit report written: ${reportPath}`);
|
|
278
|
+
}
|
|
279
|
+
logger.info("Audit completed (deterministic-only).");
|
|
280
|
+
return { ok: true, mode: "deterministic-only", reportPath: reportPath || undefined };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Continue with the LLM phase using the already-collected context.
|
|
102
284
|
const progress = createCliProgressReporter({ role: "auditor" });
|
|
103
|
-
let
|
|
285
|
+
let roleResult;
|
|
104
286
|
try {
|
|
105
|
-
|
|
106
|
-
progress.finish(
|
|
287
|
+
roleResult = await role.executeWithDeterministic({ ...roleInput, onOutput: progress.onOutput }, deterministicCtx);
|
|
288
|
+
progress.finish(roleResult.ok ? "done" : "failed");
|
|
107
289
|
} catch (err) { progress.finish("failed"); throw err; }
|
|
108
290
|
|
|
109
|
-
if (!
|
|
110
|
-
|
|
291
|
+
if (!roleResult.ok) {
|
|
292
|
+
const err = roleResult.result?.error || "Audit failed";
|
|
293
|
+
throw new Error(err);
|
|
111
294
|
}
|
|
112
295
|
|
|
113
|
-
const parsed =
|
|
114
|
-
|
|
296
|
+
const parsed = roleResult.result;
|
|
297
|
+
const usage = roleResult.usage;
|
|
298
|
+
if (parsed?.summary?.overallHealth) {
|
|
115
299
|
runLog.logText(`[audit] findings=${parsed.summary.totalFindings} (critical=${parsed.summary.critical}, high=${parsed.summary.high})`);
|
|
116
300
|
}
|
|
301
|
+
if (usage?.available) {
|
|
302
|
+
runLog.logText(`[audit] usage tokens=${usage.total_tokens} cost=$${(usage.cost_usd ?? 0).toFixed(4)} duration=${(usage.durationMs / 1000).toFixed(1)}s`);
|
|
303
|
+
}
|
|
117
304
|
|
|
305
|
+
let stdoutContent;
|
|
118
306
|
if (json) {
|
|
119
|
-
|
|
120
|
-
|
|
307
|
+
const jsonPayload = parsed
|
|
308
|
+
? { ...parsed, usage: usage || undefined }
|
|
309
|
+
: { ...roleResult, usage: usage || undefined };
|
|
310
|
+
stdoutContent = JSON.stringify(jsonPayload, null, 2);
|
|
311
|
+
} else if (parsed?.summary?.overallHealth) {
|
|
312
|
+
stdoutContent = formatAudit(parsed, usage);
|
|
313
|
+
} else if (parsed?.raw) {
|
|
314
|
+
stdoutContent = parsed.raw + (usage ? `\n\n${formatUsageSummary(usage) || ""}` : "");
|
|
315
|
+
} else {
|
|
316
|
+
stdoutContent = roleResult.summary || "Audit complete.";
|
|
121
317
|
}
|
|
318
|
+
console.log(stdoutContent);
|
|
122
319
|
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
320
|
+
if (reportPath) {
|
|
321
|
+
let payload;
|
|
322
|
+
if (reportPath.endsWith(".json")) {
|
|
323
|
+
const jsonPayload = parsed
|
|
324
|
+
? { ...parsed, usage: usage || undefined }
|
|
325
|
+
: { ...roleResult, usage: usage || undefined };
|
|
326
|
+
payload = JSON.stringify(jsonPayload, null, 2);
|
|
327
|
+
} else {
|
|
328
|
+
const header = await buildReportHeader(config?.projectDir, describeInvocation({ task, dimensions, noSonar, noOsv, noSemgrep, json, deterministicOnly, yes }));
|
|
329
|
+
payload = header + stdoutContent + "\n";
|
|
330
|
+
}
|
|
331
|
+
await fs.writeFile(reportPath, payload, "utf8");
|
|
332
|
+
runLog.logText(`[audit] report written → ${reportPath}`);
|
|
333
|
+
logger.info(`Audit report written: ${reportPath}`);
|
|
127
334
|
}
|
|
335
|
+
|
|
128
336
|
logger.info("Audit completed.");
|
|
129
|
-
return { ok: true };
|
|
337
|
+
return { ok: true, reportPath: reportPath || undefined };
|
|
130
338
|
});
|
|
131
339
|
}
|
|
340
|
+
|
|
341
|
+
// Exposed for tests — kept module-private otherwise.
|
|
342
|
+
export { formatAudit, formatUsageSummary, AUDIT_DIMENSIONS };
|
package/src/commands/doctor.js
CHANGED
|
@@ -77,7 +77,7 @@ function confirmViaTty(describe) {
|
|
|
77
77
|
* @param {{ config: Object, checkOnly?: boolean, yes?: boolean, onConfirm?: Function, logger?: Object }} args
|
|
78
78
|
* @returns {Promise<import("../checks/types.js").RunReport>}
|
|
79
79
|
*/
|
|
80
|
-
|
|
80
|
+
async function runDoctor({ config, checkOnly = false, yes = false, onConfirm, logger }) {
|
|
81
81
|
const checks = buildChecks(config);
|
|
82
82
|
return runCheckPipeline(checks, { config }, {
|
|
83
83
|
mode: checkOnly ? "check-only" : "fix",
|
|
@@ -86,7 +86,7 @@ export async function handleCiEarlyPrOrPush({ ciEnabled, config, session, emitte
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
function formatBlockingIssues(issues) {
|
|
90
90
|
return issues?.map((x) => `- ${x.id || "ISSUE"} [${x.severity || ""}] ${x.description}`).join("\n") || "";
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -119,7 +119,7 @@ export async function handleStandbyResult({ stageResult, session, emitter, event
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
function emitSolomonAlerts(alerts, emitter, eventBase, logger) {
|
|
123
123
|
for (const alert of alerts) {
|
|
124
124
|
emitProgress(emitter, makeEvent("brain:rules-alert", { ...eventBase, stage: "brain" }, {
|
|
125
125
|
status: alert.severity === "critical" ? "fail" : "warn",
|
|
@@ -198,7 +198,7 @@ export async function handleSolomonCheck({ config, session, emitter, eventBase,
|
|
|
198
198
|
return { action: "continue" };
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
async function checkSolomonCriticalAlerts({ rulesResult, askQuestion, session, i }) {
|
|
202
202
|
if (!rulesResult.hasCritical || !askQuestion) return null;
|
|
203
203
|
|
|
204
204
|
const alertSummary = rulesResult.alerts
|
|
@@ -44,23 +44,13 @@ import { runQualityGateStages } from "./iteration-phases/quality-gates.js";
|
|
|
44
44
|
import { runReviewerGateStage } from "./iteration-phases/reviewer-gate.js";
|
|
45
45
|
import { handleApprovedReview } from "./iteration-phases/handle-approved.js";
|
|
46
46
|
|
|
47
|
-
// Re-export for back-compat with any external caller; the canonical
|
|
48
|
-
// definitions now live in ./iteration-phases/.
|
|
49
|
-
export { runCoderAndRefactorerStages } from "./iteration-phases/coder-and-refactorer.js";
|
|
50
|
-
export { runGuardStages } from "./iteration-phases/guards.js";
|
|
51
|
-
export { runQualityGateStages } from "./iteration-phases/quality-gates.js";
|
|
52
|
-
export { runReviewerGateStage } from "./iteration-phases/reviewer-gate.js";
|
|
53
|
-
export { handleApprovedReview } from "./iteration-phases/handle-approved.js";
|
|
54
|
-
|
|
55
47
|
// `runCoderAndRefactorerStages`, `runGuardStages`, `runQualityGateStages`,
|
|
56
48
|
// `runReviewerGateStage` and `handleApprovedReview` were extracted to
|
|
57
49
|
// ./iteration-phases/ in the v2.7.x audit follow-up (same pattern as
|
|
58
|
-
// pre-loop-phases/). The
|
|
59
|
-
//
|
|
60
|
-
// is unchanged.
|
|
61
|
-
|
|
50
|
+
// pre-loop-phases/). The back-compat re-exports were removed in
|
|
51
|
+
// KJC-TSK-0354 PR-D — no caller imported them via this module.
|
|
62
52
|
|
|
63
|
-
|
|
53
|
+
async function runSingleIteration(ctx) {
|
|
64
54
|
// Use plannedTask (HU-scoped or planner-enriched) over the raw original task.
|
|
65
55
|
// When running per-HU sub-pipelines, plannedTask is the HU's text, not the full spec.
|
|
66
56
|
const { config, logger, emitter, eventBase, session, iteration: i } = ctx;
|
|
@@ -52,9 +52,6 @@ import { emitConfigDeprecations } from "./pre-loop-phases/config-deprecations.js
|
|
|
52
52
|
import { ensureAddyosmaniSkills } from "./pre-loop-phases/ensure-addyosmani-skills.js";
|
|
53
53
|
import { maybeGenerateAutoHuBatch } from "./pre-loop-phases/auto-hu-batch.js";
|
|
54
54
|
|
|
55
|
-
// Re-export for back-compat with any external caller; the canonical
|
|
56
|
-
// definitions now live in ./pre-loop-phases/.
|
|
57
|
-
export { ensureAddyosmaniSkills };
|
|
58
55
|
import {
|
|
59
56
|
applyTriageOverrides, applyAutoSimplify, applyFlagOverrides,
|
|
60
57
|
resolvePipelinePolicies, updateGitignoreForStack,
|
|
@@ -380,7 +377,7 @@ export async function runPreLoopStages({ config, logger, emitter, eventBase, ses
|
|
|
380
377
|
|
|
381
378
|
return { plannedTask, updatedConfig };
|
|
382
379
|
}
|
|
383
|
-
|
|
380
|
+
async function runPlanningPhases({ config, logger, emitter, eventBase, session, stageResults, pipelineFlags, coderRole, trackBudget, task, askQuestion, brainCtx }) {
|
|
384
381
|
let researchContext = null;
|
|
385
382
|
let plannedTask = task;
|
|
386
383
|
|
|
@@ -72,13 +72,6 @@ export function unregisterIntegration(name) {
|
|
|
72
72
|
registry.delete(name);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
/**
|
|
76
|
-
* Clear the registry. Test-only; production never calls this.
|
|
77
|
-
*/
|
|
78
|
-
export function __clearIntegrations() {
|
|
79
|
-
registry.clear();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
75
|
/**
|
|
83
76
|
* List registered integration names. Useful for bootstrap logging.
|
|
84
77
|
* @returns {string[]}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Creates .reviews/session_*/ directories containing markdown files per stage.
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { runCommand } from "../utils/process.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Create the journal directory for a session.
|
|
@@ -174,29 +173,6 @@ export function formatDecision({ timestamp, trigger, context, action, reasoning
|
|
|
174
173
|
return lines.join("\n");
|
|
175
174
|
}
|
|
176
175
|
|
|
177
|
-
// ── Tree of affected files ───────────────────────────────────────────────────
|
|
178
|
-
|
|
179
|
-
export async function generateFileTree(baseRef) {
|
|
180
|
-
try {
|
|
181
|
-
const result = await runCommand("git", ["diff", "--name-status", `${baseRef}...HEAD`]);
|
|
182
|
-
if (result.exitCode !== 0 || !result.stdout?.trim()) return null;
|
|
183
|
-
|
|
184
|
-
const lines = ["# Affected Files\n"];
|
|
185
|
-
const statusMap = { M: "modified", A: "added", D: "deleted", R: "renamed" };
|
|
186
|
-
|
|
187
|
-
for (const line of result.stdout.trim().split("\n")) {
|
|
188
|
-
const [status, ...fileParts] = line.split("\t");
|
|
189
|
-
const file = fileParts.join("\t");
|
|
190
|
-
const label = statusMap[status?.[0]] || status;
|
|
191
|
-
lines.push(`- [${label}] ${file}`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return lines.join("\n");
|
|
195
|
-
} catch {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
176
|
// ── Summary ──────────────────────────────────────────────────────────────────
|
|
201
177
|
|
|
202
178
|
export function generateSummary({ task, result, sessionId, iterations, durationMs, budget, stages, commits, files }) {
|
|
@@ -325,20 +301,7 @@ export async function writeDecisionsJournal(journalDir, decisionEntries) {
|
|
|
325
301
|
await writeJournalFile(journalDir, "decisions.md", content);
|
|
326
302
|
}
|
|
327
303
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const tree = await generateFileTree(baseRef);
|
|
333
|
-
if (tree) await writeJournalFile(journalDir, "tree.txt", tree);
|
|
334
|
-
return Boolean(tree);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Write the session summary.
|
|
339
|
-
*/
|
|
340
|
-
export async function writeSummaryJournal(journalDir, summaryData) {
|
|
341
|
-
const files = summaryData.files || [];
|
|
342
|
-
const content = generateSummary({ ...summaryData, files: [...files, "summary.md"] });
|
|
343
|
-
await writeJournalFile(journalDir, "summary.md", content);
|
|
344
|
-
}
|
|
304
|
+
// writeTreeJournal / writeSummaryJournal / generateFileTree were
|
|
305
|
+
// superseded in v2.7.x by src/session/journal/{tree-writer,summary-writer}.js
|
|
306
|
+
// (the V2 split). post-loop.js dynamically imports the V2 versions; this
|
|
307
|
+
// orchestrator-side copy is no longer wired anywhere. KJC-TSK-0354 PR-D.
|
package/src/plan/plan-store.js
CHANGED
|
@@ -114,20 +114,6 @@ export async function loadPlan(projectDir, planId) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/**
|
|
118
|
-
* Update a plan on disk (partial merge).
|
|
119
|
-
* @returns {Promise<boolean>}
|
|
120
|
-
*/
|
|
121
|
-
export async function updatePlan(projectDir, planId, patch) {
|
|
122
|
-
const plan = await loadPlan(projectDir, planId);
|
|
123
|
-
if (!plan) return false;
|
|
124
|
-
Object.assign(plan, patch);
|
|
125
|
-
plan.updatedAt = new Date().toISOString();
|
|
126
|
-
const filePath = path.join(plansDir(projectDir), `${planId}.json`);
|
|
127
|
-
await fs.writeFile(filePath, JSON.stringify(plan, null, 2), "utf8");
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
117
|
/**
|
|
132
118
|
* Delete a plan from disk.
|
|
133
119
|
* @returns {Promise<boolean>}
|
|
@@ -93,20 +93,6 @@ export async function createCard({ projectId, card, timeoutMs = DEFAULT_TIMEOUT_
|
|
|
93
93
|
return parseJsonResponse(response);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
export async function createAdr({ projectId, adr, timeoutMs = DEFAULT_TIMEOUT_MS }) {
|
|
97
|
-
const url = `${getApiUrl()}/projects/${encodeURIComponent(projectId)}/adrs`;
|
|
98
|
-
const response = await fetchWithRetry(
|
|
99
|
-
url,
|
|
100
|
-
{
|
|
101
|
-
method: "POST",
|
|
102
|
-
headers: { "Content-Type": "application/json" },
|
|
103
|
-
body: JSON.stringify(adr)
|
|
104
|
-
},
|
|
105
|
-
timeoutMs
|
|
106
|
-
);
|
|
107
|
-
return parseJsonResponse(response);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
96
|
export async function relateCards({ projectId, sourceCardId, targetCardId, relationType, timeoutMs = DEFAULT_TIMEOUT_MS }) {
|
|
111
97
|
const url = `${getApiUrl()}/projects/${encodeURIComponent(projectId)}/cards/relate`;
|
|
112
98
|
const response = await fetchWithRetry(
|
|
@@ -42,7 +42,7 @@ export async function initPgAdapter({ session: _session, config, logger, pgTaskI
|
|
|
42
42
|
* Handle PG task decomposition after triage.
|
|
43
43
|
* Creates subtask cards in PG when triage recommends decomposition.
|
|
44
44
|
*/
|
|
45
|
-
|
|
45
|
+
async function handlePgDecomposition({ triageResult, pgTaskId, pgProject, config, askQuestion, emitter, eventBase, session, stageResults, logger }) {
|
|
46
46
|
const shouldDecompose = triageResult.stageResult?.shouldDecompose
|
|
47
47
|
&& triageResult.stageResult.subtasks?.length > 1
|
|
48
48
|
&& pgTaskId
|