karajan-code 2.7.3 → 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 +4 -4
- 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 +97 -27
- package/src/checks/runner.js +26 -10
- package/src/checks/sonar.js +1 -1
- package/src/checks/system.js +1 -1
- package/src/checks/tokens.js +77 -24
- 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 -609
- 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 -2019
- 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 +30 -3
- 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} +8 -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,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
|
+
}
|