karajan-code 2.7.4 → 2.8.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/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 +1 -1
- package/src/checks/ports.js +85 -31
- package/src/checks/sonar.js +1 -1
- package/src/checks/system.js +1 -1
- package/src/cli/_shared.js +30 -0
- package/src/cli/register-meta.js +204 -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 +27 -3
- 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/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 +79 -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 +10 -10
- package/src/orchestrator/config-init.js +25 -7
- package/src/orchestrator/direct-actions.js +6 -2
- 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 +311 -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 +434 -0
- package/src/orchestrator/drivers/run-hu-batch.js +367 -0
- 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 +88 -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/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 +86 -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 -3
- package/src/plan/spec-detector.js +61 -0
- package/src/plan/tests-synthesizer.js +116 -0
- package/src/planning-game/client.js +3 -14
- package/src/planning-game/decomposition.js +1 -1
- package/src/planning-game/pipeline-adapter.js +36 -5
- package/src/prompts/coder.js +172 -1
- package/src/prompts/hu-reviewer.js +0 -1
- package/src/prompts/planner.js +81 -4
- package/src/review/diff-generator.js +35 -22
- package/src/review/snapshot-diff.js +1 -1
- 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/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/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/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
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-loop driver — extracted from src/orchestrator/flow-runner.js in
|
|
3
|
+
* TSK-0335 (Oleada 3 of the v2.7.4 audit refactor).
|
|
4
|
+
*
|
|
5
|
+
* Handlers for everything that happens AFTER the iteration loop decides
|
|
6
|
+
* an outcome (approved, max-iterations, or simple finalization):
|
|
7
|
+
*
|
|
8
|
+
* - handlePostLoopStages: run tester → security → final audit
|
|
9
|
+
* after the reviewer has approved; any of
|
|
10
|
+
* them can demand one more coder pass.
|
|
11
|
+
* - finalizeApprovedSession: commit/push/PR + PG card transition +
|
|
12
|
+
* full session journal + session:end event.
|
|
13
|
+
* - handleMaxIterationsReached: Brain-owned decision at the iteration
|
|
14
|
+
* cap (extend vs finalize vs escalate).
|
|
15
|
+
* Legacy path delegates to Solomon.
|
|
16
|
+
* - tryAutoStartBoard: auto-launch HU Board when config opts in.
|
|
17
|
+
* - writeHistoryRecord: non-blocking HU history write on session end.
|
|
18
|
+
*
|
|
19
|
+
* Extracted verbatim; no behavior change. flow-runner.js now imports these
|
|
20
|
+
* instead of defining them. Part of breaking up the 2254-LOC god-module —
|
|
21
|
+
* see docs/ARCHITECTURE.md and the TSK-0335 acceptance criteria.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { generateDiff } from "../../review/diff-generator.js";
|
|
25
|
+
import { finalizeGitAutomation } from "../../git/automation.js";
|
|
26
|
+
import { saveSession, markSessionStatus } from "../../session/store.js";
|
|
27
|
+
import {
|
|
28
|
+
setReviewerFeedback, setBudget, setRtkSavings, resetRetryCount,
|
|
29
|
+
} from "../../session/mutators.js";
|
|
30
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
31
|
+
import { runTesterStage, runSecurityStage, runFinalAuditStage } from "../post-loop-stages.js";
|
|
32
|
+
import { invokeSolomon } from "../solomon-escalation.js";
|
|
33
|
+
import { tryCiComment } from "../ci-integration.js";
|
|
34
|
+
import { writeIterationsJournal } from "../session-journal.js";
|
|
35
|
+
import { getIntegration } from "../integrations.js";
|
|
36
|
+
|
|
37
|
+
export async function handlePostLoopStages({ config, session, emitter, eventBase, coderRole, trackBudget, i, task, stageResults, ciEnabled: _ciEnabled, testerEnabled, securityEnabled, askQuestion, logger, brainCtx }) {
|
|
38
|
+
const postLoopDiff = await generateDiff({ baseRef: session.session_start_sha });
|
|
39
|
+
|
|
40
|
+
if (testerEnabled) {
|
|
41
|
+
const testerResult = await runTesterStage({
|
|
42
|
+
config, logger, emitter, eventBase, session, coderRole, trackBudget,
|
|
43
|
+
iteration: i, task, diff: postLoopDiff, askQuestion
|
|
44
|
+
});
|
|
45
|
+
if (testerResult.action === "pause") return { action: "return", result: testerResult.result };
|
|
46
|
+
if (testerResult.action === "continue") {
|
|
47
|
+
const summary = testerResult.stageResult?.summary || "Tester found issues";
|
|
48
|
+
setReviewerFeedback(session, `Tester FAILED — fix these issues:\n${summary}`);
|
|
49
|
+
await saveSession(session);
|
|
50
|
+
if (testerResult.stageResult) stageResults.tester = testerResult.stageResult;
|
|
51
|
+
// Brain: push tester failure into feedback queue + compress for next coder iteration
|
|
52
|
+
if (brainCtx?.enabled) {
|
|
53
|
+
const { processRoleOutput } = await import("../brain-coordinator.js");
|
|
54
|
+
processRoleOutput(brainCtx, { roleName: "tester", output: testerResult.stageResult, iteration: i });
|
|
55
|
+
}
|
|
56
|
+
return { action: "continue" };
|
|
57
|
+
}
|
|
58
|
+
if (testerResult.stageResult) {
|
|
59
|
+
stageResults.tester = testerResult.stageResult;
|
|
60
|
+
await tryCiComment({ config, session, logger, agent: "Tester", body: `Tests: ${testerResult.stageResult.summary || "completed"}` });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (securityEnabled) {
|
|
65
|
+
const securityResult = await runSecurityStage({
|
|
66
|
+
config, logger, emitter, eventBase, session, coderRole, trackBudget,
|
|
67
|
+
iteration: i, task, diff: postLoopDiff, askQuestion, brainCtx
|
|
68
|
+
});
|
|
69
|
+
if (securityResult.action === "pause") return { action: "return", result: securityResult.result };
|
|
70
|
+
if (securityResult.action === "continue") {
|
|
71
|
+
const summary = securityResult.stageResult?.summary || "Security found issues";
|
|
72
|
+
setReviewerFeedback(session, `Security FAILED — fix these issues:\n${summary}`);
|
|
73
|
+
await saveSession(session);
|
|
74
|
+
if (securityResult.stageResult) stageResults.security = securityResult.stageResult;
|
|
75
|
+
// Brain: push security findings into feedback queue + compress for next coder iteration
|
|
76
|
+
if (brainCtx?.enabled) {
|
|
77
|
+
const { processRoleOutput } = await import("../brain-coordinator.js");
|
|
78
|
+
processRoleOutput(brainCtx, { roleName: "security", output: securityResult.stageResult, iteration: i });
|
|
79
|
+
}
|
|
80
|
+
return { action: "continue" };
|
|
81
|
+
}
|
|
82
|
+
if (securityResult.stageResult) {
|
|
83
|
+
stageResults.security = securityResult.stageResult;
|
|
84
|
+
await tryCiComment({ config, session, logger, agent: "Security", body: `Security scan: ${securityResult.stageResult.summary || "completed"}` });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Final audit — last quality gate before declaring success
|
|
89
|
+
const auditResult = await runFinalAuditStage({
|
|
90
|
+
config, logger, emitter, eventBase, session, coderRole, trackBudget,
|
|
91
|
+
iteration: i, task, diff: postLoopDiff
|
|
92
|
+
});
|
|
93
|
+
if (auditResult.stageResult) {
|
|
94
|
+
stageResults.audit = auditResult.stageResult;
|
|
95
|
+
await tryCiComment({ config, session, logger, agent: "Audit", body: `Final audit: ${auditResult.stageResult.summary || "completed"}` });
|
|
96
|
+
}
|
|
97
|
+
if (auditResult.action === "retry") {
|
|
98
|
+
// Audit found actionable issues — loop back to coder
|
|
99
|
+
setReviewerFeedback(session, auditResult.feedback);
|
|
100
|
+
await saveSession(session);
|
|
101
|
+
return { action: "continue" };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { action: "proceed" };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function finalizeApprovedSession({ config, gitCtx, task, logger, session, stageResults, emitter, eventBase, budgetSummary, pgCard, pgProject, review, i, rtkTracker }) {
|
|
108
|
+
const gitResult = await finalizeGitAutomation({ config, gitCtx, task, logger, session, stageResults });
|
|
109
|
+
|
|
110
|
+
// Accumulate final commits for PG card lifecycle tracking
|
|
111
|
+
if (gitResult?.commits?.length) {
|
|
112
|
+
const tracker = getIntegration("tracker");
|
|
113
|
+
for (const c of gitResult.commits) tracker?.onCommit?.(session, c);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (stageResults.planner?.ok) {
|
|
117
|
+
stageResults.planner.completedSteps = [...(stageResults.planner.steps ?? [])];
|
|
118
|
+
}
|
|
119
|
+
setBudget(session, budgetSummary());
|
|
120
|
+
await markSessionStatus(session, "approved");
|
|
121
|
+
|
|
122
|
+
await getIntegration("tracker")?.onSessionApproved?.({ pgCard, pgProject, config, session, gitResult, logger });
|
|
123
|
+
|
|
124
|
+
const deferredIssues = session.deferred_issues || [];
|
|
125
|
+
const rtkSavings = rtkTracker?.hasData() ? rtkTracker.summary() : undefined;
|
|
126
|
+
if (rtkSavings) setRtkSavings(session, rtkSavings);
|
|
127
|
+
await saveSession(session);
|
|
128
|
+
|
|
129
|
+
// --- Journal: write final files ---
|
|
130
|
+
const journalDir = session._journalDir;
|
|
131
|
+
if (journalDir) {
|
|
132
|
+
try {
|
|
133
|
+
await writeIterationsJournal(journalDir, session._journalIterations || []);
|
|
134
|
+
// decisions.md: writes only when Solomon was invoked (AC of KJC-TSK-0287).
|
|
135
|
+
// Pulls structured data from session.solomonRulings + session.brainDecisions.
|
|
136
|
+
const { writeDecisionsJournal: writeDecisionsJournalV2 } =
|
|
137
|
+
await import("../../session/journal/decisions-writer.js");
|
|
138
|
+
const decisionsResult = await writeDecisionsJournalV2(session, { journalDir, logger });
|
|
139
|
+
// KJC-TSK-0288: use the new tree-writer with directory grouping
|
|
140
|
+
const { writeTreeJournal: writeTreeJournalV2 } =
|
|
141
|
+
await import("../../session/journal/tree-writer.js");
|
|
142
|
+
const treeResult = await writeTreeJournalV2(journalDir, session.session_start_sha, { logger });
|
|
143
|
+
|
|
144
|
+
const journalFiles = [...(session._journalFiles || [])];
|
|
145
|
+
if (session._journalIterations?.length) journalFiles.push("iterations.md");
|
|
146
|
+
if (decisionsResult.written) journalFiles.push("decisions.md");
|
|
147
|
+
if (treeResult.written) journalFiles.push("tree.txt");
|
|
148
|
+
|
|
149
|
+
// KJC-TSK-0289: use the richer summary writer (stages table, budget
|
|
150
|
+
// breakdown, brain/solomon counts, typed links to other journal files).
|
|
151
|
+
const { writeSummaryJournal: writeSummaryJournalV2 } =
|
|
152
|
+
await import("../../session/journal/summary-writer.js");
|
|
153
|
+
const startedAt = session._startedAt ? new Date(session._startedAt).toISOString() : undefined;
|
|
154
|
+
await writeSummaryJournalV2(journalDir, {
|
|
155
|
+
task: session.task,
|
|
156
|
+
result: "APPROVED",
|
|
157
|
+
sessionId: session.id,
|
|
158
|
+
iterations: i,
|
|
159
|
+
durationMs: Date.now() - (session._startedAt || Date.now()),
|
|
160
|
+
budget: budgetSummary(),
|
|
161
|
+
stages: stageResults,
|
|
162
|
+
commits: gitResult?.commits || [],
|
|
163
|
+
files: journalFiles,
|
|
164
|
+
startedAt,
|
|
165
|
+
finishedAt: new Date().toISOString(),
|
|
166
|
+
brainDecisions: Array.isArray(session.brainDecisions) ? session.brainDecisions.length : 0,
|
|
167
|
+
solomonInvocations: Array.isArray(session.solomonRulings) ? session.solomonRulings.length : 0,
|
|
168
|
+
// KJC-TSK-0327: surface skill-source decisions in the summary so the
|
|
169
|
+
// post-mortem shows which process skills (addyosmani) and stack skills
|
|
170
|
+
// (OpenSkills) actually shaped the role prompts.
|
|
171
|
+
skills: {
|
|
172
|
+
addyosmani: session.addyosmaniSkills,
|
|
173
|
+
installed: session.autoInstalledSkills,
|
|
174
|
+
recommended: session.skillsRecommended,
|
|
175
|
+
},
|
|
176
|
+
}, { logger });
|
|
177
|
+
logger.info(`Session journal written to ${journalDir}`);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
logger.warn(`Journal write failed (non-blocking): ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const endDetail = { approved: true, iterations: i, stages: stageResults, git: gitResult, budget: budgetSummary(), deferredIssues };
|
|
184
|
+
if (rtkSavings) endDetail.rtk_savings = rtkSavings;
|
|
185
|
+
emitProgress(
|
|
186
|
+
emitter,
|
|
187
|
+
makeEvent("session:end", { ...eventBase, stage: "done" }, {
|
|
188
|
+
message: deferredIssues.length > 0
|
|
189
|
+
? `Session approved (${deferredIssues.length} deferred issue(s) tracked as tech debt)`
|
|
190
|
+
: "Session approved",
|
|
191
|
+
detail: endDetail
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
const result = { approved: true, sessionId: session.id, review, git: gitResult, deferredIssues };
|
|
195
|
+
if (rtkSavings) result.rtk_savings = rtkSavings;
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function handleMaxIterationsReached({ session, budgetSummary, emitter, eventBase, config, stageResults, logger, askQuestion, task, rtkTracker, brainCtx }) {
|
|
200
|
+
const budget = budgetSummary();
|
|
201
|
+
|
|
202
|
+
// Brain-owned decision: max_iterations is guidance, not a hard rule.
|
|
203
|
+
// Brain evaluates the feedback queue state to decide extend / finalize / escalate.
|
|
204
|
+
// Solomon is only consulted if Brain cannot decide on its own.
|
|
205
|
+
if (brainCtx?.enabled) {
|
|
206
|
+
const entries = brainCtx.feedbackQueue?.entries || [];
|
|
207
|
+
const pending = entries.map(e => ({ source: e.source, category: e.category, severity: e.severity, description: e.description }));
|
|
208
|
+
const hasSecurity = entries.some(e => e.category === "security" || e.source === "security");
|
|
209
|
+
const hasCorrectness = entries.some(e => ["correctness", "tests"].includes(e.category));
|
|
210
|
+
|
|
211
|
+
if (hasSecurity) {
|
|
212
|
+
// Brain: security issues unresolved → cannot finalize, escalate
|
|
213
|
+
logger.warn(`Brain: max_iterations reached with ${entries.filter(e => e.category === "security" || e.source === "security").length} unresolved security issue(s) — cannot finalize`);
|
|
214
|
+
return { paused: true, sessionId: session.id, question: "Brain: unresolved security issues at max_iterations. Review manually or extend pipeline.", context: "brain_security_block", pending };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (hasCorrectness) {
|
|
218
|
+
// Brain: correctness/test issues pending. Cap at MAX_EXTENSIONS to avoid infinite extensions.
|
|
219
|
+
const MAX_EXTENSIONS = 2;
|
|
220
|
+
if (brainCtx.extensionCount >= MAX_EXTENSIONS) {
|
|
221
|
+
logger.warn(`Brain: ${brainCtx.extensionCount} extensions exhausted with correctness issues still pending — escalating to human`);
|
|
222
|
+
return { paused: true, sessionId: session.id, question: `Brain exhausted ${MAX_EXTENSIONS} extensions with correctness/tests still pending. Manual intervention needed.`, context: "brain_extension_cap", pending };
|
|
223
|
+
}
|
|
224
|
+
brainCtx.extensionCount += 1;
|
|
225
|
+
logger.info(`Brain: max_iterations reached with ${entries.filter(e => ["correctness", "tests"].includes(e.category)).length} correctness issue(s) pending — extending iterations (extension ${brainCtx.extensionCount}/${MAX_EXTENSIONS})`);
|
|
226
|
+
resetRetryCount(session, "reviewer");
|
|
227
|
+
await saveSession(session);
|
|
228
|
+
return { approved: false, sessionId: session.id, reason: "max_iterations_extended", extraIterations: Math.ceil(config.max_iterations / 2) };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (entries.length === 0) {
|
|
232
|
+
// Brain: no pending feedback → last reviewer approved, finalize
|
|
233
|
+
logger.info("Brain: max_iterations reached with clean feedback queue — finalizing as approved");
|
|
234
|
+
return { approved: true, sessionId: session.id, reason: "brain_approved" };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// hasStyleOnly: genuine dilemma → Brain consults Solomon
|
|
238
|
+
logger.info(`Brain: max_iterations with ${entries.length} style-only issue(s) — consulting Solomon on dilemma`);
|
|
239
|
+
const solomonResult = await invokeSolomon({
|
|
240
|
+
config, logger, emitter, eventBase, stage: "max_iterations", askQuestion, session,
|
|
241
|
+
iteration: config.max_iterations,
|
|
242
|
+
conflict: {
|
|
243
|
+
stage: "brain-max-iterations",
|
|
244
|
+
task,
|
|
245
|
+
iterationCount: config.max_iterations,
|
|
246
|
+
maxIterations: config.max_iterations,
|
|
247
|
+
budget_usd: budget?.total_cost_usd || 0,
|
|
248
|
+
dilemma: `Max iterations reached with ${entries.length} style-only issue(s) pending. Accept as-is or request more work?`,
|
|
249
|
+
pendingIssues: pending,
|
|
250
|
+
history: [{ agent: "pipeline", feedback: session.last_reviewer_feedback || "Max iterations reached" }]
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// Brain applies Solomon's decision
|
|
254
|
+
if (solomonResult.action === "approve") {
|
|
255
|
+
logger.info("Brain: Solomon advised approve for style-only pending — finalizing");
|
|
256
|
+
return { approved: true, sessionId: session.id, reason: "brain_solomon_approved" };
|
|
257
|
+
}
|
|
258
|
+
if (solomonResult.action === "continue") {
|
|
259
|
+
return { approved: false, sessionId: session.id, reason: "max_iterations_extended", extraIterations: solomonResult.extraIterations || Math.ceil(config.max_iterations / 2) };
|
|
260
|
+
}
|
|
261
|
+
if (solomonResult.action === "pause") {
|
|
262
|
+
return { paused: true, sessionId: session.id, question: solomonResult.question, context: "brain_solomon_dilemma" };
|
|
263
|
+
}
|
|
264
|
+
// Fallback: escalate to human
|
|
265
|
+
return { paused: true, sessionId: session.id, question: `Brain+Solomon cannot resolve: ${entries.length} pending issue(s) at max_iterations`, context: "max_iterations" };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Legacy path (Brain disabled): original Solomon-driven flow
|
|
269
|
+
const solomonResult = await invokeSolomon({
|
|
270
|
+
config, logger, emitter, eventBase, stage: "max_iterations", askQuestion, session,
|
|
271
|
+
iteration: config.max_iterations,
|
|
272
|
+
conflict: {
|
|
273
|
+
stage: "max_iterations",
|
|
274
|
+
task,
|
|
275
|
+
iterationCount: config.max_iterations,
|
|
276
|
+
maxIterations: config.max_iterations,
|
|
277
|
+
budget_usd: budget?.total_cost_usd || 0,
|
|
278
|
+
history: [{ agent: "pipeline", feedback: session.last_reviewer_feedback || "Max iterations reached without reviewer approval" }]
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (solomonResult.action === "approve") {
|
|
283
|
+
logger.info("Solomon approved coder's work at max_iterations checkpoint");
|
|
284
|
+
return { approved: true, sessionId: session.id, reason: "solomon_approved" };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (solomonResult.action === "continue") {
|
|
288
|
+
if (solomonResult.humanGuidance) {
|
|
289
|
+
setReviewerFeedback(session, `Solomon guidance: ${solomonResult.humanGuidance}`);
|
|
290
|
+
}
|
|
291
|
+
resetRetryCount(session, "reviewer");
|
|
292
|
+
await saveSession(session);
|
|
293
|
+
const extraIterations = solomonResult.extraIterations || config.max_iterations;
|
|
294
|
+
return { approved: false, sessionId: session.id, reason: "max_iterations_extended", humanGuidance: solomonResult.humanGuidance, extraIterations };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (solomonResult.action === "pause") {
|
|
298
|
+
return { paused: true, sessionId: session.id, question: solomonResult.question, context: "max_iterations" };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (solomonResult.action === "subtask") {
|
|
302
|
+
return { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "max_iterations_subtask" };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Solomon couldn't resolve — fail
|
|
306
|
+
setBudget(session, budgetSummary());
|
|
307
|
+
const rtkSavings = rtkTracker?.hasData() ? rtkTracker.summary() : undefined;
|
|
308
|
+
if (rtkSavings) setRtkSavings(session, rtkSavings);
|
|
309
|
+
await markSessionStatus(session, "failed");
|
|
310
|
+
const failDetail = { approved: false, reason: "max_iterations", iterations: config.max_iterations, stages: stageResults, budget: budgetSummary() };
|
|
311
|
+
if (rtkSavings) failDetail.rtk_savings = rtkSavings;
|
|
312
|
+
emitProgress(
|
|
313
|
+
emitter,
|
|
314
|
+
makeEvent("session:end", { ...eventBase, stage: "done" }, {
|
|
315
|
+
status: "fail",
|
|
316
|
+
message: "Max iterations reached (Solomon could not resolve)",
|
|
317
|
+
detail: failDetail
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
return { approved: false, sessionId: session.id, reason: "max_iterations" };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function tryAutoStartBoard(config, logger, emitter, eventBase) {
|
|
324
|
+
// TSK-0273: gate on hu_board.auto_start alone. The previous double-gate
|
|
325
|
+
// (enabled && auto_start) was confusing because `enabled` was never the
|
|
326
|
+
// user-facing switch — only auto_start was. This matches the AC: "kj run
|
|
327
|
+
// auto-inicia el board server si hu_board.auto_start es true".
|
|
328
|
+
if (!config.hu_board?.auto_start) return;
|
|
329
|
+
// Never auto-start during vitest: races the PID file and starts a detached
|
|
330
|
+
// process that outlives the test run.
|
|
331
|
+
if (process.env.VITEST || process.env.NODE_ENV === "test") return;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const { startBoard, renderBoardBanner } = await import("../../commands/board.js");
|
|
335
|
+
const { projectSlug: slugFor } = await import("../../plan/plan-store.js");
|
|
336
|
+
const boardPort = config.hu_board.port || 4000;
|
|
337
|
+
// Scope the board URL to the current run's project (`/p/<slug>`) so
|
|
338
|
+
// the user lands on a filtered view, not the global dashboard.
|
|
339
|
+
const slug = config.projectDir ? slugFor(config.projectDir) : null;
|
|
340
|
+
const boardResult = await startBoard(boardPort, { projectSlug: slug });
|
|
341
|
+
const status = boardResult.alreadyRunning ? "already running" : "started";
|
|
342
|
+
// Highlighted URL box — visible regardless of log level (matches the
|
|
343
|
+
// post-planner auto-HU banner so the user gets the same signal either
|
|
344
|
+
// way the board got started).
|
|
345
|
+
console.log(renderBoardBanner({ url: boardResult.url, status }));
|
|
346
|
+
logger.info(`HU Board ${status} at ${boardResult.url}`);
|
|
347
|
+
emitProgress(emitter, makeEvent("board:started", eventBase, {
|
|
348
|
+
message: `HU Board running at ${boardResult.url}`,
|
|
349
|
+
detail: { pid: boardResult.pid, port: boardPort, url: boardResult.url }
|
|
350
|
+
}));
|
|
351
|
+
} catch (err) {
|
|
352
|
+
logger.warn(`HU Board auto-start failed (non-blocking): ${err.message}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export async function writeHistoryRecord({ sessionId, task, result, logger }) {
|
|
357
|
+
try {
|
|
358
|
+
const { createHistoryRecord } = await import("../../hu/store.js");
|
|
359
|
+
const approved = Boolean(result?.approved);
|
|
360
|
+
const summary = result?.review?.summary || result?.reason || null;
|
|
361
|
+
await createHistoryRecord(sessionId, {
|
|
362
|
+
task,
|
|
363
|
+
result: JSON.stringify(result),
|
|
364
|
+
approved,
|
|
365
|
+
summary
|
|
366
|
+
});
|
|
367
|
+
} catch (err) {
|
|
368
|
+
logger.warn(`HU history record failed (non-blocking): ${err.message}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
|
|
3
|
+
* (audit recommendation #6). Previously an internal helper inside
|
|
4
|
+
* pre-loop.js; moved here so the driver stays under 600 LOC.
|
|
5
|
+
* Behaviour unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Auto-generates an HU batch from triage's decomposition recommendation
|
|
8
|
+
* when no manual `huFile` is present. Runs after researcher/architect/
|
|
9
|
+
* planner so context is available for better HU generation. Sets
|
|
10
|
+
* `stageResults.huReviewer` so `needsSubPipeline` picks it up later.
|
|
11
|
+
*
|
|
12
|
+
* Side effects:
|
|
13
|
+
* - Persists `batch.json` to `~/.karajan/hu-stories/<batchSessionId>/`
|
|
14
|
+
* so the sub-pipeline can update story status via `saveHuBatch`.
|
|
15
|
+
* - Auto-starts the HU board (skipped under VITEST / NODE_ENV=test).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { emitProgress, makeEvent } from "#utils/events.js";
|
|
19
|
+
|
|
20
|
+
export async function maybeGenerateAutoHuBatch({
|
|
21
|
+
flags, stageResults, task, logger, emitter, eventBase, projectDir, session,
|
|
22
|
+
}) {
|
|
23
|
+
// Skip if user passed a manual hu-file
|
|
24
|
+
if (flags?.huFile) return;
|
|
25
|
+
// Skip if hu-reviewer already produced a batch (manual enable + PG stories)
|
|
26
|
+
if (stageResults.huReviewer) return;
|
|
27
|
+
// Need triage decomposition recommendation
|
|
28
|
+
const shouldDecompose = stageResults.triage?.shouldDecompose;
|
|
29
|
+
const subtasks = stageResults.triage?.subtasks;
|
|
30
|
+
if (!shouldDecompose || !Array.isArray(subtasks) || subtasks.length < 2) return;
|
|
31
|
+
|
|
32
|
+
const { generateHuBatch } = await import("#hu/auto-generator.js");
|
|
33
|
+
|
|
34
|
+
// Detect if project is new: empty dir or only .git/.karajan/.gitignore
|
|
35
|
+
let isNewProject = false;
|
|
36
|
+
try {
|
|
37
|
+
const fs = await import("node:fs/promises");
|
|
38
|
+
const entries = await fs.readdir(projectDir);
|
|
39
|
+
const relevant = entries.filter(e => !e.startsWith(".git") && e !== ".karajan" && e !== ".gitignore");
|
|
40
|
+
isNewProject = relevant.length === 0;
|
|
41
|
+
} catch { /* ignore */ }
|
|
42
|
+
|
|
43
|
+
// Extract stack hints from planner + architect output
|
|
44
|
+
const stackHints = [];
|
|
45
|
+
const combined = `${stageResults.planner?.plan || ""} ${stageResults.architect?.architecture ? JSON.stringify(stageResults.architect.architecture) : ""} ${task}`.toLowerCase();
|
|
46
|
+
const stackKeywords = ["express", "vite", "vitest", "jest", "next", "astro", "react", "vue", "svelte", "fastapi", "django", "spring", "gin", "nestjs", "monorepo", "workspaces"];
|
|
47
|
+
for (const kw of stackKeywords) {
|
|
48
|
+
if (combined.includes(kw)) stackHints.push(kw);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const batch = generateHuBatch({
|
|
52
|
+
originalTask: task,
|
|
53
|
+
subtasks,
|
|
54
|
+
stackHints,
|
|
55
|
+
isNewProject,
|
|
56
|
+
researcherContext: stageResults.researcher?.summary || null,
|
|
57
|
+
architectContext: stageResults.architect?.architecture ? JSON.stringify(stageResults.architect.architecture) : null
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Persist batch to HU store so hu-sub-pipeline can update story status via saveHuBatch.
|
|
61
|
+
// Use session.id as batchSessionId.
|
|
62
|
+
const batchSessionId = `auto-${session.id}`;
|
|
63
|
+
try {
|
|
64
|
+
const fs = await import("node:fs/promises");
|
|
65
|
+
const path = await import("node:path");
|
|
66
|
+
const { getKarajanHome } = await import("#utils/paths.js");
|
|
67
|
+
const huDir = path.join(getKarajanHome(), "hu-stories", batchSessionId);
|
|
68
|
+
await fs.mkdir(huDir, { recursive: true });
|
|
69
|
+
const persistBatch = {
|
|
70
|
+
session_id: batchSessionId,
|
|
71
|
+
project_id: batchSessionId,
|
|
72
|
+
project_name: batch.projectName,
|
|
73
|
+
created_at: new Date().toISOString(),
|
|
74
|
+
updated_at: new Date().toISOString(),
|
|
75
|
+
stories: batch.stories
|
|
76
|
+
};
|
|
77
|
+
await fs.writeFile(path.join(huDir, "batch.json"), JSON.stringify(persistBatch, null, 2));
|
|
78
|
+
} catch (err) {
|
|
79
|
+
logger.warn(`Auto-HU: failed to persist batch (${err.message}) — sub-pipeline will use in-memory fallback`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Wrap in format compatible with needsSubPipeline + runHuSubPipeline
|
|
83
|
+
stageResults.huReviewer = {
|
|
84
|
+
ok: true,
|
|
85
|
+
stories: batch.stories,
|
|
86
|
+
total: batch.total,
|
|
87
|
+
certified: batch.certified,
|
|
88
|
+
batchSessionId,
|
|
89
|
+
auto_generated: true,
|
|
90
|
+
source: batch.source
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
logger.info(`Auto-HU: generated ${batch.total} stories (${batch.source.triage_subtasks} subtasks${isNewProject ? ", new project" : ""}${stackHints.length ? `, stack: ${stackHints.join(",")}` : ""})`);
|
|
94
|
+
emitProgress(emitter, makeEvent("hu:auto-generated", { ...eventBase, stage: "hu-auto-gen" }, {
|
|
95
|
+
message: `Auto-generated ${batch.total} HU(s) from triage decomposition`,
|
|
96
|
+
detail: { total: batch.total, subtasks: batch.source.triage_subtasks, isNewProject, stackHints, projectName: batch.projectName }
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
// Auto-start the board so the user can see the generated HUs.
|
|
100
|
+
// Always fires when auto-HU runs, independent of hu_board.auto_start flag.
|
|
101
|
+
// Never during vitest — would race the PID file and leave a detached
|
|
102
|
+
// node process around after the suite (TSK-0273).
|
|
103
|
+
if (process.env.VITEST || process.env.NODE_ENV === "test") return;
|
|
104
|
+
try {
|
|
105
|
+
const { startBoard, renderBoardBanner } = await import("../../../commands/board.js");
|
|
106
|
+
const desiredPort = session.config_snapshot?.hu_board?.port ?? 4000;
|
|
107
|
+
const boardResult = await startBoard(desiredPort);
|
|
108
|
+
const url = boardResult.url;
|
|
109
|
+
const status = boardResult.alreadyRunning ? "already running" : "started";
|
|
110
|
+
const projectName = batch.projectName || "Auto-generated HUs";
|
|
111
|
+
console.log(renderBoardBanner({ url, status, projectName }));
|
|
112
|
+
logger.info(`HU Board ${status} at ${url} (project: ${projectName})`);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
logger.warn(`HU Board auto-start failed (non-blocking): ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
|
|
3
|
+
* (audit recommendation #6: continue pre-loop extraction). pre-loop.js
|
|
4
|
+
* had grown to 626 LOC, over the 600-LOC ceiling that the file's own
|
|
5
|
+
* docstring set. Splitting the deprecation warnings, addyosmani skill
|
|
6
|
+
* bootstrap, and auto-HU batch generation into their own modules brings
|
|
7
|
+
* the driver back to ~440 LOC.
|
|
8
|
+
*
|
|
9
|
+
* Behaviour unchanged. The function is exported with the same name and
|
|
10
|
+
* signature so the move is mechanical.
|
|
11
|
+
*
|
|
12
|
+
* Emits one-shot warnings for config keys ignored since v2.7.4. Currently:
|
|
13
|
+
* - `sonarqube.enabled` (kj.config.yml) → ignored, sonar is intrinsic
|
|
14
|
+
* - `--no-sonar` CLI flag → ignored
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { emitProgress, makeEvent } from "#utils/events.js";
|
|
18
|
+
|
|
19
|
+
export function emitConfigDeprecations(config, logger, emitter, eventBase) {
|
|
20
|
+
const dep = config?._deprecated;
|
|
21
|
+
if (!dep) return;
|
|
22
|
+
|
|
23
|
+
if (dep.sonarqubeEnabledKey) {
|
|
24
|
+
const m =
|
|
25
|
+
"DEPRECATED: `sonarqube.enabled` in kj.config.yml is ignored since v2.7.4. " +
|
|
26
|
+
"Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests) and " +
|
|
27
|
+
"skipped for non-code tasks (audit/doc/infra/analysis/no-code) by policy. " +
|
|
28
|
+
"Remove the key from your config to silence this warning.";
|
|
29
|
+
logger.warn(m);
|
|
30
|
+
emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
|
|
31
|
+
message: m,
|
|
32
|
+
detail: { key: "sonarqube.enabled", since: "v2.7.4" },
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (dep.noSonarFlag) {
|
|
37
|
+
const m =
|
|
38
|
+
"DEPRECATED: `--no-sonar` flag is ignored since v2.7.4. Sonar runs for code " +
|
|
39
|
+
"tasks by policy. To skip Sonar on a one-off run, use a non-code task type " +
|
|
40
|
+
"(e.g. `--task-type doc`) or rely on Solomon's runtime override.";
|
|
41
|
+
logger.warn(m);
|
|
42
|
+
emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
|
|
43
|
+
message: m,
|
|
44
|
+
detail: { flag: "--no-sonar", since: "v2.7.4" },
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
|
|
3
|
+
* (audit recommendation #6). Previously a 60-line export inside
|
|
4
|
+
* pre-loop.js; moved here to keep that driver under the 600-LOC
|
|
5
|
+
* ceiling its own docstring set. Behaviour and signature unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Refreshes the addyosmani/agent-skills catalog (clones or pulls,
|
|
8
|
+
* gated by `refreshDays`) and resolves which slugs are relevant to
|
|
9
|
+
* the current task. Sets the result on the session via the
|
|
10
|
+
* `setAddyosmaniSkills` mutator so downstream stages can read it
|
|
11
|
+
* without re-running git.
|
|
12
|
+
*
|
|
13
|
+
* Failures are non-blocking: a missing git, a network blip, or an
|
|
14
|
+
* unparseable catalog all just mark the catalog unavailable and
|
|
15
|
+
* emit a `skills:addyosmani-unavailable` event. The pipeline keeps
|
|
16
|
+
* going — the addyosmani sources are an optional supplement.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
refreshIfStale as refreshAddyosmaniCatalog,
|
|
21
|
+
listAvailableSlugs as listAddyosmaniSlugs,
|
|
22
|
+
} from "#skills/addyosmani-catalog.js";
|
|
23
|
+
import { resolveAddyosmaniSlugs } from "#skills/addyosmani-role-map.js";
|
|
24
|
+
import { setAddyosmaniSkills } from "#session/mutators.js";
|
|
25
|
+
import { emitProgress, makeEvent } from "#utils/events.js";
|
|
26
|
+
|
|
27
|
+
export async function ensureAddyosmaniSkills({ task, config, logger, session, emitter, eventBase }) {
|
|
28
|
+
const skillsConfig = config?.skills || {};
|
|
29
|
+
const sources = Array.isArray(skillsConfig.sources) ? skillsConfig.sources : ["addyosmani", "openskills", "local"];
|
|
30
|
+
const addyConfig = skillsConfig.addyosmani || {};
|
|
31
|
+
// Test harness override: config.testHarness.defaultAddyosmaniEnabled=false
|
|
32
|
+
// prevents orchestrator tests from spawning git. Tests that need the real
|
|
33
|
+
// catalog opt in by setting config.skills.addyosmani.enabled = true.
|
|
34
|
+
// Post-v2.7.5 no longer reads globalThis directly — config.testHarness is
|
|
35
|
+
// populated by the loader from the global or the production default.
|
|
36
|
+
const harnessDefault = config?.testHarness?.defaultAddyosmaniEnabled;
|
|
37
|
+
const enabledFromConfig = addyConfig.enabled === true
|
|
38
|
+
|| (addyConfig.enabled !== false && harnessDefault !== false);
|
|
39
|
+
const enabled = enabledFromConfig && sources.includes("addyosmani");
|
|
40
|
+
if (!enabled) return;
|
|
41
|
+
|
|
42
|
+
const refreshDays = Number.isFinite(addyConfig.refreshDays) ? addyConfig.refreshDays : 7;
|
|
43
|
+
const refreshMs = Math.max(0, refreshDays) * 24 * 60 * 60 * 1000;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const refreshResult = await refreshAddyosmaniCatalog({
|
|
47
|
+
refreshMs,
|
|
48
|
+
repoUrl: addyConfig.repoUrl,
|
|
49
|
+
logger,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!refreshResult.ok) {
|
|
53
|
+
setAddyosmaniSkills(session, { available: false, reason: refreshResult.error || "refresh failed" });
|
|
54
|
+
emitProgress(emitter, makeEvent("skills:addyosmani-unavailable", { ...eventBase, stage: "skills" }, {
|
|
55
|
+
message: `addyosmani/agent-skills catalog unavailable: ${refreshResult.error || refreshResult.action}`,
|
|
56
|
+
detail: { action: refreshResult.action, hint: "Install git to enable process skills from addyosmani/agent-skills" },
|
|
57
|
+
}));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const available = new Set(await listAddyosmaniSlugs());
|
|
62
|
+
const resolved = resolveAddyosmaniSlugs({ role: null, task }); // role resolution happens per-stage later
|
|
63
|
+
const valid = resolved.filter((slug) => available.has(slug));
|
|
64
|
+
|
|
65
|
+
setAddyosmaniSkills(session, {
|
|
66
|
+
available: true,
|
|
67
|
+
action: refreshResult.action,
|
|
68
|
+
resolvedSlugs: valid,
|
|
69
|
+
allAvailable: Array.from(available),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
emitProgress(emitter, makeEvent("skills:addyosmani-ready", { ...eventBase, stage: "skills" }, {
|
|
73
|
+
message: `addyosmani/agent-skills ${refreshResult.action} — ${available.size} slug(s) available, ${valid.length} relevant to task`,
|
|
74
|
+
detail: {
|
|
75
|
+
action: refreshResult.action,
|
|
76
|
+
relevantSlugs: valid,
|
|
77
|
+
availableCount: available.size,
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
} catch (err) {
|
|
81
|
+
logger.warn(`addyosmani catalog step failed (non-blocking): ${err.message}`);
|
|
82
|
+
setAddyosmaniSkills(session, { available: false, reason: err.message });
|
|
83
|
+
}
|
|
84
|
+
}
|