devflow-kit 3.5.0 → 3.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 +178 -1
- package/README.md +10 -6
- package/dist/agents/code.md +7 -11
- package/dist/agents/{diagnose.md → debug.md} +4 -4
- package/dist/agents/{code-fix.md → fix.md} +7 -8
- package/dist/agents/{scrutinize.md → harden.md} +6 -6
- package/dist/agents/{code-edit.md → refactor.md} +8 -9
- package/dist/agents/review.md +2 -0
- package/dist/cli/agents-view/render.js +1 -1
- package/dist/cli/commands/gates.js +276 -0
- package/dist/cli/commands/init-seed.js +9 -1
- package/dist/cli/commands/init.js +34 -11
- package/dist/cli/commands/learning.js +12 -12
- package/dist/cli/commands/legacy-hooks.js +1 -1
- package/dist/cli/commands/memory.js +18 -0
- package/dist/cli/commands/uninstall.js +111 -5
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +21 -17
- package/dist/commands/code-review.md +65 -66
- package/dist/commands/debug.md +17 -17
- package/dist/commands/dynamic-build.md +104 -116
- package/dist/commands/dynamic-plan.md +13 -11
- package/dist/commands/dynamic-profile.md +9 -7
- package/dist/commands/dynamic-tickets.md +13 -11
- package/dist/commands/explore.md +15 -15
- package/dist/commands/implement.md +80 -154
- package/dist/commands/plan.md +34 -66
- package/dist/commands/release.md +11 -7
- package/dist/commands/research.md +14 -10
- package/dist/commands/resolve.md +44 -88
- package/dist/commands/self-review.md +44 -44
- package/dist/core/agent-models.js +47 -11
- package/dist/core/{learning-tuning-config.js → curate-tuning-config.js} +14 -14
- package/dist/core/echo-value.js +42 -0
- package/dist/core/evidence-policy.js +17 -5
- package/dist/core/gate-level.js +213 -0
- package/dist/core/learning-queue-cleanup.js +1 -1
- package/dist/core/manifest.js +26 -10
- package/dist/core/migrations.js +63 -44
- package/dist/core/model-discovery.js +7 -1
- package/dist/core/plugins.js +14 -12
- package/dist/core/project-paths.js +3 -3
- package/dist/core/tracker.js +5 -33
- package/dist/hud/codex-usage.js +247 -0
- package/dist/hud/components/codex-quota.js +37 -0
- package/dist/hud/components/usage-quota.js +1 -1
- package/dist/hud/config.js +1 -0
- package/dist/hud/index.js +16 -3
- package/dist/hud/render.js +3 -1
- package/dist/learning-off/agents/code.md +7 -11
- package/dist/learning-off/agents/{diagnose.md → debug.md} +4 -4
- package/dist/learning-off/agents/{code-fix.md → fix.md} +7 -8
- package/dist/learning-off/agents/{scrutinize.md → harden.md} +6 -6
- package/dist/learning-off/agents/{code-edit.md → refactor.md} +8 -9
- package/dist/learning-off/agents/review.md +2 -0
- package/dist/learning-off/commands/bug-analysis.md +9 -9
- package/dist/learning-off/commands/code-review.md +54 -59
- package/dist/learning-off/commands/debug.md +6 -10
- package/dist/learning-off/commands/dynamic-build.md +98 -112
- package/dist/learning-off/commands/dynamic-plan.md +5 -5
- package/dist/learning-off/commands/dynamic-profile.md +1 -1
- package/dist/learning-off/commands/dynamic-tickets.md +5 -5
- package/dist/learning-off/commands/explore.md +4 -8
- package/dist/learning-off/commands/implement.md +68 -146
- package/dist/learning-off/commands/plan.md +23 -58
- package/dist/learning-off/commands/release.md +4 -2
- package/dist/learning-off/commands/research.md +2 -2
- package/dist/learning-off/commands/resolve.md +33 -81
- package/dist/learning-off/commands/self-review.md +32 -36
- package/dist/skills/git/references/pr/check-merge-readiness.md +2 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +4 -6
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +7 -9
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +7 -15
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +7 -15
- package/dist/targets/claude-code/installer.js +117 -84
- package/dist/targets/claude-code/learning-install.js +15 -3
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +1 -1
- package/src/assets/agents/_partials/_code.mds +7 -8
- package/src/assets/agents/code.mds +4 -7
- package/src/assets/agents/{learning.md → curate.md} +8 -10
- package/src/assets/agents/{diagnose.mds → debug.mds} +4 -4
- package/src/assets/agents/evaluate.md +4 -4
- package/src/assets/agents/{code-fix.mds → fix.mds} +5 -5
- package/src/assets/agents/{scrutinize.mds → harden.mds} +6 -6
- package/src/assets/agents/{simplify.md → polish.md} +4 -4
- package/src/assets/agents/{code-edit.mds → refactor.mds} +6 -6
- package/src/assets/agents/review.mds +2 -0
- package/src/assets/agents/synthesize.md +2 -2
- package/src/assets/agents/test.md +2 -2
- package/src/assets/commands/_partials/_compliance.mds +1 -1
- package/src/assets/commands/_partials/_decisions.mds +26 -6
- package/src/assets/commands/_partials/_engine.mds +15 -15
- package/src/assets/commands/_partials/_evidence_policy.mds +0 -17
- package/src/assets/commands/_partials/_knowledge.mds +14 -9
- package/src/assets/commands/_partials/_preamble.mds +4 -2
- package/src/assets/commands/_partials/_roster.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_wave.mds +10 -17
- package/src/assets/commands/bug-analysis.mds +7 -7
- package/src/assets/commands/code-review.mds +51 -56
- package/src/assets/commands/debug.mds +2 -2
- package/src/assets/commands/dynamic-build.mds +67 -74
- package/src/assets/commands/implement.mds +65 -127
- package/src/assets/commands/plan.mds +21 -61
- package/src/assets/commands/release.mds +5 -1
- package/src/assets/commands/resolve.mds +28 -76
- package/src/assets/commands/self-review.mds +27 -27
- package/src/assets/mds/git/_ops_steps.mds +3 -3
- package/src/assets/mds/git/_pr.mds +6 -6
- package/src/assets/mds/tracker/_common.mds +23 -12
- package/src/assets/mds/tracker/_github.mds +2 -9
- package/src/assets/mds/tracker/_jira.mds +2 -15
- package/src/assets/mds/tracker/_linear.mds +2 -15
- package/src/assets/scripts/ci-wait.cjs +22 -4
- package/src/assets/scripts/claude-md-audit.cjs +66 -51
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
- package/src/assets/scripts/hooks/background-memory-update +404 -95
- package/src/assets/scripts/hooks/capture-prompt +1 -1
- package/src/assets/scripts/hooks/capture-question +1 -1
- package/src/assets/scripts/hooks/capture-turn +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +13 -2
- package/src/assets/scripts/hooks/json-parse +18 -3
- package/src/assets/scripts/hooks/lib/learning-store.cjs +1 -1
- package/src/assets/scripts/hooks/lib/project-paths.cjs +4 -4
- package/src/assets/scripts/hooks/memory-permission +358 -0
- package/src/assets/scripts/hooks/preamble +1 -1
- package/src/assets/scripts/hooks/session-start-context +60 -47
- package/src/assets/scripts/hooks/session-start-memory +26 -5
- package/src/assets/scripts/lib/project-config.cjs +19 -4
- package/src/assets/scripts/resolve-settings.cjs +200 -32
- package/src/assets/scripts/verify-evidence.cjs +726 -9
- package/src/assets/skills/agent-references/SKILL.md +14 -6
- package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
- package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +35 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +15 -0
- package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +1 -1
- package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
- package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
- package/src/assets/skills/apply-decisions/SKILL.md +6 -4
- package/src/assets/skills/docs-framework/SKILL.md +3 -3
- package/src/assets/skills/qa/SKILL.md +1 -1
- package/src/assets/skills/quality-gates/SKILL.md +7 -7
- package/src/assets/skills/quality-gates/references/report-template.md +7 -7
- /package/src/assets/skills/agent-references/references/{learning → curate}/maintain.md +0 -0
|
@@ -35,17 +35,17 @@ Run a comprehensive code review of the current branch by spawning parallel revie
|
|
|
35
35
|
- **Exactly one listed:** load neither the skill nor the reference — that worktree is the only candidate, and Step 0c's Git agent validates its branch
|
|
36
36
|
2. **If `--path` flag provided:** use only that worktree, skip discovery
|
|
37
37
|
**`--path` validation**: Before proceeding, verify the path exists as a directory and appears in `git worktree list` output. If not: report error and stop.
|
|
38
|
-
3. **If only 1 reviewable worktree
|
|
38
|
+
3. **If only 1 reviewable worktree**: proceed as single-worktree flow — zero behavior change
|
|
39
39
|
4. **If multiple reviewable worktrees:** report "Found N worktrees with reviewable branches: {list with paths and branches}" and proceed with multi-worktree flow
|
|
40
40
|
|
|
41
|
-
#### Step 0b: Resolve the compliance lens
|
|
41
|
+
#### Step 0b: Resolve the compliance lens and gate level
|
|
42
42
|
|
|
43
|
-
**Produces:** COMPLIANCE_ACTIVE, COMPLIANCE_FRAMEWORKS
|
|
43
|
+
**Produces:** COMPLIANCE_ACTIVE, COMPLIANCE_FRAMEWORKS, GATE_LEVEL
|
|
44
44
|
|
|
45
45
|
{{settings.settings_resolve()}}
|
|
46
46
|
|
|
47
47
|
{{compliance_gate()}}
|
|
48
|
-
Keep each worktree's values for every downstream phase
|
|
48
|
+
Keep each worktree's values for every downstream phase.
|
|
49
49
|
|
|
50
50
|
#### Step 0b-ii: Resolve the evidence policy
|
|
51
51
|
|
|
@@ -87,7 +87,7 @@ Validate branch, commit if needed, push, create PR if needed.
|
|
|
87
87
|
Return: branch, base_branch, branch-slug, PR#"
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
In multi-worktree mode, spawn all pre-flight agents **in a single message
|
|
90
|
+
In multi-worktree mode, spawn all pre-flight agents **in a single message**.
|
|
91
91
|
|
|
92
92
|
**If BLOCKED:** In single-worktree mode, stop and report. In multi-worktree mode, report the failure but continue with other worktrees.
|
|
93
93
|
|
|
@@ -127,7 +127,7 @@ For each worktree, perform a single pass over timestamped directories:
|
|
|
127
127
|
2. Iterate once: accumulate CYCLE_NUMBER count for each directory containing `resolution-summary.md`; capture the first (most-recent) such directory as PRIOR_DIR.
|
|
128
128
|
3. If CYCLE_NUMBER = 0: set PRIOR_RESOLUTIONS=(none), CYCLE_NUMBER=1, proceed.
|
|
129
129
|
4. Otherwise: set CYCLE_NUMBER = count + 1. Read `{PRIOR_DIR}/resolution-summary.md` as PRIOR_RESOLUTIONS.
|
|
130
|
-
5. If `--full`: still load PRIOR_RESOLUTIONS
|
|
130
|
+
5. If `--full`: still load PRIOR_RESOLUTIONS.
|
|
131
131
|
|
|
132
132
|
#### Step 0e-ii: Convergence Assessment
|
|
133
133
|
|
|
@@ -169,7 +169,7 @@ For each reviewable worktree, call:
|
|
|
169
169
|
### Phase 1: Analyze Changed Files
|
|
170
170
|
|
|
171
171
|
**Produces:** REVIEW_FOCUS_LIST, DIFF_CLASS, DIFF_FILES
|
|
172
|
-
**Requires:** DIFF_RANGE, REVIEW_DIR, COMPLIANCE_ACTIVE (from Step 0b)
|
|
172
|
+
**Requires:** DIFF_RANGE, REVIEW_DIR, COMPLIANCE_ACTIVE, GATE_LEVEL (from Step 0b)
|
|
173
173
|
|
|
174
174
|
Per worktree, in order. Each `git` command here writes to a file and prints nothing.
|
|
175
175
|
|
|
@@ -185,7 +185,7 @@ git -C "{WORKTREE_PATH}" diff --name-only --no-renames {DIFF_RANGE} > "{REVIEW_D
|
|
|
185
185
|
wc -l < "{REVIEW_DIR}/paths.txt"
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
Read `paths.txt` once when the count is about 40 or fewer, and in ranges of about 40 lines (Read with offset and limit) when it is more.
|
|
188
|
+
Read `paths.txt` once when the count is about 40 or fewer, and in ranges of about 40 lines (Read with offset and limit) when it is more.
|
|
189
189
|
|
|
190
190
|
Set `DIFF_CLASS` by these rules, in order; the first that decides wins:
|
|
191
191
|
|
|
@@ -202,7 +202,7 @@ Each reduced class runs only its focus set, every member reading `diff.patch`:
|
|
|
202
202
|
- `tests-only`: testing, reliability, consistency, security
|
|
203
203
|
- `lockfile-only`: dependencies, security
|
|
204
204
|
|
|
205
|
-
A mixed non-code diff runs the union of the sets of the classes it holds. Language and conditional triggers inside a reduced class are suppressed
|
|
205
|
+
A mixed non-code diff runs the union of the sets of the classes it holds. Language and conditional triggers inside a reduced class are suppressed.
|
|
206
206
|
|
|
207
207
|
#### Step 1.2: Choose the focuses
|
|
208
208
|
|
|
@@ -226,13 +226,27 @@ When `DIFF_CLASS` is `code`, read the file types from the paths in `paths.txt` t
|
|
|
226
226
|
| Docs or significant code | documentation |
|
|
227
227
|
| COMPLIANCE_ACTIVE AND diff touches regulated surface | compliance |
|
|
228
228
|
|
|
229
|
-
If `COMPLIANCE_ACTIVE` AND the diff touches regulated surface (data models, auth flows, logging/observability, payments, IaC, retention):
|
|
229
|
+
If `COMPLIANCE_ACTIVE` AND the diff touches regulated surface (data models, auth flows, logging/observability, payments, IaC, retention): on a `code` diff, add `compliance`.
|
|
230
230
|
|
|
231
231
|
**Language focus stamp.** The eight language focuses — `typescript`, `react`, `accessibility`, `ui-design`, `go`, `java`, `python`, `rust` — ship with optional plugins, and the installer stamps the ones installed on this machine onto this line, rewriting it on every install and on `uninstall --plugin`:
|
|
232
232
|
|
|
233
233
|
Installed language focuses: (none)
|
|
234
234
|
|
|
235
|
-
A language focus is spawned only when its file-type condition above fires AND its name appears in that stamped line. A focus missing from the line is never spawned, even when its trigger fires.
|
|
235
|
+
A language focus is spawned only when its file-type condition above fires AND its name appears in that stamped line. A focus missing from the line is never spawned, even when its trigger fires. On a `code` diff, REVIEW_FOCUS_LIST starts with the eight core focuses. Step 1.2b may narrow them; they are never stamp-gated.
|
|
236
|
+
|
|
237
|
+
#### Step 1.2b: Narrow by gate level
|
|
238
|
+
|
|
239
|
+
**Produces:** REVIEW_FOCUS_LIST, GATE_LEVEL
|
|
240
|
+
**Requires:** REVIEW_FOCUS_LIST, DIFF_CLASS, CHANGED_PATHS, GATE_LEVEL
|
|
241
|
+
|
|
242
|
+
**Gate level.** Narrow REVIEW_FOCUS_LIST, for every class, by this worktree's `GATE_LEVEL`. `full` keeps it and reads nothing.
|
|
243
|
+
|
|
244
|
+
At `standard` or `lean`, once per run, run `d="${CLAUDE_CONFIG_DIR:-}"; case "$d" in /*) ;; *) d="$HOME/.claude" ;; esac; printf '%s\n' "$d"` in Bash and Read `<that directory>/skills/devflow:agent-references/references/commands/code-review-levels.md` with the Read tool.
|
|
245
|
+
|
|
246
|
+
- Apply each class's row to that class's own set, then take the union of the results. A row only removes focuses.
|
|
247
|
+
- If the Read fails, or a row is missing or malformed, set `GATE_LEVEL` to `full` for every worktree of this run and say `level reference unreadable: full sets applied`.
|
|
248
|
+
- Security runs at every level and in every class.
|
|
249
|
+
- Compliance keeps the trigger above at every level.
|
|
236
250
|
|
|
237
251
|
#### Step 1.3: Write the diff files
|
|
238
252
|
|
|
@@ -277,7 +291,7 @@ The core, database, dependencies, documentation and compliance focuses, and ever
|
|
|
277
291
|
|
|
278
292
|
{{decisions_load()}}
|
|
279
293
|
<!-- learning:on -->
|
|
280
|
-
This produces a compact index of active ADR/PF entries. Pass `DECISIONS_CONTEXT` to all Review agents. Review agents use `devflow:apply-decisions` to Read full entry bodies on demand.
|
|
294
|
+
This produces a compact index of active ADR/PF entries. Pass `DECISIONS_CONTEXT` to all Review agents as one shared slice, cut over the diff's files. Review agents use `devflow:apply-decisions` to Read full entry bodies on demand.
|
|
281
295
|
|
|
282
296
|
<!-- learning:end -->
|
|
283
297
|
{{knowledge_load()}}
|
|
@@ -297,9 +311,9 @@ Pass `FEATURE_KNOWLEDGE` to all Review agents.
|
|
|
297
311
|
**Requires:** DIFF_RANGE, REVIEW_DIR, TIMESTAMP, FEATURE_KNOWLEDGE, PR_DESCRIPTION, PRIOR_RESOLUTIONS, REVIEW_FOCUS_LIST, DIFF_CLASS, DIFF_FILES
|
|
298
312
|
<!-- learning:end -->
|
|
299
313
|
|
|
300
|
-
Spawn Review agents **in a single message**,
|
|
314
|
+
Spawn Review agents **in a single message**, one per focus in REVIEW_FOCUS_LIST (max 20 per worktree):
|
|
301
315
|
|
|
302
|
-
- `code`: the 8 core reviews on the full `diff.patch
|
|
316
|
+
- `code`: the 8 core reviews on the full `diff.patch` (lean keeps 3), plus the language and conditional focuses Phase 1 added.
|
|
303
317
|
- `docs-only`, `tests-only`, `lockfile-only`: only the reduced set Phase 1 names, each reading `diff.patch`; a mixed non-code diff runs the union. No language or conditional focus is spawned, whatever its trigger.
|
|
304
318
|
|
|
305
319
|
| Focus | Runs | Pattern Skill | DIFF_FILE |
|
|
@@ -325,7 +339,7 @@ Spawn Review agents **in a single message**, for the focus set `DIFF_CLASS` sele
|
|
|
325
339
|
| documentation | conditional | devflow:documentation | diff.patch |
|
|
326
340
|
| compliance | diff-driven | devflow:compliance | diff.patch |
|
|
327
341
|
|
|
328
|
-
Review agent count: `code` runs 8 core
|
|
342
|
+
Review agent count: at `full`, `code` runs 8 core +1-12; `standard` runs up to 2 fewer; lean `code` runs 3-4; a reduced class or a mix runs 2-6.
|
|
329
343
|
|
|
330
344
|
{{compliance_focus_inject()}}
|
|
331
345
|
|
|
@@ -353,16 +367,16 @@ Follow devflow:apply-feature-knowledge for FEATURE_KNOWLEDGE — feature-specifi
|
|
|
353
367
|
IMPORTANT: Write report to {worktree_path}/.devflow/docs/reviews/{branch-slug}/{timestamp}/{focus}.md using Write tool"
|
|
354
368
|
```
|
|
355
369
|
|
|
356
|
-
In multi-worktree mode, process worktrees **sequentially
|
|
370
|
+
In multi-worktree mode, process worktrees **sequentially**: complete Phases 1-4 for one worktree before starting the next, so Review spawns never pile up.
|
|
357
371
|
|
|
358
372
|
### Phase 3: Synthesis → Review Comment (Sequential)
|
|
359
373
|
|
|
360
374
|
**Produces:** REVIEW_SUMMARY, PR_COMMENT
|
|
361
|
-
**Requires:** REVIEW_FOCUS_OUTPUTS, REVIEW_DIR, PR_INFO
|
|
375
|
+
**Requires:** REVIEW_FOCUS_OUTPUTS, REVIEW_DIR, PR_INFO, GATE_LEVEL, REVIEW_FOCUS_LIST
|
|
362
376
|
|
|
363
377
|
**WAIT** for Phase 2. For each worktree, run steps 3a and 3b in sequence:
|
|
364
378
|
|
|
365
|
-
**Step 3a — Synthesize agent** per worktree
|
|
379
|
+
**Step 3a — Synthesize agent** per worktree:
|
|
366
380
|
```
|
|
367
381
|
Agent(subagent_type="Synthesize", run_in_background=false):
|
|
368
382
|
"Mode: review
|
|
@@ -370,6 +384,8 @@ WORKTREE_PATH: {worktree_path} (omit if cwd)
|
|
|
370
384
|
REVIEW_BASE_DIR: {worktree_path}/.devflow/docs/reviews/{branch-slug}/{timestamp}
|
|
371
385
|
TIMESTAMP: {timestamp}
|
|
372
386
|
CYCLE_NUMBER: {cycle_number}
|
|
387
|
+
GATE_LEVEL: {GATE_LEVEL}
|
|
388
|
+
FOCUSES: {REVIEW_FOCUS_LIST}
|
|
373
389
|
PRIOR_RESOLUTIONS: <prior-resolution-summary>{prior_resolutions}</prior-resolution-summary>
|
|
374
390
|
Include Convergence Status section in review-summary.md.
|
|
375
391
|
Aggregate findings, determine merge recommendation.
|
|
@@ -402,8 +418,6 @@ Per worktree, after successful completion:
|
|
|
402
418
|
- Publication status: FULL (private repo) | FULL (config override) | STUB (public repository) | OFF (publication disabled by config) | STUB (visibility undeterminable) | STUB (evidence policy) (from Git agent)
|
|
403
419
|
- Artifact paths
|
|
404
420
|
|
|
405
|
-
In multi-worktree mode, report results per worktree.
|
|
406
|
-
|
|
407
421
|
## Architecture
|
|
408
422
|
|
|
409
423
|
```
|
|
@@ -411,29 +425,27 @@ In multi-worktree mode, report results per worktree.
|
|
|
411
425
|
│
|
|
412
426
|
├─ Phase 0: Worktree Discovery & Pre-flight
|
|
413
427
|
│ ├─ Step 0a: git worktree list → filter reviewable
|
|
414
|
-
│ ├─ Step 0b: Resolve the compliance lens
|
|
428
|
+
│ ├─ Step 0b: Resolve the compliance lens and gate level
|
|
415
429
|
│ ├─ Step 0c: Git agent (ensure-pr-ready) per worktree [parallel]
|
|
416
430
|
│ ├─ Step 0d: Incremental detection + timestamp setup per worktree
|
|
417
431
|
│ ├─ Step 0e-i: Load prior resolution-summary.md
|
|
418
|
-
│ ├─ Step 0e-ii: Convergence assessment (warn
|
|
432
|
+
│ ├─ Step 0e-ii: Convergence assessment (warn only)
|
|
419
433
|
│ └─ Step 0f: Resolve REVIEW_PUBLICATION per worktree (publication gate)
|
|
420
434
|
│
|
|
421
435
|
├─ Per worktree (SEQUENTIAL — one worktree at a time):
|
|
422
436
|
│ │
|
|
423
437
|
│ ├─ Phase 1: Analyze changed files
|
|
424
438
|
│ │ ├─ Classify the diff (paths.txt → DIFF_CLASS)
|
|
425
|
-
│ │ ├─ Choose the focuses (code
|
|
439
|
+
│ │ ├─ Choose the focuses (code: file types + the language stamp; all: the gate level)
|
|
426
440
|
│ │ └─ Write diff.patch and diff-{focus}.patch, by redirect
|
|
427
441
|
│ │
|
|
428
|
-
│ ├─ Phase 2: Reviews (PARALLEL within worktree)
|
|
429
|
-
│ │ ├─ code:
|
|
430
|
-
│ │ │ consistency, regression, testing, reliability (diff.patch)
|
|
431
|
-
│ │ ├─ code: + language (diff-{focus}.patch) and conditional Review agents
|
|
442
|
+
│ ├─ Phase 2: Reviews (PARALLEL within worktree), one per REVIEW_FOCUS_LIST focus
|
|
443
|
+
│ │ ├─ code: core (diff.patch), language (diff-{focus}.patch), conditional
|
|
432
444
|
│ │ └─ docs-only / tests-only / lockfile-only: the reduced set (diff.patch)
|
|
433
445
|
│ │
|
|
434
446
|
│ ├─ Phase 3: Synthesis → Review Comment (SEQUENTIAL within worktree)
|
|
435
447
|
│ │ ├─ Step 3a: Synthesize agent (mode: review, writes review-summary.md)
|
|
436
|
-
│ │ └─ Step 3b: Git agent (post-review-summary
|
|
448
|
+
│ │ └─ Step 3b: Git agent (post-review-summary)
|
|
437
449
|
│ │
|
|
438
450
|
│ └─ Phase 4: Write .last-review-head + display results
|
|
439
451
|
```
|
|
@@ -442,44 +454,27 @@ In multi-worktree mode, report results per worktree.
|
|
|
442
454
|
|
|
443
455
|
| Case | Handling |
|
|
444
456
|
|------|----------|
|
|
445
|
-
|
|
|
446
|
-
| Rebase invalidates `.last-review-head` SHA | `git cat-file -t` check fails → fallback to full diff |
|
|
447
|
-
| Same-minute review collision | `mkdir` fails → retry with seconds appended (`YYYY-MM-DD_HHMMSS`) |
|
|
448
|
-
| Worktree in detached HEAD | Filtered out (no branch name → not reviewable) |
|
|
449
|
-
| Worktree mid-rebase or mid-merge | Filtered out by status check |
|
|
450
|
-
| Two worktrees on same branch | Deduplicate by branch — review once, use first worktree's path |
|
|
451
|
-
| Worktree on protected branch | Filtered out (not reviewable) |
|
|
452
|
-
| Worktree pre-flight fails | Report failure, continue with other worktrees |
|
|
453
|
-
| `--full` in multi-worktree mode | Applies to all worktrees (global modifier) |
|
|
454
|
-
| Many worktrees (5+) | Report count and proceed — user manages their worktree count |
|
|
457
|
+
| `--full` in multi-worktree mode | Applies to all worktrees (global modifier). It sets the diff range, never the gate level |
|
|
455
458
|
| Review comment already posted | Git agent matches its own marker on the `{cycle, timestamp}` pair → skip silently (D7); a re-review in the same cycle (different timestamp) posts its own comment. The marker's format belongs to the operation; this caller passes the pair and never restates the literal |
|
|
456
|
-
|
|
|
457
|
-
|
|
|
458
|
-
|
|
|
459
|
-
|
|
|
460
|
-
|
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
| Lockfile-only diff | `lockfile-only`: dependencies, security on `diff.patch` |
|
|
464
|
-
| Mixed non-code diff (docs with tests, tests with a lockfile) | The union of the matching sets, on `diff.patch` |
|
|
465
|
-
| A path matching no class pattern, or an empty path list | `code` (fail-safe): the 8 core focuses on the full diff |
|
|
466
|
-
| A file moved across classes | `--no-renames` lists the old and the new path, so a code path makes the diff `code` |
|
|
467
|
-
| Path listing above about 40 lines | The count is printed; `paths.txt` is read in ranges of about 40 lines |
|
|
459
|
+
| Docs-only diff | Its Step 1.1 set, narrowed by level |
|
|
460
|
+
| Tests-only diff | Its Step 1.1 set, narrowed by level |
|
|
461
|
+
| Lockfile-only diff | Its Step 1.1 set, narrowed by level |
|
|
462
|
+
| Mixed non-code diff | The union of the matching sets |
|
|
463
|
+
| A path matching no class pattern, or an empty path list | `code` (fail-safe) |
|
|
464
|
+
| A file moved across classes | `--no-renames` lists both paths, so a code path makes the diff `code` |
|
|
465
|
+
| Path listing above about 40 lines | Read in ranges (Step 1.1) |
|
|
468
466
|
| Language files changed, plugin not installed | The focus is absent from the stamped line: not spawned |
|
|
469
467
|
| Public repo (`auto` mode) | Git agent probes visibility, posts counts-only STUB comment; full report stays local |
|
|
470
468
|
| `reviewPublication: off` | Git agent skips posting the review comment (OFF) — except that, only when `EVIDENCE_POLICY` is `required`, Step 0f passes `stub` and a counts-only STUB comment posts |
|
|
471
469
|
|
|
472
470
|
## Backwards Compatibility
|
|
473
471
|
|
|
474
|
-
- **
|
|
475
|
-
- **Legacy flat layout**: If `{worktree}/.devflow/docs/reviews/{branch-slug}/` contains flat `*.md` files (no timestamped subdirectories), new runs create timestamped subdirectories. Old flat files remain untouched.
|
|
472
|
+
- **Gate level**: `full` runs the sets named here; the default `standard` and `lean` narrow them. `devflow gates --set full` keeps them.
|
|
476
473
|
|
|
477
474
|
## Principles
|
|
478
475
|
|
|
479
476
|
1. **Orchestration only** - Command spawns agents, doesn't do git/review work itself. The exceptions are the read-only Phase 0 probes (`git worktree list`, `git -C ... branch --show-current`, `git -C ... cat-file -t`), the bounded Phase 1 path listing (a redirect into `paths.txt`, a `wc -l` count and ranged reads) and the redirect-only patch writes (`diff.patch`, `diff-{focus}.patch`). Redirect-only writes print nothing, so no diff content reaches this thread
|
|
480
477
|
2. **Parallel, not background** - Multiple agents in one message, but `run_in_background=false` so phases complete before proceeding
|
|
481
478
|
3. **Git agent for git work** - All git operations go through Git agent, except the exceptions named in Principle 1; redirect-only writes print nothing
|
|
482
|
-
4. **
|
|
483
|
-
5. **
|
|
484
|
-
6. **Incremental by default** - Only review new changes unless `--full` specified
|
|
485
|
-
7. **Auto-discover worktrees** - One command handles all reviewable branches
|
|
479
|
+
4. **Honest reporting** - Display agent outputs directly
|
|
480
|
+
5. **Incremental by default** - Only review new changes unless `--full` specified
|
|
@@ -202,7 +202,7 @@ Produce the final report:
|
|
|
202
202
|
|
|
203
203
|
Ask user via AskUserQuestion: "Want me to implement this fix?"
|
|
204
204
|
|
|
205
|
-
- **YES** → implement the fix through the Code agent, never in this thread, then spawn `Agent(subagent_type="
|
|
205
|
+
- **YES** → implement the fix through the Code agent, never in this thread, then spawn `Agent(subagent_type="Polish")` on the changed files. /debug loads no feature knowledge, so the fix gets none:
|
|
206
206
|
|
|
207
207
|
```
|
|
208
208
|
Agent(subagent_type="Code"):
|
|
@@ -249,7 +249,7 @@ Ask user via AskUserQuestion: "Want me to implement this fix?"
|
|
|
249
249
|
├─ Phase 6: Root cause report with confidence level
|
|
250
250
|
│
|
|
251
251
|
├─ Phase 7: Offer Fix
|
|
252
|
-
│ └─ AskUserQuestion → Code agent (fix) +
|
|
252
|
+
│ └─ AskUserQuestion → Code agent (fix) + Polish agent, or done
|
|
253
253
|
│
|
|
254
254
|
└─ Feature Knowledge Write-Back (Conditional)
|
|
255
255
|
└─ Knowledge agent (if investigation surfaced durable cross-cutting knowledge)
|
|
@@ -67,7 +67,7 @@ Author the resolved `ISSUE_REQUIRED` and `APPLY_CONVENTIONS` into the workflow s
|
|
|
67
67
|
|
|
68
68
|
{{compliance.compliance_lens()}}
|
|
69
69
|
|
|
70
|
-
Pass it as `complianceFrameworks` when invoking the workflow; the engine builds `COMPLIANCE_DIRECTIVE` from it and hands that to every Code and
|
|
70
|
+
Pass it as `complianceFrameworks` when invoking the workflow; the engine builds `COMPLIANCE_DIRECTIVE` from it and hands that to every Code and Fix agent.
|
|
71
71
|
|
|
72
72
|
**0c. Resolve the docs root**
|
|
73
73
|
|
|
@@ -165,7 +165,7 @@ const ISSUE_INPUT = args.issueInput || args.issueNumber || "(none)"; // this ti
|
|
|
165
165
|
const ISSUE_REQUIRED = String(args.issueRequired) === "false" ? "false" : "true"; // Pre-authoring step 0; only an explicit false turns it off — absent or unrecognised fails closed, like the resolver
|
|
166
166
|
const APPLY_CONVENTIONS = String(args.applyConventions) === "false" ? "false" : "true";
|
|
167
167
|
const COMPLIANCE_FRAMEWORKS = /^(?:off|none|[a-z][a-z0-9-]{0,15}(?:,[a-z][a-z0-9-]{0,15}){0,7})$/.test(String(args.complianceFrameworks)) ? String(args.complianceFrameworks) : "off"; // Pre-authoring step 0b; any other shape is no lens
|
|
168
|
-
const COMPLIANCE_DIRECTIVE = COMPLIANCE_FRAMEWORKS === "off" ? "" : `When the task touches regulated surface (data models, auth flows, logging/observability, payments, IaC, retention), invoke Skill(skill="devflow:compliance") and load references/{id}.md only for the ids in ${COMPLIANCE_FRAMEWORKS} (none: generic controls only); never fabricate guidance for a framework you were not given.`; // D-COMPLIANCE-INJECT: empty when the lens is off, so a Code or
|
|
168
|
+
const COMPLIANCE_DIRECTIVE = COMPLIANCE_FRAMEWORKS === "off" ? "" : `When the task touches regulated surface (data models, auth flows, logging/observability, payments, IaC, retention), invoke Skill(skill="devflow:compliance") and load references/{id}.md only for the ids in ${COMPLIANCE_FRAMEWORKS} (none: generic controls only); never fabricate guidance for a framework you were not given.`; // D-COMPLIANCE-INJECT: empty when the lens is off, so a Code or Fix spawn then carries nothing about compliance; otherwise the one line _compliance.mds renders as compliance_directive(), with the validated ids in place of <ids>
|
|
169
169
|
|
|
170
170
|
// Phase 1: Git setup — declare the operation; the agent owns the process, the branch name included
|
|
171
171
|
const setup = await phase("setup", () =>
|
|
@@ -220,16 +220,16 @@ After implementing, commit your changes with a conventional-commit message and r
|
|
|
220
220
|
- Any open questions or blockers`, { agentType: "Code" })
|
|
221
221
|
);
|
|
222
222
|
|
|
223
|
-
// Phase 3: Gate 1 #1 — post-code pipeline:
|
|
223
|
+
// Phase 3: Gate 1 #1 — post-code pipeline: Polish agent → Harden agent → Validate agent.
|
|
224
224
|
// Validate runs last and unconditionally, so its one full run covers every commit the pass made (see gate1_postcode()).
|
|
225
225
|
const gate1 = await phase("gate1", async () => {
|
|
226
|
-
await agent(`
|
|
226
|
+
await agent(`Polish and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Polish" });
|
|
227
227
|
|
|
228
|
-
const
|
|
229
|
-
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "
|
|
230
|
-
// Anything but an explicit PASS or FIXED is a stop: a
|
|
231
|
-
if (!["PASS", "FIXED"].includes(
|
|
232
|
-
return { verdict: "ESCALATED", type: "
|
|
228
|
+
const harden = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
|
|
229
|
+
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Harden" });
|
|
230
|
+
// Anything but an explicit PASS or FIXED is a stop: a Harden that returned no status, or one this skeleton does not know, has not accepted the code.
|
|
231
|
+
if (!["PASS", "FIXED"].includes(harden?.status)) {
|
|
232
|
+
return { verdict: "ESCALATED", type: "harden-blocked", reason: harden?.status === "BLOCKED" ? "Harden agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Harden agent returned no recognised status: counted as BLOCKED" };
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH}.
|
|
@@ -248,19 +248,19 @@ ${failureDetails}
|
|
|
248
248
|
DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
|
|
249
249
|
<!-- learning:end -->
|
|
250
250
|
${COMPLIANCE_DIRECTIVE}
|
|
251
|
-
Commit fixes with conventional-commit message.`, { agentType: "
|
|
251
|
+
Commit fixes with conventional-commit message.`, { agentType: "Fix" });
|
|
252
252
|
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} after fix attempt ${attempt} (follow your Running commands block).
|
|
253
253
|
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
254
254
|
if (recheck?.verdict === "PASS") break;
|
|
255
255
|
failureDetails = recheck?.details || failureDetails;
|
|
256
|
-
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Validation exhausted after 2
|
|
256
|
+
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Validation exhausted after 2 Fix agent attempts" };
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
return { verdict: "PASS" };
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
-
// Gate 1 #1 stop, before Gate 2: an exhausted Validate or a BLOCKED
|
|
263
|
+
// Gate 1 #1 stop, before Gate 2: an exhausted Validate or a BLOCKED Harden leaves a broken or unfinished build,
|
|
264
264
|
// so Gate 2 and the review pass never run on it — a correctness stop, like the ticket-link and branch stops.
|
|
265
265
|
if (gate1.verdict === "ESCALATED") {
|
|
266
266
|
return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: gate1.type, description: gate1.reason }] };
|
|
@@ -291,7 +291,7 @@ ${evaluation?.rationale || "see the Evaluate agent's report"}
|
|
|
291
291
|
DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
|
|
292
292
|
<!-- learning:end -->
|
|
293
293
|
${COMPLIANCE_DIRECTIVE}
|
|
294
|
-
Self-verify your fix compiles, following your Running commands block. Commit fixes.`, { agentType: "
|
|
294
|
+
Self-verify your fix compiles, following your Running commands block. Commit fixes.`, { agentType: "Fix" });
|
|
295
295
|
evalVerdict = "FAIL-FIXED"; // issues found, fixes applied, not re-evaluated by design
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -313,7 +313,7 @@ ${testResult.failures}
|
|
|
313
313
|
DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
|
|
314
314
|
<!-- learning:end -->
|
|
315
315
|
${COMPLIANCE_DIRECTIVE}
|
|
316
|
-
Self-verify your fix compiles and the scenarios pass, following your Running commands block. Commit fixes.`, { agentType: "
|
|
316
|
+
Self-verify your fix compiles and the scenarios pass, following your Running commands block. Commit fixes.`, { agentType: "Fix" });
|
|
317
317
|
testVerdict = "FAIL-FIXED"; // issues found, fixes applied, not re-evaluated by design
|
|
318
318
|
}
|
|
319
319
|
}
|
|
@@ -468,16 +468,16 @@ Return: {"status": "fixed"|"blocked", "commitShas": ["<sha>"], "unresolved": ["<
|
|
|
468
468
|
return { survivingFindings: notAddressed, fixedFindings, coverageGaps };
|
|
469
469
|
});
|
|
470
470
|
|
|
471
|
-
// Phase 5.5: Gate 1 #2 — FINAL post-fix gate (
|
|
471
|
+
// Phase 5.5: Gate 1 #2 — FINAL post-fix gate (Polish agent → Harden agent → full Validate agent, Validate last and unconditional).
|
|
472
472
|
// Runs ONCE, after the review pass. Inside the pass the Code agent self-verified its own fixes;
|
|
473
473
|
// this is the build gate before the branch is handed back. See gate1_postcode() cadence.
|
|
474
474
|
const gate1Final = await phase("gate1-final", async () => {
|
|
475
|
-
await agent(`
|
|
475
|
+
await agent(`Polish and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Polish" });
|
|
476
476
|
|
|
477
|
-
const
|
|
478
|
-
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "
|
|
479
|
-
if (!["PASS", "FIXED"].includes(
|
|
480
|
-
return { verdict: "ESCALATED", type: "
|
|
477
|
+
const harden = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
|
|
478
|
+
Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Harden" });
|
|
479
|
+
if (!["PASS", "FIXED"].includes(harden?.status)) {
|
|
480
|
+
return { verdict: "ESCALATED", type: "harden-blocked", reason: harden?.status === "BLOCKED" ? "Final Gate 1 Harden agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Final Gate 1 Harden agent returned no recognised status: counted as BLOCKED" };
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH} (final gate after all fixing).
|
|
@@ -494,12 +494,12 @@ ${failureDetails}
|
|
|
494
494
|
DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
|
|
495
495
|
<!-- learning:end -->
|
|
496
496
|
${COMPLIANCE_DIRECTIVE}
|
|
497
|
-
Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "
|
|
497
|
+
Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "Fix" });
|
|
498
498
|
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (final gate, after fix attempt ${attempt}; follow your Running commands block).
|
|
499
499
|
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
500
500
|
if (recheck?.verdict === "PASS") break;
|
|
501
501
|
failureDetails = recheck?.details || failureDetails;
|
|
502
|
-
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Final Gate 1 validation exhausted after 2
|
|
502
|
+
if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Final Gate 1 validation exhausted after 2 Fix agent attempts" };
|
|
503
503
|
}
|
|
504
504
|
}
|
|
505
505
|
|
|
@@ -575,22 +575,49 @@ When WAVE mode is detected, author a workflow that wraps the single-ticket engin
|
|
|
575
575
|
|
|
576
576
|
**Wave workflow structure (author after the SINGLE engine blocks above):**
|
|
577
577
|
|
|
578
|
-
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).
|
|
578
|
+
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).
|
|
579
579
|
|
|
580
580
|
Wave skeleton — compact reference (see `wave_loop()` doctrine for full semantics):
|
|
581
581
|
|
|
582
582
|
```js
|
|
583
|
-
// Wave round loop: Design agent reader → per-ticket try/catch → cascade quarantine via next reader
|
|
584
583
|
// runSingleTicketEngine(args) is the SINGLE skeleton above as a function of its OWN args: the wave's
|
|
585
584
|
// args.issueNumber (the tracking issue, Pre-authoring step 5) is never in its scope.
|
|
586
585
|
const WAVE_TICKETS = [...remainingTickets]; // the pre-fetch's refs, in input order — the wave block's row order
|
|
587
|
-
const results = {}; // ticketId → its row of the workflow's return
|
|
586
|
+
const results = {}; // ticketId → its row of the workflow's return
|
|
588
587
|
// A row from an engine result: the fields step 3 after the workflow renders
|
|
589
588
|
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 });
|
|
590
589
|
const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
|
|
591
|
-
const MERGE_SHA = /^[0-9a-f]{40}$/; // a
|
|
590
|
+
const MERGE_SHA = /^[0-9a-f]{40}$/; // a SHA as git prints it; with the checked branch, the only Git-reported values that reach a merge, Validate or undo prompt
|
|
591
|
+
const BRANCH_NAME = /^(?![-#]|(?:@|(?:FETCH_|ORIG_|MERGE_)?HEAD)$)(?!.*\.\.)[A-Za-z0-9._/+@,#-]{1,200}$/; // implement's ref allowlist; no leading - or #, no revision name, no ..: the name enters git lines
|
|
592
|
+
// One ticket's merge, post-merge Validate and undo (D-WAVE-MERGE-VALIDATE: Git validates nothing).
|
|
593
|
+
const mergeValidateUndo = async (ticketId, engineResult) => {
|
|
594
|
+
if (typeof engineResult.branch !== "string" || !BRANCH_NAME.test(engineResult.branch)) return { kept: false, reason: "ticket branch name refused: not a plain git ref name", halted: null, treeEqual: null };
|
|
595
|
+
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.
|
|
596
|
+
Check out ${INTEGRATION_BRANCH}, run git rev-parse ${INTEGRATION_BRANCH} as baseSha, run git rev-parse --verify --quiet refs/heads/${engineResult.branch} (on a non-zero exit return merged false, reason "no such local branch"), then run git merge --no-ff refs/heads/${engineResult.branch}. If the merge stops after it began (git rev-parse -q --verify MERGE_HEAD succeeds), run git merge --abort; if that abort fails, return merged true with no mergeSha. A merge that did not begin returns merged false with the reason.
|
|
597
|
+
Return: {"merged": true, "mergeSha": "<40-hex merge commit>", "treeEqual": <true when mergeSha^{tree} equals the tree of ${engineResult.branch}'s head>, "baseSha": "<40-hex integration HEAD before the merge>"} — or {"merged": false, "reason": "<why>"} when no merge is kept.`, { agentType: "Git" });
|
|
598
|
+
const treeEqual = merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null;
|
|
599
|
+
const done = (kept, reason, halted = null) => ({ kept, reason, halted, treeEqual });
|
|
600
|
+
if (merge?.merged !== true) return done(false, merge?.reason || "merge failed");
|
|
601
|
+
const badSha = ![merge.mergeSha, merge.baseSha].every(v => MERGE_SHA.test(String(v)));
|
|
602
|
+
if (badSha || merge.mergeSha === merge.baseSha) {
|
|
603
|
+
// No usable SHA, or no commit added: stop taking merges. Fixed reasons; no rejected value is copied.
|
|
604
|
+
const reason = badSha ? "merge reported without a 40-hex mergeSha or baseSha; the integration branch may hold an unvalidated merge" : "merge added no commit, so the wave stopped: an unchanged HEAD cannot be checked. Check that the ticket branch holds its commits, then run the wave again.";
|
|
605
|
+
return done(false, reason, { ticket: ticketId, mergeSha: null, baseSha: null, reason, kind: badSha ? "bad-sha" : "unchanged" });
|
|
606
|
+
}
|
|
607
|
+
const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
|
|
608
|
+
Follow your Running commands block for every build and test command.
|
|
609
|
+
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
610
|
+
if (post?.verdict === "PASS") {
|
|
611
|
+
return done(true, "");
|
|
612
|
+
}
|
|
613
|
+
const undo = await agent(`Undo the merge ${merge.mergeSha} on ${INTEGRATION_BRANCH}, only when no remote branch contains ${merge.mergeSha}. Run these read-only checks in order; a non-zero exit or a different value means not undone: git symbolic-ref --short HEAD must print ${INTEGRATION_BRANCH}; git rev-parse refs/heads/${INTEGRATION_BRANCH} must print ${merge.mergeSha}; git rev-parse ${merge.mergeSha}^1 must print ${merge.baseSha}. Then run exactly this one command and no other that changes a branch or a remote: git reset --keep ${merge.baseSha}
|
|
614
|
+
Return: {"undone": true, "head": "<40-hex from git rev-parse refs/heads/${INTEGRATION_BRANCH}>"} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
|
|
615
|
+
if (undo?.undone === true && undo.head === merge.baseSha) return done(false, "post-merge build red; merge undone");
|
|
616
|
+
const reason = `post-merge build red; merge not undone (${undo?.undone === true ? "undo reported a head other than the recorded pre-merge SHA" : undo?.reason || "no reason given"})`;
|
|
617
|
+
return done(false, reason, { ticket: ticketId, mergeSha: merge.mergeSha, baseSha: merge.baseSha, reason, kind: "red" });
|
|
618
|
+
};
|
|
592
619
|
const waveState = { quarantined: [], round: 0, halted: null };
|
|
593
|
-
let reAskedThisDeadlock = false;
|
|
620
|
+
let reAskedThisDeadlock = false;
|
|
594
621
|
while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS && waveState.halted === null) {
|
|
595
622
|
waveState.round++;
|
|
596
623
|
|
|
@@ -614,56 +641,28 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
|
|
|
614
641
|
reAskedThisDeadlock = true; // re-ask once with vacuous-truth rule quoted verbatim
|
|
615
642
|
continue;
|
|
616
643
|
}
|
|
617
|
-
reAskedThisDeadlock = false;
|
|
644
|
+
reAskedThisDeadlock = false;
|
|
618
645
|
|
|
619
646
|
for (const ticketId of ready) {
|
|
620
647
|
try {
|
|
621
|
-
// ticketId is
|
|
622
|
-
// and its setup-task ISSUE_INPUT; the engine returns the branch that setup-task created, merged below; plans[ticketId] is its own plan, criteria and checked testPlan (Pre-authoring step 4)
|
|
648
|
+
// ticketId is this ticket's OWN ISSUE_REF (pre-fetch heading): the engine's `ticket` and setup-task ISSUE_INPUT; the engine returns the branch setup-task created, merged below; plans[ticketId] is its own plan (Pre-authoring step 4)
|
|
623
649
|
<!-- learning:on -->
|
|
624
650
|
const engineResult = await runSingleTicketEngine({ ticket: ticketId, baseBranch: INTEGRATION_BRANCH, ...(plans[ticketId] || {}), decisionsContext: DECISIONS_CONTEXT, issueRequired: ISSUE_REQUIRED, applyConventions: APPLY_CONVENTIONS, complianceFrameworks: COMPLIANCE_FRAMEWORKS, issueInput: ticketId });
|
|
625
651
|
<!-- learning:off -->
|
|
626
652
|
const engineResult = await runSingleTicketEngine({ ticket: ticketId, baseBranch: INTEGRATION_BRANCH, ...(plans[ticketId] || {}), issueRequired: ISSUE_REQUIRED, applyConventions: APPLY_CONVENTIONS, complianceFrameworks: COMPLIANCE_FRAMEWORKS, issueInput: ticketId });
|
|
627
653
|
<!-- learning:end -->
|
|
628
|
-
//
|
|
629
|
-
// PASS and UNVERIFIED merge; PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
|
|
654
|
+
// PASS and UNVERIFIED merge (verdict, or overallVerdict as the SINGLE alias); PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
|
|
630
655
|
if (["PASS", "UNVERIFIED"].includes(engineResult.verdict || engineResult.overallVerdict)) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
let kept = false;
|
|
635
|
-
let reason = merge?.reason || "merge failed";
|
|
636
|
-
if (merge?.merged === true && !MERGE_SHA.test(String(merge.mergeSha))) {
|
|
637
|
-
// A merge may sit on the integration branch with no SHA to validate or undo it by: stop taking merges.
|
|
638
|
-
waveState.halted = { ticket: ticketId, mergeSha: null, reason: "merge reported without a 40-hex mergeSha; the integration branch may hold an unvalidated merge" };
|
|
639
|
-
reason = waveState.halted.reason;
|
|
640
|
-
} else if (merge?.merged === true) {
|
|
641
|
-
// A merge counts as kept only when this Validate returns PASS. It always runs: treeEqual is recorded, never a reason to skip.
|
|
642
|
-
const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
|
|
643
|
-
Follow your Running commands block for every build and test command.
|
|
644
|
-
Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
|
|
645
|
-
if (post?.verdict === "PASS") {
|
|
646
|
-
kept = true;
|
|
647
|
-
} else {
|
|
648
|
-
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
|
|
649
|
-
Return: {"undone": true} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
|
|
650
|
-
if (undo?.undone === true) {
|
|
651
|
-
reason = "post-merge build red; merge undone";
|
|
652
|
-
} else {
|
|
653
|
-
// A merge that stays red would poison every later ticket: stop taking merges and rounds, and name the red HEAD.
|
|
654
|
-
reason = `post-merge build red; merge not undone (${undo?.reason || "no reason given"})`;
|
|
655
|
-
waveState.halted = { ticket: ticketId, mergeSha: merge.mergeSha, reason };
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
results[ticketId] = rowOf(ticketId, engineResult, kept, merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null);
|
|
656
|
+
const { kept, reason, halted, treeEqual } = await mergeValidateUndo(ticketId, engineResult);
|
|
657
|
+
if (halted !== null) waveState.halted = halted;
|
|
658
|
+
results[ticketId] = rowOf(ticketId, engineResult, kept, treeEqual);
|
|
660
659
|
if (!kept) waveState.quarantined.push({ ticket: ticketId, reason });
|
|
661
660
|
} else {
|
|
662
661
|
results[ticketId] = rowOf(ticketId, engineResult, false);
|
|
663
662
|
waveState.quarantined.push({ ticket: ticketId, reason: engineResult.escalations?.[0]?.description || "engine fail/escalated" });
|
|
664
663
|
}
|
|
665
664
|
} catch (err) {
|
|
666
|
-
//
|
|
665
|
+
// Quarantine the ticket: the cascade reaches its dependents via the next reader round
|
|
667
666
|
results[ticketId] = rowOf(ticketId, null, false);
|
|
668
667
|
waveState.quarantined.push({ ticket: ticketId, reason: `engine crash: ${String(err)}` });
|
|
669
668
|
}
|
|
@@ -697,7 +696,7 @@ A workflow cannot pause mid-run. After the build/wave workflow returns, you (the
|
|
|
697
696
|
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.
|
|
698
697
|
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.
|
|
699
698
|
- **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.
|
|
700
|
-
- **Halted** (
|
|
699
|
+
- **Halted** (a non-null `halted`, whose `kind` is `red`, `bad-sha` or `unchanged`): record `Wave PR: skipped (<text>, wave halted at <halted.ticket>)` and go to step 4 with no wave PR. `<text>` is fixed by `kind`, and no other `halted` field is shown. `red` (a post-merge build it could not undo): `integration HEAD red at <halted.mergeSha>`. `bad-sha` (a merge without a usable SHA): `integration branch may hold an unvalidated merge`. `unchanged` (a merge that added no commit): `merge added no commit`, with the remedy to check that the ticket branch holds its commits, then run the wave again.
|
|
701
700
|
- **Nothing merged** (no entry has `merged: true`): record `Wave PR: skipped (nothing merged)` and go to step 4.
|
|
702
701
|
- Otherwise compose (a) and then (b).
|
|
703
702
|
|
|
@@ -795,22 +794,16 @@ git -C "{integration worktree root}" push origin HEAD; echo "exit=$?"
|
|
|
795
794
|
|
|
796
795
|
{{pub.publication_gate()}}
|
|
797
796
|
|
|
798
|
-
Then spawn:
|
|
797
|
+
Then run the script inline — it is the whole `update-pr-evidence` operation, with no Git agent spawn — through the Bash tool with `timeout: 180000`:
|
|
799
798
|
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
"OPERATION: update-pr-evidence
|
|
803
|
-
PR_NUMBER: {n}
|
|
804
|
-
EVIDENCE_FILE: .devflow/docs/evidence-wave-{slug}.md
|
|
805
|
-
REVIEW_PUBLICATION: {REVIEW_PUBLICATION resolved above, or auto}
|
|
806
|
-
WORKTREE_PATH: {integration worktree root}
|
|
807
|
-
Update the wave PR's test-plan block and post its evidence comment."
|
|
799
|
+
```bash
|
|
800
|
+
cd "{integration worktree root}" && node "$HOME/.devflow/scripts/verify-evidence.cjs" publish --pr {n} --evidence ".devflow/docs/evidence-wave-{slug}.md" --publication {REVIEW_PUBLICATION resolved above, or auto}; echo "exit=$?"
|
|
808
801
|
```
|
|
809
802
|
|
|
810
|
-
`
|
|
803
|
+
`{n}` is the number admitted above, and the publication value is `auto`, `full`, `off` or `stub`. `publish` decides what each publication value means for the evidence comment. Report its `## PR Evidence` block — its `EVIDENCE` line and its `**Body**:` / `**Comment**:` line — with any `TRACEABILITY: DEGRADED ({reason})` lines after it, or its `TRACEABILITY: DEGRADED (evidence unavailable)` line; empty stdout or any other exit ⇒ `TRACEABILITY: DEGRADED (evidence refresh failed)`. Whatever it returns, the run ends here.
|
|
811
804
|
|
|
812
805
|
---
|
|
813
806
|
|
|
814
807
|
### Maintenance note
|
|
815
808
|
|
|
816
|
-
This recipe
|
|
809
|
+
This recipe mirrors the `/implement` + `/code-review` + `/resolve` orchestration as of 2026-06-12. When those commands change, update it. No tooling detects drift (the LLM-vs-plumbing Iron Rule).
|