@erclx/aitk 1.7.1 → 2.1.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 (49) hide show
  1. package/README.md +2 -2
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/claude-autoship/SKILL.md +2 -2
  4. package/claude/skills/claude-docs/SKILL.md +11 -0
  5. package/claude/skills/claude-groundwork/SKILL.md +1 -1
  6. package/claude/skills/claude-seed-sync/REQUIREMENT.md +2 -2
  7. package/claude/skills/claude-seed-sync/SKILL.md +15 -18
  8. package/claude/skills/create-standard/REQUIREMENT.md +7 -10
  9. package/claude/skills/create-standard/SKILL.md +10 -11
  10. package/claude/skills/git-pr/references/labels.md +7 -1
  11. package/claude/skills/migration-standards/REQUIREMENT.md +10 -7
  12. package/claude/skills/migration-standards/SKILL.md +24 -22
  13. package/claude/skills/migration-superseded/REQUIREMENT.md +2 -2
  14. package/claude/skills/migration-superseded/SKILL.md +7 -9
  15. package/claude/skills/setup-gov/REQUIREMENT.md +2 -2
  16. package/claude/skills/toolkit-cli/SKILL.md +13 -14
  17. package/docs/agents/commands.md +1 -1
  18. package/docs/agents/install-and-sync.md +27 -41
  19. package/docs/agents/scripting.md +2 -9
  20. package/docs/target-projects.md +14 -15
  21. package/package.json +1 -1
  22. package/scripts/core/install-check.sh +5 -1
  23. package/scripts/lib/sandbox-git.sh +56 -4
  24. package/scripts/manage-sandbox.sh +4 -7
  25. package/scripts/standards/list.sh +6 -4
  26. package/src/cli.ts +1 -1
  27. package/src/commands/gov.ts +1 -7
  28. package/src/commands/init.ts +1 -4
  29. package/src/commands/standards.ts +9 -141
  30. package/src/commands/sync.ts +1 -2
  31. package/src/gov/install.ts +0 -9
  32. package/src/init/flags.ts +3 -10
  33. package/src/init/plan.ts +8 -26
  34. package/src/init/steps.ts +0 -24
  35. package/src/standards/read.ts +16 -14
  36. package/src/sync/check.ts +4 -7
  37. package/src/sync/layout.ts +8 -10
  38. package/src/sync/stamp.ts +18 -15
  39. package/src/sync/target.ts +1 -8
  40. package/standards/skill.md +6 -6
  41. package/standards/standard.md +1 -1
  42. package/tooling/claude/seeds/.claude/ARCHITECTURE.md +1 -1
  43. package/tooling/claude/seeds/.claude/DESIGN.md +1 -1
  44. package/tooling/claude/seeds/.claude/REQUIREMENTS.md +1 -1
  45. package/tooling/claude/seeds/CLAUDE.md +2 -2
  46. package/src/standards/adapter.ts +0 -51
  47. package/src/standards/closure.ts +0 -200
  48. package/src/standards/index-refresh.ts +0 -44
  49. 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` matches by filename against `.claude/standards/` and
18
- regenerates that folder's `index.md` on every completed run. It is the one sync
19
- that refuses under `AITK_NON_INTERACTIVE=1` when drift exists, logging a warning
20
- and exiting 0 without writing, because standards are seeds a project edits. Run
21
- it interactively, or use the `claude-seed-sync` skill for a per-section audit
22
- that preserves customizations.
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 selection
46
+ ## Standards resolution
48
47
 
49
- `aitk standards install --only <names>` takes a comma-separated list and
50
- defaults to `all`, so a call that omits it installs the whole corpus as before.
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 fails the run with exit 1 rather than being dropped, because a typo
53
- would otherwise omit a standard silently and compute the closure over the wrong
54
- set. `aitk init --standards <selection>` passes the same value through.
55
-
56
- The selection expands to the standards it depends on, so nothing lands with a
57
- dangling reference. A citation is a backticked filename in a standard's body,
58
- resolved case-exactly against the flat `standards/` root, which is what drops a
59
- citation naming a target's own `.claude/ARCHITECTURE.md` or a bundled standard
60
- install never copies. Whatever the expansion adds is listed under its own step
61
- in the output.
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 from the target's own copy under `.claude/standards/` or at the
195
- root, and proposes the split without writing. Where a folder has no governing
196
- standard, or neither root holds the one it has, the entry earns a named refusal
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`, `standards`, and `governance`, and warns
279
- without aborting on any other value. `--standards` defaults to `all` and reaches
280
- `aitk standards install` only when it names something narrower, so the default
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
 
@@ -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
- # Report standards drift without applying it, which is what headless does here
116
- AITK_NON_INTERACTIVE=1 aitk standards sync /path/to/project
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
@@ -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 standards, snippets, governance, tooling, and the seeds from wherever the package landed.
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, standards, and snippets, and scaffolds `.claude/wiki/`. Governance defaults to the `base` stack, so a bare init carries the rules that route to the standards it installs alongside them. Pass `--stack <name>` to install a framework stack instead.
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
- `governance`, `standards`, and `wiki` are skippable:
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
- - `--skip governance`: leave `.claude/rules/` empty, so no coding standard loads on a file match. Standards still install, and the installed copy is the first root `aitk standards <name>` resolves, so it stays the editable seed a project owns. 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.
92
- - `--skip standards`: leave standards out. The governance rules are unaffected, since each authority line names `aitk standards <name>` and that command answers from the corpus inside the CLI's own package. Toolkit skills are unaffected too, each naming the copy in its own plugin root. `aitk standards <name>` searches the corpus inside the CLI's own package behind both project roots, so it prints a standard in a project that skipped the install, and `aitk markdown audit` needs no standard at all, its ban sets and checkpoints shipping with the package as data. What the skip costs is the editable copy rather than the enforcement.
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
- - A single standard: `aitk standards install --only <names> <path>`. The selection expands to the standards it depends on, and stops at a `Does not govern:` handoff, which it reports instead. Omitting the flag installs all of them
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. Standards, 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.
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 standards 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.
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 `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.
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 are the exception inside that run, and the stamp narrows it. A standard the project customized is reported and left alone rather than overwritten. A standard still matching what was installed carries no local edits to lose, so a headless run updates it.
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`, plus installed standards under `.claude/standards/`: 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 across both surfaces and proposes per-part edits. User customizations are preserved.
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, resolves tooling to `base`, governance to `base`, snippets to `all`, and auto-enables `standards` if `docs/` exists. The preview marks both stacks as fallbacks, since neither came from a match, then the chain runs `aitk init`.
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "1.7.1",
4
+ "version": "2.1.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -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/standards/skill.md" ".claude/standards/markdown.md" ".claude/wiki/index.md" ".claude" ".claude/context/index.md" ".claude/wireframes/index.md" ".claude/diagrams/index.md" \
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
@@ -15,8 +15,13 @@ require_sandbox_anchor_config() {
15
15
  fi
16
16
  }
17
17
 
18
+ resolve_sandbox_anchor_repo() {
19
+ printf '%s\n' "${1:-${ANCHOR_REPO:-}}"
20
+ }
21
+
18
22
  sandbox_anchor_url() {
19
- local repo_name="${1:-${ANCHOR_REPO:-}}"
23
+ local repo_name
24
+ repo_name="$(resolve_sandbox_anchor_repo "$@")"
20
25
 
21
26
  if [ -z "$repo_name" ] || [ -z "${GITHUB_ORG:-}" ]; then
22
27
  log_error "sandbox_anchor_url needs GITHUB_ORG and a repository name. Call require_sandbox_anchor_config first."
@@ -52,7 +57,7 @@ configure_sandbox_git_identity() {
52
57
 
53
58
  # Every scenario that needs a remote points at the same throwaway repository, so
54
59
  # the name lives here rather than in each one.
55
- SANDBOX_ANCHOR_REPO="toolkit-sandbox"
60
+ SANDBOX_ANCHOR_REPO="aitk-sandbox"
56
61
 
57
62
  # A scenario calls this from its own use_anchor hook rather than this file
58
63
  # defining the hook. manage-sandbox.sh keys off `type -t use_anchor`, so
@@ -62,11 +67,58 @@ use_sandbox_anchor() {
62
67
  export ANCHOR_REPO="${1:-$SANDBOX_ANCHOR_REPO}"
63
68
  }
64
69
 
70
+ # gh answers an absent repository and an unreachable host with the same exit
71
+ # status, so the 404 is the only thing separating them. Anything else is a
72
+ # network or credential fault, where creating a repository would be the wrong
73
+ # answer and would fail for the same reason the read did.
74
+ ensure_sandbox_anchor_repo() {
75
+ local repo_name gh_error
76
+
77
+ repo_name="$(resolve_sandbox_anchor_repo "$@")"
78
+
79
+ if [ -z "$repo_name" ] || [ -z "${GITHUB_ORG:-}" ]; then
80
+ log_error "ensure_sandbox_anchor_repo needs GITHUB_ORG and a repository name. Call require_sandbox_anchor_config first."
81
+ fi
82
+
83
+ if gh_error="$(gh api "repos/${GITHUB_ORG}/${repo_name}" --silent 2>&1)"; then
84
+ return 0
85
+ fi
86
+
87
+ case "$gh_error" in
88
+ *"HTTP 404"*) ;;
89
+ *) log_error "Cannot reach ${GITHUB_ORG}/${repo_name}: ${gh_error}" ;;
90
+ esac
91
+
92
+ # An absent anchor is nearly always a wrong GITHUB_ORG or a rename nobody
93
+ # performed, and provisioning stages a fixture rather than cloning while every
94
+ # scenario force-pushes to main, so a repository created here would carry all
95
+ # nine to a pass against something that is not the anchor. Refusing is the safe
96
+ # default and creating is the opt-in, the shape `aitk tooling sync --write`
97
+ # already sets. `aitk records push` refuses outright for the reason
98
+ # `.claude/context/development/scratch.md` records, so the two still differ.
99
+ # The sibling SANDBOX_ flags are presence tests, so any non-empty value turns
100
+ # them on. This one allowlists instead, because a presence test would have
101
+ # SANDBOX_ANCHOR_CREATE=false provisioning a repository. Both spellings are
102
+ # accepted so the `true` those siblings are set to does not land on a refusal.
103
+ case "${SANDBOX_ANCHOR_CREATE:-}" in
104
+ 1 | true) ;;
105
+ *) log_error "${GITHUB_ORG}/${repo_name} does not exist. Check GITHUB_ORG and whether a rename is pending, then create it with 'gh repo create ${GITHUB_ORG}/${repo_name} --private' or re-run with SANDBOX_ANCHOR_CREATE=true." ;;
106
+ esac
107
+
108
+ log_warn "${GITHUB_ORG}/${repo_name} does not exist and SANDBOX_ANCHOR_CREATE is set. Creating it as private."
109
+ if ! gh_error="$(gh repo create "${GITHUB_ORG}/${repo_name}" --private 2>&1)"; then
110
+ log_error "Could not create ${GITHUB_ORG}/${repo_name}: ${gh_error}"
111
+ fi
112
+ log_info "Created ${GITHUB_ORG}/${repo_name} as a private repository."
113
+ }
114
+
65
115
  # A remote is useless without an author, so the scenarios that reach one always
66
116
  # configure both. configure_sandbox_git_identity stays callable on its own for
67
- # the scenarios that never push. The remove keeps this idempotent against a
68
- # sandbox tree that already carries an origin.
117
+ # the scenarios that never push. The probe runs first so an absent remote is
118
+ # reported before the scenario stages anything. The remove keeps this idempotent
119
+ # against a sandbox tree that already carries an origin.
69
120
  configure_sandbox_anchor_remote() {
121
+ ensure_sandbox_anchor_repo "$@"
70
122
  configure_sandbox_git_identity
71
123
  git remote remove origin 2>/dev/null || true
72
124
  git remote add origin "$(sandbox_anchor_url "$@")"
@@ -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 target applies_to
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, target: $target, appliesTo: $appliesTo, content: $content}'
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 sync ../my-app`,
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`,
@@ -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 { hasStandards, installRules, lookupRules } from '@/gov/install'
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`)
@@ -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 --standards design,wireframes ../my-app',
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 { recordStamp, runDomainSync } from '@/sync/engine'
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 (install, sync, list, <name>)')
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 .claude/standards/, then standards/, then the',
44
- 'corpus inside the aitk package, so a standard prints without a project',
45
- 'copy on disk. The frame names the copy it read.',
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 prose',
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 project
115
- * copy is what governs wherever one exists and the package copy answers only
116
- * its absence.
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
- }