claude-prism 0.1.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.ko.md +429 -0
- package/README.md +500 -0
- package/bin/cli.mjs +158 -0
- package/hooks/commit-guard.mjs +48 -0
- package/hooks/debug-loop.mjs +67 -0
- package/hooks/scope-guard.mjs +61 -0
- package/hooks/test-tracker.mjs +61 -0
- package/lib/adapter.mjs +89 -0
- package/lib/config.mjs +54 -0
- package/lib/installer.mjs +439 -0
- package/lib/omc.mjs +38 -0
- package/lib/state.mjs +52 -0
- package/lib/utils.mjs +8 -0
- package/package.json +33 -0
- package/templates/commands/claude-prism/checkpoint.md +34 -0
- package/templates/commands/claude-prism/doctor.md +42 -0
- package/templates/commands/claude-prism/help.md +42 -0
- package/templates/commands/claude-prism/plan.md +52 -0
- package/templates/commands/claude-prism/prism.md +84 -0
- package/templates/commands/claude-prism/stats.md +36 -0
- package/templates/rules.en.md +226 -0
- package/templates/rules.ja.md +226 -0
- package/templates/rules.ko.md +226 -0
- package/templates/rules.zh.md +226 -0
- package/templates/runners/commit-guard.mjs +4 -0
- package/templates/runners/debug-loop.mjs +4 -0
- package/templates/runners/scope-guard.mjs +4 -0
- package/templates/runners/test-tracker.mjs +4 -0
- package/templates/settings.json +48 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-prism — Installer
|
|
3
|
+
* Handles init, check, update, uninstall
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, rmSync, readdirSync, renameSync } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
import { detectOmc } from './omc.mjs';
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const TEMPLATES_DIR = join(__dirname, '..', 'templates');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize prism in a project
|
|
17
|
+
* @param {string} projectDir - Project root
|
|
18
|
+
* @param {Object} options
|
|
19
|
+
* @param {string} options.language - 'en' | 'ko'
|
|
20
|
+
* @param {boolean} options.hooks - Install enforcement hooks
|
|
21
|
+
*/
|
|
22
|
+
export async function init(projectDir, options = {}) {
|
|
23
|
+
const { language = 'en', hooks = true } = options;
|
|
24
|
+
|
|
25
|
+
// 1. Create directories
|
|
26
|
+
const claudeDir = join(projectDir, '.claude');
|
|
27
|
+
const hooksDir = join(claudeDir, 'hooks');
|
|
28
|
+
|
|
29
|
+
// 2. Copy namespaced slash commands
|
|
30
|
+
const nsCommandsDir = join(claudeDir, 'commands', 'claude-prism');
|
|
31
|
+
mkdirSync(nsCommandsDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
const commandFiles = ['prism.md', 'checkpoint.md', 'plan.md', 'doctor.md', 'stats.md', 'help.md'];
|
|
34
|
+
for (const cmd of commandFiles) {
|
|
35
|
+
copyFileSync(
|
|
36
|
+
join(TEMPLATES_DIR, 'commands', 'claude-prism', cmd),
|
|
37
|
+
join(nsCommandsDir, cmd)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 3. Copy hooks (optional)
|
|
42
|
+
if (hooks) {
|
|
43
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
// Copy runner scripts (executable wrappers Claude Code calls)
|
|
46
|
+
const runnersDir = join(TEMPLATES_DIR, 'runners');
|
|
47
|
+
copyFileSync(join(runnersDir, 'commit-guard.mjs'), join(hooksDir, 'commit-guard.mjs'));
|
|
48
|
+
copyFileSync(join(runnersDir, 'debug-loop.mjs'), join(hooksDir, 'debug-loop.mjs'));
|
|
49
|
+
copyFileSync(join(runnersDir, 'test-tracker.mjs'), join(hooksDir, 'test-tracker.mjs'));
|
|
50
|
+
copyFileSync(join(runnersDir, 'scope-guard.mjs'), join(hooksDir, 'scope-guard.mjs'));
|
|
51
|
+
|
|
52
|
+
// Copy rule logic files
|
|
53
|
+
const rulesDestDir = join(claudeDir, 'rules');
|
|
54
|
+
mkdirSync(rulesDestDir, { recursive: true });
|
|
55
|
+
const hooksSourceDir = join(__dirname, '..', 'hooks');
|
|
56
|
+
copyFileSync(join(hooksSourceDir, 'commit-guard.mjs'), join(rulesDestDir, 'commit-guard.mjs'));
|
|
57
|
+
copyFileSync(join(hooksSourceDir, 'debug-loop.mjs'), join(rulesDestDir, 'debug-loop.mjs'));
|
|
58
|
+
copyFileSync(join(hooksSourceDir, 'test-tracker.mjs'), join(rulesDestDir, 'test-tracker.mjs'));
|
|
59
|
+
copyFileSync(join(hooksSourceDir, 'scope-guard.mjs'), join(rulesDestDir, 'scope-guard.mjs'));
|
|
60
|
+
|
|
61
|
+
// Copy lib dependencies (adapter + state + config + utils)
|
|
62
|
+
const libDestDir = join(claudeDir, 'lib');
|
|
63
|
+
mkdirSync(libDestDir, { recursive: true });
|
|
64
|
+
const libSourceDir = join(__dirname);
|
|
65
|
+
for (const file of ['adapter.mjs', 'state.mjs', 'config.mjs', 'utils.mjs']) {
|
|
66
|
+
copyFileSync(join(libSourceDir, file), join(libDestDir, file));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Merge settings.json
|
|
70
|
+
mergeSettings(claudeDir);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 4. Inject rules into CLAUDE.md
|
|
74
|
+
injectRules(projectDir, language);
|
|
75
|
+
|
|
76
|
+
// 5. Create .claude-prism.json config
|
|
77
|
+
const configPath = join(projectDir, '.claude-prism.json');
|
|
78
|
+
if (!existsSync(configPath)) {
|
|
79
|
+
writeFileSync(configPath, JSON.stringify({
|
|
80
|
+
language,
|
|
81
|
+
hooks: {
|
|
82
|
+
'commit-guard': { enabled: true, maxTestAge: 300 },
|
|
83
|
+
'debug-loop': { enabled: true, warnAt: 3, blockAt: 5 },
|
|
84
|
+
'test-tracker': { enabled: true },
|
|
85
|
+
'scope-guard': { enabled: true, warnAt: 4, blockAt: 7, agentWarnAt: 8, agentBlockAt: 12 }
|
|
86
|
+
}
|
|
87
|
+
}, null, 2) + '\n');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check installation status
|
|
93
|
+
* @param {string} projectDir
|
|
94
|
+
* @returns {{ commands: boolean, rules: boolean, hooks: boolean, config: boolean, ok: boolean }}
|
|
95
|
+
*/
|
|
96
|
+
export function check(projectDir) {
|
|
97
|
+
const claudeDir = join(projectDir, '.claude');
|
|
98
|
+
|
|
99
|
+
const nsCommandsDir = join(claudeDir, 'commands', 'claude-prism');
|
|
100
|
+
const commands = existsSync(join(nsCommandsDir, 'prism.md'))
|
|
101
|
+
&& existsSync(join(nsCommandsDir, 'checkpoint.md'))
|
|
102
|
+
&& existsSync(join(nsCommandsDir, 'plan.md'))
|
|
103
|
+
&& existsSync(join(nsCommandsDir, 'help.md'));
|
|
104
|
+
|
|
105
|
+
const claudeMdPath = join(projectDir, 'CLAUDE.md');
|
|
106
|
+
const rules = existsSync(claudeMdPath)
|
|
107
|
+
&& readFileSync(claudeMdPath, 'utf8').includes('<!-- PRISM:START -->');
|
|
108
|
+
|
|
109
|
+
const hooks = existsSync(join(claudeDir, 'hooks', 'commit-guard.mjs'))
|
|
110
|
+
&& existsSync(join(claudeDir, 'hooks', 'debug-loop.mjs'))
|
|
111
|
+
&& existsSync(join(claudeDir, 'hooks', 'test-tracker.mjs'))
|
|
112
|
+
&& existsSync(join(claudeDir, 'hooks', 'scope-guard.mjs'));
|
|
113
|
+
|
|
114
|
+
const config = existsSync(join(projectDir, '.claude-prism.json'));
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
commands,
|
|
118
|
+
rules,
|
|
119
|
+
hooks,
|
|
120
|
+
config,
|
|
121
|
+
ok: commands && rules && config
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Uninstall prism from a project
|
|
127
|
+
* @param {string} projectDir
|
|
128
|
+
*/
|
|
129
|
+
export function uninstall(projectDir) {
|
|
130
|
+
const claudeDir = join(projectDir, '.claude');
|
|
131
|
+
|
|
132
|
+
// 1. Remove PRISM block from CLAUDE.md
|
|
133
|
+
const claudeMdPath = join(projectDir, 'CLAUDE.md');
|
|
134
|
+
if (existsSync(claudeMdPath)) {
|
|
135
|
+
const content = readFileSync(claudeMdPath, 'utf8');
|
|
136
|
+
const startMarker = '<!-- PRISM:START -->';
|
|
137
|
+
const endMarker = '<!-- PRISM:END -->';
|
|
138
|
+
if (content.includes(startMarker) && content.includes(endMarker)) {
|
|
139
|
+
const before = content.slice(0, content.indexOf(startMarker));
|
|
140
|
+
const after = content.slice(content.indexOf(endMarker) + endMarker.length);
|
|
141
|
+
writeFileSync(claudeMdPath, (before + after).replace(/\n{3,}/g, '\n\n').trim() + '\n');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 2. Remove namespaced commands directory
|
|
146
|
+
const nsCommandsDir = join(claudeDir, 'commands', 'claude-prism');
|
|
147
|
+
if (existsSync(nsCommandsDir)) rmSync(nsCommandsDir, { recursive: true });
|
|
148
|
+
|
|
149
|
+
// 2b. Remove legacy flat commands (migration cleanup)
|
|
150
|
+
for (const cmd of ['prism.md', 'checkpoint.md']) {
|
|
151
|
+
const p = join(claudeDir, 'commands', cmd);
|
|
152
|
+
if (existsSync(p)) rmSync(p);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 3. Remove prism hooks
|
|
156
|
+
for (const hook of ['commit-guard.mjs', 'debug-loop.mjs', 'test-tracker.mjs', 'scope-guard.mjs']) {
|
|
157
|
+
const p = join(claudeDir, 'hooks', hook);
|
|
158
|
+
if (existsSync(p)) rmSync(p);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 4. Remove rules/ and lib/ directories
|
|
162
|
+
const rulesDir = join(claudeDir, 'rules');
|
|
163
|
+
if (existsSync(rulesDir)) rmSync(rulesDir, { recursive: true });
|
|
164
|
+
const libDir = join(claudeDir, 'lib');
|
|
165
|
+
if (existsSync(libDir)) rmSync(libDir, { recursive: true });
|
|
166
|
+
|
|
167
|
+
// 5. Clean prism hooks from settings.json
|
|
168
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
169
|
+
if (existsSync(settingsPath)) {
|
|
170
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
171
|
+
if (settings.hooks) {
|
|
172
|
+
for (const [event, hookList] of Object.entries(settings.hooks)) {
|
|
173
|
+
settings.hooks[event] = hookList.filter(
|
|
174
|
+
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'))
|
|
175
|
+
);
|
|
176
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 6. Remove .claude-prism.json and legacy .prism.json
|
|
183
|
+
const configPath = join(projectDir, '.claude-prism.json');
|
|
184
|
+
if (existsSync(configPath)) rmSync(configPath);
|
|
185
|
+
const legacyConfigPath = join(projectDir, '.prism.json');
|
|
186
|
+
if (existsSync(legacyConfigPath)) rmSync(legacyConfigPath);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Update prism — re-install using existing config
|
|
191
|
+
* @param {string} projectDir
|
|
192
|
+
*/
|
|
193
|
+
export async function update(projectDir) {
|
|
194
|
+
// Migration: rename .prism.json to .claude-prism.json
|
|
195
|
+
const oldConfigPath = join(projectDir, '.prism.json');
|
|
196
|
+
const newConfigPath = join(projectDir, '.claude-prism.json');
|
|
197
|
+
if (existsSync(oldConfigPath) && !existsSync(newConfigPath)) {
|
|
198
|
+
renameSync(oldConfigPath, newConfigPath);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const configPath = newConfigPath;
|
|
202
|
+
let language = 'en';
|
|
203
|
+
let hooks = true;
|
|
204
|
+
|
|
205
|
+
if (existsSync(configPath)) {
|
|
206
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
207
|
+
language = config.language || 'en';
|
|
208
|
+
hooks = config.hooks?.['commit-guard']?.enabled !== false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Migration: remove legacy flat commands before re-init
|
|
212
|
+
const claudeDir = join(projectDir, '.claude');
|
|
213
|
+
for (const cmd of ['prism.md', 'checkpoint.md']) {
|
|
214
|
+
const p = join(claudeDir, 'commands', cmd);
|
|
215
|
+
if (existsSync(p)) rmSync(p);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Migration: remove deprecated namespaced commands
|
|
219
|
+
for (const cmd of ['understand.md']) {
|
|
220
|
+
const p = join(claudeDir, 'commands', 'claude-prism', cmd);
|
|
221
|
+
if (existsSync(p)) rmSync(p);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Remove old config so init creates a fresh one
|
|
225
|
+
if (existsSync(configPath)) rmSync(configPath);
|
|
226
|
+
|
|
227
|
+
await init(projectDir, { language, hooks });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Diagnose installation issues with actionable fix suggestions
|
|
232
|
+
* @param {string} projectDir
|
|
233
|
+
* @param {Object} [options]
|
|
234
|
+
* @param {string} [options.homeDir] - Override home dir (for testing)
|
|
235
|
+
* @returns {{ healthy: boolean, issues: string[], fixes: string[], omc: Object }}
|
|
236
|
+
*/
|
|
237
|
+
export function doctor(projectDir, options = {}) {
|
|
238
|
+
const claudeDir = join(projectDir, '.claude');
|
|
239
|
+
const issues = [];
|
|
240
|
+
const fixes = [];
|
|
241
|
+
|
|
242
|
+
// Check namespaced commands
|
|
243
|
+
const nsCommandsDir = join(claudeDir, 'commands', 'claude-prism');
|
|
244
|
+
const expectedCommands = ['prism.md', 'checkpoint.md', 'plan.md', 'doctor.md', 'stats.md', 'help.md'];
|
|
245
|
+
for (const cmd of expectedCommands) {
|
|
246
|
+
if (!existsSync(join(nsCommandsDir, cmd))) {
|
|
247
|
+
issues.push(`Missing command: claude-prism/${cmd}`);
|
|
248
|
+
fixes.push('Run `prism update` to restore missing files');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check for legacy flat commands (need migration)
|
|
253
|
+
for (const cmd of ['prism.md', 'checkpoint.md']) {
|
|
254
|
+
if (existsSync(join(claudeDir, 'commands', cmd))) {
|
|
255
|
+
issues.push(`Legacy command found: ${cmd} (needs migration to namespace)`);
|
|
256
|
+
fixes.push('Run `prism update` to migrate to namespaced commands');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check hooks
|
|
261
|
+
for (const hook of ['commit-guard.mjs', 'debug-loop.mjs', 'test-tracker.mjs', 'scope-guard.mjs']) {
|
|
262
|
+
if (!existsSync(join(claudeDir, 'hooks', hook))) {
|
|
263
|
+
issues.push(`Missing hook: ${hook}`);
|
|
264
|
+
fixes.push('Run `prism update` to restore missing files');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check CLAUDE.md
|
|
269
|
+
const claudeMdPath = join(projectDir, 'CLAUDE.md');
|
|
270
|
+
if (!existsSync(claudeMdPath)) {
|
|
271
|
+
issues.push('CLAUDE.md not found');
|
|
272
|
+
fixes.push('Run `prism update` to regenerate CLAUDE.md rules');
|
|
273
|
+
} else {
|
|
274
|
+
const content = readFileSync(claudeMdPath, 'utf8');
|
|
275
|
+
if (!content.includes('<!-- PRISM:START -->')) {
|
|
276
|
+
issues.push('CLAUDE.md missing PRISM rules block');
|
|
277
|
+
fixes.push('Run `prism update` to re-inject rules');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check config
|
|
282
|
+
if (!existsSync(join(projectDir, '.claude-prism.json'))) {
|
|
283
|
+
issues.push('Missing .claude-prism.json config');
|
|
284
|
+
fixes.push('Run `prism init` to create config');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check for legacy .prism.json
|
|
288
|
+
if (existsSync(join(projectDir, '.prism.json'))) {
|
|
289
|
+
issues.push('Legacy .prism.json found (needs migration to .claude-prism.json)');
|
|
290
|
+
fixes.push('Run `prism update` to migrate');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check rules/ and lib/
|
|
294
|
+
if (!existsSync(join(claudeDir, 'rules'))) {
|
|
295
|
+
issues.push('Missing .claude/rules/ directory');
|
|
296
|
+
fixes.push('Run `prism update` to restore');
|
|
297
|
+
}
|
|
298
|
+
if (!existsSync(join(claudeDir, 'lib'))) {
|
|
299
|
+
issues.push('Missing .claude/lib/ directory');
|
|
300
|
+
fixes.push('Run `prism update` to restore');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Deduplicate fixes
|
|
304
|
+
const uniqueFixes = [...new Set(fixes)];
|
|
305
|
+
|
|
306
|
+
// Detect OMC
|
|
307
|
+
const omc = detectOmc(options.homeDir);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
healthy: issues.length === 0,
|
|
311
|
+
issues,
|
|
312
|
+
fixes: uniqueFixes,
|
|
313
|
+
omc
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Show installation statistics
|
|
319
|
+
* @param {string} projectDir
|
|
320
|
+
* @param {Object} [options]
|
|
321
|
+
* @param {string} [options.homeDir] - Override home dir (for testing)
|
|
322
|
+
* @returns {{ version: string, language: string, hooks: Object, planFiles: number, omc: Object }}
|
|
323
|
+
*/
|
|
324
|
+
export function stats(projectDir, options = {}) {
|
|
325
|
+
// Read version from package.json
|
|
326
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
327
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
328
|
+
|
|
329
|
+
// Read config
|
|
330
|
+
const configPath = join(projectDir, '.claude-prism.json');
|
|
331
|
+
let language = 'en';
|
|
332
|
+
let hookConfig = {};
|
|
333
|
+
|
|
334
|
+
if (existsSync(configPath)) {
|
|
335
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
336
|
+
language = config.language || 'en';
|
|
337
|
+
if (config.hooks) {
|
|
338
|
+
for (const [name, cfg] of Object.entries(config.hooks)) {
|
|
339
|
+
hookConfig[name] = cfg.enabled !== false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Count plan files
|
|
345
|
+
let planFiles = 0;
|
|
346
|
+
const plansDir = join(projectDir, 'docs', 'plans');
|
|
347
|
+
if (existsSync(plansDir)) {
|
|
348
|
+
planFiles = readdirSync(plansDir).filter(f => f.endsWith('.md')).length;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Detect OMC
|
|
352
|
+
const omc = detectOmc(options.homeDir);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
version: pkg.version,
|
|
356
|
+
language,
|
|
357
|
+
hooks: hookConfig,
|
|
358
|
+
planFiles,
|
|
359
|
+
omc
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Reset hook state (clear edit counters, test timestamps)
|
|
365
|
+
* @returns {boolean}
|
|
366
|
+
*/
|
|
367
|
+
export function reset() {
|
|
368
|
+
const stateRoot = join(tmpdir(), '.prism');
|
|
369
|
+
if (existsSync(stateRoot)) {
|
|
370
|
+
rmSync(stateRoot, { recursive: true });
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ─── internal helpers ───
|
|
376
|
+
|
|
377
|
+
function injectRules(projectDir, language) {
|
|
378
|
+
const claudeMdPath = join(projectDir, 'CLAUDE.md');
|
|
379
|
+
const rulesFile = `rules.${language}.md`;
|
|
380
|
+
const rulesPath = join(TEMPLATES_DIR, rulesFile);
|
|
381
|
+
|
|
382
|
+
if (!existsSync(rulesPath)) {
|
|
383
|
+
// Fallback to English
|
|
384
|
+
const fallback = join(TEMPLATES_DIR, 'rules.en.md');
|
|
385
|
+
if (!existsSync(fallback)) return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const rules = readFileSync(existsSync(rulesPath) ? rulesPath : join(TEMPLATES_DIR, 'rules.en.md'), 'utf8');
|
|
389
|
+
|
|
390
|
+
let existing = '';
|
|
391
|
+
if (existsSync(claudeMdPath)) {
|
|
392
|
+
existing = readFileSync(claudeMdPath, 'utf8');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Replace existing PRISM block or append
|
|
396
|
+
const startMarker = '<!-- PRISM:START -->';
|
|
397
|
+
const endMarker = '<!-- PRISM:END -->';
|
|
398
|
+
|
|
399
|
+
if (existing.includes(startMarker) && existing.includes(endMarker)) {
|
|
400
|
+
const before = existing.slice(0, existing.indexOf(startMarker));
|
|
401
|
+
const after = existing.slice(existing.indexOf(endMarker) + endMarker.length);
|
|
402
|
+
writeFileSync(claudeMdPath, before + rules + after);
|
|
403
|
+
} else {
|
|
404
|
+
writeFileSync(claudeMdPath, existing + '\n' + rules);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function mergeSettings(claudeDir) {
|
|
409
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
410
|
+
const templatePath = join(TEMPLATES_DIR, 'settings.json');
|
|
411
|
+
|
|
412
|
+
const template = JSON.parse(readFileSync(templatePath, 'utf8'));
|
|
413
|
+
|
|
414
|
+
if (existsSync(settingsPath)) {
|
|
415
|
+
const existing = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
416
|
+
|
|
417
|
+
// Merge hooks arrays by event type
|
|
418
|
+
if (!existing.hooks) existing.hooks = {};
|
|
419
|
+
for (const [event, hookList] of Object.entries(template.hooks)) {
|
|
420
|
+
if (!existing.hooks[event]) {
|
|
421
|
+
existing.hooks[event] = hookList;
|
|
422
|
+
} else {
|
|
423
|
+
// Add prism hooks that don't already exist
|
|
424
|
+
for (const hook of hookList) {
|
|
425
|
+
const alreadyExists = existing.hooks[event].some(
|
|
426
|
+
h => h.hooks?.[0]?.command === hook.hooks?.[0]?.command
|
|
427
|
+
);
|
|
428
|
+
if (!alreadyExists) {
|
|
429
|
+
existing.hooks[event].push(hook);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + '\n');
|
|
436
|
+
} else {
|
|
437
|
+
writeFileSync(settingsPath, JSON.stringify(template, null, 2) + '\n');
|
|
438
|
+
}
|
|
439
|
+
}
|
package/lib/omc.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-prism — OMC (oh-my-claudecode) Detection
|
|
3
|
+
* Detects OMC installation and extracts version info
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect oh-my-claudecode installation
|
|
12
|
+
* @param {string} [homeDir] - Override home directory (for testing)
|
|
13
|
+
* @returns {{ detected: boolean, version: string|null }}
|
|
14
|
+
*/
|
|
15
|
+
export function detectOmc(homeDir) {
|
|
16
|
+
const home = homeDir || homedir();
|
|
17
|
+
const claudeMdPath = join(home, '.claude', 'CLAUDE.md');
|
|
18
|
+
|
|
19
|
+
if (!existsSync(claudeMdPath)) {
|
|
20
|
+
return { detected: false, version: null };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(claudeMdPath, 'utf8');
|
|
25
|
+
|
|
26
|
+
if (!content.includes('<!-- OMC:START -->')) {
|
|
27
|
+
return { detected: false, version: null };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Extract version from "Unified Agents (vX.Y.Z)" pattern
|
|
31
|
+
const versionMatch = content.match(/\(v(\d+\.\d+\.\d+)\)/);
|
|
32
|
+
const version = versionMatch ? versionMatch[1] : null;
|
|
33
|
+
|
|
34
|
+
return { detected: true, version };
|
|
35
|
+
} catch {
|
|
36
|
+
return { detected: false, version: null };
|
|
37
|
+
}
|
|
38
|
+
}
|
package/lib/state.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-prism — State Management
|
|
3
|
+
* File-based state storage for hook evaluation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { tmpdir } from 'os';
|
|
9
|
+
|
|
10
|
+
const STATE_ROOT = join(tmpdir(), '.prism');
|
|
11
|
+
|
|
12
|
+
export function getStateDir(sessionId, agentId) {
|
|
13
|
+
const key = agentId && agentId !== 'default' ? `${sessionId}-${agentId}` : sessionId;
|
|
14
|
+
const dir = join(STATE_ROOT, key);
|
|
15
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readState(stateDir, key) {
|
|
20
|
+
const file = join(stateDir, key);
|
|
21
|
+
if (!existsSync(file)) return null;
|
|
22
|
+
try {
|
|
23
|
+
return readFileSync(file, 'utf8');
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function writeState(stateDir, key, value) {
|
|
30
|
+
writeFileSync(join(stateDir, key), value, { mode: 0o600 });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function readJsonState(stateDir, key) {
|
|
34
|
+
const raw = readState(stateDir, key);
|
|
35
|
+
if (raw === null) return null;
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function writeJsonState(stateDir, key, value) {
|
|
44
|
+
writeState(stateDir, key, JSON.stringify(value));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function incrementCounter(stateDir, key) {
|
|
48
|
+
const current = parseInt(readState(stateDir, key) || '0', 10) || 0;
|
|
49
|
+
const next = current + 1;
|
|
50
|
+
writeState(stateDir, key, String(next));
|
|
51
|
+
return next;
|
|
52
|
+
}
|
package/lib/utils.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-prism",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI coding problem decomposition tool — Understand, Decompose, Execute, Checkpoint.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"prism": "./bin/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test tests/*.test.mjs"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"claude",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"tdd",
|
|
17
|
+
"decomposition",
|
|
18
|
+
"methodology"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/",
|
|
25
|
+
"lib/",
|
|
26
|
+
"hooks/",
|
|
27
|
+
"templates/",
|
|
28
|
+
"README.md",
|
|
29
|
+
"README.ko.md"
|
|
30
|
+
],
|
|
31
|
+
"author": "lazysaturday91",
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# /claude-prism:checkpoint — Progress Check
|
|
2
|
+
|
|
3
|
+
When this command is invoked:
|
|
4
|
+
|
|
5
|
+
1. **Read the plan file** from `docs/plans/` (most recent matching file)
|
|
6
|
+
2. **Check completed tasks** (marked with [x] in the plan)
|
|
7
|
+
3. **Report current status** using this standard format:
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
|
|
11
|
+
| Item | Before | After |
|
|
12
|
+
|------|--------|-------|
|
|
13
|
+
| [what changed] | [old behavior/state] | [new behavior/state] |
|
|
14
|
+
|
|
15
|
+
### Verification
|
|
16
|
+
|
|
17
|
+
| Check | Result |
|
|
18
|
+
|-------|--------|
|
|
19
|
+
| TypeScript | ✅/❌ [details] |
|
|
20
|
+
| Tests | ✅/❌ [pass count] |
|
|
21
|
+
| Lint | ✅/❌ or N/A |
|
|
22
|
+
|
|
23
|
+
### Progress
|
|
24
|
+
|
|
25
|
+
- Batches complete: N/M
|
|
26
|
+
- Current batch: [name] — [X/Y tasks done]
|
|
27
|
+
- Remaining: [batch names]
|
|
28
|
+
- Blockers: [any issues encountered]
|
|
29
|
+
|
|
30
|
+
4. **Show summary**:
|
|
31
|
+
- Files created/modified so far
|
|
32
|
+
- Tests added and their status
|
|
33
|
+
- Commits made
|
|
34
|
+
5. **Ask**: "Continue with the current plan, adjust, or stop?"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# /claude-prism:doctor — Installation Diagnostics
|
|
2
|
+
|
|
3
|
+
When this command is invoked, check all components of the prism installation:
|
|
4
|
+
|
|
5
|
+
## Checks
|
|
6
|
+
|
|
7
|
+
1. **CLAUDE.md**: Does it contain `<!-- PRISM:START -->` marker?
|
|
8
|
+
2. **Config**: Does `.claude-prism.json` exist? Is it valid JSON?
|
|
9
|
+
3. **Commands**: Do these files exist in `.claude/commands/claude-prism/`?
|
|
10
|
+
- prism.md, checkpoint.md, plan.md, doctor.md, stats.md, help.md
|
|
11
|
+
4. **Hooks**: Do these files exist in `.claude/hooks/`?
|
|
12
|
+
- commit-guard.mjs, debug-loop.mjs, test-tracker.mjs, scope-guard.mjs
|
|
13
|
+
5. **Rules**: Do these files exist in `.claude/rules/`?
|
|
14
|
+
- commit-guard.mjs, debug-loop.mjs, test-tracker.mjs, scope-guard.mjs
|
|
15
|
+
6. **Lib**: Do these files exist in `.claude/lib/`?
|
|
16
|
+
- adapter.mjs, state.mjs, config.mjs, utils.mjs
|
|
17
|
+
7. **Settings**: Does `.claude/settings.json` contain prism hook registrations?
|
|
18
|
+
8. **Legacy**: Are there old flat commands (`/prism`, `/checkpoint`) that need migration?
|
|
19
|
+
|
|
20
|
+
## Report Format
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
🌈 claude-prism doctor
|
|
24
|
+
|
|
25
|
+
CLAUDE.md: ✅ PRISM rules present
|
|
26
|
+
Config: ✅ .claude-prism.json valid
|
|
27
|
+
Commands: ✅ 7/7 installed
|
|
28
|
+
Hooks: ✅ 4/4 installed
|
|
29
|
+
Rules: ✅ 4/4 installed
|
|
30
|
+
Lib: ✅ 4/4 installed
|
|
31
|
+
Settings: ✅ Hooks registered
|
|
32
|
+
Legacy: ✅ No old commands found
|
|
33
|
+
|
|
34
|
+
Status: ✅ Healthy
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Fix Suggestions
|
|
38
|
+
|
|
39
|
+
For each issue found, suggest the fix:
|
|
40
|
+
- Missing files → "Run `prism update` to restore"
|
|
41
|
+
- Legacy commands → "Run `prism update` to migrate to namespaced commands"
|
|
42
|
+
- Missing PRISM block → "Run `prism update` to re-inject rules"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# /claude-prism:help — Command Reference
|
|
2
|
+
|
|
3
|
+
## Available Commands
|
|
4
|
+
|
|
5
|
+
| Command | Description |
|
|
6
|
+
|---------|-------------|
|
|
7
|
+
| `/claude-prism:prism` | Start full UDEC workflow (Understand → Decompose → Execute → Checkpoint). Also handles analysis-only requests — stops after U phase when no code change is needed. |
|
|
8
|
+
| `/claude-prism:checkpoint` | Check current batch progress, show next batch preview |
|
|
9
|
+
| `/claude-prism:plan` | List, create, or view plan files in `docs/plans/` |
|
|
10
|
+
| `/claude-prism:doctor` | Diagnose installation health and suggest fixes |
|
|
11
|
+
| `/claude-prism:stats` | Show project statistics, hook status, and plan progress |
|
|
12
|
+
| `/claude-prism:help` | This reference |
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
1. Before any task (code or analysis): `/claude-prism:prism`
|
|
17
|
+
2. Mid-project progress check: `/claude-prism:checkpoint`
|
|
18
|
+
3. Create a new plan: `/claude-prism:plan`
|
|
19
|
+
|
|
20
|
+
## CLI Commands
|
|
21
|
+
|
|
22
|
+
These are also available from the terminal:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
prism init [--lang=XX] [--no-hooks] # Install prism
|
|
26
|
+
prism check [--ci] # Verify installation
|
|
27
|
+
prism doctor # Diagnose issues
|
|
28
|
+
prism stats # Show summary
|
|
29
|
+
prism reset # Clear hook state
|
|
30
|
+
prism update # Re-install / migrate
|
|
31
|
+
prism uninstall # Remove prism
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## UDEC Framework
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
U — Understand: Assess sufficiency, ask questions, confirm alignment
|
|
38
|
+
(analysis-only requests stop here with findings report)
|
|
39
|
+
D — Decompose: Break into 2-5 min units, create plan file
|
|
40
|
+
E — Execute: Batch (3-4 tasks), TDD each unit, scope guard
|
|
41
|
+
C — Checkpoint: Report with before/after table, get confirmation
|
|
42
|
+
```
|