@erclx/aitk 3.35.1 โ 3.36.1
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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-pr-review/SKILL.md +42 -4
- package/docs/operating-model.md +1 -1
- package/governance/rules/ci/700-ci-workflow.md +44 -0
- package/governance/rules/core/070-planning.md +1 -0
- package/governance/stacks/base.toml +5 -3
- package/package.json +1 -1
- package/standards/rule.md +2 -2
|
@@ -18,6 +18,10 @@ none, so the most recent comment's heading reports whether any work is owed.
|
|
|
18
18
|
Owed is a finding at any severity, a Testing question, or a reviewer request nobody has answered, defined once at Step 4. Every pass is this skill, and which one it is gets detected from
|
|
19
19
|
the thread rather than named by the caller.
|
|
20
20
|
|
|
21
|
+
One live verdict stands at a time. A pass that would repeat a standing
|
|
22
|
+
close-out rewrites that comment in place rather than posting beside it, so a
|
|
23
|
+
reader scanning the thread finds the current verdict where the last one sat.
|
|
24
|
+
|
|
21
25
|
## Guards
|
|
22
26
|
|
|
23
27
|
- If no open PR resolves for the target branch via `gh pr view`, stop: `โ No open PR to review. Open one first, or use /claude-review for local changes.`
|
|
@@ -39,12 +43,14 @@ Coding standards from `.claude/rules/` are auto-loaded by Claude Code.
|
|
|
39
43
|
|
|
40
44
|
## Step 2: scope the read
|
|
41
45
|
|
|
42
|
-
Find the commit the last pass covered:
|
|
46
|
+
Find the commit the last pass covered and the verdict it posted:
|
|
43
47
|
|
|
44
48
|
```bash
|
|
45
|
-
gh pr view <number> --json reviews --jq '[.reviews[] | select(.body // "" | split("\n")[0] | rtrimstr("\r") | . == "## Review" or . == "## Review closed")] | last | .commit.oid'
|
|
49
|
+
gh pr view <number> --json reviews --jq '[.reviews[] | select(.body // "" | split("\n")[0] | rtrimstr("\r") | . == "## Review" or . == "## Review closed")] | last | select(. != null) | ((.commit.oid // "") + "\t" + (.body | split("\n")[0] | rtrimstr("\r")))'
|
|
46
50
|
```
|
|
47
51
|
|
|
52
|
+
The two fields are `<prior-oid>` and `<prior-heading>`. The commit scopes the read below and the heading feeds the repeat guard at the end of this step, so one query answers both rather than two reads of the same review. Keep the `select(. != null)` guard, since the string concatenation aborts jq on the null an empty selection returns, and an aborted command reaches the session as an error rather than as the empty result the first-pass branch reads.
|
|
53
|
+
|
|
48
54
|
Match the first line for equality against the two headings this skill posts. A prefix test also matches `## Review response` and any heading merely starting with those words, which would scope the pass to whatever commit that comment carried. The `\r` trim covers a body composed in the GitHub web editor, which stores CRLF.
|
|
49
55
|
|
|
50
56
|
An empty result is a first pass. Read the whole change:
|
|
@@ -89,6 +95,12 @@ A non-empty result carries the comment id Step 4 needs for the third filename se
|
|
|
89
95
|
|
|
90
96
|
A `## Post-review findings` reply carries no argued finding behind it, since it asserts a new defect rather than answering one, and this pass is its first independent reader. Restating it as a finding without opening anything is repeating the worker's claim rather than checking it. Read the file the comment names at `<headRefOid>`, the same `git show <headRefOid>:<path>` read Step 3 already runs to confirm a ticked box, and confirm the defect before it becomes a finding of this pass's own.
|
|
91
97
|
|
|
98
|
+
A moved head has its own way to add nothing, which the ancestor test cannot see. When `<prior-heading>` reads `## Review closed`, the standing verdict already reports the branch clear, so a pass over the new commits that raises nothing posts a comment saying what the one above it said. Two close-outs landed on `#1201` that way on 2026-08-28, and the operator caught the pair rather than any check.
|
|
99
|
+
|
|
100
|
+
The producing shape is narrow. A prior pass tells the author a change is their own call, the author makes it, and the delta reaching the next pass has nothing left to say by construction.
|
|
101
|
+
|
|
102
|
+
Carry `<prior-heading>` into Step 4 and run Step 3 unchanged. This guard cannot stop the pass the way the unchanged-head stop above does, because that stop reads an empty range and this one turns on what the pass carries, which is Step 3's output. Deciding ahead of the review would swallow the pass that does find something, and that is the costlier error, so the rule is stated beside its sibling and executed where the heading is picked.
|
|
103
|
+
|
|
92
104
|
Read each changed file in scope. Skip deleted files. Run reads in parallel.
|
|
93
105
|
|
|
94
106
|
## Step 3: review
|
|
@@ -121,11 +133,11 @@ Use severity: `critical` (blocks merge), `should-fix` (fix before merge), `minor
|
|
|
121
133
|
|
|
122
134
|
## Step 4: post to the PR
|
|
123
135
|
|
|
124
|
-
Write the comment to `.claude/.tmp/pr-review/body-<number>-<short-sha>.md
|
|
136
|
+
Write the comment to `.claude/.tmp/pr-review/body-<number>-<short-sha>.md`, which the rest of this step calls `<body-file>`. The PR number stops two sessions reviewing different pull requests from overwriting each other between the write and the post. The head commit stops a second pass overwriting the first one's body, and leaves the folder a record of which commit each review covered.
|
|
125
137
|
|
|
126
138
|
Derive both segments from Step 1. Never pick a suffix by hand, and never reuse a name the folder already holds.
|
|
127
139
|
|
|
128
|
-
When `<prior-oid>` from Step 2 equals `headRefOid`, the head repeats and the folder already holds `body-<number>-<short-sha>.md`. Add a third segment taking the id of the reply Step 2 resolved, giving `body-<number>-<short-sha>-r<comment-id>.md
|
|
140
|
+
When `<prior-oid>` from Step 2 equals `headRefOid`, the head repeats and the folder already holds `body-<number>-<short-sha>.md`. Add a third segment taking the id of the reply Step 2 resolved, giving `body-<number>-<short-sha>-r<comment-id>.md`, which is `<body-file>` on that path. That satisfies both prohibitions above rather than carving an exception into either. Step 2 already stopped the pass when that resolution came back empty, so reaching this line means the comment id is in hand.
|
|
129
141
|
|
|
130
142
|
The comment is a rendered-for-human GitHub surface, so load the `write-human` skill for voice and follow `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` for the banned words: cut editorializing, and keep every sentence load-bearing. Match this shape on a first pass:
|
|
131
143
|
|
|
@@ -208,6 +220,8 @@ Close the body with `๐ค Reviewed by Claude Code` on its own line so the review
|
|
|
208
220
|
|
|
209
221
|
Before posting, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against the body. The hook skips `.claude/.tmp/`, so this scan is the only gate on the published comment. A finding phrased against an internal phase label is what the label half of the scan catches here.
|
|
210
222
|
|
|
223
|
+
Do not run the command below when `<prior-heading>` from Step 2 reads `## Review closed` and this pass carries nothing owed. That pass replaces the standing comment rather than adding one, under `### A close-out that repeats the standing one` at the end of this step. Posting first and reaching that section afterward leaves two close-outs both naming the new head, which is worse than the pair the guard exists against.
|
|
224
|
+
|
|
211
225
|
```bash
|
|
212
226
|
gh pr review <number> --comment --body-file .claude/.tmp/pr-review/body-<number>-<short-sha>.md
|
|
213
227
|
```
|
|
@@ -234,6 +248,30 @@ A pass whose only content is a `## For the reviewer` block with every bullet ans
|
|
|
234
248
|
|
|
235
249
|
Post a close-out even when there is nothing to report. A review left with no closing comment reads as one nobody answered.
|
|
236
250
|
|
|
251
|
+
### A close-out that repeats the standing one
|
|
252
|
+
|
|
253
|
+
When `<prior-heading>` from Step 2 reads `## Review closed` and this pass carries nothing owed, the thread already holds this verdict. Replace the standing close-out rather than posting a second one beside it.
|
|
254
|
+
|
|
255
|
+
Resolve its numeric id. `gh pr view --json reviews` carries a GraphQL node id under `id`, which no REST route accepts, so read the id off the REST listing instead:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
gh api repos/{owner}/{repo}/pulls/<number>/reviews --jq '[.[] | select((.body // "") | split("\n")[0] | rtrimstr("\r") == "## Review closed")] | last | .id'
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Write the replacement body to `<body-file>`, the name Step 4 already derived at the top of this step, keeping the heading and the footer and naming what this pass covered on the scope line in place of what the old one covered. Derive that name the same way whichever path reached here, since the guard reads `<prior-heading>` alone and a repeated head resolves the third segment as usual. The folder then gains a record of every covered head rather than losing the one the standing comment named. Then replace the comment:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
gh api -X PUT repos/{owner}/{repo}/pulls/<number>/reviews/<review-id> -F body=@<body-file>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`PUT` keeps the comment's timestamp and its position in the thread, so the verdict stays where a reader already found it and the thread gains no second entry. A submitted review cannot be deleted, which is why this rewrites the standing comment rather than posting a fresh one.
|
|
268
|
+
|
|
269
|
+
The guard fires on `## Review closed` alone. Two open passes carry different findings and both are worth reading, so a repeated `## Review` posts normally. A pass carrying anything owed posts normally too, under `## Review`, which is what keeps a finding raised after a close-out from being swallowed by the guard that exists for a silent one.
|
|
270
|
+
|
|
271
|
+
What the rewrite costs is the review's `commit.oid`, which `PUT` leaves at the commit the standing close-out was first submitted against. Step 2's `<prior-oid>` and the prior commit `poll.sh` derives both read that field, so the next pass reads a range wider than its delta and its commit count spans back to the pinned commit rather than covering the delta.
|
|
272
|
+
|
|
273
|
+
The poll's `SEEN` branch is the sharper half. It fires on `prior` equalling the head, which a pinned `commit.oid` never reaches, so an out-of-band pass over this pull request reports as `MOVED` for the rest of its life and never as already covered. That is one wasted dispatch per head move rather than a repeating one, since the poll gates its report on the head it wrote to its own baseline and not on `prior`. Each cost errs toward more reading, so neither is repaired here.
|
|
274
|
+
|
|
237
275
|
## Step 5: output
|
|
238
276
|
|
|
239
277
|
```plaintext
|
package/docs/operating-model.md
CHANGED
|
@@ -40,7 +40,7 @@ One feature travels this path end to end.
|
|
|
40
40
|
2. Orchestrator checks the branch is unclaimed and the plan's file set is disjoint from every track in flight, then dispatches a background worker with `claude --bg` against the plan, naming the branch and the model on the launch rather than leaving the worker to derive either. No count caps how many run at once. The branch travels as the argument to the worker's own worktree call, which is the one place the name is read rather than inferred. It falls back to naming the invocation for a human to run through `claude-worktree` and `claude-autoship` when the check refuses, the sets overlap, or a stated reason serializes the plan behind a track already in flight. Either way, the worker enters its own worktree, builds, self-checks, opens a PR, and stops at the PR boundary.
|
|
41
41
|
3. Orchestrator reviews the PR with `claude-pr-review` and posts findings to it.
|
|
42
42
|
4. Orchestrator tells the session holding that branch to run `claude-address-review` once the pass posted a finding at any severity, resolving the target then with `aitk sessions list --branch` and reporting the invocation for the human when no live session holds it. The worker addresses the findings, rebases onto `origin/main` when a sibling landed first and left the branch unable to merge, then pushes a follow-up. A pass carrying only minor findings dispatches too, since the grade runs low often enough that a floor at should-fix loses fixes a worker would have made. `claude-pr-review` states that threshold and the heading follows it, so an open heading is itself the signal to send.
|
|
43
|
-
5. Orchestrator closes the review out with `claude-pr-review` again. The second pass reads only the commits the follow-up added, or the worker's response alone when the follow-up added none, and posts under `## Review` when it finds anything and under `## Review closed` when it finds nothing, so a reader learns from the heading whether work is still owed and takes the merge decision from the counts on the line under it. Repeat from step 4 until a pass closes the review.
|
|
43
|
+
5. Orchestrator closes the review out with `claude-pr-review` again. The second pass reads only the commits the follow-up added, or the worker's response alone when the follow-up added none, and posts under `## Review` when it finds anything and under `## Review closed` when it finds nothing, so a reader learns from the heading whether work is still owed and takes the merge decision from the counts on the line under it. A pass finding nothing where a close-out already stands rewrites that comment to cover what it read rather than posting a second one, so the thread carries one live verdict. Repeat from step 4 until a pass closes the review.
|
|
44
44
|
6. The human reads the result and merges. The orchestrator tells any trailing worker whose branch shares a seam with the merged one to run `claude-address-review`, which rebases whether or not the review left anything open.
|
|
45
45
|
|
|
46
46
|
There is no loop construct here. Each worker is a single build that halts at the
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce GitHub Actions job naming, granularity, dependency wiring, and bun pinning
|
|
3
|
+
paths:
|
|
4
|
+
- '.github/workflows/**'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CI workflow standards
|
|
8
|
+
|
|
9
|
+
## Triggers and pinning
|
|
10
|
+
|
|
11
|
+
- Include `workflow_dispatch` alongside the primary trigger on every workflow.
|
|
12
|
+
- Pin every action to a major version tag. Never `@latest` or `@main`.
|
|
13
|
+
- Run every job on `ubuntu-latest`.
|
|
14
|
+
|
|
15
|
+
## Job naming
|
|
16
|
+
|
|
17
|
+
- Name every job as an emoji followed by a title, such as `๐ก๏ธ Checks`, `๐งช Unit Tests`, `๐ฆ Build Check`, `๐ญ E2E Tests`, or `๐ Deploy`.
|
|
18
|
+
|
|
19
|
+
## Job granularity
|
|
20
|
+
|
|
21
|
+
- Fold static analysis, unit tests, and build into one job while the gate runs under two minutes end to end.
|
|
22
|
+
- Split them into parallel jobs once a run log puts the gate past two minutes.
|
|
23
|
+
- Give E2E, release, and deploy a job each from the start.
|
|
24
|
+
|
|
25
|
+
## Job dependencies
|
|
26
|
+
|
|
27
|
+
- Use `needs` for a data dependency, where a job consumes another job's artifact, or for a job whose cost is prohibitive against the gate in front of it. Leave every other job unwired so it runs in parallel.
|
|
28
|
+
- Gate E2E on the job that uploads the build artifact.
|
|
29
|
+
- Gate release and deploy on E2E.
|
|
30
|
+
- Emit a deploy, publish, or release job with a placeholder step and name what the caller fills in. Never guess a deploy command.
|
|
31
|
+
|
|
32
|
+
## Artifacts
|
|
33
|
+
|
|
34
|
+
- Upload an artifact on `if: failure()` alone, with `retention-days: 7`.
|
|
35
|
+
|
|
36
|
+
## Bun stack
|
|
37
|
+
|
|
38
|
+
- Use `oven-sh/setup-bun@v2` with `bun-version: latest`.
|
|
39
|
+
- Install with `bun install --frozen-lockfile`.
|
|
40
|
+
- Key the Playwright browser cache on the Playwright version string, never a static key.
|
|
41
|
+
|
|
42
|
+
## Authority
|
|
43
|
+
|
|
44
|
+
- Load the `aitk:ci-workflow` skill for the workflow template and the per-project adaptation. Report it rather than proceeding silently when the skill does not resolve.
|
|
@@ -14,4 +14,5 @@ description: Enforce planning standards before implementation
|
|
|
14
14
|
- Write or update tests as part of every implementation plan.
|
|
15
15
|
- Write the test for a behavior before the code that implements it. Confirm visual output after implementing it, not before.
|
|
16
16
|
- Run `aitk gov test-order` before shipping a branch. Fix what it names as reaching history ahead of its test.
|
|
17
|
+
- Load the `aitk:systematic-debugging` skill before proposing a fix for a failing test, a surfaced bug, or behavior nobody has explained yet, and report it rather than proceeding silently when the skill does not resolve.
|
|
17
18
|
- Do not modify code without a confirmed plan.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
extends = ""
|
|
2
|
-
# All
|
|
2
|
+
# All four folders whole. Every rule authored under them ships to a base
|
|
3
3
|
# consumer, so adding one is a single edit rather than a file and a name here.
|
|
4
4
|
# `snippets` carries the `@`-reference convention rule, which lost its only
|
|
5
|
-
# other delivery path when `aitk snippets install` retired.
|
|
6
|
-
|
|
5
|
+
# other delivery path when `aitk snippets install` retired. `ci` carries the
|
|
6
|
+
# workflow rule, whose glob is the only inbound route to the `ci-workflow`
|
|
7
|
+
# skill.
|
|
8
|
+
rules = ["core", "claude", "snippets", "ci", "120-bash"]
|
package/package.json
CHANGED
package/standards/rule.md
CHANGED
|
@@ -32,14 +32,14 @@ Write both when both apply. A rule stating the directive and a skill stating how
|
|
|
32
32
|
## Location
|
|
33
33
|
|
|
34
34
|
- Rules live at `.claude/rules/<subdirectory>/<n>-<slug>.md`
|
|
35
|
-
- Subdirectories group by domain: `core/`, `lang/`, `framework/`, `lib/`, `ui/`, `claude/`
|
|
35
|
+
- Subdirectories group by domain: `core/`, `lang/`, `framework/`, `lib/`, `ui/`, `claude/`, `snippets/`, `ci/`
|
|
36
36
|
- `<n>` is a number from the band reserved for the rule's source and `<slug>` is a one-to-three-word kebab topic
|
|
37
37
|
- A rule the project authored itself lives at `.claude/rules/project/<subdirectory>/<n>-<slug>.md`, keeping the same subdirectory names
|
|
38
38
|
- Give every rule a numeric prefix. A bare-word filename reads as a folder name where a stack names its rules, so a rule without one is unreachable from a stack entry.
|
|
39
39
|
|
|
40
40
|
## Reserved numbers
|
|
41
41
|
|
|
42
|
-
`000-899` is reserved for a rule set that ships to targets. `000-599` is what the six subdirectories above already divide, at 100 per subdirectory, and `600-899` is
|
|
42
|
+
`000-899` is reserved for a rule set that ships to targets. `000-599` is what the first six subdirectories above already divide, at 100 per subdirectory. `ci/` was the first to draw from the headroom past that, taking `700-799`, and `600-699` with `800-899` is what remains for a subdirectory a shipped set has not added yet.
|
|
43
43
|
|
|
44
44
|
A project-authored rule takes `900-999`, one sequence across every subdirectory under `.claude/rules/project/`. Scanning for a free number instead is what fails, because a shipped set fills its own band release by release, so what reads as free today is what a later release lands on. One target authored `claude/561-self-check.md` on a day nothing shipped at 561, met `claude/561-teach.md` on its next install, and now reads two numbers differently from everywhere else.
|
|
45
45
|
|