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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to Devflow will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.3.0] - 2026-10-09
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Agents ship chosen tiers, efforts and tool sets** ([#422](https://github.com/dean0x/devflow/issues/422)). Fifteen agents now carry a deliberate model, effort, tool policy and `omitClaudeMd` setting, held by a guard to one table. Skim moves from `sonnet` to `haiku`, the only model change. Every agent except Learning, Git and Tracker now ships an effort and no longer follows your session's effort: `high` for Code, Design, Review and Triage, `medium` for the rest. `devflow agents --set <agent> --effort inherit` restores the old behaviour for one agent. Review ships `opus` at `high` effort: an A/B run of the `/code-review` focus agents over two seeded PRs found every seeded bug at `high` that it found at `xhigh`, for about 43% lower cost. Learning keeps its model, its absent effort and its CLAUDE.md context: the cheaper candidates fell short of the capture bar, and without that context it stops retiring entries as Encoded. Code, Research, Scrutinize and Simplify deny one shared list of orchestration and host-UI tools (Scrutinize and Simplify add `Skill`) and keep their MCP and web access; every other agent holds an allowlist. Synthesize and Skim omit CLAUDE.md. Preloads leave where the body never reads them: `testing` from Validate and Test, `software-design` from Evaluate, the five pattern skills from Diagnose, which now loads its focus's skills on demand, and `apply-decisions` from Learning. `devflow learning --configure` marks as recommended the model the Learning agent ships, read from its frontmatter, so a change of the shipped tier moves the recommendation with it.
|
|
13
|
+
- **The gates run in a new order, and a script waits on CI** ([#421](https://github.com/dean0x/devflow/issues/421)). `/implement` now runs Simplify, Scrutinize, Evaluate, then one Validate over the final HEAD, then Test, then pushes and gates CI; a changed-only Validate follows only when a QA fix committed. The alignment and QA loops no longer spawn a Validate of their own, and `/self-review` validates whenever HEAD moved, so a Simplify-only commit is no longer skipped. `ci-wait.cjs` is a head-bound foreground CI wait that replaces re-spawning the Git agent once a minute: it binds the verdict to the pushed head, waits inside node and prints one line. One wait is capped at 570 seconds, and each CI gate allows at most 3 waits and 2 fixes, spawning Code only on a failing verdict. Scrutinize reports one status vocabulary, PASS, FIXED or BLOCKED (READY is gone), and each gate duty has one owner: Naming and Consistency belong to Simplify. In the dynamic-build engine both Gate 1 passes end with Validate. The Git agent now merges locally and validates nothing; the workflow runs a post-merge Validate after every merge, counts the merge as kept only on PASS, and undoes a red merge with `reset --keep`; a refused undo halts the wave and names the red HEAD, and a first Gate 1 that ends ESCALATED or BLOCKED returns early. `/resolve` waits on CI through the same script.
|
|
14
|
+
- **Skills cost less** ([#423](https://github.com/dean0x/devflow/issues/423)). Code preloads six skills (`git`, `testing`, `test-driven-development`, `worktree-support`, `apply-feature-knowledge` and `apply-decisions`) where it preloaded ten: 25,578 bytes of skill text, down from 53,450. `software-design`, `patterns`, `boundary-validation` and `dependency-research` load on demand from a table with one row per operating mode. Every Code spawn now opens its prompt with `OPERATION: <mode>`, including two new modes, `ci-fix` (fix the failing CI checks the gate names) and `edit` (a mechanical change that adds no behaviour), and the orchestrator charter's direct Code delegations do the same. `apply-decisions` reads an entry by finding its heading with `command grep -nF` and then Reading only that section by offset, instead of opening the whole file. `worktree-support` keeps its discovery algorithm in a reference that `/code-review` and `/resolve` read when they need it, rather than in every agent's preload. Skill descriptions are capped by who reads them, at 90 characters for the agent-internal skills and 200 for the user-trigger ones, and 38 shrank. The Git agent's loaded sets are 1,704 characters smaller.
|
|
15
|
+
- **The memory worker reads `agents.memory` and runs lean** ([#424](https://github.com/dean0x/devflow/issues/424)). `background-memory-update` takes its model and effort from `agents.memory` (`devflow agents --set memory ...`), checking each field on its own and falling back to the shipped default for one it cannot use. A bounded `claude --version` probe picks the argv. On Claude Code 2.1.286 or later the worker runs with `--safe-mode`, `--tools Write`, an empty strict MCP config, `--max-turns 3` and an explicit `--effort`, with any inherited `CLAUDE_CODE_EFFORT_LEVEL` cleared so the effort it names is the one that applies; below that, or when the probe fails, it runs a legacy argv that still limits the worker to Write and keeps CLAUDE.md and auto-memory out, but passes no effort, safe mode, MCP isolation or turn cap. A staged file that cannot be removed stops the run before any model call, which is what makes a Write-only tool set safe.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Only closing keywords ship an issue in a release.** `Closes`, `Fixes` and `Resolves` ship an issue; a `Refs #N` commit mentions one without closing it, yet it reached the release's Shipped Issues, its back-links and its milestone. It no longer does. The release trace map still counts `Refs` as traced, because that map asks only whether a commit names an issue at all.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
8
23
|
## [3.2.0] - 2026-10-08
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -1517,6 +1532,7 @@ devflow init
|
|
|
1517
1532
|
---
|
|
1518
1533
|
|
|
1519
1534
|
[Unreleased]: https://github.com/dean0x/devflow/compare/v2.0.0...HEAD
|
|
1535
|
+
[3.3.0]: https://github.com/dean0x/devflow/compare/v3.2.0...v3.3.0
|
|
1520
1536
|
[3.2.0]: https://github.com/dean0x/devflow/compare/v3.1.0...v3.2.0
|
|
1521
1537
|
[3.1.0]: https://github.com/dean0x/devflow/compare/v3.0.1...v3.1.0
|
|
1522
1538
|
[3.0.1]: https://github.com/dean0x/devflow/compare/v3.0.0...v3.0.1
|
package/README.md
CHANGED
|
@@ -30,9 +30,9 @@ you: /implement (hand it the plan)
|
|
|
30
30
|
|
|
31
31
|
Git branch feat/42-rate-limit-upload
|
|
32
32
|
Code implements the plan — your learned decisions, pitfalls, and feature knowledge preloaded
|
|
33
|
-
Validate build ✓ typecheck ✓ lint ✓ tests ✓
|
|
34
33
|
Simplify · Scrutinize cleanup pass, then 9-pillar quality gate
|
|
35
34
|
Evaluate implementation matches the original request ✓
|
|
35
|
+
Validate build ✓ typecheck ✓ lint ✓ tests ✓
|
|
36
36
|
Test 5/5 QA scenarios pass → PR opened
|
|
37
37
|
|
|
38
38
|
you: /code-review
|
|
@@ -4,6 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import color from 'picocolors';
|
|
6
6
|
import { getLearningDir, getLearningTuningConfigPath, } from '../../core/project-paths.js';
|
|
7
|
+
import { loadShippedAgentDefaults } from '../../core/agent-models.js';
|
|
7
8
|
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
8
9
|
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
9
10
|
import { getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
@@ -273,15 +274,34 @@ export const CONFIGURE_SCOPE_OPTIONS = [
|
|
|
273
274
|
hint: 'All projects (~/.devflow/learning.json); a devflow agents Learning mapping takes precedence over it',
|
|
274
275
|
},
|
|
275
276
|
];
|
|
277
|
+
/** The models `devflow learning --configure` offers, each with what it is good for. */
|
|
278
|
+
const LEARNING_MODEL_CHOICES = [
|
|
279
|
+
{ value: 'opus', label: 'Opus', note: 'highest quality for detection + curation judgment' },
|
|
280
|
+
{ value: 'sonnet', label: 'Sonnet', note: 'good balance of quality and speed' },
|
|
281
|
+
{ value: 'haiku', label: 'Haiku', note: 'fastest, lowest cost' },
|
|
282
|
+
];
|
|
283
|
+
/**
|
|
284
|
+
* D-LEARNING-SHIPPED-ROW: the model offered as "Recommended" is the one the Learning
|
|
285
|
+
* agent ships with, the `model:` line of its frontmatter. It is read from there and
|
|
286
|
+
* written nowhere else, so a change of the shipped tier moves the recommendation with
|
|
287
|
+
* it and this list never has to be edited. A shipped model outside the three offered
|
|
288
|
+
* (or none) recommends nothing.
|
|
289
|
+
*/
|
|
290
|
+
export function learningModelOptions(shippedModel) {
|
|
291
|
+
return LEARNING_MODEL_CHOICES.map(choice => ({
|
|
292
|
+
value: choice.value,
|
|
293
|
+
label: choice.label,
|
|
294
|
+
hint: choice.value === shippedModel
|
|
295
|
+
? `Recommended — ${choice.note}`
|
|
296
|
+
: choice.note.charAt(0).toUpperCase() + choice.note.slice(1),
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
276
299
|
async function handleConfigure() {
|
|
277
300
|
p.intro(color.bgCyan(color.black(' Learning Configuration ')));
|
|
301
|
+
const shippedModel = (await loadShippedAgentDefaults()).learning?.model;
|
|
278
302
|
const model = await p.select({
|
|
279
303
|
message: 'Model for decision detection',
|
|
280
|
-
options:
|
|
281
|
-
{ value: 'opus', label: 'Opus', hint: 'Recommended — highest quality for detection + curation judgment' },
|
|
282
|
-
{ value: 'sonnet', label: 'Sonnet', hint: 'Good balance of quality and speed' },
|
|
283
|
-
{ value: 'haiku', label: 'Haiku', hint: 'Fastest, lowest cost' },
|
|
284
|
-
],
|
|
304
|
+
options: learningModelOptions(shippedModel),
|
|
285
305
|
});
|
|
286
306
|
if (p.isCancel(model)) {
|
|
287
307
|
p.cancel('Configuration cancelled.');
|
|
@@ -24,7 +24,7 @@ Run a comprehensive code review of the current branch by spawning parallel revie
|
|
|
24
24
|
|
|
25
25
|
1. **Discover reviewable worktrees** using the `devflow:worktree-support` skill discovery algorithm:
|
|
26
26
|
- Run `git worktree list --porcelain` → parse, filter (skip protected/detached/mid-rebase), dedup by branch, sort by recent commit
|
|
27
|
-
-
|
|
27
|
+
- Invoke the `devflow:worktree-support` skill, then Read its `references/discovery.md` from the skill's base directory for the full 7-step algorithm; the canonical protected branch list stays in the skill
|
|
28
28
|
2. **If `--path` flag provided:** use only that worktree, skip discovery
|
|
29
29
|
**`--path` validation**: Before proceeding, verify the path exists as a directory and appears in `git worktree list` output. If not: report error and stop.
|
|
30
30
|
3. **If only 1 reviewable worktree** (the common case): proceed as single-worktree flow — zero behavior change
|
package/dist/commands/debug.md
CHANGED
|
@@ -244,7 +244,7 @@ If the settings line says `KNOWLEDGE=off`, skip write-back entirely. The machine
|
|
|
244
244
|
**Step 2 — Evaluate whether write-back is warranted:**
|
|
245
245
|
|
|
246
246
|
Only proceed if **at least one** of these is true:
|
|
247
|
-
- This workflow changed files in a directory that is documented by an existing feature knowledge base (a documented area changed).
|
|
247
|
+
- This workflow changed files in a directory that is documented by an existing feature knowledge base (a documented area changed). Knowledge bases are written through at that point, never on a background schedule.
|
|
248
248
|
- This workflow surfaced durable, cross-cutting knowledge about a codebase area that would help future agents working in the same area — patterns, anti-patterns, integration points, gotchas not visible from a single file read.
|
|
249
249
|
|
|
250
250
|
**Never spawn unconditionally.** If neither condition is met, skip write-back silently.
|
|
@@ -322,7 +322,8 @@ if (BRANCH === "(none)") {
|
|
|
322
322
|
|
|
323
323
|
// Phase 2: Implement
|
|
324
324
|
await phase("implement", () =>
|
|
325
|
-
agent(`
|
|
325
|
+
agent(`OPERATION: implement
|
|
326
|
+
Implement the following ticket on branch ${BRANCH}:
|
|
326
327
|
|
|
327
328
|
${TICKET}
|
|
328
329
|
|
|
@@ -343,39 +344,51 @@ After implementing, commit your changes with a conventional-commit message and r
|
|
|
343
344
|
- Any open questions or blockers`, { agentType: "Code" })
|
|
344
345
|
);
|
|
345
346
|
|
|
346
|
-
// Phase 3: Gate 1 — post-code pipeline
|
|
347
|
+
// Phase 3: Gate 1 #1 — post-code pipeline: Simplify agent → Scrutinize agent → Validate agent.
|
|
348
|
+
// Validate runs last and unconditionally, so its one full run covers every commit the pass made (see gate1_postcode()).
|
|
347
349
|
const gate1 = await phase("gate1", async () => {
|
|
348
|
-
|
|
350
|
+
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
351
|
+
|
|
352
|
+
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
|
|
353
|
+
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
|
|
354
|
+
// 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.
|
|
355
|
+
if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
|
|
356
|
+
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" };
|
|
357
|
+
}
|
|
358
|
+
|
|
349
359
|
const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH}.
|
|
350
360
|
Follow your Running commands block for every build and test command.
|
|
351
|
-
|
|
361
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
352
362
|
|
|
353
|
-
|
|
354
|
-
|
|
363
|
+
// Anything but an explicit PASS is a failure: a Validate that returned no verdict has not validated the branch.
|
|
364
|
+
if (validation?.verdict !== "PASS") {
|
|
365
|
+
let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
|
|
366
|
+
// Up to 2 fix attempts, each followed by a Validate re-run
|
|
355
367
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
356
|
-
await agent(`
|
|
357
|
-
${
|
|
368
|
+
await agent(`OPERATION: validation-fix
|
|
369
|
+
Fix the validation failures on branch ${BRANCH}:
|
|
370
|
+
${failureDetails}
|
|
358
371
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
359
372
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
360
373
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
361
374
|
Commit fixes with conventional-commit message.`, { agentType: "Code" });
|
|
362
|
-
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH}
|
|
363
|
-
|
|
364
|
-
if (
|
|
375
|
+
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} after fix attempt ${attempt} (follow your Running commands block).
|
|
376
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
377
|
+
if (recheck?.verdict === "PASS") break;
|
|
378
|
+
failureDetails = recheck?.details || failureDetails;
|
|
379
|
+
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Validation exhausted after 2 Code agent fix attempts" };
|
|
365
380
|
}
|
|
366
381
|
}
|
|
367
382
|
|
|
368
|
-
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
369
|
-
|
|
370
|
-
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
|
|
371
|
-
|
|
372
|
-
if (scrutiny.codeChanged) {
|
|
373
|
-
await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (Scrutinize agent made changes). Report: PASS or FAIL.`, { agentType: "Validate" });
|
|
374
|
-
}
|
|
375
|
-
|
|
376
383
|
return { verdict: "PASS" };
|
|
377
384
|
});
|
|
378
385
|
|
|
386
|
+
// Gate 1 #1 stop, before Gate 2: an exhausted Validate or a BLOCKED Scrutinize leaves a broken or unfinished build,
|
|
387
|
+
// so Gate 2 and the review pass never run on it — a correctness stop, like the ticket-link and branch stops.
|
|
388
|
+
if (gate1.verdict === "ESCALATED") {
|
|
389
|
+
return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: gate1.type, description: gate1.reason }] };
|
|
390
|
+
}
|
|
391
|
+
|
|
379
392
|
// Phase 4: Gate 2 — acceptance gate (once, before review pass)
|
|
380
393
|
const gate2 = await phase("gate2", async () => {
|
|
381
394
|
if (!PLAN && !CRITERIA && !TEST_PLAN) {
|
|
@@ -384,21 +397,19 @@ const gate2 = await phase("gate2", async () => {
|
|
|
384
397
|
|
|
385
398
|
let evalVerdict = "SKIPPED";
|
|
386
399
|
if (PLAN) {
|
|
387
|
-
const
|
|
388
|
-
|
|
400
|
+
const evaluation = await agent(`Acceptance and scope check on branch ${BRANCH}. Answer both questions:
|
|
401
|
+
1. Does the implementation satisfy each numbered criterion, INCLUDING negative criteria (what it must NOT do)?
|
|
402
|
+
2. Did the Code agent introduce any unplanned changes, smuggled anti-features, or drift from the plan's intent?
|
|
389
403
|
Plan: ${PLAN}
|
|
390
404
|
Criteria: ${CRITERIA || "see plan"}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
Plan: ${PLAN}
|
|
394
|
-
Report: PASS or FAIL with rationale.`, { agentType: "Evaluate" }),
|
|
395
|
-
]);
|
|
396
|
-
evalVerdict = panel.every(p => p.verdict === "PASS") ? "PASS" : "FAIL";
|
|
405
|
+
Return: {"verdict": "PASS" | "FAIL", "rationale": "..."} — FAIL if either answer is no, naming the failing criterion or the drift.`, { agentType: "Evaluate" });
|
|
406
|
+
evalVerdict = evaluation?.verdict === "PASS" ? "PASS" : "FAIL";
|
|
397
407
|
if (evalVerdict === "FAIL") {
|
|
398
408
|
// fix-and-continue — no re-evaluate, no inline Gate 1. The final Gate 1 (#2)
|
|
399
409
|
// after the review pass is the build gate before the branch is handed back.
|
|
400
|
-
await agent(`
|
|
401
|
-
|
|
410
|
+
await agent(`OPERATION: alignment-fix
|
|
411
|
+
Fix the alignment issues identified by the Evaluate agent on branch ${BRANCH}:
|
|
412
|
+
${evaluation?.rationale || "see the Evaluate agent's report"}
|
|
402
413
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
403
414
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
404
415
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
@@ -417,7 +428,8 @@ Cover: functionality, API contracts, performance, and cover every TEST_PLAN scen
|
|
|
417
428
|
testVerdict = testResult.verdict;
|
|
418
429
|
if (testVerdict === "FAIL") {
|
|
419
430
|
// fix-and-continue — no re-test, no inline Gate 1.
|
|
420
|
-
await agent(`
|
|
431
|
+
await agent(`OPERATION: qa-fix
|
|
432
|
+
Fix the failing acceptance test scenarios on branch ${BRANCH}:
|
|
421
433
|
${testResult.failures}
|
|
422
434
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
423
435
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
@@ -444,7 +456,7 @@ const reviewResult = await phase("review", async () => {
|
|
|
444
456
|
const chunkSize = 5;
|
|
445
457
|
const reviewThunks = reviewFocuses.map(focus =>
|
|
446
458
|
() => agent(`${focus.charAt(0).toUpperCase() + focus.slice(1)} review. ${reviewScope}
|
|
447
|
-
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" })
|
|
459
|
+
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" })
|
|
448
460
|
);
|
|
449
461
|
|
|
450
462
|
let rawReviews = [];
|
|
@@ -461,7 +473,7 @@ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "fil
|
|
|
461
473
|
const focus = reviewFocuses[i];
|
|
462
474
|
if (!r || r.reviewed !== true) {
|
|
463
475
|
// Dead Review agent: retry once sequentially
|
|
464
|
-
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" });
|
|
476
|
+
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" });
|
|
465
477
|
if (!retry || retry.reviewed !== true) {
|
|
466
478
|
coverageGaps.push(focus);
|
|
467
479
|
log(`review coverage incomplete: ${focus}`);
|
|
@@ -526,13 +538,18 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
|
|
|
526
538
|
// Distinct-file groups are paced in chunks of FIX_CHUNK — same pacing bar as the Review spawn
|
|
527
539
|
// path — to avoid launching all Code agents at once on a large branch (reliability: explicit bound).
|
|
528
540
|
const FIX_CHUNK = 5;
|
|
529
|
-
|
|
541
|
+
// issue-fix SCOPE per finding: CRITICAL or HIGH takes the Careful protocol (failing regression test first); anything else is Standard.
|
|
542
|
+
const CAREFUL_SEVERITIES = new Set(["critical", "high"]);
|
|
543
|
+
const fixThunks = Object.values(fileGroups).map(findings => async () => {
|
|
530
544
|
const chunkResults = [];
|
|
531
545
|
for (let i = 0; i < findings.length; i += MAX_BATCH) {
|
|
532
546
|
const chunk = findings.slice(i, i + MAX_BATCH);
|
|
533
|
-
const r = await agent(`
|
|
534
|
-
|
|
535
|
-
|
|
547
|
+
const r = await agent(`OPERATION: issue-fix
|
|
548
|
+
Fix the following confirmed review findings on branch ${BRANCH}:
|
|
549
|
+
ISSUES:
|
|
550
|
+
${chunk.map((f, n) => `${n + 1}. ${f.file ? `${f.file}${f.line ? `:${f.line}` : ""} — ` : ""}${f.description} (${f.severity})`).join("\n")}
|
|
551
|
+
SCOPE: ${chunk.map((f, n) => `${n + 1}=${CAREFUL_SEVERITIES.has(String(f.severity).toLowerCase()) ? "Careful" : "Standard"}`).join(", ")}
|
|
552
|
+
PUSH: false
|
|
536
553
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
537
554
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
538
555
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
@@ -571,38 +588,40 @@ Return: {"status": "fixed"|"blocked", "commitShas": ["<sha>"], "unresolved": ["<
|
|
|
571
588
|
return { survivingFindings: notAddressed, fixedFindings, coverageGaps };
|
|
572
589
|
});
|
|
573
590
|
|
|
574
|
-
// Phase 5.5: Gate 1 #2 — FINAL post-fix gate (
|
|
591
|
+
// Phase 5.5: Gate 1 #2 — FINAL post-fix gate (Simplify agent → Scrutinize agent → full Validate agent, Validate last and unconditional).
|
|
575
592
|
// Runs ONCE, after the review pass. Inside the pass the Code agent self-verified its own fixes;
|
|
576
593
|
// this is the build gate before the branch is handed back. See gate1_postcode() cadence.
|
|
577
594
|
const gate1Final = await phase("gate1-final", async () => {
|
|
595
|
+
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
596
|
+
|
|
597
|
+
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
|
|
598
|
+
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
|
|
599
|
+
if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
|
|
600
|
+
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" };
|
|
601
|
+
}
|
|
602
|
+
|
|
578
603
|
const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH} (final gate after all fixing).
|
|
579
604
|
Follow your Running commands block for every build and test command.
|
|
580
|
-
|
|
605
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
581
606
|
|
|
582
|
-
if (validation
|
|
583
|
-
let failureDetails = validation
|
|
607
|
+
if (validation?.verdict !== "PASS") {
|
|
608
|
+
let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
|
|
584
609
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
585
|
-
await agent(`
|
|
610
|
+
await agent(`OPERATION: validation-fix
|
|
611
|
+
Fix the final validation failures on branch ${BRANCH}:
|
|
586
612
|
${failureDetails}
|
|
587
613
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
588
614
|
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
589
615
|
COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
|
|
590
616
|
Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "Code" });
|
|
591
|
-
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (follow your Running commands block).
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
617
|
+
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (final gate, after fix attempt ${attempt}; follow your Running commands block).
|
|
618
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
619
|
+
if (recheck?.verdict === "PASS") break;
|
|
620
|
+
failureDetails = recheck?.details || failureDetails;
|
|
621
|
+
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Final Gate 1 validation exhausted after 2 Code agent fix attempts" };
|
|
595
622
|
}
|
|
596
623
|
}
|
|
597
624
|
|
|
598
|
-
await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
|
|
599
|
-
|
|
600
|
-
const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
|
|
601
|
-
|
|
602
|
-
if (scrutiny.codeChanged) {
|
|
603
|
-
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" });
|
|
604
|
-
}
|
|
605
|
-
|
|
606
625
|
return { verdict: "PASS" };
|
|
607
626
|
});
|
|
608
627
|
|
|
@@ -631,7 +650,7 @@ Write a concise report. Present only surviving findings as outstanding — never
|
|
|
631
650
|
survivingFindings: reviewResult.survivingFindings || [], fixedFindings: reviewResult.fixedFindings || [],
|
|
632
651
|
reviewCoverage: { failedFocuses: coverageGaps, complete: coverageGaps.length === 0 },
|
|
633
652
|
escalations: [
|
|
634
|
-
...(gate1Final.verdict === "ESCALATED" ? [{ type: "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
|
|
653
|
+
...(gate1Final.verdict === "ESCALATED" ? [{ type: gate1Final.type || "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
|
|
635
654
|
...coverageGaps.map(focus => ({ type: "review-coverage-incomplete", description: `review coverage incomplete: ${focus}` })),
|
|
636
655
|
],
|
|
637
656
|
gate2, report,
|
|
@@ -679,12 +698,12 @@ Run builds, typechecks, lints and tests in the foreground, each with an explicit
|
|
|
679
698
|
The standard implementation unit for one ticket. Run in order:
|
|
680
699
|
|
|
681
700
|
```
|
|
682
|
-
Code(agentType:"Code", prompt: full task + plan + DECISIONS_CONTEXT + handoff if sequential)
|
|
701
|
+
Code(agentType:"Code", prompt: "OPERATION: implement" first line, then full task + plan + DECISIONS_CONTEXT + handoff if sequential)
|
|
683
702
|
→ gate1_postcode()
|
|
684
703
|
→ gate2_acceptance() ← Gate 2 runs HERE — before the review pass, not after
|
|
685
704
|
```
|
|
686
705
|
|
|
687
|
-
|
|
706
|
+
Every Code agent prompt opens with `OPERATION: <mode>` as its first line (`implement`, `issue-fix`, `validation-fix`, `alignment-fix` or `qa-fix`). It must include: task description, implementation plan (if one exists), relevant DECISIONS_CONTEXT (the index you loaded before authoring), the compliance lens (`COMPLIANCE_FRAMEWORKS` — every Code prompt carries it, fix prompts included), and any PRIOR_PHASE_SUMMARY / HANDOFF_FILE for sequential multi-phase tickets.
|
|
688
707
|
|
|
689
708
|
Gate 2 runs at implementation acceptance — this matches devflow's deliberate placement: "evaluation is part of implementation acceptance, not post-review" (§6.1).
|
|
690
709
|
|
|
@@ -692,21 +711,21 @@ Gate 2 runs at implementation acceptance — this matches devflow's deliberate p
|
|
|
692
711
|
|
|
693
712
|
ORDER IS LOAD-BEARING. Run exactly in this sequence:
|
|
694
713
|
|
|
695
|
-
1. **
|
|
714
|
+
1. **Simplify agent** — reduce complexity, remove duplication
|
|
715
|
+
2. **Scrutinize agent** — 9-pillar self-review (deep structural analysis); returns `{"status": "PASS" | "FIXED" | "BLOCKED"}`
|
|
716
|
+
- BLOCKED (a P0 issue cannot be fixed in scope), or a missing or unrecognised status, which counts as BLOCKED → stop the pass and escalate as `scrutiny-blocked`; Validate does not run on code Scrutinize could not accept
|
|
717
|
+
3. **Validate agent** — build / typecheck / lint / test; returns `{"verdict": "PASS" | "FAIL", "details": "..."}`. It runs LAST and UNCONDITIONALLY: one full run over the HEAD that Simplify and Scrutinize left, so it covers their commits whether or not Scrutinize changed code
|
|
696
718
|
- Build/test commands follow `build_execution_doctrine()`: the Validate agent's Running commands block, foreground under an explicit Bash timeout.
|
|
697
|
-
- FAIL → Code agent fix (max 2 retries)
|
|
698
|
-
- If still FAIL after 2 retries → escalate (do not loop endlessly)
|
|
699
|
-
2. **Simplify agent** — reduce complexity, remove duplication
|
|
700
|
-
3. **Scrutinize agent** — 9-pillar self-review (deep structural analysis)
|
|
701
|
-
- If Scrutinize agent changed code → re-run Validate agent (verify the Scrutinize agent's edits compile/pass)
|
|
719
|
+
- FAIL → Code agent fix (max 2 retries), each followed by a Validate agent re-run
|
|
720
|
+
- If still FAIL after 2 retries → escalate as `validation-exhausted` (do not loop endlessly)
|
|
702
721
|
|
|
703
722
|
**Gate 1 contains NO Evaluate agent and NO Test agent.** Those are Gate 2 only.
|
|
704
723
|
|
|
705
724
|
Depth scales to change size + budget: a trivial one-line fix warrants a lighter pass; a multi-file refactor warrants the full depth.
|
|
706
725
|
|
|
707
726
|
**Cadence — Gate 1 runs at exactly TWO points per ticket:**
|
|
708
|
-
1. **Gate 1 #1** — immediately after the initial Code agent implementation (inside `implement_bundle()`).
|
|
709
|
-
2. **Gate 1 #2** — the FINAL gate, after ALL Gate-2 fixes AND the entire review pass have completed.
|
|
727
|
+
1. **Gate 1 #1** — immediately after the initial Code agent implementation (inside `implement_bundle()`). An escalated result — Validate exhausted, or Scrutinize BLOCKED — returns early: the ticket's engine returns `ESCALATED` with one escalation, and Gate 2 and the review pass never run on a broken or unfinished build.
|
|
728
|
+
2. **Gate 1 #2** — the FINAL gate, after ALL Gate-2 fixes AND the entire review pass have completed. Scrutinize BLOCKED here is `ESCALATED` as well.
|
|
710
729
|
|
|
711
730
|
It does NOT run inside the review pass, nor after each individual Gate-2 / review / QA fix. At those points the fixing Code agent self-verifies its OWN build compiles (see `review_pass()` and the Code agent's Running commands block). The final Gate 1 #2 is the invariant that all written code passes before merge.
|
|
712
731
|
|
|
@@ -716,30 +735,27 @@ Gate 2 fires ONCE: after the implement-bundle and BEFORE the review pass. It doe
|
|
|
716
735
|
|
|
717
736
|
Gate 2 inputs are produced by `/devflow:dynamic-plan`'s plan-challenge step — the acceptance criteria and test plan written for the Evaluate agent and Test agent.
|
|
718
737
|
|
|
719
|
-
**Evaluate agent
|
|
720
|
-
- Run `evaluator_panel()` — see that block for the
|
|
721
|
-
- If
|
|
738
|
+
**Evaluate agent** (only if a plan exists):
|
|
739
|
+
- Run `evaluator_panel()` — see that block for the one spawn and the two lenses it names
|
|
740
|
+
- If the Evaluate agent returns FAIL (either lens failed): fix-and-continue — the demanded fixes are applied by a Code agent that self-verifies its own build (batched per the review-pass batching doctrine if numerous). The recorded verdict becomes `FAIL-FIXED` (issues found, fixes applied, not re-evaluated by design); Gate 2 then proceeds. In SINGLE mode the run reports it as `UNVERIFIED`, never PASS.
|
|
722
741
|
|
|
723
742
|
**Test agent** (only if acceptance criteria or a test plan exist):
|
|
724
743
|
- Scenario-based acceptance tests covering functionality, API contracts, performance
|
|
725
744
|
- FAIL → fix-and-continue — a Code agent applies the demanded fixes and self-verifies its own build. The recorded verdict becomes `FAIL-FIXED`; Gate 2 then proceeds. In SINGLE mode the run reports it as `UNVERIFIED`, never PASS.
|
|
726
745
|
|
|
727
746
|
**When Gate 2 inputs are absent:**
|
|
728
|
-
- No plan → skip Evaluate agent
|
|
747
|
+
- No plan → skip the Evaluate agent silently (note in output: "Gate 2 Evaluate agent skipped — no plan available")
|
|
729
748
|
- No acceptance criteria and no test plan → skip Test agent silently (note in output: "Gate 2 Test agent skipped — no criteria available")
|
|
730
749
|
- Build proceeds Gate-1-only. Never refuse to build; never force-generate fake criteria. Trust the user.
|
|
731
750
|
|
|
732
|
-
### Evaluate agent
|
|
751
|
+
### Evaluate agent spawn (one agent, two lenses)
|
|
733
752
|
|
|
734
|
-
Run
|
|
753
|
+
Run ONE Evaluate agent whose prompt names both lenses, so a ticket pays for one spawn and one read of the plan:
|
|
735
754
|
|
|
736
|
-
1. **Acceptance
|
|
737
|
-
2. **Scope / intent
|
|
738
|
-
3. **Cross-ticket-consistency evaluator** — (wave-only; skip for single-ticket runs) "Does this ticket honor the API contracts and invariants that other wave tickets depend on?"
|
|
755
|
+
1. **Acceptance criteria** — "Does the implementation satisfy each numbered acceptance criterion, INCLUDING the negative criteria (what it must NOT do)?"
|
|
756
|
+
2. **Scope / intent drift** — "Did the Code agent smuggle in unplanned changes, deviate from the plan's intent, or introduce anti-features?"
|
|
739
757
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
Keep the panel to 2–3 agents. Do not spawn redundant agents asking the same question — that is cost with no diversity benefit.
|
|
758
|
+
It returns `{"verdict": "PASS" | "FAIL", "rationale": "..."}`: FAIL when either lens fails, the rationale naming the failing criterion or the drift. A FAIL on either lens blocks acceptance. A wave ticket runs this same skeleton through `runSingleTicketEngine`, so it gets the same single spawn.
|
|
743
759
|
|
|
744
760
|
### Acceptance criteria + test plan contract
|
|
745
761
|
|
|
@@ -784,7 +800,7 @@ Every line of a `## Test Plan` section, or of a PR's test-plan block, follows th
|
|
|
784
800
|
|
|
785
801
|
#### Consumption by Gate 2
|
|
786
802
|
|
|
787
|
-
The Evaluate agent
|
|
803
|
+
The Evaluate agent receives: the per-ticket plan + the numbered acceptance criteria (positive and negative).
|
|
788
804
|
|
|
789
805
|
The Test agent receives: the test plan's TP lines, once `check tp` has admitted them.
|
|
790
806
|
|
|
@@ -846,7 +862,7 @@ If survivors remain: batch the confirmed findings for fixing: group findings by
|
|
|
846
862
|
|
|
847
863
|
1. **Code is written ONLY by Code agents.** No other agent type writes code — not Review agent, not Evaluate agent.
|
|
848
864
|
2. **Findings are verified before any fix is written.** The adversarial verification step is not optional; unverified findings are not passed to the Code agent.
|
|
849
|
-
3. **All written code passes Gate 1.** No code merge, commit, or handoff before
|
|
865
|
+
3. **All written code passes Gate 1.** No code merge, commit, or handoff before Simplify agent + Scrutinize agent + Validate agent (in that order — Validate last, so its one run covers the commits of the two before it).
|
|
850
866
|
4. **Gate 2 runs once, at implementation acceptance.** It does not re-run after review-fixes.
|
|
851
867
|
5. **NEVER auto-merge to main or master.** All merges target the integration branch. The user merges to main themselves.
|
|
852
868
|
6. **No unauthorized tracker or remote side-effects.** Sub-agents NEVER create issues/PRs on the tracker, comment on them, or push beyond the ticket-authorized branch unless the ticket, plan, or user explicitly authorizes that exact action. This applies to whatever tracker is resolved, not to one vendor. Proposed follow-ups go in the run report.
|
|
@@ -888,7 +904,7 @@ Each ticket engine run returns a structured result. The Synthesize agent or the
|
|
|
888
904
|
},
|
|
889
905
|
"escalations": [
|
|
890
906
|
{
|
|
891
|
-
"type": "merge-conflict | gate2-fail | validation-exhausted | ambiguous-resolution | review-coverage-incomplete | dependency-blocked | engine-crash | ticket-link-missing | branch-missing",
|
|
907
|
+
"type": "merge-conflict | gate2-fail | validation-exhausted | scrutiny-blocked | ambiguous-resolution | review-coverage-incomplete | dependency-blocked | engine-crash | ticket-link-missing | branch-missing",
|
|
892
908
|
"description": "string"
|
|
893
909
|
}
|
|
894
910
|
],
|
|
@@ -947,8 +963,8 @@ For each ready ticket (sequentially by default; parallel only past the §7.1 bar
|
|
|
947
963
|
- Branch setup: the engine's setup-task creates the ticket's branch off integration HEAD at ready-time (so it already contains merged deps); every later phase, and the merge, uses the branch setup-task created, and a setup-task that reports none stops the ticket before implementing
|
|
948
964
|
- Run the single-ticket engine inside a try/catch — one ticket's crash/stall never kills the wave; catch the exception, quarantine that ticket, and continue with the remaining ready set
|
|
949
965
|
- The engine gets the ticket's own reference, the one the pre-fetch printed, as its setup-task input — never the wave's tracking issue
|
|
950
|
-
- On engine PASS or UNVERIFIED: merge to integration branch,
|
|
951
|
-
-
|
|
966
|
+
- On engine PASS or UNVERIFIED: merge to the integration branch locally through Git (no push, no build or test), then the workflow spawns the Validate agent (build + test) over the merge. The merge counts as kept only when that Validate returns PASS
|
|
967
|
+
- Validate FAIL or no PASS (build red after merge): the workflow spawns Git to undo the merge, quarantines the ticket, marks it as escalated, and continues. If the undo is refused, the wave stops taking merges and rounds
|
|
952
968
|
- On any other verdict (PARTIAL, FAIL, ESCALATED) or none: quarantine ticket, do not block independent siblings
|
|
953
969
|
|
|
954
970
|
**Cascade quarantine:** when a ticket is quarantined for any reason (Gate-1 exhausted, engine crash/stall, build-red after merge, review coverage incomplete after retry), the quarantine cascades to its direct and transitive dependents — each is marked blocked with the named reason, naming the blocker by its `{ISSUE_REF}` (e.g. "blocked: depends on {ISSUE_REF} which failed Gate-1"). Independent siblings are never affected. The quarantined list is injected into every subsequent Design agent reader prompt so the reader never schedules dependents of failed tickets.
|
|
@@ -975,7 +991,9 @@ MAX_ROUNDS = LLM judgment based on ticket count (heuristic: ticket_count * 2 + 5
|
|
|
975
991
|
|
|
976
992
|
**Parallel independent tickets:** each gets its own `git worktree add` + durable branch managed by the Git agent. Use explicit `git worktree add` — NOT the Workflow tool's ephemeral `isolation:'worktree'`. The branch must persist across implement → review → resolve → merge stages; ephemeral worktrees are gone when the agent call ends.
|
|
977
993
|
|
|
978
|
-
**Post-merge validation:** after EVERY merge into the integration branch,
|
|
994
|
+
**Post-merge validation:** after EVERY merge into the integration branch, the workflow itself spawns the Validate agent (build + test) over the integration branch; Git runs no build or test. A red build immediately after merge is the cheapest possible conflict detector. It always runs — the merge reports `treeEqual` (whether the merge commit's tree equals the ticket branch head's tree) and the wave row records it, but a true value never skips the Validate.
|
|
995
|
+
|
|
996
|
+
**Undo of a red merge:** on FAIL or no PASS verdict the workflow spawns Git to undo the merge. The undo runs only when no remote branch contains the merge commit and the integration HEAD still equals it, as `git reset --keep {mergeSha}^1` on the integration branch — never `--hard`, never a push. It returns `{"undone": true}` or `{"undone": false, "reason": "..."}`. Undone → the row reads `merged: false` and the ticket is quarantined ("post-merge build red; merge undone"). Not undone → the wave stops taking merges and rounds, and its report names the red integration HEAD.
|
|
979
997
|
|
|
980
998
|
**Commit discipline:** Git agent creates atomic commits per logical change, conventional-commit format, on the ticket branch before merge.
|
|
981
999
|
|
|
@@ -986,13 +1004,13 @@ Two parallel sibling tickets can produce real git conflicts. The resolution is *
|
|
|
986
1004
|
**Resolution procedure:**
|
|
987
1005
|
|
|
988
1006
|
1. Git agent detects the conflict and reports the conflicting files + sections
|
|
989
|
-
2. Spawn a Code agent with FULL intent context:
|
|
1007
|
+
2. Spawn a Code agent whose prompt opens with `OPERATION: implement` and carries `COMPLIANCE_FRAMEWORKS`, with FULL intent context:
|
|
990
1008
|
- Both ticket descriptions and plans
|
|
991
1009
|
- The conflicting diff sections (both sides)
|
|
992
1010
|
- Relevant ADRs from DECISIONS_CONTEXT (loaded by main model before authoring)
|
|
993
1011
|
3. Code agent resolves to PRESERVE BOTH INTENTS — the resolution must honor what both tickets were trying to achieve
|
|
994
1012
|
4. If the correct resolution is NOT UNAMBIGUOUS from the intent context: **do NOT guess** → quarantine + escalate
|
|
995
|
-
5. After any resolution: Validate
|
|
1013
|
+
5. After any resolution: the post-merge Validate covers it — the workflow spawns it after the merge, and no Validate is requested from Git
|
|
996
1014
|
|
|
997
1015
|
**Conservative-or-escalate is absolute.** An LLM silently guessing a wrong merge is the highest-danger failure mode in the whole design. When in doubt: quarantine + surface in report. The user re-runs (resume) with the escalated context.
|
|
998
1016
|
|
|
@@ -1007,7 +1025,7 @@ A workflow cannot pause mid-run (F4). "Escalate" means: quarantine-and-continue
|
|
|
1007
1025
|
- Ticket engine FAIL after max retries (Gate 1 exhausted)
|
|
1008
1026
|
- Gate 2 FAIL after max retries (Evaluate agent/Test agent not satisfied)
|
|
1009
1027
|
- Circular dependency detected (all remaining tickets blocked on each other)
|
|
1010
|
-
- Build red after merge (Validate agent fails
|
|
1028
|
+
- Build red after merge (the post-merge Validate agent fails: the merge is undone and the ticket quarantined; a refused undo halts the wave)
|
|
1011
1029
|
- Review coverage incomplete after retry (a focus area failed to produce a live Review agent result after the retry)
|
|
1012
1030
|
- Ticket engine crash/stall (unrecoverable exception or watchdog kill — quarantine cascades to dependents)
|
|
1013
1031
|
- No ticket link while issues are required (the engine stops before implementing)
|
|
@@ -1025,7 +1043,7 @@ A workflow cannot pause mid-run (F4). "Escalate" means: quarantine-and-continue
|
|
|
1025
1043
|
|
|
1026
1044
|
**Wave workflow structure (author after the SINGLE engine blocks above):**
|
|
1027
1045
|
|
|
1028
|
-
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
|
|
1046
|
+
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.
|
|
1029
1047
|
|
|
1030
1048
|
Wave skeleton — compact reference (see `wave_loop()` doctrine for full semantics):
|
|
1031
1049
|
|
|
@@ -1036,11 +1054,12 @@ Wave skeleton — compact reference (see `wave_loop()` doctrine for full semanti
|
|
|
1036
1054
|
const WAVE_TICKETS = [...remainingTickets]; // the pre-fetch's refs, in input order — the wave block's row order
|
|
1037
1055
|
const results = {}; // ticketId → its row of the workflow's return; a ticket with no entry never ran
|
|
1038
1056
|
// A row from an engine result: the fields step 3 after the workflow renders
|
|
1039
|
-
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 });
|
|
1057
|
+
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 });
|
|
1040
1058
|
const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
|
|
1041
|
-
const
|
|
1059
|
+
const MERGE_SHA = /^[0-9a-f]{40}$/; // a merge commit as git prints it: the only Git-reported value that reaches a later prompt
|
|
1060
|
+
const waveState = { quarantined: [], round: 0, halted: null };
|
|
1042
1061
|
let reAskedThisDeadlock = false; // re-ask guard: re-ask once on empty ready-set, then escalate
|
|
1043
|
-
while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS) {
|
|
1062
|
+
while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS && waveState.halted === null) {
|
|
1044
1063
|
waveState.round++;
|
|
1045
1064
|
|
|
1046
1065
|
// Design agent reader (opus) applies vacuous-truth rule: a ticket with no unmet dependencies is ALWAYS ready.
|
|
@@ -1073,10 +1092,36 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
|
|
|
1073
1092
|
// Check both verdict (engine_output_schema) and overallVerdict (SINGLE skeleton alias).
|
|
1074
1093
|
// PASS and UNVERIFIED merge; PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
|
|
1075
1094
|
if (["PASS", "UNVERIFIED"].includes(engineResult.verdict || engineResult.overallVerdict)) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1095
|
+
// Git merges locally and validates nothing: the workflow owns the post-merge Validate (D-WAVE-MERGE-VALIDATE).
|
|
1096
|
+
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.
|
|
1097
|
+
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" });
|
|
1098
|
+
let kept = false;
|
|
1099
|
+
let reason = merge?.reason || "merge failed";
|
|
1100
|
+
if (merge?.merged === true && !MERGE_SHA.test(String(merge.mergeSha))) {
|
|
1101
|
+
// A merge may sit on the integration branch with no SHA to validate or undo it by: stop taking merges.
|
|
1102
|
+
waveState.halted = { ticket: ticketId, mergeSha: null, reason: "merge reported without a 40-hex mergeSha; the integration branch may hold an unvalidated merge" };
|
|
1103
|
+
reason = waveState.halted.reason;
|
|
1104
|
+
} else if (merge?.merged === true) {
|
|
1105
|
+
// A merge counts as kept only when this Validate returns PASS. It always runs: treeEqual is recorded, never a reason to skip.
|
|
1106
|
+
const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
|
|
1107
|
+
Follow your Running commands block for every build and test command.
|
|
1108
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
1109
|
+
if (post?.verdict === "PASS") {
|
|
1110
|
+
kept = true;
|
|
1111
|
+
} else {
|
|
1112
|
+
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
|
|
1113
|
+
Return: {"undone": true} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
|
|
1114
|
+
if (undo?.undone === true) {
|
|
1115
|
+
reason = "post-merge build red; merge undone";
|
|
1116
|
+
} else {
|
|
1117
|
+
// A merge that stays red would poison every later ticket: stop taking merges and rounds, and name the red HEAD.
|
|
1118
|
+
reason = `post-merge build red; merge not undone (${undo?.reason || "no reason given"})`;
|
|
1119
|
+
waveState.halted = { ticket: ticketId, mergeSha: merge.mergeSha, reason };
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
results[ticketId] = rowOf(ticketId, engineResult, kept, merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null);
|
|
1124
|
+
if (!kept) waveState.quarantined.push({ ticket: ticketId, reason });
|
|
1080
1125
|
} else {
|
|
1081
1126
|
results[ticketId] = rowOf(ticketId, engineResult, false);
|
|
1082
1127
|
waveState.quarantined.push({ ticket: ticketId, reason: engineResult.escalations?.[0]?.description || "engine fail/escalated" });
|
|
@@ -1087,11 +1132,12 @@ Return: {"merged": true} — or {"merged": false, "reason": "<why>"} when the me
|
|
|
1087
1132
|
waveState.quarantined.push({ ticket: ticketId, reason: `engine crash: ${String(err)}` });
|
|
1088
1133
|
}
|
|
1089
1134
|
remainingTickets = remainingTickets.filter(t => t !== ticketId);
|
|
1135
|
+
if (waveState.halted !== null) break; // a red integration HEAD takes no further merge in this round
|
|
1090
1136
|
}
|
|
1091
1137
|
}
|
|
1092
1138
|
|
|
1093
1139
|
// After the wave report is written: one row per wave ticket, in input order. No entry ⇒ never ran (cascade, deadlock, MAX_ROUNDS).
|
|
1094
|
-
return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, issuePrLink: "(none)" }), quarantined: waveState.quarantined };
|
|
1140
|
+
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 };
|
|
1095
1141
|
```
|
|
1096
1142
|
|
|
1097
1143
|
---
|
|
@@ -1115,6 +1161,7 @@ A workflow cannot pause mid-run. After the build/wave workflow returns, you (the
|
|
|
1115
1161
|
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.
|
|
1116
1162
|
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.
|
|
1117
1163
|
- **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.
|
|
1164
|
+
- **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.
|
|
1118
1165
|
- **Nothing merged** (no entry has `merged: true`): record `Wave PR: skipped (nothing merged)` and go to step 4.
|
|
1119
1166
|
- Otherwise compose (a) and then (b).
|
|
1120
1167
|
|