cc4pm 1.8.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 (108) hide show
  1. package/.claude-plugin/README.md +17 -0
  2. package/.claude-plugin/plugin.json +25 -0
  3. package/LICENSE +21 -0
  4. package/README.md +157 -0
  5. package/README.zh-CN.md +134 -0
  6. package/contexts/dev.md +20 -0
  7. package/contexts/research.md +26 -0
  8. package/contexts/review.md +22 -0
  9. package/examples/CLAUDE.md +100 -0
  10. package/examples/statusline.json +19 -0
  11. package/examples/user-CLAUDE.md +109 -0
  12. package/install.sh +17 -0
  13. package/manifests/install-components.json +173 -0
  14. package/manifests/install-modules.json +335 -0
  15. package/manifests/install-profiles.json +75 -0
  16. package/package.json +117 -0
  17. package/schemas/ecc-install-config.schema.json +58 -0
  18. package/schemas/hooks.schema.json +197 -0
  19. package/schemas/install-components.schema.json +56 -0
  20. package/schemas/install-modules.schema.json +105 -0
  21. package/schemas/install-profiles.schema.json +45 -0
  22. package/schemas/install-state.schema.json +210 -0
  23. package/schemas/package-manager.schema.json +23 -0
  24. package/schemas/plugin.schema.json +58 -0
  25. package/scripts/ci/catalog.js +83 -0
  26. package/scripts/ci/validate-agents.js +81 -0
  27. package/scripts/ci/validate-commands.js +135 -0
  28. package/scripts/ci/validate-hooks.js +239 -0
  29. package/scripts/ci/validate-install-manifests.js +211 -0
  30. package/scripts/ci/validate-no-personal-paths.js +63 -0
  31. package/scripts/ci/validate-rules.js +81 -0
  32. package/scripts/ci/validate-skills.js +54 -0
  33. package/scripts/claw.js +468 -0
  34. package/scripts/doctor.js +110 -0
  35. package/scripts/ecc.js +194 -0
  36. package/scripts/hooks/auto-tmux-dev.js +88 -0
  37. package/scripts/hooks/check-console-log.js +71 -0
  38. package/scripts/hooks/check-hook-enabled.js +12 -0
  39. package/scripts/hooks/cost-tracker.js +78 -0
  40. package/scripts/hooks/doc-file-warning.js +63 -0
  41. package/scripts/hooks/evaluate-session.js +100 -0
  42. package/scripts/hooks/insaits-security-monitor.py +269 -0
  43. package/scripts/hooks/insaits-security-wrapper.js +88 -0
  44. package/scripts/hooks/post-bash-build-complete.js +27 -0
  45. package/scripts/hooks/post-bash-pr-created.js +36 -0
  46. package/scripts/hooks/post-edit-console-warn.js +54 -0
  47. package/scripts/hooks/post-edit-format.js +109 -0
  48. package/scripts/hooks/post-edit-typecheck.js +96 -0
  49. package/scripts/hooks/pre-bash-dev-server-block.js +187 -0
  50. package/scripts/hooks/pre-bash-git-push-reminder.js +28 -0
  51. package/scripts/hooks/pre-bash-tmux-reminder.js +33 -0
  52. package/scripts/hooks/pre-compact.js +48 -0
  53. package/scripts/hooks/pre-write-doc-warn.js +9 -0
  54. package/scripts/hooks/quality-gate.js +168 -0
  55. package/scripts/hooks/run-with-flags-shell.sh +32 -0
  56. package/scripts/hooks/run-with-flags.js +120 -0
  57. package/scripts/hooks/session-end-marker.js +15 -0
  58. package/scripts/hooks/session-end.js +299 -0
  59. package/scripts/hooks/session-start.js +97 -0
  60. package/scripts/hooks/suggest-compact.js +80 -0
  61. package/scripts/install-apply.js +137 -0
  62. package/scripts/install-plan.js +254 -0
  63. package/scripts/lib/hook-flags.js +74 -0
  64. package/scripts/lib/install/apply.js +23 -0
  65. package/scripts/lib/install/config.js +82 -0
  66. package/scripts/lib/install/request.js +113 -0
  67. package/scripts/lib/install/runtime.js +42 -0
  68. package/scripts/lib/install-executor.js +605 -0
  69. package/scripts/lib/install-lifecycle.js +763 -0
  70. package/scripts/lib/install-manifests.js +305 -0
  71. package/scripts/lib/install-state.js +120 -0
  72. package/scripts/lib/install-targets/antigravity-project.js +9 -0
  73. package/scripts/lib/install-targets/claude-home.js +10 -0
  74. package/scripts/lib/install-targets/codex-home.js +10 -0
  75. package/scripts/lib/install-targets/cursor-project.js +10 -0
  76. package/scripts/lib/install-targets/helpers.js +89 -0
  77. package/scripts/lib/install-targets/opencode-home.js +10 -0
  78. package/scripts/lib/install-targets/registry.js +64 -0
  79. package/scripts/lib/orchestration-session.js +299 -0
  80. package/scripts/lib/package-manager.d.ts +119 -0
  81. package/scripts/lib/package-manager.js +431 -0
  82. package/scripts/lib/project-detect.js +428 -0
  83. package/scripts/lib/resolve-formatter.js +185 -0
  84. package/scripts/lib/session-adapters/canonical-session.js +138 -0
  85. package/scripts/lib/session-adapters/claude-history.js +149 -0
  86. package/scripts/lib/session-adapters/dmux-tmux.js +80 -0
  87. package/scripts/lib/session-adapters/registry.js +111 -0
  88. package/scripts/lib/session-aliases.d.ts +136 -0
  89. package/scripts/lib/session-aliases.js +481 -0
  90. package/scripts/lib/session-manager.d.ts +131 -0
  91. package/scripts/lib/session-manager.js +464 -0
  92. package/scripts/lib/shell-split.js +86 -0
  93. package/scripts/lib/skill-improvement/amendify.js +89 -0
  94. package/scripts/lib/skill-improvement/evaluate.js +59 -0
  95. package/scripts/lib/skill-improvement/health.js +118 -0
  96. package/scripts/lib/skill-improvement/observations.js +108 -0
  97. package/scripts/lib/tmux-worktree-orchestrator.js +491 -0
  98. package/scripts/lib/utils.d.ts +183 -0
  99. package/scripts/lib/utils.js +543 -0
  100. package/scripts/list-installed.js +90 -0
  101. package/scripts/orchestrate-codex-worker.sh +92 -0
  102. package/scripts/orchestrate-worktrees.js +108 -0
  103. package/scripts/orchestration-status.js +62 -0
  104. package/scripts/repair.js +97 -0
  105. package/scripts/session-inspect.js +150 -0
  106. package/scripts/setup-package-manager.js +204 -0
  107. package/scripts/skill-create-output.js +244 -0
  108. package/scripts/uninstall.js +96 -0
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Package Manager Detection and Selection
3
+ * Automatically detects the preferred package manager or lets user choose
4
+ *
5
+ * Supports: npm, pnpm, yarn, bun
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { commandExists, getClaudeDir, readFile, writeFile } = require('./utils');
11
+
12
+ // Package manager definitions
13
+ const PACKAGE_MANAGERS = {
14
+ npm: {
15
+ name: 'npm',
16
+ lockFile: 'package-lock.json',
17
+ installCmd: 'npm install',
18
+ runCmd: 'npm run',
19
+ execCmd: 'npx',
20
+ testCmd: 'npm test',
21
+ buildCmd: 'npm run build',
22
+ devCmd: 'npm run dev'
23
+ },
24
+ pnpm: {
25
+ name: 'pnpm',
26
+ lockFile: 'pnpm-lock.yaml',
27
+ installCmd: 'pnpm install',
28
+ runCmd: 'pnpm',
29
+ execCmd: 'pnpm dlx',
30
+ testCmd: 'pnpm test',
31
+ buildCmd: 'pnpm build',
32
+ devCmd: 'pnpm dev'
33
+ },
34
+ yarn: {
35
+ name: 'yarn',
36
+ lockFile: 'yarn.lock',
37
+ installCmd: 'yarn',
38
+ runCmd: 'yarn',
39
+ execCmd: 'yarn dlx',
40
+ testCmd: 'yarn test',
41
+ buildCmd: 'yarn build',
42
+ devCmd: 'yarn dev'
43
+ },
44
+ bun: {
45
+ name: 'bun',
46
+ lockFile: 'bun.lockb',
47
+ installCmd: 'bun install',
48
+ runCmd: 'bun run',
49
+ execCmd: 'bunx',
50
+ testCmd: 'bun test',
51
+ buildCmd: 'bun run build',
52
+ devCmd: 'bun run dev'
53
+ }
54
+ };
55
+
56
+ // Priority order for detection
57
+ const DETECTION_PRIORITY = ['pnpm', 'bun', 'yarn', 'npm'];
58
+
59
+ // Config file path
60
+ function getConfigPath() {
61
+ return path.join(getClaudeDir(), 'package-manager.json');
62
+ }
63
+
64
+ /**
65
+ * Load saved package manager configuration
66
+ */
67
+ function loadConfig() {
68
+ const configPath = getConfigPath();
69
+ const content = readFile(configPath);
70
+
71
+ if (content) {
72
+ try {
73
+ return JSON.parse(content);
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Save package manager configuration
83
+ */
84
+ function saveConfig(config) {
85
+ const configPath = getConfigPath();
86
+ writeFile(configPath, JSON.stringify(config, null, 2));
87
+ }
88
+
89
+ /**
90
+ * Detect package manager from lock file in project directory
91
+ */
92
+ function detectFromLockFile(projectDir = process.cwd()) {
93
+ for (const pmName of DETECTION_PRIORITY) {
94
+ const pm = PACKAGE_MANAGERS[pmName];
95
+ const lockFilePath = path.join(projectDir, pm.lockFile);
96
+
97
+ if (fs.existsSync(lockFilePath)) {
98
+ return pmName;
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+
104
+ /**
105
+ * Detect package manager from package.json packageManager field
106
+ */
107
+ function detectFromPackageJson(projectDir = process.cwd()) {
108
+ const packageJsonPath = path.join(projectDir, 'package.json');
109
+ const content = readFile(packageJsonPath);
110
+
111
+ if (content) {
112
+ try {
113
+ const pkg = JSON.parse(content);
114
+ if (pkg.packageManager) {
115
+ // Format: "pnpm@8.6.0" or just "pnpm"
116
+ const pmName = pkg.packageManager.split('@')[0];
117
+ if (PACKAGE_MANAGERS[pmName]) {
118
+ return pmName;
119
+ }
120
+ }
121
+ } catch {
122
+ // Invalid package.json
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ /**
129
+ * Get available package managers (installed on system)
130
+ *
131
+ * WARNING: This spawns child processes (where.exe on Windows, which on Unix)
132
+ * for each package manager. Do NOT call this during session startup hooks —
133
+ * it can exceed Bun's spawn limit on Windows and freeze the plugin.
134
+ * Use detectFromLockFile() or detectFromPackageJson() for hot paths.
135
+ */
136
+ function getAvailablePackageManagers() {
137
+ const available = [];
138
+
139
+ for (const pmName of Object.keys(PACKAGE_MANAGERS)) {
140
+ if (commandExists(pmName)) {
141
+ available.push(pmName);
142
+ }
143
+ }
144
+
145
+ return available;
146
+ }
147
+
148
+ /**
149
+ * Get the package manager to use for current project
150
+ *
151
+ * Detection priority:
152
+ * 1. Environment variable CLAUDE_PACKAGE_MANAGER
153
+ * 2. Project-specific config (in .claude/package-manager.json)
154
+ * 3. package.json packageManager field
155
+ * 4. Lock file detection
156
+ * 5. Global user preference (in ~/.claude/package-manager.json)
157
+ * 6. Default to npm (no child processes spawned)
158
+ *
159
+ * @param {object} options - Options
160
+ * @param {string} options.projectDir - Project directory to detect from (default: cwd)
161
+ * @returns {object} - { name, config, source }
162
+ */
163
+ function getPackageManager(options = {}) {
164
+ const { projectDir = process.cwd() } = options;
165
+
166
+ // 1. Check environment variable
167
+ const envPm = process.env.CLAUDE_PACKAGE_MANAGER;
168
+ if (envPm && PACKAGE_MANAGERS[envPm]) {
169
+ return {
170
+ name: envPm,
171
+ config: PACKAGE_MANAGERS[envPm],
172
+ source: 'environment'
173
+ };
174
+ }
175
+
176
+ // 2. Check project-specific config
177
+ const projectConfigPath = path.join(projectDir, '.claude', 'package-manager.json');
178
+ const projectConfig = readFile(projectConfigPath);
179
+ if (projectConfig) {
180
+ try {
181
+ const config = JSON.parse(projectConfig);
182
+ if (config.packageManager && PACKAGE_MANAGERS[config.packageManager]) {
183
+ return {
184
+ name: config.packageManager,
185
+ config: PACKAGE_MANAGERS[config.packageManager],
186
+ source: 'project-config'
187
+ };
188
+ }
189
+ } catch {
190
+ // Invalid config
191
+ }
192
+ }
193
+
194
+ // 3. Check package.json packageManager field
195
+ const fromPackageJson = detectFromPackageJson(projectDir);
196
+ if (fromPackageJson) {
197
+ return {
198
+ name: fromPackageJson,
199
+ config: PACKAGE_MANAGERS[fromPackageJson],
200
+ source: 'package.json'
201
+ };
202
+ }
203
+
204
+ // 4. Check lock file
205
+ const fromLockFile = detectFromLockFile(projectDir);
206
+ if (fromLockFile) {
207
+ return {
208
+ name: fromLockFile,
209
+ config: PACKAGE_MANAGERS[fromLockFile],
210
+ source: 'lock-file'
211
+ };
212
+ }
213
+
214
+ // 5. Check global user preference
215
+ const globalConfig = loadConfig();
216
+ if (globalConfig && globalConfig.packageManager && PACKAGE_MANAGERS[globalConfig.packageManager]) {
217
+ return {
218
+ name: globalConfig.packageManager,
219
+ config: PACKAGE_MANAGERS[globalConfig.packageManager],
220
+ source: 'global-config'
221
+ };
222
+ }
223
+
224
+ // 6. Default to npm (always available with Node.js)
225
+ // NOTE: Previously this called getAvailablePackageManagers() which spawns
226
+ // child processes (where.exe/which) for each PM. This caused plugin freezes
227
+ // on Windows (see #162) because session-start hooks run during Bun init,
228
+ // and the spawned processes exceed Bun's spawn limit.
229
+ // Steps 1-5 already cover all config-based and file-based detection.
230
+ // If none matched, npm is the safe default.
231
+ return {
232
+ name: 'npm',
233
+ config: PACKAGE_MANAGERS.npm,
234
+ source: 'default'
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Set user's preferred package manager (global)
240
+ */
241
+ function setPreferredPackageManager(pmName) {
242
+ if (!PACKAGE_MANAGERS[pmName]) {
243
+ throw new Error(`Unknown package manager: ${pmName}`);
244
+ }
245
+
246
+ const config = loadConfig() || {};
247
+ config.packageManager = pmName;
248
+ config.setAt = new Date().toISOString();
249
+
250
+ try {
251
+ saveConfig(config);
252
+ } catch (err) {
253
+ throw new Error(`Failed to save package manager preference: ${err.message}`);
254
+ }
255
+
256
+ return config;
257
+ }
258
+
259
+ /**
260
+ * Set project's preferred package manager
261
+ */
262
+ function setProjectPackageManager(pmName, projectDir = process.cwd()) {
263
+ if (!PACKAGE_MANAGERS[pmName]) {
264
+ throw new Error(`Unknown package manager: ${pmName}`);
265
+ }
266
+
267
+ const configDir = path.join(projectDir, '.claude');
268
+ const configPath = path.join(configDir, 'package-manager.json');
269
+
270
+ const config = {
271
+ packageManager: pmName,
272
+ setAt: new Date().toISOString()
273
+ };
274
+
275
+ try {
276
+ writeFile(configPath, JSON.stringify(config, null, 2));
277
+ } catch (err) {
278
+ throw new Error(`Failed to save package manager config to ${configPath}: ${err.message}`);
279
+ }
280
+ return config;
281
+ }
282
+
283
+ // Allowed characters in script/binary names: alphanumeric, dash, underscore, dot, slash, @
284
+ // This prevents shell metacharacter injection while allowing scoped packages (e.g., @scope/pkg)
285
+ const SAFE_NAME_REGEX = /^[@a-zA-Z0-9_./-]+$/;
286
+
287
+ /**
288
+ * Get the command to run a script
289
+ * @param {string} script - Script name (e.g., "dev", "build", "test")
290
+ * @param {object} options - { projectDir }
291
+ * @throws {Error} If script name contains unsafe characters
292
+ */
293
+ function getRunCommand(script, options = {}) {
294
+ if (!script || typeof script !== 'string') {
295
+ throw new Error('Script name must be a non-empty string');
296
+ }
297
+ if (!SAFE_NAME_REGEX.test(script)) {
298
+ throw new Error(`Script name contains unsafe characters: ${script}`);
299
+ }
300
+
301
+ const pm = getPackageManager(options);
302
+
303
+ switch (script) {
304
+ case 'install':
305
+ return pm.config.installCmd;
306
+ case 'test':
307
+ return pm.config.testCmd;
308
+ case 'build':
309
+ return pm.config.buildCmd;
310
+ case 'dev':
311
+ return pm.config.devCmd;
312
+ default:
313
+ return `${pm.config.runCmd} ${script}`;
314
+ }
315
+ }
316
+
317
+ // Allowed characters in arguments: alphanumeric, whitespace, dashes, dots, slashes,
318
+ // equals, colons, commas, quotes, @. Rejects shell metacharacters like ; | & ` $ ( ) { } < > !
319
+ const SAFE_ARGS_REGEX = /^[@a-zA-Z0-9\s_./:=,'"*+-]+$/;
320
+
321
+ /**
322
+ * Get the command to execute a package binary
323
+ * @param {string} binary - Binary name (e.g., "prettier", "eslint")
324
+ * @param {string} args - Arguments to pass
325
+ * @throws {Error} If binary name or args contain unsafe characters
326
+ */
327
+ function getExecCommand(binary, args = '', options = {}) {
328
+ if (!binary || typeof binary !== 'string') {
329
+ throw new Error('Binary name must be a non-empty string');
330
+ }
331
+ if (!SAFE_NAME_REGEX.test(binary)) {
332
+ throw new Error(`Binary name contains unsafe characters: ${binary}`);
333
+ }
334
+ if (args && typeof args === 'string' && !SAFE_ARGS_REGEX.test(args)) {
335
+ throw new Error(`Arguments contain unsafe characters: ${args}`);
336
+ }
337
+
338
+ const pm = getPackageManager(options);
339
+ return `${pm.config.execCmd} ${binary}${args ? ' ' + args : ''}`;
340
+ }
341
+
342
+ /**
343
+ * Interactive prompt for package manager selection
344
+ * Returns a message for Claude to show to user
345
+ *
346
+ * NOTE: Does NOT spawn child processes to check availability.
347
+ * Lists all supported PMs and shows how to configure preference.
348
+ */
349
+ function getSelectionPrompt() {
350
+ let message = '[PackageManager] No package manager preference detected.\n';
351
+ message += 'Supported package managers: ' + Object.keys(PACKAGE_MANAGERS).join(', ') + '\n';
352
+ message += '\nTo set your preferred package manager:\n';
353
+ message += ' - Global: Set CLAUDE_PACKAGE_MANAGER environment variable\n';
354
+ message += ' - Or add to ~/.claude/package-manager.json: {"packageManager": "pnpm"}\n';
355
+ message += ' - Or add to package.json: {"packageManager": "pnpm@8"}\n';
356
+ message += ' - Or add a lock file to your project (e.g., pnpm-lock.yaml)\n';
357
+
358
+ return message;
359
+ }
360
+
361
+ // Escape regex metacharacters in a string before interpolating into a pattern
362
+ function escapeRegex(str) {
363
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
364
+ }
365
+
366
+ /**
367
+ * Generate a regex pattern that matches commands for all package managers
368
+ * @param {string} action - Action pattern (e.g., "run dev", "install", "test")
369
+ */
370
+ function getCommandPattern(action) {
371
+ const patterns = [];
372
+
373
+ // Trim spaces from action to handle leading/trailing whitespace gracefully
374
+ const trimmedAction = action.trim();
375
+
376
+ if (trimmedAction === 'dev') {
377
+ patterns.push(
378
+ 'npm run dev',
379
+ 'pnpm( run)? dev',
380
+ 'yarn dev',
381
+ 'bun run dev'
382
+ );
383
+ } else if (trimmedAction === 'install') {
384
+ patterns.push(
385
+ 'npm install',
386
+ 'pnpm install',
387
+ 'yarn( install)?',
388
+ 'bun install'
389
+ );
390
+ } else if (trimmedAction === 'test') {
391
+ patterns.push(
392
+ 'npm test',
393
+ 'pnpm test',
394
+ 'yarn test',
395
+ 'bun test'
396
+ );
397
+ } else if (trimmedAction === 'build') {
398
+ patterns.push(
399
+ 'npm run build',
400
+ 'pnpm( run)? build',
401
+ 'yarn build',
402
+ 'bun run build'
403
+ );
404
+ } else {
405
+ // Generic run command — escape regex metacharacters in action
406
+ const escaped = escapeRegex(trimmedAction);
407
+ patterns.push(
408
+ `npm run ${escaped}`,
409
+ `pnpm( run)? ${escaped}`,
410
+ `yarn ${escaped}`,
411
+ `bun run ${escaped}`
412
+ );
413
+ }
414
+
415
+ return `(${patterns.join('|')})`;
416
+ }
417
+
418
+ module.exports = {
419
+ PACKAGE_MANAGERS,
420
+ DETECTION_PRIORITY,
421
+ getPackageManager,
422
+ setPreferredPackageManager,
423
+ setProjectPackageManager,
424
+ getAvailablePackageManagers,
425
+ detectFromLockFile,
426
+ detectFromPackageJson,
427
+ getRunCommand,
428
+ getExecCommand,
429
+ getSelectionPrompt,
430
+ getCommandPattern
431
+ };