yadflow 3.18.0 → 3.19.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +11 -11
  3. package/bin/yad.mjs +8 -8
  4. package/cli/artifact-status.mjs +4 -4
  5. package/cli/checkpoint.mjs +25 -25
  6. package/cli/commit.mjs +1 -1
  7. package/cli/companion.mjs +2 -2
  8. package/cli/doctor.mjs +10 -10
  9. package/cli/epic-state.mjs +29 -29
  10. package/cli/errors.mjs +1 -1
  11. package/cli/gate.mjs +32 -33
  12. package/cli/hook.mjs +4 -4
  13. package/cli/hubcommit.mjs +1 -1
  14. package/cli/ledger.mjs +3 -3
  15. package/cli/lib.mjs +23 -9
  16. package/cli/manifest.mjs +42 -21
  17. package/cli/migrate.mjs +54 -12
  18. package/cli/next.mjs +5 -5
  19. package/cli/openpr.mjs +8 -8
  20. package/cli/plan.mjs +28 -9
  21. package/cli/platform.mjs +1 -1
  22. package/cli/report.mjs +1 -1
  23. package/cli/review.mjs +5 -5
  24. package/cli/setup.mjs +22 -10
  25. package/cli/ship.mjs +1 -1
  26. package/cli/skip.mjs +1 -1
  27. package/cli/thread.mjs +1 -1
  28. package/cli/tidy.mjs +2 -2
  29. package/cli/update-commit.mjs +1 -1
  30. package/package.json +1 -1
  31. package/skills/sdlc/config.yaml +30 -30
  32. package/skills/sdlc/module-help.csv +21 -21
  33. package/skills/yad-analysis/SKILL.md +10 -10
  34. package/skills/yad-architecture/SKILL.md +10 -10
  35. package/skills/yad-architecture/references/contract-format.md +2 -3
  36. package/skills/yad-backfill/SKILL.md +5 -5
  37. package/skills/yad-change/SKILL.md +13 -13
  38. package/skills/yad-change/references/triage.md +2 -3
  39. package/skills/yad-checks/SKILL.md +34 -16
  40. package/skills/yad-checks/references/check-gates.md +63 -19
  41. package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
  42. package/skills/yad-checks/templates/checks/epic-open.sh +1 -1
  43. package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
  44. package/skills/yad-checks/templates/checks/ledger-guard.sh +41 -12
  45. package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
  46. package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +3 -3
  47. package/skills/yad-checks/templates/github/yad-checks.yml +24 -3
  48. package/skills/yad-checks/templates/github/yad-hub-checks.yml +2 -2
  49. package/skills/yad-checks/templates/github/yad-verified-commits.yml +1 -1
  50. package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
  51. package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +12 -3
  52. package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +2 -2
  53. package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +1 -1
  54. package/skills/yad-checks/templates/hooks/ledger-guard.sh +1 -1
  55. package/skills/yad-commit/SKILL.md +2 -2
  56. package/skills/yad-connect-design/SKILL.md +1 -1
  57. package/skills/yad-connect-docs/SKILL.md +1 -1
  58. package/skills/yad-connect-repos/SKILL.md +32 -15
  59. package/skills/yad-connect-repos/references/code-context.md +2 -2
  60. package/skills/yad-connect-repos/references/hub-config.md +25 -11
  61. package/skills/yad-connect-repos/references/repos-registry.md +3 -3
  62. package/skills/yad-connect-testing/SKILL.md +1 -1
  63. package/skills/yad-defects/SKILL.md +1 -1
  64. package/skills/yad-discovery/SKILL.md +6 -6
  65. package/skills/yad-discovery/references/discovery-schema.md +1 -1
  66. package/skills/yad-docs/SKILL.md +3 -3
  67. package/skills/yad-docs/templates/app/package-lock.json +65 -51
  68. package/skills/yad-docs/templates/app/package.json +1 -1
  69. package/skills/yad-docs-overview/SKILL.md +3 -3
  70. package/skills/yad-docs-overview/references/pipeline-model.md +17 -11
  71. package/skills/yad-engineer-review/SKILL.md +9 -9
  72. package/skills/yad-engineer-review/references/ship-and-record.md +8 -8
  73. package/skills/yad-epic/SKILL.md +15 -15
  74. package/skills/yad-epic/references/state-schema.md +30 -30
  75. package/skills/yad-hub-bridge/SKILL.md +14 -14
  76. package/skills/yad-hub-bridge/references/bridge.md +17 -17
  77. package/skills/yad-hub-bridge/references/login-roster.md +3 -3
  78. package/skills/yad-hub-bridge/templates/checks/hub-route.sh +1 -1
  79. package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +1 -1
  80. package/skills/yad-implement/SKILL.md +3 -3
  81. package/skills/yad-open-pr/SKILL.md +4 -4
  82. package/skills/yad-pair-review/SKILL.md +12 -12
  83. package/skills/yad-pair-review/references/session-state.md +3 -3
  84. package/skills/yad-pr-template/SKILL.md +4 -4
  85. package/skills/yad-pr-template/references/risk-routing.md +1 -1
  86. package/skills/yad-pr-template/templates/checks/pr-template.sh +18 -10
  87. package/skills/yad-pr-template/templates/checks/pr-title.sh +7 -7
  88. package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +1 -1
  89. package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +1 -1
  90. package/skills/yad-reconcile/SKILL.md +1 -1
  91. package/skills/yad-report/SKILL.md +1 -1
  92. package/skills/yad-review-companion/SKILL.md +7 -7
  93. package/skills/yad-review-gate/SKILL.md +18 -18
  94. package/skills/yad-review-gate/references/gating.md +3 -3
  95. package/skills/yad-run/SKILL.md +10 -10
  96. package/skills/yad-run/references/run-loop.md +8 -8
  97. package/skills/yad-ship/SKILL.md +4 -4
  98. package/skills/yad-spec/SKILL.md +10 -11
  99. package/skills/yad-status/SKILL.md +13 -13
  100. package/skills/yad-stories/SKILL.md +12 -12
  101. package/skills/yad-stories/references/story-schema.md +3 -3
  102. package/skills/yad-stub/SKILL.md +3 -3
  103. package/skills/yad-sync-repos/SKILL.md +1 -1
  104. package/skills/yad-test-cases/SKILL.md +12 -13
  105. package/skills/yad-test-cases/references/test-cases-schema.md +1 -1
  106. package/skills/yad-ui/SKILL.md +10 -10
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env bash
2
+ # Shared package-manager detection for the dependency installer and build/test/lint gate.
3
+ # The standard package.json `packageManager` field is authoritative. A pnpm lockfile is a
4
+ # compatibility fallback for local gate execution, but CI installation requires an exact pnpm
5
+ # version so a generated workflow never silently changes toolchains.
6
+
7
+ readonly YAD_PACKAGE_JSON="package.json"
8
+ readonly YAD_PNPM_LOCKFILE="pnpm-lock.yaml"
9
+ readonly YAD_NPM_LOCKFILE="package-lock.json"
10
+ readonly YAD_NPM_SHRINKWRAP="npm-shrinkwrap.json"
11
+
12
+ # Corepack is the Node tool that downloads and activates the declared version. It is bundled with
13
+ # Node 18 through 24 (Node 25+ dropped it), and a copy older than 0.31 — before Node 18.20.7 /
14
+ # 20.19 / 22.14 — predates the 2025 npm registry key rotation and rejects anything published since
15
+ # with a raw "Cannot find matching keyid". A declared packageManager without a working Corepack must
16
+ # say so instead of dying with "command not found" or that error in the middle of the job.
17
+ readonly YAD_COREPACK_LINES="set YAD_NODE_VERSION to a current 20, 22 or 24 line, or install a current Corepack first (npm install -g corepack)"
18
+ yad_require_corepack() {
19
+ if command -v corepack >/dev/null 2>&1; then return 0; fi
20
+ echo "FAIL [package-manager]: package.json#packageManager is set but corepack is not on PATH (Node 25+ no longer bundles it) — $YAD_COREPACK_LINES." >&2
21
+ return 1
22
+ }
23
+ # `corepack prepare` with the guidance attached to every way it can fail: a stale Corepack (the
24
+ # keyid error above), no network, or a version the registry does not have.
25
+ yad_corepack_prepare() {
26
+ local spec="$1"
27
+ if corepack prepare "$spec" --activate; then return 0; fi
28
+ echo "FAIL [package-manager]: corepack could not activate $spec. A Corepack older than 0.31 cannot verify packages signed after the 2025 npm registry key rotation — $YAD_COREPACK_LINES; otherwise check that the version exists and the registry is reachable." >&2
29
+ return 1
30
+ }
31
+
32
+ # The command that runs the declared manager. Corepack activates a pinned npm but never shims it
33
+ # (`corepack enable` covers pnpm and yarn only), so a bare `npm` would be the Node image's ambient
34
+ # npm; a pinned npm is dispatched through Corepack instead, for `npm ci` and for the gate's `run`s.
35
+ # Prints the words space-separated so callers can `read -r -a` them into an array (bash 3.2 has no
36
+ # mapfile); the words are fixed literals, never repo input.
37
+ yad_package_manager_command() {
38
+ local spec="$1" manager="$2"
39
+ if [[ "$spec" == npm@* ]]; then
40
+ yad_require_corepack || return 1
41
+ printf '%s\n' "corepack npm"
42
+ else
43
+ printf '%s\n' "$manager"
44
+ fi
45
+ }
46
+
47
+ yad_validate_exact_package_manager_spec() {
48
+ local spec="$1"
49
+ # The JavaScript is deliberately single-quoted so Bash cannot expand its template literals.
50
+ # shellcheck disable=SC2016
51
+ YAD_PACKAGE_MANAGER_SPEC="$spec" node --input-type=module -e '
52
+ const spec = process.env.YAD_PACKAGE_MANAGER_SPEC;
53
+ const at = spec.indexOf("@");
54
+ const version = spec.slice(at + 1);
55
+ // The Corepack integrity suffix is `+<algorithm>.<hex digest>`: it hashes the download with the
56
+ // named algorithm and compares the LOWERCASE hex string byte for byte. Every algorithm Corepack
57
+ // has written or accepts is listed with its digest length; anything else is not a Corepack
58
+ // suffix and fails closed.
59
+ const COREPACK_DIGEST_HEX_LENGTH = { sha1: 40, sha224: 56, sha256: 64, sha384: 96, sha512: 128 };
60
+ const exactSemver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
61
+ const match = exactSemver.exec(version);
62
+ const invalidNumericPrerelease = match?.[4]?.split(".").some(
63
+ identifier => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"),
64
+ );
65
+ const isCorepackIntegrity = (build) => {
66
+ const [algorithm, digest, ...rest] = build.split(".");
67
+ const length = COREPACK_DIGEST_HEX_LENGTH[algorithm];
68
+ return rest.length === 0 && length !== undefined && new RegExp(`^[0-9a-f]{${length}}$`).test(digest ?? "");
69
+ };
70
+ const invalidBuildMetadata = match?.[5] !== undefined && !isCorepackIntegrity(match[5]);
71
+ if (!match || match[0] !== version || invalidNumericPrerelease || invalidBuildMetadata) {
72
+ const algorithms = Object.keys(COREPACK_DIGEST_HEX_LENGTH).join("|");
73
+ process.stderr.write(`FAIL [package-manager]: packageManager ${JSON.stringify(spec)} must name an exact semantic version, optionally followed by the Corepack integrity suffix +<${algorithms}>.<lowercase hex digest>.\n`);
74
+ process.exit(1);
75
+ }
76
+ '
77
+ }
78
+
79
+ yad_package_manager_spec() {
80
+ if [ ! -f "$YAD_PACKAGE_JSON" ]; then
81
+ echo "FAIL [package-manager]: package.json is missing." >&2
82
+ return 1
83
+ fi
84
+ # The JavaScript is deliberately single-quoted so Bash cannot expand its template literals.
85
+ # shellcheck disable=SC2016
86
+ node --input-type=module -e '
87
+ import fs from "node:fs";
88
+ let pkg;
89
+ // npm strips a UTF-8 byte-order mark before parsing (Windows editors often write one); do the same
90
+ // so a manifest npm itself installs from is not rejected here.
91
+ try { pkg = JSON.parse(fs.readFileSync("package.json", "utf8").replace(/^\uFEFF/, "")); }
92
+ catch { process.stderr.write("FAIL [package-manager]: package.json is not valid JSON.\n"); process.exit(1); }
93
+ if (pkg === null || typeof pkg !== "object" || Array.isArray(pkg)) {
94
+ process.stderr.write("FAIL [package-manager]: package.json must be a JSON object.\n"); process.exit(1);
95
+ }
96
+ if (pkg.packageManager !== undefined && typeof pkg.packageManager !== "string") {
97
+ process.stderr.write("FAIL [package-manager]: packageManager must be a string.\n"); process.exit(1);
98
+ }
99
+ if (pkg.packageManager && /[\r\n]/.test(pkg.packageManager)) {
100
+ process.stderr.write(`FAIL [package-manager]: packageManager ${JSON.stringify(pkg.packageManager)} must name an exact semantic version.\n`);
101
+ process.exit(1);
102
+ }
103
+ process.stdout.write(pkg.packageManager || "");
104
+ '
105
+ }
106
+ yad_detect_package_manager() {
107
+ local spec="${1-}"
108
+ case "$spec" in
109
+ "")
110
+ # No declaration: npm, exactly as before this field was read — UNLESS the repo carries a pnpm
111
+ # lockfile and no npm one. A repo with both (a stale package-lock.json after a migration is
112
+ # common) keeps the historical npm path rather than silently flipping toolchains.
113
+ if [ -f "$YAD_PNPM_LOCKFILE" ] && [ ! -f "$YAD_NPM_LOCKFILE" ] && [ ! -f "$YAD_NPM_SHRINKWRAP" ]; then
114
+ printf '%s\n' "pnpm"
115
+ else
116
+ printf '%s\n' "npm"
117
+ fi
118
+ ;;
119
+ npm@*)
120
+ yad_validate_exact_package_manager_spec "$spec" || return 1
121
+ printf '%s\n' "npm"
122
+ ;;
123
+ pnpm@*)
124
+ yad_validate_exact_package_manager_spec "$spec" || return 1
125
+ printf '%s\n' "pnpm"
126
+ ;;
127
+ *)
128
+ # yarn/bun declared for local use while CI installs from an npm lockfile was green before this
129
+ # field was read; keep that repo on the historical npm path, with a warning, rather than
130
+ # failing every PR on upgrade. Without an npm lockfile there is nothing to fall back to.
131
+ if [ -f "$YAD_NPM_LOCKFILE" ] || [ -f "$YAD_NPM_SHRINKWRAP" ]; then
132
+ echo "WARN [package-manager]: packageManager '$spec' is not supported by the gate (npm, pnpm); an npm lockfile is present, so continuing on the historical npm path." >&2
133
+ printf '%s\n' "npm"
134
+ else
135
+ echo "FAIL [package-manager]: unsupported packageManager '$spec' (supported: npm, pnpm)." >&2
136
+ return 1
137
+ fi
138
+ ;;
139
+ esac
140
+ }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
- # reconcile-debt gate (Phase 6 — hotfix debt). A hotfix may ship code BEFORE its front gates approve
3
- # (ship-first), but it opens a reconcile-debt.json entry: the front artifacts do not yet describe what
2
+ # reconcile-debt gate (Phase 6 — hotfix debt). A hotfix may ship code BEFORE its Shape gates approve
3
+ # (ship-first), but it opens a reconcile-debt.json entry: the Shape artifacts do not yet describe what
4
4
  # is in production. That debt must be PAID (artifacts updated + a regression test added) before the NEXT
5
5
  # normal change on the same feature thread can ship. This gate FAILs a non-maintenance commit whose
6
6
  # owning epic is on a thread carrying an OPEN debt that this epic does not itself own.
@@ -148,7 +148,7 @@ while IFS= read -r sha; do
148
148
  if [ -n "$blockers" ]; then
149
149
  echo "FAIL [reconcile-debt]: thread ${thread} carries OPEN hotfix debt:"
150
150
  printf '%s\n' "$blockers" | sed 's/^/ /'
151
- echo " -> Pay it first: update the front artifacts + add the regression test, then mark the"
151
+ echo " -> Pay it first: update the Shape artifacts + add the regression test, then mark the"
152
152
  echo " reconcile-debt.json entry status: paid. The thread is frozen for new changes until then."
153
153
  rc=1
154
154
  continue
@@ -34,12 +34,33 @@ jobs:
34
34
  if: github.event.action != 'edited'
35
35
  env:
36
36
  YAD_TEST_MAX_WORKERS: "2" # cap jest/vitest test workers in CI; ignored by other runners
37
+ YAD_NODE_VERSION: ${{ vars.YAD_NODE_VERSION || '22' }} # repo variable; a current 20, 22 or 24 line (Node 25+ has no Corepack)
38
+ NX_BASE: ${{ github.event.pull_request.base.sha }}
39
+ NX_HEAD: ${{ github.event.pull_request.head.sha }}
40
+ COREPACK_HOME: ${{ github.workspace }}/.corepack-cache # pinned-manager downloads, cached below
37
41
  steps:
38
42
  - uses: actions/checkout@v4
39
- with: { fetch-depth: 0 }
43
+ with:
44
+ fetch-depth: 0
45
+ filter: blob:none
40
46
  - 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)
47
+ with:
48
+ node-version: ${{ env.YAD_NODE_VERSION }}
49
+ # Dependency cache for either manager: npm's ~/.npm, pnpm's content-addressable store, and the
50
+ # Corepack home where `corepack prepare` keeps the pinned manager (otherwise re-downloaded from
51
+ # the registry on every job, even when the dependency cache hits). Keyed on the lockfiles AND
52
+ # package.json, since a packageManager bump changes no lockfile. setup-node's own `cache:` must
53
+ # name the manager up front (and needs pnpm on PATH before it runs), which package.json only
54
+ # decides later — so cache the directories directly.
55
+ - uses: actions/cache@v4
56
+ with:
57
+ path: |
58
+ ~/.npm
59
+ ~/.local/share/pnpm/store
60
+ ${{ env.COREPACK_HOME }}
61
+ key: yad-deps-${{ runner.os }}-node${{ env.YAD_NODE_VERSION }}-${{ hashFiles('package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'package.json') }}
62
+ restore-keys: yad-deps-${{ runner.os }}-node${{ env.YAD_NODE_VERSION }}-
63
+ - run: bash checks/install-deps.sh
43
64
  - run: bash checks/build-test-lint.sh
44
65
 
45
66
  # Phase 6 — feature-thread gates. lineage-check: the change links a real threaded epic. epic-open:
@@ -1,6 +1,6 @@
1
1
  # yad-managed: yad-checks
2
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
3
+ # review/EP-* PRs are Shape artifact-review vehicles — a `review: <artifact> (EP-<slug>)` title
4
4
  # and the hub artifact-review template body.
5
5
  # every other PR is a tooling/code change to the hub itself — a Conventional-Commits title and the
6
6
  # code task template body (same convention the code repos use).
@@ -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,5 +1,5 @@
1
1
  # yad-managed: yad-checks
2
- # verified-commits gate for the PRODUCT HUB: every PR (including the front-half review/EP-* PRs)
2
+ # verified-commits gate for the PRODUCT HUB: every PR (including the Shape review/EP-* PRs)
3
3
  # must contain only commits whose signature the platform marks Verified AND whose author email is a
4
4
  # known identity (.sdlc/verified-authors — generated by `yad check --fix` from the hub roster).
5
5
  # Standalone workflow so it never collides with the hub-flavored yad-checks workflow.
@@ -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
@@ -50,7 +59,7 @@ yad-build-test-lint:
50
59
  variables:
51
60
  YAD_TEST_MAX_WORKERS: "2" # cap jest/vitest test workers in CI; ignored by other runners
52
61
  script:
53
- - npm ci
62
+ - bash checks/install-deps.sh
54
63
  - bash checks/build-test-lint.sh
55
64
 
56
65
  # Phase 6 — feature-thread gates (lineage / seal / hotfix-debt). Build on spec-link's story->epic
@@ -3,7 +3,7 @@
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-
6
+ # MRs run with `--profile hub` and split by source branch: review/EP-* MRs are Shape artifact-
7
7
  # review vehicles (a `review: <artifact> (EP-<slug>)` title + the hub artifact-review template body);
8
8
  # every other MR is a tooling/code change to the hub 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
@@ -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: []
@@ -3,7 +3,7 @@
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
6
+ # Every MR (including the Shape review/EP-* MRs) must contain only commits whose signature the
7
7
  # platform marks Verified AND whose author email is a known identity (.sdlc/verified-authors).
8
8
  #
9
9
  # Needs a CI/CD variable GITLAB_TOKEN (or SDLC_API_TOKEN) with read_api scope — CI_JOB_TOKEN cannot
@@ -1,7 +1,7 @@
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
4
+ # The gate ledger is CI-owned in verified mode: `checks/ledger-guard.sh` rejects any non-bot commit
5
5
  # that changes `epics/*/.sdlc/{state,approvals,comments,hub-prs}.json` or `epics/*/reviews/*.md`.
6
6
  # This hook says so at the moment an agent tries the edit, instead of twenty minutes later in a
7
7
  # failed pipeline, and names the command that owns the transition (`yad gate open`).
@@ -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
@@ -10,7 +10,7 @@ and/or web pages — inside a design tool such as **Figma**, alongside the Markd
10
10
  (`ui-design.md` / `DESIGN.md`). This skill **connects** a design tool to the product hub 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
@@ -10,7 +10,7 @@ description: 'Connects a docs/Pages publishing target to the product hub so the
10
10
  instead of only committing its source. This skill **connects** a publishing target to the product hub
11
11
  and records *how* to reach it (the platform, the publish scope, the base path) — 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 docs registry. `yad-docs` / `yad-docs-overview`
15
15
  consume it: when a target is connected, they theme + generate the site and drive `yad docs deploy`;
16
16
  when nothing is connected (`target: "none"`), they still generate and **npm-build** the site but stop
@@ -11,15 +11,15 @@ stories that contradict or duplicate what is built. This skill **connects** code
11
11
  hub and caches an AI-readable picture of each. It is the product → code half of the 2-way link (the
12
12
  code → product half is the existing `link.md` back-pointer each spec carries).
13
13
 
14
- This is **setup/maintenance**, not a gated front state — it never touches `.sdlc/state.json` or any
14
+ This is **setup/maintenance**, not a gated Shape step — it never touches `.sdlc/state.json` or any
15
15
  epic's approvals. It only writes the project-wide registry and the per-repo context cache.
16
16
 
17
17
  ## Conventions
18
18
 
19
19
  - `{project-root}` resolves from the project working directory (the **product hub**).
20
- - The **product repo is the front-phase toolchain hub** (`config.yaml` `code_context`): Repomix (and
20
+ - The **product repo is the Shape phase toolchain hub** (`config.yaml` `code_context`): Repomix (and
21
21
  Impeccable, later) are installed/run **here** and target the connected code repos **by path**. The
22
- code repos themselves need no install for this. (The build-half CI gates are the exception — they
22
+ code repos themselves need no install for this. (The Build CI gates are the exception — they
23
23
  live inside each code repo; see `yad-checks`.)
24
24
  - **Repomix is a true CLI subprocess** (Phase 0 / RESEARCH-NOTES §3): `npx repomix@latest [flags]` —
25
25
  NOT a slash-command. It secret-scans by default (Secretlint).
@@ -90,7 +90,7 @@ Feed the pack to the AI with the **"describe what exists, do not invent"** instr
90
90
  (`references/code-context.md`) and write `{project-root}/.sdlc/code-context/<repo>/code-map.md`: a small
91
91
  index of **stack/conventions, entry points, public endpoints/APIs, events, data models/entities, and
92
92
  module layout**. Mark anything unclear `<!-- unverified: ... -->`; never fill gaps with invented
93
- behaviour. This is the cheap artifact every front phase loads by default (the full pack is read only
93
+ behaviour. This is the cheap artifact every Shape phase loads by default (the full pack is read only
94
94
  when a phase needs depth).
95
95
 
96
96
  ### Step 4 — Record the repo in the registry
@@ -122,7 +122,7 @@ new repo later is the same `connect` action.
122
122
 
123
123
  ### Step 5 — Report
124
124
  Report the connected repo, its `platform`, the pack + code-map paths, the secret-scan result, and that
125
- the front phases will now load this repo's code-map. Nothing auto-advances; this is setup.
125
+ the Shape phases will now load this repo's code-map. Nothing auto-advances; this is setup.
126
126
 
127
127
  ## Other actions
128
128
 
@@ -139,20 +139,37 @@ the front phases will now load this repo's code-map. Nothing auto-advances; this
139
139
  - **`disconnect`** — remove the repo from the registry and delete its cache dir. Leaves the **code repo
140
140
  itself untouched**.
141
141
 
142
- ## Hub detection + reviewer roster (the front-half review bridge)
142
+ ## Hub detection + reviewer roster (the Shape review bridge)
143
143
 
144
- The hub is itself a git repo on a platform. These actions record that so the front-half review/comment/
144
+ The hub is itself a git repo on a platform. These actions record that so the Shape review/comment/
145
145
  approval cycle can run through a real PR/MR on the hub (`yad-review-gate` + `yad-hub-bridge`). They
146
146
  write only `{project-root}/.sdlc/hub.json` (`config.yaml` `hub.config`) — never an epic's state/approvals.
147
147
 
148
148
  - **`detect-hub`** — detect the hub's own platform and upsert `.sdlc/hub.json`. Run
149
149
  `git remote get-url origin` **on the hub** and read the host with the SAME logic Step 1 uses for code
150
150
  repos: `github.com` → `github`, GitLab host → `gitlab`, no remote → `platform: null`. Record
151
- `git_url`, `default_branch`, `detectedAt`, and `bridge_enabled` — **`true` only when a platform was
152
- detected, `false` alongside `platform: null`** (preserve an existing roster). The two travel
153
- together: bridge mode is a platform AND the flag (`isBridge`, `cli/gate.mjs`), and `yad setup`
154
- derives both from one value, so writing the flag onto a platform-less hub creates a state no CLI
155
- path can produce and the gates read differently (#186).
151
+ `git_url`, `default_branch`, `detectedAt`, and **all three of** `ledger`, `bridge_enabled` and
152
+ `bridge`:
153
+
154
+ | a platform was detected | no platform (`platform: null`) |
155
+ |---|---|
156
+ | `"ledger": "verified"`, `bridge_enabled: true`, `bridge: true` | `"ledger": "local"`, `bridge_enabled: false`, `bridge: false` |
157
+
158
+ (Preserve an existing roster.)
159
+
160
+ **`ledger` is the one that decides.** `isVerifiedLedger` (`cli/manifest.mjs`) reads it first and
161
+ falls back to the booleans only when it is absent — so on a project that has already run
162
+ `yad migrate`, writing `bridge_enabled: true` while leaving `"ledger": "local"` in place turns
163
+ verified mode ON in the file and OFF in the engine. Nothing would be wired, no guard would arm,
164
+ and the report would say it worked. Write all three, and keep them saying the same thing.
165
+
166
+ The booleans are still written because a check gate committed in the repo may predate
167
+ `yad update`; see `docs/migrations/shape-2.md`.
168
+
169
+ A platform and a verified ledger travel together: verified mode is a platform AND the switch
170
+ (`isVerifiedLedger`, `cli/manifest.mjs`), and `yad setup` derives both from one value, so marking a
171
+ platform-less hub verified creates a state no CLI path can produce and the gates read
172
+ differently (#186).
156
173
  Auth is the local user's own `gh`/`glab`/git; **store no tokens**. Idempotent — safe to re-run.
157
174
  - **`roster`** — set one roster entry mapping a platform `login` → SDLC `name` + `email` + a per-scope
158
175
  `roles` map (`roles: { hub: ["owner","reviewer"], <repo>: ["domain-owner", …] }`). Upsert by `login`;
@@ -166,11 +183,11 @@ write only `{project-root}/.sdlc/hub.json` (`config.yaml` `hub.config`) — neve
166
183
  connected repo's role); `yad roster grant|revoke <name> <repo> <role>`; `yad roster remove <login>`.
167
184
  A `domain-owner` grant/revoke keeps `repos.json` `domain_owners` in sync so the gate never drifts.
168
185
 
169
- If the hub has no remote (`platform: null`) or the bridge is disabled, the front-half gate runs
170
- file-only with no error — the bridge is purely additive.
186
+ If the hub has no remote (`platform: null`) or the verified ledger is disabled, the Shape gate runs
187
+ local with no error — the verified ledger is purely additive.
171
188
 
172
189
  ## Live on-demand (the third context layer)
173
- The cached pack + map are the default. When a front phase needs an **area** not in the map, it may
190
+ The cached pack + map are the default. When a Shape phase needs an **area** not in the map, it may
174
191
  re-run Repomix **live**, scoped to that area:
175
192
  ```bash
176
193
  npx repomix@latest --compress --include "<area globs>" --style markdown -o -
@@ -64,12 +64,12 @@ source: repomix
64
64
  <!-- the main directories/modules and what each owns -->
65
65
  ```
66
66
 
67
- The code-map is deliberately small so every front phase can load it cheaply. The full `pack.md` is read
67
+ The code-map is deliberately small so every Shape phase can load it cheaply. The full `pack.md` is read
68
68
  only when a phase needs depth (the architecture phase, primarily).
69
69
 
70
70
  ## Layer 3 — live on-demand (a specific area, not a stale repo)
71
71
 
72
- When a front phase needs an area not captured in the code-map, it may re-pack that **slice** live,
72
+ When a Shape phase needs an area not captured in the code-map, it may re-pack that **slice** live,
73
73
  scoped to the area, without writing the cache:
74
74
 
75
75
  ```bash
@@ -1,6 +1,6 @@
1
1
  # Hub config — schema, detection, and the reviewer roster
2
2
 
3
- The hub config is the product hub's record of **its own** platform (so the front-half review/comment/
3
+ The hub config is the product hub's record of **its own** platform (so the Shape review/comment/
4
4
  approval cycle can run through a real PR/MR on the hub) and the **reviewer roster** that maps a platform
5
5
  login to an SDLC name + role. It is a single object for the hub itself — the sibling of the per-repo
6
6
  `repos.json` registry (see `repos-registry.md`), kept separate so it never pollutes that array.
@@ -15,10 +15,13 @@ login to an SDLC name + role. It is a single object for the hub itself — the s
15
15
 
16
16
  ```json
17
17
  {
18
+ "schemaVersion": 2, // the file's shape. Absent means 1 (rule 1). `yad migrate` moves it; see docs/migrations/shape-2.md
18
19
  "platform": "github", // github | gitlab (from the hub's own remote host); null when local-only
19
20
  "git_url": "https://github.com/abdelrahmannasr/yadflow.git", // REQUIRED when platform is non-null (scopes auth + opens PRs); yad doctor warns YAD-CFG-005 if absent
20
21
  "default_branch": "main",
21
- "bridge_enabled": true, // open review PRs/MRs on the hub for front-half reviews; travels WITH platform — bridge mode is both (isBridge), so never true beside platform: null (#186)
22
+ "ledger": "verified", // WHO WRITES THE LEDGER, and the one that decides: "verified" = CI only, signed; "local" = this machine. Travels WITH platform — verified is both (isVerifiedLedger), so never "verified" beside platform: null (#186)
23
+ "bridge_enabled": true, // the older spelling of the same switch, kept so a check gate that predates `yad update` still reads it. Write it to MATCH `ledger`, never against it
24
+ "bridge": true, // older still. Same rule
22
25
  "gate_sync_version": "3.15.3", // OPTIONAL exact pin for the wired gate-sync job; an exact 3.x.y, prereleases included (3.16.0-rc.1) — anything else is skipped. Omitted => the .sdlc/cli-version.json stamp if that qualifies, else floating 3
23
26
  "review": { "requireEngagement": false }, // Review Companion: false (soft) counts bare approves but nudges; true counts only verified-engagement approvals
24
27
  "detectedAt": "2026-06-08", // last detect-hub run (YYYY-MM-DD)
@@ -72,16 +75,27 @@ as the registry). `detect-hub` upserts `hub.json` in place — it is idempotent
72
75
 
73
76
  **`git_url` is required whenever `platform` is non-null.** `yad doctor` uses it to scope the auth
74
77
  probe to the hub's own host (an unscoped `glab auth status` fails on any unrelated broken instance),
75
- and the bridge/PR flow uses it to open PRs. Doctor flags its absence with a warn (`YAD-CFG-005`);
78
+ and the verified ledger/PR flow uses it to open PRs. Doctor flags its absence with a warn (`YAD-CFG-005`);
76
79
  re-running `yad setup` backfills it from the origin remote (idempotent, non-interactive).
77
80
 
78
- ## Bridge enable / degradation
81
+ ## Who writes the ledger, and what happens when it degrades
79
82
 
80
- - `bridge_enabled: true` **and** a non-null `platform` **and** `gh`/`glab` authenticated → the front-half
83
+ The switch is read in this order — `isVerifiedLedger` (`cli/manifest.mjs`) and the bash copy in
84
+ `checks/ledger-guard.sh` both do exactly this, and a test asserts they agree on every shape:
85
+
86
+ 1. **`ledger`**, whenever the key is present. `"verified"` and nothing else means verified; any other
87
+ value, including an empty string, means local.
88
+ 2. **otherwise** the older booleans `bridge_enabled`, then `bridge`.
89
+ 3. and a non-null `platform` is required either way — without one there is no Verified badge to read.
90
+
91
+ So on a migrated project `ledger` wins, and writing a boolean that contradicts it changes nothing.
92
+ Keep all three in step.
93
+
94
+ - `ledger: "verified"` **and** a non-null `platform` **and** `gh`/`glab` authenticated → the Shape
81
95
  review opens a PR/MR on the hub and `yad-review-gate action: sync` pulls platform state into the ledger.
82
- - `bridge_enabled: false`, `platform: null`, or no/unauthenticated CLI → the gate falls back to the
83
- existing **file-only** flow with no error. The file ledger is the source of truth in both modes.
84
- - The master switch `config.yaml` `hub.bridge: false` disables the bridge globally regardless of `hub.json`.
96
+ - `ledger: "local"`, `platform: null`, or no/unauthenticated CLI → the gate falls back to the
97
+ existing **local** flow with no error. The file ledger is the source of truth in both modes.
98
+ - The master switch `config.yaml` `hub.bridge: false` disables the verified ledger globally regardless of `hub.json`.
85
99
 
86
100
  ## Review Companion engagement (`review.requireEngagement`)
87
101
 
@@ -90,7 +104,7 @@ engagement gate. Each approval records `engagement: verified | none`. **Soft (`f
90
104
  bare approve still passes but draws a friendly public nudge, so review *quality* is visible without
91
105
  blocking. **Strict (`true`):** the predicate counts only `verified` approvals. The signal is gameable by
92
106
  design ("visible, not impossible") — it raises the cost of a rubber-stamp, it does not prove a human
93
- read the artifact. Applies to both the front gate and the back-half engineer review.
107
+ read the artifact. Applies to both the Shape gate and the Build engineer review.
94
108
 
95
109
  ## Git tracking
96
110
 
@@ -99,6 +113,6 @@ This mirrors how `repos.json` and the per-epic `.sdlc/` state are committed.
99
113
 
100
114
  ## Greenfield
101
115
 
102
- A brand-new hub has no `hub.json`. That is valid — the front-half gate runs file-only until `detect-hub`
103
- records a platform. The bridge is purely additive; nothing about authoring or the gate predicate changes.
116
+ A brand-new hub has no `hub.json`. That is valid — the Shape gate runs local until `detect-hub`
117
+ records a platform. The verified ledger is purely additive; nothing about authoring or the gate predicate changes.
104
118
  ```
@@ -37,7 +37,7 @@ not under any `epics/EP-<slug>/.sdlc/`.
37
37
 
38
38
  ## Rules
39
39
 
40
- - **`name`** is the join key. It MUST match the names used in epic/story `repos:` tags so the front
40
+ - **`name`** is the join key. It MUST match the names used in epic/story `repos:` tags so the Shape
41
41
  phases can map `epic.repos` → registry entries → code-maps. Keep it stable.
42
42
  - **Auth is never stored.** No tokens, passwords, or PATs in the registry. `git_url` is a plain remote;
43
43
  `connect` clones/fetches as the local user (SSH key or git credential helper).
@@ -51,7 +51,7 @@ not under any `epics/EP-<slug>/.sdlc/`.
51
51
  ## Git tracking
52
52
 
53
53
  Commit the **registry** (`repos.json`) and each repo's **`code-map.md`** — they are small, reviewable,
54
- and are what the front phases actually read (a diff on a code-map shows when a repo's surface moved).
54
+ and are what the Shape phases actually read (a diff on a code-map shows when a repo's surface moved).
55
55
  `yad repo refresh --push` commits and pushes exactly these (never `pack.md`) to the hub's default
56
56
  branch as one `chore(hub): sync code-context … [skip ci]` audit commit.
57
57
  **Ignore** the full Repomix `pack.md` — it is large and regenerable (`action: refresh`). The product
@@ -60,6 +60,6 @@ hub's `.gitignore` carries `.sdlc/code-context/*/pack.md` for this. This mirrors
60
60
 
61
61
  ## Greenfield
62
62
 
63
- A brand-new product hub has no `repos.json` (or an empty `{ "repos": [] }`). That is valid — the front
63
+ A brand-new product hub has no `repos.json` (or an empty `{ "repos": [] }`). That is valid — the Shape
64
64
  phases treat "no repos connected" as "nothing to consider yet" and proceed unchanged. The registry
65
65
  appears the first time `connect` runs.
@@ -10,7 +10,7 @@ runnable specs in a connected code repo — alongside the Markdown artifact (`te
10
10
  **connects** a testing tool such as **Playwright** to the product hub and records *how* to reach it (the
11
11
  tool, the suite references, which MCP runs 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 testing registry. `yad-test-cases` consumes it: when a
15
15
  tool is connected and its MCP is available, the `test architect` lens **generates** automation tests
16
16
  into the connected repo(s) (or **links** an existing suite and reads it back); when nothing is
@@ -34,7 +34,7 @@ Resolve the scope (`yad thread <id> --json` for a thread). Collect, across the s
34
34
  (`origin`, `severity`, `escape_stage`, `root_cause`);
35
35
  - the shipped regression fixes from each epic's build ledger (the fix that closed the defect, linking
36
36
  the change-epic → its regression story/test);
37
- - open reconcile debt (a hotfix whose front truth is not yet restored).
37
+ - open reconcile debt (a hotfix whose Shape truth is not yet restored).
38
38
 
39
39
  The build ledger is **shard-then-fold**: read it as the **union** of the folded `.sdlc/build-log.json`
40
40
  `ships` PLUS every loose `.sdlc/build-log/` shard, deduped by `(story, task, repo)` — a shard WINS over a