yadflow 3.18.1 → 4.0.0-next.1
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 +355 -0
- package/README.md +79 -26
- package/bin/commands.mjs +41 -0
- package/bin/yad.mjs +437 -124
- package/cli/artifact-status.mjs +34 -15
- package/cli/checkpoint.mjs +69 -49
- package/cli/codeowners-command.mjs +170 -0
- package/cli/codeowners.mjs +397 -0
- package/cli/commit.mjs +13 -9
- package/cli/companion.mjs +2 -2
- package/cli/dial.mjs +183 -0
- package/cli/docs.mjs +88 -32
- package/cli/doctor.mjs +1472 -97
- package/cli/epic-state.mjs +3478 -232
- package/cli/epic.mjs +506 -0
- package/cli/errors.mjs +4 -1
- package/cli/gate.mjs +1002 -209
- package/cli/history.mjs +556 -0
- package/cli/hook.mjs +266 -55
- package/cli/hubcommit.mjs +6 -17
- package/cli/index-command.mjs +87 -0
- package/cli/ledger.mjs +57 -7
- package/cli/lib.mjs +184 -18
- package/cli/manifest.mjs +367 -56
- package/cli/migrate.mjs +726 -53
- package/cli/mode.mjs +170 -0
- package/cli/next.mjs +349 -90
- package/cli/openpr.mjs +191 -39
- package/cli/people.mjs +654 -0
- package/cli/plan.mjs +417 -132
- package/cli/platform.mjs +110 -129
- package/cli/product-index.mjs +287 -0
- package/cli/protection.mjs +706 -0
- package/cli/reconcile.mjs +38 -12
- package/cli/repo-publish.mjs +24 -26
- package/cli/repo.mjs +23 -14
- package/cli/report.mjs +21 -15
- package/cli/review.mjs +24 -27
- package/cli/riskmap-command.mjs +289 -0
- package/cli/riskmap.mjs +373 -0
- package/cli/setup.mjs +139 -287
- package/cli/ship.mjs +7 -6
- package/cli/skill.mjs +180 -0
- package/cli/skip.mjs +211 -30
- package/cli/thread.mjs +42 -17
- package/cli/tidy.mjs +20 -20
- package/cli/update-commit.mjs +22 -22
- package/cli/usage.mjs +115 -109
- package/package.json +3 -3
- package/skills/sdlc/config.yaml +166 -87
- package/skills/sdlc/module-help.csv +35 -35
- package/skills/yad-analysis/SKILL.md +125 -65
- package/skills/yad-architecture/SKILL.md +34 -23
- package/skills/yad-architecture/references/contract-format.md +10 -8
- package/skills/yad-backfill/SKILL.md +14 -8
- package/skills/yad-backfill/references/backfill.md +1 -1
- package/skills/yad-change/SKILL.md +127 -52
- package/skills/yad-change/references/triage.md +42 -28
- package/skills/yad-checks/SKILL.md +89 -45
- package/skills/yad-checks/references/check-gates.md +315 -92
- package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
- package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
- package/skills/yad-checks/templates/checks/contract-check.sh +58 -2
- package/skills/yad-checks/templates/checks/epic-open.sh +3 -3
- package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
- package/skills/yad-checks/templates/checks/ledger-guard.sh +94 -18
- package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
- package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +4 -4
- package/skills/yad-checks/templates/checks/risk-map-check.sh +438 -0
- package/skills/yad-checks/templates/checks/verified-commits.sh +20 -46
- package/skills/yad-checks/templates/github/yad-checks.yml +37 -5
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
- package/skills/yad-checks/templates/github/yad-update-guard.yml +3 -4
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
- package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +22 -4
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +5 -5
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +4 -4
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +38 -7
- package/skills/yad-commit/SKILL.md +6 -6
- package/skills/yad-connect-design/SKILL.md +6 -6
- package/skills/yad-connect-design/references/design-context.md +1 -1
- package/skills/yad-connect-design/references/design-registry.md +2 -2
- package/skills/yad-connect-docs/SKILL.md +12 -12
- package/skills/yad-connect-docs/references/docs-registry.md +1 -1
- package/skills/yad-connect-learning/SKILL.md +5 -5
- package/skills/yad-connect-learning/references/learning-registry.md +2 -2
- package/skills/yad-connect-repos/SKILL.md +92 -54
- package/skills/yad-connect-repos/references/code-context.md +6 -6
- package/skills/yad-connect-repos/references/hub-config.md +68 -58
- package/skills/yad-connect-repos/references/repos-registry.md +10 -9
- package/skills/yad-connect-repos/references/risk-map.md +81 -0
- package/skills/yad-connect-testing/SKILL.md +6 -6
- package/skills/yad-connect-testing/references/testing-context.md +3 -4
- package/skills/yad-connect-testing/references/testing-registry.md +2 -2
- package/skills/yad-defects/SKILL.md +8 -8
- package/skills/yad-discovery/SKILL.md +130 -94
- package/skills/yad-discovery/references/discovery-schema.md +23 -7
- package/skills/yad-discovery/references/foundation-schema.md +374 -0
- package/skills/yad-docs/SKILL.md +16 -11
- package/skills/yad-docs/references/data-mapping.md +9 -7
- package/skills/yad-docs/templates/app/package-lock.json +3 -3
- package/skills/yad-docs-overview/SKILL.md +32 -17
- package/skills/yad-docs-overview/references/pipeline-model.md +47 -28
- package/skills/yad-docs-sync/SKILL.md +10 -5
- package/skills/yad-docs-sync/references/staleness.md +8 -7
- package/skills/yad-engineer-review/SKILL.md +88 -24
- package/skills/yad-engineer-review/references/ship-and-record.md +25 -16
- package/skills/yad-epic/SKILL.md +178 -100
- package/skills/yad-epic/references/state-schema.md +626 -117
- package/skills/yad-hub-bridge/SKILL.md +66 -48
- package/skills/yad-hub-bridge/references/bridge.md +110 -83
- package/skills/yad-hub-bridge/references/login-roster.md +163 -70
- package/skills/yad-hub-bridge/templates/checks/hub-route.sh +22 -19
- package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +34 -14
- package/skills/yad-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +2 -2
- package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +22 -12
- package/skills/yad-implement/SKILL.md +29 -15
- package/skills/yad-implement/references/implement-conventions.md +2 -2
- package/skills/yad-learn/SKILL.md +9 -9
- package/skills/yad-learn/references/learning-state.md +2 -2
- package/skills/yad-open-pr/SKILL.md +64 -29
- package/skills/yad-pair-review/SKILL.md +18 -16
- package/skills/yad-pair-review/references/session-state.md +4 -4
- package/skills/yad-pr-template/SKILL.md +48 -27
- package/skills/yad-pr-template/references/risk-routing.md +97 -24
- package/skills/yad-pr-template/templates/checks/pr-template.sh +37 -15
- package/skills/yad-pr-template/templates/checks/pr-title.sh +27 -13
- package/skills/yad-pr-template/templates/checks/risk-route.sh +107 -14
- package/skills/yad-pr-template/templates/github/pull_request_template.md +7 -5
- package/skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md +7 -5
- package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +15 -14
- package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +15 -13
- package/skills/yad-reconcile/SKILL.md +3 -3
- package/skills/yad-report/SKILL.md +5 -5
- package/skills/yad-review-companion/SKILL.md +12 -9
- package/skills/yad-review-gate/SKILL.md +198 -79
- package/skills/yad-review-gate/references/gating.md +230 -54
- package/skills/yad-run/SKILL.md +86 -56
- package/skills/yad-run/references/run-loop.md +67 -45
- package/skills/yad-ship/SKILL.md +18 -14
- package/skills/yad-spec/SKILL.md +31 -17
- package/skills/yad-spec/references/spec-handoff.md +17 -5
- package/skills/yad-status/SKILL.md +114 -56
- package/skills/yad-stories/SKILL.md +42 -27
- package/skills/yad-stories/references/story-schema.md +10 -9
- package/skills/yad-stub/SKILL.md +59 -48
- package/skills/yad-sync-repos/SKILL.md +3 -3
- package/skills/yad-test-cases/SKILL.md +37 -30
- package/skills/yad-test-cases/references/test-cases-schema.md +8 -5
- package/skills/yad-timeline/SKILL.md +8 -7
- package/skills/yad-ui/SKILL.md +46 -25
- package/cli/roster.mjs +0 -164
- package/skills/sdlc/install.sh +0 -68
|
@@ -5,9 +5,9 @@ in files on disk. Nothing hidden."). No database, no browser storage.
|
|
|
5
5
|
|
|
6
6
|
## Every file states its shape — `schemaVersion`
|
|
7
7
|
|
|
8
|
-
Each JSON **object** the CLI writes under a `.sdlc/` directory carries `"schemaVersion"
|
|
9
|
-
first key. It says what shape the file is in, so
|
|
10
|
-
upgrade it instead of guessing.
|
|
8
|
+
Each JSON **object** the CLI writes under a `.sdlc/` directory carries a `"schemaVersion"` as its
|
|
9
|
+
first key, holding the shape this release writes — **10** today. It says what shape the file is in, so
|
|
10
|
+
a future release can recognise an older file and upgrade it instead of guessing.
|
|
11
11
|
|
|
12
12
|
**When you author one of these files by hand, include the key**, exactly as the examples below show.
|
|
13
13
|
Several kinds here — `state.json` on the seeding path, `change.json`, `contract-lock.json`,
|
|
@@ -18,7 +18,7 @@ nothing to flip.
|
|
|
18
18
|
|
|
19
19
|
```json
|
|
20
20
|
{
|
|
21
|
-
"schemaVersion":
|
|
21
|
+
"schemaVersion": 10,
|
|
22
22
|
"epicId": "EP-checkout"
|
|
23
23
|
}
|
|
24
24
|
```
|
|
@@ -28,14 +28,42 @@ Three rules go with it, and they are permanent (`docs/roadmap-idea-1.md`, Part 2
|
|
|
28
28
|
1. **A file with no version counts as version 1.** Nothing has to be rewritten to be readable. Files
|
|
29
29
|
written before the stamp existed are read as shape 1, and get the key the next time the engine
|
|
30
30
|
writes them.
|
|
31
|
-
2. **The
|
|
32
|
-
`reconcile-debt.json` are JSON arrays at the top level, and an array cannot hold a key. Rule 1
|
|
31
|
+
2. **The list files never carry it.** `approvals.json`, `comments.json`, `hub-prs.json` (also written
|
|
32
|
+
as `product-prs.json`) and `reconcile-debt.json` are JSON arrays at the top level, and an array cannot hold a key. Rule 1
|
|
33
33
|
covers them: no version means version 1.
|
|
34
34
|
3. **`schemaVersion` is not the CLI version.** `.sdlc/cli-version.json` records which release of the
|
|
35
35
|
`yad` CLI set the project up, and changes on every release. `schemaVersion` describes the file's
|
|
36
36
|
shape and changes only when that shape really changes — which is rare, and always paired with a
|
|
37
37
|
`yad migrate` step that moves existing projects onto it.
|
|
38
38
|
|
|
39
|
+
## The Product index — `.sdlc/index.json` (derived; never write it by hand)
|
|
40
|
+
|
|
41
|
+
One file at the Product root that summarizes every work item (E19). It is **derived** from each item's
|
|
42
|
+
`state.json`, `epic.md` and, for the title of an item with none in `epic.md`, `change.json`, and rebuilt by `yad index` or a gate write on the default branch. On a
|
|
43
|
+
verified Product only CI writes it, and the ledger guard rejects anyone else. **A skill never writes
|
|
44
|
+
it.** A skill that writes `state.json` by hand leaves it behind, and `yad doctor` says so. To read it
|
|
45
|
+
as the files say it is now, run `yad index --json`.
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"schemaVersion": 10,
|
|
50
|
+
"inputs": "sha256:…",
|
|
51
|
+
"items": [
|
|
52
|
+
{ "id": "EP-checkout", "dir": "epics/EP-checkout", "title": "Checkout from the mobile app",
|
|
53
|
+
"kind": null, "type": "feature", "theme": null,
|
|
54
|
+
"parent": null, "thread": "EP-checkout", "profile": "classic", "currentStep": "architecture",
|
|
55
|
+
"createdAt": "2026-06-04", "repos": ["backend"],
|
|
56
|
+
"steps": { "todo": 3, "in_progress": 1, "in_review": 0, "done": 2, "skipped": 0, "deferred": 0, "satisfied": 0, "blocked": 0 },
|
|
57
|
+
"lastClosed": { "step": "epic-review", "date": "2026-06-05", "by": "ada" } },
|
|
58
|
+
{ "id": "EP-broken", "dir": "epics/EP-broken", "unreadable": true, "why": ".sdlc/state.json does not parse" }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`title` (E111) is the `title:` key in `epic.md`; if that is missing, the `title` in the item's
|
|
64
|
+
`change.json` (only change items have one); if both are missing, `null`. It is always one line. A screen prints the id when the title is `null`. The Foundation's
|
|
65
|
+
title is always `"Foundation"`, because it has no `epic.md`.
|
|
66
|
+
|
|
39
67
|
## `state.json`
|
|
40
68
|
The per-epic state machine.
|
|
41
69
|
|
|
@@ -43,66 +71,477 @@ The per-epic state machine.
|
|
|
43
71
|
|-------|---------|
|
|
44
72
|
| `epicId` | The stable `EP-<slug>` ID. Never renamed. |
|
|
45
73
|
| `createdAt` | ISO date the epic was created. |
|
|
74
|
+
| `type` | The work-item type — `feature` \| `change` \| `defect` \| `hotfix` \| `chore` — copied from `epic.md`. Shape 5 on. **Not the same field as `steps[].type`**, which is `author` \| `review+approve`, and not the same as the top-level `kind` a stub or the discovery front-zero carries. |
|
|
75
|
+
| `profile` | The lifecycle route this chain came from — `classic` \| `analysis-first` \| `chore` \| `spike` \| `discovery` \| `foundation`. Shape 6 on (`foundation` from shape 8). See "Lifecycle profiles" below. |
|
|
46
76
|
| `currentStep` | `id` of the step the workflow is waiting on right now. |
|
|
47
|
-
| `steps[]` | Ordered list of every
|
|
77
|
+
| `steps[]` | Ordered list of every Shape step. |
|
|
48
78
|
|
|
49
79
|
Each `steps[]` entry:
|
|
50
80
|
|
|
51
81
|
| Field | Values | Meaning |
|
|
52
82
|
|-------|--------|---------|
|
|
53
83
|
| `id` | `analysis`, `analysis-review`, `epic`, `epic-review`, `architecture`, `architecture-review`, `ui-design`, `ui-design-review`, `stories`, `stories-review`, `test-cases`, `test-cases-review` | Step identity. |
|
|
84
|
+
| `type` | `author` \| `review+approve` | Authoring step or a team review gate. |
|
|
85
|
+
| `artifact` | filename or folder | The file/folder this step produces or gates. |
|
|
86
|
+
| `assistance` | `none` \| `review` \| `heavy` | Dial 1 (driver) — who does the work. **The name the engine reads.** |
|
|
87
|
+
| `driver` | `human` \| `pair` \| `agent` | Dial 1, shape-4 name, written beside `assistance`: `human`=`none`, `pair`=`review`, `agent`=`heavy`. |
|
|
88
|
+
| `automation` | `human_approve` \| `machine_advance` | Dial 2 (advance) — who moves it forward. **The name the engine reads.** |
|
|
89
|
+
| `advance` | `human` \| `auto` | Dial 2, shape-4 name, written beside `automation`: `human`=`human_approve`, `auto`=`machine_advance`. A review step is NEVER `auto`. |
|
|
90
|
+
| `locked` | `true` \| `false` | Seeded `true` on every Shape step. Since E34 it decides nothing about the dial on a step the catalogue knows: an author step may be set to `auto` (for the whole project, in `.sdlc/automation.json`), and a review gate is `human` because it is a gate. It is still read as a gate on a step id the catalogue does not know. |
|
|
91
|
+
| `status` | one of the **step states** below | Where the step stands. |
|
|
92
|
+
| `record` | `{ reason, by, date, link? }` | Present on a `skipped`, `deferred`, `satisfied` or `blocked` step: WHY it is in that state. |
|
|
93
|
+
| `closed` | `{ by, date, via, pr?, commit?, hash?, mergedBy?, run?, waived?, capped? }` | Present on a `done` step that closed from this release on: HOW it closed (E18). See "Closing records" below. |
|
|
94
|
+
| `risk_tags` | subset of `contract`, `auth`, `payments` | Sets the step's full approver count (build plan §4): `contract` +2, `auth`/`payments` +1 on top of a base of 1 (the highest tag, never the sum). Only the base is enforced; the risk step is advisory and reported as a shortfall against the count capped at the active people less one, floor 1 (E72; no cap when the people cannot be counted). A team gate reports `rule: "count"`. A step with one of these tags has its review PR name and label the epic's `repos`. |
|
|
95
|
+
|
|
96
|
+
### The step catalogue
|
|
97
|
+
|
|
98
|
+
Every one of those ids is defined in **one place in the code** — `STEPS` in `cli/epic-state.mjs` (E4).
|
|
99
|
+
A row says which phase the step is in, whether it authors an artifact or gates one, which file it
|
|
100
|
+
writes, which skill runs it, and — for a gate — which step it reviews. The phase table, the skill
|
|
101
|
+
tables and the Build order are all views of it, held to it by tests, so a step cannot exist in one and
|
|
102
|
+
be missing from another.
|
|
103
|
+
|
|
104
|
+
The catalogue is code. Nothing about it is written into `state.json`, so no file shape changed.
|
|
105
|
+
|
|
106
|
+
**The one column a project overrides is the skill.** Which skill runs a step is not a fact about the
|
|
107
|
+
lifecycle — it depends on what a team installed — so it lives in `.sdlc/skills.json`, a Product-level
|
|
108
|
+
file the engine reads before it falls back to the catalogue (E6):
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{ "schemaVersion": 6, "steps": { "architecture": "our-arch-skill", "stories": ["shape-it", "yad-stories"] } }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
A value may be one skill or a list; a list runs as a **chain**, in order, each skill seeing what the
|
|
115
|
+
one before it produced, and the last output is the artifact. Write it with `yad skill bind <step>
|
|
116
|
+
<skill>…` (`yad skill list` shows what runs each step now, `yad skill unbind <step>` drops the line).
|
|
117
|
+
Nothing checks the skill NAME — the engine cannot know what you installed — but `yad doctor` reports a
|
|
118
|
+
binding that will never run: a value that is not a skill name (`YAD-CFG-006`), a step id this release
|
|
119
|
+
does not know (`skills:unknown-step`) and a review gate, which `yad gate` drives (`skills:review-step`).
|
|
120
|
+
It never rewrites the file.
|
|
121
|
+
|
|
122
|
+
**The kill switch and the Shape dials live in `.sdlc/automation.json` (E34)**, also a Product-level file,
|
|
123
|
+
also absent by default:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{ "schemaVersion": 10, "kill": { "on": true, "reason": "bad deploy", "by": "al", "date": "2026-09-15" },
|
|
127
|
+
"steps": { "architecture": "auto" } }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`kill` is written by `yad kill --reason "<why>"` / `yad unkill`; while `on`, every step is held at
|
|
131
|
+
`advance: human`; the record a `yad unkill` or a second `yad kill` replaces is kept as `kill.previous`, one level
|
|
132
|
+
deep. `steps` lists the feature Shape AUTHOR steps (not the Foundation's) the project set to `auto` with `yad dial <step> --to
|
|
133
|
+
auto` — `human` is the key's absence. A Shape `auto` is **recorded, not acted on**: nothing drives a Shape
|
|
134
|
+
step on its own until the engine runs agents (E26). A Build step's dial is not here; it is on its lane in
|
|
135
|
+
`build-state`. A review gate is never `auto`. A file that will not parse is read as the kill switch ON,
|
|
136
|
+
and nothing writes over it. `yad doctor` reports a broken file (`automation`), an active switch
|
|
137
|
+
(`automation:kill`), a gate set to auto (`automation:gate`), a Build step listed here
|
|
138
|
+
(`automation:build-step`), an unknown id or value (`automation:unknown`), and a `kill_switch: true` left in
|
|
139
|
+
`_bmad/sdlc/config.yaml`, which nothing reads any more (`automation:legacy-kill`).
|
|
140
|
+
|
|
141
|
+
**When a chain disagrees with the catalogue, the chain wins.** A change-epic seeded by hand before E42
|
|
142
|
+
may carry a chain the engine would not write, a project may run a chain from a newer release, and leaving a step out is normal
|
|
143
|
+
— not every epic has screens. `yad doctor` reports five disagreements it can see and rewrites nothing:
|
|
144
|
+
`step:artifact` (a step naming a different file from the one the gate hashes — different spellings of
|
|
145
|
+
the SAME artifact are fine, since `stories`, `stories/` and `stories.md` are one gate),
|
|
146
|
+
`step:no-artifact` (a step naming no file at all, the one case that stops `yad gate` outright),
|
|
147
|
+
`step:kind` (a step on the wrong side of the author / review line), `step:orphan-gate` (a gate whose
|
|
148
|
+
step is not in the chain, so nothing tells anyone to write what it reviews) and `step:off-route` (a
|
|
149
|
+
chain matching no lifecycle profile — see below). `skip:not-optional` is reported the same way: a step
|
|
150
|
+
marked N/A that this epic's route does not mark optional. An id the catalogue does not carry is left to
|
|
151
|
+
`phase:unknown`.
|
|
152
|
+
|
|
153
|
+
### Lifecycle profiles
|
|
154
|
+
|
|
155
|
+
A **profile** is a named, ordered chain of catalogue steps — the route an epic takes. Six are
|
|
156
|
+
defined, in `LIFECYCLE_PROFILES` (`cli/epic-state.mjs`, E5). `classic`, `analysis-first` and `discovery`
|
|
157
|
+
are routes the skills already seeded by hand; the two short lanes (E40) and `foundation` (E75) came later:
|
|
158
|
+
|
|
159
|
+
| Profile | Steps | Seeded by |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `classic` | the 10-step chain, `epic` first | `yad-epic`, `yad-stub`, `yad-change` |
|
|
162
|
+
| `analysis-first` | the 12-step chain, `analysis` before `epic` | `yad-analysis` |
|
|
163
|
+
| `chore` | `epic` · `epic-review` · `stories` · `stories-review` | `yad-epic --profile chore` |
|
|
164
|
+
| `spike` | the chore lane with `analysis` · `analysis-review` in front | `yad-analysis --profile spike` |
|
|
165
|
+
| `discovery` | `discovery` · `discovery-review` | the OLD spelling of the Product level — still read; `yad migrate --apply` converts it |
|
|
166
|
+
| `foundation` | `foundation` · `foundation-review` | `yad foundation new`, then `yad-discovery` authors the sections (E75) |
|
|
167
|
+
|
|
168
|
+
`ui-design` and its gate are the optional pair on `classic` and `analysis-first`. The short lanes mark
|
|
169
|
+
**nothing** optional — they drop the steps they do not need from the chain instead, so `yad skip` on
|
|
170
|
+
one is refused outright rather than pointing at a broken chain. **Which steps an epic may skip
|
|
171
|
+
comes from the route that epic is on**, and from nowhere else (E35): there is no engine-wide list of
|
|
172
|
+
skippable steps, so a route that drops a step no longer makes it skippable on every other route too.
|
|
173
|
+
The engine reads the route the epic **records** (`profile`, shape 6), and keeps reading it even when the
|
|
174
|
+
chain no longer fits — a chain written by a newer release carries steps this one does not know, and
|
|
175
|
+
treating that as "no route" would make a skip the epic already recorded start failing its gate.
|
|
176
|
+
`yad doctor` reports the disagreement rather than deciding it. An epic with no route at all — none
|
|
177
|
+
recorded that this release knows, and no match from its steps — has nothing optional, because inventing
|
|
178
|
+
one would let a step be skipped on the strength of a route nobody chose.
|
|
179
|
+
|
|
180
|
+
The parallel, non-blocking `test-cases` track is NOT recorded in the profile: `advanceState` still
|
|
181
|
+
decides it from the step id, and a second copy of that rule sitting unread in the profile would be
|
|
182
|
+
free to drift.
|
|
183
|
+
|
|
184
|
+
**Each epic records its route in `state.json` as `profile`** (shape 6). The value is one of `classic`,
|
|
185
|
+
`analysis-first`, `chore`, `spike`, `discovery` and `foundation`. `yad epic new` writes it when it seeds the chain,
|
|
186
|
+
the seeding skills write it in their templates, and `yad migrate` fills it in for an epic that predates
|
|
187
|
+
the field by reading the chain that epic already carries.
|
|
188
|
+
|
|
189
|
+
**`yad migrate` only ever writes `classic`, `analysis-first` or `discovery`.** It answers a question about the past, and
|
|
190
|
+
the matching rule picks the SHORTEST route a chain fits — so a chain built only from `epic`,
|
|
191
|
+
`epic-review`, `stories` and `stories-review` reads as `chore` now and read as `classic` before. No
|
|
192
|
+
chain yadflow has ever seeded is affected, because every shipped seed carries `architecture` and no
|
|
193
|
+
short lane has it; the freeze is what makes that a guarantee rather than a fact about today's data. An
|
|
194
|
+
epic seeded on a short lane never needs the stamp: `yad epic new` writes its `profile` at seed time.
|
|
195
|
+
|
|
196
|
+
The chain is still the truth. The recorded name says which route the epic was STARTED on; matching the
|
|
197
|
+
chain (`matchLifecycleProfile`) says which route the steps are on NOW, and `yad doctor` compares them:
|
|
198
|
+
|
|
199
|
+
| Check | Fires when |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `profile:unknown` | `profile` holds a value no release carries |
|
|
202
|
+
| `profile:disagree` | the chain is cleanly on a route, and it is not the one recorded |
|
|
203
|
+
| `step:off-route` | the chain is on no route at all — a step no route has, or two out of order |
|
|
204
|
+
|
|
205
|
+
A chain missing steps still matches — dropping `ui-design` is normal. An epic whose chain matches
|
|
206
|
+
nothing gets NO `profile` key from the migration: inventing one would silence `step:off-route` by
|
|
207
|
+
making the file agree with itself.
|
|
208
|
+
|
|
209
|
+
> **Two different things are called a profile.** This one is the lifecycle route. `hub.json.profile` is
|
|
210
|
+
> the unrelated setup record `yad setup` writes: `{ codebase, repo_layout, team_size }`.
|
|
211
|
+
|
|
212
|
+
### The six phases
|
|
213
|
+
|
|
214
|
+
Every step belongs to one of six named phases, and each phase sits inside one of the three parts:
|
|
215
|
+
|
|
216
|
+
| Phase | Part | Steps |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| Discover | Shape | `analysis` · `epic`, each with its review gate |
|
|
219
|
+
| Design | Shape | `architecture` · `ui-design`, each with its review gate |
|
|
220
|
+
| Plan | Shape | `stories` · `test-cases`, each with its review gate |
|
|
221
|
+
| Build | Build | `spec` · `tasks` · `implement` · `checks` · `engineer-review` |
|
|
222
|
+
| Release | Run | **planned, not built** |
|
|
223
|
+
| Operate | Run | **planned, not built** |
|
|
224
|
+
|
|
225
|
+
**A phase is never written into `state.json`.** It is worked out from `currentStep`, so there is
|
|
226
|
+
nothing to author, nothing to keep in step, and no way for it to disagree with the step it describes.
|
|
227
|
+
|
|
228
|
+
- A Shape review gate takes the phase of the artifact it reviews, so `epic-review` is Discover beside
|
|
229
|
+
`epic`. Build steps have no such gates — `engineer-review` is a step in its own right — so
|
|
230
|
+
`checks-review` is not a step and resolves to nothing.
|
|
231
|
+
- The `currentStep` markers are not steps and never appear in `steps[]`. Two of them
|
|
232
|
+
(`backfill-pending`, `backfill-done`) have no phase. **`ready-for-build` is the exception:** an epic
|
|
233
|
+
sitting on it is in Build, and stays there for the rest of its life, because the individual Build
|
|
234
|
+
steps live per story per repo in `build-state/` rather than in this file.
|
|
235
|
+
- The **Product level** — `EP-foundation` in `foundation/`, or `EP-discovery`, its older spelling — is
|
|
236
|
+
not on the six-phase ladder. It has a ladder of its own with one phase, **Foundation**, and it is in
|
|
237
|
+
that phase for its whole life, `foundation-done` / `discovery-done` included: the phase is decided by
|
|
238
|
+
the ledger's `kind`, not by the step id (E75).
|
|
239
|
+
|
|
240
|
+
`yad doctor` reports a step id no phase claims. That is the same table that binds a step to its skill,
|
|
241
|
+
so an id with no phase is an id no skill runs.
|
|
54
242
|
|
|
55
243
|
### Two valid chain shapes (analysis is optional)
|
|
56
244
|
|
|
57
245
|
The `analysis` step (and its `analysis-review` gate) is **optional** — it exists only when the team
|
|
58
|
-
ran `yad-analysis` before the epic. The entry-point skill (whichever runs first)
|
|
59
|
-
|
|
246
|
+
ran `yad-analysis` before the epic. The entry-point skill (whichever runs first) assigns `EP-<slug>`
|
|
247
|
+
and runs **`yad epic new`**, which writes `state.json` and the empty ledgers; the other skill sees an
|
|
60
248
|
existing `state.json` and does **not** re-seed.
|
|
61
249
|
|
|
62
|
-
|
|
250
|
+
**The engine owns the chain.** No skill writes one by hand on these two routes any more — `yad-epic`
|
|
251
|
+
runs `yad epic new EP-<slug>`, `yad-analysis` runs it with `--profile analysis-first`, and `yad-stub`
|
|
252
|
+
runs it with `--stub`, and `yad-discovery` runs `yad foundation new` for the Product level (E75). `yad-change`
|
|
253
|
+
runs it with `--parent` and `--inherits` for a threaded chain whose inherited steps are bound to the
|
|
254
|
+
owning epic's artifact hashes (E42). A seeded chain leaves its first author step **open**, not `done` — the
|
|
255
|
+
command runs before the artifact exists; `yad gate open` closes it when the gate opens.
|
|
256
|
+
|
|
257
|
+
- **With analysis** — the `analysis-first` route, 12 steps:
|
|
63
258
|
`analysis → analysis-review → epic → epic-review → architecture → architecture-review → ui-design →
|
|
64
259
|
ui-design-review → stories → stories-review → test-cases → test-cases-review`. Seeded `currentStep`
|
|
65
|
-
is `analysis
|
|
66
|
-
- **Without analysis**
|
|
260
|
+
is `analysis`, which starts `in_progress`; everything after it starts `todo`.
|
|
261
|
+
- **Without analysis** — the `classic` route, 10 steps, and the default:
|
|
67
262
|
`epic → epic-review → … → stories-review → test-cases → test-cases-review`. Seeded `currentStep` is
|
|
68
|
-
`epic
|
|
263
|
+
`epic`, which starts `in_progress`.
|
|
264
|
+
|
|
265
|
+
**The two short lanes.** Not every piece of work is the size of a feature, and before these the only
|
|
266
|
+
way to make the chain shorter was to skip steps — which `classic` does not allow, since only
|
|
267
|
+
`ui-design` is optional on it.
|
|
268
|
+
|
|
269
|
+
- **`chore`, 4 steps:** `epic → epic-review → stories → stories-review`. Upkeep somebody has already
|
|
270
|
+
decided on: a dependency bump, a CI move.
|
|
271
|
+
- **`spike`, 6 steps:** the same lane with `analysis → analysis-review` in front. A timeboxed
|
|
272
|
+
question, where finding the answer is the work.
|
|
273
|
+
|
|
274
|
+
Both drop `architecture`, `ui-design` and `test-cases` with their gates, and each is **absent, not
|
|
275
|
+
`optional`** — a recorded reason for skipping a step the route never had is noise in the audit trail.
|
|
276
|
+
Dropping the architecture gate drops the contract with it: a short-lane epic has no `contract.md` and
|
|
277
|
+
no lock, which is the point. **Work that moves the shared cross-repo surface belongs on `classic`,
|
|
278
|
+
whatever its size.**
|
|
279
|
+
|
|
280
|
+
Neither lane drops `epic` or `stories`, and that is not a matter of taste: `epic.md` carries the
|
|
281
|
+
work-item type and the `parent:` lineage, and `stories-review` is the step that makes the epic ready
|
|
282
|
+
for Build. The type and the route are chosen separately — `--type chore --profile classic` is right
|
|
283
|
+
for large upkeep that does touch the contract.
|
|
284
|
+
|
|
285
|
+
Both seeded values move to the review gate when `yad gate open` runs, which is also what closes the
|
|
286
|
+
authoring step. Before E17b the seeds recorded the gate directly, because a skill only seeded once it
|
|
287
|
+
had already written the artifact; a chain seeded that way is still perfectly valid and nothing rewrites
|
|
288
|
+
it.
|
|
289
|
+
|
|
290
|
+
Every other review gate — `analysis-review`, `epic-review`, `ui-design-review`, `stories-review` and
|
|
291
|
+
`test-cases-review` — carries no `risk_tags`: it needs 1 approval, and its full approver count is 1.
|
|
292
|
+
|
|
293
|
+
### The step states (shape 7)
|
|
294
|
+
|
|
295
|
+
`status` is one of eight words. Seven are the model (`docs/roadmap-idea-1.md`, Part 1); `in_review` is
|
|
296
|
+
the eighth, and is `in_progress` on a step that is a review gate.
|
|
297
|
+
|
|
298
|
+
| State | Meaning | Chain continues past it | Artifact written here | Must record |
|
|
299
|
+
|-------|---------|-------------------------|-----------------------|-------------|
|
|
300
|
+
| `todo` | Not started | no | no | — |
|
|
301
|
+
| `in_progress` | Being worked on | no | no | — |
|
|
302
|
+
| `in_review` | Its review gate is open | no | no | — |
|
|
303
|
+
| `done` | Completed here | yes | **yes** | the artifact |
|
|
304
|
+
| `skipped` | Consciously chose not to | yes | no | reason + who + when |
|
|
305
|
+
| `deferred` | Will do it, later — `yad defer` | yes | no | reason (saying who waits) + who + when |
|
|
306
|
+
| `satisfied` | Done elsewhere | yes | no | link + who + when |
|
|
307
|
+
| `blocked` | Cannot proceed, **not our choice** | no | no | who or what we wait for |
|
|
308
|
+
|
|
309
|
+
Two columns, not one, and the engine reads them through `isPassed` and `isAuthored`
|
|
310
|
+
(`cli/epic-state.mjs`). "The chain may continue" and "there is an artifact here to audit" are different
|
|
311
|
+
questions: a `skipped` step lets `stories` start and has nothing to hash, so a gate audit skips it
|
|
312
|
+
while `preconditionsMet` walks straight past it.
|
|
313
|
+
|
|
314
|
+
The four states at the bottom carry a `record`:
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{ "reason": "backend-only epic, no user-facing surface", "by": "@al", "date": "2026-07-08" }
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`reason` is required; `by` and `date` are best-effort and may be `null` (attribution is a nicety on the
|
|
321
|
+
audit trail, never a gate). A `satisfied` record also carries `link`, naming where the work happened.
|
|
322
|
+
`yad doctor` reports a recorded state with no record as `step:no-record`.
|
|
323
|
+
|
|
324
|
+
### Closing records (E18)
|
|
325
|
+
|
|
326
|
+
A `done` step says **how it closed**, in its own key, `closed`. It is not `record`: `record` says why a
|
|
327
|
+
step is *not* done, and `blocked` is read by whether it has one, so the two never share a key.
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{ "id": "architecture-review", "status": "done",
|
|
331
|
+
"closed": { "by": "yad-gate-sync[bot]", "date": "2026-06-08", "via": "merge", "pr": 7,
|
|
332
|
+
"commit": "c0ffee1", "hash": "sha256:…", "mergedBy": "al" } }
|
|
333
|
+
```
|
|
69
334
|
|
|
70
|
-
|
|
71
|
-
|
|
335
|
+
| Field | Meaning |
|
|
336
|
+
|---|---|
|
|
337
|
+
| `by` | Who **wrote** the record, as on every record: the platform login `gh`/`glab` reports, else git `user.name` (on CI, usually the bot's git name). Best-effort, may be `null`. |
|
|
338
|
+
| `date` | When the step closed: the merge date for a merge, otherwise the day the command ran. |
|
|
339
|
+
| `via` | How it closed — see the table below. |
|
|
340
|
+
| `pr` | The review PR/MR number, when there is one. |
|
|
341
|
+
| `commit` | The merge commit, when the platform reports it. |
|
|
342
|
+
| `hash` | The artifact hash the step closed on — the one approvals bind to. |
|
|
343
|
+
| `mergedBy` | The platform login that merged the PR, when the platform reports it. |
|
|
344
|
+
| `run` | Build lanes only: the `uid` of the trust-log run that moved past the step. |
|
|
345
|
+
| `waived` | `solo` on a **review** step that passed while solo mode waived its approvals (E10). Absent when the gate counted approvals, and never on the author step it closes. |
|
|
346
|
+
| `capped` | `{ needed, to, active }` on a **review** step of a TEAM gate that passed while the capacity cap LOWERED its ask (E72): the full count, the capped ask, and the number of active people it read. It records what the gate asked, not what held it — the base held (only the base holds). `yad gate status` prints it as `count capped from 3 to 1 (2 active people)`. Absent when no cap applied (the people could not be counted, or the cap lowered nothing), never in solo mode (nothing was counted), never on a step that passed by its skip or inherited shortcut (nothing was asked), and never on the author step. |
|
|
347
|
+
|
|
348
|
+
| `via` | Written by | When |
|
|
349
|
+
|---|---|---|
|
|
350
|
+
| `merge` | `yad gate sync` / `yad gate ci` | A review gate passed on its merge |
|
|
351
|
+
| `approved` | the `yad-review-gate` skill, by hand | A review gate passed on recorded approvals on a Product with no platform: nothing merged, so no `pr` or `commit` |
|
|
352
|
+
| `review-passed` | `yad gate sync` / `yad gate ci`, or that skill | Its author step, closed when its gate passed because nothing closed it earlier |
|
|
353
|
+
| `review-opened` | `yad gate open` (local ledger) / `yad gate sync` | An author step, closed when its review opened |
|
|
354
|
+
| `repair` | `yad gate repair` | A stranded author step — an escape hatch, so it says so |
|
|
355
|
+
| `auto` / `human` | the `yad-run` skill, in `build-state` | A Build lane step: the dial let the run go on by itself, or it stopped for a person |
|
|
356
|
+
|
|
357
|
+
- **Optional keys are left out**, never written as `null`: a close with no PR has no `pr`.
|
|
358
|
+
- **The first close wins.** A step that already has a `closed` keeps it; a re-sync never rewrites it.
|
|
359
|
+
- **Nothing is invented.** A field the closer does not know is left out.
|
|
360
|
+
- **Steps closed before this release carry none**, and nothing asks for one: `yad doctor` does not warn.
|
|
361
|
+
- **No shape change.** An older release ignores the key, and nothing reads `done` differently because it
|
|
362
|
+
is there.
|
|
363
|
+
- `yad gate status` prints it under each review step; `yad history show` (E20) prints it under every
|
|
364
|
+
step, author steps included, and `yad history search` finds text in it.
|
|
365
|
+
|
|
366
|
+
**`blocked` changed meaning in shape 7.** Before it, every writer used `blocked` for "waiting on an
|
|
367
|
+
earlier step" — what is now `todo`. The two are told apart by the record, not by the shape number:
|
|
368
|
+
|
|
369
|
+
> `blocked` with **no** record is the old word and reads as `todo`.
|
|
370
|
+
> `blocked` **with** a record is a real blocker, and the record says on whom.
|
|
371
|
+
|
|
372
|
+
No release before shape 7 ever wrote a record, so that is exact. The converse is why clearing a blocker
|
|
373
|
+
has a verb: `yad unblock EP-<slug> <step>` (E37) moves `status` off `blocked` **and** removes the record
|
|
374
|
+
in one write, because deleting only the record would turn the step silently into a `todo`. An author
|
|
375
|
+
step whose earlier steps have all passed goes to `in_progress`; anything else, a review gate included,
|
|
376
|
+
goes to `todo`. It never touches `build-state/<story>.json`, which the `yad-run` skill writes. On a verified
|
|
377
|
+
Product it refuses once the epic's ledger is on the default branch: CI owns `state.json` there and has no
|
|
378
|
+
step for it yet.
|
|
379
|
+
|
|
380
|
+
A status this release does not know is left alone — the file wins — and reported as
|
|
381
|
+
`step:unknown-status`. It fails closed: the step counts as neither passed nor authored.
|
|
72
382
|
|
|
73
383
|
### `ui-design` is optional (skippable)
|
|
74
384
|
|
|
75
|
-
|
|
385
|
+
Optional **on the routes that say so**, which today is `classic` and `analysis-first` — see the
|
|
386
|
+
profiles section above. The `chore` and `spike` lanes mark nothing optional, because they drop
|
|
387
|
+
`ui-design` from the chain rather than carrying it as skippable. The step (and its `ui-design-review` gate) is optional for an epic with no
|
|
76
388
|
user-facing surface — a backend/API service, a data pipeline, infra work. Unlike `analysis` (which is
|
|
77
389
|
optional by being **omitted** from the chain at seed time), `ui-design` is **always seeded** and then
|
|
78
390
|
**marked N/A in place** so the skip stays visible and auditable. The single mechanism is
|
|
79
|
-
`yad skip EP-<slug> ui-design --reason "<why>"` (reverse with
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
`
|
|
83
|
-
|
|
84
|
-
|
|
391
|
+
`yad skip EP-<slug> ui-design --reason "<why>"` (reverse with `yad unskip EP-<slug> ui-design`, or the
|
|
392
|
+
older spelling `yad skip … --undo`), usable at epic-authoring time or any point **up to authoring the
|
|
393
|
+
`ui-design` step**. On a verified Product the window is shorter: once the epic's ledger is on the default
|
|
394
|
+
branch (its first review PR has merged), `state.json` is CI's alone, and `yad skip`, `unskip`, `defer`,
|
|
395
|
+
`undefer` and `unblock` refuse and write nothing. So there, skip `ui-design` before that first PR merges:
|
|
396
|
+
right after seeding on `classic`, and during the analysis step on `analysis-first`, whose first review PR
|
|
397
|
+
is the analysis review.
|
|
398
|
+
|
|
399
|
+
When each verb is too late is a rule about the chain, not about `ui-design` (E36):
|
|
400
|
+
|
|
401
|
+
- **A skip** is refused once the step's review gate has opened (the work is committed by then), or once
|
|
402
|
+
work has started on **any later step**.
|
|
403
|
+
- **An un-skip** is refused once work has started on any step **past the step after the pair**. "The
|
|
404
|
+
step after the pair" is the first later step that is not itself skipped — the step the skip opened.
|
|
405
|
+
It may still be under way, and un-skipping pushes it back to `todo` — but not finished: a `done` step
|
|
406
|
+
was written on the assumption the skipped step did not apply, so that refuses too.
|
|
407
|
+
- **"Work has started"** means the step is `in_progress`, `in_review` or `done` in this chain. `skipped`,
|
|
408
|
+
`satisfied`, `deferred` and `blocked` are not work done here and never count. A status this release
|
|
409
|
+
cannot name counts as started. A non-step entry after the pair is refused as a malformed chain.
|
|
410
|
+
|
|
411
|
+
On `classic` and `analysis-first` the step after the `ui-design` pair is `stories`: a skip is refused
|
|
412
|
+
once `stories` starts, and an un-skip once `stories` is finished or its review opens.
|
|
413
|
+
|
|
414
|
+
**`currentStep` on an un-skip.** The restored author step becomes `currentStep` again when every earlier
|
|
415
|
+
step has passed — unless the epic **earned** `ready-for-build`: its `stories-review` is `done` (or `satisfied`, reviewed
|
|
416
|
+
in the parent epic) and comes before the restored step. That step then runs beside Build, as `test-cases` does, and `currentStep` stays
|
|
417
|
+
put. An epic that reached `ready-for-build` only by skipping (a `chore` whose stories pair was skipped by
|
|
418
|
+
hand) is moved back to the restored step.
|
|
419
|
+
|
|
420
|
+
A skipped step is `status: "skipped"` with a `record`, and keeps four legacy fields beside them:
|
|
85
421
|
|
|
86
422
|
| Field | Values | Meaning |
|
|
87
423
|
|-------|--------|---------|
|
|
88
|
-
| `
|
|
424
|
+
| `status` | `"skipped"` | The step's own state (shape 7). Short-circuited by `gatePredicate` (`rule: "skipped"`) so no review is required. |
|
|
425
|
+
| `record` | `{ reason, by, date }` | Why it was skipped, who marked it and when. |
|
|
426
|
+
| `skipped` | `true` | The pre-shape-7 spelling, still written and still read (rule 3, add before you remove). |
|
|
89
427
|
| `skipReason` | string | Why it was skipped (e.g. "backend-only service, no UI"). |
|
|
90
|
-
| `skippedBy` | login/name or `null` | Who marked it N/A (best-effort
|
|
428
|
+
| `skippedBy` | login/name or `null` | Who marked it N/A (best-effort: the platform login from `gh`/`glab`, else git `user.name`). |
|
|
91
429
|
| `skippedAt` | `YYYY-MM-DD` or `null` | When it was marked N/A. |
|
|
92
430
|
|
|
431
|
+
A pre-shape-7 file spells the same step `status: "done"` with `skipped: true` beside it, and `yad
|
|
432
|
+
migrate` translates it. Either spelling reads identically: `stepStatus` treats the flag as a legacy
|
|
433
|
+
encoding **only when `status` is `done`** — a `skipped: true` on an unstarted step is a hand edit, and
|
|
434
|
+
reading it as finished would let anyone unblock a chain by typing one word into a file.
|
|
435
|
+
|
|
93
436
|
Both the `ui-design` **and** `ui-design-review` entries carry these fields. `advanceState` steps over
|
|
94
|
-
any
|
|
95
|
-
`preconditionsMet` treats
|
|
96
|
-
strips the fields
|
|
97
|
-
|
|
437
|
+
any skipped step, so approving `architecture-review` on a UI-less epic lands directly on `stories`;
|
|
438
|
+
`preconditionsMet` treats it as passed (never as authored). `unskipStep` (via `yad unskip`, or
|
|
439
|
+
`yad skip … --undo`) strips the fields — the record included — and restores the chain to `todo`,
|
|
440
|
+
refused once the step after the pair is finished or a step past it has started (on `classic`, once `stories` is done or `stories-review` opens). `ui-design` is the
|
|
441
|
+
only step any route marks optional today; the engine reads that off the epic's own route
|
|
442
|
+
(`optionalStepsFor`) rather than from a list of its own.
|
|
443
|
+
|
|
444
|
+
### Deferring an optional step (`yad defer`)
|
|
445
|
+
|
|
446
|
+
A step the route marks optional can also be **deferred**: it applies, and the team will do it later.
|
|
447
|
+
`yad defer EP-<slug> ui-design --reason "<why, and who is waiting>"` writes this on both the author step
|
|
448
|
+
and its `-review` gate:
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{ "id": "ui-design", "type": "author", "artifact": "ui-design.md",
|
|
452
|
+
"status": "deferred",
|
|
453
|
+
"record": { "reason": "screens come with the redesign; @design is waiting on it", "by": "@al", "date": "2026-09-14" } }
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
There are no legacy fields: `deferred` was new in shape 7, so no older reader needs them. `by` is who
|
|
457
|
+
wrote the record, as on every record. `yad undefer EP-<slug> ui-design` removes the record and puts the
|
|
458
|
+
step back.
|
|
459
|
+
|
|
460
|
+
A deferral follows the skip rules above:
|
|
461
|
+
|
|
462
|
+
- **Same permission.** Only a route-optional step can be deferred, because the chain continues past a
|
|
463
|
+
deferred step (`isPassed`) with no approvals on its review. The review is not waived, though:
|
|
464
|
+
`gatePredicate` gives a deferred step no short-circuit (E38), so it still reports what is missing, and
|
|
465
|
+
`yad gate status` prints the step as "deferred (still owed)".
|
|
466
|
+
- **Same window to set aside.** `yad defer` is refused where `yad skip` is. Putting it back is
|
|
467
|
+
different — see "Picking a deferral up late" below.
|
|
468
|
+
- **Same walk.** `advanceState` and the shared skip/defer code (`setAsideStep`, `restoreStep`) step over
|
|
469
|
+
`skipped` **and** `deferred` steps (`isSetAside`). `yad gate open` refuses a set-aside step, and
|
|
470
|
+
`yad sync-status` leaves its artifact's `status:` line alone. A `deferred` status typed by hand onto a
|
|
471
|
+
required step is walked past too, as a hand-typed `skipped` always was; `yad doctor` reports both
|
|
472
|
+
(`skip:not-optional`).
|
|
473
|
+
- **Not interchangeable.** Deferring a skipped step, or skipping a deferred one, is refused with the
|
|
474
|
+
command that puts it back first, so neither record is lost.
|
|
475
|
+
|
|
476
|
+
### Picking a deferral up late (E41)
|
|
477
|
+
|
|
478
|
+
For a skip, the un-skip window is the meaning: work finished without the step was built on it not
|
|
479
|
+
applying, so `yad unskip` is still refused once the step after the pair is finished. A deferral promised
|
|
480
|
+
to come back, so `yad undefer` has **no closing window** — except on a verified Product, where it refuses once the epic's
|
|
481
|
+
ledger is on the default branch. Before later work has finished it behaves like
|
|
482
|
+
`yad unskip`. After it has finished, it **re-opens** the pair behind that work:
|
|
483
|
+
|
|
484
|
+
| Field | After a late `yad undefer` |
|
|
485
|
+
|-------|----------------------------|
|
|
486
|
+
| author step `status` | `in_progress` (or `todo` if an earlier step has not passed); `record` removed |
|
|
487
|
+
| `-review` gate `status` | `todo`; `record` removed |
|
|
488
|
+
| every later step | unchanged |
|
|
489
|
+
| `currentStep` | unchanged |
|
|
490
|
+
| `debt` | kept, if the deferral carried it |
|
|
491
|
+
|
|
492
|
+
A late re-open needs later work that is **finished** here. If later work has only started, or holds a
|
|
493
|
+
status this release cannot name, `yad undefer` is refused as before. A re-opened step can be deferred
|
|
494
|
+
again with `yad defer` (not skipped: the finished work was built without it), so a late undefer is not
|
|
495
|
+
a one-way door.
|
|
496
|
+
|
|
497
|
+
**A re-opened step is read off the chain, never off a flag:** an unfinished step with a later step
|
|
498
|
+
**completed here** (`status: "done"`, not inherited, not skipped) that is not its own `-review` gate.
|
|
499
|
+
Such a step runs beside the chain, like `test-cases`:
|
|
500
|
+
|
|
501
|
+
- `preconditionsMet` does not name it the blocker of a step past that finished work. It still blocks
|
|
502
|
+
the steps between itself and that work, its own gate included.
|
|
503
|
+
- `markInReview` moves `currentStep` only forward, so opening its review does not pull the chain back.
|
|
504
|
+
- `advanceState`, for a gate that passed behind the chain (`currentStep` past it, or `ready-for-build`),
|
|
505
|
+
closes the gate and changes nothing else. In the forward case it walks past every step that has already
|
|
506
|
+
passed — skipped, deferred, inherited (`satisfied`) or `done` — and opens the next one only if it is `todo`.
|
|
507
|
+
- `yad next` lists it under `reopened` (JSON) and prints `re-opened lane: …`.
|
|
508
|
+
|
|
509
|
+
Only `done` counts as finished work. A skipped, deferred or `satisfied` step after an unfinished step is
|
|
510
|
+
not work built here, so it never turns that step into a lane.
|
|
511
|
+
|
|
512
|
+
### Debt on a deferral (`debt: true`, E41)
|
|
513
|
+
|
|
514
|
+
`yad defer EP-<slug> <step> --reason "<why>" --debt` writes the deferral with one more key on both steps
|
|
515
|
+
of the pair:
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{ "id": "ui-design", "type": "author", "artifact": "ui-design.md",
|
|
519
|
+
"status": "deferred", "debt": true,
|
|
520
|
+
"record": { "reason": "launch date; @design is waiting on it", "by": "@al", "date": "2026-09-14" } }
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
| Rule | Detail |
|
|
524
|
+
|------|--------|
|
|
525
|
+
| What it means | owed back — set aside under pressure, and reminded until paid |
|
|
526
|
+
| Where it may sit | a `deferred` pair only; `yad skip --debt` is refused, since a skip owes nothing |
|
|
527
|
+
| What it changes | nothing about the state: a debt is exactly as passed, and as unauthored, as any deferral |
|
|
528
|
+
| Adding it later | `yad defer … --debt` on a step already deferred adds the flag and keeps the record |
|
|
529
|
+
| Paying it back | `yad undefer`, early or late; the flag stays on while the step is worked on. Not on a verified Product once the ledger is on the default branch: there a debt cannot be paid back until CI has a step for it |
|
|
530
|
+
| Setting it aside again | `yad defer` again keeps the flag; `yad skip` is refused on a step owed as debt, because a skip owes nothing and its review would never run again |
|
|
531
|
+
| When it clears | `advanceState` removes it from both steps when the `-review` gate passes — nothing else does |
|
|
532
|
+
| Reminders | `yad next` (a warning per debt, a count in the all-epics list, `debt` in JSON), `yad doctor` (`step:debt`, a warn), `yad gate status` ("deferred (still owed, as debt)") |
|
|
533
|
+
|
|
534
|
+
Step debt is **not** `reconcile-debt.json`. That file is a hotfix's whole change owed back to a feature
|
|
535
|
+
thread and is enforced by the `reconcile-debt` CI gate. Step debt is one step of one epic, and it is a
|
|
536
|
+
reminder, never a gate.
|
|
98
537
|
|
|
99
538
|
### `test-cases` is a parallel, non-blocking track
|
|
100
539
|
|
|
101
540
|
`test-cases` (and its `test-cases-review` gate) sit in `steps[]` after `stories-review`, but they are a
|
|
102
|
-
**parallel track that does not gate
|
|
103
|
-
- sets `currentStep` to the **`ready-for-build`** sentinel — so
|
|
541
|
+
**parallel track that does not gate Build**. When `stories-review` passes, `advanceState`:
|
|
542
|
+
- sets `currentStep` to the **`ready-for-build`** sentinel — so Build (`yad-spec` → … keyed off
|
|
104
543
|
`currentStep == "ready-for-build"`) can start **immediately**, and
|
|
105
|
-
- opens `test-cases` (`
|
|
544
|
+
- opens `test-cases` (`todo` → `in_progress`) so the tester can work **in parallel**.
|
|
106
545
|
|
|
107
546
|
The `test-cases` track is therefore driven by its own step `status`, **not** by `currentStep`:
|
|
108
547
|
`yad-test-cases` proceeds when `test-cases.status == "in_progress"`, and neither it nor the
|
|
@@ -116,7 +555,7 @@ unchanged.)
|
|
|
116
555
|
|
|
117
556
|
Each front **authoring** step opens its own git branch at the start of the step, named
|
|
118
557
|
`<step>/EP-<slug>` where `<step>` ∈ `analysis | epic | architecture | ui-design | stories | test-cases`
|
|
119
|
-
(`config.yaml` `defaults.
|
|
558
|
+
(`config.yaml` `defaults.shape_authoring_branch`). This is **distinct** from the review branch
|
|
120
559
|
`review/EP-<slug>/<artifact-base>` that `yad-hub-bridge` opens later for the review PR/MR.
|
|
121
560
|
|
|
122
561
|
The shared procedure (run once the `EP-<slug>` is known):
|
|
@@ -124,45 +563,52 @@ The shared procedure (run once the `EP-<slug>` is known):
|
|
|
124
563
|
(`git rev-parse --is-inside-work-tree` fails), skip branching with a note and author on the current
|
|
125
564
|
tree — no error.
|
|
126
565
|
2. Branch name = `<step>/EP-<slug>`. If it already exists, check it out; otherwise create it from the
|
|
127
|
-
|
|
128
|
-
3. Author and commit the step's artifact(s) on that branch. The
|
|
566
|
+
Product's default branch (`git checkout -b <step>/EP-<slug>`).
|
|
567
|
+
3. Author and commit the step's artifact(s) on that branch. The verified ledger's `review/…` branch is created
|
|
129
568
|
separately at review time and is untouched by this step.
|
|
130
569
|
|
|
131
|
-
**How the seed reaches the default branch.** The `.sdlc/` ledger is seeded once
|
|
132
|
-
**entry** step's authoring branch (`analysis/…`,
|
|
133
|
-
|
|
570
|
+
**How the seed reaches the default branch.** The `.sdlc/` ledger is seeded once — by `yad epic new`
|
|
571
|
+
(or `yad foundation new` for the Product level) — on the **entry** step's authoring branch (`analysis/…`,
|
|
572
|
+
`epic/…`, `change/…`, `foundation/…`, or `discovery/…` for a product level still in its old spelling).
|
|
573
|
+
No CI path creates one (`yad gate ci` only *advances* an existing chain, at merge, on the default branch).
|
|
134
574
|
So for the **first** gate of an epic, cut `review/EP-<slug>/<artifact-base>` from that authoring
|
|
135
575
|
branch: the review PR/MR then carries the seed alongside the artifact, and the ledger lands on the
|
|
136
|
-
default branch when it merges. In
|
|
576
|
+
default branch when it merges. In verified mode `ledger-guard` exempts exactly this case — **creation,
|
|
137
577
|
not mutation** (#162) — so no direct push to a protected default branch is needed. For every **later**
|
|
138
578
|
gate the ledger is already on the default branch: cut the review branch from there, commit the
|
|
139
|
-
artifact only, and leave `.sdlc/{state,approvals,comments,hub-prs}.json` and `reviews/*.md` to CI.
|
|
140
|
-
| `type` | `author` \| `review+approve` | Authoring step or a team review gate. |
|
|
141
|
-
| `artifact` | filename or folder | The file/folder this step produces or gates. |
|
|
142
|
-
| `assistance` | `none` \| `review` \| `heavy` | Dial 1 — how much AI helps (build plan §2). |
|
|
143
|
-
| `automation` | `human_approve` \| `machine_advance` | Dial 2 — who advances (build plan §2). |
|
|
144
|
-
| `locked` | `true` \| `false` | Front steps are `true`: may NOT be set to `machine_advance` in this version. |
|
|
145
|
-
| `status` | `blocked` \| `in_progress` \| `in_review` \| `done` | Lifecycle. `blocked` = upstream step not yet approved. |
|
|
146
|
-
| `risk_tags` | subset of `contract`, `auth`, `payments` | Drives review escalation (build plan §4). |
|
|
579
|
+
artifact only, and leave `.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` and `reviews/*.md` to CI.
|
|
147
580
|
|
|
148
581
|
## `approvals.json`
|
|
149
582
|
Append-only ledger (an array). Each entry:
|
|
150
583
|
|
|
151
584
|
```json
|
|
152
|
-
{ "artifact": "epic.md", "step": "epic-review", "approver": "<
|
|
585
|
+
{ "artifact": "epic.md", "step": "epic-review", "approver": "<platform login>", "status": "approved", "date": "<YYYY-MM-DD>", "source": "<bridge, optional>" }
|
|
153
586
|
```
|
|
154
587
|
|
|
155
|
-
`
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
588
|
+
`approver` is the platform login (on a Product with no platform, the name the reviewer gave). The gate
|
|
589
|
+
counts distinct approvers and checks no role. An entry with an empty `approver` counts as nobody. An older entry
|
|
590
|
+
may still carry `role` and `domain` fields from the removed roster, and name the person by the roster's
|
|
591
|
+
name. The gate never reads the role. While the roster is on disk, the next sync write (`yad gate sync`,
|
|
592
|
+
`yad gate ci`) records the login on such an entry when the roster places it for certain, removes `role`
|
|
593
|
+
and `domain`, and keeps the old name in `rosterName` (E64). Some entries are left as they are — a name two
|
|
594
|
+
logins share, or several records that do not prove they are one review (most GitLab role records); see
|
|
595
|
+
`yad-hub-bridge/references/login-roster.md`. The dated `approved.md` still prints the roles as recorded.
|
|
596
|
+
|
|
597
|
+
`source: "bridge"` marks an approval synced from a Product review PR/MR by `yad-review-gate action: sync`
|
|
598
|
+
(via `yad-hub-bridge`). Manual approvals omit `source` and are never altered by `sync`, except for the
|
|
599
|
+
login recorded on an older entry (above). A manual approval has no `artifactHash`, so an edit to the
|
|
600
|
+
artifact does not revoke it.
|
|
601
|
+
|
|
602
|
+
A **bridge** approval carries more fields, all written by `sync` and all about *what was approved*
|
|
159
603
|
rather than *who approved*:
|
|
160
604
|
|
|
161
605
|
| Field | Meaning |
|
|
162
606
|
|-------|---------|
|
|
163
|
-
| `artifactHash` | the content fingerprint the approval is bound to (`sha256:…`). The gate drops any approval whose hash ≠ the artifact's current one — this is revoke-on-change. For architecture it is the locked contract surface, for stories the whole `stories/` set. |
|
|
164
|
-
| `approvedAt` | when the platform says the review was submitted
|
|
607
|
+
| `artifactHash` | the content fingerprint the approval is bound to (`sha256:…`). The gate drops any approval whose hash ≠ the artifact's current one — this is revoke-on-change. For architecture it is the locked contract surface, for stories the whole `stories/` set. Every file is fingerprinted without its frontmatter `status:` line, which the gate and Build rewrite after review (shape 9); a hash an older release recorded over the whole file is still accepted. |
|
|
608
|
+
| `approvedAt` | when the platform says the review was submitted: GitHub's submission time, or GitLab's `approved_at`. Used to tell a genuine re-approval from the same review read again, which needs a time on both sides. A GitLab instance that does not send `approved_at`, and every GitLab record written before E64, hold the day the sync first recorded the approval — a date, which is read as "time unknown" and never compared with a time. Such a record takes the platform's time the next time the same approval is read, and keeps its `artifactHash`. |
|
|
165
609
|
| `pr` | the PR/MR number the approval arrived on. The second proof of a genuine re-approval, and the only one available on GitLab: a re-opened review is always a new PR, so an approval on a different number cannot be the old one re-read. Records written before this field existed are stamped once, from the `hub-prs.json` pointer they were recorded against. |
|
|
610
|
+
| `commit`, `url`, `reviewId` | the platform's evidence for the review (E64): the commit it was given on, its link, and its node id. GitHub only, and only when the read gave them — never written `null`. An MR approval on GitLab is not tied to a commit, so none is recorded there. For the record only; the gate never reads them. |
|
|
611
|
+
| `rosterName` | on an older entry whose login was recorded from the roster (E64), the name it had. The gate lets an exact submission time move such an entry to the person who really submitted that review. |
|
|
166
612
|
| `engagement` | `verified` when the approval carried the companion's engagement marker, else `none`. Advisory unless `hub.review.requireEngagement` is on. |
|
|
167
613
|
|
|
168
614
|
`date` is when the sync **recorded** the approval, not when it was given — it is preserved across an
|
|
@@ -171,20 +617,26 @@ unchanged re-sync so that re-reading a review never churns the ledger.
|
|
|
171
617
|
## `comments.json`
|
|
172
618
|
Append-only ledger (an array), the machine-readable counterpart to the `reviews/*--comments.md` markdown
|
|
173
619
|
("who reviewed/commented", as `approvals.json` is "who approved"). Written by `yad-review-gate`'s
|
|
174
|
-
`comment` action
|
|
620
|
+
`comment` action and by `yad gate sync`; feeds the `approved.md` participation list, not the gate predicate. Each entry:
|
|
175
621
|
|
|
176
622
|
```json
|
|
177
|
-
{ "artifact": "epic.md", "step": "epic-review", "commenter": "<
|
|
623
|
+
{ "artifact": "epic.md", "step": "epic-review", "commenter": "<platform login>", "round": <n>, "count": <comments this round>, "date": "<YYYY-MM-DD>" }
|
|
178
624
|
```
|
|
179
625
|
|
|
626
|
+
`commenter` is the platform login (on a Product with no platform, the name the reviewer gave). An older entry may still carry `role` and `domain`; the gate never reads them, and while the roster is on disk the next sync write records the login on it, keeping the old name in `rosterName` — unless two records in one round would then name the same login (E64).
|
|
627
|
+
|
|
180
628
|
## `hub-prs.json`
|
|
181
|
-
Present only when the
|
|
182
|
-
PR/MR opened on the
|
|
629
|
+
Present only when the Shape review runs through the platform bridge. Per review step, the review
|
|
630
|
+
PR/MR opened on the Product (sibling of `approvals.json`, so the locked `state.json` step shape is untouched):
|
|
183
631
|
|
|
184
632
|
```json
|
|
185
633
|
{ "step": "<review step id>", "artifact": "<artifact>", "platform": "github|gitlab", "number": <n>, "url": "<pr/mr url>", "branch": "review/EP-<slug>/<artifact-base>", "lastSyncedAt": "<YYYY-MM-DD or null>" }
|
|
186
634
|
```
|
|
187
635
|
|
|
636
|
+
The same array is written under two names: `product-prs.json` (the name from shape 3) and `hub-prs.json`
|
|
637
|
+
(the old name, kept for one major). `yad gate sync` may add `nudged`, the logins it has already asked to
|
|
638
|
+
use the review companion, so it never asks them twice.
|
|
639
|
+
|
|
188
640
|
## `design-links.json`
|
|
189
641
|
Present only when the `ui-design` step materialized the design in a connected design tool
|
|
190
642
|
(`yad-connect-design` → `.sdlc/design.json`). Written by `yad-ui`, the machine-readable screen→frame map
|
|
@@ -218,30 +670,30 @@ Human-readable review records, one file per round:
|
|
|
218
670
|
and `<artifact-base>` is the artifact without extension (e.g. `epic`, `architecture`, `stories-S01`).
|
|
219
671
|
|
|
220
672
|
## Dial defaults & locks
|
|
221
|
-
- Every step defaults to `automation: human_approve` (build plan §2).
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
|
|
673
|
+
- Every step defaults to `automation: human_approve` / `advance: human` (build plan §2).
|
|
674
|
+
- Every Shape step is seeded `locked: true`. A review gate is never `advance: auto`. Since E34 a feature
|
|
675
|
+
Shape author step may be set to `auto` project-wide in `.sdlc/automation.json`, but that is recorded,
|
|
676
|
+
not acted on (see the kill switch section above). A Build step's dial lives on its lane in `build-state`.
|
|
225
677
|
|
|
226
678
|
---
|
|
227
679
|
|
|
228
|
-
# Phase 4
|
|
680
|
+
# Phase 4 Build state (Build made dial-bearing)
|
|
229
681
|
|
|
230
|
-
Phase 3 recorded build progress only *after the fact* in `build-log.json`. Phase 4 needs the
|
|
231
|
-
steps to carry their own
|
|
682
|
+
Phase 3 recorded build progress only *after the fact* in `build-log.json`. Phase 4 needs the Build
|
|
683
|
+
steps to carry their own advance dial so the orchestrator (`yad-run`) can read it and decide
|
|
232
684
|
whether to advance on its own. Two new files under `.sdlc/` do this.
|
|
233
685
|
|
|
234
686
|
> **Who commits these.** `build-state/<story-id>.json`, `trust-log.json`, and `build-log.json` are
|
|
235
|
-
> **machine-written** by
|
|
236
|
-
> **`yad checkpoint`** — the
|
|
237
|
-
> one `chore(hub): sync
|
|
238
|
-
> default branch, staging **only** these three ledgers by an explicit allowlist (never a
|
|
239
|
-
> gate file — `state/approvals/comments/hub-prs.json`, `reviews/*.md` — so `ledger-guard` never trips).
|
|
687
|
+
> **machine-written** by Build (`yad-run`, `yad-engineer-review`) and committed by
|
|
688
|
+
> **`yad checkpoint`** — the Build analogue of the Shape `yad gate ci` sync. It lands them as
|
|
689
|
+
> one `chore(hub): sync Build state — <epic>/<story> by @<login>` audit-trail commit, on the
|
|
690
|
+
> default branch, staging **only** these three ledgers by an explicit allowlist (never a Shape
|
|
691
|
+
> gate file — `state/approvals/comments/product-prs/hub-prs.json`, `reviews/*.md` — so `ledger-guard` never trips).
|
|
240
692
|
> Teammates don't review these machine writes; the commit exists so CI, `yad status`, and other
|
|
241
693
|
> machines always see current trust evidence.
|
|
242
694
|
|
|
243
695
|
## `build-state/<story-id>.json`
|
|
244
|
-
One file per story that has entered
|
|
696
|
+
One file per story that has entered Build. Build is **per-story, per-repo**, so the
|
|
245
697
|
steps live under each repo (mirrors the per-repo shape of `build-log.json`).
|
|
246
698
|
|
|
247
699
|
```json
|
|
@@ -251,11 +703,11 @@ steps live under each repo (mirrors the per-repo shape of `build-log.json`).
|
|
|
251
703
|
"backend": {
|
|
252
704
|
"currentStep": "checks",
|
|
253
705
|
"steps": [
|
|
254
|
-
{ "id": "spec", "automation": "human_approve", "locked": false, "status": "done" },
|
|
255
|
-
{ "id": "tasks", "automation": "human_approve", "locked": false, "status": "done" },
|
|
256
|
-
{ "id": "implement", "automation": "human_approve", "locked": false, "status": "done" },
|
|
257
|
-
{ "id": "checks", "automation": "machine_advance","locked": false, "status": "in_progress" },
|
|
258
|
-
{ "id": "engineer-review", "automation": "human_approve", "locked": true, "status": "
|
|
706
|
+
{ "id": "spec", "automation": "human_approve", "advance": "human", "locked": false, "status": "done" },
|
|
707
|
+
{ "id": "tasks", "automation": "human_approve", "advance": "human", "locked": false, "status": "done" },
|
|
708
|
+
{ "id": "implement", "automation": "human_approve", "advance": "human", "locked": false, "status": "done" },
|
|
709
|
+
{ "id": "checks", "automation": "machine_advance", "advance": "auto","locked": false, "status": "in_progress" },
|
|
710
|
+
{ "id": "engineer-review", "automation": "human_approve", "advance": "human", "locked": true, "status": "todo" }
|
|
259
711
|
]
|
|
260
712
|
}
|
|
261
713
|
}
|
|
@@ -266,22 +718,48 @@ Each `steps[]` entry:
|
|
|
266
718
|
|
|
267
719
|
| Field | Values | Meaning |
|
|
268
720
|
|-------|--------|---------|
|
|
269
|
-
| `id` | `spec`, `tasks`, `implement`, `checks`, `engineer-review` |
|
|
270
|
-
| `automation` | `human_approve` \| `machine_advance` | Dial 2. Defaults to `human_approve`;
|
|
721
|
+
| `id` | `spec`, `tasks`, `implement`, `checks`, `engineer-review` | Build step identity (the `back_steps` from `config.yaml` + the human merge gate). |
|
|
722
|
+
| `automation` / `advance` | `human_approve`/`human` \| `machine_advance`/`auto` | Dial 2, written under both names (the OLD one is read). Defaults to `human_approve`; `yad dial <epic> <story> --repo <r> <step> --to auto` flips it (E34 — nothing to earn), never on the `engineer-review` gate. |
|
|
271
723
|
| `locked` | `true` \| `false` | `engineer-review` is `true` — it never auto-advances (build plan §E). |
|
|
272
|
-
| `
|
|
724
|
+
| `closed` | `{ by, date, via, run }` | Written by the `yad-run` skill when it moves the lane past the step: `via` is `auto` or `human`, and `run` names the trust-log run (E18). See "Closing records". |
|
|
725
|
+
| `status` | the same **step states** as the Shape chain | Lifecycle. This file is **not** migrated to shape 7 — the `yad-run` / `yad-implement` skills write it, not the engine, so a rewrite would be undone by their next write. `yad-run` advances `done` steps and marks a halted lane `blocked` **with a `record`** naming the halt cause (a failed check, a scope overrun, a contract touch): that record is what separates a halted lane from one nobody started, because a bare `blocked` is the pre-shape-7 spelling of `todo` and still reads that way here. A lane halted by an older `yad-run` carries no record and reads as `todo` until the next run rewrites it — nothing advances past it either way. |
|
|
273
726
|
|
|
274
727
|
`currentStep` is the `id` the orchestrator is waiting on / about to run for that repo. The file is
|
|
275
|
-
created when a story enters
|
|
728
|
+
created when a story enters Build; all dials start `advance: human` (`automation: human_approve`) (the `config.yaml`
|
|
276
729
|
`automation.default`).
|
|
277
730
|
|
|
731
|
+
**A lane skipped whole (E39).** When a story declares a repo that turns out to need no change,
|
|
732
|
+
`yad skip <epic> <story> --repo <name> --reason "<why>"` writes that repo's entry as a skipped lane —
|
|
733
|
+
no `steps`, just the state and its record:
|
|
734
|
+
|
|
735
|
+
```json
|
|
736
|
+
{ "story": "EP-<slug>-S0N", "repos": { "web": { "status": "skipped", "record": { "reason": "no UI change", "by": "<login>", "date": "<YYYY-MM-DD>" } } } }
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
- Refused before `stories-review` has passed (edit the story's `repos:` then), for a repo the story does
|
|
740
|
+
not declare, once work has started in the lane or a ship is recorded for it, and for the story's last
|
|
741
|
+
lane. `yad unskip <epic> <story> --repo <name>` removes the entry again.
|
|
742
|
+
- **Only a whole lane.** No single Build step is ever `skipped` or `deferred` — `yad doctor` reports one.
|
|
743
|
+
`yad-run` adds a missing repo's entry to an existing file, and never drives a skipped lane.
|
|
744
|
+
- It is not written into the story's `repos:` list, because that list is part of what the stories review
|
|
745
|
+
approved. `yad checkpoint --push` commits it with the rest of the Build state.
|
|
746
|
+
- Readers: `yad next` prints `skipped (N/A)` with the reason (and still points at `yad-run` while every
|
|
747
|
+
recorded lane is skipped and nothing has started); `yad foundation status` counts the lane as finished
|
|
748
|
+
once another lane really shipped; `yad checkpoint --retro-ship` refuses it, and `yad-engineer-review`
|
|
749
|
+
checks for it before recording a ship.
|
|
750
|
+
- `skipped` is honoured only over a lane with no work in it. An entry that says `skipped` beside steps that
|
|
751
|
+
have started — including a status word this release does not know — is read as the work, and
|
|
752
|
+
`yad doctor` fails it as a contradiction.
|
|
753
|
+
|
|
278
754
|
`yad next` reads these files too: once an epic is `ready-for-build`, `yad next <epic>` resolves each
|
|
279
|
-
story/repo's `currentStep` into the next build sub-step
|
|
280
|
-
|
|
281
|
-
|
|
755
|
+
story/repo's `currentStep` into the next build sub-step and prints it with the remaining chain and the
|
|
756
|
+
step's advance dial — so Build is guided, not just hinted at. The skill it names comes from
|
|
757
|
+
`.sdlc/skills.json` when the project bound one (see "The one column a project overrides is the skill"
|
|
758
|
+
above); unbound, the defaults are `spec`/`tasks` → `yad-spec`, `implement` → `yad-implement`,
|
|
759
|
+
`checks` → `yad-checks`, `engineer-review` → `yad-engineer-review`.
|
|
282
760
|
|
|
283
761
|
## `trust-log.json` (shard-then-fold)
|
|
284
|
-
Append-only ledger, the
|
|
762
|
+
Append-only ledger, the Build analogue of `approvals.json`. **This is the evidence base** that
|
|
285
763
|
decides when a step is safe to automate (build plan Step A). One entry per step run.
|
|
286
764
|
|
|
287
765
|
**Storage — loose shards + a folded file (the "loose objects + `git gc`" model).** Two people driving
|
|
@@ -295,15 +773,15 @@ back:
|
|
|
295
773
|
- **Folded file:** `epics/<epic>/.sdlc/trust-log.json` = `{ "epic": "<id>", "runs": [ <entry>, … ] }`
|
|
296
774
|
(also the legacy single-file layout, and the output of `yad tidy up`).
|
|
297
775
|
- **Union-read rule:** to read the ledger, take the folded file's `runs` array PLUS every file in the
|
|
298
|
-
`trust-log/` shard dir, and **concatenate** — every entry is a distinct run and the
|
|
776
|
+
`trust-log/` shard dir, and **concatenate** — every entry is a distinct run and the run record
|
|
299
777
|
counts re-runs, so **never dedup by `(story, repo, step)`**. (The only guard: a shard whose FULL
|
|
300
778
|
identity `(story, repo, step, uid)` already appears in the folded `runs` is a half-applied tidy and is
|
|
301
779
|
skipped — keying on `uid` alone would wrongly drop a different run that happened to reuse a token.) A legacy epic with only
|
|
302
780
|
the folded file and no shard dir still reads correctly — nothing to union.
|
|
303
781
|
- **`yad tidy up`** (manual, one person) folds a SHIPPED story's finished shards into the folded file's
|
|
304
782
|
`runs` and deletes them. Writers never fold — they only add shards; `yad checkpoint` commits the shard
|
|
305
|
-
dir, and `yad tidy up` is the
|
|
306
|
-
- The **
|
|
783
|
+
dir, and `yad tidy up` is the Build analogue of `git gc` folding loose objects.
|
|
784
|
+
- The **run record** `yad dial` prints reads this same union, filtered to the step and repo.
|
|
307
785
|
|
|
308
786
|
```json
|
|
309
787
|
{
|
|
@@ -311,7 +789,7 @@ back:
|
|
|
311
789
|
"repo": "backend",
|
|
312
790
|
"step": "checks",
|
|
313
791
|
"uid": "<short-unique-token>",
|
|
314
|
-
"automation": "human_approve",
|
|
792
|
+
"automation": "human_approve", "advance": "human",
|
|
315
793
|
"verdict": "approved-unchanged",
|
|
316
794
|
"signals": { "checks": "pass", "human_edited_diff": false, "scope_overrun": false, "contract_touch": false },
|
|
317
795
|
"ranBy": "machine",
|
|
@@ -324,7 +802,7 @@ back:
|
|
|
324
802
|
|-------|--------|---------|
|
|
325
803
|
| `step` | a `back_steps` id | Which step this run is recorded against. |
|
|
326
804
|
| `uid` | short unique token | Generated fresh per run (never reused) — makes each shard file and each re-run distinct; also the folded/loose de-dup guard. Legacy folded entries may lack it. |
|
|
327
|
-
| `automation` | dial in force at run time | So the log shows whether the run was a manual or an automated advance. |
|
|
805
|
+
| `automation` | dial in force at run time (recorded in the OLD vocabulary — this is history, and `yad migrate` deliberately does not rewrite it) | So the log shows whether the run was a manual or an automated advance. |
|
|
328
806
|
| `verdict` | `approved-unchanged` \| `approved-with-edits` \| `rejected` | The trust signal. **Provisional verdict is derived** (below); the human gate for that step confirms or overrides it and finalizes the entry. |
|
|
329
807
|
| `signals` | object | The raw inputs the provisional verdict was derived from. The fields present depend on the step (table below). |
|
|
330
808
|
| `ranBy` | `machine` \| `human` | Whether the orchestrator advanced it or a human did. |
|
|
@@ -344,11 +822,9 @@ same three-way shape, anchored to each step's human gate, never self-graded):
|
|
|
344
822
|
- accepted after a human edited the output (`human_edited_diff` / `human_edited_spec` / `task_rescoped`) → `approved-with-edits`;
|
|
345
823
|
- accepted as produced → `approved-unchanged`.
|
|
346
824
|
|
|
347
|
-
**
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
has `>= min_runs` entries AND the fraction with `verdict == "approved-unchanged"` is
|
|
351
|
-
`>= min_approved_unchanged`. The dial-setter in `yad-run` enforces this; `yad-status` surfaces it.
|
|
825
|
+
**The run record is advice (E34).** There is no trust threshold. `yad dial` prints a step's slice — runs
|
|
826
|
+
and the fraction `approved-unchanged`, from the union above — beside its dial, and never refuses on it; the
|
|
827
|
+
team decides. `yad-status` shows the same record.
|
|
352
828
|
|
|
353
829
|
## `build-log.json` (shard-then-fold)
|
|
354
830
|
The build ledger records one ship per merged task. Its schema and the ship record's fields are
|
|
@@ -377,7 +853,7 @@ storage layout is noted here (it mirrors `trust-log.json`):
|
|
|
377
853
|
After the contract locks and code ships, a change must not **mutate** a locked artifact (that destroys
|
|
378
854
|
the lock + the audit trail). Instead every change request becomes a **new epic, threaded to its parent**
|
|
379
855
|
(`config.yaml` `change:`). A feature is a **thread** of linked epics (genesis → change → defect → …); a
|
|
380
|
-
change-epic **inherits** unchanged
|
|
856
|
+
change-epic **inherits** unchanged Shape artifacts from its parent by reference and only **re-authors**
|
|
381
857
|
what it changes. So artifacts are never stale, only *superseded*; the feature's current truth is the
|
|
382
858
|
head of the thread, composed by the resolver (`yad-timeline`). `yad-change` seeds a change-epic;
|
|
383
859
|
`yad-defects` / `yad-timeline` render the thread; `yad-reconcile` flags drift; three CI gates enforce it.
|
|
@@ -385,13 +861,23 @@ head of the thread, composed by the resolver (`yad-timeline`). `yad-change` seed
|
|
|
385
861
|
## Lineage frontmatter (added to `epic.md`)
|
|
386
862
|
|
|
387
863
|
An enrichment block on `epic.md` — like the `design:` / `testing:` blocks, it does **not** change the
|
|
388
|
-
locked `state.json` step shape. Genesis epics are backfilled once with `kind: feature`, `
|
|
864
|
+
locked `state.json` step shape. Genesis epics are backfilled once with `kind: feature`, `type: feature`
|
|
865
|
+
and `thread: <self>`.
|
|
866
|
+
|
|
867
|
+
**The work-item type has two names.** `kind:` is the original and is still the one that is READ;
|
|
868
|
+
`type:` is the same value under the name from shape 5 on. **Write both, with the same value.** Writing
|
|
869
|
+
`type:` alone on a `change`/`defect`/`hotfix` is the one mistake that breaks something:
|
|
870
|
+
`lineage-check.sh` runs inside the code repo, reads `kind:`, finds none, defaults to `feature`, and
|
|
871
|
+
stops requiring the `parent:` those three must have. `yad doctor` fails on it (`type:gate-blind`).
|
|
389
872
|
|
|
390
873
|
| Field | Values | Meaning |
|
|
391
874
|
|-------|--------|---------|
|
|
392
|
-
| `
|
|
875
|
+
| `title` | one line of plain text, e.g. `Checkout from the mobile app` | **The work item's one-line name** (E111), written by `yad-epic`, `yad-change` and `yad-stub`. Optional: an item without one is shown by its id. Quotes are optional. A single-quoted title (`'…'`) is read without its quotes, with `''` read as `'`. A double-quoted title (`"…"`) is read with JSON's escapes (`\"`, `\\`, `\n`, `\t`, `\u00e9` …); one using any other escape is kept as written, quotes and backslashes included. A value that is not one quoted string (`"Login" is broken on "Safari"`, or `"x" # note`) is kept as written. **No title** is read from a value that begins with `[` and ends with `]` (the reader turns it into a list — double-quote such a title, writing an inner `"` as `\"`), from a YAML block marker (`>`, `\|`: only one line is read) or from YAML's null (`~`, `null`, `Null`, `NULL` — the whole value, unquoted: `'null'` and `Nullable fields` are text). A trailing `#` comment becomes part of the title. Whitespace runs, a newline included, become one space; the other control characters (such as ESC or BEL) and the bidi controls (such as U+202E, which reverses how text is shown) are dropped — from a `change.json` title too. Zero-width characters (the joiners among them) are kept, since emoji and some scripts need them. An item with no `title:` falls back to the `title` in its `change.json` (only change items have one). The epic review is bound to a hash of this file, so adding or rewording a title after approval drops the approval — set it while the file is written, and do not add one to an already-approved epic just to fill the gap. Read by `.sdlc/index.json`. |
|
|
876
|
+
| `kind` | `feature` \| `change` \| `defect` \| `hotfix` \| `chore` | The work-item type, under the name that is still read. Genesis is `feature` (default when absent). |
|
|
877
|
+
| `type` | the same five values | The same value under the name from shape 5 on. Write it beside `kind`, never instead of it. |
|
|
878
|
+
| `theme` | any word or short phrase, in any language, e.g. `checkout-revamp` | **Optional grouping tag.** Puts several epics under one heading. It is what this method has instead of a rung above the Epic: the ladder stays Product → Epic → Story → Task, and grouping is a label rather than a level. No fixed list and nothing to register. **One tag, never a list** — `theme: [a, b]` is read as no theme at all. Spell an existing theme exactly as the other epics do; `yad doctor` reports one theme spelled two ways (`theme:variants`), because two spellings group as two, and a `#` inside the value (`theme:commented`), because the reader keeps the whole rest of the line. Lives only in `epic.md` — there is no copy in `state.json`, so no file shape changed. Read by `yad next` (printed) and `yad thread --json`. |
|
|
393
879
|
| `thread` | `EP-<genesis>` | Stable thread id = the genesis epic's id (never renamed → stablest anchor). A **derived cache** — the authoritative thread is `parent` walked to the root; a mismatch is detectable corruption (`yad doctor`). Genesis: `thread == id`. |
|
|
394
|
-
| `parent` | `EP-<slug>` | The immediate predecessor epic. **Absent
|
|
880
|
+
| `parent` | `EP-<slug>` | The immediate predecessor epic. **Absent for the genesis types (`feature`, `chore`); required for `change`, `defect` and `hotfix`.** A chore — a dependency bump, a CI move — usually has no feature to hang off, and an invented parent would file it under a feature it has nothing to do with. |
|
|
395
881
|
| `inherits` | subset of `[epic, architecture, contract, ui-design, stories, test-cases]` | Artifact bases carried **by reference**, not re-authored. The rest are re-authored in this epic. |
|
|
396
882
|
| `supersedes` | `[EP-<slug>-S0N, …]` | Optional — specific parent story IDs this epic replaces in the head. |
|
|
397
883
|
| `origin` | `production` \| `staging` \| `qa` \| `review` | **defect/hotfix only.** Where the defect was found. |
|
|
@@ -407,11 +893,11 @@ In a brownfield repo not every already-built feature has an epic, so a defect/ch
|
|
|
407
893
|
thread from (`yad-change` requires one; `lineage-check` rejects a missing parent). `yad-stub` mints the
|
|
408
894
|
smallest **real** node — a **stub genesis epic** — so the bug can be captured now and formalized later.
|
|
409
895
|
|
|
410
|
-
A stub is a normal genesis (`
|
|
896
|
+
A stub is a normal genesis (type `feature` under both names, `thread == id`, no `parent`) whose `epic.md` carries
|
|
411
897
|
`stub: backfill-pending` + `verified: false` and whose `state.json` uses a **sentinel**, mirroring
|
|
412
898
|
`EP-discovery` / `discovery-done`:
|
|
413
899
|
- top-level `kind: "stub"` and `currentStep: "backfill-pending"`;
|
|
414
|
-
- the **same 10-step
|
|
900
|
+
- the **same 10-step Shape chain** as a normal epic, every step `status: "todo"` (so `validateState`
|
|
415
901
|
passes and `promote` can "wake" the chain into normal authoring with no re-seed);
|
|
416
902
|
- empty `approvals.json` / `comments.json`; **no** `contract-lock.json` (no surface locked yet).
|
|
417
903
|
|
|
@@ -433,33 +919,36 @@ approved backfill spec, **and** rewrites `state.json` — removing `kind: "stub"
|
|
|
433
919
|
off the sentinel:
|
|
434
920
|
- **light promote (default)** → `currentStep: "backfill-done"`, a **terminal sentinel** (like
|
|
435
921
|
`discovery-done`): the feature is a real, verified anchor documented by its backfill spec; `nextAction`
|
|
436
|
-
reports "documented anchor — evolve it by threading a change/defect", never a pending stub, and
|
|
437
|
-
|
|
438
|
-
- **full promote (opt-in)** → `currentStep: "epic"`, `epic.status: "in_progress"`, to run the normal
|
|
439
|
-
|
|
922
|
+
reports "documented anchor — evolve it by threading a change/defect", never a pending stub, and Build
|
|
923
|
+
never runs directly against it;
|
|
924
|
+
- **full promote (opt-in)** → `currentStep: "epic"`, `epic.status: "in_progress"`, to run the normal Shape
|
|
925
|
+
part and lock a real contract.
|
|
440
926
|
|
|
441
927
|
From promotion on, the thread's contract protection is live.
|
|
442
928
|
|
|
443
929
|
## Inherited steps in `state.json`
|
|
444
930
|
|
|
445
931
|
A change-epic's `state.json` is structurally identical (so `advanceState` / `nextAction` / `gatePredicate`
|
|
446
|
-
/ the
|
|
447
|
-
only re-authored steps run.
|
|
932
|
+
/ the verified ledger run unchanged), but **inherited** steps are pre-marked `done` with two extra fields, and
|
|
933
|
+
only re-authored steps run. `yad epic new <slug> --type <change|defect|hotfix> --parent <EP-parent> --inherits <bases>`
|
|
934
|
+
writes it (E42) and sets `currentStep` to the first re-authored step.
|
|
448
935
|
|
|
449
936
|
```json
|
|
450
937
|
{ "id": "architecture", "type": "author", "artifact": "architecture.md",
|
|
451
|
-
"assistance": "review", "automation": "human_approve", "locked": true,
|
|
452
|
-
"status": "
|
|
938
|
+
"assistance": "review", "driver": "pair", "automation": "human_approve", "advance": "human", "locked": true,
|
|
939
|
+
"status": "satisfied", "inherited": true, "inheritedFrom": "EP-checkout",
|
|
453
940
|
"boundHash": "sha256:…", "risk_tags": [] }
|
|
454
941
|
```
|
|
455
942
|
|
|
456
943
|
- `inherited` — `true` when this step's artifact is taken by reference from the thread (not authored here).
|
|
457
|
-
- `inheritedFrom` — the epic
|
|
944
|
+
- `inheritedFrom` — the epic along the parent's line that owns the referenced artifact (not always the parent).
|
|
458
945
|
- `boundHash` — the artifact's hash at inherit time (contract surface hash for `architecture`; the
|
|
459
|
-
`storiesHash`/file hash for others
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
block and are
|
|
946
|
+
`storiesHash`/file hash for others — without the frontmatter `status:` line from shape 9, and an older
|
|
947
|
+
whole-file hash is still accepted). The gate predicate short-circuits an `inherited` step as
|
|
948
|
+
**satisfied** iff `boundHash` still equals the current hash of that artifact in the owning epic
|
|
949
|
+
(`inheritedFrom`) — which holds unless the owner's copy changes, so inherited steps never block and are
|
|
950
|
+
never re-reviewed. Compare against the owner's copy, not the child's: a change-epic writes its own
|
|
951
|
+
`epic.md` (the change brief), which is not the epic it carries.
|
|
463
952
|
|
|
464
953
|
`approvals.json` gets a **provenance** record per inherited gate (not a forged approval):
|
|
465
954
|
|
|
@@ -468,9 +957,29 @@ only re-authored steps run. The seeder sets `currentStep` to the first re-author
|
|
|
468
957
|
"from": "EP-checkout", "boundHash": "sha256:…", "date": "<YYYY-MM-DD>" }
|
|
469
958
|
```
|
|
470
959
|
|
|
960
|
+
**What may be inherited (E42).** The engine carries a base only from the epic that owns it along the
|
|
961
|
+
PARENT's line (`inheritedFrom` — a sibling change off the same genesis is not something this epic builds
|
|
962
|
+
on), and only when that owner wrote AND approved it: both the step and its `-review` are `done`, and the
|
|
963
|
+
artifact is on disk. `epic`, `architecture` + `contract` (always together) and `ui-design` may be
|
|
964
|
+
inherited; `analysis` rides with `epic`. `stories` and `test-cases` never are — the thread's set is the
|
|
965
|
+
union of every contributor, and `stories-review` is what hands an epic to Build. Everything else is
|
|
966
|
+
refused before anything is written:
|
|
967
|
+
|
|
968
|
+
| Upstream | Refused because | Instead |
|
|
969
|
+
|---|---|---|
|
|
970
|
+
| the step is `skipped` | a skip is a decision, not an artifact — carrying it would claim a review nobody did | leave the base out, then `yad skip` it on this epic |
|
|
971
|
+
| the step is `deferred` | the work is still owed | leave the base out and author it here, so the owed work follows the thread |
|
|
972
|
+
| the step or its review is unfinished | nothing approved exists yet | finish it on the owner first |
|
|
973
|
+
| architecture approved, but no usable lock | rule 5 — you may skip authoring a contract, never having one | lock the owner's surface first |
|
|
974
|
+
| the owner's surface no longer matches its lock | a pointer would pass the drift down the thread | re-lock the owner first |
|
|
975
|
+
| the route has no such step (a short lane) | nothing to inherit | leave the base out |
|
|
976
|
+
|
|
977
|
+
A brownfield anchor (`kind: stub`, or a light-promoted `backfill-done`) is the one exception: its chain
|
|
978
|
+
was never started, so its bases carry `boundHash: null` and no pointer-lock is written.
|
|
979
|
+
|
|
471
980
|
## The pointer-lock — `contract-lock.json` in a change-epic
|
|
472
981
|
|
|
473
|
-
When `architecture` is inherited,
|
|
982
|
+
When `architecture` is inherited, `yad epic new` writes a **derived** `contract-lock.json` carrying the
|
|
474
983
|
parent's hash **verbatim** so `contract-check.sh` (which reads only `hash`) passes unchanged. There is
|
|
475
984
|
no `contract.md` in the child to edit, so the surface physically cannot drift.
|
|
476
985
|
|
|
@@ -481,7 +990,7 @@ no `contract.md` in the child to edit, so the surface physically cannot drift.
|
|
|
481
990
|
|
|
482
991
|
Omitting `architecture` from `inherits` (depth `contract-surface`) is what triggers a **real re-lock**:
|
|
483
992
|
`yad-architecture` re-authors `contract.md`, computes a **new** hash, and `architecture-review` carries
|
|
484
|
-
`risk_tags: ["contract"]` → the usual
|
|
993
|
+
`risk_tags: ["contract"]` → the usual contract-risk review (full approver count 3, capped at the active people less one and reported, E72; only the base 1 is enforced). This unifies "route back to the
|
|
485
994
|
architecture gate" with "open a contract-surface change-epic" — one mechanism, not two.
|
|
486
995
|
|
|
487
996
|
## `change.json`
|
|
@@ -489,7 +998,7 @@ Intake + triage record, one per change/defect/hotfix epic (sibling of `approvals
|
|
|
489
998
|
|
|
490
999
|
```json
|
|
491
1000
|
{ "epicId": "EP-checkout-queue-filter", "thread": "EP-checkout", "parent": "EP-checkout",
|
|
492
|
-
"kind": "defect", "depth": "defect-fix", "intakeBy": "alice", "intakeDate": "<YYYY-MM-DD>",
|
|
1001
|
+
"kind": "defect", "type": "defect", "depth": "defect-fix", "intakeBy": "alice", "intakeDate": "<YYYY-MM-DD>",
|
|
493
1002
|
"title": "Pending queue returns fulfilled orders", "description": "…",
|
|
494
1003
|
"affectedArtifacts": ["stories", "test-cases"],
|
|
495
1004
|
"reauthors": ["stories", "test-cases"], "inherits": ["epic", "architecture", "contract", "ui-design"],
|
|
@@ -506,7 +1015,7 @@ Thread-level rollups (`yad-timeline` / `yad-defects`) are **derived** — walk e
|
|
|
506
1015
|
sharing `thread` and read each `change.json`; there is no duplicated thread registry.
|
|
507
1016
|
|
|
508
1017
|
## `reconcile-debt.json`
|
|
509
|
-
Append-only ledger of hotfix ship-first debt (a hotfix shipped code before its
|
|
1018
|
+
Append-only ledger of hotfix ship-first debt (a hotfix shipped code before its Shape gates approved).
|
|
510
1019
|
|
|
511
1020
|
```json
|
|
512
1021
|
[ { "thread": "EP-checkout", "epicId": "EP-checkout-hotfix-x", "openedDate": "<date>",
|
|
@@ -516,5 +1025,5 @@ Append-only ledger of hotfix ship-first debt (a hotfix shipped code before its f
|
|
|
516
1025
|
```
|
|
517
1026
|
|
|
518
1027
|
`status: "open"` blocks the **next** normal change on the thread (`reconcile-debt-check.sh`) until it is
|
|
519
|
-
`"paid"` (evidence: the
|
|
1028
|
+
`"paid"` (evidence: the Shape artifacts updated **and** a regression test added). The debt lets a hotfix
|
|
520
1029
|
jump the queue once, but freezes new thread work until the SDLC again describes production.
|