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/README.md +109 -442
- package/bin/cli.mjs +11 -11
- package/hooks/plan-enforcement.mjs +58 -0
- package/lib/config.mjs +1 -4
- package/lib/installer.mjs +68 -83
- package/lib/messages.mjs +1 -6
- package/package.json +2 -5
- package/templates/rules.md +130 -110
- package/templates/runners/post-tool.mjs +0 -6
- package/templates/runners/pre-tool.mjs +2 -2
- package/templates/settings.json +6 -7
- package/hooks/alignment.mjs +0 -94
- package/hooks/debug-loop.mjs +0 -90
- package/hooks/scope-guard.mjs +0 -92
- package/hooks/turn-reporter.mjs +0 -70
- package/lib/adapter.mjs +0 -90
- package/templates/runners/debug-loop.mjs +0 -4
- package/templates/runners/scope-guard.mjs +0 -4
- package/templates/runners/user-prompt.mjs +0 -7
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('ā
|
|
64
|
-
console.log('ā
Commands
|
|
63
|
+
console.log('ā
UDEC methodology ā CLAUDE.md');
|
|
64
|
+
console.log('ā
Commands ā /prism, /checkpoint, /plan');
|
|
65
65
|
if (hooks) {
|
|
66
|
-
console.log('ā
|
|
66
|
+
console.log('ā
Commit guard ā blocks commits with failing tests');
|
|
67
67
|
} else {
|
|
68
|
-
console.log('āļø Hooks skipped (
|
|
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
|
|
131
|
-
console.log('\nš Fresh start.
|
|
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('ā
|
|
167
|
+
console.log('ā
UDEC methodology updated');
|
|
168
168
|
console.log('ā
Commands updated');
|
|
169
|
-
console.log('ā
|
|
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
|
|
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
|
|
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
|
|
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
|
-
'
|
|
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
|
|
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, '
|
|
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
|
|
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 ['
|
|
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
|
-
'
|
|
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
|
|
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
|
|
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
|
|
159
|
-
for (const hook of ['
|
|
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('
|
|
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
|
|
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
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
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
|
|
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
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
"description": "AI coding
|
|
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
|
],
|