@soleri/cli 1.2.0 → 1.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 (48) hide show
  1. package/README.md +30 -1
  2. package/dist/commands/create.js +5 -2
  3. package/dist/commands/create.js.map +1 -1
  4. package/dist/commands/hooks.js +40 -11
  5. package/dist/commands/hooks.js.map +1 -1
  6. package/dist/hook-packs/a11y/hookify.focus-ring-required.local.md +1 -0
  7. package/dist/hook-packs/a11y/hookify.semantic-html.local.md +1 -0
  8. package/dist/hook-packs/a11y/hookify.ux-touch-targets.local.md +1 -0
  9. package/dist/hook-packs/a11y/manifest.json +1 -0
  10. package/dist/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +1 -0
  11. package/dist/hook-packs/clean-commits/manifest.json +1 -0
  12. package/dist/hook-packs/css-discipline/hookify.no-important.local.md +1 -0
  13. package/dist/hook-packs/css-discipline/hookify.no-inline-styles.local.md +1 -0
  14. package/dist/hook-packs/css-discipline/manifest.json +1 -0
  15. package/dist/hook-packs/full/manifest.json +1 -0
  16. package/dist/hook-packs/installer.d.ts +18 -5
  17. package/dist/hook-packs/installer.js +29 -10
  18. package/dist/hook-packs/installer.js.map +1 -1
  19. package/dist/hook-packs/installer.ts +42 -10
  20. package/dist/hook-packs/registry.d.ts +6 -2
  21. package/dist/hook-packs/registry.js +46 -13
  22. package/dist/hook-packs/registry.js.map +1 -1
  23. package/dist/hook-packs/registry.ts +49 -13
  24. package/dist/hook-packs/typescript-safety/hookify.no-any-types.local.md +1 -0
  25. package/dist/hook-packs/typescript-safety/hookify.no-console-log.local.md +1 -0
  26. package/dist/hook-packs/typescript-safety/manifest.json +1 -0
  27. package/dist/utils/checks.d.ts +1 -0
  28. package/dist/utils/checks.js +17 -0
  29. package/dist/utils/checks.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/commands/create.ts +4 -2
  32. package/src/commands/hooks.ts +54 -11
  33. package/src/hook-packs/a11y/hookify.focus-ring-required.local.md +1 -0
  34. package/src/hook-packs/a11y/hookify.semantic-html.local.md +1 -0
  35. package/src/hook-packs/a11y/hookify.ux-touch-targets.local.md +1 -0
  36. package/src/hook-packs/a11y/manifest.json +1 -0
  37. package/src/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +1 -0
  38. package/src/hook-packs/clean-commits/manifest.json +1 -0
  39. package/src/hook-packs/css-discipline/hookify.no-important.local.md +1 -0
  40. package/src/hook-packs/css-discipline/hookify.no-inline-styles.local.md +1 -0
  41. package/src/hook-packs/css-discipline/manifest.json +1 -0
  42. package/src/hook-packs/full/manifest.json +1 -0
  43. package/src/hook-packs/installer.ts +42 -10
  44. package/src/hook-packs/registry.ts +49 -13
  45. package/src/hook-packs/typescript-safety/hookify.no-any-types.local.md +1 -0
  46. package/src/hook-packs/typescript-safety/hookify.no-console-log.local.md +1 -0
  47. package/src/hook-packs/typescript-safety/manifest.json +1 -0
  48. package/src/utils/checks.ts +18 -0
@@ -8,14 +8,17 @@ import { homedir } from 'node:os';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
10
10
  /** Root directory containing all built-in hook packs. */
11
- function getPacksRoot() {
11
+ function getBuiltinRoot() {
12
12
  return __dirname;
13
13
  }
14
- /**
15
- * List all available built-in hook packs.
16
- */
17
- export function listPacks() {
18
- const root = getPacksRoot();
14
+ /** Local custom packs directory. */
15
+ function getLocalRoot() {
16
+ return join(process.cwd(), '.soleri', 'hook-packs');
17
+ }
18
+ /** Scan a directory for pack manifests. */
19
+ function scanPacksDir(root, source) {
20
+ if (!existsSync(root))
21
+ return [];
19
22
  const entries = readdirSync(root, { withFileTypes: true });
20
23
  const packs = [];
21
24
  for (const entry of entries) {
@@ -26,6 +29,7 @@ export function listPacks() {
26
29
  continue;
27
30
  try {
28
31
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
32
+ manifest.source = source;
29
33
  packs.push(manifest);
30
34
  }
31
35
  catch {
@@ -35,17 +39,46 @@ export function listPacks() {
35
39
  return packs;
36
40
  }
37
41
  /**
38
- * Get a specific pack by name.
42
+ * List all available hook packs (built-in + local custom).
43
+ * Local packs in .soleri/hook-packs/ override built-in packs with the same name.
44
+ */
45
+ export function listPacks() {
46
+ const builtIn = scanPacksDir(getBuiltinRoot(), 'built-in');
47
+ const local = scanPacksDir(getLocalRoot(), 'local');
48
+ // Local packs override built-in packs with same name
49
+ const byName = new Map();
50
+ for (const pack of builtIn)
51
+ byName.set(pack.name, pack);
52
+ for (const pack of local)
53
+ byName.set(pack.name, pack);
54
+ return Array.from(byName.values());
55
+ }
56
+ /**
57
+ * Get a specific pack by name. Local packs take precedence.
39
58
  */
40
59
  export function getPack(name) {
41
- const root = getPacksRoot();
42
- const dir = join(root, name);
43
- const manifestPath = join(dir, 'manifest.json');
44
- if (!existsSync(manifestPath))
60
+ // Check local first
61
+ const localDir = join(getLocalRoot(), name);
62
+ const localManifest = join(localDir, 'manifest.json');
63
+ if (existsSync(localManifest)) {
64
+ try {
65
+ const manifest = JSON.parse(readFileSync(localManifest, 'utf-8'));
66
+ manifest.source = 'local';
67
+ return { manifest, dir: localDir };
68
+ }
69
+ catch {
70
+ // Fall through to built-in
71
+ }
72
+ }
73
+ // Then built-in
74
+ const builtinDir = join(getBuiltinRoot(), name);
75
+ const builtinManifest = join(builtinDir, 'manifest.json');
76
+ if (!existsSync(builtinManifest))
45
77
  return null;
46
78
  try {
47
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
48
- return { manifest, dir };
79
+ const manifest = JSON.parse(readFileSync(builtinManifest, 'utf-8'));
80
+ manifest.source = 'built-in';
81
+ return { manifest, dir: builtinDir };
49
82
  }
50
83
  catch {
51
84
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hook-packs/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AASlC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,yDAAyD;AACzD,SAAS,YAAY;IACnB,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAuB,EAAE,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAS;QAExC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAqB,CAAC;YACrF,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEhD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAqB,CAAC;QACrF,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3C,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,IAAI,WAAW,CAAC,CAAC,CACxD,CAAC;QACF,IAAI,UAAU,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hook-packs/registry.ts"],"names":[],"mappings":"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"}
@@ -11,21 +11,27 @@ export interface HookPackManifest {
11
11
  description: string;
12
12
  hooks: string[];
13
13
  composedFrom?: string[];
14
+ version?: string;
15
+ /** Whether this pack is built-in or user-defined */
16
+ source?: 'built-in' | 'local';
14
17
  }
15
18
 
16
19
  const __filename = fileURLToPath(import.meta.url);
17
20
  const __dirname = dirname(__filename);
18
21
 
19
22
  /** Root directory containing all built-in hook packs. */
20
- function getPacksRoot(): string {
23
+ function getBuiltinRoot(): string {
21
24
  return __dirname;
22
25
  }
23
26
 
24
- /**
25
- * List all available built-in hook packs.
26
- */
27
- export function listPacks(): HookPackManifest[] {
28
- const root = getPacksRoot();
27
+ /** Local custom packs directory. */
28
+ function getLocalRoot(): string {
29
+ return join(process.cwd(), '.soleri', 'hook-packs');
30
+ }
31
+
32
+ /** Scan a directory for pack manifests. */
33
+ function scanPacksDir(root: string, source: 'built-in' | 'local'): HookPackManifest[] {
34
+ if (!existsSync(root)) return [];
29
35
  const entries = readdirSync(root, { withFileTypes: true });
30
36
  const packs: HookPackManifest[] = [];
31
37
 
@@ -36,6 +42,7 @@ export function listPacks(): HookPackManifest[] {
36
42
 
37
43
  try {
38
44
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as HookPackManifest;
45
+ manifest.source = source;
39
46
  packs.push(manifest);
40
47
  } catch {
41
48
  // Skip malformed manifests
@@ -46,18 +53,47 @@ export function listPacks(): HookPackManifest[] {
46
53
  }
47
54
 
48
55
  /**
49
- * Get a specific pack by name.
56
+ * List all available hook packs (built-in + local custom).
57
+ * Local packs in .soleri/hook-packs/ override built-in packs with the same name.
58
+ */
59
+ export function listPacks(): HookPackManifest[] {
60
+ const builtIn = scanPacksDir(getBuiltinRoot(), 'built-in');
61
+ const local = scanPacksDir(getLocalRoot(), 'local');
62
+
63
+ // Local packs override built-in packs with same name
64
+ const byName = new Map<string, HookPackManifest>();
65
+ for (const pack of builtIn) byName.set(pack.name, pack);
66
+ for (const pack of local) byName.set(pack.name, pack);
67
+
68
+ return Array.from(byName.values());
69
+ }
70
+
71
+ /**
72
+ * Get a specific pack by name. Local packs take precedence.
50
73
  */
51
74
  export function getPack(name: string): { manifest: HookPackManifest; dir: string } | null {
52
- const root = getPacksRoot();
53
- const dir = join(root, name);
54
- const manifestPath = join(dir, 'manifest.json');
75
+ // Check local first
76
+ const localDir = join(getLocalRoot(), name);
77
+ const localManifest = join(localDir, 'manifest.json');
78
+ if (existsSync(localManifest)) {
79
+ try {
80
+ const manifest = JSON.parse(readFileSync(localManifest, 'utf-8')) as HookPackManifest;
81
+ manifest.source = 'local';
82
+ return { manifest, dir: localDir };
83
+ } catch {
84
+ // Fall through to built-in
85
+ }
86
+ }
55
87
 
56
- if (!existsSync(manifestPath)) return null;
88
+ // Then built-in
89
+ const builtinDir = join(getBuiltinRoot(), name);
90
+ const builtinManifest = join(builtinDir, 'manifest.json');
91
+ if (!existsSync(builtinManifest)) return null;
57
92
 
58
93
  try {
59
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as HookPackManifest;
60
- return { manifest, dir };
94
+ const manifest = JSON.parse(readFileSync(builtinManifest, 'utf-8')) as HookPackManifest;
95
+ manifest.source = 'built-in';
96
+ return { manifest, dir: builtinDir };
61
97
  } catch {
62
98
  return null;
63
99
  }
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: typescript-safety
3
+ # Version: 1.0.0
3
4
  # Rule: no-any-types
4
5
  name: no-any-types
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: typescript-safety
3
+ # Version: 1.0.0
3
4
  # Rule: no-console-log
4
5
  name: no-console-log
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "typescript-safety",
3
+ "version": "1.0.0",
3
4
  "description": "Block unsafe TypeScript patterns",
4
5
  "hooks": ["no-any-types", "no-console-log"]
5
6
  }
@@ -8,5 +8,6 @@ export declare function checkNpm(): CheckResult;
8
8
  export declare function checkAgentProject(dir?: string): CheckResult;
9
9
  export declare function checkAgentBuild(dir?: string): CheckResult;
10
10
  export declare function checkNodeModules(dir?: string): CheckResult;
11
+ export declare function checkHookPacks(): CheckResult;
11
12
  export declare function runAllChecks(dir?: string): CheckResult[];
12
13
  export {};
@@ -6,6 +6,7 @@ import { join } from 'node:path';
6
6
  import { execFileSync } from 'node:child_process';
7
7
  import { homedir } from 'node:os';
8
8
  import { detectAgent } from './agent-context.js';
9
+ import { getInstalledPacks } from '../hook-packs/registry.js';
9
10
  export function checkNodeVersion() {
10
11
  const [major] = process.versions.node.split('.').map(Number);
11
12
  if (major >= 18) {
@@ -123,6 +124,21 @@ function checkCognee() {
123
124
  };
124
125
  }
125
126
  }
127
+ export function checkHookPacks() {
128
+ const installed = getInstalledPacks();
129
+ if (installed.length === 0) {
130
+ return {
131
+ status: 'warn',
132
+ label: 'Hook packs',
133
+ detail: 'none installed — run soleri hooks list-packs',
134
+ };
135
+ }
136
+ return {
137
+ status: 'pass',
138
+ label: 'Hook packs',
139
+ detail: installed.join(', '),
140
+ };
141
+ }
126
142
  export function runAllChecks(dir) {
127
143
  return [
128
144
  checkNodeVersion(),
@@ -132,6 +148,7 @@ export function runAllChecks(dir) {
132
148
  checkNodeModules(dir),
133
149
  checkAgentBuild(dir),
134
150
  checkMcpRegistration(dir),
151
+ checkHookPacks(),
135
152
  checkCognee(),
136
153
  ];
137
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/utils/checks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/utils/checks.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleri/cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Developer CLI for creating and managing Soleri AI agents.",
5
5
  "keywords": [
6
6
  "agent",
@@ -40,9 +40,11 @@ export function registerCreate(program: Command): void {
40
40
  }
41
41
  }
42
42
 
43
- // Hook packs (interactive only)
43
+ // Hook packs — from config file or interactive prompt
44
44
  let selectedPacks: string[] = [];
45
- if (!opts?.config) {
45
+ if (config.hookPacks && config.hookPacks.length > 0) {
46
+ selectedPacks = config.hookPacks;
47
+ } else if (!opts?.config) {
46
48
  const packs = listPacks();
47
49
  const packChoices = packs.map((pk) => ({
48
50
  value: pk.name,
@@ -87,8 +87,9 @@ export function registerHooks(program: Command): void {
87
87
  hooks
88
88
  .command('add-pack')
89
89
  .argument('<pack>', 'Hook pack name')
90
- .description('Install a hook pack globally (~/.claude/)')
91
- .action((packName: string) => {
90
+ .option('--project', 'Install to project .claude/ instead of global ~/.claude/')
91
+ .description('Install a hook pack globally (~/.claude/) or per-project (--project)')
92
+ .action((packName: string, opts: { project?: boolean }) => {
92
93
  const pack = getPack(packName);
93
94
  if (!pack) {
94
95
  const available = listPacks().map((p) => p.name);
@@ -96,15 +97,17 @@ export function registerHooks(program: Command): void {
96
97
  process.exit(1);
97
98
  }
98
99
 
99
- const { installed, skipped } = installPack(packName);
100
+ const projectDir = opts.project ? process.cwd() : undefined;
101
+ const target = opts.project ? '.claude/' : '~/.claude/';
102
+ const { installed, skipped } = installPack(packName, { projectDir });
100
103
  for (const hook of installed) {
101
- log.pass(`Installed hookify.${hook}.local.md`);
104
+ log.pass(`Installed hookify.${hook}.local.md → ${target}`);
102
105
  }
103
106
  for (const hook of skipped) {
104
107
  log.dim(` hookify.${hook}.local.md — already exists, skipped`);
105
108
  }
106
109
  if (installed.length > 0) {
107
- log.info(`Pack "${packName}" installed (${installed.length} hooks)`);
110
+ log.info(`Pack "${packName}" installed (${installed.length} hooks) → ${target}`);
108
111
  } else {
109
112
  log.info(`Pack "${packName}" — all hooks already installed`);
110
113
  }
@@ -113,8 +116,9 @@ export function registerHooks(program: Command): void {
113
116
  hooks
114
117
  .command('remove-pack')
115
118
  .argument('<pack>', 'Hook pack name')
116
- .description('Remove a hook pack from ~/.claude/')
117
- .action((packName: string) => {
119
+ .option('--project', 'Remove from project .claude/ instead of global ~/.claude/')
120
+ .description('Remove a hook pack')
121
+ .action((packName: string, opts: { project?: boolean }) => {
118
122
  const pack = getPack(packName);
119
123
  if (!pack) {
120
124
  const available = listPacks().map((p) => p.name);
@@ -122,7 +126,8 @@ export function registerHooks(program: Command): void {
122
126
  process.exit(1);
123
127
  }
124
128
 
125
- const { removed } = removePack(packName);
129
+ const projectDir = opts.project ? process.cwd() : undefined;
130
+ const { removed } = removePack(packName, { projectDir });
126
131
  if (removed.length === 0) {
127
132
  log.info(`No hooks from pack "${packName}" found to remove.`);
128
133
  } else {
@@ -144,15 +149,53 @@ export function registerHooks(program: Command): void {
144
149
  for (const pack of packs) {
145
150
  const status = isPackInstalled(pack.name);
146
151
 
152
+ const versionLabel = pack.version ? ` v${pack.version}` : '';
153
+ const sourceLabel = pack.source === 'local' ? ' [local]' : '';
154
+
147
155
  if (status === true) {
148
- log.pass(`${pack.name}`, `${pack.description} (${pack.hooks.length} hooks)`);
156
+ log.pass(
157
+ `${pack.name}${versionLabel}${sourceLabel}`,
158
+ `${pack.description} (${pack.hooks.length} hooks)`,
159
+ );
149
160
  } else if (status === 'partial') {
150
- log.warn(`${pack.name}`, `${pack.description} (${pack.hooks.length} hooks) — partial`);
161
+ log.warn(
162
+ `${pack.name}${versionLabel}${sourceLabel}`,
163
+ `${pack.description} (${pack.hooks.length} hooks) — partial`,
164
+ );
151
165
  } else {
152
- log.dim(` ${pack.name} — ${pack.description} (${pack.hooks.length} hooks)`);
166
+ log.dim(
167
+ ` ${pack.name}${versionLabel}${sourceLabel} — ${pack.description} (${pack.hooks.length} hooks)`,
168
+ );
153
169
  }
154
170
  }
155
171
  });
172
+
173
+ hooks
174
+ .command('upgrade-pack')
175
+ .argument('<pack>', 'Hook pack name')
176
+ .option('--project', 'Upgrade in project .claude/ instead of global ~/.claude/')
177
+ .description('Upgrade a hook pack to the latest version (overwrites existing files)')
178
+ .action((packName: string, opts: { project?: boolean }) => {
179
+ const pack = getPack(packName);
180
+ if (!pack) {
181
+ const available = listPacks().map((p) => p.name);
182
+ log.fail(`Unknown pack "${packName}". Available: ${available.join(', ')}`);
183
+ process.exit(1);
184
+ }
185
+
186
+ const projectDir = opts.project ? process.cwd() : undefined;
187
+ const packVersion = pack.manifest.version ?? 'unknown';
188
+
189
+ // Remove then reinstall to force overwrite
190
+ removePack(packName, { projectDir });
191
+ const { installed } = installPack(packName, { projectDir });
192
+
193
+ for (const hook of installed) {
194
+ log.pass(`hookify.${hook}.local.md → v${packVersion}`);
195
+ }
196
+
197
+ log.info(`Pack "${packName}" upgraded to v${packVersion} (${installed.length} hooks)`);
198
+ });
156
199
  }
157
200
 
158
201
  function isValidEditor(editor: string): editor is EditorId {
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: a11y
3
+ # Version: 1.0.0
3
4
  # Rule: focus-ring-required
4
5
  name: focus-ring-required
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: a11y
3
+ # Version: 1.0.0
3
4
  # Rule: semantic-html
4
5
  name: semantic-html
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: a11y
3
+ # Version: 1.0.0
3
4
  # Rule: ux-touch-targets
4
5
  name: ux-touch-targets
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "a11y",
3
+ "version": "1.0.0",
3
4
  "description": "Accessibility enforcement",
4
5
  "hooks": ["semantic-html", "focus-ring-required", "ux-touch-targets"]
5
6
  }
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: clean-commits
3
+ # Version: 1.0.0
3
4
  # Rule: no-ai-attribution
4
5
  name: no-ai-attribution
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "clean-commits",
3
+ "version": "1.0.0",
3
4
  "description": "No AI attribution in git commits",
4
5
  "hooks": ["no-ai-attribution"]
5
6
  }
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: css-discipline
3
+ # Version: 1.0.0
3
4
  # Rule: no-important
4
5
  name: no-important
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # Soleri Hook Pack: css-discipline
3
+ # Version: 1.0.0
3
4
  # Rule: no-inline-styles
4
5
  name: no-inline-styles
5
6
  enabled: true
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "css-discipline",
3
+ "version": "1.0.0",
3
4
  "description": "CSS best practices",
4
5
  "hooks": ["no-important", "no-inline-styles"]
5
6
  }
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "full",
3
+ "version": "1.0.0",
3
4
  "description": "Complete quality suite — all 8 hooks",
4
5
  "hooks": ["no-any-types", "no-console-log", "no-important", "no-inline-styles", "semantic-html", "focus-ring-required", "ux-touch-targets", "no-ai-attribution"],
5
6
  "composedFrom": ["typescript-safety", "a11y", "css-discipline", "clean-commits"]
@@ -1,11 +1,17 @@
1
1
  /**
2
- * Hook pack installer — copies hookify files to ~/.claude/ for global enforcement.
2
+ * Hook pack installer — copies hookify files to ~/.claude/ (global) or project .claude/ (local).
3
3
  */
4
- import { existsSync, copyFileSync, unlinkSync, mkdirSync } from 'node:fs';
4
+ import { existsSync, copyFileSync, unlinkSync, mkdirSync, readFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
6
  import { homedir } from 'node:os';
7
7
  import { getPack } from './registry.js';
8
8
 
9
+ /** Resolve the target .claude/ directory. */
10
+ function resolveClaudeDir(projectDir?: string): string {
11
+ if (projectDir) return join(projectDir, '.claude');
12
+ return join(homedir(), '.claude');
13
+ }
14
+
9
15
  /**
10
16
  * Resolve all hookify file paths for a pack, handling composed packs.
11
17
  * Returns a map of hook name → source file path.
@@ -38,16 +44,19 @@ function resolveHookFiles(packName: string): Map<string, string> {
38
44
  }
39
45
 
40
46
  /**
41
- * Install a hook pack globally to ~/.claude/.
47
+ * Install a hook pack to ~/.claude/ (default) or project .claude/ (--project).
42
48
  * Skips files that already exist (idempotent).
43
49
  */
44
- export function installPack(packName: string): { installed: string[]; skipped: string[] } {
50
+ export function installPack(
51
+ packName: string,
52
+ options?: { projectDir?: string },
53
+ ): { installed: string[]; skipped: string[] } {
45
54
  const pack = getPack(packName);
46
55
  if (!pack) {
47
56
  throw new Error(`Unknown hook pack: "${packName}"`);
48
57
  }
49
58
 
50
- const claudeDir = join(homedir(), '.claude');
59
+ const claudeDir = resolveClaudeDir(options?.projectDir);
51
60
  mkdirSync(claudeDir, { recursive: true });
52
61
 
53
62
  const hookFiles = resolveHookFiles(packName);
@@ -68,15 +77,18 @@ export function installPack(packName: string): { installed: string[]; skipped: s
68
77
  }
69
78
 
70
79
  /**
71
- * Remove a hook pack's files from ~/.claude/.
80
+ * Remove a hook pack's files from target directory.
72
81
  */
73
- export function removePack(packName: string): { removed: string[] } {
82
+ export function removePack(
83
+ packName: string,
84
+ options?: { projectDir?: string },
85
+ ): { removed: string[] } {
74
86
  const pack = getPack(packName);
75
87
  if (!pack) {
76
88
  throw new Error(`Unknown hook pack: "${packName}"`);
77
89
  }
78
90
 
79
- const claudeDir = join(homedir(), '.claude');
91
+ const claudeDir = resolveClaudeDir(options?.projectDir);
80
92
  const removed: string[] = [];
81
93
 
82
94
  for (const hook of pack.manifest.hooks) {
@@ -94,11 +106,14 @@ export function removePack(packName: string): { removed: string[] } {
94
106
  * Check if a pack is installed.
95
107
  * Returns true (all hooks present), false (none present), or 'partial'.
96
108
  */
97
- export function isPackInstalled(packName: string): boolean | 'partial' {
109
+ export function isPackInstalled(
110
+ packName: string,
111
+ options?: { projectDir?: string },
112
+ ): boolean | 'partial' {
98
113
  const pack = getPack(packName);
99
114
  if (!pack) return false;
100
115
 
101
- const claudeDir = join(homedir(), '.claude');
116
+ const claudeDir = resolveClaudeDir(options?.projectDir);
102
117
  let present = 0;
103
118
 
104
119
  for (const hook of pack.manifest.hooks) {
@@ -111,3 +126,20 @@ export function isPackInstalled(packName: string): boolean | 'partial' {
111
126
  if (present === pack.manifest.hooks.length) return true;
112
127
  return 'partial';
113
128
  }
129
+
130
+ /**
131
+ * Get the installed version of a hook from its file header.
132
+ * Returns null if no version found or file doesn't exist.
133
+ */
134
+ export function getInstalledHookVersion(
135
+ hook: string,
136
+ options?: { projectDir?: string },
137
+ ): string | null {
138
+ const claudeDir = resolveClaudeDir(options?.projectDir);
139
+ const filePath = join(claudeDir, `hookify.${hook}.local.md`);
140
+ if (!existsSync(filePath)) return null;
141
+
142
+ const content = readFileSync(filePath, 'utf-8');
143
+ const match = content.match(/^# Version: (.+)$/m);
144
+ return match ? match[1] : null;
145
+ }