@erclx/aitk 0.62.0 → 0.63.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  One source for your repos' AI conventions. Install once, sync everywhere.
8
8
 
9
- ![The aitk catalog, listing skills, governance rules, and standards with the count each ships and a sample of their names](assets/hero.png)
9
+ ![The aitk catalog, listing skills, governance rules, and standards with the count each ships, the workflow skills named, and a sample of the rule and standard names](assets/hero.png)
10
10
 
11
11
  If you work across more than one repository and your AI setup has started to drift between them, this is for you. The counts above are read from the catalogs when the image is built, so they're what the repo actually ships today.
12
12
 
@@ -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.62.0",
4
+ "version": "0.63.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -13,9 +13,11 @@ The queue fails in both directions. It empties and a free worker waits, or it fi
13
13
 
14
14
  The session also writes surfaces it is told only how to read. Refilling the queue promotes and demotes rows in the priority file with no stated method, so a session invents one, and an inline rewrite exits clean when it matches nothing and leaves the board wrong with nothing reporting it. A row carrying prose where a plan pointer belongs costs a worker dispatch, because the ship chain refuses at its guard after the worktree is already open. A plan archived from a worker's own branch strands the pointer the board still carries, and the row reads as correct until someone follows it.
15
15
 
16
+ The ban on writing at all fails on a different axis, which is that nothing enforces it and a session weighs proportionality against it. A one-line prose correction found while orchestrating satisfies the root instruction to handle a small edit immediately and violates this rule, and a session given no statement of which one wins takes the smaller apparent cost and authors the change. That removes the independent pass the repository built to catch what a self-review misses, and the vantage does not come back, since no later session can review the change without re-deriving the context that produced it. A correction no open task owns has nowhere to go either, so the route matters as much as the ban.
17
+
16
18
  Output drifts everywhere the contract stops. The specified shape covers invocation alone, so a sweep report, a board report, and an analysis each end in a decision the human owns and each buries it under the evidence they would have skipped. A compaction is the same failure one step earlier, since the file that survives it has a stated reader and no stated writer, and the session improvises what to save and loses the reasoning the board never held.
17
19
 
18
- The runbooks the session cannot run from memory fail on a third axis, which is where they are kept. A runbook reached by an installed path is a dependency on a channel this skill does not travel on, so a project holding the plugin and running no install follows the citation to nothing, and nothing reports the break because a missing file produces no error until someone opens the path. The three that cover the moments the loop cannot detect therefore have to ship inside the skill.
20
+ The runbooks the session cannot run from memory fail on a third axis, which is where they are kept. A runbook reached by an installed path is a dependency on a channel this skill does not travel on, so a project holding the plugin and running no install follows the citation to nothing, and nothing reports the break because a missing file produces no error until someone opens the path. Every one of them therefore has to ship inside the skill, the three covering the moments the loop cannot detect and the fourth holding the review trigger. That fourth one fails on an axis of its own, since it names one client's command as the way to start the loop, so a session holding a different recurring-prompt scheduler reaches no path and a client without that command reaches none either, and the file ships to every target holding the plugin.
19
21
 
20
22
  ## Must
21
23
 
@@ -31,12 +33,14 @@ The runbooks the session cannot run from memory fail on a third axis, which is w
31
33
  - Re-resolve the board's plan pointers after any archive, since the archiving skill rewrites the task file and knows nothing about the board
32
34
  - Lead a sweep report, a board report, and an analysis with the state, the open decisions, and the next action, keeping the evidence below them
33
35
  - Write the pre-compact handoff with what no other file carries, and name the runbook that reads it back
34
- - Carry the handoff, resume, and sweep runbooks inside the skill, so every citation resolves for a project holding the plugin and nothing else
36
+ - Carry every runbook the session cannot run from memory inside the skill, so each citation resolves for a project holding the plugin and nothing else
37
+ - State which rule wins where the tracked-file ban collides with the root instruction on small edits, and where a correction no open task owns goes
38
+ - State the review trigger as a recurring prompt rather than as one client's command, since the runbook ships to targets running whatever client they run
35
39
  - Read the handoff or the resume runbook when the human asks for that side of a compaction, since the request is the only signal available for a moment the loop cannot detect
36
40
 
37
41
  ## Must not
38
42
 
39
- - Implement a feature or edit any tracked file from this session
43
+ - Implement a feature or edit any tracked file from this session, at any size, since the ban offers no proportionality exception
40
44
  - Merge. Recommend merge or changes and leave the gate to the human.
41
45
  - Spawn worker sessions with agents, since every build is meant to be an independent steerable stream
42
46
  - Hand a worker anything but a plan, because scope lives there
@@ -37,7 +37,7 @@ The roadmap is optional and this skill does not require it. It carries why a seq
37
37
 
38
38
  A compaction is a moment this skill cannot detect, so the human asks for each side of it and this skill reads the matching runbook when they do. On a request to write the handoff or save the session, read `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` and follow it. It writes `.claude/tasks/session.md` with the state of play, the decisions taken under delegated authority, the mistakes worth not repeating, and the standing cautions. On a request to resume after a compaction, read `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md`, which reads that file back with the board and the groundwork behind the live work. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
39
39
 
40
- The review trigger takes the same shape. `references/orchestrator-poll.md` holds the loop prompt and the condition under which the poll runs, and `scripts/poll.sh` is what the prompt invokes. Nothing starts or stops that loop on its own, so the condition holds only while a session applies it.
40
+ The review trigger takes the same shape. `references/orchestrator-poll.md` holds the loop prompt and the condition under which the poll runs, and `scripts/poll.sh` is what the prompt invokes. A session holding a recurring-prompt scheduler starts and cancels that loop itself, and no hook or check does, so the condition holds only while whoever holds the loop applies it.
41
41
 
42
42
  That routing lives in this body and this skill is user-invoked, so a session that has dropped the body routes nothing and the request lands as ordinary conversation. Approaching a compaction is when a long session is likeliest to have dropped it, which is the same moment the handoff exists for. Re-invoke `/aitk:claude-orchestrate` first whenever the session has run long or the ask goes unanswered. The two runbooks sit at `references/orchestrator-handoff.md` and `references/orchestrator-resume.md` inside this skill's own folder, so a person who knows their plugin root opens either one directly and follows it without this skill loaded at all.
43
43
 
@@ -107,9 +107,11 @@ Write no shape for a correction. A correction is a sentence, and a format for ad
107
107
  - Do not implement features in this session. Hand the plan to a worker.
108
108
  - Do not merge. Recommend merge or changes. The human merges.
109
109
  - Do not spawn worker sessions with agents. The human launches each worktree so every build is an independent, steerable stream with its own PR.
110
- - Do not edit tracked files from this session. Record a change identified while orchestrating against the task that owns it, so it ships from that task's branch and lands in a pull request.
110
+ - Do not edit tracked files from this session, at any size. The boundary offers no proportionality exception and nothing enforces it.
111
111
  - Do not hand a worker anything but a plan, since scope lives there. A plan carries exact diffs only when they are already known, otherwise it states the scope and the open questions and lets the worker write the diff.
112
112
 
113
+ The tracked-file boundary collides with `CLAUDE.md`, which says to handle a small edit immediately without a task entry, and this rule wins wherever the two meet. A session that writes a change cannot review it independently afterwards and no later session recovers that vantage, which is the separation `claude-pr-review` exists to supply. Record a change identified while orchestrating against the task that owns it, fold one no task owns into the next task touching the same surface, and file a task only when no such task exists or is expected. Run `claude-review` when the boundary is crossed anyway, since a branch-diff pass is not independent and is the only check a self-authored change can get.
114
+
113
115
  ## Refilling the ready queue
114
116
 
115
117
  Keep enough planned, non-conflicting tasks available that a free worker never waits, and place the findings the last merge produced before promoting anything new. Run this after every merge and whenever the ready list thins. `${CLAUDE_SKILL_DIR}/references/orchestrator-sweep.md` wraps this procedure for a batch of merges and adds the plan re-verification that a merge invalidates.
@@ -11,16 +11,22 @@ Run the orchestrator's review trigger. The poll reports pull request movement an
11
11
 
12
12
  Start the poll on a dispatch and stop it when the last pull request merges with nothing else out. An open pull request or a dispatched worker is the condition, and both resolve from the board plus `gh pr list` without asking the operator. A release pull request alone does not qualify, since its sweep carries no findings.
13
13
 
14
- Nothing enforces this. No hook starts the poll and no check stops it, so the condition holds only while a session applies it. Left always-on it fires into an empty board through every gap between a dispatch and its push, which leaves stopping it to the operator.
14
+ A session holding a recurring-prompt scheduler starts the loop itself on that condition and cancels it on the same test, without waiting for the operator. Both halves belong to whoever holds the loop, since a session that can start one can stop one, and a runbook stating only the start leaves the always-on failure unaddressed on the side that causes it.
15
+
16
+ Nothing enforces this. No hook starts the poll and no check stops it, so the condition holds only while whoever holds the loop applies it. Left always-on it fires into an empty board through every gap between a dispatch and its push.
15
17
 
16
18
  The poll is session-scoped and dies with the session that started it. Restart it after a compaction, and take the prompt from this file rather than from a transcript, since a running loop holds whatever wording it was started with and a correction here does not reach it.
17
19
 
18
20
  ## The prompt
19
21
 
20
- Resolve `${CLAUDE_SKILL_DIR}/scripts/poll.sh` to an absolute path and paste that in place of `<POLL_SCRIPT>` below. The variable expands while this runbook renders and not in a `/loop` turn, which arrives as a standalone prompt, so a block carrying the variable reaches the session as a literal string and the run improvises a substitute.
22
+ The requirement is a recurring prompt at roughly three minutes carrying the block below. `/loop 3m <the block>` is the mechanism this repository uses and one example among the schedulers a client may hold, so a client without that command reaches the same requirement through whatever recurring prompt it can schedule. Naming one vendor's command as the only path dates a file that ships to every target holding the plugin.
23
+
24
+ The interval belongs to the schedule rather than to the script. One run is a single snapshot returning in about six seconds, so a session that reads `3m` as the script's runtime and relaunches on completion fires every few seconds and never settles, which happened once for 35 minutes.
25
+
26
+ Resolve `${CLAUDE_SKILL_DIR}/scripts/poll.sh` to an absolute path and paste that in place of `<POLL_SCRIPT>` below. The variable expands while this runbook renders and not in a standalone loop turn, so a block carrying the variable reaches the session as a literal string and the run improvises a substitute.
21
27
 
22
28
  ```plaintext
23
- /loop 3m Poll GitHub for pull request movement by running <POLL_SCRIPT>, then act on what it reports.
29
+ Poll GitHub for pull request movement by running <POLL_SCRIPT>, then act on what it reports.
24
30
 
25
31
  - MOVED or RESPONSE on a pull request I have already reviewed: run the aitk:claude-pr-review skill on it immediately, narrow pass. Re-reviews read prior..head and gain nothing from waiting.
26
32
  - OPENED, or a pull request with no prior review pass: report it and stop. First passes wait for the operator, because reading several together is what surfaces cross-PR findings.
@@ -16,15 +16,21 @@ order, port collisions), see [Claude Code and git worktrees](../wiki/claude/clau
16
16
 
17
17
  The split is by vantage, not by capability. Both are Claude Code sessions.
18
18
 
19
- | Role | Session | Owns | Does not |
20
- | ------------ | ------------------------------------- | ---------------------------------------------- | ------------------------- |
21
- | Orchestrator | One warm, long-lived session | Roadmap, planning, deep PR review, merge order | Build features, merge PRs |
22
- | Worker | One cold worktree session per feature | Implement, self-check, open PR | Question the plan, merge |
19
+ | Role | Session | Owns | Does not |
20
+ | ------------ | ------------------------------------- | ---------------------------------------------- | ----------------------------- |
21
+ | Orchestrator | One warm, long-lived session | Roadmap, planning, deep PR review, merge order | Edit tracked files, merge PRs |
22
+ | Worker | One cold worktree session per feature | Implement, self-check, open PR | Question the plan, merge |
23
23
 
24
24
  The orchestrator is worth asserting explicitly at the start of a session with
25
25
  `claude-orchestrate`, which loads the loop and its boundaries. It is framing and
26
26
  dispatch, not logic.
27
27
 
28
+ The orchestrator's cell reads every tracked file rather than every feature, and
29
+ it offers no exception for a small one. A correction found while orchestrating
30
+ goes to the task that owns the surface, or folds into the next task touching it,
31
+ because a session that writes a change cannot review it independently afterwards
32
+ and no later session recovers that vantage.
33
+
28
34
  ## The loop
29
35
 
30
36
  One feature travels this path end to end.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.62.0",
4
+ "version": "0.63.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -19,6 +19,7 @@ PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
19
19
 
20
20
  TEMPLATE="$PROJECT_ROOT/assets/hero.html.tmpl"
21
21
  OUTPUT="$PROJECT_ROOT/assets/hero.html"
22
+ CLI_ENTRY="$PROJECT_ROOT/src/cli.ts"
22
23
  LISTED=10
23
24
 
24
25
  # `bun src/cli.ts` rather than `aitk`, since a globally linked binary resolves to
@@ -32,6 +33,20 @@ if [ ! -f "$TEMPLATE" ]; then
32
33
  exit 1
33
34
  fi
34
35
 
36
+ # The commands have no `--json` catalog to read, so the count comes from the
37
+ # registration block in the CLI entry point, which is the one place a command is
38
+ # added. `--help` is hand-authored ASCII and would drift from what is registered.
39
+ if [ ! -f "$CLI_ENTRY" ]; then
40
+ echo "regen-hero: missing CLI entry point at $CLI_ENTRY" >&2
41
+ exit 1
42
+ fi
43
+
44
+ COMMAND_COUNT="$(grep -c '^import { register as ' "$CLI_ENTRY" || true)"
45
+ if [ "${COMMAND_COUNT:-0}" -eq 0 ]; then
46
+ echo "regen-hero: no command registrations found in $CLI_ENTRY, refusing to write a zeroed hero" >&2
47
+ exit 1
48
+ fi
49
+
35
50
  SKILLS_JSON="$(catalog claude skills list)"
36
51
  GOV_JSON="$(catalog gov list)"
37
52
  STANDARDS_JSON="$(catalog standards list)"
@@ -59,12 +74,12 @@ printf '%s' "$SNIPPETS_JSON" >"$PAYLOAD_DIR/snippets.json"
59
74
  printf '%s' "$TOOLING_JSON" >"$PAYLOAD_DIR/tooling.json"
60
75
 
61
76
  export PAYLOAD_DIR
62
- export TEMPLATE OUTPUT LISTED PROJECT_ROOT
77
+ export TEMPLATE OUTPUT LISTED PROJECT_ROOT COMMAND_COUNT
63
78
 
64
79
  bun --eval '
65
80
  const { readFileSync } = require("node:fs")
66
81
 
67
- const { PAYLOAD_DIR, TEMPLATE, OUTPUT, LISTED, PROJECT_ROOT } = process.env
82
+ const { PAYLOAD_DIR, TEMPLATE, OUTPUT, LISTED, PROJECT_ROOT, COMMAND_COUNT } = process.env
68
83
 
69
84
  const payload = (name) => readFileSync(PAYLOAD_DIR + "/" + name + ".json", "utf8")
70
85
 
@@ -79,7 +94,13 @@ const skills = JSON.parse(SKILLS_JSON).skills.map((entry) => entry.name)
79
94
  const gov = JSON.parse(GOV_JSON)
80
95
  // Rule names carry a numeric prefix that orders the load, not the identity a
81
96
  // reader knows them by, so the frame shows the slug alone.
82
- const rules = gov.rules.map((entry) => entry.name.replace(/^\d+-/, ""))
97
+ const slug = (entry) => entry.name.replace(/^\d+-/, "")
98
+ const rules = gov.rules.map(slug)
99
+ // A rule no stack names is opt-in behind `--add`, so featuring one advertises
100
+ // an entry no ordinary `aitk gov install` delivers. The column samples the
101
+ // stack-reached subset while its count and remainder describe the whole catalog.
102
+ const stacked = new Set(gov.stacks.flatMap((stack) => stack.rules))
103
+ const deliveredRules = gov.rules.filter((entry) => stacked.has(entry.name)).map(slug)
83
104
  const standards = JSON.parse(STANDARDS_JSON).standards.map((entry) => entry.name)
84
105
  const snippets = new Set(
85
106
  JSON.parse(SNIPPETS_JSON).categories.flatMap((category) => category.entries),
@@ -89,20 +110,62 @@ const toolingStacks = JSON.parse(TOOLING_JSON).stacks
89
110
  const escape = (value) =>
90
111
  value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
91
112
 
92
- // Even spacing across the sorted catalog rather than its first N. The skill
93
- // names are prefixed by domain, so an alphabetical head returns eight
94
- // `claude-*` entries and reads as a narrower catalog than the one that ships.
113
+ // Even spacing across the sorted catalog rather than its first N, so a column
114
+ // whose names share a prefix does not read as a narrower catalog than the one
115
+ // that ships. Rules and standards sample. Skills do not, because the even step
116
+ // lands on the entries closest to commodity and on none of the workflow the
117
+ // toolkit exists to carry.
95
118
  const sample = (names) => {
96
119
  if (names.length <= listed) return names
97
120
  const step = names.length / listed
98
121
  return Array.from({ length: listed }, (_, i) => names[Math.floor(i * step)])
99
122
  }
100
123
 
101
- const entries = (names) =>
102
- sample(names)
124
+ // The first seven are the method and exist nowhere else. The last three are
125
+ // recognizable on sight and are what keeps a column of chosen names from
126
+ // reading as an all-`claude-` catalog, which is the failure the sampler above
127
+ // was written against.
128
+ const FEATURED_SKILLS = [
129
+ "claude-feature",
130
+ "claude-groundwork",
131
+ "claude-intake",
132
+ "claude-orchestrate",
133
+ "claude-autoship",
134
+ "claude-pr-review",
135
+ "claude-tasks",
136
+ "git-ship",
137
+ "setup-init",
138
+ "systematic-debugging",
139
+ ]
140
+
141
+ // A chosen name is maintained by hand where a sample never goes stale, so a
142
+ // rename has to fail the run rather than quietly shrink the column. This is
143
+ // what makes the trade acceptable.
144
+ //
145
+ // The length is asserted because the `+N more` figure below counts down from
146
+ // `listed` rather than from what this returns, so a list of any other length
147
+ // renders a remainder that is wrong by the difference.
148
+ const featured = (names, chosen) => {
149
+ if (chosen.length !== listed) {
150
+ console.error(`regen-hero: ${chosen.length} featured skills, expected ${listed}`)
151
+ process.exit(1)
152
+ }
153
+ const catalog = new Set(names)
154
+ const missing = chosen.filter((name) => !catalog.has(name))
155
+ if (missing.length > 0) {
156
+ console.error(`regen-hero: featured skills missing from the catalog: ${missing.join(", ")}`)
157
+ process.exit(1)
158
+ }
159
+ return chosen
160
+ }
161
+
162
+ const markup = (names) =>
163
+ names
103
164
  .map((name) => ` <div class="entry">${escape(name)}</div>`)
104
165
  .join("\n")
105
166
 
167
+ const entries = (names) => markup(sample(names))
168
+
106
169
  const remaining = (names) => String(Math.max(0, names.length - listed))
107
170
 
108
171
  // An empty array is well-formed JSON, so the shell guard on an empty payload
@@ -111,6 +174,7 @@ const remaining = (names) => String(Math.max(0, names.length - listed))
111
174
  for (const [label, list] of [
112
175
  ["skills", skills], ["rules", rules], ["standards", standards],
113
176
  ["snippets", [...snippets]], ["tooling stacks", toolingStacks], ["gov stacks", gov.stacks],
177
+ ["stack-delivered rules", deliveredRules],
114
178
  ]) {
115
179
  if (list.length === 0) {
116
180
  console.error(`regen-hero: the ${label} catalog is empty, refusing to write a zeroed hero`)
@@ -125,8 +189,9 @@ const values = {
125
189
  SNIPPET_COUNT: String(snippets.size),
126
190
  GOV_STACK_COUNT: String(gov.stacks.length),
127
191
  TOOLING_STACK_COUNT: String(toolingStacks.length),
128
- SKILL_ENTRIES: entries(skills),
129
- RULE_ENTRIES: entries(rules),
192
+ COMMAND_COUNT: String(Number(COMMAND_COUNT)),
193
+ SKILL_ENTRIES: markup(featured(skills, FEATURED_SKILLS)),
194
+ RULE_ENTRIES: entries(deliveredRules),
130
195
  STANDARD_ENTRIES: entries(standards),
131
196
  SKILL_MORE: remaining(skills),
132
197
  RULE_MORE: remaining(rules),