@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
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { logger } from '../../../utils/logger.mjs';
6
+
7
+ async function resolvePackage(name, options = {}) {
8
+ const pkgJsonPath = resolvePackageJsonPath(name);
9
+ if (!pkgJsonPath) {
10
+ logger.error(`cannot resolve package "${name}". Install it in the current project or pass the correct name.`);
11
+ process.exit(2);
12
+ }
13
+ const packageRoot = dirname(pkgJsonPath);
14
+ const raw = await readFile(pkgJsonPath, 'utf-8');
15
+ const pkg = JSON.parse(raw);
16
+ if (typeof pkg.name !== 'string' || typeof pkg.version !== 'string') {
17
+ if (options.skipMissingAsset) {
18
+ logger.warn(`"${name}" package.json is missing a string "name" or "version" — skipping.`);
19
+ return null;
20
+ }
21
+ logger.error(`${pkgJsonPath} must define string "name" and "version".`);
22
+ process.exit(2);
23
+ }
24
+ const assetPath = pkg.claude?.assetPath;
25
+ if (typeof assetPath !== 'string' || assetPath.length === 0) {
26
+ if (options.skipMissingAsset) {
27
+ logger.warn(`"${name}" is missing "claude.assetPath" — skipping (the package does not ship Claude assets).`);
28
+ return null;
29
+ }
30
+ logger.error(`"${name}" is missing "claude.assetPath" in its package.json — the package does not ship Claude assets.`);
31
+ process.exit(2);
32
+ }
33
+ return {
34
+ packageRoot,
35
+ packageName: pkg.name,
36
+ packageVersion: pkg.version,
37
+ assetPath,
38
+ };
39
+ }
40
+ function resolvePackageJsonPath(name) {
41
+ const require = createRequire(import.meta.url);
42
+ try {
43
+ return require.resolve(`${name}/package.json`);
44
+ }
45
+ catch (err) {
46
+ const code = err?.code;
47
+ if (code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED')
48
+ return null;
49
+ }
50
+ let mainEntry;
51
+ try {
52
+ mainEntry = require.resolve(name);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ let dir = dirname(mainEntry);
58
+ while (dir && dir !== dirname(dir)) {
59
+ const candidate = resolve(dir, 'package.json');
60
+ if (existsSync(candidate)) {
61
+ try {
62
+ const pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
63
+ if (pkg.name === name)
64
+ return candidate;
65
+ }
66
+ catch {
67
+ }
68
+ }
69
+ dir = dirname(dir);
70
+ }
71
+ return null;
72
+ }
73
+
74
+ export { resolvePackage };
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ var node_fs = require('node:fs');
4
+ var promises = require('node:fs/promises');
5
+ var node_path = require('node:path');
6
+ var logger = require('../../../utils/logger.cjs');
7
+ var resolvePackage = require('./resolvePackage.cjs');
8
+
9
+ async function resolveScopeAlias(scope, rootCwd) {
10
+ const packagesRoot = findPackagesRoot(rootCwd);
11
+ if (!packagesRoot) {
12
+ logger.logger.error(`cannot locate a monorepo root with a "packages/" directory starting from "${rootCwd}". Scope alias "@${scope}" requires a workspace root.`);
13
+ process.exit(2);
14
+ }
15
+ const scopeDir = node_path.join(packagesRoot, 'packages', scope);
16
+ let entries;
17
+ try {
18
+ entries = await promises.readdir(scopeDir);
19
+ }
20
+ catch {
21
+ logger.logger.error(`scope alias "@${scope}" has no matching directory at ${scopeDir}.`);
22
+ process.exit(2);
23
+ }
24
+ const matchedNames = [];
25
+ const expectedPrefix = `@${scope}/`;
26
+ for (const entry of entries) {
27
+ const pkgJsonPath = node_path.join(scopeDir, entry, 'package.json');
28
+ if (!node_fs.existsSync(pkgJsonPath))
29
+ continue;
30
+ let parsed;
31
+ try {
32
+ const raw = await promises.readFile(pkgJsonPath, 'utf-8');
33
+ parsed = JSON.parse(raw);
34
+ }
35
+ catch {
36
+ continue;
37
+ }
38
+ if (typeof parsed.name === 'string' &&
39
+ parsed.name.startsWith(expectedPrefix) &&
40
+ parsed.name.length > expectedPrefix.length) {
41
+ matchedNames.push(parsed.name);
42
+ }
43
+ }
44
+ if (matchedNames.length === 0) {
45
+ logger.logger.warn(`scope alias "@${scope}" matched no workspace packages under ${scopeDir}.`);
46
+ return [];
47
+ }
48
+ const resolved = [];
49
+ for (const name of matchedNames) {
50
+ const meta = await resolvePackage.resolvePackage(name, { skipMissingAsset: true });
51
+ if (meta)
52
+ resolved.push(meta);
53
+ }
54
+ return resolved;
55
+ }
56
+ function findPackagesRoot(start) {
57
+ let cur = node_path.resolve(start);
58
+ while (true) {
59
+ if (node_fs.existsSync(node_path.join(cur, 'package.json')) && node_fs.existsSync(node_path.join(cur, 'packages'))) {
60
+ return cur;
61
+ }
62
+ const parent = node_path.dirname(cur);
63
+ if (parent === cur)
64
+ return null;
65
+ cur = parent;
66
+ }
67
+ }
68
+
69
+ exports.resolveScopeAlias = resolveScopeAlias;
@@ -0,0 +1,2 @@
1
+ import { type ResolvedMetadata } from './resolvePackage.js';
2
+ export declare function resolveScopeAlias(scope: string, rootCwd: string): Promise<ResolvedMetadata[]>;
@@ -0,0 +1,67 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readdir, readFile } from 'node:fs/promises';
3
+ import { join, resolve, dirname } from 'node:path';
4
+ import { logger } from '../../../utils/logger.mjs';
5
+ import { resolvePackage } from './resolvePackage.mjs';
6
+
7
+ async function resolveScopeAlias(scope, rootCwd) {
8
+ const packagesRoot = findPackagesRoot(rootCwd);
9
+ if (!packagesRoot) {
10
+ logger.error(`cannot locate a monorepo root with a "packages/" directory starting from "${rootCwd}". Scope alias "@${scope}" requires a workspace root.`);
11
+ process.exit(2);
12
+ }
13
+ const scopeDir = join(packagesRoot, 'packages', scope);
14
+ let entries;
15
+ try {
16
+ entries = await readdir(scopeDir);
17
+ }
18
+ catch {
19
+ logger.error(`scope alias "@${scope}" has no matching directory at ${scopeDir}.`);
20
+ process.exit(2);
21
+ }
22
+ const matchedNames = [];
23
+ const expectedPrefix = `@${scope}/`;
24
+ for (const entry of entries) {
25
+ const pkgJsonPath = join(scopeDir, entry, 'package.json');
26
+ if (!existsSync(pkgJsonPath))
27
+ continue;
28
+ let parsed;
29
+ try {
30
+ const raw = await readFile(pkgJsonPath, 'utf-8');
31
+ parsed = JSON.parse(raw);
32
+ }
33
+ catch {
34
+ continue;
35
+ }
36
+ if (typeof parsed.name === 'string' &&
37
+ parsed.name.startsWith(expectedPrefix) &&
38
+ parsed.name.length > expectedPrefix.length) {
39
+ matchedNames.push(parsed.name);
40
+ }
41
+ }
42
+ if (matchedNames.length === 0) {
43
+ logger.warn(`scope alias "@${scope}" matched no workspace packages under ${scopeDir}.`);
44
+ return [];
45
+ }
46
+ const resolved = [];
47
+ for (const name of matchedNames) {
48
+ const meta = await resolvePackage(name, { skipMissingAsset: true });
49
+ if (meta)
50
+ resolved.push(meta);
51
+ }
52
+ return resolved;
53
+ }
54
+ function findPackagesRoot(start) {
55
+ let cur = resolve(start);
56
+ while (true) {
57
+ if (existsSync(join(cur, 'package.json')) && existsSync(join(cur, 'packages'))) {
58
+ return cur;
59
+ }
60
+ const parent = dirname(cur);
61
+ if (parent === cur)
62
+ return null;
63
+ cur = parent;
64
+ }
65
+ }
66
+
67
+ export { resolveScopeAlias };
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var logger = require('../../../utils/logger.cjs');
4
+ var classifyTarget = require('./classifyTarget.cjs');
5
+ var resolvePackage = require('./resolvePackage.cjs');
6
+ var resolveScopeAlias = require('./resolveScopeAlias.cjs');
7
+
8
+ async function resolveTargets(targets, rootCwd) {
9
+ if (targets.length === 0)
10
+ return [];
11
+ const isSingleTarget = targets.length === 1;
12
+ const seen = new Set();
13
+ const results = [];
14
+ for (const target of targets) {
15
+ const classification = classifyTarget.classifyTarget(target);
16
+ if (classification.kind === 'invalid') {
17
+ logger.logger.error(classification.reason);
18
+ process.exit(2);
19
+ }
20
+ let candidates;
21
+ if (classification.kind === 'scope') {
22
+ candidates = await resolveScopeAlias.resolveScopeAlias(classification.scope, rootCwd);
23
+ }
24
+ else {
25
+ const meta = await resolvePackage.resolvePackage(classification.name, {
26
+ skipMissingAsset: !isSingleTarget,
27
+ });
28
+ candidates = meta ? [meta] : [];
29
+ }
30
+ for (const meta of candidates) {
31
+ if (!seen.has(meta.packageName)) {
32
+ seen.add(meta.packageName);
33
+ results.push(meta);
34
+ }
35
+ }
36
+ }
37
+ return results;
38
+ }
39
+
40
+ exports.resolveTargets = resolveTargets;
@@ -0,0 +1,15 @@
1
+ import { type ResolvedMetadata } from './resolvePackage.js';
2
+ /**
3
+ * Classify each `--package` value, resolve them all, and dedupe the
4
+ * result by `packageName`.
5
+ *
6
+ * - `@<scope>` values enumerate through `resolveScopeAlias` (soft skip
7
+ * when a workspace package lacks `claude.assetPath`).
8
+ * - `@<scope>/<name>` and `<name>` values go through `resolvePackage`.
9
+ * When there is a single `--package` value, the call is strict
10
+ * (asset-missing → exit 2); otherwise asset-missing is a soft skip
11
+ * so the rest of the batch can proceed.
12
+ *
13
+ * Invalid `--package` values exit with code 2 before any filesystem IO.
14
+ */
15
+ export declare function resolveTargets(targets: readonly string[], rootCwd: string): Promise<ResolvedMetadata[]>;
@@ -0,0 +1,38 @@
1
+ import { logger } from '../../../utils/logger.mjs';
2
+ import { classifyTarget } from './classifyTarget.mjs';
3
+ import { resolvePackage } from './resolvePackage.mjs';
4
+ import { resolveScopeAlias } from './resolveScopeAlias.mjs';
5
+
6
+ async function resolveTargets(targets, rootCwd) {
7
+ if (targets.length === 0)
8
+ return [];
9
+ const isSingleTarget = targets.length === 1;
10
+ const seen = new Set();
11
+ const results = [];
12
+ for (const target of targets) {
13
+ const classification = classifyTarget(target);
14
+ if (classification.kind === 'invalid') {
15
+ logger.error(classification.reason);
16
+ process.exit(2);
17
+ }
18
+ let candidates;
19
+ if (classification.kind === 'scope') {
20
+ candidates = await resolveScopeAlias(classification.scope, rootCwd);
21
+ }
22
+ else {
23
+ const meta = await resolvePackage(classification.name, {
24
+ skipMissingAsset: !isSingleTarget,
25
+ });
26
+ candidates = meta ? [meta] : [];
27
+ }
28
+ for (const meta of candidates) {
29
+ if (!seen.has(meta.packageName)) {
30
+ seen.add(meta.packageName);
31
+ results.push(meta);
32
+ }
33
+ }
34
+ }
35
+ return results;
36
+ }
37
+
38
+ export { resolveTargets };
@@ -6,31 +6,47 @@ var logger = require('../../../utils/logger.cjs');
6
6
  var injectOne = require('./injectOne.cjs');
7
7
  var resolveScopeFlag = require('./resolveScopeFlag.cjs');
8
8
 
9
- async function runInject(flags, options) {
10
- if (!options.packageRoot ||
11
- !options.packageName ||
12
- !options.packageVersion ||
13
- !options.assetPath) {
14
- logger.logger.error('runCli requires { packageRoot, packageName, packageVersion, assetPath }.');
15
- process.exit(2);
9
+ async function runInject(flags, metadataList) {
10
+ if (metadataList.length === 0)
11
+ return;
12
+ for (const metadata of metadataList) {
13
+ if (!node_path.isAbsolute(metadata.packageRoot)) {
14
+ logger.logger.error(`packageRoot must be an absolute path; received: ${metadata.packageRoot}`);
15
+ process.exit(2);
16
+ }
16
17
  }
17
- if (!node_path.isAbsolute(options.packageRoot)) {
18
- logger.logger.error(`packageRoot must be an absolute path; received: ${options.packageRoot}`);
19
- process.exit(2);
20
- }
21
- const assetRoot = node_path.resolve(options.packageRoot, options.assetPath);
22
- const hashesPath = node_path.join(options.packageRoot, 'dist', 'claude-hashes.json');
23
- const hashesPresent = await promises.stat(hashesPath).then(() => true, () => false);
24
- const target = {
25
- name: options.packageName,
26
- version: options.packageVersion,
27
- packageRoot: options.packageRoot,
28
- assetRoot,
29
- hashesPresent,
30
- };
31
18
  const originCwd = flags.root ?? process.cwd();
32
19
  const scope = await resolveScopeFlag.resolveScopeFlag(flags.scope);
33
- await injectOne.injectOne(target, scope, flags, originCwd);
20
+ const fatalOnError = metadataList.length === 1;
21
+ let failureCount = 0;
22
+ for (const metadata of metadataList) {
23
+ const assetRoot = node_path.resolve(metadata.packageRoot, metadata.assetPath);
24
+ const hashesPath = node_path.join(metadata.packageRoot, 'dist', 'claude-hashes.json');
25
+ const hashesPresent = await promises.stat(hashesPath).then(() => true, () => false);
26
+ const target = {
27
+ name: metadata.packageName,
28
+ version: metadata.packageVersion,
29
+ packageRoot: metadata.packageRoot,
30
+ assetRoot,
31
+ hashesPresent,
32
+ };
33
+ let exitCode;
34
+ try {
35
+ exitCode = await injectOne.injectOne(target, scope, flags, originCwd);
36
+ }
37
+ catch (err) {
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ logger.logger.error(`${target.name}: ${msg}`);
40
+ exitCode = 1;
41
+ }
42
+ if (exitCode !== 0) {
43
+ if (fatalOnError)
44
+ process.exit(exitCode);
45
+ failureCount += 1;
46
+ }
47
+ }
48
+ if (failureCount > 0)
49
+ process.exit(1);
34
50
  }
35
51
 
36
52
  exports.runInject = runInject;
@@ -1,2 +1,3 @@
1
- import type { DefaultFlags, RunCliOptions } from '../type.js';
2
- export declare function runInject(flags: DefaultFlags, options: RunCliOptions): Promise<void>;
1
+ import type { DefaultFlags } from '../type.js';
2
+ import type { ResolvedMetadata } from './resolvePackage.js';
3
+ export declare function runInject(flags: DefaultFlags, metadataList: readonly ResolvedMetadata[]): Promise<void>;
@@ -4,31 +4,47 @@ import { logger } from '../../../utils/logger.mjs';
4
4
  import { injectOne } from './injectOne.mjs';
5
5
  import { resolveScopeFlag } from './resolveScopeFlag.mjs';
6
6
 
7
- async function runInject(flags, options) {
8
- if (!options.packageRoot ||
9
- !options.packageName ||
10
- !options.packageVersion ||
11
- !options.assetPath) {
12
- logger.error('runCli requires { packageRoot, packageName, packageVersion, assetPath }.');
13
- process.exit(2);
7
+ async function runInject(flags, metadataList) {
8
+ if (metadataList.length === 0)
9
+ return;
10
+ for (const metadata of metadataList) {
11
+ if (!isAbsolute(metadata.packageRoot)) {
12
+ logger.error(`packageRoot must be an absolute path; received: ${metadata.packageRoot}`);
13
+ process.exit(2);
14
+ }
14
15
  }
15
- if (!isAbsolute(options.packageRoot)) {
16
- logger.error(`packageRoot must be an absolute path; received: ${options.packageRoot}`);
17
- process.exit(2);
18
- }
19
- const assetRoot = resolve(options.packageRoot, options.assetPath);
20
- const hashesPath = join(options.packageRoot, 'dist', 'claude-hashes.json');
21
- const hashesPresent = await stat(hashesPath).then(() => true, () => false);
22
- const target = {
23
- name: options.packageName,
24
- version: options.packageVersion,
25
- packageRoot: options.packageRoot,
26
- assetRoot,
27
- hashesPresent,
28
- };
29
16
  const originCwd = flags.root ?? process.cwd();
30
17
  const scope = await resolveScopeFlag(flags.scope);
31
- await injectOne(target, scope, flags, originCwd);
18
+ const fatalOnError = metadataList.length === 1;
19
+ let failureCount = 0;
20
+ for (const metadata of metadataList) {
21
+ const assetRoot = resolve(metadata.packageRoot, metadata.assetPath);
22
+ const hashesPath = join(metadata.packageRoot, 'dist', 'claude-hashes.json');
23
+ const hashesPresent = await stat(hashesPath).then(() => true, () => false);
24
+ const target = {
25
+ name: metadata.packageName,
26
+ version: metadata.packageVersion,
27
+ packageRoot: metadata.packageRoot,
28
+ assetRoot,
29
+ hashesPresent,
30
+ };
31
+ let exitCode;
32
+ try {
33
+ exitCode = await injectOne(target, scope, flags, originCwd);
34
+ }
35
+ catch (err) {
36
+ const msg = err instanceof Error ? err.message : String(err);
37
+ logger.error(`${target.name}: ${msg}`);
38
+ exitCode = 1;
39
+ }
40
+ if (exitCode !== 0) {
41
+ if (fatalOnError)
42
+ process.exit(exitCode);
43
+ failureCount += 1;
44
+ }
45
+ }
46
+ if (failureCount > 0)
47
+ process.exit(1);
32
48
  }
33
49
 
34
50
  export { runInject };
@@ -13,7 +13,7 @@ async function applyAction(action, assetRoot) {
13
13
  else if (action.kind === 'delete')
14
14
  await promises.unlink(action.dstAbs).catch((error) => {
15
15
  if (error?.code !== 'ENOENT') {
16
- logger.logger.warn(`[claude-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
16
+ logger.logger.warn(`[claude-assets-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
17
17
  }
18
18
  });
19
19
  }
@@ -11,7 +11,7 @@ async function applyAction(action, assetRoot) {
11
11
  else if (action.kind === 'delete')
12
12
  await unlink(action.dstAbs).catch((error) => {
13
13
  if (error?.code !== 'ENOENT') {
14
- logger.warn(`[claude-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
14
+ logger.warn(`[claude-assets-sync] unlink failed: ${action.dstAbs} (${error?.code ?? error})`);
15
15
  }
16
16
  });
17
17
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { runCli, type RunCliOptions } from './commands/index.js';
1
+ export { runCli } from './commands/index.js';
2
2
  export { HASH_MANIFEST_FILENAME, computeNamespacePrefixes, injectDocs, isInteractive, isValidScope, readHashManifest, resolveScope, type HashManifest, type InjectOptions, type InjectReport, type Scope, type ScopeResolution, } from './core/index.js';
3
3
  export type { AssetType } from './utils/types.js';
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const VERSION = '0.2.0';
3
+ const VERSION = '0.3.0';
4
4
 
5
5
  exports.VERSION = VERSION;
@@ -2,4 +2,4 @@
2
2
  * Current package version from package.json
3
3
  * Automatically synchronized during build process
4
4
  */
5
- export declare const VERSION = "0.2.0";
5
+ export declare const VERSION = "0.3.0";
@@ -1,3 +1,3 @@
1
- const VERSION = '0.2.0';
1
+ const VERSION = '0.3.0';
2
2
 
3
3
  export { VERSION };
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: claude-docs-asset-wiring
3
+ description: "Wire a consumer package's docs/claude assets into the @slats/claude-assets-sync engine. Adds package.json.claude.assetPath, points scripts.build:hashes at claude-build-hashes, declares the engine as a dependency, updates CLAUDE.md, and runs the dispatcher smoke test. Idempotent — asks before clobbering."
4
+ user-invocable: true
5
+ disable-model-invocation: true
6
+ argument-hint: <target-package-path>
7
+ ---
8
+
9
+ # claude-docs-asset-wiring
10
+
11
+ Wire a consumer package's `docs/claude/**` into `@slats/claude-assets-sync`
12
+ so end users can inject those assets via the engine's `inject-claude-settings`
13
+ bin. Reference consumer: `packages/canard/schema-form`.
14
+
15
+ The engine is single-dispatcher. Consumers do NOT ship their own bin stubs
16
+ — they declare `claude.assetPath` in `package.json` and let the engine's
17
+ `claude-build-hashes` bin regenerate `dist/claude-hashes.json` during
18
+ build. `src/core/**` never reads `package.json`; only the engine's `bin/`
19
+ layer resolves a single explicitly-named target.
20
+
21
+ **Outcome**
22
+
23
+ ```bash
24
+ npx -p @slats/claude-assets-sync inject-claude-settings \
25
+ --package=<PACKAGE_NAME> \
26
+ --scope=user|project [--dry-run] [--force]
27
+ ```
28
+
29
+ ## Role
30
+
31
+ You are a monorepo wiring specialist. Execute the 6 steps below as a
32
+ single, idempotent procedure. On any conflicting existing value — ask
33
+ the user before overwriting. Never clobber silently.
34
+
35
+ ## Knowledge Resources
36
+
37
+ Consult these files as needed during execution. Do NOT preload everything;
38
+ load on demand.
39
+
40
+ - `knowledge/reference-files.md` — what the consumer should (and should not) own
41
+ - `knowledge/package-json-patches.md` — every required `package.json` edit, with guard conditions
42
+ - `knowledge/claude-md-template.md` — the `## Claude Docs Injector` section to inject into the target `CLAUDE.md`
43
+ - `knowledge/smoke-tests.md` — E2E 8-path matrix via the engine dispatcher
44
+ - `knowledge/dependency-cruiser.md` — optional CI-time isolation rule
45
+ - `knowledge/gotchas.md` — invariants and pitfalls
46
+
47
+ ## Inputs
48
+
49
+ Resolve these before starting. If any is missing, stop and ask.
50
+
51
+ | Variable | Source |
52
+ |----------------|-----------------------------------------------------------------------------------------------------------|
53
+ | `TARGET_PATH` | Skill argument (e.g. `packages/lerx/promise-modal`). If absent, ask the user. |
54
+ | `PACKAGE_NAME` | `name` field of `${TARGET_PATH}/package.json`. |
55
+ | `SHORTCUT` | Root `package.json` `scripts` entry whose value equals `yarn workspace ${PACKAGE_NAME}`; else unset. |
56
+
57
+ `SHORTCUT` is a convenience only. When unset, fall back to full workspace
58
+ syntax: `yarn workspace ${PACKAGE_NAME} <subcommand>`.
59
+
60
+ ## Pre-Flight
61
+
62
+ Stop and report on any failure. Do not attempt to fix silently.
63
+
64
+ - [ ] `${TARGET_PATH}/docs/claude/skills/<name>/SKILL.md` and `knowledge/*.md` exist — the docs to be injected.
65
+ - [ ] `${TARGET_PATH}/package.json` has `"type": "module"` and `"sideEffects": false`.
66
+ - [ ] Build pipeline uses `rollup -c && yarn build:types` where `build:types` runs `node ../../aileron/script/build/buildTypes.mjs`.
67
+ - [ ] `git status` in `${TARGET_PATH}` is clean. Unrelated changes present → confirm with user before proceeding.
68
+
69
+ ## Steps
70
+
71
+ Execute in order. Each step is idempotent; on conflict, ask rather than overwrite.
72
+
73
+ ### Step 1 — Patch `${TARGET_PATH}/package.json`
74
+
75
+ See `knowledge/package-json-patches.md` for the complete patch list:
76
+
77
+ - `claude.assetPath` — set to `docs/claude` (or the consumer's chosen path).
78
+ - `scripts.build` — ensure the chain ends with `&& yarn build:hashes`.
79
+ - `scripts.build:hashes` — set to `claude-build-hashes` (the engine's bin).
80
+ - `scripts.prepublishOnly` — `yarn build` if not already present.
81
+ - `dependencies."@slats/claude-assets-sync"` — add (NOT `devDependencies`, NOT `peerDependencies`).
82
+ - `files` — ensure `"dist"`, `"docs"`, `"README.md"` are listed. Never include `"bin"` or `"scripts"`.
83
+
84
+ Do NOT add any `bin` entry. Do NOT add `./bin/*` or `./docs/*` to `exports`.
85
+ Do NOT create `bin/` or `scripts/` directories in the consumer.
86
+
87
+ ### Step 2 — Patch `${TARGET_PATH}/CLAUDE.md`
88
+
89
+ If `CLAUDE.md` exists, append or replace the `## Claude Docs Injector`
90
+ section from `knowledge/claude-md-template.md`, substituting
91
+ `${PACKAGE_NAME}`. Skip if `CLAUDE.md` does not exist (do not create one).
92
+
93
+ ### Step 3 — (Optional) Dependency-cruiser isolation gate
94
+
95
+ Skip unless `${TARGET_PATH}/.dependency-cruiser.cjs` already exists or the
96
+ user explicitly asks. See `knowledge/dependency-cruiser.md` for the single
97
+ remaining forbidden rule (`src/**` → `docs/**`) and the `depcheck` script.
98
+
99
+ ### Step 4 — Install and build
100
+
101
+ ```bash
102
+ yarn install
103
+ yarn ${SHORTCUT:-workspace ${PACKAGE_NAME}} build
104
+ ```
105
+
106
+ Expected: `rollup` → `buildTypes` → `claude-build-hashes` succeed, and
107
+ `${TARGET_PATH}/dist/claude-hashes.json` is written.
108
+
109
+ ### Step 5 — E2E smoke via engine dispatcher
110
+
111
+ Run from `/tmp/...`, never from the monorepo root or `${TARGET_PATH}/` —
112
+ `--scope=project` walks `cwd` upward looking for an existing `.claude`,
113
+ which would mutate the real repo's. See `knowledge/smoke-tests.md` for
114
+ the full 8-path matrix, expected exit codes, and rationale.
115
+
116
+ ### Step 6 — Report
117
+
118
+ Summarize:
119
+
120
+ - Files patched vs. skipped (with reason for each skip).
121
+ - Manifest file count from `dist/claude-hashes.json`.
122
+ - Smoke-test exit codes (all 8).
123
+ - Recommendation: commit this change on its own, separate from other work.
124
+
125
+ ## Report Template
126
+
127
+ ```markdown
128
+ ## claude-docs-asset-wiring — ${PACKAGE_NAME}
129
+
130
+ **Files patched**
131
+ - package.json — patched: [claude.assetPath, scripts.build, scripts.build:hashes, dependencies, files]
132
+ - CLAUDE.md — section added | skipped (no CLAUDE.md)
133
+ - .dependency-cruiser.cjs — updated | skipped (not present)
134
+
135
+ **Manifest**
136
+ - dist/claude-hashes.json: <N> files
137
+
138
+ **Smoke tests**
139
+ | # | command | expected | actual |
140
+ |---|-------------------------------------------------------------|----------|--------|
141
+ | 1 | --package=${PACKAGE_NAME} --scope=project --dry-run | 0 | <n> |
142
+ | 2 | --package=${PACKAGE_NAME} --scope=project | 0 | <n> |
143
+ | 3 | --package=${PACKAGE_NAME} --scope=project (up-to-date) | 0 | <n> |
144
+ | 4 | CI=true --package=${PACKAGE_NAME} --scope=project (tampered)| 2 | <n> |
145
+ | 5 | CI=true --package=${PACKAGE_NAME} --scope=project --force | 0 | <n> |
146
+ | 6 | CI=true --package=${PACKAGE_NAME} (missing --scope) | 2 | <n> |
147
+ | 7 | (missing --package) | 2 | <n> |
148
+ | 8 | --package=@does/not-exist | 2 | <n> |
149
+
150
+ **Next**: commit on its own — do not bundle with unrelated changes.
151
+ ```
152
+
153
+ ## Termination Conditions
154
+
155
+ - **Pre-Flight fails** → stop, report the failing check. Do not proceed.
156
+ - **Conflict during patch** → stop, show the diff, ask user whether to overwrite.
157
+ - **Build fails at Step 4** → stop, report error. Do not run smoke tests on a broken build.
158
+ - **Smoke test mismatch** → stop, report the failing path with captured exit code.
159
+ - **All steps pass** → emit the report from the template above.