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.
Files changed (192) hide show
  1. package/README.md +1 -1
  2. package/package.json +24 -4
  3. package/scripts/diet-baseline.mjs +170 -0
  4. package/scripts/regen-arch-stats.sh +71 -0
  5. package/src/agents/availability.js +15 -9
  6. package/src/agents/claude-agent.js +143 -45
  7. package/src/agents/model-registry.js +1 -1
  8. package/src/audit/agent-readiness.js +240 -0
  9. package/src/audit/basal-cost.js +92 -9
  10. package/src/audit/deterministic-summary.js +200 -0
  11. package/src/audit/osv-findings.js +162 -0
  12. package/src/audit/semgrep-findings.js +117 -0
  13. package/src/audit/sonar-findings.js +81 -0
  14. package/src/audit/webperf-input.js +90 -0
  15. package/src/bootstrap.js +26 -1
  16. package/src/brain/decision-tracker.js +3 -1
  17. package/src/brain/decisor.js +1 -1
  18. package/src/canvas/parse-md.js +238 -0
  19. package/src/canvas/schema.js +101 -0
  20. package/src/canvas/to-flat.js +85 -0
  21. package/src/canvas/validate.js +163 -0
  22. package/src/checks/binaries.js +3 -3
  23. package/src/checks/ci.js +2 -2
  24. package/src/checks/config-files.js +6 -6
  25. package/src/checks/dir-setup.js +4 -0
  26. package/src/checks/mcp-health.js +6 -0
  27. package/src/checks/ports.js +91 -29
  28. package/src/checks/rtk.js +1 -1
  29. package/src/checks/skills.js +5 -1
  30. package/src/checks/sonar.js +2 -2
  31. package/src/checks/system.js +3 -3
  32. package/src/checks/tokens.js +1 -1
  33. package/src/checks/types.js +0 -12
  34. package/src/cli/_shared.js +30 -0
  35. package/src/cli/register-meta.js +218 -0
  36. package/src/cli/register-pipeline.js +212 -0
  37. package/src/cli/register-plan.js +152 -0
  38. package/src/cli/register-roles-skills.js +38 -0
  39. package/src/cli/register-sonar.js +28 -0
  40. package/src/cli.js +83 -468
  41. package/src/commands/agents.js +18 -6
  42. package/src/commands/architect.js +29 -2
  43. package/src/commands/audit.js +265 -30
  44. package/src/commands/board.js +160 -30
  45. package/src/commands/clean.js +83 -0
  46. package/src/commands/code.js +7 -2
  47. package/src/commands/discover.js +7 -2
  48. package/src/commands/doctor.js +1 -1
  49. package/src/commands/init.js +2 -2
  50. package/src/commands/plan/_shared.js +40 -0
  51. package/src/commands/plan/add-hu.js +21 -0
  52. package/src/commands/plan/delete.js +9 -0
  53. package/src/commands/plan/generate.js +284 -0
  54. package/src/commands/plan/index.js +14 -0
  55. package/src/commands/plan/list.js +21 -0
  56. package/src/commands/plan/ready.js +46 -0
  57. package/src/commands/plan/remove-hu.js +17 -0
  58. package/src/commands/plan/show.js +81 -0
  59. package/src/commands/plan/validate.js +20 -0
  60. package/src/commands/plan.js +14 -291
  61. package/src/commands/researcher.js +7 -2
  62. package/src/commands/resume.js +24 -1
  63. package/src/commands/review.js +7 -2
  64. package/src/commands/run.js +52 -1
  65. package/src/commands/status.js +1 -1
  66. package/src/commands/sync.js +75 -0
  67. package/src/commands/triage.js +7 -2
  68. package/src/commands/undo.js +1 -1
  69. package/src/config/defaults.js +213 -0
  70. package/src/config/loader.js +137 -0
  71. package/src/config/overrides.js +199 -0
  72. package/src/config/role-resolver.js +159 -0
  73. package/src/config/test-harness.js +78 -0
  74. package/src/config.js +30 -632
  75. package/src/git/automation.js +2 -2
  76. package/src/guards/output-guard.js +0 -2
  77. package/src/hu/acceptance-runner.js +52 -10
  78. package/src/hu/store.js +11 -0
  79. package/src/mcp/handlers/direct-handlers.js +2 -5
  80. package/src/mcp/handlers/management-handlers.js +48 -4
  81. package/src/mcp/handlers/run-handler.js +9 -8
  82. package/src/mcp/preflight.js +32 -13
  83. package/src/mcp/server-handlers.js +4 -3
  84. package/src/mcp/suggest-handler.js +3 -2
  85. package/src/mcp/tools.js +15 -0
  86. package/src/orchestrator/agent-fallback.js +1 -1
  87. package/src/orchestrator/ci-integration.js +11 -11
  88. package/src/orchestrator/config-init.js +25 -7
  89. package/src/orchestrator/direct-actions.js +7 -3
  90. package/src/orchestrator/drivers/error-recovery.js +287 -0
  91. package/src/orchestrator/drivers/init-context.js +222 -0
  92. package/src/orchestrator/drivers/iteration-loop.js +301 -0
  93. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
  94. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  95. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  96. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
  97. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  98. package/src/orchestrator/drivers/post-loop.js +370 -0
  99. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
  100. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  101. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  102. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
  103. package/src/orchestrator/drivers/pre-loop.js +431 -0
  104. package/src/orchestrator/drivers/run-hu-batch.js +367 -0
  105. package/src/orchestrator/feedback-queue.js +0 -1
  106. package/src/orchestrator/flow-control.js +4 -3
  107. package/src/orchestrator/flow-runner.js +121 -2077
  108. package/src/orchestrator/fs-leak-detector.js +156 -0
  109. package/src/orchestrator/hu-sub-pipeline.js +197 -9
  110. package/src/orchestrator/integrations.js +81 -0
  111. package/src/orchestrator/post-loop-stages.js +35 -8
  112. package/src/orchestrator/preflight-checks.js +20 -7
  113. package/src/orchestrator/repair/repair-detector.js +117 -0
  114. package/src/orchestrator/repair/repair-runner.js +142 -0
  115. package/src/orchestrator/reviewer-fallback.js +1 -1
  116. package/src/orchestrator/role-output-compressor.js +0 -1
  117. package/src/orchestrator/session-journal.js +4 -41
  118. package/src/orchestrator/solomon-escalation.js +3 -2
  119. package/src/orchestrator/solomon-rules.js +7 -3
  120. package/src/orchestrator/stages/architect-stage.js +12 -11
  121. package/src/orchestrator/stages/coder-stage.js +56 -7
  122. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  123. package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
  124. package/src/orchestrator/stages/planner-stage.js +1 -1
  125. package/src/orchestrator/stages/research-stage.js +1 -1
  126. package/src/orchestrator/stages/reviewer-stage.js +7 -7
  127. package/src/orchestrator/stages/sonar-stage.js +94 -10
  128. package/src/orchestrator/stages/stage-classes.js +84 -0
  129. package/src/orchestrator/stages/stage-executor.js +1 -1
  130. package/src/orchestrator/stages/triage-stage.js +1 -1
  131. package/src/orchestrator/standby.js +3 -2
  132. package/src/orchestrator/verification-gate.js +20 -12
  133. package/src/plan/adr-generator.js +170 -0
  134. package/src/plan/adr-loader.js +60 -0
  135. package/src/plan/plan-executor.js +19 -1
  136. package/src/plan/plan-hu-ops.js +170 -1
  137. package/src/plan/plan-reviewer.js +199 -0
  138. package/src/plan/plan-schema.js +69 -1
  139. package/src/plan/plan-store.js +40 -17
  140. package/src/plan/spec-detector.js +61 -0
  141. package/src/plan/tests-synthesizer.js +116 -0
  142. package/src/planning-game/client.js +3 -28
  143. package/src/planning-game/decomposition.js +1 -1
  144. package/src/planning-game/pipeline-adapter.js +37 -6
  145. package/src/prompts/audit.js +206 -6
  146. package/src/prompts/coder.js +172 -1
  147. package/src/prompts/hu-reviewer.js +0 -1
  148. package/src/prompts/planner.js +81 -4
  149. package/src/prompts/rtk-snippet.js +1 -1
  150. package/src/review/diff-generator.js +35 -22
  151. package/src/review/snapshot-diff.js +1 -1
  152. package/src/roles/audit-role.js +125 -13
  153. package/src/roles/coder-role.js +25 -3
  154. package/src/roles/planner-role.js +42 -1
  155. package/src/roles/repairer-role.js +181 -0
  156. package/src/roles/tester-role.js +70 -4
  157. package/src/session/mutators.js +304 -0
  158. package/src/session/runtime-overrides.js +59 -0
  159. package/src/{session-store.js → session/store.js} +2 -2
  160. package/src/skills/semantic-detector.js +10 -5
  161. package/src/skills/skill-detector.js +1 -1
  162. package/src/sonar/api.js +1 -1
  163. package/src/sonar/cloud-scanner.js +0 -1
  164. package/src/sonar/credentials.js +14 -6
  165. package/src/sonar/discovery.js +123 -0
  166. package/src/sonar/manager.js +18 -0
  167. package/src/sync/detect-drift.js +113 -0
  168. package/src/tracker-bootstrap.js +44 -0
  169. package/src/utils/agent-detect.js +15 -11
  170. package/src/utils/board-prompt-bridge.js +109 -0
  171. package/src/utils/cli-progress.js +161 -0
  172. package/src/utils/cwd-confirm.js +39 -0
  173. package/src/utils/derive-project-name-from-cwd.js +119 -0
  174. package/src/utils/display/event-handlers.js +1 -1
  175. package/src/utils/display/header.js +1 -1
  176. package/src/utils/display/session-summary.js +3 -3
  177. package/src/utils/display/solomon.js +1 -1
  178. package/src/utils/garbage-collector.js +396 -0
  179. package/src/utils/git.js +16 -4
  180. package/src/utils/locale.js +1 -1
  181. package/src/utils/paths.js +21 -1
  182. package/src/utils/process.js +1 -1
  183. package/src/utils/prompt-project-name.js +35 -0
  184. package/src/utils/role-assigner.js +1 -1
  185. package/src/utils/run-context.js +75 -0
  186. package/src/utils/run-log.js +12 -0
  187. package/src/utils/task-file.js +1 -1
  188. package/src/utils/telemetry.js +13 -1
  189. package/src/webperf/devtools-detect.js +1 -1
  190. package/templates/coder-rules.md +13 -0
  191. package/templates/roles/coder.md +34 -0
  192. package/templates/roles/repairer.md +94 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Karajan's default configuration — the baseline every user's config is
3
+ * merged on top of.
4
+ *
5
+ * This is **pure data**. It was extracted from `src/config.js` in TSK-0332
6
+ * (Oleada 2 of the v2.7.4 audit refactor) so that consumers that only
7
+ * need the shape (schema tests, display, docs) don't drag the loader's
8
+ * fs/yaml imports.
9
+ *
10
+ * Source of truth is here; `src/config.js` re-exports `DEFAULTS` for
11
+ * back-compat.
12
+ */
13
+
14
+ const DEFAULTS = {
15
+ coder: "claude",
16
+ reviewer: "codex",
17
+ roles: {
18
+ planner: { provider: null, model: null },
19
+ coder: { provider: null, model: null },
20
+ reviewer: { provider: null, model: null },
21
+ refactorer: { provider: null, model: null },
22
+ solomon: { provider: null, model: null },
23
+ researcher: { provider: null, model: null },
24
+ tester: { provider: null, model: null },
25
+ security: { provider: null, model: null },
26
+ impeccable: { provider: null, model: null },
27
+ triage: { provider: null, model: null },
28
+ discover: { provider: null, model: null },
29
+ architect: { provider: null, model: null },
30
+ hu_reviewer: { provider: null, model: null }
31
+ },
32
+ pipeline: {
33
+ planner: { enabled: false },
34
+ refactorer: { enabled: false },
35
+ solomon: { enabled: true },
36
+ researcher: { enabled: false },
37
+ tester: { enabled: true },
38
+ security: { enabled: true },
39
+ impeccable: { enabled: false },
40
+ triage: { enabled: true },
41
+ discover: { enabled: false },
42
+ architect: { enabled: false },
43
+ hu_reviewer: { enabled: false },
44
+ brain: { enabled: true },
45
+ auto_simplify: true
46
+ },
47
+ brain: {
48
+ enabled: true,
49
+ provider: "claude",
50
+ bypass_solomon_on_correctness: true,
51
+ max_consecutive_verification_failures: 2
52
+ },
53
+ review_mode: "standard",
54
+ max_iterations: 5,
55
+ hu_max_iterations: 3,
56
+ max_budget_usd: null,
57
+ review_rules: "./.karajan/review-rules.md",
58
+ coder_rules: "./.karajan/coder-rules.md",
59
+ base_branch: "main",
60
+ coder_options: { model: null, auto_approve: true, fallback_coder: null },
61
+ reviewer_options: {
62
+ output_format: "json",
63
+ require_schema: true,
64
+ model: null,
65
+ deterministic: true,
66
+ retries: 1,
67
+ fallback_reviewer: null
68
+ },
69
+ development: {
70
+ methodology: "tdd",
71
+ require_test_changes: true,
72
+ test_file_patterns: ["/tests/", "/__tests__/", ".test.", ".spec."],
73
+ source_file_extensions: [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".rb", ".php", ".cs"]
74
+ },
75
+ sonarqube: {
76
+ enabled: true,
77
+ host: "http://localhost:9000",
78
+ external: false,
79
+ container_name: "karajan-sonarqube",
80
+ network: "karajan_sonar_net",
81
+ volumes: {
82
+ data: "karajan_sonar_data",
83
+ logs: "karajan_sonar_logs",
84
+ extensions: "karajan_sonar_extensions"
85
+ },
86
+ timeouts: {
87
+ healthcheck_seconds: 5,
88
+ compose_up_ms: 300000,
89
+ compose_control_ms: 120000,
90
+ logs_ms: 30000,
91
+ scanner_ms: 900000
92
+ },
93
+ token: null,
94
+ project_key: null,
95
+ admin_user: null,
96
+ admin_password: null,
97
+ coverage: {
98
+ enabled: false,
99
+ command: null,
100
+ timeout_ms: 300000,
101
+ block_on_failure: true,
102
+ lcov_report_path: null
103
+ },
104
+ quality_gate: true,
105
+ enforcement_profile: "pragmatic",
106
+ gate_block_on: [
107
+ "new_reliability_rating=E",
108
+ "new_security_rating=E",
109
+ "new_maintainability_rating=E",
110
+ "new_coverage<80",
111
+ "new_duplicated_lines_density>5"
112
+ ],
113
+ fail_on: ["BLOCKER", "CRITICAL"],
114
+ ignore_on: ["INFO"],
115
+ max_scan_retries: 3,
116
+ scanner: {
117
+ sources: "src,public,lib",
118
+ exclusions: "**/node_modules/**,**/fake-apps/**,**/scripts/**,**/playground/**,**/dist/**,**/build/**,**/*.min.js",
119
+ test_inclusions: "**/*.test.js,**/*.spec.js,**/tests/**,**/__tests__/**",
120
+ coverage_exclusions: "**/tests/**,**/__tests__/**,**/*.test.js,**/*.spec.js",
121
+ disabled_rules: ["javascript:S1116", "javascript:S3776"]
122
+ }
123
+ },
124
+ sonarcloud: {
125
+ enabled: false,
126
+ organization: null,
127
+ token: null,
128
+ project_key: null,
129
+ host: "https://sonarcloud.io",
130
+ scanner: {
131
+ sources: "src,public,lib",
132
+ exclusions: "**/node_modules/**,**/dist/**,**/build/**,**/*.min.js",
133
+ test_inclusions: "**/*.test.js,**/*.spec.js,**/tests/**,**/__tests__/**"
134
+ }
135
+ },
136
+ hu_board: {
137
+ enabled: false,
138
+ port: 4000,
139
+ auto_start: false
140
+ },
141
+ language: "en",
142
+ hu_language: "en",
143
+ policies: {},
144
+ serena: { enabled: false },
145
+ planning_game: { enabled: false, project_id: null, codeveloper: null },
146
+ skills: {
147
+ mode: "auto",
148
+ sources: ["addyosmani", "openskills", "local"],
149
+ addyosmani: {
150
+ enabled: true,
151
+ refreshDays: 7,
152
+ repoUrl: "https://github.com/addyosmani/agent-skills.git"
153
+ }
154
+ },
155
+ ci: { enabled: false, review_event: "kj-review", comment_event: "kj-comment", comment_prefix: true },
156
+ git: { auto_commit: false, auto_push: false, auto_pr: false, auto_rebase: true, branch_prefix: "feat/" },
157
+ output: { report_dir: "./.reviews", log_level: "info", quiet: true },
158
+ budget: {
159
+ warn_threshold_pct: 80,
160
+ currency: "usd",
161
+ exchange_rate_eur: 0.92
162
+ },
163
+ model_selection: {
164
+ enabled: true,
165
+ tiers: {},
166
+ role_overrides: {}
167
+ },
168
+ session: {
169
+ max_iteration_minutes: 30,
170
+ max_total_minutes: 120,
171
+ max_planner_minutes: 60,
172
+ checkpoint_interval_minutes: 5,
173
+ max_agent_silence_minutes: 20,
174
+ fail_fast_repeats: 2,
175
+ repeat_detection_threshold: 2,
176
+ max_sonar_retries: 3,
177
+ max_reviewer_retries: 3,
178
+ max_tester_retries: 1,
179
+ max_security_retries: 1,
180
+ max_auto_resumes: 2,
181
+ expiry_days: 30
182
+ },
183
+ failFast: {
184
+ repeatThreshold: 2
185
+ },
186
+ webperf: {
187
+ enabled: true,
188
+ devtools_mcp: false,
189
+ thresholds: { lcp: 2500, cls: 0.1, inp: 200 }
190
+ },
191
+ telemetry: true,
192
+ guards: {
193
+ output: {
194
+ enabled: true,
195
+ patterns: [],
196
+ protected_files: [],
197
+ on_violation: "block"
198
+ },
199
+ perf: {
200
+ enabled: true,
201
+ patterns: [],
202
+ block_on_warning: false,
203
+ frontend_extensions: []
204
+ },
205
+ intent: {
206
+ enabled: false,
207
+ patterns: [],
208
+ confidence_threshold: 0.85
209
+ }
210
+ }
211
+ };
212
+ export { DEFAULTS };
213
+
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Config I/O layer — reads and writes Karajan's YAML config files.
3
+ *
4
+ * Extracted from `src/config.js` in TSK-0332 (Oleada 2 of the v2.7.4 audit
5
+ * refactor). The public API (`loadConfig`, `writeConfig`, `getConfigPath`,
6
+ * `getProjectConfigPath`, `loadProjectConfig`) stayed exactly the same so
7
+ * the 28+ files that imported from `src/config.js` didn't have to change.
8
+ *
9
+ * Responsibilities:
10
+ * - Resolve the global config path under `~/.karajan/kj.config.yml`
11
+ * (respecting `KJ_HOME`).
12
+ * - Resolve the project config path under `<projectDir>/.karajan/kj.config.yml`.
13
+ * - Load + merge global over DEFAULTS, then project over merged.
14
+ * - Validate the merged config via the Valibot schema.
15
+ * - Record deprecation flags (e.g. `sonarqube.enabled` → ignored since v2.7.4)
16
+ * for the orchestrator to surface as warnings at run start.
17
+ *
18
+ * Intentionally NOT here: flag overrides (src/config/overrides.js),
19
+ * role resolution (src/config/role-resolver.js), DEFAULTS (src/config/defaults.js).
20
+ */
21
+
22
+ import fs from "node:fs/promises";
23
+ import path from "node:path";
24
+ import yaml from "js-yaml";
25
+ import { ensureDir, exists } from "../utils/fs.js";
26
+ import { getKarajanHome } from "../utils/paths.js";
27
+ import { safeParseConfig, formatConfigIssues } from "./schema.js";
28
+ import { DEFAULTS } from "./defaults.js";
29
+ import { resolveTestHarness } from "./test-harness.js";
30
+
31
+ export function mergeDeep(base, override) {
32
+ const output = { ...base };
33
+ for (const [key, value] of Object.entries(override || {})) {
34
+ if (Array.isArray(value)) {
35
+ output[key] = value;
36
+ } else if (value && typeof value === "object") {
37
+ output[key] = mergeDeep(base[key] || {}, value);
38
+ } else {
39
+ output[key] = value;
40
+ }
41
+ }
42
+ return output;
43
+ }
44
+
45
+ export function getConfigPath() {
46
+ return path.join(getKarajanHome(), "kj.config.yml");
47
+ }
48
+
49
+ export function getProjectConfigPath(projectDir = process.cwd()) {
50
+ return path.join(projectDir, ".karajan", "kj.config.yml");
51
+ }
52
+
53
+ export async function loadProjectConfig(projectDir = process.cwd()) {
54
+ const projectConfigPath = getProjectConfigPath(projectDir);
55
+ if (!(await exists(projectConfigPath))) {
56
+ return null;
57
+ }
58
+ const raw = await fs.readFile(projectConfigPath, "utf8");
59
+ return yaml.load(raw, { json: true }) || {};
60
+ }
61
+
62
+ async function loadProjectPricingOverrides(projectDir = process.cwd()) {
63
+ const projectConfigPath = path.join(projectDir, ".karajan.yml");
64
+ if (!(await exists(projectConfigPath))) {
65
+ return null;
66
+ }
67
+
68
+ const raw = await fs.readFile(projectConfigPath, "utf8");
69
+ const parsed = yaml.load(raw, { json: true }) || {};
70
+ const pricing = parsed?.budget?.pricing;
71
+ if (!pricing || typeof pricing !== "object") {
72
+ return null;
73
+ }
74
+
75
+ return pricing;
76
+ }
77
+
78
+ export async function loadConfig(projectDir) {
79
+ const configPath = getConfigPath();
80
+ const projectPricing = await loadProjectPricingOverrides(projectDir);
81
+
82
+ // Load global config
83
+ let globalConfig = {};
84
+ const globalExists = await exists(configPath);
85
+ if (globalExists) {
86
+ const raw = await fs.readFile(configPath, "utf8");
87
+ globalConfig = yaml.load(raw, { json: true }) || {};
88
+ }
89
+
90
+ // Load project config (.karajan/kj.config.yml)
91
+ const projectConfig = await loadProjectConfig(projectDir);
92
+
93
+ // Merge: DEFAULTS < global < project
94
+ let merged = mergeDeep(DEFAULTS, globalConfig);
95
+ if (projectConfig) {
96
+ merged = mergeDeep(merged, projectConfig);
97
+ }
98
+
99
+ if (projectPricing) {
100
+ merged.budget = mergeDeep(merged.budget || {}, { pricing: projectPricing });
101
+ }
102
+
103
+ // KJC-TSK-0318 — schema-validate the merged config so invalid values
104
+ // (review_mode typos, max_iterations=0, out-of-range ports) surface at
105
+ // load time with a clear error instead of crashing deep inside the
106
+ // orchestrator. safeParse + explicit error prefix keeps the message
107
+ // actionable.
108
+ const validation = safeParseConfig(merged);
109
+ if (!validation.success) {
110
+ throw new Error(`Invalid Karajan config:\n${formatConfigIssues(validation.issues)}`);
111
+ }
112
+
113
+ // Deprecation tracking — `sonarqube.enabled` was a footgun: users would
114
+ // set it to `false` in their global ~/.karajan/kj.config.yml and then
115
+ // wonder why Sonar didn't run on a code task. Since v2.7.4 Sonar is
116
+ // intrinsic to Karajan for code tasks; the field is ignored. We surface
117
+ // a warning at run start (see emitConfigDeprecations in flow-runner.js)
118
+ // so users notice and clean up their config. Don't throw — keep
119
+ // back-compat so existing scripts keep working.
120
+ const userSetSonarEnabled = (globalConfig?.sonarqube?.enabled !== undefined)
121
+ || (projectConfig?.sonarqube?.enabled !== undefined);
122
+ if (userSetSonarEnabled) {
123
+ merged._deprecated = merged._deprecated || {};
124
+ merged._deprecated.sonarqubeEnabledKey = true;
125
+ }
126
+
127
+ merged.testHarness = resolveTestHarness(merged.testHarness);
128
+ return { config: merged, path: configPath, exists: globalExists, hasProjectConfig: !!projectConfig };
129
+ }
130
+
131
+ export async function writeConfig(configPath, config) {
132
+ await ensureDir(path.dirname(configPath));
133
+ await fs.writeFile(configPath, yaml.dump(config, { lineWidth: 120 }), "utf8");
134
+ }
135
+
136
+ // Declarative mappings for applyRunOverrides to reduce cognitive complexity.
137
+
@@ -0,0 +1,199 @@
1
+ /**
2
+ * CLI-flag overrides applied on top of a loaded config.
3
+ *
4
+ * `applyRunOverrides(config, flags)` takes the merged config from the
5
+ * loader plus the CLI flags (also forwarded from MCP tool args) and
6
+ * produces the final config the orchestrator operates on. Overrides are
7
+ * declarative where possible (ROLE_PROVIDER_FLAGS, ROLE_MODEL_FLAGS,
8
+ * PIPELINE_ENABLE_FLAGS, SCALAR_FLAGS, UNDEF_CHECK_FLAGS) and imperative
9
+ * only where the mapping is not 1:1 (methodology, CI, output mode,
10
+ * deprecated flags like `--no-sonar`).
11
+ *
12
+ * Extracted from `src/config.js` in TSK-0332 (Oleada 2 of the v2.7.4
13
+ * audit refactor). Public API: `applyRunOverrides`. The apply* helpers
14
+ * are internal.
15
+ */
16
+
17
+ import { mergeDeep } from "./loader.js";
18
+ import { DEFAULTS } from "./defaults.js";
19
+ import { safeParseConfig, formatConfigIssues } from "./schema.js";
20
+
21
+ const ROLE_PROVIDER_FLAGS = [
22
+ ["planner", "planner"], ["coder", "coder"], ["reviewer", "reviewer"],
23
+ ["refactorer", "refactorer"], ["solomon", "solomon"], ["researcher", "researcher"],
24
+ ["tester", "tester"], ["security", "security"], ["triage", "triage"],
25
+ ["discover", "discover"], ["architect", "architect"]
26
+ ];
27
+
28
+ // Role model flags: [flagName, roleName] — truthy check, String coercion
29
+ const ROLE_MODEL_FLAGS = [
30
+ ["plannerModel", "planner"], ["coderModel", "coder"], ["reviewerModel", "reviewer"],
31
+ ["refactorerModel", "refactorer"], ["solomonModel", "solomon"], ["discoverModel", "discover"],
32
+ ["architectModel", "architect"]
33
+ ];
34
+
35
+ // Pipeline enable flags: [flagName, pipelineKey] — !== undefined check, Boolean coercion
36
+ const PIPELINE_ENABLE_FLAGS = [
37
+ ["enablePlanner", "planner"], ["enableRefactorer", "refactorer"],
38
+ ["enableSolomon", "solomon"], ["enableResearcher", "researcher"],
39
+ ["enableTester", "tester"], ["enableSecurity", "security"], ["enableImpeccable", "impeccable"],
40
+ ["enableTriage", "triage"], ["enableDiscover", "discover"],
41
+ ["enableArchitect", "architect"],
42
+ ["enableHuReviewer", "hu_reviewer"]
43
+ ];
44
+
45
+ const AUTO_SIMPLIFY_FLAG = "autoSimplify";
46
+
47
+ // Scalar flags: [flagName, setter] — truthy check.
48
+ // Note: max_iterations was previously here but `0` was silently ignored
49
+ // (truthy check skipped it). It now lives in UNDEF_CHECK_FLAGS so
50
+ // `--max-iterations 0` reaches the schema validator and errors cleanly
51
+ // instead of falling through to the default (KJC-TSK-0318).
52
+ const SCALAR_FLAGS = [
53
+ ["mode", (out, v) => { out.review_mode = v; }],
54
+ ["maxIterationMinutes", (out, v) => { out.session.max_iteration_minutes = Number(v); }],
55
+ ["maxTotalMinutes", (out, v) => { out.session.max_total_minutes = Number(v); }],
56
+ ["checkpointInterval", (out, v) => { out.session.checkpoint_interval_minutes = Number(v); }],
57
+ ["baseBranch", (out, v) => { out.base_branch = v; }],
58
+ ["coderFallback", (out, v) => { out.coder_options.fallback_coder = v; }],
59
+ ["reviewerFallback", (out, v) => { out.reviewer_options.fallback_reviewer = v; }],
60
+ ["taskType", (out, v) => { out.taskType = String(v); }],
61
+ ["branchPrefix", (out, v) => { out.git.branch_prefix = String(v); }]
62
+ ];
63
+
64
+ // Boolean/undefined-check flags: [flagName, setter] — !== undefined check.
65
+ // These preserve falsy values (false, 0) so --no-rebase correctly sets
66
+ // auto_rebase=false and --reviewer-retries 0 correctly disables retries.
67
+ const UNDEF_CHECK_FLAGS = [
68
+ ["maxIterations", (out, v) => { out.max_iterations = Number(v); }],
69
+ ["reviewerRetries", (out, v) => { out.reviewer_options.retries = Number(v); }],
70
+ ["autoCommit", (out, v) => { out.git.auto_commit = Boolean(v); }],
71
+ ["autoPush", (out, v) => { out.git.auto_push = Boolean(v); }],
72
+ ["autoPr", (out, v) => { out.git.auto_pr = Boolean(v); }],
73
+ ["autoRebase", (out, v) => { out.git.auto_rebase = Boolean(v); }],
74
+ ["enableSerena", (out, v) => { out.serena.enabled = Boolean(v); }]
75
+ ];
76
+
77
+ function applyRoleOverrides(out, flags) {
78
+ for (const [flag, role] of ROLE_PROVIDER_FLAGS) {
79
+ if (flags[flag]) out.roles[role].provider = flags[flag];
80
+ }
81
+ // coder/reviewer also update top-level aliases
82
+ if (flags.coder) out.coder = flags.coder;
83
+ if (flags.reviewer) out.reviewer = flags.reviewer;
84
+
85
+ for (const [flag, role] of ROLE_MODEL_FLAGS) {
86
+ if (flags[flag]) out.roles[role].model = String(flags[flag]);
87
+ }
88
+ // reviewerModel also updates reviewer_options
89
+ if (flags.reviewerModel) out.reviewer_options.model = String(flags.reviewerModel);
90
+ }
91
+
92
+ function applyPipelineOverrides(out, flags) {
93
+ for (const [flag, key] of PIPELINE_ENABLE_FLAGS) {
94
+ if (flags[flag] !== undefined) out.pipeline[key].enabled = Boolean(flags[flag]);
95
+ }
96
+ if (flags.enableReviewer !== undefined) {
97
+ out.pipeline.reviewer = out.pipeline.reviewer || {};
98
+ out.pipeline.reviewer.enabled = Boolean(flags.enableReviewer);
99
+ }
100
+ }
101
+
102
+ function applyScalarAndBooleanOverrides(out, flags) {
103
+ for (const [flag, setter] of SCALAR_FLAGS) {
104
+ if (flags[flag]) setter(out, flags[flag]);
105
+ }
106
+ for (const [flag, setter] of UNDEF_CHECK_FLAGS) {
107
+ if (flags[flag] !== undefined) setter(out, flags[flag]);
108
+ }
109
+ }
110
+
111
+ function applyMethodologyOverride(out, flags) {
112
+ if (!flags.methodology) return;
113
+ const methodology = String(flags.methodology).toLowerCase();
114
+ out.development.methodology = methodology;
115
+ out.development.require_test_changes = methodology === "tdd";
116
+ }
117
+
118
+ function applyCiOverride(out, flags) {
119
+ out.ci = out.ci || { enabled: false };
120
+ if (flags.enableCi === undefined) return;
121
+ out.ci.enabled = Boolean(flags.enableCi);
122
+ // CI requires git automation (commit + push + PR)
123
+ if (out.ci.enabled) {
124
+ out.git.auto_commit = true;
125
+ out.git.auto_push = true;
126
+ out.git.auto_pr = true;
127
+ }
128
+ }
129
+
130
+ function applyOutputModeOverrides(out, flags) {
131
+ out.output = out.output || {};
132
+ // --verbose explicitly overrides quiet
133
+ if (flags.verbose === true) {
134
+ out.output.quiet = false;
135
+ } else if (flags.quiet === true) {
136
+ out.output.quiet = true;
137
+ }
138
+ // quiet defaults to true (set in DEFAULTS)
139
+ }
140
+
141
+ function applyMiscOverrides(out, flags) {
142
+ if (flags[AUTO_SIMPLIFY_FLAG] !== undefined) out.pipeline.auto_simplify = Boolean(flags[AUTO_SIMPLIFY_FLAG]);
143
+ // `--no-sonar` and `sonarqube.enabled: false` are deprecated since v2.7.4.
144
+ // Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests) and
145
+ // skipped for non-code tasks (audit/doc/infra/analysis/no-code) by policy.
146
+ // The flag is still accepted to avoid breaking scripts; it now emits a
147
+ // warning at run start and is otherwise ignored. See preflight-checks.js
148
+ // and flow-runner.js for the actual gate (resolvedPolicies.sonar).
149
+ if (flags.noSonar || flags.sonar === false) {
150
+ out._deprecated = out._deprecated || {};
151
+ out._deprecated.noSonarFlag = true;
152
+ }
153
+ out.sonarcloud = out.sonarcloud || {};
154
+ if (flags.enableSonarcloud === true) out.sonarcloud.enabled = true;
155
+ if (flags.noSonarcloud === true || flags.sonarcloud === false) out.sonarcloud.enabled = false;
156
+
157
+ out.planning_game = out.planning_game || {};
158
+ if (flags.pgTask) out.planning_game.enabled = true;
159
+ if (flags.pgProject) out.planning_game.project_id = flags.pgProject;
160
+
161
+ out.model_selection = out.model_selection || { enabled: true, tiers: {}, role_overrides: {} };
162
+ if (flags.smartModels === true) out.model_selection.enabled = true;
163
+ if (flags.smartModels === false || flags.noSmartModels === true) out.model_selection.enabled = false;
164
+ }
165
+
166
+ export function applyRunOverrides(config, flags) {
167
+ const out = mergeDeep(config, {});
168
+ out.coder_options = out.coder_options || {};
169
+ out.reviewer_options = out.reviewer_options || {};
170
+ out.session = out.session || {};
171
+ out.git = out.git || {};
172
+ out.development = out.development || {};
173
+ out.sonarqube = out.sonarqube || {};
174
+ if (out.max_budget_usd === undefined || out.max_budget_usd === null) {
175
+ out.max_budget_usd = out.session.max_budget_usd ?? null;
176
+ }
177
+ out.budget = mergeDeep(DEFAULTS.budget, out.budget || {});
178
+ out.roles = mergeDeep(DEFAULTS.roles, out.roles || {});
179
+ out.pipeline = mergeDeep(DEFAULTS.pipeline, out.pipeline || {});
180
+ out.serena = out.serena || { enabled: false };
181
+
182
+ applyRoleOverrides(out, flags);
183
+ applyPipelineOverrides(out, flags);
184
+ applyScalarAndBooleanOverrides(out, flags);
185
+ applyMethodologyOverride(out, flags);
186
+ applyCiOverride(out, flags);
187
+ applyMiscOverrides(out, flags);
188
+ applyOutputModeOverrides(out, flags);
189
+
190
+ // KJC-TSK-0318 — re-run schema validation after overrides so CLI flags
191
+ // that would produce an invalid config (e.g. --max-iterations 0) fail
192
+ // fast with the same clear message loadConfig would have produced.
193
+ const validation = safeParseConfig(out);
194
+ if (!validation.success) {
195
+ throw new Error(`Invalid Karajan config after CLI overrides:\n${formatConfigIssues(validation.issues)}`);
196
+ }
197
+
198
+ return out;
199
+ }