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.
- package/CHANGELOG.md +46 -0
- package/README.md +11 -11
- package/bin/yad.mjs +8 -8
- package/cli/artifact-status.mjs +4 -4
- package/cli/checkpoint.mjs +25 -25
- package/cli/commit.mjs +1 -1
- package/cli/companion.mjs +2 -2
- package/cli/doctor.mjs +10 -10
- package/cli/epic-state.mjs +29 -29
- package/cli/errors.mjs +1 -1
- package/cli/gate.mjs +32 -33
- package/cli/hook.mjs +4 -4
- package/cli/hubcommit.mjs +1 -1
- package/cli/ledger.mjs +3 -3
- package/cli/lib.mjs +23 -9
- package/cli/manifest.mjs +42 -21
- package/cli/migrate.mjs +54 -12
- package/cli/next.mjs +5 -5
- package/cli/openpr.mjs +8 -8
- package/cli/plan.mjs +28 -9
- package/cli/platform.mjs +1 -1
- package/cli/report.mjs +1 -1
- package/cli/review.mjs +5 -5
- package/cli/setup.mjs +22 -10
- package/cli/ship.mjs +1 -1
- package/cli/skip.mjs +1 -1
- package/cli/thread.mjs +1 -1
- package/cli/tidy.mjs +2 -2
- package/cli/update-commit.mjs +1 -1
- package/package.json +1 -1
- package/skills/sdlc/config.yaml +30 -30
- package/skills/sdlc/module-help.csv +21 -21
- package/skills/yad-analysis/SKILL.md +10 -10
- package/skills/yad-architecture/SKILL.md +10 -10
- package/skills/yad-architecture/references/contract-format.md +2 -3
- package/skills/yad-backfill/SKILL.md +5 -5
- package/skills/yad-change/SKILL.md +13 -13
- package/skills/yad-change/references/triage.md +2 -3
- package/skills/yad-checks/SKILL.md +34 -16
- package/skills/yad-checks/references/check-gates.md +63 -19
- package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
- package/skills/yad-checks/templates/checks/epic-open.sh +1 -1
- package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
- package/skills/yad-checks/templates/checks/ledger-guard.sh +41 -12
- package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +3 -3
- package/skills/yad-checks/templates/github/yad-checks.yml +24 -3
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +2 -2
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +1 -1
- package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +12 -3
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +2 -2
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +1 -1
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +1 -1
- package/skills/yad-commit/SKILL.md +2 -2
- package/skills/yad-connect-design/SKILL.md +1 -1
- package/skills/yad-connect-docs/SKILL.md +1 -1
- package/skills/yad-connect-repos/SKILL.md +32 -15
- package/skills/yad-connect-repos/references/code-context.md +2 -2
- package/skills/yad-connect-repos/references/hub-config.md +25 -11
- package/skills/yad-connect-repos/references/repos-registry.md +3 -3
- package/skills/yad-connect-testing/SKILL.md +1 -1
- package/skills/yad-defects/SKILL.md +1 -1
- package/skills/yad-discovery/SKILL.md +6 -6
- package/skills/yad-discovery/references/discovery-schema.md +1 -1
- package/skills/yad-docs/SKILL.md +3 -3
- package/skills/yad-docs/templates/app/package-lock.json +65 -51
- package/skills/yad-docs/templates/app/package.json +1 -1
- package/skills/yad-docs-overview/SKILL.md +3 -3
- package/skills/yad-docs-overview/references/pipeline-model.md +17 -11
- package/skills/yad-engineer-review/SKILL.md +9 -9
- package/skills/yad-engineer-review/references/ship-and-record.md +8 -8
- package/skills/yad-epic/SKILL.md +15 -15
- package/skills/yad-epic/references/state-schema.md +30 -30
- package/skills/yad-hub-bridge/SKILL.md +14 -14
- package/skills/yad-hub-bridge/references/bridge.md +17 -17
- package/skills/yad-hub-bridge/references/login-roster.md +3 -3
- package/skills/yad-hub-bridge/templates/checks/hub-route.sh +1 -1
- package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +1 -1
- package/skills/yad-implement/SKILL.md +3 -3
- package/skills/yad-open-pr/SKILL.md +4 -4
- package/skills/yad-pair-review/SKILL.md +12 -12
- package/skills/yad-pair-review/references/session-state.md +3 -3
- package/skills/yad-pr-template/SKILL.md +4 -4
- package/skills/yad-pr-template/references/risk-routing.md +1 -1
- package/skills/yad-pr-template/templates/checks/pr-template.sh +18 -10
- package/skills/yad-pr-template/templates/checks/pr-title.sh +7 -7
- package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +1 -1
- package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +1 -1
- package/skills/yad-reconcile/SKILL.md +1 -1
- package/skills/yad-report/SKILL.md +1 -1
- package/skills/yad-review-companion/SKILL.md +7 -7
- package/skills/yad-review-gate/SKILL.md +18 -18
- package/skills/yad-review-gate/references/gating.md +3 -3
- package/skills/yad-run/SKILL.md +10 -10
- package/skills/yad-run/references/run-loop.md +8 -8
- package/skills/yad-ship/SKILL.md +4 -4
- package/skills/yad-spec/SKILL.md +10 -11
- package/skills/yad-status/SKILL.md +13 -13
- package/skills/yad-stories/SKILL.md +12 -12
- package/skills/yad-stories/references/story-schema.md +3 -3
- package/skills/yad-stub/SKILL.md +3 -3
- package/skills/yad-sync-repos/SKILL.md +1 -1
- package/skills/yad-test-cases/SKILL.md +12 -13
- package/skills/yad-test-cases/references/test-cases-schema.md +1 -1
- package/skills/yad-ui/SKILL.md +10 -10
|
@@ -7,21 +7,21 @@ description: 'Phase 6 post-lock change management — the change-request/defect
|
|
|
7
7
|
|
|
8
8
|
**Goal:** Turn a post-lock change request, defect, or hotfix into a **new epic threaded to its parent**,
|
|
9
9
|
so the feature's locked artifacts are never mutated — only *superseded*. The change-epic **inherits**
|
|
10
|
-
the
|
|
10
|
+
the Shape artifacts it does not change (by reference) and **re-authors** only the ones it does, so the
|
|
11
11
|
thread head always describes current behaviour and the SDLC stays a trusted source of truth for the next
|
|
12
12
|
change. This skill does the **intake + triage + seeding** and then hands off to the normal authoring
|
|
13
|
-
skills + `yad-review-gate`. It is a **
|
|
13
|
+
skills + `yad-review-gate`. It is a **Shape step**: human-confirmed, **never auto-advances**.
|
|
14
14
|
|
|
15
|
-
This is the answer to "the
|
|
16
|
-
no longer ship through
|
|
15
|
+
This is the answer to "the Shape/spec docs go stale after the contract locks": a behavioural change can
|
|
16
|
+
no longer ship through Build against an old story — `epic-open` seals a fully-shipped epic, so
|
|
17
17
|
new behaviour must enter here, and its re-authored stories/test-cases describe the change.
|
|
18
18
|
|
|
19
19
|
## Conventions
|
|
20
20
|
|
|
21
21
|
- `{project-root}` resolves from the product hub.
|
|
22
22
|
- Artifacts live under `{project-root}/epics/EP-<slug>/` — the change-epic gets its OWN `EP-<slug>`
|
|
23
|
-
(assigned here, never renamed) and its own `stories/EP-<slug>-S0N`, so every existing gate, the
|
|
24
|
-
`yad next`, and the
|
|
23
|
+
(assigned here, never renamed) and its own `stories/EP-<slug>-S0N`, so every existing gate, the verified ledger,
|
|
24
|
+
`yad next`, and the Build traceability keep working unchanged.
|
|
25
25
|
- The thread is **derived** from `parent:` frontmatter (no registry); `thread:` is a cache that must
|
|
26
26
|
equal the computed root (`yad doctor` flags a mismatch). Thread id = the genesis epic's id.
|
|
27
27
|
- Lineage frontmatter, the inherited-step shape, the pointer-lock, `change.json`, and
|
|
@@ -67,7 +67,7 @@ in `references/triage.md`).
|
|
|
67
67
|
|
|
68
68
|
### Step 2 — Gather the change + triage the DEPTH (auto-propose, human-confirm)
|
|
69
69
|
With the requester, classify the change into one **depth** (the `yad-backfill` discipline: auto-propose,
|
|
70
|
-
human-confirm). The depth decides which
|
|
70
|
+
human-confirm). The depth decides which Shape steps are **re-authored** vs **inherited**:
|
|
71
71
|
|
|
72
72
|
| depth | re-authors (active) | inherits (pre-done, by reference) | first step |
|
|
73
73
|
|-------|---------------------|-----------------------------------|-----------|
|
|
@@ -121,7 +121,7 @@ by `yad-architecture` downstream.
|
|
|
121
121
|
|
|
122
122
|
### Step 5 — Seed `state.json` (inherited steps pre-done; only the changed steps run)
|
|
123
123
|
Create `.sdlc/state.json` with the **same 10-step chain** as `yad-epic` (so `advanceState`/`nextAction`/
|
|
124
|
-
`gatePredicate`/the
|
|
124
|
+
`gatePredicate`/the verified ledger run unchanged), but:
|
|
125
125
|
- **Inherited** authoring steps **and their review gates**: `status: "done"`, `"inherited": true`,
|
|
126
126
|
`"inheritedFrom": "<owning epic from the resolved truth>"`, `"boundHash": "<that artifact's current
|
|
127
127
|
hash>"`.
|
|
@@ -136,7 +136,7 @@ Seed `.sdlc/comments.json` = `[]` and create `reviews/`.
|
|
|
136
136
|
|
|
137
137
|
Commit the seed on the `change/EP-<slug>` branch. It reaches the hub's default branch through this
|
|
138
138
|
change-epic's **first** review PR/MR — cut the `review/EP-<slug>/<artifact>` branch from `change/…` so
|
|
139
|
-
it carries the seed. In
|
|
139
|
+
it carries the seed. In verified mode `ledger-guard` exempts a new epic's ledger (creation, not mutation,
|
|
140
140
|
#162), so no direct push to a protected default branch is needed; every later change to that ledger is
|
|
141
141
|
CI's. See `../yad-epic/references/state-schema.md`, "Authoring branches".
|
|
142
142
|
|
|
@@ -166,16 +166,16 @@ defect to the gate that should have caught it. Add `"parentStub": true` when the
|
|
|
166
166
|
un-promoted stub (Step 5) — omit it (or `false`) otherwise.
|
|
167
167
|
|
|
168
168
|
### Step 7 — Hotfix only: record the ship-first exception + open reconcile debt
|
|
169
|
-
If `kind: hotfix`,
|
|
169
|
+
If `kind: hotfix`, Build MAY run before these Shape gates approve (severity demands it). Record
|
|
170
170
|
`hotfix: { "shipFirst": true }` in `change.json` and **append** to `.sdlc/reconcile-debt.json`:
|
|
171
171
|
`{ "thread": "<…>", "epicId": "<…>", "openedDate": "<today>", "reason": "<why>", "requires": ["artifacts-updated","regression-test"], "status": "open", "paidDate": null, "paidBy": null, "evidence": { "artifacts": [], "regressionTest": "" } }`.
|
|
172
172
|
Tell the user the debt **freezes the next normal change** on this thread (`reconcile-debt` gate) until it
|
|
173
|
-
is paid (
|
|
173
|
+
is paid (Shape artifacts updated **and** a regression test added, then `status: "paid"`).
|
|
174
174
|
|
|
175
175
|
### Step 8 — Stop; hand off (NO auto-advance)
|
|
176
176
|
Report: the new `EP-<slug>`, its thread + parent, the re-author-vs-inherit split, the seeded
|
|
177
177
|
`currentStep`, and the next skill — `yad-architecture` (contract-surface), else `yad-stories` /
|
|
178
|
-
`yad-test-cases` — followed by `yad-review-gate`.
|
|
178
|
+
`yad-test-cases` — followed by `yad-review-gate`. Shape steps do not auto-advance. Suggest
|
|
179
179
|
`yad thread <thread>` to see the evolution and `yad-timeline` / `yad-defects` to render it.
|
|
180
180
|
|
|
181
181
|
## Hard rules
|
|
@@ -186,7 +186,7 @@ Report: the new `EP-<slug>`, its thread + parent, the re-author-vs-inherit split
|
|
|
186
186
|
`boundHash`; the pointer-lock carries the parent hash verbatim. The gate never re-reviews them.
|
|
187
187
|
- **Contract-surface ⇒ re-author architecture.** Omitting `architecture` from `inherits` is the ONLY way
|
|
188
188
|
to change the surface; it re-locks (new hash) and routes through the escalated architecture review —
|
|
189
|
-
the same mechanism as the
|
|
189
|
+
the same mechanism as the Build `Contract-Change` route, unified.
|
|
190
190
|
- **A hotfix opens debt, never waives it.** Ship-first is allowed once; the thread freezes for new work
|
|
191
191
|
until the debt is paid.
|
|
192
192
|
- **Never auto-advances.** This skill seeds + records; humans author and approve via the normal gates.
|
|
@@ -69,15 +69,14 @@ Write `.sdlc/contract-lock.json` with the parent's hash **verbatim**:
|
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
Get the parent hash from the owning epic's `.sdlc/contract-lock.json` `hash` field (do NOT recompute — copy
|
|
72
|
-
it). `contract-check.sh` reads only `hash`, so a
|
|
72
|
+
it). `contract-check.sh` reads only `hash`, so a Build story in the change-epic pins this identical
|
|
73
73
|
hash via its `link.md` and the gate passes unchanged. There is no `contract.md` in the change-epic, so
|
|
74
74
|
the surface physically cannot drift.
|
|
75
75
|
|
|
76
76
|
**To CHANGE the surface instead:** do not inherit `architecture`. Then `yad-architecture` re-authors
|
|
77
77
|
`contract.md` in the change-epic between fresh `CONTRACT-SURFACE` markers, computes a **new** hash, and
|
|
78
78
|
writes a real (non-pointer) `contract-lock.json`. `architecture-review` carries `risk_tags: ["contract"]`
|
|
79
|
-
→ the escalated domain-owner review. This is the same re-lock-invalidates-approvals behaviour the
|
|
80
|
-
half already has, relocated from "edit the locked file" to "author a contract-surface change-epic".
|
|
79
|
+
→ the escalated domain-owner review. This is the same re-lock-invalidates-approvals behaviour Shape already has, relocated from "edit the locked file" to "author a contract-surface change-epic".
|
|
81
80
|
|
|
82
81
|
## Genesis migration (one-time, per feature)
|
|
83
82
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-checks
|
|
3
|
-
description: 'Build
|
|
3
|
+
description: 'Build Step C of the gated SDLC — the production-safety check gates. Wire and run the CI gates on a code repo: spec-link (every change links a real story/spec via its Task trailer), contract-check (a diff that changes the contract surface without a Contract-Change + an updated, re-locked contract FAILS and routes back to the architecture gate), build/test/lint, verified-commits (no unverified commits from unverified users — platform-Verified signature + roster-allowlisted author, on the hub and every repo), and the Phase 6 feature-thread gates lineage-check / epic-open / reconcile-debt (a change links a real threaded epic; a sealed epic refuses new behaviour; a thread with open hotfix debt is frozen until paid). The gates are CI-agnostic bash, invoked by GitHub Actions and GitLab CI. Use when the user says "wire the check gates", "run the gates", "require signed commits", or "set up CI checks" for a repo.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# SDLC — Check Gates (
|
|
6
|
+
# SDLC — Check Gates (Build Step C)
|
|
7
7
|
|
|
8
8
|
**Goal:** Install and run the **check gates** that protect production for a code repo. They run
|
|
9
9
|
in CI on every PR/MR and must pass before merge (build plan §C). Each is a small, separate check:
|
|
@@ -17,6 +17,16 @@ in CI on every PR/MR and must pass before merge (build plan §C). Each is a smal
|
|
|
17
17
|
never widened from inside a code repo (Phase 2 contract representation: delimited block + SHA-256 lock).
|
|
18
18
|
Every story whose slice the diff touches is checked, not just the first — see `references/check-gates.md`.
|
|
19
19
|
3. **build/test/lint** — standard quality stage; tests must actually exercise new behavior, not just pass.
|
|
20
|
+
CI installs and runs through the package manager declared by the repo's standard
|
|
21
|
+
`package.json#packageManager` field (`npm` and `pnpm` are supported; any other manager stays on
|
|
22
|
+
npm with a warning when an npm lockfile is present, and fails closed otherwise); an npm repo
|
|
23
|
+
without that field retains the historical npm behavior. A declared manager requires a full, exact semantic
|
|
24
|
+
version (with Corepack's optional integrity suffix supported), and CI activates that exact version.
|
|
25
|
+
The code-repo `yad-checks` workflow uses Node 22 by default (the build/test/lint job on GitHub; every
|
|
26
|
+
`yad-*` gate job's image in the GitLab fragment) and reads `YAD_NODE_VERSION` (a GitHub repository
|
|
27
|
+
variable or GitLab CI/CD variable) for repos whose declared runtime differs (a declared `packageManager` needs Corepack, so the override must be a line that
|
|
28
|
+
bundles a current one: 20.19+, 22.14+ or 24; Node 25+ dropped it). The hub-side workflows (verified-commits, hub-checks, update-guard,
|
|
29
|
+
gate-sync) only run the `yad` CLI and keep their own pinned Node; the variable does not reach them.
|
|
20
30
|
The CI job sets `YAD_TEST_MAX_WORKERS` (default `2`); the gate caps jest/vitest test concurrency at
|
|
21
31
|
that and is a no-op for other runners (see `references/check-gates.md`).
|
|
22
32
|
4. **verified-commits** — no unverified commits from unverified users: every commit in the range must
|
|
@@ -30,7 +40,7 @@ in CI on every PR/MR and must pass before merge (build plan §C). Each is a smal
|
|
|
30
40
|
resolution; the every-code-change-has-a-threaded-epic enforcement.
|
|
31
41
|
6. **epic-open** (Phase 6, the staleness preventer) — an epic is **sealed** once every story is
|
|
32
42
|
`shipped`; a commit targeting a sealed epic **FAILS**, forcing new behaviour into a new threaded
|
|
33
|
-
change-epic (so the
|
|
43
|
+
change-epic (so the Shape artifacts can never go stale).
|
|
34
44
|
7. **reconcile-debt** (Phase 6) — a hotfix that shipped first opens debt; the **next** change on its
|
|
35
45
|
thread **FAILS** until the debt is paid (artifacts updated + a regression test added).
|
|
36
46
|
|
|
@@ -50,10 +60,11 @@ and GitLab CI. This step is **by hand** in Phase 3 — run the gates with the sk
|
|
|
50
60
|
(`config.yaml` `build.code_repos_root`).
|
|
51
61
|
- Canonical gate sources live in this skill's `templates/` (the source of truth that gets installed
|
|
52
62
|
into each code repo):
|
|
53
|
-
- `templates/checks/{spec-link,contract-check,build-test-lint,verified-commits}.sh`
|
|
54
|
-
- `templates/checks/ledger-guard.sh` → **hub-only** gate, active **only in
|
|
55
|
-
carries BOTH a `platform` and `
|
|
56
|
-
|
|
63
|
+
- `templates/checks/{spec-link,contract-check,package-manager,install-deps,build-test-lint,verified-commits}.sh`
|
|
64
|
+
- `templates/checks/ledger-guard.sh` → **hub-only** gate, active **only in verified mode** — hub.json
|
|
65
|
+
carries BOTH a `platform` and `ledger: "verified"` — or, before `yad migrate`, `bridge_enabled`
|
|
66
|
+
(or the legacy `bridge`) true. The same predicate
|
|
67
|
+
`isVerifiedLedger` (`cli/manifest.mjs`) applies, so the gate and the CLI can never disagree about who owns the
|
|
57
68
|
ledger (#186). A no-op otherwise, when humans legitimately own it. On review PRs it FAILs any
|
|
58
69
|
commit that touches the
|
|
59
70
|
CI-owned gate ledger (`.sdlc/{state,approvals,comments,hub-prs}.json`, `reviews/*.md`) unless it
|
|
@@ -65,17 +76,24 @@ and GitLab CI. This step is **by hand** in Phase 3 — run the gates with the sk
|
|
|
65
76
|
default branch the guard is absolute again. Runs in `yad-hub-checks`
|
|
66
77
|
alongside `verified-commits` (which waives the allowlist for the bot but still requires its
|
|
67
78
|
signature). See `yad-hub-bridge`.
|
|
68
|
-
- `templates/hooks/ledger-guard.sh` → **hub-only** agent guardrail, active **only in
|
|
69
|
-
(the same `
|
|
79
|
+
- `templates/hooks/ledger-guard.sh` → **hub-only** agent guardrail, active **only in verified mode**
|
|
80
|
+
(the same `isVerifiedLedger` predicate). Not a CI gate: it is a **harness hook** that refuses an agent's
|
|
70
81
|
edit to the CI-owned ledger at the moment it is attempted and names `yad gate open` instead — the
|
|
71
82
|
local half of `checks/ledger-guard.sh` (#171). Installed to `<hub>/hooks/ledger-guard.sh` with the
|
|
72
83
|
`PreToolUse` entry in `.claude/settings.json`. Fails OPEN; see "Step 2b" below.
|
|
73
84
|
- `templates/github/yad-verified-commits.yml` + `templates/gitlab/yad-verified-commits.gitlab-ci.yml`
|
|
74
85
|
→ the standalone hub-side verified-commits CI (installed by `yad check --fix` with the hub wiring)
|
|
75
|
-
- `templates/github/yad-checks.yml` → installs to `.github/workflows/yad-checks.yml` (marked `# yad-managed: yad-checks`)
|
|
76
|
-
|
|
86
|
+
- `templates/github/yad-checks.yml` → installs to `.github/workflows/yad-checks.yml` (marked `# yad-managed: yad-checks`);
|
|
87
|
+
its quality job uses `install-deps.sh`, reads the optional `YAD_NODE_VERSION` repository variable,
|
|
88
|
+
exports the PR's exact `NX_BASE`/`NX_HEAD`, and filters unrelated history blobs while preserving
|
|
89
|
+
the full commit graph.
|
|
90
|
+
- `templates/gitlab/yad-checks.gitlab-ci.yml` → includable fragment, installs to `.gitlab/ci/yad-checks.yml`;
|
|
91
|
+
it uses the same installer, honors a project/group `YAD_NODE_VERSION` CI/CD variable, and exports
|
|
92
|
+
the MR's exact `NX_BASE`/`NX_HEAD`.
|
|
77
93
|
- `templates/gitlab/gitlab-ci.include-root.yml` → minimal root written only when no root `.gitlab-ci.yml` exists
|
|
78
|
-
- `templates/gitlab/.gitlab-ci.yml` → legacy standalone root (greenfield single-file option)
|
|
94
|
+
- `templates/gitlab/.gitlab-ci.yml` → legacy standalone root (greenfield single-file option), kept
|
|
95
|
+
semantically aligned with the includable fragment's Node version, Nx range, dependency install,
|
|
96
|
+
and test-worker cap
|
|
79
97
|
- The gates depend on the conventions from earlier steps: the `Task:`/`Contract-Change:` commit
|
|
80
98
|
trailers (`yad-implement`), the `specs/<story>/link.md` + `contracts/` slice (`yad-spec`), and the
|
|
81
99
|
locked `contract.md` (`yad-architecture`).
|
|
@@ -151,9 +169,9 @@ with a hub-flavored gate set — see "Wiring the hub" in `references/check-gates
|
|
|
151
169
|
`<hub>/hooks/ledger-guard.sh`, plus the `PreToolUse` entry in `.claude/settings.json`. `yad setup`
|
|
152
170
|
and `yad check --fix` install both; there is nothing to do by hand.
|
|
153
171
|
|
|
154
|
-
### Step 2b — the agent guardrail (harness hooks,
|
|
172
|
+
### Step 2b — the agent guardrail (harness hooks, verified mode only)
|
|
155
173
|
The CI gates speak at CI time. That is too late for one failure the field kept hitting (#171): in
|
|
156
|
-
|
|
174
|
+
verified mode the gate ledger is **CI-owned**, so an agent that hand-edits
|
|
157
175
|
`epics/*/.sdlc/state.json` only finds out twenty minutes later, from a `ledger-guard` FAIL with
|
|
158
176
|
nothing connecting cause to effect — and by then the write has to be reverted before the review
|
|
159
177
|
PR/MR can go green.
|
|
@@ -171,7 +189,7 @@ file-editing tool call and refuses the write up front, naming the command that o
|
|
|
171
189
|
`contract-lock.json`, `change.json`, and every artifact. A **new** epic's ledger is exempt too —
|
|
172
190
|
creation, not mutation (#162), decided by listing the epics the **base ref** carries (an
|
|
173
191
|
`origin/` ref, case-folded slugs), never by looking at the working tree.
|
|
174
|
-
- **A no-op
|
|
192
|
+
- **A no-op with a local ledger.** There the ledger is locally owned and the hand-edit the authoring
|
|
175
193
|
skills describe is *correct*, so nothing is wired and nothing is blocked.
|
|
176
194
|
- **It fails OPEN** — no `yad`, no hub, an unreadable config, an unparseable payload all ALLOW, with
|
|
177
195
|
a note on stderr. `ledger-guard` in CI fails *closed* and remains the authority. `YAD_HOOK_DISABLE=1`
|
|
@@ -181,7 +199,7 @@ file-editing tool call and refuses the write up front, naming the command that o
|
|
|
181
199
|
hub**, since a harness reads hooks from its own project root — a session opened at the workspace
|
|
182
200
|
above the hub never loads the hub's `.claude/settings.json`.
|
|
183
201
|
|
|
184
|
-
`yad doctor` reports the guardrail as `agent ledger guard wired` / `not wired` on a
|
|
202
|
+
`yad doctor` reports the guardrail as `agent ledger guard wired` / `not wired` on a verified hub.
|
|
185
203
|
See `references/check-gates.md` §"The agent guardrail".
|
|
186
204
|
|
|
187
205
|
### Step 3 — `run` (run the gates now)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Check gates — definitions, scripts, CI wiring, convention map
|
|
2
2
|
|
|
3
|
-
The gates are the production-safety core of
|
|
3
|
+
The gates are the production-safety core of Build (Phase 3 build plan §C). They are
|
|
4
4
|
deliberately small, separate, and CI-agnostic: plain bash in `checks/`, invoked by whatever CI the
|
|
5
5
|
repo uses. Each reads conventions established by earlier steps — it invents nothing.
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ repo uses. Each reads conventions established by earlier steps — it invents no
|
|
|
10
10
|
|------|-------|-------------|
|
|
11
11
|
| spec-link | the `Task: <story>-<task>` commit trailer; `specs/<story>/link.md` | `yad-implement` (trailer), `yad-spec` (link.md) |
|
|
12
12
|
| contract-check | changed files under `specs/<story>/contracts/`; the `Contract-Change: yes` trailer; `link.md`'s pinned `contract-lock`; the product repo's `contract-lock.json` | `yad-architecture` (lock), `yad-spec` (slice + link), `yad-implement` (trailer) |
|
|
13
|
-
| build/test/lint | the repo's
|
|
13
|
+
| build/test/lint | the repo's configured package manager running `lint` / `build` / `test` | the repo |
|
|
14
14
|
| lineage-check | the `Task:` trailer → `link.md` (`epic` + `product-repo`); the owning epic's `kind`/`parent` frontmatter in the hub | `yad-spec` (link.md), `yad-change` (lineage frontmatter) |
|
|
15
15
|
| epic-open | the `Task:` trailer → `link.md` → the hub epic's `stories/*.md` `status:` (sealed = all `shipped`) | `yad-engineer-review` (story status), `yad-change` (the change-epic) |
|
|
16
16
|
| reconcile-debt | the `Task:` trailer → `link.md` → the hub epic's `thread`; every thread epic's `reconcile-debt.json` | `yad-change` (opens hotfix debt) |
|
|
@@ -93,13 +93,44 @@ own CI runs, plus an assertion that each one actually *assigns* `BASE` from it.
|
|
|
93
93
|
|
|
94
94
|
## 3. build/test/lint (`templates/checks/build-test-lint.sh`)
|
|
95
95
|
|
|
96
|
-
-
|
|
96
|
+
- Reads the standard `package.json#packageManager` field and runs `lint`, `build`, and `test` through
|
|
97
|
+
that manager in order; any non-zero exit fails the gate. `npm` and `pnpm` are supported; another
|
|
98
|
+
manager (yarn, bun) fails closed unless an npm lockfile is present, in which case the repo stays on
|
|
99
|
+
the historical npm path with a warning — a yarn-locally, npm-in-CI repo was green before this
|
|
100
|
+
field was read and must not go red on upgrade. A repo
|
|
101
|
+
without the field retains npm behavior unless it carries `pnpm-lock.yaml` and no npm lockfile
|
|
102
|
+
(`package-lock.json` / `npm-shrinkwrap.json`) — a repo with both keeps the npm path rather than
|
|
103
|
+
silently changing toolchains.
|
|
104
|
+
- CI runs `install-deps.sh` first. A declared npm or pnpm manager must use a full, exact semantic
|
|
105
|
+
version with either no build metadata or Corepack's integrity suffix,
|
|
106
|
+
`+<sha1|sha224|sha256|sha384|sha512>.<lowercase hex digest>` at that algorithm's digest length
|
|
107
|
+
(Corepack hashes the download with the named algorithm and compares the lowercase hex string).
|
|
108
|
+
Other build metadata, digest algorithms, digest lengths, uppercase digests, or trailing
|
|
109
|
+
identifiers fail closed. The installer activates the declared version through
|
|
110
|
+
Corepack. Pinned npm is dispatched through Corepack — `corepack npm ci` here and `corepack npm run`
|
|
111
|
+
in the gate, since Corepack activates npm but never shims it — so Node's ambient npm cannot
|
|
112
|
+
override the declaration anywhere; packageManager-absent npm preserves the historical `npm ci` path. pnpm uses
|
|
113
|
+
`pnpm install --frozen-lockfile`. Partial versions, ranges, and tags also fail closed instead of
|
|
114
|
+
floating to a different toolchain.
|
|
115
|
+
- The build/test/lint job defaults to Node 22. Set the GitHub repository variable or GitLab
|
|
116
|
+
project/group CI/CD variable `YAD_NODE_VERSION` when the repo requires another supported Node
|
|
117
|
+
release; generated files remain managed instead of accumulating consumer-specific edits. The
|
|
118
|
+
variable is read by the code repo's `yad-checks` workflow only (on GitHub the build/test/lint job;
|
|
119
|
+
in the GitLab fragment it sets the `node:` image of every `yad-*` gate job, which all share one
|
|
120
|
+
anchor) — the hub-side workflows run the `yad` CLI, not the repo's toolchain, and keep their own
|
|
121
|
+
pinned Node. A declared `packageManager` needs
|
|
122
|
+
Corepack, which ships with Node 18 through 24 (Node 25+ dropped it) and, before 18.20.7 / 20.19 /
|
|
123
|
+
22.14, carries registry keys too old to verify anything published since 2025 — so the override
|
|
124
|
+
should stay on a current 20, 22 or 24 line (or install a current Corepack first). The installer
|
|
125
|
+
checks for the binary and wraps `corepack prepare`, so both an absent and a stale Corepack fail
|
|
126
|
+
with that guidance instead of a bare "command not found" or "Cannot find matching keyid".
|
|
97
127
|
- Tests must actually exercise behavior (build plan §C) — an empty or trivially-passing suite does not
|
|
98
128
|
satisfy the gate's intent.
|
|
99
129
|
- **Test worker cap.** When the CI job sets `YAD_TEST_MAX_WORKERS` (the templates default it to `2`)
|
|
100
130
|
and the repo's `test` script is jest/vitest, the gate forwards `--maxWorkers=<n>` to bound CI
|
|
101
|
-
concurrency
|
|
102
|
-
|
|
131
|
+
concurrency (as `-- --maxWorkers=<n>` under npm, which consumes the separator, and bare under pnpm,
|
|
132
|
+
which would forward a literal `--` to the script). For any other runner (`node --test`, mocha, …)
|
|
133
|
+
it is a no-op — the flag is never passed, so the gate cannot break on an unknown option. Override it per repo via the
|
|
103
134
|
`YAD_TEST_MAX_WORKERS` CI variable, or unset it to remove the cap.
|
|
104
135
|
|
|
105
136
|
### Canonical `package.json` scripts (Node demo)
|
|
@@ -176,13 +207,13 @@ from the event payload):
|
|
|
176
207
|
period (`config.yaml build.pr_title_style: same_as_commit_subject` — one task = one PR, the title is
|
|
177
208
|
the squash-merge subject).
|
|
178
209
|
- `--profile hub` → splits by the PR/MR **head branch** (passed via `--head`, injected by CI):
|
|
179
|
-
- `review/EP-*` head (or no `--head` — stays strict) → a
|
|
210
|
+
- `review/EP-*` head (or no `--head` — stays strict) → a Shape artifact-review title
|
|
180
211
|
`review: <artifact> (EP-<slug>)`, the shape `yad gate open` creates.
|
|
181
212
|
- any other head → a tooling/code change to the hub itself, so it follows the `code` convention (a
|
|
182
213
|
Conventional-Commits subject). This is what lets a PR that changes the hub's own workflows/checks
|
|
183
214
|
pass — it has no EP artifact to review.
|
|
184
215
|
- **Anti-bypass guard.** The branch name alone is not trusted: a non-review head that actually
|
|
185
|
-
changes
|
|
216
|
+
changes Shape artifacts (any path under `epics/**`) **FAILS** — those changes must go through
|
|
186
217
|
a `review/EP-*` PR and the artifact-review workflow. CI passes the PR's changed paths via
|
|
187
218
|
`--changed <file>` (computed from the diff against the base ref); without that list (a direct
|
|
188
219
|
by-hand caller) the guard is inert and the branch split alone applies.
|
|
@@ -196,10 +227,10 @@ catches a free-form description that bypassed it:
|
|
|
196
227
|
`Risk level:` (`low|medium|high`).
|
|
197
228
|
- `--profile hub` → splits by the PR/MR **head branch** (passed via `--head`, injected by CI):
|
|
198
229
|
- `review/EP-*` head (or no `--head`) → requires the artifact-review template: `## Artifact under
|
|
199
|
-
review`, `## Impact & Risk (front-half)
|
|
230
|
+
review`, `## Impact & Risk (front-half)` (or `(Shape)`), `## Checklist`, and a `Risk tags:` line.
|
|
200
231
|
- any other head → a hub tooling PR, so it requires the `code` task template (`## Summary`,
|
|
201
232
|
`## Impact & Risk`, `## Checklist`, filled `Risk level:`).
|
|
202
|
-
- **Anti-bypass guard** (same as pr-title): a non-review head that changes
|
|
233
|
+
- **Anti-bypass guard** (same as pr-title): a non-review head that changes Shape artifacts
|
|
203
234
|
(`epics/**`, detected from the CI-supplied `--changed <file>` list) **FAILS** — artifact changes
|
|
204
235
|
must go through a `review/EP-*` PR.
|
|
205
236
|
|
|
@@ -244,7 +275,7 @@ four copies stay byte-identical.
|
|
|
244
275
|
epic in a thread" enforcement, layered on spec-link.
|
|
245
276
|
- **epic-open** — an epic is **sealed** iff it has ≥1 story and **every** `stories/*.md` `status:` is
|
|
246
277
|
`shipped`. A commit whose owning epic is sealed **FAILS**: new behaviour cannot mutate a shipped epic;
|
|
247
|
-
it must land in a new threaded change-epic. This is what stops the
|
|
278
|
+
it must land in a new threaded change-epic. This is what stops the Shape artifacts from going stale.
|
|
248
279
|
- **reconcile-debt** — resolves the epic's `thread` (its `thread:` frontmatter, else the epic id) and
|
|
249
280
|
scans every thread epic's `reconcile-debt.json`. An **open** entry the current epic does not own
|
|
250
281
|
**FAILS** the change (the thread is frozen until the hotfix debt is paid: artifacts updated + a
|
|
@@ -296,11 +327,24 @@ The gates run identically under either CI; the config just invokes the scripts w
|
|
|
296
327
|
read the title/body from the event payload: `pr-title` takes `${{ github.event.pull_request.title }}`
|
|
297
328
|
and `pr-template` writes `${{ github.event.pull_request.body }}` to a temp file. All `--profile code`.
|
|
298
329
|
The Phase 6 thread gates (`lineage-check`, `epic-open`, `reconcile-debt`) run as their own jobs with
|
|
299
|
-
`fetch-depth: 0`, the same `origin/${{ github.base_ref }}` base.
|
|
330
|
+
`fetch-depth: 0`, the same `origin/${{ github.base_ref }}` base. The build/test/lint checkout also
|
|
331
|
+
uses `filter: blob:none`; its installer follows `package.json#packageManager`, and `NX_BASE` /
|
|
332
|
+
`NX_HEAD` carry the exact base/head SHAs so Nx affected commands evaluate the PR rather than a
|
|
333
|
+
stale default. `YAD_NODE_VERSION` is read from GitHub repository variables with `22` as the default.
|
|
334
|
+
Dependencies are cached with `actions/cache` (npm's `~/.npm`, pnpm's store, and the Corepack home
|
|
335
|
+
holding the pinned manager, keyed on the lockfiles and package.json) rather than setup-node's
|
|
336
|
+
npm-only `cache:`, which must name the manager before package.json has been read.
|
|
300
337
|
- **GitLab CI** — `templates/gitlab/yad-checks.gitlab-ci.yml` → `.gitlab/ci/yad-checks.yml`, pulled in
|
|
301
338
|
by the root `.gitlab-ci.yml`'s `include:`. The jobs run on `merge_request_event` with `GIT_DEPTH: 0`,
|
|
302
339
|
passing `origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME`; the pattern jobs read `$CI_MERGE_REQUEST_TITLE`
|
|
303
|
-
and `$CI_MERGE_REQUEST_DESCRIPTION`.
|
|
340
|
+
and `$CI_MERGE_REQUEST_DESCRIPTION`. The quality job uses the same package-manager-aware installer;
|
|
341
|
+
`NX_BASE=$CI_MERGE_REQUEST_DIFF_BASE_SHA` and `NX_HEAD=$CI_COMMIT_SHA` provide the equivalent Nx
|
|
342
|
+
range. Its `node:${YAD_NODE_VERSION}` image defaults to `22` and a project/group CI/CD variable may
|
|
343
|
+
override it without editing the managed fragment. Those three variables sit on the `.sdlc_mr_only`
|
|
344
|
+
anchor, not the fragment's top-level `variables:` — an included top-level block merges into the host
|
|
345
|
+
pipeline's globals, where `NX_BASE`/`NX_HEAD` could collide with a host's own Nx setup. The retained greenfield standalone template
|
|
346
|
+
(`templates/gitlab/.gitlab-ci.yml`) carries the same Node default, exact Nx range, and dependency
|
|
347
|
+
installer and test-worker-cap semantics. All `--profile code`.
|
|
304
348
|
|
|
305
349
|
## Sync with existing CI (merge, never clobber)
|
|
306
350
|
|
|
@@ -344,7 +388,7 @@ merge-not-clobber logic, with a **hub-flavored gate set** appropriate to a "thin
|
|
|
344
388
|
- **approvals-present** — an epic at `ready-for-build` has the approvals its gate rule requires recorded
|
|
345
389
|
in `.sdlc/approvals.json` (the same predicate `yad-review-gate` enforces).
|
|
346
390
|
|
|
347
|
-
These are advisory checks on the hub's own PRs (the
|
|
391
|
+
These are advisory checks on the hub's own PRs (the Shape review PRs the verified ledger opens); they keep
|
|
348
392
|
the hub's artifacts internally consistent. The hub never runs the code-repo `spec-link`/`build-test-lint`
|
|
349
393
|
gates. Author the hub gate scripts under the hub's `checks/` following the same CI-agnostic-bash pattern.
|
|
350
394
|
|
|
@@ -352,12 +396,12 @@ The hub **does** run the verified-commits gate — `yad check --fix` installs `c
|
|
|
352
396
|
plus a standalone workflow (`templates/github/yad-verified-commits.yml` →
|
|
353
397
|
`.github/workflows/yad-verified-commits.yml`, or the GitLab fragment
|
|
354
398
|
`templates/gitlab/yad-verified-commits.gitlab-ci.yml` → `.gitlab/ci/yad-verified-commits.yml` +
|
|
355
|
-
its one include line) whenever `.sdlc/hub.json` has a platform with
|
|
356
|
-
|
|
399
|
+
its one include line) whenever `.sdlc/hub.json` has a platform with a verified ledger. So the
|
|
400
|
+
Shape review PRs are held to the same rule as code-repo PRs: signed, known authors only.
|
|
357
401
|
|
|
358
402
|
The hub **also** runs the three pattern gates (`commit-message`, `pr-title`, `pr-template`) with
|
|
359
403
|
`--profile hub`. The pattern gates split by the PR/MR **head branch** (passed via `--head`): a
|
|
360
|
-
`review/EP-*` head is a
|
|
404
|
+
`review/EP-*` head is a Shape review PR — Conventional-Commits commit subjects, a
|
|
361
405
|
`review: <artifact> (EP-<slug>)` title, and the hub artifact-review template body; **any other head is
|
|
362
406
|
a tooling/code change to the hub itself** and follows the `code` convention (a Conventional-Commits
|
|
363
407
|
title + the code task template), so a PR that changes the hub's own workflows/checks can pass.
|
|
@@ -370,7 +414,7 @@ line). Code repos run the same three with `--profile code` inside the main `yad-
|
|
|
370
414
|
|
|
371
415
|
Not a CI gate — a **harness hook**, and the only piece of yadflow that runs *inside* an agent's tool
|
|
372
416
|
loop. It exists because of the gap #171 reported: `checks/ledger-guard.sh` is correct and blocking,
|
|
373
|
-
but it speaks at CI time. An agent that hand-edits `epics/*/.sdlc/state.json` in
|
|
417
|
+
but it speaks at CI time. An agent that hand-edits `epics/*/.sdlc/state.json` in verified mode learns
|
|
374
418
|
twenty minutes later, from a FAIL with nothing connecting cause to effect, and by then the write must
|
|
375
419
|
be reverted before the review PR/MR can go green.
|
|
376
420
|
|
|
@@ -410,7 +454,7 @@ logic is unit-tested (`cli/hook.mjs`, `cli/test.mjs`) instead of living in bash.
|
|
|
410
454
|
- **Slugs are case-folded**, as the gate folds them. On a case-insensitive filesystem `epics/ep-x/…`
|
|
411
455
|
and `epics/EP-X/…` are the same file, so a byte-exact compare would let a mutation be laundered as
|
|
412
456
|
a creation.
|
|
413
|
-
-
|
|
457
|
+
- Verified-only by the same `isVerifiedLedger` predicate the CLI and the wiring read (#186): without the
|
|
414
458
|
bridge the ledger is locally owned, the hand-edit the authoring skills describe is correct, and
|
|
415
459
|
nothing is wired or blocked.
|
|
416
460
|
|
|
@@ -461,7 +505,7 @@ The settings file is the team's, so the rules around that one entry are delibera
|
|
|
461
505
|
`.claude` is the only IDE target wired: it is the only one with a defined hook protocol. Other
|
|
462
506
|
targets get the script, and the contract above is what they would wire by hand.
|
|
463
507
|
|
|
464
|
-
`yad doctor` reports the guard on a
|
|
508
|
+
`yad doctor` reports the guard on a verified hub, and distinguishes the three states that matter — it
|
|
465
509
|
reads the same persisted `ideTargets` the wiring reads, so every gap it names is one the command it
|
|
466
510
|
names can actually close:
|
|
467
511
|
|
|
@@ -1,26 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# build / test / lint gate (Phase 3 build plan §C).
|
|
3
3
|
# Standard quality stage: lint, build, and tests that actually exercise behavior (not just pass).
|
|
4
|
-
# Delegates to the
|
|
4
|
+
# Delegates to the package manager named by package.json so each repo owns the specifics.
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
|
+
YAD_CHECKS_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
readonly YAD_CHECKS_DIR
|
|
9
|
+
# shellcheck source-path=SCRIPTDIR
|
|
10
|
+
# shellcheck source=package-manager.sh
|
|
11
|
+
source "$YAD_CHECKS_DIR/package-manager.sh"
|
|
12
|
+
# Two assignments on purpose: nested inside another $(...), a failing yad_package_manager_spec would
|
|
13
|
+
# be swallowed, and the gate would fall back to lockfile detection and PASS on a package.json it
|
|
14
|
+
# had just rejected. Each step fails the gate on its own under `set -e`.
|
|
15
|
+
package_manager_spec="$(yad_package_manager_spec)"
|
|
16
|
+
package_manager="$(yad_detect_package_manager "$package_manager_spec")"
|
|
17
|
+
# `npm`, `pnpm`, or `corepack npm` for a pinned npm — see yad_package_manager_command.
|
|
18
|
+
run_with_words="$(yad_package_manager_command "$package_manager_spec" "$package_manager")"
|
|
19
|
+
read -r -a run_with <<< "$run_with_words"
|
|
20
|
+
|
|
7
21
|
echo "[build/test/lint] lint…"
|
|
8
|
-
|
|
22
|
+
"${run_with[@]}" run --silent lint
|
|
9
23
|
echo "[build/test/lint] build…"
|
|
10
|
-
|
|
24
|
+
"${run_with[@]}" run --silent build
|
|
11
25
|
|
|
12
26
|
# Worker cap: when YAD_TEST_MAX_WORKERS is set AND the repo's test script is jest/vitest (the
|
|
13
27
|
# runners that accept --maxWorkers), forward it to bound CI test concurrency. For any other runner
|
|
14
28
|
# (node --test, mocha, …) it is a deliberate no-op so the gate never breaks on an unknown flag.
|
|
29
|
+
# npm needs the `--` separator to hand the flag to the script; pnpm (7+) forwards a literal `--` to
|
|
30
|
+
# the script instead, where jest reads `--maxWorkers=N` as a test-path pattern and finds no tests.
|
|
15
31
|
extra=""
|
|
16
32
|
if [ -n "${YAD_TEST_MAX_WORKERS:-}" ]; then
|
|
17
|
-
case "$(
|
|
18
|
-
*jest*|*vitest*)
|
|
33
|
+
case "$(node --input-type=module -e 'import fs from "node:fs"; const p=JSON.parse(fs.readFileSync("package.json", "utf8").replace(/^\uFEFF/, "")); process.stdout.write(p.scripts?.test || "")' 2>/dev/null || true)" in
|
|
34
|
+
*jest*|*vitest*)
|
|
35
|
+
if [ "$package_manager" = "pnpm" ]; then extra="--maxWorkers=${YAD_TEST_MAX_WORKERS}"; else extra="-- --maxWorkers=${YAD_TEST_MAX_WORKERS}"; fi
|
|
36
|
+
;;
|
|
19
37
|
esac
|
|
20
38
|
fi
|
|
21
39
|
echo "[build/test/lint] test…"
|
|
22
|
-
# Intentional word-splitting: $extra is
|
|
40
|
+
# Intentional word-splitting: $extra is empty, `-- --maxWorkers=N` (npm), or `--maxWorkers=N` (pnpm).
|
|
23
41
|
# shellcheck disable=SC2086
|
|
24
|
-
|
|
42
|
+
"${run_with[@]}" run --silent test $extra
|
|
25
43
|
|
|
26
44
|
echo "PASS [build/test/lint]: lint, build, and tests all green."
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# is `shipped`. A SEALED epic's artifacts are the final, approved description of shipped behaviour — so
|
|
4
4
|
# new behaviour must NOT be added to it; it belongs in a NEW threaded change-epic whose re-authored
|
|
5
5
|
# stories/test-cases describe the change. This gate FAILs any non-maintenance commit whose owning epic
|
|
6
|
-
# is sealed, forcing
|
|
6
|
+
# is sealed, forcing Shape to stay current (staleness becomes unshippable).
|
|
7
7
|
#
|
|
8
8
|
# The owning epic lives in the PRODUCT repo (via specs/<story>/link.md `product-repo`). When it is not
|
|
9
9
|
# reachable from CI, the seal cannot be read, so the commit PASSes with a note (degraded, fail-open here
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Deterministic CI dependency installation selected by package.json#packageManager.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
YAD_CHECKS_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
readonly YAD_CHECKS_DIR
|
|
7
|
+
# shellcheck source-path=SCRIPTDIR
|
|
8
|
+
# shellcheck source=package-manager.sh
|
|
9
|
+
source "$YAD_CHECKS_DIR/package-manager.sh"
|
|
10
|
+
|
|
11
|
+
package_manager_spec="$(yad_package_manager_spec)"
|
|
12
|
+
package_manager="$(yad_detect_package_manager "$package_manager_spec")"
|
|
13
|
+
|
|
14
|
+
case "$package_manager" in
|
|
15
|
+
npm)
|
|
16
|
+
if [ ! -f "$YAD_NPM_LOCKFILE" ] && [ ! -f "$YAD_NPM_SHRINKWRAP" ]; then
|
|
17
|
+
echo "FAIL [install-deps]: npm requires package-lock.json or npm-shrinkwrap.json." >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
if [[ "$package_manager_spec" == npm@* ]]; then
|
|
21
|
+
yad_require_corepack
|
|
22
|
+
corepack enable
|
|
23
|
+
yad_corepack_prepare "$package_manager_spec"
|
|
24
|
+
# npm's shim is not enabled by Corepack, so dispatch it explicitly through Corepack — the same
|
|
25
|
+
# `corepack npm` the gate uses for lint/build/test, so package.json#packageManager selects the
|
|
26
|
+
# npm version end to end instead of the Node image's ambient npm binary.
|
|
27
|
+
corepack npm ci
|
|
28
|
+
else
|
|
29
|
+
npm ci
|
|
30
|
+
fi
|
|
31
|
+
;;
|
|
32
|
+
pnpm)
|
|
33
|
+
if [ ! -f "$YAD_PNPM_LOCKFILE" ]; then
|
|
34
|
+
echo "FAIL [install-deps]: pnpm requires pnpm-lock.yaml." >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
if [[ "$package_manager_spec" != pnpm@* ]]; then
|
|
38
|
+
echo "FAIL [install-deps]: pnpm CI requires an exact package.json#packageManager value (for example pnpm@9.15.0)." >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
yad_require_corepack
|
|
42
|
+
corepack enable
|
|
43
|
+
yad_corepack_prepare "$package_manager_spec"
|
|
44
|
+
pnpm install --frozen-lockfile
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ledger-guard gate.
|
|
3
|
-
# In
|
|
3
|
+
# In verified mode the gate ledger is CI-owned: only the yad gate-sync bot may change the
|
|
4
4
|
# machine-written gate-state files. A commit on a review PR by anyone else that modifies them is
|
|
5
5
|
# rejected — the human keeps the artifact, CI keeps the ledger. This makes "CI is the sole writer of
|
|
6
6
|
# the ledger" a mechanical guarantee instead of a convention.
|
|
@@ -20,21 +20,22 @@
|
|
|
20
20
|
# distinguishes CI-generated commits. A spoofed-author commit that is not Verified is treated as a
|
|
21
21
|
# human edit and rejected.
|
|
22
22
|
#
|
|
23
|
-
# Scope: enforced ONLY when the
|
|
24
|
-
# `
|
|
25
|
-
# `
|
|
26
|
-
#
|
|
23
|
+
# Scope: enforced ONLY when the ledger is verified — hub.json carries BOTH a `platform` and either
|
|
24
|
+
# `ledger: "verified"` or, on a project that has not run `yad migrate` yet, `bridge_enabled` (or the
|
|
25
|
+
# legacy `bridge`) true. That is the same predicate `isVerifiedLedger` (cli/manifest.mjs) and
|
|
26
|
+
# `hubActions` (cli/plan.mjs) apply. With a local ledger — or a platform-less hub — humans
|
|
27
|
+
# legitimately write the ledger themselves, so the gate is a no-op.
|
|
27
28
|
#
|
|
28
29
|
# Degradation: a base ref that cannot be resolved FAILs closed; no platform (cannot read the Verified
|
|
29
30
|
# badge) WARNs and waives the signature half — the same stance verified-commits takes.
|
|
30
31
|
set -euo pipefail
|
|
31
32
|
|
|
32
33
|
# ---- bridge gate: only CI-owned ledgers are guarded -------------------------------------------
|
|
33
|
-
# The predicate is BOTH a platform and the
|
|
34
|
+
# The predicate is BOTH a platform and the verified ledger flag, exactly as `isVerifiedLedger` (`cli/manifest.mjs`) and
|
|
34
35
|
# `hubActions` (cli/plan.mjs) define it. Requiring the flag alone put this gate out of step with every
|
|
35
|
-
# other
|
|
36
|
+
# other ledger reader (issue #186): a hub carrying `bridge_enabled: true` with no `platform` would
|
|
36
37
|
# have its human ledger commits rejected here while the CLI, reading the same file, called it
|
|
37
|
-
#
|
|
38
|
+
# local and kept the LOCAL write path — no CI writer and no permitted human writer, so no gate
|
|
38
39
|
# could advance. Reachable through a stale install (platform set, script wired, platform later
|
|
39
40
|
# nulled), not through `yad setup`, which derives both from one value.
|
|
40
41
|
#
|
|
@@ -45,7 +46,7 @@ set -euo pipefail
|
|
|
45
46
|
# Matched at the ROOT LEVEL only. The shared `default_branch` read below is depth-blind, and that is
|
|
46
47
|
# survivable there — a false match yields a bogus branch name and the gate fails loudly. Here it is
|
|
47
48
|
# not: a nested `"bridge": true` (say under `review`) would silently ENABLE this gate on a hub whose
|
|
48
|
-
# `
|
|
49
|
+
# `isVerifiedLedger` is false, recreating the exact no-writer deadlock #186 is about, from the other side. So
|
|
49
50
|
# the nesting is stripped rather than ignored: peel the outermost braces, then delete innermost
|
|
50
51
|
# objects/arrays until none remain, leaving only root-level pairs to match against. Not a JSON parser
|
|
51
52
|
# — a value containing a literal brace would confuse it — but hub.json is machine-written and the
|
|
@@ -67,9 +68,37 @@ done
|
|
|
67
68
|
# SIGPIPE-under-pipefail problem this avoids).
|
|
68
69
|
hub_str() { sed -nE "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\1/p" <<< "$HUB_ROOT"; }
|
|
69
70
|
hub_true() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*true" <<< "$HUB_ROOT"; }
|
|
71
|
+
# Is the key present with a STRING value at all, empty included? `hub_str` cannot answer this: it
|
|
72
|
+
# returns nothing both for a missing key and for an empty one.
|
|
73
|
+
hub_has_str() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*\"" <<< "$HUB_ROOT"; }
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
# READ ORDER, identical to `isVerifiedLedger` (cli/manifest.mjs) — the two must never disagree:
|
|
76
|
+
# 1. `ledger`, if hub.json carries it — shape 2 and later. "verified" and nothing else.
|
|
77
|
+
# 2. otherwise the old booleans `bridge_enabled` (canonical) or `bridge` (older still).
|
|
78
|
+
# A platform is required either way, for the reason in the header.
|
|
79
|
+
#
|
|
80
|
+
# Step 2 is NOT dead weight. This script is committed inside the user's repo and refreshed by
|
|
81
|
+
# `yad update`, which is a separate act from `yad migrate` — so an un-migrated hub.json (no `ledger`
|
|
82
|
+
# key at all) will be read by this version of the script, and it has to keep saying "verified".
|
|
83
|
+
# cli/test-checks.mjs runs a table of hub.json variants through this script AND through the JS
|
|
84
|
+
# reader and asserts they agree on every row.
|
|
85
|
+
# Written as if/else rather than `cmd; verified=$?`: under the `set -e` above, a bare failing test
|
|
86
|
+
# would EXIT the script instead of recording a false — and exiting mid-guard is indistinguishable
|
|
87
|
+
# from passing, so the gate would silently stop guarding.
|
|
88
|
+
# PRESENCE of the key decides which branch runs, not whether its value is non-empty. `"ledger": ""`
|
|
89
|
+
# is a present key with a string value: the JS reader stops there and answers "local", so this must
|
|
90
|
+
# too. Testing `-n` instead sent an empty value down to the old booleans and the two readers gave
|
|
91
|
+
# OPPOSITE answers — the guard rejecting human writes while the CLI kept the local path open, which
|
|
92
|
+
# is the no-writer deadlock of #186 reached from a third direction.
|
|
93
|
+
verified=no
|
|
94
|
+
if hub_has_str ledger; then
|
|
95
|
+
if [ "$(hub_str ledger)" = "verified" ]; then verified=yes; fi
|
|
96
|
+
elif hub_true bridge_enabled || hub_true bridge; then
|
|
97
|
+
verified=yes
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if [ ! -f "$HUB" ] || [ -z "$(hub_str platform)" ] || [ "$verified" != yes ]; then
|
|
101
|
+
echo "PASS [ledger-guard]: the ledger is locally owned (ledger: local) — nothing to guard."
|
|
73
102
|
exit 0
|
|
74
103
|
fi
|
|
75
104
|
|
|
@@ -159,7 +188,7 @@ trusted_bot() {
|
|
|
159
188
|
# A brand-new epic's ledger has no CI author. `gate ci` only ADVANCES an existing chain — it bails on
|
|
160
189
|
# a missing state.json ("the review branch is cut from the default branch, so it should carry it") and
|
|
161
190
|
# writes only at merge, on the default branch — and the engine itself reads a missing state.json as
|
|
162
|
-
# "not seeded yet". `gate open` writes nothing in
|
|
191
|
+
# "not seeded yet". `gate open` writes nothing in verified mode, and `checkpoint` stages Build
|
|
163
192
|
# ledgers only. So the seed the authoring skills write (yad-epic / yad-change / yad-analysis /
|
|
164
193
|
# yad-discovery / yad-stub) can reach the trunk ONLY through the first review PR/MR — the one place
|
|
165
194
|
# this gate runs. Guarding it there makes the documented flow unshippable on a protected trunk, so an
|