@chllming/wave-orchestration 0.6.3 → 0.7.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/CHANGELOG.md +57 -1
- package/README.md +39 -7
- package/docs/agents/wave-orchestrator-role.md +50 -0
- package/docs/agents/wave-planner-role.md +39 -0
- package/docs/context7/bundles.json +9 -0
- package/docs/context7/planner-agent/README.md +25 -0
- package/docs/context7/planner-agent/manifest.json +83 -0
- package/docs/context7/planner-agent/papers/cooperbench-why-coding-agents-cannot-be-your-teammates-yet.md +3283 -0
- package/docs/context7/planner-agent/papers/dova-deliberation-first-multi-agent-orchestration-for-autonomous-research-automation.md +1699 -0
- package/docs/context7/planner-agent/papers/dpbench-large-language-models-struggle-with-simultaneous-coordination.md +2251 -0
- package/docs/context7/planner-agent/papers/incremental-planning-to-control-a-blackboard-based-problem-solver.md +1729 -0
- package/docs/context7/planner-agent/papers/silo-bench-a-scalable-environment-for-evaluating-distributed-coordination-in-multi-agent-llm-systems.md +3747 -0
- package/docs/context7/planner-agent/papers/todoevolve-learning-to-architect-agent-planning-systems.md +1675 -0
- package/docs/context7/planner-agent/papers/verified-multi-agent-orchestration-a-plan-execute-verify-replan-framework-for-complex-query-resolution.md +1173 -0
- package/docs/context7/planner-agent/papers/why-do-multi-agent-llm-systems-fail.md +5211 -0
- package/docs/context7/planner-agent/topics/planning-and-orchestration.md +24 -0
- package/docs/evals/README.md +96 -1
- package/docs/evals/arm-templates/README.md +13 -0
- package/docs/evals/arm-templates/full-wave.json +15 -0
- package/docs/evals/arm-templates/single-agent.json +15 -0
- package/docs/evals/benchmark-catalog.json +7 -0
- package/docs/evals/cases/README.md +47 -0
- package/docs/evals/cases/wave-blackboard-inbox-targeting.json +73 -0
- package/docs/evals/cases/wave-contradiction-conflict.json +104 -0
- package/docs/evals/cases/wave-expert-routing-preservation.json +69 -0
- package/docs/evals/cases/wave-hidden-profile-private-evidence.json +81 -0
- package/docs/evals/cases/wave-premature-closure-guard.json +71 -0
- package/docs/evals/cases/wave-silo-cross-agent-state.json +77 -0
- package/docs/evals/cases/wave-simultaneous-lockstep.json +92 -0
- package/docs/evals/cooperbench/real-world-mitigation.md +341 -0
- package/docs/evals/external-benchmarks.json +85 -0
- package/docs/evals/external-command-config.sample.json +9 -0
- package/docs/evals/external-command-config.swe-bench-pro.json +8 -0
- package/docs/evals/pilots/README.md +47 -0
- package/docs/evals/pilots/swe-bench-pro-public-full-wave-review-10.json +64 -0
- package/docs/evals/pilots/swe-bench-pro-public-pilot.json +111 -0
- package/docs/evals/wave-benchmark-program.md +302 -0
- package/docs/guides/planner.md +48 -11
- package/docs/plans/context7-wave-orchestrator.md +20 -0
- package/docs/plans/current-state.md +8 -1
- package/docs/plans/examples/wave-benchmark-improvement.md +108 -0
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/examples/wave-example-rollout-fidelity.md +340 -0
- package/docs/plans/wave-orchestrator.md +62 -11
- package/docs/plans/waves/reviews/wave-1-benchmark-operator.md +118 -0
- package/docs/reference/coordination-and-closure.md +436 -0
- package/docs/reference/live-proof-waves.md +25 -3
- package/docs/reference/npmjs-trusted-publishing.md +3 -3
- package/docs/reference/proof-metrics.md +90 -0
- package/docs/reference/runtime-config/README.md +61 -0
- package/docs/reference/sample-waves.md +29 -18
- package/docs/reference/wave-control.md +164 -0
- package/docs/reference/wave-planning-lessons.md +131 -0
- package/package.json +5 -4
- package/releases/manifest.json +18 -0
- package/scripts/research/agent-context-archive.mjs +18 -0
- package/scripts/research/manifests/agent-context-expanded-2026-03-22.mjs +17 -0
- package/scripts/research/sync-planner-context7-bundle.mjs +133 -0
- package/scripts/wave-orchestrator/artifact-schemas.mjs +232 -0
- package/scripts/wave-orchestrator/autonomous.mjs +7 -0
- package/scripts/wave-orchestrator/benchmark-cases.mjs +374 -0
- package/scripts/wave-orchestrator/benchmark-external.mjs +1384 -0
- package/scripts/wave-orchestrator/benchmark.mjs +972 -0
- package/scripts/wave-orchestrator/clarification-triage.mjs +78 -12
- package/scripts/wave-orchestrator/config.mjs +175 -0
- package/scripts/wave-orchestrator/control-cli.mjs +1123 -0
- package/scripts/wave-orchestrator/control-plane.mjs +697 -0
- package/scripts/wave-orchestrator/coord-cli.mjs +360 -2
- package/scripts/wave-orchestrator/coordination-store.mjs +211 -9
- package/scripts/wave-orchestrator/coordination.mjs +84 -0
- package/scripts/wave-orchestrator/dashboard-renderer.mjs +38 -3
- package/scripts/wave-orchestrator/dashboard-state.mjs +22 -0
- package/scripts/wave-orchestrator/evals.mjs +23 -0
- package/scripts/wave-orchestrator/executors.mjs +3 -2
- package/scripts/wave-orchestrator/feedback.mjs +55 -0
- package/scripts/wave-orchestrator/install.mjs +55 -1
- package/scripts/wave-orchestrator/launcher-closure.mjs +4 -1
- package/scripts/wave-orchestrator/launcher-runtime.mjs +24 -21
- package/scripts/wave-orchestrator/launcher.mjs +796 -35
- package/scripts/wave-orchestrator/planner-context.mjs +75 -0
- package/scripts/wave-orchestrator/planner.mjs +2270 -136
- package/scripts/wave-orchestrator/proof-cli.mjs +195 -0
- package/scripts/wave-orchestrator/proof-registry.mjs +317 -0
- package/scripts/wave-orchestrator/replay.mjs +10 -4
- package/scripts/wave-orchestrator/retry-cli.mjs +184 -0
- package/scripts/wave-orchestrator/retry-control.mjs +225 -0
- package/scripts/wave-orchestrator/shared.mjs +26 -0
- package/scripts/wave-orchestrator/swe-bench-pro-task.mjs +1004 -0
- package/scripts/wave-orchestrator/traces.mjs +157 -2
- package/scripts/wave-orchestrator/wave-control-client.mjs +532 -0
- package/scripts/wave-orchestrator/wave-control-schema.mjs +309 -0
- package/scripts/wave-orchestrator/wave-files.mjs +17 -5
- package/scripts/wave.mjs +27 -0
- package/skills/repo-coding-rules/SKILL.md +1 -0
- package/skills/role-cont-eval/SKILL.md +1 -0
- package/skills/role-cont-qa/SKILL.md +13 -6
- package/skills/role-deploy/SKILL.md +1 -0
- package/skills/role-documentation/SKILL.md +4 -0
- package/skills/role-implementation/SKILL.md +4 -0
- package/skills/role-infra/SKILL.md +2 -1
- package/skills/role-integration/SKILL.md +15 -8
- package/skills/role-planner/SKILL.md +39 -0
- package/skills/role-planner/skill.json +21 -0
- package/skills/role-research/SKILL.md +1 -0
- package/skills/role-security/SKILL.md +2 -2
- package/skills/runtime-claude/SKILL.md +2 -1
- package/skills/runtime-codex/SKILL.md +1 -0
- package/skills/runtime-local/SKILL.md +2 -0
- package/skills/runtime-opencode/SKILL.md +1 -0
- package/skills/wave-core/SKILL.md +25 -6
- package/skills/wave-core/references/marker-syntax.md +16 -8
- package/wave.config.json +45 -0
|
@@ -1,11 +1,35 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
1
3
|
import fs from "node:fs";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import readline from "node:readline/promises";
|
|
4
6
|
import { stdin, stderr } from "node:process";
|
|
5
7
|
import { EXIT_CONTRACT_COMPLETION_VALUES, EXIT_CONTRACT_DOC_IMPACT_VALUES, EXIT_CONTRACT_DURABILITY_VALUES, EXIT_CONTRACT_PROOF_VALUES } from "./agent-state.mjs";
|
|
6
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_PLANNER_AGENTIC_CORE_CONTEXT_PATHS,
|
|
10
|
+
DEFAULT_PLANNER_AGENTIC_CONTEXT7_BUNDLE,
|
|
11
|
+
DEFAULT_PLANNER_AGENTIC_CONTEXT7_QUERY,
|
|
12
|
+
DEFAULT_PLANNER_AGENTIC_EXECUTOR_PROFILE,
|
|
13
|
+
DEFAULT_PLANNER_AGENTIC_LESSONS_PATHS,
|
|
14
|
+
DEFAULT_PLANNER_AGENTIC_MAX_REPLAN_ITERATIONS,
|
|
15
|
+
DEFAULT_PLANNER_AGENTIC_MAX_WAVES,
|
|
16
|
+
DEFAULT_PLANNER_AGENTIC_RESEARCH_TOPIC_PATHS,
|
|
17
|
+
loadWaveConfig,
|
|
18
|
+
} from "./config.mjs";
|
|
19
|
+
import {
|
|
20
|
+
describeContext7Libraries,
|
|
21
|
+
loadContext7BundleIndex,
|
|
22
|
+
prefetchContext7ForSelection,
|
|
23
|
+
resolveContext7Selection,
|
|
24
|
+
} from "./context7.mjs";
|
|
25
|
+
import {
|
|
26
|
+
PLANNER_CONTEXT7_BUNDLE_ID,
|
|
27
|
+
PLANNER_CONTEXT7_DEFAULT_QUERY,
|
|
28
|
+
PLANNER_CONTEXT7_PAPER_PATHS,
|
|
29
|
+
PLANNER_CONTEXT7_SOURCE_DIR,
|
|
30
|
+
} from "./planner-context.mjs";
|
|
7
31
|
import { loadComponentCutoverMatrix, parseWaveFile, requiredDocumentationStewardPathsForWave, SHARED_PLAN_DOC_PATHS, validateWaveDefinition, applyExecutorSelectionsToWave } from "./wave-files.mjs";
|
|
8
|
-
import { buildLanePaths, ensureDirectory, REPO_ROOT, writeJsonAtomic, writeTextAtomic } from "./shared.mjs";
|
|
32
|
+
import { buildLanePaths, ensureDirectory, readJsonOrNull, REPO_ROOT, writeJsonAtomic, writeTextAtomic } from "./shared.mjs";
|
|
9
33
|
import {
|
|
10
34
|
DEPLOY_ENVIRONMENT_KINDS,
|
|
11
35
|
DRAFT_TEMPLATES,
|
|
@@ -23,6 +47,30 @@ import { normalizeTerminalSurface } from "./terminals.mjs";
|
|
|
23
47
|
|
|
24
48
|
const COMPONENT_ID_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
25
49
|
const WAVE_SPEC_SCHEMA_VERSION = 1;
|
|
50
|
+
const AGENTIC_PLANNER_SCHEMA_VERSION = 1;
|
|
51
|
+
const PLANNER_RUN_PREFIX = "planner";
|
|
52
|
+
const DEFAULT_GENERATED_WAVE_CONTEXT7_BUNDLE = "node-typescript";
|
|
53
|
+
const DEFAULT_GENERATED_WAVE_CONTEXT7_QUERY =
|
|
54
|
+
"Architecture-aware wave planning, ownership boundaries, proof surfaces, and closure readiness";
|
|
55
|
+
const LIVE_PROOF_REVIEW_DIR = "docs/plans/waves/reviews";
|
|
56
|
+
const LIVE_PROOF_OPERATIONS_DIR = "docs/plans/operations";
|
|
57
|
+
const PLANNER_CANDIDATE_DIRNAME = "candidate";
|
|
58
|
+
const PLANNER_RESULT_STATES = new Set(["planned", "failed", "applied"]);
|
|
59
|
+
const COMPONENT_MATURITY_LEVELS = [
|
|
60
|
+
"inventoried",
|
|
61
|
+
"contract-frozen",
|
|
62
|
+
"repo-landed",
|
|
63
|
+
"baseline-proved",
|
|
64
|
+
"pilot-live",
|
|
65
|
+
"qa-proved",
|
|
66
|
+
"fleet-ready",
|
|
67
|
+
"cutover-ready",
|
|
68
|
+
"deprecation-ready",
|
|
69
|
+
];
|
|
70
|
+
const COMPONENT_MATURITY_ORDER = Object.fromEntries(
|
|
71
|
+
COMPONENT_MATURITY_LEVELS.map((level, index) => [level, index]),
|
|
72
|
+
);
|
|
73
|
+
const PROOF_CENTRIC_COMPONENT_LEVEL = "pilot-live";
|
|
26
74
|
let bufferedNonTtyAnswers = null;
|
|
27
75
|
let bufferedNonTtyAnswerIndex = 0;
|
|
28
76
|
|
|
@@ -75,6 +123,160 @@ function normalizeRepoPathList(values, label) {
|
|
|
75
123
|
});
|
|
76
124
|
}
|
|
77
125
|
|
|
126
|
+
function uniqueStrings(values) {
|
|
127
|
+
return Array.from(
|
|
128
|
+
new Set(
|
|
129
|
+
(Array.isArray(values) ? values : [])
|
|
130
|
+
.map((value) => cleanText(value))
|
|
131
|
+
.filter(Boolean),
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function repoRelativePath(targetPath) {
|
|
137
|
+
return path.relative(REPO_ROOT, targetPath).replaceAll(path.sep, "/");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeRepoRelativePath(value, label) {
|
|
141
|
+
const normalized = cleanText(value)
|
|
142
|
+
.replaceAll("\\", "/")
|
|
143
|
+
.replace(/^\.\/+/, "")
|
|
144
|
+
.replace(/\/+/g, "/");
|
|
145
|
+
if (!normalized) {
|
|
146
|
+
throw new Error(`${label} is required`);
|
|
147
|
+
}
|
|
148
|
+
if (normalized.startsWith("/") || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
149
|
+
throw new Error(`${label} must stay inside the repository`);
|
|
150
|
+
}
|
|
151
|
+
return normalized;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function ensureRepoRelativePathList(values, label) {
|
|
155
|
+
return uniqueStrings(values).map((value, index) =>
|
|
156
|
+
normalizeRepoRelativePath(value, `${label}[${index}]`),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizePlannerRunId(value) {
|
|
161
|
+
const normalized = cleanText(value)
|
|
162
|
+
.toLowerCase()
|
|
163
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
164
|
+
.replace(/-+/g, "-")
|
|
165
|
+
.replace(/^-+|-+$/g, "");
|
|
166
|
+
if (!normalized) {
|
|
167
|
+
throw new Error("Planner run id is required");
|
|
168
|
+
}
|
|
169
|
+
return normalized;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildPlannerRunId() {
|
|
173
|
+
const stamp = new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
174
|
+
const random = crypto.randomBytes(3).toString("hex");
|
|
175
|
+
return normalizePlannerRunId(`${PLANNER_RUN_PREFIX}-${stamp}-${random}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function buildPlannerRunPaths(runId) {
|
|
179
|
+
const normalizedRunId = normalizePlannerRunId(runId);
|
|
180
|
+
const runDir = path.join(REPO_ROOT, ".wave", "planner", "runs", normalizedRunId);
|
|
181
|
+
const candidateDir = path.join(runDir, PLANNER_CANDIDATE_DIRNAME);
|
|
182
|
+
return {
|
|
183
|
+
runId: normalizedRunId,
|
|
184
|
+
runDir,
|
|
185
|
+
requestPath: path.join(runDir, "request.json"),
|
|
186
|
+
sourcesPath: path.join(runDir, "sources.json"),
|
|
187
|
+
planPath: path.join(runDir, "plan.json"),
|
|
188
|
+
verificationPath: path.join(runDir, "verification.json"),
|
|
189
|
+
resultPath: path.join(runDir, "result.json"),
|
|
190
|
+
promptPath: path.join(runDir, "planner-prompt.md"),
|
|
191
|
+
candidateDir,
|
|
192
|
+
candidateWavesDir: path.join(candidateDir, "waves"),
|
|
193
|
+
candidateSpecsDir: path.join(candidateDir, "specs"),
|
|
194
|
+
previewMatrixJsonPath: path.join(candidateDir, "component-cutover-matrix.json"),
|
|
195
|
+
previewMatrixDocPath: path.join(candidateDir, "component-cutover-matrix.md"),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function readTextIfExists(relPath) {
|
|
200
|
+
const absolutePath = path.resolve(REPO_ROOT, relPath);
|
|
201
|
+
if (!fs.existsSync(absolutePath)) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return fs.readFileSync(absolutePath, "utf8");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function hashContent(value) {
|
|
208
|
+
return crypto.createHash("sha256").update(String(value ?? ""), "utf8").digest("hex");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function splitTaskTerms(task) {
|
|
212
|
+
return uniqueStrings(
|
|
213
|
+
String(task || "")
|
|
214
|
+
.toLowerCase()
|
|
215
|
+
.replace(/[`"'().,:;!?]/g, " ")
|
|
216
|
+
.split(/\s+/)
|
|
217
|
+
.filter((term) => term.length >= 4),
|
|
218
|
+
).slice(0, 10);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function isLikelyExternalPathHint(value) {
|
|
222
|
+
const normalized = cleanText(value).replaceAll("\\", "/");
|
|
223
|
+
if (!normalized) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(normalized) || normalized.startsWith("//")) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
const firstSegment = normalized.split("/")[0] || "";
|
|
230
|
+
return (
|
|
231
|
+
!firstSegment.startsWith(".") &&
|
|
232
|
+
/^(?:www\.)?(?:[a-z0-9-]+\.)+[a-z]{2,}$/i.test(firstSegment)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function extractRepoPathHints(task) {
|
|
237
|
+
const text = String(task || "");
|
|
238
|
+
const matches = [];
|
|
239
|
+
const pushPath = (value) => {
|
|
240
|
+
const normalized = cleanText(value).replace(/^["'`(]+|["'`),.:;]+$/g, "");
|
|
241
|
+
if (!normalized || isLikelyExternalPathHint(normalized)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const looksLikePath =
|
|
245
|
+
normalized.includes("/") ||
|
|
246
|
+
/^(README|CHANGELOG|package|pnpm-workspace|tsconfig|wave\.config)\.[a-z0-9._-]+$/i.test(
|
|
247
|
+
normalized,
|
|
248
|
+
);
|
|
249
|
+
if (!looksLikePath) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
matches.push(normalizeRepoRelativePath(normalized, "task path hint"));
|
|
254
|
+
} catch {
|
|
255
|
+
// Ignore invalid hints.
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
for (const match of text.matchAll(/`([^`]+)`/g)) {
|
|
259
|
+
pushPath(match[1]);
|
|
260
|
+
}
|
|
261
|
+
for (const match of text.matchAll(/(?:^|\s)([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+(?:\/)?)(?=$|\s)/g)) {
|
|
262
|
+
pushPath(match[1]);
|
|
263
|
+
}
|
|
264
|
+
for (const match of text.matchAll(
|
|
265
|
+
/(?:^|\s)((?:README|CHANGELOG|package|pnpm-workspace|tsconfig|wave\.config)\.[A-Za-z0-9._-]+)(?=$|\s)/g,
|
|
266
|
+
)) {
|
|
267
|
+
pushPath(match[1]);
|
|
268
|
+
}
|
|
269
|
+
return uniqueStrings(matches);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function humanizeComponentId(componentId) {
|
|
273
|
+
return cleanText(componentId)
|
|
274
|
+
.split(/[._-]+/)
|
|
275
|
+
.filter(Boolean)
|
|
276
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
277
|
+
.join(" ");
|
|
278
|
+
}
|
|
279
|
+
|
|
78
280
|
function defaultWorkerRoleKindForTemplate(template) {
|
|
79
281
|
if (template === "infra") {
|
|
80
282
|
return "infra";
|
|
@@ -287,6 +489,47 @@ function renderBulletLines(items) {
|
|
|
287
489
|
return items.map((item) => `- ${item}`);
|
|
288
490
|
}
|
|
289
491
|
|
|
492
|
+
function renderPathSection(items) {
|
|
493
|
+
return renderBulletLines(
|
|
494
|
+
(Array.isArray(items) ? items : [])
|
|
495
|
+
.map((item) => cleanText(item))
|
|
496
|
+
.filter(Boolean),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function renderProofArtifactsSection(proofArtifacts) {
|
|
501
|
+
if (!Array.isArray(proofArtifacts) || proofArtifacts.length === 0) {
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
return proofArtifacts.map((artifact) => {
|
|
505
|
+
const pathValue = cleanText(artifact?.path);
|
|
506
|
+
const fields = [`path: ${pathValue}`];
|
|
507
|
+
if (cleanText(artifact?.kind)) {
|
|
508
|
+
fields.push(`kind: ${cleanText(artifact.kind)}`);
|
|
509
|
+
}
|
|
510
|
+
const requiredFor = uniqueStrings(artifact?.requiredFor || []);
|
|
511
|
+
if (requiredFor.length > 0) {
|
|
512
|
+
fields.push(`required-for: ${requiredFor.join(", ")}`);
|
|
513
|
+
}
|
|
514
|
+
return fields.length === 1 && !cleanText(artifact?.kind) && requiredFor.length === 0
|
|
515
|
+
? `- ${pathValue}`
|
|
516
|
+
: `- ${fields.join(" | ")}`;
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function stringifyExecutorValue(value) {
|
|
521
|
+
if (Array.isArray(value)) {
|
|
522
|
+
return value.join(", ");
|
|
523
|
+
}
|
|
524
|
+
if (typeof value === "boolean") {
|
|
525
|
+
return value ? "true" : "false";
|
|
526
|
+
}
|
|
527
|
+
if (value && typeof value === "object") {
|
|
528
|
+
return JSON.stringify(value);
|
|
529
|
+
}
|
|
530
|
+
return String(value);
|
|
531
|
+
}
|
|
532
|
+
|
|
290
533
|
function renderPromptBlock({
|
|
291
534
|
primaryGoal,
|
|
292
535
|
collaborationNotes = [],
|
|
@@ -342,18 +585,87 @@ function renderPromptBlock({
|
|
|
342
585
|
}
|
|
343
586
|
|
|
344
587
|
function renderExecutorSection(agent) {
|
|
588
|
+
const executor = agent.executor || null;
|
|
589
|
+
if (!executor) {
|
|
590
|
+
return [];
|
|
591
|
+
}
|
|
345
592
|
const lines = [];
|
|
346
|
-
if (
|
|
347
|
-
lines.push(`- profile: ${
|
|
593
|
+
if (executor.profile) {
|
|
594
|
+
lines.push(`- profile: ${executor.profile}`);
|
|
595
|
+
}
|
|
596
|
+
if (executor.id) {
|
|
597
|
+
lines.push(`- id: ${executor.id}`);
|
|
598
|
+
}
|
|
599
|
+
if (executor.model) {
|
|
600
|
+
lines.push(`- model: ${executor.model}`);
|
|
601
|
+
}
|
|
602
|
+
if (Array.isArray(executor.fallbacks) && executor.fallbacks.length > 0) {
|
|
603
|
+
lines.push(`- fallbacks: ${executor.fallbacks.join(", ")}`);
|
|
348
604
|
}
|
|
349
|
-
if (
|
|
350
|
-
lines.push(`-
|
|
605
|
+
if (Array.isArray(executor.tags) && executor.tags.length > 0) {
|
|
606
|
+
lines.push(`- tags: ${executor.tags.join(", ")}`);
|
|
351
607
|
}
|
|
352
|
-
if (
|
|
353
|
-
lines.push(`-
|
|
608
|
+
if (executor.retryPolicy) {
|
|
609
|
+
lines.push(`- retry-policy: ${executor.retryPolicy}`);
|
|
354
610
|
}
|
|
355
|
-
if (
|
|
356
|
-
lines.push(
|
|
611
|
+
if (typeof executor.allowFallbackOnRetry === "boolean") {
|
|
612
|
+
lines.push(
|
|
613
|
+
`- allow-fallback-on-retry: ${executor.allowFallbackOnRetry ? "true" : "false"}`,
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
if (executor.budget?.turns) {
|
|
617
|
+
lines.push(`- budget.turns: ${executor.budget.turns}`);
|
|
618
|
+
}
|
|
619
|
+
if (executor.budget?.minutes) {
|
|
620
|
+
lines.push(`- budget.minutes: ${executor.budget.minutes}`);
|
|
621
|
+
}
|
|
622
|
+
for (const [runtimeKey, fields] of [
|
|
623
|
+
["codex", executor.codex],
|
|
624
|
+
["claude", executor.claude],
|
|
625
|
+
["opencode", executor.opencode],
|
|
626
|
+
]) {
|
|
627
|
+
if (!fields || typeof fields !== "object") {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
for (const [fieldKey, fieldValue] of Object.entries(fields)) {
|
|
631
|
+
if (
|
|
632
|
+
fieldValue === null ||
|
|
633
|
+
fieldValue === undefined ||
|
|
634
|
+
fieldValue === "" ||
|
|
635
|
+
(Array.isArray(fieldValue) && fieldValue.length === 0)
|
|
636
|
+
) {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
let normalizedKey = fieldKey;
|
|
640
|
+
if (fieldKey === "profileName") {
|
|
641
|
+
normalizedKey = "profile_name";
|
|
642
|
+
} else if (fieldKey === "addDirs") {
|
|
643
|
+
normalizedKey = "add_dirs";
|
|
644
|
+
} else if (fieldKey === "permissionMode") {
|
|
645
|
+
normalizedKey = "permission_mode";
|
|
646
|
+
} else if (fieldKey === "permissionPromptTool") {
|
|
647
|
+
normalizedKey = "permission_prompt_tool";
|
|
648
|
+
} else if (fieldKey === "maxTurns") {
|
|
649
|
+
normalizedKey = "max_turns";
|
|
650
|
+
} else if (fieldKey === "mcpConfig") {
|
|
651
|
+
normalizedKey = "mcp_config";
|
|
652
|
+
} else if (fieldKey === "settingsJson") {
|
|
653
|
+
normalizedKey = "settings_json";
|
|
654
|
+
} else if (fieldKey === "hooksJson") {
|
|
655
|
+
normalizedKey = "hooks_json";
|
|
656
|
+
} else if (fieldKey === "allowedHttpHookUrls") {
|
|
657
|
+
normalizedKey = "allowed_http_hook_urls";
|
|
658
|
+
} else if (fieldKey === "outputFormat") {
|
|
659
|
+
normalizedKey = "output_format";
|
|
660
|
+
} else if (fieldKey === "allowedTools") {
|
|
661
|
+
normalizedKey = "allowed_tools";
|
|
662
|
+
} else if (fieldKey === "disallowedTools") {
|
|
663
|
+
normalizedKey = "disallowed_tools";
|
|
664
|
+
} else if (fieldKey === "configJson") {
|
|
665
|
+
normalizedKey = "config_json";
|
|
666
|
+
}
|
|
667
|
+
lines.push(`- ${runtimeKey}.${normalizedKey}: ${stringifyExecutorValue(fieldValue)}`);
|
|
668
|
+
}
|
|
357
669
|
}
|
|
358
670
|
return lines;
|
|
359
671
|
}
|
|
@@ -471,6 +783,18 @@ export function renderWaveMarkdown(spec, lanePaths) {
|
|
|
471
783
|
sections.push("");
|
|
472
784
|
sections.push(...renderBulletLines(agent.capabilities));
|
|
473
785
|
}
|
|
786
|
+
if (Array.isArray(agent.deliverables) && agent.deliverables.length > 0) {
|
|
787
|
+
sections.push("");
|
|
788
|
+
sections.push("### Deliverables");
|
|
789
|
+
sections.push("");
|
|
790
|
+
sections.push(...renderPathSection(agent.deliverables));
|
|
791
|
+
}
|
|
792
|
+
if (Array.isArray(agent.proofArtifacts) && agent.proofArtifacts.length > 0) {
|
|
793
|
+
sections.push("");
|
|
794
|
+
sections.push("### Proof artifacts");
|
|
795
|
+
sections.push("");
|
|
796
|
+
sections.push(...renderProofArtifactsSection(agent.proofArtifacts));
|
|
797
|
+
}
|
|
474
798
|
if (agent.exitContract) {
|
|
475
799
|
sections.push("");
|
|
476
800
|
sections.push("### Exit contract");
|
|
@@ -595,6 +919,8 @@ function buildSpecialAgents({ spec, lanePaths, standardRoles }) {
|
|
|
595
919
|
context7: { bundle: "none", query: "Architecture evaluation only; repository docs remain canonical" },
|
|
596
920
|
components: [],
|
|
597
921
|
capabilities: [],
|
|
922
|
+
deliverables: [`docs/plans/waves/reviews/wave-${spec.wave}-cont-qa.md`],
|
|
923
|
+
proofArtifacts: [],
|
|
598
924
|
exitContract: null,
|
|
599
925
|
primaryGoal: `Run continuous QA for Wave ${spec.wave} and publish the final closure verdict.`,
|
|
600
926
|
collaborationNotes: [
|
|
@@ -624,6 +950,8 @@ function buildSpecialAgents({ spec, lanePaths, standardRoles }) {
|
|
|
624
950
|
context7: { bundle: "none", query: "Eval tuning only; repository docs remain canonical" },
|
|
625
951
|
components: [],
|
|
626
952
|
capabilities: ["eval"],
|
|
953
|
+
deliverables: [`docs/plans/waves/reviews/wave-${spec.wave}-cont-eval.md`],
|
|
954
|
+
proofArtifacts: [],
|
|
627
955
|
exitContract: null,
|
|
628
956
|
primaryGoal: `Run the Wave ${spec.wave} eval tuning loop until the declared eval targets are satisfied or explicitly blocked.`,
|
|
629
957
|
collaborationNotes: [
|
|
@@ -653,6 +981,11 @@ function buildSpecialAgents({ spec, lanePaths, standardRoles }) {
|
|
|
653
981
|
context7: { bundle: "none", query: "Integration synthesis only; repository docs remain canonical" },
|
|
654
982
|
components: [],
|
|
655
983
|
capabilities: ["integration", "docs-shared-plan"],
|
|
984
|
+
deliverables: [
|
|
985
|
+
path.join(path.relative(REPO_ROOT, lanePaths.integrationDir), `wave-${spec.wave}.md`).replaceAll("\\", "/"),
|
|
986
|
+
path.join(path.relative(REPO_ROOT, lanePaths.integrationDir), `wave-${spec.wave}.json`).replaceAll("\\", "/"),
|
|
987
|
+
],
|
|
988
|
+
proofArtifacts: [],
|
|
656
989
|
exitContract: null,
|
|
657
990
|
primaryGoal: `Synthesize the final Wave ${spec.wave} state before documentation and cont-QA closure.`,
|
|
658
991
|
collaborationNotes: [
|
|
@@ -682,6 +1015,8 @@ function buildSpecialAgents({ spec, lanePaths, standardRoles }) {
|
|
|
682
1015
|
context7: { bundle: "none", query: "Shared plan documentation only; repository docs remain canonical" },
|
|
683
1016
|
components: [],
|
|
684
1017
|
capabilities: [],
|
|
1018
|
+
deliverables: sharedDocs.filter((entry) => !cleanText(entry).endsWith("/")),
|
|
1019
|
+
proofArtifacts: [],
|
|
685
1020
|
exitContract: null,
|
|
686
1021
|
primaryGoal: `Keep shared plan docs aligned with Wave ${spec.wave} end-to-end.`,
|
|
687
1022
|
collaborationNotes: [
|
|
@@ -736,10 +1071,15 @@ function buildWorkerAgentSpec({
|
|
|
736
1071
|
agentId,
|
|
737
1072
|
title,
|
|
738
1073
|
rolePromptPaths:
|
|
739
|
-
|
|
1074
|
+
Array.isArray(values.rolePromptPaths) && values.rolePromptPaths.length > 0
|
|
1075
|
+
? values.rolePromptPaths
|
|
1076
|
+
: roleKind === "security"
|
|
1077
|
+
? [lanePaths.securityRolePromptPath]
|
|
1078
|
+
: [],
|
|
740
1079
|
skills: values.skills || [],
|
|
741
1080
|
executor: {
|
|
742
|
-
|
|
1081
|
+
...(values.executor || {}),
|
|
1082
|
+
...(values.executorProfile ? { profile: values.executorProfile } : {}),
|
|
743
1083
|
},
|
|
744
1084
|
context7: {
|
|
745
1085
|
bundle: values.context7Bundle,
|
|
@@ -747,10 +1087,15 @@ function buildWorkerAgentSpec({
|
|
|
747
1087
|
},
|
|
748
1088
|
components: values.components,
|
|
749
1089
|
capabilities,
|
|
1090
|
+
deliverables: Array.isArray(values.deliverables)
|
|
1091
|
+
? values.deliverables
|
|
1092
|
+
: values.ownedPaths.filter((ownedPath) => !cleanText(ownedPath).endsWith("/")),
|
|
1093
|
+
proofArtifacts: Array.isArray(values.proofArtifacts) ? values.proofArtifacts : [],
|
|
750
1094
|
exitContract: values.exitContract,
|
|
751
1095
|
primaryGoal:
|
|
752
1096
|
values.primaryGoal || buildDefaultPrimaryGoal(template, roleKind, title),
|
|
753
1097
|
collaborationNotes: [
|
|
1098
|
+
...(Array.isArray(values.collaborationNotes) ? values.collaborationNotes : []),
|
|
754
1099
|
"Re-read the wave message board before major decisions, before validation, and before final output.",
|
|
755
1100
|
`Notify Agent ${lanePaths.contQaAgentId} when your evidence changes the closure picture.`,
|
|
756
1101
|
],
|
|
@@ -835,137 +1180,1831 @@ function upsertComponentMatrix(matrix, spec) {
|
|
|
835
1180
|
return next;
|
|
836
1181
|
}
|
|
837
1182
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1183
|
+
function componentMaturityIndex(level) {
|
|
1184
|
+
return COMPONENT_MATURITY_ORDER[cleanText(level)] ?? -1;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function isProofCentricLevel(level) {
|
|
1188
|
+
return componentMaturityIndex(level) >= componentMaturityIndex(PROOF_CENTRIC_COMPONENT_LEVEL);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function escapeRegExp(value) {
|
|
1192
|
+
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function commandExists(command) {
|
|
1196
|
+
const normalized = cleanText(command);
|
|
1197
|
+
if (!normalized) {
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
const result = spawnSync("bash", ["-lc", `command -v ${JSON.stringify(normalized)} >/dev/null 2>&1`], {
|
|
1201
|
+
cwd: REPO_ROOT,
|
|
1202
|
+
encoding: "utf8",
|
|
848
1203
|
});
|
|
1204
|
+
return result.status === 0;
|
|
849
1205
|
}
|
|
850
1206
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
)
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
DEPLOY_ENVIRONMENT_KINDS,
|
|
908
|
-
existingEnvironment?.kind || "custom",
|
|
909
|
-
);
|
|
910
|
-
const isDefault = await prompt.askBoolean(
|
|
911
|
-
`Mark deploy environment ${id} as the default?`,
|
|
912
|
-
existingEnvironment?.isDefault === true || (index === 0 && base.deployEnvironments.length === 0),
|
|
913
|
-
);
|
|
914
|
-
const notes = cleanText(
|
|
915
|
-
await prompt.ask(
|
|
916
|
-
`Deploy environment ${id} notes`,
|
|
917
|
-
existingEnvironment?.notes || "",
|
|
918
|
-
),
|
|
919
|
-
);
|
|
920
|
-
deployEnvironments.push({
|
|
921
|
-
id,
|
|
922
|
-
name,
|
|
923
|
-
kind,
|
|
924
|
-
isDefault,
|
|
925
|
-
notes: notes || null,
|
|
926
|
-
});
|
|
1207
|
+
function fileExists(relPath) {
|
|
1208
|
+
return fs.existsSync(path.resolve(REPO_ROOT, relPath));
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function listTaskMatchedPaths(task) {
|
|
1212
|
+
const terms = splitTaskTerms(task);
|
|
1213
|
+
if (terms.length === 0 || !commandExists("rg")) {
|
|
1214
|
+
return [];
|
|
1215
|
+
}
|
|
1216
|
+
const pattern = terms.map((term) => escapeRegExp(term)).join("|");
|
|
1217
|
+
const result = spawnSync(
|
|
1218
|
+
"rg",
|
|
1219
|
+
["-l", "-i", pattern, "AGENTS.md", "README.md", "docs", "scripts", "test"],
|
|
1220
|
+
{
|
|
1221
|
+
cwd: REPO_ROOT,
|
|
1222
|
+
encoding: "utf8",
|
|
1223
|
+
},
|
|
1224
|
+
);
|
|
1225
|
+
if (result.error) {
|
|
1226
|
+
return [];
|
|
1227
|
+
}
|
|
1228
|
+
return uniqueStrings(
|
|
1229
|
+
String(result.stdout || "")
|
|
1230
|
+
.split(/\r?\n/)
|
|
1231
|
+
.map((entry) => cleanText(entry)),
|
|
1232
|
+
).slice(0, 16);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function listExistingWaveArtifacts(fromWave, lanePaths) {
|
|
1236
|
+
if (!fs.existsSync(lanePaths.wavesDir)) {
|
|
1237
|
+
return [];
|
|
1238
|
+
}
|
|
1239
|
+
const entries = fs
|
|
1240
|
+
.readdirSync(lanePaths.wavesDir, { withFileTypes: true })
|
|
1241
|
+
.filter((entry) => entry.isFile())
|
|
1242
|
+
.map((entry) => entry.name)
|
|
1243
|
+
.map((name) => {
|
|
1244
|
+
const match = name.match(/^wave-(\d+)\.md$/);
|
|
1245
|
+
return match
|
|
1246
|
+
? {
|
|
1247
|
+
wave: Number.parseInt(match[1], 10),
|
|
1248
|
+
name,
|
|
1249
|
+
}
|
|
1250
|
+
: null;
|
|
1251
|
+
})
|
|
1252
|
+
.filter(Boolean)
|
|
1253
|
+
.filter((entry) => entry.wave < fromWave)
|
|
1254
|
+
.sort((left, right) => right.wave - left.wave)
|
|
1255
|
+
.slice(0, 4);
|
|
1256
|
+
const paths = [];
|
|
1257
|
+
for (const entry of entries) {
|
|
1258
|
+
const wavePath = path.join(lanePaths.wavesDir, entry.name);
|
|
1259
|
+
const specPath = path.join(lanePaths.wavesDir, "specs", `wave-${entry.wave}.json`);
|
|
1260
|
+
paths.push(repoRelativePath(wavePath));
|
|
1261
|
+
if (fs.existsSync(specPath)) {
|
|
1262
|
+
paths.push(repoRelativePath(specPath));
|
|
927
1263
|
}
|
|
928
|
-
const profile = writeProjectProfile(
|
|
929
|
-
{
|
|
930
|
-
...base,
|
|
931
|
-
newProject,
|
|
932
|
-
defaultOversightMode,
|
|
933
|
-
defaultTerminalSurface,
|
|
934
|
-
deployEnvironments,
|
|
935
|
-
plannerDefaults: {
|
|
936
|
-
template,
|
|
937
|
-
lane,
|
|
938
|
-
},
|
|
939
|
-
},
|
|
940
|
-
{ config },
|
|
941
|
-
);
|
|
942
|
-
return profile;
|
|
943
|
-
} finally {
|
|
944
|
-
await prompt.close();
|
|
945
1264
|
}
|
|
1265
|
+
return uniqueStrings(paths);
|
|
946
1266
|
}
|
|
947
1267
|
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
const
|
|
951
|
-
|
|
1268
|
+
function createPlannerSourceAccumulator() {
|
|
1269
|
+
const order = [];
|
|
1270
|
+
const entries = new Map();
|
|
1271
|
+
return {
|
|
1272
|
+
add(sourcePath, category, reason) {
|
|
1273
|
+
const normalizedPath = normalizeRepoRelativePath(sourcePath, "planner source path");
|
|
1274
|
+
let entry = entries.get(normalizedPath);
|
|
1275
|
+
if (!entry) {
|
|
1276
|
+
entry = {
|
|
1277
|
+
path: normalizedPath,
|
|
1278
|
+
categories: new Set(),
|
|
1279
|
+
reasons: new Set(),
|
|
1280
|
+
};
|
|
1281
|
+
entries.set(normalizedPath, entry);
|
|
1282
|
+
order.push(normalizedPath);
|
|
1283
|
+
}
|
|
1284
|
+
if (category) {
|
|
1285
|
+
entry.categories.add(cleanText(category));
|
|
1286
|
+
}
|
|
1287
|
+
if (reason) {
|
|
1288
|
+
entry.reasons.add(cleanText(reason));
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1291
|
+
finalize() {
|
|
1292
|
+
return order.map((sourcePath) => {
|
|
1293
|
+
const entry = entries.get(sourcePath);
|
|
1294
|
+
const text = readTextIfExists(sourcePath);
|
|
1295
|
+
return {
|
|
1296
|
+
path: sourcePath,
|
|
1297
|
+
exists: text !== null,
|
|
1298
|
+
bytes: text === null ? 0 : Buffer.byteLength(text, "utf8"),
|
|
1299
|
+
sha256: text === null ? null : hashContent(text),
|
|
1300
|
+
categories: Array.from(entry.categories),
|
|
1301
|
+
reasons: Array.from(entry.reasons),
|
|
1302
|
+
};
|
|
1303
|
+
});
|
|
1304
|
+
},
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function collectPlannerSources({ config, lanePaths, task, fromWave }) {
|
|
1309
|
+
const plannerConfig = config.planner?.agentic || {};
|
|
1310
|
+
const taskPathHints = extractRepoPathHints(task);
|
|
1311
|
+
const taskMatchedPaths = listTaskMatchedPaths(task);
|
|
1312
|
+
const priorWaveArtifacts = listExistingWaveArtifacts(fromWave, lanePaths);
|
|
1313
|
+
const sources = createPlannerSourceAccumulator();
|
|
1314
|
+
sources.add("docs/agents/wave-planner-role.md", "planner-role", "standing planner role");
|
|
1315
|
+
sources.add("skills/role-planner/SKILL.md", "planner-skill", "planner skill checklist");
|
|
1316
|
+
for (const sourcePath of ensureRepoRelativePathList(
|
|
1317
|
+
plannerConfig.coreContextPaths || DEFAULT_PLANNER_AGENTIC_CORE_CONTEXT_PATHS,
|
|
1318
|
+
"planner.agentic.coreContextPaths",
|
|
1319
|
+
)) {
|
|
1320
|
+
sources.add(sourcePath, "core-context", "planner core context");
|
|
1321
|
+
}
|
|
1322
|
+
if (fileExists(PROJECT_PROFILE_PATH)) {
|
|
1323
|
+
sources.add(repoRelativePath(PROJECT_PROFILE_PATH), "project-profile", "saved project profile");
|
|
1324
|
+
}
|
|
1325
|
+
for (const sourcePath of ensureRepoRelativePathList(
|
|
1326
|
+
plannerConfig.lessonsPaths || DEFAULT_PLANNER_AGENTIC_LESSONS_PATHS,
|
|
1327
|
+
"planner.agentic.lessonsPaths",
|
|
1328
|
+
)) {
|
|
1329
|
+
sources.add(sourcePath, "lessons", "repo-local planning lessons");
|
|
1330
|
+
}
|
|
1331
|
+
for (const sourcePath of ensureRepoRelativePathList(
|
|
1332
|
+
plannerConfig.researchTopicPaths || DEFAULT_PLANNER_AGENTIC_RESEARCH_TOPIC_PATHS,
|
|
1333
|
+
"planner.agentic.researchTopicPaths",
|
|
1334
|
+
)) {
|
|
1335
|
+
sources.add(sourcePath, "research-topic", "planning research topic index");
|
|
1336
|
+
}
|
|
1337
|
+
for (const sourcePath of PLANNER_CONTEXT7_PAPER_PATHS) {
|
|
1338
|
+
sources.add(sourcePath, "research-paper", "fixed planning research slice");
|
|
1339
|
+
}
|
|
1340
|
+
for (const sourcePath of taskPathHints) {
|
|
1341
|
+
sources.add(sourcePath, "task-hint", "path mentioned directly in task");
|
|
1342
|
+
}
|
|
1343
|
+
for (const sourcePath of taskMatchedPaths) {
|
|
1344
|
+
sources.add(sourcePath, "task-search", "task term match");
|
|
1345
|
+
}
|
|
1346
|
+
for (const sourcePath of priorWaveArtifacts) {
|
|
1347
|
+
sources.add(sourcePath, "prior-wave", "nearby prior wave artifact");
|
|
1348
|
+
}
|
|
1349
|
+
return sources.finalize();
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function summarizeDeployEnvironments(profile) {
|
|
1353
|
+
const environments = Array.isArray(profile?.deployEnvironments) ? profile.deployEnvironments : [];
|
|
1354
|
+
if (environments.length === 0) {
|
|
1355
|
+
return ["- none declared"];
|
|
1356
|
+
}
|
|
1357
|
+
return environments.map((environment) => {
|
|
1358
|
+
const suffix = environment.notes ? ` (${environment.notes})` : "";
|
|
1359
|
+
return `- ${environment.id}: ${environment.kind}${environment.isDefault ? " default" : ""}${suffix}`;
|
|
952
1360
|
});
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function summarizeCurrentComponentLevels(matrix) {
|
|
1364
|
+
return Object.entries(matrix.components || {})
|
|
1365
|
+
.sort((left, right) => left[0].localeCompare(right[0]))
|
|
1366
|
+
.slice(0, 20)
|
|
1367
|
+
.map(([componentId, component]) => `- ${componentId}: ${component.currentLevel}`);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function renderPlannerContext7Section(plannerContext7) {
|
|
1371
|
+
if (!plannerContext7?.selection) {
|
|
1372
|
+
return [];
|
|
1373
|
+
}
|
|
1374
|
+
const libraries = describeContext7Libraries(plannerContext7.selection) || "none";
|
|
1375
|
+
const lines = [
|
|
1376
|
+
"## Planner Context7",
|
|
1377
|
+
"",
|
|
1378
|
+
`- bundle: ${plannerContext7.selection.bundleId}`,
|
|
1379
|
+
`- query: ${plannerContext7.selection.query || "none"}`,
|
|
1380
|
+
`- libraries: ${libraries}`,
|
|
1381
|
+
`- mode: ${plannerContext7.prefetch?.mode || "none"}`,
|
|
1382
|
+
];
|
|
1383
|
+
if (plannerContext7.selection.bundleId === PLANNER_CONTEXT7_BUNDLE_ID) {
|
|
1384
|
+
lines.push(
|
|
1385
|
+
`- repo-export-dir: ${PLANNER_CONTEXT7_SOURCE_DIR}`,
|
|
1386
|
+
`- default-query: ${PLANNER_CONTEXT7_DEFAULT_QUERY}`,
|
|
962
1387
|
);
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1388
|
+
}
|
|
1389
|
+
if (plannerContext7.prefetch?.warning) {
|
|
1390
|
+
lines.push(`- warning: ${plannerContext7.prefetch.warning}`);
|
|
1391
|
+
}
|
|
1392
|
+
if (plannerContext7.prefetch?.promptText) {
|
|
1393
|
+
lines.push(
|
|
1394
|
+
"",
|
|
1395
|
+
"### Planner Context7 Snippets",
|
|
1396
|
+
"",
|
|
1397
|
+
"Treat this block as non-canonical external reference material.",
|
|
1398
|
+
"",
|
|
1399
|
+
plannerContext7.prefetch.promptText,
|
|
966
1400
|
);
|
|
967
|
-
|
|
968
|
-
|
|
1401
|
+
}
|
|
1402
|
+
return lines;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function buildPlannerPromptText({ request, sources, profile, bundleIndex, matrix, plannerContext7 }) {
|
|
1406
|
+
const existingSources = sources.filter((source) => source.exists);
|
|
1407
|
+
const missingSources = sources.filter((source) => !source.exists);
|
|
1408
|
+
const sourceLines = existingSources.map(
|
|
1409
|
+
(source) => `- ${source.path}${source.reasons.length > 0 ? ` (${source.reasons.join("; ")})` : ""}`,
|
|
1410
|
+
);
|
|
1411
|
+
const missingLines = missingSources.map((source) => `- ${source.path}`);
|
|
1412
|
+
const bundleIds = Object.keys(bundleIndex?.bundles || {}).sort();
|
|
1413
|
+
const plannerContext7Lines = renderPlannerContext7Section(plannerContext7);
|
|
1414
|
+
return [
|
|
1415
|
+
"# Agentic Wave Planner",
|
|
1416
|
+
"",
|
|
1417
|
+
"Read the standing planner role and planner skill first, then read the repo-local planning docs, nearby wave artifacts, and research files listed below before drafting the plan.",
|
|
1418
|
+
"",
|
|
1419
|
+
"## Request",
|
|
1420
|
+
"",
|
|
1421
|
+
`- task: ${request.task}`,
|
|
1422
|
+
`- lane: ${request.lane}`,
|
|
1423
|
+
`- from-wave: ${request.fromWave}`,
|
|
1424
|
+
`- max-waves: ${request.maxWaves}`,
|
|
1425
|
+
`- planner-executor-profile: ${request.plannerExecutorProfile}`,
|
|
1426
|
+
"",
|
|
1427
|
+
"## Project Profile",
|
|
1428
|
+
"",
|
|
1429
|
+
`- new-project: ${profile?.newProject ? "yes" : "no"}`,
|
|
1430
|
+
`- default-oversight-mode: ${profile?.defaultOversightMode || "oversight"}`,
|
|
1431
|
+
`- default-terminal-surface: ${profile?.defaultTerminalSurface || "codex-cli"}`,
|
|
1432
|
+
"- deploy-environments:",
|
|
1433
|
+
...summarizeDeployEnvironments(profile),
|
|
1434
|
+
"",
|
|
1435
|
+
"## Current Component Levels",
|
|
1436
|
+
"",
|
|
1437
|
+
...(summarizeCurrentComponentLevels(matrix).length > 0
|
|
1438
|
+
? summarizeCurrentComponentLevels(matrix)
|
|
1439
|
+
: ["- none declared"]),
|
|
1440
|
+
"",
|
|
1441
|
+
...plannerContext7Lines,
|
|
1442
|
+
...(plannerContext7Lines.length > 0 ? [""] : []),
|
|
1443
|
+
"## Available Context7 Bundles",
|
|
1444
|
+
"",
|
|
1445
|
+
...renderBulletLines(bundleIds.length > 0 ? bundleIds : ["none"]),
|
|
1446
|
+
"",
|
|
1447
|
+
"## Source Files To Read",
|
|
1448
|
+
"",
|
|
1449
|
+
...(sourceLines.length > 0 ? sourceLines : ["- none"]),
|
|
1450
|
+
...(missingLines.length > 0
|
|
1451
|
+
? ["", "## Missing Suggested Sources", "", ...missingLines]
|
|
1452
|
+
: []),
|
|
1453
|
+
"",
|
|
1454
|
+
"## Output Rules",
|
|
1455
|
+
"",
|
|
1456
|
+
"- Return JSON only. No markdown fences. No explanatory prose before or after the JSON.",
|
|
1457
|
+
"- Keep the plan reviewable and narrow. Split broad work into multiple waves instead of overclaiming maturity.",
|
|
1458
|
+
"- Keep each promoted component to one honest maturity jump per wave unless the request explicitly requires otherwise.",
|
|
1459
|
+
"- For pilot-live and above, include an explicit live-proof owner with `.tmp/` proof bundle artifacts, a runbook under `docs/plans/operations/`, and rollback or restart evidence.",
|
|
1460
|
+
"- Give every worker agent exact deliverables. Give proof-centric owners exact proof artifacts.",
|
|
1461
|
+
"- Keep A8, A9, and A0 as closure gates through `standardRoles` unless there is a repo-specific reason not to.",
|
|
1462
|
+
"",
|
|
1463
|
+
"## Required JSON Shape",
|
|
1464
|
+
"",
|
|
1465
|
+
"The top-level object must have this shape:",
|
|
1466
|
+
"",
|
|
1467
|
+
"{",
|
|
1468
|
+
' "summary": "short decision-ready summary",',
|
|
1469
|
+
' "openQuestions": ["question"],',
|
|
1470
|
+
' "waves": [',
|
|
1471
|
+
" {",
|
|
1472
|
+
' "title": "Wave title",',
|
|
1473
|
+
' "commitMessage": "Feat: ...",',
|
|
1474
|
+
' "template": "implementation",',
|
|
1475
|
+
' "sequencingNote": "optional",',
|
|
1476
|
+
' "referenceRule": "optional",',
|
|
1477
|
+
' "oversightMode": "oversight",',
|
|
1478
|
+
' "context7Defaults": { "bundle": "none", "query": "" },',
|
|
1479
|
+
' "standardRoles": { "contQa": true, "contEval": false, "integration": true, "documentation": true },',
|
|
1480
|
+
' "evalTargets": [],',
|
|
1481
|
+
' "componentCatalog": [',
|
|
1482
|
+
' { "componentId": "component-id", "title": "Title", "currentLevel": "repo-landed", "targetLevel": "baseline-proved", "canonicalDocs": ["docs/..."], "proofSurfaces": ["tests"] }',
|
|
1483
|
+
" ],",
|
|
1484
|
+
' "workerAgents": [',
|
|
1485
|
+
' {',
|
|
1486
|
+
' "agentId": "A1",',
|
|
1487
|
+
' "title": "Implementation Track 1",',
|
|
1488
|
+
' "roleKind": "implementation",',
|
|
1489
|
+
' "executor": { "profile": "implement-fast" },',
|
|
1490
|
+
' "ownedPaths": ["scripts/..."],',
|
|
1491
|
+
' "deliverables": ["scripts/..."],',
|
|
1492
|
+
' "proofArtifacts": [{ "path": ".tmp/...", "kind": "proof-bundle", "requiredFor": ["pilot-live"] }],',
|
|
1493
|
+
' "components": ["component-id"],',
|
|
1494
|
+
' "capabilities": ["implementation"],',
|
|
1495
|
+
' "additionalContext": ["docs/plans/current-state.md"],',
|
|
1496
|
+
' "earlierWaveOutputs": [],',
|
|
1497
|
+
' "requirements": ["exact requirement"],',
|
|
1498
|
+
' "validationCommand": "pnpm test",',
|
|
1499
|
+
' "outputSummary": "what the final report must summarize",',
|
|
1500
|
+
' "primaryGoal": "what this owner is proving",',
|
|
1501
|
+
' "deployEnvironmentId": null,',
|
|
1502
|
+
' "context7Bundle": "none",',
|
|
1503
|
+
' "context7Query": "",',
|
|
1504
|
+
' "exitContract": { "completion": "contract", "durability": "none", "proof": "unit", "docImpact": "owned" }',
|
|
1505
|
+
" }",
|
|
1506
|
+
" ]",
|
|
1507
|
+
" }",
|
|
1508
|
+
" ]",
|
|
1509
|
+
"}",
|
|
1510
|
+
"",
|
|
1511
|
+
].join("\n");
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function ensurePlannerRunDirectories(runPaths) {
|
|
1515
|
+
ensureDirectory(runPaths.runDir);
|
|
1516
|
+
ensureDirectory(runPaths.candidateDir);
|
|
1517
|
+
ensureDirectory(runPaths.candidateWavesDir);
|
|
1518
|
+
ensureDirectory(runPaths.candidateSpecsDir);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function resolvePlannerContext7Selection({ config, lanePaths, bundleIndex, request }) {
|
|
1522
|
+
const plannerAgentic = config.planner?.agentic || {};
|
|
1523
|
+
return resolveContext7Selection({
|
|
1524
|
+
lane: lanePaths.lane,
|
|
1525
|
+
waveDefaults: {
|
|
1526
|
+
bundle: plannerAgentic.context7Bundle || DEFAULT_PLANNER_AGENTIC_CONTEXT7_BUNDLE,
|
|
1527
|
+
query: plannerAgentic.context7Query || DEFAULT_PLANNER_AGENTIC_CONTEXT7_QUERY,
|
|
1528
|
+
},
|
|
1529
|
+
agentConfig: null,
|
|
1530
|
+
agent: {
|
|
1531
|
+
agentId: "planner",
|
|
1532
|
+
title: "Agentic Wave Planner",
|
|
1533
|
+
promptOverlay: request.task,
|
|
1534
|
+
},
|
|
1535
|
+
bundleIndex,
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function buildAgenticPlannerRequest({ config, lanePaths, task, fromWave, maxWaves, plannerExecutorProfile }) {
|
|
1540
|
+
return {
|
|
1541
|
+
schemaVersion: AGENTIC_PLANNER_SCHEMA_VERSION,
|
|
1542
|
+
createdAt: new Date().toISOString(),
|
|
1543
|
+
lane: lanePaths.lane,
|
|
1544
|
+
task: compactSingleLine(task),
|
|
1545
|
+
fromWave,
|
|
1546
|
+
maxWaves,
|
|
1547
|
+
plannerExecutorProfile,
|
|
1548
|
+
plannerConfig: {
|
|
1549
|
+
executorProfile:
|
|
1550
|
+
config.planner?.agentic?.executorProfile || DEFAULT_PLANNER_AGENTIC_EXECUTOR_PROFILE,
|
|
1551
|
+
maxReplanIterations:
|
|
1552
|
+
config.planner?.agentic?.maxReplanIterations ||
|
|
1553
|
+
DEFAULT_PLANNER_AGENTIC_MAX_REPLAN_ITERATIONS,
|
|
1554
|
+
context7Bundle:
|
|
1555
|
+
config.planner?.agentic?.context7Bundle || DEFAULT_PLANNER_AGENTIC_CONTEXT7_BUNDLE,
|
|
1556
|
+
context7Query:
|
|
1557
|
+
config.planner?.agentic?.context7Query || DEFAULT_PLANNER_AGENTIC_CONTEXT7_QUERY,
|
|
1558
|
+
},
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function normalizeGeneratedComponentId(value, fallback = "planned-component") {
|
|
1563
|
+
const normalized = cleanText(value)
|
|
1564
|
+
.toLowerCase()
|
|
1565
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
1566
|
+
.replace(/-+/g, "-")
|
|
1567
|
+
.replace(/^-+|-+$/g, "");
|
|
1568
|
+
if (!normalized) {
|
|
1569
|
+
return fallback;
|
|
1570
|
+
}
|
|
1571
|
+
return normalizeComponentId(normalized, "planner component id");
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function plannerMaterialLevelIndex(level) {
|
|
1575
|
+
const normalized = cleanText(level);
|
|
1576
|
+
const ladder = [
|
|
1577
|
+
"repo-landed",
|
|
1578
|
+
"baseline-proved",
|
|
1579
|
+
"pilot-live",
|
|
1580
|
+
"qa-proved",
|
|
1581
|
+
"fleet-ready",
|
|
1582
|
+
"cutover-ready",
|
|
1583
|
+
"deprecation-ready",
|
|
1584
|
+
];
|
|
1585
|
+
const index = ladder.indexOf(normalized);
|
|
1586
|
+
return index >= 0 ? index : 0;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function detectRequestedTargetLevel(task) {
|
|
1590
|
+
const text = String(task || "").toLowerCase();
|
|
1591
|
+
if (text.includes("deprecat")) {
|
|
1592
|
+
return "deprecation-ready";
|
|
1593
|
+
}
|
|
1594
|
+
if (text.includes("cutover")) {
|
|
1595
|
+
return "cutover-ready";
|
|
1596
|
+
}
|
|
1597
|
+
if (text.includes("fleet") || text.includes("rollout")) {
|
|
1598
|
+
return "fleet-ready";
|
|
1599
|
+
}
|
|
1600
|
+
if (text.includes("qa")) {
|
|
1601
|
+
return "qa-proved";
|
|
1602
|
+
}
|
|
1603
|
+
if (
|
|
1604
|
+
text.includes("pilot") ||
|
|
1605
|
+
text.includes("live") ||
|
|
1606
|
+
text.includes("deploy") ||
|
|
1607
|
+
text.includes("rollback") ||
|
|
1608
|
+
text.includes("operator")
|
|
1609
|
+
) {
|
|
1610
|
+
return "pilot-live";
|
|
1611
|
+
}
|
|
1612
|
+
if (text.includes("baseline")) {
|
|
1613
|
+
return "baseline-proved";
|
|
1614
|
+
}
|
|
1615
|
+
return "repo-landed";
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function buildPlannerWaveTargets(currentLevel, requestedTargetLevel, maxWaves) {
|
|
1619
|
+
const ladder = [
|
|
1620
|
+
"repo-landed",
|
|
1621
|
+
"baseline-proved",
|
|
1622
|
+
"pilot-live",
|
|
1623
|
+
"qa-proved",
|
|
1624
|
+
"fleet-ready",
|
|
1625
|
+
"cutover-ready",
|
|
1626
|
+
"deprecation-ready",
|
|
1627
|
+
];
|
|
1628
|
+
const currentIndex = Math.max(0, plannerMaterialLevelIndex(currentLevel));
|
|
1629
|
+
const targetIndex = Math.max(currentIndex, plannerMaterialLevelIndex(requestedTargetLevel));
|
|
1630
|
+
const targets = [];
|
|
1631
|
+
for (let index = currentIndex; index <= targetIndex && targets.length < maxWaves; index += 1) {
|
|
1632
|
+
if (ladder[index] !== cleanText(currentLevel)) {
|
|
1633
|
+
targets.push(ladder[index]);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
if (targets.length === 0) {
|
|
1637
|
+
targets.push(ladder[targetIndex] || requestedTargetLevel);
|
|
1638
|
+
}
|
|
1639
|
+
return targets;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function findRelevantComponentsForTask(task, matrix) {
|
|
1643
|
+
const terms = splitTaskTerms(task);
|
|
1644
|
+
const ranked = Object.entries(matrix.components || {})
|
|
1645
|
+
.map(([componentId, component]) => {
|
|
1646
|
+
const haystack = `${componentId} ${component.title || ""}`.toLowerCase();
|
|
1647
|
+
const score = terms.reduce(
|
|
1648
|
+
(total, term) => (haystack.includes(term) ? total + 1 : total),
|
|
1649
|
+
0,
|
|
1650
|
+
);
|
|
1651
|
+
return {
|
|
1652
|
+
componentId,
|
|
1653
|
+
title: component.title || humanizeComponentId(componentId),
|
|
1654
|
+
currentLevel: component.currentLevel || "inventoried",
|
|
1655
|
+
canonicalDocs: Array.isArray(component.canonicalDocs) ? component.canonicalDocs : [],
|
|
1656
|
+
proofSurfaces: Array.isArray(component.proofSurfaces) ? component.proofSurfaces : [],
|
|
1657
|
+
score,
|
|
1658
|
+
};
|
|
1659
|
+
})
|
|
1660
|
+
.filter((entry) => entry.score > 0)
|
|
1661
|
+
.sort((left, right) => right.score - left.score || left.componentId.localeCompare(right.componentId));
|
|
1662
|
+
return ranked.slice(0, 2);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function selectTaskImplementationFiles(sources) {
|
|
1666
|
+
const preferred = (Array.isArray(sources) ? sources : [])
|
|
1667
|
+
.filter((source) => source.exists)
|
|
1668
|
+
.filter((source) => source.categories.includes("task-hint") || source.categories.includes("task-search"))
|
|
1669
|
+
.map((source) => source.path)
|
|
1670
|
+
.filter((sourcePath) =>
|
|
1671
|
+
/^(?:scripts|test|docs\/guides|docs\/reference|docs\/plans|README\.md|CHANGELOG\.md|package\.json)/.test(
|
|
1672
|
+
sourcePath,
|
|
1673
|
+
),
|
|
1674
|
+
)
|
|
1675
|
+
.filter(
|
|
1676
|
+
(sourcePath) =>
|
|
1677
|
+
!sourcePath.startsWith("docs/research/agent-context-cache/papers/") &&
|
|
1678
|
+
!sourcePath.startsWith("docs/research/") &&
|
|
1679
|
+
!sourcePath.startsWith("docs/context7/"),
|
|
1680
|
+
);
|
|
1681
|
+
return uniqueStrings(preferred).slice(0, 6);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function buildHeuristicWorkerAgents({
|
|
1685
|
+
waveNumber,
|
|
1686
|
+
waveTitle,
|
|
1687
|
+
componentId,
|
|
1688
|
+
targetLevel,
|
|
1689
|
+
taskFiles,
|
|
1690
|
+
bundleIndex,
|
|
1691
|
+
}) {
|
|
1692
|
+
const codeFiles = taskFiles.filter((filePath) => filePath.startsWith("scripts/"));
|
|
1693
|
+
const testFiles = taskFiles.filter((filePath) => filePath.startsWith("test/"));
|
|
1694
|
+
const docFiles = taskFiles.filter((filePath) => filePath.startsWith("docs/"));
|
|
1695
|
+
const implementationDeliverables = uniqueStrings(
|
|
1696
|
+
[...codeFiles.slice(0, 2), ...testFiles.slice(0, 1), ...docFiles.slice(0, 1)],
|
|
1697
|
+
);
|
|
1698
|
+
const implementationOwnedPaths =
|
|
1699
|
+
implementationDeliverables.length > 0
|
|
1700
|
+
? implementationDeliverables
|
|
1701
|
+
: ["README.md"];
|
|
1702
|
+
const agents = [
|
|
1703
|
+
{
|
|
1704
|
+
agentId: "A1",
|
|
1705
|
+
title: waveTitle.includes("Planner") ? "Planner Implementation Slice" : "Implementation Slice",
|
|
1706
|
+
roleKind: "implementation",
|
|
1707
|
+
executor: {
|
|
1708
|
+
profile: "implement-fast",
|
|
1709
|
+
},
|
|
1710
|
+
ownedPaths: implementationOwnedPaths,
|
|
1711
|
+
deliverables: implementationDeliverables.length > 0 ? implementationDeliverables : ["README.md"],
|
|
1712
|
+
proofArtifacts: [],
|
|
1713
|
+
components: [componentId],
|
|
1714
|
+
capabilities: ["implementation"],
|
|
1715
|
+
additionalContext: ["docs/plans/current-state.md", "docs/plans/master-plan.md"],
|
|
1716
|
+
earlierWaveOutputs: [],
|
|
1717
|
+
requirements: [
|
|
1718
|
+
"Keep ownership boundaries explicit and machine-checkable.",
|
|
1719
|
+
"Leave exact follow-up proof gaps in the final output instead of overclaiming maturity.",
|
|
1720
|
+
],
|
|
1721
|
+
validationCommand:
|
|
1722
|
+
testFiles.length > 0
|
|
1723
|
+
? `pnpm test -- ${testFiles[0]}`
|
|
1724
|
+
: "pnpm test -- test/wave-orchestrator/planner.test.ts",
|
|
1725
|
+
outputSummary: "Summarize the landed implementation, tests, and any remaining proof gaps.",
|
|
1726
|
+
primaryGoal: `Implement the ${componentId} slice to the declared maturity target without broadening scope.`,
|
|
1727
|
+
deployEnvironmentId: null,
|
|
1728
|
+
context7Bundle: bundleIndex?.bundles?.[DEFAULT_GENERATED_WAVE_CONTEXT7_BUNDLE]
|
|
1729
|
+
? DEFAULT_GENERATED_WAVE_CONTEXT7_BUNDLE
|
|
1730
|
+
: "none",
|
|
1731
|
+
context7Query: DEFAULT_GENERATED_WAVE_CONTEXT7_QUERY,
|
|
1732
|
+
exitContract: defaultExitContract("implementation"),
|
|
1733
|
+
},
|
|
1734
|
+
];
|
|
1735
|
+
if (isProofCentricLevel(targetLevel)) {
|
|
1736
|
+
const liveProofBase = `.tmp/wave-${waveNumber}-${componentId}-proof`;
|
|
1737
|
+
const runbookPath = `${LIVE_PROOF_OPERATIONS_DIR}/${componentId}-wave-${waveNumber}.md`;
|
|
1738
|
+
const reviewPath = `${LIVE_PROOF_REVIEW_DIR}/wave-${waveNumber}-${componentId}-live-proof.md`;
|
|
1739
|
+
agents.push({
|
|
1740
|
+
agentId: "A2",
|
|
1741
|
+
title: "Live Proof Owner",
|
|
1742
|
+
roleKind: "deploy",
|
|
1743
|
+
executor: {
|
|
1744
|
+
profile: "ops-triage",
|
|
1745
|
+
retryPolicy: "sticky",
|
|
1746
|
+
budget: { minutes: 30 },
|
|
1747
|
+
},
|
|
1748
|
+
ownedPaths: [
|
|
1749
|
+
runbookPath,
|
|
1750
|
+
reviewPath,
|
|
1751
|
+
`${liveProofBase}/summary.md`,
|
|
1752
|
+
`${liveProofBase}/rollback.md`,
|
|
1753
|
+
],
|
|
1754
|
+
deliverables: [runbookPath, reviewPath],
|
|
1755
|
+
proofArtifacts: [
|
|
1756
|
+
{
|
|
1757
|
+
path: `${liveProofBase}/summary.md`,
|
|
1758
|
+
kind: "proof-bundle",
|
|
1759
|
+
requiredFor: [targetLevel],
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
path: `${liveProofBase}/rollback.md`,
|
|
1763
|
+
kind: "rollback-evidence",
|
|
1764
|
+
requiredFor: [targetLevel],
|
|
1765
|
+
},
|
|
1766
|
+
],
|
|
1767
|
+
components: [componentId],
|
|
1768
|
+
capabilities: ["deploy", "live-proof"],
|
|
1769
|
+
additionalContext: ["docs/reference/live-proof-waves.md", "docs/plans/current-state.md"],
|
|
1770
|
+
earlierWaveOutputs: [],
|
|
1771
|
+
requirements: [
|
|
1772
|
+
"Capture restart or rollback evidence, not only one-shot success.",
|
|
1773
|
+
"Author the exact operator runbook and keep it aligned with the proof bundle.",
|
|
1774
|
+
],
|
|
1775
|
+
validationCommand:
|
|
1776
|
+
"Re-read the proof bundle and runbook; fail the wave if rollback or restart evidence is missing.",
|
|
1777
|
+
outputSummary: "Summarize the live proof bundle, rollback posture, and operator-visible caveats.",
|
|
1778
|
+
primaryGoal: `Own the live-proof surface for ${componentId} and justify the ${targetLevel} claim honestly.`,
|
|
1779
|
+
deployEnvironmentId: null,
|
|
1780
|
+
context7Bundle: "none",
|
|
1781
|
+
context7Query: "",
|
|
1782
|
+
exitContract: defaultExitContract("deploy"),
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
return agents;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function buildHeuristicPlannerPayload({ request, sources, matrix, bundleIndex }) {
|
|
1789
|
+
const matchingComponents = findRelevantComponentsForTask(request.task, matrix);
|
|
1790
|
+
const component =
|
|
1791
|
+
matchingComponents[0] || {
|
|
1792
|
+
componentId: normalizeGeneratedComponentId(splitTaskTerms(request.task).join("-") || "planned-slice"),
|
|
1793
|
+
title: humanizeComponentId(normalizeGeneratedComponentId(splitTaskTerms(request.task).join("-") || "planned-slice")),
|
|
1794
|
+
currentLevel: "inventoried",
|
|
1795
|
+
canonicalDocs: ["docs/roadmap.md"],
|
|
1796
|
+
proofSurfaces: ["tests", "docs"],
|
|
1797
|
+
};
|
|
1798
|
+
const requestedTargetLevel = detectRequestedTargetLevel(request.task);
|
|
1799
|
+
const targetLevels = buildPlannerWaveTargets(
|
|
1800
|
+
component.currentLevel || "inventoried",
|
|
1801
|
+
requestedTargetLevel,
|
|
1802
|
+
request.maxWaves,
|
|
1803
|
+
);
|
|
1804
|
+
const taskFiles = selectTaskImplementationFiles(sources);
|
|
1805
|
+
const openQuestions = [];
|
|
1806
|
+
if (plannerMaterialLevelIndex(requestedTargetLevel) > plannerMaterialLevelIndex(targetLevels[targetLevels.length - 1])) {
|
|
1807
|
+
openQuestions.push(
|
|
1808
|
+
`The requested end state appears to exceed the configured max-waves limit (${request.maxWaves}); plan review should decide whether to add more waves.`,
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
const waves = targetLevels.map((targetLevel, index) => {
|
|
1812
|
+
const waveNumber = request.fromWave + index;
|
|
1813
|
+
const isProofWave = isProofCentricLevel(targetLevel);
|
|
1814
|
+
const titleSuffix = isProofWave ? "Live Proof" : index === 0 ? "Foundation" : "Closure";
|
|
1815
|
+
return {
|
|
1816
|
+
wave: waveNumber,
|
|
1817
|
+
title: `${humanizeComponentId(component.componentId)} ${titleSuffix}`,
|
|
1818
|
+
commitMessage:
|
|
1819
|
+
targetLevel === "repo-landed"
|
|
1820
|
+
? `Feat: land ${component.componentId} ${titleSuffix.toLowerCase()}`
|
|
1821
|
+
: `Docs: plan ${component.componentId} ${targetLevel}`,
|
|
1822
|
+
template: "implementation",
|
|
1823
|
+
sequencingNote:
|
|
1824
|
+
index === 0
|
|
1825
|
+
? "Keep the first wave repo-honest and only promote the next maturity step once closure artifacts exist."
|
|
1826
|
+
: `This wave assumes Wave ${waveNumber - 1} closed honestly before raising the maturity claim.`,
|
|
1827
|
+
referenceRule:
|
|
1828
|
+
"Read the planning lessons, current-state, master-plan, component matrix, and nearby wave artifacts before execution.",
|
|
1829
|
+
oversightMode: "oversight",
|
|
1830
|
+
context7Defaults: {
|
|
1831
|
+
bundle: bundleIndex?.bundles?.[DEFAULT_GENERATED_WAVE_CONTEXT7_BUNDLE]
|
|
1832
|
+
? DEFAULT_GENERATED_WAVE_CONTEXT7_BUNDLE
|
|
1833
|
+
: "none",
|
|
1834
|
+
query: DEFAULT_GENERATED_WAVE_CONTEXT7_QUERY,
|
|
1835
|
+
},
|
|
1836
|
+
standardRoles: {
|
|
1837
|
+
contQa: true,
|
|
1838
|
+
contEval: false,
|
|
1839
|
+
integration: true,
|
|
1840
|
+
documentation: true,
|
|
1841
|
+
},
|
|
1842
|
+
evalTargets: [],
|
|
1843
|
+
componentCatalog: [
|
|
1844
|
+
{
|
|
1845
|
+
componentId: component.componentId,
|
|
1846
|
+
title: component.title,
|
|
1847
|
+
currentLevel:
|
|
1848
|
+
index === 0
|
|
1849
|
+
? component.currentLevel || "inventoried"
|
|
1850
|
+
: targetLevels[index - 1],
|
|
1851
|
+
targetLevel,
|
|
1852
|
+
canonicalDocs:
|
|
1853
|
+
component.canonicalDocs && component.canonicalDocs.length > 0
|
|
1854
|
+
? component.canonicalDocs
|
|
1855
|
+
: ["docs/roadmap.md"],
|
|
1856
|
+
proofSurfaces:
|
|
1857
|
+
component.proofSurfaces && component.proofSurfaces.length > 0
|
|
1858
|
+
? component.proofSurfaces
|
|
1859
|
+
: isProofWave
|
|
1860
|
+
? ["tests", "runbook", "rollback-evidence"]
|
|
1861
|
+
: ["tests", "docs"],
|
|
1862
|
+
},
|
|
1863
|
+
],
|
|
1864
|
+
workerAgents: buildHeuristicWorkerAgents({
|
|
1865
|
+
waveNumber,
|
|
1866
|
+
waveTitle: `${humanizeComponentId(component.componentId)} ${titleSuffix}`,
|
|
1867
|
+
componentId: component.componentId,
|
|
1868
|
+
targetLevel,
|
|
1869
|
+
taskFiles,
|
|
1870
|
+
bundleIndex,
|
|
1871
|
+
}),
|
|
1872
|
+
};
|
|
1873
|
+
});
|
|
1874
|
+
return {
|
|
1875
|
+
summary: `Draft ${waves.length === 1 ? "one wave" : `${waves.length} waves`} that promote ${component.componentId} without overclaiming maturity.`,
|
|
1876
|
+
openQuestions,
|
|
1877
|
+
waves,
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
function extractJsonPayload(text) {
|
|
1882
|
+
const normalized = String(text || "").trim();
|
|
1883
|
+
if (!normalized) {
|
|
1884
|
+
throw new Error("Planner returned empty output");
|
|
1885
|
+
}
|
|
1886
|
+
const withoutFence = normalized
|
|
1887
|
+
.replace(/^```(?:json)?\s*/i, "")
|
|
1888
|
+
.replace(/\s*```$/, "")
|
|
1889
|
+
.trim();
|
|
1890
|
+
try {
|
|
1891
|
+
return JSON.parse(withoutFence);
|
|
1892
|
+
} catch {}
|
|
1893
|
+
const firstBrace = withoutFence.indexOf("{");
|
|
1894
|
+
const lastBrace = withoutFence.lastIndexOf("}");
|
|
1895
|
+
if (firstBrace < 0 || lastBrace <= firstBrace) {
|
|
1896
|
+
throw new Error("Planner output did not contain a JSON object");
|
|
1897
|
+
}
|
|
1898
|
+
return JSON.parse(withoutFence.slice(firstBrace, lastBrace + 1));
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function cloneJson(value) {
|
|
1902
|
+
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
function normalizePlannerProofArtifacts(rawArtifacts) {
|
|
1906
|
+
return (Array.isArray(rawArtifacts) ? rawArtifacts : []).map((artifact, index) => {
|
|
1907
|
+
if (typeof artifact === "string") {
|
|
1908
|
+
return {
|
|
1909
|
+
path: normalizeRepoRelativePath(artifact, `proofArtifacts[${index}]`),
|
|
1910
|
+
kind: null,
|
|
1911
|
+
requiredFor: [],
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
return {
|
|
1915
|
+
path: normalizeRepoRelativePath(artifact?.path, `proofArtifacts[${index}].path`),
|
|
1916
|
+
kind: cleanText(artifact?.kind) || null,
|
|
1917
|
+
requiredFor: uniqueStrings(
|
|
1918
|
+
(Array.isArray(artifact?.requiredFor) ? artifact.requiredFor : [])
|
|
1919
|
+
.map((level) => cleanText(level))
|
|
1920
|
+
.filter((level) => COMPONENT_MATURITY_ORDER[level] !== undefined),
|
|
1921
|
+
),
|
|
1922
|
+
};
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function normalizePlannerContext7Bundle(bundle, bundleIndex) {
|
|
1927
|
+
const normalized = cleanText(bundle) || "none";
|
|
1928
|
+
if (!bundleIndex?.bundles?.[normalized]) {
|
|
1929
|
+
throw new Error(`Unknown planner Context7 bundle: ${normalized}`);
|
|
1930
|
+
}
|
|
1931
|
+
return normalized;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function normalizePlannerWorkerAgent(rawAgent, context) {
|
|
1935
|
+
const agentId = cleanText(rawAgent?.agentId) || `A${context.index + 1}`;
|
|
1936
|
+
const roleKind = [
|
|
1937
|
+
"implementation",
|
|
1938
|
+
"qa",
|
|
1939
|
+
"infra",
|
|
1940
|
+
"deploy",
|
|
1941
|
+
"research",
|
|
1942
|
+
"security",
|
|
1943
|
+
].includes(cleanText(rawAgent?.roleKind))
|
|
1944
|
+
? cleanText(rawAgent.roleKind)
|
|
1945
|
+
: "implementation";
|
|
1946
|
+
const ownedPaths = ensureRepoRelativePathList(
|
|
1947
|
+
Array.isArray(rawAgent?.ownedPaths) ? rawAgent.ownedPaths : [],
|
|
1948
|
+
`${agentId}.ownedPaths`,
|
|
1949
|
+
);
|
|
1950
|
+
if (ownedPaths.length === 0) {
|
|
1951
|
+
throw new Error(`Planner worker ${agentId} must declare ownedPaths`);
|
|
1952
|
+
}
|
|
1953
|
+
const deliverables = ensureRepoRelativePathList(
|
|
1954
|
+
Array.isArray(rawAgent?.deliverables) && rawAgent.deliverables.length > 0
|
|
1955
|
+
? rawAgent.deliverables
|
|
1956
|
+
: ownedPaths.filter((entry) => !cleanText(entry).endsWith("/")),
|
|
1957
|
+
`${agentId}.deliverables`,
|
|
1958
|
+
).filter((entry) => !cleanText(entry).endsWith("/"));
|
|
1959
|
+
const explicitComponents =
|
|
1960
|
+
rawAgent && Object.prototype.hasOwnProperty.call(rawAgent, "components");
|
|
1961
|
+
const components = uniqueStrings(
|
|
1962
|
+
(
|
|
1963
|
+
explicitComponents
|
|
1964
|
+
? Array.isArray(rawAgent?.components)
|
|
1965
|
+
? rawAgent.components
|
|
1966
|
+
: []
|
|
1967
|
+
: context.componentIds
|
|
1968
|
+
).map((componentId) => normalizeComponentId(componentId, `${agentId}.components`)),
|
|
1969
|
+
);
|
|
1970
|
+
const context7Bundle = normalizePlannerContext7Bundle(
|
|
1971
|
+
rawAgent?.context7Bundle || context.waveContext7Bundle || "none",
|
|
1972
|
+
context.bundleIndex,
|
|
1973
|
+
);
|
|
1974
|
+
const exitDefaults = defaultExitContract(roleKind);
|
|
1975
|
+
const rawExitContract = rawAgent?.exitContract || exitDefaults;
|
|
1976
|
+
const exitContract = rawExitContract
|
|
1977
|
+
? {
|
|
1978
|
+
completion:
|
|
1979
|
+
EXIT_CONTRACT_COMPLETION_VALUES.includes(cleanText(rawExitContract.completion))
|
|
1980
|
+
? cleanText(rawExitContract.completion)
|
|
1981
|
+
: exitDefaults?.completion,
|
|
1982
|
+
durability:
|
|
1983
|
+
EXIT_CONTRACT_DURABILITY_VALUES.includes(cleanText(rawExitContract.durability))
|
|
1984
|
+
? cleanText(rawExitContract.durability)
|
|
1985
|
+
: exitDefaults?.durability,
|
|
1986
|
+
proof:
|
|
1987
|
+
EXIT_CONTRACT_PROOF_VALUES.includes(cleanText(rawExitContract.proof))
|
|
1988
|
+
? cleanText(rawExitContract.proof)
|
|
1989
|
+
: exitDefaults?.proof,
|
|
1990
|
+
docImpact:
|
|
1991
|
+
EXIT_CONTRACT_DOC_IMPACT_VALUES.includes(cleanText(rawExitContract.docImpact))
|
|
1992
|
+
? cleanText(rawExitContract.docImpact)
|
|
1993
|
+
: exitDefaults?.docImpact,
|
|
1994
|
+
}
|
|
1995
|
+
: null;
|
|
1996
|
+
return {
|
|
1997
|
+
agentId,
|
|
1998
|
+
title:
|
|
1999
|
+
cleanText(rawAgent?.title) ||
|
|
2000
|
+
buildDefaultPrimaryGoal(context.template, roleKind, agentId).replace(/^Implement and prove the /, ""),
|
|
2001
|
+
roleKind,
|
|
2002
|
+
rolePromptPaths: ensureRepoRelativePathList(
|
|
2003
|
+
Array.isArray(rawAgent?.rolePromptPaths) ? rawAgent.rolePromptPaths : [],
|
|
2004
|
+
`${agentId}.rolePromptPaths`,
|
|
2005
|
+
),
|
|
2006
|
+
executorProfile: cleanText(rawAgent?.executorProfile) || null,
|
|
2007
|
+
executor:
|
|
2008
|
+
rawAgent?.executor && typeof rawAgent.executor === "object" && !Array.isArray(rawAgent.executor)
|
|
2009
|
+
? cloneJson(rawAgent.executor)
|
|
2010
|
+
: rawAgent?.executorProfile
|
|
2011
|
+
? { profile: cleanText(rawAgent.executorProfile) }
|
|
2012
|
+
: { profile: defaultExecutorProfile(roleKind) },
|
|
2013
|
+
ownedPaths,
|
|
2014
|
+
deliverables,
|
|
2015
|
+
proofArtifacts: normalizePlannerProofArtifacts(rawAgent?.proofArtifacts),
|
|
2016
|
+
components,
|
|
2017
|
+
capabilities: uniqueStrings(rawAgent?.capabilities || []),
|
|
2018
|
+
skills: uniqueStrings(rawAgent?.skills || []),
|
|
2019
|
+
additionalContext: ensureRepoRelativePathList(
|
|
2020
|
+
Array.isArray(rawAgent?.additionalContext) && rawAgent.additionalContext.length > 0
|
|
2021
|
+
? rawAgent.additionalContext
|
|
2022
|
+
: ["docs/plans/current-state.md"],
|
|
2023
|
+
`${agentId}.additionalContext`,
|
|
2024
|
+
),
|
|
2025
|
+
earlierWaveOutputs: ensureRepoRelativePathList(
|
|
2026
|
+
Array.isArray(rawAgent?.earlierWaveOutputs) ? rawAgent.earlierWaveOutputs : [],
|
|
2027
|
+
`${agentId}.earlierWaveOutputs`,
|
|
2028
|
+
),
|
|
2029
|
+
requirements: uniqueStrings(rawAgent?.requirements || []),
|
|
2030
|
+
validationCommand:
|
|
2031
|
+
cleanText(rawAgent?.validationCommand) ||
|
|
2032
|
+
buildDefaultValidationCommand(context.template, roleKind),
|
|
2033
|
+
outputSummary:
|
|
2034
|
+
cleanText(rawAgent?.outputSummary) ||
|
|
2035
|
+
buildDefaultOutputSummary(context.template, roleKind),
|
|
2036
|
+
primaryGoal:
|
|
2037
|
+
cleanText(rawAgent?.primaryGoal) ||
|
|
2038
|
+
buildDefaultPrimaryGoal(context.template, roleKind, cleanText(rawAgent?.title) || agentId),
|
|
2039
|
+
deployEnvironmentId: cleanText(rawAgent?.deployEnvironmentId) || null,
|
|
2040
|
+
context7Bundle,
|
|
2041
|
+
context7Query: cleanText(rawAgent?.context7Query) || context.waveContext7Query || "",
|
|
2042
|
+
exitContract:
|
|
2043
|
+
exitContract && Object.values(exitContract).every(Boolean)
|
|
2044
|
+
? exitContract
|
|
2045
|
+
: exitDefaults,
|
|
2046
|
+
collaborationNotes: uniqueStrings(rawAgent?.collaborationNotes || []),
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
function normalizePlannerWavePlan(rawWave, context) {
|
|
2051
|
+
const waveNumber = Number.isFinite(rawWave?.wave)
|
|
2052
|
+
? Number.parseInt(String(rawWave.wave), 10)
|
|
2053
|
+
: context.waveNumber;
|
|
2054
|
+
const template = normalizeDraftTemplate(rawWave?.template || "implementation");
|
|
2055
|
+
const context7Defaults =
|
|
2056
|
+
rawWave?.context7Defaults && typeof rawWave.context7Defaults === "object"
|
|
2057
|
+
? rawWave.context7Defaults
|
|
2058
|
+
: {};
|
|
2059
|
+
const waveContext7Bundle = normalizePlannerContext7Bundle(
|
|
2060
|
+
context7Defaults.bundle ||
|
|
2061
|
+
(context.bundleIndex?.bundles?.[DEFAULT_GENERATED_WAVE_CONTEXT7_BUNDLE]
|
|
2062
|
+
? DEFAULT_GENERATED_WAVE_CONTEXT7_BUNDLE
|
|
2063
|
+
: "none"),
|
|
2064
|
+
context.bundleIndex,
|
|
2065
|
+
);
|
|
2066
|
+
const componentCatalog = (Array.isArray(rawWave?.componentCatalog) ? rawWave.componentCatalog : [])
|
|
2067
|
+
.map((entry, index) => {
|
|
2068
|
+
const componentId = normalizeGeneratedComponentId(
|
|
2069
|
+
entry?.componentId || `component-${waveNumber}-${index + 1}`,
|
|
2070
|
+
);
|
|
2071
|
+
const matrixEntry = context.matrix.components?.[componentId] || null;
|
|
2072
|
+
const currentLevel = cleanText(entry?.currentLevel) || matrixEntry?.currentLevel || "inventoried";
|
|
2073
|
+
const targetLevel = cleanText(entry?.targetLevel) || currentLevel || "repo-landed";
|
|
2074
|
+
if (COMPONENT_MATURITY_ORDER[currentLevel] === undefined) {
|
|
2075
|
+
throw new Error(`Unknown currentLevel "${currentLevel}" for component ${componentId}`);
|
|
2076
|
+
}
|
|
2077
|
+
if (COMPONENT_MATURITY_ORDER[targetLevel] === undefined) {
|
|
2078
|
+
throw new Error(`Unknown targetLevel "${targetLevel}" for component ${componentId}`);
|
|
2079
|
+
}
|
|
2080
|
+
return {
|
|
2081
|
+
componentId,
|
|
2082
|
+
title: cleanText(entry?.title) || matrixEntry?.title || humanizeComponentId(componentId),
|
|
2083
|
+
currentLevel,
|
|
2084
|
+
targetLevel,
|
|
2085
|
+
canonicalDocs: ensureRepoRelativePathList(
|
|
2086
|
+
Array.isArray(entry?.canonicalDocs) && entry.canonicalDocs.length > 0
|
|
2087
|
+
? entry.canonicalDocs
|
|
2088
|
+
: matrixEntry?.canonicalDocs || ["docs/roadmap.md"],
|
|
2089
|
+
`${componentId}.canonicalDocs`,
|
|
2090
|
+
),
|
|
2091
|
+
proofSurfaces: uniqueStrings(
|
|
2092
|
+
Array.isArray(entry?.proofSurfaces) && entry.proofSurfaces.length > 0
|
|
2093
|
+
? entry.proofSurfaces
|
|
2094
|
+
: matrixEntry?.proofSurfaces || ["tests", "docs"],
|
|
2095
|
+
),
|
|
2096
|
+
};
|
|
2097
|
+
});
|
|
2098
|
+
if (componentCatalog.length === 0) {
|
|
2099
|
+
throw new Error(`Planner wave ${waveNumber} must declare componentCatalog`);
|
|
2100
|
+
}
|
|
2101
|
+
const componentIds = componentCatalog.map((entry) => entry.componentId);
|
|
2102
|
+
const workerAgents = (Array.isArray(rawWave?.workerAgents) ? rawWave.workerAgents : []).map(
|
|
2103
|
+
(workerAgent, index) =>
|
|
2104
|
+
normalizePlannerWorkerAgent(workerAgent, {
|
|
2105
|
+
index,
|
|
2106
|
+
componentIds,
|
|
2107
|
+
bundleIndex: context.bundleIndex,
|
|
2108
|
+
waveContext7Bundle,
|
|
2109
|
+
waveContext7Query: cleanText(context7Defaults.query),
|
|
2110
|
+
template,
|
|
2111
|
+
}),
|
|
2112
|
+
);
|
|
2113
|
+
if (workerAgents.length === 0) {
|
|
2114
|
+
throw new Error(`Planner wave ${waveNumber} must declare at least one worker agent`);
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
wave: waveNumber,
|
|
2118
|
+
lane: context.lane,
|
|
2119
|
+
template,
|
|
2120
|
+
title: cleanText(rawWave?.title) || `Wave ${waveNumber} Planned Slice`,
|
|
2121
|
+
commitMessage: cleanText(rawWave?.commitMessage) || `Feat: plan wave ${waveNumber}`,
|
|
2122
|
+
sequencingNote: cleanText(rawWave?.sequencingNote) || null,
|
|
2123
|
+
referenceRule: cleanText(rawWave?.referenceRule) || null,
|
|
2124
|
+
oversightMode: normalizeOversightMode(rawWave?.oversightMode || "oversight"),
|
|
2125
|
+
context7Bundle: waveContext7Bundle,
|
|
2126
|
+
context7Query: cleanText(context7Defaults.query) || "",
|
|
2127
|
+
standardRoles: {
|
|
2128
|
+
contQa: rawWave?.standardRoles?.contQa !== false,
|
|
2129
|
+
contEval:
|
|
2130
|
+
rawWave?.standardRoles?.contEval === true ||
|
|
2131
|
+
(Array.isArray(rawWave?.evalTargets) && rawWave.evalTargets.length > 0),
|
|
2132
|
+
integration: rawWave?.standardRoles?.integration !== false,
|
|
2133
|
+
documentation: rawWave?.standardRoles?.documentation !== false,
|
|
2134
|
+
},
|
|
2135
|
+
evalTargets: Array.isArray(rawWave?.evalTargets) ? cloneJson(rawWave.evalTargets) : [],
|
|
2136
|
+
componentPromotions: componentCatalog.map((entry) => ({
|
|
2137
|
+
componentId: entry.componentId,
|
|
2138
|
+
targetLevel: entry.targetLevel,
|
|
2139
|
+
})),
|
|
2140
|
+
componentCatalog,
|
|
2141
|
+
workerAgents,
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function normalizePlannerPlanPayload(rawPlan, context) {
|
|
2146
|
+
const payload = rawPlan && typeof rawPlan === "object" && !Array.isArray(rawPlan) ? rawPlan : {};
|
|
2147
|
+
const rawWaves = Array.isArray(payload.waves) ? payload.waves.slice(0, context.maxWaves) : [];
|
|
2148
|
+
if (rawWaves.length === 0) {
|
|
2149
|
+
throw new Error("Planner output must include at least one wave");
|
|
2150
|
+
}
|
|
2151
|
+
const waves = rawWaves.map((rawWave, index) =>
|
|
2152
|
+
normalizePlannerWavePlan(rawWave, {
|
|
2153
|
+
...context,
|
|
2154
|
+
waveNumber: context.fromWave + index,
|
|
2155
|
+
}),
|
|
2156
|
+
);
|
|
2157
|
+
return {
|
|
2158
|
+
summary: cleanText(payload.summary) || `Drafted ${waves.length} candidate waves.`,
|
|
2159
|
+
openQuestions: uniqueStrings(payload.openQuestions || []),
|
|
2160
|
+
waveOrder: waves.map((wave) => wave.wave),
|
|
2161
|
+
waves,
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
function plannerHonestStepIndex(level) {
|
|
2166
|
+
const normalized = cleanText(level);
|
|
2167
|
+
if (["inventoried", "contract-frozen", "repo-landed", "baseline-proved"].includes(normalized)) {
|
|
2168
|
+
return 0;
|
|
2169
|
+
}
|
|
2170
|
+
if (normalized === "pilot-live") {
|
|
2171
|
+
return 1;
|
|
2172
|
+
}
|
|
2173
|
+
if (normalized === "qa-proved") {
|
|
2174
|
+
return 2;
|
|
2175
|
+
}
|
|
2176
|
+
if (normalized === "fleet-ready") {
|
|
2177
|
+
return 3;
|
|
2178
|
+
}
|
|
2179
|
+
if (normalized === "cutover-ready") {
|
|
2180
|
+
return 4;
|
|
2181
|
+
}
|
|
2182
|
+
if (normalized === "deprecation-ready") {
|
|
2183
|
+
return 5;
|
|
2184
|
+
}
|
|
2185
|
+
return 0;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
function plannerPathOwnershipOverlaps(leftPath, rightPath) {
|
|
2189
|
+
const left = cleanText(leftPath);
|
|
2190
|
+
const right = cleanText(rightPath);
|
|
2191
|
+
if (!left || !right) {
|
|
2192
|
+
return false;
|
|
2193
|
+
}
|
|
2194
|
+
if (left === right) {
|
|
2195
|
+
return true;
|
|
2196
|
+
}
|
|
2197
|
+
if (left.endsWith("/")) {
|
|
2198
|
+
return right.startsWith(left);
|
|
2199
|
+
}
|
|
2200
|
+
if (right.endsWith("/")) {
|
|
2201
|
+
return left.startsWith(right);
|
|
2202
|
+
}
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function collectNonClosureAgents(spec, lanePaths) {
|
|
2207
|
+
const reservedAgentIds = new Set([
|
|
2208
|
+
lanePaths.contQaAgentId,
|
|
2209
|
+
lanePaths.contEvalAgentId,
|
|
2210
|
+
lanePaths.integrationAgentId,
|
|
2211
|
+
lanePaths.documentationAgentId,
|
|
2212
|
+
]);
|
|
2213
|
+
return (Array.isArray(spec.agents) ? spec.agents : []).filter(
|
|
2214
|
+
(agent) => !reservedAgentIds.has(agent.agentId),
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
function validateAgenticPlanArtifacts({ waveArtifacts, matrix, lanePaths, bundleIndex }) {
|
|
2219
|
+
const errors = [];
|
|
2220
|
+
const warnings = [];
|
|
2221
|
+
const projectedLevels = Object.fromEntries(
|
|
2222
|
+
Object.entries(matrix.components || {}).map(([componentId, component]) => [
|
|
2223
|
+
componentId,
|
|
2224
|
+
component.currentLevel || "inventoried",
|
|
2225
|
+
]),
|
|
2226
|
+
);
|
|
2227
|
+
for (const artifact of waveArtifacts) {
|
|
2228
|
+
const spec = artifact.spec;
|
|
2229
|
+
const workerAgents = collectNonClosureAgents(spec, lanePaths);
|
|
2230
|
+
for (const promotion of spec.componentPromotions || []) {
|
|
2231
|
+
const previousLevel = projectedLevels[promotion.componentId] || "inventoried";
|
|
2232
|
+
if (componentMaturityIndex(promotion.targetLevel) < componentMaturityIndex(previousLevel)) {
|
|
2233
|
+
errors.push(
|
|
2234
|
+
`Wave ${spec.wave} regresses ${promotion.componentId} from ${previousLevel} to ${promotion.targetLevel}.`,
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
if (
|
|
2238
|
+
plannerHonestStepIndex(promotion.targetLevel) - plannerHonestStepIndex(previousLevel) >
|
|
2239
|
+
1
|
|
2240
|
+
) {
|
|
2241
|
+
errors.push(
|
|
2242
|
+
`Wave ${spec.wave} overclaims ${promotion.componentId} by jumping from ${previousLevel} to ${promotion.targetLevel}.`,
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
projectedLevels[promotion.componentId] = promotion.targetLevel;
|
|
2246
|
+
}
|
|
2247
|
+
for (const agent of workerAgents) {
|
|
2248
|
+
if (!Array.isArray(agent.deliverables) || agent.deliverables.length === 0) {
|
|
2249
|
+
errors.push(`Wave ${spec.wave} agent ${agent.agentId} must declare at least one deliverable.`);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
for (let index = 0; index < workerAgents.length; index += 1) {
|
|
2253
|
+
for (let peerIndex = index + 1; peerIndex < workerAgents.length; peerIndex += 1) {
|
|
2254
|
+
const left = workerAgents[index];
|
|
2255
|
+
const right = workerAgents[peerIndex];
|
|
2256
|
+
if (
|
|
2257
|
+
(left.ownedPaths || []).some((leftPath) =>
|
|
2258
|
+
(right.ownedPaths || []).some((rightPath) =>
|
|
2259
|
+
plannerPathOwnershipOverlaps(leftPath, rightPath),
|
|
2260
|
+
),
|
|
2261
|
+
)
|
|
2262
|
+
) {
|
|
2263
|
+
errors.push(
|
|
2264
|
+
`Wave ${spec.wave} has overlapping ownership between ${left.agentId} and ${right.agentId}.`,
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const proofCentricWave = (spec.componentPromotions || []).some((promotion) =>
|
|
2270
|
+
isProofCentricLevel(promotion.targetLevel),
|
|
2271
|
+
);
|
|
2272
|
+
if (proofCentricWave) {
|
|
2273
|
+
const proofOwners = workerAgents.filter(
|
|
2274
|
+
(agent) =>
|
|
2275
|
+
Array.isArray(agent.proofArtifacts) &&
|
|
2276
|
+
agent.proofArtifacts.length > 0,
|
|
2277
|
+
);
|
|
2278
|
+
if (proofOwners.length === 0) {
|
|
2279
|
+
errors.push(`Wave ${spec.wave} targets pilot-live or above but has no live-proof owner.`);
|
|
2280
|
+
}
|
|
2281
|
+
const hasRunbook = proofOwners.some((agent) =>
|
|
2282
|
+
[...(agent.deliverables || []), ...(agent.ownedPaths || [])].some((entry) =>
|
|
2283
|
+
cleanText(entry).startsWith(`${LIVE_PROOF_OPERATIONS_DIR}/`),
|
|
2284
|
+
),
|
|
2285
|
+
);
|
|
2286
|
+
if (!hasRunbook) {
|
|
2287
|
+
errors.push(
|
|
2288
|
+
`Wave ${spec.wave} targets pilot-live or above but no proof owner owns a runbook under ${LIVE_PROOF_OPERATIONS_DIR}/.`,
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
const hasTmpBundle = proofOwners.some((agent) =>
|
|
2292
|
+
(agent.proofArtifacts || []).some((artifact) => cleanText(artifact.path).startsWith(".tmp/")),
|
|
2293
|
+
);
|
|
2294
|
+
if (!hasTmpBundle) {
|
|
2295
|
+
errors.push(
|
|
2296
|
+
`Wave ${spec.wave} targets pilot-live or above but no proof owner writes a .tmp/ proof bundle.`,
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2299
|
+
const hasRollbackOrRestart = proofOwners.some((agent) =>
|
|
2300
|
+
(agent.proofArtifacts || []).some((artifact) =>
|
|
2301
|
+
/rollback|restart/i.test(`${artifact.kind || ""} ${artifact.path || ""}`),
|
|
2302
|
+
),
|
|
2303
|
+
);
|
|
2304
|
+
if (!hasRollbackOrRestart) {
|
|
2305
|
+
errors.push(
|
|
2306
|
+
`Wave ${spec.wave} targets pilot-live or above but no proof artifact captures rollback or restart evidence.`,
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
const documentationAgent = (spec.agents || []).find(
|
|
2311
|
+
(agent) => agent.agentId === lanePaths.documentationAgentId,
|
|
2312
|
+
);
|
|
2313
|
+
if (!documentationAgent) {
|
|
2314
|
+
errors.push(`Wave ${spec.wave} is missing ${lanePaths.documentationAgentId}.`);
|
|
2315
|
+
} else {
|
|
2316
|
+
const requiredDocs = requiredDocumentationStewardPathsForWave(spec.wave, {
|
|
2317
|
+
laneProfile: lanePaths.laneProfile,
|
|
2318
|
+
});
|
|
2319
|
+
for (const requiredDoc of requiredDocs) {
|
|
2320
|
+
if (!(documentationAgent.deliverables || []).includes(requiredDoc)) {
|
|
2321
|
+
errors.push(
|
|
2322
|
+
`Wave ${spec.wave} documentation closure is missing required deliverable ${requiredDoc}.`,
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
for (const agent of spec.agents || []) {
|
|
2328
|
+
try {
|
|
2329
|
+
normalizePlannerContext7Bundle(agent.context7?.bundle || "none", bundleIndex);
|
|
2330
|
+
} catch (error) {
|
|
2331
|
+
errors.push(`Wave ${spec.wave} agent ${agent.agentId}: ${error.message}`);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
if (!(spec.agents || []).some((agent) => agent.agentId === lanePaths.integrationAgentId)) {
|
|
2335
|
+
errors.push(`Wave ${spec.wave} is missing ${lanePaths.integrationAgentId}.`);
|
|
2336
|
+
}
|
|
2337
|
+
if (!(spec.agents || []).some((agent) => agent.agentId === lanePaths.contQaAgentId)) {
|
|
2338
|
+
errors.push(`Wave ${spec.wave} is missing ${lanePaths.contQaAgentId}.`);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
return {
|
|
2342
|
+
ok: errors.length === 0,
|
|
2343
|
+
errors,
|
|
2344
|
+
warnings,
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
function writePlannerCandidateArtifacts({ plan, config, lanePaths, profile, matrix, runPaths }) {
|
|
2349
|
+
ensurePlannerRunDirectories(runPaths);
|
|
2350
|
+
let projectedMatrix = matrix;
|
|
2351
|
+
const waveArtifacts = [];
|
|
2352
|
+
for (const draftValues of plan.waves) {
|
|
2353
|
+
const spec = buildSpecPayload({
|
|
2354
|
+
config,
|
|
2355
|
+
lanePaths,
|
|
2356
|
+
profile,
|
|
2357
|
+
draftValues,
|
|
2358
|
+
});
|
|
2359
|
+
const markdown = renderWaveMarkdown(spec, lanePaths);
|
|
2360
|
+
const wavePath = path.join(runPaths.candidateWavesDir, `wave-${spec.wave}.md`);
|
|
2361
|
+
const specPath = path.join(runPaths.candidateSpecsDir, `wave-${spec.wave}.json`);
|
|
2362
|
+
writeJsonAtomic(specPath, spec);
|
|
2363
|
+
writeTextAtomic(wavePath, `${markdown}\n`);
|
|
2364
|
+
projectedMatrix = upsertComponentMatrix(projectedMatrix, spec);
|
|
2365
|
+
waveArtifacts.push({
|
|
2366
|
+
wave: spec.wave,
|
|
2367
|
+
wavePath,
|
|
2368
|
+
specPath,
|
|
2369
|
+
markdown,
|
|
2370
|
+
spec,
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
writeJsonAtomic(runPaths.previewMatrixJsonPath, projectedMatrix);
|
|
2374
|
+
writeTextAtomic(runPaths.previewMatrixDocPath, `${renderComponentMatrixMarkdown(projectedMatrix)}\n`);
|
|
2375
|
+
const candidateLaneProfile = {
|
|
2376
|
+
...lanePaths.laneProfile,
|
|
2377
|
+
paths: {
|
|
2378
|
+
...lanePaths.laneProfile.paths,
|
|
2379
|
+
componentCutoverMatrixJsonPath: repoRelativePath(runPaths.previewMatrixJsonPath),
|
|
2380
|
+
componentCutoverMatrixDocPath: repoRelativePath(runPaths.previewMatrixDocPath),
|
|
2381
|
+
},
|
|
2382
|
+
validation: {
|
|
2383
|
+
...lanePaths.laneProfile.validation,
|
|
2384
|
+
requireDocumentationStewardFromWave: null,
|
|
2385
|
+
requireComponentPromotionsFromWave: null,
|
|
2386
|
+
requireAgentComponentsFromWave: null,
|
|
2387
|
+
},
|
|
2388
|
+
};
|
|
2389
|
+
for (const artifact of waveArtifacts) {
|
|
2390
|
+
const parsedWave = parseWaveFile(artifact.wavePath, { laneProfile: candidateLaneProfile });
|
|
2391
|
+
validateWaveDefinition(
|
|
2392
|
+
applyExecutorSelectionsToWave(parsedWave, { laneProfile: candidateLaneProfile }),
|
|
2393
|
+
{ laneProfile: candidateLaneProfile },
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
return {
|
|
2397
|
+
waveArtifacts,
|
|
2398
|
+
previewMatrix: projectedMatrix,
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function buildPlannerVerificationPayload({ plan, verification, waveArtifacts, runPaths }) {
|
|
2403
|
+
return {
|
|
2404
|
+
schemaVersion: AGENTIC_PLANNER_SCHEMA_VERSION,
|
|
2405
|
+
generatedAt: new Date().toISOString(),
|
|
2406
|
+
ok: verification.ok,
|
|
2407
|
+
summary: plan.summary,
|
|
2408
|
+
waveOrder: plan.waveOrder,
|
|
2409
|
+
errors: verification.errors,
|
|
2410
|
+
warnings: verification.warnings,
|
|
2411
|
+
candidate: {
|
|
2412
|
+
previewMatrixJsonPath: repoRelativePath(runPaths.previewMatrixJsonPath),
|
|
2413
|
+
previewMatrixDocPath: repoRelativePath(runPaths.previewMatrixDocPath),
|
|
2414
|
+
waves: waveArtifacts.map((artifact) => ({
|
|
2415
|
+
wave: artifact.wave,
|
|
2416
|
+
markdownPath: repoRelativePath(artifact.wavePath),
|
|
2417
|
+
specPath: repoRelativePath(artifact.specPath),
|
|
2418
|
+
})),
|
|
2419
|
+
},
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
function resolvePlannerExecutorProfile(lanePaths, plannerExecutorProfile) {
|
|
2424
|
+
const profileName =
|
|
2425
|
+
cleanText(plannerExecutorProfile) || DEFAULT_PLANNER_AGENTIC_EXECUTOR_PROFILE;
|
|
2426
|
+
const profile = lanePaths.executors?.profiles?.[profileName];
|
|
2427
|
+
if (!profile) {
|
|
2428
|
+
throw new Error(`Unknown planner executor profile: ${profileName}`);
|
|
2429
|
+
}
|
|
2430
|
+
return {
|
|
2431
|
+
profileName,
|
|
2432
|
+
profile,
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
function resolvePlannerFixturePayload() {
|
|
2437
|
+
const inlinePayload =
|
|
2438
|
+
process.env.WAVE_PLANNER_AGENTIC_RESPONSE_JSON || process.env.WAVE_PLANNER_AGENTIC_RESPONSE;
|
|
2439
|
+
if (cleanText(inlinePayload)) {
|
|
2440
|
+
return {
|
|
2441
|
+
source: "fixture-inline",
|
|
2442
|
+
rawPayload: extractJsonPayload(inlinePayload),
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
const fixturePath = cleanText(process.env.WAVE_PLANNER_AGENTIC_RESPONSE_FILE);
|
|
2446
|
+
if (!fixturePath) {
|
|
2447
|
+
return null;
|
|
2448
|
+
}
|
|
2449
|
+
const absolutePath = path.isAbsolute(fixturePath)
|
|
2450
|
+
? fixturePath
|
|
2451
|
+
: path.resolve(REPO_ROOT, fixturePath);
|
|
2452
|
+
return {
|
|
2453
|
+
source: "fixture-file",
|
|
2454
|
+
rawPayload: extractJsonPayload(fs.readFileSync(absolutePath, "utf8")),
|
|
2455
|
+
fixturePath: absolutePath,
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function runPlannerCodexExecutor({ lanePaths, plannerExecutorProfile, promptText, runPaths }) {
|
|
2460
|
+
const { profileName, profile } = resolvePlannerExecutorProfile(lanePaths, plannerExecutorProfile);
|
|
2461
|
+
const executorId = profile.id || lanePaths.executors?.default || "codex";
|
|
2462
|
+
if (executorId !== "codex") {
|
|
2463
|
+
throw new Error(
|
|
2464
|
+
`Planner executor profile ${profileName} resolves to ${executorId}; agentic draft currently supports codex or fixture input only`,
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
const codexRuntime = {
|
|
2468
|
+
...(lanePaths.executors?.codex || {}),
|
|
2469
|
+
...(profile.codex || {}),
|
|
2470
|
+
};
|
|
2471
|
+
const command = cleanText(codexRuntime.command) || "codex";
|
|
2472
|
+
if (!commandExists(command)) {
|
|
2473
|
+
throw new Error(`Planner executor command is not available: ${command}`);
|
|
2474
|
+
}
|
|
2475
|
+
const args = [
|
|
2476
|
+
"--ask-for-approval",
|
|
2477
|
+
"never",
|
|
2478
|
+
"exec",
|
|
2479
|
+
"--skip-git-repo-check",
|
|
2480
|
+
"--sandbox",
|
|
2481
|
+
codexRuntime.sandbox || "read-only",
|
|
2482
|
+
];
|
|
2483
|
+
if (profile.model) {
|
|
2484
|
+
args.push("--model", profile.model);
|
|
2485
|
+
}
|
|
2486
|
+
if (codexRuntime.profileName) {
|
|
2487
|
+
args.push("--profile", codexRuntime.profileName);
|
|
2488
|
+
}
|
|
2489
|
+
for (const configValue of codexRuntime.config || []) {
|
|
2490
|
+
args.push("-c", configValue);
|
|
2491
|
+
}
|
|
2492
|
+
if (codexRuntime.search) {
|
|
2493
|
+
args.push("--search");
|
|
2494
|
+
}
|
|
2495
|
+
for (const imagePath of codexRuntime.images || []) {
|
|
2496
|
+
args.push("--image", imagePath);
|
|
2497
|
+
}
|
|
2498
|
+
for (const dirPath of codexRuntime.addDirs || []) {
|
|
2499
|
+
args.push("--add-dir", dirPath);
|
|
2500
|
+
}
|
|
2501
|
+
if (codexRuntime.ephemeral) {
|
|
2502
|
+
args.push("--ephemeral");
|
|
2503
|
+
}
|
|
2504
|
+
args.push("-");
|
|
2505
|
+
const timeoutMs = Math.max(
|
|
2506
|
+
60_000,
|
|
2507
|
+
((profile.budget?.minutes || DEFAULT_PLANNER_AGENTIC_MAX_REPLAN_ITERATIONS * 10) * 60_000),
|
|
2508
|
+
);
|
|
2509
|
+
const result = spawnSync(command, args, {
|
|
2510
|
+
cwd: REPO_ROOT,
|
|
2511
|
+
encoding: "utf8",
|
|
2512
|
+
input: promptText,
|
|
2513
|
+
timeout: timeoutMs,
|
|
2514
|
+
});
|
|
2515
|
+
writeTextAtomic(path.join(runPaths.runDir, "planner-executor.stdout.log"), String(result.stdout || ""));
|
|
2516
|
+
writeTextAtomic(path.join(runPaths.runDir, "planner-executor.stderr.log"), String(result.stderr || ""));
|
|
2517
|
+
if (result.error) {
|
|
2518
|
+
throw result.error;
|
|
2519
|
+
}
|
|
2520
|
+
if (result.status !== 0) {
|
|
2521
|
+
throw new Error(
|
|
2522
|
+
`Planner executor exited with status ${result.status}: ${cleanText(result.stderr) || cleanText(result.stdout) || "no output"}`,
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
return {
|
|
2526
|
+
source: "executor-codex",
|
|
2527
|
+
rawPayload: extractJsonPayload(result.stdout),
|
|
2528
|
+
runtime: {
|
|
2529
|
+
profile: profileName,
|
|
2530
|
+
command,
|
|
2531
|
+
timeoutMs,
|
|
2532
|
+
},
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
function loadPlannerRunBundle(runId) {
|
|
2537
|
+
const runPaths = buildPlannerRunPaths(runId);
|
|
2538
|
+
const request = readJsonOrNull(runPaths.requestPath);
|
|
2539
|
+
const sources = readJsonOrNull(runPaths.sourcesPath);
|
|
2540
|
+
const plan = readJsonOrNull(runPaths.planPath);
|
|
2541
|
+
const verification = readJsonOrNull(runPaths.verificationPath);
|
|
2542
|
+
const result = readJsonOrNull(runPaths.resultPath);
|
|
2543
|
+
if (!request || !result) {
|
|
2544
|
+
throw new Error(`Planner run ${runPaths.runId} is missing request.json or result.json`);
|
|
2545
|
+
}
|
|
2546
|
+
return {
|
|
2547
|
+
runPaths,
|
|
2548
|
+
request,
|
|
2549
|
+
sources,
|
|
2550
|
+
plan,
|
|
2551
|
+
verification,
|
|
2552
|
+
result,
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
function parsePlannerWaveSelection(rawValue, waveOrder) {
|
|
2557
|
+
const value = cleanText(rawValue);
|
|
2558
|
+
if (!value || value === "all") {
|
|
2559
|
+
return waveOrder.slice();
|
|
2560
|
+
}
|
|
2561
|
+
return uniqueStrings(value.split(",")).map((entry) => {
|
|
2562
|
+
const parsed = Number.parseInt(entry, 10);
|
|
2563
|
+
if (!Number.isFinite(parsed)) {
|
|
2564
|
+
throw new Error(`Invalid wave selection token: ${entry}`);
|
|
2565
|
+
}
|
|
2566
|
+
return parsed;
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
async function runAgenticDraftFlow(options = {}) {
|
|
2571
|
+
const config = options.config || loadWaveConfig();
|
|
2572
|
+
const profile = await ensureProjectProfile({ config });
|
|
2573
|
+
const lane = options.lane || profile.plannerDefaults.lane || config.defaultLane;
|
|
2574
|
+
const lanePaths = buildLanePaths(lane, { config });
|
|
2575
|
+
const matrix = loadComponentCutoverMatrix({ laneProfile: lanePaths.laneProfile });
|
|
2576
|
+
const bundleIndex = loadContext7BundleIndex(lanePaths.context7BundleIndexPath);
|
|
2577
|
+
const plannerExecutorProfile =
|
|
2578
|
+
cleanText(options.plannerExecutorProfile) ||
|
|
2579
|
+
config.planner?.agentic?.executorProfile ||
|
|
2580
|
+
DEFAULT_PLANNER_AGENTIC_EXECUTOR_PROFILE;
|
|
2581
|
+
const maxWaves =
|
|
2582
|
+
Number.isFinite(options.maxWaves) && options.maxWaves > 0
|
|
2583
|
+
? options.maxWaves
|
|
2584
|
+
: config.planner?.agentic?.defaultMaxWaves || DEFAULT_PLANNER_AGENTIC_MAX_WAVES;
|
|
2585
|
+
const request = buildAgenticPlannerRequest({
|
|
2586
|
+
config,
|
|
2587
|
+
lanePaths,
|
|
2588
|
+
task: options.task,
|
|
2589
|
+
fromWave: options.fromWave,
|
|
2590
|
+
maxWaves,
|
|
2591
|
+
plannerExecutorProfile,
|
|
2592
|
+
});
|
|
2593
|
+
const runPaths = buildPlannerRunPaths(options.runId || buildPlannerRunId());
|
|
2594
|
+
ensurePlannerRunDirectories(runPaths);
|
|
2595
|
+
const sources = collectPlannerSources({
|
|
2596
|
+
config,
|
|
2597
|
+
lanePaths,
|
|
2598
|
+
task: request.task,
|
|
2599
|
+
fromWave: request.fromWave,
|
|
2600
|
+
});
|
|
2601
|
+
const plannerContext7Selection = resolvePlannerContext7Selection({
|
|
2602
|
+
config,
|
|
2603
|
+
lanePaths,
|
|
2604
|
+
bundleIndex,
|
|
2605
|
+
request,
|
|
2606
|
+
});
|
|
2607
|
+
const plannerContext7Prefetch = await prefetchContext7ForSelection(plannerContext7Selection, {
|
|
2608
|
+
cacheDir: lanePaths.context7CacheDir,
|
|
2609
|
+
});
|
|
2610
|
+
const promptText = buildPlannerPromptText({
|
|
2611
|
+
request,
|
|
2612
|
+
sources,
|
|
2613
|
+
profile,
|
|
2614
|
+
bundleIndex,
|
|
2615
|
+
matrix,
|
|
2616
|
+
plannerContext7: {
|
|
2617
|
+
selection: plannerContext7Selection,
|
|
2618
|
+
prefetch: plannerContext7Prefetch,
|
|
2619
|
+
},
|
|
2620
|
+
});
|
|
2621
|
+
writeJsonAtomic(runPaths.requestPath, request);
|
|
2622
|
+
writeJsonAtomic(runPaths.sourcesPath, {
|
|
2623
|
+
schemaVersion: AGENTIC_PLANNER_SCHEMA_VERSION,
|
|
2624
|
+
generatedAt: new Date().toISOString(),
|
|
2625
|
+
sources,
|
|
2626
|
+
plannerContext7: {
|
|
2627
|
+
selection: plannerContext7Selection,
|
|
2628
|
+
prefetch: {
|
|
2629
|
+
mode: plannerContext7Prefetch.mode,
|
|
2630
|
+
warning: plannerContext7Prefetch.warning,
|
|
2631
|
+
snippetHash: plannerContext7Prefetch.snippetHash,
|
|
2632
|
+
},
|
|
2633
|
+
},
|
|
2634
|
+
});
|
|
2635
|
+
writeTextAtomic(runPaths.promptPath, `${promptText}\n`);
|
|
2636
|
+
const attempts = [];
|
|
2637
|
+
const maxReplanIterations =
|
|
2638
|
+
config.planner?.agentic?.maxReplanIterations || DEFAULT_PLANNER_AGENTIC_MAX_REPLAN_ITERATIONS;
|
|
2639
|
+
let plan = null;
|
|
2640
|
+
let verification = null;
|
|
2641
|
+
let waveArtifacts = [];
|
|
2642
|
+
let previewMatrix = null;
|
|
2643
|
+
let plannerSource = null;
|
|
2644
|
+
let plannerRuntime = null;
|
|
2645
|
+
let lastError = null;
|
|
2646
|
+
for (let attempt = 0; attempt <= maxReplanIterations; attempt += 1) {
|
|
2647
|
+
try {
|
|
2648
|
+
const attemptSource =
|
|
2649
|
+
attempt === 0
|
|
2650
|
+
? resolvePlannerFixturePayload() ||
|
|
2651
|
+
runPlannerCodexExecutor({
|
|
2652
|
+
lanePaths,
|
|
2653
|
+
plannerExecutorProfile,
|
|
2654
|
+
promptText,
|
|
2655
|
+
runPaths,
|
|
2656
|
+
})
|
|
2657
|
+
: {
|
|
2658
|
+
source: "heuristic-replan",
|
|
2659
|
+
rawPayload: buildHeuristicPlannerPayload({
|
|
2660
|
+
request,
|
|
2661
|
+
sources,
|
|
2662
|
+
matrix,
|
|
2663
|
+
bundleIndex,
|
|
2664
|
+
}),
|
|
2665
|
+
};
|
|
2666
|
+
plannerSource = attemptSource.source;
|
|
2667
|
+
plannerRuntime = attemptSource.runtime || plannerRuntime;
|
|
2668
|
+
plan = normalizePlannerPlanPayload(attemptSource.rawPayload, {
|
|
2669
|
+
fromWave: request.fromWave,
|
|
2670
|
+
maxWaves: request.maxWaves,
|
|
2671
|
+
lane: lanePaths.lane,
|
|
2672
|
+
matrix,
|
|
2673
|
+
bundleIndex,
|
|
2674
|
+
});
|
|
2675
|
+
const artifactBundle = writePlannerCandidateArtifacts({
|
|
2676
|
+
plan,
|
|
2677
|
+
config,
|
|
2678
|
+
lanePaths,
|
|
2679
|
+
profile,
|
|
2680
|
+
matrix,
|
|
2681
|
+
runPaths,
|
|
2682
|
+
});
|
|
2683
|
+
waveArtifacts = artifactBundle.waveArtifacts;
|
|
2684
|
+
previewMatrix = artifactBundle.previewMatrix;
|
|
2685
|
+
verification = validateAgenticPlanArtifacts({
|
|
2686
|
+
waveArtifacts,
|
|
2687
|
+
matrix,
|
|
2688
|
+
lanePaths,
|
|
2689
|
+
bundleIndex,
|
|
2690
|
+
});
|
|
2691
|
+
attempts.push({
|
|
2692
|
+
attempt,
|
|
2693
|
+
source: plannerSource,
|
|
2694
|
+
ok: verification.ok,
|
|
2695
|
+
errors: verification.errors,
|
|
2696
|
+
});
|
|
2697
|
+
if (verification.ok) {
|
|
2698
|
+
break;
|
|
2699
|
+
}
|
|
2700
|
+
lastError = new Error(verification.errors.join("; "));
|
|
2701
|
+
} catch (error) {
|
|
2702
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2703
|
+
attempts.push({
|
|
2704
|
+
attempt,
|
|
2705
|
+
source: plannerSource || (attempt === 0 ? "executor" : "heuristic-replan"),
|
|
2706
|
+
ok: false,
|
|
2707
|
+
errors: [lastError.message],
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
if (!plan || !verification) {
|
|
2712
|
+
verification = {
|
|
2713
|
+
ok: false,
|
|
2714
|
+
errors: [lastError?.message || "Planner did not produce a usable plan"],
|
|
2715
|
+
warnings: [],
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
writeJsonAtomic(runPaths.planPath, {
|
|
2719
|
+
schemaVersion: AGENTIC_PLANNER_SCHEMA_VERSION,
|
|
2720
|
+
generatedAt: new Date().toISOString(),
|
|
2721
|
+
source: plannerSource || "failed",
|
|
2722
|
+
runtime: plannerRuntime,
|
|
2723
|
+
summary: plan?.summary || null,
|
|
2724
|
+
openQuestions: plan?.openQuestions || [],
|
|
2725
|
+
waveOrder: plan?.waveOrder || [],
|
|
2726
|
+
waves: plan?.waves || [],
|
|
2727
|
+
attempts,
|
|
2728
|
+
});
|
|
2729
|
+
writeJsonAtomic(
|
|
2730
|
+
runPaths.verificationPath,
|
|
2731
|
+
buildPlannerVerificationPayload({
|
|
2732
|
+
plan: plan || { summary: "", waveOrder: [] },
|
|
2733
|
+
verification,
|
|
2734
|
+
waveArtifacts,
|
|
2735
|
+
runPaths,
|
|
2736
|
+
}),
|
|
2737
|
+
);
|
|
2738
|
+
const state = verification.ok ? "planned" : "failed";
|
|
2739
|
+
const resultPayload = {
|
|
2740
|
+
schemaVersion: AGENTIC_PLANNER_SCHEMA_VERSION,
|
|
2741
|
+
runId: runPaths.runId,
|
|
2742
|
+
generatedAt: new Date().toISOString(),
|
|
2743
|
+
state,
|
|
2744
|
+
lane: lanePaths.lane,
|
|
2745
|
+
plannerSource,
|
|
2746
|
+
plannerExecutorProfile,
|
|
2747
|
+
attempts,
|
|
2748
|
+
waveOrder: plan?.waveOrder || [],
|
|
2749
|
+
openQuestions: plan?.openQuestions || [],
|
|
2750
|
+
plannerContext7: {
|
|
2751
|
+
bundleId: plannerContext7Selection.bundleId,
|
|
2752
|
+
query: plannerContext7Selection.query,
|
|
2753
|
+
mode: plannerContext7Prefetch.mode,
|
|
2754
|
+
warning: plannerContext7Prefetch.warning,
|
|
2755
|
+
snippetHash: plannerContext7Prefetch.snippetHash,
|
|
2756
|
+
},
|
|
2757
|
+
paths: {
|
|
2758
|
+
requestPath: repoRelativePath(runPaths.requestPath),
|
|
2759
|
+
sourcesPath: repoRelativePath(runPaths.sourcesPath),
|
|
2760
|
+
promptPath: repoRelativePath(runPaths.promptPath),
|
|
2761
|
+
planPath: repoRelativePath(runPaths.planPath),
|
|
2762
|
+
verificationPath: repoRelativePath(runPaths.verificationPath),
|
|
2763
|
+
resultPath: repoRelativePath(runPaths.resultPath),
|
|
2764
|
+
candidateDir: repoRelativePath(runPaths.candidateDir),
|
|
2765
|
+
previewMatrixJsonPath: repoRelativePath(runPaths.previewMatrixJsonPath),
|
|
2766
|
+
previewMatrixDocPath: repoRelativePath(runPaths.previewMatrixDocPath),
|
|
2767
|
+
},
|
|
2768
|
+
previewMatrixAvailable: Boolean(previewMatrix),
|
|
2769
|
+
};
|
|
2770
|
+
writeJsonAtomic(runPaths.resultPath, resultPayload);
|
|
2771
|
+
updateProjectProfile(
|
|
2772
|
+
(current) => ({
|
|
2773
|
+
...current,
|
|
2774
|
+
plannerDefaults: {
|
|
2775
|
+
...(current.plannerDefaults || {}),
|
|
2776
|
+
lane: lanePaths.lane,
|
|
2777
|
+
},
|
|
2778
|
+
}),
|
|
2779
|
+
{ config },
|
|
2780
|
+
);
|
|
2781
|
+
return resultPayload;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
async function runApplyPlannerRun(options = {}) {
|
|
2785
|
+
const bundle = loadPlannerRunBundle(options.runId);
|
|
2786
|
+
if (!PLANNER_RESULT_STATES.has(cleanText(bundle.result.state))) {
|
|
2787
|
+
throw new Error(`Planner run ${bundle.runPaths.runId} is in an unknown state`);
|
|
2788
|
+
}
|
|
2789
|
+
if (bundle.result.state === "failed" && !options.force) {
|
|
2790
|
+
throw new Error(
|
|
2791
|
+
`Planner run ${bundle.runPaths.runId} failed verification. Re-run with --force only if you intentionally want to materialize it anyway.`,
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
const config = options.config || loadWaveConfig();
|
|
2795
|
+
const lanePaths = buildLanePaths(bundle.request.lane, { config });
|
|
2796
|
+
const selectedWaves = parsePlannerWaveSelection(options.waves, bundle.plan?.waveOrder || []);
|
|
2797
|
+
if (selectedWaves.length === 0) {
|
|
2798
|
+
throw new Error("No waves selected for apply");
|
|
2799
|
+
}
|
|
2800
|
+
const selectedArtifacts = [];
|
|
2801
|
+
let matrix = loadComponentCutoverMatrix({ laneProfile: lanePaths.laneProfile });
|
|
2802
|
+
for (const waveNumber of selectedWaves.toSorted((left, right) => left - right)) {
|
|
2803
|
+
const candidateWavePath = path.join(bundle.runPaths.candidateWavesDir, `wave-${waveNumber}.md`);
|
|
2804
|
+
const candidateSpecPath = path.join(bundle.runPaths.candidateSpecsDir, `wave-${waveNumber}.json`);
|
|
2805
|
+
if (!fs.existsSync(candidateWavePath) || !fs.existsSync(candidateSpecPath)) {
|
|
2806
|
+
throw new Error(`Planner run ${bundle.runPaths.runId} is missing candidate files for wave ${waveNumber}`);
|
|
2807
|
+
}
|
|
2808
|
+
const canonicalPaths = ensureWavePaths(lanePaths, waveNumber);
|
|
2809
|
+
if (
|
|
2810
|
+
!options.force &&
|
|
2811
|
+
(fs.existsSync(canonicalPaths.wavePath) || fs.existsSync(canonicalPaths.specPath))
|
|
2812
|
+
) {
|
|
2813
|
+
throw new Error(
|
|
2814
|
+
`Wave ${waveNumber} already exists. Re-run with --force to overwrite ${repoRelativePath(canonicalPaths.wavePath)} and ${repoRelativePath(canonicalPaths.specPath)}.`,
|
|
2815
|
+
);
|
|
2816
|
+
}
|
|
2817
|
+
const spec = readJsonOrNull(candidateSpecPath);
|
|
2818
|
+
matrix = upsertComponentMatrix(matrix, spec);
|
|
2819
|
+
selectedArtifacts.push({
|
|
2820
|
+
wave: waveNumber,
|
|
2821
|
+
candidateWavePath,
|
|
2822
|
+
candidateSpecPath,
|
|
2823
|
+
canonicalPaths,
|
|
2824
|
+
spec,
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
writeJsonAtomic(path.resolve(REPO_ROOT, lanePaths.componentCutoverMatrixJsonPath), matrix);
|
|
2828
|
+
writeTextAtomic(
|
|
2829
|
+
path.resolve(REPO_ROOT, lanePaths.componentCutoverMatrixDocPath),
|
|
2830
|
+
`${renderComponentMatrixMarkdown(matrix)}\n`,
|
|
2831
|
+
);
|
|
2832
|
+
const applied = [];
|
|
2833
|
+
for (const artifact of selectedArtifacts) {
|
|
2834
|
+
ensureDirectory(path.dirname(artifact.canonicalPaths.specPath));
|
|
2835
|
+
writeTextAtomic(artifact.canonicalPaths.wavePath, fs.readFileSync(artifact.candidateWavePath, "utf8"));
|
|
2836
|
+
writeTextAtomic(artifact.canonicalPaths.specPath, fs.readFileSync(artifact.candidateSpecPath, "utf8"));
|
|
2837
|
+
const parsedWave = parseWaveFile(artifact.canonicalPaths.wavePath, { laneProfile: lanePaths.laneProfile });
|
|
2838
|
+
validateWaveDefinition(
|
|
2839
|
+
applyExecutorSelectionsToWave(parsedWave, { laneProfile: lanePaths.laneProfile }),
|
|
2840
|
+
{ laneProfile: lanePaths.laneProfile },
|
|
2841
|
+
);
|
|
2842
|
+
applied.push({
|
|
2843
|
+
wave: artifact.wave,
|
|
2844
|
+
markdownPath: repoRelativePath(artifact.canonicalPaths.wavePath),
|
|
2845
|
+
specPath: repoRelativePath(artifact.canonicalPaths.specPath),
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
const priorAppliedWaves = new Set(Array.isArray(bundle.result.appliedWaves) ? bundle.result.appliedWaves : []);
|
|
2849
|
+
for (const entry of applied) {
|
|
2850
|
+
priorAppliedWaves.add(entry.wave);
|
|
2851
|
+
}
|
|
2852
|
+
const nextResult = {
|
|
2853
|
+
...bundle.result,
|
|
2854
|
+
state: "applied",
|
|
2855
|
+
appliedAt: new Date().toISOString(),
|
|
2856
|
+
appliedWaves: Array.from(priorAppliedWaves).sort((left, right) => left - right),
|
|
2857
|
+
applied,
|
|
2858
|
+
matrixJsonPath: repoRelativePath(path.resolve(REPO_ROOT, lanePaths.componentCutoverMatrixJsonPath)),
|
|
2859
|
+
matrixDocPath: repoRelativePath(path.resolve(REPO_ROOT, lanePaths.componentCutoverMatrixDocPath)),
|
|
2860
|
+
};
|
|
2861
|
+
writeJsonAtomic(bundle.runPaths.resultPath, nextResult);
|
|
2862
|
+
return nextResult;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
function showPlannerRun(options = {}) {
|
|
2866
|
+
const bundle = loadPlannerRunBundle(options.runId);
|
|
2867
|
+
return {
|
|
2868
|
+
runId: bundle.runPaths.runId,
|
|
2869
|
+
request: bundle.request,
|
|
2870
|
+
sources: bundle.sources,
|
|
2871
|
+
plan: bundle.plan,
|
|
2872
|
+
verification: bundle.verification,
|
|
2873
|
+
result: bundle.result,
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
async function ensureProjectProfile(options = {}) {
|
|
2878
|
+
const config = options.config || loadWaveConfig();
|
|
2879
|
+
const existing = readProjectProfile({ config });
|
|
2880
|
+
if (existing) {
|
|
2881
|
+
return existing;
|
|
2882
|
+
}
|
|
2883
|
+
return runProjectSetupFlow({
|
|
2884
|
+
config,
|
|
2885
|
+
json: false,
|
|
2886
|
+
fromDraft: true,
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
async function runProjectSetupFlow(options = {}) {
|
|
2891
|
+
const config = options.config || loadWaveConfig();
|
|
2892
|
+
const existing = readProjectProfile({ config });
|
|
2893
|
+
const base = existing || buildDefaultProjectProfile(config);
|
|
2894
|
+
const prompt = new PromptSession();
|
|
2895
|
+
try {
|
|
2896
|
+
const laneChoices = Array.from(
|
|
2897
|
+
new Set([config.defaultLane, ...Object.keys(config.lanes || {})].filter(Boolean)),
|
|
2898
|
+
);
|
|
2899
|
+
const newProject = await prompt.askBoolean("Treat this repository as a new project?", base.newProject);
|
|
2900
|
+
const defaultOversightMode = normalizeOversightMode(
|
|
2901
|
+
await prompt.askChoice(
|
|
2902
|
+
"Default execution posture",
|
|
2903
|
+
PROJECT_OVERSIGHT_MODES,
|
|
2904
|
+
base.defaultOversightMode,
|
|
2905
|
+
),
|
|
2906
|
+
);
|
|
2907
|
+
const defaultTerminalSurface = normalizeTerminalSurface(
|
|
2908
|
+
await prompt.askChoice(
|
|
2909
|
+
"Default terminal surface",
|
|
2910
|
+
PROJECT_PROFILE_TERMINAL_SURFACES,
|
|
2911
|
+
base.defaultTerminalSurface,
|
|
2912
|
+
),
|
|
2913
|
+
);
|
|
2914
|
+
const template = normalizeDraftTemplate(
|
|
2915
|
+
await prompt.askChoice(
|
|
2916
|
+
"Default draft template",
|
|
2917
|
+
DRAFT_TEMPLATES,
|
|
2918
|
+
base.plannerDefaults.template,
|
|
2919
|
+
),
|
|
2920
|
+
);
|
|
2921
|
+
const lane = await prompt.askChoice(
|
|
2922
|
+
"Default draft lane",
|
|
2923
|
+
laneChoices,
|
|
2924
|
+
base.plannerDefaults.lane,
|
|
2925
|
+
);
|
|
2926
|
+
const deployEnvironmentCount = await prompt.askInteger(
|
|
2927
|
+
"How many deploy environments should the planner remember?",
|
|
2928
|
+
base.deployEnvironments.length,
|
|
2929
|
+
{ min: 0 },
|
|
2930
|
+
);
|
|
2931
|
+
const deployEnvironments = [];
|
|
2932
|
+
for (let index = 0; index < deployEnvironmentCount; index += 1) {
|
|
2933
|
+
const existingEnvironment = base.deployEnvironments[index] || null;
|
|
2934
|
+
const id = normalizeComponentId(
|
|
2935
|
+
await prompt.ask(
|
|
2936
|
+
`Deploy environment ${index + 1} id`,
|
|
2937
|
+
existingEnvironment?.id || (index === 0 ? "default" : `env-${index + 1}`),
|
|
2938
|
+
),
|
|
2939
|
+
`deploy environment ${index + 1} id`,
|
|
2940
|
+
);
|
|
2941
|
+
const name = cleanText(
|
|
2942
|
+
await prompt.ask(`Deploy environment ${index + 1} name`, existingEnvironment?.name || id),
|
|
2943
|
+
);
|
|
2944
|
+
const kind = await prompt.askChoice(
|
|
2945
|
+
`Deploy environment ${index + 1} provider`,
|
|
2946
|
+
DEPLOY_ENVIRONMENT_KINDS,
|
|
2947
|
+
existingEnvironment?.kind || "custom",
|
|
2948
|
+
);
|
|
2949
|
+
const isDefault = await prompt.askBoolean(
|
|
2950
|
+
`Mark deploy environment ${id} as the default?`,
|
|
2951
|
+
existingEnvironment?.isDefault === true || (index === 0 && base.deployEnvironments.length === 0),
|
|
2952
|
+
);
|
|
2953
|
+
const notes = cleanText(
|
|
2954
|
+
await prompt.ask(
|
|
2955
|
+
`Deploy environment ${id} notes`,
|
|
2956
|
+
existingEnvironment?.notes || "",
|
|
2957
|
+
),
|
|
2958
|
+
);
|
|
2959
|
+
deployEnvironments.push({
|
|
2960
|
+
id,
|
|
2961
|
+
name,
|
|
2962
|
+
kind,
|
|
2963
|
+
isDefault,
|
|
2964
|
+
notes: notes || null,
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
const profile = writeProjectProfile(
|
|
2968
|
+
{
|
|
2969
|
+
...base,
|
|
2970
|
+
newProject,
|
|
2971
|
+
defaultOversightMode,
|
|
2972
|
+
defaultTerminalSurface,
|
|
2973
|
+
deployEnvironments,
|
|
2974
|
+
plannerDefaults: {
|
|
2975
|
+
template,
|
|
2976
|
+
lane,
|
|
2977
|
+
},
|
|
2978
|
+
},
|
|
2979
|
+
{ config },
|
|
2980
|
+
);
|
|
2981
|
+
return profile;
|
|
2982
|
+
} finally {
|
|
2983
|
+
await prompt.close();
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
async function collectComponentPromotions({ prompt, matrix, template, waveNumber }) {
|
|
2988
|
+
const targetLevel = defaultTargetLevel(template);
|
|
2989
|
+
const promotionCount = await prompt.askInteger("How many component promotions belong in this wave?", 1, {
|
|
2990
|
+
min: 0,
|
|
2991
|
+
});
|
|
2992
|
+
const componentPromotions = [];
|
|
2993
|
+
const componentCatalog = [];
|
|
2994
|
+
for (let index = 0; index < promotionCount; index += 1) {
|
|
2995
|
+
const componentId = normalizeComponentId(
|
|
2996
|
+
await prompt.ask(
|
|
2997
|
+
`Promotion ${index + 1} component id`,
|
|
2998
|
+
index === 0 ? "new-component" : `component-${index + 1}`,
|
|
2999
|
+
),
|
|
3000
|
+
`promotion ${index + 1} component id`,
|
|
3001
|
+
);
|
|
3002
|
+
const existing = matrix.components[componentId] || null;
|
|
3003
|
+
const title = cleanText(
|
|
3004
|
+
await prompt.ask(`Component ${componentId} title`, existing?.title || componentId),
|
|
3005
|
+
);
|
|
3006
|
+
const currentLevel = existing?.currentLevel
|
|
3007
|
+
? existing.currentLevel
|
|
969
3008
|
: await prompt.askChoice(
|
|
970
3009
|
`Component ${componentId} current level`,
|
|
971
3010
|
matrix.levels,
|
|
@@ -1004,7 +3043,15 @@ async function collectComponentPromotions({ prompt, matrix, template, waveNumber
|
|
|
1004
3043
|
return { componentPromotions, componentCatalog };
|
|
1005
3044
|
}
|
|
1006
3045
|
|
|
1007
|
-
async function collectWorkerAgents({
|
|
3046
|
+
async function collectWorkerAgents({
|
|
3047
|
+
prompt,
|
|
3048
|
+
template,
|
|
3049
|
+
profile,
|
|
3050
|
+
componentPromotions,
|
|
3051
|
+
waveNumber,
|
|
3052
|
+
lane,
|
|
3053
|
+
context7BundleChoices,
|
|
3054
|
+
}) {
|
|
1008
3055
|
const defaultRoleKind = defaultWorkerRoleKindForTemplate(template);
|
|
1009
3056
|
const workerCount = await prompt.askInteger("How many worker agents should this wave include?", 1, {
|
|
1010
3057
|
min: 1,
|
|
@@ -1112,7 +3159,7 @@ async function collectWorkerAgents({ prompt, template, profile, componentPromoti
|
|
|
1112
3159
|
}
|
|
1113
3160
|
const context7Bundle = await prompt.askChoice(
|
|
1114
3161
|
`Worker ${agentId} Context7 bundle`,
|
|
1115
|
-
|
|
3162
|
+
context7BundleChoices,
|
|
1116
3163
|
"none",
|
|
1117
3164
|
);
|
|
1118
3165
|
const context7Query = cleanText(await prompt.ask(`Worker ${agentId} Context7 query`, ""));
|
|
@@ -1222,6 +3269,10 @@ async function runDraftFlow(options = {}) {
|
|
|
1222
3269
|
const lane = options.lane || profile.plannerDefaults.lane || config.defaultLane;
|
|
1223
3270
|
const lanePaths = buildLanePaths(lane, { config });
|
|
1224
3271
|
const matrix = loadComponentCutoverMatrix({ laneProfile: lanePaths.laneProfile });
|
|
3272
|
+
const bundleIndex = loadContext7BundleIndex(lanePaths.context7BundleIndexPath);
|
|
3273
|
+
const context7BundleChoices = Object.keys(bundleIndex.bundles || {}).sort((left, right) =>
|
|
3274
|
+
left.localeCompare(right),
|
|
3275
|
+
);
|
|
1225
3276
|
const template = normalizeDraftTemplate(options.template || profile.plannerDefaults.template);
|
|
1226
3277
|
const prompt = new PromptSession();
|
|
1227
3278
|
try {
|
|
@@ -1262,7 +3313,11 @@ async function runDraftFlow(options = {}) {
|
|
|
1262
3313
|
profile.defaultOversightMode,
|
|
1263
3314
|
),
|
|
1264
3315
|
);
|
|
1265
|
-
const context7Bundle = await prompt.askChoice(
|
|
3316
|
+
const context7Bundle = await prompt.askChoice(
|
|
3317
|
+
"Wave Context7 bundle",
|
|
3318
|
+
context7BundleChoices,
|
|
3319
|
+
"none",
|
|
3320
|
+
);
|
|
1266
3321
|
const context7Query = cleanText(await prompt.ask("Wave Context7 query", ""));
|
|
1267
3322
|
const standardRoles = {
|
|
1268
3323
|
contQa: await prompt.askBoolean("Use the standard cont-QA role?", true),
|
|
@@ -1286,6 +3341,7 @@ async function runDraftFlow(options = {}) {
|
|
|
1286
3341
|
componentPromotions,
|
|
1287
3342
|
waveNumber,
|
|
1288
3343
|
lane: lanePaths.lane,
|
|
3344
|
+
context7BundleChoices,
|
|
1289
3345
|
});
|
|
1290
3346
|
const draftValues = {
|
|
1291
3347
|
wave: waveNumber,
|
|
@@ -1355,6 +3411,9 @@ function printPlannerHelp() {
|
|
|
1355
3411
|
wave project setup [--json]
|
|
1356
3412
|
wave project show [--json]
|
|
1357
3413
|
wave draft --wave <n> [--lane <lane>] [--template implementation|qa|infra|release] [--force] [--json]
|
|
3414
|
+
wave draft --agentic --task "<text>" --from-wave <n> [--lane <lane>] [--max-waves <n>] [--planner-executor <profile>] [--json]
|
|
3415
|
+
wave draft --show-run <run-id> [--json]
|
|
3416
|
+
wave draft --apply-run <run-id> [--waves <list>|all] [--force] [--json]
|
|
1358
3417
|
`);
|
|
1359
3418
|
}
|
|
1360
3419
|
|
|
@@ -1367,6 +3426,14 @@ export async function runPlannerCli(argv) {
|
|
|
1367
3426
|
wave: null,
|
|
1368
3427
|
lane: null,
|
|
1369
3428
|
template: null,
|
|
3429
|
+
agentic: false,
|
|
3430
|
+
task: null,
|
|
3431
|
+
fromWave: null,
|
|
3432
|
+
maxWaves: null,
|
|
3433
|
+
plannerExecutorProfile: null,
|
|
3434
|
+
showRun: null,
|
|
3435
|
+
applyRun: null,
|
|
3436
|
+
waves: null,
|
|
1370
3437
|
};
|
|
1371
3438
|
if (!subcommand) {
|
|
1372
3439
|
printPlannerHelp();
|
|
@@ -1432,6 +3499,22 @@ export async function runPlannerCli(argv) {
|
|
|
1432
3499
|
options.json = true;
|
|
1433
3500
|
} else if (arg === "--force") {
|
|
1434
3501
|
options.force = true;
|
|
3502
|
+
} else if (arg === "--agentic") {
|
|
3503
|
+
options.agentic = true;
|
|
3504
|
+
} else if (arg === "--task") {
|
|
3505
|
+
options.task = cleanText(args[++index]);
|
|
3506
|
+
} else if (arg === "--from-wave") {
|
|
3507
|
+
options.fromWave = Number.parseInt(String(args[++index] || ""), 10);
|
|
3508
|
+
} else if (arg === "--max-waves") {
|
|
3509
|
+
options.maxWaves = Number.parseInt(String(args[++index] || ""), 10);
|
|
3510
|
+
} else if (arg === "--planner-executor") {
|
|
3511
|
+
options.plannerExecutorProfile = cleanText(args[++index]);
|
|
3512
|
+
} else if (arg === "--show-run") {
|
|
3513
|
+
options.showRun = cleanText(args[++index]);
|
|
3514
|
+
} else if (arg === "--apply-run") {
|
|
3515
|
+
options.applyRun = cleanText(args[++index]);
|
|
3516
|
+
} else if (arg === "--waves") {
|
|
3517
|
+
options.waves = cleanText(args[++index]);
|
|
1435
3518
|
} else if (arg === "--wave") {
|
|
1436
3519
|
options.wave = Number.parseInt(String(args[++index] || ""), 10);
|
|
1437
3520
|
} else if (arg === "--lane") {
|
|
@@ -1445,6 +3528,57 @@ export async function runPlannerCli(argv) {
|
|
|
1445
3528
|
throw new Error(`Unknown argument: ${arg}`);
|
|
1446
3529
|
}
|
|
1447
3530
|
}
|
|
3531
|
+
if (options.showRun && options.applyRun) {
|
|
3532
|
+
throw new Error("--show-run and --apply-run are mutually exclusive.");
|
|
3533
|
+
}
|
|
3534
|
+
if (options.showRun) {
|
|
3535
|
+
const result = showPlannerRun({ runId: options.showRun });
|
|
3536
|
+
if (options.json) {
|
|
3537
|
+
printJson(result);
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
console.log(`[wave:draft] run=${result.runId}`);
|
|
3541
|
+
console.log(`[wave:draft] state=${result.result?.state || "unknown"}`);
|
|
3542
|
+
console.log(`[wave:draft] lane=${result.request?.lane || "unknown"}`);
|
|
3543
|
+
console.log(`[wave:draft] waves=${(result.plan?.waveOrder || []).join(", ") || "none"}`);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
if (options.applyRun) {
|
|
3547
|
+
const result = await runApplyPlannerRun({
|
|
3548
|
+
...options,
|
|
3549
|
+
runId: options.applyRun,
|
|
3550
|
+
});
|
|
3551
|
+
if (options.json) {
|
|
3552
|
+
printJson(result);
|
|
3553
|
+
return;
|
|
3554
|
+
}
|
|
3555
|
+
console.log(`[wave:draft] run=${result.runId}`);
|
|
3556
|
+
console.log(`[wave:draft] state=${result.state}`);
|
|
3557
|
+
console.log(`[wave:draft] applied_waves=${(result.appliedWaves || []).join(", ")}`);
|
|
3558
|
+
console.log(`[wave:draft] matrix_json=${result.matrixJsonPath}`);
|
|
3559
|
+
console.log(`[wave:draft] matrix_md=${result.matrixDocPath}`);
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
if (options.agentic) {
|
|
3563
|
+
if (!cleanText(options.task)) {
|
|
3564
|
+
throw new Error("--task \"...\" is required for `wave draft --agentic`.");
|
|
3565
|
+
}
|
|
3566
|
+
if (!Number.isFinite(options.fromWave) || options.fromWave < 0) {
|
|
3567
|
+
throw new Error("--from-wave <n> is required for `wave draft --agentic`.");
|
|
3568
|
+
}
|
|
3569
|
+
const result = await runAgenticDraftFlow(options);
|
|
3570
|
+
if (options.json) {
|
|
3571
|
+
printJson(result);
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
console.log(`[wave:draft] run=${result.runId}`);
|
|
3575
|
+
console.log(`[wave:draft] state=${result.state}`);
|
|
3576
|
+
console.log(`[wave:draft] lane=${result.lane}`);
|
|
3577
|
+
console.log(`[wave:draft] planner_source=${result.plannerSource || "unknown"}`);
|
|
3578
|
+
console.log(`[wave:draft] waves=${(result.waveOrder || []).join(", ") || "none"}`);
|
|
3579
|
+
console.log(`[wave:draft] candidate_dir=${result.paths.candidateDir}`);
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
1448
3582
|
if (!Number.isFinite(options.wave) || options.wave < 0) {
|
|
1449
3583
|
throw new Error("--wave <n> is required for `wave draft`.");
|
|
1450
3584
|
}
|