@slats/claude-assets-sync 0.1.4 → 0.3.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 (239) hide show
  1. package/README.md +89 -581
  2. package/bin/inject-claude-settings.mjs +4 -0
  3. package/dist/claude-hashes.json +20 -0
  4. package/dist/commands/index.d.ts +1 -112
  5. package/dist/commands/runCli/index.d.ts +2 -0
  6. package/dist/commands/runCli/runCli.cjs +53 -0
  7. package/dist/commands/runCli/runCli.d.ts +14 -0
  8. package/dist/commands/runCli/runCli.mjs +51 -0
  9. package/dist/commands/runCli/type.d.ts +19 -0
  10. package/dist/commands/runCli/utils/classifyTarget.cjs +48 -0
  11. package/dist/commands/runCli/utils/classifyTarget.d.ts +19 -0
  12. package/dist/commands/runCli/utils/classifyTarget.mjs +46 -0
  13. package/dist/commands/runCli/utils/injectOne.cjs +47 -0
  14. package/dist/commands/runCli/utils/injectOne.d.ts +3 -0
  15. package/dist/commands/runCli/utils/injectOne.mjs +45 -0
  16. package/dist/commands/runCli/utils/resolvePackage.cjs +77 -0
  17. package/dist/commands/runCli/utils/resolvePackage.d.ts +16 -0
  18. package/dist/commands/runCli/utils/resolvePackage.mjs +74 -0
  19. package/dist/commands/runCli/utils/resolveScopeAlias.cjs +69 -0
  20. package/dist/commands/runCli/utils/resolveScopeAlias.d.ts +2 -0
  21. package/dist/commands/runCli/utils/resolveScopeAlias.mjs +67 -0
  22. package/dist/commands/runCli/utils/resolveScopeFlag.cjs +28 -0
  23. package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +2 -0
  24. package/dist/commands/runCli/utils/resolveScopeFlag.mjs +26 -0
  25. package/dist/commands/runCli/utils/resolveTargets.cjs +40 -0
  26. package/dist/commands/runCli/utils/resolveTargets.d.ts +15 -0
  27. package/dist/commands/runCli/utils/resolveTargets.mjs +38 -0
  28. package/dist/commands/runCli/utils/runInject.cjs +52 -0
  29. package/dist/commands/runCli/utils/runInject.d.ts +3 -0
  30. package/dist/commands/runCli/utils/runInject.mjs +50 -0
  31. package/dist/core/buildPlan/buildPlan.cjs +42 -0
  32. package/dist/core/buildPlan/buildPlan.d.ts +2 -0
  33. package/dist/core/buildPlan/buildPlan.mjs +40 -0
  34. package/dist/core/buildPlan/index.d.ts +2 -0
  35. package/dist/core/buildPlan/type.d.ts +32 -0
  36. package/dist/core/buildPlan/utils/toPosix.cjs +9 -0
  37. package/dist/core/buildPlan/utils/toPosix.d.ts +1 -0
  38. package/dist/core/buildPlan/utils/toPosix.mjs +7 -0
  39. package/dist/core/buildPlan/utils/walkFiles.cjs +25 -0
  40. package/dist/core/buildPlan/utils/walkFiles.d.ts +1 -0
  41. package/dist/core/buildPlan/utils/walkFiles.mjs +23 -0
  42. package/dist/core/hash/hash.cjs +30 -0
  43. package/dist/core/hash/hash.d.ts +4 -0
  44. package/dist/core/hash/hash.mjs +26 -0
  45. package/dist/core/hash/index.d.ts +1 -0
  46. package/dist/core/hashManifest/hashManifest.cjs +27 -0
  47. package/dist/core/hashManifest/hashManifest.d.ts +17 -0
  48. package/dist/core/hashManifest/hashManifest.mjs +23 -0
  49. package/dist/core/hashManifest/index.d.ts +1 -0
  50. package/dist/core/index.d.ts +5 -0
  51. package/dist/core/injectDocs/index.d.ts +2 -0
  52. package/dist/core/injectDocs/injectDocs.cjs +43 -0
  53. package/dist/core/injectDocs/injectDocs.d.ts +2 -0
  54. package/dist/core/injectDocs/injectDocs.mjs +41 -0
  55. package/dist/core/injectDocs/type.d.ts +30 -0
  56. package/dist/core/injectDocs/utils/applyAction.cjs +21 -0
  57. package/dist/core/injectDocs/utils/applyAction.d.ts +2 -0
  58. package/dist/core/injectDocs/utils/applyAction.mjs +19 -0
  59. package/dist/core/injectDocs/utils/emitCiForceList.cjs +10 -0
  60. package/dist/core/injectDocs/utils/emitCiForceList.d.ts +2 -0
  61. package/dist/core/injectDocs/utils/emitCiForceList.mjs +8 -0
  62. package/dist/core/injectDocs/utils/printPlan.cjs +20 -0
  63. package/dist/core/injectDocs/utils/printPlan.d.ts +2 -0
  64. package/dist/core/injectDocs/utils/printPlan.mjs +18 -0
  65. package/dist/core/injectDocs/utils/summarize.cjs +27 -0
  66. package/dist/core/injectDocs/utils/summarize.d.ts +3 -0
  67. package/dist/core/injectDocs/utils/summarize.mjs +25 -0
  68. package/dist/core/scope/index.d.ts +1 -0
  69. package/dist/core/scope/scope.cjs +46 -0
  70. package/dist/core/scope/scope.d.ts +16 -0
  71. package/dist/core/scope/scope.mjs +41 -0
  72. package/dist/core/scope/utils/isDirectory.cjs +14 -0
  73. package/dist/core/scope/utils/isDirectory.d.ts +1 -0
  74. package/dist/core/scope/utils/isDirectory.mjs +12 -0
  75. package/dist/index.cjs +15 -9
  76. package/dist/index.d.ts +3 -5
  77. package/dist/index.mjs +7 -3
  78. package/dist/prompts/confirmForce.cjs +27 -0
  79. package/dist/prompts/confirmForce.d.ts +1 -0
  80. package/dist/prompts/confirmForce.mjs +25 -0
  81. package/dist/prompts/index.d.ts +2 -0
  82. package/dist/prompts/selectScope.cjs +30 -0
  83. package/dist/prompts/selectScope.d.ts +2 -0
  84. package/dist/prompts/selectScope.mjs +28 -0
  85. package/dist/utils/heartbeat.cjs +25 -0
  86. package/dist/utils/heartbeat.d.ts +16 -0
  87. package/dist/utils/heartbeat.mjs +23 -0
  88. package/dist/utils/logger.cjs +7 -0
  89. package/dist/utils/logger.d.ts +8 -0
  90. package/dist/utils/logger.mjs +7 -0
  91. package/dist/utils/types.d.ts +1 -252
  92. package/dist/utils/version.cjs +2 -14
  93. package/dist/utils/version.d.ts +3 -53
  94. package/dist/utils/version.mjs +2 -13
  95. package/docs/bundle-size-decision.md +36 -0
  96. package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +159 -0
  97. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +86 -0
  98. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md +54 -0
  99. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +122 -0
  100. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +145 -0
  101. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +37 -0
  102. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/smoke-tests.md +111 -0
  103. package/docs/consumer-integration.md +94 -0
  104. package/package.json +24 -16
  105. package/scripts/build-hashes.mjs +30 -0
  106. package/scripts/buildHashes.d.mts +15 -0
  107. package/scripts/buildHashes.mjs +82 -0
  108. package/scripts/claude-build-hashes.mjs +42 -0
  109. package/scripts/inject-version.js +112 -0
  110. package/dist/cli.cjs +0 -8
  111. package/dist/cli.d.ts +0 -1
  112. package/dist/cli.mjs +0 -7
  113. package/dist/commands/add.cjs +0 -80
  114. package/dist/commands/add.d.ts +0 -8
  115. package/dist/commands/add.mjs +0 -78
  116. package/dist/commands/list.cjs +0 -94
  117. package/dist/commands/list.d.ts +0 -15
  118. package/dist/commands/list.mjs +0 -91
  119. package/dist/commands/migrate.cjs +0 -9
  120. package/dist/commands/migrate.d.ts +0 -6
  121. package/dist/commands/migrate.mjs +0 -7
  122. package/dist/commands/remove.cjs +0 -127
  123. package/dist/commands/remove.d.ts +0 -6
  124. package/dist/commands/remove.mjs +0 -105
  125. package/dist/commands/status.cjs +0 -193
  126. package/dist/commands/status.d.ts +0 -6
  127. package/dist/commands/status.mjs +0 -171
  128. package/dist/commands/sync.cjs +0 -28
  129. package/dist/commands/sync.d.ts +0 -6
  130. package/dist/commands/sync.mjs +0 -26
  131. package/dist/commands/types.d.ts +0 -89
  132. package/dist/commands/update.cjs +0 -209
  133. package/dist/commands/update.d.ts +0 -29
  134. package/dist/commands/update.mjs +0 -206
  135. package/dist/components/add/AddCommand.cjs +0 -103
  136. package/dist/components/add/AddCommand.d.ts +0 -14
  137. package/dist/components/add/AddCommand.mjs +0 -101
  138. package/dist/components/add/BulkAddView.cjs +0 -165
  139. package/dist/components/add/BulkAddView.d.ts +0 -11
  140. package/dist/components/add/BulkAddView.mjs +0 -163
  141. package/dist/components/add/index.d.ts +0 -2
  142. package/dist/components/index.d.ts +0 -2
  143. package/dist/components/list/EditableTreeItem.d.ts +0 -13
  144. package/dist/components/list/ListCommand.cjs +0 -651
  145. package/dist/components/list/ListCommand.d.ts +0 -5
  146. package/dist/components/list/ListCommand.mjs +0 -649
  147. package/dist/components/list/SyncedPackageTree.d.ts +0 -14
  148. package/dist/components/list/index.d.ts +0 -10
  149. package/dist/components/list/types.d.ts +0 -14
  150. package/dist/components/primitives/Box.d.ts +0 -4
  151. package/dist/components/primitives/Spinner.d.ts +0 -6
  152. package/dist/components/primitives/Text.d.ts +0 -4
  153. package/dist/components/primitives/index.d.ts +0 -3
  154. package/dist/components/remove/RemoveConfirm.cjs +0 -18
  155. package/dist/components/remove/RemoveConfirm.d.ts +0 -11
  156. package/dist/components/remove/RemoveConfirm.mjs +0 -16
  157. package/dist/components/shared/Confirm.cjs +0 -30
  158. package/dist/components/shared/Confirm.d.ts +0 -8
  159. package/dist/components/shared/Confirm.mjs +0 -28
  160. package/dist/components/shared/MenuItem.cjs +0 -18
  161. package/dist/components/shared/MenuItem.d.ts +0 -7
  162. package/dist/components/shared/MenuItem.mjs +0 -16
  163. package/dist/components/shared/ProgressBar.d.ts +0 -7
  164. package/dist/components/shared/StepRunner.cjs +0 -58
  165. package/dist/components/shared/StepRunner.d.ts +0 -15
  166. package/dist/components/shared/StepRunner.mjs +0 -56
  167. package/dist/components/shared/Table.cjs +0 -19
  168. package/dist/components/shared/Table.d.ts +0 -8
  169. package/dist/components/shared/Table.mjs +0 -17
  170. package/dist/components/shared/index.d.ts +0 -6
  171. package/dist/components/status/PackageStatusCard.d.ts +0 -10
  172. package/dist/components/status/StatusDisplay.cjs +0 -26
  173. package/dist/components/status/StatusDisplay.d.ts +0 -23
  174. package/dist/components/status/StatusDisplay.mjs +0 -24
  175. package/dist/components/status/StatusTreeNode.cjs +0 -40
  176. package/dist/components/status/StatusTreeNode.d.ts +0 -15
  177. package/dist/components/status/StatusTreeNode.mjs +0 -38
  178. package/dist/components/status/index.d.ts +0 -6
  179. package/dist/components/tree/AssetTreeNode.cjs +0 -54
  180. package/dist/components/tree/AssetTreeNode.d.ts +0 -12
  181. package/dist/components/tree/AssetTreeNode.mjs +0 -52
  182. package/dist/components/tree/TreeSelect.cjs +0 -129
  183. package/dist/components/tree/TreeSelect.d.ts +0 -12
  184. package/dist/components/tree/TreeSelect.mjs +0 -127
  185. package/dist/components/tree/index.d.ts +0 -4
  186. package/dist/core/assetStructure.cjs +0 -30
  187. package/dist/core/assetStructure.d.ts +0 -36
  188. package/dist/core/assetStructure.mjs +0 -27
  189. package/dist/core/cli.cjs +0 -106
  190. package/dist/core/cli.d.ts +0 -9
  191. package/dist/core/cli.mjs +0 -103
  192. package/dist/core/constants.cjs +0 -28
  193. package/dist/core/constants.d.ts +0 -94
  194. package/dist/core/constants.mjs +0 -21
  195. package/dist/core/filesystem.cjs +0 -98
  196. package/dist/core/filesystem.d.ts +0 -94
  197. package/dist/core/filesystem.mjs +0 -88
  198. package/dist/core/github.cjs +0 -115
  199. package/dist/core/github.d.ts +0 -61
  200. package/dist/core/github.mjs +0 -107
  201. package/dist/core/io.cjs +0 -46
  202. package/dist/core/io.d.ts +0 -40
  203. package/dist/core/io.mjs +0 -39
  204. package/dist/core/listOperations.cjs +0 -228
  205. package/dist/core/listOperations.d.ts +0 -43
  206. package/dist/core/listOperations.mjs +0 -205
  207. package/dist/core/localSource.cjs +0 -126
  208. package/dist/core/localSource.d.ts +0 -33
  209. package/dist/core/localSource.mjs +0 -120
  210. package/dist/core/migration.cjs +0 -201
  211. package/dist/core/migration.d.ts +0 -57
  212. package/dist/core/migration.mjs +0 -198
  213. package/dist/core/packageScanner.cjs +0 -360
  214. package/dist/core/packageScanner.d.ts +0 -22
  215. package/dist/core/packageScanner.mjs +0 -356
  216. package/dist/core/sync.cjs +0 -400
  217. package/dist/core/sync.d.ts +0 -21
  218. package/dist/core/sync.mjs +0 -397
  219. package/dist/core/syncMeta.cjs +0 -242
  220. package/dist/core/syncMeta.d.ts +0 -75
  221. package/dist/core/syncMeta.mjs +0 -229
  222. package/dist/utils/dependencies.cjs +0 -57
  223. package/dist/utils/dependencies.d.ts +0 -10
  224. package/dist/utils/dependencies.mjs +0 -34
  225. package/dist/utils/nameTransform.cjs +0 -13
  226. package/dist/utils/nameTransform.d.ts +0 -65
  227. package/dist/utils/nameTransform.mjs +0 -11
  228. package/dist/utils/package.cjs +0 -170
  229. package/dist/utils/package.d.ts +0 -105
  230. package/dist/utils/package.mjs +0 -157
  231. package/dist/utils/packageName.cjs +0 -24
  232. package/dist/utils/packageName.d.ts +0 -32
  233. package/dist/utils/packageName.mjs +0 -21
  234. package/dist/utils/paths.cjs +0 -18
  235. package/dist/utils/paths.d.ts +0 -55
  236. package/dist/utils/paths.mjs +0 -15
  237. package/dist/version.cjs +0 -5
  238. package/dist/version.d.ts +0 -5
  239. package/dist/version.mjs +0 -3
@@ -0,0 +1,111 @@
1
+ # E2E Smoke Tests — 8-path matrix via engine dispatcher
2
+
3
+ **Run from `/tmp/...` — never from the monorepo root or `${TARGET_PATH}/`.**
4
+
5
+ `--scope=project` walks `cwd` upward looking for an existing `.claude`
6
+ directory. Running from the monorepo would reuse or mutate the real
7
+ repo's `.claude`, which is a destructive error.
8
+
9
+ No fake `node_modules` needed — the engine uses
10
+ `createRequire(import.meta.url).resolve(`${PACKAGE_NAME}/package.json`)`
11
+ from the engine's own installed location.
12
+
13
+ ---
14
+
15
+ ## Setup
16
+
17
+ ```bash
18
+ BIN="$PWD/packages/slats/claude-assets-sync/bin/inject-claude-settings.mjs"
19
+ DIR=/tmp/inject-smoke-${SHORTCUT:-target}
20
+ [ -d "$DIR" ] && find "$DIR" -mindepth 1 -delete
21
+ mkdir -p "$DIR" && cd "$DIR"
22
+ ```
23
+
24
+ `[ -d ... ] && find -delete` keeps the setup idempotent. **Never** use
25
+ `rm -rf` or unquoted `*` globs — too easy to nuke the wrong directory.
26
+
27
+ ---
28
+
29
+ ## Matrix
30
+
31
+ Execute sequentially. `EXIT=$?` after each so the value is captured
32
+ before the next command overwrites `$?`.
33
+
34
+ | # | Command | Expected exit | Purpose |
35
+ |----|--------------------------------------------------------------------------------------------------|---------------|--------------------------------------------------------------|
36
+ | 1 | `node "$BIN" --package=${PACKAGE_NAME} --scope=project --dry-run` | 0 | Dry run — previews actions, no writes. |
37
+ | 2 | `node "$BIN" --package=${PACKAGE_NAME} --scope=project` | 0 | First real install — writes `.claude/` under `$DIR`. |
38
+ | 3 | `node "$BIN" --package=${PACKAGE_NAME} --scope=project` | 0 | Re-run — no-op (idempotent). |
39
+ | 4 | (after tampering) `CI=true node "$BIN" --package=${PACKAGE_NAME} --scope=project` | **2** | CI + tampered content → refuse to overwrite. |
40
+ | 5 | `CI=true node "$BIN" --package=${PACKAGE_NAME} --scope=project --force` | 0 | `--force` overrides the refusal. |
41
+ | 6 | `CI=true node "$BIN" --package=${PACKAGE_NAME}` | **2** | Missing `--scope` in non-TTY context. |
42
+ | 7 | `node "$BIN"` | **2** | Missing `--package` (dispatcher-specific). |
43
+ | 8 | `node "$BIN" --package=@does/not-exist` | **2** | Unresolvable package (dispatcher-specific). |
44
+
45
+ ### Tamper step (between path 3 and path 4)
46
+
47
+ ```bash
48
+ find .claude -name SKILL.md -exec sh -c 'echo tampered >> "$1"' _ {} \;
49
+ ```
50
+
51
+ Appends `tampered` to every `SKILL.md` under the local `.claude/`.
52
+ Simulates a human edit that the CI-mode dispatcher must detect and
53
+ refuse to clobber.
54
+
55
+ ---
56
+
57
+ ## Execution Shape
58
+
59
+ Split into **two bash calls** because `cwd` resets between Bash tool
60
+ invocations.
61
+
62
+ **First call** — paths 1–3:
63
+
64
+ ```bash
65
+ BIN="$PWD/packages/slats/claude-assets-sync/bin/inject-claude-settings.mjs"
66
+ DIR=/tmp/inject-smoke-${SHORTCUT:-target}
67
+ [ -d "$DIR" ] && find "$DIR" -mindepth 1 -delete
68
+ mkdir -p "$DIR" && cd "$DIR"
69
+
70
+ node "$BIN" --package=${PACKAGE_NAME} --scope=project --dry-run; echo "EXIT=$?"
71
+ node "$BIN" --package=${PACKAGE_NAME} --scope=project; echo "EXIT=$?"
72
+ node "$BIN" --package=${PACKAGE_NAME} --scope=project; echo "EXIT=$?"
73
+ ```
74
+
75
+ **Second call** — paths 4–8:
76
+
77
+ ```bash
78
+ DIR=/tmp/inject-smoke-${SHORTCUT:-target}
79
+ BIN="$PWD/packages/slats/claude-assets-sync/bin/inject-claude-settings.mjs"
80
+ cd "$DIR"
81
+
82
+ find .claude -name SKILL.md -exec sh -c 'echo tampered >> "$1"' _ {} \;
83
+ CI=true node "$BIN" --package=${PACKAGE_NAME} --scope=project; echo "EXIT=$?"
84
+ CI=true node "$BIN" --package=${PACKAGE_NAME} --scope=project --force; echo "EXIT=$?"
85
+ CI=true node "$BIN" --package=${PACKAGE_NAME}; echo "EXIT=$?"
86
+ node "$BIN"; echo "EXIT=$?"
87
+ node "$BIN" --package=@does/not-exist; echo "EXIT=$?"
88
+ ```
89
+
90
+ Note: `$PWD` in the second call is the parent shell's cwd (monorepo
91
+ root), so `BIN` resolves correctly. `cd "$DIR"` then moves into the
92
+ smoke directory before invoking.
93
+
94
+ ---
95
+
96
+ ## Failure Handling
97
+
98
+ | Observed | Meaning | Action |
99
+ |----------|-------------------------------------------------------------------------|--------------------------------------------------------------------|
100
+ | 1 ≠ 0 | Dry-run crashed. Likely an engine bug or bad `claude.assetPath`. | Stop, capture stderr, report. |
101
+ | 2 ≠ 0 | First write failed. Permissions, engine bug, or manifest issue. | Stop, inspect `dist/claude-hashes.json`, report. |
102
+ | 3 ≠ 0 | Idempotency broken — re-run should be no-op. | Stop, diff `$DIR/.claude` before/after, report. |
103
+ | 4 = 0 | CI mode did not refuse tampered files. Safety regression. | Stop — the engine's CI gate is broken. |
104
+ | 5 ≠ 0 | `--force` failed to override. Check engine. | Stop, report. |
105
+ | 6 = 0 | Engine defaulted a scope in non-TTY context. Should require `--scope`. | Stop, report. |
106
+ | 7 = 0 | Dispatcher accepted no `--package`. Violates contract. | Stop — dispatcher bug. |
107
+ | 8 = 0 | Dispatcher succeeded on unresolvable package. Violates contract. | Stop — dispatcher bug. |
108
+
109
+ Do not attempt to "make the tests pass" by altering expectations. The
110
+ matrix encodes invariants of the engine — a mismatch is a real
111
+ regression upstream.
@@ -0,0 +1,94 @@
1
+ # Consumer Integration Template
2
+
3
+ How to make a package ship Claude Code assets through the `inject-claude-settings` dispatcher. Two fields in `package.json`; no stub code.
4
+
5
+ ## 1. `package.json` additions
6
+
7
+ ```jsonc
8
+ {
9
+ "name": "@your-scope/your-package",
10
+ "version": "…",
11
+ "scripts": {
12
+ "build": "… && yarn build:hashes",
13
+ "build:hashes": "claude-build-hashes"
14
+ },
15
+ "dependencies": {
16
+ "@slats/claude-assets-sync": "workspace:^"
17
+ },
18
+ "files": ["dist", "docs", "README.md"],
19
+ "claude": {
20
+ "assetPath": "docs/claude"
21
+ }
22
+ }
23
+ ```
24
+
25
+ - `@slats/claude-assets-sync` MUST be in `dependencies`, not `devDependencies` or `peerDependencies`.
26
+ - Do **not** add any `bin` field. Bin names collide across consumers under `node_modules/.bin/` and the engine is the sole CLI surface.
27
+ - Do **not** expose `./bin/*` or `./docs/*` in `exports`. Exposing them would let bundlers pull CLI code or the docs tree into app bundles.
28
+ - Do **not** create a `bin/` or `scripts/` directory in the consumer. The engine's `claude-build-hashes` bin (resolved via `node_modules/.bin/`) handles build-time hashing.
29
+
30
+ ## 2. `docs/claude/` authoring
31
+
32
+ Any file tree works, but the recommended layout is:
33
+
34
+ ```
35
+ docs/claude/
36
+ ├── skills/
37
+ │ └── <skill-name>/
38
+ │ ├── SKILL.md
39
+ │ └── knowledge/...
40
+ ├── rules/...
41
+ └── commands/...
42
+ ```
43
+
44
+ The build step (`yarn build:hashes` → `claude-build-hashes`) hashes every file under `docs/claude/` relative to the asset root and writes `dist/claude-hashes.json`. On inject, the CLI copies `skills`/`rules`/`commands` into the matching subtree under `.claude/`.
45
+
46
+ ## 3. Isolation guardrails (optional but recommended)
47
+
48
+ In a `.dependency-cruiser.cjs`:
49
+
50
+ ```javascript
51
+ {
52
+ name: 'src-no-docs',
53
+ severity: 'error',
54
+ comment:
55
+ 'src/ must not import from docs/. docs/claude/** contains pure markdown ' +
56
+ 'assets meant only for the engine dispatcher, not for the library runtime.',
57
+ from: { path: '^src/' },
58
+ to: { path: '^docs/' },
59
+ },
60
+ ```
61
+
62
+ Plus `"sideEffects": false` in `package.json`. These ensure the docs tree never leaks into consumer runtime bundles.
63
+
64
+ The legacy `src-no-bin` and `src-no-claude-assets-sync` rules are no longer load-bearing — the consumer owns no `bin/`, and the engine isn't referenced from `src/` anyway. Do not reintroduce them.
65
+
66
+ ## 4. End-user invocations
67
+
68
+ | Install topology | Invocation |
69
+ |---|---|
70
+ | End user installs consumer as a **direct dep** on npm / yarn-classic | `npx inject-claude-settings --package=@your-scope/your-package --scope=user` |
71
+ | End user installs consumer as a **direct dep** on pnpm strict / yarn-berry PnP | `npx -p @slats/claude-assets-sync inject-claude-settings --package=@your-scope/your-package --scope=user` |
72
+ | End user has **no consumer installed** yet | `npx -p @slats/claude-assets-sync inject-claude-settings --package=@your-scope/your-package --scope=user` (resolves to whatever is on the registry under that exact name) |
73
+ | End user has **multiple consumers installed** | Call `inject-claude-settings --package=<one-name>` per target. There is no `--all`. |
74
+
75
+ ### Scope resolution (project)
76
+
77
+ For `--scope=project`, the target `.claude` directory is resolved by walking up from `process.cwd()` and reusing the first existing `.claude` directory found. Only if no ancestor owns a `.claude` does the CLI fall back to `process.cwd()/.claude`. The CLI logs `(auto-located)` when this happens.
78
+
79
+ ```
80
+ workspace/
81
+ .claude/ ← reused target (auto-located)
82
+ packages/
83
+ @your-scope/your-package/ ← cd here and run inject-claude-settings
84
+ ```
85
+
86
+ Running `inject-claude-settings --package=@your-scope/your-package --scope=project` from `packages/@your-scope/your-package/` injects into `workspace/.claude`, not `packages/@your-scope/your-package/.claude`.
87
+
88
+ ## 5. Verification checklist
89
+
90
+ - [ ] `yarn build` succeeds and emits `dist/claude-hashes.json` alongside the rest of `dist/`.
91
+ - [ ] `node packages/slats/claude-assets-sync/bin/inject-claude-settings.mjs --help` prints the dispatcher usage.
92
+ - [ ] `node packages/slats/claude-assets-sync/bin/inject-claude-settings.mjs --package=@your-scope/your-package --scope=project --dry-run` emits a copy plan when run from `/tmp/...`.
93
+ - [ ] If you enabled Section 3's depcheck, `yarn depcheck` reports zero violations.
94
+ - [ ] Consumer bundler tree-shakes away docs — grep the built bundle for `@slats/claude-assets-sync`, `inject-claude-settings`, and `docs/claude`; all three should be absent.
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@slats/claude-assets-sync",
3
- "version": "0.1.4",
4
- "description": "CLI tool to sync Claude commands and skills from npm packages to your project's .claude directory",
3
+ "version": "0.3.0",
4
+ "description": "Shared CLI engine that lets consumer packages inject their Claude docs (skills, rules, commands) into a user's .claude directory via a thin bin/inject-docs wrapper.",
5
5
  "keywords": [
6
6
  "claude",
7
7
  "claude-code",
8
8
  "cli",
9
- "sync",
10
- "commands",
9
+ "inject",
10
+ "docs",
11
11
  "skills",
12
12
  "assets"
13
13
  ],
@@ -29,23 +29,34 @@
29
29
  "source": "./src/index.ts",
30
30
  "import": "./dist/index.mjs",
31
31
  "require": "./dist/index.cjs"
32
+ },
33
+ "./buildHashes": {
34
+ "import": "./scripts/buildHashes.mjs"
32
35
  }
33
36
  },
34
37
  "main": "dist/index.cjs",
35
38
  "module": "dist/index.mjs",
36
39
  "types": "dist/index.d.ts",
37
- "bin": "./dist/cli.mjs",
40
+ "bin": {
41
+ "claude-build-hashes": "./scripts/claude-build-hashes.mjs",
42
+ "inject-claude-settings": "./bin/inject-claude-settings.mjs"
43
+ },
38
44
  "files": [
39
45
  "dist",
46
+ "docs",
47
+ "bin",
48
+ "scripts",
40
49
  "README.md"
41
50
  ],
42
51
  "scripts": {
43
- "build": "node scripts/inject-version.js && rollup -c && yarn build:types",
52
+ "build": "node scripts/inject-version.js && rollup -c && yarn build:types && yarn build:hashes",
53
+ "build:hashes": "node scripts/build-hashes.mjs",
44
54
  "build:publish:npm": "yarn build && yarn publish:npm",
45
55
  "build:types": "node ../../aileron/script/build/buildTypes.mjs",
46
- "dev": "node scripts/inject-version.js && tsx src/cli.ts",
56
+ "dev": "node scripts/inject-version.js && tsx src/main.ts",
47
57
  "format": "prettier --write \"src/**/*.ts\"",
48
58
  "lint": "eslint \"src/**/*.ts\"",
59
+ "prepublishOnly": "yarn build",
49
60
  "publish:npm": "yarn npm publish --access public",
50
61
  "test": "vitest",
51
62
  "version:major": "yarn version major",
@@ -53,17 +64,14 @@
53
64
  "version:patch": "yarn version patch"
54
65
  },
55
66
  "dependencies": {
56
- "@winglet/react-utils": "^0.11.2",
67
+ "@inquirer/prompts": "^8.4.2",
57
68
  "commander": "^12.1.0",
58
- "ink": "^6.6.0",
59
- "ink-select-input": "^6.2.0",
60
- "ink-spinner": "^5.0.0",
61
- "picocolors": "^1.1.1",
62
- "react": "^19.1.1",
63
- "react-dom": "^19.1.1"
69
+ "picocolors": "^1.1.1"
64
70
  },
65
71
  "devDependencies": {
66
- "@types/react": "^19.0.0",
67
- "ink-testing-library": "^4.0.0"
72
+ "@inquirer/testing": "^3.3.5"
73
+ },
74
+ "claude": {
75
+ "assetPath": "docs/claude"
68
76
  }
69
77
  }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ // Thin wrapper — parses this package's package.json and delegates to
3
+ // @slats/claude-assets-sync/buildHashes. The `claude.assetPath` convention
4
+ // lives here, in the consumer; the library is generic.
5
+ import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
6
+ import { readFile } from 'node:fs/promises';
7
+ import { dirname, resolve } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
+ const pkg = JSON.parse(
12
+ await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
13
+ );
14
+
15
+ if (typeof pkg.claude?.assetPath === 'string') {
16
+ try {
17
+ const { outPath, fileCount } = await buildHashes({
18
+ packageRoot,
19
+ packageName: pkg.name,
20
+ packageVersion: pkg.version,
21
+ assetPath: pkg.claude.assetPath,
22
+ });
23
+ console.log(
24
+ `✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`,
25
+ );
26
+ } catch (err) {
27
+ console.error('❌ build-hashes failed:', err?.message ?? err);
28
+ process.exit(1);
29
+ }
30
+ }
@@ -0,0 +1,15 @@
1
+ export interface BuildHashesOptions {
2
+ packageRoot?: string;
3
+ packageName?: string;
4
+ packageVersion?: string;
5
+ assetPathRel?: string;
6
+ }
7
+
8
+ export interface BuildHashesResult {
9
+ outPath: string;
10
+ fileCount: number;
11
+ }
12
+
13
+ export function buildHashes(
14
+ opts?: BuildHashesOptions,
15
+ ): Promise<BuildHashesResult>;
@@ -0,0 +1,82 @@
1
+ // Library + importable implementation for the hash manifest builder.
2
+ //
3
+ // Self-executing CLI behavior lives in `./claude-build-hashes.mjs` so this
4
+ // file stays free of top-level await and can be bundled into CJS/ESM outputs
5
+ // via Rollup without format errors.
6
+ //
7
+ // Exported for:
8
+ // - Consumer packages: `import { buildHashes } from '@slats/claude-assets-sync/buildHashes'`
9
+ // - Standalone bin: `./claude-build-hashes.mjs`
10
+ //
11
+ // The caller owns all package metadata. This function does not read
12
+ // package.json — consumers parse their own manifest and pass a ready-made
13
+ // set of values so the library stays free of field-shape assumptions.
14
+
15
+ import { createHash } from 'node:crypto';
16
+ import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
17
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
18
+
19
+ const MANIFEST_FILENAME = 'claude-hashes.json';
20
+ const NOISE = [/(^|\/)\.omc(\/|$)/, /(^|\/)\.DS_Store$/, /\.log$/];
21
+
22
+ const toPosix = (p) => (sep === '/' ? p : p.split(sep).join('/'));
23
+
24
+ async function* walk(root) {
25
+ let entries;
26
+ try {
27
+ entries = await readdir(root, { withFileTypes: true });
28
+ } catch (err) {
29
+ if (err.code === 'ENOENT') return;
30
+ throw err;
31
+ }
32
+ for (const entry of entries) {
33
+ const abs = join(root, entry.name);
34
+ if (entry.isDirectory()) yield* walk(abs);
35
+ else if (entry.isFile()) yield abs;
36
+ }
37
+ }
38
+
39
+ export async function buildHashes(opts) {
40
+ if (
41
+ !opts ||
42
+ typeof opts.packageRoot !== 'string' ||
43
+ typeof opts.packageName !== 'string' ||
44
+ typeof opts.packageVersion !== 'string' ||
45
+ typeof opts.assetPath !== 'string'
46
+ ) {
47
+ throw new Error(
48
+ 'buildHashes requires { packageRoot, packageName, packageVersion, assetPath }.',
49
+ );
50
+ }
51
+ const { packageRoot, packageName, packageVersion, assetPath } = opts;
52
+ if (!isAbsolute(packageRoot)) {
53
+ throw new Error(
54
+ `packageRoot must be an absolute path; received: ${packageRoot}`,
55
+ );
56
+ }
57
+ const assetRoot = resolve(packageRoot, assetPath);
58
+ const files = {};
59
+ for await (const abs of walk(assetRoot)) {
60
+ const rel = toPosix(relative(assetRoot, abs));
61
+ if (NOISE.some((re) => re.test(rel))) continue;
62
+ const buf = await readFile(abs);
63
+ files[rel] = createHash('sha256').update(buf).digest('hex');
64
+ }
65
+ const sorted = Object.fromEntries(
66
+ Object.entries(files).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
67
+ );
68
+ const manifest = {
69
+ schemaVersion: 1,
70
+ package: { name: packageName, version: packageVersion },
71
+ generatedAt: new Date().toISOString(),
72
+ algorithm: 'sha256',
73
+ assetRoot: assetPath,
74
+ files: sorted,
75
+ previousVersions: {},
76
+ };
77
+ const distDir = resolve(packageRoot, 'dist');
78
+ await mkdir(distDir, { recursive: true });
79
+ const outPath = join(distDir, MANIFEST_FILENAME);
80
+ await writeFile(outPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
81
+ return { outPath, fileCount: Object.keys(sorted).length };
82
+ }
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // Standalone CLI for `claude-build-hashes`.
3
+ //
4
+ // Reads the consumer's package.json at process.cwd() to extract the asset
5
+ // path, then delegates to `buildHashes`. Consumers who want a different
6
+ // manifest layout can skip this bin and ship a one-line
7
+ // `scripts/build-hashes.mjs` that calls `buildHashes` directly with their
8
+ // own parsed values.
9
+ //
10
+ // Convention: `pkg.claude?.assetPath` with a fallback of `'claude'`.
11
+ // This convention is purely consumer-side — the library itself enforces
12
+ // nothing about package.json shape.
13
+ import { readFile } from 'node:fs/promises';
14
+ import { resolve } from 'node:path';
15
+
16
+ import { buildHashes } from './buildHashes.mjs';
17
+
18
+ try {
19
+ const packageRoot = process.cwd();
20
+ const pkg = JSON.parse(
21
+ await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
22
+ );
23
+ if (typeof pkg.name !== 'string' || typeof pkg.version !== 'string') {
24
+ throw new Error(
25
+ `${packageRoot}/package.json must define "name" and "version".`,
26
+ );
27
+ }
28
+ const assetPath =
29
+ typeof pkg.claude?.assetPath === 'string' && pkg.claude.assetPath.length > 0
30
+ ? pkg.claude.assetPath
31
+ : 'claude';
32
+ const { outPath, fileCount } = await buildHashes({
33
+ packageRoot,
34
+ packageName: pkg.name,
35
+ packageVersion: pkg.version,
36
+ assetPath,
37
+ });
38
+ console.log(`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`);
39
+ } catch (err) {
40
+ console.error('❌ buildHashes failed:', err?.message ?? err);
41
+ process.exit(1);
42
+ }
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ /**
11
+ * Semantic version regex (https://semver.org/)
12
+ * Matches: 0.0.1, 1.2.3, 1.0.0-beta.1, 2.0.0+build.123, etc.
13
+ */
14
+ const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
15
+
16
+ /**
17
+ * Read and validate package.json version
18
+ */
19
+ function readPackageVersion() {
20
+ const packageJsonPath = join(__dirname, '..', 'package.json');
21
+
22
+ try {
23
+ const content = readFileSync(packageJsonPath, 'utf-8');
24
+ const pkg = JSON.parse(content);
25
+
26
+ if (!pkg.version) {
27
+ console.error('❌ Error: No version field found in package.json');
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!SEMVER_REGEX.test(pkg.version)) {
32
+ console.error(`❌ Error: Invalid semantic version "${pkg.version}" in package.json`);
33
+ console.error(' Expected format: MAJOR.MINOR.PATCH (e.g., 1.2.3, 1.0.0-beta.1)');
34
+ process.exit(1);
35
+ }
36
+
37
+ return pkg.version;
38
+ } catch (error) {
39
+ if (error.code === 'ENOENT') {
40
+ console.error('❌ Error: package.json not found at', packageJsonPath);
41
+ } else if (error instanceof SyntaxError) {
42
+ console.error('❌ Error: Invalid JSON in package.json');
43
+ } else {
44
+ console.error('❌ Error reading package.json:', error.message);
45
+ }
46
+ process.exit(1);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Generate src/version.ts with current version
52
+ */
53
+ function generateVersionFile(version) {
54
+ const versionFilePath = join(__dirname, '..', 'src', 'utils', 'version.ts');
55
+
56
+ try {
57
+ // Check if file exists and read current version
58
+ let currentVersion = null;
59
+ try {
60
+ const existingContent = readFileSync(versionFilePath, 'utf-8');
61
+ const match = existingContent.match(/VERSION = ['"]([^'"]+)['"]/);
62
+ if (match) {
63
+ currentVersion = match[1];
64
+ }
65
+ } catch {
66
+ // File doesn't exist yet, that's fine
67
+ }
68
+
69
+ if (currentVersion === version) {
70
+ console.log(`✓ Version file already up to date: ${version}`);
71
+ return false;
72
+ }
73
+
74
+ // Generate new version file
75
+ const content = `// Auto-generated by scripts/inject-version.js
76
+ // DO NOT EDIT MANUALLY - This file is generated during build
77
+
78
+ /**
79
+ * Current package version from package.json
80
+ * Automatically synchronized during build process
81
+ */
82
+ export const VERSION = '${version}';
83
+ `;
84
+
85
+ writeFileSync(versionFilePath, content, 'utf-8');
86
+
87
+ if (currentVersion) {
88
+ console.log(`✓ Version file updated: ${currentVersion} → ${version}`);
89
+ } else {
90
+ console.log(`✓ Version file created: ${version}`);
91
+ }
92
+ return true;
93
+ } catch (error) {
94
+ console.error('❌ Error generating version file:', error.message);
95
+ process.exit(1);
96
+ }
97
+ }
98
+
99
+ // Main execution
100
+ try {
101
+ const version = readPackageVersion();
102
+ const updated = generateVersionFile(version);
103
+
104
+ if (!updated) {
105
+ console.log(`\n✅ Version is synchronized: ${version}`);
106
+ } else {
107
+ console.log(`\n✅ Version synchronization complete: ${version}`);
108
+ }
109
+ } catch (error) {
110
+ console.error('❌ Unexpected error:', error.message);
111
+ process.exit(1);
112
+ }
package/dist/cli.cjs DELETED
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var cli = require('./core/cli.cjs');
4
-
5
- cli.run().catch((error) => {
6
- console.error('Fatal error:', error.message);
7
- process.exit(1);
8
- });
package/dist/cli.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
package/dist/cli.mjs DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import { run } from './core/cli.mjs';
3
-
4
- run().catch((error) => {
5
- console.error('Fatal error:', error.message);
6
- process.exit(1);
7
- });
@@ -1,80 +0,0 @@
1
- 'use strict';
2
-
3
- var ink = require('ink');
4
- var React = require('react');
5
- var AddCommand = require('../components/add/AddCommand.cjs');
6
- var BulkAddView = require('../components/add/BulkAddView.cjs');
7
- var sync = require('../core/sync.cjs');
8
-
9
- async function runAddCommand(options, cwd) {
10
- const workingDir = process.cwd();
11
- if (options.pattern) {
12
- try {
13
- new RegExp(options.pattern);
14
- }
15
- catch (err) {
16
- const msg = err instanceof Error ? err.message : String(err);
17
- throw new Error(`Invalid regex pattern "${options.pattern}": ${msg}`);
18
- }
19
- const { waitUntilExit } = ink.render(React.createElement(BulkAddView.BulkAddView, {
20
- pattern: options.pattern,
21
- cwd: workingDir,
22
- local: options.local ?? false,
23
- ref: options.ref,
24
- }));
25
- await waitUntilExit();
26
- return;
27
- }
28
- if (!options.package) {
29
- console.error('Error: either --package or --pattern must be provided');
30
- console.error(' Usage: claude-assets-sync add -p <name>');
31
- console.error(' claude-assets-sync add --pattern <regex>');
32
- process.exit(1);
33
- }
34
- return new Promise((resolve, reject) => {
35
- const { waitUntilExit } = ink.render(React.createElement(AddCommand.AddCommand, {
36
- packageName: options.package,
37
- local: options.local ?? false,
38
- ref: options.ref,
39
- onComplete: async (selection) => {
40
- try {
41
- await performSync(selection, workingDir);
42
- resolve();
43
- }
44
- catch (error) {
45
- reject(error);
46
- }
47
- },
48
- onError: (error) => {
49
- reject(error);
50
- },
51
- onCancel: () => {
52
- console.log('\nSync cancelled');
53
- resolve();
54
- },
55
- }));
56
- waitUntilExit().catch(reject);
57
- });
58
- }
59
- async function performSync(selection, cwd) {
60
- const exclusions = {
61
- directories: [],
62
- files: [],
63
- };
64
- for (const assetType of Object.keys(selection.excludedAssets)) {
65
- for (const excludedPath of selection.excludedAssets[assetType]) {
66
- exclusions.files.push(excludedPath);
67
- }
68
- }
69
- await sync.syncPackage(selection.packageName, {
70
- force: true,
71
- dryRun: false,
72
- local: selection.source === 'local',
73
- ref: selection.ref,
74
- flat: true,
75
- }, cwd, exclusions.directories.length > 0 || exclusions.files.length > 0
76
- ? exclusions
77
- : undefined, undefined);
78
- }
79
-
80
- exports.runAddCommand = runAddCommand;