karajan-code 1.13.2 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -97,6 +97,7 @@ const DEFAULTS = {
|
|
|
97
97
|
disabled_rules: ["javascript:S1116", "javascript:S3776"]
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
|
+
policies: {},
|
|
100
101
|
serena: { enabled: false },
|
|
101
102
|
planning_game: { enabled: false, project_id: null, codeveloper: null },
|
|
102
103
|
becaria: { enabled: false, review_event: "becaria-review", comment_event: "becaria-comment", comment_prefix: true },
|
|
@@ -285,6 +286,7 @@ export function applyRunOverrides(config, flags) {
|
|
|
285
286
|
out.development.methodology = methodology;
|
|
286
287
|
out.development.require_test_changes = methodology === "tdd";
|
|
287
288
|
}
|
|
289
|
+
if (flags.taskType) out.taskType = String(flags.taskType);
|
|
288
290
|
if (flags.noSonar || flags.sonar === false) out.sonarqube.enabled = false;
|
|
289
291
|
out.serena = out.serena || { enabled: false };
|
|
290
292
|
if (flags.enableSerena !== undefined) out.serena.enabled = Boolean(flags.enableSerena);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const VALID_TASK_TYPES = ["sw", "infra", "doc", "add-tests", "refactor"];
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_POLICIES = {
|
|
4
|
+
sw: { tdd: true, sonar: true, reviewer: true, testsRequired: true },
|
|
5
|
+
infra: { tdd: false, sonar: false, reviewer: true, testsRequired: false },
|
|
6
|
+
doc: { tdd: false, sonar: false, reviewer: true, testsRequired: false },
|
|
7
|
+
"add-tests": { tdd: false, sonar: true, reviewer: true, testsRequired: true },
|
|
8
|
+
refactor: { tdd: true, sonar: true, reviewer: true, testsRequired: false },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const FALLBACK_TYPE = "sw";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve pipeline policies for a given taskType.
|
|
15
|
+
* Unknown / null / undefined taskType falls back to "sw" (conservative).
|
|
16
|
+
* configOverrides optionally merges over defaults per taskType.
|
|
17
|
+
*/
|
|
18
|
+
export function resolvePolicies(taskType, configOverrides) {
|
|
19
|
+
const resolvedType = VALID_TASK_TYPES.includes(taskType) ? taskType : FALLBACK_TYPE;
|
|
20
|
+
const base = { ...DEFAULT_POLICIES[resolvedType] };
|
|
21
|
+
const overrides = configOverrides?.[resolvedType];
|
|
22
|
+
if (overrides && typeof overrides === "object") {
|
|
23
|
+
Object.assign(base, overrides);
|
|
24
|
+
}
|
|
25
|
+
return base;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve policies for a taskType and return a flat object with the resolved
|
|
30
|
+
* taskType plus all policy flags. This is the main entry point for the
|
|
31
|
+
* orchestrator to determine which pipeline stages to enable/disable.
|
|
32
|
+
*/
|
|
33
|
+
export function applyPolicies({ taskType, policies } = {}) {
|
|
34
|
+
const resolvedType = VALID_TASK_TYPES.includes(taskType) ? taskType : FALLBACK_TYPE;
|
|
35
|
+
const resolved = resolvePolicies(taskType, policies);
|
|
36
|
+
return { taskType: resolvedType, ...resolved };
|
|
37
|
+
}
|
|
@@ -3,7 +3,7 @@ import { CoderRole } from "../roles/coder-role.js";
|
|
|
3
3
|
import { RefactorerRole } from "../roles/refactorer-role.js";
|
|
4
4
|
import { SonarRole } from "../roles/sonar-role.js";
|
|
5
5
|
import { addCheckpoint, markSessionStatus, saveSession, pauseSession } from "../session-store.js";
|
|
6
|
-
import { generateDiff } from "../review/diff-generator.js";
|
|
6
|
+
import { generateDiff, getUntrackedFiles } from "../review/diff-generator.js";
|
|
7
7
|
import { evaluateTddPolicy } from "../review/tdd-policy.js";
|
|
8
8
|
import { validateReviewResult } from "../review/schema.js";
|
|
9
9
|
import { filterReviewScope, buildDeferredContext } from "../review/scope-filter.js";
|
|
@@ -198,7 +198,8 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
198
198
|
export async function runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration, askQuestion }) {
|
|
199
199
|
logger.setContext({ iteration, stage: "tdd" });
|
|
200
200
|
const tddDiff = await generateDiff({ baseRef: session.session_start_sha });
|
|
201
|
-
const
|
|
201
|
+
const untrackedFiles = await getUntrackedFiles();
|
|
202
|
+
const tddEval = evaluateTddPolicy(tddDiff, config.development, untrackedFiles);
|
|
202
203
|
await addCheckpoint(session, {
|
|
203
204
|
stage: "tdd-policy",
|
|
204
205
|
iteration,
|
|
@@ -227,34 +228,42 @@ export async function runTddCheckStage({ config, logger, emitter, eventBase, ses
|
|
|
227
228
|
session.repeated_issue_count += 1;
|
|
228
229
|
await saveSession(session);
|
|
229
230
|
if (session.repeated_issue_count >= config.session.fail_fast_repeats) {
|
|
230
|
-
const question = `TDD policy has failed ${session.repeated_issue_count} times. The coder is not creating tests. How should we proceed? Issue: ${tddEval.reason}`;
|
|
231
|
-
if (askQuestion) {
|
|
232
|
-
const answer = await askQuestion(question, { iteration, stage: "tdd" });
|
|
233
|
-
if (answer) {
|
|
234
|
-
session.last_reviewer_feedback += `\nUser guidance: ${answer}`;
|
|
235
|
-
session.repeated_issue_count = 0;
|
|
236
|
-
await saveSession(session);
|
|
237
|
-
return { action: "continue" };
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
await pauseSession(session, {
|
|
241
|
-
question,
|
|
242
|
-
context: {
|
|
243
|
-
iteration,
|
|
244
|
-
stage: "tdd",
|
|
245
|
-
lastFeedback: tddEval.message,
|
|
246
|
-
repeatedCount: session.repeated_issue_count
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
231
|
emitProgress(
|
|
250
232
|
emitter,
|
|
251
|
-
makeEvent("
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
detail: { question, sessionId: session.id }
|
|
233
|
+
makeEvent("solomon:escalate", { ...eventBase, stage: "tdd" }, {
|
|
234
|
+
message: `TDD sub-loop limit reached (${session.repeated_issue_count}/${config.session.fail_fast_repeats})`,
|
|
235
|
+
detail: { subloop: "tdd", retryCount: session.repeated_issue_count, reason: tddEval.reason }
|
|
255
236
|
})
|
|
256
237
|
);
|
|
257
|
-
|
|
238
|
+
|
|
239
|
+
const solomonResult = await invokeSolomon({
|
|
240
|
+
config, logger, emitter, eventBase, stage: "tdd", askQuestion, session, iteration,
|
|
241
|
+
conflict: {
|
|
242
|
+
stage: "tdd",
|
|
243
|
+
task: session.task,
|
|
244
|
+
iterationCount: session.repeated_issue_count,
|
|
245
|
+
maxIterations: config.session.fail_fast_repeats,
|
|
246
|
+
reason: tddEval.reason,
|
|
247
|
+
sourceFiles: tddEval.sourceFiles,
|
|
248
|
+
testFiles: tddEval.testFiles,
|
|
249
|
+
history: [{ agent: "tdd-policy", feedback: tddEval.message }]
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (solomonResult.action === "pause") {
|
|
254
|
+
return { action: "pause", result: { paused: true, sessionId: session.id, question: solomonResult.question, context: "tdd_fail_fast" } };
|
|
255
|
+
}
|
|
256
|
+
if (solomonResult.action === "continue") {
|
|
257
|
+
if (solomonResult.humanGuidance) {
|
|
258
|
+
session.last_reviewer_feedback += `\nUser guidance: ${solomonResult.humanGuidance}`;
|
|
259
|
+
}
|
|
260
|
+
session.repeated_issue_count = 0;
|
|
261
|
+
await saveSession(session);
|
|
262
|
+
return { action: "continue" };
|
|
263
|
+
}
|
|
264
|
+
if (solomonResult.action === "subtask") {
|
|
265
|
+
return { action: "pause", result: { paused: true, sessionId: session.id, subtask: solomonResult.subtask, context: "tdd_subtask" } };
|
|
266
|
+
}
|
|
258
267
|
}
|
|
259
268
|
return { action: "continue" };
|
|
260
269
|
}
|
package/src/orchestrator.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
incrementalPush
|
|
23
23
|
} from "./git/automation.js";
|
|
24
24
|
import { resolveRoleMdPath, loadFirstExisting } from "./roles/base-role.js";
|
|
25
|
+
import { applyPolicies } from "./guards/policy-resolver.js";
|
|
25
26
|
import { resolveReviewProfile } from "./review/profiles.js";
|
|
26
27
|
import { CoderRole } from "./roles/coder-role.js";
|
|
27
28
|
import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
|
|
@@ -47,6 +48,10 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
47
48
|
|
|
48
49
|
// --- Dry-run: return summary without executing anything ---
|
|
49
50
|
if (flags.dryRun) {
|
|
51
|
+
const dryRunPolicies = applyPolicies({
|
|
52
|
+
taskType: flags.taskType || config.taskType || null,
|
|
53
|
+
policies: config.policies,
|
|
54
|
+
});
|
|
50
55
|
const projectDir = config.projectDir || process.cwd();
|
|
51
56
|
const { rules: reviewRules } = await resolveReviewProfile({ mode: config.review_mode, projectDir });
|
|
52
57
|
const coderRules = await loadFirstExisting(resolveRoleMdPath("coder", projectDir));
|
|
@@ -56,6 +61,7 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
56
61
|
const summary = {
|
|
57
62
|
dry_run: true,
|
|
58
63
|
task,
|
|
64
|
+
policies: dryRunPolicies,
|
|
59
65
|
roles: {
|
|
60
66
|
planner: plannerRole,
|
|
61
67
|
coder: coderRole,
|
|
@@ -275,6 +281,32 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
275
281
|
if (flags.enableTester !== undefined) testerEnabled = Boolean(flags.enableTester);
|
|
276
282
|
if (flags.enableSecurity !== undefined) securityEnabled = Boolean(flags.enableSecurity);
|
|
277
283
|
|
|
284
|
+
// --- Policy resolver: gate stages by taskType ---
|
|
285
|
+
const resolvedPolicies = applyPolicies({
|
|
286
|
+
taskType: flags.taskType || config.taskType || null,
|
|
287
|
+
policies: config.policies,
|
|
288
|
+
});
|
|
289
|
+
session.resolved_policies = resolvedPolicies;
|
|
290
|
+
|
|
291
|
+
// Apply policy gates on shallow copies (never mutate the caller's config)
|
|
292
|
+
if (!resolvedPolicies.tdd) {
|
|
293
|
+
config = { ...config, development: { ...config.development, methodology: "standard", require_test_changes: false } };
|
|
294
|
+
}
|
|
295
|
+
if (!resolvedPolicies.sonar) {
|
|
296
|
+
config = { ...config, sonarqube: { ...config.sonarqube, enabled: false } };
|
|
297
|
+
}
|
|
298
|
+
if (!resolvedPolicies.reviewer) {
|
|
299
|
+
reviewerEnabled = false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
emitProgress(
|
|
303
|
+
emitter,
|
|
304
|
+
makeEvent("policies:resolved", eventBase, {
|
|
305
|
+
message: `Policies resolved for taskType="${resolvedPolicies.taskType}"`,
|
|
306
|
+
detail: resolvedPolicies
|
|
307
|
+
})
|
|
308
|
+
);
|
|
309
|
+
|
|
278
310
|
// --- Researcher (pre-planning) ---
|
|
279
311
|
let researchContext = null;
|
|
280
312
|
if (researcherEnabled) {
|
|
@@ -20,3 +20,9 @@ export async function generateDiff({ baseRef }) {
|
|
|
20
20
|
}
|
|
21
21
|
return result.stdout;
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
export async function getUntrackedFiles() {
|
|
25
|
+
const result = await runCommand("git", ["ls-files", "--others", "--exclude-standard"]);
|
|
26
|
+
if (result.exitCode !== 0) return [];
|
|
27
|
+
return result.stdout.trim().split("\n").filter(Boolean);
|
|
28
|
+
}
|
package/src/review/tdd-policy.js
CHANGED
|
@@ -19,13 +19,15 @@ function isSourceFile(file, extensions = []) {
|
|
|
19
19
|
return extensions.some((ext) => file.endsWith(ext));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export function evaluateTddPolicy(diff, developmentConfig = {}) {
|
|
22
|
+
export function evaluateTddPolicy(diff, developmentConfig = {}, untrackedFiles = []) {
|
|
23
23
|
const requireTestChanges = developmentConfig.require_test_changes !== false;
|
|
24
24
|
const patterns = developmentConfig.test_file_patterns || ["/tests/", "/__tests__/", ".test.", ".spec."];
|
|
25
25
|
const extensions =
|
|
26
26
|
developmentConfig.source_file_extensions || [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".rb", ".php", ".cs"];
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const diffFiles = extractChangedFiles(diff);
|
|
29
|
+
const extra = Array.isArray(untrackedFiles) ? untrackedFiles : [];
|
|
30
|
+
const files = [...new Set([...diffFiles, ...extra])];
|
|
29
31
|
const sourceFiles = files.filter((f) => isSourceFile(f, extensions) && !isTestFile(f, patterns));
|
|
30
32
|
const testFiles = files.filter((f) => isTestFile(f, patterns));
|
|
31
33
|
|