devflow-kit 3.2.0 → 3.3.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 +16 -0
- package/README.md +1 -1
- package/dist/cli/commands/learning.js +25 -5
- package/dist/commands/code-review.md +1 -1
- package/dist/commands/debug.md +1 -1
- package/dist/commands/dynamic-build.md +141 -94
- package/dist/commands/dynamic-plan.md +1 -1
- package/dist/commands/explore.md +1 -1
- package/dist/commands/implement.md +133 -133
- package/dist/commands/resolve.md +27 -16
- package/dist/commands/self-review.md +16 -11
- package/dist/core/agent-models.js +10 -7
- package/dist/core/learning-tuning-config.js +8 -0
- package/dist/core/plugins.js +7 -2
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +1 -1
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +1 -1
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +1 -1
- package/package.json +1 -1
- package/src/assets/agents/code.md +71 -18
- package/src/assets/agents/design.md +10 -0
- package/src/assets/agents/diagnose.md +16 -11
- package/src/assets/agents/evaluate.md +14 -25
- package/src/assets/agents/knowledge.md +5 -3
- package/src/assets/agents/learning.md +4 -6
- package/src/assets/agents/research.md +19 -0
- package/src/assets/agents/review.md +10 -0
- package/src/assets/agents/scrutinize.md +34 -10
- package/src/assets/agents/simplify.md +20 -0
- package/src/assets/agents/skim.md +4 -2
- package/src/assets/agents/synthesize.md +12 -0
- package/src/assets/agents/test.md +2 -2
- package/src/assets/agents/triage.md +7 -1
- package/src/assets/agents/validate.md +7 -2
- package/src/assets/commands/_partials/_engine.mds +21 -24
- package/src/assets/commands/_partials/_knowledge.mds +1 -1
- package/src/assets/commands/_partials/_plan_contract.mds +1 -1
- package/src/assets/commands/_partials/_wave.mds +8 -6
- package/src/assets/commands/code-review.mds +1 -1
- package/src/assets/commands/dynamic-build.mds +111 -63
- package/src/assets/commands/implement.mds +132 -132
- package/src/assets/commands/resolve.mds +26 -15
- package/src/assets/commands/self-review.mds +15 -10
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/scripts/ci-wait.cjs +636 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +1 -1
- package/src/assets/scripts/hooks/background-memory-update +290 -9
- package/src/assets/scripts/release-trace.cjs +27 -10
- package/src/assets/skills/accessibility/SKILL.md +1 -1
- package/src/assets/skills/apply-decisions/SKILL.md +12 -82
- package/src/assets/skills/apply-feature-knowledge/SKILL.md +8 -42
- package/src/assets/skills/architecture/SKILL.md +1 -1
- package/src/assets/skills/boundary-validation/SKILL.md +1 -1
- package/src/assets/skills/complexity/SKILL.md +1 -1
- package/src/assets/skills/compliance/SKILL.md +1 -1
- package/src/assets/skills/consistency/SKILL.md +1 -1
- package/src/assets/skills/database/SKILL.md +1 -1
- package/src/assets/skills/dependencies/SKILL.md +1 -1
- package/src/assets/skills/dependency-research/SKILL.md +3 -6
- package/src/assets/skills/design-review/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +1 -1
- package/src/assets/skills/documentation/SKILL.md +1 -1
- package/src/assets/skills/gap-analysis/SKILL.md +1 -1
- package/src/assets/skills/git/SKILL.md +1 -1
- package/src/assets/skills/go/SKILL.md +1 -1
- package/src/assets/skills/java/SKILL.md +1 -1
- package/src/assets/skills/patterns/SKILL.md +1 -1
- package/src/assets/skills/performance/SKILL.md +1 -1
- package/src/assets/skills/python/SKILL.md +1 -1
- package/src/assets/skills/qa/SKILL.md +1 -3
- package/src/assets/skills/quality-gates/SKILL.md +9 -12
- package/src/assets/skills/quality-gates/references/report-template.md +20 -20
- package/src/assets/skills/react/SKILL.md +1 -1
- package/src/assets/skills/regression/SKILL.md +1 -1
- package/src/assets/skills/reliability/SKILL.md +1 -1
- package/src/assets/skills/research-codebase/SKILL.md +1 -1
- package/src/assets/skills/research-competitor/SKILL.md +1 -1
- package/src/assets/skills/research-external/SKILL.md +1 -1
- package/src/assets/skills/research-technology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/rust/SKILL.md +1 -1
- package/src/assets/skills/security/SKILL.md +1 -1
- package/src/assets/skills/software-design/SKILL.md +1 -1
- package/src/assets/skills/test-driven-development/SKILL.md +9 -29
- package/src/assets/skills/testing/SKILL.md +1 -1
- package/src/assets/skills/typescript/SKILL.md +1 -1
- package/src/assets/skills/ui-design/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/SKILL.md +3 -55
- package/src/assets/skills/worktree-support/references/discovery.md +48 -0
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
|
@@ -187,7 +187,8 @@ if (BRANCH === "(none)") {
|
|
|
187
187
|
|
|
188
188
|
// Phase 2: Implement
|
|
189
189
|
await phase("implement", () =>
|
|
190
|
-
agent(`
|
|
190
|
+
agent(`OPERATION: implement
|
|
191
|
+
Implement the following ticket on branch ${BRANCH}:
|
|
191
192
|
|
|
192
193
|
${TICKET}
|
|
193
194
|
|
|
@@ -208,39 +209,51 @@ After implementing, commit your changes with a conventional-commit message and r
|
|
|
208
209
|
- Any open questions or blockers`, { agentType: "Code" })
|
|
209
210
|
);
|
|
210
211
|
|
|
211
|
-
// Phase 3: Gate 1 — post-code pipeline
|
|
212
|
+
// Phase 3: Gate 1 #1 — post-code pipeline: Simplify agent → Scrutinize agent → Validate agent.
|
|
213
|
+
// Validate runs last and unconditionally, so its one full run covers every commit the pass made (see gate1_postcode()).
|
|
212
214
|
const gate1 = await phase("gate1", async () => {
|
|
213
|
-
|
|
215
|
+
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
216
|
+
|
|
217
|
+
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
|
|
218
|
+
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
|
|
219
|
+
// Anything but an explicit PASS or FIXED is a stop: a Scrutinize that returned no status, or one this skeleton does not know, has not accepted the code.
|
|
220
|
+
if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
|
|
221
|
+
return { verdict: "ESCALATED", type: "scrutiny-blocked", reason: scrutiny?.status === "BLOCKED" ? "Scrutinize agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Scrutinize agent returned no recognised status: counted as BLOCKED" };
|
|
222
|
+
}
|
|
223
|
+
|
|
214
224
|
const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH}.
|
|
215
225
|
Follow your Running commands block for every build and test command.
|
|
216
|
-
|
|
226
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
217
227
|
|
|
218
|
-
|
|
219
|
-
|
|
228
|
+
// Anything but an explicit PASS is a failure: a Validate that returned no verdict has not validated the branch.
|
|
229
|
+
if (validation?.verdict !== "PASS") {
|
|
230
|
+
let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
|
|
231
|
+
// Up to 2 fix attempts, each followed by a Validate re-run
|
|
220
232
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
221
|
-
await agent(`
|
|
222
|
-
${
|
|
233
|
+
await agent(`OPERATION: validation-fix
|
|
234
|
+
Fix the validation failures on branch ${BRANCH}:
|
|
235
|
+
${failureDetails}
|
|
223
236
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
224
237
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
225
238
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
226
239
|
Commit fixes with conventional-commit message.`, { agentType: "Code" });
|
|
227
|
-
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH}
|
|
228
|
-
|
|
229
|
-
if (
|
|
240
|
+
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} after fix attempt ${attempt} (follow your Running commands block).
|
|
241
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
242
|
+
if (recheck?.verdict === "PASS") break;
|
|
243
|
+
failureDetails = recheck?.details || failureDetails;
|
|
244
|
+
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Validation exhausted after 2 Code agent fix attempts" };
|
|
230
245
|
}
|
|
231
246
|
}
|
|
232
247
|
|
|
233
|
-
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
234
|
-
|
|
235
|
-
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
|
|
236
|
-
|
|
237
|
-
if (scrutiny.codeChanged) {
|
|
238
|
-
await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (Scrutinize agent made changes). Report: PASS or FAIL.`, { agentType: "Validate" });
|
|
239
|
-
}
|
|
240
|
-
|
|
241
248
|
return { verdict: "PASS" };
|
|
242
249
|
});
|
|
243
250
|
|
|
251
|
+
// Gate 1 #1 stop, before Gate 2: an exhausted Validate or a BLOCKED Scrutinize leaves a broken or unfinished build,
|
|
252
|
+
// so Gate 2 and the review pass never run on it — a correctness stop, like the ticket-link and branch stops.
|
|
253
|
+
if (gate1.verdict === "ESCALATED") {
|
|
254
|
+
return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: gate1.type, description: gate1.reason }] };
|
|
255
|
+
}
|
|
256
|
+
|
|
244
257
|
// Phase 4: Gate 2 — acceptance gate (once, before review pass)
|
|
245
258
|
const gate2 = await phase("gate2", async () => {
|
|
246
259
|
if (!PLAN && !CRITERIA && !TEST_PLAN) {
|
|
@@ -249,21 +262,19 @@ const gate2 = await phase("gate2", async () => {
|
|
|
249
262
|
|
|
250
263
|
let evalVerdict = "SKIPPED";
|
|
251
264
|
if (PLAN) {
|
|
252
|
-
const
|
|
253
|
-
|
|
265
|
+
const evaluation = await agent(`Acceptance and scope check on branch ${BRANCH}. Answer both questions:
|
|
266
|
+
1. Does the implementation satisfy each numbered criterion, INCLUDING negative criteria (what it must NOT do)?
|
|
267
|
+
2. Did the Code agent introduce any unplanned changes, smuggled anti-features, or drift from the plan's intent?
|
|
254
268
|
Plan: ${PLAN}
|
|
255
269
|
Criteria: ${CRITERIA || "see plan"}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
Plan: ${PLAN}
|
|
259
|
-
Report: PASS or FAIL with rationale.`, { agentType: "Evaluate" }),
|
|
260
|
-
]);
|
|
261
|
-
evalVerdict = panel.every(p => p.verdict === "PASS") ? "PASS" : "FAIL";
|
|
270
|
+
Return: {"verdict": "PASS" | "FAIL", "rationale": "..."} — FAIL if either answer is no, naming the failing criterion or the drift.`, { agentType: "Evaluate" });
|
|
271
|
+
evalVerdict = evaluation?.verdict === "PASS" ? "PASS" : "FAIL";
|
|
262
272
|
if (evalVerdict === "FAIL") {
|
|
263
273
|
// fix-and-continue — no re-evaluate, no inline Gate 1. The final Gate 1 (#2)
|
|
264
274
|
// after the review pass is the build gate before the branch is handed back.
|
|
265
|
-
await agent(`
|
|
266
|
-
|
|
275
|
+
await agent(`OPERATION: alignment-fix
|
|
276
|
+
Fix the alignment issues identified by the Evaluate agent on branch ${BRANCH}:
|
|
277
|
+
${evaluation?.rationale || "see the Evaluate agent's report"}
|
|
267
278
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
268
279
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
269
280
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
@@ -282,7 +293,8 @@ Cover: functionality, API contracts, performance, and cover every TEST_PLAN scen
|
|
|
282
293
|
testVerdict = testResult.verdict;
|
|
283
294
|
if (testVerdict === "FAIL") {
|
|
284
295
|
// fix-and-continue — no re-test, no inline Gate 1.
|
|
285
|
-
await agent(`
|
|
296
|
+
await agent(`OPERATION: qa-fix
|
|
297
|
+
Fix the failing acceptance test scenarios on branch ${BRANCH}:
|
|
286
298
|
${testResult.failures}
|
|
287
299
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
288
300
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
@@ -309,7 +321,7 @@ const reviewResult = await phase("review", async () => {
|
|
|
309
321
|
const chunkSize = 5;
|
|
310
322
|
const reviewThunks = reviewFocuses.map(focus =>
|
|
311
323
|
() => agent(`${focus.charAt(0).toUpperCase() + focus.slice(1)} review. ${reviewScope}
|
|
312
|
-
Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" })
|
|
324
|
+
Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "line": <number-or-null>, "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" })
|
|
313
325
|
);
|
|
314
326
|
|
|
315
327
|
let rawReviews = [];
|
|
@@ -326,7 +338,7 @@ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "fil
|
|
|
326
338
|
const focus = reviewFocuses[i];
|
|
327
339
|
if (!r || r.reviewed !== true) {
|
|
328
340
|
// Dead Review agent: retry once sequentially
|
|
329
|
-
const retry = await agent(`Retry ${focus} review. ${reviewScope} Focus: ${focus} discipline. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" });
|
|
341
|
+
const retry = await agent(`Retry ${focus} review. ${reviewScope} Focus: ${focus} discipline. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "line": <number-or-null>, "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" });
|
|
330
342
|
if (!retry || retry.reviewed !== true) {
|
|
331
343
|
coverageGaps.push(focus);
|
|
332
344
|
log(`review coverage incomplete: ${focus}`);
|
|
@@ -391,13 +403,18 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
|
|
|
391
403
|
// Distinct-file groups are paced in chunks of FIX_CHUNK — same pacing bar as the Review spawn
|
|
392
404
|
// path — to avoid launching all Code agents at once on a large branch (reliability: explicit bound).
|
|
393
405
|
const FIX_CHUNK = 5;
|
|
394
|
-
|
|
406
|
+
// issue-fix SCOPE per finding: CRITICAL or HIGH takes the Careful protocol (failing regression test first); anything else is Standard.
|
|
407
|
+
const CAREFUL_SEVERITIES = new Set(["critical", "high"]);
|
|
408
|
+
const fixThunks = Object.values(fileGroups).map(findings => async () => {
|
|
395
409
|
const chunkResults = [];
|
|
396
410
|
for (let i = 0; i < findings.length; i += MAX_BATCH) {
|
|
397
411
|
const chunk = findings.slice(i, i + MAX_BATCH);
|
|
398
|
-
const r = await agent(`
|
|
399
|
-
|
|
400
|
-
|
|
412
|
+
const r = await agent(`OPERATION: issue-fix
|
|
413
|
+
Fix the following confirmed review findings on branch ${BRANCH}:
|
|
414
|
+
ISSUES:
|
|
415
|
+
${chunk.map((f, n) => `${n + 1}. ${f.file ? `${f.file}${f.line ? `:${f.line}` : ""} — ` : ""}${f.description} (${f.severity})`).join("\n")}
|
|
416
|
+
SCOPE: ${chunk.map((f, n) => `${n + 1}=${CAREFUL_SEVERITIES.has(String(f.severity).toLowerCase()) ? "Careful" : "Standard"}`).join(", ")}
|
|
417
|
+
PUSH: false
|
|
401
418
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
402
419
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
403
420
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
@@ -436,38 +453,40 @@ Return: {"status": "fixed"|"blocked", "commitShas": ["<sha>"], "unresolved": ["<
|
|
|
436
453
|
return { survivingFindings: notAddressed, fixedFindings, coverageGaps };
|
|
437
454
|
});
|
|
438
455
|
|
|
439
|
-
// Phase 5.5: Gate 1 #2 — FINAL post-fix gate (
|
|
456
|
+
// Phase 5.5: Gate 1 #2 — FINAL post-fix gate (Simplify agent → Scrutinize agent → full Validate agent, Validate last and unconditional).
|
|
440
457
|
// Runs ONCE, after the review pass. Inside the pass the Code agent self-verified its own fixes;
|
|
441
458
|
// this is the build gate before the branch is handed back. See gate1_postcode() cadence.
|
|
442
459
|
const gate1Final = await phase("gate1-final", async () => {
|
|
460
|
+
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
461
|
+
|
|
462
|
+
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
|
|
463
|
+
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
|
|
464
|
+
if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
|
|
465
|
+
return { verdict: "ESCALATED", type: "scrutiny-blocked", reason: scrutiny?.status === "BLOCKED" ? "Final Gate 1 Scrutinize agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Final Gate 1 Scrutinize agent returned no recognised status: counted as BLOCKED" };
|
|
466
|
+
}
|
|
467
|
+
|
|
443
468
|
const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH} (final gate after all fixing).
|
|
444
469
|
Follow your Running commands block for every build and test command.
|
|
445
|
-
|
|
470
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
446
471
|
|
|
447
|
-
if (validation
|
|
448
|
-
let failureDetails = validation
|
|
472
|
+
if (validation?.verdict !== "PASS") {
|
|
473
|
+
let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
|
|
449
474
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
450
|
-
await agent(`
|
|
475
|
+
await agent(`OPERATION: validation-fix
|
|
476
|
+
Fix the final validation failures on branch ${BRANCH}:
|
|
451
477
|
${failureDetails}
|
|
452
478
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
453
479
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
454
480
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
455
481
|
Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "Code" });
|
|
456
|
-
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (follow your Running commands block).
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
482
|
+
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (final gate, after fix attempt ${attempt}; follow your Running commands block).
|
|
483
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
484
|
+
if (recheck?.verdict === "PASS") break;
|
|
485
|
+
failureDetails = recheck?.details || failureDetails;
|
|
486
|
+
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Final Gate 1 validation exhausted after 2 Code agent fix attempts" };
|
|
460
487
|
}
|
|
461
488
|
}
|
|
462
489
|
|
|
463
|
-
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
464
|
-
|
|
465
|
-
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
|
|
466
|
-
|
|
467
|
-
if (scrutiny.codeChanged) {
|
|
468
|
-
await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (Scrutinize agent made changes; follow your Running commands block). Report: PASS or FAIL.`, { agentType: "Validate" });
|
|
469
|
-
}
|
|
470
|
-
|
|
471
490
|
return { verdict: "PASS" };
|
|
472
491
|
});
|
|
473
492
|
|
|
@@ -496,7 +515,7 @@ Write a concise report. Present only surviving findings as outstanding — never
|
|
|
496
515
|
survivingFindings: reviewResult.survivingFindings || [], fixedFindings: reviewResult.fixedFindings || [],
|
|
497
516
|
reviewCoverage: { failedFocuses: coverageGaps, complete: coverageGaps.length === 0 },
|
|
498
517
|
escalations: [
|
|
499
|
-
...(gate1Final.verdict === "ESCALATED" ? [{ type: "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
|
|
518
|
+
...(gate1Final.verdict === "ESCALATED" ? [{ type: gate1Final.type || "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
|
|
500
519
|
...coverageGaps.map(focus => ({ type: "review-coverage-incomplete", description: `review coverage incomplete: ${focus}` })),
|
|
501
520
|
],
|
|
502
521
|
gate2, report,
|
|
@@ -540,7 +559,7 @@ When WAVE mode is detected, author a workflow that wraps the single-ticket engin
|
|
|
540
559
|
|
|
541
560
|
**Wave workflow structure (author after the SINGLE engine blocks above):**
|
|
542
561
|
|
|
543
|
-
The wave workflow uses the same phases as SINGLE but wraps them in a wave loop. The integration branch is `wave/<initiative>` — the initiative slug, referenced below as `{slug}` — (or the user's current branch). Each ticket works on the branch setup-task created. The Git agent manages worktrees for parallel-eligible tickets. After every merge
|
|
562
|
+
The wave workflow uses the same phases as SINGLE but wraps them in a wave loop. The integration branch is `wave/<initiative>` — the initiative slug, referenced below as `{slug}` — (or the user's current branch). Each ticket works on the branch setup-task created. The Git agent manages worktrees for parallel-eligible tickets. After every merge the workflow spawns the Validate agent (build + test) and undoes a red merge. Escalations accumulate in a list; the final report lists all of them.
|
|
544
563
|
|
|
545
564
|
Wave skeleton — compact reference (see `wave_loop()` doctrine for full semantics):
|
|
546
565
|
|
|
@@ -551,11 +570,12 @@ Wave skeleton — compact reference (see `wave_loop()` doctrine for full semanti
|
|
|
551
570
|
const WAVE_TICKETS = [...remainingTickets]; // the pre-fetch's refs, in input order — the wave block's row order
|
|
552
571
|
const results = {}; // ticketId → its row of the workflow's return; a ticket with no entry never ran
|
|
553
572
|
// A row from an engine result: the fields step 3 after the workflow renders
|
|
554
|
-
const rowOf = (ticketId, r, merged) => ({ ticket: ticketId, ran: true, verdict: r?.verdict || r?.overallVerdict || null, merged, issuePrLink: r?.issuePrLink || "(none)", evaluateVerdict: r?.gate2?.evaluateVerdict, testVerdict: r?.gate2?.testVerdict, surviving: r?.survivingFindings?.length, coverageComplete: r?.reviewCoverage?.complete });
|
|
573
|
+
const rowOf = (ticketId, r, merged, treeEqual = null) => ({ ticket: ticketId, ran: true, verdict: r?.verdict || r?.overallVerdict || null, merged, treeEqual, issuePrLink: r?.issuePrLink || "(none)", evaluateVerdict: r?.gate2?.evaluateVerdict, testVerdict: r?.gate2?.testVerdict, surviving: r?.survivingFindings?.length, coverageComplete: r?.reviewCoverage?.complete });
|
|
555
574
|
const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
|
|
556
|
-
const
|
|
575
|
+
const MERGE_SHA = /^[0-9a-f]{40}$/; // a merge commit as git prints it: the only Git-reported value that reaches a later prompt
|
|
576
|
+
const waveState = { quarantined: [], round: 0, halted: null };
|
|
557
577
|
let reAskedThisDeadlock = false; // re-ask guard: re-ask once on empty ready-set, then escalate
|
|
558
|
-
while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS) {
|
|
578
|
+
while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS && waveState.halted === null) {
|
|
559
579
|
waveState.round++;
|
|
560
580
|
|
|
561
581
|
// Design agent reader (opus) applies vacuous-truth rule: a ticket with no unmet dependencies is ALWAYS ready.
|
|
@@ -588,10 +608,36 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
|
|
|
588
608
|
// Check both verdict (engine_output_schema) and overallVerdict (SINGLE skeleton alias).
|
|
589
609
|
// PASS and UNVERIFIED merge; PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
|
|
590
610
|
if (["PASS", "UNVERIFIED"].includes(engineResult.verdict || engineResult.overallVerdict)) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
611
|
+
// Git merges locally and validates nothing: the workflow owns the post-merge Validate (D-WAVE-MERGE-VALIDATE).
|
|
612
|
+
const merge = await agent(`Merge ${engineResult.branch} to ${INTEGRATION_BRANCH} locally. Include ticket ID ${ticketId} in the merge commit message. Do not push, and run no build or test.
|
|
613
|
+
Return: {"merged": true, "mergeSha": "<40-hex merge commit>", "treeEqual": <true when mergeSha^{tree} equals the tree of ${engineResult.branch}'s head>} — or {"merged": false, "reason": "<why>"} when no merge is kept.`, { agentType: "Git" });
|
|
614
|
+
let kept = false;
|
|
615
|
+
let reason = merge?.reason || "merge failed";
|
|
616
|
+
if (merge?.merged === true && !MERGE_SHA.test(String(merge.mergeSha))) {
|
|
617
|
+
// A merge may sit on the integration branch with no SHA to validate or undo it by: stop taking merges.
|
|
618
|
+
waveState.halted = { ticket: ticketId, mergeSha: null, reason: "merge reported without a 40-hex mergeSha; the integration branch may hold an unvalidated merge" };
|
|
619
|
+
reason = waveState.halted.reason;
|
|
620
|
+
} else if (merge?.merged === true) {
|
|
621
|
+
// A merge counts as kept only when this Validate returns PASS. It always runs: treeEqual is recorded, never a reason to skip.
|
|
622
|
+
const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
|
|
623
|
+
Follow your Running commands block for every build and test command.
|
|
624
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
625
|
+
if (post?.verdict === "PASS") {
|
|
626
|
+
kept = true;
|
|
627
|
+
} else {
|
|
628
|
+
const undo = await agent(`Undo the merge ${merge.mergeSha} on ${INTEGRATION_BRANCH}, only when no remote branch contains ${merge.mergeSha} and the HEAD of ${INTEGRATION_BRANCH} still equals ${merge.mergeSha}. On the integration branch run exactly this one command and no other that changes a branch or a remote: git reset --keep ${merge.mergeSha}^1
|
|
629
|
+
Return: {"undone": true} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
|
|
630
|
+
if (undo?.undone === true) {
|
|
631
|
+
reason = "post-merge build red; merge undone";
|
|
632
|
+
} else {
|
|
633
|
+
// A merge that stays red would poison every later ticket: stop taking merges and rounds, and name the red HEAD.
|
|
634
|
+
reason = `post-merge build red; merge not undone (${undo?.reason || "no reason given"})`;
|
|
635
|
+
waveState.halted = { ticket: ticketId, mergeSha: merge.mergeSha, reason };
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
results[ticketId] = rowOf(ticketId, engineResult, kept, merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null);
|
|
640
|
+
if (!kept) waveState.quarantined.push({ ticket: ticketId, reason });
|
|
595
641
|
} else {
|
|
596
642
|
results[ticketId] = rowOf(ticketId, engineResult, false);
|
|
597
643
|
waveState.quarantined.push({ ticket: ticketId, reason: engineResult.escalations?.[0]?.description || "engine fail/escalated" });
|
|
@@ -602,11 +648,12 @@ Return: {"merged": true} — or {"merged": false, "reason": "<why>"} when the me
|
|
|
602
648
|
waveState.quarantined.push({ ticket: ticketId, reason: `engine crash: ${String(err)}` });
|
|
603
649
|
}
|
|
604
650
|
remainingTickets = remainingTickets.filter(t => t !== ticketId);
|
|
651
|
+
if (waveState.halted !== null) break; // a red integration HEAD takes no further merge in this round
|
|
605
652
|
}
|
|
606
653
|
}
|
|
607
654
|
|
|
608
655
|
// After the wave report is written: one row per wave ticket, in input order. No entry ⇒ never ran (cascade, deadlock, MAX_ROUNDS).
|
|
609
|
-
return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, issuePrLink: "(none)" }), quarantined: waveState.quarantined };
|
|
656
|
+
return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, treeEqual: null, issuePrLink: "(none)" }), quarantined: waveState.quarantined, halted: waveState.halted };
|
|
610
657
|
```
|
|
611
658
|
|
|
612
659
|
---
|
|
@@ -630,6 +677,7 @@ A workflow cannot pause mid-run. After the build/wave workflow returns, you (the
|
|
|
630
677
|
In WAVE mode, if no tracking-issue number was resolved in Pre-authoring step 5: state `TRACEABILITY: DEGRADED (no tracking issue for this run)` in the run summary and skip — never skip silently.
|
|
631
678
|
3. **Compose the wave PR inputs** (WAVE mode only — skip this step entirely in SINGLE mode). The workflow returned `tickets`: one entry per wave ticket, in its input order, each `{ticket, ran, verdict, merged, issuePrLink, evaluateVerdict, testVerdict, surviving, coverageComplete}`. Every value in it is agent-reported and treated as untrusted: nothing below is repaired, and no reference is ever composed from a number.
|
|
632
679
|
- **Branch check.** Run `git -C "{integration worktree root}" branch --show-current`. Only a name matching `^wave/[a-z0-9][a-z0-9-]{0,59}$` opens a wave PR, and its part after `wave/` is the `{slug}` below. Any other name: record `TRACEABILITY: DEGRADED (not a wave branch)` and go to step 4 with no wave PR.
|
|
680
|
+
- **Halted** (the workflow returned a non-null `halted`: a post-merge build it could not undo, or a merge reported with no SHA): the integration HEAD may be red. Record `Wave PR: skipped (integration HEAD red, wave halted at <halted.ticket>)`, name `halted.mergeSha` (or `none reported`) and `halted.reason` as the red integration HEAD in the run summary, and go to step 4 with no wave PR.
|
|
633
681
|
- **Nothing merged** (no entry has `merged: true`): record `Wave PR: skipped (nothing merged)` and go to step 4.
|
|
634
682
|
- Otherwise compose (a) and then (b).
|
|
635
683
|
|