@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.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-autoship/SKILL.md +15 -10
- package/claude/skills/claude-docs/SKILL.md +16 -2
- package/claude/skills/claude-memory-capture/REQUIREMENT.md +14 -4
- package/claude/skills/claude-memory-capture/SKILL.md +45 -16
- package/claude/skills/claude-memory-review/REQUIREMENT.md +8 -2
- package/claude/skills/claude-memory-review/SKILL.md +41 -25
- package/claude/skills/claude-orchestrate/SKILL.md +5 -0
- package/claude/skills/claude-seed-sync/REQUIREMENT.md +3 -1
- package/claude/skills/claude-seed-sync/SKILL.md +16 -4
- package/claude/skills/git-ship/SKILL.md +14 -11
- package/claude/skills/session-resume/SKILL.md +2 -2
- package/claude/skills/toolkit-operator/SKILL.md +18 -1
- package/docs/agents/commands.md +36 -35
- package/docs/agents/index.md +1 -0
- package/docs/agents/indexes.md +3 -1
- package/docs/agents/install-and-sync.md +46 -0
- package/docs/agents/scripting.md +4 -3
- package/docs/agents/skills-audit.md +55 -0
- package/docs/ai-workflow.md +3 -2
- package/docs/target-projects.md +5 -1
- package/package.json +1 -1
- package/scripts/core/verify.sh +8 -0
- package/src/claude/seeds.ts +7 -1
- package/src/claude/skills-audit.ts +215 -0
- package/src/claude/skills-list.ts +3 -3
- package/src/commands/claude.ts +283 -5
- package/src/commands/context.ts +1 -4
- package/src/commands/sync.ts +54 -1
- package/src/sync/check.ts +71 -0
- package/src/sync/layout.ts +139 -0
- package/src/sync/seeds-report.ts +111 -0
- package/src/ui.ts +5 -0
- package/tooling/claude/reference.md +11 -1
- package/tooling/claude/seeds/.claude/hooks/memory-index.sh +60 -0
- package/tooling/claude/seeds/.claude/memory/index.md +8 -0
- package/tooling/claude/seeds/.claude/settings.json +4 -0
- 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-
|
|
18
|
-
2. Invoke `aitk:docs
|
|
19
|
-
3.
|
|
20
|
-
4.
|
|
21
|
-
5. Invoke `aitk:git-
|
|
22
|
-
6. Invoke `aitk:git-
|
|
23
|
-
7.
|
|
24
|
-
8.
|
|
25
|
-
9. If
|
|
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
|
|
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
|
|
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/
|
|
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`
|
package/docs/agents/commands.md
CHANGED
|
@@ -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
|
|
13
|
-
|
|
|
14
|
-
| `aitk init [path]`
|
|
15
|
-
| `aitk sync [path]`
|
|
16
|
-
| `aitk sync --check`
|
|
17
|
-
| `aitk sandbox [cat:cmd]`
|
|
18
|
-
| `aitk sandbox reset`
|
|
19
|
-
| `aitk sandbox clean`
|
|
20
|
-
| `aitk sandbox check`
|
|
21
|
-
| `aitk sandbox coverage`
|
|
22
|
-
| `aitk indexes regen`
|
|
23
|
-
| `aitk docs [topic]`
|
|
24
|
-
| `aitk design render`
|
|
25
|
-
| `aitk slides render`
|
|
26
|
-
| `aitk slides list`
|
|
27
|
-
| `aitk feedback`
|
|
28
|
-
| `aitk transcripts <url>`
|
|
29
|
-
| `aitk tasks archive`
|
|
30
|
-
| `aitk tasks validate`
|
|
31
|
-
| `aitk comments scan`
|
|
32
|
-
| `aitk context audit`
|
|
33
|
-
| `aitk
|
|
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
|
|
package/docs/agents/index.md
CHANGED
|
@@ -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
|
package/docs/agents/indexes.md
CHANGED
|
@@ -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/`
|
|
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
|
package/docs/agents/scripting.md
CHANGED
|
@@ -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.
|
|
57
|
-
|
|
58
|
-
a
|
|
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.
|
package/docs/ai-workflow.md
CHANGED
|
@@ -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
|
-
-
|
|
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
|
|
package/docs/target-projects.md
CHANGED
|
@@ -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
|
-
|
|
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
package/scripts/core/verify.sh
CHANGED
|
@@ -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"
|
package/src/claude/seeds.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|