@erclx/aitk 0.55.0 → 0.56.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "0.55.0",
4
+ "version": "0.56.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -9,10 +9,16 @@ description: Why the root-to-.claude relocation ships as git mv commands the use
9
9
 
10
10
  Without this skill, a project whose rules cite `.claude/standards/` while the files sit at the root gets fixed by hand, and the hand fix loses what version control was holding. A plain `mv` breaks the rename chain, so every relocated standard reads as a delete beside an unrelated add and its history stops at the move.
11
11
 
12
+ A fourth failure comes before those three, from deciding what is unmigrated by listing the folder. A root `standards/` can hold the project's own docs and nothing the toolkit ever installed, and a listing cannot tell the two apart, so the skill proposes moving project files under `.claude/` where a sync walks them. The drift report already answers this, counting only files whose basename the toolkit ships, and it is the same report `toolkit-operator` reads to route here.
13
+
14
+ Taking detection from a command opens a failure the listing never had. A CLI predating the field exits zero with a well-formed report that never mentions it, so a skill treating an absent key as an empty answer tells a project whose every domain sits at the root that it has nothing to relocate. That is the population this skill exists for, and a silent false negative there costs more than the unfiltered count reading the folder would have produced.
15
+
12
16
  Three failures follow from acting without looking first. A move onto an existing `.claude/standards/` copy overwrites the files already installed there, which is the case the relocation was supposed to be unnecessary for. A `git mv` on a dirty tree lands the relocation in the same commit as unrelated work, so neither can be reviewed or reverted alone. And a session that rewrites every inbound reference spends its effort on toolkit-owned rules and skills, which the next sync overwrites, while the author-owned lines that actually break go unmentioned.
13
17
 
14
18
  ## Must
15
19
 
20
+ - Take the set of domains to relocate from `aitk sync --check --json`, and report its filtered count rather than a folder listing
21
+ - Keep the listing as the fallback for a target where `aitk` is absent, the command fails, or the report carries no `unmigrated` key, and say that its counts are unfiltered
16
22
  - Detect an existing copy under `.claude/` and skip that folder's move rather than merging into it
17
23
  - Read the working tree state and require it clean before the moves, since the relocation has to be revertible on its own
18
24
  - Propose `git mv` so history follows each file
@@ -10,21 +10,38 @@ description: Proposes `git mv` commands to relocate a target project's root `sta
10
10
  - If neither `standards/` nor `snippets/` exists at `pwd`, stop: `❌ No root standards/ or snippets/ to relocate.`
11
11
  - If `pwd` is not a git work tree, stop: `❌ Not a git repository. git mv needs version control.`
12
12
 
13
- ## Step 1: discover
13
+ ## Step 1: read the report
14
14
 
15
- Run these in parallel from the project root:
15
+ Run `aitk sync --check . --json` from the project root. Its `unmigrated` array is the detection. Each entry carries `domain`, `rootPath`, `installPath`, and `files`, and a domain appears only when the root folder holds a file the toolkit ships and nothing sits at the install path. Report `files` as the count, since it excludes files the project wrote into the same folder.
16
16
 
17
- - `ls standards/*.md 2>/dev/null`: root standards present
18
- - `ls snippets/ 2>/dev/null`: root snippets present
19
- - `ls .claude/standards/ 2>/dev/null`: detect an existing target copy
20
- - `ls .claude/snippets/ 2>/dev/null`: detect an existing target copy
17
+ Run these beside it, in parallel. The report answers which domains to move and neither of these is derivable from it:
18
+
19
+ - `ls .claude/standards/ 2>/dev/null`: separate a domain already relocated from one never installed, which the report cannot, since both are absent from `unmigrated`
20
+ - `ls .claude/snippets/ 2>/dev/null`: the same, for the other domain
21
21
  - `git status --short 2>/dev/null`: confirm a clean tree before proposing moves
22
22
 
23
+ Keep the two listings as separate commands. `ls` labels its output with a `dir:` header only when more than one operand succeeds, so a combined call with one directory present prints that directory's filenames bare while the redirect swallows the other's failure, and the result reads as whichever directory was expected.
24
+
25
+ ### When the report is unavailable
26
+
27
+ Fall back to `ls standards/*.md` and `ls snippets/` on any of three conditions. Say in the output that the counts are unfiltered, because a root folder can hold project-authored files the report would have excluded and the fallback counts every one of them.
28
+
29
+ - `aitk` is not on `PATH`
30
+ - The command exits non-zero
31
+ - The report parses and carries no `unmigrated` key at all
32
+
33
+ The third is the one that decides whether this skill works for the projects it exists for. `unmigrated` reached a release in `0.46.0`, and a CLI older than that exits zero with a well-formed report that never mentions the field. Reading an absent key as an empty list sends the run down the nothing-to-relocate branch and reports a clean layout to a project whose every domain sits at the root, which is a silent false negative where the unfiltered count is a visible imprecision.
34
+
35
+ An absent key and an empty array are different states, so test for the key rather than for emptiness. A current CLI reporting `"unmigrated": []` has looked and found nothing, and falling back there would trade a correct answer for a listing that proposes moving whatever the folder happens to hold.
36
+
37
+ Do not fall back on `historyUnavailable`. That field reports failed attribution on a domain or on `seeds`, and `unmigrated` is a filesystem read carrying no attribution of its own, so a report that cannot date a file still detects the layout correctly.
38
+
23
39
  ## Step 2: check conflicts
24
40
 
25
41
  - If `.claude/standards/` already holds `.md` files, mark standards as "already relocated" and skip its move.
26
42
  - If `.claude/snippets/` already holds `.md` files, mark snippets as "already relocated" and skip its move.
27
43
  - If `git status` is not clean, add a TODO line telling the user to commit or stash first. `git mv` on a dirty tree mixes the move with unrelated changes.
44
+ - If a root folder holds files and no domain names it in a report that carried the key, propose nothing for it. The content is the project's own, and moving it under `.claude/` puts project files where a sync walks. This reads a present key alone. A report with no `unmigrated` key never reaches here, since Step 1 sends it to the fallback.
28
45
 
29
46
  ## Step 3: find author-owned inbound references
30
47
 
@@ -70,6 +87,9 @@ aitk gov sync .
70
87
  Re-syncing reinstalls toolkit-owned rules and standards at the new path. Author-owned references above need a manual one-line fix.
71
88
  ```
72
89
 
73
- If both folders are already relocated, output: `✅ standards/ and snippets/ already live under .claude/. Nothing to relocate.`
90
+ Two states produce no proposal at all, and each gets its own line rather than an empty block:
91
+
92
+ - Both folders already live under `.claude/`: `✅ standards/ and snippets/ already live under .claude/. Nothing to relocate.`
93
+ - A root folder exists and no domain names it in `unmigrated`: `✅ <folder>/ holds no file the toolkit installed. Nothing to relocate.` Name every such folder. The guard passed because the folder is there, so a session that printed nothing would leave the user reading silence as a failed run rather than as the answer.
74
94
 
75
95
  Do not run the `git mv` commands. Do not edit `CLAUDE.md`, rule files, or docs. The user runs the commands and fixes author-owned references after reviewing.
@@ -164,6 +164,13 @@ to, with nothing at the path the current one reads. It carries `rootPath`,
164
164
  root reports zero entries for that domain and reads as clean, which is the most
165
165
  misleading state the report can produce. Route it to `migration-standards`.
166
166
 
167
+ That skill reads this field rather than listing the folder itself, so the entry
168
+ is the detection on both sides of the handoff and the two cannot disagree. The
169
+ count is what makes the difference visible: it counts only files whose basename
170
+ the toolkit ships, so a root folder holding the project's own documents beside
171
+ the installed ones reports the installed subset, where a listing reports every
172
+ file and proposes relocating the lot.
173
+
167
174
  `unmigrated` counts toward `--exit-code`, since running the relocation closes it.
168
175
  `superseded` and every seed state are excluded, for the reason `orphaned` already
169
176
  is: only the user can move content they wrote, so failing a job on it leaves the
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.55.0",
4
+ "version": "0.56.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -78,6 +78,27 @@ append_from_fixtures() {
78
78
  done < <(list_fixture_files "$source_dir")
79
79
  }
80
80
 
81
+ # Stages the first `count` markdown files the toolkit ships for a domain into
82
+ # `dest`, flattening any source subfolder. Source rather than fixture tree,
83
+ # because an arm modelling a real install wants the files a target actually
84
+ # received and a copy under `fixtures/` would drift from them silently.
85
+ #
86
+ # The flattening is load-bearing rather than incidental. Both `detectUnmigrated`
87
+ # and the sync engine match a target file to its source by basename against the
88
+ # flat domain root, so a file lifted out of a source subfolder such as `bundled/`
89
+ # has no flat sibling and reads as project-authored. An arm staging one claims a
90
+ # drift or an unmigrated domain it did not stage.
91
+ stage_toolkit_markdown() {
92
+ local src="$1"
93
+ local dest="$2"
94
+ local count="$3"
95
+
96
+ mkdir -p "$dest"
97
+ while IFS= read -r file; do
98
+ cp "$file" "$dest/$(basename "$file")"
99
+ done < <(find "$src" -maxdepth 1 -type f -name "*.md" ! -name "index.md" | sort | head -n "$count")
100
+ }
101
+
81
102
  # Stages one step of a scenario arm into the sandbox working directory.
82
103
  # Scenarios call this once per step so their own git operations stay between
83
104
  # the steps, where they are visible.