yadflow 4.0.0-next.1 → 4.0.0-next.3
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 +54 -0
- package/README.md +85 -0
- package/bin/commands.mjs +5 -0
- package/bin/yad.mjs +80 -4
- package/cli/capture.mjs +417 -0
- package/cli/claims.mjs +165 -0
- package/cli/doctor.mjs +218 -1
- package/cli/epic-state.mjs +9 -6
- package/cli/fold.mjs +247 -0
- package/cli/gate-local.mjs +296 -0
- package/cli/hubcommit.mjs +3 -2
- package/cli/manifest.mjs +48 -0
- package/cli/next.mjs +24 -4
- package/cli/openpr.mjs +7 -0
- package/cli/owners.mjs +266 -0
- package/cli/people.mjs +4 -1
- package/cli/plan.mjs +83 -8
- package/cli/reconcile.mjs +3 -1
- package/cli/report.mjs +4 -0
- package/cli/setup.mjs +9 -1
- package/package.json +1 -1
- package/skills/yad-analysis/SKILL.md +4 -2
- package/skills/yad-architecture/SKILL.md +3 -2
- package/skills/yad-backfill/references/backfill.md +7 -0
- package/skills/yad-backfill/templates/checks/backfill-check.sh +10 -2
- package/skills/yad-change/SKILL.md +3 -1
- package/skills/yad-checks/SKILL.md +7 -0
- package/skills/yad-checks/references/check-gates.md +53 -7
- package/skills/yad-checks/templates/checks/contract-check.sh +75 -10
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +2 -2
- package/skills/yad-checks/templates/github/yad-update-guard.yml +4 -1
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +2 -2
- package/skills/yad-checks/templates/hooks/yad-capture.sh +53 -0
- package/skills/yad-discovery/SKILL.md +2 -1
- package/skills/yad-epic/SKILL.md +5 -2
- package/skills/yad-epic/references/state-schema.md +12 -5
- package/skills/yad-hub-bridge/SKILL.md +2 -1
- package/skills/yad-pr-template/templates/checks/pr-template.sh +10 -1
- package/skills/yad-pr-template/templates/checks/pr-title.sh +10 -1
- package/skills/yad-review-gate/SKILL.md +76 -85
- package/skills/yad-review-gate/references/gating.md +7 -5
- package/skills/yad-status/SKILL.md +7 -0
- package/skills/yad-stories/SKILL.md +3 -2
- package/skills/yad-test-cases/SKILL.md +3 -2
- package/skills/yad-ui/SKILL.md +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
+
# [4.0.0-next.3](https://github.com/abdelrahmannasr/yadflow/compare/v4.0.0-next.2...v4.0.0-next.3) (2026-09-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **test:** strip the publisher's git identity from the whole test process ([8660f56](https://github.com/abdelrahmannasr/yadflow/commit/8660f5611f1c9f24a67eeead4302c4acca260ba3))
|
|
7
|
+
|
|
8
|
+
# [4.0.0-next.2](https://github.com/abdelrahmannasr/yadflow/compare/v4.0.0-next.1...v4.0.0-next.2) (2026-09-26)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **capture:** continue origin's branch when there is no local one; name a refused push (E43 review 2) ([8e2c07d](https://github.com/abdelrahmannasr/yadflow/commit/8e2c07d32d1b6882c7b543b9b3c65f96ca67e919))
|
|
14
|
+
* **capture:** every git call takes the environment runCapture was given (E43) ([c347c94](https://github.com/abdelrahmannasr/yadflow/commit/c347c9460154e6b30472a3a1aae2869354a6fce4))
|
|
15
|
+
* **capture:** prune deleted capture branches; read [a-z] and list brackets as GitHub does (E43 review 3) ([2322dcb](https://github.com/abdelrahmannasr/yadflow/commit/2322dcb0321ff02f0ebf22ecfe61b2979236944d))
|
|
16
|
+
* **capture:** subfolder Products, staged-then-deleted files, a plain push, and a truer workflow scan (E43 review) ([14f0eeb](https://github.com/abdelrahmannasr/yadflow/commit/14f0eeb6e5fe9ce21bdfe5fb168ace88a41d7ad6))
|
|
17
|
+
* **capture:** take design-links.json and test-links.json with the artifacts (E44) ([ad4f2cf](https://github.com/abdelrahmannasr/yadflow/commit/ad4f2cfc1f134c3b17616348081795a09939049a))
|
|
18
|
+
* **checks:** a C-quoted path is an artifact change; warn on a rename-blind workflow (E47 review 4) ([e6af8df](https://github.com/abdelrahmannasr/yadflow/commit/e6af8df0f4dafd3ec593da44114b07c45b67f54a))
|
|
19
|
+
* **checks:** E114 review 1 — one odd name per test, split lines, full hint ([df9f4dc](https://github.com/abdelrahmannasr/yadflow/commit/df9f4dcaf5736bfbd93a49c017ffae257e2b4ef6))
|
|
20
|
+
* **checks:** fold the long s into specs; say a file is a file (E115 review 2) ([373ba16](https://github.com/abdelrahmannasr/yadflow/commit/373ba16c81bc3c0eb5c56bb7adae48441b4f07a8))
|
|
21
|
+
* **checks:** let a PR of step owner files alone through on a Product (E47 review 2) ([f7ff5b0](https://github.com/abdelrahmannasr/yadflow/commit/f7ff5b062a51b455a18190a31ccbd5488fe5785f))
|
|
22
|
+
* **checks:** list a rename by both paths in contract-check and backfill-check (E114) ([09bb4de](https://github.com/abdelrahmannasr/yadflow/commit/09bb4de3f36e0fec0eb247565fc2364a46b482b8))
|
|
23
|
+
* **checks:** list renames and unquoted paths in the hub-checks diff (E47 review 3) ([bc270e0](https://github.com/abdelrahmannasr/yadflow/commit/bc270e0583a7583ffccea94b9ecaa1a11498ec46))
|
|
24
|
+
* **checks:** read specs/ without case; name a failed tree read (E115 review 1) ([a9fb725](https://github.com/abdelrahmannasr/yadflow/commit/a9fb72599a5980e9f2e8307cb87eca9ae31f17ca))
|
|
25
|
+
* **checks:** read the changed list as bytes; check each workflow line (E47 review 5) ([edc850b](https://github.com/abdelrahmannasr/yadflow/commit/edc850b20495a13d376b6f01dc02db61658d4e27))
|
|
26
|
+
* **checks:** refuse a second spelling of contracts/ or a story folder (E115 review 3) ([debc7b6](https://github.com/abdelrahmannasr/yadflow/commit/debc7b68f8bbddf5634322228f633327aba8c50a))
|
|
27
|
+
* **checks:** refuse a symlink or submodule under specs/ in contract-check (E115) ([e93387a](https://github.com/abdelrahmannasr/yadflow/commit/e93387aa036cc40b9d6741291a1ef518a0d03e1b))
|
|
28
|
+
* **checks:** say what the story-spelling rule does not catch (E115 review 4) ([3d36c93](https://github.com/abdelrahmannasr/yadflow/commit/3d36c934406b148c3601c5a6bfdacd40abccbad3))
|
|
29
|
+
* **claims:** find the first capture's base in one git call, with no cap (E46 review 2) ([e4a6fcf](https://github.com/abdelrahmannasr/yadflow/commit/e4a6fcf2a33acba0c4b76b5ba54e99d740dcd1d7))
|
|
30
|
+
* **claims:** keep the once-an-hour memory across a push; own edits only; over-report a missing base (E46 review 1) ([7ab038d](https://github.com/abdelrahmannasr/yadflow/commit/7ab038d436fc9d46009fd31139bd7327083ebac3))
|
|
31
|
+
* **claims:** pin the first-capture search against the person's git config (E46 review 3) ([80db192](https://github.com/abdelrahmannasr/yadflow/commit/80db1926695f60884a47b6a72b5a79f634ac5519))
|
|
32
|
+
* **claims:** read the Yad-Base trailer with a pinned separator (E46 review 4) ([c026fef](https://github.com/abdelrahmannasr/yadflow/commit/c026fef2454f6a5799b6e0f8fe5a04ee6cbbad62))
|
|
33
|
+
* **doctor:** read a gate one bash command at a time (E114 review 2) ([06d3f69](https://github.com/abdelrahmannasr/yadflow/commit/06d3f69113e3b19f3b9cee1cf77a637999372775))
|
|
34
|
+
* **doctor:** satisfy eslint — split on ** instead of a NUL placeholder; no useless assignment (E43) ([aad5822](https://github.com/abdelrahmannasr/yadflow/commit/aad582240fa0b8f4a279b0221100fba967ced20d))
|
|
35
|
+
* **fold:** fold a staged deletion; refuse mid-merge and on a detached HEAD (E44 review 1) ([4391b1b](https://github.com/abdelrahmannasr/yadflow/commit/4391b1b1eb6d22990b672a0d3f64b5fb5e77c5ec))
|
|
36
|
+
* **fold:** name a case-only rename only when git shows the new spelling (E44 review 4) ([2a6395c](https://github.com/abdelrahmannasr/yadflow/commit/2a6395c7f9ae7c7d9ee8bf1877e4cb770b2197c0))
|
|
37
|
+
* **fold:** never run git add with an empty list; refuse a git rm --cached path (E44 review 2) ([cfbaefc](https://github.com/abdelrahmannasr/yadflow/commit/cfbaefc9d8334a20ddbd585b9c2c25a0d31992f7))
|
|
38
|
+
* **fold:** tell a case-only rename from git rm --cached (E44 review 3) ([a374fc3](https://github.com/abdelrahmannasr/yadflow/commit/a374fc314f266354cdf96200b54dfa631423d4c2))
|
|
39
|
+
* **fold:** the Product level is seeded under either spelling; truer words (E44 review 6) ([c711326](https://github.com/abdelrahmannasr/yadflow/commit/c71132643b8c3ca1fb1d0b5c3a4739d0b18209dd))
|
|
40
|
+
* **fold:** the verified seed rule asks ledger-guard's own question; a change-epic's seed rides along (E44) ([f0ee36d](https://github.com/abdelrahmannasr/yadflow/commit/f0ee36d60bad06ac0920b64d97bf16e9f19a7a06))
|
|
41
|
+
* **gate:** a round keeps the fingerprint it opened with; one spelling across approvals and comments (E112 review 2) ([d7dbe57](https://github.com/abdelrahmannasr/yadflow/commit/d7dbe57d507f5c798236715e55b70ef42f273dbe))
|
|
42
|
+
* **gate:** approve keeps the review record; one spelling per person; a round is one version (E112 review) ([6f44c18](https://github.com/abdelrahmannasr/yadflow/commit/6f44c1887ae2c846bdcd1c14cf276624a2be91b6))
|
|
43
|
+
* **owners:** never fold an owner file; name off-chain files in doctor (E47 review 1) ([f23f606](https://github.com/abdelrahmannasr/yadflow/commit/f23f606f220aef583500c6f2a632ee8927307b6c))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Features
|
|
47
|
+
|
|
48
|
+
* **capture:** yad capture snapshots Shape artifacts onto private yad/wip branches (E43) ([58b0181](https://github.com/abdelrahmannasr/yadflow/commit/58b0181f72fd88e97d391e0e52a46e8b5cdf1183))
|
|
49
|
+
* **claims:** yad claims — who else is editing which artifact, read from the capture branches (E46) ([6668441](https://github.com/abdelrahmannasr/yadflow/commit/66684416b5d3c1a2a04b069cd35b0d7f13e55ddb))
|
|
50
|
+
* **doctor:** warn when a step owner file does nothing (E47) ([b189abd](https://github.com/abdelrahmannasr/yadflow/commit/b189abdbc94aa0a36041d501b9cd09c5577d5a62))
|
|
51
|
+
* **fold:** yad fold <epic> <step> — one clean commit per authoring step (E44) ([a4a5285](https://github.com/abdelrahmannasr/yadflow/commit/a4a52857c2536b83d954e6a313863ecb16ecf01d))
|
|
52
|
+
* **gate:** yad gate approve, comment and advance for a Product with no platform (E112) ([50f7618](https://github.com/abdelrahmannasr/yadflow/commit/50f7618aae85fb269578055e5e12d33e58643fab))
|
|
53
|
+
* **owners:** assign an authoring step to one person (E47) ([f9f9bda](https://github.com/abdelrahmannasr/yadflow/commit/f9f9bdaf31206d8ac4c7d46b4d512f23b1e816d2))
|
|
54
|
+
|
|
1
55
|
# [4.0.0-next.1](https://github.com/abdelrahmannasr/yadflow/compare/v3.19.0-next.2...v4.0.0-next.1) (2026-09-24)
|
|
2
56
|
|
|
3
57
|
|
package/README.md
CHANGED
|
@@ -96,6 +96,10 @@ In one pass it produces:
|
|
|
96
96
|
agent the CI-owned gate-ledger write at the moment it tries it and names the command that owns the
|
|
97
97
|
transition, instead of letting it surface as a CI failure twenty minutes later. Harness-agnostic
|
|
98
98
|
(stdin payload, exit 0 allows / 2 denies) and fails open — the CI gate stays the authority.
|
|
99
|
+
- **Background capture** on every Product, in both ledger modes (E43) — `hooks/yad-capture.sh`, a
|
|
100
|
+
harness hook that runs `yad capture` after each agent edit. It snapshots every changed Shape artifact
|
|
101
|
+
onto your private `yad/wip/<you>/<epic>` branches, so a draft is never lost and nobody types a git
|
|
102
|
+
command. See [Background capture](#background-capture) below.
|
|
99
103
|
- **PR/MR templates** and an opt-in CodeRabbit config.
|
|
100
104
|
|
|
101
105
|
Your first `yad-epic` seeds the `epics/EP-<slug>/` ledger — state, approvals, and the contract lock —
|
|
@@ -129,6 +133,87 @@ has an INSTALL in one of them — a `skills/` folder, or an armed hook entry —
|
|
|
129
133
|
instead; a `.cursor/` holding only Cursor rules is not an install, and is offered the default. Checked against each agent's
|
|
130
134
|
own documentation on 2026-09-16; `yad doctor` prints the same table's verdict for your project.
|
|
131
135
|
|
|
136
|
+
### Background capture
|
|
137
|
+
|
|
138
|
+
`yad capture` saves your work in progress without touching your checkout (E43). It takes every changed
|
|
139
|
+
file under `epics/` and `foundation/` — except the ledger (any `.sdlc/` folder and `reviews/`; the four
|
|
140
|
+
files a person or a skill writes there, `contract-lock.json`, `change.json`, `design-links.json` and
|
|
141
|
+
`test-links.json`, are included) — and commits it onto
|
|
142
|
+
a private branch per epic, `yad/wip/<your git name>/<epic>`. It uses git's low-level commands in a
|
|
143
|
+
throwaway index, so your branch, your staged changes and your files stay exactly as they were, and no
|
|
144
|
+
git hook runs.
|
|
145
|
+
|
|
146
|
+
| What | How |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| When | After every agent edit, by a hook yad wires in both ledger modes: Claude Code `PostToolUse` in `.claude/settings.json`, Cursor `afterFileEdit` in `.cursor/hooks.json`. Any other agent or editor: run `yad capture` yourself. Each capture takes every changed artifact, so your own editor's edits ride along |
|
|
149
|
+
| Push | A capture commits at once, locally. The hook pushes your capture branches (and fetches everyone else's, for claims) in the background at most once every 5 minutes, with no password prompt and a short timeout, so an edit never waits on the network. `yad capture` by hand pushes straight away. With no remote, or offline, the captures stay local and nothing fails |
|
|
150
|
+
| CI | yadflow's own push workflow skips `yad/wip/**`. `yad doctor` names any of **your** workflows a push to `yad/wip/*` would start (no branch filter, `branches: ["**"]`, or a `branches-ignore` that does not name it), so you can add `branches-ignore: ["yad/wip/**"]` |
|
|
151
|
+
| Off | `"capture": false` in the Product config (`yad check --fix` then removes the hook), or `YAD_CAPTURE=0` for one shell |
|
|
152
|
+
|
|
153
|
+
The capture commits are unsigned on purpose — a signing prompt inside a hook would hang the agent — and
|
|
154
|
+
each carries `Yad-Epic`, `Yad-Base` and `Yad-Branch` lines. They are drafts: the commit your history
|
|
155
|
+
keeps is the fold, below. The push is never forced: if you capture the same epic on two machines, the second push is refused rather than overwriting the first, and a `yad capture` you run by hand says so and names the branch (delete one copy with `git branch -D` to continue the other) — the hook's background push cannot report it. **To remove a capture branch for good** — say a secret was captured — delete it on origin first, then on every machine that has it run `git branch -D yad/wip/<you>/<epic>` **and** `git branch -dr origin/yad/wip/<you>/<epic>` (the second deletes that machine's saved copy of origin's branch; it says so if there is none): a machine that still holds either one rebuilds the branch and pushes it back. On a fresh clone, a capture continues the branch already on origin. Two people with the same git name share branches. The people count that caps review gates
|
|
156
|
+
(E71) does not count capture commits.
|
|
157
|
+
|
|
158
|
+
### Who else is editing a file (claims)
|
|
159
|
+
|
|
160
|
+
`yad claims` lists who else is editing which artifact right now (E46). It is **advice, not a lock**:
|
|
161
|
+
nothing stops anyone, because with no server a real lock is impossible. There is nothing to record by
|
|
162
|
+
hand — a claim is read from the capture branches: someone's `yad/wip/<name>/<epic>` branch holds a
|
|
163
|
+
saved change to that file that is not on the default branch yet.
|
|
164
|
+
|
|
165
|
+
| What | How |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| A claim | A file of the epic that differs between the capture branch's last save and the commit that save was built on — the person's own edits, never what a pull brought in |
|
|
168
|
+
| Ends | 4 hours after that branch's last save, or at once when the file on the default branch matches the saved one (the work was merged) |
|
|
169
|
+
| `yad claims [<epic>]` | Fetches everyone's capture branches first (short timeout, no prompts), then lists each claim: file, person, last save. `--no-fetch` reads what is already here; `--json` too |
|
|
170
|
+
| At edit time | When an edit changes a file someone else has a claim on, the capture hook says so. Under Claude Code the agent reads it (and you see it); under Cursor it is a line on stderr. It never blocks, never waits on the network, and names the same person and file at most once an hour |
|
|
171
|
+
| Fresh enough | The hook's background push (at most every 5 minutes) also fetches everyone's capture branches in the background |
|
|
172
|
+
|
|
173
|
+
**Limits.** Someone with capture off, or offline, is invisible. A claim can be up to about 5 minutes
|
|
174
|
+
late. Two people with the same git name share capture branches, so they never see each other. Times
|
|
175
|
+
are the saver's own clock, shown in UTC.
|
|
176
|
+
|
|
177
|
+
### Who owns a step (assign)
|
|
178
|
+
|
|
179
|
+
`yad assign` gives one authoring step to one person (E47) — for example "Bob writes the
|
|
180
|
+
architecture". It is stronger advice than a claim, because it is decided in advance, but it is still
|
|
181
|
+
**advice, not a lock**: it never blocks an edit, never holds a review gate and never stops a fold.
|
|
182
|
+
|
|
183
|
+
| What | How |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `yad assign <epic> <step> [--to <name>]` | Assigns the step to you, or to the person whose **git name** you give (the name they commit with). Only authoring steps that write an artifact (`epic`, `architecture`, `stories`, …); review steps are not assigned, because the platform and the gate count decide who approves. If someone else already owns the step it is refused and names them; `--force` replaces them |
|
|
186
|
+
| `yad unassign <epic> <step>` | Removes the assignment. Your own freely; someone else's needs `--force` |
|
|
187
|
+
| `yad owners [<epic>]` | Lists every assignment, whether it is live, and any owner file that cannot be read. `--json` too |
|
|
188
|
+
| Where it is kept | One small file per step: `epics/<epic>/.sdlc/owners/<step>.json` (`foundation/.sdlc/owners/` for the Foundation). It is not a ledger file, so you can write it in both ledger modes. `yad assign` does not commit it, and `yad fold` never takes it (an assignment is not authoring): commit it on its own — on a `verified` Product, in a PR of its own (the Product checks let a PR that changes only owner files through, since E47; run `yad update` so your copies of the checks and of the hub-checks workflow have that rule; `yad doctor` warns `owners:rename-blind` if a hand-edited workflow was kept) — so the team sees it when they pull. It is captured to your `yad/wip/…` branch like any artifact, but it is not counted as a claim |
|
|
189
|
+
| Live | While the step's work is open: the step is not finished, or its review has not passed yet. After that the file stays as a record; re-opening the step makes it live again. There is no time limit |
|
|
190
|
+
| Shown | `yad next` prints `owner: <name>` under the step (and under its review) while it is live |
|
|
191
|
+
| At edit time | When your edit changes a file of a step someone else owns, the capture hook says so — to the agent under Claude Code, on stderr under Cursor — at most once an hour per step. One note carries both this and any claim warning |
|
|
192
|
+
|
|
193
|
+
**Limits.** The name is the git name, made branch-safe the way capture branch names are — so it matches
|
|
194
|
+
the capture hook without asking GitHub or GitLab. Two people with the same git name look like one owner.
|
|
195
|
+
A git name with no Latin letters is branch-safe only through the person's email, so someone else can
|
|
196
|
+
assign it only once that person has a capture branch here (`--to` finds it by the git name the branch was
|
|
197
|
+
saved under). With capture off, there is no edit-time warning.
|
|
198
|
+
|
|
199
|
+
### Folding a step into one commit
|
|
200
|
+
|
|
201
|
+
`yad fold <epic> <step>` ends an authoring step (E44). It makes the one commit the record keeps,
|
|
202
|
+
`docs(<epic>): author <step>` — for example `docs(EP-checkout): author architecture`. The authoring
|
|
203
|
+
skills run it at the end of their step; you can run it yourself too.
|
|
204
|
+
|
|
205
|
+
| What | How |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| Files | Only that step's files: the ones its review covers (architecture is `architecture.md`, `contract.md` and `.sdlc/contract-lock.json`; stories is everything under `stories/`), plus what the step's skill writes beside them (`DESIGN.md` and `.sdlc/design-links.json` for the UI step, `.sdlc/test-links.json` for test cases). Other changed files of the epic are named and left on disk, so a half-written draft of another step never lands in this commit |
|
|
208
|
+
| The ledger | Follows `ledger` mode. `local`: the epic's changed ledger files go in the same commit — one step, one commit. `verified`: CI writes the ledger at merge, so the fold leaves those files for CI — except while the epic is brand new (no `.sdlc/state.json` in HEAD yet): then its new ledger files are its seed, which is the case `ledger-guard` allows and the only way a seed reaches the default branch. Once the epic is seeded, even a NEW ledger file (a `reviews/*.md` written by hand) is left for CI, because the guard would reject it |
|
|
209
|
+
| The seed | While the epic is brand new, the first fold also takes the new person-written `.sdlc/` files beside the ledger — a change-epic's `change.json` and its pointer `contract-lock.json` — in both modes |
|
|
210
|
+
| The commit | A normal `git commit`, so it is signed if your git signs (with `ledger: verified` it warns when signing is off: the review PR's signature check would fail), and your commit hooks run. Only the step's files are committed; anything else you staged stays staged. It ends with `Yad-Epic`, `Yad-Step` and `Yad-Folded` lines — the last is the tip of your capture branch, the drafts this commit folds (`none` with capture off) |
|
|
211
|
+
| Where | With `ledger: verified`, never on the default branch — artifacts reach it only through the review PR. With `ledger: local`, anywhere |
|
|
212
|
+
| The drafts | Your `yad/wip/<you>/<epic>` branch is left as it is: the draft history stays for the later rework measurement (E95), and nothing has to be deleted on other machines. The next capture has nothing new to save |
|
|
213
|
+
|
|
214
|
+
A step with no changed files has nothing to fold, and says so. `yad open-pr` on a review branch warns
|
|
215
|
+
when that step's files have changes no fold has committed — they are not in the review.
|
|
216
|
+
|
|
132
217
|
### The local ledger guard, per agent
|
|
133
218
|
|
|
134
219
|
In **verified mode** — where CI owns the gate ledger — yadflow installs a local guardrail that stops
|
package/bin/commands.mjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
export { runSetup } from '../cli/setup.mjs';
|
|
14
14
|
export { reconcile } from '../cli/reconcile.mjs';
|
|
15
15
|
export { gateOpen, gateSync, gateComments, gateStatus, gateCi, gateReview, gateTrailer, gateWalkthrough, gateRepair } from '../cli/gate.mjs';
|
|
16
|
+
export { gateApprove, gateComment, gateAdvance } from '../cli/gate-local.mjs';
|
|
16
17
|
export { isValidEpicId, seedableProfiles } from '../cli/epic-state.mjs';
|
|
17
18
|
export { runEpicNew, runFoundationNew, runFoundationStatus } from '../cli/epic.mjs';
|
|
18
19
|
export { runSkillBind, runSkillList, runSkillUnbind } from '../cli/skill.mjs';
|
|
@@ -22,6 +23,10 @@ export { reviewTrailer, reviewContext, reviewNudge, reviewReconcile, reviewWalkt
|
|
|
22
23
|
export { runShip } from '../cli/ship.mjs';
|
|
23
24
|
export { runCheckpoint } from '../cli/checkpoint.mjs';
|
|
24
25
|
export { runTidy } from '../cli/tidy.mjs';
|
|
26
|
+
export { runCapture } from '../cli/capture.mjs';
|
|
27
|
+
export { runFold } from '../cli/fold.mjs';
|
|
28
|
+
export { runClaims } from '../cli/claims.mjs';
|
|
29
|
+
export { runAssign, runUnassign, runOwners } from '../cli/owners.mjs';
|
|
25
30
|
export { runIndex } from '../cli/index-command.mjs';
|
|
26
31
|
export { runHistory, HISTORY_FLAGS } from '../cli/history.mjs';
|
|
27
32
|
export { runRepo } from '../cli/repo.mjs';
|
package/bin/yad.mjs
CHANGED
|
@@ -151,6 +151,12 @@ ${c.bold('Review gate (Shape)')}
|
|
|
151
151
|
in verified mode — there, recover with 'yad gate ci' below
|
|
152
152
|
yad gate comments <epic> [artifact] Fetch unresolved review comments to address
|
|
153
153
|
yad gate status <epic> Show each review step + approvals
|
|
154
|
+
yad gate approve <epic> <artifact> --by <name> [--engagement verified|none]
|
|
155
|
+
No platform only: record an approval, bound to the artifact's
|
|
156
|
+
content (an edit revokes it). Reports the verdict; never advances
|
|
157
|
+
yad gate comment <epic> <artifact> --by <name> [--count <n>] [--new-round]
|
|
158
|
+
No platform only: record who commented, and how often, this round
|
|
159
|
+
yad gate advance <epic> <artifact> No platform only: pass the gate when its approvals hold
|
|
154
160
|
yad gate repair <epic> [--push] Close an author step stranded behind a passed review gate
|
|
155
161
|
(YAD-STATE-005); --push commits state.json to the default branch
|
|
156
162
|
yad gate review <epic> [artifact] Print the grounding bundle for the review companion
|
|
@@ -181,6 +187,21 @@ ${c.bold('Build helpers')}
|
|
|
181
187
|
(merged before ledger tracking), then carry its status: shipped
|
|
182
188
|
flip in the same commit (--merge-commit <sha>, --task <t> opt.);
|
|
183
189
|
one repo per run — re-run per --repo for a multi-repo story
|
|
190
|
+
yad capture [--no-push] Snapshot every changed Shape artifact onto your private
|
|
191
|
+
yad/wip/<you>/<epic> branches — never your checkout; pushes them
|
|
192
|
+
(a harness hook runs it after each agent edit, with --hook)
|
|
193
|
+
yad claims [<epic>] [--no-fetch] Who else is editing which artifact: read from everyone's
|
|
194
|
+
capture branches (fetched first) — advice, not a lock; a claim
|
|
195
|
+
ends 4 hours after the last save, or once the file is merged
|
|
196
|
+
yad assign <epic> <step> [--to <name>] [--force]
|
|
197
|
+
Assign an authoring step to one person (you, or their git
|
|
198
|
+
name) — advice, not a lock: the capture hook warns anyone
|
|
199
|
+
else who edits its files; --force replaces another owner
|
|
200
|
+
yad unassign <epic> <step> [--force] Remove an assignment (someone else's needs --force)
|
|
201
|
+
yad owners [<epic>] List every step assignment, and whether it is live
|
|
202
|
+
yad fold <epic> <step> End an authoring step: commit ITS artifacts as one commit,
|
|
203
|
+
docs(<epic>): author <step> (signed if git signs) — the drafts
|
|
204
|
+
stay on yad/wip/…; with ledger: local the epic's ledger rides along
|
|
184
205
|
yad tidy up [<epic>] [--push] Fold FINISHED Build shards (a shipped story's
|
|
185
206
|
trust-log/build-log entries) back into the single folded
|
|
186
207
|
ledger, as one chore(hub) commit — the manual "pack it up"
|
|
@@ -264,7 +285,7 @@ ${c.bold('Environment')}
|
|
|
264
285
|
YAD_NO_REPORT=1 Never offer to file a bug report after a failure
|
|
265
286
|
YAD_PLATFORM_LOGIN=0 Name a record's author by git user.name; never ask gh/glab who is logged in`;
|
|
266
287
|
|
|
267
|
-
const VALUE_FLAGS = new Set(['--dir', '--type', '--message', '--task', '--ai', '--risk', '--repo', '--platform', '--base', '--title', '--scope', '--branch', '--pr', '--epic', '--team', '--body', '--out', '--since', '--until', '--member', '--format', '--reason', '--profile', '--parent', '--inherits', '--to', '--retro-ship', '--merge-commit', '--path', '--ide-targets', '--theme', '--thread']);
|
|
288
|
+
const VALUE_FLAGS = new Set(['--dir', '--type', '--message', '--task', '--ai', '--risk', '--repo', '--platform', '--base', '--title', '--scope', '--branch', '--pr', '--epic', '--team', '--body', '--out', '--since', '--until', '--member', '--format', '--reason', '--profile', '--parent', '--inherits', '--to', '--retro-ship', '--merge-commit', '--path', '--ide-targets', '--theme', '--thread', '--by', '--count', '--engagement']);
|
|
268
289
|
|
|
269
290
|
function parseArgs(argv) {
|
|
270
291
|
const o = { _: [], dir: process.cwd(), fix: false, force: false, scope: 'all' };
|
|
@@ -275,6 +296,7 @@ function parseArgs(argv) {
|
|
|
275
296
|
else if (a === '--force') o.force = true;
|
|
276
297
|
else if (a === '--contract-change') o.contractChange = true;
|
|
277
298
|
else if (a === '--no-push') o.noPush = true;
|
|
299
|
+
else if (a === '--no-fetch') o.noFetch = true;
|
|
278
300
|
else if (a === '--push') o.push = true;
|
|
279
301
|
else if (a === '--allow-branch') o.allowBranch = true;
|
|
280
302
|
else if (a === '--overwrite-local') o.overwriteLocal = true;
|
|
@@ -290,6 +312,8 @@ function parseArgs(argv) {
|
|
|
290
312
|
else if (a === '--done') o.done = true;
|
|
291
313
|
else if (a === '--undo') o.undo = true;
|
|
292
314
|
else if (a === '--debt') o.debt = true;
|
|
315
|
+
else if (a === '--new-round') o.newRound = true;
|
|
316
|
+
else if (a === '--hook') o.hook = true;
|
|
293
317
|
else if (a === '--stub') o.stub = true;
|
|
294
318
|
// setup profile flags (pre-answer the Step 0 interview, for CI/scripts)
|
|
295
319
|
else if (a === '--solo') o.solo = true;
|
|
@@ -345,7 +369,7 @@ const ACTIONS = {
|
|
|
345
369
|
epic: { known: ['new'] },
|
|
346
370
|
foundation: { known: ['new', 'status'] },
|
|
347
371
|
skill: { known: ['list', 'bind', 'unbind'], default: 'list' },
|
|
348
|
-
gate: { known: ['open', 'sync', 'comments', 'status', 'repair', 'review', 'walkthrough', 'trailer', 'ci'] },
|
|
372
|
+
gate: { known: ['open', 'sync', 'comments', 'status', 'repair', 'review', 'walkthrough', 'trailer', 'ci', 'approve', 'comment', 'advance'] },
|
|
349
373
|
review: { known: ['trailer', 'context', 'chat', 'cards', 'walkthrough', 'nudge', 'reconcile'] },
|
|
350
374
|
tidy: { known: ['up'] },
|
|
351
375
|
history: { known: ['list', 'show', 'search'], default: 'list' },
|
|
@@ -403,6 +427,15 @@ async function main() {
|
|
|
403
427
|
return;
|
|
404
428
|
}
|
|
405
429
|
|
|
430
|
+
// The same hot path for the post-edit capture hook (E43): it runs after every agent edit, never fails,
|
|
431
|
+
// and needs `cli/capture.mjs` alone.
|
|
432
|
+
if (cmd === 'capture' && o.hook) {
|
|
433
|
+
const { runCapture } = await import('../cli/capture.mjs');
|
|
434
|
+
try { await runCapture(o.dir, { hook: true, noPush: !!o.noPush, format: o.format || null }); } catch (e) { process.stderr.write(` • yad capture: ${e.message}\n`); }
|
|
435
|
+
process.exitCode = 0;
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
406
439
|
// Every other command. One await, once, for a process that is about to do real work anyway.
|
|
407
440
|
const commands = await import('./commands.mjs');
|
|
408
441
|
if (o.help || !cmd) {
|
|
@@ -555,7 +588,7 @@ async function main() {
|
|
|
555
588
|
const [, action, epic, artifact] = o._;
|
|
556
589
|
// `gate ci` takes no positionals — epic/artifact come from --branch (or a sweep of all PRs).
|
|
557
590
|
if (action === 'ci') { result = await commands.gateCi(o.dir, { branch: o.branch, pr: o.pr, merged: o.merged, push: !o.noPush, today }); break; }
|
|
558
|
-
if (!epic) { refuse('usage: yad gate <open|sync|comments|status|repair|review|walkthrough|trailer|ci> <epic> [artifact]'); break; }
|
|
591
|
+
if (!epic) { refuse('usage: yad gate <open|sync|comments|status|repair|review|walkthrough|trailer|ci|approve|comment|advance> <epic> [artifact]'); break; }
|
|
559
592
|
// The epic id becomes a path segment under epics/ — reject anything but EP-<slug> outright.
|
|
560
593
|
if (!commands.isValidEpicId(epic)) { refuse(`invalid epic id: ${epic} (expected EP-<slug>, [a-z0-9-] only)`); break; }
|
|
561
594
|
// In verified mode CI is the sole ledger writer: `open` only opens the PR, and local `sync` is
|
|
@@ -569,7 +602,11 @@ async function main() {
|
|
|
569
602
|
else if (action === 'review') result = await commands.gateReview(o.dir, { epic, artifact });
|
|
570
603
|
else if (action === 'walkthrough') result = await commands.gateWalkthrough(o.dir, { epic, artifact });
|
|
571
604
|
else if (action === 'trailer') result = await commands.gateTrailer(o.dir, { epic, artifact, body: o.body || o.message, number: o.pr });
|
|
572
|
-
|
|
605
|
+
// E112 — the gate on a Product with no platform: record an approval, record a comment round, advance.
|
|
606
|
+
else if (action === 'approve') result = await commands.gateApprove(o.dir, { epic, artifact, by: o.by, engagement: o.engagement ?? null, today });
|
|
607
|
+
else if (action === 'comment') result = await commands.gateComment(o.dir, { epic, artifact, by: o.by, count: o.count ?? null, newRound: !!o.newRound, today });
|
|
608
|
+
else if (action === 'advance') result = await commands.gateAdvance(o.dir, { epic, artifact, today });
|
|
609
|
+
else { refuse(`unknown gate action: ${action} (open|sync|comments|status|repair|review|walkthrough|trailer|ci|approve|comment|advance)`); }
|
|
573
610
|
break;
|
|
574
611
|
}
|
|
575
612
|
case 'review': {
|
|
@@ -609,6 +646,45 @@ async function main() {
|
|
|
609
646
|
result = await commands.runCheckpoint(o.dir, { push: o.push, allowBranch: o.allowBranch, dryRun: o.dryRun, retroShip });
|
|
610
647
|
break;
|
|
611
648
|
}
|
|
649
|
+
case 'capture': {
|
|
650
|
+
// E43. `--hook` is the harness's post-edit command: it never fails and never prints to stdout.
|
|
651
|
+
if (o._[1]) { refuse(`yad capture takes no word (got ${o._[1]})`, 'usage: yad capture [--no-push]'); break; }
|
|
652
|
+
result = await commands.runCapture(o.dir, { hook: !!o.hook, noPush: !!o.noPush });
|
|
653
|
+
if (o.hook) process.exitCode = 0;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
case 'claims': {
|
|
657
|
+
// E46. Read from the capture branches; nothing is recorded here.
|
|
658
|
+
const [, epic, extra] = o._;
|
|
659
|
+
if (extra) { refuse(`yad claims takes at most an epic (got an extra word: ${extra})`, 'usage: yad claims [<epic>] [--no-fetch]'); break; }
|
|
660
|
+
result = await commands.runClaims(o.dir, { epic, noFetch: !!o.noFetch });
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
case 'assign':
|
|
664
|
+
case 'unassign': {
|
|
665
|
+
// E47. A file per step; nothing is committed and nothing is blocked.
|
|
666
|
+
const [verb, epic, step, extra] = o._;
|
|
667
|
+
const usage = verb === 'assign' ? 'usage: yad assign <epic> <step> [--to <git name>] [--force]' : 'usage: yad unassign <epic> <step> [--force]';
|
|
668
|
+
if (extra) { refuse(`yad ${verb} takes an epic and a step (got an extra word: ${extra})`, usage); break; }
|
|
669
|
+
if (verb === 'unassign' && o.to != null) { refuse('yad unassign takes no --to: it removes whoever is assigned', usage); break; }
|
|
670
|
+
result = verb === 'assign'
|
|
671
|
+
? await commands.runAssign(o.dir, { epic, step, to: o.to ?? null, force: !!o.force, today })
|
|
672
|
+
: await commands.runUnassign(o.dir, { epic, step, force: !!o.force });
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
case 'owners': {
|
|
676
|
+
const [, epic, extra] = o._;
|
|
677
|
+
if (extra) { refuse(`yad owners takes at most an epic (got an extra word: ${extra})`, 'usage: yad owners [<epic>]'); break; }
|
|
678
|
+
result = await commands.runOwners(o.dir, { epic });
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
case 'fold': {
|
|
682
|
+
// E44. The step-boundary commit; the authoring skills run it where they used to say "commit".
|
|
683
|
+
const [, epic, step, extra] = o._;
|
|
684
|
+
if (extra) { refuse(`yad fold takes an epic and a step (got an extra word: ${extra})`, 'usage: yad fold <epic> <step>'); break; }
|
|
685
|
+
result = await commands.runFold(o.dir, { epic, step });
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
612
688
|
case 'tidy': {
|
|
613
689
|
const [, action, epic] = o._;
|
|
614
690
|
if (action !== 'up') {
|