yadflow 4.0.0-next.3 → 4.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 (128) hide show
  1. package/CHANGELOG.md +699 -0
  2. package/README.md +85 -22
  3. package/bin/commands.mjs +3 -1
  4. package/bin/yad.mjs +183 -29
  5. package/cli/capture.mjs +4 -2
  6. package/cli/checkpoint.mjs +10 -10
  7. package/cli/claims.mjs +4 -4
  8. package/cli/commit.mjs +48 -12
  9. package/cli/docs.mjs +15 -13
  10. package/cli/doctor.mjs +557 -141
  11. package/cli/epic-state.mjs +41 -18
  12. package/cli/errors.mjs +3 -2
  13. package/cli/find-product.mjs +197 -0
  14. package/cli/fold.mjs +4 -4
  15. package/cli/gate-local.mjs +13 -13
  16. package/cli/gate.mjs +111 -103
  17. package/cli/history.mjs +13 -13
  18. package/cli/hook.mjs +201 -30
  19. package/cli/index-command.mjs +6 -6
  20. package/cli/lib.mjs +91 -29
  21. package/cli/manifest.mjs +171 -104
  22. package/cli/migrate.mjs +208 -17
  23. package/cli/mode.mjs +22 -22
  24. package/cli/next.mjs +6 -6
  25. package/cli/openpr.mjs +32 -24
  26. package/cli/people.mjs +22 -10
  27. package/cli/plan.mjs +718 -45
  28. package/cli/platform.mjs +13 -7
  29. package/cli/product-index.mjs +4 -4
  30. package/cli/{hubcommit.mjs → productcommit.mjs} +11 -7
  31. package/cli/protection.mjs +19 -9
  32. package/cli/reconcile.mjs +98 -23
  33. package/cli/repo-publish.mjs +22 -16
  34. package/cli/repo.mjs +128 -33
  35. package/cli/report.mjs +4 -2
  36. package/cli/review.mjs +26 -19
  37. package/cli/setup.mjs +105 -36
  38. package/cli/ship.mjs +1 -1
  39. package/cli/skip.mjs +4 -4
  40. package/cli/tidy.mjs +7 -7
  41. package/cli/update-commit.mjs +22 -19
  42. package/cli/workspace.mjs +495 -0
  43. package/package.json +7 -4
  44. package/skills/sdlc/config.yaml +14 -13
  45. package/skills/sdlc/module-help.csv +8 -8
  46. package/skills/yad-analysis/SKILL.md +6 -4
  47. package/skills/yad-architecture/SKILL.md +4 -3
  48. package/skills/yad-backfill/SKILL.md +11 -5
  49. package/skills/yad-backfill/references/backfill.md +33 -3
  50. package/skills/yad-backfill/templates/checks/backfill-check.sh +200 -17
  51. package/skills/yad-change/SKILL.md +1 -1
  52. package/skills/yad-checks/SKILL.md +48 -27
  53. package/skills/yad-checks/references/check-gates.md +213 -53
  54. package/skills/yad-checks/templates/checks/commit-message.sh +39 -13
  55. package/skills/yad-checks/templates/checks/contract-check.sh +472 -23
  56. package/skills/yad-checks/templates/checks/epic-open.sh +398 -8
  57. package/skills/yad-checks/templates/checks/ledger-guard.sh +75 -25
  58. package/skills/yad-checks/templates/checks/lineage-check.sh +391 -7
  59. package/skills/yad-checks/templates/checks/product-checkout.sh +141 -0
  60. package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +399 -8
  61. package/skills/yad-checks/templates/checks/risk-map-check.sh +35 -3
  62. package/skills/yad-checks/templates/checks/spec-link.sh +36 -3
  63. package/skills/yad-checks/templates/checks/verified-commits.sh +26 -2
  64. package/skills/yad-checks/templates/github/yad-checks.yml +33 -13
  65. package/skills/yad-checks/templates/github/{yad-hub-checks.yml → yad-product-checks.yml} +14 -10
  66. package/skills/yad-checks/templates/github/yad-update-guard.yml +1 -1
  67. package/skills/yad-checks/templates/github/yad-verified-commits.yml +1 -1
  68. package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +12 -0
  69. package/skills/yad-checks/templates/gitlab/{yad-hub-checks.gitlab-ci.yml → yad-product-checks.gitlab-ci.yml} +16 -16
  70. package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +2 -2
  71. package/skills/yad-checks/templates/hooks/ledger-guard-cursor.mjs +130 -0
  72. package/skills/yad-checks/templates/hooks/ledger-guard.mjs +117 -0
  73. package/skills/yad-checks/templates/hooks/yad-capture.mjs +117 -0
  74. package/skills/yad-commit/SKILL.md +18 -7
  75. package/skills/yad-connect-design/SKILL.md +1 -1
  76. package/skills/yad-connect-design/references/design-registry.md +1 -1
  77. package/skills/yad-connect-docs/SKILL.md +16 -11
  78. package/skills/yad-connect-docs/references/docs-registry.md +8 -8
  79. package/skills/yad-connect-learning/SKILL.md +1 -1
  80. package/skills/yad-connect-learning/references/learning-registry.md +1 -1
  81. package/skills/yad-connect-repos/SKILL.md +31 -8
  82. package/skills/yad-connect-repos/references/code-context.md +1 -1
  83. package/skills/yad-connect-repos/references/{hub-config.md → product-config.md} +19 -11
  84. package/skills/yad-connect-repos/references/repos-registry.md +1 -1
  85. package/skills/yad-connect-testing/SKILL.md +1 -1
  86. package/skills/yad-connect-testing/references/testing-registry.md +1 -1
  87. package/skills/yad-discovery/SKILL.md +2 -2
  88. package/skills/yad-discovery/references/discovery-schema.md +1 -1
  89. package/skills/yad-docs/SKILL.md +1 -1
  90. package/skills/yad-docs/references/data-mapping.md +1 -1
  91. package/skills/yad-docs/templates/app/package-lock.json +8 -8
  92. package/skills/yad-docs/templates/app/package.json +1 -1
  93. package/skills/yad-docs/templates/app/src/components/Canvas/FlowCanvas.tsx +2 -2
  94. package/skills/yad-docs-overview/references/pipeline-model.md +3 -3
  95. package/skills/yad-engineer-review/SKILL.md +2 -2
  96. package/skills/yad-engineer-review/references/ship-and-record.md +3 -3
  97. package/skills/yad-epic/SKILL.md +6 -4
  98. package/skills/yad-epic/references/state-schema.md +13 -12
  99. package/skills/yad-open-pr/SKILL.md +1 -1
  100. package/skills/yad-pair-review/SKILL.md +5 -5
  101. package/skills/yad-pair-review/references/session-state.md +2 -2
  102. package/skills/yad-pr-template/SKILL.md +14 -12
  103. package/skills/yad-pr-template/templates/checks/pr-template.sh +18 -16
  104. package/skills/yad-pr-template/templates/checks/pr-title.sh +15 -13
  105. package/skills/yad-pr-template/templates/{hub → product}/github/pull_request_template.md +2 -2
  106. package/skills/yad-pr-template/templates/{hub → product}/gitlab/merge_request_templates/Default.md +2 -2
  107. package/skills/{yad-hub-bridge → yad-product-bridge}/SKILL.md +15 -15
  108. package/skills/{yad-hub-bridge → yad-product-bridge}/references/bridge.md +15 -10
  109. package/skills/{yad-hub-bridge → yad-product-bridge}/references/login-roster.md +2 -2
  110. package/skills/{yad-hub-bridge/templates/checks/hub-route.sh → yad-product-bridge/templates/checks/product-route.sh} +2 -2
  111. package/skills/{yad-hub-bridge → yad-product-bridge}/templates/github/yad-gate-sync.yml +29 -16
  112. package/skills/{yad-hub-bridge → yad-product-bridge}/templates/gitlab/gitlab-ci.include-root.yml +1 -1
  113. package/skills/{yad-hub-bridge → yad-product-bridge}/templates/gitlab/yad-gate-sync.gitlab-ci.yml +5 -3
  114. package/skills/yad-report/SKILL.md +1 -1
  115. package/skills/yad-review-companion/SKILL.md +3 -3
  116. package/skills/yad-review-gate/SKILL.md +22 -23
  117. package/skills/yad-review-gate/references/gating.md +13 -13
  118. package/skills/yad-run/SKILL.md +2 -2
  119. package/skills/yad-run/references/run-loop.md +2 -2
  120. package/skills/yad-spec/SKILL.md +2 -1
  121. package/skills/yad-spec/references/spec-handoff.md +19 -10
  122. package/skills/yad-stories/SKILL.md +2 -2
  123. package/skills/yad-sync-repos/SKILL.md +19 -3
  124. package/skills/yad-test-cases/SKILL.md +2 -2
  125. package/skills/yad-ui/SKILL.md +2 -2
  126. package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +0 -91
  127. package/skills/yad-checks/templates/hooks/ledger-guard.sh +0 -100
  128. package/skills/yad-checks/templates/hooks/yad-capture.sh +0 -53
@@ -5,7 +5,7 @@
5
5
  # that bypassed the template.
6
6
  # --profile code (default) — the code-repo task template (yad-pr-template templates/<platform>/):
7
7
  # requires `## Summary`, `## Impact & Risk`, `## Checklist`, and a filled `Risk level:` (low|medium|high).
8
- # --profile hub — the Shape artifact-review template (templates/hub/<platform>/):
8
+ # --profile product (old name: hub) — the Shape artifact-review template (templates/product/<platform>/):
9
9
  # requires `## Artifact under review`, `## Impact & Risk (front-half)` (or `(Shape)`), `## Checklist`, and a `Risk tags:` line.
10
10
  # BUT only for review/EP-* head branches. Every other Product PR is a tooling/code change to the Product
11
11
  # itself and uses the code task template instead; pass the head ref via --head so the gate knows
@@ -35,20 +35,22 @@ while [ $# -gt 0 ]; do
35
35
  *) ARGS+=("$1"); shift ;;
36
36
  esac
37
37
  done
38
- case "$PROFILE" in code|hub|product) ;; *) echo "FAIL [pr-template]: unknown --profile '$PROFILE' (code|hub|product)."; exit 1 ;; esac
39
- # `product` is the new name for the `hub` profile and BOTH are accepted.
38
+ case "$PROFILE" in code|hub|product) ;; *) echo "FAIL [pr-template]: unknown --profile '$PROFILE' (code|product, or hub, the old name for product)."; exit 1 ;; esac
39
+ # `product` is the Product's profile. `hub` is its old name, and BOTH are accepted.
40
40
  #
41
- # Not because the two sides update separately — they do not: this script and the workflow that passes
42
- # the flag are both in PRODUCT_WIRING (cli/manifest.mjs) and land together on one `yad update`. It is
43
- # the plain add-before-remove ladder instead: accept the new spelling now, switch the workflow
44
- # templates to emit it in a later release, drop the old one after that. Every shipped template still
45
- # passes `--profile hub` today, so this arm is dead weight until that switch — which is the point.
41
+ # This script and the workflow that passes the flag are both in PRODUCT_WIRING (cli/manifest.mjs) and
42
+ # normally land together on one `yad update` — but not always: a copy the team edited is kept. So the
43
+ # order is add-before-remove: the new spelling was accepted first, the workflow templates switched to
44
+ # emit it in E123 (`yad-product-checks.yml`), and the old one is dropped after that. `hub` stays
45
+ # accepted meanwhile, for a workflow of the team's own or one they edited. The other direction — an
46
+ # edited copy of THIS script from before 4.0, which rejects `product`, beside the new workflow — fails
47
+ # every Product PR; `yad update` and `yad doctor` (`profile:`) say so.
46
48
  #
47
- # Normalised to `hub` immediately, so nothing below has to know there are two spellings. That is
48
- # load-bearing in pr-title.sh and pr-template.sh: leave `$PROFILE` as `product` and the `= hub`
49
+ # Normalised to `product` immediately, so nothing below has to know there are two spellings. That is
50
+ # load-bearing in pr-title.sh and pr-template.sh: leave `$PROFILE` as `hub` and the `= product`
49
51
  # branch is skipped, taking the whole review-branch arm with it — including the guard that stops a
50
52
  # plain code title carrying an artifact change past its review.
51
- [ "$PROFILE" = product ] && PROFILE=hub
53
+ [ "$PROFILE" = hub ] && PROFILE=product
52
54
 
53
55
 
54
56
  # True when the PR changes a Shape artifact (anything under epics/** — or foundation/**, the Product level, E75). Reads the --changed list
@@ -115,14 +117,14 @@ check_code_body() {
115
117
  }
116
118
 
117
119
  # The Shape artifact-review template.
118
- check_hub_body() {
120
+ check_product_body() {
119
121
  require_heading '## Artifact under review' '## Artifact under review'
120
122
  # Add before you remove (change-safety rule 3). `front-half` is the ORIGINAL spelling and it is
121
123
  # still what `yad gate open` emits and what the shipped Product template carries; `Shape` is the
122
124
  # replacement, accepted here first so that this script is lenient BEFORE anything starts writing
123
125
  # the new wording. That ordering is the whole point: this file is refreshed by `yad update`
124
126
  # (PRODUCT_WIRING, cli/manifest.mjs), but the PR template beside it is NOT — it is installed only by
125
- # the `yad-pr-template repo:hub action: wire` skill, which nothing runs automatically. So a Product
127
+ # the `yad-pr-template repo:product action: wire` skill, which nothing runs automatically. So a Product
126
128
  # WILL sit with a refreshed checker next to an old template, and the checker has to accept both.
127
129
  # The emitter flips to `(Shape)` in the next major; only after that may `front-half` be dropped.
128
130
  require_heading '## Impact & Risk \((front-half|Shape)\)' '## Impact & Risk (front-half)'
@@ -134,9 +136,9 @@ check_hub_body() {
134
136
  }
135
137
 
136
138
  KIND="$PROFILE"
137
- if [ "$PROFILE" = hub ]; then
139
+ if [ "$PROFILE" = product ]; then
138
140
  case "$HEADREF" in
139
- review/EP-*|"") check_hub_body ;; # artifact-review PR (or unknown head — stay strict)
141
+ review/EP-*|"") check_product_body ;; # artifact-review PR (or unknown head — stay strict)
140
142
  *)
141
143
  # tooling/code change to the Product itself — UNLESS it changes Shape artifacts (epics/**),
142
144
  # which must go through a review/EP-* PR. Without this guard a non-review head could carry an
@@ -145,7 +147,7 @@ if [ "$PROFILE" = hub ]; then
145
147
  echo "FAIL [pr-template]: head '${HEADREF}' changes Shape artifacts (epics/** or foundation/**) but is not a review/EP-* branch — artifact changes must go through a review PR."
146
148
  rc=1
147
149
  else
148
- check_code_body; KIND="hub-tooling"
150
+ check_code_body; KIND="product-tooling"
149
151
  fi
150
152
  ;;
151
153
  esac
@@ -4,7 +4,7 @@
4
4
  # --profile code (default) — a Conventional-Commits subject "<type>: <description>", no trailing
5
5
  # period (config.yaml build.pr_title_style: same_as_commit_subject; one task = one PR, the title is
6
6
  # the squash-merge subject). Keep <type> in sync with cli/manifest.mjs COMMIT_TYPES.
7
- # --profile hub — a Shape artifact-review title "review: <artifact> (EP-<slug>)", the shape
7
+ # --profile product (old name: hub) — a Shape artifact-review title "review: <artifact> (EP-<slug>)", the shape
8
8
  # `yad gate open` creates (cli/gate.mjs) — BUT only for review/EP-* head branches. Every other
9
9
  # Product PR is a tooling/code change to the Product itself and follows the code convention; pass the
10
10
  # head ref via --head so the gate can tell the two apart (a tooling PR has no EP artifact to
@@ -34,20 +34,22 @@ while [ $# -gt 0 ]; do
34
34
  *) ARGS+=("$1"); shift ;;
35
35
  esac
36
36
  done
37
- case "$PROFILE" in code|hub|product) ;; *) echo "FAIL [pr-title]: unknown --profile '$PROFILE' (code|hub|product)."; exit 1 ;; esac
38
- # `product` is the new name for the `hub` profile and BOTH are accepted.
37
+ case "$PROFILE" in code|hub|product) ;; *) echo "FAIL [pr-title]: unknown --profile '$PROFILE' (code|product, or hub, the old name for product)."; exit 1 ;; esac
38
+ # `product` is the Product's profile. `hub` is its old name, and BOTH are accepted.
39
39
  #
40
- # Not because the two sides update separately — they do not: this script and the workflow that passes
41
- # the flag are both in PRODUCT_WIRING (cli/manifest.mjs) and land together on one `yad update`. It is
42
- # the plain add-before-remove ladder instead: accept the new spelling now, switch the workflow
43
- # templates to emit it in a later release, drop the old one after that. Every shipped template still
44
- # passes `--profile hub` today, so this arm is dead weight until that switch — which is the point.
40
+ # This script and the workflow that passes the flag are both in PRODUCT_WIRING (cli/manifest.mjs) and
41
+ # normally land together on one `yad update` — but not always: a copy the team edited is kept. So the
42
+ # order is add-before-remove: the new spelling was accepted first, the workflow templates switched to
43
+ # emit it in E123 (`yad-product-checks.yml`), and the old one is dropped after that. `hub` stays
44
+ # accepted meanwhile, for a workflow of the team's own or one they edited. The other direction — an
45
+ # edited copy of THIS script from before 4.0, which rejects `product`, beside the new workflow — fails
46
+ # every Product PR; `yad update` and `yad doctor` (`profile:`) say so.
45
47
  #
46
- # Normalised to `hub` immediately, so nothing below has to know there are two spellings. That is
47
- # load-bearing in pr-title.sh and pr-template.sh: leave `$PROFILE` as `product` and the `= hub`
48
+ # Normalised to `product` immediately, so nothing below has to know there are two spellings. That is
49
+ # load-bearing in pr-title.sh and pr-template.sh: leave `$PROFILE` as `hub` and the `= product`
48
50
  # branch is skipped, taking the whole review-branch arm with it — including the guard that stops a
49
51
  # plain code title carrying an artifact change past its review.
50
- [ "$PROFILE" = product ] && PROFILE=hub
52
+ [ "$PROFILE" = hub ] && PROFILE=product
51
53
 
52
54
 
53
55
  # True when the PR changes a Shape artifact (anything under epics/** — or foundation/**, the Product level, E75). Reads the --changed list
@@ -84,12 +86,12 @@ check_code_title() {
84
86
  exit 0
85
87
  }
86
88
 
87
- if [ "$PROFILE" = hub ]; then
89
+ if [ "$PROFILE" = product ]; then
88
90
  # review/EP-* head branch (or unknown head ref) => Shape artifact-review PR: 'review: <artifact> (EP-<slug>)'.
89
91
  case "$HEADREF" in
90
92
  review/EP-*|"")
91
93
  if printf '%s' "$TITLE" | grep -qE '^review: .+ \(EP-[a-z0-9-]+\)$'; then
92
- echo "PASS [pr-title]: '${TITLE}' (profile: hub, artifact-review)"
94
+ echo "PASS [pr-title]: '${TITLE}' (profile: product, artifact-review)"
93
95
  exit 0
94
96
  fi
95
97
  echo "FAIL [pr-title]: '${TITLE}' is not a Product review title 'review: <artifact> (EP-<slug>)'."
@@ -1,4 +1,4 @@
1
- <!-- SDLC HUB PR template — Shape artifact review (epic / architecture+contract / ui-design / stories). -->
1
+ <!-- SDLC Product PR template — Shape artifact review (epic / architecture+contract / ui-design / stories). -->
2
2
  <!-- This PR is a REVIEW VEHICLE on the Product, not a code merge. The file gate (yad-review-gate)
3
3
  advances the step when this PR is MERGED with the approvals met and every thread resolved. Reviewers
4
4
  approve/comment here; `yad gate sync` (or CI at merge, with a verified ledger) pulls that into the file ledger. -->
@@ -22,7 +22,7 @@
22
22
  `auth`/`payments`, 1 otherwise (the largest step, never the sum). No approval should be the author’s own.
23
23
  - Enforced: **1 approval** (the base). The risk step is **advisory**. `yad gate status <epic>`
24
24
  prints the count capped at the active people less one (one seat is left for the author), and
25
- `yad-hub-bridge`'s `hub-route.sh` run on this description prints the full count.
25
+ `yad-product-bridge`'s `product-route.sh` run on this description prints the full count.
26
26
  - No reviewers are requested automatically — ask them on this PR. The `domain:<repo>` labels show the touched repos.
27
27
 
28
28
  ## How to review (this drives the gate)
@@ -1,4 +1,4 @@
1
- <!-- SDLC HUB MR template — Shape artifact review (epic / architecture+contract / ui-design / stories). -->
1
+ <!-- SDLC Product MR template — Shape artifact review (epic / architecture+contract / ui-design / stories). -->
2
2
  <!-- This MR is a REVIEW VEHICLE on the Product, not a code merge. The file gate (yad-review-gate)
3
3
  advances the step when this MR is MERGED with the approvals met and every thread resolved. Reviewers
4
4
  approve/comment here; `yad gate sync` (or CI at merge, with a verified ledger) pulls that into the file ledger. -->
@@ -31,7 +31,7 @@
31
31
  `auth`/`payments`, 1 otherwise (the largest step, never the sum). No approval should be the author’s own.
32
32
  - Enforced: **1 approval** (the base). The risk step is **advisory**. `yad gate status <epic>`
33
33
  prints the count capped at the active people less one (one seat is left for the author), and
34
- `yad-hub-bridge`'s `hub-route.sh` run on this description prints the full count.
34
+ `yad-product-bridge`'s `product-route.sh` run on this description prints the full count.
35
35
  - No reviewers are requested automatically — ask them on this MR. The `domain:<repo>` labels show the touched repos.
36
36
 
37
37
  ## How to review (this drives the gate)
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: yad-hub-bridge
3
- description: 'The templated PR/MR bridge for the Shape review gate. When the Product has a platform (.sdlc/hub.json), it opens a review PR/MR on the Product for an authored artifact (the optional analysis / epic / architecture+contract / ui-design / stories / test-cases), applies the touched-domain labels (it requests no reviewers), and provides the read-only gh/glab recipes that yad-review-gate uses to pull platform comments + approvals back into the file ledger. Can also wire merge-time sync on the Product: a CI workflow that runs `yad gate ci` when a human merges a review PR/MR — CI is the sole ledger writer and writes only at merge, on the default branch (during review the platform PR/MR is the source of truth; CI never touches the review branch). Local-user auth only — no stored tokens. The file ledger stays the source of truth; degrades to the local gate when there is no platform / no CLI. Use when the user says "open the review PR", "route the review", "wire the gate sync", or it is invoked by yad-review-gate open/sync.'
2
+ name: yad-product-bridge
3
+ description: 'The templated PR/MR bridge for the Shape review gate. When the Product has a platform (.sdlc/product.json), it opens a review PR/MR on the Product for an authored artifact (analysis / epic / architecture+contract / ui-design / stories / test-cases), applies the touched-domain labels (it requests no reviewers), and provides the read-only gh/glab recipes that yad-review-gate uses to pull platform comments + approvals back into the file ledger. Can also wire merge-time sync on the Product: a CI workflow that runs `yad gate ci` when a human merges a review PR/MR — CI is the sole ledger writer and writes only at merge, on the default branch (during review the platform PR/MR is the source of truth; CI never touches the review branch). Local-user auth only — no stored tokens. The file ledger stays the source of truth; degrades to the local gate when there is no platform / no CLI. Use when the user says "open the review PR", "route the review", "wire the gate sync", or it is invoked by yad-review-gate open/sync.'
4
4
  ---
5
5
 
6
- # SDLC — Hub Review Bridge (the templated PR/MR bridge)
6
+ # SDLC — Product Review Bridge (the templated PR/MR bridge)
7
7
 
8
8
  **Goal:** Run the Shape review/comment/approval cycle through a **real PR/MR on the Product**,
9
9
  without changing the gate's predicate or making the file ledger optional. The verified ledger is an **alternate
@@ -17,12 +17,12 @@ keeps platform mechanics out of the gate). `yad-review-gate` *calls* it; it neve
17
17
 
18
18
  ## Conventions
19
19
 
20
- - `{project-root}` is the **Product**. The Product platform lives in `.sdlc/hub.json` — it holds no
21
- list of people (`config.yaml` `product.config` (older projects: `hub.config`); schema in `../yad-connect-repos/references/hub-config.md`).
20
+ - `{project-root}` is the **Product**. The Product platform lives in `.sdlc/product.json` (written together with its older name `.sdlc/hub.json`, same bytes, until v5; read from `hub.json` only when `product.json` is absent) — it holds no
21
+ list of people (`config.yaml` `product.config` (older projects: `hub.config`); schema in `../yad-connect-repos/references/product-config.md`).
22
22
  - Per-step review-PR record: `epics/EP-<slug>/.sdlc/product-prs.json`, written together with its
23
23
  older name `hub-prs.json` (`config.yaml` `product.pr_ledger` (older projects: `hub.pr_ledger`)) — a
24
24
  sibling ledger to `approvals.json`, so `state.json`'s locked step shape is untouched.
25
- - The review-PR body is the Product template from `yad-pr-template` (`templates/hub/<platform>/…`).
25
+ - The review-PR body is the Product template from `yad-pr-template` (`templates/product/<platform>/…`).
26
26
  - Branch per artifact: `review/EP-<slug>/<artifact-base>` (`config.yaml` `product.artifact_branch` (older projects: `hub.artifact_branch`)).
27
27
  - **Local-user auth only; store no tokens.** Use the user's own `gh`/`glab`. If neither is installed/
28
28
  authenticated, STOP this path and tell the gate to fall back to local — never embed a credential.
@@ -36,20 +36,20 @@ keeps platform mechanics out of the gate). `yad-review-gate` *calls* it; it neve
36
36
 
37
37
  ## Preconditions (the verified ledger runs only when all hold)
38
38
 
39
- `.sdlc/hub.json` exists with a non-null `platform`, `ledger: "verified"` (or, before `yad migrate`, `bridge_enabled: true`), `config.yaml` `product.bridge: true`
39
+ `.sdlc/product.json` (or, on an older Product, `.sdlc/hub.json`) exists with a non-null `platform`, `ledger: "verified"` (or, before `yad migrate`, `bridge_enabled: true`), `config.yaml` `product.bridge: true`
40
40
  (older projects: `hub.bridge: true`), and `gh` (GitHub) / `glab` (GitLab) installed **and authenticated**. If any fails, report that the
41
41
  gate proceeds **local** (no error) and stop.
42
42
 
43
43
  ## On Activation
44
44
 
45
45
  ### Step 1 — Resolve platform + routing
46
- Read `.sdlc/hub.json` for the platform, `epics/<epic>/epic.md` for `repos` + `owner` (the artifact's
46
+ Read `.sdlc/product.json` (`.sdlc/hub.json` on an older Product that has only that name) for the platform, `epics/<epic>/epic.md` for `repos` + `owner` (the artifact's
47
47
  author), and the matching `review+approve` step's `risk_tags` from `.sdlc/state.json`. Compute the
48
48
  **count** with `route` (below) — the same rule `yad-review-gate` enforces: `needed = base 1 + risk step`
49
49
  distinct approvers, where `contract` adds 2 and `auth`/`payments` add 1 (the highest tag, never the sum).
50
50
  Only the base (1 distinct approver, who should not be the author) holds the gate; the risk step is
51
51
  advisory. The gate caps the count at the active people less one and prints it (E72), but does
52
- not enforce it yet; when it cannot count the people — as in Product CI, which checks out only the hub —
52
+ not enforce it yet; when it cannot count the people — as in Product CI, which checks out only the Product —
53
53
  no cap is shown. Compute the **touched domains** too: the union of story `repos` for
54
54
  `stories-review`, the epic's `repos` for a step tagged `contract`, `auth` or `payments`, none otherwise.
55
55
  They become `domain:<repo>` labels and
@@ -63,7 +63,7 @@ are a hint for whom to ask. There is no roster to turn them into people.
63
63
  (`epic/…`, `change/…`, `analysis/…`, `discovery/…`) instead, so it carries the `.sdlc/` **seed** —
64
64
  that PR/MR is the only way a new epic's ledger reaches the default branch, and `ledger-guard`
65
65
  exempts it (see step 4).
66
- 2. Open the PR/MR with `gh`/`glab` using the Product body template (`yad-pr-template` `templates/hub/…`),
66
+ 2. Open the PR/MR with `gh`/`glab` using the Product body template (`yad-pr-template` `templates/product/…`),
67
67
  filled with the epic, artifact, gate step, owner, `epic.repos`, and the step's risk tags.
68
68
  3. **Request no reviewers.** Assign the person opening it — `@me` on GitHub (`gh` resolves it), the
69
69
  login `glab api user` reports on GitLab (no assignee when that lookup fails) — and add a
@@ -72,7 +72,7 @@ are a hint for whom to ask. There is no roster to turn them into people.
72
72
  reviewer suggestion: E68's suggestion reads a code change, so only `yad open-pr` (and `yad ship`) on a code repo prints it.)
73
73
  4. **Do not write the ledger.** CI is the sole writer and writes only at merge. During review nothing
74
74
  is recorded in the ledger — the platform PR/MR holds the review state (native approvals + threads).
75
- At merge, CI records the `hub-prs.json` entry (in the shape below) and advances on the default
75
+ At merge, CI records the PR-ledger entry in the shape below (in `product-prs.json` and `hub-prs.json`) and advances on the default
76
76
  branch:
77
77
  ```json
78
78
  { "step": "<review step id>", "artifact": "<artifact>", "platform": "github|gitlab",
@@ -88,7 +88,7 @@ are a hint for whom to ask. There is no roster to turn them into people.
88
88
  act on the platform; CI (`yad gate ci`) reconciles it onto the default branch at merge.
89
89
 
90
90
  ### Step 3 — `route` (print the count)
91
- Compute and print the count as above. Use `templates/checks/hub-route.sh <body>` to parse a PR/MR
91
+ Compute and print the count as above. Use `templates/checks/product-route.sh <body>` to parse a PR/MR
92
92
  body's Impact & Risk block when given one; it prints e.g. `ROUTE: 3 approvers = base 1 + contract risk 2
93
93
  (risk tag: contract)`, says only the base holds the gate (1 approval), that the risk step is advisory
94
94
  and `yad gate status` prints it capped by the active people, and lists the touched repos as a hint for whom
@@ -103,7 +103,7 @@ writer** of the ledger. There is no pre-merge CI write — during review the pla
103
103
  source of truth (native approvals + threads). On merge, CI re-reads approvals from the platform,
104
104
  advances the step, and flips the artifact `status:` on the **default branch** (the only place CI ever
105
105
  commits). Also install the `ledger-guard` check (yad-checks) so humans cannot commit gate-state files,
106
- and its local counterpart `hooks/ledger-guard.sh` — the harness hook that refuses an **agent** the
106
+ and its local counterpart `hooks/ledger-guard.mjs` — the harness hook that refuses an **agent** the
107
107
  same write at the moment it tries it, instead of letting it surface as a CI failure later (#171).
108
108
  Revoke-on-change is enforced at merge: on **GitHub** in code (an approval whose commit ≠ the merged
109
109
  head is dropped — no setting needed); on **GitLab** it has no per-approval commit SHA, so enabling the
@@ -115,14 +115,14 @@ a maintainer can force it with `yad gate ci --branch <review-branch> --pr <n> --
115
115
  default branch. (local mode keeps `yad gate sync` as the local writer.)
116
116
 
117
117
  1. Run `yad check --fix` (the wiring is manifest-driven, like `yad-checks`): with a platform +
118
- enabled bridge in `.sdlc/hub.json` it installs
118
+ enabled bridge in `.sdlc/product.json` it installs
119
119
  - GitHub → `.github/workflows/yad-gate-sync.yml` (from `templates/github/yad-gate-sync.yml`)
120
120
  - GitLab → `.gitlab/ci/yad-gate-sync.yml` (from `templates/gitlab/yad-gate-sync.gitlab-ci.yml`)
121
121
  - plus the Product-side **verified-commits** gate (`checks/verified-commits.sh` + its workflow/fragment,
122
122
  owned by `yad-checks`) so review PRs accept only platform-Verified (signed) commits — there is no
123
123
  author allowlist
124
124
  - the wired job runs an **exact** version, resolved at run time from the repo: the `YAD_VERSION`
125
- variable, else `hub.json` `gate_sync_version`, else the `.sdlc/cli-version.json` stamp, else the
125
+ variable, else `product.json` `gate_sync_version` (`hub.json` when it is absent), else the `.sdlc/cli-version.json` stamp, else the
126
126
  floating major the fragment ships with (`YAD_MAJOR`, `4` in this release) — a committed pin is used
127
127
  only when it is an exact release of that major, so `yad update` moves the fragment and the stamp
128
128
  to a new major together. It stopped floating on the major because that let a release change a scheduled job's
@@ -102,7 +102,7 @@ repository access decides who may approve. A review with no login is not counted
102
102
  (there is no per-comment id). A new round is opened only when the commenters or their counts change;
103
103
  an unchanged re-read rewrites the latest round in place, keeping its date. Comment rounds are recorded
104
104
  for an **open** step only, so re-visiting a merged review does not append a new round per pass.
105
- - Update the step's `hub-prs.json` `lastSyncedAt` when the sync **learned something** — every sync on
105
+ - Update the step's PR-ledger `lastSyncedAt` (`product-prs.json` + `hub-prs.json`, same bytes) when the sync **learned something** — every sync on
106
106
  an open step, and on a closed one only when the approval record actually changed (a re-opened review
107
107
  that was re-approved). An identical re-sync leaves it alone, so the ledger does not churn.
108
108
  - **Write the ledgers in a canonical order.** `approvals.json`, `comments.json` and the PR ledger
@@ -124,7 +124,7 @@ permanently. On an older yadflow the workaround is to disable the pipeline sched
124
124
  advance gates via the push path; only the catch-up for squash merges and bare approvals is lost.
125
125
  Since the wired job now runs an exact pin rather than floating on the major, that upgrade is a
126
126
  deliberate act: `yad update` (which re-stamps `.sdlc/cli-version.json`), or a `gate_sync_version` in
127
- `hub.json` — see the version table below.
127
+ `.sdlc/product.json` — see the version table below.
128
128
 
129
129
  ## Contract re-lock invalidates prior platform approvals too
130
130
 
@@ -204,7 +204,7 @@ and fall back to the major the fragment ships with — `YAD_MAJOR` in its `yad-p
204
204
  | # | Source | Set it in |
205
205
  |---|---|---|
206
206
  | 1 | `YAD_VERSION` — used **verbatim**, the operator's override | GitHub: Settings → Secrets and variables → Actions → **Variables**. GitLab: Settings → CI/CD → **Variables** (beside `SDLC_GATE_TOKEN`) |
207
- | 2 | `.sdlc/hub.json` → `gate_sync_version` — this Product's committed pin | edit `hub.json`, commit it |
207
+ | 2 | `.sdlc/product.json` → `gate_sync_version` (`.sdlc/hub.json` when `product.json` is absent) — this Product's committed pin | edit `product.json`, run `yad migrate --apply --keep product` (it copies it over `hub.json`; two that differ are refused, YAD-STATE-008), commit both. A Product that has only `hub.json`: edit that, and never create a `product.json` beside it by hand |
208
208
  | 3 | `.sdlc/cli-version.json` → `version` — the yadflow that last wired the Product | `yad update` re-stamps it |
209
209
  | 4 | `$YAD_MAJOR` — floating major, only when nothing above resolves. While only prereleases of that major exist (`4.0.0-next.N`) npm cannot resolve a bare major, so the job says so on stderr first; a Product wired by that release always has a stamp that resolves | — |
210
210
 
@@ -217,8 +217,9 @@ so anything that is not an exact release of this major is skipped, loudly, in fa
217
217
 
218
218
  **A new major moves both files at once.** `yad update` rewrites the fragment from the release's template
219
219
  and re-stamps `.sdlc/cli-version.json` in the same run, so a Product that updates to 4.x gets a fragment
220
- with `YAD_MAJOR=4` and a `4.x` stamp together. A `gate_sync_version` pin in `hub.json` from the old major
221
- is then SKIPPED (the job logs `ignoring pin … not an exact 4.x release`) — update it or remove it. A
220
+ with `YAD_MAJOR=4` and a `4.x` stamp together. A `gate_sync_version` pin in `product.json` from the old major
221
+ is then SKIPPED (the job logs `ignoring pin … not an exact 4.x release`) — update it or remove it (edit
222
+ `product.json`, then `yad migrate --apply --keep product`; on a Product that has only `hub.json`, edit that). A
222
223
  fragment the team EDITED is kept by `yad update` while the stamp still moves, so an edited fragment on
223
224
  the old major would skip the new stamp; `yad update` names that case, and `--overwrite-local` replaces
224
225
  the fragment (saving the edit beside it). The
@@ -287,13 +288,17 @@ which is exactly the intended revoke-on-change. Merge advances serialize on the
287
288
  push retries with a rebase.
288
289
 
289
290
  **Tokens.**
290
- - GitHub: the ephemeral `github.token` with `contents: write` + `pull-requests: read` — nothing stored.
291
- Only the merge job pushes, and only the default branch.
291
+ - GitHub: the ephemeral `github.token` — nothing stored. The workflow default is `contents: read`; the
292
+ two jobs that push (merge and reconcile) each ask for `contents: write` + `pull-requests: read`, and
293
+ push only the default branch.
292
294
  - GitLab: a masked `SDLC_GATE_TOKEN` project access token (`read_api` + `write_repository`) — the one
293
295
  documented bend of the no-stored-tokens rule; `CI_JOB_TOKEN` can neither read the approvals API nor
294
296
  push. Used only for the merge-time default-branch push + the API reads.
295
- - Protected default branch (GitHub): the merge advance needs to push it — prefer a ruleset bypass for
296
- Actions, else a fine-grained PAT as `SDLC_GATE_TOKEN` on the mergesync checkout.
297
+ - Protected default branch (GitHub): the merge advance needs to push it, and the built-in Actions token
298
+ cannot bypass branch protection or a ruleset (a ruleset's bypass list takes actors such as roles,
299
+ teams and GitHub Apps, never GitHub Actions). Pass a token whose owner may bypass to both jobs' `actions/checkout` — a GitHub
300
+ App on the bypass list, or a fine-grained PAT as `SDLC_GATE_TOKEN`. See yadflow's
301
+ `docs/branch-protection.md`.
297
302
 
298
303
  **Manual sync & recovery.** In verified mode `yad gate sync` is **advisory** (read-only) — it prints the
299
304
  predicate (and any `! may not be met:` / `! if the risk step were enforced:` warning, E73) but writes
@@ -304,7 +309,7 @@ the default branch: `yad gate ci --branch <review-branch> --pr <n> --merged` (th
304
309
  unlike advisory `yad gate sync`). Local mode (no platform) keeps `yad gate sync` as the local writer.
305
310
  The file ledger is still the source of truth.
306
311
 
307
- Because CI records the `hub-prs.json` pointer only at merge, a review the ledger has never seen still
312
+ Because CI records the PR-ledger pointer (`product-prs.json` + `hub-prs.json`) only at merge, a review the ledger has never seen still
308
313
  has to be nameable — otherwise `sync` refuses a PR that is sitting merged on the platform. With no
309
314
  recorded pointer, `yad gate sync <epic> <artifact>` resolves the PR/MR from the review branch
310
315
  (`review/<epic>/<artifact>`) itself, and `--pr <n>` names it outright:
@@ -153,10 +153,10 @@ None of these decides anything any more. `yad doctor` warns so the team can dele
153
153
 
154
154
  | Warning | What is left on disk |
155
155
  |---|---|
156
- | `people:roster-unused` | a non-empty `roster` in `.sdlc/hub.json` — still read for its name → login pairs only (see "Older records"); the hint counts the older records still waiting for a gate write to record their login, and says when it is safe to delete |
156
+ | `people:roster-unused` | a non-empty `roster` in `.sdlc/product.json` — still read for its name → login pairs only (see "Older records"); the hint counts the older records still waiting for a gate write to record their login, and says when it is safe to delete |
157
157
  | `people:roster-ambiguous` | a roster name given to more than one login (only checked when the roster is non-empty) |
158
158
  | `people:domain-owners-unused` | a repo in `.sdlc/repos.json` that lists `domain_owner` / `domain_owners` |
159
- | `people:verified-authors-unused` | a `verified_authors` list in `.sdlc/hub.json`, or a `.sdlc/verified-authors` file in the Product or a connected repo |
159
+ | `people:verified-authors-unused` | a `verified_authors` list in `.sdlc/product.json`, or a `.sdlc/verified-authors` file in the Product or a connected repo |
160
160
  | `people:allowlist-gate-stale` | an older `checks/verified-commits.sh` that still enforces the author list (a local-ledger Product's CI files are not refreshed by `yad check --fix`) |
161
161
 
162
162
  `yad roster` is removed (typing it prints a notice and exits 1), and `yad setup` collects no reviewers,
@@ -10,8 +10,8 @@
10
10
  # review; it does not approve or merge.
11
11
  set -euo pipefail
12
12
 
13
- BODY="${1:?usage: hub-route.sh <hub-pr-description-file>}"
14
- [ -f "$BODY" ] || { echo "hub-route: file not found: $BODY" >&2; exit 2; }
13
+ BODY="${1:?usage: product-route.sh <product-pr-description-file>}"
14
+ [ -f "$BODY" ] || { echo "product-route: file not found: $BODY" >&2; exit 2; }
15
15
 
16
16
  # Value side of the FIRST line matching a label regex, comments + markdown markers stripped. Tolerant:
17
17
  # a missing label yields empty (never aborts) — an advisory helper must still print for a half-filled body.
@@ -1,4 +1,4 @@
1
- # yad-managed: yad-hub-bridge
1
+ # yad-managed: yad-product-bridge
2
2
  # Merge-time gate sync for the PRODUCT (Path B). CI is the SOLE writer of the ledger, and it
3
3
  # writes ONLY at merge, ONLY to the default branch.
4
4
  #
@@ -30,11 +30,13 @@
30
30
  #
31
31
  # CI never approves and never merges — the merge click is the human approval act.
32
32
  #
33
- # Protected default branch? Only these jobs push there. If the default branch is protected against
34
- # Actions:
35
- # a) add a ruleset bypass so GitHub Actions may push to it, or
36
- # b) store a fine-grained PAT as the SDLC_GATE_TOKEN secret and pass it to actions/checkout below
37
- # via `with: { token: ${{ secrets.SDLC_GATE_TOKEN }} }` — see references/bridge.md.
33
+ # Protected default branch? Only these jobs push there, and the built-in Actions token cannot get past
34
+ # branch protection or a ruleset: a ruleset's bypass list takes actors such as roles, teams and GitHub
35
+ # Apps, never GitHub Actions. So give the push a token whose owner may bypass, and pass it to actions/checkout
36
+ # below via `with: { token: ${{ secrets.SDLC_GATE_TOKEN }} }`:
37
+ # a) a GitHub App on the bypass list (its installation token), or
38
+ # b) a fine-grained PAT of someone on the bypass list, stored as the SDLC_GATE_TOKEN secret.
39
+ # See docs/branch-protection.md in the yadflow repo, and references/bridge.md.
38
40
  name: yad-gate-sync
39
41
  on:
40
42
  pull_request:
@@ -42,9 +44,10 @@ on:
42
44
  schedule:
43
45
  - cron: "*/15 * * * *"
44
46
 
47
+ # Read-only by default. Each job below asks for the write access it needs, so a job added later
48
+ # does not inherit a token that can push.
45
49
  permissions:
46
- contents: write # push the merge-advance ledger commit to the default branch
47
- pull-requests: read # gh pr view + reviewThreads GraphQL
50
+ contents: read
48
51
 
49
52
  env:
50
53
  # The manual override for which yadflow these jobs run: set a repository (or organization) Actions
@@ -71,15 +74,18 @@ jobs:
71
74
  concurrency:
72
75
  group: yad-gate-mergesync
73
76
  cancel-in-progress: false
77
+ permissions:
78
+ contents: write # push the merge-advance ledger commit to the default branch
79
+ pull-requests: read # gh pr view + reviewThreads GraphQL
74
80
  env:
75
81
  GH_TOKEN: ${{ github.token }}
76
82
  steps:
77
83
  # Check out the default branch — the merge already landed the artifact here.
78
- - uses: actions/checkout@v7
84
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
79
85
  with:
80
86
  ref: ${{ github.event.pull_request.base.ref }}
81
87
  fetch-depth: 0
82
- - uses: actions/setup-node@v7
88
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
83
89
  with:
84
90
  node-version: "20"
85
91
  - name: Resolve the yadflow pin
@@ -87,7 +93,7 @@ jobs:
87
93
  # >>> yad-pin
88
94
  # Which yadflow this job runs, most explicit source first:
89
95
  # 1. $YAD_VERSION — the platform variable, a human's deliberate override, verbatim
90
- # 2. .sdlc/hub.json — "gate_sync_version", the committed pin for this Product
96
+ # 2. .sdlc/product.json — "gate_sync_version" (.sdlc/hub.json when it is absent), the committed pin for this Product
91
97
  # 3. .sdlc/cli-version.json — the version that last wired it (`yad update` re-stamps this)
92
98
  # 4. $YAD_MAJOR — floating major, only when nothing above resolves
93
99
  # Sources 2 and 3 are machine-written files a stale or hand-edited checkout can get wrong, so
@@ -108,9 +114,11 @@ jobs:
108
114
  tr -d '\n' < "$1" 2>/dev/null | sed -nE "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\1/p"
109
115
  }
110
116
  yad_pin_valid() { printf '%s' "${1:-}" | grep -Eq "^${YAD_MAJOR}\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?\$"; }
117
+ # The Product settings under their new name first (E122); the old one until v5 deletes it.
118
+ YAD_PIN_FILE=.sdlc/product.json; [ -f "$YAD_PIN_FILE" ] || YAD_PIN_FILE=.sdlc/hub.json
111
119
  YAD_PKG="${YAD_VERSION:-}"
112
120
  if [ -z "$YAD_PKG" ]; then
113
- for _cand in "$(yad_pin_read .sdlc/hub.json gate_sync_version)" "$(yad_pin_read .sdlc/cli-version.json version)"; do
121
+ for _cand in "$(yad_pin_read "$YAD_PIN_FILE" gate_sync_version)" "$(yad_pin_read .sdlc/cli-version.json version)"; do
114
122
  [ -n "$_cand" ] || continue
115
123
  if yad_pin_valid "$_cand"; then YAD_PKG="$_cand"; break; fi
116
124
  echo "yad-gate-sync: ignoring pin '${_cand}' — not an exact ${YAD_MAJOR}.x release." >&2
@@ -150,14 +158,17 @@ jobs:
150
158
  concurrency:
151
159
  group: yad-gate-mergesync
152
160
  cancel-in-progress: false
161
+ permissions:
162
+ contents: write # push the merge-advance ledger commit to the default branch
163
+ pull-requests: read # gh pr view + reviewThreads GraphQL
153
164
  env:
154
165
  GH_TOKEN: ${{ github.token }}
155
166
  steps:
156
- - uses: actions/checkout@v7
167
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
157
168
  with:
158
169
  ref: ${{ github.event.repository.default_branch }}
159
170
  fetch-depth: 0
160
- - uses: actions/setup-node@v7
171
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
161
172
  with:
162
173
  node-version: "20"
163
174
  - name: Resolve the yadflow pin
@@ -165,7 +176,7 @@ jobs:
165
176
  # >>> yad-pin
166
177
  # Which yadflow this job runs, most explicit source first:
167
178
  # 1. $YAD_VERSION — the platform variable, a human's deliberate override, verbatim
168
- # 2. .sdlc/hub.json — "gate_sync_version", the committed pin for this Product
179
+ # 2. .sdlc/product.json — "gate_sync_version" (.sdlc/hub.json when it is absent), the committed pin for this Product
169
180
  # 3. .sdlc/cli-version.json — the version that last wired it (`yad update` re-stamps this)
170
181
  # 4. $YAD_MAJOR — floating major, only when nothing above resolves
171
182
  # Sources 2 and 3 are machine-written files a stale or hand-edited checkout can get wrong, so
@@ -186,9 +197,11 @@ jobs:
186
197
  tr -d '\n' < "$1" 2>/dev/null | sed -nE "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\1/p"
187
198
  }
188
199
  yad_pin_valid() { printf '%s' "${1:-}" | grep -Eq "^${YAD_MAJOR}\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?\$"; }
200
+ # The Product settings under their new name first (E122); the old one until v5 deletes it.
201
+ YAD_PIN_FILE=.sdlc/product.json; [ -f "$YAD_PIN_FILE" ] || YAD_PIN_FILE=.sdlc/hub.json
189
202
  YAD_PKG="${YAD_VERSION:-}"
190
203
  if [ -z "$YAD_PKG" ]; then
191
- for _cand in "$(yad_pin_read .sdlc/hub.json gate_sync_version)" "$(yad_pin_read .sdlc/cli-version.json version)"; do
204
+ for _cand in "$(yad_pin_read "$YAD_PIN_FILE" gate_sync_version)" "$(yad_pin_read .sdlc/cli-version.json version)"; do
192
205
  [ -n "$_cand" ] || continue
193
206
  if yad_pin_valid "$_cand"; then YAD_PKG="$_cand"; break; fi
194
207
  echo "yad-gate-sync: ignoring pin '${_cand}' — not an exact ${YAD_MAJOR}.x release." >&2
@@ -1,5 +1,5 @@
1
1
  # SDLC gate sync — minimal root pipeline for the PRODUCT.
2
- # Written by the yad-hub-bridge `wire` action ONLY when the Product has no existing root
2
+ # Written by the yad-product-bridge `wire` action ONLY when the Product has no existing root
3
3
  # `.gitlab-ci.yml`. The job itself lives in the included fragment so the same single source is
4
4
  # reused whether or not a root pipeline already exists. If a root later grows real jobs, they
5
5
  # coexist with the included yad-* job (which carries `needs: []` and no `stage:`).
@@ -1,4 +1,4 @@
1
- # yad-managed-include: yad-hub-bridge
1
+ # yad-managed-include: yad-product-bridge
2
2
  # Merge-time gate sync for the PRODUCT, as an INCLUDABLE fragment (Path B). Pulled into the
3
3
  # Product's root .gitlab-ci.yml via:
4
4
  # include:
@@ -90,7 +90,7 @@ yad-gate-sync:
90
90
  # >>> yad-pin
91
91
  # Which yadflow this job runs, most explicit source first:
92
92
  # 1. $YAD_VERSION — the CI/CD variable, a human's deliberate override, verbatim
93
- # 2. .sdlc/hub.json — "gate_sync_version", the committed pin for this Product
93
+ # 2. .sdlc/product.json — "gate_sync_version" (.sdlc/hub.json when it is absent), the committed pin for this Product
94
94
  # 3. .sdlc/cli-version.json — the version that last wired it (`yad update` re-stamps this)
95
95
  # 4. $YAD_MAJOR — floating major, only when nothing above resolves
96
96
  # Sources 2 and 3 are machine-written files a stale or hand-edited checkout can get wrong, so
@@ -111,9 +111,11 @@ yad-gate-sync:
111
111
  tr -d '\n' < "$1" 2>/dev/null | sed -nE "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\1/p"
112
112
  }
113
113
  yad_pin_valid() { printf '%s' "${1:-}" | grep -Eq "^${YAD_MAJOR}\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?\$"; }
114
+ # The Product settings under their new name first (E122); the old one until v5 deletes it.
115
+ YAD_PIN_FILE=.sdlc/product.json; [ -f "$YAD_PIN_FILE" ] || YAD_PIN_FILE=.sdlc/hub.json
114
116
  YAD_PKG="${YAD_VERSION:-}"
115
117
  if [ -z "$YAD_PKG" ]; then
116
- for _cand in "$(yad_pin_read .sdlc/hub.json gate_sync_version)" "$(yad_pin_read .sdlc/cli-version.json version)"; do
118
+ for _cand in "$(yad_pin_read "$YAD_PIN_FILE" gate_sync_version)" "$(yad_pin_read .sdlc/cli-version.json version)"; do
117
119
  [ -n "$_cand" ] || continue
118
120
  if yad_pin_valid "$_cand"; then YAD_PKG="$_cand"; break; fi
119
121
  echo "yad-gate-sync: ignoring pin '${_cand}' — not an exact ${YAD_MAJOR}.x release." >&2
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yad-report
3
- description: 'Self issue reporter for the yad CLI. When a yadflow flow breaks, file a well-formed bug in the upstream yadflow repo (abdelrahmannasr/yadflow) with auto-scrubbed diagnostics attached, so recurring issues surface to the maintainers. Drives the `yad report` command: it captures ONLY a privacy-safe allowlist (yadflow/node/os version, tool present+authenticated booleans, Product platform enum, the YadError code/hint, a path-scrubbed error message, and the failing command + flag NAMES) — never absolute paths, hostnames, git URLs, repo names, logins/emails, epic/story IDs, branch names, or flag values. It searches open issues first to avoid duplicates, shows the exact payload, and asks before posting anything to the public repo. Files directly via an authenticated gh (the upstream repo is on GitHub), or falls back to a prefilled issues/new URL. Also offered automatically after an unexpected failure (interactive only; YAD_NO_REPORT=1 or SDLC_NONINTERACTIVE disables it). Use when the user says "yad report", "report this bug", "file an issue", or "something broke in the flow".'
3
+ description: 'Self issue reporter for the yad CLI. When a yadflow flow breaks, file a well-formed bug in the upstream yadflow repo (abdelrahmannasr/yadflow) with auto-scrubbed diagnostics attached. Drives the `yad report` command: it captures ONLY a privacy-safe allowlist (yadflow/node/os version, tool present+authenticated booleans, Product platform enum, the YadError code/hint, a path-scrubbed error message, and the failing command + flag NAMES) — never absolute paths, hostnames, git URLs, repo names, logins/emails, epic/story IDs, branch names, or flag values. It searches open issues first to avoid duplicates, shows the exact payload, and asks before posting anything to the public repo. Files directly via an authenticated gh, or falls back to a prefilled issues/new URL. Also offered automatically after an unexpected failure (interactive only; YAD_NO_REPORT=1 or SDLC_NONINTERACTIVE disables it). Use when the user says "yad report", "report this bug", "file an issue", or "something broke in the flow".'
4
4
  ---
5
5
 
6
6
  # yad — Self Issue Reporter
@@ -32,7 +32,7 @@ The CLI never calls an LLM — **you** (this skill) generate the text and post i
32
32
  questions and flagged concerns become the review record. If you **cannot** answer from the material,
33
33
  say so — that gap is itself a finding (post it as a genuine, blocking comment).
34
34
  4. **🏆 Social (E)** — a verified-engagement mark on a real companion review, a friendly public
35
- @-mention nudge on a bare approve, and light read-only gamification surfaced by `yad status`.
35
+ @-mention nudge on a bare approve, and light read-only gamification surfaced by the `yad-status` skill.
36
36
 
37
37
  ## Markers (the contract with the gate — all live in PLATFORM data, never a ledger file)
38
38
 
@@ -54,7 +54,7 @@ Inputs: `epic` + `artifact` (Shape) **or** `repo` + `pr` (Build); and the `actio
54
54
  `requireEngagement`, and `step.gateRule` — the step's full approver count (`base`, `riskStep`,
55
55
  `needed`, `risk`) — with `step.cap`, an object `{ active, limit, to, capped }` (E72): `to` is the count
56
56
  capped by the active people, `capped` is true when the cap lowered it, and the field is `null` when
57
- the people could not be counted, which is worth naming in the trailer on a high-risk step and never a blocker. Build: `yad review chat --repo <r>` (see `yad-engineer-review`) provides the
57
+ the people could not be counted, which is worth naming in the trailer on a high-risk step and never a blocker. Build: `yad review context --repo <r> --pr <n>` (see `yad-engineer-review`) provides the
58
58
  diff + code-map grounding. Read the named files yourself — never invent content.
59
59
  2. **Trailer.** Generate ≤6 lines (what / risk / read-time), grounded only in the bundle. Post it:
60
60
  `yad gate trailer <epic> <artifact> --body "<text>" [--pr <n>]` (idempotent; re-run after edits).
@@ -81,7 +81,7 @@ Inputs: `epic` + `artifact` (Shape) **or** `repo` + `pr` (Build); and the `actio
81
81
  - **Companion comments never block; genuine concerns always do.** Flag the former with `yad:noblock`;
82
82
  leave the latter unflagged.
83
83
  - **The engagement signal is gameable and you say so.** It makes review quality visible; it is not
84
- proof. The strict-mode switch is `hub.review.requireEngagement` (off by default).
84
+ proof. The strict-mode switch is `review.requireEngagement` (Product settings) (off by default).
85
85
  - **The companion never approves on a human's behalf and never merges.** It assists; the human acts.
86
86
 
87
87
  ## local mode (no platform)