@erclx/aitk 0.111.0 → 0.112.0

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": "0.111.0",
4
+ "version": "0.112.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -53,6 +53,8 @@ The baseline lives at `.claude/.tmp/pr-poll/baseline.txt` under the main worktre
53
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
55
 
56
+ `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
+
56
58
  `STALLED` is the one state the script derives from a heading rather than from a commit or a count, since `claude-pr-review` posts the open heading exactly when a dispatch is owed, per the threshold that skill states. The heading alone cannot carry it, because an open pass means a dispatch was owed and made, so the ordinary healthy thread is a worker still working. The age of that pass is the third test: the state fires when the open pass covers the head, nothing has followed it, and it is older than the `STALE_AFTER` seconds set at the top of the script. It reports once per entry and fires again after any commit or reply resets the thread. A project whose workers run longer than the default two hours raises that number.
57
59
 
58
60
  The state reaches every stalled dispatch, since one threshold governs the heading and the dispatch alike and a pass carrying anything posts the open heading. A minors-only pass therefore reports here on the same terms as a blocking one, which widens the state from what it caught while the two were split. It stays a heading test rather than a count test, so nothing here pins the summary line, which is a second string this script does not own.
@@ -48,11 +48,19 @@ JQ_LAST_REVIEWED_HEAD='
48
48
  | . == "## Review" or . == "## Review closed")
49
49
  ] | last | .commit.oid // empty
50
50
  '
51
- JQ_REPLY_COUNT='
51
+ # The count alone answers whether a reply is new to this script, which is not
52
+ # the same question as whether it is newer than the pass it answers. The stamp
53
+ # of the newest reply comes out of the same selection so the recency test costs
54
+ # no second filter, and it is the one this file compares against `submittedAt`.
55
+ JQ_REPLY_STATE='
52
56
  [ .comments[]
53
57
  | select((.body // "") | split("\n")[0] | rtrimstr("\r")
54
58
  | . == "## Review response" or . == "## Rebase")
55
- ] | length
59
+ ] as $replies
60
+ | ($replies | length | tostring)
61
+ + " "
62
+ + ([ $replies[] | .createdAt // empty | fromdateiso8601 | floor ]
63
+ | max // 0 | tostring)
56
64
  '
57
65
 
58
66
  # `claude-pr-review` states the threshold and posts `## Review` exactly when a
@@ -69,18 +77,28 @@ JQ_REPLY_COUNT='
69
77
  # script runs wherever the plugin is installed. A review carrying no stamp reads
70
78
  # as age zero and classifies nothing, which is the same answer the carry-forward
71
79
  # path gives a pull request this run could not read.
80
+ #
81
+ # The stamp itself is emitted as a third field beside the age it was derived
82
+ # from, because the reply test below compares against the instant rather than
83
+ # against the elapsed seconds. A missing stamp reads as zero there too, where it
84
+ # sends a reply to be reported rather than suppressed. The two readers therefore
85
+ # fail in opposite directions on the same absent field, since zero silences the
86
+ # age test above and zero is the value the reply test reports on.
72
87
  JQ_LAST_REVIEW_STATE='
73
88
  [ .reviews[]
74
89
  | select((.body // "") | split("\n")[0] | rtrimstr("\r")
75
90
  | . == "## Review" or . == "## Review closed")
76
91
  ] | last
77
- | if . == null then "none 0"
78
- else ((.body | split("\n")[0] | rtrimstr("\r")
79
- | if . == "## Review" then "open" else "closed" end)
80
- + " "
81
- + (if .submittedAt == null then "0"
82
- else ((now - (.submittedAt | fromdateiso8601)) | floor | tostring)
83
- end))
92
+ | if . == null then "none 0 0"
93
+ else (if .submittedAt == null then 0
94
+ else (.submittedAt | fromdateiso8601 | floor)
95
+ end) as $at
96
+ | ((.body | split("\n")[0] | rtrimstr("\r")
97
+ | if . == "## Review" then "open" else "closed" end)
98
+ + " "
99
+ + (if $at == 0 then "0" else (((now | floor) - $at) | tostring) end)
100
+ + " "
101
+ + ($at | tostring))
84
102
  end
85
103
  '
86
104
 
@@ -102,7 +120,10 @@ carry_forward() {
102
120
  old=$(grep "^$n " "$STATE" || true)
103
121
  if [ -n "$old" ]; then
104
122
  # Every other classification compares a field against itself on a carried
105
- # line, so it fires nothing. STALLED reads a heading that is carried too, so
123
+ # line, so it fires nothing. That is also what keeps the two stamps unread
124
+ # here: the baseline never held them, so both arrive empty, and the count
125
+ # test in front of them fails before either reaches a numeric comparison.
126
+ # STALLED reads a heading that is carried too, so
106
127
  # the field is blanked to a value no branch matches. Echoing it intact would
107
128
  # classify a pull request this run never reached while stderr below says the
108
129
  # opposite, and one successful read restores it a run later. An already
@@ -146,9 +167,14 @@ snapshot() {
146
167
  fi
147
168
 
148
169
  prior=$(jq -r "$JQ_LAST_REVIEWED_HEAD" <<<"$payload")
149
- resp=$(jq -r "$JQ_REPLY_COUNT" <<<"$payload")
150
- # Two space-separated fields, so the line below carries them as its own
151
- # sixth and seventh rather than needing a split.
170
+ # Split here rather than carried whole, because the count keeps the fourth
171
+ # column every baseline written so far already reads, and the stamp goes to
172
+ # the end of the line beside the pass stamp it is compared against.
173
+ reply_state=$(jq -r "$JQ_REPLY_STATE" <<<"$payload")
174
+ resp=${reply_state%% *}
175
+ reply_at=${reply_state##* }
176
+ # Three space-separated fields, so the line below carries them as its own
177
+ # sixth, seventh, and eighth rather than needing a split.
152
178
  review_state=$(jq -r "$JQ_LAST_REVIEW_STATE" <<<"$payload")
153
179
 
154
180
  # `gh pr view --json mergeable` reports UNKNOWN until GitHub finishes
@@ -168,7 +194,7 @@ snapshot() {
168
194
  merges=conflict
169
195
  fi
170
196
 
171
- echo "$n $head ${prior:-none} $resp $merges $review_state"
197
+ echo "$n $head ${prior:-none} $resp $merges $review_state $reply_at"
172
198
  done
173
199
  }
174
200
 
@@ -185,7 +211,7 @@ CHANGED=0
185
211
  # would fire on every later run and the board would never read "No movement."
186
212
  FINAL=""
187
213
 
188
- while read -r n head prior resp merges heading age; do
214
+ while read -r n head prior resp merges heading age pass_at reply_at; do
189
215
  [ -z "$n" ] && continue
190
216
  state=$heading
191
217
  old=$(grep "^$n " "$STATE" || true)
@@ -244,7 +270,26 @@ while read -r n head prior resp merges heading age; do
244
270
  fi
245
271
  fi
246
272
  CHANGED=1
247
- elif [ "$resp" -gt "$old_resp" ]; then
273
+ elif [ "$resp" -gt "$old_resp" ] &&
274
+ { [ "$pass_at" = 0 ] || [ "$reply_at" -gt "$pass_at" ]; }; then
275
+ # The count says the reply is new to this script and the stamp says it is
276
+ # newer than the pass, and both are needed. A worker answers a finding and
277
+ # the reviewing session closes out seconds later, so the count alone reports
278
+ # an answered thread on the next run and the session spends a turn learning
279
+ # `claude-pr-review` will refuse it. The gaps observed were 96, 24, and 35
280
+ # seconds, which is a reviewing session reading a reply and posting, so this
281
+ # is the ordinary handback rather than a race.
282
+ #
283
+ # A pull request carrying no pass at all reads as stamp zero and reports,
284
+ # which is the case the count was the right test for: a reply with nothing
285
+ # behind it is a worker talking to nobody and worth the turn. The test is
286
+ # strictly greater to match the guard `claude-pr-review` states on the same
287
+ # two fields, which drops a reply landing inside the same second as the
288
+ # pass, a narrower failure than the one it removes.
289
+ #
290
+ # The gate sits in the condition rather than in the body so a suppressed
291
+ # reply falls through to STALLED below, which is a thread this run should
292
+ # still be able to reach.
248
293
  echo "RESPONSE #$n answered with no new commit"
249
294
  CHANGED=1
250
295
  elif [ "$heading" = open ] && [ "$old_heading" != reported ] &&
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Route .claude/teach/ edits to the teach standard for layout, ordinal naming, and the mission and record formats, in the projects that open a learning workspace
3
+ paths:
4
+ - '.claude/teach/**'
5
+ ---
6
+
7
+ # Teach standards
8
+
9
+ ## What a workspace keeps
10
+
11
+ - Never renumber an existing folder, lesson, or record. A later reader cites it by the name it opened under.
12
+ - Write every success line as a task the learner can be asked to perform, never as something they will understand.
13
+ - Keep a reference page free of the learner. A page carrying second person or a quiz belongs under `lessons/`.
14
+ - Record the wrong answer a learner gave, never a count of what they missed.
15
+ - Date the workspace once, in the `MISSION.md` frontmatter. Never repeat the date in the body of any file.
16
+
17
+ ## Authority
18
+
19
+ - Follow `.claude/standards/teach.md` for the folder layout, ordinal naming, frontmatter, and the mission and learning-record formats. It is the single source.
20
+ - Follow `.claude/standards/glossary.md` for the glossary every workspace carries at its root.
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Route .claude/tasks/ session map edits to the session standard for the filename, the core sections, and what a handoff carries
3
+ paths:
4
+ - '.claude/tasks/session-*.md'
5
+ ---
6
+
7
+ # Session map standards
8
+
9
+ ## What a handoff carries
10
+
11
+ - Follow this rule rather than `555-tasks.md` for a `session-` file. A handoff is not a task and carries neither `## Outcomes` nor `## Findings`.
12
+ - Never write into a file another session owns. Overwrite only the file the writing session already wrote.
13
+ - Resolve the containing folder at the main worktree root, never inside a linked worktree.
14
+ - Write only what a compaction destroys. Do not restate the board and do not summarize what shipped.
15
+ - Cite a commit, a task, or a file and line for every claim, so a reader can tell a read from a recall.
16
+
17
+ ## Authority
18
+
19
+ - Follow `.claude/standards/session.md` for the filename and location, frontmatter, the core sections, and the write and read procedures. It is the single source.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.111.0",
4
+ "version": "0.112.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {