devflow-kit 3.15.0 → 3.16.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 +32 -0
- package/README.md +5 -42
- package/dist/agents/code.md +2 -5
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +1 -2
- package/dist/agents/git.md +1 -2
- package/dist/agents/harden.md +1 -3
- package/dist/agents/refactor.md +1 -2
- package/dist/agents/review.md +1 -1
- package/dist/cli/commands/compliance-prompts.js +2 -2
- package/dist/cli/commands/init.js +5 -6
- package/dist/cli/commands/uninstall.js +1 -1
- package/dist/commands/bug-analysis.md +1 -1
- package/dist/commands/code-review.md +2 -2
- package/dist/compliance-off/commands/bug-analysis.md +1 -1
- package/dist/compliance-off/commands/code-review.md +2 -2
- package/dist/compliance-off/skills/git/references/tracker/_contract.md +1 -1
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +3 -4
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +3 -4
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +3 -4
- package/dist/core/installed-variants.js +1 -1
- package/dist/core/mds-variants.js +6 -8
- package/dist/core/plugins.js +5 -40
- package/dist/core/retired-shipped-names.js +7 -0
- package/dist/curation-compliance-off/commands/bug-analysis.md +1 -1
- package/dist/curation-compliance-off/commands/code-review.md +2 -2
- package/dist/curation-off/agents/code.md +2 -5
- package/dist/curation-off/agents/design.md +1 -1
- package/dist/curation-off/agents/fix.md +1 -2
- package/dist/curation-off/agents/harden.md +1 -3
- package/dist/curation-off/agents/refactor.md +1 -2
- package/dist/curation-off/agents/review.md +1 -1
- package/dist/curation-off/commands/bug-analysis.md +1 -1
- package/dist/curation-off/commands/code-review.md +2 -2
- package/dist/curation-tdd-off/agents/code.md +2 -5
- package/dist/skills/git/references/tracker/_contract.md +1 -1
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +3 -3
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +3 -3
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +3 -3
- package/dist/targets/claude-code/curation-install.js +28 -3
- package/dist/targets/claude-code/language-stamp.js +1 -2
- package/dist/{cli/commands → targets/claude-code}/legacy-sweep.js +45 -12
- package/dist/tdd-off/agents/code.md +2 -5
- package/dist/variant-axes.json +0 -20
- package/package.json +2 -3
- package/src/assets/agents/_partials/_code.mds +1 -2
- package/src/assets/agents/code.mds +1 -3
- package/src/assets/agents/design.mds +1 -1
- package/src/assets/agents/git.mds +1 -2
- package/src/assets/agents/harden.mds +1 -3
- package/src/assets/agents/review.mds +1 -1
- package/src/assets/agents/test.md +1 -2
- package/src/assets/agents/tracker.md +1 -5
- package/src/assets/agents/validate.md +0 -1
- package/src/assets/commands/_partials/_compliance.mds +0 -1
- package/src/assets/commands/_partials/_decisions.mds +2 -3
- package/src/assets/commands/_partials/_docs_root.mds +1 -1
- package/src/assets/commands/_partials/_plan_contract.mds +0 -56
- package/src/assets/commands/bug-analysis.mds +1 -1
- package/src/assets/commands/code-review.mds +2 -2
- package/src/assets/mds/git/_ops.mds +3 -20
- package/src/assets/mds/git/_ops_steps.mds +8 -6
- package/src/assets/mds/tracker/_common.mds +3 -14
- package/src/assets/mds/tracker/_contract.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +0 -32
- package/src/assets/mds/tracker/_jira.mds +0 -31
- package/src/assets/mds/tracker/_linear.mds +0 -31
- package/src/assets/mds/tracker/_steps.mds +0 -6
- package/src/assets/scripts/pr-evidence.cjs +3 -268
- package/src/assets/scripts/resolve-base.cjs +1 -3
- package/src/assets/scripts/verify-evidence.cjs +13 -18
- package/src/assets/skills/docs-framework/SKILL.md +5 -19
- package/src/assets/skills/docs-framework/references/patterns.md +1 -2
- package/dist/commands/dynamic-build.md +0 -1218
- package/dist/commands/dynamic-plan.md +0 -447
- package/dist/commands/dynamic-profile.md +0 -102
- package/dist/commands/dynamic-tickets.md +0 -555
- package/dist/compliance-off/commands/dynamic-build.md +0 -1197
- package/dist/compliance-off/commands/dynamic-tickets.md +0 -555
- package/dist/curation-compliance-off/commands/dynamic-build.md +0 -1161
- package/dist/curation-compliance-off/commands/dynamic-tickets.md +0 -515
- package/dist/curation-off/commands/dynamic-build.md +0 -1182
- package/dist/curation-off/commands/dynamic-plan.md +0 -406
- package/dist/curation-off/commands/dynamic-tickets.md +0 -515
- package/dist/skills/git/references/tracker/github/post-wave-report.md +0 -44
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +0 -49
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +0 -49
- package/src/assets/commands/_partials/_engine.mds +0 -216
- package/src/assets/commands/_partials/_factory.mds +0 -122
- package/src/assets/commands/_partials/_preamble.mds +0 -112
- package/src/assets/commands/_partials/_roster.mds +0 -31
- package/src/assets/commands/_partials/_ticket_template.mds +0 -56
- package/src/assets/commands/_partials/_wave.mds +0 -123
- package/src/assets/commands/dynamic-build.mds +0 -882
- package/src/assets/commands/dynamic-plan.mds +0 -289
- package/src/assets/commands/dynamic-profile.mds +0 -103
- package/src/assets/commands/dynamic-tickets.mds +0 -281
|
@@ -453,34 +453,6 @@ The D3 section headings are the canonical ones; only the transport differs from
|
|
|
453
453
|
<!-- compliance:end -->
|
|
454
454
|
@end
|
|
455
455
|
|
|
456
|
-
@define post_wave_report():
|
|
457
|
-
## Operation: post-wave-report
|
|
458
|
-
|
|
459
|
-
{{ops.post_wave_report_contract()}}
|
|
460
|
-
|
|
461
|
-
Load when the resolved tracker provider is `linear` and the operation is `post-wave-report`.
|
|
462
|
-
|
|
463
|
-
**Mechanics held here:** the `### Process` body — locating the wave's tracking item and posting the report once.
|
|
464
|
-
|
|
465
|
-
{{common.wave_report_inputs()}}
|
|
466
|
-
|
|
467
|
-
### Process
|
|
468
|
-
|
|
469
|
-
**Setup (once):** resolve the capability set and the reached rung. This provider lands at rank 4, so the scan below is unfiltered by author and every run emits `TRACEABILITY: DEGRADED (dedup unavailable — duplicate possible)`; the ladder and the marker's second discriminator are stated once, with the dedup ladder in this operation's `backlink-shipped-issues` reference.
|
|
470
|
-
|
|
471
|
-
1. Check for an existing marker on the tracking item.
|
|
472
|
-
- Read the tracking item's comments through the *list comments with authors* capability. The author column cannot be compared against devflow's own account here, so the match rests on the marker alone.
|
|
473
|
-
- This operation owns the `devflow:wave` namespace and no other. Match **line 1** of each comment for equality against `devflow:wave {WAVE_ID} · https://github.com/dean0x/devflow`; a marker on any later line **does not suppress**.
|
|
474
|
-
- **The scan is a FULL scan, not a newest-first early exit.** A wave report's marker carries a wave id, and wave ids are not monotonic in comment order, so an early exit can miss the one comment that matters. Bound it at `≤5` pages and **fail closed**: if the bound is reached before the scan completes, report `TRUNCATED ({n} not processed)` and **DO NOT POST** — a duplicate wave report is a worse outcome than a missing one, because the next run cannot tell which is authoritative. This is the one place the fail-closed direction wins over post-with-warning, and the difference is the condition: an absent capability says nothing about whether a post happened, while a truncated scan says the evidence exists and was not read.
|
|
475
|
-
- If found: skip — report `Skipped: wave report for {WAVE_ID} already posted`.
|
|
476
|
-
{{steps.wave_report_read_step()}}
|
|
477
|
-
3. Compose the comment: line 1 the marker `devflow:wave {WAVE_ID} · https://github.com/dean0x/devflow`, then the contents of `WAVE_REPORT_PATH`. Cap the composed content at `{{comment_cap()}}` characters; over the cap, truncate in **preservation order** — the marker, then the status and DEGRADED lines, then the pointer sentence — and end with `…truncated — full report in the local wave artifact {WAVE_REPORT_PATH} (not committed; ask the author)`.
|
|
478
|
-
4. Post it through `### Posting gate` below.
|
|
479
|
-
|
|
480
|
-
{{mcp.posting_gate_head("the write")}}
|
|
481
|
-
4. Post through the *add comment* capability with arguments (issue reference, body: {SCRUBBED_BODY}).
|
|
482
|
-
@end
|
|
483
|
-
|
|
484
456
|
@define ensure_pr_ready():
|
|
485
457
|
## Operation: ensure-pr-ready
|
|
486
458
|
|
|
@@ -527,8 +499,5 @@ Load when the resolved tracker provider is `linear` and the operation is `ensure
|
|
|
527
499
|
<!-- op: ensure-traceable-issue -->
|
|
528
500
|
{{ensure_traceable_issue()}}
|
|
529
501
|
|
|
530
|
-
<!-- op: post-wave-report -->
|
|
531
|
-
{{post_wave_report()}}
|
|
532
|
-
|
|
533
502
|
<!-- op: ensure-pr-ready -->
|
|
534
503
|
{{ensure_pr_ready()}}
|
|
@@ -83,11 +83,6 @@ Neutralise any `</untrusted-issue-body>` in the fetched body before wrapping it
|
|
|
83
83
|
5. Gate each candidate against that provider's grammar, full match and anchored at both ends. Where the grammar is `KEY-N`, its KEY must equal the resolved project key after ASCII-upper normalisation; a well-formed reference carrying another key is dropped and reported once as `TRACEABILITY: DEGRADED (foreign issue reference {ref})`. Deduplicate the SURVIVORS — after the gate, never before — then take the first ≤50, appending `…and {n} more issues` if more exist. A `Merge pull request` subject and a trailing parenthesised reference carry no keyword and are never candidates; an empty `SHIPPED_ISSUES` is reported empty, not degraded, unless the Mechanics flag merged PRs they could not resolve.
|
|
84
84
|
@end
|
|
85
85
|
|
|
86
|
-
@define wave_report_read_step():
|
|
87
|
-
2. Resolve and read `WAVE_REPORT_PATH`: if absolute, use as-is; if repo-relative, resolve against WORKTREE_PATH when supplied, else against cwd. Read the resulting file (the wave-report.md written by the wave orchestrator).
|
|
88
|
-
- The wave report MUST NOT reproduce verbatim `<external-thread>` or `<untrusted-issue-body>` content (Principle 8).
|
|
89
|
-
@end
|
|
90
|
-
|
|
91
86
|
@export base_branch_step
|
|
92
87
|
@export branch_create_steps
|
|
93
88
|
@export fetch_issue_neutralise
|
|
@@ -95,4 +90,3 @@ Neutralise any `</untrusted-issue-body>` in the fetched body before wrapping it
|
|
|
95
90
|
@export release_evidence_tag_step
|
|
96
91
|
@export release_evidence_range_steps
|
|
97
92
|
@export release_evidence_gate_step
|
|
98
|
-
@export wave_report_read_step
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/assets/scripts/pr-evidence.cjs
|
|
2
2
|
//
|
|
3
|
-
// The PURE core of PR test-plan evidence: the TP-line, claim, exception
|
|
4
|
-
// EVIDENCE-line
|
|
3
|
+
// The PURE core of PR test-plan evidence: the TP-line, claim, exception and
|
|
4
|
+
// EVIDENCE-line grammars, the marker literals, the state ladder,
|
|
5
5
|
// rendering, the CRLF-aware body splice, the tally, the link check and the one
|
|
6
6
|
// implementation of the trust rule. Installed as a top-level sibling of redact-secrets.cjs under
|
|
7
7
|
// ~/.devflow/scripts/ and required by verify-evidence.cjs, which performs every
|
|
@@ -30,12 +30,7 @@
|
|
|
30
30
|
/** @typedef {'ci' | 'local' | 'manual'} Method */
|
|
31
31
|
/** @typedef {'PASS' | 'FAIL' | 'SKIP'} Outcome */
|
|
32
32
|
/** @typedef {'ticket-link' | 'test-plan'} ExceptionKind */
|
|
33
|
-
/** @typedef {'
|
|
34
|
-
/**
|
|
35
|
-
* @typedef {'oversize' | 'malformed' | 'empty' | 'order' | 'duplicate' | 'mismatch' | 'invalid'
|
|
36
|
-
* | 'orphan' | 'unlinked' | 'unmerged'} ErrorCode
|
|
37
|
-
* The last three are the wave block's cross rules (see parseWaveBlock).
|
|
38
|
-
*/
|
|
33
|
+
/** @typedef {'oversize' | 'malformed' | 'empty' | 'order' | 'duplicate' | 'mismatch' | 'invalid'} ErrorCode */
|
|
39
34
|
|
|
40
35
|
/** The six TP states, closed. The contract's `States (closed)` list is pinned to this order. */
|
|
41
36
|
const STATES = Object.freeze(/** @type {State[]} */ ([
|
|
@@ -64,9 +59,6 @@ const EXCEPTION_KINDS = Object.freeze(/** @type {ExceptionKind[]} */ (['ticket-l
|
|
|
64
59
|
* PATH_CHARS / DIFF_FILES a diff path, and the diff paths, a glob is matched against
|
|
65
60
|
* RUNS_PER_SHA runs at one SHA (`gh run list --limit 20`)
|
|
66
61
|
* TRUST_LOOKUPS permission lookups per spawn
|
|
67
|
-
* WAVE_BLOCK_CHARS a whole wave block
|
|
68
|
-
* WAVE_ROWS a wave block's table rows, and its related lines (at most one per row,
|
|
69
|
-
* plus the one tracking line — see D-WAVE-TRACKING)
|
|
70
62
|
*/
|
|
71
63
|
const LIMITS = Object.freeze({
|
|
72
64
|
TP_MAX: 200,
|
|
@@ -83,8 +75,6 @@ const LIMITS = Object.freeze({
|
|
|
83
75
|
DIFF_FILES: 5000,
|
|
84
76
|
RUNS_PER_SHA: 20,
|
|
85
77
|
TRUST_LOOKUPS: 20,
|
|
86
|
-
WAVE_BLOCK_CHARS: 16000,
|
|
87
|
-
WAVE_ROWS: 100,
|
|
88
78
|
});
|
|
89
79
|
|
|
90
80
|
/**
|
|
@@ -210,79 +200,6 @@ const EM_DASH = '—';
|
|
|
210
200
|
const UNTICKED = '- [ ] ';
|
|
211
201
|
const TICKED = '- [x] ';
|
|
212
202
|
|
|
213
|
-
/**
|
|
214
|
-
* D-WAVE: the wave block — the wave PR body's `## Related Issues` section and its
|
|
215
|
-
* evidence table, rendered by /devflow:dynamic-build and admitted only through
|
|
216
|
-
* `verify-evidence.cjs check wave`:
|
|
217
|
-
*
|
|
218
|
-
* ## Related Issues
|
|
219
|
-
* Refs #9
|
|
220
|
-
* Closes #12
|
|
221
|
-
* Refs #13
|
|
222
|
-
*
|
|
223
|
-
* ## Wave Evidence
|
|
224
|
-
* | T | Ticket | Verdict | Evaluate | Test | Surviving | Coverage |
|
|
225
|
-
* |---|---|---|---|---|---|---|
|
|
226
|
-
* | T1 | #12 | PASS | PASS | PASS | 0 | complete |
|
|
227
|
-
* | T2 | #13 | QUARANTINED | FAIL-FIXED | SKIPPED | 2 | incomplete |
|
|
228
|
-
*
|
|
229
|
-
* Every cell is a closed-vocabulary token or a bounded number, and every related
|
|
230
|
-
* line is a shape-gated reference, so no byte of a ticket title, a finding or an
|
|
231
|
-
* agent's prose can ride into the PR body on it. The two headings, in order; an
|
|
232
|
-
* admitted block IS the body's `## Related Issues` section. `Refs #9` above is the
|
|
233
|
-
* optional tracking line (D-WAVE-TRACKING).
|
|
234
|
-
*/
|
|
235
|
-
const WAVE_HEADINGS = Object.freeze(['## Related Issues', '## Wave Evidence']);
|
|
236
|
-
|
|
237
|
-
/** The evidence table's header line, verbatim. */
|
|
238
|
-
const WAVE_TABLE_HEADER = '| T | Ticket | Verdict | Evaluate | Test | Surviving | Coverage |';
|
|
239
|
-
|
|
240
|
-
/** The separator under it: one bare `---` per column, no alignment colon. */
|
|
241
|
-
const WAVE_TABLE_SEPARATOR = '|---|---|---|---|---|---|---|';
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* A wave row's verdict: merged as PASS; merged as UNVERIFIED (a FAIL-FIXED Gate 2,
|
|
245
|
-
* fixes applied and not re-run); QUARANTINED (it ran and did not merge, or its
|
|
246
|
-
* merge was quarantined after a red build); BLOCKED (it never ran).
|
|
247
|
-
*/
|
|
248
|
-
const WAVE_VERDICTS = Object.freeze(/** @type {WaveVerdict[]} */ (['PASS', 'UNVERIFIED', 'QUARANTINED', 'BLOCKED']));
|
|
249
|
-
|
|
250
|
-
/** The verdicts of a ticket that merged — the only rows a `Closes` line may name. */
|
|
251
|
-
const WAVE_MERGED_VERDICTS = Object.freeze(/** @type {WaveVerdict[]} */ (['PASS', 'UNVERIFIED']));
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* An Evaluate or Test cell: a Gate 2 verdict, or `—` where the gate never ran or a Test run gave no result.
|
|
255
|
-
* `BLOCKED` is never a cell here, and it has two meanings in the table: a row Verdict `BLOCKED` is a ticket that
|
|
256
|
-
* never ran, and a Test result `BLOCKED` is a run that gave no result, shown as `—`.
|
|
257
|
-
*/
|
|
258
|
-
const WAVE_GATE_VALUES = Object.freeze(['PASS', 'FAIL', 'FAIL-FIXED', 'SKIPPED', EM_DASH]);
|
|
259
|
-
|
|
260
|
-
/** A Coverage cell: whether the review pass covered every focus. */
|
|
261
|
-
const WAVE_COVERAGE_VALUES = Object.freeze(['complete', 'incomplete', EM_DASH]);
|
|
262
|
-
|
|
263
|
-
/** A row's first cell, `T1` … `T100` (the sequence itself is checked by the parser). */
|
|
264
|
-
const WAVE_T_RE = /^T(?:100|[1-9][0-9]?)$/;
|
|
265
|
-
|
|
266
|
-
/** A Surviving cell: the review pass's surviving-finding count, or `—`. */
|
|
267
|
-
const WAVE_SURVIVING_RE = /^(?:[0-9]{1,3}|—)$/;
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* D-WAVE-REF: one related line of a wave block. `Closes` takes only a GitHub `#N`
|
|
271
|
-
* — Jira and Linear render `Refs` — and `Refs` takes `#N` (an unmerged GitHub
|
|
272
|
-
* ticket: its captured `Closes` line with the keyword swapped) or a keyed ref
|
|
273
|
-
* whose key is the union of the Jira `[A-Z][A-Z0-9_]{1,9}` and the Linear
|
|
274
|
-
* `[A-Z][A-Z0-9]{0,9}` key grammars. It admits every line code.md's R7 paste gate
|
|
275
|
-
* admits, their Closes→Refs swap, and nothing else (parity-pinned by
|
|
276
|
-
* tests/evidence/wave-block.test.ts).
|
|
277
|
-
*/
|
|
278
|
-
const RELATED_LINE_RE = Object.freeze(/^(?:Closes (?<closes>#[1-9][0-9]{0,8})|Refs (?<refs>#[1-9][0-9]{0,8}|[A-Z][A-Z0-9_]{0,9}-[1-9][0-9]{0,8}))$/);
|
|
279
|
-
|
|
280
|
-
/** A wave row's Ticket cell: a reference of RELATED_LINE_RE's shapes, or `(none)`. */
|
|
281
|
-
const WAVE_TICKET_RE = Object.freeze(/^(?:#[1-9][0-9]{0,8}|[A-Z][A-Z0-9_]{0,9}-[1-9][0-9]{0,8}|\(none\))$/);
|
|
282
|
-
|
|
283
|
-
/** The Ticket cell of a row with no reference. */
|
|
284
|
-
const NO_TICKET = '(none)';
|
|
285
|
-
|
|
286
203
|
/** The three evidence-file sections, by heading. A Map, so no prototype key can match. */
|
|
287
204
|
const SECTION_KEYS = new Map([
|
|
288
205
|
[PLAN_HEADING, 'testPlan'],
|
|
@@ -390,15 +307,6 @@ const TRUSTED_PERMISSIONS = Object.freeze(['admin', 'write']);
|
|
|
390
307
|
* stale: readonly number[], exceptions: readonly ExceptionKind[],
|
|
391
308
|
* approval: 'yes' | 'no' | 'unchecked', key: string, posted: 'yes' | 'no' | 'n/a',
|
|
392
309
|
* body: 'same' | 'changed' }} EvidenceFields
|
|
393
|
-
*
|
|
394
|
-
* @typedef {{ keyword: 'Closes' | 'Refs', ref: string, line: number }} WaveRelated
|
|
395
|
-
* One related line; `line` is its 1-based line in the block.
|
|
396
|
-
* @typedef {{ k: number, ticket: string | null, verdict: WaveVerdict, evaluate: string, test: string,
|
|
397
|
-
* surviving: number | null, coverage: string, line: number }} WaveRow
|
|
398
|
-
* One table row; `ticket` is null for `(none)`, `surviving` for `—`.
|
|
399
|
-
* @typedef {{ tracking: WaveRelated | null, related: readonly WaveRelated[], rows: readonly WaveRow[] }} WaveBlock
|
|
400
|
-
* `related` holds the lines that name a row; `tracking` is the leading `Refs`
|
|
401
|
-
* line that names none (D-WAVE-TRACKING), or null.
|
|
402
310
|
*/
|
|
403
311
|
|
|
404
312
|
// ---------------------------------------------------------------------------
|
|
@@ -1785,171 +1693,6 @@ function parseEvidenceLine(line) {
|
|
|
1785
1693
|
}));
|
|
1786
1694
|
}
|
|
1787
1695
|
|
|
1788
|
-
// ---------------------------------------------------------------------------
|
|
1789
|
-
// The wave block (D-WAVE)
|
|
1790
|
-
// ---------------------------------------------------------------------------
|
|
1791
|
-
|
|
1792
|
-
/**
|
|
1793
|
-
* Parse one wave row: seven ` | `-separated cells inside `| ` and ` |`, each in its
|
|
1794
|
-
* closed vocabulary. No cell value holds ` | `, so the split is unambiguous and a
|
|
1795
|
-
* forged cell leaves the row with the wrong count. A BLOCKED row never ran, so
|
|
1796
|
-
* its last four cells are `—`. Null when the row is outside the grammar; the
|
|
1797
|
-
* caller checks the T sequence.
|
|
1798
|
-
*
|
|
1799
|
-
* @param {string} line
|
|
1800
|
-
* @returns {Omit<WaveRow, 'line'> | null}
|
|
1801
|
-
*/
|
|
1802
|
-
function parseWaveRow(line) {
|
|
1803
|
-
if (!line.startsWith('| ') || !line.endsWith(' |')) return null;
|
|
1804
|
-
const cells = line.slice(2, -2).split(' | ');
|
|
1805
|
-
if (cells.length !== 7) return null;
|
|
1806
|
-
const [t, ticket, verdict, evaluate, test, surviving, coverage] = cells;
|
|
1807
|
-
if (!WAVE_T_RE.test(t) || !WAVE_TICKET_RE.test(ticket)
|
|
1808
|
-
|| !WAVE_VERDICTS.includes(/** @type {WaveVerdict} */ (verdict))
|
|
1809
|
-
|| !WAVE_GATE_VALUES.includes(evaluate) || !WAVE_GATE_VALUES.includes(test)
|
|
1810
|
-
|| !WAVE_SURVIVING_RE.test(surviving) || !WAVE_COVERAGE_VALUES.includes(coverage)) return null;
|
|
1811
|
-
if (verdict === 'BLOCKED' && [evaluate, test, surviving, coverage].some(c => c !== EM_DASH)) return null;
|
|
1812
|
-
return {
|
|
1813
|
-
k: Number(t.slice(1)),
|
|
1814
|
-
ticket: ticket === NO_TICKET ? null : ticket,
|
|
1815
|
-
verdict: /** @type {WaveVerdict} */ (verdict),
|
|
1816
|
-
evaluate,
|
|
1817
|
-
test,
|
|
1818
|
-
surviving: surviving === EM_DASH ? null : Number(surviving),
|
|
1819
|
-
coverage,
|
|
1820
|
-
};
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
/**
|
|
1824
|
-
* D-WAVE-LINKS-ONLY: the links-only block, for related lines that ran to the end of the
|
|
1825
|
-
* text. Only `Refs #<n>` and `Refs <KEY-N>` lines make a block of their own (RELATED_LINE_RE
|
|
1826
|
-
* has already shaped each ref), and a ref may not repeat. Null when the lines are not all
|
|
1827
|
-
* `Refs` lines: the caller refuses the block as before.
|
|
1828
|
-
*
|
|
1829
|
-
* @param {readonly WaveRelated[]} related
|
|
1830
|
-
* @returns {Result<WaveBlock> | null}
|
|
1831
|
-
*/
|
|
1832
|
-
function parseLinksOnlyBlock(related) {
|
|
1833
|
-
if (related.length === 0 || !related.every(rel => rel.keyword === 'Refs')) return null;
|
|
1834
|
-
/** @type {Set<string>} */
|
|
1835
|
-
const seen = new Set();
|
|
1836
|
-
for (const rel of related) {
|
|
1837
|
-
if (seen.has(rel.ref)) return fail('duplicate', rel.line);
|
|
1838
|
-
seen.add(rel.ref);
|
|
1839
|
-
}
|
|
1840
|
-
return ok(Object.freeze({ tracking: null, related: Object.freeze(related), rows: Object.freeze([]) }));
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
/**
|
|
1844
|
-
* D-WAVE: parse a wave block (LF or CRLF, at most WAVE_BLOCK_CHARS). It holds
|
|
1845
|
-
* exactly, in order: `## Related Issues`; 0–WAVE_ROWS + 1 related lines; one
|
|
1846
|
-
* blank line; `## Wave Evidence`; the header and separator verbatim; 1–WAVE_ROWS
|
|
1847
|
-
* rows T1…Tn; then only blank lines. No leading text, no other line anywhere.
|
|
1848
|
-
*
|
|
1849
|
-
* D-WAVE-CLOSE — the cross rules, which make a wrong closing line unrepresentable:
|
|
1850
|
-
* - a related ref names exactly one row's Ticket (`orphan` when none — the one
|
|
1851
|
-
* tracking line below aside), and no ref repeats (`duplicate`) — nor does a
|
|
1852
|
-
* Ticket across rows (`duplicate`), so "exactly one" is never two;
|
|
1853
|
-
* - `Closes` names only a PASS or UNVERIFIED row (`unmerged`);
|
|
1854
|
-
* - every PASS or UNVERIFIED row with a Ticket has its related line (`unlinked`);
|
|
1855
|
-
* - so any other row carries at most one line, and that line is a `Refs`.
|
|
1856
|
-
*
|
|
1857
|
-
* D-WAVE-TRACKING: the FIRST related line may instead be the wave's tracking
|
|
1858
|
-
* issue — a `Refs` line whose ref names no row. It is admitted once and only
|
|
1859
|
-
* there: a later line naming no row is `orphan`, a second leading one included,
|
|
1860
|
-
* and a `Closes` naming no row is `orphan` wherever it stands, because a tracking
|
|
1861
|
-
* issue outlives the wave and the wave PR never closes it. The line is shape-gated
|
|
1862
|
-
* like every related line, so it adds no free text to the body. A leading `Refs`
|
|
1863
|
-
* that names a row is that row's line, never a tracking line.
|
|
1864
|
-
*
|
|
1865
|
-
* D-WAVE-LINKS-ONLY: with `options.linksOnly === true` (the opt-in, spelled
|
|
1866
|
-
* `check wave --links-only` on the command line), a block may also be the
|
|
1867
|
-
* `## Related Issues` heading and 1–WAVE_ROWS + 1 `Refs #<n>` or `Refs <KEY-N>` lines and
|
|
1868
|
-
* nothing after them (blank lines aside): no evidence section, no rows. It parses to no
|
|
1869
|
-
* tracking line and no rows. A ref may not repeat (`duplicate`). `Closes` is never
|
|
1870
|
-
* admitted there: with no row, nothing has merged, so nothing may close an issue.
|
|
1871
|
-
* Without the opt-in, and for any other block that stops after its related lines, a lone
|
|
1872
|
-
* `Closes #<n>` included, the block is `malformed`, as the grammar stood before the opt-in.
|
|
1873
|
-
*
|
|
1874
|
-
* Structure is checked before the cross rules, so the first failure found is the
|
|
1875
|
-
* one reported. An error carries a code and a line number, never input bytes.
|
|
1876
|
-
*
|
|
1877
|
-
* @param {unknown} text
|
|
1878
|
-
* @param {{ linksOnly?: boolean }} [options]
|
|
1879
|
-
* @returns {Result<WaveBlock>}
|
|
1880
|
-
*/
|
|
1881
|
-
function parseWaveBlock(text, options) {
|
|
1882
|
-
if (typeof text !== 'string') return fail('invalid');
|
|
1883
|
-
if (text.length > LIMITS.WAVE_BLOCK_CHARS) return fail('oversize');
|
|
1884
|
-
const rows = textLines(text);
|
|
1885
|
-
let end = rows.length;
|
|
1886
|
-
while (end > 0 && rows[end - 1] === '') end--;
|
|
1887
|
-
if (rows[0] !== WAVE_HEADINGS[0]) return fail('malformed', 1);
|
|
1888
|
-
|
|
1889
|
-
/** @type {WaveRelated[]} */
|
|
1890
|
-
const related = [];
|
|
1891
|
-
let i = 1;
|
|
1892
|
-
for (; i < end && rows[i] !== ''; i++) {
|
|
1893
|
-
// One line per row at most, plus the tracking line (D-WAVE-TRACKING).
|
|
1894
|
-
if (related.length === LIMITS.WAVE_ROWS + 1) return fail('oversize', i + 1);
|
|
1895
|
-
const m = RELATED_LINE_RE.exec(rows[i]);
|
|
1896
|
-
if (m === null || m.groups === undefined) return fail('malformed', i + 1);
|
|
1897
|
-
const closes = m.groups.closes;
|
|
1898
|
-
related.push(Object.freeze({
|
|
1899
|
-
keyword: /** @type {'Closes' | 'Refs'} */ (closes === undefined ? 'Refs' : 'Closes'),
|
|
1900
|
-
ref: closes === undefined ? m.groups.refs : closes,
|
|
1901
|
-
line: i + 1,
|
|
1902
|
-
}));
|
|
1903
|
-
}
|
|
1904
|
-
// D-WAVE-LINKS-ONLY: the opt-in is set and the related lines ran to the end of the text.
|
|
1905
|
-
if (isObject(options) && options.linksOnly === true && i >= end) {
|
|
1906
|
-
const linksOnly = parseLinksOnlyBlock(related);
|
|
1907
|
-
if (linksOnly !== null) return linksOnly;
|
|
1908
|
-
}
|
|
1909
|
-
// rows[i] is the one blank line; indices past `end` hold only blanks, so a
|
|
1910
|
-
// missing section fails the first comparison that expects a non-blank line.
|
|
1911
|
-
if (rows[i + 1] !== WAVE_HEADINGS[1]) return fail('malformed', i + 2);
|
|
1912
|
-
if (rows[i + 2] !== WAVE_TABLE_HEADER) return fail('malformed', i + 3);
|
|
1913
|
-
if (rows[i + 3] !== WAVE_TABLE_SEPARATOR) return fail('malformed', i + 4);
|
|
1914
|
-
|
|
1915
|
-
/** @type {WaveRow[]} */
|
|
1916
|
-
const table = [];
|
|
1917
|
-
/** @type {Map<string, WaveRow>} */
|
|
1918
|
-
const byTicket = new Map();
|
|
1919
|
-
for (let j = i + 4; j < end; j++) {
|
|
1920
|
-
if (table.length === LIMITS.WAVE_ROWS) return fail('oversize', j + 1);
|
|
1921
|
-
const parsed = parseWaveRow(rows[j]);
|
|
1922
|
-
if (parsed === null) return fail('malformed', j + 1);
|
|
1923
|
-
if (parsed.k !== table.length + 1) return fail('order', j + 1);
|
|
1924
|
-
const r = Object.freeze({ ...parsed, line: j + 1 });
|
|
1925
|
-
if (r.ticket !== null) {
|
|
1926
|
-
if (byTicket.has(r.ticket)) return fail('duplicate', j + 1);
|
|
1927
|
-
byTicket.set(r.ticket, r);
|
|
1928
|
-
}
|
|
1929
|
-
table.push(r);
|
|
1930
|
-
}
|
|
1931
|
-
if (table.length === 0) return fail('empty');
|
|
1932
|
-
|
|
1933
|
-
// D-WAVE-TRACKING: only the first line, only as `Refs`, and only naming no row.
|
|
1934
|
-
const lead = related[0];
|
|
1935
|
-
const tracking = lead !== undefined && lead.keyword === 'Refs' && !byTicket.has(lead.ref) ? lead : null;
|
|
1936
|
-
const rowLines = tracking === null ? related : related.slice(1);
|
|
1937
|
-
|
|
1938
|
-
/** @type {Set<string>} */
|
|
1939
|
-
const linked = new Set();
|
|
1940
|
-
for (const rel of rowLines) {
|
|
1941
|
-
const r = byTicket.get(rel.ref);
|
|
1942
|
-
if (r === undefined) return fail('orphan', rel.line);
|
|
1943
|
-
if (linked.has(rel.ref)) return fail('duplicate', rel.line);
|
|
1944
|
-
if (rel.keyword === 'Closes' && !WAVE_MERGED_VERDICTS.includes(r.verdict)) return fail('unmerged', rel.line);
|
|
1945
|
-
linked.add(rel.ref);
|
|
1946
|
-
}
|
|
1947
|
-
for (const r of table) {
|
|
1948
|
-
if (r.ticket !== null && WAVE_MERGED_VERDICTS.includes(r.verdict) && !linked.has(r.ticket)) return fail('unlinked', r.line);
|
|
1949
|
-
}
|
|
1950
|
-
return ok(Object.freeze({ tracking, related: Object.freeze(rowLines), rows: Object.freeze(table) }));
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
1696
|
// ---------------------------------------------------------------------------
|
|
1954
1697
|
// Exports — frozen; verify-evidence.cjs and the unit tests are the consumers
|
|
1955
1698
|
// ---------------------------------------------------------------------------
|
|
@@ -1972,14 +1715,6 @@ module.exports = Object.freeze({
|
|
|
1972
1715
|
SHA_RE,
|
|
1973
1716
|
GLOB_RE,
|
|
1974
1717
|
LOGIN_RE,
|
|
1975
|
-
WAVE_HEADINGS,
|
|
1976
|
-
WAVE_TABLE_HEADER,
|
|
1977
|
-
WAVE_VERDICTS,
|
|
1978
|
-
WAVE_MERGED_VERDICTS,
|
|
1979
|
-
WAVE_GATE_VALUES,
|
|
1980
|
-
RELATED_LINE_RE,
|
|
1981
|
-
WAVE_TICKET_RE,
|
|
1982
|
-
parseWaveBlock,
|
|
1983
1718
|
parsePlan,
|
|
1984
1719
|
parseClaims,
|
|
1985
1720
|
parseExceptions,
|
|
@@ -126,9 +126,7 @@ const tty = require('tty');
|
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
128
|
* The strict base-name class, one literal. Every prompt that checks a base name copies its
|
|
129
|
-
* `source` exactly, and a test holds each copy equal to this export.
|
|
130
|
-
* variant is the dynamic-build helper's BRANCH_NAME: a ticket branch is local, so the helper
|
|
131
|
-
* holds this class less the `origin/` refusal, and a test holds that relation. In words: 1 to
|
|
129
|
+
* `source` exactly, and a test holds each copy equal to this export. In words: 1 to
|
|
132
130
|
* 200 ASCII letters, digits and `._/+@,#-`; not starting with `-`, `#` or `origin/`; no `..`;
|
|
133
131
|
* not the whole name `@`, `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` or `MERGE_HEAD`.
|
|
134
132
|
*/
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
// through SHA ancestry, the diff since the claim, and `gh run view --attempt`.
|
|
10
10
|
//
|
|
11
11
|
// Usage:
|
|
12
|
-
// node verify-evidence.cjs check tp|block|exceptions
|
|
13
|
-
// node verify-evidence.cjs check wave --links-only <file>
|
|
12
|
+
// node verify-evidence.cjs check tp|block|exceptions <file>
|
|
14
13
|
// node verify-evidence.cjs render --plan <file>
|
|
15
14
|
// node verify-evidence.cjs verify --pr <n> [--publication <mode>] [--evidence <file>]
|
|
16
15
|
// [--state <dir>] [--block-out <file>] [--comment-out <file>] [--stale-out <file>] [--approval]
|
|
@@ -25,10 +24,7 @@
|
|
|
25
24
|
// stdout (D-VERIFY-STDOUT) is empty or exactly one of these shapes, each closed-vocabulary
|
|
26
25
|
// (the render block's TP lines come from the caller's own plan file):
|
|
27
26
|
// check nothing — the exit code is the answer; the diagnostic is on stderr.
|
|
28
|
-
//
|
|
29
|
-
// `## Related Issues` heading and `Refs #<n>` or `Refs <KEY-N>` lines, no
|
|
30
|
-
// evidence section); plain `check wave` refuses that block as malformed
|
|
31
|
-
// render the creation block: markers, `## Test Plan`, every TP unticked
|
|
27
|
+
// render the creation block: markers, `## Test Plan`, every TP unticked
|
|
32
28
|
// verify EVIDENCE pr:<n> head:<sha> total:<n> VERIFIED-CI:<n> ATTESTED-LOCAL:<n>
|
|
33
29
|
// UNVERIFIED:<n> STALE:<n> FAILED:<n> INDETERMINATE:<n> stale:<ids|none>
|
|
34
30
|
// exceptions:<kinds|none> approval:<yes|no|unchecked> key:<hex>
|
|
@@ -262,8 +258,7 @@ const CLAIM_REFUSAL_RE = /^CLAIM refused \((?<reason>head not one sha|evidence f
|
|
|
262
258
|
const CLAIM_ARG_RE = /^TP-(?<n>[1-9][0-9]{0,2})=(?<outcome>PASS|FAIL|SKIP)(?::(?<exit>[^\s]{1,12}))?$/;
|
|
263
259
|
|
|
264
260
|
const USAGE = [
|
|
265
|
-
'Usage: node verify-evidence.cjs check tp|block|exceptions
|
|
266
|
-
' node verify-evidence.cjs check wave --links-only <file>',
|
|
261
|
+
'Usage: node verify-evidence.cjs check tp|block|exceptions <file>',
|
|
267
262
|
' node verify-evidence.cjs render --plan <file>',
|
|
268
263
|
' node verify-evidence.cjs verify --pr <n> [--publication <mode>] [--evidence <file>]',
|
|
269
264
|
' [--state <dir>] [--block-out <file>] [--comment-out <file>] [--stale-out <file>] [--approval]',
|
|
@@ -323,7 +318,7 @@ const USAGE = [
|
|
|
323
318
|
|
|
324
319
|
/**
|
|
325
320
|
* @typedef {{ kind: 'usage' }
|
|
326
|
-
* | { kind: 'check', what: 'tp' | 'block' | 'exceptions'
|
|
321
|
+
* | { kind: 'check', what: 'tp' | 'block' | 'exceptions', file: string }
|
|
327
322
|
* | { kind: 'render', plan: string }
|
|
328
323
|
* | { kind: 'verify', pr: number, publication: string, evidence: string | null, state: string | null,
|
|
329
324
|
* blockOut: string | null, commentOut: string | null, staleOut: string | null, approval: boolean }
|
|
@@ -394,12 +389,10 @@ function parseArgs(argv) {
|
|
|
394
389
|
const sub = rest[0];
|
|
395
390
|
const tail = rest.slice(1);
|
|
396
391
|
if (sub === 'check') {
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
if ((tail.length !== 2 && !linksOnly) || !['tp', 'block', 'exceptions', 'wave'].includes(tail[0])) return USAGE_ARGS;
|
|
400
|
-
const file = tail[tail.length - 1];
|
|
392
|
+
if (tail.length !== 2 || !['tp', 'block', 'exceptions'].includes(tail[0])) return USAGE_ARGS;
|
|
393
|
+
const file = tail[1];
|
|
401
394
|
if (typeof file !== 'string' || file === '' || file.startsWith('-') || file.length > MAX_ARG_CHARS) return USAGE_ARGS;
|
|
402
|
-
return { kind: 'check', what: /** @type {'tp' | 'block' | 'exceptions'
|
|
395
|
+
return { kind: 'check', what: /** @type {'tp' | 'block' | 'exceptions'} */ (tail[0]), file };
|
|
403
396
|
}
|
|
404
397
|
if (sub === 'render') {
|
|
405
398
|
const f = parseFlags(tail, { '--plan': 'value' });
|
|
@@ -2137,8 +2130,7 @@ function sectionOrWhole(text, section) {
|
|
|
2137
2130
|
}
|
|
2138
2131
|
|
|
2139
2132
|
/**
|
|
2140
|
-
* `check
|
|
2141
|
-
* own, never a section of an evidence file.
|
|
2133
|
+
* `check tp|block|exceptions`: the exit code is the answer, and stdout stays empty.
|
|
2142
2134
|
*
|
|
2143
2135
|
* @param {Io} io
|
|
2144
2136
|
* @param {Extract<ParsedArgs, { kind: 'check' }>} args
|
|
@@ -2150,12 +2142,15 @@ function runCheck(io, args) {
|
|
|
2150
2142
|
let result;
|
|
2151
2143
|
if (args.what === 'tp') result = PE.parsePlan(sectionOrWhole(text, 'testPlan'));
|
|
2152
2144
|
else if (args.what === 'exceptions') result = PE.parseExceptions(sectionOrWhole(text, 'exceptions'));
|
|
2153
|
-
else if (args.what === '
|
|
2154
|
-
else {
|
|
2145
|
+
else if (args.what === 'block') {
|
|
2155
2146
|
const b = PE.parseBlock(text);
|
|
2156
2147
|
// R7 pastes only the creation form: TP lines, none ticked.
|
|
2157
2148
|
result = b.ok && (b.value.kind !== 'lines' || b.value.ticked.length > 0) ? { ok: false, error: { code: 'invalid', line: 0 } } : b;
|
|
2158
2149
|
}
|
|
2150
|
+
if (result === undefined) {
|
|
2151
|
+
io.stderr(USAGE + '\n');
|
|
2152
|
+
return { code: EXIT_CODES.USAGE, stdout: '' };
|
|
2153
|
+
}
|
|
2159
2154
|
if (result.ok) return { code: EXIT_CODES.OK, stdout: '' };
|
|
2160
2155
|
io.stderr('verify-evidence: check ' + args.what + ': ' + result.error.code
|
|
2161
2156
|
+ (result.error.line > 0 ? ' at line ' + result.error.line : '') + '\n');
|
|
@@ -46,17 +46,9 @@ All generated documentation lives under `.devflow/docs/` at the checkout's tople
|
|
|
46
46
|
├── design/
|
|
47
47
|
│ ├── {ISSUE_ID}-{topic-slug}.{timestamp}.md # /plan, one issue
|
|
48
48
|
│ ├── multi-{topic-slug}.{timestamp}.md # /plan, several issues (frontmatter issue: pending)
|
|
49
|
-
│
|
|
50
|
-
│ └── {slug}/{timestamp}/ # /dynamic-plan run
|
|
51
|
-
│ ├── {ticket-slug}-plan.md # one plan per ticket
|
|
52
|
-
│ └── DECISIONS-NEEDED.md
|
|
53
|
-
├── tickets/{slug}/{timestamp}/ # /dynamic-tickets
|
|
54
|
-
│ ├── {ticket-slug}.md
|
|
55
|
-
│ └── tracking-issue.md
|
|
56
|
-
├── waves/{slug}/{timestamp}/wave-report.md # /dynamic-build
|
|
49
|
+
│ └── {topic-slug}.{timestamp}.md # /plan, no issue
|
|
57
50
|
├── handoff-{branch-slug}.md # Code agent phase handoff; deleted once the PR exists
|
|
58
|
-
|
|
59
|
-
└── evidence-wave-{slug}.md # /dynamic-build wave test plan and claims
|
|
51
|
+
└── evidence-{branch-slug}.md # /implement test plan and claims; stays after the PR
|
|
60
52
|
|
|
61
53
|
.devflow/memory/
|
|
62
54
|
├── WORKING-MEMORY.md # Auto-maintained by Stop hook (overwritten)
|
|
@@ -77,7 +69,6 @@ The head markers for incremental runs are not part of this tree. `/code-review`
|
|
|
77
69
|
|
|
78
70
|
`YYYY-MM-DD_HHMM`, local time (sortable, readable), computed once per run and reused for every path of that run.
|
|
79
71
|
- **Same-minute collision.** `/code-review`, `/bug-analysis` and `/research` create their run directory themselves: when it already exists, the second run appends seconds (`2025-12-26_143005`). It sorts after its minute, so newest-first listings stay correct.
|
|
80
|
-
- **Workflow runs** (`tickets/`, `design/{slug}/`, `waves/`) are stamped in local time by the workflow that creates them and take no collision suffix: a second run in the same minute writes into the first's directory.
|
|
81
72
|
- A design document's timestamp is the suffix before `.md`, so a listing sorts by that suffix, not by the leading `{ISSUE_ID}`.
|
|
82
73
|
|
|
83
74
|
### Branch Slugs
|
|
@@ -87,16 +78,11 @@ A slug builds a path or reaches a shell only when it matches `^[A-Za-z0-9._-]{1,
|
|
|
87
78
|
|
|
88
79
|
### Topic Slugs
|
|
89
80
|
|
|
90
|
-
One rule for every topic
|
|
81
|
+
One rule for every topic `{slug}`: lowercase; each run of characters outside `a-z0-9` becomes one `-`; leading and trailing `-` are trimmed; at most 50 characters (trim again after the cut); `untitled` when nothing is left:
|
|
91
82
|
```bash
|
|
92
83
|
TOPIC_SLUG=$(s=$(echo "$TOPIC" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' | cut -c1-50 | sed -E 's/-+$//'); echo "${s:-untitled}")
|
|
93
84
|
```
|
|
94
|
-
`/plan` and `/research` take `{topic-slug}` from it.
|
|
95
|
-
|
|
96
|
-
### Exceptions
|
|
97
|
-
|
|
98
|
-
- **`{ticket-slug}`** (`tickets/…/{ticket-slug}.md`, `design/…/{ticket-slug}-plan.md`): the ticket title lowercased with each run outside `a-z0-9` as one `-`, nothing trimmed and no length cap. `/dynamic-tickets` matches a ticket file by this name when it amends the ticket, so trimming would rename a file its own amend phase already wrote.
|
|
99
|
-
- **Wave `{slug}`** (`waves/`, `evidence-wave-{slug}.md`): the part of the integration branch after `wave/`, at most 60 characters, not derived by the topic rule. It makes `evidence-wave-{slug}.md` the `evidence-{branch-slug}.md` of the branch `wave/{slug}`.
|
|
85
|
+
`/plan` and `/research` take `{topic-slug}` from it.
|
|
100
86
|
|
|
101
87
|
### File Naming Patterns
|
|
102
88
|
|
|
@@ -126,7 +112,7 @@ ensure_docs_dir() { mkdir -p "$(get_docs_root)/.devflow/docs/$1"; }
|
|
|
126
112
|
- **Reviews**: the Review agents and the Synthesize agent (review mode) write into `reviews/{branch-slug}/{timestamp}/`; `/resolve` adds `resolution-summary.md` (Phase 5); `/code-review` overwrites its `.last-review-head` marker, under the git directory, with the HEAD SHA.
|
|
127
113
|
- **Bug analysis**: the Debug agents and the Synthesize agent (bug-analysis mode) write into `bug-analysis/{branch-slug}/{timestamp}/`; the scanner calls write the SARIF files, the changed-name list for the Snyk filter and the CodeQL log, and the counts step writes the findings tables, all into that run folder; `/bug-analysis` overwrites its `.last-analysis-head` marker, under the git directory, with the HEAD SHA only when every tool that was to run completed, or `--no-static` is set.
|
|
128
114
|
- **Research**: the Research agents write `{type}.md`, and the Synthesize agent (research mode) writes `research-summary.md` through `RESEARCH_BASE_DIR`, both into `research/{topic-slug}/{timestamp}/`.
|
|
129
|
-
- **Design**: `/plan` writes the design document
|
|
115
|
+
- **Design**: `/plan` writes the design document.
|
|
130
116
|
- **Handoff and evidence**: `/implement` writes `evidence-{branch-slug}.md`, which stays after the PR, and the handoff file, to which each Code agent appends one phase section and which is deleted once the PR exists.
|
|
131
117
|
- **Memory and curation**: the Stop hook overwrites `.devflow/memory/WORKING-MEMORY.md`; `decisions.md` and `pitfalls.md` are rendered from `decisions-ledger.jsonl` (active entries in full, inactive ones in an Inactive table).
|
|
132
118
|
|
|
@@ -18,8 +18,7 @@ Correct patterns for Devflow documentation artifacts with templates and helper f
|
|
|
18
18
|
│ └── {timestamp}/ # Incremental review
|
|
19
19
|
│ └── ...
|
|
20
20
|
├── design/ # Implementation plans
|
|
21
|
-
│
|
|
22
|
-
│ └── {slug}/{timestamp}/ # /dynamic-plan: {ticket-slug}-plan.md, DECISIONS-NEEDED.md
|
|
21
|
+
│ └── {ISSUE_ID}-{topic-slug}.{timestamp}.md # /plan (multi-{topic-slug} / {topic-slug} without an issue)
|
|
23
22
|
└── CATCH_UP.md # Latest summary (overwritten)
|
|
24
23
|
```
|
|
25
24
|
|