@erclx/aitk 3.11.0 → 3.11.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.11.0",
4
+ "version": "3.11.1",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -194,3 +194,13 @@ Rebased PR #<number> onto origin/main. <N> files resolved by hand, <N> rebuilt b
194
194
  ```
195
195
 
196
196
  Do not merge. Hand back to the orchestrator for re-review.
197
+
198
+ ## Post-review findings
199
+
200
+ Not everything worth reaching the reviewing session surfaces inside the numbered flow above. A worker that settled a risk, filed a follow-up, or found something else worth reporting after Step 7 already closed the review posts it directly rather than waiting on a review pass that has nothing left to trigger it. Write the body the way Step 6 writes a reply: load `write-human` for voice, follow `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` for the banned words, and run the `${CLAUDE_SKILL_DIR}/../../standards/publish.md` scan before posting.
201
+
202
+ Open with `## Post-review findings` rather than `## Review response`, since nothing on the thread is being answered. `claude-pr-review` states the full heading set this belongs to and routes it the same as a response: `claude-orchestrate`'s poll picks it up and sends the reviewing session back for a pass. Close the body with `🤖 Addressed by Claude Code` on its own line, matching the reply's footer.
203
+
204
+ ```bash
205
+ gh pr comment <number> --body-file .claude/.tmp/address-review/reply-<number>.md
206
+ ```
@@ -36,6 +36,7 @@ Poll GitHub for pull request movement by running <POLL_SCRIPT>, then act on what
36
36
  - SEEN: report it and stop. A pass already covers that head, whether it arrived out of band or before the poll first saw the pull request, so no review follows.
37
37
  - STALLED: read the last pass and report what it carried. The pass has sat open for hours with nothing following it, so a worker mid-task is already ruled out and the dispatch either never went out or the session holding it is gone. Confirm and re-send it under the dispatch rule below, whatever grades the pass carried. Do not re-run a review to correct the heading, since a pass on an unchanged head with no response behind it stops by design.
38
38
  - CONFLICT: report it and stop. The branch owner rebases, not this session.
39
+ - UNMATCHED: report it and stop. A comment posted under a heading outside the known set reaches nobody automatically, so a person decides whether to answer it by hand or the set needs a sixth heading.
39
40
  - GONE: report it, then sweep the board by invoking the aitk:claude-orchestrate skill and following its queue-refill sweep.
40
41
  - A line starting `poll:`: report it verbatim and treat that pull request as unread this run. It is a failed query, not a state.
41
42
  - Nothing changed: say exactly "No movement." and nothing else.
@@ -51,7 +52,9 @@ The script exits non-zero and classifies nothing when the open pull request list
51
52
 
52
53
  The baseline lives at `.claude/.tmp/pr-poll/baseline.txt` under the main worktree root and is per-machine. A first run against a board already in flight reports each open pull request once before it settles.
53
54
 
54
- The four review headings the script matches are written by `claude-pr-review` and `claude-address-review`. A project that posts its reviews under different headings edits the three jq filters in the script to match, or every pull request reads as never reviewed.
55
+ The five review headings the script matches are written by `claude-pr-review` and `claude-address-review`, and the whole set is stated once in the first. A project that posts its reviews under different headings edits the jq filters in the script to match, or every pull request reads as never reviewed.
56
+
57
+ `UNMATCHED` is what a heading outside the five reaches, carried the same way `RESPONSE` is: a rising count against the baseline is what is new to this script, and the message names the heading so a person can tell whether to answer it by hand or add it to the set. It fires on a tracked pull request only, since a first sighting reports `SEEN` or `OPENED` and takes whatever count already sits on the thread as its starting baseline rather than flagging history the poll never watched.
55
58
 
56
59
  `RESPONSE` is qualified by recency as well as by count, so it means a reply the last pass has not already answered rather than one this script has not seen before. A worker answers a finding and the reviewing session posts its close-out seconds later, which is the ordinary handback rather than a race, so a count on its own reported the answered thread on the next run and the re-review it routed to stopped at its own guard. The state now fires when the newest reply is stamped later than the last pass, and on a pull request carrying no pass at all, which is a worker talking to nobody and worth the turn. A reply landing inside the same second as the pass is dropped, matching the comparison `claude-pr-review` makes on the same two fields.
57
60
 
@@ -32,16 +32,19 @@ if [ -z "$BASE_REF" ]; then
32
32
  fi
33
33
  BASE_BRANCH="${BASE_REF#origin/}"
34
34
 
35
- # These four strings are owned elsewhere and pinned here. `claude-pr-review`
36
- # writes `## Review` and `## Review closed`, and `claude-address-review` writes
37
- # `## Review response` and `## Rebase`. All three surfaces ship separately, so a
35
+ # These five strings are owned elsewhere and pinned here. `claude-pr-review`
36
+ # writes `## Review` and `## Review closed`, and states the full five-heading
37
+ # set once, beside the threshold it already states once. `claude-address-review`
38
+ # writes `## Review response`, `## Rebase`, and `## Post-review findings`, the
39
+ # last for a finding a worker produces after a close-out rather than in answer
40
+ # to one already on the thread. All three surfaces ship separately, so a
38
41
  # heading added in either skill breaks a test here that no check reaches across.
39
42
  #
40
- # Both families match on the first line alone so the two tests stay symmetric.
41
- # The reply family carries `## Rebase` because a run sent straight to the rebase
42
- # step posts under a heading deliberately kept outside the `## Review` family.
43
- # Widening one family without the other is what left the reply test narrow, so
44
- # a fifth heading is added here beside its sibling.
43
+ # Both families match on the first line alone so the tests stay symmetric. The
44
+ # reply family carries `## Rebase` and `## Post-review findings` beside
45
+ # `## Review response` because neither answers a comment already on the thread,
46
+ # which is why both were kept outside the `## Review` family rather than folded
47
+ # into it.
45
48
  JQ_LAST_REVIEWED_HEAD='
46
49
  [ .reviews[]
47
50
  | select((.body // "") | split("\n")[0] | rtrimstr("\r")
@@ -55,13 +58,37 @@ JQ_LAST_REVIEWED_HEAD='
55
58
  JQ_REPLY_STATE='
56
59
  [ .comments[]
57
60
  | select((.body // "") | split("\n")[0] | rtrimstr("\r")
58
- | . == "## Review response" or . == "## Rebase")
61
+ | . == "## Review response" or . == "## Rebase"
62
+ or . == "## Post-review findings")
59
63
  ] as $replies
60
64
  | ($replies | length | tostring)
61
65
  + " "
62
66
  + ([ $replies[] | .createdAt // empty | fromdateiso8601 | floor ]
63
67
  | max // 0 | tostring)
64
68
  '
69
+ # A comment matching neither family above is the gap this filter exists to
70
+ # surface rather than absorb: a worker inventing a sixth heading used to reach
71
+ # this script as silence, indistinguishable from no comment at all. The count
72
+ # is read the same way the reply count is, a rising value against the baseline
73
+ # being new to this script, and the newest heading's text rides along for the
74
+ # report. It carries its spaces intact rather than encoded, since it is the
75
+ # last field on both sides of the pipe: `${unmatched_state#* }` below strips
76
+ # only the first space, and `read -r` further down hands its last named
77
+ # variable the rest of the line whole. Neither reads a heading's own spaces
78
+ # as a field separator, so a heading round-trips verbatim into the report a
79
+ # person reads when deciding whether to answer it by hand.
80
+ JQ_UNMATCHED_STATE='
81
+ [ .comments[]
82
+ | (.body // "") | split("\n")[0] | rtrimstr("\r")
83
+ | select(startswith("## "))
84
+ | select(. != "## Review" and . != "## Review closed"
85
+ and . != "## Review response" and . != "## Rebase"
86
+ and . != "## Post-review findings")
87
+ ] as $unclassified
88
+ | ($unclassified | length | tostring)
89
+ + " "
90
+ + (($unclassified | last) // "none")
91
+ '
65
92
 
66
93
  # `claude-pr-review` states the threshold and posts `## Review` exactly when a
67
94
  # pass carries a finding, so the heading of the last review is what says whether
@@ -176,6 +203,11 @@ snapshot() {
176
203
  # Three space-separated fields, so the line below carries them as its own
177
204
  # sixth, seventh, and eighth rather than needing a split.
178
205
  review_state=$(jq -r "$JQ_LAST_REVIEW_STATE" <<<"$payload")
206
+ # Split the same way as the reply state, carried as the line's tenth and
207
+ # eleventh fields.
208
+ unmatched_state=$(jq -r "$JQ_UNMATCHED_STATE" <<<"$payload")
209
+ unmatched_count=${unmatched_state%% *}
210
+ unmatched_heading=${unmatched_state#* }
179
211
 
180
212
  # `gh pr view --json mergeable` reports UNKNOWN until GitHub finishes
181
213
  # computing it, which is exactly when a poll asks. merge-tree answers
@@ -194,7 +226,7 @@ snapshot() {
194
226
  merges=conflict
195
227
  fi
196
228
 
197
- echo "$n $head ${prior:-none} $resp $merges $review_state $reply_at"
229
+ echo "$n $head ${prior:-none} $resp $merges $review_state $reply_at $unmatched_count $unmatched_heading"
198
230
  done
199
231
  }
200
232
 
@@ -211,7 +243,7 @@ CHANGED=0
211
243
  # would fire on every later run and the board would never read "No movement."
212
244
  FINAL=""
213
245
 
214
- while read -r n head prior resp merges heading age pass_at reply_at; do
246
+ while read -r n head prior resp merges heading age pass_at reply_at unmatched_count unmatched_heading; do
215
247
  [ -z "$n" ] && continue
216
248
  state=$heading
217
249
  old=$(grep "^$n " "$STATE" || true)
@@ -229,7 +261,7 @@ while read -r n head prior resp merges heading age pass_at reply_at; do
229
261
  echo "OPENED #$n at ${head:0:7}, $merges against $BASE_BRANCH"
230
262
  fi
231
263
  CHANGED=1
232
- FINAL+="$n $head $prior $resp $merges $state $age"$'\n'
264
+ FINAL+="$n $head $prior $resp $merges $state $unmatched_count"$'\n'
233
265
  continue
234
266
  fi
235
267
  old_head=$(echo "$old" | cut -d' ' -f2)
@@ -240,6 +272,7 @@ while read -r n head prior resp merges heading age pass_at reply_at; do
240
272
  # age decides the rest, so the first run after an upgrade needs no history and
241
273
  # classifies a thread already past the threshold rather than waiting a run.
242
274
  old_heading=$(echo "$old" | cut -d' ' -f6)
275
+ old_unmatched=$(echo "$old" | cut -d' ' -f7)
243
276
 
244
277
  # A conflict arrives from the base moving, not from the branch, so it is
245
278
  # reported on the transition rather than only when the head changes.
@@ -248,6 +281,19 @@ while read -r n head prior resp merges heading age pass_at reply_at; do
248
281
  CHANGED=1
249
282
  fi
250
283
 
284
+ # A rising count is what is new to this script, the same test RESPONSE
285
+ # below runs against the reply family. It fires on a tracked pull request
286
+ # only: a first sighting reports SEEN or OPENED and takes whatever count
287
+ # already sits on the thread as its starting baseline rather than flagging
288
+ # history retroactively. A carried line supplies neither, since
289
+ # carry_forward re-echoes the shorter baseline shape rather than a full
290
+ # snapshot line, so both sides default to zero the way old_unmatched
291
+ # already does.
292
+ if [ "${unmatched_count:-0}" -gt "${old_unmatched:-0}" ]; then
293
+ echo "UNMATCHED #$n posted under '$unmatched_heading'"
294
+ CHANGED=1
295
+ fi
296
+
251
297
  if [ "$head" != "$old_head" ]; then
252
298
  if [ "$prior" = "none" ]; then
253
299
  echo "MOVED #$n -> ${head:0:7}, never reviewed"
@@ -312,7 +358,7 @@ while read -r n head prior resp merges heading age pass_at reply_at; do
312
358
  # re-enters next run and STALLED oscillates instead of reporting once.
313
359
  state=reported
314
360
  fi
315
- FINAL+="$n $head $prior $resp $merges $state"$'\n'
361
+ FINAL+="$n $head $prior $resp $merges $state $unmatched_count"$'\n'
316
362
  done <<<"$NEW"
317
363
 
318
364
  while read -r n _rest; do
@@ -170,7 +170,9 @@ A minor the dispatched worker declines is what needs a surface that survives the
170
170
 
171
171
  Read the state off the most recent review comment rather than off the presence of a closed one. A close-out does not close the pull request, so a commit pushed after it gets its own pass, and that pass reopens the review under `## Review` when it raises a finding of any grade.
172
172
 
173
- Both of this skill's headings anchor as a section distinct from human threads. Do not invent one beyond those two and the `## Review response` a sibling owns, and do not append the PR number, which GitHub already renders above the comment.
173
+ Both of this skill's headings anchor as a section distinct from human threads, and neither invents beyond what the whole set already states. That set is five headings across two families, stated here once so `claude-orchestrate`'s poll and every reply-posting skill cite it rather than carry a copy. The review family, `## Review` and `## Review closed`, belongs to this skill alone, and the reply family, `## Review response`, `## Rebase`, and `## Post-review findings`, belongs to `claude-address-review`.
174
+
175
+ The first reply heading answers a finding this skill posted, the second reports a stale branch resolved without one, and the third carries a finding a worker produces after a close-out rather than in answer to one already on the thread, since a finding produced late is still a finding. A comment posted under a heading outside these five reaches the poll as unclassified rather than as silence, so an invented sixth heading is a gap the next run reports instead of one it repeats. Do not append the PR number, which GitHub already renders above the comment.
174
176
 
175
177
  Name the scope in every summary line after the first pass, since a reader cannot otherwise tell a narrow read from a full one. When the fallback in Step 2 fired, replace the commit count with `Re-reviewed the full change, the prior pass's commit is no longer on the branch`.
176
178
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.11.0",
4
+ "version": "3.11.1",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {