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 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 flags duplicated rules, low-signal rules, trim candidates, and rules that appear ineffective based on recent session evidence.
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 dead-weight rules |
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 dead-rule analysis
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 = "ignored-or-ineffective";
193
+ status = "observably-violated";
142
194
  score = 15;
143
- recommendation = `Move this rule into a live guardrail/firewall or make it more specific; session evidence suggests it is not changing behavior.`;
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 = "dead-weight-candidate";
158
- score = 20;
159
- recommendation = "Rewrite with observable behavior or remove if it is generic preference text.";
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 deadWeight = auditedRules.filter((rule) => ["dead-weight-candidate", "ignored-or-ineffective", "duplicate", "trim-candidate"].includes(rule.status));
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 wastedTokensPerLoad = deadWeight.reduce((sum, rule) => sum + Number(rule.tokens || 0), 0);
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
- deadWeightCandidates: deadWeight.length,
277
+ prunableCandidates: prunable.length,
278
+ partialCompliance: partialCompliance.length,
279
+ influenceUnknown: influenceUnknown.length,
217
280
  duplicatedRules: duplicatedRules.length,
218
- wastedTokensPerLoad,
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
- deadWeight: deadWeight.sort((a, b) => a.score - b.score || b.tokens - a.tokens).slice(0, 20),
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
- deadWeight.length ? "Trim or rewrite dead-weight candidates first." : "No obvious dead-weight rules detected yet.",
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(`Dead-weight candidates: ${audit.summary.deadWeightCandidates} (~${formatTokenCount(audit.summary.wastedTokensPerLoad)} tokens/load)`);
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("Dead Weight / Rewrite Candidates");
258
- if (audit.deadWeight.length) {
259
- audit.deadWeight.slice(0, 8).forEach((rule) => {
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
  };
@@ -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 dead-weight candidates.", {
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",
@@ -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 dead-weight candidates.
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, stale or low-signal lines, and rules that look ignored by recent session evidence.`,
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 positional = getPositionals(rest, new Set(["--limit"]));
1055
- const subcommand = positional[0] && positional[0].toLowerCase() === "audit" ? "audit" : "audit";
1056
- const target = subcommand === "audit" && positional[0]?.toLowerCase() === "audit"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Local AI coding workflow scanner for Codex, Claude Code, Cursor, and token-waste diagnostics.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/shanirsh/prismodev#readme",