@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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "3.35.1",
4
+ "version": "3.36.1",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -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`. 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.
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`. 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.
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
@@ -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 three folders whole. Every rule authored under them ships to a base
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
- rules = ["core", "claude", "snippets", "120-bash"]
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.35.1",
4
+ "version": "3.36.1",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
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 held for subdirectories a shipped set has not added yet.
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