getprismo 0.1.30 → 0.1.32
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 +6 -3
- package/lib/prismo-dev/instructions.js +249 -15
- package/lib/prismo-dev/mcp.js +16 -1
- package/lib/prismo-dev-scan.js +29 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -622,9 +622,10 @@ it surfaces recurring waste patterns such as the same lockfile leaking into many
|
|
|
622
622
|
|
|
623
623
|
```bash
|
|
624
624
|
npx getprismo instructions audit
|
|
625
|
+
npx getprismo instructions ablate --dry-run
|
|
625
626
|
```
|
|
626
627
|
|
|
627
|
-
it scores rules in `CLAUDE.md`, `AGENTS.md`, `.codex/AGENTS.md`, `.codex/instructions.md`, and `.openai/instructions.md`, then
|
|
628
|
+
it scores rules in `CLAUDE.md`, `AGENTS.md`, `.codex/AGENTS.md`, `.codex/instructions.md`, and `.openai/instructions.md`, then separates observable violations, partial compliance, duplicated rules, trim candidates, and influence-unknown rules. `instructions ablate --dry-run` creates a conservative ablation plan with candidates, sample-count guidance, rollback notes, and variance warnings; it does not edit files.
|
|
628
629
|
|
|
629
630
|
`boundaries` checks parallel-agent isolation:
|
|
630
631
|
|
|
@@ -719,7 +720,8 @@ no install needed. npx runs it directly.
|
|
|
719
720
|
| `receipt` | run receipt for reads, repeats, output, artifacts, likely influence, and next-run scope |
|
|
720
721
|
| `replay` | incident replay with root cause and recovery prompt |
|
|
721
722
|
| `timeline` | recurring context-waste patterns across recent sessions |
|
|
722
|
-
| `instructions audit` | instruction ROI audit for CLAUDE.md / AGENTS.md
|
|
723
|
+
| `instructions audit` | instruction ROI audit for CLAUDE.md / AGENTS.md violations, partial compliance, duplicates, and influence-unknown rules |
|
|
724
|
+
| `instructions ablate --dry-run` | conservative ablation plan for instruction candidates without editing files |
|
|
723
725
|
| `boundaries` | multi-agent boundary check for shared files/artifacts and worktree overlap |
|
|
724
726
|
| `scan --usage` | full repo scan with local usage data |
|
|
725
727
|
| `scan --optimizer-fit` | recommend which token-optimization path fits your repo/session |
|
|
@@ -810,6 +812,7 @@ npx getprismo mcp /path/to/repo
|
|
|
810
812
|
- `prismo_cursor_sessions`
|
|
811
813
|
- `prismo_receipt`
|
|
812
814
|
- `prismo_instructions_audit`
|
|
815
|
+
- `prismo_instructions_ablate`
|
|
813
816
|
- `prismo_timeline`
|
|
814
817
|
- `prismo_replay`
|
|
815
818
|
- `prismo_boundaries`
|
|
@@ -990,7 +993,7 @@ lib/prismo-dev/context-optimize.js context packs, scoped prompts
|
|
|
990
993
|
lib/prismo-dev/boundaries.js multi-agent boundary and worktree overlap checks
|
|
991
994
|
lib/prismo-dev/doctor.js doctor/dev/init orchestration
|
|
992
995
|
lib/prismo-dev/fixes.js safe ignore/template generation
|
|
993
|
-
lib/prismo-dev/instructions.js instruction ROI and
|
|
996
|
+
lib/prismo-dev/instructions.js instruction ROI, partial-compliance, and ablation planning
|
|
994
997
|
lib/prismo-dev/mcp.js local MCP server and Prismo tool bindings
|
|
995
998
|
lib/prismo-dev/receipt.js run receipts for reads, output, artifacts, and next scope
|
|
996
999
|
lib/prismo-dev/report.js terminal, markdown, ci reports
|
|
@@ -83,9 +83,11 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
83
83
|
let generatedArtifactMentions = 0;
|
|
84
84
|
let repeatedReadMentions = 0;
|
|
85
85
|
let repeatedCommandMentions = 0;
|
|
86
|
+
let failureMentions = 0;
|
|
86
87
|
let loopSessions = 0;
|
|
87
88
|
for (const session of sessions) {
|
|
88
89
|
toolOutputTokens += Number(session.estimatedToolTokens || 0);
|
|
90
|
+
failureMentions += Number(session.failureMentions || 0);
|
|
89
91
|
if (session.loopSuspicion) loopSessions += 1;
|
|
90
92
|
for (const item of session.repeatedPathMentions || []) {
|
|
91
93
|
evidenceText.push(item.value);
|
|
@@ -107,10 +109,55 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
107
109
|
generatedArtifactMentions,
|
|
108
110
|
repeatedReadMentions,
|
|
109
111
|
repeatedCommandMentions,
|
|
112
|
+
failureMentions,
|
|
110
113
|
loopSessions,
|
|
111
114
|
};
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
function normalizeEvidenceTerm(value) {
|
|
118
|
+
return normalizeRule(String(value || "").replace(/['"`]/g, ""));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractRuleTerms(ruleText) {
|
|
122
|
+
const source = String(ruleText || "");
|
|
123
|
+
const terms = [];
|
|
124
|
+
const pathPattern = /(?:[\w.@-]+\/)+[\w.@+-]+\.[A-Za-z0-9]{1,12}|[\w.@+-]+\.[A-Za-z0-9]{1,12}/g;
|
|
125
|
+
for (const match of source.matchAll(pathPattern)) terms.push(match[0]);
|
|
126
|
+
|
|
127
|
+
const beforeEditMatch = source.match(/\b(?:edit|editing|change|changing|modify|modifying|touch|touching)\s+([A-Za-z0-9_./@-]+(?:\s+[A-Za-z0-9_./@-]+){0,2})/i);
|
|
128
|
+
if (beforeEditMatch) terms.push(beforeEditMatch[1]);
|
|
129
|
+
|
|
130
|
+
return Array.from(new Set(terms.map(normalizeEvidenceTerm).filter((term) => term.length >= 3)));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getRitualComplianceSignal(rule, evidence) {
|
|
134
|
+
const text = rule.normalized;
|
|
135
|
+
const asksReadBeforeEdit =
|
|
136
|
+
/\b(?:always|must|first|before|prior to)\b/.test(text) &&
|
|
137
|
+
/\b(?:read|review|check|inspect|open)\b/.test(text) &&
|
|
138
|
+
/\b(?:before|prior to)\b/.test(text) &&
|
|
139
|
+
/\b(?:edit|editing|change|changing|modify|modifying|touch|touching)\b/.test(text);
|
|
140
|
+
if (!asksReadBeforeEdit) return null;
|
|
141
|
+
|
|
142
|
+
const terms = extractRuleTerms(rule.text);
|
|
143
|
+
if (terms.length < 2) return null;
|
|
144
|
+
|
|
145
|
+
const matchedTerms = terms.filter((term) => evidence.text.includes(term));
|
|
146
|
+
const enoughRitualEvidence = matchedTerms.length >= 2;
|
|
147
|
+
const outcomeStillNoisy =
|
|
148
|
+
evidence.failureMentions > 0 ||
|
|
149
|
+
evidence.repeatedCommandMentions >= 3 ||
|
|
150
|
+
evidence.toolOutputTokens >= 25000 ||
|
|
151
|
+
evidence.loopSessions > 0;
|
|
152
|
+
|
|
153
|
+
if (!enoughRitualEvidence || !outcomeStillNoisy) return null;
|
|
154
|
+
return {
|
|
155
|
+
terms,
|
|
156
|
+
matchedTerms,
|
|
157
|
+
reason: "read-before-edit ritual appeared in session evidence, but noisy/failing session evidence suggests it may not have constrained the final work",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
114
161
|
function classifyRule(rule, evidence, duplicateCount) {
|
|
115
162
|
const text = rule.normalized;
|
|
116
163
|
const evidenceHits = rule.keywords.filter((keyword) => evidence.text.includes(keyword));
|
|
@@ -132,15 +179,24 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
132
179
|
|
|
133
180
|
if (evidenceHits.length) signals.push("session-correlated");
|
|
134
181
|
if (asksNoArtifacts || asksNoOutputFlood || asksNarrowReads) signals.push("guardrail");
|
|
182
|
+
const ritualCompliance = getRitualComplianceSignal(rule, evidence);
|
|
183
|
+
if (ritualCompliance) {
|
|
184
|
+
concerns.push("paraphrased-not-applied");
|
|
185
|
+
signals.push("ritual-compliance");
|
|
186
|
+
}
|
|
135
187
|
|
|
136
188
|
let status = "unclear";
|
|
137
189
|
let recommendation = "Keep for now, but review after more sessions.";
|
|
138
190
|
let score = 35;
|
|
139
191
|
|
|
140
192
|
if (concerns.includes("violated-by-session-evidence")) {
|
|
141
|
-
status = "
|
|
193
|
+
status = "observably-violated";
|
|
142
194
|
score = 15;
|
|
143
|
-
recommendation = `
|
|
195
|
+
recommendation = `Keep the intent, but move it into a live guardrail/firewall or make it more enforceable; session evidence shows a measurable violation.`;
|
|
196
|
+
} else if (concerns.includes("paraphrased-not-applied")) {
|
|
197
|
+
status = "partial-compliance";
|
|
198
|
+
score = 40;
|
|
199
|
+
recommendation = "Rewrite this as an observable acceptance check; the session appears to perform the read/check ritual but still shows failure or noisy follow-through.";
|
|
144
200
|
} else if (signals.includes("session-correlated") && signals.includes("directive")) {
|
|
145
201
|
status = "pulling-weight";
|
|
146
202
|
score = 80;
|
|
@@ -154,9 +210,9 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
154
210
|
score = 25;
|
|
155
211
|
recommendation = "Deduplicate this rule so it is paid for once, not in every instruction file.";
|
|
156
212
|
} else if (concerns.includes("low-signal")) {
|
|
157
|
-
status = "
|
|
158
|
-
score =
|
|
159
|
-
recommendation = "
|
|
213
|
+
status = "influence-unknown";
|
|
214
|
+
score = 45;
|
|
215
|
+
recommendation = "Do not prune automatically. This rule has no grep-able behavioral signal; use A/B ablation or rewrite it into observable instructions.";
|
|
160
216
|
} else if (rule.tokens > 80) {
|
|
161
217
|
status = "trim-candidate";
|
|
162
218
|
score = 30;
|
|
@@ -170,6 +226,7 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
170
226
|
signals,
|
|
171
227
|
concerns,
|
|
172
228
|
evidenceHits,
|
|
229
|
+
partialCompliance: ritualCompliance || null,
|
|
173
230
|
recommendation,
|
|
174
231
|
};
|
|
175
232
|
}
|
|
@@ -189,10 +246,13 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
189
246
|
|
|
190
247
|
const byStatus = {};
|
|
191
248
|
for (const rule of auditedRules) byStatus[rule.status] = (byStatus[rule.status] || 0) + 1;
|
|
192
|
-
const
|
|
249
|
+
const prunable = auditedRules.filter((rule) => ["observably-violated", "duplicate", "trim-candidate"].includes(rule.status));
|
|
250
|
+
const partialCompliance = auditedRules.filter((rule) => rule.status === "partial-compliance");
|
|
251
|
+
const influenceUnknown = auditedRules.filter((rule) => rule.status === "influence-unknown");
|
|
193
252
|
const pullingWeight = auditedRules.filter((rule) => rule.status === "pulling-weight" || rule.status === "guardrail-no-recent-violation");
|
|
194
253
|
const duplicatedRules = auditedRules.filter((rule) => (duplicates.get(rule.normalized) || 0) > 1);
|
|
195
|
-
const
|
|
254
|
+
const prunableTokensPerLoad = prunable.reduce((sum, rule) => sum + Number(rule.tokens || 0), 0);
|
|
255
|
+
const sortedPrunable = prunable.sort((a, b) => a.score - b.score || b.tokens - a.tokens);
|
|
196
256
|
|
|
197
257
|
return {
|
|
198
258
|
schemaVersion: 1,
|
|
@@ -208,20 +268,30 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
208
268
|
generatedArtifactMentions: evidence.generatedArtifactMentions,
|
|
209
269
|
repeatedReadMentions: evidence.repeatedReadMentions,
|
|
210
270
|
repeatedCommandMentions: evidence.repeatedCommandMentions,
|
|
271
|
+
failureMentions: evidence.failureMentions,
|
|
211
272
|
loopSessions: evidence.loopSessions,
|
|
212
273
|
},
|
|
213
274
|
summary: {
|
|
214
275
|
totalRules: auditedRules.length,
|
|
215
276
|
byStatus,
|
|
216
|
-
|
|
277
|
+
prunableCandidates: prunable.length,
|
|
278
|
+
partialCompliance: partialCompliance.length,
|
|
279
|
+
influenceUnknown: influenceUnknown.length,
|
|
217
280
|
duplicatedRules: duplicatedRules.length,
|
|
218
|
-
|
|
281
|
+
prunableTokensPerLoad,
|
|
282
|
+
deadWeightCandidates: prunable.length,
|
|
283
|
+
wastedTokensPerLoad: prunableTokensPerLoad,
|
|
219
284
|
},
|
|
220
285
|
pullingWeight: pullingWeight.sort((a, b) => b.score - a.score).slice(0, 10),
|
|
221
|
-
|
|
286
|
+
prunable: sortedPrunable.slice(0, 20),
|
|
287
|
+
partialCompliance: partialCompliance.sort((a, b) => a.score - b.score || b.tokens - a.tokens).slice(0, 20),
|
|
288
|
+
influenceUnknown: influenceUnknown.sort((a, b) => b.tokens - a.tokens).slice(0, 20),
|
|
289
|
+
deadWeight: sortedPrunable.slice(0, 20),
|
|
222
290
|
rules: auditedRules,
|
|
223
291
|
next: [
|
|
224
|
-
|
|
292
|
+
prunable.length ? "Fix observably violated, duplicate, or trim-safe rules first." : "No safely prunable rules detected yet.",
|
|
293
|
+
partialCompliance.length ? "Rewrite partial-compliance rules as observable acceptance checks." : null,
|
|
294
|
+
influenceUnknown.length ? "Treat influence-unknown rules as A/B ablation candidates, not safe deletes." : null,
|
|
225
295
|
duplicatedRules.length ? "Deduplicate repeated rules across CLAUDE.md / AGENTS.md / Codex instruction files." : null,
|
|
226
296
|
evidence.sessions.length < 5 ? "Run this again after more sessions; multi-session evidence makes instruction ROI stronger." : null,
|
|
227
297
|
`${NPX_COMMAND} firewall <task> for rules that only matter in one workflow.`,
|
|
@@ -242,7 +312,9 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
242
312
|
lines.push(`Files: ${audit.files.map((file) => `${file.path} (${formatTokenCount(file.tokens)} tokens)`).join(", ")}`);
|
|
243
313
|
lines.push(`Rules: ${audit.summary.totalRules}`);
|
|
244
314
|
lines.push(`Sessions analyzed: ${audit.sessionsAnalyzed}`);
|
|
245
|
-
lines.push(`
|
|
315
|
+
lines.push(`Safely prunable candidates: ${audit.summary.prunableCandidates} (~${formatTokenCount(audit.summary.prunableTokensPerLoad)} tokens/load)`);
|
|
316
|
+
lines.push(`Partial compliance: ${audit.summary.partialCompliance}`);
|
|
317
|
+
lines.push(`Influence unknown: ${audit.summary.influenceUnknown}`);
|
|
246
318
|
lines.push(`Duplicates: ${audit.summary.duplicatedRules}`);
|
|
247
319
|
lines.push("");
|
|
248
320
|
lines.push("Pulling Weight");
|
|
@@ -254,9 +326,9 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
254
326
|
lines.push("- No high-confidence useful rules detected yet.");
|
|
255
327
|
}
|
|
256
328
|
lines.push("");
|
|
257
|
-
lines.push("
|
|
258
|
-
if (audit.
|
|
259
|
-
audit.
|
|
329
|
+
lines.push("Safely Prunable / Rewrite Candidates");
|
|
330
|
+
if (audit.prunable.length) {
|
|
331
|
+
audit.prunable.slice(0, 8).forEach((rule) => {
|
|
260
332
|
lines.push(`- ${rule.file}:${rule.line} [${rule.status}] ${rule.text}`);
|
|
261
333
|
lines.push(` ${rule.recommendation}`);
|
|
262
334
|
});
|
|
@@ -264,13 +336,175 @@ module.exports = function createInstructionsAudit(deps) {
|
|
|
264
336
|
lines.push("- No obvious candidates.");
|
|
265
337
|
}
|
|
266
338
|
lines.push("");
|
|
339
|
+
lines.push("Partial Compliance / Ritual Without Proof");
|
|
340
|
+
if (audit.partialCompliance.length) {
|
|
341
|
+
audit.partialCompliance.slice(0, 6).forEach((rule) => {
|
|
342
|
+
lines.push(`- ${rule.file}:${rule.line} [${rule.status}] ${rule.text}`);
|
|
343
|
+
if (rule.partialCompliance?.matchedTerms?.length) {
|
|
344
|
+
lines.push(` Matched: ${rule.partialCompliance.matchedTerms.join(", ")}`);
|
|
345
|
+
}
|
|
346
|
+
lines.push(` ${rule.recommendation}`);
|
|
347
|
+
});
|
|
348
|
+
} else {
|
|
349
|
+
lines.push("- None detected.");
|
|
350
|
+
}
|
|
351
|
+
lines.push("");
|
|
352
|
+
lines.push("Influence Unknown / A-B Candidates");
|
|
353
|
+
if (audit.influenceUnknown.length) {
|
|
354
|
+
audit.influenceUnknown.slice(0, 6).forEach((rule) => {
|
|
355
|
+
lines.push(`- ${rule.file}:${rule.line} [${rule.status}] ${rule.text}`);
|
|
356
|
+
lines.push(` ${rule.recommendation}`);
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
lines.push("- None detected.");
|
|
360
|
+
}
|
|
361
|
+
lines.push("");
|
|
267
362
|
lines.push("Next");
|
|
268
363
|
audit.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
269
364
|
return lines.join("\n");
|
|
270
365
|
}
|
|
271
366
|
|
|
367
|
+
function makeAblationCandidate(rule, mode, reason, expectedSignal) {
|
|
368
|
+
return {
|
|
369
|
+
id: rule.id,
|
|
370
|
+
file: rule.file,
|
|
371
|
+
line: rule.line,
|
|
372
|
+
text: rule.text,
|
|
373
|
+
status: rule.status,
|
|
374
|
+
tokens: rule.tokens,
|
|
375
|
+
mode,
|
|
376
|
+
reason,
|
|
377
|
+
expectedSignal,
|
|
378
|
+
rollback: `Restore ${rule.file}:${rule.line} if failures, context growth, or repeated reads increase during the ablation window.`,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function buildInstructionsAblationPlan(options = {}) {
|
|
383
|
+
const root = options.cwd || process.cwd();
|
|
384
|
+
const samples = Math.max(3, Number(options.samples || 10));
|
|
385
|
+
const audit = buildInstructionsAudit(options);
|
|
386
|
+
const duplicateCandidates = audit.prunable
|
|
387
|
+
.filter((rule) => rule.status === "duplicate")
|
|
388
|
+
.map((rule) => makeAblationCandidate(
|
|
389
|
+
rule,
|
|
390
|
+
"dedupe",
|
|
391
|
+
"Duplicate rule appears more than once; remove or consolidate the repeated copy first.",
|
|
392
|
+
"No regression in repeated reads, artifact leaks, or task failures after dedupe."
|
|
393
|
+
));
|
|
394
|
+
const trimCandidates = audit.prunable
|
|
395
|
+
.filter((rule) => rule.status === "trim-candidate")
|
|
396
|
+
.map((rule) => makeAblationCandidate(
|
|
397
|
+
rule,
|
|
398
|
+
"trim",
|
|
399
|
+
"Verbose persistent rule adds recurring context weight; test a shorter equivalent.",
|
|
400
|
+
"Similar session outcomes with fewer persistent-instruction tokens."
|
|
401
|
+
));
|
|
402
|
+
const violationCandidates = audit.prunable
|
|
403
|
+
.filter((rule) => rule.status === "observably-violated")
|
|
404
|
+
.map((rule) => makeAblationCandidate(
|
|
405
|
+
rule,
|
|
406
|
+
"rewrite-as-guardrail",
|
|
407
|
+
"The rule maps to measurable session violations; do not simply delete the intent.",
|
|
408
|
+
"Violation count falls after moving the rule into a live guardrail/firewall or making it more enforceable."
|
|
409
|
+
));
|
|
410
|
+
const partialCandidates = audit.partialCompliance.map((rule) => makeAblationCandidate(
|
|
411
|
+
rule,
|
|
412
|
+
"rewrite-as-acceptance-check",
|
|
413
|
+
"The session appears to perform the visible ritual, but noisy/failing evidence suggests weak follow-through.",
|
|
414
|
+
"The agent reports how the referenced file constrained the edit, and related failures/retries decrease."
|
|
415
|
+
));
|
|
416
|
+
const influenceCandidates = audit.influenceUnknown.map((rule) => makeAblationCandidate(
|
|
417
|
+
rule,
|
|
418
|
+
"ablate",
|
|
419
|
+
"Influence is not directly observable; remove only for a controlled sample window.",
|
|
420
|
+
"No worse outcome across matched task runs, with stable decoding settings and similar task mix."
|
|
421
|
+
));
|
|
422
|
+
|
|
423
|
+
const candidates = [
|
|
424
|
+
...duplicateCandidates,
|
|
425
|
+
...trimCandidates,
|
|
426
|
+
...violationCandidates,
|
|
427
|
+
...partialCandidates,
|
|
428
|
+
...influenceCandidates,
|
|
429
|
+
].slice(0, options.limit || 20);
|
|
430
|
+
|
|
431
|
+
const groups = {
|
|
432
|
+
cheapWins: duplicateCandidates.length + trimCandidates.length,
|
|
433
|
+
rewriteFirst: violationCandidates.length + partialCandidates.length,
|
|
434
|
+
controlledAblation: influenceCandidates.length,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
schemaVersion: 1,
|
|
439
|
+
command: "instructions ablate",
|
|
440
|
+
dryRun: true,
|
|
441
|
+
generatedAt: new Date().toISOString(),
|
|
442
|
+
scannedPath: root,
|
|
443
|
+
confidence: audit.confidence,
|
|
444
|
+
samples,
|
|
445
|
+
auditSummary: audit.summary,
|
|
446
|
+
groups,
|
|
447
|
+
candidates,
|
|
448
|
+
protocol: [
|
|
449
|
+
"Change one instruction candidate at a time; do not rewrite the whole file during the same window.",
|
|
450
|
+
`Collect at least ${samples} comparable sessions before trusting an influence-unknown ablation.`,
|
|
451
|
+
"Keep model, decoding temperature, tools, and task type as stable as possible.",
|
|
452
|
+
"Track repeated reads, generated artifact leaks, command loops, failures, and task completion quality.",
|
|
453
|
+
"Prefer dedupe/trim and scoped guardrail rewrites before removing aspirational rules.",
|
|
454
|
+
],
|
|
455
|
+
warnings: [
|
|
456
|
+
"Ablation is noisy: removing one rule can re-weight attention across the remaining instruction file.",
|
|
457
|
+
"Influence-unknown does not mean dead; it means local logs do not expose a clean behavioral signal.",
|
|
458
|
+
"Observably violated and partial-compliance rules should usually be rewritten or scoped, not blindly deleted.",
|
|
459
|
+
],
|
|
460
|
+
next: candidates.length
|
|
461
|
+
? [
|
|
462
|
+
`Start with ${candidates[0].file}:${candidates[0].line} (${candidates[0].mode}).`,
|
|
463
|
+
"Run the next sessions with a receipt/timeline comparison.",
|
|
464
|
+
`${NPX_COMMAND} receipt --limit ${Math.min(samples, 10)}`,
|
|
465
|
+
]
|
|
466
|
+
: [
|
|
467
|
+
"No ablation candidates found. Run instructions audit again after more sessions.",
|
|
468
|
+
],
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function renderInstructionsAblationTerminal(plan) {
|
|
473
|
+
const lines = [];
|
|
474
|
+
lines.push("");
|
|
475
|
+
lines.push("Prismo Instruction Ablation Plan");
|
|
476
|
+
lines.push("");
|
|
477
|
+
lines.push("Mode: dry run (no files changed)");
|
|
478
|
+
lines.push(`Candidates: ${plan.candidates.length}`);
|
|
479
|
+
lines.push(`Sample target: ${plan.samples} comparable sessions`);
|
|
480
|
+
lines.push(`Cheap wins: ${plan.groups.cheapWins} | Rewrite first: ${plan.groups.rewriteFirst} | Controlled ablation: ${plan.groups.controlledAblation}`);
|
|
481
|
+
lines.push("");
|
|
482
|
+
lines.push("Candidates");
|
|
483
|
+
if (plan.candidates.length) {
|
|
484
|
+
plan.candidates.slice(0, 10).forEach((candidate, index) => {
|
|
485
|
+
lines.push(`${index + 1}. ${candidate.file}:${candidate.line} [${candidate.mode}] ${candidate.text}`);
|
|
486
|
+
lines.push(` Why: ${candidate.reason}`);
|
|
487
|
+
lines.push(` Measure: ${candidate.expectedSignal}`);
|
|
488
|
+
});
|
|
489
|
+
} else {
|
|
490
|
+
lines.push("- No candidates found.");
|
|
491
|
+
}
|
|
492
|
+
lines.push("");
|
|
493
|
+
lines.push("Protocol");
|
|
494
|
+
plan.protocol.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
495
|
+
lines.push("");
|
|
496
|
+
lines.push("Warnings");
|
|
497
|
+
plan.warnings.forEach((item) => lines.push(`- ${item}`));
|
|
498
|
+
lines.push("");
|
|
499
|
+
lines.push("Next");
|
|
500
|
+
plan.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
501
|
+
return lines.join("\n");
|
|
502
|
+
}
|
|
503
|
+
|
|
272
504
|
return {
|
|
505
|
+
buildInstructionsAblationPlan,
|
|
273
506
|
buildInstructionsAudit,
|
|
507
|
+
renderInstructionsAblationTerminal,
|
|
274
508
|
renderInstructionsAuditTerminal,
|
|
275
509
|
};
|
|
276
510
|
};
|
package/lib/prismo-dev/mcp.js
CHANGED
|
@@ -33,6 +33,7 @@ function createMcpTools(deps) {
|
|
|
33
33
|
getClaudeCodeCostSummary,
|
|
34
34
|
getCursorSessionSummary,
|
|
35
35
|
buildBoundaryCheck,
|
|
36
|
+
buildInstructionsAblationPlan,
|
|
36
37
|
buildInstructionsAudit,
|
|
37
38
|
buildMultiSessionTimeline,
|
|
38
39
|
buildReceipt,
|
|
@@ -122,10 +123,15 @@ function createMcpTools(deps) {
|
|
|
122
123
|
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
123
124
|
limit: limitProperty,
|
|
124
125
|
}),
|
|
125
|
-
makeTool("prismo_instructions_audit", "Audit persistent instruction files for useful rules, duplicates, and
|
|
126
|
+
makeTool("prismo_instructions_audit", "Audit persistent instruction files for useful rules, observable violations, partial compliance, duplicates, and influence-unknown rules.", {
|
|
126
127
|
path: pathProperty,
|
|
127
128
|
limit: limitProperty,
|
|
128
129
|
}),
|
|
130
|
+
makeTool("prismo_instructions_ablate", "Create a dry-run instruction ablation plan with candidates, protocol, and variance warnings.", {
|
|
131
|
+
path: pathProperty,
|
|
132
|
+
limit: limitProperty,
|
|
133
|
+
samples: { type: "number", description: "Comparable session sample target for controlled ablation." },
|
|
134
|
+
}),
|
|
129
135
|
makeTool("prismo_timeline", "Return recurring context-waste patterns across recent sessions.", {
|
|
130
136
|
path: pathProperty,
|
|
131
137
|
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
@@ -254,6 +260,14 @@ function createMcpTools(deps) {
|
|
|
254
260
|
}));
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
if (name === "prismo_instructions_ablate") {
|
|
264
|
+
return createTextResult(buildInstructionsAblationPlan({
|
|
265
|
+
cwd: target,
|
|
266
|
+
limit: Number(args.limit) || 20,
|
|
267
|
+
samples: Number(args.samples) || 10,
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
|
|
257
271
|
if (name === "prismo_timeline") {
|
|
258
272
|
return createTextResult(buildMultiSessionTimeline({
|
|
259
273
|
cwd: target,
|
|
@@ -379,6 +393,7 @@ async function runMcpDoctor(deps) {
|
|
|
379
393
|
"prismo_cursor_sessions",
|
|
380
394
|
"prismo_receipt",
|
|
381
395
|
"prismo_instructions_audit",
|
|
396
|
+
"prismo_instructions_ablate",
|
|
382
397
|
"prismo_timeline",
|
|
383
398
|
"prismo_replay",
|
|
384
399
|
"prismo_boundaries",
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -260,7 +260,9 @@ const {
|
|
|
260
260
|
});
|
|
261
261
|
|
|
262
262
|
const {
|
|
263
|
+
buildInstructionsAblationPlan,
|
|
263
264
|
buildInstructionsAudit,
|
|
265
|
+
renderInstructionsAblationTerminal,
|
|
264
266
|
renderInstructionsAuditTerminal,
|
|
265
267
|
} = require("./prismo-dev/instructions")({
|
|
266
268
|
fs,
|
|
@@ -334,6 +336,7 @@ Usage:
|
|
|
334
336
|
prismo cursor [list|authorship|timeline|files|all] [--json] [--limit N] [path]
|
|
335
337
|
prismo receipt [codex|claude|cursor|all] [--json] [--limit N] [path]
|
|
336
338
|
prismo instructions audit [--json] [--limit N] [path]
|
|
339
|
+
prismo instructions ablate --dry-run [--json] [--limit N] [--samples N] [path]
|
|
337
340
|
prismo timeline [codex|claude|cursor|all] [--json] [--last N] [path]
|
|
338
341
|
prismo replay [codex|claude|cursor|all] [--json] [--limit N] [path]
|
|
339
342
|
prismo boundaries [codex|claude|cursor|all] [--json] [--limit N] [path]
|
|
@@ -355,7 +358,7 @@ Commands:
|
|
|
355
358
|
cc Show Claude Code token cost, cache cost, and all-time totals.
|
|
356
359
|
cursor Show Cursor session data, AI authorship, and AI-generated file tracking.
|
|
357
360
|
receipt Generate a run receipt for reads, repeats, output, artifacts, and next-run scope.
|
|
358
|
-
instructions Audit CLAUDE.md / AGENTS.md rule ROI and
|
|
361
|
+
instructions Audit or ablate CLAUDE.md / AGENTS.md rule ROI, violations, partial compliance, duplicates, and influence-unknown rules.
|
|
359
362
|
timeline Show recurring context-waste patterns across recent sessions.
|
|
360
363
|
replay Reconstruct why a session went sideways and print a recovery prompt.
|
|
361
364
|
boundaries Check whether parallel agents are isolated or overlapping.
|
|
@@ -514,14 +517,18 @@ Output:
|
|
|
514
517
|
|
|
515
518
|
Usage:
|
|
516
519
|
prismo instructions audit [--json] [--limit N] [path]
|
|
520
|
+
prismo instructions ablate --dry-run [--json] [--limit N] [--samples N] [path]
|
|
517
521
|
|
|
518
522
|
Examples:
|
|
519
523
|
prismo instructions audit
|
|
520
524
|
prismo instructions audit --json
|
|
525
|
+
prismo instructions ablate --dry-run
|
|
526
|
+
prismo instructions ablate --dry-run --json
|
|
521
527
|
|
|
522
528
|
Output:
|
|
523
529
|
Scores persistent instruction rules from CLAUDE.md, AGENTS.md, and Codex/OpenAI instruction files.
|
|
524
|
-
Highlights useful guardrails, duplicates,
|
|
530
|
+
Highlights useful guardrails, duplicates, partial compliance, influence-unknown lines, and conservative ablation candidates.
|
|
531
|
+
Ablation mode is dry-run only: it produces a protocol and candidate list without editing files.`,
|
|
525
532
|
timeline: `Prismo Multi-Session Timeline
|
|
526
533
|
|
|
527
534
|
Usage:
|
|
@@ -887,6 +894,7 @@ async function runCli(argv) {
|
|
|
887
894
|
getClaudeCodeCostSummary,
|
|
888
895
|
getCursorSessionSummary,
|
|
889
896
|
buildBoundaryCheck,
|
|
897
|
+
buildInstructionsAblationPlan,
|
|
890
898
|
buildInstructionsAudit,
|
|
891
899
|
buildMultiSessionTimeline,
|
|
892
900
|
buildReceipt,
|
|
@@ -1051,11 +1059,25 @@ async function runCli(argv) {
|
|
|
1051
1059
|
if (command === "instructions") {
|
|
1052
1060
|
const json = rest.includes("--json");
|
|
1053
1061
|
const limitIndex = rest.indexOf("--limit");
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1062
|
+
const samplesIndex = rest.indexOf("--samples");
|
|
1063
|
+
const positional = getPositionals(rest, new Set(["--limit", "--samples"]));
|
|
1064
|
+
const subcommand = ["audit", "ablate"].includes(positional[0]?.toLowerCase()) ? positional[0].toLowerCase() : "audit";
|
|
1065
|
+
const target = ["audit", "ablate"].includes(positional[0]?.toLowerCase())
|
|
1057
1066
|
? positional[1] || process.cwd()
|
|
1058
1067
|
: positional[0] || process.cwd();
|
|
1068
|
+
if (subcommand === "ablate") {
|
|
1069
|
+
const plan = buildInstructionsAblationPlan({
|
|
1070
|
+
cwd: path.resolve(target),
|
|
1071
|
+
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
|
|
1072
|
+
samples: parsePositiveInt(samplesIndex >= 0 ? rest[samplesIndex + 1] : null, 10),
|
|
1073
|
+
});
|
|
1074
|
+
if (json) {
|
|
1075
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
console.log(renderInstructionsAblationTerminal(plan));
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1059
1081
|
const audit = buildInstructionsAudit({
|
|
1060
1082
|
cwd: path.resolve(target),
|
|
1061
1083
|
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
|
|
@@ -1317,6 +1339,7 @@ module.exports = {
|
|
|
1317
1339
|
renderSimpleScanReport,
|
|
1318
1340
|
renderClaudeCostTerminal,
|
|
1319
1341
|
renderCursorTerminal,
|
|
1342
|
+
renderInstructionsAblationTerminal,
|
|
1320
1343
|
renderInstructionsAuditTerminal,
|
|
1321
1344
|
renderMultiSessionTimelineTerminal,
|
|
1322
1345
|
renderReceiptTerminal,
|
|
@@ -1342,6 +1365,7 @@ module.exports = {
|
|
|
1342
1365
|
getClaudeCodeCostSummary,
|
|
1343
1366
|
getCursorSessionSummary,
|
|
1344
1367
|
buildBoundaryCheck,
|
|
1368
|
+
buildInstructionsAblationPlan,
|
|
1345
1369
|
buildReceipt,
|
|
1346
1370
|
buildInstructionsAudit,
|
|
1347
1371
|
buildMultiSessionTimeline,
|
package/package.json
CHANGED