@erclx/aitk 0.57.0 → 0.59.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.57.0",
4
+ "version": "0.59.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -41,6 +41,8 @@ If the two commands differ, the session is already in a linked worktree. Continu
41
41
 
42
42
  Read `.claude/plans/feature-<slug>.md` at the main worktree root. This file is the scope for this run.
43
43
 
44
+ Its sections and its answer contract are fixed by `.claude/standards/plan.md`, or `${CLAUDE_SKILL_DIR}/../../standards/plan.md` when the project does not have it. A blank `- Answer:` accepts the `- Suggested:` line above it, so an unanswered question is a decision this run executes rather than a reason to stop.
45
+
44
46
  ## Step 2: implement
45
47
 
46
48
  Implement only what the plan describes. Do not expand scope. Do not refactor neighbors. Do not touch files outside the plan's "Files to touch" list without reason.
@@ -234,7 +234,7 @@ The base lint-staged config runs `aitk indexes regen` on every committed `*.md`,
234
234
 
235
235
  Sweep reviews this session consumed, and sweep plans across the whole board. Resolve all paths at the main worktree root, not the current worktree. See Worktrees in `CLAUDE.md`.
236
236
 
237
- **Plans.** Scan every file in `.claude/tasks/`, not only the ones this session touched. For each task file whose outcomes are now all `[x]`, check for a `Plan:` line directly under the title and parse the target. The line carries a markdown link, so read the target out of the parentheses rather than taking the rest of the line. A task still carrying the older bare-path form parses the same way once the link is absent, so accept both. Resolve the target against `.claude/tasks/` before routing on it, which lands `../plans/x.md` and `.claude/plans/x.md` on the same file. The bullets below name resolved locations, so an unresolved target falls to the last one and no plan is ever archived. Never delete a plan. `CLAUDE.md` owns why a shipped plan is archived rather than removed.
237
+ **Plans.** Scan every file in `.claude/tasks/`, not only the ones this session touched. For each task file whose outcomes are now all `[x]`, check for a `Plan:` line directly under the title and parse the target. The line carries a markdown link, so read the target out of the parentheses rather than taking the rest of the line. A task still carrying the older bare-path form parses the same way once the link is absent, so accept both. Resolve the target against `.claude/tasks/` before routing on it, which lands `../plans/x.md` and `.claude/plans/x.md` on the same file. The bullets below name resolved locations, so an unresolved target falls to the last one and no plan is ever archived. Never delete a plan. `.claude/standards/plan.md` owns the archive destination and why a shipped plan is moved rather than removed.
238
238
 
239
239
  Board-wide scope is the one place this sweep reaches past Step 3's rule against touching task files the session did not change. A board carrying a task that closed while an earlier run missed its archive is the defect this exists to clear, and skipping those tasks would preserve it. Reaching them is safe because the archive moves the plan and points the task at the new path, so a task from unrelated work ends up with a working pointer rather than a broken one.
240
240
 
@@ -35,33 +35,14 @@ Measure against the tree rather than recall. Grep for each construct the plan wi
35
35
 
36
36
  ## Step 3: build the plan
37
37
 
38
- Construct the plan with these sections:
38
+ The section list, what each section holds, the suggested-and-answer contract, and the lifecycle are fixed by `.claude/standards/plan.md`, or `${CLAUDE_SKILL_DIR}/../../standards/plan.md` when the project does not have it. Read it before writing the file and follow it rather than working the shape from memory.
39
39
 
40
- - **Summary:** three to five one-line bullets covering the goal, the main deliverables, and the key trade-off or decision. Aimed at humans scanning the plan, not agents executing it. Full mode only.
41
- - **Constraints:** durable rules the work must respect (patterns to reuse, surfaces not to touch, platform limits). Optional. Include when an orchestrator or prior context supplies them, omit the section otherwise. A constraint naming a surface to leave alone carries the distinction under Constraints below.
42
- - **Files to touch:** each file with a one-line reason
43
- - **Risks:** conflicts, coupling, or tricky spots. When the plan establishes a resource with more than one consumer, list the consumers and mark each read or write, because a policy stated over that resource has to hold for the writers and not just the consumer that prompted it. If none, use `None identified.`
44
- - **Questions:** numbered list of things to resolve before starting. Each carries a `- Suggested:` line and an `- Answer:` slot (see Suggestions below). If none, use `None identified.`
40
+ What this skill adds on top of the standard:
45
41
 
46
- Prefer `None identified.` over low-signal fillers. A small feature should produce a short plan, not a padded one. Small mode skips the summary since the plan is already short enough to scan in full.
47
-
48
- ### Constraints
49
-
50
- A constraint naming a surface to leave alone forbids two different acts. Name which one, since a constraint carrying only the surface leaves the executing session to guess.
51
-
52
- - Forbid conforming the surface to whatever shape the change introduces. This is the act a scope constraint means, and it keeps the branch from growing a second concern.
53
- - Never forbid retargeting a pointer the change breaks. A rename, a split, or a deletion that leaves a citation behind ships a dangling reference, so fixing it is required work rather than scope creep.
54
- - Decide both acts for every surface the constraint names. Carving the distinction out for one file and leaving its siblings under the bare wording is how a plan ships one correct call beside one broken reference.
55
-
56
- ### Suggestions
57
-
58
- Attach a `- Suggested:` line to every question, then an empty `- Answer:` slot below it. A blank answer means accept the suggestion at execution time. This makes the plan decision-ready in one pass, with no separate decision-help round.
59
-
60
- - Apply senior judgment: pick the best option and state it in one line with its reason or main tradeoff. No padding, no alternatives unless they change the pick.
42
+ - Apply senior judgment to every `- Suggested:` line. Pick the best option and state it in one line with its reason or main tradeoff. No padding, no alternatives unless they change the pick.
61
43
  - Suggest a real default when best practice, the codebase, or prior context points to one.
62
- - When the answer hinges on the user's preference or their intent is unclear, write `- Suggested: needs your call, <why>` rather than fabricating a technical default.
63
-
64
- When three or more questions remain, keep chat output to the file pointer plus a short summary. Inline chat is fine when two or fewer remain.
44
+ - Prefer `None identified.` over low-signal fillers. A small feature should produce a short plan, not a padded one.
45
+ - When three or more questions remain, keep chat output to the file pointer plus a short summary. Inline chat is fine when two or fewer remain.
65
46
 
66
47
  ## Step 4: output
67
48
 
@@ -74,7 +55,7 @@ A consumer list is a `Risks` entry, which already forces Full. Establishing a re
74
55
 
75
56
  ### Small mode
76
57
 
77
- Output the plan to chat. Do not write a plan file.
58
+ Output the plan to chat. Do not write a plan file. The markers match the standard's, so a plan that later grows into a file keeps the shape it was drafted in.
78
59
 
79
60
  ```markdown
80
61
  **Files to touch:**
@@ -98,37 +79,9 @@ Omit empty sections. Do not print `None identified.` in chat.
98
79
 
99
80
  Derive a 2-to-4-word kebab-case slug from the feature description. Write the full plan to `.claude/plans/feature-<slug>.md` at the main worktree root, not the current worktree. See Worktrees in `CLAUDE.md`. Create the directory if it does not exist.
100
81
 
101
- File format:
102
-
103
- ```markdown
104
- # Feature: <short title>
105
-
106
- <feature description>
107
-
108
- ## Summary
109
-
110
- - <one-line bullet covering the goal>
111
- - <one-line bullet covering the main deliverables>
112
- - <one-line bullet covering the key decision or trade-off>
113
-
114
- **Constraints:**
82
+ The file follows the template in `.claude/standards/plan.md`. Copy the shape from there rather than from this body, so one edit to the standard moves every plan.
115
83
 
116
- - <durable rule the work must respect>
117
-
118
- **Files to touch:**
119
-
120
- - `path/to/file`: reason
121
-
122
- **Risks:**
123
-
124
- - <risk>
125
-
126
- **Questions:**
127
-
128
- 1. <question>
129
- - Suggested: <pick>, <reason or tradeoff>
130
- - Answer:
131
- ```
84
+ Run `aitk records validate plans` after writing the file when the CLI is on PATH. It reports a section, a filename, or an answer slot that does not hold, and it writes nothing.
132
85
 
133
86
  Then output in chat:
134
87
 
@@ -28,6 +28,7 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
28
28
  | `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
29
29
  | `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
30
30
  | `aitk tasks validate` | Report board rows whose plan, task file, group, or file set does not hold (`--json`) |
31
+ | `aitk records validate` | Report a plan, groundwork track, or intake dump against the standard governing it (`--json`) |
31
32
  | `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
32
33
  | `aitk context audit` | Report required sections, length, depth, bullet weight, cited paths, provenance, and drift |
33
34
  | `aitk claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
@@ -18,6 +18,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
18
18
  - [Install and sync](install-and-sync.md): What each install and sync verb writes, refuses, or leaves alone, and how drift is attributed in a target project
19
19
  - [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
20
20
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
21
+ - [Records](records.md): Validating the gitignored session records under .claude/, the per-kind checks, the refusal reasons, and why the root defaults to the main worktree
21
22
  - [Sandbox](sandbox.md): Scenario routing, the expectation scoring surface, and the coverage census over scenarios and skills
22
23
  - [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
23
24
  - [Skill audit](skills-audit.md): Measuring both skill corpora against standards/skill.md, the checks it reads, and the requirement gate that is the only failing one
@@ -0,0 +1,54 @@
1
+ ---
2
+ title: Records
3
+ description: Validating the gitignored session records under .claude/, the per-kind checks, the refusal reasons, and why the root defaults to the main worktree
4
+ ---
5
+
6
+ # Records
7
+
8
+ ## Validate
9
+
10
+ `aitk records validate <kind>` reports where a session record and the standard governing it disagree. The three kinds are `plans`, `groundwork`, and `intake`, each a gitignored folder under `.claude/`.
11
+
12
+ ```bash
13
+ aitk records validate plans
14
+ aitk records validate intake --json
15
+ ```
16
+
17
+ | Option | Behavior |
18
+ | --------------- | --------------------------------------------- |
19
+ | `--json` | Add a machine-readable record on stdout |
20
+ | `--root <path>` | Project root, defaulting to the main worktree |
21
+
22
+ It reads and never writes. Each folder is per-machine scratch with no history behind it, so a repair that guessed wrong could not be undone, and the report names the record for a session to fix.
23
+
24
+ Nothing fires it automatically. The folders are gitignored, so the standards-audit hook exits early on them and any check reading changed files from git never lists one. The verb runs at the moment a session claims the record is finished, which is the same placement `aitk tasks validate` takes over the board.
25
+
26
+ ### What each kind checks
27
+
28
+ | Kind | What it reports |
29
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
30
+ | `plans` | A filename that is not `feature-<slug>.md`, a missing `# Feature:` heading, a missing required section, a files-to-touch entry naming no file or saying nothing about one, and a question carrying no suggestion or no answer slot |
31
+ | `groundwork` | A track with no `README.md` or no `01-current-state.md`, a file missing `title` or `description`, a `README.md` with no `date` as `YYYY-MM-DD`, an unnumbered file, and a track holding a decision without its handoff or the reverse |
32
+ | `intake` | A dump with no `00-overview.md`, the same frontmatter and numbering checks, an item missing any of `Problem`, `Fix`, `Worth it`, or `You`, and an item carrying `Open` with no `Suggested` |
33
+
34
+ The half-closed track is the groundwork check a reader cannot run by eye. A folder holding `06` without `07` reads as closed to anyone scanning filenames while the file a returning session actually opens is absent.
35
+
36
+ The item check skips `00-overview.md` and `99-next-session.md`, since neither holds items and running it over the handoff would report every heading it carries.
37
+
38
+ A plan section opens as a bold label or as an H2 and the check counts both, naming the standard's spelling when it reports one missing. The corpus splits roughly four to one between the two forms, so failing the variant would report nearly every plan on a rule that costs a reader nothing.
39
+
40
+ A section runs to the next marker-shaped line whatever it names, so a plan carrying a label of its own closes the section above it rather than collecting into it. Fenced blocks are dropped before any of this, since a plan showing the shape it writes puts real-looking bullets and headings inside a fence.
41
+
42
+ ### Exit codes and refusals
43
+
44
+ Exit codes: `0` every check passed, `1` refused, `2` at least one record carries a finding. A `reason` field carries which gate fired: `no-folder` when the kind's directory does not exist, and `unknown-kind` when the argument names no published kind.
45
+
46
+ The folders are shared scratch at the main worktree root, so `--root` defaults to the first entry of `git worktree list` rather than the working directory. A linked worktree validates the same records every other session reads.
47
+
48
+ Skills branch on the findings rather than on the exit code:
49
+
50
+ ```bash
51
+ aitk records validate plans --json | jq -r '.findings[] | "\(.kind): \(.subject)"'
52
+ ```
53
+
54
+ For the shapes each check enforces, see `.claude/standards/plan.md`, `.claude/standards/groundwork.md`, and `.claude/standards/intake.md`.
@@ -11,15 +11,15 @@ What a skill or script reads to discover names at runtime, and how each domain i
11
11
 
12
12
  Use these to discover what's available instead of hardcoding names.
13
13
 
14
- | Command | Returns |
15
- | -------------------------------- | --------------------------------------------- |
16
- | `aitk tooling list --json` | Stacks, extends chain, dep and script counts |
17
- | `aitk snippets list --json` | Presets and categories with their slugs |
18
- | `aitk standards list --json` | Standards docs and the paths each governs |
19
- | `aitk gov list --json` | Governance stacks and rule sets |
20
- | `aitk claude seeds list --json` | Seed doc sources with content |
21
- | `aitk claude skills list --json` | Plugin skills, descriptions, requirement flag |
22
- | `aitk docs list --json` | Consumer docs plus per-domain context |
14
+ | Command | Returns |
15
+ | -------------------------------- | ---------------------------------------------------- |
16
+ | `aitk tooling list --json` | Stacks, extends chain, dep and script counts |
17
+ | `aitk snippets list --json` | Presets and categories with their slugs |
18
+ | `aitk standards list --json` | Standards docs and the paths each governs |
19
+ | `aitk gov list --json` | Governance stacks, rule sets, and unreferenced rules |
20
+ | `aitk claude seeds list --json` | Seed doc sources with content |
21
+ | `aitk claude skills list --json` | Plugin skills, descriptions, requirement flag |
22
+ | `aitk docs list --json` | Consumer docs plus per-domain context |
23
23
 
24
24
  ### Catalog fields
25
25
 
@@ -40,6 +40,18 @@ listing and the install cannot disagree. It now reports
40
40
  `.claude/context/index.md`, which `init` has always installed and the listing
41
41
  never named, and it emits the project-level `CLAUDE.md` last rather than first.
42
42
 
43
+ `aitk gov list` reports each stack's rules already expanded. A stack entry names
44
+ either a rule or a whole rule folder under `governance/rules/`, and the folder
45
+ form reaches this listing as the rules it stands for rather than as the folder
46
+ name. A consumer deduping `--add` extras against a stack therefore reads the same
47
+ set the install writes, whichever form the stack file uses.
48
+
49
+ It also carries `unreferenced`, the rules no stack reaches, on every invocation
50
+ rather than behind a flag. These install only through `--add`, so the key answers
51
+ what a stack leaves out without a caller resolving every stack itself. The key is
52
+ present alongside `stacks` and `rules` and survives `--stacks` or `--rules`
53
+ narrowing the other two.
54
+
43
55
  ### The skills catalog
44
56
 
45
57
  `aitk claude skills list` reads `claude/skills/*/SKILL.md` and reports the folder
@@ -79,6 +79,10 @@ Before a handoff, the orchestrator checks the plan against the tree rather than
79
79
 
80
80
  `.claude/plans/`, `.claude/review/`, and `.claude/memory/` all resolve at the main worktree root, so artifacts created in any session are visible from any sibling worktree. See [Claude Code and git worktrees](../wiki/claude/claude-worktrees.md) for the full rule and the domain-level fan-out guidance.
81
81
 
82
+ The plan's shape is fixed by `.claude/standards/plan.md`: the section list, the filename, the lifecycle, and the contract its questions keep. Every question carries a `- Suggested:` line and an empty `- Answer:` slot, and a blank answer accepts the suggestion at execution time. That default is what makes a plan decision-ready in one pass, and it is the opposite of the contract an intake folder keeps, where an empty slot means nobody reached the item.
83
+
84
+ `aitk records validate plans` reports where a plan and that standard disagree: a filename that is not `feature-<slug>.md`, a missing required section, a files-to-touch entry naming no file, and a question carrying a suggestion with no answer slot. The same verb takes `groundwork` and `intake`, which are governed the same way and were unreachable for the same reason. Nothing fires it automatically, because all three folders are gitignored and every check the repository runs reads changed files from git. It reports and never writes, since the folders are per-machine scratch with no history to recover a wrong repair from.
85
+
82
86
  A plan that ships is archived, never deleted. `aitk:claude-docs` moves it to `.claude/plans-archive/` and retargets the task file's `Plan:` line at the new location, so a completed task still leads to the reasoning behind it. Both folders are gitignored, which is why a deleted plan had no recovery path. A plan cited by more than one task stays put until the last of them closes, since moving it early would strand every other pointer.
83
87
 
84
88
  The sweep reads the whole board rather than the tasks the session touched. It is the one place the skill reaches past its own rule against editing a task file the session did not change, because a task that closed while an earlier run missed its archive is exactly what the sweep exists to clear. Reaching it is safe: the archive moves the plan and retargets the pointer in the same pass, so an untouched task ends up with a working link rather than a broken one.
@@ -0,0 +1,22 @@
1
+ ---
2
+ description: Route .claude/plans/ edits to the plan standard for sections, the answer contract, and the archive move
3
+ paths:
4
+ - '.claude/plans/**'
5
+ - '.claude/plans-archive/**'
6
+ ---
7
+
8
+ # Plan standards
9
+
10
+ ## The answer contract
11
+
12
+ - Never fill an `- Answer:` slot on behalf of the person who owns it. A blank slot accepts the suggestion at execution time.
13
+ - Never ship a question without a `- Suggested:` line. Write `- Suggested: needs your call, <why>` where the answer turns on preference.
14
+
15
+ ## Archiving
16
+
17
+ - Move a shipped plan to `.claude/plans-archive/`. Never delete one.
18
+ - Amend a plan in place when a decision changes. Do not append a second passage narrating the change.
19
+
20
+ ## Authority
21
+
22
+ - Follow `.claude/standards/plan.md` for the filename and slug, the required sections, the suggested-and-answer contract, and the lifecycle. It is the single source.
@@ -1,2 +1,4 @@
1
1
  extends = ""
2
- rules = ["000-constitution", "010-testing", "020-concurrency", "030-error-handling", "040-performance", "050-logging", "060-naming", "070-planning", "080-config-comments", "090-code-comments", "500-prose", "501-markdown", "510-context", "520-wireframes", "530-requirements", "540-architecture", "550-design", "555-tasks", "556-groundwork", "557-intake", "560-diagrams", "570-skill", "580-readme", "590-rule-authoring", "591-standard-authoring"]
2
+ # Both folders whole. Every rule authored under them ships to a base consumer,
3
+ # so adding one is a single edit rather than a file and a name here.
4
+ rules = ["core", "claude"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.57.0",
4
+ "version": "0.59.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -18,6 +18,12 @@ CHANGED_FILES=""
18
18
  # unarmed and why.
19
19
  SANDBOX_UNDECLARED_CEILING=47
20
20
 
21
+ # Rules no stack reaches, space separated and sorted the way `aitk gov list`
22
+ # emits them. Both are opt-in libraries a project may not want, so they are
23
+ # recorded here rather than in a config file: the list is what a reader compares
24
+ # a new arrival against, and a config file would absorb the arrival silently.
25
+ GOV_EXPECTED_UNREFERENCED="260-shadcn 320-tanstack-query"
26
+
21
27
  check_dependencies() {
22
28
  command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
23
29
  }
@@ -208,6 +214,55 @@ main() {
208
214
  run_check "bash $PROJECT_ROOT/scripts/core/check-plugin-boundary.sh" "Plugin ships toolkit-internal content."
209
215
  log_info "Plugin boundary clean"
210
216
 
217
+ # A stack entry naming a rule folder takes every rule in it, which is what
218
+ # stops a new rule from needing a second edit to reach a target. The failure
219
+ # it leaves open is a rule authored into a folder no stack names, which
220
+ # installs for nobody and reports nothing on its own.
221
+ #
222
+ # This reports and never fails. Both standing findings are opt-in libraries
223
+ # this repository ships on purpose, so gating would fail every push over the
224
+ # deliberate case and teach a reader to route around the stage. Revisit when a
225
+ # third appears and the pattern is either a library set or an accident.
226
+ log_step "Unreferenced rules"
227
+ local gov_json gov_status=0 unreferenced
228
+ gov_json=$(cd "$PROJECT_ROOT" && bun src/cli.ts gov list --json 2>/dev/null) || gov_status=$?
229
+ if [ "$gov_status" -ne 0 ] || [ -z "$gov_json" ]; then
230
+ log_warn "Skipped, the governance catalog did not report"
231
+ else
232
+ # `bun --eval` rather than a grep, because the key holds an array of names
233
+ # and the numeric matches the stages above use reach a scalar alone.
234
+ #
235
+ # The `ok:` sentinel carries success rather than the exit code, because
236
+ # `bun --eval` reading piped stdin exits 0 even when the script throws.
237
+ # Measured on Bun 1.3.14: the same throw exits 1 with no pipe attached. A
238
+ # payload that parses as text but not as JSON would therefore print nothing
239
+ # and exit clean, and empty already means every rule is reached, so reading
240
+ # the exit code would report a broken catalog as a clean sweep. A missing
241
+ # or non-array key takes the same branch, since a stage that cannot measure
242
+ # should say so rather than claim the sweep found nothing.
243
+ local reported
244
+ reported=$(printf '%s' "$gov_json" | bun --eval '
245
+ try {
246
+ const data = JSON.parse(require("node:fs").readFileSync(0, "utf8"))
247
+ if (!Array.isArray(data.unreferenced)) throw new Error("no field")
248
+ console.log("ok:" + data.unreferenced.join(" "))
249
+ } catch {
250
+ console.log("unreadable:")
251
+ }
252
+ ')
253
+ unreferenced="${reported#ok:}"
254
+ if [ "${reported%%:*}" != "ok" ]; then
255
+ log_warn "Skipped, the governance catalog carried no readable unreferenced list"
256
+ elif [ -z "$unreferenced" ]; then
257
+ log_info "Every rule is reached by a stack"
258
+ elif [ "$unreferenced" = "$GOV_EXPECTED_UNREFERENCED" ]; then
259
+ log_info "Reached by no stack: $unreferenced (opt-in, install with --add)"
260
+ else
261
+ log_warn "Reached by no stack: $unreferenced"
262
+ log_warn "Expected: $GOV_EXPECTED_UNREFERENCED. Name the new rule in a stack, or update GOV_EXPECTED_UNREFERENCED in this script and say why it ships opt-in."
263
+ fi
264
+ fi
265
+
211
266
  # Only the citation half of the audit gates. Length, depth, table, and index
212
267
  # findings are judgment thresholds, and failing a push on one would make the
213
268
  # stage something to route around. `bun src/cli.ts` rather than `aitk`, since a
package/src/cli.ts CHANGED
@@ -22,6 +22,7 @@ import { register as transcripts } from '@/commands/transcripts'
22
22
  import { register as tasks } from '@/commands/tasks'
23
23
  import { register as comments } from '@/commands/comments'
24
24
  import { register as context } from '@/commands/context'
25
+ import { register as records } from '@/commands/records'
25
26
  import { PROJECT_ROOT } from '@/exec'
26
27
 
27
28
  const GREY = '\x1b[0;90m'
@@ -53,6 +54,7 @@ function showHelp(): void {
53
54
  `${GREY}│${NC} tasks [cmd] ${GREY}# Task board commands (archive)${NC}`,
54
55
  `${GREY}│${NC} comments [cmd] ${GREY}# Measure comment density and trend (scan)${NC}`,
55
56
  `${GREY}│${NC} context [cmd] ${GREY}# Report context folder health (audit)${NC}`,
57
+ `${GREY}│${NC} records [cmd] ${GREY}# Check session records under .claude/ (validate)${NC}`,
56
58
  `${GREY}│${NC}`,
57
59
  `${GREY}│${NC} ${WHITE}Sandbox:${NC}`,
58
60
  `${GREY}│${NC} aitk sandbox ${GREY}# Interactive scenario picker${NC}`,
@@ -85,6 +87,7 @@ function showHelp(): void {
85
87
  `${GREY}│${NC} aitk tasks archive --pull-request 673 --json`,
86
88
  `${GREY}│${NC} aitk comments scan src --json`,
87
89
  `${GREY}│${NC} aitk context audit --json`,
90
+ `${GREY}│${NC} aitk records validate plans`,
88
91
  `${GREY}└${NC}`,
89
92
  ]
90
93
  console.log(lines.join('\n'))
@@ -138,5 +141,6 @@ transcripts(program)
138
141
  tasks(program)
139
142
  comments(program)
140
143
  context(program)
144
+ records(program)
141
145
 
142
146
  program.parse()
@@ -2,11 +2,11 @@ import { existsSync } from 'node:fs'
2
2
  import { mkdir, writeFile } from 'node:fs/promises'
3
3
  import { basename, dirname, join, resolve } from 'node:path'
4
4
  import type { Command } from 'commander'
5
- import { registerPassThroughVerbs } from '@/commands/pass-through'
6
5
  import { PROJECT_ROOT } from '@/exec'
7
6
  import { createGovAdapter } from '@/gov/adapter'
8
7
  import { regenConsumedRules } from '@/gov/consumed'
9
8
  import { hasStandards, installRules, lookupRules } from '@/gov/install'
9
+ import { buildGovCatalog, describeRule, describeStack } from '@/gov/list'
10
10
  import { buildRulesPayload, listRuleFiles } from '@/gov/payload'
11
11
  import {
12
12
  govStackExists,
@@ -34,8 +34,6 @@ const NC = '\x1b[0m'
34
34
  const PAYLOAD_REL = join('.claude', '.tmp', 'gov', 'rules.md')
35
35
  const RULES_REL = join('.claude', 'rules')
36
36
 
37
- const PASS_THROUGH_VERBS = ['list'] as const
38
-
39
37
  interface InstallOptions {
40
38
  readonly add?: string
41
39
  }
@@ -44,6 +42,12 @@ interface RegenOptions {
44
42
  readonly root?: string
45
43
  }
46
44
 
45
+ interface ListOptions {
46
+ readonly stacks?: boolean
47
+ readonly rules?: boolean
48
+ readonly json?: boolean
49
+ }
50
+
47
51
  export function register(program: Command): void {
48
52
  const gov = program
49
53
  .command('gov')
@@ -119,7 +123,78 @@ export function register(program: Command): void {
119
123
  process.exitCode = await runRegen(opts)
120
124
  })
121
125
 
122
- registerPassThroughVerbs(gov, 'gov', PASS_THROUGH_VERBS)
126
+ gov
127
+ .command('list')
128
+ .description('Emit the catalog of stacks and rules')
129
+ .helpOption('-h, --help', 'Show this help message')
130
+ .option('--stacks', 'Only list stacks')
131
+ .option('--rules', 'Only list rules')
132
+ .option('--json', 'Emit machine-readable JSON')
133
+ .action((opts: ListOptions) => {
134
+ process.exitCode = runList(opts)
135
+ })
136
+ }
137
+
138
+ /**
139
+ * Both selectors absent means both sections, which is the bash default. Naming
140
+ * both is the same as naming neither rather than an error, since the two flags
141
+ * read as filters and a caller passing both is asking for everything.
142
+ */
143
+ function selectedSections(opts: ListOptions): {
144
+ stacks: boolean
145
+ rules: boolean
146
+ } {
147
+ const stacks = opts.stacks === true
148
+ const rules = opts.rules === true
149
+ if (stacks === rules) return { stacks: true, rules: true }
150
+ return { stacks, rules }
151
+ }
152
+
153
+ /**
154
+ * `JSON.stringify` replaces a `printf` that interpolated a description into a
155
+ * JSON string literal through a hand-rolled escaper, so a rule carrying a
156
+ * character that escaper missed emitted output a consuming skill could not
157
+ * parse.
158
+ *
159
+ * `unreferenced` rides the same payload rather than taking a flag of its own.
160
+ * The verify stage and a skill asking what a stack leaves out read one call,
161
+ * and the key is additive, so a consumer reading `stacks` or `rules` is
162
+ * untouched by it.
163
+ */
164
+ function runList(opts: ListOptions): number {
165
+ const catalog = buildGovCatalog(PROJECT_ROOT)
166
+ const sections = selectedSections(opts)
167
+
168
+ if (opts.json) {
169
+ process.stdout.write(
170
+ `${JSON.stringify({
171
+ ...(sections.stacks ? { stacks: catalog.stacks } : {}),
172
+ ...(sections.rules ? { rules: catalog.rules } : {}),
173
+ unreferenced: catalog.unreferenced,
174
+ })}\n`,
175
+ )
176
+ return 0
177
+ }
178
+
179
+ intro('aitk gov list')
180
+
181
+ if (sections.stacks) {
182
+ logStep('Stacks')
183
+ for (const entry of catalog.stacks) logInfo(describeStack(entry))
184
+ }
185
+
186
+ if (sections.rules) {
187
+ logStep('Rules')
188
+ for (const entry of catalog.rules) logInfo(describeRule(entry))
189
+ }
190
+
191
+ if (catalog.unreferenced.length > 0) {
192
+ logStep('Reached by no stack')
193
+ for (const rule of catalog.unreferenced) logInfo(rule)
194
+ }
195
+
196
+ outro()
197
+ return 0
123
198
  }
124
199
 
125
200
  /**