karajan-code 1.16.0 → 1.18.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/package.json +1 -1
- package/src/activity-log.js +13 -13
- package/src/agents/availability.js +2 -3
- package/src/agents/claude-agent.js +42 -21
- package/src/agents/model-registry.js +1 -1
- package/src/becaria/dispatch.js +1 -1
- package/src/becaria/repo.js +3 -3
- package/src/cli.js +5 -2
- package/src/commands/doctor.js +154 -108
- package/src/commands/init.js +101 -90
- package/src/commands/plan.js +1 -1
- package/src/commands/report.js +77 -71
- package/src/commands/roles.js +0 -1
- package/src/commands/run.js +2 -3
- package/src/config.js +174 -93
- package/src/git/automation.js +3 -4
- package/src/guards/intent-guard.js +123 -0
- package/src/guards/output-guard.js +158 -0
- package/src/guards/perf-guard.js +126 -0
- package/src/guards/policy-resolver.js +3 -3
- package/src/mcp/orphan-guard.js +1 -2
- package/src/mcp/progress.js +4 -3
- package/src/mcp/run-kj.js +1 -0
- package/src/mcp/server-handlers.js +242 -253
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +2 -0
- package/src/orchestrator/agent-fallback.js +1 -3
- package/src/orchestrator/iteration-stages.js +206 -170
- package/src/orchestrator/pre-loop-stages.js +200 -34
- package/src/orchestrator/solomon-rules.js +2 -2
- package/src/orchestrator.js +902 -746
- package/src/planning-game/adapter.js +23 -20
- package/src/planning-game/architect-adrs.js +45 -0
- package/src/planning-game/client.js +15 -1
- package/src/planning-game/decomposition.js +7 -5
- package/src/prompts/architect.js +88 -0
- package/src/prompts/discover.js +54 -53
- package/src/prompts/planner.js +53 -33
- package/src/prompts/triage.js +8 -16
- package/src/review/parser.js +18 -19
- package/src/review/profiles.js +2 -2
- package/src/review/schema.js +3 -3
- package/src/review/scope-filter.js +3 -4
- package/src/roles/architect-role.js +122 -0
- package/src/roles/commiter-role.js +2 -2
- package/src/roles/discover-role.js +59 -67
- package/src/roles/index.js +1 -0
- package/src/roles/planner-role.js +54 -38
- package/src/roles/refactorer-role.js +8 -7
- package/src/roles/researcher-role.js +6 -7
- package/src/roles/reviewer-role.js +4 -5
- package/src/roles/security-role.js +3 -4
- package/src/roles/solomon-role.js +6 -18
- package/src/roles/sonar-role.js +5 -1
- package/src/roles/tester-role.js +8 -5
- package/src/roles/triage-role.js +2 -2
- package/src/session-cleanup.js +29 -24
- package/src/session-store.js +1 -1
- package/src/sonar/api.js +1 -1
- package/src/sonar/manager.js +1 -1
- package/src/sonar/project-key.js +5 -5
- package/src/sonar/scanner.js +34 -65
- package/src/utils/display.js +312 -272
- package/src/utils/git.js +3 -3
- package/src/utils/logger.js +6 -1
- package/src/utils/model-selector.js +5 -5
- package/src/utils/process.js +80 -102
- package/src/utils/rate-limit-detector.js +13 -13
- package/src/utils/run-log.js +55 -52
- package/templates/kj.config.yml +33 -0
- package/templates/roles/architect.md +62 -0
- package/templates/roles/planner.md +1 -0
package/src/utils/display.js
CHANGED
|
@@ -87,308 +87,348 @@ export function printHeader({ task, config }) {
|
|
|
87
87
|
if (pipeline.security?.enabled) activeRoles.push("Security");
|
|
88
88
|
if (pipeline.solomon?.enabled) activeRoles.push(`Solomon (${config.roles?.solomon?.provider || "?"})`);
|
|
89
89
|
if (activeRoles.length > 0) {
|
|
90
|
-
|
|
90
|
+
const separator = ` ${ANSI.dim}|${ANSI.reset} `;
|
|
91
|
+
console.log(`${ANSI.bold}Pipeline:${ANSI.reset} ${activeRoles.join(separator)}`);
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
console.log(BAR);
|
|
94
95
|
console.log();
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
const icon = ICONS[event.type] || "\u2022";
|
|
99
|
-
const elapsed = event.elapsed !== undefined ? `${ANSI.dim}[${formatElapsed(event.elapsed)}]${ANSI.reset}` : "";
|
|
100
|
-
const status = event.status ? STATUS_ICON[event.status] || "" : "";
|
|
101
|
-
|
|
102
|
-
switch (event.type) {
|
|
103
|
-
case "session:start":
|
|
104
|
-
break;
|
|
105
|
-
|
|
106
|
-
case "iteration:start":
|
|
107
|
-
console.log(
|
|
108
|
-
`\n${ANSI.bold}${icon} Iteration ${event.detail?.iteration}/${event.detail?.maxIterations}${ANSI.reset} ${elapsed}`
|
|
109
|
-
);
|
|
110
|
-
break;
|
|
111
|
-
|
|
112
|
-
case "planner:start":
|
|
113
|
-
console.log(` \u251c\u2500 ${icon} Planner (${event.detail?.planner || "?"}) running...`);
|
|
114
|
-
break;
|
|
115
|
-
|
|
116
|
-
case "planner:end":
|
|
117
|
-
console.log(` \u251c\u2500 ${status} Planner completed ${elapsed}`);
|
|
118
|
-
break;
|
|
119
|
-
|
|
120
|
-
case "coder:start":
|
|
121
|
-
console.log(` \u251c\u2500 ${icon} Coder (${event.detail?.coder || "?"}) running...`);
|
|
122
|
-
break;
|
|
123
|
-
|
|
124
|
-
case "coder:end":
|
|
125
|
-
console.log(` \u251c\u2500 ${status} Coder completed ${elapsed}`);
|
|
126
|
-
break;
|
|
127
|
-
|
|
128
|
-
case "refactorer:start":
|
|
129
|
-
console.log(` \u251c\u2500 ${icon} Refactorer (${event.detail?.refactorer || "?"}) running...`);
|
|
130
|
-
break;
|
|
131
|
-
|
|
132
|
-
case "refactorer:end":
|
|
133
|
-
console.log(` \u251c\u2500 ${status} Refactorer completed ${elapsed}`);
|
|
134
|
-
break;
|
|
135
|
-
|
|
136
|
-
case "tdd:result": {
|
|
137
|
-
const tdd = event.detail || {};
|
|
138
|
-
const label = tdd.ok ? `${ANSI.green}PASS${ANSI.reset}` : `${ANSI.red}FAIL${ANSI.reset}`;
|
|
139
|
-
const files = tdd.sourceFiles !== undefined ? ` (${tdd.sourceFiles} src, ${tdd.testFiles} test)` : "";
|
|
140
|
-
console.log(` \u251c\u2500 ${icon} TDD policy: ${label}${files}`);
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
98
|
+
/* ── Helper: role start/end one-liners ───────────────────────── */
|
|
143
99
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
100
|
+
function roleStart(icon, label, provider) {
|
|
101
|
+
console.log(` \u251c\u2500 ${icon} ${label} (${provider || "?"}) running...`);
|
|
102
|
+
}
|
|
147
103
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
104
|
+
function roleEnd(status, label, elapsed) {
|
|
105
|
+
console.log(` \u251c\u2500 ${status} ${label} completed ${elapsed}`);
|
|
106
|
+
}
|
|
151
107
|
|
|
152
|
-
|
|
153
|
-
console.log(` \u251c\u2500 ${icon} SonarQube scanning...`);
|
|
154
|
-
break;
|
|
108
|
+
/* ── Helper: pass/fail stage result ─────────────────────────── */
|
|
155
109
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
110
|
+
function passFailStage(detail, label, failDefault, elapsed) {
|
|
111
|
+
if (detail?.ok === false) {
|
|
112
|
+
const summary = detail?.summary || failDefault;
|
|
113
|
+
console.log(` \u251c\u2500 ${ANSI.red}\u274c ${label}: ${summary}${ANSI.reset} ${elapsed}`);
|
|
114
|
+
} else {
|
|
115
|
+
console.log(` \u251c\u2500 ${ANSI.green}\u2705 ${label}: passed${ANSI.reset} ${elapsed}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
162
118
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
}
|
|
119
|
+
/* ── Helper: solomon ruling display ─────────────────────────── */
|
|
120
|
+
|
|
121
|
+
const SOLOMON_RULING_HANDLERS = {
|
|
122
|
+
approve(detail, elapsed) {
|
|
123
|
+
const dismissedCount = detail?.dismissed?.length || 0;
|
|
124
|
+
const dismissedSuffix = dismissedCount > 0 ? ` (${dismissedCount} dismissed)` : "";
|
|
125
|
+
console.log(` \u251c\u2500 ${ANSI.green}\u2696\ufe0f Solomon: APPROVE${dismissedSuffix}${ANSI.reset} ${elapsed}`);
|
|
126
|
+
},
|
|
127
|
+
approve_with_conditions(detail, elapsed) {
|
|
128
|
+
const condCount = detail?.conditions?.length || 0;
|
|
129
|
+
console.log(` \u251c\u2500 ${ANSI.yellow}\u2696\ufe0f Solomon: ${condCount} condition${condCount === 1 ? "" : "s"}${ANSI.reset} ${elapsed}`);
|
|
130
|
+
if (detail?.conditions) {
|
|
131
|
+
for (const cond of detail.conditions) {
|
|
132
|
+
console.log(` \u2502 ${ANSI.dim}${cond}${ANSI.reset}`);
|
|
179
133
|
}
|
|
180
|
-
break;
|
|
181
134
|
}
|
|
135
|
+
},
|
|
136
|
+
escalate_human(detail, elapsed) {
|
|
137
|
+
const reason = detail?.escalate_reason || "unknown reason";
|
|
138
|
+
console.log(` \u251c\u2500 ${ANSI.red}\u2696\ufe0f Solomon: ESCALATE \u2014 ${reason}${ANSI.reset} ${elapsed}`);
|
|
139
|
+
},
|
|
140
|
+
create_subtask(detail, elapsed) {
|
|
141
|
+
const subtaskTitle = detail?.subtask?.title || "untitled";
|
|
142
|
+
console.log(` \u251c\u2500 ${ANSI.magenta}\u2696\ufe0f Solomon: SUBTASK \u2014 ${subtaskTitle}${ANSI.reset} ${elapsed}`);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
182
145
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(` \u251c\u2500 ${ANSI.red}\u274c Tester: ${testerSummary}${ANSI.reset} ${elapsed}`);
|
|
194
|
-
}
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
146
|
+
function printSolomonRuling(detail, elapsed) {
|
|
147
|
+
const ruling = detail?.ruling || "unknown";
|
|
148
|
+
const handler = SOLOMON_RULING_HANDLERS[ruling];
|
|
149
|
+
if (handler) {
|
|
150
|
+
handler(detail, elapsed);
|
|
151
|
+
} else {
|
|
152
|
+
const rulingUpper = ruling.toUpperCase().replaceAll("_", " ");
|
|
153
|
+
console.log(` \u251c\u2500 \u2696\ufe0f Solomon: ${rulingUpper} ${elapsed}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
197
156
|
|
|
198
|
-
|
|
199
|
-
console.log(` \u251c\u2500 ${icon} Security auditing...`);
|
|
200
|
-
break;
|
|
201
|
-
|
|
202
|
-
case "security:end": {
|
|
203
|
-
const secOk = event.detail?.ok !== false;
|
|
204
|
-
if (secOk) {
|
|
205
|
-
console.log(` \u251c\u2500 ${ANSI.green}\u2705 Security: passed${ANSI.reset} ${elapsed}`);
|
|
206
|
-
} else {
|
|
207
|
-
const secSummary = event.detail?.summary || "vulnerabilities found";
|
|
208
|
-
console.log(` \u251c\u2500 ${ANSI.red}\u274c Security: ${secSummary}${ANSI.reset} ${elapsed}`);
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
157
|
+
/* ── Helper: budget color selection ─────────────────────────── */
|
|
212
158
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const ruling = event.detail?.ruling || "unknown";
|
|
219
|
-
const rulingUpper = ruling.toUpperCase().replace(/_/g, " ");
|
|
220
|
-
if (ruling === "approve") {
|
|
221
|
-
const dismissedCount = event.detail?.dismissed?.length || 0;
|
|
222
|
-
console.log(` \u251c\u2500 ${ANSI.green}\u2696\ufe0f Solomon: APPROVE${dismissedCount > 0 ? ` (${dismissedCount} dismissed)` : ""}${ANSI.reset} ${elapsed}`);
|
|
223
|
-
} else if (ruling === "approve_with_conditions") {
|
|
224
|
-
const condCount = event.detail?.conditions?.length || 0;
|
|
225
|
-
console.log(` \u251c\u2500 ${ANSI.yellow}\u2696\ufe0f Solomon: ${condCount} condition${condCount !== 1 ? "s" : ""}${ANSI.reset} ${elapsed}`);
|
|
226
|
-
if (event.detail?.conditions) {
|
|
227
|
-
for (const cond of event.detail.conditions) {
|
|
228
|
-
console.log(` \u2502 ${ANSI.dim}${cond}${ANSI.reset}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
} else if (ruling === "escalate_human") {
|
|
232
|
-
const reason = event.detail?.escalate_reason || "unknown reason";
|
|
233
|
-
console.log(` \u251c\u2500 ${ANSI.red}\u2696\ufe0f Solomon: ESCALATE \u2014 ${reason}${ANSI.reset} ${elapsed}`);
|
|
234
|
-
} else if (ruling === "create_subtask") {
|
|
235
|
-
const subtaskTitle = event.detail?.subtask?.title || "untitled";
|
|
236
|
-
console.log(` \u251c\u2500 ${ANSI.magenta}\u2696\ufe0f Solomon: SUBTASK \u2014 ${subtaskTitle}${ANSI.reset} ${elapsed}`);
|
|
237
|
-
} else {
|
|
238
|
-
console.log(` \u251c\u2500 \u2696\ufe0f Solomon: ${rulingUpper} ${elapsed}`);
|
|
239
|
-
}
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
159
|
+
function budgetColor(max, pct, warn) {
|
|
160
|
+
if (max > 0 && pct >= 100) return ANSI.red;
|
|
161
|
+
if (max > 0 && pct >= warn) return ANSI.yellow;
|
|
162
|
+
return ANSI.green;
|
|
163
|
+
}
|
|
242
164
|
|
|
243
|
-
|
|
244
|
-
const subloop = event.detail?.subloop || "?";
|
|
245
|
-
const retryCount = event.detail?.retryCount || 0;
|
|
246
|
-
const limit = event.detail?.limit || "?";
|
|
247
|
-
console.log(` \u251c\u2500 ${icon} ${subloop} sub-loop limit reached (${retryCount}/${limit}), invoking Solomon...`);
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
165
|
+
/* ── Helpers: session:end sub-sections ──────────────────────── */
|
|
250
166
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
167
|
+
function printSessionStages(stages) {
|
|
168
|
+
if (!stages) return;
|
|
169
|
+
if (stages.researcher?.summary) {
|
|
170
|
+
console.log(` ${ANSI.dim}\ud83d\udd2c Research: ${stages.researcher.summary}${ANSI.reset}`);
|
|
171
|
+
}
|
|
172
|
+
printSessionPlanner(stages.planner);
|
|
173
|
+
if (stages.tester?.summary) {
|
|
174
|
+
console.log(` ${ANSI.dim}\ud83e\uddea Tester: ${stages.tester.summary}${ANSI.reset}`);
|
|
175
|
+
}
|
|
176
|
+
if (stages.security?.summary) {
|
|
177
|
+
console.log(` ${ANSI.dim}\ud83d\udd12 Security: ${stages.security.summary}${ANSI.reset}`);
|
|
178
|
+
}
|
|
179
|
+
printSessionSonar(stages.sonar);
|
|
180
|
+
}
|
|
258
181
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
182
|
+
function printSessionPlanner(planner) {
|
|
183
|
+
if (!planner?.title && !planner?.approach && !planner?.completedSteps?.length) return;
|
|
184
|
+
const planParts = [];
|
|
185
|
+
if (planner.title) planParts.push(planner.title);
|
|
186
|
+
if (planner.approach) planParts.push(`approach: ${planner.approach}`);
|
|
187
|
+
console.log(` ${ANSI.dim}\ud83d\uddfa Plan: ${planParts.join(" | ")}${ANSI.reset}`);
|
|
188
|
+
for (const step of planner.completedSteps || []) {
|
|
189
|
+
console.log(` ${ANSI.dim} \u2713 ${step}${ANSI.reset}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
264
192
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
193
|
+
function printSessionSonar(sonar) {
|
|
194
|
+
if (!sonar) return;
|
|
195
|
+
const gateLabel = sonar.gateStatus === "OK" ? ANSI.green : ANSI.red;
|
|
196
|
+
console.log(` ${ANSI.dim}\ud83d\udd0d Sonar: ${gateLabel}${sonar.gateStatus}${ANSI.reset}${ANSI.dim} (${sonar.openIssues ?? 0} issues)${ANSI.reset}`);
|
|
197
|
+
if (typeof sonar.issuesInitial === "number" || typeof sonar.issuesResolved === "number") {
|
|
198
|
+
const issuesInitial = sonar.issuesInitial ?? sonar.openIssues ?? 0;
|
|
199
|
+
const issuesFinal = sonar.issuesFinal ?? sonar.openIssues ?? 0;
|
|
200
|
+
const issuesResolved = sonar.issuesResolved ?? Math.max(issuesInitial - issuesFinal, 0);
|
|
201
|
+
console.log(` ${ANSI.dim}\ud83d\udee0 Issues: ${issuesInitial} detected, ${issuesFinal} open, ${issuesResolved} resolved${ANSI.reset}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function printSessionGit(git) {
|
|
206
|
+
if (!git?.branch) return;
|
|
207
|
+
const parts = [`branch: ${git.branch}`];
|
|
208
|
+
if (git.committed) parts.push("committed");
|
|
209
|
+
if (git.pushed) parts.push("pushed");
|
|
210
|
+
if (git.pr || git.prUrl) parts.push(`PR: ${git.pr || git.prUrl}`);
|
|
211
|
+
console.log(` ${ANSI.dim}\ud83d\udcce Git: ${parts.join(", ")}${ANSI.reset}`);
|
|
212
|
+
if (Array.isArray(git.commits) && git.commits.length > 0) {
|
|
213
|
+
console.log(` ${ANSI.dim}\ud83e\uddfe Commits:${ANSI.reset}`);
|
|
214
|
+
for (const commit of git.commits) {
|
|
215
|
+
const shortHash = (commit.hash || "").slice(0, 7) || "unknown";
|
|
216
|
+
const message = commit.message || "";
|
|
217
|
+
console.log(` ${ANSI.dim} - ${shortHash} ${message}${ANSI.reset}`);
|
|
283
218
|
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
284
221
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
console.log(` ${ANSI.dim}\ud83d\udd2c Research: ${stages.researcher.summary}${ANSI.reset}`);
|
|
296
|
-
}
|
|
297
|
-
if (stages.planner?.title || stages.planner?.approach || stages.planner?.completedSteps?.length) {
|
|
298
|
-
const planParts = [];
|
|
299
|
-
if (stages.planner.title) planParts.push(stages.planner.title);
|
|
300
|
-
if (stages.planner.approach) planParts.push(`approach: ${stages.planner.approach}`);
|
|
301
|
-
console.log(` ${ANSI.dim}\ud83d\uddfa Plan: ${planParts.join(" | ")}${ANSI.reset}`);
|
|
302
|
-
for (const step of stages.planner.completedSteps || []) {
|
|
303
|
-
console.log(` ${ANSI.dim} \u2713 ${step}${ANSI.reset}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (stages.tester?.summary) {
|
|
307
|
-
console.log(` ${ANSI.dim}\ud83e\uddea Tester: ${stages.tester.summary}${ANSI.reset}`);
|
|
308
|
-
}
|
|
309
|
-
if (stages.security?.summary) {
|
|
310
|
-
console.log(` ${ANSI.dim}\ud83d\udd12 Security: ${stages.security.summary}${ANSI.reset}`);
|
|
311
|
-
}
|
|
312
|
-
if (stages.sonar) {
|
|
313
|
-
const gateLabel = stages.sonar.gateStatus === "OK" ? ANSI.green : ANSI.red;
|
|
314
|
-
console.log(` ${ANSI.dim}\ud83d\udd0d Sonar: ${gateLabel}${stages.sonar.gateStatus}${ANSI.reset}${ANSI.dim} (${stages.sonar.openIssues ?? 0} issues)${ANSI.reset}`);
|
|
315
|
-
if (typeof stages.sonar.issuesInitial === "number" || typeof stages.sonar.issuesResolved === "number") {
|
|
316
|
-
const issuesInitial = stages.sonar.issuesInitial ?? stages.sonar.openIssues ?? 0;
|
|
317
|
-
const issuesFinal = stages.sonar.issuesFinal ?? stages.sonar.openIssues ?? 0;
|
|
318
|
-
const issuesResolved = stages.sonar.issuesResolved ?? Math.max(issuesInitial - issuesFinal, 0);
|
|
319
|
-
console.log(` ${ANSI.dim}\ud83d\udee0 Issues: ${issuesInitial} detected, ${issuesFinal} open, ${issuesResolved} resolved${ANSI.reset}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
222
|
+
function printSessionBudget(budget) {
|
|
223
|
+
if (!budget) return;
|
|
224
|
+
console.log(` ${ANSI.dim}\ud83d\udcb0 Total tokens: ${budget.total_tokens ?? 0}${ANSI.reset}`);
|
|
225
|
+
console.log(` ${ANSI.dim}\ud83d\udcb0 Total cost: $${Number(budget.total_cost_usd || 0).toFixed(2)}${ANSI.reset}`);
|
|
226
|
+
for (const [role, metrics] of Object.entries(budget.breakdown_by_role || {})) {
|
|
227
|
+
console.log(
|
|
228
|
+
` ${ANSI.dim} - ${role}: ${metrics.total_tokens ?? 0} tokens, $${Number(metrics.total_cost_usd || 0).toFixed(2)}${ANSI.reset}`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
323
232
|
|
|
324
|
-
|
|
325
|
-
if (git?.branch) {
|
|
326
|
-
const parts = [`branch: ${git.branch}`];
|
|
327
|
-
if (git.committed) parts.push("committed");
|
|
328
|
-
if (git.pushed) parts.push("pushed");
|
|
329
|
-
if (git.pr || git.prUrl) parts.push(`PR: ${git.pr || git.prUrl}`);
|
|
330
|
-
console.log(` ${ANSI.dim}\ud83d\udcce Git: ${parts.join(", ")}${ANSI.reset}`);
|
|
331
|
-
if (Array.isArray(git.commits) && git.commits.length > 0) {
|
|
332
|
-
console.log(` ${ANSI.dim}\ud83e\uddfe Commits:${ANSI.reset}`);
|
|
333
|
-
for (const commit of git.commits) {
|
|
334
|
-
const shortHash = (commit.hash || "").slice(0, 7) || "unknown";
|
|
335
|
-
const message = commit.message || "";
|
|
336
|
-
console.log(` ${ANSI.dim} - ${shortHash} ${message}${ANSI.reset}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
233
|
+
/* ── Helper: pipeline tracker stage icon/color ──────────────── */
|
|
340
234
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
235
|
+
const TRACKER_STATUS = {
|
|
236
|
+
done: { icon: "\u2713", color: ANSI.green },
|
|
237
|
+
running: { icon: "\u25b6", color: ANSI.cyan },
|
|
238
|
+
failed: { icon: "\u2717", color: ANSI.red }
|
|
239
|
+
};
|
|
240
|
+
const TRACKER_DEFAULT = { icon: "\u00b7", color: ANSI.dim };
|
|
241
|
+
|
|
242
|
+
/* ── Event handler map ──────────────────────────────────────── */
|
|
243
|
+
|
|
244
|
+
const EVENT_HANDLERS = {
|
|
245
|
+
"session:start": () => {},
|
|
246
|
+
|
|
247
|
+
"iteration:start": (event, icon, elapsed) => {
|
|
248
|
+
console.log(
|
|
249
|
+
`\n${ANSI.bold}${icon} Iteration ${event.detail?.iteration}/${event.detail?.maxIterations}${ANSI.reset} ${elapsed}`
|
|
250
|
+
);
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
"planner:start": (event, icon) => {
|
|
254
|
+
roleStart(icon, "Planner", event.detail?.planner);
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
"planner:end": (_event, _icon, elapsed, status) => {
|
|
258
|
+
roleEnd(status, "Planner", elapsed);
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
"coder:start": (event, icon) => {
|
|
262
|
+
roleStart(icon, "Coder", event.detail?.coder);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
"coder:end": (_event, _icon, elapsed, status) => {
|
|
266
|
+
roleEnd(status, "Coder", elapsed);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
"refactorer:start": (event, icon) => {
|
|
270
|
+
roleStart(icon, "Refactorer", event.detail?.refactorer);
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
"refactorer:end": (_event, _icon, elapsed, status) => {
|
|
274
|
+
roleEnd(status, "Refactorer", elapsed);
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
"tdd:result": (event, icon) => {
|
|
278
|
+
const tdd = event.detail || {};
|
|
279
|
+
const label = tdd.ok ? `${ANSI.green}PASS${ANSI.reset}` : `${ANSI.red}FAIL${ANSI.reset}`;
|
|
280
|
+
const files = tdd.sourceFiles === undefined ? "" : ` (${tdd.sourceFiles} src, ${tdd.testFiles} test)`;
|
|
281
|
+
console.log(` \u251c\u2500 ${icon} TDD policy: ${label}${files}`);
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
"researcher:start": (event, icon) => {
|
|
285
|
+
console.log(` \u251c\u2500 ${icon} Researcher (${event.detail?.researcher || "?"}) investigating...`);
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
"researcher:end": (_event, _icon, elapsed, status) => {
|
|
289
|
+
roleEnd(status, "Researcher", elapsed);
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
"sonar:start": (_event, icon) => {
|
|
293
|
+
console.log(` \u251c\u2500 ${icon} SonarQube scanning...`);
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
"sonar:end": (event, _icon, elapsed, status) => {
|
|
297
|
+
const gate = event.detail?.gateStatus || "?";
|
|
298
|
+
const gateColor = gate === "OK" ? ANSI.green : ANSI.red;
|
|
299
|
+
console.log(` \u251c\u2500 ${status} Quality gate: ${gateColor}${gate}${ANSI.reset} ${elapsed}`);
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
"reviewer:start": (event, icon) => {
|
|
303
|
+
console.log(` \u251c\u2500 ${icon} Reviewer (${event.detail?.reviewer || "?"}) running...`);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
"reviewer:end": (event, _icon, elapsed) => {
|
|
307
|
+
const review = event.detail || {};
|
|
308
|
+
if (review.approved) {
|
|
309
|
+
console.log(` \u251c\u2500 ${ANSI.green}\u2705 Review: APPROVED${ANSI.reset} ${elapsed}`);
|
|
310
|
+
} else {
|
|
311
|
+
const count = review.blockingCount || 0;
|
|
312
|
+
console.log(` \u251c\u2500 ${ANSI.red}\u274c Review: REJECTED (${count} blocking)${ANSI.reset}`);
|
|
313
|
+
if (review.issues) {
|
|
314
|
+
for (const issue of review.issues) {
|
|
315
|
+
console.log(` \u2502 ${ANSI.dim}${issue}${ANSI.reset}`);
|
|
353
316
|
}
|
|
354
317
|
}
|
|
355
|
-
|
|
356
|
-
console.log(`${ANSI.dim}Session: ${event.sessionId}${ANSI.reset}`);
|
|
357
|
-
break;
|
|
358
318
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
"tester:start": (_event, icon) => {
|
|
322
|
+
console.log(` \u251c\u2500 ${icon} Tester evaluating...`);
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
"tester:end": (event, _icon, elapsed) => {
|
|
326
|
+
passFailStage(event.detail, "Tester", "issues found", elapsed);
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
"security:start": (_event, icon) => {
|
|
330
|
+
console.log(` \u251c\u2500 ${icon} Security auditing...`);
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
"security:end": (event, _icon, elapsed) => {
|
|
334
|
+
passFailStage(event.detail, "Security", "vulnerabilities found", elapsed);
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
"solomon:start": (event, icon) => {
|
|
338
|
+
console.log(` \u251c\u2500 ${icon} Solomon arbitrating ${event.detail?.conflictStage || "?"} conflict...`);
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
"solomon:end": (event, _icon, elapsed) => {
|
|
342
|
+
printSolomonRuling(event.detail, elapsed);
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
"solomon:escalate": (event, icon) => {
|
|
346
|
+
const subloop = event.detail?.subloop || "?";
|
|
347
|
+
const retryCount = event.detail?.retryCount || 0;
|
|
348
|
+
const limit = event.detail?.limit || "?";
|
|
349
|
+
console.log(` \u251c\u2500 ${icon} ${subloop} sub-loop limit reached (${retryCount}/${limit}), invoking Solomon...`);
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
"coder:standby": (event, icon) => {
|
|
353
|
+
const until = event.detail?.cooldownUntil || "?";
|
|
354
|
+
const attempt = event.detail?.retryCount || "?";
|
|
355
|
+
const maxRetries = event.detail?.maxRetries || "?";
|
|
356
|
+
console.log(` \u251c\u2500 ${ANSI.yellow}${icon} Rate limited \u2014 standby until ${until} (attempt ${attempt}/${maxRetries})${ANSI.reset}`);
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
"coder:standby_heartbeat": (event, icon) => {
|
|
360
|
+
const remaining = event.detail?.remainingMs === undefined ? "?" : Math.round(event.detail.remainingMs / 1000);
|
|
361
|
+
console.log(` \u251c\u2500 ${ANSI.yellow}${icon} Standby: ${remaining}s remaining${ANSI.reset}`);
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
"coder:standby_resume": (event, icon) => {
|
|
365
|
+
console.log(` \u251c\u2500 ${ANSI.green}${icon} Cooldown expired \u2014 resuming with ${event.detail?.coder || event.detail?.provider || "?"}${ANSI.reset}`);
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
"iteration:end": (event, icon, elapsed) => {
|
|
369
|
+
console.log(` \u2514\u2500 ${icon} Duration: ${formatElapsed(event.detail?.duration)} ${elapsed}`);
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
"budget:update": (event, icon) => {
|
|
373
|
+
const total = Number(event.detail?.total_cost_usd || 0);
|
|
374
|
+
const max = Number(event.detail?.max_budget_usd);
|
|
375
|
+
const pct = Number(event.detail?.pct_used ?? 0);
|
|
376
|
+
const warn = Number(event.detail?.warn_threshold_pct ?? 80);
|
|
377
|
+
const color = budgetColor(max, pct, warn);
|
|
378
|
+
if (Number.isFinite(max) && max >= 0) {
|
|
379
|
+
console.log(` \u251c\u2500 ${icon} Budget: ${color}$${total.toFixed(2)} / $${max.toFixed(2)} (${pct.toFixed(1)}%)${ANSI.reset}`);
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
"session:end": (event, icon, elapsed) => {
|
|
384
|
+
console.log();
|
|
385
|
+
const resultLabel = event.detail?.approved
|
|
386
|
+
? `${ANSI.bold}${ANSI.green}APPROVED${ANSI.reset}`
|
|
387
|
+
: `${ANSI.bold}${ANSI.red}${event.detail?.reason || "FAILED"}${ANSI.reset}`;
|
|
388
|
+
console.log(`${icon} Result: ${resultLabel} ${elapsed}`);
|
|
389
|
+
printSessionStages(event.detail?.stages);
|
|
390
|
+
printSessionGit(event.detail?.git);
|
|
391
|
+
printSessionBudget(event.detail?.budget);
|
|
392
|
+
console.log(`${ANSI.dim}Session: ${event.sessionId}${ANSI.reset}`);
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
question: (event, icon) => {
|
|
396
|
+
console.log();
|
|
397
|
+
console.log(`${ANSI.bold}${ANSI.yellow}${icon} Paused - question:${ANSI.reset}`);
|
|
398
|
+
console.log(` ${event.detail?.question || event.message}`);
|
|
399
|
+
console.log(`${ANSI.dim}Resume with: kj resume ${event.sessionId} --answer "<response>"${ANSI.reset}`);
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
"pipeline:tracker": (event) => {
|
|
403
|
+
const trackerStages = event.detail?.stages || [];
|
|
404
|
+
console.log(` ${ANSI.dim}\u250c Pipeline${ANSI.reset}`);
|
|
405
|
+
for (const stage of trackerStages) {
|
|
406
|
+
const { icon: stIcon, color: stColor } = TRACKER_STATUS[stage.status] || TRACKER_DEFAULT;
|
|
407
|
+
let suffix = "";
|
|
408
|
+
if (stage.summary) {
|
|
409
|
+
suffix = stage.status === "running" ? ` (${stage.summary})` : ` \u2192 ${stage.summary}`;
|
|
382
410
|
}
|
|
383
|
-
console.log(` ${ANSI.dim}\
|
|
384
|
-
break;
|
|
411
|
+
console.log(` ${ANSI.dim}\u2502${ANSI.reset} ${stColor}${stIcon} ${stage.name}${suffix}${ANSI.reset}`);
|
|
385
412
|
}
|
|
413
|
+
console.log(` ${ANSI.dim}\u2514${ANSI.reset}`);
|
|
414
|
+
},
|
|
386
415
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
416
|
+
"agent:output": (event) => {
|
|
417
|
+
console.log(` \u2502 ${ANSI.dim}${event.message}${ANSI.reset}`);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
/* ── Main entry point ───────────────────────────────────────── */
|
|
422
|
+
|
|
423
|
+
export function printEvent(event) {
|
|
424
|
+
const icon = ICONS[event.type] || "\u2022";
|
|
425
|
+
const elapsed = event.elapsed === undefined ? "" : `${ANSI.dim}[${formatElapsed(event.elapsed)}]${ANSI.reset}`;
|
|
426
|
+
const status = event.status ? STATUS_ICON[event.status] || "" : "";
|
|
390
427
|
|
|
391
|
-
|
|
392
|
-
|
|
428
|
+
const handler = EVENT_HANDLERS[event.type];
|
|
429
|
+
if (handler) {
|
|
430
|
+
handler(event, icon, elapsed, status);
|
|
431
|
+
} else {
|
|
432
|
+
console.log(` \u251c\u2500 ${icon} ${event.message || event.type} ${elapsed}`);
|
|
393
433
|
}
|
|
394
434
|
}
|
package/src/utils/git.js
CHANGED
|
@@ -3,8 +3,8 @@ import { runCommand } from "./process.js";
|
|
|
3
3
|
function slugifyTask(task) {
|
|
4
4
|
return String(task)
|
|
5
5
|
.toLowerCase()
|
|
6
|
-
.
|
|
7
|
-
.
|
|
6
|
+
.replaceAll(/[^a-z0-9]+/g, "-")
|
|
7
|
+
.replaceAll(/(^-+)|(-+$)/g, "")
|
|
8
8
|
.slice(0, 40);
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -68,7 +68,7 @@ export async function createBranch(branchName) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export function buildBranchName(prefix, task) {
|
|
71
|
-
const stamp = new Date().toISOString().
|
|
71
|
+
const stamp = new Date().toISOString().replaceAll(/[:.]/g, "-").slice(0, 16);
|
|
72
72
|
return `${prefix}${slugifyTask(task) || "task"}-${stamp}`;
|
|
73
73
|
}
|
|
74
74
|
|
package/src/utils/logger.js
CHANGED
|
@@ -58,7 +58,12 @@ export function createLogger(level = "info", mode = "cli") {
|
|
|
58
58
|
const ts = `${ANSI.dim}${timestamp()}${ANSI.reset}`;
|
|
59
59
|
const prefix = `${color}[${lvl}]${ANSI.reset}`;
|
|
60
60
|
const ctx = formatContext(context);
|
|
61
|
-
|
|
61
|
+
let stream = console.log;
|
|
62
|
+
if (lvl === "error") {
|
|
63
|
+
stream = console.error;
|
|
64
|
+
} else if (lvl === "warn") {
|
|
65
|
+
stream = console.warn;
|
|
66
|
+
}
|
|
62
67
|
stream(`${ts} ${prefix} ${ctx}${args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" ")}`);
|
|
63
68
|
}
|
|
64
69
|
|