yadflow 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +137 -134
  3. package/bin/{sdlc.mjs → yad.mjs} +18 -17
  4. package/cli/commit.mjs +3 -3
  5. package/cli/epic-state.mjs +2 -2
  6. package/cli/gate.mjs +8 -8
  7. package/cli/lib.mjs +1 -1
  8. package/cli/manifest.mjs +77 -36
  9. package/cli/openpr.mjs +3 -3
  10. package/cli/plan.mjs +88 -1
  11. package/cli/platform.mjs +2 -2
  12. package/cli/reconcile.mjs +18 -10
  13. package/cli/repo.mjs +5 -5
  14. package/cli/setup.mjs +7 -7
  15. package/docs/index.html +1227 -0
  16. package/package.json +10 -7
  17. package/skills/sdlc/config.yaml +24 -24
  18. package/skills/sdlc/install.sh +2 -2
  19. package/skills/sdlc/module-help.csv +16 -16
  20. package/skills/{sdlc-author-analysis → yad-analysis}/SKILL.md +12 -12
  21. package/skills/{sdlc-author-architecture → yad-architecture}/SKILL.md +12 -12
  22. package/skills/{sdlc-author-architecture → yad-architecture}/references/contract-format.md +1 -1
  23. package/skills/{sdlc-backfill → yad-backfill}/SKILL.md +4 -4
  24. package/skills/{sdlc-backfill → yad-backfill}/references/backfill.md +2 -2
  25. package/skills/{sdlc-backfill → yad-backfill}/templates/checks/backfill-check.sh +1 -1
  26. package/skills/{sdlc-checks → yad-checks}/SKILL.md +20 -20
  27. package/skills/{sdlc-checks → yad-checks}/references/check-gates.md +21 -21
  28. package/skills/{sdlc-checks → yad-checks}/templates/checks/contract-check.sh +2 -2
  29. package/skills/{sdlc-checks → yad-checks}/templates/checks/verified-commits.sh +2 -2
  30. package/skills/{sdlc-checks/templates/github/sdlc-checks.yml → yad-checks/templates/github/yad-checks.yml} +3 -3
  31. package/skills/{sdlc-checks/templates/github/sdlc-verified-commits.yml → yad-checks/templates/github/yad-verified-commits.yml} +4 -4
  32. package/skills/{sdlc-checks → yad-checks}/templates/gitlab/gitlab-ci.include-root.yml +3 -3
  33. package/skills/{sdlc-checks/templates/gitlab/sdlc-checks.gitlab-ci.yml → yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml} +7 -7
  34. package/skills/{sdlc-checks/templates/gitlab/sdlc-verified-commits.gitlab-ci.yml → yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml} +4 -4
  35. package/skills/{sdlc-connect-repos → yad-connect-repos}/SKILL.md +7 -7
  36. package/skills/{sdlc-connect-repos → yad-connect-repos}/references/code-context.md +6 -6
  37. package/skills/{sdlc-connect-repos → yad-connect-repos}/references/hub-config.md +3 -3
  38. package/skills/{sdlc-author-epic → yad-epic}/SKILL.md +13 -13
  39. package/skills/{sdlc-author-epic → yad-epic}/references/state-schema.md +13 -13
  40. package/skills/{sdlc-hub-bridge → yad-hub-bridge}/SKILL.md +24 -24
  41. package/skills/{sdlc-hub-bridge → yad-hub-bridge}/references/bridge.md +11 -11
  42. package/skills/{sdlc-hub-bridge → yad-hub-bridge}/references/login-roster.md +2 -2
  43. package/skills/{sdlc-hub-bridge → yad-hub-bridge}/templates/checks/hub-route.sh +3 -3
  44. package/skills/{sdlc-hub-bridge/templates/github/sdlc-gate-sync.yml → yad-hub-bridge/templates/github/yad-gate-sync.yml} +10 -10
  45. package/skills/{sdlc-hub-bridge → yad-hub-bridge}/templates/gitlab/gitlab-ci.include-root.yml +3 -3
  46. package/skills/{sdlc-hub-bridge/templates/gitlab/sdlc-gate-sync.gitlab-ci.yml → yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml} +11 -11
  47. package/skills/{sdlc-implement → yad-implement}/SKILL.md +14 -14
  48. package/skills/{sdlc-implement → yad-implement}/references/implement-conventions.md +4 -4
  49. package/skills/{sdlc-pr-template → yad-pr-template}/SKILL.md +11 -11
  50. package/skills/{sdlc-pr-template → yad-pr-template}/references/risk-routing.md +5 -5
  51. package/skills/{sdlc-pr-template → yad-pr-template}/templates/checks/risk-route.sh +2 -2
  52. package/skills/{sdlc-pr-template → yad-pr-template}/templates/github/pull_request_template.md +1 -1
  53. package/skills/{sdlc-pr-template → yad-pr-template}/templates/gitlab/merge_request_templates/Default.md +1 -1
  54. package/skills/{sdlc-pr-template → yad-pr-template}/templates/hub/github/pull_request_template.md +4 -4
  55. package/skills/{sdlc-pr-template → yad-pr-template}/templates/hub/gitlab/merge_request_templates/Default.md +4 -4
  56. package/skills/{sdlc-review-comments → yad-review-comments}/SKILL.md +6 -6
  57. package/skills/{sdlc-review-comments → yad-review-comments}/references/comment-conventions.md +6 -6
  58. package/skills/{sdlc-review-comments → yad-review-comments}/templates/github/REVIEW_COMMENTS.md +2 -2
  59. package/skills/{sdlc-review-comments → yad-review-comments}/templates/gitlab/REVIEW_COMMENTS.md +2 -2
  60. package/skills/{sdlc-review-gate → yad-review-gate}/SKILL.md +13 -13
  61. package/skills/{sdlc-review-gate → yad-review-gate}/references/gating.md +3 -3
  62. package/skills/{sdlc-run → yad-run}/SKILL.md +12 -12
  63. package/skills/{sdlc-run → yad-run}/references/run-loop.md +10 -10
  64. package/skills/{sdlc-ship → yad-ship}/SKILL.md +8 -8
  65. package/skills/{sdlc-ship → yad-ship}/references/ship-and-record.md +3 -3
  66. package/skills/{sdlc-ship → yad-ship}/templates/.coderabbit.yaml +1 -1
  67. package/skills/{sdlc-spec → yad-spec}/SKILL.md +11 -11
  68. package/skills/{sdlc-spec → yad-spec}/references/spec-handoff.md +2 -2
  69. package/skills/{sdlc-status → yad-status}/SKILL.md +6 -6
  70. package/skills/{sdlc-author-stories → yad-stories}/SKILL.md +10 -10
  71. package/skills/{sdlc-author-stories → yad-stories}/references/story-schema.md +1 -1
  72. package/skills/{sdlc-author-ui → yad-ui}/SKILL.md +9 -9
  73. /package/skills/{sdlc-checks → yad-checks}/templates/checks/build-test-lint.sh +0 -0
  74. /package/skills/{sdlc-checks → yad-checks}/templates/checks/spec-link.sh +0 -0
  75. /package/skills/{sdlc-checks → yad-checks}/templates/gitlab/.gitlab-ci.yml +0 -0
  76. /package/skills/{sdlc-connect-repos → yad-connect-repos}/references/repos-registry.md +0 -0
  77. /package/skills/{sdlc-implement → yad-implement}/templates/.gitmessage +0 -0
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // `sdlc` — setup/maintenance + the PR-driven review gate + build helpers for the SDLC module.
2
+ // `yad` — setup/maintenance + the PR-driven review gate + build helpers for the SDLC module.
3
3
  import { VERSION } from '../cli/manifest.mjs';
4
4
  import { c, log, closePrompts } from '../cli/lib.mjs';
5
5
  import { runSetup } from '../cli/setup.mjs';
@@ -10,28 +10,29 @@ import { runCommit } from '../cli/commit.mjs';
10
10
  import { runOpenPr } from '../cli/openpr.mjs';
11
11
  import { runRepo } from '../cli/repo.mjs';
12
12
 
13
- const HELP = `${c.bold('sdlc')} — setup, review-gate & build helpers for the SDLC Workflow module ${c.dim('v' + VERSION)}
13
+ const HELP = `${c.bold('yad')} — setup, review-gate & build helpers for the SDLC Workflow module ${c.dim('v' + VERSION)}
14
14
 
15
15
  ${c.bold('Setup & maintenance')}
16
- sdlc setup Guided first-run setup (install module, connect & wire repos)
17
- sdlc check Report what is missing / drifted / stale (read-only)
18
- sdlc check --fix Reconcile: fill what is missing, update what changed
19
- sdlc update Apply drift only (alias for: check --fix --scope=changed)
16
+ yad setup Guided first-run setup (install module, connect & wire repos)
17
+ yad check Report what is missing / drifted / stale / legacy (read-only)
18
+ yad check --fix Reconcile: fill what is missing, update what changed
19
+ yad update Apply drift only (alias for: check --fix --scope=changed);
20
+ also migrates pre-2.0 sdlc-* installs to the yad-* names
20
21
 
21
22
  ${c.bold('Review gate (front half)')}
22
- sdlc gate open <epic> <artifact> Open the review PR/MR; mark the step in_review
23
- sdlc gate sync <epic> [artifact] Pull PR state -> ledger; advance on approved+resolved+merged
24
- sdlc gate comments <epic> [artifact] Fetch unresolved review comments to address
25
- sdlc gate status <epic> Show each review step + approvals
26
- sdlc gate ci [--branch <head>] [--pr <n>]
23
+ yad gate open <epic> <artifact> Open the review PR/MR; mark the step in_review
24
+ yad gate sync <epic> [artifact] Pull PR state -> ledger; advance on approved+resolved+merged
25
+ yad gate comments <epic> [artifact] Fetch unresolved review comments to address
26
+ yad gate status <epic> Show each review step + approvals
27
+ yad gate ci [--branch <head>] [--pr <n>]
27
28
  CI entry (hub workflow): derive epic/artifact from the review branch,
28
29
  sync, commit the ledger to the default branch (sweep all PRs if no --branch)
29
30
 
30
31
  ${c.bold('Build helpers')}
31
- sdlc commit --type <t> -m <subject> Commit by convention (trailers, atomic guard)
32
- sdlc open-pr [--repo <name>] Open a code-repo task PR/MR from the template
33
- sdlc repo list Show connected repos (fresh / stale)
34
- sdlc repo refresh [name] Re-pack a stale repo (a human decision)
32
+ yad commit --type <t> -m <subject> Commit by convention (trailers, atomic guard)
33
+ yad open-pr [--repo <name>] Open a code-repo task PR/MR from the template
34
+ yad repo list Show connected repos (fresh / stale)
35
+ yad repo refresh [name] Re-pack a stale repo (a human decision)
35
36
 
36
37
  ${c.bold('Options')}
37
38
  --dir <path> Target project root (default: cwd)
@@ -99,7 +100,7 @@ async function main() {
99
100
  const [, action, epic, artifact] = o._;
100
101
  // `gate ci` takes no positionals — epic/artifact come from --branch (or a sweep of all PRs).
101
102
  if (action === 'ci') { await gateCi(o.dir, { branch: o.branch, pr: o.pr, push: !o.noPush, today }); break; }
102
- if (!epic) { log(c.red('usage: sdlc gate <open|sync|comments|status|ci> <epic> [artifact]')); process.exitCode = 1; break; }
103
+ if (!epic) { log(c.red('usage: yad gate <open|sync|comments|status|ci> <epic> [artifact]')); process.exitCode = 1; break; }
103
104
  // The epic id becomes a path segment under epics/ — reject anything but EP-<slug> outright.
104
105
  if (!isValidEpicId(epic)) { log(c.red(`invalid epic id: ${epic} (expected EP-<slug>, [a-z0-9-] only)`)); process.exitCode = 1; break; }
105
106
  if (action === 'open') await gateOpen(o.dir, { epic, artifact, today });
@@ -129,7 +130,7 @@ async function main() {
129
130
 
130
131
  main()
131
132
  .catch((err) => {
132
- log(c.red(`\nsdlc failed: ${err?.message || err}`));
133
+ log(c.red(`\nyad failed: ${err?.message || err}`));
133
134
  process.exitCode = 1;
134
135
  })
135
136
  .finally(closePrompts);
package/cli/commit.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // `sdlc commit` — commit by the SDLC conventions (CONTRIBUTING.md / config.yaml build).
1
+ // `yad commit` — commit by the SDLC conventions (CONTRIBUTING.md / config.yaml build).
2
2
  // Subject is Conventional Commits; trailers are emitted in the fixed order
3
3
  // Task -> Contract-Change -> Co-Authored-By. The human git author OWNS the commit; the AI is only a
4
4
  // co-author (flagged with --ai, or `none` for human-only). An atomic-commit guard keeps diffs small.
@@ -36,7 +36,7 @@ export function taskFromBranch(branch = '') {
36
36
  }
37
37
 
38
38
  export async function runCommit(root, opts = {}) {
39
- log(c.bold('\nsdlc commit'));
39
+ log(c.bold('\nyad commit'));
40
40
  if (!exists(path.join(root, '.git'))) { fail('not a git repo'); process.exitCode = 1; return; }
41
41
 
42
42
  const staged = run('git', ['diff', '--cached', '--name-only'], { cwd: root }).stdout.split('\n').filter(Boolean);
@@ -70,7 +70,7 @@ export async function runCommit(root, opts = {}) {
70
70
  return { message };
71
71
  }
72
72
 
73
- // installed by sdlc-implement, but offer it here too for convenience.
73
+ // installed by yad-implement, but offer it here too for convenience.
74
74
  export function ensureGitMessage(repoRoot, templateSrc) {
75
75
  const dest = path.join(repoRoot, '.gitmessage');
76
76
  if (exists(dest)) return false;
@@ -55,7 +55,7 @@ export function upsertHubPr(hubPrs = [], rec) {
55
55
  }
56
56
 
57
57
  // SHA-256 of the contract surface block (architecture only). Mirrors
58
- // sdlc-author-architecture/references/contract-format.md (awk markers + sha256).
58
+ // yad-architecture/references/contract-format.md (awk markers + sha256).
59
59
  // Line endings are normalized to LF so the same surface hashes identically across
60
60
  // platforms (a CRLF re-save must not revoke approvals). A BEGIN without an END is
61
61
  // malformed and yields null — never a silent hash of everything to end-of-file.
@@ -194,7 +194,7 @@ export function gatePredicate({
194
194
  };
195
195
  }
196
196
 
197
- // Advance the step in state.json once the predicate passes. Mirrors sdlc-review-gate Step 3:
197
+ // Advance the step in state.json once the predicate passes. Mirrors yad-review-gate Step 3:
198
198
  // mark this review step done, unblock the next step, or set `ready-for-build` for the last one.
199
199
  export function advanceState(state, step) {
200
200
  const i = state.steps.findIndex((s) => s.id === step.id);
package/cli/gate.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // `sdlc gate open|sync|comments|status` — the PR/MR-driven front-half review gate.
1
+ // `yad gate open|sync|comments|status` — the PR/MR-driven front-half review gate.
2
2
  // The platform PR/MR is the review UI; this command syncs its state into the file ledger and, when
3
3
  // the gate passes (approvals satisfied + all comment threads resolved + PR merged), auto-advances the
4
4
  // step. The merge click is the human approval act, so front steps still never machine_advance.
@@ -159,7 +159,7 @@ export async function gateSync(root, { epic, artifact, today, reader = readPr }
159
159
 
160
160
  let { approvals, comments, hubPrs, state } = ledger;
161
161
  const targets = hubPrs.filter((p) => !artifact || p.artifact === artifact);
162
- if (!targets.length) { warn(`no open review PR recorded for ${epic}${artifact ? ` / ${artifact}` : ''} (run \`sdlc gate open\` first)`); return { synced: 0 }; }
162
+ if (!targets.length) { warn(`no open review PR recorded for ${epic}${artifact ? ` / ${artifact}` : ''} (run \`yad gate open\` first)`); return { synced: 0 }; }
163
163
 
164
164
  let synced = 0;
165
165
  for (const pr of targets) {
@@ -212,7 +212,7 @@ export async function gateSync(root, { epic, artifact, today, reader = readPr }
212
212
  return { synced };
213
213
  }
214
214
 
215
- // `sdlc gate ci` — the self-sufficient entry point hub CI calls on platform events (review
215
+ // `yad gate ci` — the self-sufficient entry point hub CI calls on platform events (review
216
216
  // submitted/dismissed, PR synchronize, PR merged) and on the GitLab schedule. Event mode derives
217
217
  // epic/artifact from the `review/EP-<slug>/<base>` head branch (so it works even when the author
218
218
  // never committed hub-prs.json); sweep mode (no --branch) re-syncs every open review PR. Either way
@@ -223,7 +223,7 @@ export async function gateCi(root, { branch, pr, today, push = true, reader = re
223
223
  if (!hub?.platform) { warn('no hub platform configured (.sdlc/hub.json) — nothing to sync'); return { synced: 0 }; }
224
224
  const git = (...args) => run('git', args, { cwd: root });
225
225
  // Push target: an explicit hub.default_branch wins; else the branch CI actually checked out (the
226
- // workflow checks out the PR base / $CI_DEFAULT_BRANCH — hub.json from `sdlc setup` has no
226
+ // workflow checks out the PR base / $CI_DEFAULT_BRANCH — hub.json from `yad setup` has no
227
227
  // default_branch field, so the checkout is the truth); 'main' only as the last resort.
228
228
  const head = git('rev-parse', '--abbrev-ref', 'HEAD').stdout;
229
229
  const target = hub.default_branch || (head && head !== 'HEAD' ? head : 'main');
@@ -343,7 +343,7 @@ export async function gateCi(root, { branch, pr, today, push = true, reader = re
343
343
  if (!git('pull', '--rebase', 'origin', target).ok) git('rebase', '--abort'); // never leave a wedged rebase
344
344
  }
345
345
  }
346
- fail(`could not push the ledger to origin/${target} — protected branch? allow the CI actor to push (see sdlc-hub-bridge references/bridge.md) or run \`sdlc gate sync\` locally`);
346
+ fail(`could not push the ledger to origin/${target} — protected branch? allow the CI actor to push (see yad-hub-bridge references/bridge.md) or run \`yad gate sync\` locally`);
347
347
  process.exitCode = 1;
348
348
  return { synced };
349
349
  }
@@ -354,7 +354,7 @@ export async function gateComments(root, { epic, artifact, today, reader = readP
354
354
  const epicDir = epicRoot(root, epic);
355
355
  const ledger = loadLedger(epicDir);
356
356
  const targets = (ledger.hubPrs || []).filter((p) => !artifact || p.artifact === artifact);
357
- if (!targets.length) { warn('no review PR recorded — run `sdlc gate open` first'); return; }
357
+ if (!targets.length) { warn('no review PR recorded — run `yad gate open` first'); return; }
358
358
  for (const pr of targets) {
359
359
  const pull = reader(hub.platform, pr.number, { cwd: root });
360
360
  if (!pull.ok) { warn(`${pr.artifact}: ${pull.reason}`); continue; }
@@ -391,7 +391,7 @@ export async function gateOpen(root, { epic, artifact, today } = {}) {
391
391
  const epicDir = epicRoot(root, epic);
392
392
  const ledger = loadLedger(epicDir);
393
393
  if (!ledger.state) { fail(`no epic state at ${epicDir}`); process.exitCode = 1; return; }
394
- if (!artifact) { fail('artifact is required: `sdlc gate open <epic> <artifact>`'); process.exitCode = 1; return; }
394
+ if (!artifact) { fail('artifact is required: `yad gate open <epic> <artifact>`'); process.exitCode = 1; return; }
395
395
  const step = findReviewStep(ledger.state, artifact);
396
396
  if (!step) { fail(`no review step for ${artifact}`); process.exitCode = 1; return; }
397
397
  const b = base(artifact);
@@ -416,7 +416,7 @@ export async function gateOpen(root, { epic, artifact, today } = {}) {
416
416
  ledger.hubPrs = upsertHubPr(ledger.hubPrs, { step: step.id, artifact, platform: hub.platform, number, url: r.url, branch, lastSyncedAt: null });
417
417
  writeJSON(ledger.files.hubPrs, ledger.hubPrs);
418
418
  ok(`opened ${r.url}`);
419
- hand(`reviewers approve/comment there; then run \`sdlc gate sync ${epic} ${artifact}\``);
419
+ hand(`reviewers approve/comment there; then run \`yad gate sync ${epic} ${artifact}\``);
420
420
  }
421
421
 
422
422
  // ---- helpers ------------------------------------------------------------------------------------
package/cli/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // Shared helpers for the `sdlc` CLI. Node >=18 built-ins only — no dependencies.
1
+ // Shared helpers for the `yad` CLI. Node >=18 built-ins only — no dependencies.
2
2
  import { createHash } from 'node:crypto';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import * as readline from 'node:readline/promises';
package/cli/manifest.mjs CHANGED
@@ -10,27 +10,68 @@ import { readFileSync } from 'node:fs';
10
10
  const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
11
11
  export const VERSION = version;
12
12
 
13
- // The 17 hand-authored sdlc-* skills (mirrors skills/sdlc/install.sh).
13
+ // The 17 hand-authored yad-* skills (mirrors skills/sdlc/install.sh).
14
14
  export const SKILLS = [
15
- 'sdlc-author-analysis',
16
- 'sdlc-author-epic',
17
- 'sdlc-author-architecture',
18
- 'sdlc-author-ui',
19
- 'sdlc-author-stories',
20
- 'sdlc-connect-repos',
21
- 'sdlc-spec',
22
- 'sdlc-implement',
23
- 'sdlc-checks',
24
- 'sdlc-pr-template',
25
- 'sdlc-review-comments',
26
- 'sdlc-hub-bridge',
27
- 'sdlc-ship',
28
- 'sdlc-backfill',
29
- 'sdlc-run',
30
- 'sdlc-review-gate',
31
- 'sdlc-status',
15
+ 'yad-analysis',
16
+ 'yad-epic',
17
+ 'yad-architecture',
18
+ 'yad-ui',
19
+ 'yad-stories',
20
+ 'yad-connect-repos',
21
+ 'yad-spec',
22
+ 'yad-implement',
23
+ 'yad-checks',
24
+ 'yad-pr-template',
25
+ 'yad-review-comments',
26
+ 'yad-hub-bridge',
27
+ 'yad-ship',
28
+ 'yad-backfill',
29
+ 'yad-run',
30
+ 'yad-review-gate',
31
+ 'yad-status',
32
32
  ];
33
33
 
34
+ // Pre-2.0 skill names (the sdlc-* -> yad-* rename). `check`/`update` migrate any install
35
+ // still carrying an old name: remove the old copy, install the renamed one.
36
+ export const LEGACY_SKILLS = {
37
+ 'yad-analysis': 'sdlc-author-analysis',
38
+ 'yad-epic': 'sdlc-author-epic',
39
+ 'yad-architecture': 'sdlc-author-architecture',
40
+ 'yad-ui': 'sdlc-author-ui',
41
+ 'yad-stories': 'sdlc-author-stories',
42
+ 'yad-connect-repos': 'sdlc-connect-repos',
43
+ 'yad-spec': 'sdlc-spec',
44
+ 'yad-implement': 'sdlc-implement',
45
+ 'yad-checks': 'sdlc-checks',
46
+ 'yad-pr-template': 'sdlc-pr-template',
47
+ 'yad-review-comments': 'sdlc-review-comments',
48
+ 'yad-hub-bridge': 'sdlc-hub-bridge',
49
+ 'yad-ship': 'sdlc-ship',
50
+ 'yad-backfill': 'sdlc-backfill',
51
+ 'yad-run': 'sdlc-run',
52
+ 'yad-review-gate': 'sdlc-review-gate',
53
+ 'yad-status': 'sdlc-status',
54
+ };
55
+
56
+ // Pre-2.0 wired-file dests replaced by renamed ones (old dest -> new dest, per platform).
57
+ // An old file is removed ONLY when its first line carries the old ownership marker —
58
+ // a same-named file the user authored themselves is never touched.
59
+ export const LEGACY_MARKER = '# sdlc-managed';
60
+ export const LEGACY_REPO_FILES = {
61
+ github: { '.github/workflows/sdlc-checks.yml': '.github/workflows/yad-checks.yml' },
62
+ gitlab: { '.gitlab/ci/sdlc-checks.yml': '.gitlab/ci/yad-checks.yml' },
63
+ };
64
+ export const LEGACY_HUB_FILES = {
65
+ github: {
66
+ '.github/workflows/sdlc-gate-sync.yml': '.github/workflows/yad-gate-sync.yml',
67
+ '.github/workflows/sdlc-verified-commits.yml': '.github/workflows/yad-verified-commits.yml',
68
+ },
69
+ gitlab: {
70
+ '.gitlab/ci/sdlc-gate-sync.yml': '.gitlab/ci/yad-gate-sync.yml',
71
+ '.gitlab/ci/sdlc-verified-commits.yml': '.gitlab/ci/yad-verified-commits.yml',
72
+ },
73
+ };
74
+
34
75
  // IDE install targets (relative to the target project root).
35
76
  export const IDE_FOLDER_TARGETS = ['.claude', '.agents', '.zencoder']; // <ide>/skills/<skill>/ (folder copy)
36
77
  export const IDE_OPENCODE_DIR = '.opencode/commands'; // <skill>.md (flat SKILL.md copy)
@@ -45,7 +86,7 @@ export const PROJECT_FILES = {
45
86
  version: '.sdlc/cli-version.json',
46
87
  };
47
88
 
48
- // ---- `sdlc commit` conventions (mirror skills/sdlc/config.yaml `build`) ----
89
+ // ---- `yad commit` conventions (mirror skills/sdlc/config.yaml `build`) ----
49
90
  // Conventional-commit types (config.yaml commit_subject_style).
50
91
  export const COMMIT_TYPES = ['feat', 'fix', 'docs', 'refactor', 'test', 'perf', 'build', 'ci', 'chore', 'revert'];
51
92
  // Per-commit AI co-author choices (config.yaml build.ai_coauthor.allowed). The human git author OWNS
@@ -77,21 +118,21 @@ export const epicFiles = (epicRoot) => ({
77
118
  // `common` always installs; the platform key installs by detected platform.
78
119
  export const REPO_WIRING = {
79
120
  common: [
80
- { src: 'skills/sdlc-checks/templates/checks/spec-link.sh', dest: 'checks/spec-link.sh', exec: true },
81
- { src: 'skills/sdlc-checks/templates/checks/contract-check.sh', dest: 'checks/contract-check.sh', exec: true },
82
- { src: 'skills/sdlc-checks/templates/checks/build-test-lint.sh', dest: 'checks/build-test-lint.sh', exec: true },
83
- { src: 'skills/sdlc-checks/templates/checks/verified-commits.sh', dest: 'checks/verified-commits.sh', exec: true },
84
- { src: 'skills/sdlc-pr-template/templates/checks/risk-route.sh', dest: 'checks/risk-route.sh', exec: true },
121
+ { src: 'skills/yad-checks/templates/checks/spec-link.sh', dest: 'checks/spec-link.sh', exec: true },
122
+ { src: 'skills/yad-checks/templates/checks/contract-check.sh', dest: 'checks/contract-check.sh', exec: true },
123
+ { src: 'skills/yad-checks/templates/checks/build-test-lint.sh', dest: 'checks/build-test-lint.sh', exec: true },
124
+ { src: 'skills/yad-checks/templates/checks/verified-commits.sh', dest: 'checks/verified-commits.sh', exec: true },
125
+ { src: 'skills/yad-pr-template/templates/checks/risk-route.sh', dest: 'checks/risk-route.sh', exec: true },
85
126
  ],
86
127
  github: [
87
- { src: 'skills/sdlc-checks/templates/github/sdlc-checks.yml', dest: '.github/workflows/sdlc-checks.yml' },
88
- { src: 'skills/sdlc-pr-template/templates/github/pull_request_template.md', dest: '.github/pull_request_template.md' },
89
- { src: 'skills/sdlc-review-comments/templates/github/REVIEW_COMMENTS.md', dest: '.github/REVIEW_COMMENTS.md' },
128
+ { src: 'skills/yad-checks/templates/github/yad-checks.yml', dest: '.github/workflows/yad-checks.yml' },
129
+ { src: 'skills/yad-pr-template/templates/github/pull_request_template.md', dest: '.github/pull_request_template.md' },
130
+ { src: 'skills/yad-review-comments/templates/github/REVIEW_COMMENTS.md', dest: '.github/REVIEW_COMMENTS.md' },
90
131
  ],
91
132
  gitlab: [
92
- { src: 'skills/sdlc-checks/templates/gitlab/sdlc-checks.gitlab-ci.yml', dest: '.gitlab/ci/sdlc-checks.yml' },
93
- { src: 'skills/sdlc-pr-template/templates/gitlab/merge_request_templates/Default.md', dest: '.gitlab/merge_request_templates/Default.md' },
94
- { src: 'skills/sdlc-review-comments/templates/gitlab/REVIEW_COMMENTS.md', dest: '.gitlab/REVIEW_COMMENTS.md' },
133
+ { src: 'skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml', dest: '.gitlab/ci/yad-checks.yml' },
134
+ { src: 'skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md', dest: '.gitlab/merge_request_templates/Default.md' },
135
+ { src: 'skills/yad-review-comments/templates/gitlab/REVIEW_COMMENTS.md', dest: '.gitlab/REVIEW_COMMENTS.md' },
95
136
  ],
96
137
  };
97
138
 
@@ -102,18 +143,18 @@ export const wiringFor = (platform) => [
102
143
 
103
144
  // Hub wiring: CI installed on the PRODUCT HUB itself (dest is the project root — the hub IS the
104
145
  // root). Installed only when hub.json has a platform and the bridge is enabled. Carries the
105
- // event-driven gate sync (approvals/change requests/the merge trigger `sdlc gate ci`) and the
146
+ // event-driven gate sync (approvals/change requests/the merge trigger `yad gate ci`) and the
106
147
  // verified-commits gate (no unverified commits from unverified users reach merge on the hub).
107
148
  export const HUB_WIRING = {
108
149
  common: [
109
- { src: 'skills/sdlc-checks/templates/checks/verified-commits.sh', dest: 'checks/verified-commits.sh', exec: true },
150
+ { src: 'skills/yad-checks/templates/checks/verified-commits.sh', dest: 'checks/verified-commits.sh', exec: true },
110
151
  ],
111
152
  github: [
112
- { src: 'skills/sdlc-hub-bridge/templates/github/sdlc-gate-sync.yml', dest: '.github/workflows/sdlc-gate-sync.yml' },
113
- { src: 'skills/sdlc-checks/templates/github/sdlc-verified-commits.yml', dest: '.github/workflows/sdlc-verified-commits.yml' },
153
+ { src: 'skills/yad-hub-bridge/templates/github/yad-gate-sync.yml', dest: '.github/workflows/yad-gate-sync.yml' },
154
+ { src: 'skills/yad-checks/templates/github/yad-verified-commits.yml', dest: '.github/workflows/yad-verified-commits.yml' },
114
155
  ],
115
156
  gitlab: [
116
- { src: 'skills/sdlc-hub-bridge/templates/gitlab/sdlc-gate-sync.gitlab-ci.yml', dest: '.gitlab/ci/sdlc-gate-sync.yml' },
117
- { src: 'skills/sdlc-checks/templates/gitlab/sdlc-verified-commits.gitlab-ci.yml', dest: '.gitlab/ci/sdlc-verified-commits.yml' },
157
+ { src: 'skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml', dest: '.gitlab/ci/yad-gate-sync.yml' },
158
+ { src: 'skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml', dest: '.gitlab/ci/yad-verified-commits.yml' },
118
159
  ],
119
160
  };
package/cli/openpr.mjs CHANGED
@@ -1,6 +1,6 @@
1
- // `sdlc 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 half).
2
2
  // Detects the platform, pushes the current branch, and creates the PR/MR with Summary / Story-task /
3
- // Impact & Risk prefilled. Distinct from `sdlc gate open`, which opens a front-half artifact-review PR
3
+ // Impact & Risk prefilled. Distinct from `yad gate open`, which opens a front-half artifact-review PR
4
4
  // on the product hub.
5
5
  import path from 'node:path';
6
6
  import fs from 'node:fs';
@@ -33,7 +33,7 @@ function templateBody(repoRoot, platform, { task, risk, contract, domains }) {
33
33
  }
34
34
 
35
35
  export async function runOpenPr(root, opts = {}) {
36
- log(c.bold('\nsdlc open-pr'));
36
+ log(c.bold('\nyad open-pr'));
37
37
  const { repoRoot, meta } = resolveRepo(root, opts);
38
38
  if (!exists(path.join(repoRoot, '.git'))) { fail(`not a git repo: ${repoRoot}`); process.exitCode = 1; return; }
39
39
 
package/cli/plan.mjs CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  } from './lib.mjs';
9
9
  import {
10
10
  SKILLS, IDE_FOLDER_TARGETS, IDE_OPENCODE_DIR, MODULE_FILES, wiringFor, HUB_WIRING, PROJECT_FILES,
11
+ LEGACY_SKILLS, LEGACY_MARKER, LEGACY_REPO_FILES, LEGACY_HUB_FILES,
11
12
  } from './manifest.mjs';
12
13
 
13
14
  // status: 'ok' | 'missing' | 'outdated'
@@ -65,6 +66,92 @@ export function moduleActions(root, ideTargets = ideTargetsFor(root)) {
65
66
  return actions;
66
67
  }
67
68
 
69
+ // Migration of a pre-2.0 install (the sdlc-* -> yad-* rename). Status is 'legacy' — unlike
70
+ // 'missing' it is applied by `yad update` (--scope=changed) too, because the skill IS
71
+ // installed, just under its old name. apply() removes the old copy AND installs the renamed
72
+ // one, so a single update completes the rename even when the new copy would otherwise be
73
+ // skipped as missing-scope.
74
+ export function legacyModuleActions(root, ideTargets = ideTargetsFor(root)) {
75
+ const actions = [];
76
+ for (const ide of ideTargets) {
77
+ for (const [skill, old] of Object.entries(LEGACY_SKILLS)) {
78
+ if (ide === '.opencode') {
79
+ const oldDest = path.join(root, IDE_OPENCODE_DIR, `${old}.md`);
80
+ if (!exists(oldDest)) continue;
81
+ actions.push({
82
+ scope: ide,
83
+ item: `${old}.md → ${skill}.md`,
84
+ status: 'legacy',
85
+ apply: () => {
86
+ fs.rmSync(oldDest, { force: true });
87
+ copyFile(asset('skills', skill, 'SKILL.md'), path.join(root, IDE_OPENCODE_DIR, `${skill}.md`));
88
+ },
89
+ });
90
+ } else {
91
+ const oldDest = path.join(root, ide, 'skills', old);
92
+ if (!exists(oldDest)) continue;
93
+ actions.push({
94
+ scope: ide,
95
+ item: `${old} → ${skill}`,
96
+ status: 'legacy',
97
+ apply: () => {
98
+ fs.rmSync(oldDest, { recursive: true, force: true });
99
+ copyDir(asset('skills', skill), path.join(root, ide, 'skills', skill));
100
+ },
101
+ });
102
+ }
103
+ }
104
+ }
105
+ return actions;
106
+ }
107
+
108
+ // True only for a file WE installed pre-2.0: its first line carries the old ownership marker
109
+ // (`# sdlc-managed:` / `# sdlc-managed-include:`). A same-named user-authored file is never ours.
110
+ function ownedByOldInstall(p) {
111
+ try { return fs.readFileSync(p, 'utf8').startsWith(LEGACY_MARKER); } catch { return false; }
112
+ }
113
+
114
+ // old-dest -> new-dest migrations for wired CI files: remove the marker-owned old file and
115
+ // install its renamed replacement from the current wiring. GitLab fragments are referenced by
116
+ // path from the root `.gitlab-ci.yml` (`include: - local: ...`, written by the wire step), so
117
+ // the migration must also rewrite that include — otherwise the pipeline hard-fails on a
118
+ // `local file does not exist` the moment the old fragment is removed.
119
+ function legacyFileActions(scope, baseRoot, fileMap, wiring) {
120
+ const actions = [];
121
+ for (const [oldDest, newDest] of Object.entries(fileMap || {})) {
122
+ const oldPath = path.join(baseRoot, oldDest);
123
+ if (!ownedByOldInstall(oldPath)) continue;
124
+ const w = wiring.find((x) => x.dest === newDest);
125
+ if (!w) continue; // never delete a working file without a replacement to install
126
+ actions.push({
127
+ scope,
128
+ item: `${oldDest} → ${newDest}`,
129
+ status: 'legacy',
130
+ apply: () => {
131
+ fs.rmSync(oldPath, { force: true });
132
+ copyFile(asset(w.src), path.join(baseRoot, newDest), { exec: !!w.exec });
133
+ const rootCi = path.join(baseRoot, '.gitlab-ci.yml');
134
+ try {
135
+ const txt = fs.readFileSync(rootCi, 'utf8');
136
+ if (txt.includes(oldDest)) fs.writeFileSync(rootCi, txt.split(oldDest).join(newDest));
137
+ } catch { /* no root .gitlab-ci.yml (github repo, or fragment-only gitlab) — nothing to rewrite */ }
138
+ },
139
+ });
140
+ }
141
+ return actions;
142
+ }
143
+
144
+ export function legacyRepoActions(root, repo) {
145
+ return legacyFileActions(repo.name, path.resolve(root, repo.path), LEGACY_REPO_FILES[repo.platform], wiringFor(repo.platform));
146
+ }
147
+
148
+ export function legacyHubActions(root) {
149
+ const hub = readJSON(path.join(root, PROJECT_FILES.hubConfig));
150
+ if (!hub?.platform || !(hub.bridge_enabled === true || hub.bridge === true)) return [];
151
+ const wiring = [...HUB_WIRING.common, ...(HUB_WIRING[hub.platform] || [])];
152
+ return legacyFileActions('hub', root, LEGACY_HUB_FILES[hub.platform], wiring);
153
+ }
154
+
68
155
  // Per-repo wiring (gate scripts, CI, PR template, comment scaffold).
69
156
  export function repoActions(root, repo) {
70
157
  const repoRoot = path.resolve(root, repo.path);
@@ -107,7 +194,7 @@ export function authorsActions(root, repos = []) {
107
194
  const emails = verifiedAuthorEmails(hub);
108
195
  if (!emails.length) return [];
109
196
  const desired = [
110
- '# Generated by `sdlc check --fix` from .sdlc/hub.json (roster emails + verified_authors).',
197
+ '# Generated by `yad check --fix` from .sdlc/hub.json (roster emails + verified_authors).',
111
198
  '# The verified-commits gate accepts only these author emails. Edit hub.json, not this file.',
112
199
  ...emails,
113
200
  ].join('\n') + '\n';
package/cli/platform.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // Platform adapter — the ONLY place that shells out to gh/glab. Read recipes mirror
2
- // skills/sdlc-hub-bridge/references/bridge.md. Everything runs as the local user (gh/glab own auth);
2
+ // skills/yad-hub-bridge/references/bridge.md. Everything runs as the local user (gh/glab own auth);
3
3
  // no tokens are stored. Pure mapping fns (resolveLogin/mapApprovers) are exported for unit tests;
4
4
  // readPr is injectable so the gate can be tested with a fake.
5
5
  import { run, has } from './lib.mjs';
@@ -23,7 +23,7 @@ export function platformReady(platform) {
23
23
  return !!cli && has(cli);
24
24
  }
25
25
 
26
- // ---- login -> sdlc identity (roster + derived domain-owner) -------------------------------------
26
+ // ---- login -> yad identity (roster + derived domain-owner) -------------------------------------
27
27
  // Returns the records this login's APPROVED review contributes. A roster reviewer who owns a touched
28
28
  // repo's domain contributes BOTH a base record and a domain-owner record (bridge.md "Login -> role").
29
29
  export function resolveLogin(login, roster = [], repos = [], touchedDomains = []) {
package/cli/reconcile.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // `sdlc check` (report) and `sdlc check --fix` (reconcile) — and `sdlc update`
1
+ // `yad check` (report) and `yad check --fix` (reconcile) — and `yad update`
2
2
  // as a thin alias (--scope=changed). Inspects actual project state against the
3
3
  // manifest: missing setup, drifted files, stale code-context.
4
4
  import path from 'node:path';
@@ -6,10 +6,13 @@ import {
6
6
  c, log, ok, info, warn, hand, fail, readJSON, writeJSON, exists,
7
7
  } from './lib.mjs';
8
8
  import { VERSION, PROJECT_FILES } from './manifest.mjs';
9
- import { moduleActions, repoActions, hubActions, authorsActions } from './plan.mjs';
9
+ import {
10
+ moduleActions, repoActions, hubActions, authorsActions,
11
+ legacyModuleActions, legacyRepoActions, legacyHubActions,
12
+ } from './plan.mjs';
10
13
  import { gitHead, packRepo } from './setup.mjs';
11
14
 
12
- const MARK = { missing: c.red('missing'), outdated: c.yellow('outdated'), stale: c.yellow('stale'), ok: c.green('ok') };
15
+ const MARK = { missing: c.red('missing'), outdated: c.yellow('outdated'), stale: c.yellow('stale'), legacy: c.yellow('legacy'), ok: c.green('ok') };
13
16
 
14
17
  export async function reconcile(root, { fix = false, scope = 'all', force = false } = {}) {
15
18
  log(c.bold(`\nSDLC reconcile ${c.dim('v' + VERSION)}`));
@@ -22,9 +25,14 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
22
25
  const registry = readJSON(path.join(root, PROJECT_FILES.reposRegistry), { repos: [] });
23
26
  if (!exists(path.join(root, PROJECT_FILES.reposRegistry))) gaps.push('no repos registered (.sdlc/repos.json absent)');
24
27
 
25
- // --- deterministic file actions (module + hub CI + author allowlists + every registered repo) ---
26
- const actions = [...moduleActions(root), ...hubActions(root), ...authorsActions(root, registry.repos)];
27
- for (const repo of registry.repos) actions.push(...repoActions(root, repo));
28
+ // --- deterministic file actions (module + hub CI + author allowlists + every registered repo),
29
+ // plus pre-2.0 sdlc-* -> yad-* migrations ('legacy': old name installed; rename in place) ---
30
+ const actions = [
31
+ ...moduleActions(root), ...legacyModuleActions(root),
32
+ ...hubActions(root), ...legacyHubActions(root),
33
+ ...authorsActions(root, registry.repos),
34
+ ];
35
+ for (const repo of registry.repos) actions.push(...repoActions(root, repo), ...legacyRepoActions(root, repo));
28
36
 
29
37
  // --- stale code-context (HEAD moved since last pack) ---
30
38
  const staleRepos = [];
@@ -42,7 +50,7 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
42
50
  if (!byScope.has(a.scope)) byScope.set(a.scope, []);
43
51
  byScope.get(a.scope).push(a);
44
52
  }
45
- const counts = { missing: 0, outdated: 0, stale: 0, ok: 0 };
53
+ const counts = { missing: 0, outdated: 0, stale: 0, legacy: 0, ok: 0 };
46
54
  for (const [scopeName, items] of byScope) {
47
55
  const notOk = items.filter((i) => i.status !== 'ok');
48
56
  items.forEach((i) => counts[i.status]++);
@@ -56,10 +64,10 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
56
64
  a.status !== 'ok' && (scope === 'all' ? true : a.status !== 'missing'),
57
65
  );
58
66
  log('');
59
- log(c.dim(`summary: ${counts.missing} missing, ${counts.outdated} outdated, ${counts.stale} stale, ${counts.ok} ok`));
67
+ log(c.dim(`summary: ${counts.missing} missing, ${counts.outdated} outdated, ${counts.stale} stale, ${counts.legacy} legacy, ${counts.ok} ok`));
60
68
 
61
69
  if (!fix) {
62
- if (fixable.length || gaps.length) hand('run `sdlc check --fix` to reconcile (or `sdlc setup` for missing one-time setup).');
70
+ if (fixable.length || gaps.length) hand('run `yad check --fix` to reconcile (or `yad setup` for missing one-time setup).');
63
71
  return { counts, gaps, applied: 0 };
64
72
  }
65
73
 
@@ -78,6 +86,6 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
78
86
  const rec = readJSON(path.join(root, PROJECT_FILES.version), {});
79
87
  writeJSON(path.join(root, PROJECT_FILES.version), { ...rec, version: VERSION });
80
88
  applied ? ok(`reconciled ${applied} item(s)`) : info('nothing to fix');
81
- if (gaps.length) hand('one-time setup still missing — run `sdlc setup`.');
89
+ if (gaps.length) hand('one-time setup still missing — run `yad setup`.');
82
90
  return { counts, gaps, applied };
83
91
  }
package/cli/repo.mjs CHANGED
@@ -1,5 +1,5 @@
1
- // `sdlc repo list|refresh` — connected-repo staleness as an explicit HUMAN decision.
2
- // Skill steps no longer silently repack a stale repo; they flag it and point here. (`sdlc check --fix`
1
+ // `yad repo list|refresh` — connected-repo staleness as an explicit HUMAN decision.
2
+ // Skill steps no longer silently repack a stale repo; they flag it and point here. (`yad check --fix`
3
3
  // still refreshes too — it is also human-invoked.)
4
4
  import path from 'node:path';
5
5
  import { c, log, ok, info, warn, hand, fail, readJSON, writeJSON } from './lib.mjs';
@@ -20,7 +20,7 @@ function staleness(root, repo) {
20
20
 
21
21
  export async function runRepo(root, { action = 'list', name, today } = {}) {
22
22
  const { regPath, registry } = load(root);
23
- if (!registry.repos.length) { warn('no repos registered (.sdlc/repos.json) — run `sdlc setup`'); return { repos: 0 }; }
23
+ if (!registry.repos.length) { warn('no repos registered (.sdlc/repos.json) — run `yad setup`'); return { repos: 0 }; }
24
24
 
25
25
  if (action === 'list') {
26
26
  log(c.bold('\nconnected repos'));
@@ -31,7 +31,7 @@ export async function runRepo(root, { action = 'list', name, today } = {}) {
31
31
  if (stale) { staleCount++; warn(`${repo.name} ${c.dim(`(${repo.path})`)} — ${c.yellow('stale')} (HEAD moved since last pack)`); }
32
32
  else ok(`${repo.name} ${c.dim('— fresh')}`);
33
33
  }
34
- if (staleCount) hand(`refresh with \`sdlc repo refresh${registry.repos.length > 1 ? ' <name>' : ''}\` (or \`sdlc repo refresh\` for all)`);
34
+ if (staleCount) hand(`refresh with \`yad repo refresh${registry.repos.length > 1 ? ' <name>' : ''}\` (or \`yad repo refresh\` for all)`);
35
35
  return { repos: registry.repos.length, stale: staleCount };
36
36
  }
37
37
 
@@ -51,7 +51,7 @@ export async function runRepo(root, { action = 'list', name, today } = {}) {
51
51
  }
52
52
  writeJSON(regPath, registry);
53
53
  refreshed ? ok(`refreshed ${refreshed} repo(s)`) : info('nothing refreshed');
54
- hand('regenerate the code-map in Claude Code (sdlc-connect-repos) — the pack is cached, the map is the AI step');
54
+ hand('regenerate the code-map in Claude Code (yad-connect-repos) — the pack is cached, the map is the AI step');
55
55
  return { refreshed };
56
56
  }
57
57