@erclx/aitk 3.13.0 → 3.14.1
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 +1 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-address-review/SKILL.md +8 -5
- package/claude/skills/claude-pr-review/SKILL.md +4 -0
- package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
- package/claude/skills/git-followup/REQUIREMENT.md +2 -0
- package/claude/skills/git-followup/SKILL.md +8 -4
- package/docs/agents/install-and-sync.md +11 -3
- package/docs/target-projects.md +6 -3
- package/governance/rules/snippets/505-at-references.md +9 -0
- package/package.json +1 -1
- package/scripts/core/install-check.sh +7 -5
- package/scripts/core/verify.sh +16 -11
- package/src/commands/init.ts +1 -1
- package/src/commands/snippets.ts +4 -0
- package/src/init/flags.ts +1 -2
- package/src/init/plan.ts +20 -3
- package/src/init/steps.ts +20 -6
- package/src/snippets/categories.ts +7 -0
- package/src/snippets/install.ts +47 -0
- package/standards/pr.md +1 -0
- package/tooling/claude/seeds/CLAUDE.md +0 -4
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ git init
|
|
|
116
116
|
aitk init
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
`aitk init` installs base tooling configs, Claude seeds, governance rules
|
|
119
|
+
`aitk init` installs base tooling configs, Claude seeds, and governance rules in one pass, and scaffolds a `.claude/wiki/` stub for your project's own reference pages. Governance defaults to the `base` stack, so a bare init lands the coding and doc-authoring rules in `.claude/rules/`. Each rule names the standard it answers to and reads it with `aitk standards <name>`, so no corpus is copied into your project. Pass `--stack <name>` for a framework stack, or `--skip governance` to leave rules out. Snippets install only when you name a category with `--snippets <category>`, such as `--snippets essentials`. Run `aitk tooling list --json` to see the catalog.
|
|
120
120
|
|
|
121
121
|
For the full journey from scaffold through adding a domain later to syncing upstream drift, see [target projects](docs/target-projects.md).
|
|
122
122
|
|
|
@@ -78,11 +78,14 @@ the `git-followup` skill, invoked with `reply-owned` so it stages, commits,
|
|
|
78
78
|
pushes, and refreshes the open PR body without posting its own comment. This
|
|
79
79
|
skill owns the reply. Do not reimplement that flow here.
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
the
|
|
81
|
+
`git-followup` syncs the body and title against the fix commit on every
|
|
82
|
+
invocation, including `reply-owned`, so the merge record reflects what this pass
|
|
83
|
+
changed rather than only what opened the PR. The reply comment still carries the
|
|
84
|
+
fix log mapped to each finding, since the two serve different readers: the body
|
|
85
|
+
is the merge record and the reply is the review's own thread. A rebase in step 5
|
|
86
|
+
rewrote the branch, so that push is a force-push and `git-followup` resolves it
|
|
87
|
+
from the tracking branch. Worker branches are single-owner here, which is what
|
|
88
|
+
makes overwriting the remote safe.
|
|
86
89
|
|
|
87
90
|
`git-followup` stops on an unchanged tree. A run that answered every finding as a
|
|
88
91
|
conscious-accept and whose rebase left the generated files alone has nothing for
|
|
@@ -87,6 +87,8 @@ Then read the description's `## Testing` section, which is the one part of a pul
|
|
|
87
87
|
|
|
88
88
|
Test every unchecked box against the testing discipline in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`, which reserves an unchecked box for a capability the agent lacks. Raise the box when it names no human at all, when the human it names is a live agent session, or when it names a person for a step the repository ships a harness for, `scripts/sandbox/run.sh` and `scripts/eval/run.sh` being the two. Cost alone is not an answer, since authorizing a spend is the operator's and performing the run is not. A refusal the author actually met is an answer, and it names which one.
|
|
89
89
|
|
|
90
|
+
Test every ticked box too, bounded to one naming a file or a command. Confirm what it names still exists at `<headRefOid>`, `git show <headRefOid>:<path>` for a file or `git grep <command> <headRefOid>` for a command, rather than re-running what it claims. A box naming neither stays untested, since nothing here can confirm a claim carrying no artifact. A box ticked before a later commit removed or replaced what it names is a `should-fix` finding on the body itself, filed under a `**PR body**` block ahead of the file blocks, since what it corrupts is the merge record rather than a file in the diff.
|
|
91
|
+
|
|
90
92
|
Ask rather than grade. Whether a human is genuinely required is a reading the branch author may hold a reason this session cannot see, so the question carries no severity and enters no count. What it does carry is the heading and the dispatch, on the rule Step 4 states, because a question the author never receives corrects nothing and the author is the only party who can answer it. Answering it closes it, and the answer may be that the requirement holds.
|
|
91
93
|
|
|
92
94
|
Read `## For the reviewer` the same way, bounded to the bullets under that heading rather than the Summary or the Technical Context around it. Those two carry the author's argument for the change, and reading them while judging it is most of what an independent pass exists to avoid, so the read stops at the section itself.
|
|
@@ -140,6 +142,8 @@ X critical, Y should-fix, Z minor. Reviewed against project docs and the board.
|
|
|
140
142
|
🤖 Reviewed by Claude Code
|
|
141
143
|
```
|
|
142
144
|
|
|
145
|
+
A stale ticked box goes in a `**PR body**` block, in place of a `**`path/to/file.ext`**` block and ahead of every one of those, since it precedes the code the diff carries rather than sitting inside it.
|
|
146
|
+
|
|
143
147
|
A later pass carrying findings keeps that shape and changes only the summary line:
|
|
144
148
|
|
|
145
149
|
```markdown
|
|
@@ -15,7 +15,7 @@ A body written around a named tool fails a third way. A session on a surface car
|
|
|
15
15
|
|
|
16
16
|
The opposite failure is the one that ships silently. A session that takes a preference decision itself produces work the operator did not ask for and never learns a choice was made, because nothing in the output says a fork was passed.
|
|
17
17
|
|
|
18
|
-
`CLAUDE.md` states both branches of the rule, sending an ordinary judgment call to a pick with the tradeoff in one sentence and a preference-deciding call to the operator. The first branch has the toolkit's `snippets/decision-help.md` behind it, which installs into a project as `.claude/snippets/decision-help.md`. The second branch now states how a question is shaped and where it is put, so this skill inherits both and adds only what a batch needs on top of one question.
|
|
18
|
+
`CLAUDE.md` states both branches of the rule, sending an ordinary judgment call to a pick with the tradeoff in one sentence and a preference-deciding call to the operator. The first branch has the toolkit's `snippets/decision-help.md` behind it, which installs into a project that took snippets as `.claude/snippets/decision-help.md`. The second branch now states how a question is shaped and where it is put, so this skill inherits both and adds only what a batch needs on top of one question.
|
|
19
19
|
|
|
20
20
|
## Must
|
|
21
21
|
|
|
@@ -11,6 +11,8 @@ Without this skill, an edit made after a pull request is already open ships as a
|
|
|
11
11
|
|
|
12
12
|
A caller that rebased the branch before handing over hits a rejected push, since the tracking branch no longer reaches the head. A branch entered through a worktree hits the opposite shape, carrying an open pull request with no tracking ref at all, which read as a branch that had never been pushed.
|
|
13
13
|
|
|
14
|
+
A fix commit answering a review is the sharpest case of the first paragraph, since it always changes what shipped. The routing that once picked whether to sync the body keyed off the same branch that decides whether to reply, so reaching `reply-owned` or a nonzero comment count meant the sync never ran at all. The description a merge record carries was true when the pull request opened and false by the time review finished, with nothing between the two that read it back.
|
|
15
|
+
|
|
14
16
|
## Must
|
|
15
17
|
|
|
16
18
|
- Refuse unless the branch has an open pull request, since every later step addresses it
|
|
@@ -8,7 +8,7 @@ description: Ships a small self-review edit on the current PR branch by staging,
|
|
|
8
8
|
Ship a small self-review edit on the current PR branch in one pass.
|
|
9
9
|
|
|
10
10
|
When invoked with `reply-owned`, a caller such as `claude-address-review` posts
|
|
11
|
-
its own reply, so skip the comment in step
|
|
11
|
+
its own reply, so skip the comment in step 7. The push and body sync still run.
|
|
12
12
|
|
|
13
13
|
## Guards
|
|
14
14
|
|
|
@@ -30,10 +30,14 @@ A missing tracking ref is no longer a guard. An open pull request proves the bra
|
|
|
30
30
|
The lease is what stops the force from overwriting a commit this session never read. Run the ancestry test only where an upstream resolves, since it reads `@{u}`.
|
|
31
31
|
|
|
32
32
|
5. Check for existing review comments: `gh api 'repos/{owner}/{repo}/pulls/<number>/comments' --jq 'length'`, resolving `<number>` from `gh pr view --json number`.
|
|
33
|
-
6.
|
|
34
|
-
-
|
|
33
|
+
6. Sync the body and title on every invocation, before the routing below decides on the reply.
|
|
34
|
+
- Run `gh pr view --json url,title,body` and update the body with `gh pr edit --body` when the new commit changes scope, and the title with `gh pr edit --title` when the scope shifted enough to make it inaccurate.
|
|
35
|
+
- A fix commit answering a review changes what shipped exactly as much as an ordinary followup does, so the sync cannot wait on the invocation or the comment count below.
|
|
36
|
+
- A body a person edited by hand between rounds gets no special handling: judge it against the tree the same way regardless of who wrote it last, since a hand-edit the fix commit has made stale is the exact drift this sync exists to close.
|
|
37
|
+
7. Route on the invocation and the comment count for the reply alone.
|
|
38
|
+
- When invoked with `reply-owned`, skip this step: the caller posts its own reply.
|
|
35
39
|
- Otherwise, if the count is above zero, the followup addresses review feedback: post a one-line summary of the fix with `gh pr comment --body`, first running the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against it, since the hook does not see an inline comment body.
|
|
36
|
-
- If it is zero,
|
|
40
|
+
- If it is zero, nothing further runs. The sync in step 6 already did this branch's job.
|
|
37
41
|
|
|
38
42
|
## After completion
|
|
39
43
|
|
|
@@ -363,9 +363,17 @@ which is what makes it scriptable.
|
|
|
363
363
|
|
|
364
364
|
`--stack` defaults to `base`, and the default
|
|
365
365
|
does not read as a passed flag, so a bare `aitk init` installs governance and
|
|
366
|
-
still prompts. `--skip` takes `wiki` and `
|
|
367
|
-
on any other value. There is no `--standards`, since no run
|
|
368
|
-
into the target.
|
|
366
|
+
still prompts. `--skip` takes `wiki`, `governance`, and `snippets`, and warns
|
|
367
|
+
without aborting on any other value. There is no `--standards`, since no run
|
|
368
|
+
writes a standard into the target.
|
|
369
|
+
|
|
370
|
+
`--snippets` carries no default. A bare `aitk init` installs no snippets and
|
|
371
|
+
reports the domain as skipped, the same way an explicit `--skip snippets`
|
|
372
|
+
does, both naming `aitk snippets install essentials <target>` as the recovery
|
|
373
|
+
command. `--snippets none` is a distinct, deliberate way to reach the same
|
|
374
|
+
zero-file result while still running the step, which is what lets a scripted
|
|
375
|
+
caller assert "install nothing" without depending on an empty string, which
|
|
376
|
+
`aitk snippets install` refuses.
|
|
369
377
|
|
|
370
378
|
## Unguarded tooling primitives
|
|
371
379
|
|
package/docs/target-projects.md
CHANGED
|
@@ -70,7 +70,7 @@ Each diagram entry records the commit and date it was last verified against, and
|
|
|
70
70
|
|
|
71
71
|
### Stack decision
|
|
72
72
|
|
|
73
|
-
The default path is `base`. `aitk init` on `base` installs base tooling configs, Claude seeds, governance core rules, and
|
|
73
|
+
The default path is `base`. `aitk init` on `base` installs base tooling configs, Claude seeds, and governance core rules, and scaffolds an empty `.claude/wiki/`. Most projects need nothing more.
|
|
74
74
|
|
|
75
75
|
Escalate only for real web apps. The `setup-init` skill reads `package.json` and root configs, then picks the matching tooling stack (`vite-react` today) and the matching governance stack (`react`, `astro`, `node`).
|
|
76
76
|
|
|
@@ -84,13 +84,16 @@ 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,
|
|
87
|
+
`aitk init` installs base tooling, Claude workflow, and governance, 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
|
-
|
|
91
|
+
Snippets does not install by default. Pass `--snippets <category>`, such as `--snippets essentials`, to take it. Its own rule describing the `@` reference convention installs alongside it, so a project that declines snippets never receives a rule for a behavior it holds no snippet to exercise.
|
|
92
|
+
|
|
93
|
+
`governance`, `snippets`, and `wiki` are skippable:
|
|
92
94
|
|
|
93
95
|
- `--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.
|
|
96
|
+
- `--skip snippets`: the explicit spelling for what a bare init already does, since `--snippets` carries no default. Both print the `aitk snippets install essentials <path>` command afterward.
|
|
94
97
|
- `--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.
|
|
95
98
|
|
|
96
99
|
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.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Execute an @-referenced snippet's instructions immediately using session context
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Snippet reference standards
|
|
6
|
+
|
|
7
|
+
## Execution
|
|
8
|
+
|
|
9
|
+
- When a snippet is referenced with `@`, execute its instructions immediately using available session context
|
package/package.json
CHANGED
|
@@ -67,7 +67,7 @@ mkdir -p "$TARGET_DIR"
|
|
|
67
67
|
log_info "Initialized git in $TARGET_DIR"
|
|
68
68
|
|
|
69
69
|
log_step "Run aitk init"
|
|
70
|
-
(cd "$TARGET_DIR" && AITK_NON_INTERACTIVE=1 bun run "$CLONE_DIR/src/cli.ts" init --stack base 2>&1 | pipe_output) || log_error "aitk init failed"
|
|
70
|
+
(cd "$TARGET_DIR" && AITK_NON_INTERACTIVE=1 bun run "$CLONE_DIR/src/cli.ts" init --stack base --snippets essentials 2>&1 | pipe_output) || log_error "aitk init failed"
|
|
71
71
|
log_info "aitk init completed"
|
|
72
72
|
|
|
73
73
|
log_step "Assert scaffold"
|
|
@@ -80,10 +80,12 @@ log_step "Assert scaffold"
|
|
|
80
80
|
# reads a standard through `aitk standards <name>`, which resolves against the
|
|
81
81
|
# copy inside the package.
|
|
82
82
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# editing this line fails the gate on a correct
|
|
86
|
-
|
|
83
|
+
# Snippets carries no default, so the run above passes `--snippets essentials`
|
|
84
|
+
# explicitly. The snippets path has to name a slug that preset still carries;
|
|
85
|
+
# editing the preset without editing this line fails the gate on a correct
|
|
86
|
+
# install. The rule path confirms the domain's own convention rule installs
|
|
87
|
+
# alongside it, since a target that declined snippets never receives it.
|
|
88
|
+
for path in "CLAUDE.md" ".claude/snippets/decision-help.md" ".claude/rules/snippets/505-at-references.md" ".claude/wiki/index.md" ".claude" ".claude/context/index.md" ".claude/wireframes/index.md" ".claude/diagrams/index.md" \
|
|
87
89
|
".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
|
|
88
90
|
".claude/rules/core/000-constitution.md"; do
|
|
89
91
|
if [ ! -e "$TARGET_DIR/$path" ]; then
|
package/scripts/core/verify.sh
CHANGED
|
@@ -20,10 +20,14 @@ CHANGED_FILES=""
|
|
|
20
20
|
SANDBOX_UNDECLARED_CEILING=47
|
|
21
21
|
|
|
22
22
|
# Rules no stack reaches, space separated and sorted the way `aitk gov list`
|
|
23
|
-
# emits them.
|
|
24
|
-
#
|
|
25
|
-
# a
|
|
26
|
-
|
|
23
|
+
# emits them. `260-shadcn` and `320-tanstack-query` are opt-in libraries a
|
|
24
|
+
# project may not want; `505-at-references` ships with no stack on purpose,
|
|
25
|
+
# since a rule under `claude/` would reach every base consumer through the
|
|
26
|
+
# folder-whole entry, and only `aitk snippets install` writes it, alongside
|
|
27
|
+
# the snippets a caller took. All three are recorded here rather than in a
|
|
28
|
+
# config file: the list is what a reader compares a new arrival against, and a
|
|
29
|
+
# config file would absorb the arrival silently.
|
|
30
|
+
GOV_EXPECTED_UNREFERENCED="260-shadcn 320-tanstack-query 505-at-references"
|
|
27
31
|
|
|
28
32
|
# The retained counts the audit stage compares each run against. Spelled here
|
|
29
33
|
# rather than derived, because this script only ever names the file in a remedy
|
|
@@ -292,12 +296,13 @@ main() {
|
|
|
292
296
|
# A stack entry naming a rule folder takes every rule in it, which is what
|
|
293
297
|
# stops a new rule from needing a second edit to reach a target. The failure
|
|
294
298
|
# it leaves open is a rule authored into a folder no stack names, which
|
|
295
|
-
#
|
|
299
|
+
# `aitk gov install` never reaches on its own, whether or not another domain
|
|
300
|
+
# installs the file by a different route.
|
|
296
301
|
#
|
|
297
|
-
# This reports and never fails.
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
#
|
|
302
|
+
# This reports and never fails. All three standing findings ship this way on
|
|
303
|
+
# purpose, so gating would fail every push over the deliberate case and teach
|
|
304
|
+
# a reader to route around the stage. Revisit if the set keeps growing and
|
|
305
|
+
# the pattern turns out to be an accident rather than a design.
|
|
301
306
|
log_step "Unreferenced rules"
|
|
302
307
|
local gov_json gov_status=0 unreferenced
|
|
303
308
|
gov_json=$(cd "$PROJECT_ROOT" && bun src/cli.ts gov list --json 2>/dev/null) || gov_status=$?
|
|
@@ -331,10 +336,10 @@ main() {
|
|
|
331
336
|
elif [ -z "$unreferenced" ]; then
|
|
332
337
|
log_info "Every rule is reached by a stack"
|
|
333
338
|
elif [ "$unreferenced" = "$GOV_EXPECTED_UNREFERENCED" ]; then
|
|
334
|
-
log_info "Reached by no stack: $unreferenced (
|
|
339
|
+
log_info "Reached by no stack: $unreferenced (each recorded above with why)"
|
|
335
340
|
else
|
|
336
341
|
log_warn "Reached by no stack: $unreferenced"
|
|
337
|
-
log_warn "Expected: $GOV_EXPECTED_UNREFERENCED. Name the new rule in a stack, or update GOV_EXPECTED_UNREFERENCED in this script and say why it
|
|
342
|
+
log_warn "Expected: $GOV_EXPECTED_UNREFERENCED. Name the new rule in a stack, or update GOV_EXPECTED_UNREFERENCED in this script and say why it reaches no stack."
|
|
338
343
|
fi
|
|
339
344
|
fi
|
|
340
345
|
|
package/src/commands/init.ts
CHANGED
package/src/commands/snippets.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { BASE_CATEGORY } from '@/snippets/categories'
|
|
|
6
6
|
import {
|
|
7
7
|
ALL_CATEGORY,
|
|
8
8
|
installSnippets,
|
|
9
|
+
installSnippetsRule,
|
|
9
10
|
installableCategories,
|
|
10
11
|
resolveSnippets,
|
|
11
12
|
} from '@/snippets/install'
|
|
@@ -176,6 +177,9 @@ async function runInstall(
|
|
|
176
177
|
for (const rel of await installSnippets(resolution.files, resolved)) {
|
|
177
178
|
logAdd(rel)
|
|
178
179
|
}
|
|
180
|
+
for (const rel of await installSnippetsRule(PROJECT_ROOT, resolved)) {
|
|
181
|
+
logAdd(rel)
|
|
182
|
+
}
|
|
179
183
|
await recordStamp(createSnippetsAdapter(PROJECT_ROOT), resolved, new Date())
|
|
180
184
|
|
|
181
185
|
const { GREEN, NC } = palette(process.stderr)
|
package/src/init/flags.ts
CHANGED
|
@@ -29,8 +29,7 @@ export const INIT_OPTIONS: readonly InitOptionSpec[] = [
|
|
|
29
29
|
{
|
|
30
30
|
key: 'snippets',
|
|
31
31
|
flags: '--snippets <category>',
|
|
32
|
-
description: "Snippets preset, category, or 'all'",
|
|
33
|
-
defaultValue: 'essentials',
|
|
32
|
+
description: "Snippets preset, category, 'none', or 'all'. Omit to skip",
|
|
34
33
|
},
|
|
35
34
|
{
|
|
36
35
|
key: 'skip',
|
package/src/init/plan.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SKIPPABLE_DOMAINS = ['wiki', 'governance'] as const
|
|
1
|
+
export const SKIPPABLE_DOMAINS = ['wiki', 'governance', 'snippets'] as const
|
|
2
2
|
|
|
3
3
|
export type SkippableDomain = (typeof SKIPPABLE_DOMAINS)[number]
|
|
4
4
|
|
|
@@ -30,10 +30,23 @@ export interface InitPlan {
|
|
|
30
30
|
export interface InitFlags {
|
|
31
31
|
readonly stack?: string
|
|
32
32
|
readonly add?: string
|
|
33
|
-
|
|
33
|
+
/** Absent when `--snippets` was never passed, which installs none. */
|
|
34
|
+
readonly snippets?: string
|
|
34
35
|
readonly skip: SkipPlan
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Reads why the snippets step will not run, or `undefined` when it will.
|
|
40
|
+
* Omitting `--snippets` reaches the same skip as `--skip snippets` explicitly,
|
|
41
|
+
* so a plain `aitk init` installs none and both routes share one recovery
|
|
42
|
+
* line rather than two behaviors with one spelling each.
|
|
43
|
+
*/
|
|
44
|
+
export function snippetsSkipReason(flags: InitFlags): string | undefined {
|
|
45
|
+
if (flags.skip.skipped.has('snippets')) return '--skip snippets'
|
|
46
|
+
if (flags.snippets === undefined) return 'no --snippets given'
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
/**
|
|
38
51
|
* Resolves the stack the run will install. An empty `--stack` reads as absent
|
|
39
52
|
* rather than as a way to decline, so the flag carries a real name or nothing
|
|
@@ -98,7 +111,11 @@ export function planInit(flags: InitFlags): InitPlan {
|
|
|
98
111
|
})
|
|
99
112
|
}
|
|
100
113
|
|
|
101
|
-
|
|
114
|
+
if (snippetsSkipReason(flags) !== undefined) {
|
|
115
|
+
preview.push({ level: 'warn', text: 'snippets (skipped)' })
|
|
116
|
+
} else {
|
|
117
|
+
preview.push({ level: 'info', text: `snippets (${flags.snippets})` })
|
|
118
|
+
}
|
|
102
119
|
|
|
103
120
|
if (!flags.skip.skipped.has('wiki')) {
|
|
104
121
|
preview.push({
|
package/src/init/steps.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitFlags, resolveStack } from '@/init/plan'
|
|
1
|
+
import { type InitFlags, resolveStack, snippetsSkipReason } from '@/init/plan'
|
|
2
2
|
import type { DomainStep } from '@/init/run'
|
|
3
3
|
|
|
4
4
|
/** Builds the child-process invocation for one domain. */
|
|
@@ -48,11 +48,25 @@ export function buildSteps(
|
|
|
48
48
|
})
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
// `snippets === undefined` here can never fire on its own: `snippetsSkipReason`
|
|
52
|
+
// already returns a reason whenever `flags.snippets` is undefined. It is what
|
|
53
|
+
// narrows `snippets` to `string` for the child() call below, since TypeScript
|
|
54
|
+
// cannot narrow a property through a function call.
|
|
55
|
+
const snippets = flags.snippets
|
|
56
|
+
const skipReason = snippetsSkipReason(flags)
|
|
57
|
+
if (skipReason !== undefined || snippets === undefined) {
|
|
58
|
+
steps.push({
|
|
59
|
+
kind: 'skip',
|
|
60
|
+
label: 'Snippets',
|
|
61
|
+
notice: `Skipped: ${skipReason ?? 'no --snippets given'}. Run 'aitk snippets install essentials ${target}' to install snippets.`,
|
|
62
|
+
})
|
|
63
|
+
} else {
|
|
64
|
+
steps.push({
|
|
65
|
+
kind: 'run',
|
|
66
|
+
label: 'Snippets',
|
|
67
|
+
run: child(['snippets', 'install', snippets, resolved]),
|
|
68
|
+
})
|
|
69
|
+
}
|
|
56
70
|
|
|
57
71
|
if (!flags.skip.skipped.has('wiki')) {
|
|
58
72
|
steps.push({
|
|
@@ -3,6 +3,8 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { isDirectory } from '@/target'
|
|
4
4
|
|
|
5
5
|
export const BASE_CATEGORY = 'base'
|
|
6
|
+
/** A real category resolving to zero entries, for a caller declining on purpose. */
|
|
7
|
+
export const NONE_CATEGORY = 'none'
|
|
6
8
|
|
|
7
9
|
export function snippetsSourceDir(root: string): string {
|
|
8
10
|
return join(root, 'snippets')
|
|
@@ -39,8 +41,13 @@ export function categoryDir(root: string, category: string): string {
|
|
|
39
41
|
* Tests for a directory rather than mere existence. `snippets/` holds files
|
|
40
42
|
* alongside its category folders, so an argument naming one of them resolves
|
|
41
43
|
* to a real path that cannot be scanned as a category.
|
|
44
|
+
*
|
|
45
|
+
* An empty category refuses rather than resolving, since `join(source, '')`
|
|
46
|
+
* is `source` and would otherwise report the base directory as the category,
|
|
47
|
+
* widening an unset value to every base entry instead of naming none.
|
|
42
48
|
*/
|
|
43
49
|
export function categoryExists(root: string, category: string): boolean {
|
|
50
|
+
if (category === '') return false
|
|
44
51
|
return isDirectory(categoryDir(root, category))
|
|
45
52
|
}
|
|
46
53
|
|
package/src/snippets/install.ts
CHANGED
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
categoryExists,
|
|
8
8
|
listCategories,
|
|
9
9
|
listEntries,
|
|
10
|
+
NONE_CATEGORY,
|
|
10
11
|
snippetsSourceDir,
|
|
11
12
|
} from '@/snippets/categories'
|
|
12
13
|
import { findPreset, loadPresets } from '@/snippets/presets'
|
|
14
|
+
import { isDirectory } from '@/target'
|
|
13
15
|
|
|
14
16
|
export const ALL_CATEGORY = 'all'
|
|
15
17
|
|
|
@@ -72,6 +74,15 @@ export function resolveSnippets(
|
|
|
72
74
|
return { ok: true, step: 'Resolving all categories', files, missing: [] }
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
if (category === NONE_CATEGORY) {
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
step: 'Resolving category: none',
|
|
81
|
+
files: [],
|
|
82
|
+
missing: [],
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
const preset = findPreset(root, category)
|
|
76
87
|
if (preset) {
|
|
77
88
|
const files: SnippetFile[] = []
|
|
@@ -123,3 +134,39 @@ export async function installSnippets(
|
|
|
123
134
|
|
|
124
135
|
return installed
|
|
125
136
|
}
|
|
137
|
+
|
|
138
|
+
function snippetsRuleSourceDir(root: string): string {
|
|
139
|
+
return join(root, 'governance', 'rules', 'snippets')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function installedSnippetsRuleDir(target: string): string {
|
|
143
|
+
return join(target, '.claude', 'rules', 'snippets')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Installs the `@`-reference convention rule alongside the snippets a caller
|
|
148
|
+
* took. No stack names this folder, so a project that declined snippets never
|
|
149
|
+
* receives a rule describing a behavior it holds no snippet to exercise.
|
|
150
|
+
*/
|
|
151
|
+
export async function installSnippetsRule(
|
|
152
|
+
root: string,
|
|
153
|
+
target: string,
|
|
154
|
+
): Promise<string[]> {
|
|
155
|
+
const dir = snippetsRuleSourceDir(root)
|
|
156
|
+
if (!isDirectory(dir)) return []
|
|
157
|
+
|
|
158
|
+
const installed: string[] = []
|
|
159
|
+
|
|
160
|
+
for (const rel of new Bun.Glob('*.md').scanSync({
|
|
161
|
+
cwd: dir,
|
|
162
|
+
onlyFiles: true,
|
|
163
|
+
})) {
|
|
164
|
+
await copyPreservingMode(
|
|
165
|
+
join(dir, rel),
|
|
166
|
+
join(installedSnippetsRuleDir(target), rel),
|
|
167
|
+
)
|
|
168
|
+
installed.push(join('.claude', 'rules', 'snippets', rel))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return installed
|
|
172
|
+
}
|
package/standards/pr.md
CHANGED
|
@@ -51,6 +51,7 @@ Does not govern:
|
|
|
51
51
|
- Run the check before writing its line. A `- [ ]` reports a check that has not run rather than one that is planned.
|
|
52
52
|
- Tick the box and state the observed result. `- [x] npm test passes, 42 tests` beats `- [ ] run npm test`.
|
|
53
53
|
- Quote the count or output the run reported, never a figure carried from elsewhere.
|
|
54
|
+
- A ticked box describes the tree as it stands, not as it stood when it was written. A later commit that removes or replaces what it names makes the box false, and the branch that made that commit corrects it in the same pass, the same as any other stale claim in the body.
|
|
54
55
|
- Leave a box unchecked only when a human is required, and name which human and why on the same line.
|
|
55
56
|
- Human-only covers visual or aesthetic judgment, anything needing credentials or a live third-party service, anything needing a second machine or a fresh OS, and judgment about whether a boundary or an abstraction reads correctly. The agent runs everything else.
|
|
56
57
|
- What makes a human required is a capability the agent lacks, never the cost of the run. Authorizing a spend is the operator's and performing the run is not, so an arm the repository ships a harness for gets driven once the operator has cleared the spend, and the box records what it returned.
|
|
@@ -65,10 +65,6 @@
|
|
|
65
65
|
- When cspell flags a word, rewrite typos. Add real terms to the appropriate dictionary in `cspell.json`.
|
|
66
66
|
- Keep dictionary files sorted alphabetically
|
|
67
67
|
|
|
68
|
-
## Snippets
|
|
69
|
-
|
|
70
|
-
- When a snippet is referenced with `@`, execute its instructions immediately using available session context
|
|
71
|
-
|
|
72
68
|
## Tasks
|
|
73
69
|
|
|
74
70
|
- `.claude/tasks/` is gitignored local session scratch, one file per task. Edit freely. No staging or revert before commits.
|