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
@@ -1,24 +1,23 @@
1
1
  #!/usr/bin/env bash
2
2
  # verified-commits gate.
3
- # Every commit in the range under review must (1) carry a signature the PLATFORM marks as verified
4
- # (the GitHub/GitLab "Verified" badge — proves the commit was signed by a key registered to the
5
- # account that owns the author email) and (2) be AUTHORED by a known identity: the author email must
6
- # appear in the .sdlc/verified-authors allowlist (generated by `yad check --fix` from the hub
7
- # roster's `email` fields + hub.json `verified_authors`). Unsigned commits and commits from unknown
8
- # authors do not reach merge — on the product hub and on every connected repo alike.
3
+ # Every commit in the range under review must carry a signature the PLATFORM marks as verified (the
4
+ # GitHub/GitLab "Verified" badge — proves the commit was signed by a key registered to the account that
5
+ # owns the author email). Unsigned commits do not reach merge — on the Product and on every connected
6
+ # repo alike.
9
7
  #
10
- # Only the AUTHOR email is checked against the allowlist: platform-generated merge/squash/update
11
- # commits set the platform itself as the committer (e.g. noreply@github.com), and their integrity is
12
- # covered by the signature check (the platform signs them).
8
+ # There is no author allowlist. It used to be `.sdlc/verified-authors`, generated from the Product
9
+ # roster's emails; yadflow removed the roster (E62) and keeps no list of people. Who may author a commit
10
+ # is who has write access to the repository — the platform's own answer, which never goes stale — and
11
+ # the signature proves which platform account made the commit. A `.sdlc/verified-authors` file an older
12
+ # release generated is no longer read, and the gate says so when it finds one.
13
13
  #
14
14
  # Merge-commit signature exemption: a merge that introduces NO content of its own (its combined diff
15
15
  # is empty — no conflict-resolution / evil-merge hunks) is signature-waived, because every change it
16
- # carries already lives in its individually author+signature-checked parents. This unblocks self-hosted
16
+ # carries already lives in its individually signature-checked parents. This unblocks self-hosted
17
17
  # GitLab, which does not sign UI-created merge commits (the signature API returns 404). A merge that
18
18
  # DOES introduce content of its own still requires a verified signature (fail-closed).
19
19
  #
20
20
  # Degradation is explicit, never silent:
21
- # - no allowlist file -> author check SKIPPED with a warning (configure emails, re-wire)
22
21
  # - no GitHub/GitLab remote -> signature check SKIPPED with a warning (no platform, no badge)
23
22
  # - platform API unreachable -> FAIL closed (a security gate must not pass on a broken check)
24
23
  #
@@ -64,13 +63,10 @@ if [ -z "$commits" ]; then
64
63
  exit 0
65
64
  fi
66
65
 
67
- # ---- author allowlist (one email per line; # comments; case-insensitive) -------------------------
68
- ALLOWLIST="${SDLC_VERIFIED_AUTHORS:-.sdlc/verified-authors}"
69
- authors_on=0
70
- if [ -f "$ALLOWLIST" ]; then
71
- authors_on=1
72
- else
73
- echo "WARN [verified-commits]: ${ALLOWLIST} not found — author allowlist NOT enforced. Add 'email' to the hub roster (or hub.json 'verified_authors'), then run \`yad check --fix\`."
66
+ # ---- the author allowlist is gone (E62) ------------------------------------------------------------
67
+ # Said out loud when an older one is still on disk, so nobody edits it believing it decides something.
68
+ if [ -f .sdlc/verified-authors ]; then
69
+ echo "note [verified-commits]: .sdlc/verified-authors is no longer read — write access to the repo decides who can author; the signature is still required."
74
70
  fi
75
71
 
76
72
  # ---- platform for the signature check (override with SDLC_PLATFORM=github|gitlab|none) -----------
@@ -120,7 +116,7 @@ signature_verified() {
120
116
  # 0 when a merge commit introduced NO content of its own. The combined diff (--cc) lists only hunks
121
117
  # that differ from ALL parents, so empty output means every change lives in an individually-checked
122
118
  # parent — there is no conflict-resolution or evil-merge content unique to the merge commit, hence
123
- # nothing an unverified author could smuggle in past the per-parent author+signature checks.
119
+ # nothing an unsigned change could smuggle in past the per-parent signature checks.
124
120
  merge_introduces_no_content() {
125
121
  # `local` on its own line: `local out=$(...)` would mask the substitution's exit status (local
126
122
  # always returns 0). A git error (e.g. a parent tree missing in a shallow clone) must fail closed —
@@ -134,37 +130,15 @@ rc=0
134
130
  while IFS= read -r sha; do
135
131
  [ -z "$sha" ] && continue
136
132
  short="$(git log -1 --format=%h "$sha")"
137
- author="$(git log -1 --format=%ae "$sha" | tr '[:upper:]' '[:lower:]')"
138
- # The gate-sync bot is a machine identity, not a roster human — waive the allowlist for it. Its
139
- # commits are still held to the SIGNATURE check below, so a contributor cannot spoof the bot author
140
- # to dodge the allowlist (a forged-author commit is not platform-Verified). Mirrors how the
141
- # platform-committer merge commits are allowlist-exempt but signature-covered.
142
- is_bot=0
143
- case "${author}|$(git log -1 --format=%an "$sha" | tr '[:upper:]' '[:lower:]')" in *yad-gate-sync*) is_bot=1 ;; esac
144
133
 
145
- # A merge commit (2+ parents) is authored by whoever pressed "merge" — often a platform noreply
146
- # address, not a roster human — while its CONTENT already passed the PR gate suite. Waive the
147
- # allowlist for it (like the bot), but STILL require the signature below: a platform merge commit is
148
- # Verified, so a locally-forged merge pushed direct-to-default cannot dodge the check. This matters
149
- # for the push-on-default yad-update-guard, which (unlike the PR-triggered gates) sees merge commits.
134
+ # A merge commit (2+ parents) whose content is all in its parents may go unsigned — see
135
+ # merge_introduces_no_content. Every other commit, merges with content of their own included, needs
136
+ # the signature: a platform merge commit is Verified, so a locally-forged merge pushed
137
+ # direct-to-default cannot dodge the check. This matters for the push-on-default yad-update-guard,
138
+ # which (unlike the PR-triggered gates) sees merge commits.
150
139
  is_merge=0
151
140
  [ "$(git log -1 --format=%P "$sha" | wc -w | tr -d '[:space:]')" -ge 2 ] && is_merge=1
152
141
 
153
- if [ "$authors_on" = 1 ] && [ "$is_bot" = 0 ] && [ "$is_merge" = 0 ]; then
154
- # tolerate CRLF / stray surrounding whitespace in a hand-edited allowlist
155
- if grep -vE '^[[:space:]]*(#|$)' "$ALLOWLIST" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \
156
- | tr '[:upper:]' '[:lower:]' | grep -qxF "$author"; then
157
- echo "PASS [verified-commits]: ${short} author <${author}> is a known identity"
158
- else
159
- echo "FAIL [verified-commits]: ${short} author <${author}> is not in ${ALLOWLIST} — unverified user."
160
- rc=1
161
- fi
162
- elif [ "$is_bot" = 1 ]; then
163
- echo "PASS [verified-commits]: ${short} gate-sync bot — allowlist waived (signature still required)"
164
- elif [ "$is_merge" = 1 ]; then
165
- echo "PASS [verified-commits]: ${short} merge commit — allowlist waived (signature still required)"
166
- fi
167
-
168
142
  if [ -n "$platform" ]; then
169
143
  if signature_verified "$sha"; then
170
144
  echo "PASS [verified-commits]: ${short} signature verified by ${platform}"
@@ -1,5 +1,5 @@
1
1
  # yad-managed: yad-checks
2
- # SDLC check gates (Phase 3 build plan §C). The three gates run on every PR and must pass before
2
+ # SDLC check gates (Phase 3 build plan §C). The gates below run on every PR and must pass before
3
3
  # merge. They are CI-agnostic bash in checks/ — this workflow just invokes them with the PR base.
4
4
  # This workflow file is OWNED by yad-checks (the marker on line 1 identifies it). It runs
5
5
  # independently of any other workflow in .github/workflows/, so wiring never edits a foreign workflow.
@@ -29,17 +29,49 @@ jobs:
29
29
  with: { fetch-depth: 0 }
30
30
  - run: bash checks/contract-check.sh "origin/${{ github.base_ref }}"
31
31
 
32
+ # E65/E66 — the risk map (.sdlc/risk-map: a risk level per directory, no names). Warnings, and the
33
+ # approver count read from the base branch's map. The script always exits 0: the count is reported,
34
+ # not enforced, and a stale map never blocks a merge.
35
+ risk-map:
36
+ runs-on: ubuntu-latest
37
+ if: github.event.action != 'edited'
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ with: { fetch-depth: 0 }
41
+ - run: bash checks/risk-map-check.sh "origin/${{ github.base_ref }}"
42
+
32
43
  build-test-lint:
33
44
  runs-on: ubuntu-latest
34
45
  if: github.event.action != 'edited'
35
46
  env:
36
47
  YAD_TEST_MAX_WORKERS: "2" # cap jest/vitest test workers in CI; ignored by other runners
48
+ YAD_NODE_VERSION: ${{ vars.YAD_NODE_VERSION || '22' }} # repo variable; a current 20, 22 or 24 line (Node 25+ has no Corepack)
49
+ NX_BASE: ${{ github.event.pull_request.base.sha }}
50
+ NX_HEAD: ${{ github.event.pull_request.head.sha }}
51
+ COREPACK_HOME: ${{ github.workspace }}/.corepack-cache # pinned-manager downloads, cached below
37
52
  steps:
38
53
  - uses: actions/checkout@v4
39
- with: { fetch-depth: 0 }
54
+ with:
55
+ fetch-depth: 0
56
+ filter: blob:none
40
57
  - uses: actions/setup-node@v4
41
- with: { node-version: "20", cache: "npm" }
42
- - run: npm ci # install deps so the real lint/build/test toolchain can run (mirrors the GitLab template)
58
+ with:
59
+ node-version: ${{ env.YAD_NODE_VERSION }}
60
+ # Dependency cache for either manager: npm's ~/.npm, pnpm's content-addressable store, and the
61
+ # Corepack home where `corepack prepare` keeps the pinned manager (otherwise re-downloaded from
62
+ # the registry on every job, even when the dependency cache hits). Keyed on the lockfiles AND
63
+ # package.json, since a packageManager bump changes no lockfile. setup-node's own `cache:` must
64
+ # name the manager up front (and needs pnpm on PATH before it runs), which package.json only
65
+ # decides later — so cache the directories directly.
66
+ - uses: actions/cache@v4
67
+ with:
68
+ path: |
69
+ ~/.npm
70
+ ~/.local/share/pnpm/store
71
+ ${{ env.COREPACK_HOME }}
72
+ key: yad-deps-${{ runner.os }}-node${{ env.YAD_NODE_VERSION }}-${{ hashFiles('package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'package.json') }}
73
+ restore-keys: yad-deps-${{ runner.os }}-node${{ env.YAD_NODE_VERSION }}-
74
+ - run: bash checks/install-deps.sh
43
75
  - run: bash checks/build-test-lint.sh
44
76
 
45
77
  # Phase 6 — feature-thread gates. lineage-check: the change links a real threaded epic. epic-open:
@@ -99,7 +131,7 @@ jobs:
99
131
  body="$(mktemp)"; printf '%s' "$PR_BODY" > "$body"
100
132
  bash checks/pr-template.sh --profile code "$body"
101
133
 
102
- # No unverified commits from unverified users: platform-Verified signature + allowlisted author.
134
+ # No unverified commits: a platform-Verified signature on every commit (write access decides who authors).
103
135
  # Reads the commit range, not the title/body — skip it on a bare `edited` event.
104
136
  verified-commits:
105
137
  runs-on: ubuntu-latest
@@ -1,8 +1,8 @@
1
1
  # yad-managed: yad-checks
2
- # Pattern gates for the PRODUCT HUB. They run with `--profile hub` and split by head branch:
3
- # review/EP-* PRs are front-half artifact-review vehicles — a `review: <artifact> (EP-<slug>)` title
4
- # and the hub artifact-review template body.
5
- # every other PR is a tooling/code change to the hub itself — a Conventional-Commits title and the
2
+ # Pattern gates for the PRODUCT. They run with `--profile hub` and split by head branch:
3
+ # review/EP-* PRs are Shape artifact-review vehicles — a `review: <artifact> (EP-<slug>)` title
4
+ # and the Product artifact-review template body.
5
+ # every other PR is a tooling/code change to the Product itself — a Conventional-Commits title and the
6
6
  # code task template body (same convention the code repos use).
7
7
  # The head ref is passed via --head so the gate can tell the two apart. Commit subjects are always
8
8
  # Conventional-Commits. Standalone workflow so it never collides with the code-repo yad-checks workflow.
@@ -27,7 +27,7 @@ jobs:
27
27
 
28
28
  # Pass the title via env (never interpolate untrusted ${{ }} into a run line — injection-safe).
29
29
  # fetch-depth: 0 + the base ref give us the PR's changed paths, so the gate can reject an artifact
30
- # change (epics/**) riding a non-review head past the front-half review with a plain code title.
30
+ # change (epics/**) riding a non-review head past the Shape review with a plain code title.
31
31
  pr-title:
32
32
  runs-on: ubuntu-latest
33
33
  env:
@@ -1,14 +1,13 @@
1
1
  # yad-managed: yad-checks
2
2
  # yad-update-guard — integrity gate for DIRECT pushes to the default branch.
3
3
  #
4
- # `yad update --push` commits the applied SDLC drift (skills, gate scripts, CI wiring, verified-
5
- # authors) and pushes it straight to the default branch with NO pull request — so the pull_request
4
+ # `yad update --push` commits the applied SDLC drift (skills, gate scripts, CI wiring) and pushes it
5
+ # straight to the default branch with NO pull request — so the pull_request
6
6
  # gate suite never fires. This workflow is the "skipped from CI EXCEPT verified-commits + the pattern
7
7
  # gate" contract: on every push to the default branch it runs ONLY those two gates over the pushed
8
8
  # range. It is not scoped to yad-update commits on purpose — ANY direct-to-main commit (a hotfix, a
9
9
  # force-push) then gets signature + subject-format checked, which is a strictly-good invariant. A
10
- # normal PR merge sails through: a GitHub merge/squash commit is platform-Verified, verified-commits.sh
11
- # waives the allowlist for merge commits (whose author is whoever clicked merge), and commit-message.sh
10
+ # normal PR merge sails through: a GitHub merge/squash commit is platform-Verified, and commit-message.sh
12
11
  # skips merges. CAVEAT: a *rebase-merge* recreates the PR commits WITHOUT GitHub's signature, so they
13
12
  # lose the Verified badge and fail the signature check on the direct push — teams using rebase-merge
14
13
  # should sign their commits or not wire this guard.
@@ -1,8 +1,8 @@
1
1
  # yad-managed: yad-checks
2
- # verified-commits gate for the PRODUCT HUB: every PR (including the front-half review/EP-* PRs)
3
- # must contain only commits whose signature the platform marks Verified AND whose author email is a
4
- # known identity (.sdlc/verified-authors — generated by `yad check --fix` from the hub roster).
5
- # Standalone workflow so it never collides with the hub-flavored yad-checks workflow.
2
+ # verified-commits gate for the PRODUCT: every PR (including the Shape review/EP-* PRs)
3
+ # must contain only commits whose signature the platform marks Verified. Who may author them is who has
4
+ # write access to the repo — yadflow keeps no author list.
5
+ # Standalone workflow so it never collides with the Product-flavored yad-checks workflow.
6
6
  name: yad-verified-commits
7
7
  on:
8
8
  pull_request:
@@ -4,7 +4,7 @@
4
4
  stages: [spec-link, contract-check, build-test-lint, verified-commits]
5
5
 
6
6
  default:
7
- image: node:20
7
+ image: node:${YAD_NODE_VERSION}
8
8
 
9
9
  .mr_only:
10
10
  rules:
@@ -12,6 +12,9 @@ default:
12
12
 
13
13
  variables:
14
14
  GIT_DEPTH: "0" # full history so the gates can diff against the target branch
15
+ YAD_NODE_VERSION: "22" # override with a project/group CI variable (a current 20, 22 or 24 line; Node 25+ has no Corepack)
16
+ NX_BASE: $CI_MERGE_REQUEST_DIFF_BASE_SHA
17
+ NX_HEAD: $CI_COMMIT_SHA
15
18
 
16
19
  spec-link:
17
20
  stage: spec-link
@@ -28,7 +31,10 @@ contract-check:
28
31
  build-test-lint:
29
32
  stage: build-test-lint
30
33
  extends: .mr_only
34
+ variables:
35
+ YAD_TEST_MAX_WORKERS: "2" # cap jest/vitest test workers in CI; ignored by other runners
31
36
  script:
37
+ - bash checks/install-deps.sh
32
38
  - bash checks/build-test-lint.sh
33
39
 
34
40
  # Needs a CI/CD variable GITLAB_TOKEN (or SDLC_API_TOKEN) with read_api scope — CI_JOB_TOKEN cannot
@@ -14,10 +14,19 @@
14
14
  # top-level `default:` merges into the HOST root pipeline; if the host already sets a top-level
15
15
  # `image:`, GitLab rejects the whole config ("image is defined in top-level and 'default:' entry").
16
16
  # Putting them on the anchor scopes them to the gate jobs (which all `extends: .sdlc_mr_only`) and
17
- # never touches the host root.
17
+ # never touches the host root. The same goes for the gate jobs' own variables (`YAD_NODE_VERSION`,
18
+ # `NX_BASE`, `NX_HEAD`): an included top-level `variables:` merges into the host's globals, where a
19
+ # generic name like NX_BASE can collide with one the host already computes for its own Nx jobs.
20
+ # Only `GIT_DEPTH` stays global — the gates need the full history and a host that sets its own wins.
18
21
 
19
22
  .sdlc_mr_only:
20
- image: node:20
23
+ image: node:${YAD_NODE_VERSION}
24
+ variables:
25
+ # A project/group CI/CD variable overrides a YAML value, so YAD_NODE_VERSION can be changed in
26
+ # project settings without editing the managed fragment (a current 20, 22 or 24 line; Node 25+ has no Corepack).
27
+ YAD_NODE_VERSION: "22"
28
+ NX_BASE: $CI_MERGE_REQUEST_DIFF_BASE_SHA
29
+ NX_HEAD: $CI_COMMIT_SHA
21
30
  # Runner selection. These jobs need a docker/dind executor; on instances whose runners are all
22
31
  # tag-locked (run_untagged: false) an untagged image job never starts. Set the YAD_RUNNER_TAGS
23
32
  # CI/CD variable (Settings → CI/CD → Variables, e.g. `dind_runner`) to route them — it lives in
@@ -44,13 +53,22 @@ yad-contract-check:
44
53
  script:
45
54
  - bash checks/contract-check.sh "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
46
55
 
56
+ # E65/E66 — the risk map (.sdlc/risk-map: a risk level per directory, no names). Warnings, and the
57
+ # approver count read from the base branch's map. It never fails the pipeline (the count is reported,
58
+ # not enforced), so the job needs no allow_failure.
59
+ yad-risk-map:
60
+ extends: .sdlc_mr_only
61
+ needs: []
62
+ script:
63
+ - bash checks/risk-map-check.sh "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
64
+
47
65
  yad-build-test-lint:
48
66
  extends: .sdlc_mr_only
49
67
  needs: []
50
68
  variables:
51
69
  YAD_TEST_MAX_WORKERS: "2" # cap jest/vitest test workers in CI; ignored by other runners
52
70
  script:
53
- - npm ci
71
+ - bash checks/install-deps.sh
54
72
  - bash checks/build-test-lint.sh
55
73
 
56
74
  # Phase 6 — feature-thread gates (lineage / seal / hotfix-debt). Build on spec-link's story->epic
@@ -93,7 +111,7 @@ yad-pr-template:
93
111
  - body="$(mktemp)"; printf '%s' "$CI_MERGE_REQUEST_DESCRIPTION" > "$body"
94
112
  - bash checks/pr-template.sh --profile code "$body"
95
113
 
96
- # No unverified commits from unverified users: platform-Verified signature + allowlisted author.
114
+ # No unverified commits: a platform-Verified signature on every commit (write access decides who authors).
97
115
  # Needs a CI/CD variable GITLAB_TOKEN (or SDLC_API_TOKEN) with read_api scope — CI_JOB_TOKEN cannot
98
116
  # read the commit-signature API. Without it the job FAILS closed with guidance.
99
117
  yad-verified-commits:
@@ -1,11 +1,11 @@
1
1
  # yad-managed-include: yad-checks
2
- # Pattern gates for the PRODUCT HUB, as an INCLUDABLE fragment. Pulled into the hub's root
2
+ # Pattern gates for the PRODUCT, as an INCLUDABLE fragment. Pulled into the Product's root
3
3
  # .gitlab-ci.yml via:
4
4
  # include:
5
5
  # - local: '.gitlab/ci/yad-hub-checks.yml'
6
- # MRs run with `--profile hub` and split by source branch: review/EP-* MRs are front-half artifact-
7
- # review vehicles (a `review: <artifact> (EP-<slug>)` title + the hub artifact-review template body);
8
- # every other MR is a tooling/code change to the hub itself and follows the code convention (a
6
+ # MRs run with `--profile hub` and split by source branch: review/EP-* MRs are Shape artifact-
7
+ # review vehicles (a `review: <artifact> (EP-<slug>)` title + the Product artifact-review template body);
8
+ # every other MR is a tooling/code change to the Product itself and follows the code convention (a
9
9
  # Conventional-Commits title + the code task template body). The source branch is passed via --head so
10
10
  # the gate can tell the two apart. Commit subjects are always Conventional-Commits.
11
11
  # Job names are yad-hub-prefixed so they coexist with any code-repo fragment in one pipeline; jobs use
@@ -28,7 +28,7 @@ yad-hub-commit-message:
28
28
  - bash checks/commit-message.sh --profile hub "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
29
29
 
30
30
  # GIT_DEPTH: 0 (above) gives the changed paths, so the gate can reject an artifact change (epics/**)
31
- # riding a non-review source branch past the front-half review with a plain code title/template.
31
+ # riding a non-review source branch past the Shape review with a plain code title/template.
32
32
  yad-hub-pr-title:
33
33
  extends: .yad_hub_mr_only
34
34
  needs: []
@@ -1,13 +1,13 @@
1
1
  # yad-managed-include: yad-checks
2
- # verified-commits gate for the PRODUCT HUB, as an INCLUDABLE fragment. Pulled into the hub's root
2
+ # verified-commits gate for the PRODUCT, as an INCLUDABLE fragment. Pulled into the Product's root
3
3
  # .gitlab-ci.yml via:
4
4
  # include:
5
5
  # - local: '.gitlab/ci/yad-verified-commits.yml'
6
- # Every MR (including the front-half review/EP-* MRs) must contain only commits whose signature the
7
- # platform marks Verified AND whose author email is a known identity (.sdlc/verified-authors).
6
+ # Every MR (including the Shape review/EP-* MRs) must contain only commits whose signature the
7
+ # platform marks Verified. Who may author them is who has write access to the repo — no author list.
8
8
  #
9
9
  # Needs a CI/CD variable GITLAB_TOKEN (or SDLC_API_TOKEN) with read_api scope — CI_JOB_TOKEN cannot
10
- # read the commit-signature API. The hub's SDLC_GATE_TOKEN (from the gate-sync wiring) also works:
10
+ # read the commit-signature API. The Product's SDLC_GATE_TOKEN (from the gate-sync wiring) also works:
11
11
  # set GITLAB_TOKEN: $SDLC_GATE_TOKEN in the job if you prefer one token.
12
12
  # Job name is yad-hub-prefixed so it can coexist with the code-repo fragment in one pipeline.
13
13
  yad-hub-verified-commits:
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env bash
2
+ # ledger-guard CURSOR ADAPTER — the same guard, answered in the protocol Cursor requires (E11).
3
+ #
4
+ # Cursor's `preToolUse` is a PERMISSION hook, and its docs are explicit: for a permission hook,
5
+ # "invalid JSON or a response that doesn't match the hook's schema blocks the action". Empty stdout is
6
+ # invalid JSON. The plain `ledger-guard.sh` prints nothing when it allows — so wiring it directly
7
+ # would have blocked EVERY file write in a verified project, which is the exact opposite of this
8
+ # guard's fail-open design.
9
+ #
10
+ # This wrapper guarantees one thing: a permission answer on stdout, always, whatever happens below.
11
+ #
12
+ # allow {"permission":"allow"} exit 0
13
+ # deny {"permission":"deny","user_message":"<reason>","agent_message":"<reason>"} exit 0
14
+ #
15
+ # A deny exits 0 because the JSON is the authoritative answer and exit 0 is what tells Cursor to read
16
+ # it; exit 2 blocks too, but is documented as the code for "no JSON to read" and would discard the
17
+ # reason — and naming the command that owns the transition is the whole point of speaking at edit time.
18
+ #
19
+ # It takes NO arguments, deliberately. Cursor's `hooks.json` holds one command STRING and does not
20
+ # document whether it is run through a shell, so a command with a flag in it might never be split into
21
+ # program and argument — a command not found, silently, on every call.
22
+ set -uo pipefail
23
+
24
+ # `CDPATH=` is load-bearing, and the Cursor entry is what makes it so: that entry invokes the script
25
+ # by a RELATIVE path, so `${BASH_SOURCE[0]}` is relative and `cd` consults $CDPATH before the current
26
+ # directory. An exported CDPATH in a login profile — an ordinary thing to have, and inherited by the
27
+ # harness — would resolve `hooks` to some other tree entirely, and the guard would fail open on every
28
+ # write for as long as that profile lived.
29
+ #
30
+ # The loop resolves a symlinked SCRIPT, which `cd -P` does not: `-P` resolves the directory path, and
31
+ # a script reached through a link in another directory would otherwise look for its sibling beside the
32
+ # LINK and not find it. `readlink` without `-f` because `-f` is not portable to macOS.
33
+ _src="${BASH_SOURCE[0]}"
34
+ while [ -L "$_src" ]; do
35
+ _dir="$(CDPATH= cd -P -- "$(dirname -- "$_src")" && pwd)"
36
+ _src="$(readlink -- "$_src")"
37
+ case "$_src" in /*) ;; *) _src="$_dir/$_src" ;; esac
38
+ done
39
+ HOOK_DIR="$(CDPATH= cd -P -- "$(dirname -- "$_src")" && pwd)"
40
+ ALLOW='{"permission":"allow"}'
41
+
42
+ # Every failure below this line resolves to ALLOW, because this guard fails open by design and CI is
43
+ # what fails closed. A wrapper that blocked when it could not reach `yad` would stop an agent editing
44
+ # anything at all the moment an install went sideways.
45
+ if [ ! -x "$HOOK_DIR/ledger-guard.sh" ]; then
46
+ echo " • yad hook: $HOOK_DIR/ledger-guard.sh is missing or not executable — allowing (run \`yad check --fix\`)" >&2
47
+ echo "$ALLOW"
48
+ exit 0
49
+ fi
50
+
51
+ # stdout is CAPTURED (the JSON verdict), stderr is not (it flows through to Cursor's log). stdin is
52
+ # inherited by the command substitution, so the tool-call payload still reaches the guard.
53
+ raw="$("$HOOK_DIR/ledger-guard.sh" --format cursor)"
54
+ rc=$?
55
+
56
+ # THE VERDICT IS THE LAST NON-EMPTY LINE, not the whole of stdout, and it is recognised by containing
57
+ # a `"permission"` key rather than by starting with one.
58
+ #
59
+ # Both halves of that matter. `yad`'s own top-level failure handler prints to STDOUT, so a usage error
60
+ # or a crash banner can land in front of the JSON; a prefix test would then fail to recognise our own
61
+ # deny and fall through to the allow below, turning a real refusal into a permitted write with nothing
62
+ # on stderr to say so. And a containment test does not care what order the JSON's properties are in,
63
+ # so the deny answer's shape on the other side of this language boundary is not silently load-bearing.
64
+ verdict="$(printf '%s\n' "$raw" | awk 'NF { line = $0 } END { print line }')"
65
+
66
+ # `ledger-guard.sh` has its own fail-open branches that exit 0 with EMPTY stdout — no `yad` on PATH,
67
+ # an install it cannot resolve — and each one of those would otherwise reach Cursor as invalid JSON
68
+ # and block the write.
69
+ case "$verdict" in
70
+ *'"permission"'*) echo "$verdict"; exit 0 ;;
71
+ esac
72
+
73
+ # EXIT 2 IS A DENY, even with nothing on stdout — and this branch is reached in a real, ordinary case.
74
+ # `ledger-guard.sh` resolves `yad` from the Product's own `node_modules/yadflow` BEFORE `PATH`, so a
75
+ # project pinned to a yadflow older than `--format` parses the flag, ignores it, and answers in the
76
+ # exit protocol: exit 2, empty stdout. Falling through to the generic allow below would turn a real
77
+ # refusal into a permitted write, and print "exited 2 — allowing" while doing it.
78
+ #
79
+ # The message is a FIXED literal with nothing interpolated. The reason the older `yad` printed has
80
+ # already gone to stderr, where Cursor logs it; escaping arbitrary multi-line text into JSON in bash
81
+ # is exactly the sort of thing that produces an off-schema response, and there is no need to risk it.
82
+ if [ "$rc" -eq 2 ]; then
83
+ echo '{"permission":"deny","user_message":"yad: this edit touches CI-owned gate state.","agent_message":"This edit touches CI-owned gate state, which CI is the sole writer of. Do not edit the ledger by hand: use `yad gate open` for the transition, and commit only the artifact. The installed yadflow is older than this hook, so the full reason is in the hook log rather than here — `yad doctor` will name the version gap."}'
84
+ exit 0
85
+ fi
86
+
87
+ if [ "$rc" -ne 0 ]; then
88
+ echo " • yad hook: ledger-guard.sh exited $rc — allowing (run \`yad doctor\` to check the install)" >&2
89
+ fi
90
+ echo "$ALLOW"
91
+ exit 0
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env bash
2
2
  # ledger-guard HARNESS HOOK — the local half of the CI gate of the same name (#171).
3
3
  #
4
- # The gate ledger is CI-owned in bridge mode: `checks/ledger-guard.sh` rejects any non-bot commit
5
- # that changes `epics/*/.sdlc/{state,approvals,comments,hub-prs}.json` or `epics/*/reviews/*.md`.
4
+ # The gate ledger is CI-owned in verified mode: `checks/ledger-guard.sh` rejects any non-bot commit
5
+ # that changes `epics/*/.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` or `epics/*/reviews/*.md`,
6
+ # and the same files for the Foundation in `foundation/.sdlc/` and `foundation/reviews/` (E75).
6
7
  # This hook says so at the moment an agent tries the edit, instead of twenty minutes later in a
7
8
  # failed pipeline, and names the command that owns the transition (`yad gate open`).
8
9
  #
@@ -14,21 +15,51 @@
14
15
  # exit 0 allow
15
16
  # exit 2 deny, reason on stderr
16
17
  #
17
- # Wired for Claude Code as a `PreToolUse` hook in `.claude/settings.json` (`yad check --fix` writes
18
- # that entry). Any harness that can run a command and read those two exit codes can use it.
18
+ # Wired by `yad check --fix` for every harness that can refuse a write BEFORE it lands:
19
+ # Claude Code a `PreToolUse` hook in `.claude/settings.json`, pointing straight at THIS script
20
+ # Cursor a `preToolUse` hook in `.cursor/hooks.json`, pointing at `ledger-guard-cursor.sh`
21
+ #
22
+ # DO NOT WIRE THIS SCRIPT INTO CURSOR DIRECTLY, and check the same thing before wiring it into any
23
+ # other harness by hand. Cursor's `preToolUse` is a PERMISSION hook: it wants a JSON verdict on
24
+ # stdout, and treats an empty or off-schema answer as a refusal. This script prints NOTHING when it
25
+ # allows — so wired there directly it would block every file write in the project, which is the
26
+ # opposite of the fail-open stance below. `ledger-guard-cursor.sh` exists to speak that protocol.
27
+ #
28
+ # A harness that reads the EXIT CODE can use this script as it stands. A hook that only fires AFTER
29
+ # the write — Cursor's `afterFileEdit`, for one — is deliberately not used: it could report the edit
30
+ # but never refuse it, and reporting is what the CI gate already does.
31
+ #
32
+ # The project root is resolved from this script's own location below, so it does not matter which
33
+ # variable the harness sets ($CLAUDE_PROJECT_DIR, $CURSOR_PROJECT_DIR) or where it runs the command
34
+ # from; those variables appear only in the wiring, to point at this file.
19
35
  #
20
36
  # FAIL-OPEN: if no `yad` can be found, this ALLOWS and says why on stderr. A guardrail that blocked
21
37
  # every edit the moment an install went sideways would be worse than the problem. The CI gate fails
22
38
  # CLOSED and is what actually protects the ledger.
23
39
  set -uo pipefail
24
40
 
25
- # The hub root is this script's grandparent — hooks/ledger-guard.sh — so the resolution below does
41
+ # The Product root is this script's grandparent — hooks/ledger-guard.sh — so the resolution below does
26
42
  # not depend on the harness's working directory.
27
- HOOK_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
43
+ # `CDPATH=` is load-bearing, and the Cursor entry is what makes it so: that entry invokes the script
44
+ # by a RELATIVE path, so `${BASH_SOURCE[0]}` is relative and `cd` consults $CDPATH before the current
45
+ # directory. An exported CDPATH in a login profile — an ordinary thing to have, and inherited by the
46
+ # harness — would resolve `hooks` to some other tree entirely, and the guard would fail open on every
47
+ # write for as long as that profile lived.
48
+ #
49
+ # The loop resolves a symlinked SCRIPT, which `cd -P` does not: `-P` resolves the directory path, and
50
+ # a script reached through a link in another directory would otherwise look for its sibling beside the
51
+ # LINK and not find it. `readlink` without `-f` because `-f` is not portable to macOS.
52
+ _src="${BASH_SOURCE[0]}"
53
+ while [ -L "$_src" ]; do
54
+ _dir="$(CDPATH= cd -P -- "$(dirname -- "$_src")" && pwd)"
55
+ _src="$(readlink -- "$_src")"
56
+ case "$_src" in /*) ;; *) _src="$_dir/$_src" ;; esac
57
+ done
58
+ HOOK_DIR="$(CDPATH= cd -P -- "$(dirname -- "$_src")" && pwd)"
28
59
  HUB_ROOT="$(dirname -- "$HOOK_DIR")"
29
60
 
30
61
  # Resolution order, cheapest and most specific first: an explicit override, then the copy installed
31
- # in this hub, then whatever is on PATH, then a network-free npx. `--no-install` matters — a hook
62
+ # in this Product, then whatever is on PATH, then a network-free npx. `--no-install` matters — a hook
32
63
  # runs on every tool call and must never pause an agent to download a package.
33
64
  CMD=()
34
65
  _yad_bin="${YAD_BIN:-}"
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: yad-commit
3
- description: 'Build-half helper of the gated SDLC. Commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer order (Task → Contract-Change, plus an OPTIONAL Co-Authored-By), and an atomic-file guard (≤3 files). By default the commit carries NO AI footer: the human git author owns it, and a Co-Authored-By trailer is added ONLY when --ai <id> is explicitly passed (claude|copilot|cursor|coderabbit; default none = human-only). The flag is the sole switch — never add the footer on the AI''s own initiative. Drives the zero-dependency `yad commit` CLI; never auto-advances. Use when the user says "commit this", "commit by convention", or "make an atomic commit".'
3
+ description: 'Build helper of the gated SDLC. Commit ONE staged atomic change by the conventions — a Conventional-Commits subject, the fixed trailer order (Task → Contract-Change, plus an OPTIONAL Co-Authored-By), and an atomic-file guard (≤3 files). By default the commit carries NO AI footer: the human git author owns it, and a Co-Authored-By trailer is added ONLY when --ai <id> is explicitly passed (claude|copilot|cursor|coderabbit; default none = human-only). The flag is the sole switch — never add the footer on the AI''s own initiative. Drives the zero-dependency `yad commit` CLI; never auto-advances. Use when the user says "commit this", "commit by convention", or "make an atomic commit".'
4
4
  ---
5
5
 
6
- # SDLC — Commit by Convention (build-half helper)
6
+ # SDLC — Commit by Convention (Build helper)
7
7
 
8
8
  **Goal:** Turn ONE staged atomic change into a single commit that satisfies the project conventions
9
9
  (`CONTRIBUTING.md` / `config.yaml` `build`): a Conventional-Commits subject, the fixed trailer order
@@ -14,7 +14,7 @@ and `yad-ship` use. It **never auto-advances**; it just commits.
14
14
  ## Conventions
15
15
 
16
16
  - Run **inside the repo holding the staged change** — a code repo under
17
- `{project-root}/demo-repos/<repo>/`, or the product hub itself. Use absolute paths.
17
+ `{project-root}/demo-repos/<repo>/`, or the Product itself. Use absolute paths.
18
18
  - **Stage first.** Only the staged (`git add`) atomic change is committed. The guard refuses more than
19
19
  `ATOMIC_FILE_LIMIT` (3) staged files unless `--force` — split the change instead.
20
20
  - **Subject** — `<type>: <lowercase imperative description, no trailing period>`; types are
@@ -22,8 +22,8 @@ and `yad-ship` use. It **never auto-advances**; it just commits.
22
22
  - **Task trailer** — required on a code repo (anchors the `spec-link` + `commit-message` gates). Given
23
23
  with `--task` (a `<story>-T<NN>` id — an explicit value is validated against the `spec-link`
24
24
  contract, so a malformed id like a bare `EP-x-S01` fails locally instead of after a push), else
25
- derived from the branch (`feat/<story>-<task>-…`). Hub commits are not
26
- task-scoped, so the trailer is optional there — a missing-Task warning is informational on the hub
25
+ derived from the branch (`feat/<story>-<task>-…`). Product commits are not
26
+ task-scoped, so the trailer is optional there — a missing-Task warning is informational on the Product
27
27
  (`spec-link` is a code-repo gate) and only flags a real gate failure in a code repo.
28
28
  - **Contract-Change trailer** — `--contract-change` only when the diff touches the locked contract
29
29
  surface; it routes the change back to the architecture gate.
@@ -70,6 +70,6 @@ the architecture gate. To also open the PR/MR in the same step, use `yad-ship`.
70
70
 
71
71
  ## Reference
72
72
  - Branch/commit conventions + the file-boundary rule: `../yad-implement/references/implement-conventions.md`.
73
- - The full convention text: `CONTRIBUTING.md`; the config: `skills/sdlc/config.yaml` `build`.
73
+ - The full convention text: `CONTRIBUTING.md`; the config: `.sdlc/config.yaml` `build`.
74
74
  - The gate that enforces the subject pattern: `../yad-checks/references/check-gates.md` (`commit-message`).
75
75
  - Open the PR/MR after committing: `../yad-open-pr/SKILL.md`; both at once: `../yad-ship/SKILL.md`.
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: yad-connect-design
3
- description: 'Connects a design tool (Figma, or another tool — pluggable) to the product hub so the UI design step can materialize the full feature design (mobile screens / web pages) inside it, not just Markdown. Registers the tool into the project-wide .sdlc/design.json (local-user / MCP-session auth, no stored tokens), detecting whether a design-tool MCP is available and degrading to markdown-only when it is not. Run at setup or any time the design tool changes. Reusable, idempotent, refreshable. Use when the user says "connect Figma", "connect a design tool", "refresh the design connection", or "list the design connection".'
3
+ description: 'Connects a design tool (Figma, or another tool — pluggable) to the Product so the UI design step can materialize the full feature design (mobile screens / web pages) inside it, not just Markdown. Registers the tool into the project-wide .sdlc/design.json (local-user / MCP-session auth, no stored tokens), detecting whether a design-tool MCP is available and degrading to markdown-only when it is not. Run at setup or any time the design tool changes. Reusable, idempotent, refreshable. Use when the user says "connect Figma", "connect a design tool", "refresh the design connection", or "list the design connection".'
4
4
  ---
5
5
 
6
6
  # SDLC — Connect a Design Tool (make the UI step design-tool aware)
7
7
 
8
8
  **Goal:** Let the UI design step (`yad-ui`) produce the **actual feature design** — the mobile screens
9
9
  and/or web pages — inside a design tool such as **Figma**, alongside the Markdown artifacts
10
- (`ui-design.md` / `DESIGN.md`). This skill **connects** a design tool to the product hub and records
10
+ (`ui-design.md` / `DESIGN.md`). This skill **connects** a design tool to the Product and records
11
11
  *how* to reach it (the tool, the project/file references, which MCP renders it) — never a credential.
12
12
 
13
- This is **setup/maintenance**, not a gated front state — it never touches `.sdlc/state.json` or any
13
+ This is **setup/maintenance**, not a gated Shape step — it never touches `.sdlc/state.json` or any
14
14
  epic's approvals. It only writes the project-wide design registry. `yad-ui` consumes it: when a tool is
15
15
  connected and its MCP is available, the `ux-designer` lens **generates** screens into the tool (or
16
16
  **links** an existing human-made design and reads it back); when nothing is connected, `yad-ui` runs
@@ -18,7 +18,7 @@ markdown-only exactly as before.
18
18
 
19
19
  ## Conventions
20
20
 
21
- - `{project-root}` resolves from the project working directory (the **product hub**).
21
+ - `{project-root}` resolves from the project working directory (the **Product**).
22
22
  - The integration is **Figma-first but pluggable** (`config.yaml` `design.tools`): a design-tool
23
23
  *adapter*, like the GitHub/GitLab platform adapter. Figma is the primary provider; `pencil`
24
24
  (the `.pen` web/mobile editor) is a second, write-capable provider; `none` → markdown-only.
@@ -29,7 +29,7 @@ markdown-only exactly as before.
29
29
  the sibling of `.sdlc/repos.json` and `.sdlc/hub.json`.
30
30
  - Per-epic screen→frame links are written later by `yad-ui` (`epics/EP-<slug>/.sdlc/design-links.json`),
31
31
  not here.
32
- - Speak in the configured `communication_language`; write documents in `document_output_language`.
32
+ - Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.
33
33
 
34
34
  ## Inputs
35
35
 
@@ -94,7 +94,7 @@ Nothing auto-advances; this is setup.
94
94
  **available/unavailable** flag for the MCP (best-effort, the user's own session). No design tool
95
95
  connected ⇒ "markdown-only".
96
96
  - **`disconnect`** — remove the registry file (or set `tool: "none"`). The design tool's own
97
- project/files are **never touched** — only the hub's record of them.
97
+ project/files are **never touched** — only the Product's record of them.
98
98
 
99
99
  ## Hard rules
100
100
 
@@ -46,7 +46,7 @@ a screen, capture its node id + URL, and map components/tokens into `DESIGN.md`.
46
46
 
47
47
  Either direction ends by writing `epics/EP-<slug>/.sdlc/design-links.json` — the machine-readable
48
48
  screen→frame map — and a `## Design (<tool>)` section in `ui-design.md` linking each screen to its frame
49
- URL. The design itself lives in the tool; the hub keeps the *links* and the Markdown spec beside the
49
+ URL. The design itself lives in the tool; the Product keeps the *links* and the Markdown spec beside the
50
50
  other epic artifacts.
51
51
 
52
52
  ## Degrade path (no MCP / no tool)