@ikon85/agent-workflow-kit 0.34.1 → 0.34.4
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/.agents/skills/diagnose/SKILL.md +35 -0
- package/.agents/skills/implement/SKILL.md +33 -0
- package/.agents/skills/kit-release/SKILL.md +28 -6
- package/.agents/skills/orchestrate-wave/SKILL.md +25 -2
- package/.agents/skills/setup-workflow/issue-tracker-github.md +11 -0
- package/.agents/skills/setup-workflow/issue-tracker-gitlab.md +11 -0
- package/.agents/skills/setup-workflow/issue-tracker-local.md +11 -0
- package/.claude/skills/diagnose/SKILL.md +35 -0
- package/.claude/skills/implement/SKILL.md +33 -0
- package/.claude/skills/kit-release/SKILL.md +28 -6
- package/.claude/skills/orchestrate-wave/SKILL.md +25 -2
- package/.claude/skills/setup-workflow/issue-tracker-github.md +11 -0
- package/.claude/skills/setup-workflow/issue-tracker-gitlab.md +11 -0
- package/.claude/skills/setup-workflow/issue-tracker-local.md +11 -0
- package/README.md +29 -0
- package/agent-workflow-kit.package.json +17 -17
- package/docs/adr/0004-release-intent-is-a-version-tag.md +11 -2
- package/docs/agents/issue-tracker.md +11 -0
- package/package.json +1 -1
- package/scripts/kit-release.test.mjs +12 -0
- package/scripts/release-delta-guard.mjs +28 -1
- package/scripts/release-delta-guard.test.mjs +45 -0
- package/scripts/release-state.mjs +7 -3
- package/scripts/release-state.test.mjs +22 -0
- package/scripts/test_issue_claim_contract.py +169 -0
- package/scripts/test_orchestrate_wave_contract.py +13 -1
|
@@ -11,6 +11,41 @@ A discipline for hard bugs. Skip phases only when explicitly justified.
|
|
|
11
11
|
|
|
12
12
|
When exploring the codebase, read `CONTEXT.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.
|
|
13
13
|
|
|
14
|
+
## Phase 0 — Claim the tracked issue
|
|
15
|
+
|
|
16
|
+
Applies only when the bug arrives as a tracked issue. A bug reported in chat has
|
|
17
|
+
nothing to claim; go straight to Phase 1.
|
|
18
|
+
|
|
19
|
+
<!-- issue-claim:start -->
|
|
20
|
+
**Claim the issue before you build.** A worktree, branch, or PR check only sees
|
|
21
|
+
this machine, and only once someone has pushed — the claim on the issue itself
|
|
22
|
+
is the only signal a second session, a second machine, or a cloud agent can
|
|
23
|
+
read.
|
|
24
|
+
|
|
25
|
+
1. **Check first.** Read the issue's assignee and its claim comments. An
|
|
26
|
+
`<!-- agent-claim: ... -->` marker you did not plant, or an assignee that is
|
|
27
|
+
not you, is a **foreign claim**: **STOP**, report the claimed branch and
|
|
28
|
+
worktree, and ask the user how to proceed. Take the issue over only on their
|
|
29
|
+
word, and never delete a foreign claim. A claim whose branch and worktree no
|
|
30
|
+
longer exist is stale — say that and let the user decide; do not assume it is
|
|
31
|
+
dead.
|
|
32
|
+
2. **Plant your claim, before the first edit.** Assign the issue to yourself
|
|
33
|
+
where the tracker supports it, and post the claim marker as a comment:
|
|
34
|
+
`<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->`
|
|
35
|
+
Branch and worktree are the payload — without them a colliding session sees
|
|
36
|
+
the collision but cannot find the work in progress.
|
|
37
|
+
3. **Release.** A PR that references the issue **supersedes** the claim, so
|
|
38
|
+
landing needs no extra step. If you abandon or hand back the work, remove
|
|
39
|
+
your own claim (unassign plus a one-line `claim released` comment) before you
|
|
40
|
+
stop.
|
|
41
|
+
|
|
42
|
+
The concrete commands are your tracker's — `docs/agents/issue-tracker.md`,
|
|
43
|
+
§Pickup claim. If that layer documents no claim convention, fall back to the two
|
|
44
|
+
generic operations above: self-assign, plus the marker comment.
|
|
45
|
+
<!-- issue-claim:end -->
|
|
46
|
+
|
|
47
|
+
Building the Phase-1 loop is already work: claim before it, not after.
|
|
48
|
+
|
|
14
49
|
## Phase 1 — Build a feedback loop
|
|
15
50
|
|
|
16
51
|
**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug — one that goes red on _this_ bug — you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
|
|
@@ -6,6 +6,39 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
Implement the work described by the user in the PRD or issues.
|
|
8
8
|
|
|
9
|
+
## Pickup — claim the issue first
|
|
10
|
+
|
|
11
|
+
Applies whenever the work comes from a tracked issue. Work handed over as plain
|
|
12
|
+
text or a local plan has nothing to claim; skip this section then.
|
|
13
|
+
|
|
14
|
+
<!-- issue-claim:start -->
|
|
15
|
+
**Claim the issue before you build.** A worktree, branch, or PR check only sees
|
|
16
|
+
this machine, and only once someone has pushed — the claim on the issue itself
|
|
17
|
+
is the only signal a second session, a second machine, or a cloud agent can
|
|
18
|
+
read.
|
|
19
|
+
|
|
20
|
+
1. **Check first.** Read the issue's assignee and its claim comments. An
|
|
21
|
+
`<!-- agent-claim: ... -->` marker you did not plant, or an assignee that is
|
|
22
|
+
not you, is a **foreign claim**: **STOP**, report the claimed branch and
|
|
23
|
+
worktree, and ask the user how to proceed. Take the issue over only on their
|
|
24
|
+
word, and never delete a foreign claim. A claim whose branch and worktree no
|
|
25
|
+
longer exist is stale — say that and let the user decide; do not assume it is
|
|
26
|
+
dead.
|
|
27
|
+
2. **Plant your claim, before the first edit.** Assign the issue to yourself
|
|
28
|
+
where the tracker supports it, and post the claim marker as a comment:
|
|
29
|
+
`<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->`
|
|
30
|
+
Branch and worktree are the payload — without them a colliding session sees
|
|
31
|
+
the collision but cannot find the work in progress.
|
|
32
|
+
3. **Release.** A PR that references the issue **supersedes** the claim, so
|
|
33
|
+
landing needs no extra step. If you abandon or hand back the work, remove
|
|
34
|
+
your own claim (unassign plus a one-line `claim released` comment) before you
|
|
35
|
+
stop.
|
|
36
|
+
|
|
37
|
+
The concrete commands are your tracker's — `docs/agents/issue-tracker.md`,
|
|
38
|
+
§Pickup claim. If that layer documents no claim convention, fall back to the two
|
|
39
|
+
generic operations above: self-assign, plus the marker comment.
|
|
40
|
+
<!-- issue-claim:end -->
|
|
41
|
+
|
|
9
42
|
Use /tdd where possible, at pre-agreed seams.
|
|
10
43
|
|
|
11
44
|
Run typechecking regularly, single test files regularly, and the full test suite once at the end.
|
|
@@ -23,11 +23,20 @@ never publishes to a registry or creates a GitHub release directly.
|
|
|
23
23
|
recommend Major, additions recommend Minor, and changed content recommends
|
|
24
24
|
Patch.
|
|
25
25
|
|
|
26
|
-
2.
|
|
27
|
-
metadata before confirmation. A recommendation is advice; the confirmed
|
|
28
|
-
target is authority.
|
|
26
|
+
2. Resolve authority for exactly one target Semver:
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
- Normally, ask the user to confirm the target before changing release
|
|
29
|
+
metadata. A recommendation is advice; the confirmed target is authority.
|
|
30
|
+
- When the caller carries an **explicit AFK end-to-end mandate** whose scope
|
|
31
|
+
includes release preparation, accept the deterministic recommendation as
|
|
32
|
+
the target for **reversible metadata preparation** and record that choice.
|
|
33
|
+
Do not turn a narrower build-only or single-action request into this
|
|
34
|
+
authority.
|
|
35
|
+
|
|
36
|
+
Both routes authorize one prepared target only. Neither authorizes
|
|
37
|
+
publication.
|
|
38
|
+
|
|
39
|
+
3. Prepare that authorized exact version:
|
|
31
40
|
|
|
32
41
|
```sh
|
|
33
42
|
npm run release:prepare -- --version <confirmed-version>
|
|
@@ -45,8 +54,9 @@ never publishes to a registry or creates a GitHub release directly.
|
|
|
45
54
|
|
|
46
55
|
5. After merge, report the exact integrated version and commit as
|
|
47
56
|
`awaiting-tag`. Merging integrates the prepared release; it cannot start
|
|
48
|
-
publication.
|
|
49
|
-
|
|
57
|
+
publication. Creating and pushing the annotated `v<version>` tag requires a
|
|
58
|
+
**separate explicit confirmation** to publish that exact version. The
|
|
59
|
+
earlier Semver confirmation or AFK preparation mandate authorized metadata
|
|
50
60
|
preparation, not publication.
|
|
51
61
|
|
|
52
62
|
6. After that confirmation, verify that the package version on current
|
|
@@ -100,3 +110,15 @@ manifest and compares it with both the base and the checked-in manifest. It
|
|
|
100
110
|
blocks an unbumped shipped delta, a stale manifest, a too-small Semver bump, or
|
|
101
111
|
a dead manifest entry and prints the concrete paths. CI runs the same command;
|
|
102
112
|
there is no separately remembered shipped-file list.
|
|
113
|
+
|
|
114
|
+
It also blocks a **version bump stacked on an untagged previous release**: if
|
|
115
|
+
the base version carries no matching annotated tag, that release is still
|
|
116
|
+
`awaiting-tag` and never became its own artifact. Tag and publish it first —
|
|
117
|
+
never bury it under the next bump. A repository with no matching tag at all is
|
|
118
|
+
bootstrapping its first release and is not blocked.
|
|
119
|
+
|
|
120
|
+
A red release run does not prove nothing was published. Reconstruct the real
|
|
121
|
+
state from the registry and the release before reacting; a stale local
|
|
122
|
+
packument cache can answer "not published" for a package that is live, and the
|
|
123
|
+
recovery route is the idempotent reconciler on the existing tag, never a new
|
|
124
|
+
version.
|
|
@@ -62,6 +62,16 @@ node scripts/readiness.mjs check --skill orchestrate-wave --json
|
|
|
62
62
|
change — current gate, last green milestone, next step, visible blocker/risk. A
|
|
63
63
|
still-running tool session is stated as such; never wait silently for the final
|
|
64
64
|
result.
|
|
65
|
+
- **Authorization scope is sticky and gates are local.** A single-Wave mandate
|
|
66
|
+
authorizes only that Wave. A later explicit whole-Program mandate authorizes all
|
|
67
|
+
planned Waves in that Program and overrides a default per-Wave authorization
|
|
68
|
+
sentence in the Program-PRD; record that scope once and **Do not re-ask at each
|
|
69
|
+
Wave boundary**. A manual or irreversible gate pauses only the gated action:
|
|
70
|
+
continue every dependency-safe reversible or read-only step inside the
|
|
71
|
+
authorized scope, including preparation for later Waves. A persistent goal
|
|
72
|
+
must not be marked `blocked` while meaningful authorized safe work remains
|
|
73
|
+
anywhere in that scope. None of this carries authorization into the gated
|
|
74
|
+
irreversible action itself.
|
|
65
75
|
- **Degrade by subtraction, never by improvisation.** The defined failure paths:
|
|
66
76
|
- **Slice stays red after escalation** (2-strikes rule, see Routing): pull the
|
|
67
77
|
slice — do not merge it (or revert its merge), pull every dependent that
|
|
@@ -180,6 +190,14 @@ Before each slice, bind **(a) inline vs delegate** and **(b) tier + effort** und
|
|
|
180
190
|
Standing rules. Tiny mechanical work may stay inline.
|
|
181
191
|
For Path A/B, create one worktree per agent from wave HEAD, using `§Setup` or
|
|
182
192
|
`git worktree add <path> -b feat/<anchor>-<slug>`.
|
|
193
|
+
- **Claim each slice issue at builder launch — the wave claim is not a slice
|
|
194
|
+
claim.** It guards the anchor on this machine only; the slice issue is the
|
|
195
|
+
grabbable unit a second machine or cloud agent sees. Before dispatch, skip and
|
|
196
|
+
report any slice already carrying a foreign claim (assignee or
|
|
197
|
+
`<!-- agent-claim: ... -->` comment this run did not plant) — never overwrite
|
|
198
|
+
one. Otherwise assign it to yourself and post
|
|
199
|
+
`<!-- agent-claim: branch=<slice-branch>; worktree=<builder-worktree>; date=<YYYY-MM-DD> -->`
|
|
200
|
+
so the claim names the WIP. Commands: `docs/agents/issue-tracker.md`, §Pickup claim.
|
|
183
201
|
- **Build the prompt from [`references/builder-contract.md`](references/builder-contract.md)**
|
|
184
202
|
— fill the slots with the slice's VERBATIM What+AC, plan decision, recon
|
|
185
203
|
file:line map and consume-only lines; never paraphrase (paraphrase drift has
|
|
@@ -190,7 +208,8 @@ For Path A/B, create one worktree per agent from wave HEAD, using `§Setup` or
|
|
|
190
208
|
|
|
191
209
|
**Done when:** every dispatched slice reported back with commit SHA + green
|
|
192
210
|
package tests + green fast gate (`§Builder Commands`) — or a STOP item you resolved
|
|
193
|
-
(answered / re-dispatched) before integration
|
|
211
|
+
(answered / re-dispatched) before integration · every dispatched slice issue
|
|
212
|
+
carries this run's claim, every foreign-claimed slice skipped and reported.
|
|
194
213
|
|
|
195
214
|
## Phase 3 — Serial integration
|
|
196
215
|
|
|
@@ -302,6 +321,9 @@ written · merge order documented.
|
|
|
302
321
|
`wave-active/<anchor>`, call `releaseWaveClaim` with this run's owner after
|
|
303
322
|
success or abort; its owner check, not a broad `wave-active/*` pattern,
|
|
304
323
|
authorizes cleanup, and a foreign marker is left untouched.
|
|
324
|
+
- **Release this run's slice claims.** A landed slice's PR supersedes its claim;
|
|
325
|
+
for a pulled or abandoned slice remove the claim this run planted (unassign +
|
|
326
|
+
`claim released` comment) so it stays grabbable. Foreign ones stay untouched.
|
|
305
327
|
- **Before removing any slice worktree, read its `ANNAHMEN.md`** (an assumptions
|
|
306
328
|
log, gitignored at worktree-root) and propagate each build-time assumption marker
|
|
307
329
|
to the sibling issue it carries. A hand-driven multi-PR / migration landing does
|
|
@@ -331,7 +353,8 @@ written · merge order documented.
|
|
|
331
353
|
- Leave slice worktrees for the user to inspect, or note they're
|
|
332
354
|
post-merge-removable.
|
|
333
355
|
|
|
334
|
-
**Done when:** no orphan process · this run's claim removed ·
|
|
356
|
+
**Done when:** no orphan process · this run's wave claim removed · this run's
|
|
357
|
+
unlanded slice claims released · ANNAHMEN propagated ·
|
|
335
358
|
anchor reconciled + leaf closes verified · anchor closure decided and documented ·
|
|
336
359
|
Program-PRD propagation completed or explicitly skipped · final report lists
|
|
337
360
|
landed/pulled slices as **X of Y**.
|
|
@@ -23,6 +23,17 @@ Create a GitHub issue.
|
|
|
23
23
|
|
|
24
24
|
Run `gh issue view <number> --comments`.
|
|
25
25
|
|
|
26
|
+
## Pickup claim
|
|
27
|
+
|
|
28
|
+
Used by `/implement`, `/diagnose`, and `/orchestrate-wave`: the visible claim
|
|
29
|
+
that keeps a second session, machine, or cloud agent off an issue already being
|
|
30
|
+
built. The marker carries the WIP location, the assignee makes the claim visible
|
|
31
|
+
in list views.
|
|
32
|
+
|
|
33
|
+
- **Check** (before any edit): `gh issue view <n> --json assignees,comments --jq '{assignees: [.assignees[].login], claims: [.comments[].body | select(contains("<!-- agent-claim:"))]}'` — a foreign assignee or a marker this session did not plant is a foreign claim: stop and report it, never overwrite or delete it.
|
|
34
|
+
- **Claim**: `gh issue edit <n> --add-assignee @me`, then post the marker as a comment — `gh issue comment <n> --body '<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->'`.
|
|
35
|
+
- **Release**: a PR referencing the issue supersedes the claim — no extra step when the work lands. On abandon: `gh issue edit <n> --remove-assignee @me` plus `gh issue comment <n> --body "claim released"`.
|
|
36
|
+
|
|
26
37
|
## Wayfinding operations
|
|
27
38
|
|
|
28
39
|
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
|
@@ -22,6 +22,17 @@ Create a GitLab issue.
|
|
|
22
22
|
|
|
23
23
|
Run `glab issue view <number> --comments`.
|
|
24
24
|
|
|
25
|
+
## Pickup claim
|
|
26
|
+
|
|
27
|
+
Used by `/implement`, `/diagnose`, and `/orchestrate-wave`: the visible claim
|
|
28
|
+
that keeps a second session, machine, or cloud agent off an issue already being
|
|
29
|
+
built. The marker carries the WIP location, the assignee makes the claim visible
|
|
30
|
+
in list views.
|
|
31
|
+
|
|
32
|
+
- **Check** (before any edit): `glab issue view <n> --comments` (or `-F json`) — a foreign assignee, or an `<!-- agent-claim: ... -->` note this session did not plant, is a foreign claim: stop and report it, never overwrite or delete it.
|
|
33
|
+
- **Claim**: `glab issue update <n> --assignee @me`, then post the marker as a note — `glab issue note <n> --message '<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->'`.
|
|
34
|
+
- **Release**: a merge request referencing the issue supersedes the claim — no extra step when the work lands. On abandon, drop yourself with the `/unassign @<your-user>` quick action (`glab issue note <n> --message "/unassign @<your-user>"`) and post a `claim released` note.
|
|
35
|
+
|
|
25
36
|
## Wayfinding operations
|
|
26
37
|
|
|
27
38
|
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
|
@@ -18,6 +18,17 @@ Create a new file under `.scratch/<feature-slug>/` (creating the directory if ne
|
|
|
18
18
|
|
|
19
19
|
Read the file at the referenced path. The user will normally pass the path or the issue number directly.
|
|
20
20
|
|
|
21
|
+
## Pickup claim
|
|
22
|
+
|
|
23
|
+
Used by `/implement`, `/diagnose`, and `/orchestrate-wave`: the visible claim
|
|
24
|
+
that keeps a second session, machine, or cloud agent off an issue already being
|
|
25
|
+
built. There is no assignee field here, so the marker line carries the whole
|
|
26
|
+
claim — commit it, otherwise no other checkout can see it.
|
|
27
|
+
|
|
28
|
+
- **Check** (before any edit): read the issue file and grep it for `<!-- agent-claim:`. A marker this session did not plant is a foreign claim: stop and report it, never overwrite or delete it.
|
|
29
|
+
- **Claim**: set `Status: claimed` and add one marker line directly under it — `<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->` — then commit and push the issue file so other checkouts see the claim.
|
|
30
|
+
- **Release**: a PR referencing the issue supersedes the claim. On abandon, delete your own marker line, reset `Status:` to its pre-pickup role, and commit.
|
|
31
|
+
|
|
21
32
|
## Wayfinding operations
|
|
22
33
|
|
|
23
34
|
Used by `/wayfinder`. The **map** is a file with one **child** file per ticket.
|
|
@@ -11,6 +11,41 @@ A discipline for hard bugs. Skip phases only when explicitly justified.
|
|
|
11
11
|
|
|
12
12
|
When exploring the codebase, read `CONTEXT.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.
|
|
13
13
|
|
|
14
|
+
## Phase 0 — Claim the tracked issue
|
|
15
|
+
|
|
16
|
+
Applies only when the bug arrives as a tracked issue. A bug reported in chat has
|
|
17
|
+
nothing to claim; go straight to Phase 1.
|
|
18
|
+
|
|
19
|
+
<!-- issue-claim:start -->
|
|
20
|
+
**Claim the issue before you build.** A worktree, branch, or PR check only sees
|
|
21
|
+
this machine, and only once someone has pushed — the claim on the issue itself
|
|
22
|
+
is the only signal a second session, a second machine, or a cloud agent can
|
|
23
|
+
read.
|
|
24
|
+
|
|
25
|
+
1. **Check first.** Read the issue's assignee and its claim comments. An
|
|
26
|
+
`<!-- agent-claim: ... -->` marker you did not plant, or an assignee that is
|
|
27
|
+
not you, is a **foreign claim**: **STOP**, report the claimed branch and
|
|
28
|
+
worktree, and ask the user how to proceed. Take the issue over only on their
|
|
29
|
+
word, and never delete a foreign claim. A claim whose branch and worktree no
|
|
30
|
+
longer exist is stale — say that and let the user decide; do not assume it is
|
|
31
|
+
dead.
|
|
32
|
+
2. **Plant your claim, before the first edit.** Assign the issue to yourself
|
|
33
|
+
where the tracker supports it, and post the claim marker as a comment:
|
|
34
|
+
`<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->`
|
|
35
|
+
Branch and worktree are the payload — without them a colliding session sees
|
|
36
|
+
the collision but cannot find the work in progress.
|
|
37
|
+
3. **Release.** A PR that references the issue **supersedes** the claim, so
|
|
38
|
+
landing needs no extra step. If you abandon or hand back the work, remove
|
|
39
|
+
your own claim (unassign plus a one-line `claim released` comment) before you
|
|
40
|
+
stop.
|
|
41
|
+
|
|
42
|
+
The concrete commands are your tracker's — `docs/agents/issue-tracker.md`,
|
|
43
|
+
§Pickup claim. If that layer documents no claim convention, fall back to the two
|
|
44
|
+
generic operations above: self-assign, plus the marker comment.
|
|
45
|
+
<!-- issue-claim:end -->
|
|
46
|
+
|
|
47
|
+
Building the Phase-1 loop is already work: claim before it, not after.
|
|
48
|
+
|
|
14
49
|
## Phase 1 — Build a feedback loop
|
|
15
50
|
|
|
16
51
|
**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug — one that goes red on _this_ bug — you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
|
|
@@ -6,6 +6,39 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
Implement the work described by the user in the PRD or issues.
|
|
8
8
|
|
|
9
|
+
## Pickup — claim the issue first
|
|
10
|
+
|
|
11
|
+
Applies whenever the work comes from a tracked issue. Work handed over as plain
|
|
12
|
+
text or a local plan has nothing to claim; skip this section then.
|
|
13
|
+
|
|
14
|
+
<!-- issue-claim:start -->
|
|
15
|
+
**Claim the issue before you build.** A worktree, branch, or PR check only sees
|
|
16
|
+
this machine, and only once someone has pushed — the claim on the issue itself
|
|
17
|
+
is the only signal a second session, a second machine, or a cloud agent can
|
|
18
|
+
read.
|
|
19
|
+
|
|
20
|
+
1. **Check first.** Read the issue's assignee and its claim comments. An
|
|
21
|
+
`<!-- agent-claim: ... -->` marker you did not plant, or an assignee that is
|
|
22
|
+
not you, is a **foreign claim**: **STOP**, report the claimed branch and
|
|
23
|
+
worktree, and ask the user how to proceed. Take the issue over only on their
|
|
24
|
+
word, and never delete a foreign claim. A claim whose branch and worktree no
|
|
25
|
+
longer exist is stale — say that and let the user decide; do not assume it is
|
|
26
|
+
dead.
|
|
27
|
+
2. **Plant your claim, before the first edit.** Assign the issue to yourself
|
|
28
|
+
where the tracker supports it, and post the claim marker as a comment:
|
|
29
|
+
`<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->`
|
|
30
|
+
Branch and worktree are the payload — without them a colliding session sees
|
|
31
|
+
the collision but cannot find the work in progress.
|
|
32
|
+
3. **Release.** A PR that references the issue **supersedes** the claim, so
|
|
33
|
+
landing needs no extra step. If you abandon or hand back the work, remove
|
|
34
|
+
your own claim (unassign plus a one-line `claim released` comment) before you
|
|
35
|
+
stop.
|
|
36
|
+
|
|
37
|
+
The concrete commands are your tracker's — `docs/agents/issue-tracker.md`,
|
|
38
|
+
§Pickup claim. If that layer documents no claim convention, fall back to the two
|
|
39
|
+
generic operations above: self-assign, plus the marker comment.
|
|
40
|
+
<!-- issue-claim:end -->
|
|
41
|
+
|
|
9
42
|
Use /tdd where possible, at pre-agreed seams.
|
|
10
43
|
|
|
11
44
|
Run typechecking regularly, single test files regularly, and the full test suite once at the end.
|
|
@@ -23,11 +23,20 @@ never publishes to a registry or creates a GitHub release directly.
|
|
|
23
23
|
recommend Major, additions recommend Minor, and changed content recommends
|
|
24
24
|
Patch.
|
|
25
25
|
|
|
26
|
-
2.
|
|
27
|
-
metadata before confirmation. A recommendation is advice; the confirmed
|
|
28
|
-
target is authority.
|
|
26
|
+
2. Resolve authority for exactly one target Semver:
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
- Normally, ask the user to confirm the target before changing release
|
|
29
|
+
metadata. A recommendation is advice; the confirmed target is authority.
|
|
30
|
+
- When the caller carries an **explicit AFK end-to-end mandate** whose scope
|
|
31
|
+
includes release preparation, accept the deterministic recommendation as
|
|
32
|
+
the target for **reversible metadata preparation** and record that choice.
|
|
33
|
+
Do not turn a narrower build-only or single-action request into this
|
|
34
|
+
authority.
|
|
35
|
+
|
|
36
|
+
Both routes authorize one prepared target only. Neither authorizes
|
|
37
|
+
publication.
|
|
38
|
+
|
|
39
|
+
3. Prepare that authorized exact version:
|
|
31
40
|
|
|
32
41
|
```sh
|
|
33
42
|
npm run release:prepare -- --version <confirmed-version>
|
|
@@ -45,8 +54,9 @@ never publishes to a registry or creates a GitHub release directly.
|
|
|
45
54
|
|
|
46
55
|
5. After merge, report the exact integrated version and commit as
|
|
47
56
|
`awaiting-tag`. Merging integrates the prepared release; it cannot start
|
|
48
|
-
publication.
|
|
49
|
-
|
|
57
|
+
publication. Creating and pushing the annotated `v<version>` tag requires a
|
|
58
|
+
**separate explicit confirmation** to publish that exact version. The
|
|
59
|
+
earlier Semver confirmation or AFK preparation mandate authorized metadata
|
|
50
60
|
preparation, not publication.
|
|
51
61
|
|
|
52
62
|
6. After that confirmation, verify that the package version on current
|
|
@@ -100,3 +110,15 @@ manifest and compares it with both the base and the checked-in manifest. It
|
|
|
100
110
|
blocks an unbumped shipped delta, a stale manifest, a too-small Semver bump, or
|
|
101
111
|
a dead manifest entry and prints the concrete paths. CI runs the same command;
|
|
102
112
|
there is no separately remembered shipped-file list.
|
|
113
|
+
|
|
114
|
+
It also blocks a **version bump stacked on an untagged previous release**: if
|
|
115
|
+
the base version carries no matching annotated tag, that release is still
|
|
116
|
+
`awaiting-tag` and never became its own artifact. Tag and publish it first —
|
|
117
|
+
never bury it under the next bump. A repository with no matching tag at all is
|
|
118
|
+
bootstrapping its first release and is not blocked.
|
|
119
|
+
|
|
120
|
+
A red release run does not prove nothing was published. Reconstruct the real
|
|
121
|
+
state from the registry and the release before reacting; a stale local
|
|
122
|
+
packument cache can answer "not published" for a package that is live, and the
|
|
123
|
+
recovery route is the idempotent reconciler on the existing tag, never a new
|
|
124
|
+
version.
|
|
@@ -70,6 +70,16 @@ node scripts/readiness.mjs check --skill orchestrate-wave --json
|
|
|
70
70
|
change — current gate, last green milestone, next step, visible blocker/risk. A
|
|
71
71
|
still-running tool session is stated as such; never wait silently for the final
|
|
72
72
|
result.
|
|
73
|
+
- **Authorization scope is sticky and gates are local.** A single-Wave mandate
|
|
74
|
+
authorizes only that Wave. A later explicit whole-Program mandate authorizes all
|
|
75
|
+
planned Waves in that Program and overrides a default per-Wave authorization
|
|
76
|
+
sentence in the Program-PRD; record that scope once and **Do not re-ask at each
|
|
77
|
+
Wave boundary**. A manual or irreversible gate pauses only the gated action:
|
|
78
|
+
continue every dependency-safe reversible or read-only step inside the
|
|
79
|
+
authorized scope, including preparation for later Waves. A persistent goal
|
|
80
|
+
must not be marked `blocked` while meaningful authorized safe work remains
|
|
81
|
+
anywhere in that scope. None of this carries authorization into the gated
|
|
82
|
+
irreversible action itself.
|
|
73
83
|
- **Degrade by subtraction, never by improvisation.** The defined failure paths:
|
|
74
84
|
- **Slice stays red after escalation** (2-strikes rule, see Routing): pull the
|
|
75
85
|
slice — do not merge it (or revert its merge), pull every dependent that
|
|
@@ -188,6 +198,14 @@ Before each slice, bind **(a) inline vs delegate** and **(b) tier + effort** und
|
|
|
188
198
|
Standing rules. Tiny mechanical work may stay inline.
|
|
189
199
|
For Path A/B, create one worktree per agent from wave HEAD, using `§Setup` or
|
|
190
200
|
`git worktree add <path> -b feat/<anchor>-<slug>`.
|
|
201
|
+
- **Claim each slice issue at builder launch — the wave claim is not a slice
|
|
202
|
+
claim.** It guards the anchor on this machine only; the slice issue is the
|
|
203
|
+
grabbable unit a second machine or cloud agent sees. Before dispatch, skip and
|
|
204
|
+
report any slice already carrying a foreign claim (assignee or
|
|
205
|
+
`<!-- agent-claim: ... -->` comment this run did not plant) — never overwrite
|
|
206
|
+
one. Otherwise assign it to yourself and post
|
|
207
|
+
`<!-- agent-claim: branch=<slice-branch>; worktree=<builder-worktree>; date=<YYYY-MM-DD> -->`
|
|
208
|
+
so the claim names the WIP. Commands: `docs/agents/issue-tracker.md`, §Pickup claim.
|
|
191
209
|
- **Build the prompt from [`references/builder-contract.md`](references/builder-contract.md)**
|
|
192
210
|
— fill the slots with the slice's VERBATIM What+AC, plan decision, recon
|
|
193
211
|
file:line map and consume-only lines; never paraphrase (paraphrase drift has
|
|
@@ -198,7 +216,8 @@ For Path A/B, create one worktree per agent from wave HEAD, using `§Setup` or
|
|
|
198
216
|
|
|
199
217
|
**Done when:** every dispatched slice reported back with commit SHA + green
|
|
200
218
|
package tests + green fast gate (`§Builder Commands`) — or a STOP item you resolved
|
|
201
|
-
(answered / re-dispatched) before integration
|
|
219
|
+
(answered / re-dispatched) before integration · every dispatched slice issue
|
|
220
|
+
carries this run's claim, every foreign-claimed slice skipped and reported.
|
|
202
221
|
|
|
203
222
|
## Phase 3 — Serial integration
|
|
204
223
|
|
|
@@ -310,6 +329,9 @@ written · merge order documented.
|
|
|
310
329
|
`wave-active/<anchor>`, call `releaseWaveClaim` with this run's owner after
|
|
311
330
|
success or abort; its owner check, not a broad `wave-active/*` pattern,
|
|
312
331
|
authorizes cleanup, and a foreign marker is left untouched.
|
|
332
|
+
- **Release this run's slice claims.** A landed slice's PR supersedes its claim;
|
|
333
|
+
for a pulled or abandoned slice remove the claim this run planted (unassign +
|
|
334
|
+
`claim released` comment) so it stays grabbable. Foreign ones stay untouched.
|
|
313
335
|
- **Before removing any slice worktree, read its `ANNAHMEN.md`** (an assumptions
|
|
314
336
|
log, gitignored at worktree-root) and propagate each build-time assumption marker
|
|
315
337
|
to the sibling issue it carries. A hand-driven multi-PR / migration landing does
|
|
@@ -339,7 +361,8 @@ written · merge order documented.
|
|
|
339
361
|
- Leave slice worktrees for the user to inspect, or note they're
|
|
340
362
|
post-merge-removable.
|
|
341
363
|
|
|
342
|
-
**Done when:** no orphan process · this run's claim removed ·
|
|
364
|
+
**Done when:** no orphan process · this run's wave claim removed · this run's
|
|
365
|
+
unlanded slice claims released · ANNAHMEN propagated ·
|
|
343
366
|
anchor reconciled + leaf closes verified · anchor closure decided and documented ·
|
|
344
367
|
Program-PRD propagation completed or explicitly skipped · final report lists
|
|
345
368
|
landed/pulled slices as **X of Y**.
|
|
@@ -23,6 +23,17 @@ Create a GitHub issue.
|
|
|
23
23
|
|
|
24
24
|
Run `gh issue view <number> --comments`.
|
|
25
25
|
|
|
26
|
+
## Pickup claim
|
|
27
|
+
|
|
28
|
+
Used by `/implement`, `/diagnose`, and `/orchestrate-wave`: the visible claim
|
|
29
|
+
that keeps a second session, machine, or cloud agent off an issue already being
|
|
30
|
+
built. The marker carries the WIP location, the assignee makes the claim visible
|
|
31
|
+
in list views.
|
|
32
|
+
|
|
33
|
+
- **Check** (before any edit): `gh issue view <n> --json assignees,comments --jq '{assignees: [.assignees[].login], claims: [.comments[].body | select(contains("<!-- agent-claim:"))]}'` — a foreign assignee or a marker this session did not plant is a foreign claim: stop and report it, never overwrite or delete it.
|
|
34
|
+
- **Claim**: `gh issue edit <n> --add-assignee @me`, then post the marker as a comment — `gh issue comment <n> --body '<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->'`.
|
|
35
|
+
- **Release**: a PR referencing the issue supersedes the claim — no extra step when the work lands. On abandon: `gh issue edit <n> --remove-assignee @me` plus `gh issue comment <n> --body "claim released"`.
|
|
36
|
+
|
|
26
37
|
## Wayfinding operations
|
|
27
38
|
|
|
28
39
|
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
|
@@ -22,6 +22,17 @@ Create a GitLab issue.
|
|
|
22
22
|
|
|
23
23
|
Run `glab issue view <number> --comments`.
|
|
24
24
|
|
|
25
|
+
## Pickup claim
|
|
26
|
+
|
|
27
|
+
Used by `/implement`, `/diagnose`, and `/orchestrate-wave`: the visible claim
|
|
28
|
+
that keeps a second session, machine, or cloud agent off an issue already being
|
|
29
|
+
built. The marker carries the WIP location, the assignee makes the claim visible
|
|
30
|
+
in list views.
|
|
31
|
+
|
|
32
|
+
- **Check** (before any edit): `glab issue view <n> --comments` (or `-F json`) — a foreign assignee, or an `<!-- agent-claim: ... -->` note this session did not plant, is a foreign claim: stop and report it, never overwrite or delete it.
|
|
33
|
+
- **Claim**: `glab issue update <n> --assignee @me`, then post the marker as a note — `glab issue note <n> --message '<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->'`.
|
|
34
|
+
- **Release**: a merge request referencing the issue supersedes the claim — no extra step when the work lands. On abandon, drop yourself with the `/unassign @<your-user>` quick action (`glab issue note <n> --message "/unassign @<your-user>"`) and post a `claim released` note.
|
|
35
|
+
|
|
25
36
|
## Wayfinding operations
|
|
26
37
|
|
|
27
38
|
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
|
@@ -18,6 +18,17 @@ Create a new file under `.scratch/<feature-slug>/` (creating the directory if ne
|
|
|
18
18
|
|
|
19
19
|
Read the file at the referenced path. The user will normally pass the path or the issue number directly.
|
|
20
20
|
|
|
21
|
+
## Pickup claim
|
|
22
|
+
|
|
23
|
+
Used by `/implement`, `/diagnose`, and `/orchestrate-wave`: the visible claim
|
|
24
|
+
that keeps a second session, machine, or cloud agent off an issue already being
|
|
25
|
+
built. There is no assignee field here, so the marker line carries the whole
|
|
26
|
+
claim — commit it, otherwise no other checkout can see it.
|
|
27
|
+
|
|
28
|
+
- **Check** (before any edit): read the issue file and grep it for `<!-- agent-claim:`. A marker this session did not plant is a foreign claim: stop and report it, never overwrite or delete it.
|
|
29
|
+
- **Claim**: set `Status: claimed` and add one marker line directly under it — `<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->` — then commit and push the issue file so other checkouts see the claim.
|
|
30
|
+
- **Release**: a PR referencing the issue supersedes the claim. On abandon, delete your own marker line, reset `Status:` to its pre-pickup role, and commit.
|
|
31
|
+
|
|
21
32
|
## Wayfinding operations
|
|
22
33
|
|
|
23
34
|
Used by `/wayfinder`. The **map** is a file with one **child** file per ticket.
|
package/README.md
CHANGED
|
@@ -387,6 +387,35 @@ the old way. Decision record:
|
|
|
387
387
|
|
|
388
388
|
## Release notes
|
|
389
389
|
|
|
390
|
+
### 0.34.4
|
|
391
|
+
|
|
392
|
+
- changed: `.agents/skills/kit-release/SKILL.md`
|
|
393
|
+
- changed: `.claude/skills/kit-release/SKILL.md`
|
|
394
|
+
- changed: `scripts/release-delta-guard.mjs`
|
|
395
|
+
- changed: `scripts/release-state.mjs`
|
|
396
|
+
|
|
397
|
+
### 0.34.3
|
|
398
|
+
|
|
399
|
+
- changed: `.agents/skills/diagnose/SKILL.md`
|
|
400
|
+
- changed: `.agents/skills/implement/SKILL.md`
|
|
401
|
+
- changed: `.agents/skills/orchestrate-wave/SKILL.md`
|
|
402
|
+
- changed: `.agents/skills/setup-workflow/issue-tracker-github.md`
|
|
403
|
+
- changed: `.agents/skills/setup-workflow/issue-tracker-gitlab.md`
|
|
404
|
+
- changed: `.agents/skills/setup-workflow/issue-tracker-local.md`
|
|
405
|
+
- changed: `.claude/skills/diagnose/SKILL.md`
|
|
406
|
+
- changed: `.claude/skills/implement/SKILL.md`
|
|
407
|
+
- changed: `.claude/skills/orchestrate-wave/SKILL.md`
|
|
408
|
+
- changed: `.claude/skills/setup-workflow/issue-tracker-github.md`
|
|
409
|
+
- changed: `.claude/skills/setup-workflow/issue-tracker-gitlab.md`
|
|
410
|
+
- changed: `.claude/skills/setup-workflow/issue-tracker-local.md`
|
|
411
|
+
|
|
412
|
+
### 0.34.2
|
|
413
|
+
|
|
414
|
+
- changed: `.agents/skills/kit-release/SKILL.md`
|
|
415
|
+
- changed: `.agents/skills/orchestrate-wave/SKILL.md`
|
|
416
|
+
- changed: `.claude/skills/kit-release/SKILL.md`
|
|
417
|
+
- changed: `.claude/skills/orchestrate-wave/SKILL.md`
|
|
418
|
+
|
|
390
419
|
### 0.34.1
|
|
391
420
|
|
|
392
421
|
- changed: `.agents/skills/kit-release/SKILL.md`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"kitVersion": "0.34.
|
|
2
|
+
"kitVersion": "0.34.4",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": ".agents/skills/ask-matt/SKILL.md",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"ownerSkill": "diagnose",
|
|
138
138
|
"surface": "codex",
|
|
139
139
|
"installRole": "consumer",
|
|
140
|
-
"sha256": "
|
|
140
|
+
"sha256": "8a6b1036968146beff15a03e03c7b0f7d71ab65a666201ee8e1588c484f32ce6",
|
|
141
141
|
"mode": 420,
|
|
142
142
|
"origin": "kit"
|
|
143
143
|
},
|
|
@@ -267,7 +267,7 @@
|
|
|
267
267
|
"ownerSkill": "implement",
|
|
268
268
|
"surface": "codex",
|
|
269
269
|
"installRole": "consumer",
|
|
270
|
-
"sha256": "
|
|
270
|
+
"sha256": "dff8c4bd4a1cfd376bc5fdd9d97641301d61055fd9155bde50c1e9634a6015d2",
|
|
271
271
|
"mode": 420,
|
|
272
272
|
"origin": "kit"
|
|
273
273
|
},
|
|
@@ -367,7 +367,7 @@
|
|
|
367
367
|
"ownerSkill": "kit-release",
|
|
368
368
|
"surface": "codex",
|
|
369
369
|
"installRole": "maintainer",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "f584caa1753ed34de584418a4176015cc3e2382db06efae488bb6d785eab71b7",
|
|
371
371
|
"mode": 420,
|
|
372
372
|
"origin": "kit"
|
|
373
373
|
},
|
|
@@ -447,7 +447,7 @@
|
|
|
447
447
|
"ownerSkill": "orchestrate-wave",
|
|
448
448
|
"surface": "codex",
|
|
449
449
|
"installRole": "consumer",
|
|
450
|
-
"sha256": "
|
|
450
|
+
"sha256": "4c40d95e773649ba87e5010740180b66682a18cdf98ba7d8b7907397ea212ed0",
|
|
451
451
|
"mode": 420,
|
|
452
452
|
"origin": "kit"
|
|
453
453
|
},
|
|
@@ -627,7 +627,7 @@
|
|
|
627
627
|
"ownerSkill": "setup-workflow",
|
|
628
628
|
"surface": "codex",
|
|
629
629
|
"installRole": "consumer",
|
|
630
|
-
"sha256": "
|
|
630
|
+
"sha256": "6c896543b1a1a50ca3f591d63e364a3f0d624b8ae02ef93183e3665a27248d3c",
|
|
631
631
|
"mode": 420,
|
|
632
632
|
"origin": "kit"
|
|
633
633
|
},
|
|
@@ -637,7 +637,7 @@
|
|
|
637
637
|
"ownerSkill": "setup-workflow",
|
|
638
638
|
"surface": "codex",
|
|
639
639
|
"installRole": "consumer",
|
|
640
|
-
"sha256": "
|
|
640
|
+
"sha256": "be08d8e86f4573049c9cd809cde9417a334d364f3e823548bcd54136b1da4b01",
|
|
641
641
|
"mode": 420,
|
|
642
642
|
"origin": "kit"
|
|
643
643
|
},
|
|
@@ -647,7 +647,7 @@
|
|
|
647
647
|
"ownerSkill": "setup-workflow",
|
|
648
648
|
"surface": "codex",
|
|
649
649
|
"installRole": "consumer",
|
|
650
|
-
"sha256": "
|
|
650
|
+
"sha256": "b4bf369b9c7791b9b9c76438aaa4cd9a07a0860fcc6bb9b52eebe12c6427ccbb",
|
|
651
651
|
"mode": 420,
|
|
652
652
|
"origin": "kit"
|
|
653
653
|
},
|
|
@@ -1359,7 +1359,7 @@
|
|
|
1359
1359
|
"ownerSkill": "diagnose",
|
|
1360
1360
|
"surface": "claude",
|
|
1361
1361
|
"installRole": "consumer",
|
|
1362
|
-
"sha256": "
|
|
1362
|
+
"sha256": "44edd6a5af6429fd711ddbb3a4123b51a9c497573c147c2fc7b874e6e2a1336c",
|
|
1363
1363
|
"mode": 420,
|
|
1364
1364
|
"origin": "kit"
|
|
1365
1365
|
},
|
|
@@ -1579,7 +1579,7 @@
|
|
|
1579
1579
|
"ownerSkill": "implement",
|
|
1580
1580
|
"surface": "claude",
|
|
1581
1581
|
"installRole": "consumer",
|
|
1582
|
-
"sha256": "
|
|
1582
|
+
"sha256": "dff8c4bd4a1cfd376bc5fdd9d97641301d61055fd9155bde50c1e9634a6015d2",
|
|
1583
1583
|
"mode": 420,
|
|
1584
1584
|
"origin": "kit"
|
|
1585
1585
|
},
|
|
@@ -1679,7 +1679,7 @@
|
|
|
1679
1679
|
"ownerSkill": "kit-release",
|
|
1680
1680
|
"surface": "claude",
|
|
1681
1681
|
"installRole": "maintainer",
|
|
1682
|
-
"sha256": "
|
|
1682
|
+
"sha256": "f584caa1753ed34de584418a4176015cc3e2382db06efae488bb6d785eab71b7",
|
|
1683
1683
|
"mode": 420,
|
|
1684
1684
|
"origin": "kit"
|
|
1685
1685
|
},
|
|
@@ -1759,7 +1759,7 @@
|
|
|
1759
1759
|
"ownerSkill": "orchestrate-wave",
|
|
1760
1760
|
"surface": "claude",
|
|
1761
1761
|
"installRole": "consumer",
|
|
1762
|
-
"sha256": "
|
|
1762
|
+
"sha256": "24c90d6048f04df29c0031cd6b643ab433536c8a176dd4983111c6be6e0fcb19",
|
|
1763
1763
|
"mode": 420,
|
|
1764
1764
|
"origin": "kit"
|
|
1765
1765
|
},
|
|
@@ -1959,7 +1959,7 @@
|
|
|
1959
1959
|
"ownerSkill": "setup-workflow",
|
|
1960
1960
|
"surface": "claude",
|
|
1961
1961
|
"installRole": "consumer",
|
|
1962
|
-
"sha256": "
|
|
1962
|
+
"sha256": "6c896543b1a1a50ca3f591d63e364a3f0d624b8ae02ef93183e3665a27248d3c",
|
|
1963
1963
|
"mode": 420,
|
|
1964
1964
|
"origin": "kit"
|
|
1965
1965
|
},
|
|
@@ -1969,7 +1969,7 @@
|
|
|
1969
1969
|
"ownerSkill": "setup-workflow",
|
|
1970
1970
|
"surface": "claude",
|
|
1971
1971
|
"installRole": "consumer",
|
|
1972
|
-
"sha256": "
|
|
1972
|
+
"sha256": "be08d8e86f4573049c9cd809cde9417a334d364f3e823548bcd54136b1da4b01",
|
|
1973
1973
|
"mode": 420,
|
|
1974
1974
|
"origin": "kit"
|
|
1975
1975
|
},
|
|
@@ -1979,7 +1979,7 @@
|
|
|
1979
1979
|
"ownerSkill": "setup-workflow",
|
|
1980
1980
|
"surface": "claude",
|
|
1981
1981
|
"installRole": "consumer",
|
|
1982
|
-
"sha256": "
|
|
1982
|
+
"sha256": "b4bf369b9c7791b9b9c76438aaa4cd9a07a0860fcc6bb9b52eebe12c6427ccbb",
|
|
1983
1983
|
"mode": 420,
|
|
1984
1984
|
"origin": "kit"
|
|
1985
1985
|
},
|
|
@@ -2655,7 +2655,7 @@
|
|
|
2655
2655
|
"path": "scripts/release-delta-guard.mjs",
|
|
2656
2656
|
"kind": "script",
|
|
2657
2657
|
"installRole": "maintainer",
|
|
2658
|
-
"sha256": "
|
|
2658
|
+
"sha256": "1d2aab5f15bca25167b166b37232139916cf814e94de873fa74a653ecf9a4619",
|
|
2659
2659
|
"mode": 420,
|
|
2660
2660
|
"origin": "kit"
|
|
2661
2661
|
},
|
|
@@ -2671,7 +2671,7 @@
|
|
|
2671
2671
|
"path": "scripts/release-state.mjs",
|
|
2672
2672
|
"kind": "script",
|
|
2673
2673
|
"installRole": "consumer",
|
|
2674
|
-
"sha256": "
|
|
2674
|
+
"sha256": "fd9f7f86e26ab3faba819e0c46e5e87c2dd3a84dc9a6111b4eceb4ebe6a3a344",
|
|
2675
2675
|
"mode": 420,
|
|
2676
2676
|
"origin": "kit"
|
|
2677
2677
|
},
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Status: accepted (2026-07-22, issue #204)
|
|
4
4
|
|
|
5
|
+
Clarified 2026-07-25 by issue #239: an explicit AFK end-to-end mandate may
|
|
6
|
+
authorize deterministic reversible release preparation, but never the
|
|
7
|
+
publication tag itself.
|
|
8
|
+
|
|
5
9
|
The release workflow currently publishes whenever a merge to `main` changes
|
|
6
10
|
`package.json`. That keeps the canonical branch and npm close together, but it
|
|
7
11
|
also makes an ordinary merge an irreversible public action whose consequence is
|
|
@@ -36,7 +40,12 @@ We decided that integration and publication are separate transitions:
|
|
|
36
40
|
## Consequences
|
|
37
41
|
|
|
38
42
|
- A prepared version may temporarily exist on `main` in an `awaiting-tag` state.
|
|
39
|
-
- An AFK
|
|
40
|
-
|
|
43
|
+
- An explicit AFK Wave/Program mandate may cover the deterministic reversible
|
|
44
|
+
metadata preparation and landing that it names. It never by itself authorizes
|
|
45
|
+
creating or pushing the publication tag; that irreversible action requires a
|
|
46
|
+
separate explicit confirmation.
|
|
47
|
+
- A single-Wave mandate does not authorize the next Program Wave. A later
|
|
48
|
+
explicit whole-Program mandate does authorize all planned Waves and remains in
|
|
49
|
+
force across their boundaries.
|
|
41
50
|
- Reruns reconcile the same tag and never infer that a red workflow means
|
|
42
51
|
nothing was published.
|
|
@@ -10,3 +10,14 @@ Issues and PRDs for this repo live in `iKon85/agent-workflow-kit` as GitHub issu
|
|
|
10
10
|
- Route board writes, status/wave/cluster field edits, dependencies, and sub-issue links through `scripts/board-sync.py`, not bare `gh project` commands.
|
|
11
11
|
- Board state is authoritative for workflow status; GitHub issues remain the durable content source.
|
|
12
12
|
|
|
13
|
+
## Pickup claim
|
|
14
|
+
|
|
15
|
+
Used by `/implement`, `/diagnose`, and `/orchestrate-wave` before the first
|
|
16
|
+
edit. Board status `In Progress` is the human-facing signal; the claim below is
|
|
17
|
+
the machine-readable one, and it is the only one that names the branch and
|
|
18
|
+
worktree a colliding session would have to find.
|
|
19
|
+
|
|
20
|
+
- **Check**: `gh issue view <n> --json assignees,comments --jq '{assignees: [.assignees[].login], claims: [.comments[].body | select(contains("<!-- agent-claim:"))]}'` — a foreign assignee or a marker this session did not plant is a foreign claim: stop, report the claimed branch/worktree, and leave it alone.
|
|
21
|
+
- **Claim**: `gh issue edit <n> --add-assignee @me`, then `gh issue comment <n> --body '<!-- agent-claim: branch=<branch>; worktree=<absolute-path>; date=<YYYY-MM-DD> -->'`. Board status stays a `scripts/board-sync.py` write, never a bare `gh project` call.
|
|
22
|
+
- **Release**: the slice PR (`closes #<n>` / `Part of #<anchor>`) supersedes the claim, so `/wrapup` needs no extra step. On abandon: `gh issue edit <n> --remove-assignee @me` plus a `claim released` comment.
|
|
23
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikon85/agent-workflow-kit",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.4",
|
|
4
4
|
"description": "Portable AI-agent workflow skills (plan → execute → land → learn) for Claude Code & Codex — grilling, TDD, diagnosis, two-axis code review, cross-model Codex review, design & domain-modeling, plus a skill router (ask-matt). npx init/update/diff/uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -184,6 +184,18 @@ test('both release skill surfaces name only the owned scoped npm package', async
|
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
+
test('an explicit AFK scope can authorize reversible preparation but never publication', async () => {
|
|
188
|
+
const claude = await readFile(join(REPO, '.claude/skills/kit-release/SKILL.md'), 'utf8');
|
|
189
|
+
const codex = await readFile(join(REPO, '.agents/skills/kit-release/SKILL.md'), 'utf8');
|
|
190
|
+
assert.equal(codex, claude);
|
|
191
|
+
for (const body of [claude, codex]) {
|
|
192
|
+
assert.match(body, /explicit AFK end-to-end mandate/i);
|
|
193
|
+
assert.match(body, /deterministic recommendation/i);
|
|
194
|
+
assert.match(body, /reversible metadata preparation/i);
|
|
195
|
+
assert.match(body, /annotated `v<version>` tag[\s\S]*separate explicit confirmation/i);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
187
199
|
test('maintainer docs and the accepted ADR agree that merge integrates and an annotated tag publishes', async () => {
|
|
188
200
|
const paths = [
|
|
189
201
|
'CLAUDE.md',
|
|
@@ -57,6 +57,18 @@ export function assessRelease(input) {
|
|
|
57
57
|
if (hasDelta(payloadDrift)) {
|
|
58
58
|
errors.push(`npm package payload does not match built manifest (${describe(payloadDrift)})`);
|
|
59
59
|
}
|
|
60
|
+
// Merging integrates a prepared version; only its annotated tag publishes it.
|
|
61
|
+
// Nothing else notices when that tag never arrives, so the next release PR
|
|
62
|
+
// would silently stack on top and the skipped version would never exist as
|
|
63
|
+
// its own artifact. Block that here, where a human is present anyway. A
|
|
64
|
+
// repository without any matching tag is bootstrapping, not stacking.
|
|
65
|
+
const { baseTag } = input;
|
|
66
|
+
if (baseTag?.repoHasTags && !baseTag.exists && input.currentVersion !== input.baseVersion) {
|
|
67
|
+
errors.push(
|
|
68
|
+
`previous release ${input.baseVersion} is still awaiting-tag (no ${baseTag.name} tag); `
|
|
69
|
+
+ `tag and publish it before preparing ${input.currentVersion}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
60
72
|
const recommendedBump = recommendBump(delta);
|
|
61
73
|
const actual = bumpKind(input.baseVersion, input.currentVersion);
|
|
62
74
|
const rank = { patch: 1, minor: 2, major: 3 };
|
|
@@ -73,6 +85,19 @@ function gitShowJson(repoRoot, ref, path) {
|
|
|
73
85
|
return JSON.parse(execFileSync('git', ['show', `${ref}:${path}`], { cwd: repoRoot, encoding: 'utf8' }));
|
|
74
86
|
}
|
|
75
87
|
|
|
88
|
+
function git(repoRoot, args) {
|
|
89
|
+
return execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8' }).trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveBaseTag({ repoRoot, baseVersion, tagPrefix = 'v' }) {
|
|
93
|
+
const name = `${tagPrefix}${baseVersion}`;
|
|
94
|
+
let exists = true;
|
|
95
|
+
try {
|
|
96
|
+
git(repoRoot, ['rev-parse', '-q', '--verify', `refs/tags/${name}`]);
|
|
97
|
+
} catch { exists = false; }
|
|
98
|
+
return { name, exists, repoHasTags: git(repoRoot, ['tag', '-l', `${tagPrefix}*`]) !== '' };
|
|
99
|
+
}
|
|
100
|
+
|
|
76
101
|
export async function packedPayloadManifest({ repoRoot, manifest }) {
|
|
77
102
|
const tempRoot = await mkdtemp(join(tmpdir(), 'awkit-package-payload-'));
|
|
78
103
|
try {
|
|
@@ -103,8 +128,10 @@ export async function checkReleaseDelta({ repoRoot, baseRef = 'origin/main' } =
|
|
|
103
128
|
await buildKit({ repoRoot, distDir });
|
|
104
129
|
const currentPackage = JSON.parse(await readFile(join(repoRoot, 'package.json'), 'utf8'));
|
|
105
130
|
const builtManifest = JSON.parse(await readFile(join(distDir, 'agent-workflow-kit.package.json'), 'utf8'));
|
|
131
|
+
const baseVersion = gitShowJson(repoRoot, baseRef, 'package.json').version;
|
|
106
132
|
return assessRelease({
|
|
107
|
-
baseVersion
|
|
133
|
+
baseVersion,
|
|
134
|
+
baseTag: resolveBaseTag({ repoRoot, baseVersion }),
|
|
108
135
|
currentVersion: currentPackage.version,
|
|
109
136
|
baseManifest: gitShowJson(repoRoot, baseRef, 'agent-workflow-kit.package.json'),
|
|
110
137
|
checkedManifest: JSON.parse(await readFile(join(repoRoot, 'agent-workflow-kit.package.json'), 'utf8')),
|
|
@@ -75,6 +75,51 @@ test('shipped content rejects downgrade and malformed version transitions', () =
|
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
test('a bump on top of an untagged previous release is blocked', () => {
|
|
79
|
+
const current = { kitVersion: '1.3.0', files: [file('new.md', 'one')] };
|
|
80
|
+
const result = assessRelease({
|
|
81
|
+
baseVersion: '1.2.3', currentVersion: '1.3.0',
|
|
82
|
+
baseManifest: { kitVersion: '1.2.3', files: [] }, builtManifest: current,
|
|
83
|
+
checkedManifest: current, payloadManifest: current,
|
|
84
|
+
baseTag: { name: 'v1.2.3', exists: false, repoHasTags: true },
|
|
85
|
+
});
|
|
86
|
+
assert.equal(result.ok, false);
|
|
87
|
+
assert.match(result.errors.join('\n'), /1\.2\.3 is still awaiting-tag/);
|
|
88
|
+
assert.match(result.errors.join('\n'), /v1\.2\.3/);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('a bump passes once the previous release carries its tag', () => {
|
|
92
|
+
const current = { kitVersion: '1.3.0', files: [file('new.md', 'one')] };
|
|
93
|
+
const result = assessRelease({
|
|
94
|
+
baseVersion: '1.2.3', currentVersion: '1.3.0',
|
|
95
|
+
baseManifest: { kitVersion: '1.2.3', files: [] }, builtManifest: current,
|
|
96
|
+
checkedManifest: current, payloadManifest: current,
|
|
97
|
+
baseTag: { name: 'v1.2.3', exists: true, repoHasTags: true },
|
|
98
|
+
});
|
|
99
|
+
assert.equal(result.ok, true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('a never-tagged repository is not blocked from its first release', () => {
|
|
103
|
+
const current = { kitVersion: '0.1.0', files: [file('new.md', 'one')] };
|
|
104
|
+
const result = assessRelease({
|
|
105
|
+
baseVersion: '0.0.0', currentVersion: '0.1.0',
|
|
106
|
+
baseManifest: { kitVersion: '0.0.0', files: [] }, builtManifest: current,
|
|
107
|
+
checkedManifest: current, payloadManifest: current,
|
|
108
|
+
baseTag: { name: 'v0.0.0', exists: false, repoHasTags: false },
|
|
109
|
+
});
|
|
110
|
+
assert.equal(result.ok, true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('an untagged base blocks nothing while the version stays put', () => {
|
|
114
|
+
const same = { kitVersion: '1.2.3', files: [file('skill.md', 'one')] };
|
|
115
|
+
const result = assessRelease({
|
|
116
|
+
baseVersion: '1.2.3', currentVersion: '1.2.3',
|
|
117
|
+
baseManifest: same, builtManifest: same, checkedManifest: same, payloadManifest: same,
|
|
118
|
+
baseTag: { name: 'v1.2.3', exists: false, repoHasTags: true },
|
|
119
|
+
});
|
|
120
|
+
assert.equal(result.ok, true);
|
|
121
|
+
});
|
|
122
|
+
|
|
78
123
|
test('actual npm payload drift is blocked even when checked and built manifests match', () => {
|
|
79
124
|
const scrubbed = { kitVersion: '1.2.3', files: [file('skill.md', 'scrubbed')] };
|
|
80
125
|
const result = assessRelease({
|
|
@@ -99,9 +99,13 @@ export function npmTarballFilename(name, version) {
|
|
|
99
99
|
return `${name.replace(/^@/, '').replaceAll('/', '-')}-${version}.tgz`;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
async function packedTarball(run, spec, directory, repoRoot) {
|
|
102
|
+
async function packedTarball(run, spec, directory, repoRoot, { preferOnline = false } = {}) {
|
|
103
|
+
// A stale local packument answers ETARGET for a version that IS published.
|
|
104
|
+
// Read through it for registry specs: the status would otherwise report
|
|
105
|
+
// `awaiting-npm` for a released package and invite a second publish.
|
|
106
|
+
const cachePolicy = preferOnline ? ['--prefer-online'] : [];
|
|
103
107
|
const { stdout } = await run(
|
|
104
|
-
'npm', ['pack', spec, '--json', '--pack-destination', directory], { cwd: repoRoot },
|
|
108
|
+
'npm', ['pack', spec, '--json', '--pack-destination', directory, ...cachePolicy], { cwd: repoRoot },
|
|
105
109
|
);
|
|
106
110
|
const result = JSON.parse(stdout);
|
|
107
111
|
if (!Array.isArray(result) || result.length !== 1 || !result[0].filename) {
|
|
@@ -120,7 +124,7 @@ function releaseReaders(context) {
|
|
|
120
124
|
async function npm(identity) {
|
|
121
125
|
try {
|
|
122
126
|
state.npmTarball = await packedTarball(
|
|
123
|
-
run, `${identity.name}@${identity.version}`, scratch, repoRoot,
|
|
127
|
+
run, `${identity.name}@${identity.version}`, scratch, repoRoot, { preferOnline: true },
|
|
124
128
|
);
|
|
125
129
|
return await releaseIdentityFromTarball(state.npmTarball);
|
|
126
130
|
} catch (error) {
|
|
@@ -170,6 +170,28 @@ test('post-merge status inspection is read-only and reports the reconstructable
|
|
|
170
170
|
assert.deepEqual(fixture.events, ['read npm', 'read github']);
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
+
test('the registry read bypasses the local packument cache, the local pack does not', async () => {
|
|
174
|
+
const calls = [];
|
|
175
|
+
const run = async (command, args) => {
|
|
176
|
+
calls.push({ command, args });
|
|
177
|
+
throw Object.assign(new Error('stubbed pack'), { stderr: 'stubbed pack' });
|
|
178
|
+
};
|
|
179
|
+
const probe = await createCommandAdapter({ repoRoot: REPO, run });
|
|
180
|
+
try {
|
|
181
|
+
await assert.rejects(() => probe.local());
|
|
182
|
+
await assert.rejects(() => probe.npm(identity));
|
|
183
|
+
} finally { await probe.dispose(); }
|
|
184
|
+
|
|
185
|
+
const [localPack, registryPack] = calls;
|
|
186
|
+
// A stale packument makes `npm pack <name>@<version>` answer ETARGET for a
|
|
187
|
+
// version that IS published — the status would then claim awaiting-npm and
|
|
188
|
+
// invite a second publish. The local pack must NOT reach for the registry.
|
|
189
|
+
assert.ok(registryPack.args.includes('--prefer-online'), 'registry read must bypass the cache');
|
|
190
|
+
assert.ok(registryPack.args.includes(`${identity.name}@${identity.version}`));
|
|
191
|
+
assert.ok(!localPack.args.includes('--prefer-online'), 'local pack must stay offline');
|
|
192
|
+
assert.ok(localPack.args.includes('.'));
|
|
193
|
+
});
|
|
194
|
+
|
|
173
195
|
test('local Claude overrides cannot change the packed release identity or enter the tarball', async () => {
|
|
174
196
|
const fixture = await mkdtemp(join(tmpdir(), 'awkit-local-overrides-'));
|
|
175
197
|
await mkdir(join(fixture, '.claude'), { recursive: true });
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Issue-claim-on-pickup contract (#231).
|
|
3
|
+
|
|
4
|
+
A skill that ACCEPTS a tracked issue for building must leave a claim on the
|
|
5
|
+
issue itself before it builds — the local worktree/branch/PR guard only sees
|
|
6
|
+
the same machine, and only until someone pushes. The claim is what a second
|
|
7
|
+
session, a second machine, or a cloud agent can actually see.
|
|
8
|
+
|
|
9
|
+
Enforced here:
|
|
10
|
+
|
|
11
|
+
- `implement` and `diagnose` carry one byte-identical claim block on both
|
|
12
|
+
surfaces, and it sits before their first build instruction.
|
|
13
|
+
- The block covers all three legs: check for a foreign claim (STOP), plant a
|
|
14
|
+
claim naming branch + worktree, release it.
|
|
15
|
+
- `orchestrate-wave` claims each slice issue at builder-launch time and
|
|
16
|
+
releases only its own slice claims.
|
|
17
|
+
- Every tracker seed and this repo's own tracker layer document the concrete
|
|
18
|
+
claim / check / release operations, so the tracker-neutral skill prose
|
|
19
|
+
resolves to real commands.
|
|
20
|
+
|
|
21
|
+
Run: python3 scripts/test_issue_claim_contract.py
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import unittest
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
29
|
+
SURFACES = (".claude/skills", ".agents/skills")
|
|
30
|
+
PICKUP_SKILLS = ("implement", "diagnose")
|
|
31
|
+
START = "<!-- issue-claim:start -->"
|
|
32
|
+
END = "<!-- issue-claim:end -->"
|
|
33
|
+
MARKER = "<!-- agent-claim:"
|
|
34
|
+
TRACKER_DOC = "docs/agents/issue-tracker.md"
|
|
35
|
+
TRACKER_SEEDS = (
|
|
36
|
+
"setup-workflow/issue-tracker-github.md",
|
|
37
|
+
"setup-workflow/issue-tracker-gitlab.md",
|
|
38
|
+
"setup-workflow/issue-tracker-local.md",
|
|
39
|
+
)
|
|
40
|
+
# The first instruction that already assumes the build has started.
|
|
41
|
+
FIRST_BUILD_ANCHOR = {
|
|
42
|
+
"implement": "Use /tdd where possible",
|
|
43
|
+
"diagnose": "## Phase 1 — Build a feedback loop",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def skill_body(surface: str, skill: str) -> str:
|
|
48
|
+
return (ROOT / surface / skill / "SKILL.md").read_text(encoding="utf-8")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def claim_block(body: str) -> str:
|
|
52
|
+
return body.split(START, 1)[1].split(END, 1)[0]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def section(body: str, heading: str) -> str:
|
|
56
|
+
"""Return one second-level Markdown section, excluding the next one."""
|
|
57
|
+
start = body.index(heading) + len(heading)
|
|
58
|
+
end = body.find("\n## ", start)
|
|
59
|
+
return body[start:] if end == -1 else body[start:end]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ClaimBlockContract(unittest.TestCase):
|
|
63
|
+
def test_pickup_skills_carry_exactly_one_claim_block_on_both_surfaces(self):
|
|
64
|
+
for skill in PICKUP_SKILLS:
|
|
65
|
+
for surface in SURFACES:
|
|
66
|
+
with self.subTest(skill=skill, surface=surface):
|
|
67
|
+
body = skill_body(surface, skill)
|
|
68
|
+
self.assertEqual(body.count(START), 1)
|
|
69
|
+
self.assertEqual(body.count(END), 1)
|
|
70
|
+
self.assertLess(body.index(START), body.index(END))
|
|
71
|
+
|
|
72
|
+
def test_claim_block_is_byte_identical_across_skills_and_surfaces(self):
|
|
73
|
+
blocks = {
|
|
74
|
+
(skill, surface): claim_block(skill_body(surface, skill))
|
|
75
|
+
for skill in PICKUP_SKILLS
|
|
76
|
+
for surface in SURFACES
|
|
77
|
+
}
|
|
78
|
+
first = blocks[(PICKUP_SKILLS[0], SURFACES[0])]
|
|
79
|
+
for key, block in blocks.items():
|
|
80
|
+
with self.subTest(key=key):
|
|
81
|
+
self.assertEqual(block, first)
|
|
82
|
+
|
|
83
|
+
def test_claim_block_covers_check_plant_and_release(self):
|
|
84
|
+
block = claim_block(skill_body(SURFACES[0], PICKUP_SKILLS[0]))
|
|
85
|
+
# Check leg — a foreign claim stops the pickup, and is never removed.
|
|
86
|
+
self.assertIn("foreign claim", block)
|
|
87
|
+
self.assertIn("STOP", block)
|
|
88
|
+
self.assertIn("never delete a foreign claim", block)
|
|
89
|
+
# Plant leg — the marker must carry branch AND worktree, or a colliding
|
|
90
|
+
# session cannot find the work in progress.
|
|
91
|
+
self.assertIn(MARKER, block)
|
|
92
|
+
self.assertIn("branch=", block)
|
|
93
|
+
self.assertIn("worktree=", block)
|
|
94
|
+
# Release leg — the PR supersedes it; an abandoned claim is removed.
|
|
95
|
+
self.assertIn("supersedes", block)
|
|
96
|
+
self.assertIn("abandon", block)
|
|
97
|
+
|
|
98
|
+
def test_claim_block_resolves_through_the_tracker_layer_with_a_fallback(self):
|
|
99
|
+
block = claim_block(skill_body(SURFACES[0], PICKUP_SKILLS[0]))
|
|
100
|
+
self.assertIn(TRACKER_DOC, block)
|
|
101
|
+
self.assertIn("fall back", block)
|
|
102
|
+
|
|
103
|
+
def test_claim_is_planted_before_the_first_build_instruction(self):
|
|
104
|
+
for skill in PICKUP_SKILLS:
|
|
105
|
+
for surface in SURFACES:
|
|
106
|
+
with self.subTest(skill=skill, surface=surface):
|
|
107
|
+
body = skill_body(surface, skill)
|
|
108
|
+
self.assertLess(
|
|
109
|
+
body.index(END), body.index(FIRST_BUILD_ANCHOR[skill])
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class OrchestrateWaveSliceClaimContract(unittest.TestCase):
|
|
114
|
+
def test_dispatch_claims_each_slice_issue_at_builder_launch(self):
|
|
115
|
+
for surface in SURFACES:
|
|
116
|
+
with self.subTest(surface=surface):
|
|
117
|
+
dispatch = section(
|
|
118
|
+
skill_body(surface, "orchestrate-wave"),
|
|
119
|
+
"## Phase 2 — Dispatch one wave in parallel",
|
|
120
|
+
)
|
|
121
|
+
self.assertIn(MARKER, dispatch)
|
|
122
|
+
self.assertIn("slice issue", dispatch)
|
|
123
|
+
self.assertIn("branch=", dispatch)
|
|
124
|
+
self.assertIn("worktree=", dispatch)
|
|
125
|
+
|
|
126
|
+
def test_wave_claim_is_not_treated_as_a_slice_claim(self):
|
|
127
|
+
for surface in SURFACES:
|
|
128
|
+
with self.subTest(surface=surface):
|
|
129
|
+
dispatch = section(
|
|
130
|
+
skill_body(surface, "orchestrate-wave"),
|
|
131
|
+
"## Phase 2 — Dispatch one wave in parallel",
|
|
132
|
+
)
|
|
133
|
+
self.assertIn("wave claim", dispatch)
|
|
134
|
+
|
|
135
|
+
def test_cleanup_releases_only_this_runs_slice_claims(self):
|
|
136
|
+
for surface in SURFACES:
|
|
137
|
+
with self.subTest(surface=surface):
|
|
138
|
+
cleanup = section(
|
|
139
|
+
skill_body(surface, "orchestrate-wave"),
|
|
140
|
+
"## Phase 6 — Cleanup + close",
|
|
141
|
+
)
|
|
142
|
+
self.assertIn("slice claim", cleanup)
|
|
143
|
+
self.assertIn("this run", cleanup)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TrackerLayerClaimOperations(unittest.TestCase):
|
|
147
|
+
def documents(self) -> dict[str, str]:
|
|
148
|
+
docs = {TRACKER_DOC: (ROOT / TRACKER_DOC).read_text(encoding="utf-8")}
|
|
149
|
+
for surface in SURFACES:
|
|
150
|
+
for seed in TRACKER_SEEDS:
|
|
151
|
+
path = ROOT / surface / seed
|
|
152
|
+
docs[str(path.relative_to(ROOT))] = path.read_text(encoding="utf-8")
|
|
153
|
+
return docs
|
|
154
|
+
|
|
155
|
+
def test_every_tracker_layer_defines_claim_check_and_release(self):
|
|
156
|
+
for name, body in self.documents().items():
|
|
157
|
+
with self.subTest(document=name):
|
|
158
|
+
self.assertIn("## Pickup claim", body)
|
|
159
|
+
claim = section(body, "## Pickup claim")
|
|
160
|
+
self.assertIn(MARKER, claim)
|
|
161
|
+
self.assertIn("branch=", claim)
|
|
162
|
+
self.assertIn("worktree=", claim)
|
|
163
|
+
# a read side (detect a foreign claim) and a release side
|
|
164
|
+
self.assertIn("Check", claim)
|
|
165
|
+
self.assertIn("Release", claim)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
unittest.main()
|
|
@@ -95,6 +95,14 @@ BEHAVIORAL_PARITY = {
|
|
|
95
95
|
"Program-PRD",
|
|
96
96
|
"program sync",
|
|
97
97
|
),
|
|
98
|
+
"program authorization continuity": (
|
|
99
|
+
"explicit whole-Program mandate",
|
|
100
|
+
"all planned Waves",
|
|
101
|
+
"Do not re-ask at each Wave boundary",
|
|
102
|
+
"gated action",
|
|
103
|
+
"meaningful authorized safe work",
|
|
104
|
+
"must not be marked `blocked`",
|
|
105
|
+
),
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
|
|
@@ -175,7 +183,11 @@ class OrchestrateWaveContract(unittest.TestCase):
|
|
|
175
183
|
):
|
|
176
184
|
self.assertIn(fragment, self.skill)
|
|
177
185
|
|
|
178
|
-
|
|
186
|
+
# Skeleton-size ratchet: detail belongs in references/, not here. 345 ->
|
|
187
|
+
# 360 carried the sticky Program-authorization rule; 360 -> 368 adds the
|
|
188
|
+
# per-slice pickup claim (#231). Both are phase-level responsibilities
|
|
189
|
+
# the reference files cannot carry — they gate dispatch itself.
|
|
190
|
+
self.assertLessEqual(len(self.skill.splitlines()), 368)
|
|
179
191
|
|
|
180
192
|
def test_registry_ownership_distinguishes_safe_from_eager_registries(self):
|
|
181
193
|
prose = " ".join(self.skill.split())
|