yadflow 3.13.0 → 3.13.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 +30 -0
- package/bin/yad.mjs +8 -3
- package/cli/doctor.mjs +135 -1
- package/cli/epic-state.mjs +14 -6
- package/cli/gate.mjs +191 -25
- package/cli/platform.mjs +80 -0
- package/package.json +3 -3
- package/skills/yad-architecture/SKILL.md +15 -3
- package/skills/yad-architecture/references/contract-format.md +4 -1
- package/skills/yad-checks/references/check-gates.md +18 -1
- package/skills/yad-checks/templates/checks/contract-check.sh +46 -3
- package/skills/yad-checks/templates/checks/epic-open.sh +41 -9
- package/skills/yad-checks/templates/checks/lineage-check.sh +38 -9
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +39 -7
- package/skills/yad-checks/templates/checks/spec-link.sh +17 -5
- package/skills/yad-docs/templates/app/package-lock.json +7 -519
- package/skills/yad-epic/references/state-schema.md +13 -0
- package/skills/yad-hub-bridge/references/bridge.md +50 -5
- package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +6 -3
- package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +13 -2
- package/skills/yad-review-gate/SKILL.md +13 -3
- package/skills/yad-review-gate/references/gating.md +7 -3
- package/skills/yad-spec/references/spec-handoff.md +12 -2
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
# - MERGE push (default branch, commit names a review branch): resolve the merged MR's IID from its
|
|
20
20
|
# source branch, then advance + flip status on the default branch — near-immediate.
|
|
21
21
|
# - SCHEDULED sweep (catch-up): enumerate recently-merged review MRs via the API and advance any
|
|
22
|
-
# not yet advanced (idempotent —
|
|
22
|
+
# not yet advanced (idempotent — an already-`done` step is never re-advanced, and re-syncing it
|
|
23
|
+
# writes nothing unless its approvals changed). Covers bare approvals and
|
|
23
24
|
# squash merges whose commit message dropped the branch name. Create a pipeline schedule
|
|
24
25
|
# (one-time, cannot be committed as code): cron `*/15 * * * *` with variable SDLC_GATE_SYNC=true.
|
|
25
26
|
# UI: CI/CD > Schedules, or:
|
|
@@ -39,6 +40,15 @@ variables:
|
|
|
39
40
|
|
|
40
41
|
yad-gate-sync:
|
|
41
42
|
needs: []
|
|
43
|
+
# Serialize every advance: the merge-push pipeline and the scheduled sweep both commit the ledger
|
|
44
|
+
# and push the DEFAULT branch, and the sweep's 7-day window covers MRs the merge path is still
|
|
45
|
+
# processing — so without this they race on the same MR. Both produce the advance, one pushes, the
|
|
46
|
+
# loser rebases onto it and pushes the same commit message under a different SHA. That is where the
|
|
47
|
+
# duplicate `chore(gate): advance …` commits with shifting SHAs come from, and every one of them
|
|
48
|
+
# carries [skip ci] and lands directly on the default branch, so nothing validates the churn.
|
|
49
|
+
# `resource_group` is GitLab's equivalent of the `concurrency` group the GitHub workflow sets on both
|
|
50
|
+
# of its jobs: at most one instance runs at a time, the rest queue.
|
|
51
|
+
resource_group: yad-gate-mergesync
|
|
42
52
|
# Runner selection: set the YAD_RUNNER_TAGS CI/CD variable (e.g. `dind_runner`) to route this
|
|
43
53
|
# docker job on instances whose runners are tag-locked (run_untagged: false) — it lives in
|
|
44
54
|
# project Settings → CI/CD → Variables, so it survives every `yad` sync. When unset, current
|
|
@@ -69,7 +79,8 @@ yad-gate-sync:
|
|
|
69
79
|
rc=0
|
|
70
80
|
if [ "$CI_PIPELINE_SOURCE" = "schedule" ]; then
|
|
71
81
|
# SCHEDULED SWEEP — discover merged review MRs from the platform (Path B keeps no per-branch
|
|
72
|
-
# ledger), then advance each. `gate ci` is idempotent:
|
|
82
|
+
# ledger), then advance each. `gate ci` is idempotent: an already-`done` step is never
|
|
83
|
+
# re-advanced, and re-syncing it writes nothing unless its approvals changed.
|
|
73
84
|
# A stuck review MR (a squash merge whose commit dropped the branch name, or a failed merge
|
|
74
85
|
# push) is always RECENT, so sweep a generous recent window and PAGINATE it fully (--paginate)
|
|
75
86
|
# — this bounds cost without the old hard 50-row cap that could permanently strand older MRs.
|
|
@@ -131,8 +131,9 @@ via the local user's `gh`/`glab`. For each:
|
|
|
131
131
|
- an `APPROVED` review / MR approval → append an `approved` record to `approvals.json` tagged
|
|
132
132
|
`"source": "bridge"`; a `COMMENTED`/`CHANGES_REQUESTED`/note → write to
|
|
133
133
|
`reviews/<artifact-base>--<YYYY-MM-DD>--comments.md` + `comments.json` (never an approval).
|
|
134
|
-
**Idempotent:** upsert bridge approvals by `(step, approver, role, domain)`, supersede revoked ones
|
|
135
|
-
|
|
134
|
+
**Idempotent:** upsert bridge approvals by `(step, approver, role, domain)`, supersede revoked ones
|
|
135
|
+
**while the step is open** (a step already `done` keeps its approvals — they are the record of why it
|
|
136
|
+
passed), and key comments on the platform comment id (re-running `sync` does not duplicate). **Manual approvals (no
|
|
136
137
|
`source` tag) are never touched.** For the architecture+contract step, discard bridge approvals dated
|
|
137
138
|
before a new contract lock (re-lock invalidates platform approvals too). Then refresh the `approved.md`
|
|
138
139
|
roster, set `hub-prs.json` `lastSyncedAt`, and **re-evaluate Step 3**. Under the PR-driven CLI (`yad
|
|
@@ -170,7 +171,9 @@ If the predicate **passes**:
|
|
|
170
171
|
|
|
171
172
|
### PR-driven automation (the `yad gate` CLI)
|
|
172
173
|
When the hub has a platform, **CI is the sole writer of the ledger**. `yad gate open` opens the review
|
|
173
|
-
PR only
|
|
174
|
+
PR only — against the `review/<epic>/<artifact>` branch, which must already exist (create it and run
|
|
175
|
+
`yad open-pr` from it, which pushes it first). CI (`yad gate ci`) writes the `.sdlc/` + `reviews/`
|
|
176
|
+
records this skill describes. The skill's
|
|
174
177
|
job is the human half: presenting the artifact, helping the owner address comments, and narrating the
|
|
175
178
|
gate. Local `yad gate sync` is advisory in bridge mode (reads the platform, prints status, writes
|
|
176
179
|
nothing); a human must never commit gate-state files (the `ledger-guard` check rejects it).
|
|
@@ -179,6 +182,13 @@ Under that CLI the gate **advances on merge**: a review PR/MR whose reviewer rul
|
|
|
179
182
|
comment threads are **all resolved**, and which has been **merged** auto-marks the step `done` and
|
|
180
183
|
unblocks the next step. (Until those three hold, the step stays `in_review`.)
|
|
181
184
|
|
|
185
|
+
**Re-reviewing a step that already advanced.** A step is advanced **once** — the chain is never pulled
|
|
186
|
+
backward. But a step that is `done` is still *synced*: when the artifact is edited (for architecture, a
|
|
187
|
+
re-locked contract surface) its prior approvals go stale, and the approvals arriving on the new review
|
|
188
|
+
PR/MR are recorded and bound to the new content. `yad gate status` then shows the truth — how many
|
|
189
|
+
approvals are live against what is in the file today, and how many were revoked — instead of a `done`
|
|
190
|
+
step whose approvals all belong to the version before the edit.
|
|
191
|
+
|
|
182
192
|
The flow is **merge-driven** (wired by `yad-hub-bridge` `wire`): during review CI writes nothing — the
|
|
183
193
|
platform PR/MR is the source of truth (native approvals + threads), and CI never touches the review
|
|
184
194
|
branch (so an in-flight approval is never dismissed and required checks never strand). On the human
|
|
@@ -42,7 +42,9 @@ content. This prevents "approve, then quietly change it" (build plan §5 spirit)
|
|
|
42
42
|
For the architecture+contract review there is a second, content-based staleness check: recompute the
|
|
43
43
|
SHA-256 of the contract-surface block and compare it to `.sdlc/contract-lock.json`. A mismatch means
|
|
44
44
|
the locked surface changed even if the file's mtime looks fine — approvals are stale, re-lock and
|
|
45
|
-
re-approve. (Hash recipe: `yad-architecture/references/contract-format.md`.)
|
|
45
|
+
re-approve. (Hash recipe: `yad-architecture/references/contract-format.md`.) `yad` computes the
|
|
46
|
+
identical digest, so `yad doctor` performs this comparison for every epic and FAILs on a surface that
|
|
47
|
+
drifted from its lock — run it rather than recomputing by hand.
|
|
46
48
|
|
|
47
49
|
## Worked example — epic gate
|
|
48
50
|
|
|
@@ -91,8 +93,10 @@ approvals regardless of how they were recorded.
|
|
|
91
93
|
- On PR/MR open the assignee is the committer and reviewers are the scope's `reviewer` + `domain-owner`
|
|
92
94
|
members (minus the committer); the owner/author is recorded, not requested. See
|
|
93
95
|
`../yad-hub-bridge/references/login-roster.md`.
|
|
94
|
-
- `sync` is idempotent (upsert by `(step, approver, role, domain)`;
|
|
95
|
-
|
|
96
|
+
- `sync` is idempotent (upsert by `(step, approver, role, domain)`; key comments on comment id) and
|
|
97
|
+
never touches **manual** approvals. A revoked approval is superseded **while the step is open**; once
|
|
98
|
+
the step is `done` its approvals are kept as the record of why it passed, and a re-sync only re-binds
|
|
99
|
+
new ones (see `../yad-hub-bridge/references/bridge.md` → "Idempotent re-sync").
|
|
96
100
|
- The architecture+contract staleness rule applies to bridge approvals too: a re-lock discards bridge
|
|
97
101
|
approvals dated before the new lock.
|
|
98
102
|
- No platform / no CLI → the gate runs file-only with no error. Detail: `../yad-hub-bridge/references/bridge.md`.
|
|
@@ -68,7 +68,7 @@ story: EP-<slug>-S0N
|
|
|
68
68
|
epic: EP-<slug>
|
|
69
69
|
repo: <repo>
|
|
70
70
|
feature-id: EP-<slug>-S0N
|
|
71
|
-
product-repo: <absolute or relative
|
|
71
|
+
product-repo: <path to the product repo — absolute, or relative to THIS file's dir (specs/<story>/)>
|
|
72
72
|
contract-lock: sha256:<hex copied from epics/EP-<slug>/.sdlc/contract-lock.json>
|
|
73
73
|
speckit: installed | not-installed
|
|
74
74
|
generated: <YYYY-MM-DD>
|
|
@@ -86,6 +86,16 @@ The contract surface above is **referenced, not re-defined**. Any change to the
|
|
|
86
86
|
back to the architecture gate in the product repo — it is never widened from this code repo.
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
`product-repo` is the one field CI resolves on disk (contract-check, lineage-check, epic-open,
|
|
90
|
+
reconcile-debt all read it). Every gate resolves it the SAME way: an **absolute** path is used as-is; a
|
|
91
|
+
**relative** path is joined to this `link.md`'s own directory, `specs/<story>/` — `../../` climbs out
|
|
92
|
+
of the story dir and out of `specs/` to the code-repo root, so a hub checked out beside the code repo
|
|
93
|
+
is `../../../<hub-dir>`. A relative value that only resolves from the **repo root** still works too
|
|
94
|
+
(what contract-check historically did), so older `link.md` files keep gating. Write it as an
|
|
95
|
+
**unquoted scalar** — `"..."` or `'...'` is taken literally, and a leading `~`/`$VAR` is never
|
|
96
|
+
expanded. An unreachable path degrades the hub-reading gates to a PASS-with-note rather than failing
|
|
97
|
+
them, and each one now says so in its output.
|
|
98
|
+
|
|
89
99
|
## Do not re-invent the contract
|
|
90
100
|
|
|
91
101
|
The spec **quotes** the locked surface; it never extends it. To confirm the surface the spec relies on
|
|
@@ -93,7 +103,7 @@ matches the lock, run from the **product** repo:
|
|
|
93
103
|
|
|
94
104
|
```bash
|
|
95
105
|
awk '/CONTRACT-SURFACE:BEGIN/{f=1;next} /CONTRACT-SURFACE:END/{f=0} f' \
|
|
96
|
-
epics/EP-<slug>/contract.md | shasum -a 256
|
|
106
|
+
epics/EP-<slug>/contract.md | tr -d '\r' | shasum -a 256
|
|
97
107
|
# compare against epics/EP-<slug>/.sdlc/contract-lock.json
|
|
98
108
|
```
|
|
99
109
|
|