@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.
- package/README.md +46 -62
- package/bin/inject-claude-settings.mjs +4 -0
- package/dist/claude-hashes.json +9 -9
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/runCli/index.d.ts +1 -1
- package/dist/commands/runCli/runCli.cjs +27 -5
- package/dist/commands/runCli/runCli.d.ts +10 -6
- package/dist/commands/runCli/runCli.mjs +27 -5
- package/dist/commands/runCli/type.d.ts +3 -12
- package/dist/commands/runCli/utils/classifyTarget.cjs +48 -0
- package/dist/commands/runCli/utils/classifyTarget.d.ts +19 -0
- package/dist/commands/runCli/utils/classifyTarget.mjs +46 -0
- package/dist/commands/runCli/utils/injectOne.cjs +2 -3
- package/dist/commands/runCli/utils/injectOne.d.ts +1 -1
- package/dist/commands/runCli/utils/injectOne.mjs +2 -3
- package/dist/commands/runCli/utils/resolvePackage.cjs +77 -0
- package/dist/commands/runCli/utils/resolvePackage.d.ts +16 -0
- package/dist/commands/runCli/utils/resolvePackage.mjs +74 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.cjs +69 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.d.ts +2 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.mjs +67 -0
- package/dist/commands/runCli/utils/resolveTargets.cjs +40 -0
- package/dist/commands/runCli/utils/resolveTargets.d.ts +15 -0
- package/dist/commands/runCli/utils/resolveTargets.mjs +38 -0
- package/dist/commands/runCli/utils/runInject.cjs +38 -22
- package/dist/commands/runCli/utils/runInject.d.ts +3 -2
- package/dist/commands/runCli/utils/runInject.mjs +38 -22
- package/dist/core/injectDocs/utils/applyAction.cjs +1 -1
- package/dist/core/injectDocs/utils/applyAction.mjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/utils/version.cjs +1 -1
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.mjs +1 -1
- package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +159 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +86 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md +54 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +122 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +145 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +37 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/smoke-tests.md +111 -0
- package/docs/consumer-integration.md +41 -100
- package/package.json +2 -2
- package/bin/claude-sync.mjs +0 -24
- package/docs/claude/skills/claude-sync-applier/SKILL.md +0 -195
- package/docs/claude/skills/claude-sync-applier/knowledge/claude-md-template.md +0 -77
- package/docs/claude/skills/claude-sync-applier/knowledge/dependency-cruiser.md +0 -126
- package/docs/claude/skills/claude-sync-applier/knowledge/gotchas.md +0 -139
- package/docs/claude/skills/claude-sync-applier/knowledge/package-json-patches.md +0 -130
- package/docs/claude/skills/claude-sync-applier/knowledge/reference-files.md +0 -120
- 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.
|