yadflow 3.18.1 → 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 +38 -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-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
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
# [3.19.0-next.2](https://github.com/abdelrahmannasr/yadflow/compare/v3.19.0-next.1...v3.19.0-next.2) (2026-09-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **release:** let a pre-release publish past its own test suite ([70286ce](https://github.com/abdelrahmannasr/yadflow/commit/70286ce833b300d02d2781ac589d727481f5e7eb))
|
|
7
|
+
|
|
8
|
+
# [3.19.0-next.1](https://github.com/abdelrahmannasr/yadflow/compare/v3.18.1...v3.19.0-next.1) (2026-09-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **checks:** accept every integrity algorithm Corepack accepts in packageManager ([367947d](https://github.com/abdelrahmannasr/yadflow/commit/367947d8d9856138b531061e4b686057ee9f715f))
|
|
14
|
+
* **checks:** cache the Corepack home alongside the dependency cache ([23d92bc](https://github.com/abdelrahmannasr/yadflow/commit/23d92bc8204063bbd7754bfd038585eeb6f705bb))
|
|
15
|
+
* **checks:** close configurable CI review findings ([6d90018](https://github.com/abdelrahmannasr/yadflow/commit/6d900183bbee5785f4ff0d5c868a6a55ce01a9b7))
|
|
16
|
+
* **checks:** fail with guidance when corepack is missing for a declared packageManager ([3a9f576](https://github.com/abdelrahmannasr/yadflow/commit/3a9f5760bcca050d87008e414b856d5b307f30b8))
|
|
17
|
+
* **checks:** give the same guidance when Corepack is present but stale ([0cdf4d1](https://github.com/abdelrahmannasr/yadflow/commit/0cdf4d157946d9185bfcc286ebddcdfb5d65a6aa))
|
|
18
|
+
* **checks:** keep a yarn/bun-declared repo with an npm lockfile on the npm path ([713e707](https://github.com/abdelrahmannasr/yadflow/commit/713e7074b6813de0e0fd6e12e970e8372a1309eb))
|
|
19
|
+
* **checks:** keep the gate jobs' variables off the host GitLab pipeline ([7cb2c35](https://github.com/abdelrahmannasr/yadflow/commit/7cb2c35a3011dbd8bf8d048f7aa02578ac54a1db))
|
|
20
|
+
* **checks:** keep the npm path when a repo carries both lockfiles ([67fbdb4](https://github.com/abdelrahmannasr/yadflow/commit/67fbdb4e2c82188b37b1d175260b48da17d5e051))
|
|
21
|
+
* **checks:** make build-test-lint fail closed on a rejected package.json ([83241ee](https://github.com/abdelrahmannasr/yadflow/commit/83241eed61e4306853151e6105c0b7c8bdd06d55))
|
|
22
|
+
* **checks:** pass the worker cap to jest/vitest under pnpm without npm's `--` ([55db7ee](https://github.com/abdelrahmannasr/yadflow/commit/55db7eed85173472a17e7591636d7fc830a5a5b6))
|
|
23
|
+
* **checks:** read package.json the way npm does before judging it ([8021c78](https://github.com/abdelrahmannasr/yadflow/commit/8021c7850d4f7d0887923165adc585fc27da260a))
|
|
24
|
+
* **checks:** require lowercase Corepack digests ([4750b8b](https://github.com/abdelrahmannasr/yadflow/commit/4750b8b597bfdd4d7d2739a0525f91a2002e41a3))
|
|
25
|
+
* **checks:** restore dependency caching in the GitHub quality job, for pnpm too ([21c92f0](https://github.com/abdelrahmannasr/yadflow/commit/21c92f026aa590ec464eec7152af27ad2be9a49d))
|
|
26
|
+
* **checks:** run the gate's lint/build/test through the pinned npm too ([8404da3](https://github.com/abdelrahmannasr/yadflow/commit/8404da326f8a19ae807c58808b6203572aadb9b4))
|
|
27
|
+
* **checks:** support configurable CI toolchains ([30557e4](https://github.com/abdelrahmannasr/yadflow/commit/30557e419a9fec5adca06a3ecb8ca09469b2a58a))
|
|
28
|
+
* **checks:** validate Corepack integrity metadata ([a6c3fd3](https://github.com/abdelrahmannasr/yadflow/commit/a6c3fd3d84a370f81477ed9b551bc29f5765296f))
|
|
29
|
+
* **skills:** teach the skill layer that `ledger` is the switch ([d4622ab](https://github.com/abdelrahmannasr/yadflow/commit/d4622ab2293cfe49a36791f6cf51472b4d5701ad)), closes [#186](https://github.com/abdelrahmannasr/yadflow/issues/186)
|
|
30
|
+
* **state:** read an unstamped file as shape 1, not as the engine's shape ([6d8c608](https://github.com/abdelrahmannasr/yadflow/commit/6d8c608215175fac98933ffee6f714471454a8d9))
|
|
31
|
+
* **update:** install templates newly added to a wired repo on `yad update` ([8e8d92f](https://github.com/abdelrahmannasr/yadflow/commit/8e8d92f1265e2ed375bf0a53307270a25d8c33f3))
|
|
32
|
+
* **update:** take only yad's own wiring as proof a repo is wired ([3c90134](https://github.com/abdelrahmannasr/yadflow/commit/3c90134985eebb3b7faa91be2c45387d54851324))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Features
|
|
36
|
+
|
|
37
|
+
* **state:** record who writes the ledger as `ledger: verified | local` ([6623199](https://github.com/abdelrahmannasr/yadflow/commit/66231997156bb1ffa446ebb5edd28d8ebf8def91)), closes [#186](https://github.com/abdelrahmannasr/yadflow/issues/186)
|
|
38
|
+
|
|
1
39
|
## [3.18.1](https://github.com/abdelrahmannasr/yadflow/compare/v3.18.0...v3.18.1) (2026-09-05)
|
|
2
40
|
|
|
3
41
|
|
package/README.md
CHANGED
|
@@ -38,11 +38,11 @@ one product hub + many code repos, solo or team.
|
|
|
38
38
|
## How the workflow looks
|
|
39
39
|
|
|
40
40
|
<!-- Source: docs/diagrams/sdlc-overview.mmd — edit the .mmd and run `npm run diagrams` to regenerate -->
|
|
41
|
-

|
|
42
42
|
|
|
43
43
|
**Legend:** 🟨 **artifact** (a step writes a file and stops) · 🟧 **gate** (a human review that must
|
|
44
|
-
pass) · 🟦 **earns automation** (a
|
|
45
|
-
⬜ **locked** (the engineer review and every
|
|
44
|
+
pass) · 🟦 **earns automation** (a Build step that can later auto-advance once it proves itself) ·
|
|
45
|
+
⬜ **locked** (the engineer review and every Shape step — permanently human).
|
|
46
46
|
|
|
47
47
|
## Quickstart
|
|
48
48
|
|
|
@@ -56,7 +56,7 @@ Then, in your AI IDE, drive the lifecycle by invoking skills by name:
|
|
|
56
56
|
|
|
57
57
|
```text
|
|
58
58
|
run yad-epic # 2. author + gate the "thinking": epic → architecture → UI → stories
|
|
59
|
-
run yad-spec … # 3.
|
|
59
|
+
run yad-spec … # 3. Build: spec → implement → checks → ship (per story, per repo)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Every step stops at a gate until a human approves. New here? **Walk it lesson-by-lesson in the
|
|
@@ -89,7 +89,7 @@ In one pass it produces:
|
|
|
89
89
|
push-on-main **`yad-update-guard`** (which re-checks any direct-to-default commit — e.g. from
|
|
90
90
|
`yad update --push` — with just `verified-commits` + `commit-message`), shipped as CI-agnostic bash
|
|
91
91
|
under `checks/`.
|
|
92
|
-
- **An agent guardrail** on a
|
|
92
|
+
- **An agent guardrail** on a verified hub — `hooks/ledger-guard.sh`, a harness hook that refuses an
|
|
93
93
|
agent the CI-owned gate-ledger write at the moment it tries it and names the command that owns the
|
|
94
94
|
transition, instead of letting it surface as a CI failure twenty minutes later. Harness-agnostic
|
|
95
95
|
(stdin payload, exit 0 allows / 2 denies) and fails open — the CI gate stays the authority.
|
|
@@ -109,21 +109,21 @@ setup → AI drafts an artifact → ⛔ gate waits → you approve → next step
|
|
|
109
109
|
1. **`npx yadflow setup`** — the wizard installs skills, connects your repo, and wires the gates.
|
|
110
110
|
2. **Run `yad-epic`** in your assistant — it drafts the epic, then **stops** and writes it to a file.
|
|
111
111
|
3. **A gate waits.** Nothing advances until you review it.
|
|
112
|
-
4. **You approve** —
|
|
112
|
+
4. **You approve** — local, or by merging the review PR/MR.
|
|
113
113
|
5. **The workflow continues** to the next step, which stops again.
|
|
114
114
|
|
|
115
115
|
Every step is the same contract: *AI proposes → a human decides → the trail is recorded.*
|
|
116
116
|
|
|
117
117
|
## How it works (in five points)
|
|
118
118
|
|
|
119
|
-
- **
|
|
119
|
+
- **Shape = decide.** Once per epic, in the product hub: epic, architecture + a locked contract,
|
|
120
120
|
UI, stories, test cases. Always human-gated — nothing auto-advances.
|
|
121
|
-
- **Build
|
|
122
|
-
- **Every step stops at a gate.** A human moves it forward (
|
|
121
|
+
- **Build = make it real.** Once per story per code repo: spec → implement → checks → ship.
|
|
122
|
+
- **Every step stops at a gate.** A human moves it forward (local, or by merging a review PR/MR).
|
|
123
123
|
<!-- IMAGE: docs/media/pr-gate.png — "The review gate rides a real PR/MR: approve to advance, comment to block." -->
|
|
124
|
-
- **Automation is opt-in and earned.** A safe
|
|
124
|
+
- **Automation is opt-in and earned.** A safe Build step can earn auto-advance after it proves
|
|
125
125
|
itself — and a one-command kill switch reverts everything to manual. The engineer review and all
|
|
126
|
-
|
|
126
|
+
Shape steps are never automatable.
|
|
127
127
|
- **Everything is files.** State, approvals, the contract lock, the build log — all plain files under
|
|
128
128
|
`epics/EP-<slug>/`. No database. The audit trail *is* the repo.
|
|
129
129
|
|
package/bin/yad.mjs
CHANGED
|
@@ -59,7 +59,7 @@ ${c.bold('Setup & maintenance')}
|
|
|
59
59
|
(no paths/hosts/repo names/logins/flag values). Also offered
|
|
60
60
|
automatically after an unexpected failure. YAD_NO_REPORT=1 disables.
|
|
61
61
|
yad hook ledger-guard ${c.dim('harness-invoked, not typed')} — refuse an agent's edit to the
|
|
62
|
-
CI-owned gate ledger in
|
|
62
|
+
CI-owned gate ledger in verified mode and name the command that owns
|
|
63
63
|
the transition. Reads a tool-call payload on stdin (or --path <p>);
|
|
64
64
|
exit 0 allows, exit 2 denies with the reason on stderr. Wired into
|
|
65
65
|
.claude/settings.json by setup / check --fix. YAD_HOOK_DISABLE=1 skips.
|
|
@@ -91,14 +91,14 @@ ${c.bold('Where am I / what next')}
|
|
|
91
91
|
Stays visible & auditable (pre-done, gate short-circuited);
|
|
92
92
|
--undo reverses it until the stories review opens
|
|
93
93
|
|
|
94
|
-
${c.bold('Review gate (
|
|
94
|
+
${c.bold('Review gate (Shape)')}
|
|
95
95
|
yad gate open <epic> <artifact> Open the review PR/MR; mark the step in_review. The review
|
|
96
96
|
branch must already be on origin (it is never created here)
|
|
97
97
|
yad gate sync <epic> [artifact] [--pr <n>]
|
|
98
98
|
Pull PR state -> ledger; advance on approved+resolved+merged.
|
|
99
99
|
With no recorded PR, resolves it from the review branch; --pr
|
|
100
100
|
names one (and overrides a stale recorded pointer). Advisory
|
|
101
|
-
in
|
|
101
|
+
in verified mode — there, recover with 'yad gate ci' below
|
|
102
102
|
yad gate comments <epic> [artifact] Fetch unresolved review comments to address
|
|
103
103
|
yad gate status <epic> Show each review step + approvals
|
|
104
104
|
yad gate repair <epic> [--push] Close an author step stranded behind a passed review gate
|
|
@@ -117,11 +117,11 @@ ${c.bold('Build helpers')}
|
|
|
117
117
|
yad commit --type <t> -m <subject> Commit by convention (trailers, atomic guard)
|
|
118
118
|
yad open-pr [--repo <name>] Open a task PR/MR against the repo's DEFAULT branch (never a
|
|
119
119
|
hardcoded main; --base overrides) — stage-aware on the hub: a
|
|
120
|
-
review/EP-* branch opens the
|
|
120
|
+
review/EP-* branch opens the Shape artifact-review PR
|
|
121
121
|
(delegates to gate open), any other hub branch uses the
|
|
122
122
|
code-task template
|
|
123
123
|
yad ship --type <t> -m <subject> Commit AND open the task PR/MR in one step (stage-aware)
|
|
124
|
-
yad checkpoint [--push] Commit the machine-written
|
|
124
|
+
yad checkpoint [--push] Commit the machine-written Build hub state
|
|
125
125
|
(trust-log/build-log/build-state) — plus any story
|
|
126
126
|
status: flip (→ in-build/shipped) backed by a build-log
|
|
127
127
|
ship — as one audit-trail chore(hub) commit; default
|
|
@@ -131,7 +131,7 @@ ${c.bold('Build helpers')}
|
|
|
131
131
|
(merged before ledger tracking), then carry its status: shipped
|
|
132
132
|
flip in the same commit (--merge-commit <sha>, --task <t> opt.);
|
|
133
133
|
one repo per run — re-run per --repo for a multi-repo story
|
|
134
|
-
yad tidy up [<epic>] [--push] Fold FINISHED
|
|
134
|
+
yad tidy up [<epic>] [--push] Fold FINISHED Build shards (a shipped story's
|
|
135
135
|
trust-log/build-log entries) back into the single folded
|
|
136
136
|
ledger, as one chore(hub) commit — the manual "pack it up"
|
|
137
137
|
for the shard files; a no-op when nothing is foldable
|
|
@@ -320,9 +320,9 @@ async function main() {
|
|
|
320
320
|
if (!epic) { log(c.red('usage: yad gate <open|sync|comments|status|repair|review|walkthrough|trailer|ci> <epic> [artifact]')); process.exitCode = 1; break; }
|
|
321
321
|
// The epic id becomes a path segment under epics/ — reject anything but EP-<slug> outright.
|
|
322
322
|
if (!isValidEpicId(epic)) { log(c.red(`invalid epic id: ${epic} (expected EP-<slug>, [a-z0-9-] only)`)); process.exitCode = 1; break; }
|
|
323
|
-
// In
|
|
323
|
+
// In verified mode CI is the sole ledger writer: `open` only opens the PR, and local `sync` is
|
|
324
324
|
// advisory (reads the platform, prints status, writes nothing). The artifact status flip is
|
|
325
|
-
// CI's job at merge — never wired into the local gate.
|
|
325
|
+
// CI's job at merge — never wired into the local gate. Local mode keeps local writes.
|
|
326
326
|
if (action === 'open') await gateOpen(o.dir, { epic, artifact });
|
|
327
327
|
else if (action === 'sync') await gateSync(o.dir, { epic, artifact, today, number: o.pr, local: true });
|
|
328
328
|
else if (action === 'comments') await gateComments(o.dir, { epic, artifact, today });
|
package/cli/artifact-status.mjs
CHANGED
|
@@ -9,20 +9,20 @@ import { c, log, ok, info, readJSONStrict } from './lib.mjs';
|
|
|
9
9
|
import { epicRoot, artifactBase, artifactFromBase, findReviewStep, DISCOVERY_FILES } from './epic-state.mjs';
|
|
10
10
|
import { epicFiles } from './manifest.mjs';
|
|
11
11
|
|
|
12
|
-
// The
|
|
12
|
+
// The Shape gate lifecycle this command manages. Forward-only: a status is only ever moved UP this
|
|
13
13
|
// ladder, so a re-run never regresses anything.
|
|
14
14
|
const RANK = { draft: 0, 'in-review': 1, approved: 2 };
|
|
15
15
|
|
|
16
16
|
// Values owned by other parts of the workflow — left untouched. `locked` is the contract surface;
|
|
17
|
-
// `in-build` / `shipped` are set by
|
|
18
|
-
// `done`, `blocked` are roll-ups/states we must not overwrite from the
|
|
17
|
+
// `in-build` / `shipped` are set by Build (engineer-review) per story; `ready-for-build`,
|
|
18
|
+
// `done`, `blocked` are roll-ups/states we must not overwrite from the Shape gate view.
|
|
19
19
|
const PRESERVE = new Set(['locked', 'in-build', 'shipped', 'ready-for-build', 'done', 'blocked']);
|
|
20
20
|
|
|
21
21
|
// The per-epic artifact files this command considers (bases). Story files are handled separately
|
|
22
22
|
// because they live under stories/ and all map to the single stories / stories-review step pair.
|
|
23
23
|
const ARTIFACT_FILES = ['analysis.md', 'epic.md', 'architecture.md', 'contract.md', 'ui-design.md', 'test-cases.md'];
|
|
24
24
|
|
|
25
|
-
// The desired
|
|
25
|
+
// The desired Shape gate status for an artifact base, derived purely from state.json. Returns null
|
|
26
26
|
// when the chain has no steps for this base (nothing to manage) — e.g. contract has no own step.
|
|
27
27
|
export function desiredStatus(state, base) {
|
|
28
28
|
if (!state?.steps) return null;
|
package/cli/checkpoint.mjs
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// `yad checkpoint` — commit the machine-written
|
|
2
|
-
// build-state) as one audit-trail commit. This is the
|
|
3
|
-
// (cli/gate.mjs): the SDLC
|
|
1
|
+
// `yad checkpoint` — commit the machine-written Build hub state (trust-log / build-log /
|
|
2
|
+
// build-state) as one audit-trail commit. This is the Build analogue of the Shape gate sync
|
|
3
|
+
// (cli/gate.mjs): the SDLC's Build part (yad-run, yad-engineer-review) WRITES these ledgers into the
|
|
4
4
|
// working tree but never commits them, so teammates/CI/`yad status` on other machines see stale trust
|
|
5
5
|
// evidence. checkpoint lands them with a `chore(hub): ...` message.
|
|
6
6
|
//
|
|
7
|
-
// It also carries the
|
|
7
|
+
// It also carries the Build story `status:` flip (approved → in-build/shipped) that
|
|
8
8
|
// yad-engineer-review writes into stories/<id>.md but no command committed — the #112 drift where
|
|
9
9
|
// build-log said shipped while the story artifact still said approved. Only story files with build-log
|
|
10
10
|
// ship evidence are carried (storyStatusPathspecs), AND only when their staged change is the `status:`
|
|
11
|
-
// line alone (stagedStoryIsStatusOnly) — so it stays a
|
|
11
|
+
// line alone (stagedStoryIsStatusOnly) — so it stays a Build record, never a raw edit that would
|
|
12
12
|
// slip prose onto the default branch under a `[skip ci]` commit that bypasses review.
|
|
13
13
|
//
|
|
14
14
|
// Two invariants keep it out of the gates' way:
|
|
15
|
-
// 1. It stages ONLY the
|
|
16
|
-
// never `git add -A`, which would sweep the CI-owned
|
|
15
|
+
// 1. It stages ONLY the Build ledgers + build-log-backed story flips by an explicit allowlist —
|
|
16
|
+
// never `git add -A`, which would sweep the CI-owned Shape ledger (state/approvals/
|
|
17
17
|
// comments/hub-prs.json, reviews/*.md) and trip the ledger-guard gate. (ledger-guard does NOT
|
|
18
18
|
// protect stories/*.md, and this commits to the default branch, never a PR range, so the carried
|
|
19
19
|
// story flip is safe.)
|
|
@@ -30,7 +30,7 @@ import { hubGit, resolveDefaultBranch, guardDefaultBranch } from './hubcommit.mj
|
|
|
30
30
|
import { readShips, writeRetroShip } from './ledger.mjs';
|
|
31
31
|
import { readFrontmatter } from './epic-state.mjs';
|
|
32
32
|
|
|
33
|
-
// The machine-written
|
|
33
|
+
// The machine-written Build ledgers, relative to an epic's dir. The two append-only logs are
|
|
34
34
|
// shard-then-fold (cli/ledger.mjs): each is a folded file PLUS a shard dir of loose per-entry files —
|
|
35
35
|
// both are allowlisted so a checkpoint commits new shards and any `yad tidy up` fold. `build-state` is
|
|
36
36
|
// the whole dir (one JSON per story). Keep in sync with cli/manifest.mjs epicFiles.
|
|
@@ -40,9 +40,9 @@ const BACK_HALF = [
|
|
|
40
40
|
'.sdlc/build-state',
|
|
41
41
|
];
|
|
42
42
|
|
|
43
|
-
// PURE — the repo-relative pathspecs to stage: every
|
|
43
|
+
// PURE — the repo-relative pathspecs to stage: every Build ledger that exists under any epic.
|
|
44
44
|
// Explicit allowlist by design (see invariant 1 above).
|
|
45
|
-
export function
|
|
45
|
+
export function buildLedgerPathspecs(root) {
|
|
46
46
|
const epicsDir = path.join(root, 'epics');
|
|
47
47
|
if (!fs.existsSync(epicsDir)) return [];
|
|
48
48
|
const out = [];
|
|
@@ -55,17 +55,17 @@ export function backHalfPathspecs(root) {
|
|
|
55
55
|
return out;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// The two
|
|
59
|
-
// Both are set ONLY by
|
|
58
|
+
// The two Build story statuses. `in-build` = some of a story's tasks shipped; `shipped` = all did.
|
|
59
|
+
// Both are set ONLY by Build (yad-engineer-review), never by the Shape gate ladder
|
|
60
60
|
// (cli/artifact-status.mjs PRESERVEs them). See #112.
|
|
61
61
|
const BACK_HALF_STATUSES = new Set(['in-build', 'shipped']);
|
|
62
62
|
|
|
63
|
-
// The repo-relative pathspecs for story files whose
|
|
63
|
+
// The repo-relative pathspecs for story files whose Build `status:` flip we carry alongside the
|
|
64
64
|
// ledgers (#112). The flip is authored by yad-engineer-review into the working tree but no command
|
|
65
65
|
// committed it, so it drifted (build-log said shipped, stories/<id>.md still said approved) and the
|
|
66
66
|
// only recovery was a raw git-to-main push. A story is a CANDIDATE iff BOTH hold:
|
|
67
|
-
// 1. it has >=1 ship recorded in build-log (the
|
|
68
|
-
// 2. its current frontmatter `status:` is a
|
|
67
|
+
// 1. it has >=1 ship recorded in build-log (the Build evidence), and
|
|
68
|
+
// 2. its current frontmatter `status:` is a Build value (in-build | shipped).
|
|
69
69
|
// A candidate is only actually carried when its staged diff is the `status:` line ALONE — runCheckpoint
|
|
70
70
|
// drops any candidate whose working tree also changed prose/other frontmatter (stagedStoryIsStatusOnly),
|
|
71
71
|
// so an unrelated edit can never ride into a `chore(hub) … [skip ci]` commit that bypasses review.
|
|
@@ -74,7 +74,7 @@ const BACK_HALF_STATUSES = new Set(['in-build', 'shipped']);
|
|
|
74
74
|
//
|
|
75
75
|
// A corrupt build-log in one epic must not block checkpointing every OTHER epic's ledgers, so a
|
|
76
76
|
// readShips throw is caught per epic (that epic simply carries no story flip; its corrupt ledger is
|
|
77
|
-
// still staged by
|
|
77
|
+
// still staged by buildLedgerPathspecs for a human to see).
|
|
78
78
|
export function storyStatusPathspecs(root) {
|
|
79
79
|
const epicsDir = path.join(root, 'epics');
|
|
80
80
|
if (!fs.existsSync(epicsDir)) return [];
|
|
@@ -107,7 +107,7 @@ export function summarizeStaged(files = []) {
|
|
|
107
107
|
const epics = new Set();
|
|
108
108
|
const basenames = [];
|
|
109
109
|
for (const f of files) {
|
|
110
|
-
// A
|
|
110
|
+
// A Build ledger (…/.sdlc/…) or a carried story-status flip (…/stories/<id>.md, #112).
|
|
111
111
|
const m = f.match(/^epics\/([^/]+)\/(?:\.sdlc\/(.+)|stories\/(.+\.md))$/);
|
|
112
112
|
if (!m) continue;
|
|
113
113
|
const [, epic] = m;
|
|
@@ -143,7 +143,7 @@ export function checkpointAuthor(login, name) {
|
|
|
143
143
|
// Co-Authored-By footer: this is human-owned machine state, not an authored code change. `label` and
|
|
144
144
|
// `author` are collapsed to one line so nothing can split the subject or forge a trailer.
|
|
145
145
|
export function buildCheckpointMessage({ label, author, basenames = [] }) {
|
|
146
|
-
const subject = `chore(hub): sync
|
|
146
|
+
const subject = `chore(hub): sync Build state — ${oneLine(label)} by ${oneLine(author)} [skip ci]`;
|
|
147
147
|
const body = basenames.length ? `Updated: ${basenames.join(', ')}` : '';
|
|
148
148
|
return body ? `${subject}\n\n${body}` : subject;
|
|
149
149
|
}
|
|
@@ -170,7 +170,7 @@ export function stagedStoryIsStatusOnly(git, file) {
|
|
|
170
170
|
// normal checkpoint path carries the story's already-made `status:` flip. Returns { ok, file } — ok:false
|
|
171
171
|
// (with a printed reason) aborts the commit; `file` is the shard just written, so a dry run can delete it
|
|
172
172
|
// and leave no side effect. Does NOT author the story frontmatter — it only supplies the missing
|
|
173
|
-
// evidence, and the human must have ALREADY flipped `status:` to a
|
|
173
|
+
// evidence, and the human must have ALREADY flipped `status:` to a Build value in the working tree.
|
|
174
174
|
//
|
|
175
175
|
// ONE repo per run (#166). A story that shipped in several repos is backfilled by re-running with each
|
|
176
176
|
// `--repo`; the second run finds the flip already committed, so it lands only the new ship shard.
|
|
@@ -201,7 +201,7 @@ export function recordRetroShip(root, { epic, story, repo, task, mergeCommit, to
|
|
|
201
201
|
if (!exists(storyFile)) { fail(`no story ${story} under epics/${epic}/stories/`); return { ok: false }; }
|
|
202
202
|
|
|
203
203
|
// Evidence and the flip must land TOGETHER — the #112 no-drift invariant. Refuse unless the human has
|
|
204
|
-
// already flipped the story frontmatter to a
|
|
204
|
+
// already flipped the story frontmatter to a Build status in the working tree; otherwise the ship
|
|
205
205
|
// shard would commit while the artifact still says e.g. `approved` — the very drift #112 prevents.
|
|
206
206
|
const storyStatus = readFrontmatter(storyFile).status;
|
|
207
207
|
if (!BACK_HALF_STATUSES.has(storyStatus)) {
|
|
@@ -290,7 +290,7 @@ export async function runCheckpoint(root, opts = {}) {
|
|
|
290
290
|
log(c.bold('\nyad checkpoint'));
|
|
291
291
|
if (!exists(path.join(root, '.git'))) { fail('not a git repo'); process.exitCode = 1; return; }
|
|
292
292
|
if (!exists(path.join(root, PROJECT_FILES.hubConfig))) {
|
|
293
|
-
fail('no .sdlc/hub.json — checkpoint commits the hub
|
|
293
|
+
fail('no .sdlc/hub.json — checkpoint commits the hub Build ledger; run it from the product hub');
|
|
294
294
|
process.exitCode = 1;
|
|
295
295
|
return;
|
|
296
296
|
}
|
|
@@ -304,7 +304,7 @@ export async function runCheckpoint(root, opts = {}) {
|
|
|
304
304
|
if (!guardDefaultBranch(branch, defaultBranch, { allowBranch: opts.allowBranch, cmd: 'yad checkpoint' })) return;
|
|
305
305
|
|
|
306
306
|
// --retro-ship (#142): record a retroactive build-log ship for a PRE-TRACKING story (merged before
|
|
307
|
-
// the
|
|
307
|
+
// the Build ledger existed, so it has no ship and its `status:` flip can't be carried). Done
|
|
308
308
|
// AFTER the branch guard so we never leave a dangling shard on the wrong branch; the flip the human
|
|
309
309
|
// already wrote is then carried by the normal storyStatusPathspecs path below — no raw git needed.
|
|
310
310
|
let retroFile;
|
|
@@ -324,8 +324,8 @@ export async function runCheckpoint(root, opts = {}) {
|
|
|
324
324
|
|
|
325
325
|
// The machine ledgers PLUS any build-log-backed story `status:` flip (#112) — one commit records
|
|
326
326
|
// both, so the story artifact never drifts from build-log and no raw git-to-main push is needed.
|
|
327
|
-
const pathspecs = [...
|
|
328
|
-
if (!pathspecs.length) { rollbackRetro(); info('no
|
|
327
|
+
const pathspecs = [...buildLedgerPathspecs(root), ...storyStatusPathspecs(root)];
|
|
328
|
+
if (!pathspecs.length) { rollbackRetro(); info('no Build ledgers found — nothing to checkpoint'); return; }
|
|
329
329
|
|
|
330
330
|
// Stage the allowlist. `git add -- <spec>` picks up new + modified files, and deletions of tracked
|
|
331
331
|
// files WITHIN a still-present spec (e.g. a removed build-state/<story>.json). A wholesale-deleted
|
|
@@ -348,7 +348,7 @@ export async function runCheckpoint(root, opts = {}) {
|
|
|
348
348
|
|
|
349
349
|
if (git('diff', '--cached', '--quiet', '--', ...pathspecs).ok) {
|
|
350
350
|
rollbackRetro();
|
|
351
|
-
info('
|
|
351
|
+
info('Build state unchanged — nothing to commit');
|
|
352
352
|
return;
|
|
353
353
|
}
|
|
354
354
|
// The exact files staged from the allowlist — all known to git by construction, so they are the
|
package/cli/commit.mjs
CHANGED
|
@@ -62,7 +62,7 @@ export async function runCommit(root, opts = {}) {
|
|
|
62
62
|
const task = opts.task || taskFromBranch(branch);
|
|
63
63
|
if (!task) {
|
|
64
64
|
// spec-link is a code-repo gate (REPO_WIRING.common), not a hub gate — so a missing Task trailer
|
|
65
|
-
// is expected on a hub PR (
|
|
65
|
+
// is expected on a hub PR (Shape artifact review or hub tooling) and only matters on a repo.
|
|
66
66
|
const onHub = exists(path.join(root, PROJECT_FILES.hubConfig));
|
|
67
67
|
warn(onHub
|
|
68
68
|
? 'no Task trailer (none given and branch has no -S0N-T0N) — fine for a hub PR; required on code-repo tasks (spec-link gate)'
|
package/cli/companion.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// cli/companion.mjs — pure, perspective-neutral helpers for the Review Companion, shared by
|
|
2
|
-
//
|
|
1
|
+
// cli/companion.mjs — pure, perspective-neutral helpers for the Review Companion, shared by Shape
|
|
2
|
+
// (yad gate …) and Build (yad review …). The CLI never calls an LLM — generation happens
|
|
3
3
|
// in the skill/harness layer (like yad-learn/yad-docs). This module owns the platform MARKERS, the
|
|
4
4
|
// engagement parsing, the trailer-block upsert, and the message text the skill and the gate share.
|
|
5
5
|
//
|
package/cli/doctor.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import { c, log, ok, info, warn, fail, hand, run, has, exists, readJSON, readJSONStrict } from './lib.mjs';
|
|
9
|
-
import { VERSION, PROJECT_FILES, DESIGN_TOOLS, TESTING_TOOLS, LEARNING_TOOLS, HOOK_SETTINGS, HOOK_TOOL_MATCHER,
|
|
9
|
+
import { VERSION, PROJECT_FILES, DESIGN_TOOLS, TESTING_TOOLS, LEARNING_TOOLS, HOOK_SETTINGS, HOOK_TOOL_MATCHER, isVerifiedLedger } from './manifest.mjs';
|
|
10
10
|
import { mergeHookSettings, hookMatcherFires, ideTargetsFor } from './plan.mjs';
|
|
11
11
|
import { planMigration } from './migrate.mjs';
|
|
12
12
|
import { loadLedger, epicRoot, isValidEpicId, epicLineage, resolveThread, stateInvariants, contractSurfaceHash, artifactHash } from './epic-state.mjs';
|
|
@@ -67,7 +67,7 @@ export function projectChecks(checks, root) {
|
|
|
67
67
|
// version stamp
|
|
68
68
|
const ver = readJSON(verPath, null);
|
|
69
69
|
if (!ver) check(checks, 'cli-version', 'project', 'warn', `${PROJECT_FILES.version} missing or unreadable`, 'run `yad check --fix`');
|
|
70
|
-
// The stamp is not only cosmetic: in
|
|
70
|
+
// The stamp is not only cosmetic: in verified mode the wired gate-sync job resolves the yadflow it
|
|
71
71
|
// RUNS from it — unless hub.json pins `gate_sync_version`, a YAD_VERSION variable overrides, or the
|
|
72
72
|
// stamp is not an exact release of the current major (then the job skips it and floats). So a stale
|
|
73
73
|
// stamp can mean CI is running an old gate; say so, or the warning reads as bookkeeping.
|
|
@@ -77,7 +77,7 @@ export function projectChecks(checks, root) {
|
|
|
77
77
|
// hub.json: parse + shape
|
|
78
78
|
let hub = null;
|
|
79
79
|
if (!exists(hubPath)) {
|
|
80
|
-
check(checks, 'hub', 'project', 'warn', `${PROJECT_FILES.hubConfig} absent —
|
|
80
|
+
check(checks, 'hub', 'project', 'warn', `${PROJECT_FILES.hubConfig} absent — local gate`, 'run `yad setup` to configure a platform + roster');
|
|
81
81
|
} else {
|
|
82
82
|
let hubBroken = false;
|
|
83
83
|
try {
|
|
@@ -92,13 +92,13 @@ export function projectChecks(checks, root) {
|
|
|
92
92
|
// Mirror gate.mjs's roster shape check so doctor never reports "ok" on a hub the gate would reject.
|
|
93
93
|
else if (hub.roster !== undefined && !Array.isArray(hub.roster)) check(checks, 'hub', 'project', 'fail', `${PROJECT_FILES.hubConfig}: \`roster\` must be an array [YAD-STATE-002]`, 'fix the file or re-run `yad setup`');
|
|
94
94
|
else {
|
|
95
|
-
check(checks, 'hub', 'project', 'ok', `hub: ${hub.platform || '
|
|
95
|
+
check(checks, 'hub', 'project', 'ok', `hub: ${hub.platform || 'local'}, ${(hub.roster || []).length} reviewer(s)`);
|
|
96
96
|
if (isSolo(hub)) check(checks, 'solo', 'project', 'ok', 'mode: solo — approval waived; the PR merge + resolved threads gate the step');
|
|
97
97
|
// platform CLI + auth (best-effort; auth probing is the user's own session)
|
|
98
98
|
const cli = cliFor(hub.platform);
|
|
99
99
|
if (cli) {
|
|
100
100
|
// git_url is required whenever a platform is set — doctor needs it to scope the auth probe
|
|
101
|
-
// and the
|
|
101
|
+
// and the verified ledger/PR flow needs it to open PRs. Warn on its absence directly (not on the
|
|
102
102
|
// resolved host), so it fires even when an origin remote can substitute: the field itself
|
|
103
103
|
// is required regardless.
|
|
104
104
|
if (!hostFromGitUrl(hub.git_url)) {
|
|
@@ -113,7 +113,7 @@ export function projectChecks(checks, root) {
|
|
|
113
113
|
// rather than run the flaky unscoped form.
|
|
114
114
|
const host = hostFromGitUrl(hub.git_url)
|
|
115
115
|
|| hostFromGitUrl(run('git', ['remote', 'get-url', 'origin'], { cwd: root }).stdout);
|
|
116
|
-
if (!has(cli)) check(checks, 'platform-cli', 'project', 'warn', `${cli} not found on PATH [YAD-ENV-002]`, `install ${cli} — the gate degrades to
|
|
116
|
+
if (!has(cli)) check(checks, 'platform-cli', 'project', 'warn', `${cli} not found on PATH [YAD-ENV-002]`, `install ${cli} — the gate degrades to local without it`);
|
|
117
117
|
else if (!host) check(checks, 'platform-cli', 'project', 'warn', 'auth check skipped — hub host unknown (no git_url / origin)', 'add git_url to hub.json so the auth probe can target the right host');
|
|
118
118
|
else if (!run(cli, ['auth', 'status', '--hostname', host]).ok) check(checks, 'platform-cli', 'project', 'warn', `${cli} present but not authenticated for ${host} [YAD-ENV-002]`, `run \`${cli} auth login --hostname ${host}\``);
|
|
119
119
|
else {
|
|
@@ -155,12 +155,12 @@ export function projectChecks(checks, root) {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
// The harness ledger guard (#171). Only meaningful in
|
|
158
|
+
// The harness ledger guard (#171). Only meaningful in verified mode: there the ledger is CI-owned and
|
|
159
159
|
// an agent's hand-edit is always rejected later by `ledger-guard`, so the local hook that refuses it
|
|
160
|
-
// up front should be installed.
|
|
160
|
+
// up front should be installed. With a local ledger nothing guards it, and the hand-edit the
|
|
161
161
|
// authoring skills describe is correct — nothing to report, so the check is silent rather than `ok`.
|
|
162
162
|
const hubForHooks = readJSON(hubPath, null);
|
|
163
|
-
if (
|
|
163
|
+
if (isVerifiedLedger(hubForHooks)) {
|
|
164
164
|
const unwired = [];
|
|
165
165
|
const broken = [];
|
|
166
166
|
if (!exists(path.join(root, 'hooks', 'ledger-guard.sh'))) unwired.push('hooks/ledger-guard.sh');
|
|
@@ -322,7 +322,7 @@ export function ciTagsChecks(checks, root, hub, registry) {
|
|
|
322
322
|
} catch { return false; } // absent fragment is not this check's concern
|
|
323
323
|
};
|
|
324
324
|
const fragments = [];
|
|
325
|
-
if (hub?.platform === 'gitlab' && (hub
|
|
325
|
+
if (hub?.platform === 'gitlab' && isVerifiedLedger(hub)) {
|
|
326
326
|
fragments.push(
|
|
327
327
|
{ scope: 'hub', file: '.gitlab/ci/yad-gate-sync.yml', path: path.join(root, '.gitlab/ci/yad-gate-sync.yml') },
|
|
328
328
|
{ scope: 'hub', file: '.gitlab/ci/yad-verified-commits.yml', path: path.join(root, '.gitlab/ci/yad-verified-commits.yml') },
|