claude-prism 0.5.3 → 0.6.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/hooks/alignment.mjs +10 -1
- package/hooks/debug-loop.mjs +20 -6
- package/hooks/scope-guard.mjs +12 -3
- package/lib/config.mjs +1 -1
- package/lib/messages.mjs +2 -0
- package/package.json +1 -1
package/hooks/alignment.mjs
CHANGED
|
@@ -33,7 +33,16 @@ export const alignment = {
|
|
|
33
33
|
const dir = ctx.filePath.split('/').slice(0, -1).join('/') || '.';
|
|
34
34
|
let scopeDirs = readJsonState(stateDir, 'scope-directories') || [];
|
|
35
35
|
|
|
36
|
-
if (
|
|
36
|
+
// Check if dir is within any existing base scope (subdirectory match)
|
|
37
|
+
const isWithinScope = scopeDirs.some(base =>
|
|
38
|
+
dir === base || dir.startsWith(base + '/')
|
|
39
|
+
);
|
|
40
|
+
// Also check if dir is a parent of an existing scope dir
|
|
41
|
+
const isParentOfScope = scopeDirs.some(base =>
|
|
42
|
+
base.startsWith(dir + '/')
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (!isWithinScope && !isParentOfScope && !scopeDirs.includes(dir)) {
|
|
37
46
|
// First 3 unique directories establish the "base scope"
|
|
38
47
|
if (scopeDirs.length < 3) {
|
|
39
48
|
scopeDirs.push(dir);
|
package/hooks/debug-loop.mjs
CHANGED
|
@@ -69,6 +69,13 @@ export const debugLoop = {
|
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
// Common statement prefixes that don't indicate "same area"
|
|
73
|
+
const BOILERPLATE_RE = /^(?:import\s.*?from\s|import\s*\{|export\s+(?:default\s+)?(?:const|let|var|function|class|interface|type)\s|const\s|let\s|var\s|function\s|return\s)/;
|
|
74
|
+
|
|
75
|
+
function stripBoilerplate(snippet) {
|
|
76
|
+
return snippet.replace(BOILERPLATE_RE, '').trim();
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
function analyzePattern(log) {
|
|
73
80
|
if (log.length < 3) return null;
|
|
74
81
|
const recent = log.slice(-3).map(e => e.snippet);
|
|
@@ -80,11 +87,18 @@ function analyzePattern(log) {
|
|
|
80
87
|
const uniqueSnippets = new Set(recent).size;
|
|
81
88
|
if (uniqueSnippets === 1) return 'divergent';
|
|
82
89
|
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
// Strip boilerplate prefixes before comparing (import X from, const, etc.)
|
|
91
|
+
const stripped = recent.map(stripBoilerplate);
|
|
92
|
+
const uniqueStripped = new Set(stripped).size;
|
|
93
|
+
|
|
94
|
+
// After stripping, if all unique → editing different areas
|
|
95
|
+
if (uniqueStripped === recent.length) return 'convergent';
|
|
96
|
+
|
|
97
|
+
// Check for meaningful overlap using stripped content
|
|
98
|
+
const baseSnippet = stripped[0].slice(0, 30);
|
|
99
|
+
if (baseSnippet.length < 10) return uniqueStripped <= 1 ? 'divergent' : 'convergent';
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
// Require all 3 to overlap (stricter than previous >= 2)
|
|
102
|
+
const overlap = stripped.filter(s => s.includes(baseSnippet)).length;
|
|
103
|
+
return overlap >= 3 ? 'divergent' : 'convergent';
|
|
90
104
|
}
|
package/hooks/scope-guard.mjs
CHANGED
|
@@ -69,11 +69,19 @@ export const scopeGuard = {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
if (hasPlan) {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const multiplier = config.planMultiplier || 3;
|
|
73
|
+
warnAt *= multiplier;
|
|
74
|
+
blockAt *= multiplier;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
if (count >= blockAt) {
|
|
78
|
+
// With a plan: downgrade block → warn (planned large tasks are expected)
|
|
79
|
+
if (hasPlan) {
|
|
80
|
+
return {
|
|
81
|
+
type: 'warn',
|
|
82
|
+
message: getMessage(lang, 'scope-guard.block-with-plan', { count, blockAt })
|
|
83
|
+
};
|
|
84
|
+
}
|
|
77
85
|
return {
|
|
78
86
|
type: 'block',
|
|
79
87
|
message: getMessage(lang, 'scope-guard.block', { count })
|
|
@@ -81,9 +89,10 @@ export const scopeGuard = {
|
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
if (count >= warnAt) {
|
|
92
|
+
const msgKey = hasPlan ? 'scope-guard.warn-with-plan' : 'scope-guard.warn';
|
|
84
93
|
return {
|
|
85
94
|
type: 'warn',
|
|
86
|
-
message: getMessage(lang,
|
|
95
|
+
message: getMessage(lang, msgKey, { count, blockAt })
|
|
87
96
|
};
|
|
88
97
|
}
|
|
89
98
|
|
package/lib/config.mjs
CHANGED
|
@@ -14,7 +14,7 @@ const DEFAULTS = {
|
|
|
14
14
|
'commit-guard': { enabled: true, maxTestAge: 300 },
|
|
15
15
|
'debug-loop': { enabled: true, warnAt: 3, blockAt: 5 },
|
|
16
16
|
'test-tracker': { enabled: true },
|
|
17
|
-
'scope-guard': { enabled: true, warnAt: 4, blockAt: 7, agentWarnAt: 8, agentBlockAt: 12 },
|
|
17
|
+
'scope-guard': { enabled: true, warnAt: 4, blockAt: 7, agentWarnAt: 8, agentBlockAt: 12, planMultiplier: 3 },
|
|
18
18
|
'alignment': { enabled: true, driftThreshold: 2 }
|
|
19
19
|
}
|
|
20
20
|
};
|
package/lib/messages.mjs
CHANGED
|
@@ -10,7 +10,9 @@ const MESSAGES = {
|
|
|
10
10
|
'debug-loop.warn.divergent': '🌈 Prism > Debug Loop: {name} edited {count} times on same area. Stop and investigate root cause.',
|
|
11
11
|
'debug-loop.warn.convergent': '🌈 Prism > Debug Loop: {name} edited {count} times (different areas). Consider if this is expected.',
|
|
12
12
|
'scope-guard.block': '🌈 Prism ✋ Scope Guard: {count} unique files modified without a plan. Run /prism to decompose before continuing.',
|
|
13
|
+
'scope-guard.block-with-plan': '🌈 Prism > Scope Guard: {count} files modified (plan threshold: {blockAt}). Verify changes stay within plan scope.',
|
|
13
14
|
'scope-guard.warn': '🌈 Prism > Scope Guard: {count} unique files modified. Consider running /prism to decompose the task.',
|
|
15
|
+
'scope-guard.warn-with-plan': '🌈 Prism > Scope Guard: {count} files modified (plan threshold: {blockAt}). On track if within plan.',
|
|
14
16
|
'scope-guard.plan-detected': '🌈 Prism 📋 Plan file detected. Scope thresholds raised.',
|
|
15
17
|
'test-tracker.warn.failed': '🌈 Prism 📊 Tests FAILED. Fix before committing.',
|
|
16
18
|
};
|