yadflow 3.19.0-next.2 → 4.0.0-next.3

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +371 -0
  2. package/README.md +160 -22
  3. package/bin/commands.mjs +46 -0
  4. package/bin/yad.mjs +506 -117
  5. package/cli/artifact-status.mjs +30 -11
  6. package/cli/capture.mjs +417 -0
  7. package/cli/checkpoint.mjs +47 -27
  8. package/cli/claims.mjs +165 -0
  9. package/cli/codeowners-command.mjs +170 -0
  10. package/cli/codeowners.mjs +397 -0
  11. package/cli/commit.mjs +13 -9
  12. package/cli/dial.mjs +183 -0
  13. package/cli/docs.mjs +88 -32
  14. package/cli/doctor.mjs +1682 -90
  15. package/cli/epic-state.mjs +3460 -211
  16. package/cli/epic.mjs +506 -0
  17. package/cli/errors.mjs +3 -0
  18. package/cli/fold.mjs +247 -0
  19. package/cli/gate-local.mjs +296 -0
  20. package/cli/gate.mjs +977 -183
  21. package/cli/history.mjs +556 -0
  22. package/cli/hook.mjs +264 -53
  23. package/cli/hubcommit.mjs +9 -19
  24. package/cli/index-command.mjs +87 -0
  25. package/cli/ledger.mjs +54 -4
  26. package/cli/lib.mjs +161 -9
  27. package/cli/manifest.mjs +376 -38
  28. package/cli/migrate.mjs +683 -52
  29. package/cli/mode.mjs +170 -0
  30. package/cli/next.mjs +365 -86
  31. package/cli/openpr.mjs +193 -34
  32. package/cli/owners.mjs +266 -0
  33. package/cli/people.mjs +657 -0
  34. package/cli/plan.mjs +466 -125
  35. package/cli/platform.mjs +109 -128
  36. package/cli/product-index.mjs +287 -0
  37. package/cli/protection.mjs +706 -0
  38. package/cli/reconcile.mjs +40 -12
  39. package/cli/repo-publish.mjs +24 -26
  40. package/cli/repo.mjs +23 -14
  41. package/cli/report.mjs +24 -14
  42. package/cli/review.mjs +19 -22
  43. package/cli/riskmap-command.mjs +289 -0
  44. package/cli/riskmap.mjs +373 -0
  45. package/cli/setup.mjs +127 -279
  46. package/cli/ship.mjs +6 -5
  47. package/cli/skill.mjs +180 -0
  48. package/cli/skip.mjs +211 -30
  49. package/cli/thread.mjs +41 -16
  50. package/cli/tidy.mjs +18 -18
  51. package/cli/update-commit.mjs +21 -21
  52. package/cli/usage.mjs +115 -109
  53. package/package.json +3 -3
  54. package/skills/sdlc/config.yaml +149 -70
  55. package/skills/sdlc/module-help.csv +24 -24
  56. package/skills/yad-analysis/SKILL.md +121 -59
  57. package/skills/yad-architecture/SKILL.md +30 -18
  58. package/skills/yad-architecture/references/contract-format.md +8 -5
  59. package/skills/yad-backfill/SKILL.md +10 -4
  60. package/skills/yad-backfill/references/backfill.md +8 -1
  61. package/skills/yad-backfill/templates/checks/backfill-check.sh +10 -2
  62. package/skills/yad-change/SKILL.md +119 -42
  63. package/skills/yad-change/references/triage.md +42 -27
  64. package/skills/yad-checks/SKILL.md +68 -35
  65. package/skills/yad-checks/references/check-gates.md +310 -85
  66. package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
  67. package/skills/yad-checks/templates/checks/contract-check.sh +130 -9
  68. package/skills/yad-checks/templates/checks/epic-open.sh +2 -2
  69. package/skills/yad-checks/templates/checks/ledger-guard.sh +55 -8
  70. package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
  71. package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +1 -1
  72. package/skills/yad-checks/templates/checks/risk-map-check.sh +438 -0
  73. package/skills/yad-checks/templates/checks/verified-commits.sh +20 -46
  74. package/skills/yad-checks/templates/github/yad-checks.yml +13 -2
  75. package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
  76. package/skills/yad-checks/templates/github/yad-update-guard.yml +7 -5
  77. package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
  78. package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +10 -1
  79. package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +5 -5
  80. package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +3 -3
  81. package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
  82. package/skills/yad-checks/templates/hooks/ledger-guard.sh +37 -6
  83. package/skills/yad-checks/templates/hooks/yad-capture.sh +53 -0
  84. package/skills/yad-commit/SKILL.md +4 -4
  85. package/skills/yad-connect-design/SKILL.md +5 -5
  86. package/skills/yad-connect-design/references/design-context.md +1 -1
  87. package/skills/yad-connect-design/references/design-registry.md +2 -2
  88. package/skills/yad-connect-docs/SKILL.md +11 -11
  89. package/skills/yad-connect-docs/references/docs-registry.md +1 -1
  90. package/skills/yad-connect-learning/SKILL.md +5 -5
  91. package/skills/yad-connect-learning/references/learning-registry.md +2 -2
  92. package/skills/yad-connect-repos/SKILL.md +66 -45
  93. package/skills/yad-connect-repos/references/code-context.md +4 -4
  94. package/skills/yad-connect-repos/references/hub-config.md +47 -51
  95. package/skills/yad-connect-repos/references/repos-registry.md +8 -7
  96. package/skills/yad-connect-repos/references/risk-map.md +81 -0
  97. package/skills/yad-connect-testing/SKILL.md +5 -5
  98. package/skills/yad-connect-testing/references/testing-context.md +3 -4
  99. package/skills/yad-connect-testing/references/testing-registry.md +2 -2
  100. package/skills/yad-defects/SKILL.md +7 -7
  101. package/skills/yad-discovery/SKILL.md +131 -94
  102. package/skills/yad-discovery/references/discovery-schema.md +22 -6
  103. package/skills/yad-discovery/references/foundation-schema.md +374 -0
  104. package/skills/yad-docs/SKILL.md +13 -8
  105. package/skills/yad-docs/references/data-mapping.md +9 -7
  106. package/skills/yad-docs/templates/app/package-lock.json +3 -3
  107. package/skills/yad-docs-overview/SKILL.md +30 -15
  108. package/skills/yad-docs-overview/references/pipeline-model.md +33 -20
  109. package/skills/yad-docs-sync/SKILL.md +10 -5
  110. package/skills/yad-docs-sync/references/staleness.md +8 -7
  111. package/skills/yad-engineer-review/SKILL.md +81 -17
  112. package/skills/yad-engineer-review/references/ship-and-record.md +17 -8
  113. package/skills/yad-epic/SKILL.md +173 -92
  114. package/skills/yad-epic/references/state-schema.md +610 -94
  115. package/skills/yad-hub-bridge/SKILL.md +57 -38
  116. package/skills/yad-hub-bridge/references/bridge.md +94 -67
  117. package/skills/yad-hub-bridge/references/login-roster.md +163 -70
  118. package/skills/yad-hub-bridge/templates/checks/hub-route.sh +22 -19
  119. package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +34 -14
  120. package/skills/yad-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +2 -2
  121. package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +21 -11
  122. package/skills/yad-implement/SKILL.md +27 -13
  123. package/skills/yad-implement/references/implement-conventions.md +2 -2
  124. package/skills/yad-learn/SKILL.md +9 -9
  125. package/skills/yad-learn/references/learning-state.md +2 -2
  126. package/skills/yad-open-pr/SKILL.md +61 -26
  127. package/skills/yad-pair-review/SKILL.md +7 -5
  128. package/skills/yad-pair-review/references/session-state.md +1 -1
  129. package/skills/yad-pr-template/SKILL.md +47 -26
  130. package/skills/yad-pr-template/references/risk-routing.md +97 -24
  131. package/skills/yad-pr-template/templates/checks/pr-template.sh +34 -11
  132. package/skills/yad-pr-template/templates/checks/pr-title.sh +32 -9
  133. package/skills/yad-pr-template/templates/checks/risk-route.sh +107 -14
  134. package/skills/yad-pr-template/templates/github/pull_request_template.md +7 -5
  135. package/skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md +7 -5
  136. package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +14 -13
  137. package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +14 -12
  138. package/skills/yad-reconcile/SKILL.md +3 -3
  139. package/skills/yad-report/SKILL.md +5 -5
  140. package/skills/yad-review-companion/SKILL.md +6 -3
  141. package/skills/yad-review-gate/SKILL.md +203 -93
  142. package/skills/yad-review-gate/references/gating.md +232 -54
  143. package/skills/yad-run/SKILL.md +81 -51
  144. package/skills/yad-run/references/run-loop.md +61 -39
  145. package/skills/yad-ship/SKILL.md +15 -11
  146. package/skills/yad-spec/SKILL.md +21 -6
  147. package/skills/yad-spec/references/spec-handoff.md +17 -5
  148. package/skills/yad-status/SKILL.md +115 -50
  149. package/skills/yad-stories/SKILL.md +37 -21
  150. package/skills/yad-stories/references/story-schema.md +7 -6
  151. package/skills/yad-stub/SKILL.md +58 -47
  152. package/skills/yad-sync-repos/SKILL.md +2 -2
  153. package/skills/yad-test-cases/SKILL.md +32 -23
  154. package/skills/yad-test-cases/references/test-cases-schema.md +7 -4
  155. package/skills/yad-timeline/SKILL.md +8 -7
  156. package/skills/yad-ui/SKILL.md +41 -18
  157. package/cli/roster.mjs +0 -164
  158. package/skills/sdlc/install.sh +0 -68
@@ -1,8 +1,9 @@
1
- # `yad-run` — the loop, the trust verdict, the threshold
1
+ # `yad-run` — the loop and the trust verdict
2
2
 
3
3
  This is the detail behind `SKILL.md`. It restates the orchestration so the skill is self-contained,
4
- and pins down the two judgments the skill makes: **what trust verdict to record** and **when a step
5
- has earned `machine_advance`**.
4
+ and pins down the judgment the skill makes: **what trust verdict to record**. Whether a step advances on
5
+ its own is not a judgment any more — the team sets the dial with `yad dial` (E34), and the engine answers
6
+ what applies.
6
7
 
7
8
  ## The Build steps
8
9
 
@@ -12,39 +13,66 @@ From `config.yaml` `automation.back_steps` plus the human merge gate:
12
13
  spec → tasks → implement → checks → engineer-review(locked)
13
14
  ```
14
15
 
15
- `spec` and `tasks` are the two legs of `yad-spec` (the heavy ceremony, then the atomic `tasks.md`).
16
- `implement` is one atomic task via `yad-implement`. `checks` is `yad-checks (action: run)`.
17
- `engineer-review` is the human gate at `yad-engineer-review` — always a stop, never automated.
16
+ **These names are the engine's DEFAULTS, not a fixed wiring.** Which skill runs a step is the
17
+ project's setting in `.sdlc/skills.json`, so the loop reads `yad skill list --json` once at Step 0 and
18
+ runs every entry of that step's `skills`, in order, when it is bound to more than one. Unbound: `spec` and `tasks` are the two legs of `yad-spec` (the heavy ceremony, then the atomic
19
+ `tasks.md`), `implement` is one atomic task via `yad-implement`, and `checks` is
20
+ `yad-checks (action: run)`. `engineer-review` is the human gate at `yad-engineer-review` — always a
21
+ stop, never automated, and bindable like any other step.
18
22
 
19
23
  ## The loop (pseudocode)
20
24
 
21
25
  ```
22
26
  cfg = config.yaml.automation
23
- bs = build-state/<story>.json.repos[<repo>] # create from defaults if absent
27
+ bs = build-state/<story>.json.repos[<repo>] # create the FILE from defaults if absent; if the file
28
+ # exists but has no entry for <repo>, add one from the
29
+ # defaults — never remove an entry you did not add
30
+ if bs.status == "skipped": # E39: the whole lane was set aside with
31
+ STOP — report bs.record.reason # `yad skip <epic> <story> --repo <repo>`; do not drive it.
32
+ # `yad unskip <epic> <story> --repo <repo>` puts it back.
33
+ # Never write a single Build step as skipped or deferred.
24
34
  step = from or bs.currentStep
25
35
 
26
36
  while step is a Build step (not engineer-review):
27
- result = run_step_skill(step) # yad-spec | yad-implement | yad-checks
37
+ result = run_step_skill(step) # the skill(s) `yad next --json` names for this lane
28
38
 
29
39
  signals = derive_signals(step, result) # see "Deriving signals"
30
40
  verdict = derive_verdict(signals) # rejected | approved-with-edits | approved-unchanged
31
41
  uid = fresh_short_token() # e.g. `openssl rand -hex 4` — unique per run,
32
42
  # never reuse; a collision would fuse two runs
33
43
  write trust-log/<story>-<repo>-<step>-<uid>.json # ONE shard file = ONE entry (never append to a shared file)
34
- = { story, repo, step, uid, automation: bs.step.automation, verdict, signals, ranBy, date }
44
+ # `automation` is recorded in the OLD vocabulary (this is a history record, and `yad migrate`
45
+ # deliberately leaves it alone). Read the step's dial from EITHER spelling.
46
+ = { story, repo, step, uid, automation: bs.step.automation ?? old_name_for(bs.step.advance), verdict, signals, ranBy, date }
35
47
 
36
48
  eff = effective_dial(step, bs, cfg) # see "Effective dial"
37
49
 
38
50
  if result is a HALT (failed / scope overrun / contract touch / ambiguous):
39
- bs.step.status = "blocked"; persist; checkpoint; STOP and report the human action needed
40
- elif eff == "machine_advance":
41
- bs.step.status = "done"; advance bs.currentStep to next; persist; checkpoint; continue # Step B advance
42
- else: # human_approve
43
- bs.step.status = "done"; persist; checkpoint; STOP and report "waiting for human at <next>"
51
+ # `blocked` needs its `record` (E38) — that is what separates a halted lane from one that has
52
+ # simply not started. `reason` is the halt cause you already have in hand.
53
+ bs.step.status = "blocked"
54
+ bs.step.record = { reason: "<check FAIL | scope overrun | contract touch | ambiguous>",
55
+ by: "<platform login, else git user.name, or null>", date: "<YYYY-MM-DD>" }
56
+ persist; checkpoint; STOP and report the human action needed
57
+ elif eff == "auto":
58
+ bs.step.status = "done"
59
+ bs.step.closed = { by: "<platform login, else git user.name, or null>", date: "<YYYY-MM-DD>", via: "auto", run: uid }
60
+ advance bs.currentStep to next; persist; checkpoint; continue # Step B advance
61
+ else: # "human"
62
+ bs.step.status = "done"
63
+ bs.step.closed = { by: "<platform login, else git user.name, or null>", date: "<YYYY-MM-DD>", via: "human", run: uid }
64
+ persist; checkpoint; STOP and report "waiting for human at <next>"
44
65
 
45
66
  # reached engineer-review: always stop, hand to yad-engineer-review (human gate, finalizes the verdict)
46
67
  ```
47
68
 
69
+ `closed` is the step's **closing record** (E18): who wrote it, when, and how the lane moved past the
70
+ step. `via: "auto"` means the dial let the run go on by itself; `via: "human"` means it stopped for a
71
+ person. `run` is the `uid` of the trust-log shard this run wrote, so the record points at the evidence.
72
+ `by` is the login `gh api user` / `glab api user` reports, else git `user.name`, else `null`
73
+ (`YAD_PLATFORM_LOGIN=0` turns the lookup off).
74
+ Write it only when you write `done`, and never over a `closed` already on the step.
75
+
48
76
  `ranBy` is `machine` when the *previous* step's effective dial caused this step to run without a human
49
77
  nudge; otherwise `human`. Persist build-state after every transition so a halt leaves an accurate,
50
78
  resumable record.
@@ -56,24 +84,22 @@ way `git gc` folds loose objects later (`yad tidy up` folds finished shards into
56
84
  `trust-log.json` / `build-log.json`) — plus any story `status:` flip (→ in-build/shipped) once that
57
85
  story has a build-log ship (#112) — as one
58
86
  `chore(hub): sync Build state — <epic>/<story> by @<login> [skip ci]`
59
- audit-trail commit, on the default branch only,
87
+ audit-trail commit (`@<login>` when the platform login is known, else the git `user.name`, else `unknown`), on the default branch only,
60
88
  staging *only* those files by an explicit allowlist (never a Shape gate file — so `ledger-guard`
61
89
  never trips). It is idempotent (a no-op when nothing changed), so calling it after every transition —
62
90
  including a halt — is safe and keeps the shared trust evidence current for CI, teammates, and
63
91
  `yad status` on other machines. It refuses to run off the default branch (an unsigned `[skip ci]`
64
92
  commit inside a future PR range would fail `verified-commits` and strand the PR).
65
93
 
66
- ## Effective dial (kill switch & locks always win)
94
+ ## Effective dial (the kill switch and gates always win)
67
95
 
68
96
  ```
69
- eff = bs.step.automation # human_approve | machine_advance
70
- if cfg.kill_switch == true: eff = "human_approve"
71
- if bs.step.locked == true: eff = "human_approve"
72
- if step in cfg.locked_steps: eff = "human_approve"
97
+ eff = (yad dial <epic> <story> --repo <repo> <step> --json).advance # "human" | "auto"
73
98
  ```
74
99
 
75
- So a kill switch, a `locked` flag, or membership in `locked_steps` forces a stop no matter what the
76
- per-step dial says. `engineer-review` and the five Shape steps are covered by `locked` / `locked_steps`.
100
+ The engine reads the lane step's dial under either spelling (the OLD name wins), and answers `human`
101
+ when the step is a gate (`engineer-review`) or the kill switch in `.sdlc/automation.json` is on — whatever
102
+ the dial says. Its `why` says which: `lane`, `gate`, `kill` or `unknown` (a step id this release does not know), and `automationError` is present when the file cannot be read.
77
103
 
78
104
  ## Deriving signals & the provisional verdict
79
105
 
@@ -106,35 +132,31 @@ may also override it to `rejected` — e.g. the human discards/regenerates the s
106
132
  rejects the diff at the engineer review for a reason no signal captured. A human override always wins.
107
133
 
108
134
  Rationale: the trust log should count an output as fully trustworthy (`approved-unchanged`) only when
109
- the machine's work was accepted as-is. Any human correction is `approved-with-edits` (useful but not
110
- yet trustworthy enough to automate); any failure or boundary breach is `rejected`.
135
+ the machine's work was accepted as-is. Any human correction is `approved-with-edits` (useful, and a sign
136
+ the step still wants a person); any failure or boundary breach is `rejected`.
111
137
 
112
- ## The trust threshold (when a step is earned)
138
+ ## The run record as advice (E34)
113
139
 
114
- A step is a **candidate** for `machine_advance` only when its trust evidence clears
115
- `config.yaml` `automation.trust_threshold`. `set-dial` enforces this predicate before flipping a step
116
- to `machine_advance`:
140
+ `yad dial` prints a step's record when someone sets its dial, and whenever it is asked with no `--to`:
117
141
 
118
142
  ```
119
- # read the ledger by UNION: the folded trust-log.json `runs` array PLUS every loose trust-log/ shard
120
- # (concatenate — every shard is a distinct run; never dedup by story/repo/step, the threshold counts re-runs)
121
- all = trust-log.json.runs + [read each file in trust-log/]
122
- slice = entries in `all` for this step (this story's repo; widen to the project if you track it there)
143
+ all = trust-log.json.runs + [read each file in trust-log/] # UNION; every shard is a distinct run
144
+ slice = entries in `all` for this step in this repo
123
145
  runs = len(slice)
124
146
  unchanged = count(e.verdict == "approved-unchanged" in slice)
125
- earned = runs >= trust_threshold.min_runs
126
- AND (unchanged / runs) >= trust_threshold.min_approved_unchanged
127
147
  ```
128
148
 
129
- Defaults: `min_runs: 5`, `min_approved_unchanged: 0.8`. If `earned` is false, `set-dial` refuses and
130
- reports `runs`, the `unchanged/runs` fraction, and how far short of the bar it is.
149
+ It is **advice, never a rule.** There is no threshold and no refusal: the team decides whether a step
150
+ runs on auto, and the record is what they read to decide. A step with no runs says so — no evidence either
151
+ way is not evidence against.
131
152
 
132
- Reverting (`to: human_approve`) is never gated — automation must be reversible in one move.
153
+ Reverting (`--to human`) and the kill switch are always one move.
133
154
 
134
155
  ## What stays human, always
135
156
 
136
157
  - `engineer-review` — the merge gate. `yad-run` always stops here and hands to `yad-engineer-review`.
137
- - The five Shape steps (`epic`, `architecture`, `ui-design`, `stories`, `test-cases`) — not in
138
- `back_steps`, in `locked_steps`; the dial-setter refuses them.
158
+ - The Shape steps — this loop never drives them. A Shape author step's dial may be set to auto for the
159
+ project (`yad dial <step> --to auto`), and is only recorded until the engine runs agents (E26); every
160
+ Shape review gate is a person.
139
161
  - Any contract-surface change — halts the loop and routes back to the architecture gate, regardless of
140
162
  the dial.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yad-ship
3
- description: 'Build helper of the gated SDLC — commit AND open the task PR/MR in one step. A thin orchestration over yad-commit then yad-open-pr: commit the staged atomic change by the conventions (Conventional-Commits subject, Task → Contract-Change trailers, an OPTIONAL Co-Authored-By footer that is OFF by default and added only when --ai <id> is explicitly passed, ≤3-file atomic guard), then push the branch and open the PR/MR from the committed template with the roster auto-assigned. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the `yad ship` CLI; never merges. Use when the user says "ship this task", "commit and open the PR", or "commit and raise the MR". (For the engineer review + merge, use yad-engineer-review.)'
3
+ description: 'Build helper of the gated SDLC — commit AND open the task PR/MR in one step. A thin orchestration over yad-commit then yad-open-pr: commit the staged atomic change by the conventions (Conventional-Commits subject, Task → Contract-Change trailers, an OPTIONAL Co-Authored-By footer that is OFF by default and added only when --ai <id> is explicitly passed, ≤3-file atomic guard), then push the branch and open the PR/MR from the committed template, assigned to the logged-in gh/glab login with no reviewers requested. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the `yad ship` CLI; never merges. Use when the user says "ship this task", "commit and open the PR", or "commit and raise the MR". (For the engineer review + merge, use yad-engineer-review.)'
4
4
  ---
5
5
 
6
6
  # SDLC — Commit + Open PR/MR (Build helper)
@@ -19,12 +19,12 @@ its own and **never merges**. The engineer review + merge are Step E (`yad-engin
19
19
  `Task → Contract-Change → Co-Authored-By` (the footer **off by default** — added only via an explicit
20
20
  `--ai`), the ≤3-file atomic guard (`../yad-commit/SKILL.md`).
21
21
  - PR/MR: pushed branch, the committed template prefilled, title defaulting to the commit subject,
22
- roster auto-assign, risk routing (`../yad-open-pr/SKILL.md`).
22
+ the assignee set and no reviewers requested, risk routing (`../yad-open-pr/SKILL.md`).
23
23
  - **Order matters:** the PR/MR is opened **only if the commit lands**. A failed commit, a tripped
24
24
  atomic guard, or `--dry-run` stops the step before anything is pushed.
25
- - **Stage-aware on the product hub** (via `yad-open-pr`): on a `review/EP-*` branch `ship` opens the
25
+ - **Stage-aware on the Product** (via `yad-open-pr`): on a `review/EP-*` branch `ship` opens the
26
26
  Shape **artifact-review** PR (delegating to `yad gate open` — `--title` is ignored); on any
27
- other hub branch it opens the **code-task** PR from the bundled code-task template. In a code repo
27
+ other Product branch it opens the **code-task** PR from the bundled code-task template. In a code repo
28
28
  it is unchanged.
29
29
 
30
30
  ## Inputs
@@ -33,11 +33,11 @@ its own and **never merges**. The engineer review + merge are Step E (`yad-engin
33
33
  - `ai` — co-author footer: `claude|copilot|cursor|coderabbit|none` (default `none` = **no
34
34
  footer**; the `Co-Authored-By` trailer appears only when this flag names a tool).
35
35
  - `task` — Task trailer (optional; derived from the branch when omitted).
36
- - `contractChange` — flag; marks the contract surface touched (commit trailer + PR escalation).
36
+ - `contractChange` — flag; marks the contract surface touched (commit trailer + a raised approver count on the PR).
37
37
  - `repo` / `risk` / `platform` / `title` — PR/MR options (see `yad-open-pr`).
38
38
  - `base` — override the PR/MR base. The default is the repo's own default branch, resolved
39
39
  by the full chain in **Base branch** (`yad-open-pr`) — the canonical description, including the
40
- hub rung — never a hardcoded `main`. A non-default base loses the AI first pass; `ship` warns and
40
+ Product rung — never a hardcoded `main`. A non-default base loses the AI first pass; `ship` warns and
41
41
  still opens.
42
42
 
43
43
  ## On Activation
@@ -53,12 +53,16 @@ yad ship --type <type> -m "<subject>" [--ai <id>] [--task <id>] [--contract-chan
53
53
  [--repo <name>] [--risk <level>] [--title "<subject>"]
54
54
  ```
55
55
  The CLI runs `yad commit` and, only if it succeeds, `yad open-pr` — committing the change, pushing the
56
- branch, and opening the PR/MR with the template prefilled and reviewers auto-assigned.
56
+ branch, and opening the PR/MR with the template prefilled and the assignee set. No reviewers are
57
+ requested: it prints `no reviewers were requested — ask them on the PR itself`.
57
58
 
58
59
  ### Step 3 — Route + stop (no merge)
59
- On `high` risk or a contract touch, run `bash checks/risk-route.sh <pr-body>` for the required
60
- domain-owner reviewers. Report the commit + the PR/MR URL. The PR now runs the check gates (Step C);
61
- the engineer review and merge are Step E (`yad-engineer-review`).
60
+ The open step prints how many approvers the PR asks for — the body's level and the base branch's risk
61
+ map, the larger step winning (E66). When it asks for more than one, `bash checks/risk-route.sh "<pr body>"`
62
+ lists the touched domains to ask; ask reviewers on the PR/MR itself. It also prints a reviewer
63
+ **suggestion** (E68): who has committed in the touched folders in the last 30 days, and what CODEOWNERS
64
+ lists — a hint about who may know the code, never a request and never a list of owners. Report the commit + the PR/MR URL. The PR now
65
+ runs the check gates (Step C); the engineer review and merge are Step E (`yad-engineer-review`).
62
66
 
63
67
  ## Hard rules
64
68
 
@@ -67,7 +71,7 @@ the engineer review and merge are Step E (`yad-engineer-review`).
67
71
  - **No AI footer by default.** The wrapped commit writes a `Co-Authored-By` trailer ONLY when `--ai <id>`
68
72
  is explicitly passed; never add it on the AI's own initiative.
69
73
  - **No PR without a landed commit.** A failed/`--dry-run` commit stops the step before pushing.
70
- - **High risk routes to domain owners** — the same escalation as the gate.
74
+ - **High risk raises the approver count** — the same count as the gate.
71
75
  - **Shipping here never merges.** The human owns the merge in `yad-engineer-review`.
72
76
 
73
77
  ## Reference
@@ -30,7 +30,7 @@ is the same graceful-degradation pattern `yad-ui` uses for Impeccable.
30
30
  `specs/<story-id>/`.
31
31
  - Spec Kit output layout (RESEARCH-NOTES §2): `specs/<feature-id>/spec.md` (+ `research.md`,
32
32
  `data-model.md`, `contracts/`), `plan.md`, `tasks.md`; constitution at `.specify/memory/constitution.md`.
33
- - Speak in the configured `communication_language`; write documents in `document_output_language`.
33
+ - Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.
34
34
 
35
35
  ## Inputs
36
36
 
@@ -59,7 +59,20 @@ Read `epics/<epic>/contract.md` and `epics/<epic>/.sdlc/contract-lock.json`. The
59
59
  inputs (the API shapes, data-model entities, and events the story touches) are **quoted from the
60
60
  locked surface**, not re-authored. The spec MUST stay within that surface. If the story needs surface
61
61
  the contract does not define, STOP and route back to the **architecture gate** — never extend the
62
- contract here.
62
+ contract here. (On a route that HAS one; the short lanes do not — see below.)
63
+
64
+ - **Short-lane-safe:** read `epics/<epic>/.sdlc/state.json`'s `profile` first (an epic seeded before
65
+ file shape 6 has no such key — fall back to what its `steps[]` contains). On the `chore` and
66
+ `spike` routes there is no architecture step, so `contract.md` and `contract-lock.json` **never
67
+ exist** — that is the route's design, not a missing file to chase. Write the spec from the story and
68
+ `epic.md` alone, record `contract-lock: none` in Step 6's frontmatter, and say in the spec that this
69
+ epic defines no contract surface.
70
+ - A short lane has no architecture gate to route back to. So the rule above becomes stricter, not
71
+ looser: if the story turns out to need the shared cross-repo surface to CHANGE, **STOP and escalate
72
+ to a human** — the work belongs to a new epic on `classic`. Do not write the change into a spec on a
73
+ short lane. Do not create `specs/<story>/contracts/` at all on one — a short-lane story has no
74
+ surface to quote, and a slice folder under a lockless epic is what the CI gate refuses. (If one
75
+ already exists, a commit that only DELETES it is allowed through, which is the way back out.)
63
76
 
64
77
  ### Step 4 — Detect Spec Kit
65
78
  Check for `/speckit.*` slash-commands and/or `demo-repos/<repo>/.specify/`. Record the result for
@@ -83,7 +96,9 @@ to read. See `references/spec-handoff.md` for the exact file map and per-file de
83
96
  Write `demo-repos/<repo>/specs/<story>/link.md` (template in `references/spec-handoff.md`) with
84
97
  frontmatter linking the spec back to the product repo: `story`, `epic`, `repo`, `feature-id`,
85
98
  `product-repo` (path), `contract-lock` (the hash **copied** from `contract-lock.json`, NOT recomputed
86
- in the code repo), `speckit` (`installed | not-installed`), `generated` (date). This `link.md` plus the
99
+ in the code repo — or the literal `none` on a short-lane epic, which has no lock; never a made-up or
100
+ empty hash, which the CI gate reads as a broken lock and fails), `speckit`
101
+ (`installed | not-installed`), `generated` (date). This `link.md` plus the
87
102
  spec folder is the authoritative record that this story's spec exists.
88
103
 
89
104
  ### Step 7 — Stop (Shape state untouched)
@@ -93,7 +108,7 @@ Report: the spec folder path, the files written, whether Spec Kit was used, the
93
108
 
94
109
  ### Step 8 — Record the `spec` trust signal (Phase 4b)
95
110
  When this step runs under the orchestrator (`yad-run`), the generated spec is a Build run that the
96
- trust log measures (it is the evidence that could later earn the `spec` step a `machine_advance`). The
111
+ trust log measures (part of the run record `yad dial` shows beside the `spec` dial). The
97
112
  verdict is **anchored to the human who accepts the spec**, never self-graded:
98
113
  - the human approves the generated `specs/<story>/` untouched → `approved-unchanged`;
99
114
  - the human edits the spec/plan/tasks before accepting → `approved-with-edits` (signal
@@ -104,8 +119,8 @@ pattern as the engineer review finalizing `implement` at `yad-engineer-review`).
104
119
  entry in its own shard `epics/<epic>/.sdlc/trust-log/<story>-<repo>-spec-<uid>.json` (a fresh `uid` per
105
120
  run; readers union the folded `trust-log.json` + the loose shards). Schema:
106
121
  `../yad-epic/references/state-schema.md`. **Run standalone, no trust entry is written** — the
107
- log measures orchestrated runs. `spec` stays `human_approve` until its slice clears the threshold;
108
- this step only *gathers* the evidence, it does not flip the dial.
122
+ log measures orchestrated runs. The record is advice: the `spec` dial is
123
+ whatever the team sets with `yad dial`, and this step never changes it.
109
124
 
110
125
  ## Hard rules (build plan §A, Cross-cutting)
111
126
 
@@ -33,6 +33,7 @@ demo-repos/<repo>/specs/<story-id>/
33
33
  research.md # decisions/unknowns resolved during clarify
34
34
  data-model.md # entities/fields THIS repo implements (quoting the shared ones from the contract)
35
35
  contracts/ # the API/event surface this repo implements (quoted from the locked contract)
36
+ # OMITTED ENTIRELY on a short-lane epic — see below
36
37
  plan.md # technical approach for this repo
37
38
  tasks.md # numbered atomic tasks (T01…), each scoped to the files it may touch
38
39
  link.md # back-pointer to the story in the product repo (Step A adds this; not a Spec Kit file)
@@ -54,6 +55,11 @@ comes from the story's acceptance criteria and the **locked contract surface**
54
55
  - **`contracts/`** — the slice of the API/event surface this repo implements, **quoted from the locked
55
56
  `contract.md`** (e.g. `POST /orders` request/response). Add a one-line note that this is a quote of
56
57
  the locked surface, not a new definition.
58
+ **Do NOT create this folder on a short-lane epic** (`profile: chore` or `spike`). Those routes have
59
+ no architecture step, so there is no locked `contract.md` to quote and no lock for CI to check the
60
+ quote against — the gate refuses a slice under a lockless epic. A short-lane story consumes the
61
+ shared surface through the code it calls; it never restates it. If one already exists, a commit
62
+ that only DELETES it is allowed through, which is the way back out.
57
63
  - **`plan.md`** — the technical approach for this repo at story altitude (components, sequence,
58
64
  test approach). No new cross-repo surface.
59
65
  - **`tasks.md`** — numbered atomic tasks. Each task: an ID (`T01`, `T02`, …), a one-line goal, and an
@@ -69,7 +75,9 @@ epic: EP-<slug>
69
75
  repo: <repo>
70
76
  feature-id: EP-<slug>-S0N
71
77
  product-repo: <path to the product repo — absolute, or relative to THIS file's dir (specs/<story>/)>
72
- contract-lock: sha256:<hex copied from epics/EP-<slug>/.sdlc/contract-lock.json>
78
+ contract-lock: sha256:<hex copied from epics/EP-<slug>/.sdlc/contract-lock.json> # or the literal
79
+ # `none` on a short-lane epic, which has no lock. Never an empty or invented hash:
80
+ # CI reads an unparseable value as a broken lock and fails.
73
81
  speckit: installed | not-installed
74
82
  generated: <YYYY-MM-DD>
75
83
  ---
@@ -89,12 +97,16 @@ back to the architecture gate in the product repo — it is never widened from t
89
97
  `product-repo` is the one field CI resolves on disk (contract-check, lineage-check, epic-open,
90
98
  reconcile-debt all read it). Every gate resolves it the SAME way: an **absolute** path is used as-is; a
91
99
  **relative** path is joined to this `link.md`'s own directory, `specs/<story>/` — `../../` climbs out
92
- of the story dir and out of `specs/` to the code-repo root, so a hub checked out beside the code repo
93
- is `../../../<hub-dir>`. A relative value that only resolves from the **repo root** still works too
100
+ of the story dir and out of `specs/` to the code-repo root, so a Product checked out beside the code repo
101
+ is `../../../<product-dir>`. A relative value that only resolves from the **repo root** still works too
94
102
  (what contract-check historically did), so older `link.md` files keep gating. Write it as an
95
103
  **unquoted scalar** — `"..."` or `'...'` is taken literally, and a leading `~`/`$VAR` is never
96
- expanded. An unreachable path degrades the hub-reading gates to a PASS-with-note rather than failing
97
- them, and each one now says so in its output.
104
+ expanded. An unreachable path degrades the Product-reading gates to a PASS-with-note rather than failing
105
+ them, and each one now says so in its output. **Reachable-but-lockless is a different case and does
106
+ NOT degrade:** when the Product resolves and `epics/<epic>/.sdlc/` is there with no
107
+ `contract-lock.json` in it, a claimed `Contract-Change: yes` FAILS, because nothing exists for the
108
+ claim to be true of. That is the ordinary state of a short-lane epic, of a stub, and of a classic epic
109
+ that has not reached its architecture gate yet.
98
110
 
99
111
  ## Do not re-invent the contract
100
112
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yad-status
3
- description: 'Read-only view of an SDLC epic: prints the current step, each step''s dials (assistance/automation) and status, and which approvals are still required at the active gate. For stories in Build it also prints each Build step''s automation dial, status, and trust record (runs / % approved-unchanged / whether it clears the threshold to be earned), plus the system-wide kill-switch state — so the team can see WHY a step is automated and reverse it with evidence. Also prints the cross-cutting personal skills-log roll-up from the LOCAL-ONLY learning ledger (gitignored, never committed/pushed — the local learner''s own learning, by stage). Surfaces the Phase 5 instrumentation signals: per-step "earned but manual" (nudge cost) and, across multiple epics, a fleet roll-up (scale of read). Use when the user says "yad status", "where is epic EP-...", "what is blocking the gate", "show the trust record", "team skills", or "fleet status".'
3
+ description: 'Read-only view of an SDLC epic: prints the current step, each step''s dials (driver/advance, under either spelling) and status, and which approvals are still required at the active gate. For stories in Build it also prints each Build step''s automation dial, status, and run record (runs / % approved-unchanged, as advice), plus the kill switch and the Shape author dials from .sdlc/automation.json — so the team can see WHY a step is automated and reverse it. Also prints the cross-cutting personal skills-log roll-up from the LOCAL-ONLY learning ledger (gitignored, never committed/pushed — the local learner''s own learning, by stage). Across multiple epics, prints a fleet roll-up (scale of read). Use when the user says "yad status", "where is epic EP-...", "what is blocking the gate", "show the trust record", "team skills", or "fleet status".'
4
4
  ---
5
5
 
6
6
  # SDLC — Status (read-only)
@@ -10,7 +10,8 @@ reads `.sdlc/` and `reviews/` and reports.
10
10
 
11
11
  ## Conventions
12
12
  - `{project-root}` resolves from the project working directory.
13
- - Operate on one epic: `{project-root}/epics/EP-<slug>/`.
13
+ - Operate on one epic: `{project-root}/epics/EP-<slug>/`. The Product level is the exception:
14
+ `EP-foundation` lives at `{project-root}/foundation/` (its `.sdlc/` and `reviews/` are there).
14
15
 
15
16
  ## On Activation
16
17
 
@@ -27,7 +28,9 @@ distinct run; never dedup by story/repo/step — but DO skip a shard whose full
27
28
  `(story,repo,step,uid)` already appears in the folded `runs`, i.e. a half-applied `yad tidy up`). All are committed by `yad checkpoint` (so a fresh clone
28
29
  or another machine sees current evidence; `yad tidy up` folds finished shards into `trust-log.json`).
29
30
  Also read the `automation` block of
30
- `skills/sdlc/config.yaml` (`back_steps`, `trust_threshold`, `locked_steps`, `kill_switch`). For the
31
+ `.sdlc/config.yaml` (`back_steps`), and `.sdlc/automation.json` if present — the kill switch and the
32
+ Shape author steps set to auto (absent = the switch off, every Shape step human). The effective dial of any
33
+ step is `yad dial … --json`, which applies the kill switch and gates for you. For the
31
34
  cross-cutting learning layer, also read — if present — the **local-only** `.sdlc/learning-records.json`
32
35
  (the per-epic learning ledger, gitignored) and the project-wide `{project-root}/.sdlc/learning-records.json`.
33
36
  Do not modify any of them.
@@ -35,45 +38,115 @@ Do not modify any of them.
35
38
  ### Step 3 — Report
36
39
  Print, in this order:
37
40
 
38
- 1. **Header:** render the kind noun from `epic.md` frontmatter `kind` — **Change request** (`change`),
39
- **Defect** (`defect`), **Hotfix** (`hotfix`), or **Epic** (`feature`, and the default when `kind` is
40
- absent) — followed by `epicId`, then `status` from `epic.md` frontmatter, `currentStep`, and `repos`
41
+ 1. **Header:** render the type noun from the `epic.md` frontmatter work-item type. Two names carry it,
42
+ `kind:` and `type:`; read `kind:` first, then `type:`. **Change request** (`change`),
43
+ **Defect** (`defect`), **Hotfix** (`hotfix`), **Chore** (`chore`), or **Epic** (`feature`, and the default when neither is
44
+ present) — followed by `epicId`, then `status` from `epic.md` frontmatter, `currentStep`, and `repos`
41
45
  (the touched domains). Example: `Defect EP-checkout-queue-filter — draft @ stories`. A bug is a defect
42
- (`kind: defect`) — there is no separate noun. This is presentation only; the artifact is still an epic.
43
- 2. **Steps table** — for every Shape step in `steps[]` order (10, or 12 when the optional analysis step
44
- was run): `id`, `type`, `status`, `assistance`, `automation`, `locked`, and `risk_tags`. Mark the
45
- `currentStep` with `→`. The gating chain is `[analysis → analysis-review →] epic → epic-review →
46
- architecture → architecture-review → ui-design → ui-design-review → stories → stories-review` →
47
- **`ready-for-build`** (the bracketed `analysis` prefix is present only when `yad-analysis` seeded it).
46
+ (type `defect`) — there is no separate noun. This is presentation only; the artifact is still an epic.
47
+ 2. **Steps table** — for every Shape step in `steps[]` order, however many there are: `id`, `type`,
48
+ `status`, the two dials (`driver`/`assistance` and `advance`/`automation` — read whichever the step carries, the OLD name wins), `locked`, and `risk_tags`. Mark the
49
+ `currentStep` with `→`. **The chain length is a fact about the epic's ROUTE, never a constant** —
50
+ read `profile` from `state.json`: `classic` is 10 steps, `analysis-first` 12, and the short lanes
51
+ `chore` 4 and `spike` 6 (E40). On `classic` the gating chain is `[analysis → analysis-review →]
52
+ epic → epic-review → architecture → architecture-review → ui-design → ui-design-review → stories →
53
+ stories-review` → **`ready-for-build`** (the bracketed `analysis` prefix is not on `classic`: it is
54
+ what makes `analysis-first`, the route `yad-analysis` seeds). A short lane runs `epic → epic-review → stories → stories-review` →
55
+ **`ready-for-build`**, with `analysis` in front on `spike`, and has no architecture, UI-design or
56
+ test-case rows at all — render what `steps[]` holds and never a step it lacks.
48
57
  `test-cases → test-cases-review` is a **parallel, non-blocking track**: it opens when `stories-review`
49
58
  passes and runs alongside Build, so when `currentStep` is `ready-for-build` the `test-cases`
50
59
  step may still be `in_progress`/`in_review` — show its status, and note "parallel" so it is clear it
51
60
  does not gate the build. Always render exactly the steps present in `steps[]`.
52
- - **Skipped (N/A) steps:** the optional `ui-design` step may be marked N/A for an epic with no
53
- user-facing surface. **Both** the `ui-design` author step **and** its `ui-design-review` gate stay
54
- in `steps[]`, each carrying `skipped: true`, `status: "done"`, and a `skipReason`. Render **each**
55
- as `<id> — SKIPPED (N/A: <skipReason>)` (with `skippedBy`/`skippedAt` if present) instead of the
56
- plain status, so the deliberate skip and its reason are visible and the gate does not appear to
57
- vanish. The chain then reads `… architecture-review → ui-design (N/A) → ui-design-review (N/A) →
61
+ - **Skipped (N/A) steps:** a step the epic's route marks optional — `ui-design`, on the routes that
62
+ ship — may be marked N/A with `yad skip`. **Both** the author step **and** its `-review` gate stay
63
+ in `steps[]`, each with `status: "skipped"` and a `record` (`{ reason, by, date }`). A file from
64
+ before shape 7 spells the same thing `status: "done"` with `skipped: true` and a `skipReason`
65
+ beside it; read either. Render **each** as `<id> — SKIPPED (N/A: <reason>)` (with who and when if
66
+ present) instead of the plain status, so the deliberate skip and its reason are visible and the
67
+ gate does not appear to vanish. The chain then reads `… architecture-review → ui-design (N/A) → ui-design-review (N/A) →
58
68
  stories → …`; the review gate never needs approvals.
69
+ - **Deferred steps:** `yad defer` sets an optional step aside to come back to later. Both the author
70
+ step and its `-review` gate carry `status: "deferred"` and a `record`. Render each as
71
+ `<id> — DEFERRED (later: <reason>)`, with who and when if present. A deferred step is still owed:
72
+ never render it as done or as N/A.
73
+ - **Debt:** a step carrying `"debt": true` (written by `yad defer … --debt`) is owed back and must be
74
+ reminded until paid. Add `· DEBT` to its line, and list every debt in a short **Owed** block under
75
+ the chain with the command that pays it: `yad undefer <epic> <id>` while it is still `deferred` (on a verified Product,
76
+ say that verb is refused once the ledger is on the default branch), or
77
+ "being paid back — clears when `<id>-review` passes" once it is back in progress. The flag is
78
+ removed only when that review passes. `yad doctor` reports the same list as `step:debt`.
79
+ - **Closed steps:** a `done` step may carry `closed` (E18) — `{ by, date, via, pr?, … }`. Add
80
+ `(closed <date>, via <via>, PR #<pr>)` to its line when it is there, and nothing when it is not: a
81
+ step closed before this release has none, and that is not a fault.
82
+ - **Re-opened steps:** an unfinished step with a later step `done` (not its own `-review` gate) was
83
+ put back after the chain built past it — a late `yad undefer`. Render it as
84
+ `<id> — RE-OPENED (beside finished work)`: it does not block the finished steps after it, and
85
+ `currentStep` stays where it is. `yad next <epic>` prints it as a `re-opened lane`.
86
+ - **Blocked steps:** a step with `status: "blocked"` **and** a `record` is waiting on someone outside
87
+ the workflow. Render it as `<id> — BLOCKED (<reason>)`, with who recorded it and when, and say it is
88
+ cleared with `yad unblock <epic> <id>` once the wait is over — on a verified Product that verb refuses
89
+ once the ledger is on the default branch, so say so. A `blocked` with no record is the older
90
+ word for `todo`: render it as not started.
91
+ - **Owned steps:** `yad assign` (E47) gives an authoring step to one person, in
92
+ `<epic>/.sdlc/owners/<step>.json` (`{ step, owner, name, assignedBy, date }`). Add `· owner: <name>`
93
+ to the author step's line **and** its `-review` line while it is live — the author step, or its
94
+ review, not yet passed (`done`, `skipped`, `deferred`, `satisfied`). A passed step's file is a record:
95
+ say nothing. It is advice, not a lock, and never changes what the gate needs. A file that is not valid
96
+ JSON, or whose `step` is not its own file name, is not an owner: name it and point to `yad owners`.
97
+ `yad owners [<epic>] --json` gives the same list ready-made.
59
98
  3. **Active gate** — for the `currentStep` (if it is a `review+approve` step), compute and show:
60
- - the reviewer rule in force — **base** (`owner + 1 reviewer`), **escalated** (list the required
61
- domains), or **per-repo** for `stories-review` (list each repo needing sign-off),
62
- - approvals **recorded so far** (from `approvals.json`), and
63
- - approvals **still required** to pass the gate (name the missing domains/repos).
99
+ - the count in force — **distinct approvers** recorded, against the **base** (1, enforced) and the
100
+ **full count** from the step's risk tags (advisory),
101
+ - approvals **recorded so far** (from `approvals.json`, one line per approver), and
102
+ - approvals **still required** to pass the gate (`1 approval(s)`, or none).
64
103
  Do not advance — just state whether the gate would pass right now.
65
104
 
66
105
  Apply the same predicate `yad-review-gate` uses (restated here so this skill is
67
106
  self-contained). From the `approved` records in `approvals.json` for the current step:
68
- - `owners` = records with `role == "owner"`; `reviewers` = distinct `role == "reviewer"`;
69
- `domainOwners` = `role == "domain-owner"`, grouped by `domain`.
70
- - **Base pass:** `|owners| >= 1` AND `|reviewers| >= 1` (the configured `default_reviewers`).
71
- - **Escalated pass** (step `risk_tags` ∩ `{contract, auth, payments}` ≠ ∅): base pass AND, for
72
- every touched domain, `|domainOwners[domain]| >= 1`. Touched domains = `epic.repos` for
73
- `architecture-review`; the union of every story's `repos` for `stories-review`.
74
- - Approvals are **stale** (gate fails) if the artifact was edited after the newest `approved`
75
- record. For `architecture-review`, also flag staleness if the contract-surface hash no longer
76
- matches `.sdlc/contract-lock.json`.
107
+ - `approvers` = the distinct, non-empty `approver` values of the approvals that are not stale (below).
108
+ A record that names nobody counts as nobody. When `requireEngagement` is on in the Product config,
109
+ only approvals with `engagement: verified` count. There are no roles; a `role` or `domain` an older
110
+ record carries is legacy data, and it counts for nothing.
111
+ - **Pass (team mode):** `|approvers| >= 1` — the base — plus, with a platform, resolved review threads and a merged
112
+ review PR/MR. Solo mode waives approvals entirely.
113
+ - **The full count, reported on every step that is not inherited, skipped or deferred:** `needed = base 1 + risk step` — plus `2` when the
114
+ step's `risk_tags` carry `contract`, or `1` when they carry `auth`/`payments` (the highest tag,
115
+ never the sum). The engine **caps** that ask at `active − 1` (floor 1) and prints it (E72) — for
116
+ example a contract gate asks 3, and with 2 active people the capped ask is 1. The risk step is
117
+ ADVISORY, capped or not — it never decides whether the gate would pass, because the count
118
+ of people can read high (a git name and a platform login are two people until proven one). When the
119
+ people could not be counted, no cap is shown. `yad gate status` prints the sum and the shortfall
120
+ (`; count: 3 approvers = base 1 + contract risk 2 — capped to 1: 2 active people, less one seat
121
+ for the author — base enforced, risk step advisory — 1 short`, or without the `capped to …` part
122
+ when the cap lowered nothing) — read it from there rather than recomputing it, and say "short N"
123
+ rather than "blocked" when it is short. A closed team gate whose ask was lowered says
124
+ `count capped from 3 to 1 (2 active people)`. In solo mode it prints no shortfall.
125
+ - **A gate that may not pass (E73):** under a team gate that has not passed, `yad gate status` can
126
+ print a warning line. Relay it as stated, as a warning and never as "blocked": it holds nothing.
127
+ A line starting `! may not be met:` means there is no approval in the counting window and the one
128
+ required approval may have nobody but the author to give it; another person's approval settles it.
129
+ A line starting `! if the risk step were enforced:` is a what-if: the extra approvals that risk
130
+ tags add could not all be given by the people counted. The gate can still pass today, and the line
131
+ says so.
132
+ - **How many people there are to ask (E71), printed once per epic and not per step:** `active people:
133
+ 4 in the last 90 days — caps each gate's count at 3 approvers (one seat is left for the author);
134
+ reported, only the base is enforced`, with the window's basis on the line under it. It is
135
+ the count the cap (E72) uses, so never report a step as blocked by it. A source that could not be
136
+ read prints `active people: NOT COUNTED — <which source> — no cap can be shown, and only the base
137
+ holds each gate`: report that wording as it stands and NEVER as a number or as "no people" — an
138
+ unreadable input is not a small team.
139
+ - **Solo mode may be wrong (E74):** in solo mode, under the `active people:` line, `yad gate status`
140
+ can print `! solo mode is on, but … so more than one person may work on this Product`. Relay it as
141
+ a suggestion to run `yad mode team`, never as a fault and never as switched: nothing changes until
142
+ a person runs it. It never suggests solo mode.
143
+ - Touched domains (`epic.repos` for a step with a risk tag; the union of every story's `repos` for
144
+ `stories-review`) only label the review. They add no approvals.
145
+ - An approval is **stale** (it no longer counts) when the `artifactHash` it recorded no longer
146
+ matches the artifact's current fingerprint — the content changed after that person approved. For
147
+ `architecture-review` the fingerprint is the contract surface in `contract.md`. An approval with no
148
+ `artifactHash` is never stale. `yad gate status` prints the stale count. For `architecture-review`,
149
+ also flag when the contract-surface hash no longer matches `.sdlc/contract-lock.json`.
77
150
  4. **Contract lock** — if `.sdlc/contract-lock.json` exists, show the locked hash and `lockedAt`
78
151
  (and, when at/after `architecture-review`, whether the current surface still matches it).
79
152
  5. **Stories** — if `stories/` has files, list each story `id` and its `repos` tags.
@@ -81,26 +154,18 @@ Print, in this order:
81
154
  7. **Build (per story, per repo)** — if any `.sdlc/build-state/<story-id>.json` exists, then for
82
155
  each such story and each of its repos print the Build chain
83
156
  `spec → tasks → implement → checks → engineer-review`, marking each step's `status`, its
84
- `automation` dial, and `locked`. Mark that repo's `currentStep` with `→`. This shows, at a glance,
157
+ `advance` dial (or `automation`, whichever the step carries), and `locked`. Mark that repo's `currentStep` with `→`. A repo whose lane is `status: skipped` (a whole lane set aside with `yad skip <epic> <story> --repo <repo>`, E39) prints `skipped (N/A)` and its `record.reason` instead of the chain. This shows, at a glance,
85
158
  which Build steps are automated and where a run is waiting. (For the single *next* build sub-step to
86
159
  take per story/repo — rather than this full status view — point the user at `yad next <epic>`, which
87
160
  reads the same `build-state` files.)
88
- 8. **Automation & trust** — print the system-wide **kill switch** state from `config.yaml`
89
- `automation.kill_switch` (when `on`, note that every step is forced to `human_approve`). Then, for
90
- each Build step that has entries in the trust ledger — the **union** of the folded
91
- `.sdlc/trust-log.json` `runs` plus every loose `.sdlc/trust-log/` shard — print its **trust record**:
92
- number of runs, the fraction with `verdict == "approved-unchanged"`, and whether that clears
93
- `automation.trust_threshold` (`min_runs`, `min_approved_unchanged`) — i.e. whether the step is
94
- **earned** (eligible to be flipped to `machine_advance`) or still **gathering evidence**. Restate
95
- the predicate (self-contained): `earned = runs >= min_runs AND unchanged/runs >= min_approved_unchanged`.
96
- Never recommend flipping a locked step or a Shape step — those can never be `machine_advance`.
97
-
98
- **Nudge-cost signal (Phase 5 instrumentation).** For each Build step that is **earned but its dial
99
- is still `human_approve`** (and it is not locked / not a Shape step), flag it:
100
- `⚠ earned but manual — could be machine_advance`. This is the *nudge cost* the Phase 5 trigger
101
- watches: automation that is proven safe but still hand-started. It is a read-only observation, not a
102
- recommendation to flip — earning the evidence and flipping the dial stay deliberate human acts
103
- (`yad-run action: set-dial`). See `docs/phase-5-build-plan.md` §"What to instrument now".
161
+ 8. **Automation & run record** — print the **kill switch** from `.sdlc/automation.json` (when on, who
162
+ turned it on, when and why, and that every step is held at `advance: human`), and the Shape author
163
+ steps the project set to `auto` — noting that a Shape `auto` is recorded only: nothing drives a Shape
164
+ step on its own yet. Then, for each Build step that has entries in the trust ledger — the **union** of
165
+ the folded `.sdlc/trust-log.json` `runs` plus every loose `.sdlc/trust-log/` shard — print its **run
166
+ record**: number of runs and the fraction with `verdict == "approved-unchanged"`. It is **advice**:
167
+ there is no threshold and nothing is "earned" (E34). Do not recommend flipping a dial; the team sets
168
+ dials with `yad dial`. A review gate is never auto.
104
169
 
105
170
  9. **My skills (the learning layer — local-only).** If `.sdlc/learning-records.json` exists for the epic
106
171
  (or the project-wide ledger does), print the **personal skills-log** roll-up from it — read-only. These
@@ -118,9 +183,9 @@ Print, in this order:
118
183
  10. **Fleet roll-up (overview only).** When the user asked for an overview, or more than one epic exists
119
184
  under `{project-root}/epics/`, print a one-line-per-epic roll-up across the fleet: each epic's
120
185
  `currentStep` (Shape gate) and, for stories in Build, a count of Build steps **waiting
121
- at a human gate** and of steps flagged **earned-but-manual**, plus a **local skills-log** count (records
186
+ at a human gate**, plus a **local skills-log** count (records
122
187
  in the local-only `learning-records.json`: learned / in-progress). Close with fleet totals (epics at
123
- each Shape gate; total earned-but-manual Build steps; total concepts learned locally across the fleet).
188
+ each Shape gate; total concepts learned locally across the fleet).
124
189
  This is the
125
190
  *scale-of-read* signal the Phase 5 trigger watches — when this roll-up stops fitting in one glance,
126
191
  that is the measured bottleneck. Still strictly read-only; it only scans the per-epic files.