@slats/claude-assets-sync 0.2.0 → 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 (50) hide show
  1. package/README.md +46 -62
  2. package/bin/inject-claude-settings.mjs +4 -0
  3. package/dist/claude-hashes.json +9 -9
  4. package/dist/commands/index.d.ts +1 -1
  5. package/dist/commands/runCli/index.d.ts +1 -1
  6. package/dist/commands/runCli/runCli.cjs +27 -5
  7. package/dist/commands/runCli/runCli.d.ts +10 -6
  8. package/dist/commands/runCli/runCli.mjs +27 -5
  9. package/dist/commands/runCli/type.d.ts +3 -12
  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 +2 -3
  14. package/dist/commands/runCli/utils/injectOne.d.ts +1 -1
  15. package/dist/commands/runCli/utils/injectOne.mjs +2 -3
  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/resolveTargets.cjs +40 -0
  23. package/dist/commands/runCli/utils/resolveTargets.d.ts +15 -0
  24. package/dist/commands/runCli/utils/resolveTargets.mjs +38 -0
  25. package/dist/commands/runCli/utils/runInject.cjs +38 -22
  26. package/dist/commands/runCli/utils/runInject.d.ts +3 -2
  27. package/dist/commands/runCli/utils/runInject.mjs +38 -22
  28. package/dist/core/injectDocs/utils/applyAction.cjs +1 -1
  29. package/dist/core/injectDocs/utils/applyAction.mjs +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/utils/version.cjs +1 -1
  32. package/dist/utils/version.d.ts +1 -1
  33. package/dist/utils/version.mjs +1 -1
  34. package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +159 -0
  35. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +86 -0
  36. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md +54 -0
  37. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +122 -0
  38. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +145 -0
  39. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +37 -0
  40. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/smoke-tests.md +111 -0
  41. package/docs/consumer-integration.md +41 -100
  42. package/package.json +2 -2
  43. package/bin/claude-sync.mjs +0 -24
  44. package/docs/claude/skills/claude-sync-applier/SKILL.md +0 -195
  45. package/docs/claude/skills/claude-sync-applier/knowledge/claude-md-template.md +0 -77
  46. package/docs/claude/skills/claude-sync-applier/knowledge/dependency-cruiser.md +0 -126
  47. package/docs/claude/skills/claude-sync-applier/knowledge/gotchas.md +0 -139
  48. package/docs/claude/skills/claude-sync-applier/knowledge/package-json-patches.md +0 -130
  49. package/docs/claude/skills/claude-sync-applier/knowledge/reference-files.md +0 -120
  50. package/docs/claude/skills/claude-sync-applier/knowledge/smoke-tests.md +0 -102
@@ -1,139 +0,0 @@
1
- # Invariants and Gotchas
2
-
3
- Hard-earned rules. Each one reflects a previous incident or a design
4
- constraint of the `@slats/claude-assets-sync` engine.
5
-
6
- ---
7
-
8
- ## Stubs are verbatim
9
-
10
- The `bin/claude-sync.mjs` and `scripts/build-hashes.mjs` files are
11
- **identical across all consumers** in this monorepo. No per-package
12
- substitution. Runtime `import.meta.url` (in the bin) and `process.cwd()` (in
13
- the build script) discover the package root; package metadata is parsed from
14
- the adjacent `package.json`.
15
-
16
- If you find yourself "adapting" the stub for a new consumer, stop — that's a
17
- sign you misread the design. The engine is generic; the consumer-specific
18
- convention is the `claude.assetPath` field inside `package.json`.
19
-
20
- ---
21
-
22
- ## Silent no-op on missing `claude.assetPath`
23
-
24
- Both stubs guard with `if (typeof pkg.claude?.assetPath === 'string')`.
25
-
26
- - Missing field → exit 0, produce no output.
27
- - Non-string value → exit 0, produce no output.
28
-
29
- This is the intentional opt-out path: a package can ship the bin and script
30
- but disable the feature by removing the config field. Do not add "helpful"
31
- error messages for this case — it would break the opt-out.
32
-
33
- ---
34
-
35
- ## `@slats/claude-assets-sync` must be in `dependencies`
36
-
37
- **Not** `devDependencies`, **not** `peerDependencies`.
38
-
39
- Isolation from the library bundle is enforced by:
40
-
41
- 1. The import graph (`src/**` never references the engine), and
42
- 2. Optionally, dependency-cruiser static rules.
43
-
44
- It is **not** enforced by dependency-type. Placing the engine in
45
- `devDependencies` would break `npx <pkg> claude-sync` when a downstream
46
- consumer installs the package — their `npm install` omits dev deps.
47
-
48
- ---
49
-
50
- ## Never add `./bin/*` to `exports`
51
-
52
- The `exports` map in `package.json` controls which subpaths a consumer's
53
- bundler can resolve. Leaving `./bin/*` out of `exports` is what prevents a
54
- bundler from deep-importing the CLI by accident.
55
-
56
- Adding `./bin/claude-sync.mjs` to `exports` — even with `"default"` —
57
- creates a path for a careless `import "@canard/schema-form/bin/claude-sync"`
58
- to pull the CLI engine (and `@slats/claude-assets-sync`) into the library
59
- consumer's production bundle. Three-layer isolation collapses to zero.
60
-
61
- ---
62
-
63
- ## Do not commit `dist/claude-hashes.json`
64
-
65
- It is a build artifact. The `yarn build` chain regenerates it via
66
- `build:hashes`. It should be in `.gitignore` (usually via a catch-all `dist/`
67
- rule). If you see it in `git status`, stop — something is misconfigured.
68
-
69
- ---
70
-
71
- ## `yarn workspace ${PACKAGE_NAME} build` can fail with `rollup: command not found`
72
-
73
- Yarn v4 workspace dispatch does not always propagate the workspace-local
74
- PATH. Prefer `yarn ${SHORTCUT} build` from the monorepo root, where
75
- `${SHORTCUT}` is the root-level script alias (e.g. `yarn schemaForm`).
76
-
77
- If no shortcut exists, the full form `yarn workspace ${PACKAGE_NAME} build`
78
- may still work depending on yarn version and cache state — but if it fails
79
- with `rollup: command not found`, add a shortcut to the root `package.json`
80
- rather than debugging the nested call.
81
-
82
- ---
83
-
84
- ## `--scope=project` walks upward
85
-
86
- `--scope=project` walks `process.cwd()` upward looking for an existing
87
- `.claude` directory. The first one found is reused; if none is found, the
88
- engine creates one at `cwd`.
89
-
90
- Consequence: running the smoke tests from the monorepo root would reuse the
91
- monorepo's real `.claude`, corrupting it. Always run smoke tests from
92
- `/tmp/...` with a fresh directory.
93
-
94
- ---
95
-
96
- ## Bin paths are orphans on purpose
97
-
98
- `bin/claude-sync.mjs` is an executable entry point. It is never imported from
99
- `src/`. That makes it an orphan in dependency-cruiser's eyes.
100
-
101
- Step 4 explicitly excludes `^bin/` from the `no-orphans` rule to silence this
102
- warning. If you add new bin entry points, extend the `pathNot` pattern — do
103
- not disable the rule wholesale.
104
-
105
- ---
106
-
107
- ## Three-layer isolation — why
108
-
109
- 1. **Import graph** — `src/**` never references `bin/**`, `docs/**`, or
110
- `@slats/claude-assets-sync`. Primary defense.
111
- 2. **`sideEffects: false`** — allows the bundler to tree-shake any accidental
112
- reference.
113
- 3. **No `./bin/*` in `exports`** — consumer bundlers cannot reach the bin
114
- through subpath imports.
115
- 4. **(Optional) dependency-cruiser** — CI-time regression check that layer 1
116
- stays intact.
117
-
118
- Step 8 (`grep` on `dist/index.{mjs,cjs}`) is the post-build verification that
119
- all three primary layers held. Any match → one of them is broken.
120
-
121
- ---
122
-
123
- ## Commit this change alone
124
-
125
- The change set from this skill touches `bin/`, `scripts/`, `package.json`,
126
- and possibly `CLAUDE.md` + `.dependency-cruiser.cjs`. It should land in a
127
- single commit, with no unrelated changes interleaved.
128
-
129
- Reasons:
130
-
131
- - Easier to revert as a unit if an issue appears downstream.
132
- - The CI signal (smoke tests, bundle grep) is bound to the state of these
133
- files and nothing else.
134
- - The change is almost entirely mechanical — reviewers can skim-verify
135
- against the reference consumer without reviewing business logic.
136
-
137
- If the user asks to bundle with other work, push back once: recommend a
138
- separate commit. If they still want it bundled, proceed but note it in the
139
- Step 10 report.
@@ -1,130 +0,0 @@
1
- # `package.json` Patches
2
-
3
- All edits below are **additive**. Existing non-conflicting values remain
4
- untouched. On any conflicting existing value, stop and ask the user — do not
5
- overwrite.
6
-
7
- Reference: `packages/canard/schema-form/package.json`.
8
-
9
- ---
10
-
11
- ## 1. `bin`
12
-
13
- ```json
14
- "bin": {
15
- "claude-sync": "./bin/claude-sync.mjs"
16
- }
17
- ```
18
-
19
- If `bin` already exists with other entries, merge in the `claude-sync` key;
20
- preserve siblings. If `bin.claude-sync` already points elsewhere, ask.
21
-
22
- ---
23
-
24
- ## 2. `files`
25
-
26
- Append `"bin"` to the `files` array if not already present. Order within the
27
- array is not load-bearing, but to match the reference:
28
-
29
- ```json
30
- "files": [
31
- "dist",
32
- "docs",
33
- "bin",
34
- "README.md"
35
- ]
36
- ```
37
-
38
- If `files` is absent, create it with at least `["dist", "bin"]`, preserving
39
- any conventions already present in sibling packages.
40
-
41
- ---
42
-
43
- ## 3. `scripts.build`
44
-
45
- Append ` && yarn build:hashes` to whatever the package currently has.
46
-
47
- **Guard against double-append.** If the existing value already ends with
48
- `yarn build:hashes` or already contains `build:hashes`, leave it alone.
49
-
50
- Reference value:
51
-
52
- ```json
53
- "build": "rollup -c && yarn build:types && yarn build:hashes"
54
- ```
55
-
56
- ---
57
-
58
- ## 4. `scripts.build:hashes`
59
-
60
- ```json
61
- "build:hashes": "node scripts/build-hashes.mjs"
62
- ```
63
-
64
- If a different `build:hashes` script exists, ask.
65
-
66
- ---
67
-
68
- ## 5. `scripts.prepublishOnly`
69
-
70
- ```json
71
- "prepublishOnly": "yarn build"
72
- ```
73
-
74
- If the target already has a `prepublishOnly` that calls `yarn build`
75
- (directly or transitively), leave it alone.
76
-
77
- ---
78
-
79
- ## 6. `dependencies."@slats/claude-assets-sync"`
80
-
81
- **Must be in `dependencies`, never `devDependencies`.**
82
-
83
- ```json
84
- "dependencies": {
85
- "@slats/claude-assets-sync": "workspace:^"
86
- }
87
- ```
88
-
89
- Rationale: isolation from the library bundle is enforced by the import graph
90
- (and optionally by dependency-cruiser), not by dependency-type. Placing it in
91
- `devDependencies` would make `npm install` on a published package fail when a
92
- consumer runs `npx <pkg> claude-sync`.
93
-
94
- If the target already has it in `devDependencies`, move it. Do not duplicate.
95
-
96
- ---
97
-
98
- ## 7. `claude.assetPath`
99
-
100
- Default value (used when the field is absent):
101
-
102
- ```json
103
- "claude": {
104
- "assetPath": "docs/claude"
105
- }
106
- ```
107
-
108
- **If `claude.assetPath` already exists with a non-default value, preserve it.**
109
- The convention lives in the consumer, not the library.
110
-
111
- The bin stub guards on `typeof pkg.claude?.assetPath === 'string'` — a missing
112
- or non-string value is a silent no-op. That is the intentional opt-out path.
113
-
114
- ---
115
-
116
- ## Must-NOT
117
-
118
- - **Never** add `./bin/*` (or any bin path) to the `exports` field. Leaving bin
119
- subpaths unexported is what prevents a consumer bundler from accidentally
120
- reaching into the CLI assets via deep imports.
121
-
122
- ---
123
-
124
- ## Full Reference
125
-
126
- See `packages/canard/schema-form/package.json` for the canonical shape. The
127
- relevant keys are `bin`, `files`, `scripts.build`, `scripts.build:hashes`,
128
- `scripts.prepublishOnly`, `dependencies."@slats/claude-assets-sync"`, and
129
- `claude.assetPath`. Everything else in that file is schema-form-specific and
130
- must not be copied.
@@ -1,120 +0,0 @@
1
- # Reference Files
2
-
3
- The two stub files are **identical across all consumers**. Copy verbatim, do
4
- no substitution — the stubs discover package metadata at runtime via
5
- `import.meta.url`.
6
-
7
- Reference consumer: `packages/canard/schema-form`.
8
-
9
- ---
10
-
11
- ## `bin/claude-sync.mjs`
12
-
13
- Source of truth: `packages/canard/schema-form/bin/claude-sync.mjs`.
14
-
15
- Expected content (must match exactly):
16
-
17
- ```js
18
- #!/usr/bin/env node
19
- import { runCli } from '@slats/claude-assets-sync';
20
- import { readFile } from 'node:fs/promises';
21
- import { dirname, resolve } from 'node:path';
22
- import { fileURLToPath } from 'node:url';
23
-
24
- const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
25
- const pkg = JSON.parse(
26
- await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
27
- );
28
-
29
- if (typeof pkg.claude?.assetPath === 'string') {
30
- runCli(process.argv, {
31
- packageRoot,
32
- packageName: pkg.name,
33
- packageVersion: pkg.version,
34
- assetPath: pkg.claude.assetPath,
35
- }).catch((err) => {
36
- process.stderr.write(
37
- `[${pkg.name}] claude-sync failed: ${err instanceof Error ? err.message : String(err)}\n`,
38
- );
39
- process.exit(1);
40
- });
41
- }
42
- ```
43
-
44
- **Notes**
45
-
46
- - `import.meta.url` is resolved at runtime to the installed file path, so
47
- `packageRoot` works both during local development and when consumed as a
48
- dependency.
49
- - `pkg.claude?.assetPath` missing → the stub is a silent no-op. That is the
50
- feature that lets a package opt out without shipping broken bins.
51
- - Emit to stderr on failure and exit 1. Do not `throw` — the CLI is the
52
- process boundary.
53
-
54
- After writing, make it executable:
55
-
56
- ```bash
57
- chmod +x ${TARGET_PATH}/bin/claude-sync.mjs
58
- ```
59
-
60
- ---
61
-
62
- ## `scripts/build-hashes.mjs`
63
-
64
- Source of truth: `packages/canard/schema-form/scripts/build-hashes.mjs`.
65
-
66
- Expected content (must match exactly):
67
-
68
- ```js
69
- #!/usr/bin/env node
70
- // Thin wrapper — parses this package's package.json and delegates to
71
- // @slats/claude-assets-sync/buildHashes. The `claude.assetPath` convention
72
- // lives here, in the consumer; the library is generic.
73
- import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
74
- import { readFile } from 'node:fs/promises';
75
- import { resolve } from 'node:path';
76
-
77
- const packageRoot = process.cwd();
78
- const pkg = JSON.parse(
79
- await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
80
- );
81
-
82
- if (typeof pkg.claude?.assetPath === 'string') {
83
- try {
84
- const { outPath, fileCount } = await buildHashes({
85
- packageRoot,
86
- packageName: pkg.name,
87
- packageVersion: pkg.version,
88
- assetPath: pkg.claude.assetPath,
89
- });
90
- console.log(
91
- `✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`,
92
- );
93
- } catch (err) {
94
- console.error('❌ build-hashes failed:', err?.message ?? err);
95
- process.exit(1);
96
- }
97
- }
98
- ```
99
-
100
- **Notes**
101
-
102
- - `packageRoot = process.cwd()` intentionally — this script runs via
103
- `yarn <shortcut> build:hashes` from the package directory.
104
- - Same no-op behavior when `claude.assetPath` is missing.
105
- - Emits to stdout on success (humans read it during the build).
106
-
107
- No `chmod +x` needed — it's invoked via `node scripts/build-hashes.mjs`.
108
-
109
- ---
110
-
111
- ## Verification
112
-
113
- Before declaring Step 1/Step 2 complete, diff against the reference:
114
-
115
- ```bash
116
- diff packages/canard/schema-form/bin/claude-sync.mjs ${TARGET_PATH}/bin/claude-sync.mjs
117
- diff packages/canard/schema-form/scripts/build-hashes.mjs ${TARGET_PATH}/scripts/build-hashes.mjs
118
- ```
119
-
120
- Both diffs must be empty.
@@ -1,102 +0,0 @@
1
- # E2E Smoke Tests — 6-path matrix
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 repo's
7
- `.claude`, which is a destructive error.
8
-
9
- No fake `node_modules` needed — the bin resolves everything via
10
- `import.meta.url`.
11
-
12
- ---
13
-
14
- ## Setup
15
-
16
- ```bash
17
- BIN=$PWD/${TARGET_PATH}/bin/claude-sync.mjs
18
- DIR=/tmp/claude-sync-smoke-${SHORTCUT:-target}
19
- [ -d "$DIR" ] && find "$DIR" -mindepth 1 -delete
20
- mkdir -p "$DIR" && cd "$DIR"
21
- ```
22
-
23
- `[ -d ... ] && find -delete` keeps the setup idempotent. **Never** use
24
- `rm -rf` or unquoted `*` globs — too easy to nuke the wrong directory.
25
-
26
- ---
27
-
28
- ## Matrix
29
-
30
- Execute sequentially. `EXIT=$?` after each so the value is captured before the
31
- next command overwrites `$?`.
32
-
33
- | # | Command | Expected exit | Purpose |
34
- |---|-----------------------------------------------------------------------------|---------------|----------------------------------------------------------|
35
- | 1 | `node "$BIN" --scope=project --dry-run` | 0 | Dry run on empty dir — previews actions, no writes. |
36
- | 2 | `node "$BIN" --scope=project` | 0 | First real install — writes `.claude/` under `$DIR`. |
37
- | 3 | `node "$BIN" --scope=project` | 0 | Re-run — no-op because everything is already up-to-date. |
38
- | 4 | (after tampering) `CI=true node "$BIN" --scope=project` | **2** | CI + tampered content → refuse to overwrite. |
39
- | 5 | `CI=true node "$BIN" --scope=project --force` | 0 | `--force` overrides the refusal. |
40
- | 6 | `CI=true node "$BIN"` | **2** | Missing `--scope` in non-TTY context → exit 2. |
41
-
42
- ### Tamper step (between path 3 and path 4)
43
-
44
- ```bash
45
- find .claude -name SKILL.md -exec sh -c 'echo tampered >> "$1"' _ {} \;
46
- ```
47
-
48
- Appends `tampered` to every `SKILL.md` under the local `.claude/`. This
49
- simulates a human edit that the CI-mode bin must detect and refuse to clobber.
50
-
51
- ---
52
-
53
- ## Execution Shape
54
-
55
- Split into **two bash calls** because `cwd` resets between Bash tool
56
- invocations.
57
-
58
- **First call** — paths 1–3:
59
-
60
- ```bash
61
- BIN=$PWD/${TARGET_PATH}/bin/claude-sync.mjs
62
- DIR=/tmp/claude-sync-smoke-${SHORTCUT:-target}
63
- [ -d "$DIR" ] && find "$DIR" -mindepth 1 -delete
64
- mkdir -p "$DIR" && cd "$DIR"
65
-
66
- node "$BIN" --scope=project --dry-run; echo "EXIT=$?" # expect 0
67
- node "$BIN" --scope=project; echo "EXIT=$?" # expect 0
68
- node "$BIN" --scope=project; echo "EXIT=$?" # expect 0
69
- ```
70
-
71
- **Second call** — paths 4–6 (re-enter `$DIR`, reuse state from first call):
72
-
73
- ```bash
74
- DIR=/tmp/claude-sync-smoke-${SHORTCUT:-target}
75
- BIN=$PWD/${TARGET_PATH}/bin/claude-sync.mjs
76
- cd "$DIR"
77
-
78
- find .claude -name SKILL.md -exec sh -c 'echo tampered >> "$1"' _ {} \;
79
- CI=true node "$BIN" --scope=project; echo "EXIT=$?" # expect 2
80
- CI=true node "$BIN" --scope=project --force; echo "EXIT=$?" # expect 0
81
- CI=true node "$BIN"; echo "EXIT=$?" # expect 2
82
- ```
83
-
84
- Note: `$PWD` in the second call is the parent shell's cwd (monorepo root), so
85
- `BIN` resolves correctly. `cd "$DIR"` then moves into the smoke directory
86
- before invoking.
87
-
88
- ---
89
-
90
- ## Failure Handling
91
-
92
- | Observed | Meaning | Action |
93
- |----------|-------------------------------------------------------------------------|--------------------------------------------------------------------|
94
- | 1 ≠ 0 | Dry-run crashed. Likely a stub or engine bug. | Stop, capture stderr, report. |
95
- | 2 ≠ 0 | First write failed. Likely permissions, engine bug, or manifest issue. | Stop, inspect `dist/claude-hashes.json`, report. |
96
- | 3 ≠ 0 | Idempotency broken — re-run should be no-op. | Stop, diff `$DIR/.claude` before/after, report. |
97
- | 4 = 0 | CI mode did not refuse tampered files. Safety regression. | Stop — the engine's CI gate is broken. |
98
- | 5 ≠ 0 | `--force` failed to override. Check engine. | Stop, report. |
99
- | 6 = 0 | Engine defaulted a scope in non-TTY context. Should require `--scope`. | Stop, report. |
100
-
101
- Do not attempt to "make the tests pass" by altering expectations. The matrix
102
- encodes invariants of the engine — a mismatch is a real regression upstream.