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,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-loop driver — extracted from src/orchestrator/flow-runner.js in
|
|
3
|
+
* TSK-0335 (Oleada 3 of the v2.7.4 audit refactor).
|
|
4
|
+
*
|
|
5
|
+
* Everything that runs BEFORE the coder/reviewer iteration loop:
|
|
6
|
+
*
|
|
7
|
+
* - runPreLoopStages: the main pre-loop orchestration
|
|
8
|
+
* (HU-reviewer → intent → discover → triage →
|
|
9
|
+
* Brain decisor → domain → auto-simplify →
|
|
10
|
+
* PG decomposition → TDD detect → preflight →
|
|
11
|
+
* plan injection → planning phases →
|
|
12
|
+
* .gitignore update → auto-HU → skill
|
|
13
|
+
* auto-install).
|
|
14
|
+
* - runPlanningPhases: researcher → architect → planner.
|
|
15
|
+
* - emitConfigDeprecations: one-shot warnings for ignored config keys.
|
|
16
|
+
* - ensureAddyosmaniSkills: refresh + resolve addyosmani/agent-skills.
|
|
17
|
+
* - maybeGenerateAutoHuBatch: auto-generate HUs when triage recommends
|
|
18
|
+
* decomposition and no manual huFile was passed.
|
|
19
|
+
*
|
|
20
|
+
* Extracted verbatim; no behavior change. flow-runner.js now imports these
|
|
21
|
+
* instead of defining them. initFlowContext lives in ./init-context.js to
|
|
22
|
+
* keep each driver under the 600-LOC ceiling. Part of breaking up the
|
|
23
|
+
* 2254-LOC god-module — see docs/ARCHITECTURE.md and the TSK-0335
|
|
24
|
+
* acceptance criteria.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { emitProgress, makeEvent } from "../../utils/events.js";
|
|
28
|
+
import { resolveRole } from "../../config.js";
|
|
29
|
+
import { classifyIntent } from "../../guards/intent-guard.js";
|
|
30
|
+
import { persistInlineDomain } from "../../domains/domain-loader.js";
|
|
31
|
+
import { detectTestFramework } from "../../utils/project-detect.js";
|
|
32
|
+
import { detectNeededSkills, autoInstallSkills } from "../../skills/skill-detector.js";
|
|
33
|
+
import { refineSkillsSemantically, resolveSkillsMode } from "../../skills/semantic-detector.js";
|
|
34
|
+
import { saveSession } from "../../session/store.js";
|
|
35
|
+
import {
|
|
36
|
+
setPreflight, setAutoInstalledSkills, setSkillsRecommended,
|
|
37
|
+
} from "../../session/mutators.js";
|
|
38
|
+
import {
|
|
39
|
+
runResearcherStage, runArchitectStage, runPlannerStage,
|
|
40
|
+
runDiscoverStage, runHuReviewerStage,
|
|
41
|
+
} from "../pre-loop-stages.js";
|
|
42
|
+
// TSK-0336: triage goes through the StageRegistry / StageExecutor contract
|
|
43
|
+
// so canRun / execute / onFailure are actually exercised in production.
|
|
44
|
+
// The registry is a singleton with TriageStage / CoderStage / ReviewerStage
|
|
45
|
+
// pre-registered; `runStage` returns null when `canRun` vetoes execution.
|
|
46
|
+
import { stageRegistry } from "../stages/stage-classes.js";
|
|
47
|
+
import { runStage } from "../stages/stage-executor.js";
|
|
48
|
+
import { runDomainCuratorStage } from "../stages/domain-curator-stage.js";
|
|
49
|
+
import { runPreflightChecks } from "../preflight-checks.js";
|
|
50
|
+
import { injectLoadedPlan } from "./pre-loop-phases/inject-loaded-plan.js";
|
|
51
|
+
import { emitConfigDeprecations } from "./pre-loop-phases/config-deprecations.js";
|
|
52
|
+
import { ensureAddyosmaniSkills } from "./pre-loop-phases/ensure-addyosmani-skills.js";
|
|
53
|
+
import { maybeGenerateAutoHuBatch } from "./pre-loop-phases/auto-hu-batch.js";
|
|
54
|
+
|
|
55
|
+
// Re-export for back-compat with any external caller; the canonical
|
|
56
|
+
// definitions now live in ./pre-loop-phases/.
|
|
57
|
+
export { ensureAddyosmaniSkills };
|
|
58
|
+
import {
|
|
59
|
+
applyTriageOverrides, applyAutoSimplify, applyFlagOverrides,
|
|
60
|
+
resolvePipelinePolicies, updateGitignoreForStack,
|
|
61
|
+
} from "../config-init.js";
|
|
62
|
+
import { tryCiComment } from "../ci-integration.js";
|
|
63
|
+
import { getIntegration } from "../integrations.js";
|
|
64
|
+
|
|
65
|
+
export async function runPreLoopStages({ config, logger, emitter, eventBase, session, flags, pipelineFlags, coderRole, trackBudget, task, askQuestion, pgTaskId, pgProject, stageResults, brainCtx }) {
|
|
66
|
+
// --- HU Reviewer (first stage, before everything else, opt-in) ---
|
|
67
|
+
const huFile = flags.huFile || null;
|
|
68
|
+
if (flags.enableHuReviewer !== undefined) pipelineFlags.huReviewerEnabled = Boolean(flags.enableHuReviewer);
|
|
69
|
+
if (pipelineFlags.huReviewerEnabled && huFile) {
|
|
70
|
+
const huResult = await runHuReviewerStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, huFile, askQuestion, pgStories: null });
|
|
71
|
+
stageResults.huReviewer = huResult.stageResult;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- Intent classifier (deterministic pre-triage, opt-in) ---
|
|
75
|
+
if (config.guards?.intent?.enabled) {
|
|
76
|
+
const intentResult = classifyIntent(task, config);
|
|
77
|
+
stageResults.intent = intentResult;
|
|
78
|
+
if (intentResult.classified) {
|
|
79
|
+
emitProgress(emitter, makeEvent("intent:classified", { ...eventBase, stage: "intent" }, {
|
|
80
|
+
message: `Intent classified: ${intentResult.taskType} (${intentResult.level}) — ${intentResult.message}`,
|
|
81
|
+
detail: intentResult
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Discover (pre-triage, opt-in) ---
|
|
87
|
+
if (flags.enableDiscover !== undefined) pipelineFlags.discoverEnabled = Boolean(flags.enableDiscover);
|
|
88
|
+
if (pipelineFlags.discoverEnabled) {
|
|
89
|
+
const discoverResult = await runDiscoverStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget });
|
|
90
|
+
stageResults.discover = discoverResult.stageResult;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- Triage (always on) — routed through StageRegistry (TSK-0336) ---
|
|
94
|
+
// canRun here is `pipelineFlags.triageEnabled !== false`; triageEnabled is
|
|
95
|
+
// never set to false in production, so behavior matches the previous
|
|
96
|
+
// unconditional call. If a caller ever flips it off, runStage returns null
|
|
97
|
+
// and we skip apply+persist with reasonable defaults (no overrides, no
|
|
98
|
+
// stageResult).
|
|
99
|
+
const triageCtx = { config, logger, emitter, eventBase, session, coderRole, trackBudget, pipelineFlags };
|
|
100
|
+
const triageResult = await runStage(stageRegistry.get("triage"), triageCtx) ?? { roleOverrides: null, stageResult: null };
|
|
101
|
+
applyTriageOverrides(pipelineFlags, triageResult.roleOverrides);
|
|
102
|
+
stageResults.triage = triageResult.stageResult;
|
|
103
|
+
|
|
104
|
+
// --- Brain decisor (opt-in, intent-driven routing) ---
|
|
105
|
+
//
|
|
106
|
+
// When enabled, translates triage output + task text + CLI overrides into a
|
|
107
|
+
// structured Decision and applies it as pipelineFlags overrides. The rest
|
|
108
|
+
// of the orchestrator continues to branch on pipelineFlags as before — the
|
|
109
|
+
// Brain is a router that adjusts WHICH roles run, not HOW they run.
|
|
110
|
+
//
|
|
111
|
+
// Flag precedence:
|
|
112
|
+
// 1. flags.brain === "off" → forced off
|
|
113
|
+
// 2. flags.brain === "on" → forced on
|
|
114
|
+
// 3. config.brain.decisor.enabled (if set)
|
|
115
|
+
// 4. config.testHarness.defaultBrainDecisor (test harness = false)
|
|
116
|
+
// 5. true in production
|
|
117
|
+
const brainDefault = config?.testHarness?.defaultBrainDecisor ?? true;
|
|
118
|
+
let brainDecisorEnabled = config?.brain?.decisor?.enabled ?? brainDefault;
|
|
119
|
+
if (flags?.brain === "off") brainDecisorEnabled = false;
|
|
120
|
+
if (flags?.brain === "on") brainDecisorEnabled = true;
|
|
121
|
+
if (brainDecisorEnabled) {
|
|
122
|
+
try {
|
|
123
|
+
const { buildDecision, applyDecisionToFlags } = await import("../../brain/decisor.js");
|
|
124
|
+
const { createTracker, recordDecision, checkLimits } = await import("../../brain/decision-tracker.js");
|
|
125
|
+
const tracker = createTracker(session, { max: config?.brain?.decisor?.maxDecisions });
|
|
126
|
+
const limitStatus = checkLimits(tracker);
|
|
127
|
+
if (limitStatus.status === "ok") {
|
|
128
|
+
let decision = buildDecision({
|
|
129
|
+
triage: triageResult.stageResult,
|
|
130
|
+
task,
|
|
131
|
+
config,
|
|
132
|
+
overrides: {
|
|
133
|
+
// Commander variadic --force-role a b → flags.forceRole = ["a","b"].
|
|
134
|
+
// Back-compat: accept both singular (commander) and plural (legacy / API).
|
|
135
|
+
forceRoles: flags?.forceRole || flags?.forceRoles || [],
|
|
136
|
+
skipRoles: flags?.skipRole || flags?.skipRoles || [],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
// If the decision is low-confidence or ambiguous, consult Solomon
|
|
140
|
+
// for a refined routing suggestion. Solomon's ruling is advisory —
|
|
141
|
+
// if it fails or returns nothing useful, the baseline decision stands.
|
|
142
|
+
if (decision.consultSolomon) {
|
|
143
|
+
try {
|
|
144
|
+
const { consultSolomonForRouting } = await import("../../brain/solomon-consult.js");
|
|
145
|
+
decision = await consultSolomonForRouting({
|
|
146
|
+
decision, triage: triageResult.stageResult, task, config, logger,
|
|
147
|
+
emitter, eventBase, session,
|
|
148
|
+
});
|
|
149
|
+
} catch (err) {
|
|
150
|
+
logger.warn(`Brain → Solomon consult failed (non-blocking): ${err.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const newFlags = applyDecisionToFlags(decision, pipelineFlags);
|
|
154
|
+
Object.assign(pipelineFlags, newFlags);
|
|
155
|
+
recordDecision(tracker, decision, {
|
|
156
|
+
taskType: triageResult.stageResult?.taskType,
|
|
157
|
+
level: triageResult.stageResult?.level,
|
|
158
|
+
});
|
|
159
|
+
emitProgress(emitter, makeEvent("brain:decision", { ...eventBase, stage: "brain" }, {
|
|
160
|
+
status: "ok",
|
|
161
|
+
message: `Brain routing: ${decision.rolesOn.length} role(s) active — ${decision.rationale}`,
|
|
162
|
+
detail: {
|
|
163
|
+
rolesOn: decision.rolesOn,
|
|
164
|
+
rolesOff: decision.rolesOff,
|
|
165
|
+
confidence: decision.confidence,
|
|
166
|
+
consultSolomon: decision.consultSolomon,
|
|
167
|
+
appliedOverrides: decision.appliedOverrides,
|
|
168
|
+
},
|
|
169
|
+
}));
|
|
170
|
+
} else {
|
|
171
|
+
logger.warn(`Brain decisor skipped: ${limitStatus.detail}`);
|
|
172
|
+
emitProgress(emitter, makeEvent("brain:decision", { ...eventBase, stage: "brain" }, {
|
|
173
|
+
status: "warn",
|
|
174
|
+
message: `Brain decisor skipped: ${limitStatus.status}`,
|
|
175
|
+
detail: limitStatus,
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
logger.warn(`Brain decisor failed (non-blocking): ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// --- Persist inline domain if provided via --domain flag ---
|
|
184
|
+
if (flags?.domain) {
|
|
185
|
+
try {
|
|
186
|
+
await persistInlineDomain(flags.domain, config.projectDir || process.cwd());
|
|
187
|
+
} catch (err) {
|
|
188
|
+
logger.warn(`Failed to persist inline domain: ${err.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// --- Domain Curator (after triage + skill auto-install, before planning phases) ---
|
|
193
|
+
const domainHints = triageResult.stageResult?.domainHints || [];
|
|
194
|
+
if (domainHints.length > 0 || config.projectDir) {
|
|
195
|
+
try {
|
|
196
|
+
const { domainContext, stageResult: dcStageResult } = await runDomainCuratorStage({
|
|
197
|
+
config, logger, emitter, eventBase, session, trackBudget,
|
|
198
|
+
domainHints, askQuestion
|
|
199
|
+
});
|
|
200
|
+
stageResults.domainCurator = dcStageResult;
|
|
201
|
+
if (domainContext) {
|
|
202
|
+
config = { ...config, domainContext };
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
logger.warn(`Domain Curator failed (non-blocking): ${err.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- HU Reviewer auto-activation from triage (post-triage, no huFile needed) ---
|
|
210
|
+
const triageRoles = new Set(triageResult.stageResult?.roles || []);
|
|
211
|
+
if (triageRoles.has("hu-reviewer") && !stageResults.huReviewer) {
|
|
212
|
+
pipelineFlags.huReviewerEnabled = true;
|
|
213
|
+
// Feed tracker card structured data to hu-reviewer when available
|
|
214
|
+
let pgStories = null;
|
|
215
|
+
if (pgTaskId && pgProject && session.pg_card) {
|
|
216
|
+
pgStories = getIntegration("tracker")?.buildHuStoriesFromCard?.(session.pg_card) ?? null;
|
|
217
|
+
}
|
|
218
|
+
const huResult = await runHuReviewerStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, huFile: null, askQuestion, pgStories });
|
|
219
|
+
stageResults.huReviewer = huResult.stageResult;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// --- Auto-simplify pipeline for simple tasks (before explicit flag overrides) ---
|
|
223
|
+
const simplified = applyAutoSimplify({
|
|
224
|
+
pipelineFlags,
|
|
225
|
+
triageLevel: triageResult.stageResult?.level || null,
|
|
226
|
+
config, flags, logger, emitter, eventBase
|
|
227
|
+
});
|
|
228
|
+
if (simplified) stageResults.triage.autoSimplified = true;
|
|
229
|
+
|
|
230
|
+
await getIntegration("tracker")?.handleDecomposition?.({ triageResult, pgTaskId, pgProject, config, askQuestion, emitter, eventBase, session, stageResults, logger });
|
|
231
|
+
|
|
232
|
+
applyFlagOverrides(pipelineFlags, flags);
|
|
233
|
+
|
|
234
|
+
// --- Auto-detect TDD applicability when methodology not explicitly set ---
|
|
235
|
+
if (!flags.methodology) {
|
|
236
|
+
const projectDir = config.projectDir || process.cwd();
|
|
237
|
+
const detection = await detectTestFramework(projectDir);
|
|
238
|
+
if (!detection.hasTests) {
|
|
239
|
+
config = { ...config, development: { ...config.development, methodology: "standard", require_test_changes: false } };
|
|
240
|
+
logger.info("No test framework detected — using standard methodology");
|
|
241
|
+
} else {
|
|
242
|
+
config = { ...config, development: { ...config.development, methodology: "tdd", require_test_changes: true } };
|
|
243
|
+
logger.info(`Test framework detected (${detection.framework}) — using TDD methodology`);
|
|
244
|
+
}
|
|
245
|
+
emitProgress(emitter, makeEvent("tdd:auto-detect", { ...eventBase, stage: "pre-loop" }, {
|
|
246
|
+
message: detection.hasTests
|
|
247
|
+
? `TDD auto-detected: ${detection.framework}`
|
|
248
|
+
: "TDD skipped: no test framework found",
|
|
249
|
+
detail: detection
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let updatedConfig = resolvePipelinePolicies({ flags, config, stageResults, emitter, eventBase, session, pipelineFlags });
|
|
254
|
+
|
|
255
|
+
// Deprecation warnings recorded at config load time. Done here, after
|
|
256
|
+
// policies are resolved, so the message lands in context: if the user's
|
|
257
|
+
// legacy `sonarqube.enabled: false` is being ignored, this is the right
|
|
258
|
+
// moment for them to see it.
|
|
259
|
+
emitConfigDeprecations(updatedConfig, logger, emitter, eventBase);
|
|
260
|
+
|
|
261
|
+
// --- Preflight environment checks ---
|
|
262
|
+
const preflightResult = await runPreflightChecks({
|
|
263
|
+
config: updatedConfig, logger, emitter, eventBase,
|
|
264
|
+
resolvedPolicies: session.resolved_policies,
|
|
265
|
+
securityEnabled: pipelineFlags.securityEnabled
|
|
266
|
+
});
|
|
267
|
+
setPreflight(session, preflightResult);
|
|
268
|
+
await saveSession(session);
|
|
269
|
+
|
|
270
|
+
// Hard fail if blocking checks failed (SonarQube enabled but not available)
|
|
271
|
+
if (!preflightResult.ok) {
|
|
272
|
+
const errorLines = (preflightResult.errors || [])
|
|
273
|
+
.map(e => ` - ${e.message}\n Fix: ${e.fix}`)
|
|
274
|
+
.join("\n");
|
|
275
|
+
throw new Error(
|
|
276
|
+
`Preflight FAILED — environment changed during session. Fix the issues and retry:\n${errorLines}`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (preflightResult.configOverrides.securityDisabled) {
|
|
281
|
+
pipelineFlags.securityEnabled = false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// --- Plan injection: skip researcher/architect/planner if a persisted plan is loaded ---
|
|
285
|
+
// Extracted to pre-loop-phases/inject-loaded-plan.js (PR-K).
|
|
286
|
+
// 189 LOC of plan-load + tests-gate + auto-certify + assertRunnable +
|
|
287
|
+
// huBatch + --hu filter + callback wiring + plannedTask build.
|
|
288
|
+
const planInjection = await injectLoadedPlan({
|
|
289
|
+
flags, updatedConfig, session, stageResults, eventBase, emitter, logger, task,
|
|
290
|
+
});
|
|
291
|
+
if (planInjection.handled) {
|
|
292
|
+
return { plannedTask: planInjection.plannedTask, updatedConfig };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
// --- Researcher → Planner ---
|
|
297
|
+
const { plannedTask } = await runPlanningPhases({ config: updatedConfig, logger, emitter, eventBase, session, stageResults, pipelineFlags, coderRole, trackBudget, task, askQuestion, brainCtx });
|
|
298
|
+
|
|
299
|
+
// --- Update .gitignore with stack-specific entries based on planner/architect output ---
|
|
300
|
+
const projectDir = updatedConfig.projectDir || process.cwd();
|
|
301
|
+
await updateGitignoreForStack(projectDir, { stageResults, task, logger });
|
|
302
|
+
|
|
303
|
+
// --- Auto-HU: when triage recommended decomposition and no manual huFile, generate HU batch ---
|
|
304
|
+
await maybeGenerateAutoHuBatch({
|
|
305
|
+
flags, stageResults, task, plannedTask, logger, emitter, eventBase, projectDir, session
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// --- Auto-install skills based on task + planner output + project detection ---
|
|
309
|
+
// Runs AFTER triage and planner so that the planned task text (which includes
|
|
310
|
+
// planner output like implementation steps) is available for keyword detection.
|
|
311
|
+
// This ensures greenfield projects with no package.json still get correct skills.
|
|
312
|
+
//
|
|
313
|
+
// Detection runs UNCONDITIONALLY — even when the openskills CLI is missing —
|
|
314
|
+
// so the session report can recommend which skills would have been used. The
|
|
315
|
+
// actual install is skipped inside autoInstallSkills when unavailable.
|
|
316
|
+
const skillProjectDir = updatedConfig.projectDir || process.cwd();
|
|
317
|
+
try {
|
|
318
|
+
const skillsMode = resolveSkillsMode(updatedConfig, flags);
|
|
319
|
+
if (skillsMode === "none") {
|
|
320
|
+
// User explicitly opted out. Skip detection entirely.
|
|
321
|
+
return { plannedTask, updatedConfig };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// FIRST source: addyosmani/agent-skills (process skills per role).
|
|
325
|
+
// This runs BEFORE OpenSkills detection because process skills are the
|
|
326
|
+
// canonical workflows (TDD, code-review, security, performance...) that
|
|
327
|
+
// should shape every role's prompt; stack skills come second.
|
|
328
|
+
await ensureAddyosmaniSkills({
|
|
329
|
+
task: plannedTask,
|
|
330
|
+
config: updatedConfig,
|
|
331
|
+
logger,
|
|
332
|
+
session,
|
|
333
|
+
emitter,
|
|
334
|
+
eventBase,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
let neededSkills = skillsMode === "semantic"
|
|
338
|
+
? []
|
|
339
|
+
: await detectNeededSkills(plannedTask, skillProjectDir);
|
|
340
|
+
// Semantic mode augments regex detection with a classifier call.
|
|
341
|
+
// "auto" = regex always + semantic when budget allows (v1: always when mode=auto and classifier reachable).
|
|
342
|
+
if (skillsMode === "auto" || skillsMode === "semantic") {
|
|
343
|
+
const extra = await refineSkillsSemantically({
|
|
344
|
+
task: plannedTask,
|
|
345
|
+
alreadyDetected: neededSkills,
|
|
346
|
+
config: updatedConfig,
|
|
347
|
+
logger,
|
|
348
|
+
});
|
|
349
|
+
if (extra.length > 0) {
|
|
350
|
+
logger.info(`Semantic skill detection added: ${extra.join(", ")}`);
|
|
351
|
+
neededSkills = Array.from(new Set([...neededSkills, ...extra]));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (neededSkills.length > 0) {
|
|
355
|
+
const skillResult = await autoInstallSkills(neededSkills, skillProjectDir);
|
|
356
|
+
if (skillResult.installed.length > 0) {
|
|
357
|
+
setAutoInstalledSkills(session, skillResult.installed);
|
|
358
|
+
}
|
|
359
|
+
if (skillResult.osAvailable === false && skillResult.wouldHaveUsed.length > 0) {
|
|
360
|
+
setSkillsRecommended(session, skillResult.wouldHaveUsed);
|
|
361
|
+
logger.warn(`OpenSkills CLI not available — would have used: ${skillResult.wouldHaveUsed.join(", ")}. Install openskills globally to auto-inject them next time.`);
|
|
362
|
+
emitProgress(emitter, makeEvent("skills:unavailable", { ...eventBase, stage: "skills" }, {
|
|
363
|
+
message: `OpenSkills CLI not available — would have used: ${skillResult.wouldHaveUsed.join(", ")}`,
|
|
364
|
+
detail: {
|
|
365
|
+
wouldHaveUsed: skillResult.wouldHaveUsed,
|
|
366
|
+
hint: "Install openskills globally: npm install -g openskills",
|
|
367
|
+
},
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
emitProgress(emitter, makeEvent("skills:auto-install", { ...eventBase, stage: "skills" }, {
|
|
371
|
+
message: skillResult.installed.length > 0
|
|
372
|
+
? `Auto-installed ${skillResult.installed.length} skill(s): ${skillResult.installed.join(", ")}`
|
|
373
|
+
: `Skills detected (${neededSkills.join(", ")}) — all already installed or unavailable`,
|
|
374
|
+
detail: skillResult,
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
} catch (err) {
|
|
378
|
+
logger.warn(`Skill auto-install failed (non-blocking): ${err.message}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return { plannedTask, updatedConfig };
|
|
382
|
+
}
|
|
383
|
+
export async function runPlanningPhases({ config, logger, emitter, eventBase, session, stageResults, pipelineFlags, coderRole, trackBudget, task, askQuestion, brainCtx }) {
|
|
384
|
+
let researchContext = null;
|
|
385
|
+
let plannedTask = task;
|
|
386
|
+
|
|
387
|
+
// Brain: track compression across pre-loop roles
|
|
388
|
+
const brainCompress = brainCtx?.enabled
|
|
389
|
+
? (await import("../brain-coordinator.js")).processRoleOutput
|
|
390
|
+
: null;
|
|
391
|
+
|
|
392
|
+
if (pipelineFlags.researcherEnabled) {
|
|
393
|
+
const researcherResult = await runResearcherStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget });
|
|
394
|
+
researchContext = researcherResult.researchContext;
|
|
395
|
+
stageResults.researcher = researcherResult.stageResult;
|
|
396
|
+
if (brainCompress) brainCompress(brainCtx, { roleName: "researcher", output: researcherResult.stageResult, iteration: 0 });
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// --- Architect (between researcher and planner) ---
|
|
400
|
+
let architectContext = null;
|
|
401
|
+
if (pipelineFlags.architectEnabled) {
|
|
402
|
+
const architectResult = await runArchitectStage({
|
|
403
|
+
config, logger, emitter, eventBase, session, coderRole, trackBudget,
|
|
404
|
+
researchContext,
|
|
405
|
+
discoverResult: stageResults.discover || null,
|
|
406
|
+
triageLevel: stageResults.triage?.level || null,
|
|
407
|
+
askQuestion
|
|
408
|
+
});
|
|
409
|
+
architectContext = architectResult.architectContext;
|
|
410
|
+
stageResults.architect = architectResult.stageResult;
|
|
411
|
+
if (brainCompress) brainCompress(brainCtx, { roleName: "architect", output: architectResult.stageResult, iteration: 0 });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const triageDecomposition = stageResults.triage?.shouldDecompose ? stageResults.triage.subtasks : null;
|
|
415
|
+
if (pipelineFlags.plannerEnabled) {
|
|
416
|
+
const plannerRole = resolveRole(config, "planner");
|
|
417
|
+
const plannerResult = await runPlannerStage({ config, logger, emitter, eventBase, session, plannerRole, researchContext, architectContext, triageDecomposition, trackBudget });
|
|
418
|
+
plannedTask = plannerResult.plannedTask;
|
|
419
|
+
stageResults.planner = plannerResult.stageResult;
|
|
420
|
+
if (brainCompress) brainCompress(brainCtx, { roleName: "planner", output: plannerResult.stageResult, iteration: 0 });
|
|
421
|
+
|
|
422
|
+
await tryCiComment({
|
|
423
|
+
config, session, logger,
|
|
424
|
+
agent: "Planner",
|
|
425
|
+
body: `Plan: ${plannerResult.stageResult?.summary || plannedTask}`
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return { plannedTask };
|
|
430
|
+
}
|
|
431
|
+
// `emitConfigDeprecations`, `ensureAddyosmaniSkills` and
|
|
432
|
+
// `maybeGenerateAutoHuBatch` were extracted to ./pre-loop-phases/ in
|
|
433
|
+
// TSK-0337 to keep this driver under the 600-LOC ceiling. Imports at
|
|
434
|
+
// the top of this file pull them back in; behaviour is unchanged.
|