@erclx/aitk 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-autoship/SKILL.md +2 -2
- package/claude/skills/claude-docs/SKILL.md +11 -0
- package/claude/skills/claude-groundwork/SKILL.md +1 -1
- package/claude/skills/claude-seed-sync/REQUIREMENT.md +2 -2
- package/claude/skills/claude-seed-sync/SKILL.md +15 -18
- package/claude/skills/create-standard/REQUIREMENT.md +7 -10
- package/claude/skills/create-standard/SKILL.md +10 -11
- package/claude/skills/git-pr/references/labels.md +7 -1
- package/claude/skills/migration-standards/REQUIREMENT.md +10 -7
- package/claude/skills/migration-standards/SKILL.md +24 -22
- package/claude/skills/migration-superseded/REQUIREMENT.md +2 -2
- package/claude/skills/migration-superseded/SKILL.md +7 -9
- package/claude/skills/setup-gov/REQUIREMENT.md +2 -2
- package/claude/skills/toolkit-cli/SKILL.md +13 -14
- package/docs/agents/commands.md +1 -1
- package/docs/agents/install-and-sync.md +27 -41
- package/docs/agents/records.md +1 -1
- package/docs/agents/scripting.md +2 -9
- package/docs/target-projects.md +14 -15
- package/package.json +1 -1
- package/scripts/core/install-check.sh +5 -1
- package/scripts/manage-sandbox.sh +4 -7
- package/scripts/standards/list.sh +6 -4
- package/src/cli.ts +1 -1
- package/src/commands/gov.ts +1 -7
- package/src/commands/init.ts +1 -4
- package/src/commands/standards.ts +9 -141
- package/src/commands/sync.ts +1 -2
- package/src/gov/install.ts +0 -9
- package/src/init/flags.ts +3 -10
- package/src/init/plan.ts +8 -26
- package/src/init/steps.ts +0 -24
- package/src/records/backup.ts +11 -4
- package/src/standards/read.ts +16 -14
- package/src/sync/check.ts +4 -7
- package/src/sync/layout.ts +8 -10
- package/src/sync/stamp.ts +18 -15
- package/src/sync/target.ts +1 -8
- package/standards/skill.md +6 -6
- package/standards/standard.md +1 -1
- package/tooling/claude/manifest.toml +1 -1
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/ARCHITECTURE.md +1 -1
- package/tooling/claude/seeds/.claude/DESIGN.md +1 -1
- package/tooling/claude/seeds/.claude/REQUIREMENTS.md +1 -1
- package/tooling/claude/seeds/CLAUDE.md +2 -2
- package/src/standards/adapter.ts +0 -51
- package/src/standards/closure.ts +0 -200
- package/src/standards/index-refresh.ts +0 -44
- package/src/standards/install.ts +0 -52
|
@@ -14,14 +14,13 @@ never adds new ones. A rule the toolkit does not ship is left alone, which is
|
|
|
14
14
|
how project-authored rules survive. It also removes a stale `.claude/GOV.md`
|
|
15
15
|
from the retired build. Use `aitk gov install` to add rules.
|
|
16
16
|
|
|
17
|
-
`aitk standards sync`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
and
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`aitk snippets sync` behaves the same way against `.claude/snippets/`. It
|
|
17
|
+
There is no `aitk standards sync` and no `aitk standards install`. The corpus
|
|
18
|
+
installs into no project, so the domain has nothing in a target to reconcile.
|
|
19
|
+
`aitk standards <name>` prints one, resolving `standards/` at the working root
|
|
20
|
+
and then the corpus inside the package, and `aitk standards list --json` carries
|
|
21
|
+
the catalog.
|
|
22
|
+
|
|
23
|
+
`aitk snippets sync` matches by path relative to `.claude/snippets/`. It
|
|
25
24
|
matches by path relative to that directory, so a snippet the toolkit no longer
|
|
26
25
|
ships, or one authored directly in the target, is reported and skipped rather
|
|
27
26
|
than deleted. It is not preset-aware, so a project that installed `essentials`
|
|
@@ -44,33 +43,22 @@ headlessly, so a call that names its stack or category is unchanged.
|
|
|
44
43
|
`aitk snippets install`. Both resolve the target before anything else, so a path
|
|
45
44
|
that does not exist fails rather than being scaffolded.
|
|
46
45
|
|
|
47
|
-
## Standards
|
|
46
|
+
## Standards resolution
|
|
48
47
|
|
|
49
|
-
`aitk standards
|
|
50
|
-
|
|
48
|
+
`aitk standards <name>` writes the document to stdout and the root it answered
|
|
49
|
+
from to stderr, so a caller capturing with `$(...)` receives the document alone.
|
|
51
50
|
A name resolves with or without its `.md` extension, and one that matches no
|
|
52
|
-
standard
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
in
|
|
62
|
-
|
|
63
|
-
A citation inside a standard's `Does not govern:` list is a handoff rather than
|
|
64
|
-
a dependency, and the closure stops at it. That entry names a concern a sibling
|
|
65
|
-
owns and this standard does not, so a caller who did not ask for that concern
|
|
66
|
-
does not need the file. Each one is reported under a `Scope handoffs not
|
|
67
|
-
installed` step, naming what to add to `--only` if the project wants it after
|
|
68
|
-
all.
|
|
69
|
-
|
|
70
|
-
That split is what keeps a selection to a slice. Nearly all the citation density
|
|
71
|
-
in the corpus sits inside those scope lists, so following them pulls the whole
|
|
72
|
-
corpus in behind any single name. Following dependencies alone, a single name
|
|
73
|
-
lands between one and three of the fifteen.
|
|
51
|
+
standard exits 1 after listing the catalog on stderr.
|
|
52
|
+
|
|
53
|
+
Two roots answer, in order: `standards/` at the working directory, then the
|
|
54
|
+
corpus inside the package. A project that authors standards of its own uses the
|
|
55
|
+
first, and this repository's own authoring root is the same path. `.claude/standards/`
|
|
56
|
+
is not among them. It is this repository's generated mirror, and a copy an older
|
|
57
|
+
toolkit installed into a target resolves nothing.
|
|
58
|
+
|
|
59
|
+
There is no citation closure to compute, since nothing is copied. A standard
|
|
60
|
+
that hands a concern to a sibling names it in `Does not govern:` and a reader
|
|
61
|
+
runs the verb again for that name.
|
|
74
62
|
|
|
75
63
|
## Governance regen
|
|
76
64
|
|
|
@@ -191,10 +179,9 @@ suffixed variant such as `TASKS-ARCHIVE.md` unreported.
|
|
|
191
179
|
|
|
192
180
|
Route it to `migration-superseded`. That skill resolves the standard governing
|
|
193
181
|
`replacedBy` from the `appliesTo` the standards catalog declares, reads the
|
|
194
|
-
destination shape
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
rather than a shape the project never adopted.
|
|
182
|
+
destination shape with `aitk standards <name>`, and proposes the split without
|
|
183
|
+
writing. Where a folder has no governing standard, the entry earns a named
|
|
184
|
+
refusal rather than a shape nobody stated.
|
|
198
185
|
|
|
199
186
|
`unmigrated` names a domain sitting at the root layout an older toolkit installed
|
|
200
187
|
to, with nothing at the path the current one reads. It carries `rootPath`,
|
|
@@ -275,10 +262,9 @@ which is what makes it scriptable.
|
|
|
275
262
|
|
|
276
263
|
`--stack` defaults to `base`, and the default
|
|
277
264
|
does not read as a passed flag, so a bare `aitk init` installs governance and
|
|
278
|
-
still prompts. `--skip` takes `wiki
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
run spawns the command it always did.
|
|
265
|
+
still prompts. `--skip` takes `wiki` and `governance`, and warns without aborting
|
|
266
|
+
on any other value. There is no `--standards`, since no run writes a standard
|
|
267
|
+
into the target.
|
|
282
268
|
|
|
283
269
|
## Unguarded tooling primitives
|
|
284
270
|
|
package/docs/agents/records.md
CHANGED
|
@@ -110,7 +110,7 @@ aitk records push --json
|
|
|
110
110
|
aitk records pull
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
The backed folders are `groundwork`, `intake`, `memory`, `plans`, `plans-archive`, `review`, `review-archive`, `task-archive`, `tasks`, and `teach`, all under `.claude/`. They are the
|
|
113
|
+
The backed folders are `groundwork`, `intake`, `memory`, `plans`, `plans-archive`, `review`, `review-archive`, `task-archive`, `tasks`, and `teach`, all under `.claude/`. They are the Claude ignore group the claude manifest ships, minus three entries: `.claude/.tmp`, which is deletable without loss, `.claude/worktrees/`, whose contents belong to the project repository already, and `.claude/.records.git/`, which is the history the other ten are pushed into. The list is a constant rather than configuration, matching the four folder names `validate` hardcodes.
|
|
114
114
|
|
|
115
115
|
Records are gitignored by design, so the history lives in a second git directory at `.claude/.records.git` with `.claude/` as its work tree. Every path stays where it is, which is what a separate checkout could not do. The verbs stage the ten folders by explicit pathspec with `--force`, so nothing outside them can enter the index however the ignore rules read, and the project working tree and its index are never touched.
|
|
116
116
|
|
package/docs/agents/scripting.md
CHANGED
|
@@ -112,18 +112,11 @@ AITK_NON_INTERACTIVE=1 aitk snippets install essentials /path/to/project
|
|
|
112
112
|
# Update snippets already installed, leaving project-authored ones alone
|
|
113
113
|
AITK_NON_INTERACTIVE=1 aitk snippets sync /path/to/project
|
|
114
114
|
|
|
115
|
-
#
|
|
116
|
-
AITK_NON_INTERACTIVE=1 aitk standards
|
|
117
|
-
|
|
118
|
-
# Copy every standard into a target, overwriting what is there
|
|
119
|
-
AITK_NON_INTERACTIVE=1 aitk standards install /path/to/project
|
|
120
|
-
|
|
121
|
-
# Install a named subset, expanded to the standards it cites
|
|
122
|
-
AITK_NON_INTERACTIVE=1 aitk standards install --only slug /path/to/project
|
|
115
|
+
# Print one standard. Nothing installs the corpus, so this is how a target reads one
|
|
116
|
+
AITK_NON_INTERACTIVE=1 aitk standards slug >slug.md
|
|
123
117
|
|
|
124
118
|
# Bootstrap a project. Any flag suppresses the confirmation prompt
|
|
125
119
|
AITK_NON_INTERACTIVE=1 aitk init --stack astro --skip wiki /path/to/project
|
|
126
|
-
AITK_NON_INTERACTIVE=1 aitk init --standards design,wireframes /path/to/project
|
|
127
120
|
|
|
128
121
|
# Run every domain sync. The git workflow is refused headlessly, so nothing is pushed
|
|
129
122
|
AITK_NON_INTERACTIVE=1 aitk sync /path/to/project
|
package/docs/target-projects.md
CHANGED
|
@@ -27,7 +27,7 @@ The `aitk` CLI is separate. Twenty skills invoke it in a command position, and a
|
|
|
27
27
|
bun install --global @erclx/aitk
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
The package ships the catalogs the CLI reads, not only `src/`, so `aitk init` resolves
|
|
30
|
+
The package ships the catalogs the CLI reads, not only `src/`, so `aitk init` resolves snippets, governance, tooling, and the seeds from wherever the package landed, and `aitk standards <name>` reads the corpus from there too.
|
|
31
31
|
|
|
32
32
|
Pointing Claude Code at a checkout stays the development path, where a local skill edit overrides the installed copy for that session.
|
|
33
33
|
|
|
@@ -84,12 +84,13 @@ Run `aitk tooling list --json` and `aitk gov list --json` to see the current cat
|
|
|
84
84
|
|
|
85
85
|
### Core domains and skips
|
|
86
86
|
|
|
87
|
-
`aitk init` installs base tooling, Claude workflow, governance,
|
|
87
|
+
`aitk init` installs base tooling, Claude workflow, governance, and snippets, and scaffolds `.claude/wiki/`. Governance defaults to the `base` stack, so a bare init carries the rules that route a project. Pass `--stack <name>` to install a framework stack instead.
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
No standard is written into the project. Each governance rule's authority line names `aitk standards <name>`, which answers from the corpus inside the CLI's own package, and every toolkit skill names the copy in its own plugin root. `aitk markdown audit` needs no standard at all, its ban sets and checkpoints shipping with the package as data.
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
`governance` and `wiki` are skippable:
|
|
92
|
+
|
|
93
|
+
- `--skip governance`: leave `.claude/rules/` empty, so no coding standard loads on a file match. The preview names any `--add` rules the skip drops, and the run prints the `aitk gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
|
|
93
94
|
- `--skip wiki`: skip the `.claude/wiki/` scaffold. A target that already carries a root `wiki/` keeps it, since the verb reports that folder rather than migrating it.
|
|
94
95
|
|
|
95
96
|
The plugin corpus carries runtime behavior rather than reference prose alone, because the pre-publish scan and the branch-slug transform each have a standard of their own, `publish.md` and `slug.md`, cited by the skills that run them.
|
|
@@ -102,7 +103,8 @@ When a new need appears after scaffold, install the one domain without re-runnin
|
|
|
102
103
|
- Project-specific rule the toolkit does not ship: invoke `aitk:create-rule`. It scaffolds a rule into `.claude/rules/` with a non-colliding number, and `aitk gov sync` leaves it untouched.
|
|
103
104
|
- Index.md system for a markdown-heavy folder that emerged: invoke `aitk:setup-indexes`
|
|
104
105
|
- A snippet preset or category: `aitk snippets install <preset|category|all> <path>`. The argument is required, since the picker refuses headlessly rather than choosing for the caller
|
|
105
|
-
|
|
106
|
+
|
|
107
|
+
Standards are not on that list, and there is nothing to add. No standard installs into a project, by default or by flag, so a session that needs one runs `aitk standards <name>` and reads it from the copy inside the package. A project holding a `.claude/standards/` folder from an older toolkit is carrying a stale artifact nothing reads, and deleting it is safe.
|
|
106
108
|
|
|
107
109
|
Per-domain mechanics live in the corresponding `docs/<domain>.md`. The skill body in `claude/skills/<skill>/SKILL.md` covers detection and preview.
|
|
108
110
|
|
|
@@ -122,15 +124,15 @@ The report opens by naming the binary running it. The installed version reads ag
|
|
|
122
124
|
|
|
123
125
|
A `stale` file still matches what the toolkit installed, so the update is mechanical. A `customized` file carries local edits, so taking the upstream version is a decision and `aitk:claude-seed-sync` is the tool for it. A `stranded` file sits where an older toolkit installed it and the toolkit has since moved, which is what `aitk:migration-standards` handles.
|
|
124
126
|
|
|
125
|
-
That attribution comes from `.claude/aitk.json`, a stamp every install and sync writes.
|
|
127
|
+
That attribution comes from `.claude/aitk.json`, a stamp every install and sync writes. Snippets and governance record a hash per installed file. Tooling records the stack chain it resolved instead, since its install runs no per-file walk to attribute.
|
|
126
128
|
|
|
127
|
-
Each domain holds its own toolkit commit, so syncing governance today does not move the revision
|
|
129
|
+
Each domain holds its own toolkit commit, so syncing governance today does not move the revision snippets measures against, and each domain reports the upstream commits touching its own source path. Running any sync stamps that domain, and the report names the ones still unstamped.
|
|
128
130
|
|
|
129
131
|
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.
|
|
130
132
|
|
|
131
133
|
Four 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`.
|
|
132
134
|
|
|
133
|
-
That third one matters most on an older project. Before it existed, a target holding `
|
|
135
|
+
That third one matters most on an older project. Before it existed, a target holding `snippets/` at its root reported zero entries for that domain, so a project that had never migrated was indistinguishable from one that was fully current. It covers snippets alone, since a root `standards/` folder in a target is the project's own authoring surface and nothing proposes moving it.
|
|
134
136
|
|
|
135
137
|
#### What the toolkit stopped shipping
|
|
136
138
|
|
|
@@ -158,15 +160,12 @@ Reconcile the configs with `aitk tooling sync <stack> <path> --check` to read wh
|
|
|
158
160
|
|
|
159
161
|
It never touches user-owned seed files. Governance rules in `.claude/rules/`, tooling configs, and reference docs refresh in place. Stale `.claude/GOV.md` from earlier installs is removed.
|
|
160
162
|
|
|
161
|
-
Standards
|
|
162
|
-
|
|
163
|
-
An unstamped project reaches the same split through the history fallback, so a headless run updates every standard it can prove untouched and refuses while any file resists attribution. To take the upstream version of a customized file, run `aitk standards sync <path>` interactively, or use `aitk:claude-seed-sync` below to merge section by section.
|
|
163
|
+
Standards take no part in that run. Nothing installed them, so there is no copy to reconcile and no `aitk standards sync` to reach for.
|
|
164
164
|
|
|
165
165
|
### Targeted
|
|
166
166
|
|
|
167
|
-
- Claude seed docs such as `CLAUDE.md` and `.claude/REQUIREMENTS.md
|
|
167
|
+
- Claude seed docs such as `CLAUDE.md` and `.claude/REQUIREMENTS.md`: invoke `aitk:claude-seed-sync`. The skill splits each file into a preamble (between the H1 and the first H2) plus one part per `##` section, then diffs part by part and proposes per-part edits. User customizations are preserved.
|
|
168
168
|
- Governance rules already installed: `aitk gov sync <path>` diffs and applies, and never adds new rules
|
|
169
|
-
- Standards already installed: `aitk standards sync <path>` diffs and applies whole files, and refuses to apply without a prompt
|
|
170
169
|
- Tooling configs and seeds: `aitk tooling <stack> <path>` overwrites golden configs and merges seeds
|
|
171
170
|
- Reference docs for a stack: `aitk tooling ref <stack> <path>`
|
|
172
171
|
- Index regeneration after markdown edits: `aitk indexes regen`
|
|
@@ -186,7 +185,7 @@ cd <your-project>
|
|
|
186
185
|
claude
|
|
187
186
|
```
|
|
188
187
|
|
|
189
|
-
In the session, invoke `aitk:setup-init`. The skill detects no framework
|
|
188
|
+
In the session, invoke `aitk:setup-init`. The skill detects no framework and resolves tooling to `base`, governance to `base`, and snippets to `all`. The preview marks both stacks as fallbacks, since neither came from a match, then the chain runs `aitk init`.
|
|
190
189
|
|
|
191
190
|
Ongoing: run `aitk sync --check .` to see what has drifted, then invoke `aitk:claude-seed-sync` for seed drift or `aitk sync .` for a catch-all refresh.
|
|
192
191
|
|
package/package.json
CHANGED
|
@@ -76,10 +76,14 @@ log_step "Assert scaffold"
|
|
|
76
76
|
# catches a failed domain, and the gate stays green while the target is
|
|
77
77
|
# missing everything that domain provides.
|
|
78
78
|
#
|
|
79
|
+
# Standards name no path because the corpus installs into no target. A scaffold
|
|
80
|
+
# reads a standard through `aitk standards <name>`, which resolves against the
|
|
81
|
+
# copy inside the package.
|
|
82
|
+
#
|
|
79
83
|
# The snippets path has to name a slug the default preset still carries, since
|
|
80
84
|
# init resolves snippets through `essentials`. Editing that preset without
|
|
81
85
|
# editing this line fails the gate on a correct install.
|
|
82
|
-
for path in "CLAUDE.md" ".claude/snippets/decision-help.md" ".claude/
|
|
86
|
+
for path in "CLAUDE.md" ".claude/snippets/decision-help.md" ".claude/wiki/index.md" ".claude" ".claude/context/index.md" ".claude/wireframes/index.md" ".claude/diagrams/index.md" \
|
|
83
87
|
".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
|
|
84
88
|
".claude/rules/core/000-constitution.md"; do
|
|
85
89
|
if [ ! -e "$TARGET_DIR/$path" ]; then
|
|
@@ -213,12 +213,6 @@ run_sandbox_install() {
|
|
|
213
213
|
rm -f "$install_log"
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
inject_documentation() {
|
|
217
|
-
[ ! -d "$PROJECT_ROOT/standards" ] && return
|
|
218
|
-
|
|
219
|
-
run_sandbox_install "standards" standards install "$SANDBOX"
|
|
220
|
-
}
|
|
221
|
-
|
|
222
216
|
# The stack decides which rules land. `base` carries the language-agnostic core,
|
|
223
217
|
# which is what a scenario asserting on rule behavior reads. A scenario needing a
|
|
224
218
|
# framework's rules overrides the variable in `use_config`.
|
|
@@ -245,9 +239,12 @@ commit_environment_setup() {
|
|
|
245
239
|
)
|
|
246
240
|
}
|
|
247
241
|
|
|
242
|
+
# No standards injection. The corpus installs into no target, so a sandbox
|
|
243
|
+
# without one is the shape a scaffolded project has, and a scenario driving a
|
|
244
|
+
# skill that reads a standard exercises the `aitk standards <name>` path a real
|
|
245
|
+
# project takes.
|
|
248
246
|
setup_sandbox_assets() {
|
|
249
247
|
[ -n "$SANDBOX_INJECT_SEEDS" ] && inject_seeds
|
|
250
|
-
[ -n "$SANDBOX_INJECT_STANDARDS" ] && inject_documentation
|
|
251
248
|
[ -n "$SANDBOX_INJECT_GOV" ] && inject_gov_rules
|
|
252
249
|
commit_environment_setup
|
|
253
250
|
}
|
|
@@ -59,15 +59,18 @@ list_text() {
|
|
|
59
59
|
done < <(find "$STANDARDS_DIR" -maxdepth 1 -type f -name "*.md" | sort)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
# No `target` field. It named where a standard installed, and nothing installs
|
|
63
|
+
# one now, so every value it could carry is either a path only this repository
|
|
64
|
+
# has or a duplicate of what `aitk standards <name>` reports. `content` already
|
|
65
|
+
# carries the document, which is what a consumer wanted the path for.
|
|
62
66
|
list_json() {
|
|
63
67
|
local first=1
|
|
64
|
-
local file name title
|
|
68
|
+
local file name title applies_to
|
|
65
69
|
printf '['
|
|
66
70
|
while IFS= read -r file; do
|
|
67
71
|
name=$(basename "$file" .md)
|
|
68
72
|
[ "$name" = "index" ] && continue
|
|
69
73
|
title=$(read_frontmatter_field "$file" "description")
|
|
70
|
-
target=".claude/standards/$(basename "$file")"
|
|
71
74
|
applies_to=$(read_applies_to "$file")
|
|
72
75
|
if [ "$first" -eq 0 ]; then
|
|
73
76
|
printf ','
|
|
@@ -75,10 +78,9 @@ list_json() {
|
|
|
75
78
|
jq -nc \
|
|
76
79
|
--arg name "$name" \
|
|
77
80
|
--arg description "$title" \
|
|
78
|
-
--arg target "$target" \
|
|
79
81
|
--argjson appliesTo "$applies_to" \
|
|
80
82
|
--rawfile content "$file" \
|
|
81
|
-
'{name: $name, description: $description,
|
|
83
|
+
'{name: $name, description: $description, appliesTo: $appliesTo, content: $content}'
|
|
82
84
|
first=0
|
|
83
85
|
done < <(find "$STANDARDS_DIR" -maxdepth 1 -type f -name "*.md" | sort)
|
|
84
86
|
printf ']'
|
package/src/cli.ts
CHANGED
|
@@ -77,7 +77,7 @@ function showHelp(): void {
|
|
|
77
77
|
`${GREY}│${NC} aitk sandbox git:commit`,
|
|
78
78
|
`${GREY}│${NC} aitk gov install react`,
|
|
79
79
|
`${GREY}│${NC} aitk gov sync ../my-app`,
|
|
80
|
-
`${GREY}│${NC} aitk standards
|
|
80
|
+
`${GREY}│${NC} aitk standards markdown`,
|
|
81
81
|
`${GREY}│${NC} aitk snippets install base ../my-app`,
|
|
82
82
|
`${GREY}│${NC} aitk snippets sync ../my-app`,
|
|
83
83
|
`${GREY}│${NC} aitk init ../my-app`,
|
package/src/commands/gov.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Command } from 'commander'
|
|
|
5
5
|
import { PROJECT_ROOT } from '@/project-root'
|
|
6
6
|
import { createGovAdapter } from '@/gov/adapter'
|
|
7
7
|
import { regenConsumedRules } from '@/gov/consumed'
|
|
8
|
-
import {
|
|
8
|
+
import { installRules, lookupRules } from '@/gov/install'
|
|
9
9
|
import { buildGovCatalog, describeRule, describeStack } from '@/gov/list'
|
|
10
10
|
import { buildRulesPayload, listRuleFiles } from '@/gov/payload'
|
|
11
11
|
import {
|
|
@@ -453,12 +453,6 @@ async function runInstall(
|
|
|
453
453
|
for (const rel of await installRules(found, resolved)) logAdd(rel)
|
|
454
454
|
await recordStamp(createGovAdapter(PROJECT_ROOT), resolved, new Date())
|
|
455
455
|
|
|
456
|
-
if (!hasStandards(resolved)) {
|
|
457
|
-
logWarn(
|
|
458
|
-
`Rules reference .claude/standards/. Run 'aitk standards install ${target}' or 'aitk init' so the references resolve.`,
|
|
459
|
-
)
|
|
460
|
-
}
|
|
461
|
-
|
|
462
456
|
const { GREEN, NC } = palette(process.stderr)
|
|
463
457
|
outro()
|
|
464
458
|
process.stderr.write(`${GREEN}✓ Rules installed${NC}\n`)
|
package/src/commands/init.ts
CHANGED
|
@@ -13,8 +13,6 @@ interface InitOptions {
|
|
|
13
13
|
readonly stack: string
|
|
14
14
|
readonly add?: string
|
|
15
15
|
readonly snippets: string
|
|
16
|
-
/** Always present: the option falls back to `all`. */
|
|
17
|
-
readonly standards: string
|
|
18
16
|
readonly skip?: string
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -37,7 +35,7 @@ export function register(program: Command): void {
|
|
|
37
35
|
' aitk init ../my-app',
|
|
38
36
|
' aitk init --stack astro --add 260-shadcn ../my-app',
|
|
39
37
|
' aitk init --skip governance ../my-app',
|
|
40
|
-
' aitk init --
|
|
38
|
+
' aitk init --snippets all ../my-app',
|
|
41
39
|
'',
|
|
42
40
|
].join('\n'),
|
|
43
41
|
)
|
|
@@ -65,7 +63,6 @@ async function runInit(
|
|
|
65
63
|
stack: options.stack,
|
|
66
64
|
add: options.add,
|
|
67
65
|
snippets: options.snippets,
|
|
68
|
-
standards: options.standards,
|
|
69
66
|
skip,
|
|
70
67
|
}
|
|
71
68
|
|
|
@@ -1,51 +1,24 @@
|
|
|
1
|
-
import { join } from 'node:path'
|
|
2
1
|
import type { Command } from 'commander'
|
|
3
2
|
import { registerPassThroughVerbs } from '@/commands/pass-through'
|
|
4
|
-
import { PROJECT_ROOT } from '@/project-root'
|
|
5
|
-
import { createStandardsAdapter, standardsSourceDir } from '@/standards/adapter'
|
|
6
|
-
import { ALL_SELECTION, selectStandards } from '@/standards/closure'
|
|
7
|
-
import {
|
|
8
|
-
refreshIndex,
|
|
9
|
-
STANDARDS_REL,
|
|
10
|
-
standardsInstallDir,
|
|
11
|
-
} from '@/standards/index-refresh'
|
|
12
|
-
import { applyInstall, planInstall } from '@/standards/install'
|
|
13
3
|
import { listStandards, readStandard, resolveStandard } from '@/standards/read'
|
|
14
|
-
import {
|
|
15
|
-
import { resolveTarget } from '@/target'
|
|
16
|
-
import {
|
|
17
|
-
intro,
|
|
18
|
-
logAdd,
|
|
19
|
-
logError,
|
|
20
|
-
logInfo,
|
|
21
|
-
logStep,
|
|
22
|
-
logWarn,
|
|
23
|
-
outro,
|
|
24
|
-
palette,
|
|
25
|
-
select,
|
|
26
|
-
} from '@/ui'
|
|
27
|
-
|
|
28
|
-
interface InstallOptions {
|
|
29
|
-
/** Always present: the option falls back to `ALL_SELECTION`. */
|
|
30
|
-
readonly only: string
|
|
31
|
-
}
|
|
4
|
+
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
32
5
|
|
|
33
6
|
export function register(program: Command): void {
|
|
34
7
|
const standards = program
|
|
35
8
|
.command('standards')
|
|
36
|
-
.description('Standards commands (
|
|
9
|
+
.description('Standards commands (list, <name>)')
|
|
37
10
|
.argument('[name]', 'Standard to print, by name with or without .md')
|
|
38
11
|
.helpOption('-h, --help', 'Show this help message')
|
|
39
12
|
.addHelpText(
|
|
40
13
|
'after',
|
|
41
14
|
[
|
|
42
15
|
'',
|
|
43
|
-
'A name resolves under
|
|
44
|
-
'
|
|
45
|
-
'
|
|
16
|
+
'A name resolves under standards/ at the working root, then the corpus',
|
|
17
|
+
'inside the aitk package. No standard installs into a project, so the',
|
|
18
|
+
'package corpus is what answers there. The frame names the copy it read.',
|
|
46
19
|
'',
|
|
47
20
|
'Examples:',
|
|
48
|
-
' aitk standards
|
|
21
|
+
' aitk standards markdown',
|
|
49
22
|
' aitk standards markdown.md',
|
|
50
23
|
'',
|
|
51
24
|
].join('\n'),
|
|
@@ -63,47 +36,6 @@ export function register(program: Command): void {
|
|
|
63
36
|
process.exitCode = print(name)
|
|
64
37
|
})
|
|
65
38
|
|
|
66
|
-
standards
|
|
67
|
-
.command('sync')
|
|
68
|
-
.description('Update standards already installed under .claude/standards/')
|
|
69
|
-
.argument('[target]', 'Target directory', '.')
|
|
70
|
-
.helpOption('-h, --help', 'Show this help message')
|
|
71
|
-
.action(async (target: string) => {
|
|
72
|
-
process.exitCode = await runDomainSync(
|
|
73
|
-
createStandardsAdapter(PROJECT_ROOT),
|
|
74
|
-
target,
|
|
75
|
-
{ protectedRoot: PROJECT_ROOT },
|
|
76
|
-
)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
standards
|
|
80
|
-
.command('install')
|
|
81
|
-
.description('Copy standards into a project (overwrites)')
|
|
82
|
-
.argument('[target]', 'Target directory', '.')
|
|
83
|
-
.option(
|
|
84
|
-
'--only <names>',
|
|
85
|
-
"Comma-separated standard names, or 'all'",
|
|
86
|
-
ALL_SELECTION,
|
|
87
|
-
)
|
|
88
|
-
.helpOption('-h, --help', 'Show this help message')
|
|
89
|
-
.addHelpText(
|
|
90
|
-
'after',
|
|
91
|
-
[
|
|
92
|
-
'',
|
|
93
|
-
'A selection expands to the standards it cites, so nothing lands with',
|
|
94
|
-
'a dangling reference.',
|
|
95
|
-
'',
|
|
96
|
-
'Examples:',
|
|
97
|
-
' aitk standards install',
|
|
98
|
-
' aitk standards install --only slug ../my-app',
|
|
99
|
-
' aitk standards install --only design,wireframes ../my-app',
|
|
100
|
-
'',
|
|
101
|
-
].join('\n'),
|
|
102
|
-
)
|
|
103
|
-
.action(async (target: string, options: InstallOptions) => {
|
|
104
|
-
process.exitCode = await runInstall(target, options.only)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
39
|
registerPassThroughVerbs(standards, 'standards', ['list'])
|
|
108
40
|
}
|
|
109
41
|
|
|
@@ -111,9 +43,9 @@ export function register(program: Command): void {
|
|
|
111
43
|
* Writes the standard to stdout and every frame line to stderr, so a caller
|
|
112
44
|
* capturing the output with `$(...)` receives the document alone.
|
|
113
45
|
*
|
|
114
|
-
* The root is the caller's directory rather than the toolkit's, since a
|
|
115
|
-
*
|
|
116
|
-
*
|
|
46
|
+
* The root is the caller's directory rather than the toolkit's, since a
|
|
47
|
+
* repository that authors standards governs its own copy and the package copy
|
|
48
|
+
* answers everywhere else.
|
|
117
49
|
*/
|
|
118
50
|
function print(name: string): number {
|
|
119
51
|
intro('aitk standards')
|
|
@@ -135,67 +67,3 @@ function print(name: string): number {
|
|
|
135
67
|
outro()
|
|
136
68
|
return 0
|
|
137
69
|
}
|
|
138
|
-
|
|
139
|
-
async function runInstall(target: string, selection: string): Promise<number> {
|
|
140
|
-
intro('aitk standards')
|
|
141
|
-
|
|
142
|
-
const resolved = resolveTarget(target, PROJECT_ROOT)
|
|
143
|
-
if (typeof resolved === 'number') return resolved
|
|
144
|
-
|
|
145
|
-
const sourceDir = standardsSourceDir(PROJECT_ROOT)
|
|
146
|
-
const destDir = standardsInstallDir(resolved)
|
|
147
|
-
|
|
148
|
-
logStep('Scanning standards')
|
|
149
|
-
const available = planInstall(sourceDir)
|
|
150
|
-
const result = selectStandards(available, selection)
|
|
151
|
-
|
|
152
|
-
if (!result.ok) {
|
|
153
|
-
logError(
|
|
154
|
-
`Standard not found: ${result.unknown.join(', ')}. Run 'aitk standards list' for the catalog.`,
|
|
155
|
-
)
|
|
156
|
-
outro()
|
|
157
|
-
return 1
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const { files, requested, added, unresolved } = result.selection
|
|
161
|
-
for (const name of requested) logInfo(join(STANDARDS_REL, name))
|
|
162
|
-
|
|
163
|
-
if (added.length > 0) {
|
|
164
|
-
logStep(`Added by citation (${added.length})`)
|
|
165
|
-
for (const name of added) logInfo(join(STANDARDS_REL, name))
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (unresolved.length > 0) {
|
|
169
|
-
logStep(`Scope handoffs not installed (${unresolved.length})`)
|
|
170
|
-
for (const name of unresolved) logWarn(name)
|
|
171
|
-
logInfo('Each names a concern these standards do not govern. Add a name')
|
|
172
|
-
logInfo('to --only if the project needs that standard as well.')
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const shouldInstall = await select({
|
|
176
|
-
message: `Install ${files.length} standards to ${destDir}?`,
|
|
177
|
-
options: [
|
|
178
|
-
{ value: true, label: 'Yes' },
|
|
179
|
-
{ value: false, label: 'No' },
|
|
180
|
-
],
|
|
181
|
-
nonInteractiveDefault: true,
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
if (!shouldInstall) {
|
|
185
|
-
logWarn('Cancelled')
|
|
186
|
-
outro()
|
|
187
|
-
return 0
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
logStep('Installing standards')
|
|
191
|
-
for (const label of await applyInstall(files, destDir)) logAdd(label)
|
|
192
|
-
await refreshIndex(sourceDir, resolved)
|
|
193
|
-
await recordStamp(createStandardsAdapter(PROJECT_ROOT), resolved, new Date())
|
|
194
|
-
|
|
195
|
-
outro()
|
|
196
|
-
const { GREEN, GREY, NC } = palette(process.stderr)
|
|
197
|
-
process.stderr.write(
|
|
198
|
-
`\n${GREEN}✓ Standards installed${NC} ${GREY}(${files.length} files)${NC}\n`,
|
|
199
|
-
)
|
|
200
|
-
return 0
|
|
201
|
-
}
|
package/src/commands/sync.ts
CHANGED
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
import { describeSkew } from '@/version/skew'
|
|
35
35
|
|
|
36
36
|
const SYNC_ARGS: Record<SyncDomain, readonly string[]> = {
|
|
37
|
-
standards: ['standards', 'sync'],
|
|
38
37
|
snippets: ['snippets', 'sync'],
|
|
39
38
|
governance: ['gov', 'sync'],
|
|
40
39
|
claude: ['claude', 'sync'],
|
|
@@ -379,7 +378,7 @@ async function runSync(target: string): Promise<number> {
|
|
|
379
378
|
|
|
380
379
|
if (existsSync(join(resolved, '.claude'))) {
|
|
381
380
|
process.stderr.write(
|
|
382
|
-
`${GREY}Tip: run \`/claude-seed-sync\` to audit seed
|
|
381
|
+
`${GREY}Tip: run \`/claude-seed-sync\` to audit seed drift per section, preserving local customizations.${NC}\n`,
|
|
383
382
|
)
|
|
384
383
|
}
|
|
385
384
|
|
package/src/gov/install.ts
CHANGED
|
@@ -109,12 +109,3 @@ export async function installRules(
|
|
|
109
109
|
|
|
110
110
|
return installed
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* The bash checked `<target>/standards`, but `aitk standards install` writes
|
|
115
|
-
* `.claude/standards/`, which is also the path the rules reference and the
|
|
116
|
-
* path the warning names. The guard therefore fired on every modern install.
|
|
117
|
-
*/
|
|
118
|
-
export function hasStandards(target: string): boolean {
|
|
119
|
-
return existsSync(join(target, '.claude', 'standards'))
|
|
120
|
-
}
|
package/src/init/flags.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Command } from 'commander'
|
|
2
2
|
import { DEFAULT_STACK, SKIPPABLE_DOMAINS } from '@/init/plan'
|
|
3
|
-
import { ALL_SELECTION } from '@/standards/closure'
|
|
4
3
|
|
|
5
4
|
interface InitOptionSpec {
|
|
6
5
|
/** The option value key commander stores the parsed value under. */
|
|
@@ -33,12 +32,6 @@ export const INIT_OPTIONS: readonly InitOptionSpec[] = [
|
|
|
33
32
|
description: "Snippets preset, category, or 'all'",
|
|
34
33
|
defaultValue: 'essentials',
|
|
35
34
|
},
|
|
36
|
-
{
|
|
37
|
-
key: 'standards',
|
|
38
|
-
flags: '--standards <selection>',
|
|
39
|
-
description: "Comma-separated standard names, or 'all'",
|
|
40
|
-
defaultValue: ALL_SELECTION,
|
|
41
|
-
},
|
|
42
35
|
{
|
|
43
36
|
key: 'skip',
|
|
44
37
|
flags: '--skip <list>',
|
|
@@ -62,9 +55,9 @@ export function applyInitOptions(command: Command): Command {
|
|
|
62
55
|
|
|
63
56
|
/**
|
|
64
57
|
* Whether the operator passed any flag, which is what makes the command
|
|
65
|
-
* scriptable by suppressing the confirmation prompt. `--stack
|
|
66
|
-
*
|
|
67
|
-
*
|
|
58
|
+
* scriptable by suppressing the confirmation prompt. `--stack` and `--snippets`
|
|
59
|
+
* both carry defaults, so presence has to be read from where the value came
|
|
60
|
+
* from rather than from the value itself.
|
|
68
61
|
*/
|
|
69
62
|
export function flagsProvided(cmd: Command): boolean {
|
|
70
63
|
return INIT_OPTIONS.some(
|