claude-prism 0.5.3 → 0.7.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/bin/cli.mjs CHANGED
@@ -60,12 +60,12 @@ switch (command) {
60
60
  console.log('🌈 claude-prism init\n');
61
61
  await init(cwd, { hooks });
62
62
 
63
- console.log('āœ… Rules injected → CLAUDE.md');
64
- console.log('āœ… Commands installed → /prism, /checkpoint');
63
+ console.log('āœ… UDEC methodology → CLAUDE.md');
64
+ console.log('āœ… Commands → /prism, /checkpoint, /plan');
65
65
  if (hooks) {
66
- console.log('āœ… Hooks installed → commit-guard, debug-loop, test-tracker, scope-guard');
66
+ console.log('āœ… Commit guard → blocks commits with failing tests');
67
67
  } else {
68
- console.log('ā­ļø Hooks skipped (use --no-hooks to skip)');
68
+ console.log('ā­ļø Hooks skipped (--no-hooks)');
69
69
  }
70
70
  console.log('\n🌈 Done. Use /prism before complex tasks.');
71
71
  break;
@@ -127,8 +127,8 @@ switch (command) {
127
127
  case 'reset': {
128
128
  console.log('🌈 claude-prism reset\n');
129
129
  reset();
130
- console.log('āœ… Hook state cleared (edit counters, test timestamps)');
131
- console.log('\n🌈 Fresh start. All hooks reset.');
130
+ console.log('āœ… Hook state cleared');
131
+ console.log('\n🌈 Fresh start.');
132
132
  break;
133
133
  }
134
134
 
@@ -164,15 +164,15 @@ switch (command) {
164
164
 
165
165
  console.log('🌈 claude-prism update\n');
166
166
  await update(cwd);
167
- console.log('āœ… Rules updated');
167
+ console.log('āœ… UDEC methodology updated');
168
168
  console.log('āœ… Commands updated');
169
- console.log('āœ… Hooks updated');
169
+ console.log('āœ… Commit guard updated');
170
170
  console.log('\n🌈 Prism updated to latest.');
171
171
  break;
172
172
  }
173
173
 
174
174
  default: {
175
- console.log(`🌈 claude-prism — AI coding problem decomposition tool
175
+ console.log(`🌈 claude-prism — UDEC methodology framework for AI coding agents
176
176
 
177
177
  Usage:
178
178
  prism init [--no-hooks] Install prism in current project
@@ -180,14 +180,14 @@ Usage:
180
180
  prism check [--ci] Verify installation
181
181
  prism doctor Diagnose issues with fix suggestions
182
182
  prism stats Show installation summary
183
- prism reset Clear hook state (edit counters, etc.)
183
+ prism reset Clear hook state
184
184
  prism update Re-install using current config
185
185
  prism update --global Update global commands + OMC skill
186
186
  prism uninstall Remove prism from current project
187
187
  prism uninstall --global Remove global commands + OMC skill
188
188
 
189
189
  Options:
190
- --no-hooks Skip enforcement hooks
190
+ --no-hooks Skip commit guard hook
191
191
  --dry-run Show what init would do without making changes
192
192
  --global Install/uninstall globally (all projects)
193
193
  --ci Output JSON for CI integration
@@ -0,0 +1,58 @@
1
+ /**
2
+ * claude-prism — Plan Enforcement
3
+ * Warns when editing many files without a plan file
4
+ * Reinforces UDEC's DECOMPOSE phase
5
+ */
6
+
7
+ import { readJsonState, writeJsonState } from '../lib/state.mjs';
8
+ import { getMessage } from '../lib/messages.mjs';
9
+ import { buildSourcePattern, buildTestPattern } from '../lib/config.mjs';
10
+ import { existsSync, readdirSync } from 'fs';
11
+ import { join } from 'path';
12
+
13
+ export const planEnforcement = {
14
+ name: 'plan-enforcement',
15
+
16
+ evaluate(ctx, config, stateDir) {
17
+ if (ctx.action !== 'edit' && ctx.action !== 'write') {
18
+ return { type: 'pass' };
19
+ }
20
+
21
+ const filePath = ctx.filePath;
22
+ if (!filePath) return { type: 'pass' };
23
+
24
+ // Only track source files
25
+ const srcPattern = buildSourcePattern(config.sourceExtensions || []);
26
+ if (!srcPattern.test(filePath)) return { type: 'pass' };
27
+
28
+ // Skip test files
29
+ const testPattern = buildTestPattern(config.testPatterns || []);
30
+ if (testPattern.test(filePath)) return { type: 'pass' };
31
+
32
+ // Track unique files
33
+ const files = readJsonState(stateDir, 'plan-files') || [];
34
+ if (!files.includes(filePath)) {
35
+ files.push(filePath);
36
+ writeJsonState(stateDir, 'plan-files', files);
37
+ }
38
+
39
+ const threshold = config.warnAt || 6;
40
+ if (files.length < threshold) return { type: 'pass' };
41
+
42
+ // Check for plan file in docs/plans/
43
+ const projectRoot = config.projectRoot || process.cwd();
44
+ const plansDir = join(projectRoot, 'docs', 'plans');
45
+ if (existsSync(plansDir)) {
46
+ const plans = readdirSync(plansDir).filter(f => f.endsWith('.md'));
47
+ if (plans.length > 0) return { type: 'pass' };
48
+ }
49
+
50
+ return {
51
+ type: 'warn',
52
+ message: getMessage('en', 'plan-enforcement.warn.no-plan', {
53
+ count: files.length,
54
+ threshold
55
+ })
56
+ };
57
+ }
58
+ };
package/lib/config.mjs CHANGED
@@ -12,10 +12,8 @@ const DEFAULTS = {
12
12
  customRules: [],
13
13
  hooks: {
14
14
  'commit-guard': { enabled: true, maxTestAge: 300 },
15
- 'debug-loop': { enabled: true, warnAt: 3, blockAt: 5 },
16
15
  'test-tracker': { enabled: true },
17
- 'scope-guard': { enabled: true, warnAt: 4, blockAt: 7, agentWarnAt: 8, agentBlockAt: 12 },
18
- 'alignment': { enabled: true, driftThreshold: 2 }
16
+ 'plan-enforcement': { enabled: true, warnAt: 6 }
19
17
  }
20
18
  };
21
19
 
@@ -37,7 +35,6 @@ export function loadConfig(projectRoot) {
37
35
  export function getHookConfig(hookName, projectRoot) {
38
36
  const config = loadConfig(projectRoot);
39
37
  const hookConfig = config.hooks?.[hookName] || DEFAULTS.hooks[hookName] || { enabled: true };
40
- // Include top-level fields needed by hooks
41
38
  hookConfig.sourceExtensions = config.sourceExtensions || DEFAULTS.sourceExtensions;
42
39
  hookConfig.testPatterns = config.testPatterns || DEFAULTS.testPatterns;
43
40
  return hookConfig;
package/lib/installer.mjs CHANGED
@@ -16,14 +16,13 @@ const TEMPLATES_DIR = join(__dirname, '..', 'templates');
16
16
  * Initialize prism in a project
17
17
  * @param {string} projectDir - Project root
18
18
  * @param {Object} options
19
- * @param {boolean} options.hooks - Install enforcement hooks
19
+ * @param {boolean} options.hooks - Install commit-guard hook
20
20
  */
21
21
  export async function init(projectDir, options = {}) {
22
22
  const { hooks = true } = options;
23
23
 
24
24
  // 1. Create directories
25
25
  const claudeDir = join(projectDir, '.claude');
26
- const hooksDir = join(claudeDir, 'hooks');
27
26
 
28
27
  // 2. Copy namespaced slash commands
29
28
  const nsCommandsDir = join(claudeDir, 'commands', 'claude-prism');
@@ -37,32 +36,29 @@ export async function init(projectDir, options = {}) {
37
36
  );
38
37
  }
39
38
 
40
- // 3. Copy hooks (optional)
39
+ // 3. Copy hooks (optional — only commit-guard + test-tracker)
41
40
  if (hooks) {
41
+ const hooksDir = join(claudeDir, 'hooks');
42
42
  mkdirSync(hooksDir, { recursive: true });
43
43
 
44
44
  // Copy unified pipeline runners
45
45
  const runnersDir = join(TEMPLATES_DIR, 'runners');
46
46
  copyFileSync(join(runnersDir, 'pre-tool.mjs'), join(hooksDir, 'pre-tool.mjs'));
47
47
  copyFileSync(join(runnersDir, 'post-tool.mjs'), join(hooksDir, 'post-tool.mjs'));
48
- copyFileSync(join(runnersDir, 'user-prompt.mjs'), join(hooksDir, 'user-prompt.mjs'));
49
48
 
50
49
  // Copy rule logic files
51
50
  const rulesDestDir = join(claudeDir, 'rules');
52
51
  mkdirSync(rulesDestDir, { recursive: true });
53
52
  const hooksSourceDir = join(__dirname, '..', 'hooks');
54
53
  copyFileSync(join(hooksSourceDir, 'commit-guard.mjs'), join(rulesDestDir, 'commit-guard.mjs'));
55
- copyFileSync(join(hooksSourceDir, 'debug-loop.mjs'), join(rulesDestDir, 'debug-loop.mjs'));
56
54
  copyFileSync(join(hooksSourceDir, 'test-tracker.mjs'), join(rulesDestDir, 'test-tracker.mjs'));
57
- copyFileSync(join(hooksSourceDir, 'scope-guard.mjs'), join(rulesDestDir, 'scope-guard.mjs'));
58
- copyFileSync(join(hooksSourceDir, 'turn-reporter.mjs'), join(rulesDestDir, 'turn-reporter.mjs'));
59
- copyFileSync(join(hooksSourceDir, 'alignment.mjs'), join(rulesDestDir, 'alignment.mjs'));
55
+ copyFileSync(join(hooksSourceDir, 'plan-enforcement.mjs'), join(rulesDestDir, 'plan-enforcement.mjs'));
60
56
 
61
- // Copy lib dependencies (adapter + state + config + utils)
57
+ // Copy lib dependencies
62
58
  const libDestDir = join(claudeDir, 'lib');
63
59
  mkdirSync(libDestDir, { recursive: true });
64
60
  const libSourceDir = join(__dirname);
65
- for (const file of ['adapter.mjs', 'state.mjs', 'config.mjs', 'utils.mjs', 'messages.mjs', 'pipeline.mjs']) {
61
+ for (const file of ['state.mjs', 'config.mjs', 'utils.mjs', 'messages.mjs', 'pipeline.mjs']) {
66
62
  copyFileSync(join(libSourceDir, file), join(libDestDir, file));
67
63
  }
68
64
 
@@ -79,15 +75,13 @@ export async function init(projectDir, options = {}) {
79
75
  writeFileSync(configPath, JSON.stringify({
80
76
  hooks: {
81
77
  'commit-guard': { enabled: true, maxTestAge: 300 },
82
- 'debug-loop': { enabled: true, warnAt: 3, blockAt: 5 },
83
78
  'test-tracker': { enabled: true },
84
- 'scope-guard': { enabled: true, warnAt: 4, blockAt: 7, agentWarnAt: 8, agentBlockAt: 12 },
85
- 'alignment': { enabled: true, driftThreshold: 2 }
79
+ 'plan-enforcement': { enabled: true, warnAt: 6 }
86
80
  }
87
81
  }, null, 2) + '\n');
88
82
  }
89
83
 
90
- // Write version file for doctor to detect mismatches
84
+ // Write version file
91
85
  const pkgPath = join(__dirname, '..', 'package.json');
92
86
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
93
87
  writeFileSync(join(claudeDir, '.prism-version'), pkg.version);
@@ -149,14 +143,20 @@ export function uninstall(projectDir) {
149
143
  const nsCommandsDir = join(claudeDir, 'commands', 'claude-prism');
150
144
  if (existsSync(nsCommandsDir)) rmSync(nsCommandsDir, { recursive: true });
151
145
 
152
- // 2b. Remove legacy flat commands (migration cleanup)
146
+ // 2b. Remove legacy flat commands
153
147
  for (const cmd of ['prism.md', 'checkpoint.md']) {
154
148
  const p = join(claudeDir, 'commands', cmd);
155
149
  if (existsSync(p)) rmSync(p);
156
150
  }
157
151
 
158
- // 3. Remove prism hooks
159
- for (const hook of ['commit-guard.mjs', 'debug-loop.mjs', 'test-tracker.mjs', 'scope-guard.mjs', 'pre-tool.mjs', 'post-tool.mjs', 'user-prompt.mjs']) {
152
+ // 3. Remove hooks
153
+ for (const hook of ['pre-tool.mjs', 'post-tool.mjs', 'user-prompt.mjs']) {
154
+ const p = join(claudeDir, 'hooks', hook);
155
+ if (existsSync(p)) rmSync(p);
156
+ }
157
+
158
+ // 3b. Remove legacy individual hook runners
159
+ for (const hook of ['commit-guard.mjs', 'debug-loop.mjs', 'test-tracker.mjs', 'scope-guard.mjs']) {
160
160
  const p = join(claudeDir, 'hooks', hook);
161
161
  if (existsSync(p)) rmSync(p);
162
162
  }
@@ -174,7 +174,7 @@ export function uninstall(projectDir) {
174
174
  if (settings.hooks) {
175
175
  for (const [event, hookList] of Object.entries(settings.hooks)) {
176
176
  settings.hooks[event] = hookList.filter(
177
- h => !h.hooks?.some(hh => hh.command?.includes('commit-guard') || hh.command?.includes('debug-loop') || hh.command?.includes('test-tracker') || hh.command?.includes('scope-guard') || hh.command?.includes('pre-tool') || hh.command?.includes('post-tool') || hh.command?.includes('user-prompt'))
177
+ h => !h.hooks?.some(hh => hh.command?.includes('pre-tool') || hh.command?.includes('post-tool') || hh.command?.includes('user-prompt') || hh.command?.includes('commit-guard') || hh.command?.includes('debug-loop') || hh.command?.includes('test-tracker') || hh.command?.includes('scope-guard'))
178
178
  );
179
179
  if (settings.hooks[event].length === 0) delete settings.hooks[event];
180
180
  }
@@ -182,7 +182,7 @@ export function uninstall(projectDir) {
182
182
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
183
183
  }
184
184
 
185
- // 6. Remove .claude-prism.json and legacy .prism.json
185
+ // 6. Remove config files
186
186
  const configPath = join(projectDir, '.claude-prism.json');
187
187
  if (existsSync(configPath)) rmSync(configPath);
188
188
  const legacyConfigPath = join(projectDir, '.prism.json');
@@ -213,31 +213,38 @@ export async function update(projectDir) {
213
213
  hooks = config.hooks?.['commit-guard']?.enabled !== false;
214
214
  }
215
215
 
216
- // Migration: remove legacy flat commands before re-init
217
216
  const claudeDir = join(projectDir, '.claude');
218
- for (const cmd of ['prism.md', 'checkpoint.md']) {
219
- const p = join(claudeDir, 'commands', cmd);
220
- if (existsSync(p)) rmSync(p);
221
- }
222
-
223
- // Migration: remove deprecated namespaced commands
224
- for (const cmd of ['understand.md']) {
225
- const p = join(claudeDir, 'commands', 'claude-prism', cmd);
226
- if (existsSync(p)) rmSync(p);
227
- }
228
217
 
229
- // Migration: remove legacy individual runners (now using unified pipeline runners)
230
- for (const runner of ['commit-guard.mjs', 'debug-loop.mjs', 'test-tracker.mjs', 'scope-guard.mjs']) {
231
- const p = join(claudeDir, 'hooks', runner);
218
+ // Migration: remove all legacy files from previous versions
219
+ const legacyFiles = [
220
+ // Legacy flat commands
221
+ join(claudeDir, 'commands', 'prism.md'),
222
+ join(claudeDir, 'commands', 'checkpoint.md'),
223
+ join(claudeDir, 'commands', 'claude-prism', 'understand.md'),
224
+ // Legacy individual runners
225
+ join(claudeDir, 'hooks', 'commit-guard.mjs'),
226
+ join(claudeDir, 'hooks', 'debug-loop.mjs'),
227
+ join(claudeDir, 'hooks', 'test-tracker.mjs'),
228
+ join(claudeDir, 'hooks', 'scope-guard.mjs'),
229
+ // Removed hooks (v1.0 cleanup)
230
+ join(claudeDir, 'hooks', 'user-prompt.mjs'),
231
+ join(claudeDir, 'rules', 'scope-guard.mjs'),
232
+ join(claudeDir, 'rules', 'debug-loop.mjs'),
233
+ join(claudeDir, 'rules', 'alignment.mjs'),
234
+ join(claudeDir, 'rules', 'turn-reporter.mjs'),
235
+ // Removed lib files
236
+ join(claudeDir, 'lib', 'adapter.mjs'),
237
+ ];
238
+ for (const p of legacyFiles) {
232
239
  if (existsSync(p)) rmSync(p);
233
240
  }
234
241
 
235
- // Migration: remove legacy individual hook entries from settings.json
242
+ // Migration: remove legacy hook entries from settings.json
236
243
  const settingsPath = join(claudeDir, 'settings.json');
237
244
  if (existsSync(settingsPath)) {
238
245
  const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
239
246
  if (settings.hooks) {
240
- const legacyCommands = ['commit-guard', 'debug-loop', 'test-tracker', 'scope-guard'];
247
+ const legacyCommands = ['commit-guard', 'debug-loop', 'test-tracker', 'scope-guard', 'user-prompt'];
241
248
  for (const [event, hookList] of Object.entries(settings.hooks)) {
242
249
  settings.hooks[event] = hookList.filter(
243
250
  h => !h.hooks?.some(hh => legacyCommands.some(lc => hh.command?.includes(lc)))
@@ -255,7 +262,7 @@ export async function update(projectDir) {
255
262
  }
256
263
 
257
264
  /**
258
- * Diagnose installation issues with actionable fix suggestions
265
+ * Diagnose installation issues
259
266
  * @param {string} projectDir
260
267
  * @param {Object} [options]
261
268
  * @param {string} [options.homeDir] - Override home dir (for testing)
@@ -276,14 +283,6 @@ export function doctor(projectDir, options = {}) {
276
283
  }
277
284
  }
278
285
 
279
- // Check for legacy flat commands (need migration)
280
- for (const cmd of ['prism.md', 'checkpoint.md']) {
281
- if (existsSync(join(claudeDir, 'commands', cmd))) {
282
- issues.push(`Legacy command found: ${cmd} (needs migration to namespace)`);
283
- fixes.push('Run `prism update` to migrate to namespaced commands');
284
- }
285
- }
286
-
287
286
  // Check hooks
288
287
  for (const hook of ['pre-tool.mjs', 'post-tool.mjs']) {
289
288
  if (!existsSync(join(claudeDir, 'hooks', hook))) {
@@ -292,14 +291,6 @@ export function doctor(projectDir, options = {}) {
292
291
  }
293
292
  }
294
293
 
295
- // Check optional hooks (warn only, don't affect ok)
296
- for (const hook of ['user-prompt.mjs']) {
297
- if (!existsSync(join(claudeDir, 'hooks', hook))) {
298
- issues.push(`Missing optional hook: ${hook} (turn reporter)`);
299
- fixes.push('Run `prism update` to restore missing files');
300
- }
301
- }
302
-
303
294
  // Check CLAUDE.md
304
295
  const claudeMdPath = join(projectDir, 'CLAUDE.md');
305
296
  if (!existsSync(claudeMdPath)) {
@@ -319,20 +310,27 @@ export function doctor(projectDir, options = {}) {
319
310
  fixes.push('Run `prism init` to create config');
320
311
  }
321
312
 
322
- // Check for legacy .prism.json
323
- if (existsSync(join(projectDir, '.prism.json'))) {
324
- issues.push('Legacy .prism.json found (needs migration to .claude-prism.json)');
325
- fixes.push('Run `prism update` to migrate');
313
+ // Check for legacy flat commands
314
+ for (const cmd of ['prism.md', 'checkpoint.md']) {
315
+ if (existsSync(join(claudeDir, 'commands', cmd))) {
316
+ issues.push(`Legacy command found: ${cmd}`);
317
+ fixes.push('Run `prism update` to migrate legacy commands');
318
+ }
326
319
  }
327
320
 
328
- // Check rules/ and lib/
329
- if (!existsSync(join(claudeDir, 'rules'))) {
330
- issues.push('Missing .claude/rules/ directory');
331
- fixes.push('Run `prism update` to restore');
332
- }
333
- if (!existsSync(join(claudeDir, 'lib'))) {
334
- issues.push('Missing .claude/lib/ directory');
335
- fixes.push('Run `prism update` to restore');
321
+ // Check for legacy files that should be cleaned up
322
+ const legacyCheck = [
323
+ { path: join(claudeDir, 'rules', 'scope-guard.mjs'), label: 'Legacy scope-guard rule' },
324
+ { path: join(claudeDir, 'rules', 'debug-loop.mjs'), label: 'Legacy debug-loop rule' },
325
+ { path: join(claudeDir, 'rules', 'alignment.mjs'), label: 'Legacy alignment rule' },
326
+ { path: join(claudeDir, 'hooks', 'user-prompt.mjs'), label: 'Legacy user-prompt hook' },
327
+ { path: join(projectDir, '.prism.json'), label: 'Legacy .prism.json config' },
328
+ ];
329
+ for (const { path, label } of legacyCheck) {
330
+ if (existsSync(path)) {
331
+ issues.push(`${label} found (removed in v1.0)`);
332
+ fixes.push('Run `prism update` to clean up legacy files');
333
+ }
336
334
  }
337
335
 
338
336
  // Check version mismatch
@@ -369,11 +367,9 @@ export function doctor(projectDir, options = {}) {
369
367
  * @returns {{ version: string, hooks: Object, planFiles: number, omc: Object }}
370
368
  */
371
369
  export function stats(projectDir, options = {}) {
372
- // Read version from package.json
373
370
  const pkgPath = join(__dirname, '..', 'package.json');
374
371
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
375
372
 
376
- // Read config
377
373
  const configPath = join(projectDir, '.claude-prism.json');
378
374
  let hookConfig = {};
379
375
 
@@ -386,14 +382,12 @@ export function stats(projectDir, options = {}) {
386
382
  }
387
383
  }
388
384
 
389
- // Count plan files
390
385
  let planFiles = 0;
391
386
  const plansDir = join(projectDir, 'docs', 'plans');
392
387
  if (existsSync(plansDir)) {
393
388
  planFiles = readdirSync(plansDir).filter(f => f.endsWith('.md')).length;
394
389
  }
395
390
 
396
- // Detect OMC
397
391
  const omc = detectOmc(options.homeDir);
398
392
 
399
393
  return {
@@ -405,7 +399,7 @@ export function stats(projectDir, options = {}) {
405
399
  }
406
400
 
407
401
  /**
408
- * Reset hook state (clear edit counters, test timestamps)
402
+ * Reset hook state
409
403
  * @returns {boolean}
410
404
  */
411
405
  export function reset() {
@@ -418,7 +412,6 @@ export function reset() {
418
412
 
419
413
  /**
420
414
  * Install prism globally (slash commands + OMC skill)
421
- * Target: ~/.claude/commands/claude-prism/ and ~/.claude/skills/prism/
422
415
  * @param {Object} [options]
423
416
  * @param {string} [options.homeDir] - Override home dir (for testing)
424
417
  */
@@ -426,7 +419,6 @@ export function initGlobal(options = {}) {
426
419
  const home = options.homeDir || homedir();
427
420
  const claudeDir = join(home, '.claude');
428
421
 
429
- // 1. Install slash commands globally
430
422
  const commandsDir = join(claudeDir, 'commands', 'claude-prism');
431
423
  mkdirSync(commandsDir, { recursive: true });
432
424
 
@@ -438,7 +430,6 @@ export function initGlobal(options = {}) {
438
430
  );
439
431
  }
440
432
 
441
- // 2. Install OMC skill
442
433
  const skillDir = join(claudeDir, 'skills', 'prism');
443
434
  mkdirSync(skillDir, { recursive: true });
444
435
  copyFileSync(
@@ -448,7 +439,7 @@ export function initGlobal(options = {}) {
448
439
  }
449
440
 
450
441
  /**
451
- * Uninstall global prism (slash commands + OMC skill)
442
+ * Uninstall global prism
452
443
  * @param {Object} [options]
453
444
  * @param {string} [options.homeDir] - Override home dir (for testing)
454
445
  */
@@ -464,7 +455,7 @@ export function uninstallGlobal(options = {}) {
464
455
  }
465
456
 
466
457
  /**
467
- * Dry-run: show what init would do without making changes
458
+ * Dry-run: show what init would do
468
459
  * @param {string} projectDir
469
460
  * @param {Object} options
470
461
  * @returns {{ actions: Array<{type: string, path: string, status: string}> }}
@@ -488,8 +479,7 @@ export function dryRun(projectDir, options = {}) {
488
479
 
489
480
  // Hooks
490
481
  if (hooks) {
491
- const hookFiles = ['pre-tool.mjs', 'post-tool.mjs', 'user-prompt.mjs'];
492
- for (const hook of hookFiles) {
482
+ for (const hook of ['pre-tool.mjs', 'post-tool.mjs']) {
493
483
  const target = join(claudeDir, 'hooks', hook);
494
484
  actions.push({
495
485
  type: 'hook',
@@ -498,8 +488,7 @@ export function dryRun(projectDir, options = {}) {
498
488
  });
499
489
  }
500
490
 
501
- const ruleFiles = ['commit-guard.mjs', 'debug-loop.mjs', 'test-tracker.mjs', 'scope-guard.mjs', 'turn-reporter.mjs', 'alignment.mjs'];
502
- for (const rule of ruleFiles) {
491
+ for (const rule of ['commit-guard.mjs', 'test-tracker.mjs', 'plan-enforcement.mjs']) {
503
492
  const target = join(claudeDir, 'rules', rule);
504
493
  actions.push({
505
494
  type: 'rule',
@@ -508,8 +497,7 @@ export function dryRun(projectDir, options = {}) {
508
497
  });
509
498
  }
510
499
 
511
- const libFiles = ['adapter.mjs', 'state.mjs', 'config.mjs', 'utils.mjs', 'messages.mjs', 'pipeline.mjs'];
512
- for (const lib of libFiles) {
500
+ for (const lib of ['state.mjs', 'config.mjs', 'utils.mjs', 'messages.mjs', 'pipeline.mjs']) {
513
501
  const target = join(claudeDir, 'lib', lib);
514
502
  actions.push({
515
503
  type: 'lib',
@@ -550,7 +538,6 @@ function injectRules(projectDir) {
550
538
  existing = readFileSync(claudeMdPath, 'utf8');
551
539
  }
552
540
 
553
- // Replace existing PRISM block or append
554
541
  const startMarker = '<!-- PRISM:START -->';
555
542
  const endMarker = '<!-- PRISM:END -->';
556
543
 
@@ -572,13 +559,11 @@ function mergeSettings(claudeDir) {
572
559
  if (existsSync(settingsPath)) {
573
560
  const existing = JSON.parse(readFileSync(settingsPath, 'utf8'));
574
561
 
575
- // Merge hooks arrays by event type
576
562
  if (!existing.hooks) existing.hooks = {};
577
563
  for (const [event, hookList] of Object.entries(template.hooks)) {
578
564
  if (!existing.hooks[event]) {
579
565
  existing.hooks[event] = hookList;
580
566
  } else {
581
- // Add prism hooks that don't already exist
582
567
  for (const hook of hookList) {
583
568
  const alreadyExists = existing.hooks[event].some(
584
569
  h => h.hooks?.[0]?.command === hook.hooks?.[0]?.command
package/lib/messages.mjs CHANGED
@@ -6,13 +6,8 @@ const MESSAGES = {
6
6
  'commit-guard.block.failed': '🌈 Prism āœ‹ Commit blocked: last test run FAILED. Fix tests before committing.',
7
7
  'commit-guard.warn.no-test': '🌈 Prism > No test run detected this session. Run tests before committing.',
8
8
  'commit-guard.warn.stale': '🌈 Prism > Last test run was {minutes}min ago. Run tests before committing.',
9
- 'debug-loop.block.divergent': '🌈 Prism āœ‹ Debug Loop blocked: {name} edited {count} times on same area. Discuss approach with user before continuing.',
10
- 'debug-loop.warn.divergent': '🌈 Prism > Debug Loop: {name} edited {count} times on same area. Stop and investigate root cause.',
11
- 'debug-loop.warn.convergent': '🌈 Prism > Debug Loop: {name} edited {count} times (different areas). Consider if this is expected.',
12
- 'scope-guard.block': '🌈 Prism āœ‹ Scope Guard: {count} unique files modified without a plan. Run /prism to decompose before continuing.',
13
- 'scope-guard.warn': '🌈 Prism > Scope Guard: {count} unique files modified. Consider running /prism to decompose the task.',
14
- 'scope-guard.plan-detected': '🌈 Prism šŸ“‹ Plan file detected. Scope thresholds raised.',
15
9
  'test-tracker.warn.failed': '🌈 Prism šŸ“Š Tests FAILED. Fix before committing.',
10
+ 'plan-enforcement.warn.no-plan': '🌈 Prism > Editing {count} unique source files without a plan. Create a plan at docs/plans/ per UDEC DECOMPOSE protocol.',
16
11
  };
17
12
 
18
13
  export function getMessage(_lang, key, params = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-prism",
3
- "version": "0.5.3",
4
- "description": "AI coding problem decomposition tool — Understand, Decompose, Execute, Checkpoint.",
3
+ "version": "0.7.0",
4
+ "description": "UDEC methodology framework for AI coding agents — Understand, Decompose, Execute, Checkpoint.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "prism": "./bin/cli.mjs"
@@ -19,9 +19,6 @@
19
19
  "ai-coding",
20
20
  "problem-decomposition",
21
21
  "claude-code-hooks",
22
- "claude-code-plugin",
23
- "code-quality",
24
- "scope-guard",
25
22
  "udec",
26
23
  "ai-agent"
27
24
  ],