yadflow 3.18.1 → 4.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +355 -0
  2. package/README.md +79 -26
  3. package/bin/commands.mjs +41 -0
  4. package/bin/yad.mjs +437 -124
  5. package/cli/artifact-status.mjs +34 -15
  6. package/cli/checkpoint.mjs +69 -49
  7. package/cli/codeowners-command.mjs +170 -0
  8. package/cli/codeowners.mjs +397 -0
  9. package/cli/commit.mjs +13 -9
  10. package/cli/companion.mjs +2 -2
  11. package/cli/dial.mjs +183 -0
  12. package/cli/docs.mjs +88 -32
  13. package/cli/doctor.mjs +1472 -97
  14. package/cli/epic-state.mjs +3478 -232
  15. package/cli/epic.mjs +506 -0
  16. package/cli/errors.mjs +4 -1
  17. package/cli/gate.mjs +1002 -209
  18. package/cli/history.mjs +556 -0
  19. package/cli/hook.mjs +266 -55
  20. package/cli/hubcommit.mjs +6 -17
  21. package/cli/index-command.mjs +87 -0
  22. package/cli/ledger.mjs +57 -7
  23. package/cli/lib.mjs +184 -18
  24. package/cli/manifest.mjs +367 -56
  25. package/cli/migrate.mjs +726 -53
  26. package/cli/mode.mjs +170 -0
  27. package/cli/next.mjs +349 -90
  28. package/cli/openpr.mjs +191 -39
  29. package/cli/people.mjs +654 -0
  30. package/cli/plan.mjs +417 -132
  31. package/cli/platform.mjs +110 -129
  32. package/cli/product-index.mjs +287 -0
  33. package/cli/protection.mjs +706 -0
  34. package/cli/reconcile.mjs +38 -12
  35. package/cli/repo-publish.mjs +24 -26
  36. package/cli/repo.mjs +23 -14
  37. package/cli/report.mjs +21 -15
  38. package/cli/review.mjs +24 -27
  39. package/cli/riskmap-command.mjs +289 -0
  40. package/cli/riskmap.mjs +373 -0
  41. package/cli/setup.mjs +139 -287
  42. package/cli/ship.mjs +7 -6
  43. package/cli/skill.mjs +180 -0
  44. package/cli/skip.mjs +211 -30
  45. package/cli/thread.mjs +42 -17
  46. package/cli/tidy.mjs +20 -20
  47. package/cli/update-commit.mjs +22 -22
  48. package/cli/usage.mjs +115 -109
  49. package/package.json +3 -3
  50. package/skills/sdlc/config.yaml +166 -87
  51. package/skills/sdlc/module-help.csv +35 -35
  52. package/skills/yad-analysis/SKILL.md +125 -65
  53. package/skills/yad-architecture/SKILL.md +34 -23
  54. package/skills/yad-architecture/references/contract-format.md +10 -8
  55. package/skills/yad-backfill/SKILL.md +14 -8
  56. package/skills/yad-backfill/references/backfill.md +1 -1
  57. package/skills/yad-change/SKILL.md +127 -52
  58. package/skills/yad-change/references/triage.md +42 -28
  59. package/skills/yad-checks/SKILL.md +89 -45
  60. package/skills/yad-checks/references/check-gates.md +315 -92
  61. package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
  62. package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
  63. package/skills/yad-checks/templates/checks/contract-check.sh +58 -2
  64. package/skills/yad-checks/templates/checks/epic-open.sh +3 -3
  65. package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
  66. package/skills/yad-checks/templates/checks/ledger-guard.sh +94 -18
  67. package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
  68. package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
  69. package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +4 -4
  70. package/skills/yad-checks/templates/checks/risk-map-check.sh +438 -0
  71. package/skills/yad-checks/templates/checks/verified-commits.sh +20 -46
  72. package/skills/yad-checks/templates/github/yad-checks.yml +37 -5
  73. package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
  74. package/skills/yad-checks/templates/github/yad-update-guard.yml +3 -4
  75. package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
  76. package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
  77. package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +22 -4
  78. package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +5 -5
  79. package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +4 -4
  80. package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
  81. package/skills/yad-checks/templates/hooks/ledger-guard.sh +38 -7
  82. package/skills/yad-commit/SKILL.md +6 -6
  83. package/skills/yad-connect-design/SKILL.md +6 -6
  84. package/skills/yad-connect-design/references/design-context.md +1 -1
  85. package/skills/yad-connect-design/references/design-registry.md +2 -2
  86. package/skills/yad-connect-docs/SKILL.md +12 -12
  87. package/skills/yad-connect-docs/references/docs-registry.md +1 -1
  88. package/skills/yad-connect-learning/SKILL.md +5 -5
  89. package/skills/yad-connect-learning/references/learning-registry.md +2 -2
  90. package/skills/yad-connect-repos/SKILL.md +92 -54
  91. package/skills/yad-connect-repos/references/code-context.md +6 -6
  92. package/skills/yad-connect-repos/references/hub-config.md +68 -58
  93. package/skills/yad-connect-repos/references/repos-registry.md +10 -9
  94. package/skills/yad-connect-repos/references/risk-map.md +81 -0
  95. package/skills/yad-connect-testing/SKILL.md +6 -6
  96. package/skills/yad-connect-testing/references/testing-context.md +3 -4
  97. package/skills/yad-connect-testing/references/testing-registry.md +2 -2
  98. package/skills/yad-defects/SKILL.md +8 -8
  99. package/skills/yad-discovery/SKILL.md +130 -94
  100. package/skills/yad-discovery/references/discovery-schema.md +23 -7
  101. package/skills/yad-discovery/references/foundation-schema.md +374 -0
  102. package/skills/yad-docs/SKILL.md +16 -11
  103. package/skills/yad-docs/references/data-mapping.md +9 -7
  104. package/skills/yad-docs/templates/app/package-lock.json +3 -3
  105. package/skills/yad-docs-overview/SKILL.md +32 -17
  106. package/skills/yad-docs-overview/references/pipeline-model.md +47 -28
  107. package/skills/yad-docs-sync/SKILL.md +10 -5
  108. package/skills/yad-docs-sync/references/staleness.md +8 -7
  109. package/skills/yad-engineer-review/SKILL.md +88 -24
  110. package/skills/yad-engineer-review/references/ship-and-record.md +25 -16
  111. package/skills/yad-epic/SKILL.md +178 -100
  112. package/skills/yad-epic/references/state-schema.md +626 -117
  113. package/skills/yad-hub-bridge/SKILL.md +66 -48
  114. package/skills/yad-hub-bridge/references/bridge.md +110 -83
  115. package/skills/yad-hub-bridge/references/login-roster.md +163 -70
  116. package/skills/yad-hub-bridge/templates/checks/hub-route.sh +22 -19
  117. package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +34 -14
  118. package/skills/yad-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +2 -2
  119. package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +22 -12
  120. package/skills/yad-implement/SKILL.md +29 -15
  121. package/skills/yad-implement/references/implement-conventions.md +2 -2
  122. package/skills/yad-learn/SKILL.md +9 -9
  123. package/skills/yad-learn/references/learning-state.md +2 -2
  124. package/skills/yad-open-pr/SKILL.md +64 -29
  125. package/skills/yad-pair-review/SKILL.md +18 -16
  126. package/skills/yad-pair-review/references/session-state.md +4 -4
  127. package/skills/yad-pr-template/SKILL.md +48 -27
  128. package/skills/yad-pr-template/references/risk-routing.md +97 -24
  129. package/skills/yad-pr-template/templates/checks/pr-template.sh +37 -15
  130. package/skills/yad-pr-template/templates/checks/pr-title.sh +27 -13
  131. package/skills/yad-pr-template/templates/checks/risk-route.sh +107 -14
  132. package/skills/yad-pr-template/templates/github/pull_request_template.md +7 -5
  133. package/skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md +7 -5
  134. package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +15 -14
  135. package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +15 -13
  136. package/skills/yad-reconcile/SKILL.md +3 -3
  137. package/skills/yad-report/SKILL.md +5 -5
  138. package/skills/yad-review-companion/SKILL.md +12 -9
  139. package/skills/yad-review-gate/SKILL.md +198 -79
  140. package/skills/yad-review-gate/references/gating.md +230 -54
  141. package/skills/yad-run/SKILL.md +86 -56
  142. package/skills/yad-run/references/run-loop.md +67 -45
  143. package/skills/yad-ship/SKILL.md +18 -14
  144. package/skills/yad-spec/SKILL.md +31 -17
  145. package/skills/yad-spec/references/spec-handoff.md +17 -5
  146. package/skills/yad-status/SKILL.md +114 -56
  147. package/skills/yad-stories/SKILL.md +42 -27
  148. package/skills/yad-stories/references/story-schema.md +10 -9
  149. package/skills/yad-stub/SKILL.md +59 -48
  150. package/skills/yad-sync-repos/SKILL.md +3 -3
  151. package/skills/yad-test-cases/SKILL.md +37 -30
  152. package/skills/yad-test-cases/references/test-cases-schema.md +8 -5
  153. package/skills/yad-timeline/SKILL.md +8 -7
  154. package/skills/yad-ui/SKILL.md +46 -25
  155. package/cli/roster.mjs +0 -164
  156. package/skills/sdlc/install.sh +0 -68
package/cli/docs.mjs CHANGED
@@ -12,7 +12,7 @@ import path from 'node:path';
12
12
  import fs from 'node:fs';
13
13
  import { createHash } from 'node:crypto';
14
14
  import { c, log, ok, info, warn, hand, fail, readJSON, run, has, exists } from './lib.mjs';
15
- import { PROJECT_FILES, VERSION } from './manifest.mjs';
15
+ import { PROJECT_FILES } from './manifest.mjs';
16
16
  import { detectPlatform, platformReady } from './platform.mjs';
17
17
  import { gitHead } from './setup.mjs';
18
18
  import { contractSurfaceHash } from './epic-state.mjs';
@@ -95,8 +95,33 @@ export function repoHeadsFor(root, repos = [], registry = { repos: [] }) {
95
95
  return out;
96
96
  }
97
97
 
98
+ // The version of the docs shell a per-epic site is copied from: the `version` in the template's own
99
+ // package.json. It moves only when the shell changes — cli/test.mjs pins it to a fingerprint of the
100
+ // template, so an edit to the shell cannot land without a new version.
101
+ //
102
+ // It replaced the yad CLI VERSION here, which moves on EVERY release: each publish marked every docs site
103
+ // "doc shell upgraded" whether or not the shell had changed. A manifest written before this carries that
104
+ // CLI version as `templateVersion`, which is never compared. What such a manifest is read as instead is
105
+ // LEGACY_SHELL_VERSION, below.
106
+ //
107
+ // Read when a freshness check asks, never at import: every `yad` command loads this module, and a copy of
108
+ // the CLI without `skills/` beside it (a test harness, a partial checkout) must still start. No template
109
+ // found means no shell version, so that one comparison is skipped.
110
+ export function shellVersion() {
111
+ try {
112
+ return JSON.parse(fs.readFileSync(new URL('../skills/yad-docs/templates/app/package.json', import.meta.url), 'utf8')).version || null;
113
+ } catch { return null; }
114
+ }
115
+
116
+ // The shell version every site built before `shellVersion` existed was built on. Exact, not a guess: the
117
+ // template's package.json has said `0.0.0` since its first commit, so a manifest that records only the old
118
+ // `templateVersion` came from that shell. Reading it this way reports nothing today, and still reports
119
+ // the first real shell upgrade — ignoring the old manifest instead would have missed that for ever,
120
+ // because only a skill rewrites a manifest and nothing prompts one to.
121
+ export const LEGACY_SHELL_VERSION = '0.0.0';
122
+
98
123
  // Compare a build manifest to the current world; list the concrete reasons it is stale.
99
- export function docsStale(manifest, { artifactHash, repoHeads = {}, templateVersion } = {}) {
124
+ export function docsStale(manifest, { artifactHash, repoHeads = {}, shellVersion } = {}) {
100
125
  const reasons = [];
101
126
  if (!manifest) return { stale: true, reasons: ['never built'] };
102
127
  if (artifactHash && manifest.artifactHash && artifactHash !== manifest.artifactHash) {
@@ -106,8 +131,9 @@ export function docsStale(manifest, { artifactHash, repoHeads = {}, templateVers
106
131
  const was = (manifest.repoHeads || {})[repo];
107
132
  if (head && was && head !== was) reasons.push(`repo ${repo} HEAD advanced`);
108
133
  }
109
- if (templateVersion && manifest.templateVersion && templateVersion !== manifest.templateVersion) {
110
- reasons.push(`doc shell upgraded (${manifest.templateVersion} → ${templateVersion})`);
134
+ const builtOn = manifest.shellVersion || (manifest.templateVersion ? LEGACY_SHELL_VERSION : null);
135
+ if (shellVersion && builtOn && shellVersion !== builtOn) {
136
+ reasons.push(`doc shell upgraded (${builtOn} → ${shellVersion})`);
111
137
  }
112
138
  return { stale: reasons.length > 0, reasons };
113
139
  }
@@ -181,16 +207,36 @@ export function pagesWorkflowPath(platform) {
181
207
  }
182
208
 
183
209
  // ---- build (subprocess) -------------------------------------------------------------------------
184
- function buildSite(dir) {
185
- if (!exists(dir)) { warn(`no generated site at ${path.relative(process.cwd(), dir)} — run the yad-docs skill first`); return { ok: false, missing: true }; }
186
- if (!has('npm')) { warn('npm not on PATH — cannot build; the CI workflow will build on push'); return { ok: false, noNpm: true }; }
210
+ // Builds one site and says what happened: `{ site, built, error }`, where `error` is why it was not
211
+ // built (null when it was). Every caller — build, deploy, sync --refresh — reads that one row, so a
212
+ // failure is said the same way on each path. A failure sets exit 1 and is a `fail` with its `hand`
213
+ // right under it: under --json that pair IS the refusal, and a later `hand` (deploy's "no Pages
214
+ // platform") can never become a failed build's hint. The one miss that is only a warning is npm not on
215
+ // PATH for a deploy or a refresh, which the CI workflow builds on push (`npmOptional`).
216
+ function buildSite(root, t, { npmOptional = false } = {}) {
217
+ const dir = siteDir(root, t);
218
+ const rel = path.relative(root, dir) || '.';
219
+ const site = label(t);
220
+ const failed = (error, next) => {
221
+ fail(error);
222
+ hand(next);
223
+ process.exitCode = 1;
224
+ return { site, built: false, error };
225
+ };
226
+ if (!exists(dir)) return failed(`${site}: no generated site at ${rel}`, `run the ${t.overview ? 'yad-docs-overview' : 'yad-docs'} skill first`);
227
+ if (!has('npm')) {
228
+ const error = `${site}: npm not on PATH — cannot build`;
229
+ if (!npmOptional) return failed(error, 'install npm (or put it on PATH), then run the command again');
230
+ warn(`${error}; the CI workflow will build on push`);
231
+ return { site, built: false, error };
232
+ }
187
233
  const install = exists(path.join(dir, 'package-lock.json')) ? ['ci'] : ['install'];
188
- log(` ${c.dim('$')} npm ${install[0]} ${c.dim(`(${path.relative(process.cwd(), dir)})`)}`);
189
- const i = run('npm', install, { cwd: dir, stdio: 'inherit' });
190
- if (!i.ok) { fail('npm install failed'); return { ok: false }; }
191
- const b = run('npm', ['run', 'build'], { cwd: dir, stdio: 'inherit' });
192
- if (!b.ok) { fail('npm run build failed'); return { ok: false }; }
193
- return { ok: true, dist: path.join(dir, 'dist') };
234
+ const again = 'fix the error npm printed above, then run the command again';
235
+ log(` ${c.dim('$')} npm ${install[0]} ${c.dim(`(${rel})`)}`);
236
+ if (!run('npm', install, { cwd: dir, stdio: 'inherit' }).ok) return failed(`${site}: npm ${install[0]} failed`, again);
237
+ if (!run('npm', ['run', 'build'], { cwd: dir, stdio: 'inherit' }).ok) return failed(`${site}: npm run build failed`, again);
238
+ ok(`built ${site} ${c.dim('→ ' + path.relative(root, path.join(dir, 'dist')))}`);
239
+ return { site, built: true, error: null };
194
240
  }
195
241
 
196
242
  // ---- orchestration ------------------------------------------------------------------------------
@@ -205,49 +251,57 @@ export async function runDocs(root, { action = 'list', epic, overview, sync } =
205
251
  info(`target ${c.cyan(docs.target)} scope ${docs.scope} base ${docs.basePath} ${docs.source === 'unavailable' ? c.yellow('(build-only)') : ''}`);
206
252
  }
207
253
  log(c.bold('\ngenerated sites'));
208
- if (!targets.length) { info('none generated yet'); return { sites: 0 }; }
254
+ if (!targets.length) { info('none generated yet'); return { action, target: docs?.target ?? null, sites: [] }; }
209
255
  for (const t of targets) reportFreshness(root, t);
210
- return { sites: targets.length };
256
+ return { action, target: docs?.target ?? null, sites: targets.map((t) => label(t)) };
211
257
  }
212
258
 
213
259
  if (action === 'build' || action === 'deploy') {
214
- let built = 0;
215
- for (const t of targets) {
216
- const r = buildSite(siteDir(root, t));
217
- if (r.ok) { built++; ok(`built ${label(t)} ${c.dim('→ ' + path.relative(root, r.dist))}`); }
218
- }
260
+ // Every site is tried, even after one fails; the run exits 1 if any did (buildSite sets it).
261
+ const sites = targets.map((t) => buildSite(root, t, { npmOptional: action === 'deploy' }));
262
+ const built = sites.filter((s) => s.built).length;
219
263
  if (action === 'deploy') {
220
264
  const platform = docs ? (docs.target === 'gitlab-pages' ? 'gitlab' : docs.target === 'github-pages' ? 'github' : null) : null;
265
+ // Both arms say the same thing about what was built here; only a run where every site built ends on ✓.
266
+ const all = built > 0 && built === sites.length;
267
+ const here = all ? null : built ? `only ${built} of ${sites.length} sites built here` : 'nothing was built here';
221
268
  if (!platform || !platformReady(platform)) {
222
- hand('no Pages platform/CLI — built locally only; commit + push so the CI workflow can publish (yad docs sync --wire)');
269
+ hand(`no Pages platform/CLI — ${here ?? 'built locally only'}; commit + push so the CI workflow can publish (yad docs sync --wire)`);
223
270
  } else {
224
- ok(`deploy via the ${platform} Pages workflow on push (yad docs sync --wire installs it)`);
271
+ (all ? ok : info)(`${here ? `${here}; ` : ''}deploy via the ${platform} Pages workflow on push (yad docs sync --wire installs it)`);
225
272
  if (docs?.basePath) info(`will publish under ${docs.basePath}`);
226
273
  }
227
274
  }
228
- return { built };
275
+ return { action, built, sites };
229
276
  }
230
277
 
231
278
  if (action === 'sync') {
232
279
  if (sync === 'wire') return wirePages(root, docs);
233
280
  // check (default) + refresh both compute staleness; refresh additionally rebuilds.
234
281
  const registry = readJSON(path.join(root, PROJECT_FILES.reposRegistry), { repos: [] });
235
- let stale = 0;
282
+ // One row per site. `built` is null when no build was tried (a check, or a fresh site), so it never
283
+ // reads as a failed build; a refresh that could not build a stale site exits 1, as a deploy does.
284
+ const sites = [];
236
285
  for (const t of targets) {
237
286
  const s = freshness(root, t, registry);
238
287
  if (s.stale) {
239
- stale++;
240
288
  warn(`${label(t)} — ${c.yellow('stale')}: ${s.reasons.join('; ')}`);
241
- if (sync === 'refresh') buildSite(siteDir(root, t));
242
- } else ok(`${label(t)} ${c.dim('— fresh')}`);
289
+ const b = sync === 'refresh' ? buildSite(root, t, { npmOptional: true }) : { built: null, error: null };
290
+ sites.push({ site: label(t), stale: true, built: b.built, error: b.error });
291
+ } else {
292
+ ok(`${label(t)} ${c.dim('— fresh')}`);
293
+ sites.push({ site: label(t), stale: false, built: null, error: null });
294
+ }
243
295
  }
296
+ const stale = sites.filter((s) => s.stale).length;
297
+ const built = sites.filter((s) => s.built).length;
244
298
  if (stale && sync !== 'refresh') hand('regenerate content with the yad-docs / yad-docs-overview skill (the AI step), then `yad docs deploy`');
245
- return { stale };
299
+ return { action, sync, stale, built, sites };
246
300
  }
247
301
 
248
302
  fail(`unknown docs action: ${action} (list | build | deploy | sync)`);
249
303
  process.exitCode = 1;
250
- return {};
304
+ return { action };
251
305
  }
252
306
 
253
307
  // ---- helpers ------------------------------------------------------------------------------------
@@ -267,10 +321,12 @@ function label(t) { return t.overview ? 'overview (docs/sdlc-site)' : `epic ${t.
267
321
  function freshness(root, t, registry) {
268
322
  const manifest = readJSON(manifestPath(root, t), null);
269
323
  if (t.overview) {
270
- // The overview is generated from the project pipeline definition, not an epic's artifacts.
324
+ // The overview is generated from the project pipeline definition, not an epic's artifacts. No shell
325
+ // version: the overview is copied from the shell once and updated in place after that
326
+ // (yad-docs-overview, Step 3), so a shell upgrade is not a reason to rebuild it.
271
327
  const files = ['skills/sdlc/config.yaml', 'skills/sdlc/module-help.csv', 'docs/diagrams/sdlc-overview.mmd']
272
328
  .map((f) => path.join(root, f)).filter(exists);
273
- return docsStale(manifest, { artifactHash: docsArtifactHash(files), templateVersion: VERSION });
329
+ return docsStale(manifest, { artifactHash: docsArtifactHash(files) });
274
330
  }
275
331
  const epicMeta = readJSON(path.join(root, 'epics', t.epic, '.sdlc/state.json'), {});
276
332
  const repos = epicMeta.repos || [];
@@ -278,7 +334,7 @@ function freshness(root, t, registry) {
278
334
  return docsStale(manifest, {
279
335
  artifactHash: docsArtifactHash(docsArtifactFiles(root, t.epic), surface || ''),
280
336
  repoHeads: repoHeadsFor(root, repos, registry),
281
- templateVersion: VERSION,
337
+ shellVersion: shellVersion(),
282
338
  });
283
339
  }
284
340
  function reportFreshness(root, t) {