@erclx/aitk 0.44.0 → 0.46.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.
Files changed (38) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/claude-autoship/SKILL.md +15 -10
  3. package/claude/skills/claude-docs/SKILL.md +16 -2
  4. package/claude/skills/claude-memory-capture/REQUIREMENT.md +14 -4
  5. package/claude/skills/claude-memory-capture/SKILL.md +45 -16
  6. package/claude/skills/claude-memory-review/REQUIREMENT.md +8 -2
  7. package/claude/skills/claude-memory-review/SKILL.md +41 -25
  8. package/claude/skills/claude-orchestrate/SKILL.md +5 -0
  9. package/claude/skills/claude-seed-sync/REQUIREMENT.md +3 -1
  10. package/claude/skills/claude-seed-sync/SKILL.md +16 -4
  11. package/claude/skills/git-ship/SKILL.md +14 -11
  12. package/claude/skills/session-resume/SKILL.md +2 -2
  13. package/claude/skills/toolkit-operator/SKILL.md +18 -1
  14. package/docs/agents/commands.md +36 -35
  15. package/docs/agents/index.md +1 -0
  16. package/docs/agents/indexes.md +3 -1
  17. package/docs/agents/install-and-sync.md +46 -0
  18. package/docs/agents/scripting.md +4 -3
  19. package/docs/agents/skills-audit.md +55 -0
  20. package/docs/ai-workflow.md +3 -2
  21. package/docs/target-projects.md +5 -1
  22. package/package.json +1 -1
  23. package/scripts/core/verify.sh +8 -0
  24. package/src/claude/seeds.ts +7 -1
  25. package/src/claude/skills-audit.ts +215 -0
  26. package/src/claude/skills-list.ts +3 -3
  27. package/src/commands/claude.ts +283 -5
  28. package/src/commands/context.ts +1 -4
  29. package/src/commands/sync.ts +54 -1
  30. package/src/sync/check.ts +71 -0
  31. package/src/sync/layout.ts +139 -0
  32. package/src/sync/seeds-report.ts +111 -0
  33. package/src/ui.ts +5 -0
  34. package/tooling/claude/reference.md +11 -1
  35. package/tooling/claude/seeds/.claude/hooks/memory-index.sh +60 -0
  36. package/tooling/claude/seeds/.claude/memory/index.md +8 -0
  37. package/tooling/claude/seeds/.claude/settings.json +4 -0
  38. package/tooling/claude/seeds/CLAUDE.md +3 -0
@@ -14,26 +14,29 @@ Run `git diff --cached --name-only 2>/dev/null` to check for staged files. If ou
14
14
 
15
15
  ## Sequence
16
16
 
17
- 1. Invoke `aitk:claude-docs` to sync internal planning docs against session decisions
18
- 2. Invoke `aitk:docs-sync` to sync public docs against changes since main
19
- 3. Run `git add -A` to stage any files the sync skills wrote
20
- 4. Invoke `aitk:git-stage` to group staged changes and commit by concern
21
- 5. Invoke `aitk:git-branch` to rename branch to match conventional format
22
- 6. Invoke `aitk:git-pr` to push branch and open pull request
23
- 7. After the PR opens, watch CI. Poll `gh pr checks <number>` until no check is pending, then read the final status. On all-pass, continue. On any failure, stop the sequence and report the failing check with its URL. Do not auto-fix. This step may output on failure, the one exception to the no-text-between-steps rule.
24
- 8. Invoke `aitk:claude-memory-capture` to extract durable patterns from the session into `.claude/memory/`
25
- 9. If `claude-memory-capture` wrote or updated at least one entry this session, invoke `aitk:claude-memory-review` scoped to those entries to propose fixes while session context is fresh. If capture wrote nothing, skip this step.
17
+ 1. Invoke `aitk:claude-memory-capture` to route what this session learned to the context entries that own it and write the residue to `.claude/memory/`
18
+ 2. Invoke `aitk:claude-docs` to sync internal planning docs against session decisions, folding in the routed facts
19
+ 3. Invoke `aitk:docs-sync` to sync public docs against changes since main
20
+ 4. Run `git add -A` to stage any files the sync skills wrote
21
+ 5. Invoke `aitk:git-stage` to group staged changes and commit by concern
22
+ 6. Invoke `aitk:git-branch` to rename branch to match conventional format
23
+ 7. Invoke `aitk:git-pr` to push branch and open pull request
24
+ 8. After the PR opens, watch CI. Poll `gh pr checks <number>` until no check is pending, then read the final status. On all-pass, continue. On any failure, stop the sequence and report the failing check with its URL. Do not auto-fix. This step may output on failure, the one exception to the no-text-between-steps rule.
25
+ 9. If step 1 wrote or updated at least one memory file, invoke `aitk:claude-memory-review` scoped to those entries to propose fixes while session context is fresh. If the pen got nothing, skip this step.
26
+
27
+ Capture leads the sequence because a routed fact lands in a context entry, which is a tracked file. Running it after the pull request opens leaves that edit off the branch entirely, so the fact reaches nothing. Memory files are gitignored either way, which is what hid the ordering while capture wrote only those.
26
28
 
27
29
  Stop at the Propose phase. Do not run Apply. Promoting an entry to `CLAUDE.md` or a skill body ships as its own change, separate from this feature.
28
30
 
29
31
  ## After completion
30
32
 
31
- Output up to three lines:
33
+ Output up to four lines:
32
34
 
33
35
  ```plaintext
34
36
  ✅ Shipped
37
+ <N facts routed to context entries>
35
38
  <N memories captured in .claude/memory/>
36
39
  <Memory proposal at .claude/review/memory-review-<slug>.md>
37
40
  ```
38
41
 
39
- Omit the second and third lines if `claude-memory-capture` wrote nothing this session, since no captures means no scoped review and no proposal.
42
+ Omit the second line if nothing routed. Omit the third and fourth if `claude-memory-capture` wrote no memory file this session, since an empty pen means no scoped review and no proposal.
@@ -13,7 +13,7 @@ Read these in parallel, skipping any that do not exist:
13
13
 
14
14
  - `.claude/tasks/index.md`: the backlog, one line per task. Read this before any individual task file.
15
15
  - `.claude/plans/*.md`: execution detail for in-progress tasks
16
- - `.claude/memory/MEMORY.md` and any memory files relevant to the top backlog item
16
+ - `.claude/memory/index.md` and any memory files relevant to the top backlog item
17
17
 
18
18
  Then read only the task files the summary needs, typically the top one. Do not read the whole folder.
19
19
 
@@ -33,4 +33,4 @@ Output three sections:
33
33
 
34
34
  End with one line: `Start with: <first Up next item>` and note whether it has a linked plan.
35
35
 
36
- Do not offer to remove entries. A completed task is archived out of `.claude/tasks/` when work ships. The git log is the authoritative record of shipped work. Plan files are archived per the plan lifecycle rule in `CLAUDE.md`. Memory is updated only when a recorded fact becomes wrong, never on resume.
36
+ Do not offer to remove entries. A completed task is archived out of `.claude/tasks/` when work ships. The git log is the authoritative record of shipped work. Plan files are archived per the plan lifecycle rule in `CLAUDE.md`. Memory is updated only when a recorded fact becomes wrong, never on resume. A domain fact reaches a session through `.claude/context/`, which `claude-memory-capture` routes to and the three-tier model loads on demand, so the memory folder read here is the residue no context entry owns.
@@ -18,9 +18,26 @@ Run these from the target project root, in parallel, before acting:
18
18
 
19
19
  Load a domain doc with `aitk docs <topic>` only when the intent touches that domain. Read the live catalog for any domain you act on with `aitk <domain> list --json`. Never hardcode stack, rule, snippet, or standards names.
20
20
 
21
+ ## Diagnose
22
+
23
+ Run `aitk sync --check . --json` before routing. It reports what a target is behind on across every surface, so the intent comes from the project state rather than from the user having to know it already. Skip only when the user named a single operation to run.
24
+
25
+ Read five fields off the report and carry each to `## Route`:
26
+
27
+ - `unmigrated`: a domain sitting at the root layout with nothing under `.claude/`. The most urgent finding, because that domain reports no drift of its own while being entirely behind
28
+ - `superseded`: a file a newer seed folder replaced. Report it and stop. No command moves it, since the content is the project's own
29
+ - `seeds`: entries are `matching`, `stale`, `drifted`, or `missing`. Anything but `matching` needs the seed handoff
30
+ - `domains[].entries`: per-file `stale`, `customized`, `stranded`, and `orphaned` as before
31
+ - `historyUnavailable` on a domain or on `seeds`: attribution failed, so treat every difference as unverified and say so rather than reporting a file as untouched
32
+
33
+ State what the report found in one line per finding before acting on any of it.
34
+
21
35
  ## Route
22
36
 
23
- Map the stated intent to one lifecycle phase, then act:
37
+ Map the stated intent, or what `## Diagnose` found, to one lifecycle phase, then act:
38
+
39
+ - A domain in `unmigrated`: hand off to `migration-standards`
40
+ - Anything in `superseded`: tell the user which files and what replaced them. Do not move or delete them
24
41
 
25
42
  - First-time scaffold of a fresh project: hand off to `setup-init`
26
43
  - Governance rules for the project stack: hand off to `setup-gov`
@@ -9,46 +9,47 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
9
9
 
10
10
  ## Project-level
11
11
 
12
- | Command | Purpose |
13
- | ------------------------ | ---------------------------------------------------------------------------------------------- |
14
- | `aitk init [path]` | Bootstrap a project with selected toolkit domains |
15
- | `aitk sync [path]` | Sync all installed domains in a target project |
16
- | `aitk sync --check` | Report toolkit drift without writing (`--json`, `--exit-code`) |
17
- | `aitk sandbox [cat:cmd]` | Run sandbox scenarios (interactive or routed), toolkit-only like the tree it reads |
18
- | `aitk sandbox reset` | Reset sandbox to baseline |
19
- | `aitk sandbox clean` | Wipe the sandbox |
20
- | `aitk sandbox check` | Score a provisioned sandbox against a scenario expectation (`--json` for the verdict) |
21
- | `aitk sandbox coverage` | Report which scenarios declare expectations (`--json`, `--strict`, `--skills`) |
22
- | `aitk indexes regen` | Regenerate `index.md` files from sibling frontmatter |
23
- | `aitk docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
24
- | `aitk design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
25
- | `aitk slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck |
26
- | `aitk slides list` | List the available slide layouts (`--json` for the catalog) |
27
- | `aitk feedback` | Write toolkit feedback from stdin to `.claude/review/`, or open a GitHub issue with `--github` |
28
- | `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
29
- | `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
30
- | `aitk tasks validate` | Report board rows whose plan, task file, group, or file set does not hold (`--json`) |
31
- | `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
32
- | `aitk context audit` | Report required sections, length, depth, bullet weight, cited paths, provenance, and drift |
33
- | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
12
+ | Command | Purpose |
13
+ | -------------------------- | ---------------------------------------------------------------------------------------------- |
14
+ | `aitk init [path]` | Bootstrap a project with selected toolkit domains |
15
+ | `aitk sync [path]` | Sync all installed domains in a target project |
16
+ | `aitk sync --check` | Report toolkit drift without writing (`--json`, `--exit-code`) |
17
+ | `aitk sandbox [cat:cmd]` | Run sandbox scenarios (interactive or routed), toolkit-only like the tree it reads |
18
+ | `aitk sandbox reset` | Reset sandbox to baseline |
19
+ | `aitk sandbox clean` | Wipe the sandbox |
20
+ | `aitk sandbox check` | Score a provisioned sandbox against a scenario expectation (`--json` for the verdict) |
21
+ | `aitk sandbox coverage` | Report which scenarios declare expectations (`--json`, `--strict`, `--skills`) |
22
+ | `aitk indexes regen` | Regenerate `index.md` files from sibling frontmatter |
23
+ | `aitk docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
24
+ | `aitk design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
25
+ | `aitk slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck |
26
+ | `aitk slides list` | List the available slide layouts (`--json` for the catalog) |
27
+ | `aitk feedback` | Write toolkit feedback from stdin to `.claude/review/`, or open a GitHub issue with `--github` |
28
+ | `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
29
+ | `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
30
+ | `aitk tasks validate` | Report board rows whose plan, task file, group, or file set does not hold (`--json`) |
31
+ | `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
32
+ | `aitk context audit` | Report required sections, length, depth, bullet weight, cited paths, provenance, and drift |
33
+ | `aitk claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
34
+ | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
34
35
 
35
36
  ## Domain commands
36
37
 
37
38
  Each domain exposes a consistent shape where applicable: `list`, `install`, `sync`, `create`.
38
39
 
39
- | Domain | Subcommands |
40
- | ----------- | ---------------------------------------------------------------------- |
41
- | `tooling` | `list`, `sync`, `ref`, `create`, `verify`, `inject`, `prune-gitignore` |
42
- | `snippets` | `list`, `install`, `sync`, `create` |
43
- | `standards` | `list`, `install`, `sync` |
44
- | `gov` | `list`, `install`, `sync`, `build` |
45
- | `claude` | `init`, `sync`, `seeds list`, `skills list`, `setup [dest]` |
46
- | `wiki` | `init` |
47
- | `design` | `render` |
48
- | `slides` | `render`, `list` |
49
- | `tasks` | `archive`, `validate` |
50
- | `comments` | `scan` |
51
- | `context` | `audit` |
40
+ | Domain | Subcommands |
41
+ | ----------- | --------------------------------------------------------------------------- |
42
+ | `tooling` | `list`, `sync`, `ref`, `create`, `verify`, `inject`, `prune-gitignore` |
43
+ | `snippets` | `list`, `install`, `sync`, `create` |
44
+ | `standards` | `list`, `install`, `sync` |
45
+ | `gov` | `list`, `install`, `sync`, `build` |
46
+ | `claude` | `init`, `sync`, `seeds list`, `skills list`, `skills audit`, `setup [dest]` |
47
+ | `wiki` | `init` |
48
+ | `design` | `render` |
49
+ | `slides` | `render`, `list` |
50
+ | `tasks` | `archive`, `validate` |
51
+ | `comments` | `scan` |
52
+ | `context` | `audit` |
52
53
 
53
54
  Common patterns:
54
55
 
@@ -20,4 +20,5 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
20
20
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
21
21
  - [Sandbox](sandbox.md): Scenario routing, the expectation scoring surface, and the coverage census over scenarios and skills
22
22
  - [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
23
+ - [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
23
24
  - [Tasks](tasks.md): Selecting a shipped task by stem or pull request, the refusal reasons, the board checks validate runs, and why the board root defaults to the main worktree
@@ -7,7 +7,9 @@ description: Flags, exit codes, and JSON shape for aitk indexes regen, plus when
7
7
 
8
8
  `aitk indexes regen` rewrites `index.md` files from sibling frontmatter. With no positional paths, it walks the current directory. With paths, each resolves by walking up to the nearest indexed ancestor, bounded by `--root`. Duplicates dedupe. The whole-repo walk prunes `.git`, `node_modules`, and anything `.gitignore` covers via `git check-ignore`.
9
9
 
10
- A positional path is not filtered that way, because the walk-up resolves on the filesystem and never consults git. That is the only way to regenerate an index inside a gitignored folder, and it is how `.claude/tasks/` stays current.
10
+ A positional path is not filtered that way, because the walk-up resolves on the filesystem and never consults git. That is the only way to regenerate an index inside a gitignored folder, and it is how `.claude/tasks/` and `.claude/memory/` both stay current.
11
+
12
+ Each is driven by a `PostToolUse` hook matching `Write|Edit|MultiEdit`, so a file moved by a shell `mv` fires nothing. A caller that relocates an entry runs the command itself once the last move is done.
11
13
 
12
14
  | Option | Behavior |
13
15
  | --------------- | ---------------------------------------------------------------- |
@@ -117,6 +117,52 @@ commits touching its own source path. The `covers` field names the domains a
117
117
  target has actually stamped, so a domain that was never stamped is legible
118
118
  rather than reading as a clean one.
119
119
 
120
+ ### Surfaces reported beside the domains
121
+
122
+ Three sections sit outside the per-domain scan, because each names something
123
+ that walk cannot see. None of them produces a change, and no sync command
124
+ applies any of them.
125
+
126
+ All three report only against a toolkit-managed target, which is one carrying a
127
+ `.claude/` directory, a `CLAUDE.md`, or a domain still at the root layout. The
128
+ report says so through `managed` in the JSON and routes an unmanaged directory to
129
+ `aitk init`. Seeds are why the gate exists, since they enumerate from the toolkit
130
+ source rather than from what a target installed, so an unmanaged directory would
131
+ otherwise report every seed as `missing`.
132
+
133
+ A root-layout domain counts as a marker on its own, because the detection fires
134
+ only on root files the toolkit ships and a project in the old layout is one the
135
+ toolkit installed. When `managed` is false every section comes back empty rather
136
+ than the render alone going quiet, so a consumer reading `--json` never acts on a
137
+ finding the rendered half withheld.
138
+
139
+ `seeds` classifies every seed the toolkit ships against the target's copy, as
140
+ `matching`, `stale`, `drifted`, or `missing`. `missing` has no per-domain
141
+ equivalent, since the domain walk lists what a target installed and cannot see a
142
+ file that never arrived. There is no `customized` verdict here, because that one
143
+ needs a stamp and seeds carry none, so a file history cannot attribute stays
144
+ `drifted`. Reconcile the section with `claude-seed-sync`, which merges one
145
+ section at a time rather than replacing a file the project edits.
146
+
147
+ `superseded` names a file a newer seed folder replaced, such as `.claude/TASKS.md`
148
+ against the `.claude/tasks/` that now ships. The entry carries `replacedBy` and
149
+ nothing else, and the file is never deleted, since the content belongs to the
150
+ project and only its author can decide where it moves. The list derives from the
151
+ seed tree rather than from a fixed set of filenames, so a folder added later is
152
+ covered without a code change. Only an exact stem matches, which leaves a
153
+ suffixed variant such as `TASKS-ARCHIVE.md` unreported.
154
+
155
+ `unmigrated` names a domain sitting at the root layout an older toolkit installed
156
+ to, with nothing at the path the current one reads. It carries `rootPath`,
157
+ `installPath`, and a file count. Without it a project holding `standards/` at its
158
+ root reports zero entries for that domain and reads as clean, which is the most
159
+ misleading state the report can produce. Route it to `migration-standards`.
160
+
161
+ `unmigrated` counts toward `--exit-code`, since running the relocation closes it.
162
+ `superseded` and every seed state are excluded, for the reason `orphaned` already
163
+ is: only the user can move content they wrote, so failing a job on it leaves the
164
+ job red with no mechanical remedy.
165
+
120
166
  ## Bootstrap
121
167
 
122
168
  `aitk init` installs up to six core domains and reports each one independently. A
@@ -53,9 +53,10 @@ names one per line.
53
53
  Each entry also carries `requirement`, whether the folder holds a sibling
54
54
  `REQUIREMENT.md`. Every skill is meant to carry one, so a `false` is a gap to
55
55
  close rather than a recorded exemption, and the flag answers which skills are
56
- missing theirs without a caller listing the directory itself. Nothing gates the
57
- rule yet, which is why the flag is worth reading against the shipped corpus after
58
- a merge.
56
+ missing theirs without a caller listing the directory itself. `aitk claude skills
57
+ audit` gates that rule across both corpora, so read this flag for the state of
58
+ the shipped catalog and the audit for whether a working tree conforms. Its
59
+ surface is in `skills-audit.md`.
59
60
 
60
61
  ## Non-interactive examples
61
62
 
@@ -0,0 +1,55 @@
1
+ ---
2
+ title: Skill audit
3
+ description: Measuring both skill corpora against standards/skill.md, the checks it reads, and the requirement gate that is the only failing one
4
+ ---
5
+
6
+ # Skill audit
7
+
8
+ `aitk claude skills audit [path]` reports both skill corpora against the rules `standards/skill.md` states mechanically. It reads and reports. Fixing what it finds is separate work.
9
+
10
+ ```bash
11
+ aitk claude skills audit
12
+ aitk claude skills audit --json
13
+ aitk claude skills audit --requirements-only
14
+ ```
15
+
16
+ | Option | Behavior |
17
+ | --------------------- | -------------------------------------------------------------------- |
18
+ | `--json` | Add a machine-readable record on stdout, keeping the frame |
19
+ | `--requirements-only` | Run the gating presence check alone, printing nothing when it passes |
20
+
21
+ ## Corpus scope
22
+
23
+ Both trees are measured. `claude/skills/` installs into a target and `.claude/skills/` stays in the toolkit, the standard governs each, so a run reading one reports a pass over half its subject. A tree the project does not carry is skipped, which puts a target holding `.claude/skills/` alone in scope. A run where neither resolves refuses, since a clean exit over nothing measured is the outcome the command exists to prevent.
24
+
25
+ The audit reads the directory it is pointed at, defaulting to the cwd. `aitk claude skills list` resolves the shipped corpus from its own install root instead, so a dev-linked binary reports `main` no matter which worktree runs it. Auditing a branch needs the cwd reading, which is why the two commands resolve their root differently.
26
+
27
+ ## What it measures
28
+
29
+ Each check traces to a line in the standard.
30
+
31
+ - `REQUIREMENT.md` present in every skill folder
32
+ - Frontmatter `name` matching the folder name
33
+ - Frontmatter `description` present
34
+ - `description` under 1024 characters
35
+ - No `README.md` inside a skill folder
36
+ - Folder name in kebab-case
37
+ - Each `REQUIREMENT.md` declaring `Gap` and `Must`, matched at any heading level
38
+
39
+ A body whose frontmatter does not parse reports as declaring neither field rather than ending the run, so one malformed file cannot hide the corpus behind it. A key present with an empty value reads as absent, since a blank `name` would otherwise report as a name disagreeing with every folder. A folder carrying no requirement is reported once under presence rather than counted again for the sections it therefore lacks.
40
+
41
+ ## What it leaves alone
42
+
43
+ The report names its own blind spots on every run, including the run where everything passed. Whether each `Must` traces to a stated gap is the rule in that standard worth the most and no parser reads it. Whether a gap states an observed failure rather than an intent, and whether a description routes, are the same kind of judgment. The 5,000-word body ceiling is mechanical and still absent here.
44
+
45
+ A check with no rule behind it prints an opinion as a defect, which is where the list stops.
46
+
47
+ ## Exit codes
48
+
49
+ Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder, and requirement-section findings print and return `0`, because each is a judgment a reader settles and failing a push on one would make the check something to route around.
50
+
51
+ ## The requirement gate
52
+
53
+ `--requirements-only` is the half wired into `bun run check`. Presence of a required file is a fact with no false positives, and the rule had nothing reading it, so a skill shipped without the sibling passed every stage while the standard required it.
54
+
55
+ The check is preventive rather than diagnostic. Every mechanical rule passed across both corpora the day it shipped, so what it buys is the regression it stops rather than a backlog it surfaces.
@@ -84,7 +84,7 @@ The sweep reads the whole board rather than the tasks the session touched. It is
84
84
 
85
85
  `aitk:claude-docs` decides which task closed by reading the diff rather than the conversation. It resolves a merge base against `origin/main`, unions the committed diff with the working tree and untracked files, then matches unchecked outcomes on the board against what shipped. A task that shipped without ever being discussed still gets marked. Requirements, architecture, and design stay session-sourced, because a diff cannot carry a judgment.
86
86
 
87
- `.claude/tasks/` is gitignored and resolves at the main worktree root, so every session shares one board. One file per task is what keeps concurrent sessions from overwriting each other, since a gitignored board has no history to recover a lost write from. Its `index.md` is generated by a hook rather than by `bun run check`, because the whole-repo index walk skips gitignored folders. A hand-maintained `priority.md` sits beside it carrying execution order and what each task is waiting on, which the alphabetical index cannot express.
87
+ `.claude/tasks/` is gitignored and resolves at the main worktree root, so every session shares one board. One file per task is what keeps concurrent sessions from overwriting each other, since a gitignored board has no history to recover a lost write from. Its `index.md` is generated by a hook rather than by `bun run check`, because the whole-repo index walk skips gitignored folders. `.claude/memory/` carries the same arrangement, its own hook regenerating `index.md` from each entry's `title`, `description`, and `category`. A hand-maintained `priority.md` sits beside it carrying execution order and what each task is waiting on, which the alphabetical index cannot express.
88
88
 
89
89
  `aitk tasks validate` checks what those rows claim against what the tree holds: every plan pointer resolves, every row and task file map one to one, no task sits in two groups, and no two rows marked ready touch the same file. That last check is the half a reader cannot run by eye, and it is what keeps two workers from being handed colliding work. It reports and never writes, because a row is the orchestrator's claim and a validator repairing one would assert the claim it exists to test. Nothing fires it automatically, since the board is gitignored per-machine scratch with no shared moment to hang a hook on, so the orchestrator's sweep calls it at the point the readiness claim is made.
90
90
 
@@ -106,7 +106,8 @@ For features on a mature stack, chain the post-plan pipeline in one session. App
106
106
  - An empty changed-file list stops the chain rather than counting as prose-only. The filename test passes vacuously on an empty set, which routed a branch past review instead of through it.
107
107
  - Every stop leaves recoverable state. Fix and resume with `/git-ship`
108
108
  - Skip autoship for auth, migrations, security-sensitive changes, or work where the plan itself is uncertain
109
- - After the PR is open, both `autoship` and `git-ship` invoke `claude-memory-capture` to write session patterns into `.claude/memory/`. If capture wrote at least one entry, `claude-memory-review` then proposes a decision-ready fix scoped to those captures while context is fresh, otherwise it is skipped. They stop at Propose. Review the receipt and run Apply yourself, on its own commit separate from the feature. Run `claude-memory-review` standalone to curate the whole pen.
109
+ - Both `autoship` and `git-ship` open with `claude-memory-capture`, which sends what the session learned to the surface that owns it. A fact about a domain carrying an entry in `.claude/context/index.md` is routed to that entry, and `claude-docs` folds it in on the next step, so it ships in the same pull request. Anything no entry owns stays a file in `.claude/memory/`. Capture leads rather than trails because a routed fact edits a tracked file, which has to reach the branch before the commit steps run.
110
+ - If capture wrote at least one memory file, `claude-memory-review` then proposes a decision-ready fix scoped to those entries while context is fresh, otherwise it is skipped. It stops at Propose. Review the receipt and run Apply yourself, on its own commit separate from the feature. Run `claude-memory-review` standalone to curate the whole pen. An entry it retires moves to `.claude/.tmp/memory-archive/` rather than being deleted, since the folder is gitignored and a bulk pass has no undo.
110
111
 
111
112
  ### UI polish
112
113
 
@@ -104,7 +104,11 @@ That attribution comes from `.claude/aitk.json`, a stamp every install and sync
104
104
 
105
105
  A project that has never synced under a toolkit new enough to write a stamp falls back to the toolkit's own git history. Installed content matching any version that history published proves the file untouched, so it reports `stale` naming the commit it came from, and content matching no published version stays `drifted`. That fallback needs the toolkit as a git checkout. Installed from the registry it ships source without history, and the report says attribution was unavailable rather than reading every file as a local edit.
106
106
 
107
- Add `--json` for the machine-readable report, and `--exit-code` to fail a CI job when a target falls behind. Files the project authored itself never count toward that exit code.
107
+ Three further causes sit outside the per-domain scan, each naming something that walk cannot see. A seed the project edited is reported under `seeds` and reconciled with `aitk:claude-seed-sync`, since no sync command touches a seed. A file a newer seed folder replaced is reported under `superseded`, such as `.claude/TASKS.md` against the `.claude/tasks/` that now ships, and nothing moves it because the content is the project's own. A domain sitting at the root layout with nothing under `.claude/` is reported under `unmigrated` and handed to `aitk:migration-standards`.
108
+
109
+ That last one matters most on an older project. Before it existed, a target holding `standards/` at its root reported zero entries for that domain, so a project that had never migrated was indistinguishable from one that was fully current.
110
+
111
+ Add `--json` for the machine-readable report, and `--exit-code` to fail a CI job when a target falls behind. Files the project authored itself never count toward that exit code, and neither do superseded artifacts or seed drift, since both need the user to move content only they can place. An unmigrated domain does count, because running the relocation closes it.
108
112
 
109
113
  Tooling is not covered by the stamp. Reconcile those configs with `aitk tooling <stack> <path>`.
110
114
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.44.0",
4
+ "version": "0.46.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -183,6 +183,14 @@ main() {
183
183
  run_check "cd $PROJECT_ROOT && bun src/cli.ts context audit --citations-only" "A cited context path does not resolve. Run bun src/cli.ts context audit."
184
184
  log_info "Context citations resolve"
185
185
 
186
+ # Presence of a required file is a fact, so it gates. The name, description,
187
+ # folder, and requirement-section measures beside it report and are read from a
188
+ # bare run. `bun src/cli.ts` for the reason the stage above uses it, and the
189
+ # command reads the cwd, so this measures the worktree being pushed.
190
+ log_step "Skill requirements"
191
+ run_check "cd $PROJECT_ROOT && bun src/cli.ts claude skills audit --requirements-only" "A skill folder carries no REQUIREMENT.md. Run bun src/cli.ts claude skills audit."
192
+ log_info "Skill requirements present"
193
+
186
194
  log_step "Plugin manifests"
187
195
  if ! command -v claude >/dev/null 2>&1; then
188
196
  log_info "Skipped, claude is not installed"
@@ -7,10 +7,16 @@ const SEEDS_DIR = join('tooling', 'claude', 'seeds')
7
7
  const CLAUDE_DIR = '.claude'
8
8
  const CLAUDE_MD = 'CLAUDE.md'
9
9
  const HOOKS = 'hooks'
10
- const SUBDIRS: readonly string[] = [
10
+ /**
11
+ * Seed subdirectories under `.claude/`. Exported because each one replaced a
12
+ * single file of the same stem in an older layout, which is what
13
+ * `@/sync/layout` pairs a target against to find a superseded artifact.
14
+ */
15
+ export const SUBDIRS: readonly string[] = [
11
16
  HOOKS,
12
17
  'context',
13
18
  'diagrams',
19
+ 'memory',
14
20
  'tasks',
15
21
  'wireframes',
16
22
  ]
@@ -0,0 +1,215 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import {
4
+ type Frontmatter,
5
+ parseFrontmatter,
6
+ readField,
7
+ } from '@/indexes/frontmatter'
8
+
9
+ /** Returned when a skill folder carries no `REQUIREMENT.md`, the gating check. */
10
+ export const EXIT_MISSING_REQUIREMENT = 2
11
+
12
+ /** The description ceiling stated in the frontmatter rules of `standards/skill.md`. */
13
+ export const DESCRIPTION_LIMIT = 1024
14
+
15
+ /** The two headings the requirement template declares, matched at any level. */
16
+ export const REQUIREMENT_SECTIONS: readonly string[] = ['Gap', 'Must']
17
+
18
+ /**
19
+ * Both trees the standard governs. `claude/skills/` ships to a target and
20
+ * `.claude/skills/` stays here, and every rule measured below applies to each,
21
+ * so a corpus reading one of them reports a pass over half the subject.
22
+ */
23
+ export const CORPORA: readonly string[] = [
24
+ join('claude', 'skills'),
25
+ join('.claude', 'skills'),
26
+ ]
27
+
28
+ /** Kebab-case, which the standard states as no spaces, capitals, or underscores. */
29
+ const KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
30
+
31
+ const HEADING = /^#{1,6}\s+(.+?)\s*$/
32
+
33
+ export interface SkillFinding {
34
+ readonly rel: string
35
+ readonly detail: string
36
+ }
37
+
38
+ export interface CorpusReport {
39
+ readonly rel: string
40
+ readonly skills: number
41
+ }
42
+
43
+ export interface SkillsAudit {
44
+ readonly corpora: readonly CorpusReport[]
45
+ readonly skills: number
46
+ readonly missingRequirement: readonly string[]
47
+ readonly readme: readonly string[]
48
+ readonly folderName: readonly string[]
49
+ readonly missingDescription: readonly string[]
50
+ readonly nameMismatch: readonly SkillFinding[]
51
+ readonly longDescription: readonly SkillFinding[]
52
+ readonly requirementSections: readonly SkillFinding[]
53
+ }
54
+
55
+ interface SkillSource {
56
+ readonly rel: string
57
+ readonly folder: string
58
+ readonly hasReadme: boolean
59
+ readonly name: string | undefined
60
+ readonly description: string | undefined
61
+ /** Undefined when the folder carries no `REQUIREMENT.md` at all. */
62
+ readonly requirementHeadings: readonly string[] | undefined
63
+ }
64
+
65
+ /**
66
+ * Measures both skill corpora against the rules `standards/skill.md` states
67
+ * mechanically. A corpus the project does not carry is skipped rather than
68
+ * reported, so a target holding only `.claude/skills/` reads as in scope.
69
+ *
70
+ * Reads raw frontmatter rather than `listSkills`, which prefers the folder name
71
+ * over the declared one and so can never surface a disagreement between them.
72
+ */
73
+ export async function auditSkills(root: string): Promise<SkillsAudit> {
74
+ const present = CORPORA.map((rel) => ({ rel, dir: join(root, rel) })).filter(
75
+ (corpus) => existsSync(corpus.dir),
76
+ )
77
+
78
+ const perCorpus = await Promise.all(
79
+ present.map((corpus) => readCorpus(corpus.rel, corpus.dir)),
80
+ )
81
+ const sources = perCorpus.flat()
82
+
83
+ return {
84
+ corpora: present.map((corpus, index) => ({
85
+ rel: corpus.rel,
86
+ skills: perCorpus[index]?.length ?? 0,
87
+ })),
88
+ skills: sources.length,
89
+ missingRequirement: sources
90
+ .filter((source) => source.requirementHeadings === undefined)
91
+ .map((source) => source.rel),
92
+ readme: sources
93
+ .filter((source) => source.hasReadme)
94
+ .map((source) => source.rel),
95
+ folderName: sources
96
+ .filter((source) => !KEBAB_CASE.test(source.folder))
97
+ .map((source) => source.rel),
98
+ missingDescription: sources
99
+ .filter((source) => source.description === undefined)
100
+ .map((source) => source.rel),
101
+ nameMismatch: sources.flatMap(nameFindings),
102
+ longDescription: sources.flatMap(lengthFindings),
103
+ requirementSections: sources.flatMap(sectionFindings),
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Only a required file that is absent sets a failing code, which is a fact.
109
+ * Every other measure is a judgment a reader settles, and failing a push on one
110
+ * teaches contributors to route around the stage.
111
+ */
112
+ export function auditExitCode(report: SkillsAudit): number {
113
+ return report.missingRequirement.length > 0 ? EXIT_MISSING_REQUIREMENT : 0
114
+ }
115
+
116
+ async function readCorpus(rel: string, dir: string): Promise<SkillSource[]> {
117
+ const paths = [
118
+ ...new Bun.Glob('*/SKILL.md').scanSync({ cwd: dir, onlyFiles: true }),
119
+ ].sort()
120
+
121
+ return Promise.all(paths.map((path) => readSkill(rel, dir, dirname(path))))
122
+ }
123
+
124
+ async function readSkill(
125
+ rel: string,
126
+ dir: string,
127
+ folder: string,
128
+ ): Promise<SkillSource> {
129
+ const skillDir = join(dir, folder)
130
+ const requirementPath = join(skillDir, 'REQUIREMENT.md')
131
+
132
+ const [body, requirement] = await Promise.all([
133
+ Bun.file(join(skillDir, 'SKILL.md')).text(),
134
+ existsSync(requirementPath)
135
+ ? Bun.file(requirementPath).text()
136
+ : Promise.resolve(undefined),
137
+ ])
138
+
139
+ const fields = readFields(body)
140
+
141
+ return {
142
+ rel: join(rel, folder),
143
+ folder,
144
+ hasReadme: existsSync(join(skillDir, 'README.md')),
145
+ name: declared(readField(fields, 'name')),
146
+ description: declared(readField(fields, 'description')),
147
+ requirementHeadings:
148
+ requirement === undefined ? undefined : headings(requirement),
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Returns undefined rather than throwing on unparseable YAML, so one malformed
154
+ * body reports as missing its required fields instead of ending the audit
155
+ * before it reaches the rest of the corpus.
156
+ */
157
+ function readFields(body: string): Frontmatter | undefined {
158
+ try {
159
+ return parseFrontmatter(body)
160
+ } catch {
161
+ return undefined
162
+ }
163
+ }
164
+
165
+ /**
166
+ * A key carrying an empty value declares nothing, so blank reads as absent
167
+ * rather than as a name that disagrees with every folder.
168
+ */
169
+ function declared(value: string | undefined): string | undefined {
170
+ const trimmed = value?.trim()
171
+ return trimmed === undefined || trimmed === '' ? undefined : trimmed
172
+ }
173
+
174
+ function headings(source: string): string[] {
175
+ return source
176
+ .split('\n')
177
+ .map((line) => HEADING.exec(line)?.[1])
178
+ .filter((text): text is string => text !== undefined)
179
+ }
180
+
181
+ function nameFindings(source: SkillSource): SkillFinding[] {
182
+ if (source.name === undefined) {
183
+ return [{ rel: source.rel, detail: 'frontmatter declares no name' }]
184
+ }
185
+ if (source.name === source.folder) return []
186
+ return [{ rel: source.rel, detail: `frontmatter name: ${source.name}` }]
187
+ }
188
+
189
+ function lengthFindings(source: SkillSource): SkillFinding[] {
190
+ const { description } = source
191
+ if (description === undefined) return []
192
+ if (description.length <= DESCRIPTION_LIMIT) return []
193
+ return [{ rel: source.rel, detail: `${description.length} characters` }]
194
+ }
195
+
196
+ /**
197
+ * Stays silent on a folder carrying no requirement at all, which the presence
198
+ * check already reports. Counting the same skill twice reads as two defects.
199
+ */
200
+ function sectionFindings(source: SkillSource): SkillFinding[] {
201
+ const { requirementHeadings } = source
202
+ if (requirementHeadings === undefined) return []
203
+
204
+ const missing = REQUIREMENT_SECTIONS.filter(
205
+ (section) => !requirementHeadings.includes(section),
206
+ )
207
+ if (missing.length === 0) return []
208
+
209
+ return [
210
+ {
211
+ rel: join(source.rel, 'REQUIREMENT.md'),
212
+ detail: `missing: ${missing.join(', ')}`,
213
+ },
214
+ ]
215
+ }