devflow-kit 2.4.0 → 2.5.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 +156 -0
- package/README.md +86 -18
- package/dist/agents/git.md +824 -0
- package/dist/cli/commands/agents.js +6 -1
- package/dist/cli/commands/attribution-prompts.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +1 -1
- package/dist/cli/commands/compliance.js +23 -1
- package/dist/cli/commands/init-seed.js +24 -26
- package/dist/cli/commands/init.js +502 -71
- package/dist/cli/commands/install-report.js +205 -0
- package/dist/cli/commands/knowledge/index.js +2 -2
- package/dist/cli/commands/knowledge/toggle.js +27 -37
- package/dist/cli/commands/learning.js +37 -30
- package/dist/cli/commands/memory.js +79 -69
- package/dist/cli/commands/prompt-io.js +4 -4
- package/dist/cli/commands/security.js +76 -16
- package/dist/cli/commands/skills.js +53 -7
- package/dist/cli/commands/tracker-prompts.js +145 -0
- package/dist/cli/commands/tracker.js +405 -0
- package/dist/cli/commands/uninstall.js +211 -65
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +22 -4
- package/dist/commands/code-review.md +44 -15
- package/dist/commands/debug.md +20 -6
- package/dist/commands/dynamic-build.md +289 -67
- package/dist/commands/dynamic-plan.md +60 -21
- package/dist/commands/dynamic-profile.md +1 -1
- package/dist/commands/dynamic-tickets.md +58 -8
- package/dist/commands/explore.md +2 -2
- package/dist/commands/implement.md +241 -53
- package/dist/commands/plan.md +88 -17
- package/dist/commands/release.md +64 -17
- package/dist/commands/resolve.md +138 -58
- package/dist/commands/self-review.md +2 -2
- package/dist/core/agent-models.js +55 -12
- package/dist/core/assets.js +58 -2
- package/dist/core/evidence-policy.js +147 -0
- package/dist/core/feature-config.js +130 -64
- package/dist/core/feature-switch.js +112 -0
- package/dist/core/flags.js +4 -4
- package/dist/core/manifest.js +33 -7
- package/dist/core/mds-variants.js +861 -0
- package/dist/core/model-discovery.js +12 -1
- package/dist/core/plugins.js +357 -9
- package/dist/core/project-paths.js +1 -1
- package/dist/core/proxy-log.js +8 -6
- package/dist/core/proxy-state.js +11 -8
- package/dist/core/reference-sweep.js +136 -0
- package/dist/core/tracker.js +407 -0
- package/dist/skills/git/references/decision-markers.md +19 -0
- package/dist/skills/git/references/learn-conventions.md +56 -0
- package/dist/skills/git/references/pr/check-ci-status.md +14 -0
- package/dist/skills/git/references/pr/check-merge-readiness.md +28 -0
- package/dist/skills/git/references/pr/ensure-pr-ready.md +24 -0
- package/dist/skills/git/references/pr/fetch-review-threads.md +22 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +40 -0
- package/dist/skills/git/references/pr/post-review-summary.md +42 -0
- package/dist/skills/git/references/pr/resolve-review-threads.md +35 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +14 -0
- package/dist/skills/git/references/pr/validate-branch.md +18 -0
- package/dist/skills/git/references/publication-gate.md +13 -0
- package/dist/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/skills/git/references/tracker/github/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +40 -0
- package/dist/skills/git/references/tracker/github/create-release.md +11 -0
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +16 -0
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +69 -0
- package/dist/skills/git/references/tracker/github/fetch-issue.md +32 -0
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +17 -0
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +19 -0
- package/dist/skills/git/references/tracker/github/manage-debt.md +101 -0
- package/dist/skills/git/references/tracker/github/post-wave-report.md +28 -0
- package/dist/skills/git/references/tracker/github/setup-task.md +26 -0
- package/dist/skills/git/references/tracker/jira/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +49 -0
- package/dist/skills/git/references/tracker/jira/create-release.md +17 -0
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/jira/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/jira/setup-task.md +31 -0
- package/dist/skills/git/references/tracker/linear/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +53 -0
- package/dist/skills/git/references/tracker/linear/create-release.md +17 -0
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/linear/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/linear/setup-task.md +32 -0
- package/dist/skills/git/references/trust-rule.md +7 -0
- package/dist/targets/claude-code/installer.js +1213 -31
- package/dist/targets/claude-code/legacy.js +5 -0
- package/dist/targets/claude-code/post-install.js +196 -74
- package/dist/targets/claude-code/tracker-install.js +161 -0
- package/package.json +4 -3
- package/src/assets/agents/code.md +42 -4
- package/src/assets/agents/design.md +1 -1
- package/src/assets/agents/git.mds +827 -0
- package/src/assets/agents/knowledge.md +1 -1
- package/src/assets/agents/learning.md +11 -0
- package/src/assets/agents/synthesize.md +1 -1
- package/src/assets/agents/test.md +16 -5
- package/src/assets/agents/tracker.md +467 -0
- package/src/assets/agents/validate.md +7 -5
- package/src/assets/commands/_partials/_engine.mds +11 -9
- package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
- package/src/assets/commands/_partials/_knowledge.mds +2 -2
- package/src/assets/commands/_partials/_plan_contract.mds +22 -7
- package/src/assets/commands/_partials/_preamble.mds +1 -1
- package/src/assets/commands/_partials/_publication.mds +3 -1
- package/src/assets/commands/_partials/_ticket_template.mds +3 -2
- package/src/assets/commands/_partials/_tracker.mds +18 -0
- package/src/assets/commands/_partials/_wave.mds +16 -10
- package/src/assets/commands/bug-analysis.mds +15 -5
- package/src/assets/commands/code-review.mds +34 -14
- package/src/assets/commands/debug.mds +11 -4
- package/src/assets/commands/dynamic-build.mds +227 -41
- package/src/assets/commands/dynamic-plan.mds +35 -13
- package/src/assets/commands/dynamic-tickets.mds +47 -5
- package/src/assets/commands/implement.mds +206 -52
- package/src/assets/commands/plan.mds +70 -17
- package/src/assets/commands/release.md +64 -17
- package/src/assets/commands/resolve.mds +126 -56
- package/src/assets/mds/git/_pr.mds +331 -0
- package/src/assets/mds/git/_references.mds +135 -0
- package/src/assets/mds/tracker/_common.mds +156 -0
- package/src/assets/mds/tracker/_github.mds +472 -0
- package/src/assets/mds/tracker/_jira.mds +407 -0
- package/src/assets/mds/tracker/_linear.mds +449 -0
- package/src/assets/mds/tracker/_mcp.mds +299 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
- package/src/assets/scripts/hooks/background-memory-update +14 -9
- package/src/assets/scripts/hooks/capture-prompt +6 -2
- package/src/assets/scripts/hooks/capture-question +6 -2
- package/src/assets/scripts/hooks/capture-turn +6 -2
- package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
- package/src/assets/scripts/hooks/ensure-root-gitignore +161 -60
- package/src/assets/scripts/hooks/hook-log-init +3 -1
- package/src/assets/scripts/hooks/json-helper.cjs +223 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -1
- package/src/assets/scripts/hooks/memory-worker +15 -8
- package/src/assets/scripts/hooks/pre-compact-memory +12 -8
- package/src/assets/scripts/hooks/preamble +1 -4
- package/src/assets/scripts/hooks/queue-append +68 -24
- package/src/assets/scripts/hooks/session-start-context +355 -8
- package/src/assets/scripts/hooks/session-start-memory +12 -8
- package/src/assets/scripts/pr-evidence.cjs +1961 -0
- package/src/assets/scripts/redact-secrets.cjs +490 -62
- package/src/assets/scripts/release-trace.cjs +1143 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +1065 -0
- package/src/assets/scripts/verify-evidence.cjs +1822 -0
- package/src/assets/skills/compliance/SKILL.md +2 -0
- package/src/assets/skills/docs-framework/SKILL.md +5 -3
- package/src/assets/skills/git/SKILL.md +8 -78
- package/src/assets/skills/git/references/github-api.md +179 -141
- package/src/assets/skills/git/references/patterns.md +11 -6
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +6 -61
- package/src/assets/skills/review-methodology/references/violations.md +14 -22
- package/src/assets/agents/git.md +0 -938
|
@@ -4,11 +4,13 @@ argument-hint: "[ticket | issue-url | plan-doc]"
|
|
|
4
4
|
output-dir: dist/commands
|
|
5
5
|
---
|
|
6
6
|
@import { authoring_preamble } from "./_partials/_preamble.mds"
|
|
7
|
-
@import {
|
|
7
|
+
@import { evidence_policy } from "./_partials/_evidence_policy.mds"
|
|
8
8
|
@import { agent_roster, agent_caveats } from "./_partials/_roster.mds"
|
|
9
9
|
@import { gate1_postcode, gate2_acceptance, evaluator_panel, implement_bundle, review_pass, concurrency_doctrine, build_execution_doctrine, engine_output_schema, engine_invariants } from "./_partials/_engine.mds"
|
|
10
10
|
@import { wave_loop, branch_merge_model, merge_doctrine, escalation_model } from "./_partials/_wave.mds"
|
|
11
11
|
@import { acceptance_criteria_contract } from "./_partials/_plan_contract.mds"
|
|
12
|
+
@import { issue_ref_grammar, issue_capture_contract } from "./_partials/_tracker.mds"
|
|
13
|
+
@import "./_partials/_publication.mds" as pub
|
|
12
14
|
|
|
13
15
|
{authoring_preamble()}
|
|
14
16
|
|
|
@@ -35,8 +37,8 @@ Before authoring, verify:
|
|
|
35
37
|
|
|
36
38
|
1. **Workflow tool available:** if the `Workflow` tool is not in your available tools, STOP and tell the user: "The Workflow tool is not available in this session. dynamic-build requires Claude Code's dynamic workflow runtime."
|
|
37
39
|
2. **`agentType` support:** confirmed available (spike F5, 2026-06-11). If spawned agents return no results, check that devflow is installed (`devflow init` has been run).
|
|
38
|
-
3. **
|
|
39
|
-
4. **No-remote path:** if the repo has no remote, skip
|
|
40
|
+
3. **Tracker paths:** an issue reference or URL in the input is read through the Git agent, which resolves the configured tracker and its access itself and reports `TRACEABILITY: DEGRADED (\{reason\})` when it cannot read one — then note it and fall back to the issue text the user provided. No tracker CLI is checked here.
|
|
41
|
+
4. **No-remote path:** if the repo has no remote, skip the remote-dependent steps (the wave PR and its evidence) and proceed with local branch operations only; the Git agent reports DEGRADED for any tracker step it cannot reach.
|
|
40
42
|
|
|
41
43
|
---
|
|
42
44
|
|
|
@@ -44,12 +46,13 @@ Before authoring, verify:
|
|
|
44
46
|
|
|
45
47
|
Before you write the workflow script:
|
|
46
48
|
|
|
47
|
-
**0. Resolve
|
|
49
|
+
**0. Resolve the evidence policy**
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
Reuse this result for every ticket in the wave.
|
|
51
|
+
**Produces:** EVIDENCE_POLICY, ISSUE_REQUIRED, APPLY_CONVENTIONS, REQUIRE_NON_AUTHOR_APPROVAL
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
{evidence_policy()}
|
|
54
|
+
|
|
55
|
+
Author the resolved `ISSUE_REQUIRED` and `APPLY_CONVENTIONS` into the workflow script as constants — pass them as `issueRequired` and `applyConventions` when invoking the workflow — and pass both to the Git `setup-task` spawn.
|
|
53
56
|
|
|
54
57
|
**1. Apply decisions context**
|
|
55
58
|
|
|
@@ -62,7 +65,8 @@ Note the `budget` value from the Workflow tool context (or default to "medium" i
|
|
|
62
65
|
**3. Detect mode: SINGLE or WAVE**
|
|
63
66
|
|
|
64
67
|
- **SINGLE mode:** input is one ticket, one issue, one task description, or one plan document
|
|
65
|
-
- **WAVE mode:** input is a set of
|
|
68
|
+
- **WAVE mode:** input is a set of tracker issues (wave labels, milestone, issue list), or the user says "wave" / "all tickets in wave N"
|
|
69
|
+
- **A `/devflow:dynamic-tickets` ticket directory** (`.devflow/docs/tickets/\{slug\}/\{ts\}/`) is WAVE input: in each ticket file (every `.md` there but `tracking-issue.md`), the `**Issue:**` line directly after `**Depends on:**` is one raw `ISSUE_REFS` token, forwarded to the wave's pre-fetch verbatim — never rendered, normalised or re-derived. A ticket file with no `**Issue:**` line, or more than one, contributes no token; name it in the run summary as `not filed`.
|
|
66
70
|
|
|
67
71
|
When ambiguous, ask the user before authoring: "Is this a single ticket or a wave of tickets?"
|
|
68
72
|
|
|
@@ -70,24 +74,41 @@ When ambiguous, ask the user before authoring: "Is this a single ticket or a wav
|
|
|
70
74
|
|
|
71
75
|
Check for (in priority order):
|
|
72
76
|
- A plan document passed as input (path or inline)
|
|
73
|
-
-
|
|
77
|
+
- An issue body (fetched via the Git agent using `OPERATION: fetch-issue`)
|
|
74
78
|
- The current working context (recent `/devflow:dynamic-plan` output)
|
|
75
79
|
- An in-context task description
|
|
76
80
|
|
|
81
|
+
{issue_capture_contract()}
|
|
82
|
+
|
|
77
83
|
Extract or note:
|
|
78
84
|
- Implementation plan (for Code agent prompt and Evaluate agent)
|
|
79
|
-
- Acceptance criteria
|
|
85
|
+
- Acceptance criteria (for Gate 2) — pass them as `criteria` when invoking the workflow
|
|
86
|
+
- The test plan (for Gate 2) — a plan's TP lines, passed only once they pass this check. Copy the plan's `## Test Plan` section byte for byte into a fresh `mktemp` file with the Write tool, never through an interpolated shell string, and run:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/verify-evidence.cjs" check tp <that file>; echo "exit=$?"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Only `exit=0` passes it: pass the section's TP lines, as one string, as `testPlan` when invoking the workflow — a test plan alone still runs the Test agent. Any other result, or a plan with no `## Test Plan` section: omit `testPlan` and note `Test plan: missing or malformed` in the run summary. Nothing is repaired, and a test plan in any other shape — an older JSON one included — is never passed.
|
|
93
|
+
|
|
94
|
+
In WAVE mode, run this step once per ticket and author the results as `plans`, keyed by the ticket's reference: each ticket gets its own `plan`, `criteria` and checked `testPlan` — never one test plan for the whole wave. Keep each ticket's checked TP lines: step 3 after the workflow builds the wave test plan from them.
|
|
80
95
|
|
|
81
96
|
If none found: build proceeds Gate-1-only (Gate 2 skipped with a note). Never refuse to build; never fabricate criteria.
|
|
82
97
|
|
|
83
98
|
**5. Resolve tracking-issue number (optional)**
|
|
84
99
|
|
|
85
100
|
Check, in priority order:
|
|
86
|
-
- An explicit issue
|
|
87
|
-
- The
|
|
101
|
+
- An explicit candidate issue reference or issue URL in the user's input (e.g. `#42`, `42`, or `https://github.com/…/issues/42`)
|
|
102
|
+
- The `**Issue:**` line directly after the H1 of the ticket set's `tracking-issue.md` (written by `/devflow:dynamic-tickets`' filing step; the file is at `.devflow/docs/tickets/\{slug\}/\{ts\}/tracking-issue.md`), as a raw token — only when the file holds exactly one `**Issue:**` line
|
|
88
103
|
- Otherwise: none
|
|
89
104
|
|
|
90
|
-
|
|
105
|
+
{issue_ref_grammar()}
|
|
106
|
+
|
|
107
|
+
If a number is found, record it as the command-level `ISSUE_NUMBER`:
|
|
108
|
+
- **SINGLE mode:** it is the ticket's own reference. Pass it as `issueNumber: <number>` when invoking the workflow; the engine hands it to setup-task as `ISSUE_INPUT`. If none is found, pass nothing.
|
|
109
|
+
- **WAVE mode:** it is the tracking issue. Only steps 2 and 3 after the workflow use it: step 2 for the wave report, step 3 for the wave block's tracking line. It never reaches a ticket: each ticket's engine gets that ticket's own reference as `issueInput`.
|
|
110
|
+
|
|
111
|
+
Never pass `ISSUE_PR_LINK` into the workflow. The engine binds each ticket's `ISSUE_NUMBER` and `ISSUE_PR_LINK` from that ticket's own setup-task Output (`### Handoff Values`), never from the token it passed in, and hands both to that ticket's Code agents. No `- **PR link line**:` captured ⇒ `ISSUE_PR_LINK` is `"(none)"` and the Code agent emits the `## Related Issues` heading with no reference.
|
|
91
112
|
|
|
92
113
|
---
|
|
93
114
|
|
|
@@ -105,22 +126,43 @@ export const meta = {
|
|
|
105
126
|
// SINGLE mode: one ticket, one branch, full engine
|
|
106
127
|
|
|
107
128
|
const TICKET = args.ticket || args[0] || "see task description";
|
|
108
|
-
const BRANCH = args.branch || `ticket/${TICKET.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`;
|
|
109
129
|
const PLAN = args.plan || null;
|
|
110
130
|
const CRITERIA = args.criteria || null;
|
|
131
|
+
const TEST_PLAN = args.testPlan || null; // the plan's checked TP lines, one string (Pre-authoring step 4)
|
|
111
132
|
const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
|
|
112
|
-
const
|
|
113
|
-
const
|
|
133
|
+
const ISSUE_INPUT = args.issueInput || args.issueNumber || "(none)"; // this ticket's OWN raw reference (Pre-authoring step 5; a wave passes issueInput) — setup-task's input, never a Code agent's
|
|
134
|
+
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
|
|
135
|
+
const APPLY_CONVENTIONS = String(args.applyConventions) === "false" ? "false" : "true";
|
|
114
136
|
|
|
115
|
-
// Phase 1: Git setup — declare the operation; the agent owns the process
|
|
116
|
-
await phase("setup", () =>
|
|
137
|
+
// Phase 1: Git setup — declare the operation; the agent owns the process, the branch name included
|
|
138
|
+
const setup = await phase("setup", () =>
|
|
117
139
|
agent(`OPERATION: setup-task
|
|
118
140
|
BASE_BRANCH: ${args.baseBranch || "HEAD"}
|
|
119
141
|
TASK_DESCRIPTION: ${TICKET}
|
|
120
|
-
|
|
121
|
-
|
|
142
|
+
ISSUE_REQUIRED: ${ISSUE_REQUIRED}
|
|
143
|
+
APPLY_CONVENTIONS: ${APPLY_CONVENTIONS}
|
|
144
|
+
${ISSUE_INPUT !== "(none)" ? "ISSUE_INPUT: " + ISSUE_INPUT : ""}
|
|
145
|
+
Return: {"branch": "<the - **Branch name**: value under your ### Branch, or (none)>", "issueId": "<the - **Issue ID**: value under your ### Handoff Values, or (none)>", "prLinkLine": "<the - **PR link line**: value, or (none)>"}`, { agentType: "Git" })
|
|
122
146
|
);
|
|
123
147
|
|
|
148
|
+
// The ticket's own Handoff Values, read from setup-task's Output — never derived from ISSUE_INPUT.
|
|
149
|
+
// An Issue ID is a bare number or a KEY-number, per the provider; any other value — "none", blank, prose — is no capture, so the stop fails closed.
|
|
150
|
+
const ISSUE_ID_SHAPE = /^(?:[1-9][0-9]{0,8}|[A-Z][A-Z0-9_]{0,9}-[1-9][0-9]{0,8})$/;
|
|
151
|
+
const ISSUE_NUMBER = ISSUE_ID_SHAPE.test(String(setup?.issueId ?? "")) ? setup.issueId : "(none)"; // the captured Issue ID; "(none)" ⇒ none was captured
|
|
152
|
+
const ISSUE_PR_LINK = setup?.prLinkLine || "(none)"; // "(none)" ⇒ Code emits the heading with no reference
|
|
153
|
+
// The branch setup-task created, as it reported it: every later phase and the merge use it. The engine never names one.
|
|
154
|
+
const BRANCH = typeof setup?.branch === "string" && setup.branch.trim() !== "" && setup.branch.trim() !== "(none)" ? setup.branch : "(none)";
|
|
155
|
+
|
|
156
|
+
// Ticket-link stop, before implement. The workflow cannot ask, so it never records an exception: it stops.
|
|
157
|
+
if (ISSUE_REQUIRED === "true" && ISSUE_NUMBER === "(none)") {
|
|
158
|
+
return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: "(none)", issuePrLink: ISSUE_PR_LINK, escalations: [{ type: "ticket-link-missing", description: "setup-task captured no Issue ID while issues are required — link or create this ticket's issue, then re-run" }] };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Branch stop, before implement: with no branch there is nothing to build on or merge — a correctness stop, not a shape gate.
|
|
162
|
+
if (BRANCH === "(none)") {
|
|
163
|
+
return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: "branch-missing", description: "setup-task reported no branch name — check its Output, then re-run" }] };
|
|
164
|
+
}
|
|
165
|
+
|
|
124
166
|
// Phase 2: Implement
|
|
125
167
|
await phase("implement", () =>
|
|
126
168
|
agent(`Implement the following ticket on branch ${BRANCH}:
|
|
@@ -133,6 +175,7 @@ Relevant architectural decisions (apply devflow:apply-decisions algorithm):
|
|
|
133
175
|
${DECISIONS_CONTEXT}
|
|
134
176
|
|
|
135
177
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
178
|
+
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
136
179
|
|
|
137
180
|
When you build or run tests to verify your work, use your "Long-running commands" discipline (background-Bash + Monitor poll) for anything that may run silent >120s, and prefer package-scoped commands.
|
|
138
181
|
|
|
@@ -155,6 +198,7 @@ Report: PASS or FAIL with details.`, { agentType: "Validate" });
|
|
|
155
198
|
await agent(`Fix the validation failures on branch ${BRANCH}:
|
|
156
199
|
${validation.details}
|
|
157
200
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
201
|
+
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
158
202
|
Commit fixes with conventional-commit message.`, { agentType: "Code" });
|
|
159
203
|
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH}. Report: PASS or FAIL.`, { agentType: "Validate" });
|
|
160
204
|
if (recheck.verdict === "PASS") break;
|
|
@@ -175,8 +219,8 @@ Commit fixes with conventional-commit message.`, { agentType: "Code" });
|
|
|
175
219
|
|
|
176
220
|
// Phase 4: Gate 2 — acceptance gate (once, before review pass)
|
|
177
221
|
const gate2 = await phase("gate2", async () => {
|
|
178
|
-
if (!PLAN && !CRITERIA) {
|
|
179
|
-
return { evaluateVerdict: "SKIPPED", testVerdict: "SKIPPED", skipReasons: ["No plan and no
|
|
222
|
+
if (!PLAN && !CRITERIA && !TEST_PLAN) {
|
|
223
|
+
return { evaluateVerdict: "SKIPPED", testVerdict: "SKIPPED", skipReasons: ["No plan, no criteria and no test plan provided"] };
|
|
180
224
|
}
|
|
181
225
|
|
|
182
226
|
let evalVerdict = "SKIPPED";
|
|
@@ -197,23 +241,26 @@ Report: PASS or FAIL with rationale.`, { agentType: "Evaluate" }),
|
|
|
197
241
|
await agent(`Fix the alignment issues identified by the Evaluate agent panel on branch ${BRANCH}:
|
|
198
242
|
${panel.filter(p => p.verdict === "FAIL").map(p => p.rationale).join("\n")}
|
|
199
243
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
244
|
+
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
200
245
|
Self-verify your fix compiles (background-Bash + Monitor for any build >120s — see your "Long-running commands" discipline). Commit fixes.`, { agentType: "Code" });
|
|
201
246
|
evalVerdict = "FAIL-FIXED"; // issues found, fixes applied, not re-evaluated by design
|
|
202
247
|
}
|
|
203
248
|
}
|
|
204
249
|
|
|
205
250
|
let testVerdict = "SKIPPED";
|
|
206
|
-
if (CRITERIA) {
|
|
251
|
+
if (CRITERIA || TEST_PLAN) {
|
|
207
252
|
const testResult = await agent(`Run scenario-based acceptance tests on branch ${BRANCH} against these criteria:
|
|
208
|
-
${CRITERIA}
|
|
253
|
+
${CRITERIA || "(none)"}
|
|
254
|
+
TEST_PLAN: ${TEST_PLAN || "(none)"}
|
|
209
255
|
For any test/build command that may run silent >120s, use the background-Bash + Monitor poll procedure (your "Long-running commands" discipline) so you never trip the 180s watchdog.
|
|
210
|
-
Cover: functionality, API contracts, performance. Report: PASS or FAIL per scenario.`, { agentType: "Test" });
|
|
256
|
+
Cover: functionality, API contracts, performance, and cover every TEST_PLAN scenario. Report: PASS or FAIL per scenario.`, { agentType: "Test" });
|
|
211
257
|
testVerdict = testResult.verdict;
|
|
212
258
|
if (testVerdict === "FAIL") {
|
|
213
259
|
// fix-and-continue — no re-test, no inline Gate 1.
|
|
214
260
|
await agent(`Fix the failing acceptance test scenarios on branch ${BRANCH}:
|
|
215
261
|
${testResult.failures}
|
|
216
262
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
263
|
+
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
217
264
|
Self-verify your fix compiles and the scenarios pass (background-Bash + Monitor for any build/test >120s). Commit fixes.`, { agentType: "Code" });
|
|
218
265
|
testVerdict = "FAIL-FIXED"; // issues found, fixes applied, not re-evaluated by design
|
|
219
266
|
}
|
|
@@ -326,6 +373,7 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
|
|
|
326
373
|
${chunk.map(f => `- ${f.description} (${f.severity})`).join("\n")}
|
|
327
374
|
|
|
328
375
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
376
|
+
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
329
377
|
Fix all findings in this batch. Self-verify your fix compiles (background-Bash + Monitor for any build >120s — see your "Long-running commands" discipline). Commit with conventional-commit message.
|
|
330
378
|
Return: {"status": "fixed"|"blocked", "commitShas": ["<sha>"], "unresolved": ["<description of any finding that could not be fixed>"]}`, { agentType: "Code" });
|
|
331
379
|
chunkResults.push({ chunk, result: r });
|
|
@@ -375,6 +423,7 @@ Report: PASS or FAIL with details.`, { agentType: "Validate" });
|
|
|
375
423
|
await agent(`Fix the final validation failures on branch ${BRANCH}:
|
|
376
424
|
${failureDetails}
|
|
377
425
|
ISSUE_NUMBER: ${ISSUE_NUMBER}
|
|
426
|
+
ISSUE_PR_LINK: ${ISSUE_PR_LINK}
|
|
378
427
|
Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "Code" });
|
|
379
428
|
const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (background+Monitor for long commands). Report: PASS or FAIL.`, { agentType: "Validate" });
|
|
380
429
|
if (recheck.verdict === "PASS") break;
|
|
@@ -395,15 +444,17 @@ Self-verify your fix compiles. Commit fixes with conventional-commit message.`,
|
|
|
395
444
|
});
|
|
396
445
|
|
|
397
446
|
// PASS requires survivingFindings.length === 0 && coverageGaps.length === 0 && gate1Final.verdict !== "ESCALATED"
|
|
447
|
+
// and no FAIL-FIXED Gate 2 verdict: fixes applied but never re-run report UNVERIFIED, never PASS
|
|
398
448
|
const coverageGaps = reviewResult.coverageGaps || [];
|
|
399
|
-
const
|
|
449
|
+
const gate2Unverified = [gate2.evaluateVerdict, gate2.testVerdict].includes("FAIL-FIXED");
|
|
450
|
+
const overallVerdict = (reviewResult.survivingFindings?.length || 0) === 0 && coverageGaps.length === 0 && gate1Final.verdict !== "ESCALATED" ? (gate2Unverified ? "UNVERIFIED" : "PASS") : "PARTIAL";
|
|
400
451
|
|
|
401
|
-
// Phase 6: Report
|
|
402
|
-
return phase("report", () =>
|
|
403
|
-
agent(`Synthesize the build run for ticket ${TICKET} on branch ${BRANCH}:
|
|
452
|
+
// Phase 6: Report — the phase returns the engine result (engine_output_schema); its verdict is overallVerdict
|
|
453
|
+
return phase("report", async () => {
|
|
454
|
+
const report = await agent(`Synthesize the build run for ticket ${TICKET} on branch ${BRANCH}:
|
|
404
455
|
- Implementation summary
|
|
405
456
|
- Gate 1 (#1 post-implementation) result
|
|
406
|
-
- Gate 2 result: ${JSON.stringify(gate2)}
|
|
457
|
+
- Gate 2 result: ${JSON.stringify(gate2)} (render FAIL-FIXED as "UNVERIFIED (fixes applied, not re-run)", never PASS)
|
|
407
458
|
- Review: single pass (full branch diff)
|
|
408
459
|
- Final Gate 1 (#2 post-fix): ${JSON.stringify(gate1Final)}
|
|
409
460
|
- Findings disposition:
|
|
@@ -411,8 +462,18 @@ return phase("report", () =>
|
|
|
411
462
|
- SURVIVING: ${reviewResult.survivingFindings?.length || 0} findings not addressed (fix Code agent failed or deferred): ${JSON.stringify(reviewResult.survivingFindings)}
|
|
412
463
|
- Overall verdict: ${overallVerdict}
|
|
413
464
|
|
|
414
|
-
Write a concise report. Present only surviving findings as outstanding — never present FIXED findings as outstanding. The branch is ready for user review — do NOT merge to main.`, { agentType: "Synthesize" })
|
|
415
|
-
|
|
465
|
+
Write a concise report. Present only surviving findings as outstanding — never present FIXED findings as outstanding. The branch is ready for user review — do NOT merge to main.`, { agentType: "Synthesize" });
|
|
466
|
+
return {
|
|
467
|
+
ticket: TICKET, branch: BRANCH, verdict: overallVerdict, issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK,
|
|
468
|
+
survivingFindings: reviewResult.survivingFindings || [], fixedFindings: reviewResult.fixedFindings || [],
|
|
469
|
+
reviewCoverage: { failedFocuses: coverageGaps, complete: coverageGaps.length === 0 },
|
|
470
|
+
escalations: [
|
|
471
|
+
...(gate1Final.verdict === "ESCALATED" ? [{ type: "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
|
|
472
|
+
...coverageGaps.map(focus => ({ type: "review-coverage-incomplete", description: `review coverage incomplete: ${focus}` })),
|
|
473
|
+
],
|
|
474
|
+
gate2, report,
|
|
475
|
+
};
|
|
476
|
+
});
|
|
416
477
|
```
|
|
417
478
|
|
|
418
479
|
{concurrency_doctrine()}
|
|
@@ -451,12 +512,18 @@ When WAVE mode is detected, author a workflow that wraps the single-ticket engin
|
|
|
451
512
|
|
|
452
513
|
**Wave workflow structure (author after the SINGLE engine blocks above):**
|
|
453
514
|
|
|
454
|
-
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).
|
|
515
|
+
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: Validate agent (build + test). Escalations accumulate in a list; the final report lists all of them.
|
|
455
516
|
|
|
456
517
|
Wave skeleton — compact reference (see `wave_loop()` doctrine for full semantics):
|
|
457
518
|
|
|
458
519
|
```js
|
|
459
520
|
// Wave round loop: Design agent reader → per-ticket try/catch → cascade quarantine via next reader
|
|
521
|
+
// runSingleTicketEngine(args) is the SINGLE skeleton above as a function of its OWN args: the wave's
|
|
522
|
+
// args.issueNumber (the tracking issue, Pre-authoring step 5) is never in its scope.
|
|
523
|
+
const WAVE_TICKETS = [...remainingTickets]; // the pre-fetch's refs, in input order — the wave block's row order
|
|
524
|
+
const results = {}; // ticketId → its row of the workflow's return; a ticket with no entry never ran
|
|
525
|
+
// A row from an engine result: the fields step 3 after the workflow renders
|
|
526
|
+
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 });
|
|
460
527
|
const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
|
|
461
528
|
const waveState = { quarantined: [], round: 0 };
|
|
462
529
|
let reAskedThisDeadlock = false; // re-ask guard: re-ask once on empty ready-set, then escalate
|
|
@@ -477,7 +544,7 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
|
|
|
477
544
|
{ agentType: "Design" }
|
|
478
545
|
);
|
|
479
546
|
|
|
480
|
-
const ready = waveRead?.ready || [];
|
|
547
|
+
const ready = (waveRead?.ready || []).filter(t => remainingTickets.includes(t)); // only the pre-fetch's own refs: a ready ID the reader invents never reaches an engine
|
|
481
548
|
if (ready.length === 0) {
|
|
482
549
|
if (reAskedThisDeadlock) break; // second empty read → declare deadlock with named blockers, break
|
|
483
550
|
reAskedThisDeadlock = true; // re-ask once with vacuous-truth rule quoted verbatim
|
|
@@ -487,20 +554,31 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
|
|
|
487
554
|
|
|
488
555
|
for (const ticketId of ready) {
|
|
489
556
|
try {
|
|
490
|
-
|
|
491
|
-
//
|
|
492
|
-
|
|
493
|
-
|
|
557
|
+
// ticketId is the ISSUE_REF the pre-fetch heading printed — this ticket's OWN reference: the engine's `ticket` (its TICKET)
|
|
558
|
+
// 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)
|
|
559
|
+
const engineResult = await runSingleTicketEngine({ ticket: ticketId, baseBranch: INTEGRATION_BRANCH, ...(plans[ticketId] || {}), decisionsContext: DECISIONS_CONTEXT, issueRequired: ISSUE_REQUIRED, applyConventions: APPLY_CONVENTIONS, issueInput: ticketId });
|
|
560
|
+
// Check both verdict (engine_output_schema) and overallVerdict (SINGLE skeleton alias).
|
|
561
|
+
// PASS and UNVERIFIED merge; PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
|
|
562
|
+
if (["PASS", "UNVERIFIED"].includes(engineResult.verdict || engineResult.overallVerdict)) {
|
|
563
|
+
const merge = await agent(`Merge ${engineResult.branch} to ${INTEGRATION_BRANCH}. Include ticket ID ${ticketId} in the merge commit message. Run Validate agent (build + test) after merge.
|
|
564
|
+
Return: {"merged": true} — or {"merged": false, "reason": "<why>"} when the merge or the post-merge build failed and the merge was not kept.`, { agentType: "Git" });
|
|
565
|
+
results[ticketId] = rowOf(ticketId, engineResult, merge?.merged === true);
|
|
566
|
+
if (merge?.merged !== true) waveState.quarantined.push({ ticket: ticketId, reason: merge?.reason || "merge or post-merge build failed" });
|
|
494
567
|
} else {
|
|
568
|
+
results[ticketId] = rowOf(ticketId, engineResult, false);
|
|
495
569
|
waveState.quarantined.push({ ticket: ticketId, reason: engineResult.escalations?.[0]?.description || "engine fail/escalated" });
|
|
496
570
|
}
|
|
497
571
|
} catch (err) {
|
|
498
572
|
// One ticket's crash/stall never kills the wave — quarantine it; cascade propagates to dependents via the next reader round
|
|
573
|
+
results[ticketId] = rowOf(ticketId, null, false);
|
|
499
574
|
waveState.quarantined.push({ ticket: ticketId, reason: `engine crash: ${String(err)}` });
|
|
500
575
|
}
|
|
501
576
|
remainingTickets = remainingTickets.filter(t => t !== ticketId);
|
|
502
577
|
}
|
|
503
578
|
}
|
|
579
|
+
|
|
580
|
+
// After the wave report is written: one row per wave ticket, in input order. No entry ⇒ never ran (cascade, deadlock, MAX_ROUNDS).
|
|
581
|
+
return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, issuePrLink: "(none)" }), quarantined: waveState.quarantined };
|
|
504
582
|
```
|
|
505
583
|
|
|
506
584
|
---
|
|
@@ -519,14 +597,122 @@ A workflow cannot pause mid-run. After the build/wave workflow returns, you (the
|
|
|
519
597
|
WAVE_ID: \{WAVE_ID\}
|
|
520
598
|
WORKTREE_PATH: \{integration worktree root, when the wave ran in a linked worktree; omit if cwd\}"
|
|
521
599
|
```
|
|
522
|
-
The Git agent deduplicates via marker
|
|
600
|
+
The Git agent deduplicates via its own marker — it skips if a report for this `WAVE_ID` is already posted. The marker's format belongs to the operation; this caller passes `WAVE_ID` and never restates the literal. On API failure it degrades gracefully (`TRACEABILITY: DEGRADED (\{reason\})`) and continues — never blocks the post-wave step. This comment is the evidence surface for the PR-less integration-branch path; no other PR machinery is invented.
|
|
523
601
|
|
|
524
602
|
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.
|
|
525
|
-
3.
|
|
526
|
-
|
|
603
|
+
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.
|
|
604
|
+
- **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.
|
|
605
|
+
- **Nothing merged** (no entry has `merged: true`): record `Wave PR: skipped (nothing merged)` and go to step 4.
|
|
606
|
+
- Otherwise compose (a) and then (b).
|
|
607
|
+
|
|
608
|
+
**(a) The wave block.** Write it with the Write tool, byte for byte, to a fresh `mktemp` file — never through an interpolated shell string — in exactly this shape: the two headings with one blank line between them, the tracking line and then the related lines in row order under the first, and the header and separator verbatim directly under the second:
|
|
609
|
+
|
|
610
|
+
```markdown
|
|
611
|
+
## Related Issues
|
|
612
|
+
Refs {tracking ref}
|
|
613
|
+
{one related line per row that has one}
|
|
614
|
+
|
|
615
|
+
## Wave Evidence
|
|
616
|
+
| T | Ticket | Verdict | Evaluate | Test | Surviving | Coverage |
|
|
617
|
+
|---|---|---|---|---|---|---|
|
|
618
|
+
| T{k} | {ticket} | {verdict} | {evaluate} | {test} | {surviving} | {coverage} |
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
One row per `tickets` entry, `T1` … `Tn` in order. Each entry maps to exactly one row, by these rules:
|
|
622
|
+
- **Verdict** — one of `PASS | UNVERIFIED | QUARANTINED | BLOCKED`. `ran: false` (cascade, deadlock, MAX_ROUNDS) ⇒ `BLOCKED`. `merged: true` with `verdict` `PASS` ⇒ `PASS`. `merged: true` with `verdict` `UNVERIFIED` ⇒ `UNVERIFIED`: the row stays flagged, and its TP lines join the wave test plan. Anything else that ran — PARTIAL, FAIL, ESCALATED, no verdict, an engine crash, a failed merge or a red post-merge build — ⇒ `QUARANTINED`.
|
|
623
|
+
- **Evaluate, Test** — the entry's `evaluateVerdict` and `testVerdict` when it is `PASS`, `FAIL`, `FAIL-FIXED` or `SKIPPED`, else `—`. **Surviving** — its `surviving` count when it is 0–999, else `—`. **Coverage** — `complete` or `incomplete` from `coverageComplete`, else `—`. A `BLOCKED` row is `—` in all four.
|
|
624
|
+
- **Ticket and related line** — the entry's captured `issuePrLink` is the only source of a closing reference. When it is not `(none)`: a `PASS` or `UNVERIFIED` row's related line is `issuePrLink` verbatim; a `QUARANTINED` row's is `issuePrLink` with a leading `Closes ` replaced by `Refs `; and the Ticket cell is the reference that line names (its text after `Closes ` or `Refs `). When it is `(none)`: no related line, and the Ticket cell is `(none)` on a `PASS` or `UNVERIFIED` row; on any other row it is the entry's `ticket` when that is a `#N` or `KEY-N` reference, else `(none)`.
|
|
625
|
+
|
|
626
|
+
**Tracking line** — only when Pre-authoring step 5 resolved a tracking issue whose token is, as a whole, a `#N` or `KEY-N` reference: `Refs ` and that token, verbatim, as the first line under `## Related Issues`. Never `Closes` — the tracking issue outlives the wave. Any other token (a bare number, a URL), or none ⇒ no tracking line: nothing is composed from a number.
|
|
627
|
+
|
|
628
|
+
Then check it:
|
|
629
|
+
|
|
630
|
+
```bash
|
|
631
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/verify-evidence.cjs" check wave <that file>; echo "exit=$?"
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Only `exit=0` admits the file's text, verbatim, as `PR_WAVE_BLOCK`. Any other result: no wave PR — record `Wave PR: not opened (wave block refused: <the code on stderr>)` and go to step 4.
|
|
635
|
+
|
|
636
|
+
**Required link** — only when `EVIDENCE_POLICY` is `required`: a `PASS` or `UNVERIFIED` row whose Ticket is `(none)` — a merged ticket whose setup-task captured an Issue ID but no link line, so the wave PR would close nothing for it — ⇒ `Wave PR: BLOCKED (no ticket link for T<k>, …)`, with no wave PR question and the remedy "link or create those tickets' issues, then re-run"; there is no exception. Under `standard` such a row stays as the Ticket rule above renders it.
|
|
637
|
+
|
|
638
|
+
**(b) The wave test plan.** Take the checked TP lines each merged row's ticket was given in Pre-authoring step 4, in row order; renumber them `TP-1`, `TP-2`, … and prefix each scenario with its row's `T<k>: `. A line is never shortened or reworded: one whose prefixed scenario would pass 200 characters leaves its ticket with no usable test plan. Write `## Test Plan` and those lines, and nothing else, to `"\{integration worktree root\}/.devflow/docs/evidence-wave-\{slug\}.md"` with the Write tool, then run (the path double-quoted; one rewrite from the same lines after a refusal, never a second):
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/verify-evidence.cjs" check tp "{integration worktree root}/.devflow/docs/evidence-wave-{slug}.md"; echo "exit=$?"
|
|
642
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/verify-evidence.cjs" render --plan "{integration worktree root}/.devflow/docs/evidence-wave-{slug}.md"; echo "exit=$?"
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Only when both exit 0 is `PR_TEST_PLAN_BLOCK` the render's stdout, byte for byte without its `exit=` line; in every other case it is `(none)`.
|
|
646
|
+
|
|
647
|
+
**Required plan** — only when `EVIDENCE_POLICY` is `required`: a merged ticket with no usable test plan ⇒ `Wave PR: BLOCKED (no test plan for T<k>, …)`, more than 200 lines in all ⇒ `Wave PR: BLOCKED (test plan over 200 lines)`, and a plan that did not check and render ⇒ `Wave PR: BLOCKED (test plan malformed)` — each with no wave PR question and the remedy "run `/devflow:dynamic-plan` for those tickets and re-run, or ship them through `/implement`"; no exception is offered here. Otherwise the block is optional: a ticket with no usable test plan is left out of it and named in the summary, and `(none)` is passed when no line remains.
|
|
648
|
+
4. Surface ALL of them — escalations AND open decisions — to the user in ONE batched `AskUserQuestion` (never one-at-a-time). `_wave.mds`'s escalation model already quarantines-and-continues; this batches the surfacing so the user answers everything in a single pass.
|
|
649
|
+
- **The wave PR question.** Only when step 3 composed `PR_WAVE_BLOCK`, the batch gains exactly one question: "Open the wave PR from wave/\{slug\}? It links \{n\} merged tickets (\{u\} UNVERIFIED) and references \{q\} quarantined." It has exactly two options: open it, or don't. The counts come from the checked block's rows: `\{n\}` PASS and UNVERIFIED, `\{u\}` UNVERIFIED, `\{q\}` QUARANTINED.
|
|
650
|
+
- A `ticket-link-missing` escalation carries its remedy: link or create that ticket's issue, then re-run. There is no per-ticket exception.
|
|
651
|
+
- **Headless** — `AskUserQuestion` is unavailable, or no answer comes — is a decline: nothing is created and nothing is pushed.
|
|
652
|
+
5. If `~/.devflow/preference-profile.md` was absent, note in your summary: "no preference profile found — N decisions surfaced that a profile might have auto-resolved; consider `/devflow:dynamic-profile`."
|
|
653
|
+
6. **Wave PR** — only after an explicit "open" in step 4. Spawn:
|
|
654
|
+
```
|
|
655
|
+
Agent(subagent_type="Git"):
|
|
656
|
+
"OPERATION: ensure-pr-ready
|
|
657
|
+
WORKTREE_PATH: \{integration worktree root\}
|
|
658
|
+
PR_DESCRIPTION_GUIDANCE: \{counts only — the wave slug and the merged, quarantined and blocked counts; never an issue title or body\}
|
|
659
|
+
APPLY_CONVENTIONS: \{APPLY_CONVENTIONS\}
|
|
660
|
+
PR_WAVE_BLOCK: \{PR_WAVE_BLOCK verbatim\}
|
|
661
|
+
PR_TEST_PLAN_BLOCK: \{PR_TEST_PLAN_BLOCK verbatim, or (none)\}"
|
|
662
|
+
```
|
|
663
|
+
The Git agent pastes each block only behind its own check. Report its `**PR**` line and any `TRACEABILITY: DEGRADED (\{reason\})` lines. The wave PR is opened here and nowhere else; it is never merged, and main is never touched — the user merges.
|
|
527
664
|
|
|
528
665
|
Do NOT ask questions mid-workflow — that is impossible (F4). The workflow only WRITES the report; you read it and ask.
|
|
529
666
|
|
|
667
|
+
7. **Wave PR evidence** — only when step 6 reported the wave PR, after it; it never blocks, and every outcome below goes into the run summary. Take `\{n\}` from step 6's `- **PR**: #\{n\}` line when `\{n\}` matches `^[1-9][0-9]\{0,9\}$` as a whole; no such line ⇒ record `TRACEABILITY: DEGRADED (wave PR number not captured)` and skip this step. `PR_TEST_PLAN_BLOCK` `(none)` ⇒ record `Wave evidence: skipped (no wave test plan)` and skip it.
|
|
668
|
+
|
|
669
|
+
**(a) Test the wave.** Spawn one Test agent on the integration worktree with the wave test plan — the TP lines of the `## Test Plan` section step 3(b) wrote to `.devflow/docs/evidence-wave-\{slug\}.md`:
|
|
670
|
+
|
|
671
|
+
```
|
|
672
|
+
Agent(subagent_type="Test"):
|
|
673
|
+
"ORIGINAL_REQUEST: the merged tickets of wave/{slug}, as the wave test plan names them
|
|
674
|
+
FILES_CHANGED: {the files wave/{slug} changes against the - **Base**: branch step 6 reported}
|
|
675
|
+
TEST_PLAN: {the TP lines of the wave evidence file's ## Test Plan section}
|
|
676
|
+
WORKTREE_PATH: {integration worktree root}
|
|
677
|
+
Cover every TEST_PLAN line on the integration branch as it stands. Report PASS or FAIL with evidence."
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Nothing is fixed here, PASS or FAIL: the wave is done and its PR is open. Report the Test agent's Status.
|
|
681
|
+
|
|
682
|
+
**(b) Claims.** Append its TP claims, PASS or FAIL alike, to the `## Claims` section of `"\{integration worktree root\}/.devflow/docs/evidence-wave-\{slug\}.md"` — the file's last section, created when absent. Append only: never edit or remove a claim. Each line is `/implement`'s TP claim, keyed to the 40-hex `HEAD:` the Test agent's report shows:
|
|
683
|
+
|
|
684
|
+
```
|
|
685
|
+
- TP-<n> <PASS|FAIL|SKIP> sha:<head> by:test exit:<0-255>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
One line per `### Test Plan Evidence` row whose TP is in the wave test plan, with the row's outcome; the line ends at `by:test` when the row's Exit is not a number from 0 to 255. A report whose `HEAD:` is not a single 40-hex SHA — a reported before/after change included — gets no claim: record `Wave evidence: no claims (HEAD not one SHA)`.
|
|
689
|
+
|
|
690
|
+
**(c) Push** the integration branch once — never force, no retry — so every claim's SHA is in the PR:
|
|
691
|
+
|
|
692
|
+
```bash
|
|
693
|
+
git -C "{integration worktree root}" push origin HEAD; echo "exit=$?"
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Any result but `exit=0`, a rejected non-fast-forward push included ⇒ record `TRACEABILITY: DEGRADED (evidence push failed)` and refresh anyway.
|
|
697
|
+
|
|
698
|
+
**(d) Refresh.** Resolve the publication value for the integration worktree:
|
|
699
|
+
|
|
700
|
+
{pub.publication_gate()}
|
|
701
|
+
|
|
702
|
+
Then spawn:
|
|
703
|
+
|
|
704
|
+
```
|
|
705
|
+
Agent(subagent_type="Git"):
|
|
706
|
+
"OPERATION: update-pr-evidence
|
|
707
|
+
PR_NUMBER: {n}
|
|
708
|
+
EVIDENCE_FILE: .devflow/docs/evidence-wave-{slug}.md
|
|
709
|
+
REVIEW_PUBLICATION: {REVIEW_PUBLICATION resolved above, or auto}
|
|
710
|
+
WORKTREE_PATH: {integration worktree root}
|
|
711
|
+
Update the wave PR's test-plan block and post its evidence comment."
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
`update-pr-evidence` decides what each publication value means for the evidence comment. Report its `## PR Evidence` block — its `EVIDENCE` line and its `**Body**:` / `**Comment**:` line — or its `TRACEABILITY: DEGRADED (\{reason\})` line; a spawn that returns neither ⇒ `TRACEABILITY: DEGRADED (evidence refresh failed)`. Whatever it returns, the run ends here.
|
|
715
|
+
|
|
530
716
|
---
|
|
531
717
|
|
|
532
718
|
### Maintenance note
|
|
@@ -6,6 +6,7 @@ output-dir: dist/commands
|
|
|
6
6
|
@import { authoring_preamble } from "./_partials/_preamble.mds"
|
|
7
7
|
@import { agent_roster, agent_caveats } from "./_partials/_roster.mds"
|
|
8
8
|
@import { acceptance_criteria_contract } from "./_partials/_plan_contract.mds"
|
|
9
|
+
@import { issue_ref_grammar, issue_capture_contract } from "./_partials/_tracker.mds"
|
|
9
10
|
|
|
10
11
|
{authoring_preamble()}
|
|
11
12
|
|
|
@@ -21,7 +22,7 @@ This command instructs you to construct and run a Claude Code dynamic Workflow t
|
|
|
21
22
|
|
|
22
23
|
---
|
|
23
24
|
|
|
24
|
-
**Requires:** ticket files directory or
|
|
25
|
+
**Requires:** ticket files directory or tracker issue list; optional `~/.devflow/preference-profile.md`
|
|
25
26
|
**Produces:** per-ticket plan files + `DECISIONS-NEEDED.md` at `.devflow/docs/design/\{slug\}/\{ts\}/`
|
|
26
27
|
|
|
27
28
|
---
|
|
@@ -32,8 +33,8 @@ Before authoring, verify:
|
|
|
32
33
|
|
|
33
34
|
1. **Workflow tool available:** if the `Workflow` tool is not in your available tools, STOP and tell the user: "The Workflow tool is not available in this session. dynamic-plan requires Claude Code's dynamic workflow runtime."
|
|
34
35
|
2. **`agentType` support:** confirmed available (spike F5, 2026-06-11).
|
|
35
|
-
3. **
|
|
36
|
-
4. **No-remote path:** if the repo has no remote,
|
|
36
|
+
3. **Tracker paths:** a list of issue references or URLs is read through the Git agent, which resolves the configured tracker and its access itself and reports `TRACEABILITY: DEGRADED (\{reason\})` when it cannot read an issue — then fall back to reading ticket `.md` files from a local path. No tracker CLI is checked here.
|
|
37
|
+
4. **No-remote path:** if the repo has no remote, read ticket files from the provided local path; the Git agent reports DEGRADED for any issue it cannot reach.
|
|
37
38
|
|
|
38
39
|
---
|
|
39
40
|
|
|
@@ -56,11 +57,15 @@ If present, note its contents as `PREFERENCE_PROFILE`. This will be used to auto
|
|
|
56
57
|
|
|
57
58
|
Determine the ticket source (in priority order):
|
|
58
59
|
- A directory of ticket `.md` files (from `/devflow:dynamic-tickets` output)
|
|
59
|
-
- A list of
|
|
60
|
+
- A list of candidate issue references or issue URLs
|
|
60
61
|
- Inline ticket descriptions passed as args
|
|
61
62
|
|
|
63
|
+
{issue_ref_grammar()}
|
|
64
|
+
|
|
62
65
|
Read or note the tickets. The agents will read them in full; you need the list and any key constraints.
|
|
63
66
|
|
|
67
|
+
{issue_capture_contract()}
|
|
68
|
+
|
|
64
69
|
---
|
|
65
70
|
|
|
66
71
|
### CRITICAL (F4) — AskUserQuestion at the command boundary, NOT inside the workflow
|
|
@@ -68,9 +73,16 @@ Read or note the tickets. The agents will read them in full; you need the list a
|
|
|
68
73
|
A workflow cannot pause mid-run. Open design decisions collected in `DECISIONS-NEEDED.md` are surfaced to the user via **AskUserQuestion AFTER the workflow returns** — at the command boundary. The workflow only WRITES the file; the command (you, the main model) reads it and asks.
|
|
69
74
|
|
|
70
75
|
After the workflow completes:
|
|
71
|
-
1.
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
1. **Check each plan's test plan.** For every path in `planPaths`, copy that plan's `## Test Plan` section — the heading and its TP lines, up to the next `## ` heading — byte for byte into a fresh `mktemp` file with the Write tool, never through an interpolated shell string, and run:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/verify-evidence.cjs" check tp <that file>; echo "exit=$?"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Check the section, never the whole plan file: the plan's other `## ` headings are not evidence-file sections, so the script would parse the whole document as the plan. `exit=0` passes. Any other result names that plan `test plan malformed (<code>)` in your summary, `<code>` being the code the script printed on stderr; a plan with no `## Test Plan` section copies as an empty file, which the script refuses. Nothing is repaired: `/devflow:dynamic-build` passes no test plan from that plan.
|
|
83
|
+
2. Read the `decisionsNeededPath` returned by the workflow (e.g. `.devflow/docs/design/<slug>/<ts>/DECISIONS-NEEDED.md`). Always use the OUTDIR-scoped path the workflow wrote — never the flat `.devflow/docs/design/DECISIONS-NEEDED.md`.
|
|
84
|
+
3. First surface the **Auto-Resolved Decisions** section (decision → resolution → source) for audit, then surface ALL open **Decisions Needed** to the user in ONE batched `AskUserQuestion` (never one-at-a-time).
|
|
85
|
+
4. The user's answers feed into their own plan edits or a follow-up `/devflow:dynamic-plan` run.
|
|
74
86
|
|
|
75
87
|
State this explicitly in the workflow script as a comment: `// AskUserQuestion happens at the command boundary after this workflow returns — NOT here.`
|
|
76
88
|
|
|
@@ -92,6 +104,7 @@ export const meta = {
|
|
|
92
104
|
const ticketSource = args.ticketSource || args[0] || "see task description";
|
|
93
105
|
const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
|
|
94
106
|
const PREFERENCE_PROFILE = args.preferenceProfile || ""; // injected before authoring
|
|
107
|
+
const TP_CONTRACT = args.tpContract || ""; // the "Test-plan line (TP)" contract below — its paragraph and four bullets — passed verbatim as tpContract when invoking the workflow, never pasted into this script (it holds backticks)
|
|
95
108
|
const slug = args.slug || "wave";
|
|
96
109
|
const ts = new Date().toISOString().slice(0,16).replace(/[-:T]/g, (c) => c === 'T' ? '_' : c === ':' ? '' : c);
|
|
97
110
|
const OUTDIR = `.devflow/docs/design/${slug}/${ts}`;
|
|
@@ -100,7 +113,7 @@ const OUTDIR = `.devflow/docs/design/${slug}/${ts}`;
|
|
|
100
113
|
const tickets = await phase("read-tickets", () =>
|
|
101
114
|
agent(`Read all tickets from: ${ticketSource}
|
|
102
115
|
For each ticket, extract: title, summary, wave, dependsOn, scope (in/out), acceptance criteria, open questions, and any existing implementation hints.
|
|
103
|
-
If the source is a directory, read all .md files. If the source is
|
|
116
|
+
If the source is a directory, read all .md files. If the source is tracker issues, use the Git agent's fetch-issue or fetch-issues-batch operation.
|
|
104
117
|
Return: array of ticket objects with all fields.`, { agentType: "Git" })
|
|
105
118
|
);
|
|
106
119
|
|
|
@@ -130,15 +143,23 @@ Decisions context: ${DECISIONS_CONTEXT}
|
|
|
130
143
|
Produce:
|
|
131
144
|
1. List of improvements / gaps / edge cases / side-effects identified.
|
|
132
145
|
2. Well-structured acceptance criteria (numbered, positive + negative, at least one negative per ticket). See the acceptance criteria contract below.
|
|
133
|
-
3.
|
|
146
|
+
3. The test plan for the Test agent as TP lines, numbered from TP-1, at least one per criterion, each in exactly the shape of the test-plan line contract below. Map each scenario onto its line:
|
|
147
|
+
- the scenario, in plain words, is the line's scenario text — never a path, a reference, a mention or markup;
|
|
148
|
+
- the number of the criterion it covers is its (AC-m);
|
|
149
|
+
- its verification method is its method: a test committed to the suite is ci; a command run and read (a load test, a script) is local; a step performed and observed is manual;
|
|
150
|
+
- the paths it exercises, from the plan's affected files, are its files: globs.
|
|
151
|
+
Setup and expected outcome never go in a line: give them per TP in testScenarios.
|
|
134
152
|
4. A list of genuine design decisions that require user input (not settled by the plan, the preference profile, or existing ADRs).
|
|
135
153
|
|
|
154
|
+
Test-plan line contract:
|
|
155
|
+
${TP_CONTRACT}
|
|
156
|
+
|
|
136
157
|
Acceptance criteria quality bar (apply strictly):
|
|
137
158
|
- Vague criteria ("the feature should work correctly") are NOT acceptable — reject and rewrite.
|
|
138
159
|
- Implementation-coupled criteria ("the function must call X") are NOT acceptable — test behavior, not implementation.
|
|
139
160
|
- Untestable criteria are NOT acceptable.
|
|
140
161
|
|
|
141
|
-
Return: { ticketTitle, improvements (array), acceptanceCriteria (array of numbered strings), testPlan (array of {
|
|
162
|
+
Return: { ticketTitle, improvements (array), acceptanceCriteria (array of numbered strings), testPlan (array of TP-line strings, TP-1 first), testScenarios (array of {tp, setup, outcome}), openDecisions (array) }.`, { agentType: "Evaluate" })
|
|
142
163
|
))
|
|
143
164
|
);
|
|
144
165
|
|
|
@@ -190,7 +211,8 @@ For each ticket, write ${OUTDIR}/{ticket-slug}-plan.md containing:
|
|
|
190
211
|
- ## Implementation Plan
|
|
191
212
|
- The plan body (incorporate cross-plan amendments)
|
|
192
213
|
- ## Acceptance Criteria (numbered, positive + negative)
|
|
193
|
-
- ## Test Plan
|
|
214
|
+
- ## Test Plan — the challenger's testPlan lines, verbatim, one per line, and nothing else: no prose, no blank line between them, no setup or outcome
|
|
215
|
+
- ## Test Scenarios — one line per TP, in TP order: TP-n: its setup, then its expected outcome, from testScenarios
|
|
194
216
|
- ## Auto-Resolved Decisions (if any — list each as: decision → resolution → source)
|
|
195
217
|
|
|
196
218
|
Then write ${OUTDIR}/DECISIONS-NEEDED.md:
|
|
@@ -228,14 +250,14 @@ The workflow returns:
|
|
|
228
250
|
|
|
229
251
|
```json
|
|
230
252
|
{
|
|
231
|
-
"planPaths": ["string — path to each per-ticket plan file"],
|
|
253
|
+
"planPaths": ["string — path to each per-ticket plan file; its ## Test Plan holds TP lines only, its ## Test Scenarios their setup and outcome"],
|
|
232
254
|
"decisionsNeededPath": "string — path to DECISIONS-NEEDED.md",
|
|
233
255
|
"decisionsNeededCount": "number — how many decisions need user input",
|
|
234
256
|
"autoResolvedCount": "number — decisions auto-resolved by preference profile"
|
|
235
257
|
}
|
|
236
258
|
```
|
|
237
259
|
|
|
238
|
-
After the workflow returns: read `DECISIONS-NEEDED.md`. First briefly surface the **Auto-Resolved Decisions** section (decision → resolution → source) so silently-settled calls are visible and reversible, then surface ALL open **Decisions Needed** in ONE batched `AskUserQuestion` (never one-at-a-time). Do not ask questions mid-workflow — this is F4. If no preference profile was found, note in your summary: "no preference profile found — N decisions were surfaced that a profile might have auto-resolved; consider `/devflow:dynamic-profile`."
|
|
260
|
+
After the workflow returns: check each plan's test plan (step 1 of the F4 list above), then read `DECISIONS-NEEDED.md`. First briefly surface the **Auto-Resolved Decisions** section (decision → resolution → source) so silently-settled calls are visible and reversible, then surface ALL open **Decisions Needed** in ONE batched `AskUserQuestion` (never one-at-a-time). Do not ask questions mid-workflow — this is F4. If no preference profile was found, note in your summary: "no preference profile found — N decisions were surfaced that a profile might have auto-resolved; consider `/devflow:dynamic-profile`."
|
|
239
261
|
|
|
240
262
|
---
|
|
241
263
|
|