yadflow 3.18.1 → 3.19.0-next.2

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 (104) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +11 -11
  3. package/bin/yad.mjs +8 -8
  4. package/cli/artifact-status.mjs +4 -4
  5. package/cli/checkpoint.mjs +25 -25
  6. package/cli/commit.mjs +1 -1
  7. package/cli/companion.mjs +2 -2
  8. package/cli/doctor.mjs +10 -10
  9. package/cli/epic-state.mjs +29 -29
  10. package/cli/errors.mjs +1 -1
  11. package/cli/gate.mjs +32 -33
  12. package/cli/hook.mjs +4 -4
  13. package/cli/hubcommit.mjs +1 -1
  14. package/cli/ledger.mjs +3 -3
  15. package/cli/lib.mjs +23 -9
  16. package/cli/manifest.mjs +42 -21
  17. package/cli/migrate.mjs +54 -12
  18. package/cli/next.mjs +5 -5
  19. package/cli/openpr.mjs +8 -8
  20. package/cli/plan.mjs +28 -9
  21. package/cli/platform.mjs +1 -1
  22. package/cli/report.mjs +1 -1
  23. package/cli/review.mjs +5 -5
  24. package/cli/setup.mjs +22 -10
  25. package/cli/ship.mjs +1 -1
  26. package/cli/skip.mjs +1 -1
  27. package/cli/thread.mjs +1 -1
  28. package/cli/tidy.mjs +2 -2
  29. package/cli/update-commit.mjs +1 -1
  30. package/package.json +1 -1
  31. package/skills/sdlc/config.yaml +30 -30
  32. package/skills/sdlc/module-help.csv +21 -21
  33. package/skills/yad-analysis/SKILL.md +10 -10
  34. package/skills/yad-architecture/SKILL.md +10 -10
  35. package/skills/yad-architecture/references/contract-format.md +2 -3
  36. package/skills/yad-backfill/SKILL.md +5 -5
  37. package/skills/yad-change/SKILL.md +13 -13
  38. package/skills/yad-change/references/triage.md +2 -3
  39. package/skills/yad-checks/SKILL.md +34 -16
  40. package/skills/yad-checks/references/check-gates.md +63 -19
  41. package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
  42. package/skills/yad-checks/templates/checks/epic-open.sh +1 -1
  43. package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
  44. package/skills/yad-checks/templates/checks/ledger-guard.sh +41 -12
  45. package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
  46. package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +3 -3
  47. package/skills/yad-checks/templates/github/yad-checks.yml +24 -3
  48. package/skills/yad-checks/templates/github/yad-hub-checks.yml +2 -2
  49. package/skills/yad-checks/templates/github/yad-verified-commits.yml +1 -1
  50. package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
  51. package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +12 -3
  52. package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +2 -2
  53. package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +1 -1
  54. package/skills/yad-checks/templates/hooks/ledger-guard.sh +1 -1
  55. package/skills/yad-commit/SKILL.md +2 -2
  56. package/skills/yad-connect-design/SKILL.md +1 -1
  57. package/skills/yad-connect-docs/SKILL.md +1 -1
  58. package/skills/yad-connect-repos/SKILL.md +32 -15
  59. package/skills/yad-connect-repos/references/code-context.md +2 -2
  60. package/skills/yad-connect-repos/references/hub-config.md +25 -11
  61. package/skills/yad-connect-repos/references/repos-registry.md +3 -3
  62. package/skills/yad-connect-testing/SKILL.md +1 -1
  63. package/skills/yad-defects/SKILL.md +1 -1
  64. package/skills/yad-discovery/SKILL.md +6 -6
  65. package/skills/yad-discovery/references/discovery-schema.md +1 -1
  66. package/skills/yad-docs/SKILL.md +3 -3
  67. package/skills/yad-docs-overview/SKILL.md +3 -3
  68. package/skills/yad-docs-overview/references/pipeline-model.md +17 -11
  69. package/skills/yad-engineer-review/SKILL.md +9 -9
  70. package/skills/yad-engineer-review/references/ship-and-record.md +8 -8
  71. package/skills/yad-epic/SKILL.md +15 -15
  72. package/skills/yad-epic/references/state-schema.md +30 -30
  73. package/skills/yad-hub-bridge/SKILL.md +14 -14
  74. package/skills/yad-hub-bridge/references/bridge.md +17 -17
  75. package/skills/yad-hub-bridge/references/login-roster.md +3 -3
  76. package/skills/yad-hub-bridge/templates/checks/hub-route.sh +1 -1
  77. package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +1 -1
  78. package/skills/yad-implement/SKILL.md +3 -3
  79. package/skills/yad-open-pr/SKILL.md +4 -4
  80. package/skills/yad-pair-review/SKILL.md +12 -12
  81. package/skills/yad-pair-review/references/session-state.md +3 -3
  82. package/skills/yad-pr-template/SKILL.md +4 -4
  83. package/skills/yad-pr-template/references/risk-routing.md +1 -1
  84. package/skills/yad-pr-template/templates/checks/pr-template.sh +18 -10
  85. package/skills/yad-pr-template/templates/checks/pr-title.sh +7 -7
  86. package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +1 -1
  87. package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +1 -1
  88. package/skills/yad-reconcile/SKILL.md +1 -1
  89. package/skills/yad-report/SKILL.md +1 -1
  90. package/skills/yad-review-companion/SKILL.md +7 -7
  91. package/skills/yad-review-gate/SKILL.md +18 -18
  92. package/skills/yad-review-gate/references/gating.md +3 -3
  93. package/skills/yad-run/SKILL.md +10 -10
  94. package/skills/yad-run/references/run-loop.md +8 -8
  95. package/skills/yad-ship/SKILL.md +4 -4
  96. package/skills/yad-spec/SKILL.md +10 -11
  97. package/skills/yad-status/SKILL.md +13 -13
  98. package/skills/yad-stories/SKILL.md +12 -12
  99. package/skills/yad-stories/references/story-schema.md +3 -3
  100. package/skills/yad-stub/SKILL.md +3 -3
  101. package/skills/yad-sync-repos/SKILL.md +1 -1
  102. package/skills/yad-test-cases/SKILL.md +12 -13
  103. package/skills/yad-test-cases/references/test-cases-schema.md +1 -1
  104. package/skills/yad-ui/SKILL.md +10 -10
package/cli/manifest.mjs CHANGED
@@ -147,21 +147,26 @@ export const TESTING_PRIMARY = 'playwright';
147
147
  export const LEARNING_TOOLS = ['deeptutor'];
148
148
  export const LEARNING_PRIMARY = 'deeptutor';
149
149
 
150
- // The shape (schema version) every file the engine writes declares, as `"schemaVersion": 1`.
150
+ // The shape (schema version) every file the engine writes declares, as `"schemaVersion": <n>`.
151
151
  //
152
152
  // Rule 1 of the change-safety rules (docs/roadmap-idea-1.md, Part 2): every file states its shape,
153
153
  // and a file with no version counts as 1.
154
154
  //
155
- // Today the stamp is written and read back, and nothing yet acts on it: raising this number would move
156
- // what new files say without upgrading existing ones. The two halves that make it usable are the next
157
- // tasks on the roadmap — `yad migrate` (E14), which moves a project from one shape to the next, and a
158
- // `yad doctor` report (E16) for a project whose files disagree with the engine. Do NOT raise this
159
- // number before both exist.
155
+ // Raising this number is not a one-line change. Three things move together, or a project is left
156
+ // holding files it cannot upgrade:
157
+ // 1. a step appended to MIGRATIONS (cli/migrate.mjs) taking a file from the old shape to the new
158
+ // one — the chain from 1 upwards must have no gap, which cli/test-migrate.mjs asserts;
159
+ // 2. `docs/migrations/shape-<n>.md`, which scripts/shape-guide-check.sh REFUSES to release without;
160
+ // 3. every writer of the changed file taught to write the new shape's fields.
161
+ // `yad doctor` reports a project whose files disagree with this number, and `yad migrate` is what
162
+ // closes the gap.
163
+ //
164
+ // 2 — `.sdlc/hub.json` records who writes the ledger as `ledger: verified | local` (E104).
160
165
  //
161
166
  // Deliberately NOT the same thing as `VERSION` above. That is which release of the CLI you are
162
167
  // running and moves on every publish; this is what the files on disk look like and moves only when
163
168
  // their shape actually changes.
164
- export const SCHEMA_VERSION = 1;
169
+ export const SCHEMA_VERSION = 2;
165
170
 
166
171
  // Project-level files setup produces (used by `check` to spot missing setup).
167
172
  export const PROJECT_FILES = {
@@ -174,17 +179,31 @@ export const PROJECT_FILES = {
174
179
  version: '.sdlc/cli-version.json',
175
180
  };
176
181
 
177
- // Bridge mode: a platform AND the gate-sync CI explicitly enabled (the canonical `bridge_enabled`,
178
- // or the older `bridge`). ONLY then is CI the sole ledger writer — so `gate open`/`sync` stay
179
- // hands-off, `hubActions` wires the hub CI, and the ledger guards (the `ledger-guard` check gate and
180
- // the `yad hook ledger-guard` harness hook) are live. A platform without the bridge keeps the local
181
- // write path, or reviews could never advance.
182
+ // Who writes the ledger. Two values, and the switch lives in `.sdlc/hub.json`:
183
+ //
184
+ // ledger: "verified" CI only, with a platform-Verified signature. A local `gate open` is
185
+ // advisory and writes nothing; `ledger-guard` rejects any non-bot commit.
186
+ // ledger: "local" your machine. Works offline, no CI needed, guarded by nothing.
187
+ //
188
+ // `verified` is the old "bridge mode" renamed. The old name described a mechanism; this one
189
+ // describes what you get, and it is the word the platform shows next to the commits.
182
190
  //
183
- // ONE definition, imported by every JS caller. Copies that drift are how #186 happened — a hub that
184
- // one reader called bridge and another called file-only had no permitted ledger writer at all.
185
- // `templates/checks/ledger-guard.sh` re-implements it in bash because the check gates are standalone
186
- // by design; that copy is the only one, and its header says so.
187
- export const isBridgeHub = (hub) => !!(hub?.platform && (hub.bridge_enabled === true || hub.bridge === true));
191
+ // READ ORDER, and it matters (rule 2 — read old, write new):
192
+ // 1. `ledger`, if the file carries it — shape 2 and later.
193
+ // 2. otherwise the old booleans `bridge_enabled` (canonical) or `bridge` (older still).
194
+ // A platform is required either way. Without one there is no Verified badge to read, so CI cannot
195
+ // be the sole writer and the local path has to stay open — otherwise a hub has no permitted writer
196
+ // at all and no gate can ever advance (issue #186).
197
+ //
198
+ // ONE definition, imported by every JS caller. `templates/checks/ledger-guard.sh` re-implements the
199
+ // SAME order in bash because the check gates are standalone by design; that copy is the only one,
200
+ // its header says so, and cli/test-checks.mjs runs a table of hub.json variants through both and
201
+ // asserts they agree on every row. Three keys is three ways for two readers to drift.
202
+ export const isVerifiedLedger = (hub) => {
203
+ if (!hub?.platform) return false;
204
+ if (typeof hub.ledger === 'string') return hub.ledger === 'verified';
205
+ return hub.bridge_enabled === true || hub.bridge === true;
206
+ };
188
207
 
189
208
  // ---- `yad commit` conventions (mirror skills/sdlc/config.yaml `build`) ----
190
209
  // Conventional-commit types (config.yaml commit_subject_style).
@@ -216,14 +235,14 @@ export const epicFiles = (epicRoot) => ({
216
235
  comments: `${epicRoot}/.sdlc/comments.json`,
217
236
  hubPrs: `${epicRoot}/.sdlc/hub-prs.json`,
218
237
  contractLock: `${epicRoot}/.sdlc/contract-lock.json`,
219
- // The two append-only back-half ledgers use shard-then-fold storage (cli/ledger.mjs): writers add
238
+ // The two append-only Build ledgers use shard-then-fold storage (cli/ledger.mjs): writers add
220
239
  // one loose shard per entry under the *Dir path (conflict-free concurrent writes); `yad tidy up`
221
240
  // folds finished shards back into the single *Log file. Readers union the folded file + loose shards.
222
241
  buildLog: `${epicRoot}/.sdlc/build-log.json`, // folded ships (also the legacy single file)
223
242
  buildLogDir: `${epicRoot}/.sdlc/build-log`, // one <story>-<task>-<repo>.json per ship
224
243
  trustLog: `${epicRoot}/.sdlc/trust-log.json`, // folded runs (also the legacy single file)
225
244
  trustLogDir: `${epicRoot}/.sdlc/trust-log`, // one <story>-<repo>-<step>-<uid>.json per run
226
- buildStateDir: `${epicRoot}/.sdlc/build-state`, // Phase 4 — per-story, per-repo back-half state
245
+ buildStateDir: `${epicRoot}/.sdlc/build-state`, // Phase 4 — per-story, per-repo Build state
227
246
 
228
247
  change: `${epicRoot}/.sdlc/change.json`, // Phase 6 — change/defect intake + triage
229
248
  reconcileDebt: `${epicRoot}/.sdlc/reconcile-debt.json`, // Phase 6 — hotfix ship-first debt
@@ -235,6 +254,8 @@ export const REPO_WIRING = {
235
254
  common: [
236
255
  { src: 'skills/yad-checks/templates/checks/spec-link.sh', dest: 'checks/spec-link.sh', exec: true },
237
256
  { src: 'skills/yad-checks/templates/checks/contract-check.sh', dest: 'checks/contract-check.sh', exec: true },
257
+ { src: 'skills/yad-checks/templates/checks/package-manager.sh', dest: 'checks/package-manager.sh', exec: true },
258
+ { src: 'skills/yad-checks/templates/checks/install-deps.sh', dest: 'checks/install-deps.sh', exec: true },
238
259
  { src: 'skills/yad-checks/templates/checks/build-test-lint.sh', dest: 'checks/build-test-lint.sh', exec: true },
239
260
  { src: 'skills/yad-checks/templates/checks/lineage-check.sh', dest: 'checks/lineage-check.sh', exec: true },
240
261
  { src: 'skills/yad-checks/templates/checks/epic-open.sh', dest: 'checks/epic-open.sh', exec: true },
@@ -275,7 +296,7 @@ export const wiringFor = (platform) => [
275
296
  ];
276
297
 
277
298
  // Hub wiring: CI installed on the PRODUCT HUB itself (dest is the project root — the hub IS the
278
- // root). Installed only when hub.json has a platform and the bridge is enabled. Carries the
299
+ // root). Installed only when hub.json has a platform and the ledger is verified. Carries the
279
300
  // event-driven gate sync (approvals/change requests/the merge trigger `yad gate ci`) and the
280
301
  // verified-commits gate (no unverified commits from unverified users reach merge on the hub).
281
302
  export const HUB_WIRING = {
@@ -305,7 +326,7 @@ export const HUB_WIRING = {
305
326
  };
306
327
 
307
328
  // Harness hooks: the LOCAL half of the ledger rule, installed on the hub beside the CI gates and
308
- // active under the same bridge predicate (#171). Kept out of `HUB_WIRING` because a hook is not a
329
+ // active under the same verified-ledger predicate (#171). Kept out of `HUB_WIRING` because a hook is not a
309
330
  // CI gate — it is advisory, fails open, and its adapter (below) is per-harness, not per-platform.
310
331
  export const HOOK_WIRING = [
311
332
  { src: 'skills/yad-checks/templates/hooks/ledger-guard.sh', dest: 'hooks/ledger-guard.sh', exec: true },
package/cli/migrate.mjs CHANGED
@@ -18,7 +18,7 @@ import fs from 'node:fs';
18
18
  import path from 'node:path';
19
19
 
20
20
  import { c, exists, fail, hand, info, log, ok, readJSON, warn, writeJSON } from './lib.mjs';
21
- import { BACKUP_SUFFIX, epicFiles, isBridgeHub, MANAGED_LEDGER, PROJECT_FILES, SCHEMA_VERSION, VERSION } from './manifest.mjs';
21
+ import { BACKUP_SUFFIX, epicFiles, isVerifiedLedger, MANAGED_LEDGER, PROJECT_FILES, SCHEMA_VERSION, VERSION } from './manifest.mjs';
22
22
  import { backupPathFor } from './plan.mjs';
23
23
  import { isValidEpicId } from './epic-state.mjs';
24
24
 
@@ -37,6 +37,36 @@ export const MIGRATIONS = [
37
37
  title: 'baseline — every file states its shape',
38
38
  apply: (obj) => obj,
39
39
  },
40
+ {
41
+ from: 1,
42
+ to: 2,
43
+ title: 'hub.json — record who writes the ledger as `ledger: verified | local`',
44
+ // "bridge mode" is renamed to `ledger: verified`, and the switch becomes a named value instead
45
+ // of a boolean. Only .sdlc/hub.json carries it; every other file passes through untouched and
46
+ // simply moves to shape 2 with the rest of the project.
47
+ //
48
+ // Two things this deliberately does NOT do:
49
+ //
50
+ // 1. It does not DELETE `bridge_enabled`. `templates/checks/ledger-guard.sh` is committed inside
51
+ // the user's own repo and is refreshed only by `yad update` — which is a separate act from
52
+ // `yad migrate`. A hub running yesterday's guard against a migrated hub.json would read no
53
+ // flag, conclude the ledger is local, and stop rejecting human commits to it. That is the one
54
+ // guarantee verified mode exists to provide, so removing the old key here would silently
55
+ // disarm the audit trail. Add before you remove (rule 3): both keys are written, the new one
56
+ // wins, and the old one goes in a later major — once nothing on either side still reads it.
57
+ //
58
+ // 2. It does not copy the flag. `bridge_enabled: true` on a hub with NO platform is `local`
59
+ // today, because the reader has always required a platform — there is no Verified badge to
60
+ // read without one. Writing `verified` there would change what the engine does to a project
61
+ // during an upgrade. So the value is computed from the reader's own answer, which means no
62
+ // project's behaviour moves: whatever it was before the migration, it is after.
63
+ // Matched on the exact project-relative path, not the basename. `hub.json` is a name a
64
+ // connected repo or a nested folder could also use, and injecting `ledger` into some other
65
+ // file is precisely the silent rewrite that passing a context was meant to prevent.
66
+ apply: (obj, ctx) => (ctx?.rel === PROJECT_FILES.hubConfig
67
+ ? { ...obj, ledger: isVerifiedLedger(obj) ? 'verified' : 'local' }
68
+ : obj),
69
+ },
40
70
  ];
41
71
 
42
72
  // ---- reading a file's shape ---------------------------------------------------------------
@@ -58,15 +88,24 @@ const isPlainObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
58
88
  const shapeOf = (v) => (isPlainObject(v) && Number.isInteger(v.schemaVersion) ? v.schemaVersion : 1);
59
89
 
60
90
  // Walk the migration list once. Returns the migrated object, the shape it ended on, and which steps ran.
61
- function applyMigrations(obj, migrations) {
91
+ // `ctx` is `{ base, rel }` — the file's basename and its project-relative path. A step that only
92
+ // concerns one kind of file (the 1 → 2 hub switch below is the first) needs to know which file it is
93
+ // holding; every other step ignores the argument. Passing it is what keeps such a step from having
94
+ // to guess from the object's own fields, which would mean a hub-shaped ledger got silently rewritten.
95
+ function applyMigrations(obj, migrations, ctx) {
62
96
  let out = obj;
63
97
  let version = shapeOf(obj);
64
98
  const applied = [];
65
99
  for (const m of migrations) {
66
100
  if (version !== m.from) continue;
67
- out = m.apply({ ...out });
101
+ const next = m.apply({ ...out }, ctx);
102
+ // A step that does not apply to this file returns it unchanged — but the file's SHAPE still
103
+ // moves, because the shape describes the whole project's file format, not this one file's
104
+ // fields. Only steps that actually changed something are named in the report.
105
+ const moved = JSON.stringify(next) !== JSON.stringify(out);
106
+ out = next;
68
107
  version = m.to;
69
- applied.push(m.title);
108
+ if (moved) applied.push(m.title);
70
109
  }
71
110
  return { obj: out, version, applied };
72
111
  }
@@ -161,7 +200,7 @@ export function projectJsonFiles(root) {
161
200
  // unchanged already on the engine's shape, bytes identical
162
201
  // list a top-level JSON array: shape 1 by rule 1, and it cannot carry a key
163
202
  // ahead the file's shape is NEWER than this engine — never touched, always reported
164
- // ci-owned a verified (bridge) hub's ledger file: CI is its only writer
203
+ // ci-owned a verified hub's ledger file: CI is its only writer
165
204
  // unreadable does not parse — reported, never rewritten
166
205
  //
167
206
  // Each row also carries `stamped`: whether the file literally holds a `schemaVersion` key. That is a
@@ -171,7 +210,7 @@ export function projectJsonFiles(root) {
171
210
  // comparison happens at all.
172
211
  export function planMigration(root, { migrations = MIGRATIONS } = {}) {
173
212
  const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig), null);
174
- const bridge = isBridgeHub(hub);
213
+ const verified = isVerifiedLedger(hub);
175
214
  // On a verified hub the ledger guard refuses a human commit to these, so rewriting them locally
176
215
  // would produce a change that cannot be committed. Of the four the guard names, only state.json is
177
216
  // an object; the rest are arrays and would be skipped anyway.
@@ -197,11 +236,11 @@ export function planMigration(root, { migrations = MIGRATIONS } = {}) {
197
236
  rows.push({ file: rel, from, to: from, action: 'ahead', changes: false, stamped: isStamped });
198
237
  continue;
199
238
  }
200
- if (bridge && ciOwned.has(path.basename(file))) {
239
+ if (verified && ciOwned.has(path.basename(file))) {
201
240
  rows.push({ file: rel, from, to: from, action: 'ci-owned', changes: false, stamped: isStamped });
202
241
  continue;
203
242
  }
204
- const { obj, version, applied } = applyMigrations(raw.value, migrations);
243
+ const { obj, version, applied } = applyMigrations(raw.value, migrations, { base: path.basename(file), rel });
205
244
  const next = serialize(stamped(obj, version));
206
245
  const current = fs.readFileSync(file, 'utf8');
207
246
  const changes = next !== current;
@@ -210,7 +249,7 @@ export function planMigration(root, { migrations = MIGRATIONS } = {}) {
210
249
  // every file by design and moves nothing, so naming it on every row would be noise reported as work.
211
250
  rows.push({ file: rel, from, to: version, action, changes, stamped: isStamped, ...(version !== from ? { steps: applied } : {}) });
212
251
  }
213
- return { engine: SCHEMA_VERSION, bridge, rows };
252
+ return { engine: SCHEMA_VERSION, verified, rows };
214
253
  }
215
254
 
216
255
  // ---- the report ----------------------------------------------------------------------------
@@ -268,7 +307,10 @@ export async function runMigrate(root, { apply = false, json = false } = {}, { m
268
307
  // file's bytes are provably ours — a ledger has no provenance record, so there is nothing to
269
308
  // prove and the copy is unconditional.
270
309
  fs.copyFileSync(file, backupPathFor(file));
271
- const { obj, version } = applyMigrations(raw.value, migrations);
310
+ // The SAME ctx the preview used. Passing it in one place and not the other is how a preview
311
+ // promises one thing and an apply writes another — the single worst failure this command can
312
+ // have, because the preview is the reason anyone trusts it enough to run --apply.
313
+ const { obj, version } = applyMigrations(raw.value, migrations, { base: path.basename(file), rel: row.file });
272
314
  writeJSON(file, stamped(obj, version));
273
315
  written.push(row.file);
274
316
  }
@@ -280,7 +322,7 @@ export async function runMigrate(root, { apply = false, json = false } = {}, { m
280
322
  ok: blocked.length === 0,
281
323
  engine: plan.engine,
282
324
  applied: apply,
283
- bridge: plan.bridge,
325
+ verified: plan.verified,
284
326
  changed: apply ? written : pending.map((r) => r.file),
285
327
  ...(ignored ? { gitignored: BACKUP_IGNORE_GLOB } : {}),
286
328
  rows: plan.rows,
@@ -302,7 +344,7 @@ export async function runMigrate(root, { apply = false, json = false } = {}, { m
302
344
  info(`${pending.length} file(s) would change — nothing has been written`);
303
345
  hand('run `yad migrate --apply` to make the change (each file is backed up first)');
304
346
  }
305
- if (plan.bridge && plan.rows.some((r) => r.action === 'ci-owned')) {
347
+ if (plan.verified && plan.rows.some((r) => r.action === 'ci-owned')) {
306
348
  info('this project is in verified mode: CI owns some ledger files and stamps them on its next gate sync');
307
349
  }
308
350
  for (const r of blocked) {
package/cli/next.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  // `yad next` — the unified next-step driver. Read-only: it never writes state or acts. It reads the
2
2
  // file ledger and prints the ONE concrete, copy-pasteable next action (and a one-line why), so a user
3
3
  // never has to remember which of the 38 skills / gate commands comes next. "Guide, don't act" — the
4
- // front half still never auto-advances. Once an epic is `ready-for-build`, it reads each story's
4
+ // Shape still never auto-advances. Once an epic is `ready-for-build`, it reads each story's
5
5
  // build-state and prints the next BUILD sub-step per repo (spec → tasks → implement → checks → engineer-review)
6
- // plus the remaining chain — so the build half is guided too, not just hinted at.
6
+ // plus the remaining chain — so Build is guided too, not just hinted at.
7
7
  //
8
8
  // yad next general orientation across the whole project
9
9
  // yad next <epic> the single next action for one epic
@@ -87,10 +87,10 @@ function actionLine(a, { solo } = {}) {
87
87
  case 'review-sync':
88
88
  return `${c.bold(a.command)}${solo ? c.dim(' (solo: no approval needed — just merge your own PR)') : ''}`;
89
89
  case 'build': {
90
- // In the build half: compact the lanes to "N lane(s) in build — next: <skill> @ <story>/<repo>".
90
+ // In Build: compact the lanes to "N lane(s) in build — next: <skill> @ <story>/<repo>".
91
91
  if (a.builds?.length) {
92
92
  const open = buildLanes(a.builds).filter((r) => !r.shipped);
93
- if (!open.length) return c.dim('build half — every lane shipped');
93
+ if (!open.length) return c.dim('Build — every lane shipped');
94
94
  // Headline the first lane with a resolvable next skill; if none, the half is started but unspecced.
95
95
  const first = open.find((r) => r.skill);
96
96
  if (!first) return c.dim(`${open.length} lane(s) in build — not specced yet`);
@@ -115,7 +115,7 @@ function printAction(a, { solo } = {}) {
115
115
  // kind of work this is. The discovery front-zero is not a feature epic — leave it un-prefixed.
116
116
  const noun = a.lineageKind && a.epicId !== DISCOVERY_EPIC ? `${kindNoun(a.lineageKind)} ` : '';
117
117
  log(`\n ${c.bold(`${noun}${a.epicId || '(epic)'}`)} ${c.dim(`— ${a.why}`)}`);
118
- // In the build half with live lanes, print each story/repo's next sub-step + remaining chain instead
118
+ // In Build with live lanes, print each story/repo's next sub-step + remaining chain instead
119
119
  // of the single static hint; otherwise the one actionable line.
120
120
  if (a.kind === 'build' && a.builds?.length) printBuildLanes(a.builds);
121
121
  else hand(actionLine(a, { solo }));
package/cli/openpr.mjs CHANGED
@@ -1,6 +1,6 @@
1
- // `yad open-pr` — open a code-repo task PR/MR from the repo's platform template (build half).
1
+ // `yad open-pr` — open a code-repo task PR/MR from the repo's platform template (Build).
2
2
  // Detects the platform, pushes the current branch, and creates the PR/MR with Summary / Story-task /
3
- // Impact & Risk prefilled. Distinct from `yad gate open`, which opens a front-half artifact-review PR
3
+ // Impact & Risk prefilled. Distinct from `yad gate open`, which opens a Shape artifact-review PR
4
4
  // on the product hub.
5
5
  import path from 'node:path';
6
6
  import fs from 'node:fs';
@@ -26,7 +26,7 @@ function resolveRepo(root, { repo, dir }) {
26
26
  // Which SDLC stage is this PR? The hub serves two vehicles; a code repo only one. Mirrors the
27
27
  // `--head` split the hub pattern gates (pr-title.sh/pr-template.sh) already apply:
28
28
  // code-repo — NOT the product hub (a registry repo via --repo, or root is not a hub).
29
- // hub-front — the hub itself AND head is a review/EP-* branch (artifact-review PR).
29
+ // hub-shape — the hub itself AND head is a review/EP-* branch (artifact-review PR).
30
30
  // hub-tooling — the hub itself AND head is anything else (a tooling/CI change to the hub).
31
31
  // `meta` (truthy when resolved from the repos registry via --repo) is a connected code repo, so it is
32
32
  // never the hub regardless of its path. Otherwise "is the hub" = repoRoot resolves to root AND root
@@ -36,7 +36,7 @@ export function detectStage(root, repoRoot, head, meta) {
36
36
  const isHub = path.resolve(repoRoot) === path.resolve(root)
37
37
  && exists(path.join(root, PROJECT_FILES.hubConfig));
38
38
  if (!isHub) return 'code-repo';
39
- return /^review\/EP-[a-z0-9-]+\//.test(head || '') ? 'hub-front' : 'hub-tooling';
39
+ return /^review\/EP-[a-z0-9-]+\//.test(head || '') ? 'hub-shape' : 'hub-tooling';
40
40
  }
41
41
 
42
42
  // The bundled code-task template — the same file `REPO_WIRING` installs into code repos, resolved
@@ -108,11 +108,11 @@ export async function runOpenPr(root, opts = {}) {
108
108
  const branch = run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoRoot }).stdout;
109
109
  const stage = detectStage(root, repoRoot, branch, meta);
110
110
 
111
- // hub-front: this is a front-half artifact-review PR (review/EP-*/<artifact> head on the hub). The
111
+ // hub-shape: this is a Shape artifact-review PR (review/EP-*/<artifact> head on the hub). The
112
112
  // artifact-review title, body, and ledger bookkeeping all live in `yad gate open` — delegate to it
113
113
  // rather than emit the code-task shape (which the hub gate would reject). Push first (gateOpen does
114
114
  // not push), then hand off; any --title/--message is dropped (gateOpen sets `review: …`).
115
- if (stage === 'hub-front') {
115
+ if (stage === 'hub-shape') {
116
116
  const parsed = parseReviewBranch(branch);
117
117
  if (!parsed) { fail(`could not parse review branch '${branch}' (expected review/EP-<slug>/<artifact>)`); process.exitCode = 1; return; }
118
118
  info(`pushing ${branch} …`);
@@ -121,7 +121,7 @@ export async function runOpenPr(root, opts = {}) {
121
121
  // Pass the branch we just pushed as the head so gateOpen opens the PR against it (its own
122
122
  // recompute would collapse a per-story base). gateOpen signals failure by returning no url —
123
123
  // mirror open-pr's own error contract so `ship` sees the non-zero exit and never reports success.
124
- // (On a platform-less hub gateOpen marks the step in_review file-only and returns no url; open-pr's
124
+ // (On a platform-less hub gateOpen marks the step in_review locally and returns no url; open-pr's
125
125
  // job is to open a PR, so "no PR opened" is a non-zero outcome here, unlike `yad gate open`.)
126
126
  const res = await gateOpen(root, { epic: parsed.epic, artifact: artifactFromBase(parsed.base), head: branch });
127
127
  if (!res?.url) process.exitCode = 1;
@@ -130,7 +130,7 @@ export async function runOpenPr(root, opts = {}) {
130
130
 
131
131
  // The hub roster + its default_branch. The latter only applies when the PR targets the hub ITSELF
132
132
  // (a hub-tooling branch) — for a connected code repo the hub's trunk belongs to a different repo and
133
- // must never leak in. Resolved AFTER the hub-front hand-off above, which delegates its own base to
133
+ // must never leak in. Resolved AFTER the hub-shape hand-off above, which delegates its own base to
134
134
  // `yad gate open`: resolving before it would spend a platform round-trip and print a base that the
135
135
  // delegated path then ignores.
136
136
  const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig), { roster: [] });
package/cli/plan.mjs CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  asset, exists, copyDir, copyFile, dirMatches, sameContent, readJSON, readJSONStrict, writeJSON, fileSha, warn,
9
9
  } from './lib.mjs';
10
10
  import {
11
- VERSION, SKILLS, IDE_TARGETS, IDE_OPENCODE_DIR, MODULE_FILES, wiringFor, HUB_WIRING, PROJECT_FILES, isBridgeHub,
11
+ VERSION, SKILLS, IDE_TARGETS, IDE_OPENCODE_DIR, MODULE_FILES, wiringFor, HUB_WIRING, PROJECT_FILES, isVerifiedLedger,
12
12
  HOOK_WIRING, HOOK_SETTINGS, HOOK_TOOL_MATCHER, HOOK_COMMAND, HOOK_COMMAND_LEGACY,
13
13
  LEGACY_SKILLS, REMOVED_SKILLS, LEGACY_MARKER, LEGACY_REPO_FILES, LEGACY_HUB_FILES, MANAGED_LEDGER, BACKUP_SUFFIX,
14
14
  } from './manifest.mjs';
@@ -442,7 +442,7 @@ export function legacyRepoActions(root, repo) {
442
442
 
443
443
  export function legacyHubActions(root) {
444
444
  const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig));
445
- if (!isBridgeHub(hub)) return [];
445
+ if (!isVerifiedLedger(hub)) return [];
446
446
  const wiring = [...HUB_WIRING.common, ...(HUB_WIRING[hub.platform] || [])];
447
447
  return legacyFileActions('hub', root, LEGACY_HUB_FILES[hub.platform], wiring);
448
448
  }
@@ -451,19 +451,38 @@ export function legacyHubActions(root) {
451
451
  export function repoActions(root, repo) {
452
452
  const repoRoot = path.resolve(root, repo.path);
453
453
  const ledger = readManagedLedger(repoRoot);
454
- return wiringFor(repo.platform).map((w) =>
454
+ const actions = wiringFor(repo.platform).map((w) =>
455
455
  wiredFileAction(repo.name, w.dest, asset(w.src), path.join(repoRoot, w.dest), { root: repoRoot, exec: !!w.exec, ledger }),
456
456
  );
457
+ // A repo that already carries ANY of its wiring is a wired repo, and a template a later release
458
+ // ADDED to the wiring is `new` there, not `missing` — the same relabel a new first-party skill
459
+ // gets, for the same reason: `yad update` (--scope=changed) excludes only the literal 'missing'.
460
+ // Without it an upgrade rewrites the files that CALL the new template (`outdated`) while skipping
461
+ // the template itself, and every PR fails the gate with "file not found" until someone runs
462
+ // `yad check --fix`. The ledger tells the two absences apart: a file yad RECORDED writing and
463
+ // someone since removed stays `missing` (the team's deletion is respected, as before), while one
464
+ // yad never wrote is the new template. A repo with none of its wiring stays `missing` throughout:
465
+ // update never does one-time setup.
466
+ //
467
+ // "Wired" means yad wired it: the ledger has records, or (an install from before the ledger) a
468
+ // gate script under checks/ is present. A team-owned file that merely occupies a wired path — a
469
+ // hand-written PR template reads as `outdated` — is not evidence, or update would perform the
470
+ // whole one-time setup on a repo that never asked for it.
471
+ const wired = Object.keys(ledger).length > 0
472
+ || actions.some((a) => a.status !== 'missing' && a.item.startsWith('checks/'));
473
+ if (!wired) return actions;
474
+ const neverWritten = (a) => !ledger[rel(a.managed.root, a.managed.dest)];
475
+ return actions.map((a) => (a.status === 'missing' && neverWritten(a) ? { ...a, status: 'new' } : a));
457
476
  }
458
477
 
459
478
  // Hub wiring (gate-sync + verified-commits CI on the product hub itself). Only when the hub has a
460
- // platform and the bridge is explicitly enabled — a file-only hub stays file-only, with no error.
479
+ // platform and the verified ledger is explicitly enabled — a local hub stays local, with no error.
461
480
  export function hubActions(root) {
462
481
  const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig));
463
- // `bridge_enabled` is the canonical flag (the documented hub-config schema); older setup versions
464
- // wrote `bridge` — `isBridgeHub` accepts an explicit true in either spelling, and is the one
482
+ // `ledger` is the canonical switch and `bridge_enabled` its older spelling (the documented hub-config schema); older setup versions
483
+ // wrote `bridge` — `isVerifiedLedger` accepts an explicit true in either spelling, and is the one
465
484
  // predicate the CLI, the wiring, and the ledger hook all read (#186). Wire nothing otherwise.
466
- if (!isBridgeHub(hub)) return [];
485
+ if (!isVerifiedLedger(hub)) return [];
467
486
  const ledger = readManagedLedger(root);
468
487
  return [...HUB_WIRING.common, ...(HUB_WIRING[hub.platform] || [])].map((w) =>
469
488
  wiredFileAction('hub', w.dest, asset(w.src), path.join(root, w.dest), { root, exec: !!w.exec, ledger }),
@@ -602,11 +621,11 @@ function hookSettingsAction(root, ide, relDest) {
602
621
  }
603
622
 
604
623
  // Harness-hook wiring on the hub: the guard script plus, per IDE target that defines a hook protocol,
605
- // the entry that invokes it. Bridge-gated exactly like `hubActions` — with no bridge the ledger is
624
+ // the entry that invokes it. Verified-only exactly like `hubActions` — with a local ledger it is
606
625
  // locally owned, the hand-edit the authoring skills describe is CORRECT, and a guard would be wrong.
607
626
  export function hookActions(root, ideTargets = ideTargetsFor(root)) {
608
627
  const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig));
609
- if (!isBridgeHub(hub)) return [];
628
+ if (!isVerifiedLedger(hub)) return [];
610
629
  const ledger = readManagedLedger(root);
611
630
  const actions = HOOK_WIRING.map((w) =>
612
631
  wiredFileAction('hub', w.dest, asset(w.src), path.join(root, w.dest), { root, exec: !!w.exec, ledger }),
package/cli/platform.mjs CHANGED
@@ -299,7 +299,7 @@ export function readPr(platform, n, opts = {}) {
299
299
  }
300
300
 
301
301
  // ---- find the PR/MR for a branch ----------------------------------------------------------------
302
- // The review PR/MR opened for `review/EP-<slug>/<artifact>`, by HEAD/source branch. Under the bridge
302
+ // The review PR/MR opened for `review/EP-<slug>/<artifact>`, by HEAD/source branch. Under the verified ledger
303
303
  // the ledger records that pointer only at merge (CI is the sole writer), so without this a human has
304
304
  // no way to name the review a merged PR belongs to — `gate sync` would just report "no open review PR
305
305
  // recorded" for a PR that is sitting merged on the platform (issue #158).
package/cli/report.mjs CHANGED
@@ -73,7 +73,7 @@ export function sanitizeArgv(argv = []) {
73
73
  // state but keeps just the booleans, never the raw checks (which carry names + paths).
74
74
  export function sanitizeContext(dir, { error = null, argv = process.argv.slice(2) } = {}) {
75
75
  const hub = readJSON(path.join(dir, PROJECT_FILES.hubConfig), null);
76
- const platform = hub && ['github', 'gitlab'].includes(hub.platform) ? hub.platform : 'file-only';
76
+ const platform = hub && ['github', 'gitlab'].includes(hub.platform) ? hub.platform : 'local';
77
77
  // Derive tool auth from doctor's checks without keeping any check text.
78
78
  const toolState = (cli, p) => (has(cli) ? (platformAuthed(p) ? 'present + authenticated' : 'present, not authenticated') : 'not installed');
79
79
  const ctx = {
package/cli/review.mjs CHANGED
@@ -1,6 +1,6 @@
1
- // `yad review trailer|context|nudge|reconcile` — the BACK-HALF Review Companion + bridge for code
2
- // PR/MRs (the analogue of `yad gate …` for the front half). The fun process (trailer/cards/chat/nudge)
3
- // makes the engineer review easy and visible; the bridge process (reconcile) maps the code PR's review
1
+ // `yad review trailer|context|nudge|reconcile` — the Build Review Companion + bridge for code
2
+ // PR/MRs (the analogue of `yad gate …` for Shape). The fun process (trailer/cards/chat/nudge)
3
+ // makes the engineer review easy and visible; the bridge to code PR/MRs (reconcile) maps the code PR's review
4
4
  // state — including the engagement signal — into the build ledger (build-log.json) at merge.
5
5
  //
6
6
  // The CLI never calls an LLM: the skill (yad-review-companion / yad-engineer-review) generates the
@@ -38,7 +38,7 @@ function platformOf(root, repoRoot, meta) {
38
38
  return detectPlatform(remote) || readJSON(path.join(root, PROJECT_FILES.hubConfig), {}).platform || null;
39
39
  }
40
40
 
41
- // Build (but don't print) the back-half grounding bundle. Shared by `context` and `walkthrough` so the
41
+ // Assemble (but don't print) the Build grounding bundle. Shared by `context` and `walkthrough` so the
42
42
  // pair walkthrough adds an ordered stop-list on top of the exact same grounding the companion uses.
43
43
  // Returns { error } on a bad --repo, else { bundle, repoRoot, base }.
44
44
  function contextBundle(root, { repo, dir, pr, runner = run } = {}) {
@@ -136,7 +136,7 @@ export async function reviewNudge(root, { repo, dir, pr, reader = readPr, poster
136
136
  return { nudged };
137
137
  }
138
138
 
139
- // `yad review reconcile --epic <id> --repo <r> --pr <n>` — the back-half BRIDGE: read the code PR's
139
+ // `yad review reconcile --epic <id> --repo <r> --pr <n>` — the Build BRIDGE: read the code PR's
140
140
  // approvals (with the engagement signal) and stamp them onto the matching build-log.json ship record,
141
141
  // so the build ledger reflects who actually engaged. The first CLI to write build-log.json. Matches the
142
142
  // ship record by its `pr` field (url or number); if none exists yet, prints the engineer_review block
package/cli/setup.mjs CHANGED
@@ -152,7 +152,7 @@ export function buildReconfiguredHub(cur, fields) {
152
152
  export function upsertRosterEntry(root, { login, name, email, roles = {}, platform } = {}) {
153
153
  if (!login) { warn('roster upsert needs a login — skipped'); return { entry: null, created: false }; }
154
154
  const hubPath = path.join(root, PROJECT_FILES.hubConfig);
155
- const hub = readJSON(hubPath, null) || { platform: platform && platform !== 'none' ? platform : null, bridge_enabled: false, bridge: false, default_branch: 'main', roster: [] };
155
+ const hub = readJSON(hubPath, null) || { platform: platform && platform !== 'none' ? platform : null, ledger: 'local', bridge_enabled: false, bridge: false, default_branch: 'main', roster: [] };
156
156
  if (!Array.isArray(hub.roster)) hub.roster = [];
157
157
  let entry = hub.roster.find((e) => e.login === login);
158
158
  const created = !entry;
@@ -484,7 +484,7 @@ export async function runSetup(root, opts = {}) {
484
484
  S(solo ? 'Hub platform (solo — no roster)' : 'Hub platform & reviewer roster');
485
485
  guide(solo
486
486
  ? [
487
- 'Your hub is this repo on GitHub/GitLab (or none for a file-only gate).',
487
+ 'Your hub is this repo on GitHub/GitLab (or none for a local gate).',
488
488
  'Solo: no roster needed — you review by merging your own PR (approval waived).',
489
489
  ]
490
490
  : [
@@ -501,7 +501,7 @@ export async function runSetup(root, opts = {}) {
501
501
  let platform = detectPlatform(remote.ok ? remote.stdout : '');
502
502
  platform = (await ask('Hub platform (github/gitlab/none)', platform || 'none')).toLowerCase();
503
503
  if (!['github', 'gitlab', 'none'].includes(platform)) {
504
- warn(`unknown platform '${platform}' — using none (file-only gate)`);
504
+ warn(`unknown platform '${platform}' — using none (local gate)`);
505
505
  platform = 'none';
506
506
  }
507
507
  const roster = [];
@@ -528,10 +528,13 @@ export async function runSetup(root, opts = {}) {
528
528
  }
529
529
  }
530
530
  const default_branch = platform === 'none' ? 'main' : await ask('Hub default branch', 'main');
531
- // `bridge_enabled` is the canonical flag (hub-config schema); keep the legacy `bridge` spelling
532
- // for anything that still reads it.
531
+ // `ledger` is the canonical switch (shape 2): "verified" = CI writes the ledger, "local" = this
532
+ // machine does. The two booleans below say the same thing in the older spelling and are written
533
+ // ALONGSIDE it, not instead of it — add before you remove (rule 3). They are what a check gate
534
+ // that has not been refreshed by `yad update` yet still reads, and what a hub that is rolled back
535
+ // to a 3.x CLI would fall back to. They go in a later major, once nothing on either side reads them.
533
536
  const enabled = platform !== 'none';
534
- // Record git_url — doctor needs it to scope the auth probe (YAD-CFG-005) and the bridge/PR flow
537
+ // Record git_url — doctor needs it to scope the auth probe (YAD-CFG-005) and the verified ledger/PR flow
535
538
  // needs it to open PRs. Derived from the origin remote already resolved above; null when local-only.
536
539
  const git_url = enabled ? ((remote.ok && remote.stdout.trim()) || null) : null;
537
540
  // Merge into the existing file, never clobber: roster + verified_authors are user-owned identity
@@ -540,8 +543,17 @@ export async function runSetup(root, opts = {}) {
540
543
  // Read strict so a corrupt hub aborts here (YAD-STATE-001) rather than fail-open to `{}` and rewrite
541
544
  // the file with identity stripped — the same silent-loss hole, just triggered by a parse failure.
542
545
  const cur = readJSONStrict(hubPath, {}) || {};
546
+ // `ledger` belongs to shape 2. On a project still on shape 1 — one that has not run
547
+ // `yad migrate` yet — writing it would leave a file DECLARING shape 1 while carrying a shape-2
548
+ // field, which is rule 1 read backwards and makes `yad doctor`'s drift report a lie about the
549
+ // one file this shape change is about. The old booleans below say the same thing and are what
550
+ // the reader falls back to when `ledger` is absent, so nothing is lost by waiting: `yad migrate`
551
+ // adds the key, and the setting it computes is the one these booleans just recorded.
552
+ const onNewShape = (cur.schemaVersion ?? 1) >= 2 || !Object.keys(cur).length;
543
553
  const next = buildReconfiguredHub(cur, {
544
- platform: enabled ? platform : null, git_url, bridge_enabled: enabled, bridge: enabled,
554
+ platform: enabled ? platform : null, git_url,
555
+ ...(onNewShape ? { ledger: enabled ? 'verified' : 'local' } : {}),
556
+ bridge_enabled: enabled, bridge: enabled,
545
557
  default_branch, roster, solo, profile: { codebase, repo_layout, team_size },
546
558
  });
547
559
  if (!roster.length && Array.isArray(cur.roster) && cur.roster.length) {
@@ -653,12 +665,12 @@ export async function runSetup(root, opts = {}) {
653
665
  guide(repo_layout === 'monorepo'
654
666
  ? [
655
667
  'One repo holds all the code; the contract lives in the hub and stories tag this single repo.',
656
- codebase === 'greenfield' ? 'Greenfield: no code yet — the repomix code-pack step is skipped.' : 'Brownfield: the repo is packed so the front phases see what already exists.',
668
+ codebase === 'greenfield' ? 'Greenfield: no code yet — the repomix code-pack step is skipped.' : 'Brownfield: the repo is packed so the Shape phases see what already exists.',
657
669
  ]
658
670
  : [
659
671
  'Register each code repo the feature touches; stories get tagged with the repos that implement them.',
660
672
  'Per repo: name → path (inside this project) → platform → domain owner(s).',
661
- codebase === 'greenfield' ? 'Greenfield: no code yet — the repomix code-pack step is skipped.' : 'Brownfield: each repo is packed so the front phases see what already exists.',
673
+ codebase === 'greenfield' ? 'Greenfield: no code yet — the repomix code-pack step is skipped.' : 'Brownfield: each repo is packed so the Shape phases see what already exists.',
662
674
  ]);
663
675
  const regPath = path.join(root, PROJECT_FILES.reposRegistry);
664
676
  const registry = readJSON(regPath, { repos: [] });
@@ -735,7 +747,7 @@ export async function runSetup(root, opts = {}) {
735
747
  }
736
748
  applyActions(legacyHubActions(root), { force: true });
737
749
  // the hub, locally: the harness ledger guard, so an agent is refused the CI-owned ledger write at
738
- // the moment it tries it rather than by a failed pipeline later (#171). Bridge-gated like the CI
750
+ // the moment it tries it rather than by a failed pipeline later (#171). Verified-only like the CI
739
751
  // above — with no bridge the ledger is locally owned and the guard would be wrong.
740
752
  const hookWiring = hookActions(root, ideTargets);
741
753
  if (hookWiring.length) {
package/cli/ship.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // `yad ship` — commit the staged atomic change AND open its task PR/MR, in one step (build half).
1
+ // `yad ship` — commit the staged atomic change AND open its task PR/MR, in one step (Build).
2
2
  // A thin orchestration over the two existing engines: `yad commit` then `yad open-pr`. It holds no
3
3
  // commit/PR logic of its own — it reuses runCommit/runOpenPr so the conventions stay in one place.
4
4
  // The PR step runs ONLY when the commit actually lands: a failed commit, a tripped atomic guard, or a
package/cli/skip.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // `yad skip <epic> <step> --reason "<why>"` (and `--undo`) — mark an OPTIONAL front step N/A for one
1
+ // `yad skip <epic> <step> --reason "<why>"` (and `--undo`) — mark an OPTIONAL Shape step N/A for one
2
2
  // epic. Today only `ui-design` is skippable: an epic with no user-facing surface (backend/API, data,
3
3
  // infra) does not need a UI-design artifact + review gate. The skip stays VISIBLE and auditable — the
4
4
  // step is pre-marked `done` with a recorded reason (and actor/date), short-circuited at the gate — and
package/cli/thread.mjs CHANGED
@@ -24,7 +24,7 @@ export const loadBuildLog = (root, epic) => ({ epic, ships: readShips(epicRoot(r
24
24
 
25
25
  // An epic is SEALED once every authored story is `shipped` (config.yaml change.seal_on). A sealed epic
26
26
  // refuses new behaviour (epic-open.sh) — a further change must open a new threaded change-epic, which is
27
- // what keeps the front artifacts from going stale. An epic with no stories is NOT sealed (nothing built).
27
+ // what keeps the Shape artifacts from going stale. An epic with no stories is NOT sealed (nothing built).
28
28
  export function sealedEpic(root, epic) {
29
29
  const dir = path.join(epicRoot(root, epic), 'stories');
30
30
  if (!exists(dir)) return false;
package/cli/tidy.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // `yad tidy up` — fold FINISHED back-half shards back into their single folded ledger, on demand.
1
+ // `yad tidy up` — fold FINISHED Build shards back into their single folded ledger, on demand.
2
2
  //
3
3
  // The two logs are shard-then-fold (cli/ledger.mjs): writers drop one loose file per entry so
4
4
  // concurrent writers never conflict. Left alone they accumulate; `yad tidy up` is the "pack it up"
@@ -72,7 +72,7 @@ export async function runTidy(root, opts = {}) {
72
72
 
73
73
  const author = checkpointAuthor(resolveCommitterLogin(root, hub?.roster || []), git('config', 'user.name').stdout);
74
74
  const label = touched.length === 1 ? touched[0] : `${touched.length} epics`;
75
- const message = `chore(hub): tidy back-half ledgers — ${label} by ${author} [skip ci]`;
75
+ const message = `chore(hub): tidy Build ledgers — ${label} by ${author} [skip ci]`;
76
76
 
77
77
  if (opts.dryRun) {
78
78
  log('\n' + c.dim(message) + '\n');