karajan-code 2.7.4 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +24 -4
- package/scripts/diet-baseline.mjs +170 -0
- package/scripts/regen-arch-stats.sh +71 -0
- package/src/agents/availability.js +15 -9
- package/src/agents/claude-agent.js +143 -45
- package/src/agents/model-registry.js +1 -1
- package/src/audit/agent-readiness.js +240 -0
- package/src/audit/basal-cost.js +92 -9
- package/src/audit/deterministic-summary.js +200 -0
- package/src/audit/osv-findings.js +162 -0
- package/src/audit/semgrep-findings.js +117 -0
- package/src/audit/sonar-findings.js +81 -0
- package/src/audit/webperf-input.js +90 -0
- package/src/bootstrap.js +26 -1
- package/src/brain/decision-tracker.js +3 -1
- package/src/brain/decisor.js +1 -1
- package/src/canvas/parse-md.js +238 -0
- package/src/canvas/schema.js +101 -0
- package/src/canvas/to-flat.js +85 -0
- package/src/canvas/validate.js +163 -0
- package/src/checks/binaries.js +3 -3
- package/src/checks/ci.js +2 -2
- package/src/checks/config-files.js +6 -6
- package/src/checks/dir-setup.js +4 -0
- package/src/checks/mcp-health.js +6 -0
- package/src/checks/ports.js +91 -29
- package/src/checks/rtk.js +1 -1
- package/src/checks/skills.js +5 -1
- package/src/checks/sonar.js +2 -2
- package/src/checks/system.js +3 -3
- package/src/checks/tokens.js +1 -1
- package/src/checks/types.js +0 -12
- package/src/cli/_shared.js +30 -0
- package/src/cli/register-meta.js +218 -0
- package/src/cli/register-pipeline.js +212 -0
- package/src/cli/register-plan.js +152 -0
- package/src/cli/register-roles-skills.js +38 -0
- package/src/cli/register-sonar.js +28 -0
- package/src/cli.js +83 -468
- package/src/commands/agents.js +18 -6
- package/src/commands/architect.js +29 -2
- package/src/commands/audit.js +265 -30
- package/src/commands/board.js +160 -30
- package/src/commands/clean.js +83 -0
- package/src/commands/code.js +7 -2
- package/src/commands/discover.js +7 -2
- package/src/commands/doctor.js +1 -1
- package/src/commands/init.js +2 -2
- package/src/commands/plan/_shared.js +40 -0
- package/src/commands/plan/add-hu.js +21 -0
- package/src/commands/plan/delete.js +9 -0
- package/src/commands/plan/generate.js +284 -0
- package/src/commands/plan/index.js +14 -0
- package/src/commands/plan/list.js +21 -0
- package/src/commands/plan/ready.js +46 -0
- package/src/commands/plan/remove-hu.js +17 -0
- package/src/commands/plan/show.js +81 -0
- package/src/commands/plan/validate.js +20 -0
- package/src/commands/plan.js +14 -291
- package/src/commands/researcher.js +7 -2
- package/src/commands/resume.js +24 -1
- package/src/commands/review.js +7 -2
- package/src/commands/run.js +52 -1
- package/src/commands/status.js +1 -1
- package/src/commands/sync.js +75 -0
- package/src/commands/triage.js +7 -2
- package/src/commands/undo.js +1 -1
- package/src/config/defaults.js +213 -0
- package/src/config/loader.js +137 -0
- package/src/config/overrides.js +199 -0
- package/src/config/role-resolver.js +159 -0
- package/src/config/test-harness.js +78 -0
- package/src/config.js +30 -632
- package/src/git/automation.js +2 -2
- package/src/guards/output-guard.js +0 -2
- package/src/hu/acceptance-runner.js +52 -10
- package/src/hu/store.js +11 -0
- package/src/mcp/handlers/direct-handlers.js +2 -5
- package/src/mcp/handlers/management-handlers.js +48 -4
- package/src/mcp/handlers/run-handler.js +9 -8
- package/src/mcp/preflight.js +32 -13
- package/src/mcp/server-handlers.js +4 -3
- package/src/mcp/suggest-handler.js +3 -2
- package/src/mcp/tools.js +15 -0
- package/src/orchestrator/agent-fallback.js +1 -1
- package/src/orchestrator/ci-integration.js +11 -11
- package/src/orchestrator/config-init.js +25 -7
- package/src/orchestrator/direct-actions.js +7 -3
- package/src/orchestrator/drivers/error-recovery.js +287 -0
- package/src/orchestrator/drivers/init-context.js +222 -0
- package/src/orchestrator/drivers/iteration-loop.js +301 -0
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
- package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
- package/src/orchestrator/drivers/post-loop.js +370 -0
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
- package/src/orchestrator/drivers/pre-loop.js +431 -0
- package/src/orchestrator/drivers/run-hu-batch.js +367 -0
- package/src/orchestrator/feedback-queue.js +0 -1
- package/src/orchestrator/flow-control.js +4 -3
- package/src/orchestrator/flow-runner.js +121 -2077
- package/src/orchestrator/fs-leak-detector.js +156 -0
- package/src/orchestrator/hu-sub-pipeline.js +197 -9
- package/src/orchestrator/integrations.js +81 -0
- package/src/orchestrator/post-loop-stages.js +35 -8
- package/src/orchestrator/preflight-checks.js +20 -7
- package/src/orchestrator/repair/repair-detector.js +117 -0
- package/src/orchestrator/repair/repair-runner.js +142 -0
- package/src/orchestrator/reviewer-fallback.js +1 -1
- package/src/orchestrator/role-output-compressor.js +0 -1
- package/src/orchestrator/session-journal.js +4 -41
- package/src/orchestrator/solomon-escalation.js +3 -2
- package/src/orchestrator/solomon-rules.js +7 -3
- package/src/orchestrator/stages/architect-stage.js +12 -11
- package/src/orchestrator/stages/coder-stage.js +56 -7
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
- package/src/orchestrator/stages/planner-stage.js +1 -1
- package/src/orchestrator/stages/research-stage.js +1 -1
- package/src/orchestrator/stages/reviewer-stage.js +7 -7
- package/src/orchestrator/stages/sonar-stage.js +94 -10
- package/src/orchestrator/stages/stage-classes.js +84 -0
- package/src/orchestrator/stages/stage-executor.js +1 -1
- package/src/orchestrator/stages/triage-stage.js +1 -1
- package/src/orchestrator/standby.js +3 -2
- package/src/orchestrator/verification-gate.js +20 -12
- package/src/plan/adr-generator.js +170 -0
- package/src/plan/adr-loader.js +60 -0
- package/src/plan/plan-executor.js +19 -1
- package/src/plan/plan-hu-ops.js +170 -1
- package/src/plan/plan-reviewer.js +199 -0
- package/src/plan/plan-schema.js +69 -1
- package/src/plan/plan-store.js +40 -17
- package/src/plan/spec-detector.js +61 -0
- package/src/plan/tests-synthesizer.js +116 -0
- package/src/planning-game/client.js +3 -28
- package/src/planning-game/decomposition.js +1 -1
- package/src/planning-game/pipeline-adapter.js +37 -6
- package/src/prompts/audit.js +206 -6
- package/src/prompts/coder.js +172 -1
- package/src/prompts/hu-reviewer.js +0 -1
- package/src/prompts/planner.js +81 -4
- package/src/prompts/rtk-snippet.js +1 -1
- package/src/review/diff-generator.js +35 -22
- package/src/review/snapshot-diff.js +1 -1
- package/src/roles/audit-role.js +125 -13
- package/src/roles/coder-role.js +25 -3
- package/src/roles/planner-role.js +42 -1
- package/src/roles/repairer-role.js +181 -0
- package/src/roles/tester-role.js +70 -4
- package/src/session/mutators.js +304 -0
- package/src/session/runtime-overrides.js +59 -0
- package/src/{session-store.js → session/store.js} +2 -2
- package/src/skills/semantic-detector.js +10 -5
- package/src/skills/skill-detector.js +1 -1
- package/src/sonar/api.js +1 -1
- package/src/sonar/cloud-scanner.js +0 -1
- package/src/sonar/credentials.js +14 -6
- package/src/sonar/discovery.js +123 -0
- package/src/sonar/manager.js +18 -0
- package/src/sync/detect-drift.js +113 -0
- package/src/tracker-bootstrap.js +44 -0
- package/src/utils/agent-detect.js +15 -11
- package/src/utils/board-prompt-bridge.js +109 -0
- package/src/utils/cli-progress.js +161 -0
- package/src/utils/cwd-confirm.js +39 -0
- package/src/utils/derive-project-name-from-cwd.js +119 -0
- package/src/utils/display/event-handlers.js +1 -1
- package/src/utils/display/header.js +1 -1
- package/src/utils/display/session-summary.js +3 -3
- package/src/utils/display/solomon.js +1 -1
- package/src/utils/garbage-collector.js +396 -0
- package/src/utils/git.js +16 -4
- package/src/utils/locale.js +1 -1
- package/src/utils/paths.js +21 -1
- package/src/utils/process.js +1 -1
- package/src/utils/prompt-project-name.js +35 -0
- package/src/utils/role-assigner.js +1 -1
- package/src/utils/run-context.js +75 -0
- package/src/utils/run-log.js +12 -0
- package/src/utils/task-file.js +1 -1
- package/src/utils/telemetry.js +13 -1
- package/src/webperf/devtools-detect.js +1 -1
- package/templates/coder-rules.md +13 -0
- package/templates/roles/coder.md +34 -0
- package/templates/roles/repairer.md +94 -0
package/src/roles/tester-role.js
CHANGED
|
@@ -48,16 +48,26 @@ export class TesterRole extends AgentRole {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
extractInput(input) {
|
|
51
|
-
if (typeof input === "string") return { task: input, diff: null, sonarIssues: null };
|
|
51
|
+
if (typeof input === "string") return { task: input, diff: null, sonarIssues: null, pendingGherkinTests: null, shellTestResults: null };
|
|
52
52
|
return {
|
|
53
53
|
task: input?.task || this.context?.task || "",
|
|
54
54
|
diff: input?.diff || null,
|
|
55
55
|
sonarIssues: input?.sonarIssues || null,
|
|
56
|
+
// Tests-first Phase 3 (v2.7.5): the tester now has two extra inputs.
|
|
57
|
+
// - pendingGherkinTests: HU-declared Gherkin scenarios that need
|
|
58
|
+
// translation into executable code tests in the project's
|
|
59
|
+
// framework. The tester writes the test files and includes them
|
|
60
|
+
// in the suite run before computing verdict.
|
|
61
|
+
// - shellTestResults: results of running the HU's shell-type
|
|
62
|
+
// acceptance_tests (already run upstream, passed at this point).
|
|
63
|
+
// Passed for context so the tester doesn't re-run them redundantly.
|
|
64
|
+
pendingGherkinTests: input?.pendingGherkinTests || null,
|
|
65
|
+
shellTestResults: input?.shellTestResults || null,
|
|
56
66
|
onOutput: input?.onOutput || null
|
|
57
67
|
};
|
|
58
68
|
}
|
|
59
69
|
|
|
60
|
-
async buildPrompt({ task, diff, sonarIssues }) {
|
|
70
|
+
async buildPrompt({ task, diff, sonarIssues, pendingGherkinTests, shellTestResults }) {
|
|
61
71
|
const projectDir = this.config?.projectDir || process.cwd();
|
|
62
72
|
const detection = await detectTestFramework(projectDir);
|
|
63
73
|
|
|
@@ -97,14 +107,65 @@ export class TesterRole extends AgentRole {
|
|
|
97
107
|
);
|
|
98
108
|
}
|
|
99
109
|
|
|
110
|
+
// Tests-first Phase 3 (v2.7.5): include the upstream shell-test results
|
|
111
|
+
// so the tester knows which part of the contract is already green.
|
|
112
|
+
if (Array.isArray(shellTestResults) && shellTestResults.length > 0) {
|
|
113
|
+
const passedCount = shellTestResults.filter((r) => r.passed).length;
|
|
114
|
+
sections.push(
|
|
115
|
+
"## Acceptance gate — shell tests",
|
|
116
|
+
`The HU's shell-type acceptance_tests already ran upstream: ${passedCount}/${shellTestResults.length} passed.`,
|
|
117
|
+
"Do NOT re-run them. Use the coverage/translation steps below to decide your verdict."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Gherkin translation block — only when the HU carries Gherkin
|
|
122
|
+
// scenarios. Tester must produce real test files (in the detected
|
|
123
|
+
// framework's language) that encode Given/When/Then. This is how
|
|
124
|
+
// behaviour-level specs become enforceable.
|
|
125
|
+
if (Array.isArray(pendingGherkinTests) && pendingGherkinTests.length > 0) {
|
|
126
|
+
sections.push(
|
|
127
|
+
"## Gherkin acceptance tests to translate",
|
|
128
|
+
"These Gherkin scenarios are the HU's behavioural contract. They are NOT yet",
|
|
129
|
+
"executable — your job is to turn each one into a concrete test case in the",
|
|
130
|
+
"project's test framework, WRITE the test files, and INCLUDE them in the suite",
|
|
131
|
+
"run that determines your verdict.",
|
|
132
|
+
"",
|
|
133
|
+
"For each scenario below:",
|
|
134
|
+
"1. Pick the appropriate test file (use `target` hint if present; otherwise",
|
|
135
|
+
" put the test next to the module it exercises).",
|
|
136
|
+
"2. Write a test that asserts the Given/When/Then as-is. Do NOT soften the",
|
|
137
|
+
" assertion — if it's ambiguous, pick the stricter reading.",
|
|
138
|
+
"3. Make sure the test runs as part of the normal suite (no separate invocation).",
|
|
139
|
+
"",
|
|
140
|
+
"If ANY translated test fails on the current code, return verdict: \"fail\" with",
|
|
141
|
+
"a failing_scenarios array listing the scenarios that didn't pass. The coder",
|
|
142
|
+
"will iterate.",
|
|
143
|
+
""
|
|
144
|
+
);
|
|
145
|
+
pendingGherkinTests.forEach((entry, i) => {
|
|
146
|
+
const content = typeof entry === "string" ? entry : entry?.content || "";
|
|
147
|
+
const target = entry && entry.file ? ` · target: \`${entry.file}\`` : "";
|
|
148
|
+
sections.push(
|
|
149
|
+
`### Scenario ${i + 1}${target}`,
|
|
150
|
+
"```gherkin",
|
|
151
|
+
content,
|
|
152
|
+
"```",
|
|
153
|
+
""
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
100
158
|
sections.push(
|
|
101
159
|
"",
|
|
102
160
|
"Return ONLY a single valid JSON object:",
|
|
103
|
-
'{"tests_pass":boolean,"coverage":{"overall":number,"services":number,"utilities":number},"missing_scenarios":[string],"quality_issues":[string],"verdict":"pass"|"fail"}',
|
|
161
|
+
'{"tests_pass":boolean,"coverage":{"overall":number,"services":number,"utilities":number},"missing_scenarios":[string],"quality_issues":[string],"failing_scenarios":[string],"translated_scenarios":[string],"verdict":"pass"|"fail"}',
|
|
104
162
|
"",
|
|
105
163
|
"- coverage.overall MUST be a real number from the test runner output, NOT an estimate",
|
|
106
164
|
"- If coverage tool is not available, set coverage.overall to null (not 0, not a guess)",
|
|
107
165
|
"- tests_pass must reflect whether the actual test run succeeded",
|
|
166
|
+
"- failing_scenarios: when Gherkin was given, list the scenarios your translated tests CAUGHT as failing",
|
|
167
|
+
"- translated_scenarios: when Gherkin was given, list the scenarios you successfully turned into passing tests",
|
|
168
|
+
"- verdict MUST be \"fail\" if any translated scenario still fails (even if coverage is green)",
|
|
108
169
|
`## Task\n${task}`
|
|
109
170
|
);
|
|
110
171
|
if (diff) sections.push(`## Git diff\n${diff}`);
|
|
@@ -136,6 +197,9 @@ export class TesterRole extends AgentRole {
|
|
|
136
197
|
coverage: parsed.coverage || {},
|
|
137
198
|
missing_scenarios: parsed.missing_scenarios || [],
|
|
138
199
|
quality_issues: parsed.quality_issues || [],
|
|
200
|
+
// Tests-first Phase 3: Gherkin translation outcomes.
|
|
201
|
+
failing_scenarios: Array.isArray(parsed.failing_scenarios) ? parsed.failing_scenarios : [],
|
|
202
|
+
translated_scenarios: Array.isArray(parsed.translated_scenarios) ? parsed.translated_scenarios : [],
|
|
139
203
|
verdict,
|
|
140
204
|
provider
|
|
141
205
|
};
|
|
@@ -147,6 +211,8 @@ export class TesterRole extends AgentRole {
|
|
|
147
211
|
const coverageStr = coverage.overall != null ? `${coverage.overall}%` : "not measured";
|
|
148
212
|
const missingPart = parsed.missing_scenarios?.length ? `; ${parsed.missing_scenarios.length} missing scenario(s)` : "";
|
|
149
213
|
const qualityPart = parsed.quality_issues?.length ? `; ${parsed.quality_issues.length} quality issue(s)` : "";
|
|
150
|
-
|
|
214
|
+
const failingPart = parsed.failing_scenarios?.length ? `; ${parsed.failing_scenarios.length} failing scenario(s)` : "";
|
|
215
|
+
const translatedPart = parsed.translated_scenarios?.length ? `; ${parsed.translated_scenarios.length} scenario(s) translated` : "";
|
|
216
|
+
return `Verdict: ${verdict}; Coverage: ${coverageStr}${translatedPart}${failingPart}${missingPart}${qualityPart}`;
|
|
151
217
|
}
|
|
152
218
|
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session mutators — the single site where the orchestrator mutates a
|
|
3
|
+
* session object in memory.
|
|
4
|
+
*
|
|
5
|
+
* Rationale (TSK-0337, KJC-PCS-0040 Oleada 3): pre-v2.7.5 there were ~68
|
|
6
|
+
* direct `session.x = value` assignments scattered across 18 files, with
|
|
7
|
+
* one file (flow-runner.js) alone accounting for 142 touches. The audit
|
|
8
|
+
* called this out as a HIGH finding: when a reviewer-retry bug landed in
|
|
9
|
+
* production, a maintainer had to grep the whole tree to find every site
|
|
10
|
+
* that writes to `session.last_reviewer_feedback` or
|
|
11
|
+
* `session.reviewer_retry_count`.
|
|
12
|
+
*
|
|
13
|
+
* The mutators below are intentionally **mechanical** — each wraps a
|
|
14
|
+
* single assignment — so the migration from `session.x = y` to
|
|
15
|
+
* `setX(session, y)` is pure plumbing and preserves exact existing
|
|
16
|
+
* semantics. The value is NOT validation or side-effects; it's that
|
|
17
|
+
* grepping `setReviewerFeedback(` finds every writer in seconds. Adding
|
|
18
|
+
* validation, event emission or persistence hooks later becomes a
|
|
19
|
+
* one-liner inside the mutator rather than a tree-wide edit.
|
|
20
|
+
*
|
|
21
|
+
* Complementary architecture test:
|
|
22
|
+
* tests/architecture/session-write-boundary.test.js
|
|
23
|
+
* enforces a budget on the remaining direct `session.x = y` assignments
|
|
24
|
+
* outside `src/session/`. The budget only decreases; any new direct
|
|
25
|
+
* assignment requires either going through a mutator or explicit audit.
|
|
26
|
+
*
|
|
27
|
+
* Naming convention:
|
|
28
|
+
* - setX — overwrite field X with a new value.
|
|
29
|
+
* - incrementX — numeric +1 (returns the new value).
|
|
30
|
+
* - resetX — numeric 0 or null.
|
|
31
|
+
* - clearX — delete the field entirely (when `in` matters).
|
|
32
|
+
* - appendToX — push onto an array field.
|
|
33
|
+
* - assignX — shallow-merge object into an object field.
|
|
34
|
+
*
|
|
35
|
+
* No mutator persists the session — callers are still responsible for
|
|
36
|
+
* `await saveSession(session)` afterwards. This keeps the mutator
|
|
37
|
+
* functions synchronous and cheap.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// --- Retry counters -------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export const RETRY_KINDS = Object.freeze([
|
|
43
|
+
"reviewer",
|
|
44
|
+
"sonar",
|
|
45
|
+
"tester",
|
|
46
|
+
"security",
|
|
47
|
+
"standby",
|
|
48
|
+
"repeated_issue",
|
|
49
|
+
"auto_resume",
|
|
50
|
+
"checkpoint_stall",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const RETRY_FIELD = {
|
|
54
|
+
reviewer: "reviewer_retry_count",
|
|
55
|
+
sonar: "sonar_retry_count",
|
|
56
|
+
tester: "tester_retry_count",
|
|
57
|
+
security: "security_retry_count",
|
|
58
|
+
standby: "standby_retry_count",
|
|
59
|
+
repeated_issue: "repeated_issue_count",
|
|
60
|
+
auto_resume: "auto_resume_count",
|
|
61
|
+
checkpoint_stall: "_checkpoint_stall_count",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function setRetryCount(session, kind, value) {
|
|
65
|
+
const field = RETRY_FIELD[kind];
|
|
66
|
+
if (!field) throw new Error(`setRetryCount: unknown retry kind "${kind}"`);
|
|
67
|
+
session[field] = Number(value) || 0;
|
|
68
|
+
return session[field];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function incrementRetryCount(session, kind) {
|
|
72
|
+
const field = RETRY_FIELD[kind];
|
|
73
|
+
if (!field) throw new Error(`incrementRetryCount: unknown retry kind "${kind}"`);
|
|
74
|
+
session[field] = (session[field] || 0) + 1;
|
|
75
|
+
return session[field];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resetRetryCount(session, kind) {
|
|
79
|
+
return setRetryCount(session, kind, 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reset all well-known retry/repeat counters to 0. Called at the top of
|
|
84
|
+
* every resume path so a resumed session doesn't inherit the previous
|
|
85
|
+
* run's stall state.
|
|
86
|
+
*/
|
|
87
|
+
export function resetAllRetryCounters(session) {
|
|
88
|
+
for (const kind of RETRY_KINDS) {
|
|
89
|
+
// Skip checkpoint_stall: it's internal state owned by flow-control,
|
|
90
|
+
// not something resume should touch.
|
|
91
|
+
if (kind === "checkpoint_stall") continue;
|
|
92
|
+
setRetryCount(session, kind, 0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Feedback -------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
export function setReviewerFeedback(session, text) {
|
|
99
|
+
session.last_reviewer_feedback = text;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function appendReviewerFeedback(session, text) {
|
|
103
|
+
session.last_reviewer_feedback = session.last_reviewer_feedback
|
|
104
|
+
? `${session.last_reviewer_feedback}\n${text}`
|
|
105
|
+
: text;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function setSonarSummary(session, text) {
|
|
109
|
+
session.last_sonar_summary = text;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function setSonarIssueSignature(session, sig) {
|
|
113
|
+
session.last_sonar_issue_signature = sig;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function setReviewerIssueSignature(session, sig) {
|
|
117
|
+
session.last_reviewer_issue_signature = sig;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Issue-signature repetition counters (distinct from retry_count:
|
|
121
|
+
// reset only when the signature changes, not every iteration).
|
|
122
|
+
export function setSonarRepeatCount(session, value) {
|
|
123
|
+
session.sonar_repeat_count = Number(value) || 0;
|
|
124
|
+
}
|
|
125
|
+
export function setReviewerRepeatCount(session, value) {
|
|
126
|
+
session.reviewer_repeat_count = Number(value) || 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- Status ---------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* In-memory status change. The persisting variant is `markSessionStatus` in
|
|
133
|
+
* store.js, which also writes to disk; `setStatus` is for transient mutations
|
|
134
|
+
* inside the runner (e.g. standby enter/exit) where the caller persists
|
|
135
|
+
* later via `saveSession`.
|
|
136
|
+
*/
|
|
137
|
+
export function setStatus(session, status) {
|
|
138
|
+
session.status = status;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Alternative agent (rate-limit recovery) ------------------------------
|
|
142
|
+
|
|
143
|
+
export function setAlternativeAgent(session, stage, provider) {
|
|
144
|
+
session._alternative_agent = { stage, provider };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function clearAlternativeAgent(session) {
|
|
148
|
+
delete session._alternative_agent;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Standby state --------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
export function setStandby(session, { agent, cooldownMs, cooldownUntil }) {
|
|
154
|
+
session.standby_agent = agent;
|
|
155
|
+
session.standby_cooldown_ms = cooldownMs;
|
|
156
|
+
session.standby_cooldown_until = cooldownUntil;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function clearStandby(session) {
|
|
160
|
+
delete session.standby_agent;
|
|
161
|
+
delete session.standby_cooldown_ms;
|
|
162
|
+
delete session.standby_cooldown_until;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// --- CI state -------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
export function setCiEarlyPr(session, { prNumber, prUrl, commits }) {
|
|
168
|
+
session.ci_pr_number = prNumber;
|
|
169
|
+
session.ci_pr_url = prUrl;
|
|
170
|
+
session.ci_commits = commits;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function appendCiCommits(session, commits) {
|
|
174
|
+
session.ci_commits = [...(session.ci_commits ?? []), ...commits];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- Pre-loop & policy metadata -------------------------------------------
|
|
178
|
+
|
|
179
|
+
export function setPreflight(session, result) {
|
|
180
|
+
session.preflight = result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function setResolvedPolicies(session, policies) {
|
|
184
|
+
session.resolved_policies = policies;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function setPgCard(session, card) {
|
|
188
|
+
session.pg_card = card;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function setPgTaskId(session, id) {
|
|
192
|
+
session.pg_task_id = id;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function appendPgCommits(session, commits) {
|
|
196
|
+
session.pg_commits = [...(session.pg_commits ?? []), ...commits];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function setPreLoopContext(session, { research, architect, plan }) {
|
|
200
|
+
if (research !== undefined) session.research_context = research;
|
|
201
|
+
if (architect !== undefined) session.architect_context = architect;
|
|
202
|
+
if (plan !== undefined) session.loaded_plan = plan;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function setPlanRef(session, ref) {
|
|
206
|
+
session._planRef = ref;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function setPlanSyncCallback(session, fn) {
|
|
210
|
+
session._syncResultsToPlan = fn;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Live status updater: invoked by hu-sub-pipeline after every status
|
|
215
|
+
* change so the plan JSON gets the new status immediately, not only at
|
|
216
|
+
* the end of the run. Without this, the board reads an unchanged plan
|
|
217
|
+
* file during execution and the columns appear frozen until the run
|
|
218
|
+
* completes (the bug we're fixing in PR1).
|
|
219
|
+
*
|
|
220
|
+
* Signature: async (huId, status) => void
|
|
221
|
+
*/
|
|
222
|
+
export function setLiveStatusUpdater(session, fn) {
|
|
223
|
+
session._liveStatusUpdater = fn;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function getLiveStatusUpdater(session) {
|
|
227
|
+
return session._liveStatusUpdater || null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Live outcome updater (PR3): invoked by hu-sub-pipeline after each
|
|
232
|
+
* HU's runSingleHu finishes. The plan JSON gets the per-HU outcome
|
|
233
|
+
* stamped immediately so the board can show a "📄 ver resumen"
|
|
234
|
+
* indicator next to each card without waiting for the run to end.
|
|
235
|
+
*
|
|
236
|
+
* Signature: async (huId, outcome) => void
|
|
237
|
+
*/
|
|
238
|
+
export function setLiveOutcomeUpdater(session, fn) {
|
|
239
|
+
session._liveOutcomeUpdater = fn;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function getLiveOutcomeUpdater(session) {
|
|
243
|
+
return session._liveOutcomeUpdater || null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// --- Skills ---------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
export function setAddyosmaniSkills(session, payload) {
|
|
249
|
+
session.addyosmaniSkills = payload;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function setAutoInstalledSkills(session, skills) {
|
|
253
|
+
session.autoInstalledSkills = skills;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function setSkillsRecommended(session, skills) {
|
|
257
|
+
session.skillsRecommended = skills;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// --- Journal breadcrumbs --------------------------------------------------
|
|
261
|
+
|
|
262
|
+
export function setJournalContext(session, { dir, files, iterations, decisions, startedAt }) {
|
|
263
|
+
if (dir !== undefined) session._journalDir = dir;
|
|
264
|
+
if (files !== undefined) session._journalFiles = files;
|
|
265
|
+
if (iterations !== undefined) session._journalIterations = iterations;
|
|
266
|
+
if (decisions !== undefined) session._journalDecisions = decisions;
|
|
267
|
+
if (startedAt !== undefined) session._startedAt = startedAt;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// --- Session outcome ------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
export function setBudget(session, budget) {
|
|
273
|
+
session.budget = budget;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function setDeferredIssues(session, issues) {
|
|
277
|
+
session.deferred_issues = issues;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function setRtkSavings(session, savings) {
|
|
281
|
+
session.rtk_savings = savings;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// --- Paused state (resume) ------------------------------------------------
|
|
285
|
+
|
|
286
|
+
export function setPausedState(session, state) {
|
|
287
|
+
session.paused_state = state;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- Brain decisions (decisor tracker) -----------------------------------
|
|
291
|
+
|
|
292
|
+
export function setBrainDecisions(session, decisions) {
|
|
293
|
+
session.brainDecisions = decisions;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// --- Suggestions (MCP) ----------------------------------------------------
|
|
297
|
+
|
|
298
|
+
export function setSuggestions(session, suggestions) {
|
|
299
|
+
session.suggestions = suggestions;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function touch(session) {
|
|
303
|
+
session.updated_at = new Date().toISOString();
|
|
304
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory runtime overrides (e.g. `roles.coder.provider` set for the
|
|
3
|
+
* current process session, not persisted to disk).
|
|
4
|
+
*
|
|
5
|
+
* Why this lives at the session layer (not under `src/mcp/`):
|
|
6
|
+
*
|
|
7
|
+
* Pre-v2.7.5, the overrides store lived in `src/mcp/preflight.js`. That
|
|
8
|
+
* made `src/commands/agents.js` (CLI) do
|
|
9
|
+
* `await import("../mcp/preflight.js")`
|
|
10
|
+
* which is a **layer violation**: commands are the top layer, MCP is a
|
|
11
|
+
* peer interface. A CLI command knowing about the MCP server's
|
|
12
|
+
* preflight state coupled two runtimes that should be independent.
|
|
13
|
+
*
|
|
14
|
+
* The "runtime override" concept is runtime-agnostic — both CLI and MCP
|
|
15
|
+
* need an in-memory scratchpad for "this process should use this
|
|
16
|
+
* provider for role X for the rest of its life". Putting it here makes
|
|
17
|
+
* both layers depend on `src/session/` (a neutral module) instead of on
|
|
18
|
+
* each other.
|
|
19
|
+
*
|
|
20
|
+
* Lifetime: the store lives for as long as the Node process. CLI
|
|
21
|
+
* invocations are short-lived (override is lost at exit — that's fine,
|
|
22
|
+
* they write to config if they want persistence). The MCP server process
|
|
23
|
+
* is long-lived; the override survives across tool calls until
|
|
24
|
+
* `clearRuntimeOverrides()` is called (typically by `ackPreflight()`).
|
|
25
|
+
*
|
|
26
|
+
* Contents: a flat `{ [key]: value }` map. Keys are typically role names
|
|
27
|
+
* (`coder`, `reviewer`, `planner`), values are provider slugs (`claude`,
|
|
28
|
+
* `codex`, `gemini`). Consumers shape-validate at read time.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
let overrides = {};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Return a shallow copy of the current overrides. Never returns the
|
|
35
|
+
* internal reference so callers cannot mutate it by accident.
|
|
36
|
+
*/
|
|
37
|
+
export function getRuntimeOverrides() {
|
|
38
|
+
return { ...overrides };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Set a single override. Silently rejects nullish keys.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} key
|
|
45
|
+
* @param {unknown} value
|
|
46
|
+
*/
|
|
47
|
+
export function setRuntimeOverride(key, value) {
|
|
48
|
+
if (!key) return;
|
|
49
|
+
overrides[key] = value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Drop all overrides. Used by `ackPreflight()` after it transfers its
|
|
54
|
+
* configured overrides onto this store, to ensure we start from a clean
|
|
55
|
+
* slate per MCP session.
|
|
56
|
+
*/
|
|
57
|
+
export function clearRuntimeOverrides() {
|
|
58
|
+
overrides = {};
|
|
59
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { ensureDir, exists } from "
|
|
4
|
-
import { getSessionRoot } from "
|
|
3
|
+
import { ensureDir, exists } from "../utils/fs.js";
|
|
4
|
+
import { getSessionRoot } from "../utils/paths.js";
|
|
5
5
|
|
|
6
6
|
const SESSION_ROOT = getSessionRoot();
|
|
7
7
|
|
|
@@ -129,18 +129,23 @@ export async function refineSkillsSemantically({ task, alreadyDetected = [], con
|
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
131
|
* Resolve the effective skills mode from config + flags.
|
|
132
|
-
* Precedence: flags.skillsMode > config.skills.mode >
|
|
132
|
+
* Precedence: flags.skillsMode > config.skills.mode > config.testHarness.defaultSkillsMode > "auto".
|
|
133
133
|
*
|
|
134
|
-
* The
|
|
135
|
-
* without each orchestrator test having to mock this module
|
|
136
|
-
*
|
|
134
|
+
* The testHarness default lets the test harness opt out of semantic
|
|
135
|
+
* detection without each orchestrator test having to mock this module.
|
|
136
|
+
* Production code reads `config.testHarness.defaultSkillsMode` only;
|
|
137
|
+
* the legacy global override is documented and exclusively read in
|
|
138
|
+
* src/config/test-harness.js (ESLint rule #557 blocks any reintroduction
|
|
139
|
+
* of `globalThis.__KJ_*` outside that one file). Legacy callers without
|
|
140
|
+
* a fully-loaded config still work because `config.testHarness` is
|
|
141
|
+
* undefined → falls through to `"auto"`.
|
|
137
142
|
*
|
|
138
143
|
* @param {Object} config
|
|
139
144
|
* @param {Object} [flags]
|
|
140
145
|
* @returns {"auto"|"regex"|"semantic"|"none"}
|
|
141
146
|
*/
|
|
142
147
|
export function resolveSkillsMode(config, flags = {}) {
|
|
143
|
-
const defaultMode =
|
|
148
|
+
const defaultMode = config?.testHarness?.defaultSkillsMode || "auto";
|
|
144
149
|
const candidate = flags.skillsMode ?? config?.skills?.mode ?? defaultMode;
|
|
145
150
|
const valid = new Set(["auto", "regex", "semantic", "none"]);
|
|
146
151
|
return valid.has(candidate) ? candidate : "auto";
|
|
@@ -226,7 +226,7 @@ async function collectPythonDeps(projectDir, needed) {
|
|
|
226
226
|
for (const line of raw.split("\n")) {
|
|
227
227
|
const trimmed = line.trim().split("#")[0].trim();
|
|
228
228
|
if (!trimmed) continue;
|
|
229
|
-
const name = trimmed.split(/[
|
|
229
|
+
const name = trimmed.split(/[<>=!~[\s]/)[0].toLowerCase();
|
|
230
230
|
const skill = PYTHON_DEPS[name];
|
|
231
231
|
if (skill) needed.add(skill);
|
|
232
232
|
}
|
package/src/sonar/api.js
CHANGED
|
@@ -3,7 +3,7 @@ import { withRetry } from "../utils/retry.js";
|
|
|
3
3
|
import { resolveSonarProjectKey } from "./project-key.js";
|
|
4
4
|
import { resolveSonarToken } from "./config-resolver.js";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
class SonarApiError extends Error {
|
|
7
7
|
constructor(message, { url, httpStatus, hint } = {}) {
|
|
8
8
|
super(message);
|
|
9
9
|
this.name = "SonarApiError";
|
|
@@ -5,7 +5,6 @@ function buildCloudScannerArgs(projectKey, config) {
|
|
|
5
5
|
const sc = config.sonarcloud || {};
|
|
6
6
|
const scanner = sc.scanner || {};
|
|
7
7
|
const host = sc.host || "https://sonarcloud.io";
|
|
8
|
-
const token = process.env.KJ_SONARCLOUD_TOKEN || sc.token;
|
|
9
8
|
const organization = process.env.KJ_SONARCLOUD_ORG || sc.organization;
|
|
10
9
|
|
|
11
10
|
const args = [
|
package/src/sonar/credentials.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Load SonarQube
|
|
2
|
+
* Load SonarQube credentials from ~/.karajan/sonar-credentials.json.
|
|
3
3
|
*
|
|
4
|
-
* File format:
|
|
4
|
+
* File format (any subset):
|
|
5
5
|
* {
|
|
6
6
|
* "user": "admin",
|
|
7
|
-
* "password": "your-password"
|
|
7
|
+
* "password": "your-password",
|
|
8
|
+
* "token": "squ_..."
|
|
8
9
|
* }
|
|
9
10
|
*
|
|
10
|
-
* Returns { user, password }
|
|
11
|
+
* Returns { user, password, token } with each field null when missing.
|
|
12
|
+
*
|
|
13
|
+
* The `token` field is consumed by resolveSonarTokenAsync as the
|
|
14
|
+
* lowest-priority fallback. Until v2.7.5 it was not extracted here, which
|
|
15
|
+
* meant a token persisted to the credentials file by `kj init` was never
|
|
16
|
+
* actually used at scan time — Karajan would silently fall back to the
|
|
17
|
+
* stale token in kj.config.yml or fail with a 401.
|
|
11
18
|
*/
|
|
12
19
|
|
|
13
20
|
import fs from "node:fs/promises";
|
|
@@ -23,10 +30,11 @@ export async function loadSonarCredentials() {
|
|
|
23
30
|
const data = JSON.parse(raw);
|
|
24
31
|
return {
|
|
25
32
|
user: data.user || null,
|
|
26
|
-
password: data.password || null
|
|
33
|
+
password: data.password || null,
|
|
34
|
+
token: data.token || null
|
|
27
35
|
};
|
|
28
36
|
} catch { /* credentials file may not exist */
|
|
29
|
-
return { user: null, password: null };
|
|
37
|
+
return { user: null, password: null, token: null };
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
40
|
|