@erclx/aitk 0.112.0 → 1.0.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-memory-review/REQUIREMENT.md +1 -1
- package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +1 -1
- package/claude/skills/setup-init/SKILL.md +6 -4
- package/claude/skills/toolkit-cli/SKILL.md +70 -2
- package/docs/agents/scripting.md +14 -3
- package/docs/target-projects.md +6 -4
- package/governance/rules/claude/559-memory.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-tooling-paths.sh +61 -0
- package/scripts/core/verify.sh +5 -0
- package/scripts/tooling/verify.sh +1 -1
- package/src/commands/sync.ts +6 -2
- package/src/commands/tooling.ts +95 -13
- package/src/init/steps.ts +1 -1
- package/standards/memory.md +1 -1
- package/tooling/astro/configs/astro.config.mjs +4 -1
- package/tooling/astro/reference.md +2 -2
- package/tooling/base/reference.md +1 -1
- package/tooling/python/reference.md +3 -3
- package/tooling/vite-react/reference.md +2 -2
- package/tooling/web/reference.md +1 -1
|
@@ -9,7 +9,7 @@ description: What memory review is for, the gaps it closes, and why every action
|
|
|
9
9
|
|
|
10
10
|
Without this skill, the memory folder grows and never drains. Entries pile up restating rules a durable surface already carries, nothing decides which memory has earned a place in one, and a rule that does get promoted arrives verbatim in a file whose voice it does not match. The folder then reads as a second source of truth that no surface points at.
|
|
11
11
|
|
|
12
|
-
A pen the caller cannot face is the same gap wearing a different shape.
|
|
12
|
+
A pen the caller cannot face is the same gap wearing a different shape. Routing at capture takes the domain facts and whatever the ownership test leaves stays, in whatever mix of types that is, so the folder still reaches a size nobody reads in one sitting. A ship-time run is scoped to the entries that session captured for exactly that reason, and the full sweep is what an operator asks for when the cross-session duplicates are the point. An entry leaving the pen is archived rather than deleted, because a folder git does not hold gives a wrong bulk call no undo.
|
|
13
13
|
|
|
14
14
|
## Must
|
|
15
15
|
|
|
@@ -25,6 +25,6 @@ The requirement is a resume request to the orchestrator skill with that skill lo
|
|
|
25
25
|
|
|
26
26
|
Step 1 exists because both other callers of capture are ship-chain skills and this session never ships. Without a call here, the session that receives every operator correction is the one session that records none. A compaction arriving with no warning takes the capture with it, and firing it once per batch of merges leaves the same window open across a long planning stretch, since a sweep runs only on a merge. The refill sweep reports the debt between handoffs so the operator knows one is owed.
|
|
27
27
|
|
|
28
|
-
Capture is told this session does not commit, so it skips routing and writes memory files alone. A routed fact lands in a context entry, which is a tracked file, and the orchestrator's boundaries forbid writing one from this session. That split is correct rather than a limitation, since a domain fact belongs to the task that owns the surface and goes in that task's Findings, while what this session produces is feedback about how to work
|
|
28
|
+
Capture is told this session does not commit, so it skips routing and writes memory files alone. A routed fact lands in a context entry, which is a tracked file, and the orchestrator's boundaries forbid writing one from this session. That split is correct rather than a limitation, since a domain fact belongs to the task that owns the surface and goes in that task's Findings, while what this session produces is feedback about how to work.
|
|
29
29
|
|
|
30
30
|
The core's drift step answers where the working directory carries `claude/skills/` with history behind it, which is the toolkit repository itself, and refuses by naming the absent tree anywhere else. A project consuming the plugin from a marketplace cache is that second case, so read the refusal as the boundary rather than as a fault.
|
|
@@ -92,17 +92,19 @@ AITK_NON_INTERACTIVE=1 aitk init \
|
|
|
92
92
|
|
|
93
93
|
Omit any flag whose resolved value is empty.
|
|
94
94
|
|
|
95
|
-
Step 2: `aitk tooling sync <tooling-stack
|
|
95
|
+
Step 2: `aitk tooling sync <tooling-stack> --write` installs stack deps, scripts, gitignore entries, seeds, golden configs, and drops the reference doc. The extends chain is walked, so syncing `vite-react` also pulls `web` and `base` configs. Skip if the tooling stack is `base` (already synced by `aitk init`).
|
|
96
|
+
|
|
97
|
+
`--write` is required. A headless run without it reports what it would replace and exits 1, since golden configs overwrite whatever the target holds at those paths. Scaffolding into a fresh target has nothing to lose, so pass it directly rather than reading a report first.
|
|
96
98
|
|
|
97
99
|
```bash
|
|
98
|
-
AITK_NON_INTERACTIVE=1 aitk tooling sync <tooling-stack> <target>
|
|
100
|
+
AITK_NON_INTERACTIVE=1 aitk tooling sync <tooling-stack> <target> --write
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
Monorepo with multiple language roots: run `aitk init` once at the repo root so `base` (husky, prettier, cspell, commitlint, CI) lands single, then sync each subtree with `--skip base` so the shared layer is not re-dropped.
|
|
102
104
|
|
|
103
105
|
```bash
|
|
104
|
-
AITK_NON_INTERACTIVE=1 aitk tooling sync vite-react ./frontend --skip base
|
|
105
|
-
AITK_NON_INTERACTIVE=1 aitk tooling sync python ./backend --skip base
|
|
106
|
+
AITK_NON_INTERACTIVE=1 aitk tooling sync vite-react ./frontend --skip base --write
|
|
107
|
+
AITK_NON_INTERACTIVE=1 aitk tooling sync python ./backend --skip base --write
|
|
106
108
|
```
|
|
107
109
|
|
|
108
110
|
Without `--skip base`, each subtree re-drops husky, and git honors only one `core.hooksPath`, so the extra hook dirs silently break. Each subtree keeps its own framework configs and its own `.claude/tooling/<stack>.md` audit docs.
|
|
@@ -11,7 +11,7 @@ What each `aitk` sync or install command does to existing files in a target proj
|
|
|
11
11
|
|
|
12
12
|
| Surface | Command | Effect on existing files |
|
|
13
13
|
| -------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------- |
|
|
14
|
-
| Golden configs
|
|
14
|
+
| Golden configs, listed in full below | `aitk tooling sync` | Overwritten once `--write` is passed. Local edits are lost. |
|
|
15
15
|
| Dictionary seeds (`.cspell/*.txt`) | `aitk tooling sync` | Merged and sorted. Existing terms preserved. |
|
|
16
16
|
| Other seeds (`cspell.json`, `.lintstagedrc`, state docs) | `aitk tooling sync` | Copy-once. Dropped on first install, untouched after. |
|
|
17
17
|
| Standards | `aitk standards install` | All overwritten. |
|
|
@@ -22,9 +22,77 @@ What each `aitk` sync or install command does to existing files in a target proj
|
|
|
22
22
|
| `.gitignore`, deps, scripts | any sync | Additive. Existing entries preserved. Deps re-pin on major skew. |
|
|
23
23
|
| Generated `index.md` | any sync or regen | Rewritten from target state. Hand edits are lost. |
|
|
24
24
|
|
|
25
|
+
## What a tooling sync can overwrite
|
|
26
|
+
|
|
27
|
+
A golden config is any file a stack ships under `configs/`, and the category is wider than its name suggests. It carries the CI workflow, the git hooks, the end-to-end harness, the shell scripts under `scripts/`, and the editor settings, alongside the linters and compilers a reader expects. A stack inherits its parent's configs, so syncing `astro` also writes everything `web` and `base` hold.
|
|
28
|
+
|
|
29
|
+
Run `aitk tooling sync <stack> <target> --check` for the list resolved against a real target. It reports every path and writes nothing. The list below is what the stacks hold as shipped, before any chain resolution.
|
|
30
|
+
|
|
31
|
+
<!-- generated:tooling-paths -->
|
|
32
|
+
|
|
33
|
+
### astro
|
|
34
|
+
|
|
35
|
+
- `astro.config.mjs`
|
|
36
|
+
- `eslint.config.js`
|
|
37
|
+
- `playwright.config.ts`
|
|
38
|
+
- `tsconfig.json`
|
|
39
|
+
- `vitest.config.ts`
|
|
40
|
+
|
|
41
|
+
### base
|
|
42
|
+
|
|
43
|
+
- `.editorconfig`
|
|
44
|
+
- `.github/pull_request_template.md`
|
|
45
|
+
- `.github/workflows/verify.yml`
|
|
46
|
+
- `.husky/commit-msg`
|
|
47
|
+
- `.husky/post-merge`
|
|
48
|
+
- `.husky/post-rewrite`
|
|
49
|
+
- `.husky/pre-commit`
|
|
50
|
+
- `.husky/pre-push`
|
|
51
|
+
- `.prettierrc`
|
|
52
|
+
- `.shellcheckrc`
|
|
53
|
+
- `.vscode/extensions.json`
|
|
54
|
+
- `.vscode/settings.json`
|
|
55
|
+
- `commitlint.config.js`
|
|
56
|
+
- `scripts/clean.sh`
|
|
57
|
+
- `scripts/update.sh`
|
|
58
|
+
- `scripts/verify.sh`
|
|
59
|
+
|
|
60
|
+
### python
|
|
61
|
+
|
|
62
|
+
- `.coveragerc`
|
|
63
|
+
- `.python-version`
|
|
64
|
+
- `mypy.ini`
|
|
65
|
+
- `pytest.ini`
|
|
66
|
+
- `ruff.toml`
|
|
67
|
+
- `scripts/verify.sh`
|
|
68
|
+
|
|
69
|
+
### vite-react
|
|
70
|
+
|
|
71
|
+
- `playwright.config.ts`
|
|
72
|
+
- `tsconfig.json`
|
|
73
|
+
- `vite.config.ts`
|
|
74
|
+
- `vitest.config.ts`
|
|
75
|
+
|
|
76
|
+
### web
|
|
77
|
+
|
|
78
|
+
- `.github/workflows/verify.yml`
|
|
79
|
+
- `.vscode/extensions.json`
|
|
80
|
+
- `.vscode/settings.json`
|
|
81
|
+
- `e2e/home.spec.ts`
|
|
82
|
+
- `e2e/screenshot.ts`
|
|
83
|
+
- `eslint.config.js`
|
|
84
|
+
- `scripts/screenshot.sh`
|
|
85
|
+
- `scripts/verify.sh`
|
|
86
|
+
- `scripts/worktree-port.sh`
|
|
87
|
+
- `src/test/setup.ts`
|
|
88
|
+
|
|
89
|
+
<!-- /generated:tooling-paths -->
|
|
90
|
+
|
|
25
91
|
## Rules
|
|
26
92
|
|
|
27
|
-
-
|
|
93
|
+
- `aitk tooling sync` writes nothing until `--write` is passed. A headless run without it reports and exits 1, so a script that forgets the flag fails rather than silently skipping the sync.
|
|
94
|
+
- Run `--check` first when the project carries local edits to any path above. The report names each file it would replace, which is the warning the user needs before the write.
|
|
95
|
+
- An interactive run still prompts. `--write` skips the prompt, and `--check` refuses to write even with a TTY.
|
|
28
96
|
- Seeds are user-owned. Dictionary `.txt` files merge and sort. Other seeds are copy-once, so re-seeding a structured file means deleting it and syncing again.
|
|
29
97
|
- Prefer `aitk standards sync` over `install` on an existing project. `install` overwrites every standard.
|
|
30
98
|
- For section-level customizations of a standard or seed doc, use the `claude-seed-sync` skill, not `aitk ... sync`. It diffs per section and preserves edits.
|
package/docs/agents/scripting.md
CHANGED
|
@@ -77,8 +77,11 @@ surface is in `skills-audit.md`.
|
|
|
77
77
|
# Create a new tooling stack
|
|
78
78
|
AITK_NON_INTERACTIVE=1 aitk tooling create astro
|
|
79
79
|
|
|
80
|
-
#
|
|
81
|
-
AITK_NON_INTERACTIVE=1 aitk tooling sync astro /path/to/project
|
|
80
|
+
# Report what a stack would change, writing nothing
|
|
81
|
+
AITK_NON_INTERACTIVE=1 aitk tooling sync astro /path/to/project --check
|
|
82
|
+
|
|
83
|
+
# Sync a stack into a target project, overwriting its golden configs
|
|
84
|
+
AITK_NON_INTERACTIVE=1 aitk tooling sync astro /path/to/project --write
|
|
82
85
|
|
|
83
86
|
# Install a governance stack (the stack argument is required headlessly)
|
|
84
87
|
AITK_NON_INTERACTIVE=1 aitk gov install astro --add 260-shadcn /path/to/project
|
|
@@ -90,7 +93,7 @@ AITK_NON_INTERACTIVE=1 aitk gov sync /path/to/project
|
|
|
90
93
|
AITK_NON_INTERACTIVE=1 aitk gov build /path/to/project
|
|
91
94
|
|
|
92
95
|
# Sync a monorepo subtree, skipping the base layer the repo root already owns
|
|
93
|
-
AITK_NON_INTERACTIVE=1 aitk tooling sync vite-react /path/to/repo/frontend --skip base
|
|
96
|
+
AITK_NON_INTERACTIVE=1 aitk tooling sync vite-react /path/to/repo/frontend --skip base --write
|
|
94
97
|
|
|
95
98
|
# Verify a stack end-to-end in a throwaway scaffold
|
|
96
99
|
aitk tooling verify vite-react
|
|
@@ -134,3 +137,11 @@ SANDBOX_SCENARIO=sync aitk sandbox infra:tooling
|
|
|
134
137
|
# Read every audit as one record. Exit 2 is a fact, 3 an audit that did not report
|
|
135
138
|
aitk audits run --json
|
|
136
139
|
```
|
|
140
|
+
|
|
141
|
+
`aitk tooling sync` is the one verb above whose flag is mandatory headlessly. It
|
|
142
|
+
overwrites every golden config a stack ships, which reaches the CI workflow, the
|
|
143
|
+
git hooks, the end-to-end harness, and the shell scripts under `scripts/`, so a
|
|
144
|
+
headless run carrying neither `--check` nor `--write` reports what it would
|
|
145
|
+
replace and exits 1 rather than applying it. Run `--check` first to read the
|
|
146
|
+
list, then `--write` to apply it. `aitk tooling sync --help` names both, and the
|
|
147
|
+
full per-stack path list sits in the `toolkit-cli` skill.
|
package/docs/target-projects.md
CHANGED
|
@@ -45,7 +45,7 @@ Two steps, in order:
|
|
|
45
45
|
The chain is:
|
|
46
46
|
|
|
47
47
|
- `aitk init` installs base tooling, Claude seeds, and governance rules into `.claude/rules/` in the same pass
|
|
48
|
-
- `aitk tooling sync <stack
|
|
48
|
+
- `aitk tooling sync <stack> --write` adds stack-specific deps, scripts, gitignore entries, and drops `.claude/tooling/<stack>.md` (plus parents) as the agent's audit context
|
|
49
49
|
- The agent follows the reference to generate eslint, vitest, playwright configs and the stack's setup script, and extends `.claude/context/ci.md` and `.claude/context/development.md` per the reference's extend sections <!-- audit-ignore-citations -->
|
|
50
50
|
- `setup-verify` runs the installed `package.json` scripts (lint, typecheck, check, test, build) and reports pass or fail
|
|
51
51
|
|
|
@@ -148,7 +148,9 @@ The same field names a proposal-only skill with a live case here under `migratio
|
|
|
148
148
|
|
|
149
149
|
Add `--json` for the machine-readable report, and `--exit-code` to fail a CI job when a target falls behind. Files the project authored itself never count toward that exit code, and neither do superseded artifacts, seed drift, tooling, or anything the reverse walk reports, since each names content the project is expected to edit or place itself. An unmigrated domain does count, because running the relocation closes it.
|
|
150
150
|
|
|
151
|
-
Tooling reports under a section of its own, and `measured` there says whether the target ever recorded a chain. One that never ran a tooling sync reports unmeasured rather than clean, which is what separates tooling nobody has looked at from tooling that is current. A workspace root records nothing either way, since each package resolves its own chain.
|
|
151
|
+
Tooling reports under a section of its own, and `measured` there says whether the target ever recorded a chain. One that never ran a tooling sync reports unmeasured rather than clean, which is what separates tooling nobody has looked at from tooling that is current. A workspace root records nothing either way, since each package resolves its own chain.
|
|
152
|
+
|
|
153
|
+
Reconcile the configs with `aitk tooling sync <stack> <path> --check` to read which files differ, then re-run it with `--write` to apply them. The drift report counts categories and the sync names paths, which is the difference worth knowing before a golden config the project edited is replaced.
|
|
152
154
|
|
|
153
155
|
### Catch-all
|
|
154
156
|
|
|
@@ -211,8 +213,8 @@ The repo root owns the shared `base` layer, and each language lives in its own s
|
|
|
211
213
|
|
|
212
214
|
```bash
|
|
213
215
|
aitk init --stack react .
|
|
214
|
-
aitk tooling sync vite-react ./frontend --skip base
|
|
215
|
-
aitk tooling sync python ./backend --skip base
|
|
216
|
+
aitk tooling sync vite-react ./frontend --skip base --write
|
|
217
|
+
aitk tooling sync python ./backend --skip base --write
|
|
216
218
|
```
|
|
217
219
|
|
|
218
220
|
`--skip base` drops the `base` layer from each subtree sync, so husky, prettier, cspell, commitlint, and CI stay single at the repo root. Without it, every subtree re-drops husky, and since git honors only one `core.hooksPath` the extra hook dirs silently break. Each subtree still gets its own framework configs (eslint, vitest, tsconfig, vite) and its own `.claude/tooling/<stack>.md` audit docs.
|
|
@@ -8,7 +8,7 @@ paths:
|
|
|
8
8
|
|
|
9
9
|
## Routing
|
|
10
10
|
|
|
11
|
-
- Write no memory entry for a fact a per-domain context entry already owns.
|
|
11
|
+
- Write no memory entry for a fact a per-domain context entry already owns.
|
|
12
12
|
|
|
13
13
|
## The pen
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
set -o pipefail
|
|
4
|
+
|
|
5
|
+
# Rewrites the generated path block in the shipped overwrite contract, which
|
|
6
|
+
# names every file a tooling sync can replace. The category reaches past the
|
|
7
|
+
# linters its name suggests, into the CI workflow, the end-to-end harness, and
|
|
8
|
+
# the shell scripts under scripts/, so a reader cannot infer the list. Generating
|
|
9
|
+
# it is what keeps a stack gaining a file from leaving the contract wrong.
|
|
10
|
+
#
|
|
11
|
+
# The stack names come from `tooling list` rather than from a directory walk,
|
|
12
|
+
# so a stack the verb excludes never reaches a contract about what the verb does.
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
16
|
+
|
|
17
|
+
TOOLING_DIR="$PROJECT_ROOT/tooling"
|
|
18
|
+
CONTRACT="$PROJECT_ROOT/claude/skills/toolkit-cli/SKILL.md"
|
|
19
|
+
|
|
20
|
+
BEGIN="<!-- generated:tooling-paths -->"
|
|
21
|
+
END="<!-- /generated:tooling-paths -->"
|
|
22
|
+
|
|
23
|
+
[ -d "$TOOLING_DIR" ] || exit 0
|
|
24
|
+
[ -f "$CONTRACT" ] || exit 0
|
|
25
|
+
|
|
26
|
+
grep -qF "$BEGIN" "$CONTRACT" || {
|
|
27
|
+
echo "Missing $BEGIN marker in $CONTRACT" >&2
|
|
28
|
+
exit 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
block="$(mktemp)"
|
|
32
|
+
trap 'rm -f "$block" "$CONTRACT.tmp"' EXIT
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
echo "$BEGIN"
|
|
36
|
+
echo
|
|
37
|
+
while IFS= read -r stack; do
|
|
38
|
+
[ -d "$TOOLING_DIR/$stack/configs" ] || continue
|
|
39
|
+
echo "### $stack"
|
|
40
|
+
echo
|
|
41
|
+
while IFS= read -r rel; do
|
|
42
|
+
echo "- \`$rel\`"
|
|
43
|
+
done < <(cd "$TOOLING_DIR/$stack/configs" && find . -type f | sed 's|^\./||' | sort)
|
|
44
|
+
echo
|
|
45
|
+
done < <(bun "$PROJECT_ROOT/src/cli.ts" tooling list --json | jq -r '.stacks[].name' | sort)
|
|
46
|
+
echo "$END"
|
|
47
|
+
} >"$block"
|
|
48
|
+
|
|
49
|
+
# awk streams the file rather than editing in place, so a pattern that matches
|
|
50
|
+
# nothing produces a file missing the block instead of an unchanged one that
|
|
51
|
+
# exits zero.
|
|
52
|
+
awk -v block_file="$block" -v begin="$BEGIN" -v end="$END" '
|
|
53
|
+
$0 == begin { while ((getline line < block_file) > 0) print line; skipping = 1; next }
|
|
54
|
+
$0 == end { skipping = 0; next }
|
|
55
|
+
!skipping { print }
|
|
56
|
+
' "$CONTRACT" >"$CONTRACT.tmp"
|
|
57
|
+
|
|
58
|
+
# The destination is a tracked file with a mode the copy must not inherit from
|
|
59
|
+
# mktemp, so the content moves onto the original rather than replacing it.
|
|
60
|
+
cat "$CONTRACT.tmp" >"$CONTRACT"
|
|
61
|
+
rm -f "$CONTRACT.tmp"
|
package/scripts/core/verify.sh
CHANGED
|
@@ -272,6 +272,11 @@ main() {
|
|
|
272
272
|
assert_no_drift "claude/skills/*/references" "Skill references drifted. Run bun run check and commit the updated reference files."
|
|
273
273
|
log_info "Skill references clean"
|
|
274
274
|
|
|
275
|
+
log_step "Tooling paths"
|
|
276
|
+
run_check "bash $PROJECT_ROOT/scripts/core/regen-tooling-paths.sh" "Tooling-path regen failed"
|
|
277
|
+
assert_no_drift "claude/skills/toolkit-cli/SKILL.md" "The overwrite contract drifted from what the stacks hold. Run bun run check and commit claude/skills/toolkit-cli/SKILL.md."
|
|
278
|
+
log_info "Tooling paths clean"
|
|
279
|
+
|
|
275
280
|
log_step "Skill paths"
|
|
276
281
|
run_check "bash $PROJECT_ROOT/scripts/core/check-skill-paths.sh" "Shipped skills reference a repo-local path."
|
|
277
282
|
log_info "Skill paths clean"
|
|
@@ -129,7 +129,7 @@ main() {
|
|
|
129
129
|
run_phase "Prepare" bash -c "cd '$tmp_dir' && $prepare"
|
|
130
130
|
fi
|
|
131
131
|
|
|
132
|
-
run_phase "Sync" bash -c "cd '$tmp_dir' && AITK_NON_INTERACTIVE=1 aitk tooling sync $stack ."
|
|
132
|
+
run_phase "Sync" bash -c "cd '$tmp_dir' && AITK_NON_INTERACTIVE=1 aitk tooling sync $stack . --write"
|
|
133
133
|
|
|
134
134
|
if [ -f "$tmp_dir/package.json" ]; then
|
|
135
135
|
run_phase "lint:fix" bash -c "cd '$tmp_dir' && bun run lint:fix"
|
package/src/commands/sync.ts
CHANGED
|
@@ -218,7 +218,9 @@ function renderTooling(report: CheckReport): void {
|
|
|
218
218
|
? 'Not stamped. No chain recorded, so tooling drift is unmeasured.'
|
|
219
219
|
: `Recorded chain names no stack this toolkit ships: ${tooling.chain.join(' < ')}.`,
|
|
220
220
|
)
|
|
221
|
-
logInfo(
|
|
221
|
+
logInfo(
|
|
222
|
+
'Run `aitk tooling sync <stack> --write` to record what this target holds.',
|
|
223
|
+
)
|
|
222
224
|
return
|
|
223
225
|
}
|
|
224
226
|
|
|
@@ -235,7 +237,9 @@ function renderTooling(report: CheckReport): void {
|
|
|
235
237
|
for (const [category, count] of Object.entries(tooling.counts)) {
|
|
236
238
|
if (count > 0) logWarn(`${count} ${category}`)
|
|
237
239
|
}
|
|
238
|
-
logInfo(
|
|
240
|
+
logInfo(
|
|
241
|
+
'Run `aitk tooling sync --check` to see which files, `--write` to apply.',
|
|
242
|
+
)
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
/**
|
package/src/commands/tooling.ts
CHANGED
|
@@ -20,7 +20,16 @@ import {
|
|
|
20
20
|
} from '@/tooling/manifest'
|
|
21
21
|
import { scan, type ScanResult } from '@/tooling/scan'
|
|
22
22
|
import { recordToolingChain } from '@/tooling/stamp'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
intro,
|
|
25
|
+
isNonInteractive,
|
|
26
|
+
logAdd,
|
|
27
|
+
logInfo,
|
|
28
|
+
logStep,
|
|
29
|
+
logWarn,
|
|
30
|
+
outro,
|
|
31
|
+
select,
|
|
32
|
+
} from '@/ui'
|
|
24
33
|
|
|
25
34
|
const GREEN = '\x1b[0;32m'
|
|
26
35
|
const NC = '\x1b[0m'
|
|
@@ -30,6 +39,8 @@ const PASS_THROUGH_VERBS = ['ref', 'create', 'verify'] as const
|
|
|
30
39
|
interface SyncOptions {
|
|
31
40
|
readonly ref?: boolean
|
|
32
41
|
readonly skip?: string
|
|
42
|
+
readonly check?: boolean
|
|
43
|
+
readonly write?: boolean
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
interface InjectOptions {
|
|
@@ -66,6 +77,24 @@ export function register(program: Command): void {
|
|
|
66
77
|
.helpOption('-h, --help', 'Show this help message')
|
|
67
78
|
.option('--no-ref', 'Skip dropping reference docs')
|
|
68
79
|
.option('--skip <stack>', 'Drop a layer from the extends chain')
|
|
80
|
+
.option('--check', 'Report what would change and write nothing')
|
|
81
|
+
.option('--write', 'Apply every change without prompting')
|
|
82
|
+
.addHelpText(
|
|
83
|
+
'after',
|
|
84
|
+
[
|
|
85
|
+
'',
|
|
86
|
+
'Examples:',
|
|
87
|
+
' aitk tooling sync base',
|
|
88
|
+
' aitk tooling sync base --check',
|
|
89
|
+
' AITK_NON_INTERACTIVE=1 aitk tooling sync base --write',
|
|
90
|
+
'',
|
|
91
|
+
'To gate CI on tooling drift, run headlessly with neither flag. That',
|
|
92
|
+
'exits 1 when a file would be replaced and 0 when none would, which is',
|
|
93
|
+
'what `aitk sync --check --exit-code` spells with a flag. Pass --check',
|
|
94
|
+
'to report the same list and always exit 0.',
|
|
95
|
+
'',
|
|
96
|
+
].join('\n'),
|
|
97
|
+
)
|
|
69
98
|
.action(
|
|
70
99
|
async (stack: string | undefined, target: string, opts: SyncOptions) => {
|
|
71
100
|
process.exitCode = await runSync(stack, target, opts)
|
|
@@ -193,6 +222,12 @@ async function runSync(
|
|
|
193
222
|
): Promise<number> {
|
|
194
223
|
intro('aitk tooling sync')
|
|
195
224
|
|
|
225
|
+
if (opts.check === true && opts.write === true) {
|
|
226
|
+
logWarn('Pass --check or --write, not both.')
|
|
227
|
+
outro()
|
|
228
|
+
return 1
|
|
229
|
+
}
|
|
230
|
+
|
|
196
231
|
const selected = stack ?? (await promptForStack())
|
|
197
232
|
if (selected === undefined) {
|
|
198
233
|
logWarn('No tooling stacks found')
|
|
@@ -220,26 +255,33 @@ async function runSync(
|
|
|
220
255
|
|
|
221
256
|
report(result, includeReferences)
|
|
222
257
|
|
|
258
|
+
const mode = resolveWriteMode(opts)
|
|
259
|
+
|
|
223
260
|
if (result.totalChanges === 0) {
|
|
224
|
-
|
|
261
|
+
// The stamp is a write like any other, so a run with no authority to write
|
|
262
|
+
// leaves the target's record alone rather than claiming a sync it never
|
|
263
|
+
// performed.
|
|
264
|
+
if (mode === 'apply' || mode === 'prompt') {
|
|
265
|
+
await stampChain(prepared.chain, prepared.target)
|
|
266
|
+
}
|
|
225
267
|
outro()
|
|
226
268
|
process.stderr.write(`${GREEN}✓ Everything up to date${NC}\n`)
|
|
227
269
|
return 0
|
|
228
270
|
}
|
|
229
271
|
|
|
230
|
-
const
|
|
231
|
-
message: `Apply ${result.totalChanges} changes (${summarize(result)})?`,
|
|
232
|
-
options: [
|
|
233
|
-
{ value: true, label: 'Apply all' },
|
|
234
|
-
{ value: false, label: 'Cancel' },
|
|
235
|
-
],
|
|
236
|
-
nonInteractiveDefault: true,
|
|
237
|
-
})
|
|
272
|
+
const decision = await decideApply(result, mode)
|
|
238
273
|
|
|
239
|
-
if (
|
|
240
|
-
logWarn(
|
|
274
|
+
if (decision !== 'apply') {
|
|
275
|
+
logWarn(
|
|
276
|
+
decision === 'cancelled'
|
|
277
|
+
? 'Sync cancelled'
|
|
278
|
+
: `Reported ${result.totalChanges} changes (${summarize(result)}). Nothing written.`,
|
|
279
|
+
)
|
|
280
|
+
if (decision === 'unauthorized') {
|
|
281
|
+
logInfo('Re-run with --write to apply them, or --check to silence this.')
|
|
282
|
+
}
|
|
241
283
|
outro()
|
|
242
|
-
return 0
|
|
284
|
+
return decision === 'unauthorized' ? 1 : 0
|
|
243
285
|
}
|
|
244
286
|
|
|
245
287
|
if (result.configs.some((entry) => entry.state !== 'matching')) {
|
|
@@ -265,6 +307,46 @@ async function runSync(
|
|
|
265
307
|
return 0
|
|
266
308
|
}
|
|
267
309
|
|
|
310
|
+
type WriteMode = 'apply' | 'prompt' | 'report' | 'unauthorized'
|
|
311
|
+
|
|
312
|
+
type ApplyDecision = 'apply' | 'cancelled' | 'reported' | 'unauthorized'
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Golden configs carry the CI workflow, the end-to-end harness, and the shell
|
|
316
|
+
* scripts under `scripts/`, so an overwrite reaches work no one would consent
|
|
317
|
+
* to losing. A headless caller therefore has no authority to write without
|
|
318
|
+
* `--write`, since a confirm prompt carrying `nonInteractiveDefault` resolves
|
|
319
|
+
* to its first option and would read silence as consent.
|
|
320
|
+
*/
|
|
321
|
+
function resolveWriteMode(opts: SyncOptions): WriteMode {
|
|
322
|
+
if (opts.check === true) return 'report'
|
|
323
|
+
if (opts.write === true) return 'apply'
|
|
324
|
+
return isNonInteractive() ? 'unauthorized' : 'prompt'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Exit 1 follows `unauthorized`, so a caller that forgets `--write` fails
|
|
329
|
+
* rather than reporting a sync it never performed.
|
|
330
|
+
*/
|
|
331
|
+
async function decideApply(
|
|
332
|
+
result: ScanResult,
|
|
333
|
+
mode: WriteMode,
|
|
334
|
+
): Promise<ApplyDecision> {
|
|
335
|
+
if (mode === 'report') return 'reported'
|
|
336
|
+
if (mode === 'apply') return 'apply'
|
|
337
|
+
if (mode === 'unauthorized') return 'unauthorized'
|
|
338
|
+
|
|
339
|
+
const shouldApply = await select({
|
|
340
|
+
message: `Apply ${result.totalChanges} changes (${summarize(result)})?`,
|
|
341
|
+
options: [
|
|
342
|
+
{ value: true, label: 'Apply all' },
|
|
343
|
+
{ value: false, label: 'Cancel' },
|
|
344
|
+
],
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
return shouldApply ? 'apply' : 'cancelled'
|
|
348
|
+
}
|
|
349
|
+
|
|
268
350
|
/**
|
|
269
351
|
* Writes the chain after the copies land, so a partial apply that throws leaves
|
|
270
352
|
* the previous record rather than a claim the target does not meet.
|
package/src/init/steps.ts
CHANGED
package/standards/memory.md
CHANGED
|
@@ -46,7 +46,7 @@ Four types, and the type decides both the filename prefix and the body shape.
|
|
|
46
46
|
|
|
47
47
|
Hold a feedback entry to the higher bar. A first-occurrence slip is noise, and a folder that records every one of them buries the rules that were paid for.
|
|
48
48
|
|
|
49
|
-
Write no entry for a fact another surface already owns. A fact about a domain belongs in that domain's entry, where sessions working the domain already read it, and the same fact in memory sits in a folder nothing opens. Memory keeps the residue, which
|
|
49
|
+
Write no entry for a fact another surface already owns. A fact about a domain belongs in that domain's entry, where sessions working the domain already read it, and the same fact in memory sits in a folder nothing opens. Memory keeps the residue, which is whatever the ownership test leaves rather than any one type.
|
|
50
50
|
|
|
51
51
|
## Filename
|
|
52
52
|
|
|
@@ -5,9 +5,12 @@ import path from 'path'
|
|
|
5
5
|
|
|
6
6
|
const portOffset = Number(process.env.WORKTREE_PORT_OFFSET) || 0
|
|
7
7
|
|
|
8
|
+
// Reserved TLD, so an unset ASTRO_SITE is visible in canonical URLs and never resolves.
|
|
9
|
+
const site = process.env.ASTRO_SITE || 'https://set-astro-site.invalid'
|
|
10
|
+
|
|
8
11
|
export default defineConfig({
|
|
9
12
|
integrations: [react()],
|
|
10
|
-
site
|
|
13
|
+
site,
|
|
11
14
|
server: {
|
|
12
15
|
port: 4321 + portOffset,
|
|
13
16
|
},
|
|
@@ -10,8 +10,8 @@ The astro stack covers Astro + TypeScript projects: content sites, marketing sit
|
|
|
10
10
|
|
|
11
11
|
1. Scaffold with `bunx create-astro@latest`. Choose `TypeScript: Strict`. Skip git init and install.
|
|
12
12
|
2. Add React integration: `bunx astro add react`. Do not use `astro add tailwind`. That command installs the v3 integration. Tailwind v4 arrives via the web manifest.
|
|
13
|
-
3. Install web tooling: `aitk tooling sync web
|
|
14
|
-
4. Install astro adapter: `aitk tooling sync astro
|
|
13
|
+
3. Install web tooling: `aitk tooling sync web . --write`
|
|
14
|
+
4. Install astro adapter: `aitk tooling sync astro . --write`
|
|
15
15
|
5. Extend the `ci` and `development` context entries under `.claude/context/` per the web reference's extend sections plus the astro rows below.
|
|
16
16
|
6. Run `bun run lint:fix` then `bun run check`.
|
|
17
17
|
|
|
@@ -8,7 +8,7 @@ A repository with several language roots layers this stack once at the root and
|
|
|
8
8
|
|
|
9
9
|
## What ships as golden configs
|
|
10
10
|
|
|
11
|
-
Golden config files live in `tooling/base/configs/` and are copied into the target on `aitk tooling sync base
|
|
11
|
+
Golden config files live in `tooling/base/configs/` and are copied into the target on `aitk tooling sync base . --write`, replacing whatever sits at those paths. They are the source of truth. The reference covers rationale and tradeoffs. Configs show the concrete setup.
|
|
12
12
|
|
|
13
13
|
- `.prettierrc`: `semi: false`, `singleQuote: true`, plus a parser override per non-standard extension (`.mdx` to `markdown`).
|
|
14
14
|
- `.shellcheckrc`: `external-sources=true`. Required for shellcheck to follow `source` directives.
|
|
@@ -11,9 +11,9 @@ Configs ship as sidecar files (`ruff.toml`, `mypy.ini`, `pytest.ini`, `.coverage
|
|
|
11
11
|
## Scaffold checklist
|
|
12
12
|
|
|
13
13
|
1. Scaffold with `uv init --app <name>`. This creates `pyproject.toml`, `.python-version` pinned to 3.14, `src/<name>/`, and a starter `main.py`. `uv init` defaults `requires-python` to `>=3.14`, which matches the `.python-version` pin this stack ships.
|
|
14
|
-
2. Seed `package.json` so the base layer's bun-side tools (husky, prettier, cspell, commitlint) have a target to install into: `bun init -y`. Without this
|
|
15
|
-
3. Install base tooling: `aitk tooling sync base
|
|
16
|
-
4. Install python tooling: `aitk tooling sync python
|
|
14
|
+
2. Seed `package.json` so the base layer's bun-side tools (husky, prettier, cspell, commitlint) have a target to install into: `bun init -y`. Without this the sync drops base configs but skips the dep install, since `resolve_missing_deps` short-circuits when `package.json` is absent.
|
|
15
|
+
3. Install base tooling: `aitk tooling sync base . --write`
|
|
16
|
+
4. Install python tooling: `aitk tooling sync python . --write`
|
|
17
17
|
5. Install Python tooling deps: `uv add --dev ruff mypy pytest pytest-cov`. v1 of this stack does not declare these in `[dependencies.dev]` because manifest injection hardcodes `bun add -D`, which can not install Python packages. Until the injector branches on `runtime`, this step is manual.
|
|
18
18
|
6. Sync the lockfile and create the venv: `uv sync`.
|
|
19
19
|
7. Annotate `main()` in the scaffold-generated `main.py` with `-> None`. `uv init --app` ships an unannotated `main()` that fails strict mypy on the first run.
|
|
@@ -9,8 +9,8 @@ The vite-react stack covers Vite + React + TypeScript projects: web apps and Chr
|
|
|
9
9
|
## Scaffold checklist
|
|
10
10
|
|
|
11
11
|
1. Scaffold with `bunx create-vite@latest <name> --template react-ts` (web apps) or `bunx create-crxjs@latest` (Chrome extensions).
|
|
12
|
-
2. Install base and web tooling: `aitk tooling sync web
|
|
13
|
-
3. Install vite-react deps and configs: `aitk tooling sync vite-react
|
|
12
|
+
2. Install base and web tooling: `aitk tooling sync web . --write`
|
|
13
|
+
3. Install vite-react deps and configs: `aitk tooling sync vite-react . --write`
|
|
14
14
|
4. Extend the `ci` and `development` context entries under `.claude/context/` per the web reference's extend sections plus the vite-react rows below.
|
|
15
15
|
5. Run `bun run lint:fix` then `bun run check`.
|
|
16
16
|
|
package/tooling/web/reference.md
CHANGED
|
@@ -8,7 +8,7 @@ The web layer covers web-universal tooling shared across Vite + React, Astro, an
|
|
|
8
8
|
|
|
9
9
|
## What ships as golden configs
|
|
10
10
|
|
|
11
|
-
Golden config files live in `tooling/web/configs/` and are copied into the target on `aitk tooling sync web
|
|
11
|
+
Golden config files live in `tooling/web/configs/` and are copied into the target on `aitk tooling sync web . --write`, replacing whatever sits at those paths. They are the source of truth. The reference covers rationale and tradeoffs. Configs show the concrete setup.
|
|
12
12
|
|
|
13
13
|
- `eslint.config.js`: flat config with `@eslint/js`, `typescript-eslint`, React hooks, import sort, check-file, vitest rules scoped to test files, `eslint-config-prettier` last.
|
|
14
14
|
- `src/test/setup.ts`: `@testing-library/jest-dom` import, `cleanup` after each test.
|