getprismo 0.1.29 → 0.1.30
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 +71 -3
- package/lib/prismo-dev/boundaries.js +86 -0
- package/lib/prismo-dev/instructions.js +276 -0
- package/lib/prismo-dev/mcp.js +75 -2
- package/lib/prismo-dev/receipt.js +291 -0
- package/lib/prismo-dev/replay.js +191 -0
- package/lib/prismo-dev/timeline.js +185 -0
- package/lib/prismo-dev/usage-watch.js +1 -0
- package/lib/prismo-dev-scan.js +251 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,13 +32,15 @@ prismodev covers the full AI coding session:
|
|
|
32
32
|
before you code npx getprismo doctor
|
|
33
33
|
while you code npx getprismo watch
|
|
34
34
|
noisy commands npx getprismo shield -- npm test
|
|
35
|
-
after you code npx getprismo
|
|
35
|
+
after you code npx getprismo receipt
|
|
36
|
+
postmortem npx getprismo replay
|
|
36
37
|
agent-native npx getprismo mcp
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
**doctor** diagnoses the repo, applies safe fixes, and shows the before/after score.
|
|
40
41
|
**watch** monitors context pressure live and warns when things go wrong.
|
|
41
|
-
**
|
|
42
|
+
**receipt** explains what repeated, what output dominated, what artifacts leaked, and what likely influenced the run.
|
|
43
|
+
**replay** reconstructs why a session went sideways and prints a recovery prompt.
|
|
42
44
|
**shield** runs noisy commands without dumping full output back into the agent context.
|
|
43
45
|
**mcp** exposes PrismoDev as local tools so compatible agents can scan, search shield output, and request scoped context directly.
|
|
44
46
|
|
|
@@ -589,6 +591,51 @@ the `prismo_cursor_sessions` mcp tool exposes all of this to compatible agents.
|
|
|
589
591
|
|
|
590
592
|
---
|
|
591
593
|
|
|
594
|
+
## run receipts and incident replay
|
|
595
|
+
|
|
596
|
+
`receipt` turns recent local sessions into a plain-English run receipt:
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
npx getprismo receipt
|
|
600
|
+
npx getprismo receipt codex --json
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
it summarizes repeated reads, generated artifacts, tool-output floods, repeated commands, likely influence, and the next scoped action to take.
|
|
604
|
+
|
|
605
|
+
`replay` is the postmortem view:
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
npx getprismo replay
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
it classifies the incident pattern, explains what happened, and prints a recovery prompt for the next agent run.
|
|
612
|
+
|
|
613
|
+
`timeline` looks across many sessions instead of one:
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
npx getprismo timeline --last 20
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
it surfaces recurring waste patterns such as the same lockfile leaking into many sessions, the same source file being repeatedly reread, or several sessions crossing high context pressure.
|
|
620
|
+
|
|
621
|
+
`instructions audit` looks at persistent rules:
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
npx getprismo instructions audit
|
|
625
|
+
```
|
|
626
|
+
|
|
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
|
+
|
|
629
|
+
`boundaries` checks parallel-agent isolation:
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
npx getprismo boundaries
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
it reports whether visible local agents are overlapping on the same files, leaking the same artifacts, or running noisy sessions that should move into shield or separate worktrees.
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
592
639
|
## how watch catches waste live
|
|
593
640
|
|
|
594
641
|
watch reads local session logs from codex, claude code, and cursor. it detects:
|
|
@@ -645,7 +692,8 @@ npx getprismo scan --simple
|
|
|
645
692
|
# the full workflow
|
|
646
693
|
npx getprismo doctor
|
|
647
694
|
npx getprismo watch --once
|
|
648
|
-
npx getprismo
|
|
695
|
+
npx getprismo receipt
|
|
696
|
+
npx getprismo replay
|
|
649
697
|
```
|
|
650
698
|
|
|
651
699
|
if you don't have node installed, get it from [nodejs.org](https://nodejs.org) (LTS). then:
|
|
@@ -668,6 +716,11 @@ no install needed. npx runs it directly.
|
|
|
668
716
|
| `cc` | claude code cost breakdown |
|
|
669
717
|
| `cc timeline` | session reconstruction with events |
|
|
670
718
|
| `cursor` | cursor session tracking and ai authorship |
|
|
719
|
+
| `receipt` | run receipt for reads, repeats, output, artifacts, likely influence, and next-run scope |
|
|
720
|
+
| `replay` | incident replay with root cause and recovery prompt |
|
|
721
|
+
| `timeline` | recurring context-waste patterns across recent sessions |
|
|
722
|
+
| `instructions audit` | instruction ROI audit for CLAUDE.md / AGENTS.md dead-weight rules |
|
|
723
|
+
| `boundaries` | multi-agent boundary check for shared files/artifacts and worktree overlap |
|
|
671
724
|
| `scan --usage` | full repo scan with local usage data |
|
|
672
725
|
| `scan --optimizer-fit` | recommend which token-optimization path fits your repo/session |
|
|
673
726
|
| `scan --report-card` | shortest decision-layer summary |
|
|
@@ -755,6 +808,11 @@ npx getprismo mcp /path/to/repo
|
|
|
755
808
|
- `prismo_firewall`
|
|
756
809
|
- `prismo_cc_timeline`
|
|
757
810
|
- `prismo_cursor_sessions`
|
|
811
|
+
- `prismo_receipt`
|
|
812
|
+
- `prismo_instructions_audit`
|
|
813
|
+
- `prismo_timeline`
|
|
814
|
+
- `prismo_replay`
|
|
815
|
+
- `prismo_boundaries`
|
|
758
816
|
|
|
759
817
|
This lets an MCP-compatible agent search prior shielded test/build output, request scoped context packs, inspect token-waste signals, or coordinate multiple local agents without pasting giant logs into the conversation.
|
|
760
818
|
|
|
@@ -929,13 +987,18 @@ then your team can run `npm run ai:doctor` without remembering the full command.
|
|
|
929
987
|
lib/prismo-dev-scan.js cli entry and command dispatch
|
|
930
988
|
lib/prismo-dev/constants.js shared defaults, pricing, patterns
|
|
931
989
|
lib/prismo-dev/context-optimize.js context packs, scoped prompts
|
|
990
|
+
lib/prismo-dev/boundaries.js multi-agent boundary and worktree overlap checks
|
|
932
991
|
lib/prismo-dev/doctor.js doctor/dev/init orchestration
|
|
933
992
|
lib/prismo-dev/fixes.js safe ignore/template generation
|
|
993
|
+
lib/prismo-dev/instructions.js instruction ROI and dead-rule analysis
|
|
934
994
|
lib/prismo-dev/mcp.js local MCP server and Prismo tool bindings
|
|
995
|
+
lib/prismo-dev/receipt.js run receipts for reads, output, artifacts, and next scope
|
|
935
996
|
lib/prismo-dev/report.js terminal, markdown, ci reports
|
|
997
|
+
lib/prismo-dev/replay.js incident replay and recovery prompts
|
|
936
998
|
lib/prismo-dev/scan.js repo scanning, scoring, readiness
|
|
937
999
|
lib/prismo-dev/scan-path-utils.js scan ignore/path helper logic
|
|
938
1000
|
lib/prismo-dev/shield.js local command shield and searchable output index
|
|
1001
|
+
lib/prismo-dev/timeline.js recurring multi-session waste patterns
|
|
939
1002
|
lib/prismo-dev/usage-cost.js Claude Code cost and timeline analysis
|
|
940
1003
|
lib/prismo-dev/usage-log-utils.js local session log parsing helpers
|
|
941
1004
|
lib/prismo-dev/cursor-sessions.js Cursor SQLite session and authorship tracking
|
|
@@ -960,6 +1023,11 @@ npx getprismo mcp --help
|
|
|
960
1023
|
npx getprismo mcp doctor
|
|
961
1024
|
npx getprismo cc --help
|
|
962
1025
|
npx getprismo cursor --help
|
|
1026
|
+
npx getprismo receipt --help
|
|
1027
|
+
npx getprismo replay --help
|
|
1028
|
+
npx getprismo timeline --help
|
|
1029
|
+
npx getprismo instructions --help
|
|
1030
|
+
npx getprismo boundaries --help
|
|
963
1031
|
npx getprismo scan --help
|
|
964
1032
|
```
|
|
965
1033
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module.exports = function createBoundaries(deps) {
|
|
2
|
+
const {
|
|
3
|
+
NPX_COMMAND,
|
|
4
|
+
buildMultiAgentView,
|
|
5
|
+
getUsageSummary,
|
|
6
|
+
} = deps;
|
|
7
|
+
|
|
8
|
+
function buildBoundaryCheck(options = {}) {
|
|
9
|
+
const cwd = options.cwd || process.cwd();
|
|
10
|
+
const limit = options.limit || 10;
|
|
11
|
+
const tool = options.tool || "all";
|
|
12
|
+
const usage = getUsageSummary({ cwd, limit, tool });
|
|
13
|
+
const multiAgent = buildMultiAgentView(usage);
|
|
14
|
+
const sameWorktree = (usage.sessions || []).length >= 2;
|
|
15
|
+
const sharedFiles = multiAgent.sharedFiles || [];
|
|
16
|
+
const sharedArtifacts = multiAgent.sharedArtifacts || [];
|
|
17
|
+
const noisyAgents = (multiAgent.agents || []).filter((agent) => ["tool-output-flood", "possible-loop"].includes(agent.liveAction?.cause));
|
|
18
|
+
const highPressureAgents = (multiAgent.agents || []).filter((agent) => agent.contextPressure === "High");
|
|
19
|
+
const score = Math.max(0, 100
|
|
20
|
+
- Math.min(35, sharedFiles.length * 12)
|
|
21
|
+
- Math.min(30, sharedArtifacts.length * 10)
|
|
22
|
+
- Math.min(25, noisyAgents.length * 10)
|
|
23
|
+
- Math.min(25, highPressureAgents.length * 10)
|
|
24
|
+
- (sameWorktree && multiAgent.agentCount >= 3 ? 10 : 0));
|
|
25
|
+
const risk =
|
|
26
|
+
score < 50 ? "High" :
|
|
27
|
+
score < 75 ? "Medium" :
|
|
28
|
+
"Low";
|
|
29
|
+
const recommendations = [];
|
|
30
|
+
if (sameWorktree && multiAgent.agentCount >= 2) recommendations.push("Run parallel agents in separate worktrees or strict task scopes when possible.");
|
|
31
|
+
if (sharedFiles.length) recommendations.push(`Assign file ownership before continuing: ${sharedFiles.slice(0, 3).map((item) => item.path).join(", ")}.`);
|
|
32
|
+
if (sharedArtifacts.length) recommendations.push(`Add/verify ignore coverage for shared artifacts: ${sharedArtifacts.slice(0, 3).map((item) => item.type).join(", ")}.`);
|
|
33
|
+
if (noisyAgents.length) recommendations.push(`Route noisy commands through ${NPX_COMMAND} shield -- <command>.`);
|
|
34
|
+
if (highPressureAgents.length) recommendations.push("Ask high-pressure agents for handoff summaries, then restart scoped sessions.");
|
|
35
|
+
if (!recommendations.length) recommendations.push("Boundaries look acceptable; keep agents scoped to separate tasks/files.");
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
command: "boundaries",
|
|
40
|
+
generatedAt: new Date().toISOString(),
|
|
41
|
+
scannedPath: cwd,
|
|
42
|
+
tool,
|
|
43
|
+
sessionsAnalyzed: usage.sessions.length,
|
|
44
|
+
agentCount: multiAgent.agentCount,
|
|
45
|
+
boundaryScore: score,
|
|
46
|
+
risk,
|
|
47
|
+
sameWorktree,
|
|
48
|
+
agents: multiAgent.agents,
|
|
49
|
+
sharedFiles,
|
|
50
|
+
sharedArtifacts,
|
|
51
|
+
coordinationWarnings: multiAgent.coordinationWarnings,
|
|
52
|
+
recommendations,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderBoundaryTerminal(check) {
|
|
57
|
+
const lines = [];
|
|
58
|
+
lines.push("");
|
|
59
|
+
lines.push("Prismo Agent Boundary Check");
|
|
60
|
+
lines.push("");
|
|
61
|
+
lines.push(`Agents visible: ${check.agentCount}`);
|
|
62
|
+
lines.push(`Boundary score: ${check.boundaryScore}/100 (${check.risk} risk)`);
|
|
63
|
+
lines.push(`Same worktree signal: ${check.sameWorktree ? "yes" : "no"}`);
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push("Coordination Warnings");
|
|
66
|
+
if (check.coordinationWarnings.length) check.coordinationWarnings.forEach((warning) => lines.push(`- ${warning}`));
|
|
67
|
+
else lines.push("- None detected.");
|
|
68
|
+
lines.push("");
|
|
69
|
+
lines.push("Shared Files");
|
|
70
|
+
if (check.sharedFiles.length) check.sharedFiles.slice(0, 5).forEach((item) => lines.push(`- ${item.path} (${item.owners.length} agents)`));
|
|
71
|
+
else lines.push("- None detected.");
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push("Shared Artifacts");
|
|
74
|
+
if (check.sharedArtifacts.length) check.sharedArtifacts.slice(0, 5).forEach((item) => lines.push(`- ${item.type} (${item.owners.length} agents)`));
|
|
75
|
+
else lines.push("- None detected.");
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push("Next");
|
|
78
|
+
check.recommendations.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
buildBoundaryCheck,
|
|
84
|
+
renderBoundaryTerminal,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
module.exports = function createInstructionsAudit(deps) {
|
|
2
|
+
const {
|
|
3
|
+
fs,
|
|
4
|
+
path,
|
|
5
|
+
INSTRUCTION_FILES,
|
|
6
|
+
NPX_COMMAND,
|
|
7
|
+
estimateTokens,
|
|
8
|
+
formatTokenCount,
|
|
9
|
+
getUsageSummary,
|
|
10
|
+
readIfText,
|
|
11
|
+
} = deps;
|
|
12
|
+
|
|
13
|
+
const RULE_KEYWORDS = [
|
|
14
|
+
"test", "tests", "pytest", "npm", "shield", "log", "logs", "dist", "build", "coverage",
|
|
15
|
+
"package-lock", "lockfile", "lockfiles", "node_modules", "generated", "artifact", "cache",
|
|
16
|
+
"read", "grep", "search", "edit", "diff", "commit", "auth", "frontend", "backend", "api",
|
|
17
|
+
"database", "migration", "security", "lint", "format", "typescript", "python",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function normalizeRule(text) {
|
|
21
|
+
return String(text || "")
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[`*_#>\-[\]().,:;]/g, " ")
|
|
24
|
+
.replace(/\s+/g, " ")
|
|
25
|
+
.trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ruleKeywords(rule) {
|
|
29
|
+
const normalized = normalizeRule(rule);
|
|
30
|
+
return RULE_KEYWORDS.filter((keyword) => normalized.includes(keyword));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractRules(file) {
|
|
34
|
+
const lines = file.text.split(/\r?\n/);
|
|
35
|
+
const rules = [];
|
|
36
|
+
lines.forEach((line, index) => {
|
|
37
|
+
const trimmed = line.trim();
|
|
38
|
+
if (!trimmed) return;
|
|
39
|
+
if (/^#{1,6}\s+/.test(trimmed)) return;
|
|
40
|
+
if (/^[-*]\s*$/.test(trimmed)) return;
|
|
41
|
+
if (/^```/.test(trimmed)) return;
|
|
42
|
+
if (trimmed.length < 8) return;
|
|
43
|
+
const cleaned = trimmed
|
|
44
|
+
.replace(/^[-*]\s+/, "")
|
|
45
|
+
.replace(/^\d+[.)]\s+/, "")
|
|
46
|
+
.trim();
|
|
47
|
+
if (cleaned.length < 8) return;
|
|
48
|
+
rules.push({
|
|
49
|
+
id: `${file.path}:${index + 1}`,
|
|
50
|
+
file: file.path,
|
|
51
|
+
line: index + 1,
|
|
52
|
+
text: cleaned,
|
|
53
|
+
tokens: estimateTokens(cleaned),
|
|
54
|
+
keywords: ruleKeywords(cleaned),
|
|
55
|
+
normalized: normalizeRule(cleaned),
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
return rules;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readInstructionFiles(root) {
|
|
62
|
+
const files = [];
|
|
63
|
+
for (const rel of INSTRUCTION_FILES.filter((item) => !["README.md", "README"].includes(item))) {
|
|
64
|
+
const fullPath = path.join(root, rel);
|
|
65
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
66
|
+
const stat = fs.statSync(fullPath);
|
|
67
|
+
if (!stat.isFile()) continue;
|
|
68
|
+
const text = readIfText(fullPath) || "";
|
|
69
|
+
files.push({
|
|
70
|
+
path: rel,
|
|
71
|
+
size: stat.size,
|
|
72
|
+
tokens: estimateTokens(text || stat.size),
|
|
73
|
+
text,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function sessionEvidence(usage) {
|
|
80
|
+
const sessions = usage?.sessions || [];
|
|
81
|
+
const evidenceText = [];
|
|
82
|
+
let toolOutputTokens = 0;
|
|
83
|
+
let generatedArtifactMentions = 0;
|
|
84
|
+
let repeatedReadMentions = 0;
|
|
85
|
+
let repeatedCommandMentions = 0;
|
|
86
|
+
let loopSessions = 0;
|
|
87
|
+
for (const session of sessions) {
|
|
88
|
+
toolOutputTokens += Number(session.estimatedToolTokens || 0);
|
|
89
|
+
if (session.loopSuspicion) loopSessions += 1;
|
|
90
|
+
for (const item of session.repeatedPathMentions || []) {
|
|
91
|
+
evidenceText.push(item.value);
|
|
92
|
+
repeatedReadMentions += Number(item.count || 0);
|
|
93
|
+
}
|
|
94
|
+
for (const item of session.generatedArtifacts || []) {
|
|
95
|
+
evidenceText.push(item.value);
|
|
96
|
+
generatedArtifactMentions += Number(item.count || 0);
|
|
97
|
+
}
|
|
98
|
+
for (const item of session.repeatedCommands || []) {
|
|
99
|
+
evidenceText.push(item.value);
|
|
100
|
+
repeatedCommandMentions += Number(item.count || 0);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
sessions,
|
|
105
|
+
text: normalizeRule(evidenceText.join(" ")),
|
|
106
|
+
toolOutputTokens,
|
|
107
|
+
generatedArtifactMentions,
|
|
108
|
+
repeatedReadMentions,
|
|
109
|
+
repeatedCommandMentions,
|
|
110
|
+
loopSessions,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function classifyRule(rule, evidence, duplicateCount) {
|
|
115
|
+
const text = rule.normalized;
|
|
116
|
+
const evidenceHits = rule.keywords.filter((keyword) => evidence.text.includes(keyword));
|
|
117
|
+
const concerns = [];
|
|
118
|
+
const signals = [];
|
|
119
|
+
|
|
120
|
+
if (duplicateCount > 1) concerns.push("duplicate");
|
|
121
|
+
if (rule.tokens > 80) concerns.push("verbose");
|
|
122
|
+
if (rule.keywords.length === 0) concerns.push("low-signal");
|
|
123
|
+
if (/(always|never|must|do not|don't|avoid)/i.test(rule.text)) signals.push("directive");
|
|
124
|
+
|
|
125
|
+
const asksNoArtifacts = /(do not|don't|avoid|never|ignore|exclude).*(generated|artifact|dist|build|coverage|node_modules|lockfile|package-lock|logs?)/i.test(rule.text);
|
|
126
|
+
const asksNoOutputFlood = /(shield|summarize|avoid|do not|don't|never).*(output|logs?|stdout|stderr|command)/i.test(rule.text);
|
|
127
|
+
const asksNarrowReads = /(small|narrow|scoped|focused|avoid broad|do not read|don't read).*(read|search|grep|context|file)/i.test(rule.text);
|
|
128
|
+
|
|
129
|
+
if (asksNoArtifacts && evidence.generatedArtifactMentions > 0) concerns.push("violated-by-session-evidence");
|
|
130
|
+
if (asksNoOutputFlood && evidence.toolOutputTokens >= 25000) concerns.push("violated-by-session-evidence");
|
|
131
|
+
if (asksNarrowReads && evidence.repeatedReadMentions >= 10) concerns.push("violated-by-session-evidence");
|
|
132
|
+
|
|
133
|
+
if (evidenceHits.length) signals.push("session-correlated");
|
|
134
|
+
if (asksNoArtifacts || asksNoOutputFlood || asksNarrowReads) signals.push("guardrail");
|
|
135
|
+
|
|
136
|
+
let status = "unclear";
|
|
137
|
+
let recommendation = "Keep for now, but review after more sessions.";
|
|
138
|
+
let score = 35;
|
|
139
|
+
|
|
140
|
+
if (concerns.includes("violated-by-session-evidence")) {
|
|
141
|
+
status = "ignored-or-ineffective";
|
|
142
|
+
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.`;
|
|
144
|
+
} else if (signals.includes("session-correlated") && signals.includes("directive")) {
|
|
145
|
+
status = "pulling-weight";
|
|
146
|
+
score = 80;
|
|
147
|
+
recommendation = "Keep this rule; it maps to observed session behavior.";
|
|
148
|
+
} else if (signals.includes("guardrail")) {
|
|
149
|
+
status = "guardrail-no-recent-violation";
|
|
150
|
+
score = 65;
|
|
151
|
+
recommendation = "Keep, but consider moving task-specific parts into a firewall/context pack.";
|
|
152
|
+
} else if (concerns.includes("duplicate")) {
|
|
153
|
+
status = "duplicate";
|
|
154
|
+
score = 25;
|
|
155
|
+
recommendation = "Deduplicate this rule so it is paid for once, not in every instruction file.";
|
|
156
|
+
} 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.";
|
|
160
|
+
} else if (rule.tokens > 80) {
|
|
161
|
+
status = "trim-candidate";
|
|
162
|
+
score = 30;
|
|
163
|
+
recommendation = "Shorten this rule or move details into a task-specific context pack.";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
...rule,
|
|
168
|
+
score,
|
|
169
|
+
status,
|
|
170
|
+
signals,
|
|
171
|
+
concerns,
|
|
172
|
+
evidenceHits,
|
|
173
|
+
recommendation,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function buildInstructionsAudit(options = {}) {
|
|
178
|
+
const root = options.cwd || process.cwd();
|
|
179
|
+
const limit = options.limit || 20;
|
|
180
|
+
const files = readInstructionFiles(root);
|
|
181
|
+
const rules = files.flatMap(extractRules);
|
|
182
|
+
const duplicates = new Map();
|
|
183
|
+
for (const rule of rules) {
|
|
184
|
+
duplicates.set(rule.normalized, (duplicates.get(rule.normalized) || 0) + 1);
|
|
185
|
+
}
|
|
186
|
+
const usage = getUsageSummary({ cwd: root, tool: options.tool || "all", limit });
|
|
187
|
+
const evidence = sessionEvidence(usage);
|
|
188
|
+
const auditedRules = rules.map((rule) => classifyRule(rule, evidence, duplicates.get(rule.normalized) || 0));
|
|
189
|
+
|
|
190
|
+
const byStatus = {};
|
|
191
|
+
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));
|
|
193
|
+
const pullingWeight = auditedRules.filter((rule) => rule.status === "pulling-weight" || rule.status === "guardrail-no-recent-violation");
|
|
194
|
+
const duplicatedRules = auditedRules.filter((rule) => (duplicates.get(rule.normalized) || 0) > 1);
|
|
195
|
+
const wastedTokensPerLoad = deadWeight.reduce((sum, rule) => sum + Number(rule.tokens || 0), 0);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
schemaVersion: 1,
|
|
199
|
+
command: "instructions audit",
|
|
200
|
+
generatedAt: new Date().toISOString(),
|
|
201
|
+
scannedPath: root,
|
|
202
|
+
confidence: usage.confidence,
|
|
203
|
+
files: files.map((file) => ({ path: file.path, tokens: file.tokens, size: file.size })),
|
|
204
|
+
sessionsAnalyzed: evidence.sessions.length,
|
|
205
|
+
evidence: {
|
|
206
|
+
sources: usage.sources || [],
|
|
207
|
+
toolOutputTokens: evidence.toolOutputTokens,
|
|
208
|
+
generatedArtifactMentions: evidence.generatedArtifactMentions,
|
|
209
|
+
repeatedReadMentions: evidence.repeatedReadMentions,
|
|
210
|
+
repeatedCommandMentions: evidence.repeatedCommandMentions,
|
|
211
|
+
loopSessions: evidence.loopSessions,
|
|
212
|
+
},
|
|
213
|
+
summary: {
|
|
214
|
+
totalRules: auditedRules.length,
|
|
215
|
+
byStatus,
|
|
216
|
+
deadWeightCandidates: deadWeight.length,
|
|
217
|
+
duplicatedRules: duplicatedRules.length,
|
|
218
|
+
wastedTokensPerLoad,
|
|
219
|
+
},
|
|
220
|
+
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),
|
|
222
|
+
rules: auditedRules,
|
|
223
|
+
next: [
|
|
224
|
+
deadWeight.length ? "Trim or rewrite dead-weight candidates first." : "No obvious dead-weight rules detected yet.",
|
|
225
|
+
duplicatedRules.length ? "Deduplicate repeated rules across CLAUDE.md / AGENTS.md / Codex instruction files." : null,
|
|
226
|
+
evidence.sessions.length < 5 ? "Run this again after more sessions; multi-session evidence makes instruction ROI stronger." : null,
|
|
227
|
+
`${NPX_COMMAND} firewall <task> for rules that only matter in one workflow.`,
|
|
228
|
+
].filter(Boolean),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function renderInstructionsAuditTerminal(audit) {
|
|
233
|
+
const lines = [];
|
|
234
|
+
lines.push("");
|
|
235
|
+
lines.push("Prismo Instruction ROI Audit");
|
|
236
|
+
lines.push("");
|
|
237
|
+
if (!audit.files.length) {
|
|
238
|
+
lines.push("No instruction files found.");
|
|
239
|
+
lines.push("Checked: CLAUDE.md, AGENTS.md, .codex/*, .openai/instructions.md");
|
|
240
|
+
return lines.join("\n");
|
|
241
|
+
}
|
|
242
|
+
lines.push(`Files: ${audit.files.map((file) => `${file.path} (${formatTokenCount(file.tokens)} tokens)`).join(", ")}`);
|
|
243
|
+
lines.push(`Rules: ${audit.summary.totalRules}`);
|
|
244
|
+
lines.push(`Sessions analyzed: ${audit.sessionsAnalyzed}`);
|
|
245
|
+
lines.push(`Dead-weight candidates: ${audit.summary.deadWeightCandidates} (~${formatTokenCount(audit.summary.wastedTokensPerLoad)} tokens/load)`);
|
|
246
|
+
lines.push(`Duplicates: ${audit.summary.duplicatedRules}`);
|
|
247
|
+
lines.push("");
|
|
248
|
+
lines.push("Pulling Weight");
|
|
249
|
+
if (audit.pullingWeight.length) {
|
|
250
|
+
audit.pullingWeight.slice(0, 5).forEach((rule) => {
|
|
251
|
+
lines.push(`- ${rule.file}:${rule.line} [${rule.status}] ${rule.text}`);
|
|
252
|
+
});
|
|
253
|
+
} else {
|
|
254
|
+
lines.push("- No high-confidence useful rules detected yet.");
|
|
255
|
+
}
|
|
256
|
+
lines.push("");
|
|
257
|
+
lines.push("Dead Weight / Rewrite Candidates");
|
|
258
|
+
if (audit.deadWeight.length) {
|
|
259
|
+
audit.deadWeight.slice(0, 8).forEach((rule) => {
|
|
260
|
+
lines.push(`- ${rule.file}:${rule.line} [${rule.status}] ${rule.text}`);
|
|
261
|
+
lines.push(` ${rule.recommendation}`);
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
lines.push("- No obvious candidates.");
|
|
265
|
+
}
|
|
266
|
+
lines.push("");
|
|
267
|
+
lines.push("Next");
|
|
268
|
+
audit.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
269
|
+
return lines.join("\n");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
buildInstructionsAudit,
|
|
274
|
+
renderInstructionsAuditTerminal,
|
|
275
|
+
};
|
|
276
|
+
};
|
package/lib/prismo-dev/mcp.js
CHANGED
|
@@ -32,6 +32,11 @@ function createMcpTools(deps) {
|
|
|
32
32
|
getUsageSummary,
|
|
33
33
|
getClaudeCodeCostSummary,
|
|
34
34
|
getCursorSessionSummary,
|
|
35
|
+
buildBoundaryCheck,
|
|
36
|
+
buildInstructionsAudit,
|
|
37
|
+
buildMultiSessionTimeline,
|
|
38
|
+
buildReceipt,
|
|
39
|
+
buildReplay,
|
|
35
40
|
runOptimize,
|
|
36
41
|
createOptimizeContext,
|
|
37
42
|
renderStarterPrompt,
|
|
@@ -67,12 +72,12 @@ function createMcpTools(deps) {
|
|
|
67
72
|
}),
|
|
68
73
|
makeTool("prismo_watch_snapshot", "Return a one-shot local session/context pressure snapshot.", {
|
|
69
74
|
path: pathProperty,
|
|
70
|
-
tool: { type: "string", enum: ["all", "codex", "claude"], description: "Which local session logs to inspect." },
|
|
75
|
+
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
71
76
|
limit: limitProperty,
|
|
72
77
|
}),
|
|
73
78
|
makeTool("prismo_multi_agent_watch", "Return multi-agent coordination risks across visible local Codex/Claude sessions.", {
|
|
74
79
|
path: pathProperty,
|
|
75
|
-
tool: { type: "string", enum: ["all", "codex", "claude"], description: "Which local session logs to inspect." },
|
|
80
|
+
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
76
81
|
limit: limitProperty,
|
|
77
82
|
}),
|
|
78
83
|
makeTool("prismo_shield_run", "Run a noisy command through Prismo shield and store full output locally.", {
|
|
@@ -112,6 +117,30 @@ function createMcpTools(deps) {
|
|
|
112
117
|
limit: limitProperty,
|
|
113
118
|
command: { type: "string", enum: ["latest", "list", "authorship", "timeline", "files"], description: "Subcommand: latest (summary), list (sessions), authorship (AI%), timeline (events), files (AI-generated)." },
|
|
114
119
|
}),
|
|
120
|
+
makeTool("prismo_receipt", "Return a run receipt covering repeated reads, tool output, artifacts, likely influence, and next-run scope.", {
|
|
121
|
+
path: pathProperty,
|
|
122
|
+
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
123
|
+
limit: limitProperty,
|
|
124
|
+
}),
|
|
125
|
+
makeTool("prismo_instructions_audit", "Audit persistent instruction files for useful rules, duplicates, and dead-weight candidates.", {
|
|
126
|
+
path: pathProperty,
|
|
127
|
+
limit: limitProperty,
|
|
128
|
+
}),
|
|
129
|
+
makeTool("prismo_timeline", "Return recurring context-waste patterns across recent sessions.", {
|
|
130
|
+
path: pathProperty,
|
|
131
|
+
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
132
|
+
limit: limitProperty,
|
|
133
|
+
}),
|
|
134
|
+
makeTool("prismo_replay", "Return incident replay and recovery prompt for recent coding-agent sessions.", {
|
|
135
|
+
path: pathProperty,
|
|
136
|
+
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
137
|
+
limit: limitProperty,
|
|
138
|
+
}),
|
|
139
|
+
makeTool("prismo_boundaries", "Check whether parallel agents are isolated or overlapping in the same repo/worktree.", {
|
|
140
|
+
path: pathProperty,
|
|
141
|
+
tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
|
|
142
|
+
limit: limitProperty,
|
|
143
|
+
}),
|
|
115
144
|
];
|
|
116
145
|
|
|
117
146
|
function resolveRoot(args) {
|
|
@@ -210,6 +239,45 @@ function createMcpTools(deps) {
|
|
|
210
239
|
}));
|
|
211
240
|
}
|
|
212
241
|
|
|
242
|
+
if (name === "prismo_receipt") {
|
|
243
|
+
return createTextResult(buildReceipt({
|
|
244
|
+
cwd: target,
|
|
245
|
+
tool: args.tool || "all",
|
|
246
|
+
limit: Number(args.limit) || 5,
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (name === "prismo_instructions_audit") {
|
|
251
|
+
return createTextResult(buildInstructionsAudit({
|
|
252
|
+
cwd: target,
|
|
253
|
+
limit: Number(args.limit) || 20,
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (name === "prismo_timeline") {
|
|
258
|
+
return createTextResult(buildMultiSessionTimeline({
|
|
259
|
+
cwd: target,
|
|
260
|
+
tool: args.tool || "all",
|
|
261
|
+
limit: Number(args.limit) || 20,
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (name === "prismo_replay") {
|
|
266
|
+
return createTextResult(buildReplay({
|
|
267
|
+
cwd: target,
|
|
268
|
+
tool: args.tool || "all",
|
|
269
|
+
limit: Number(args.limit) || 5,
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (name === "prismo_boundaries") {
|
|
274
|
+
return createTextResult(buildBoundaryCheck({
|
|
275
|
+
cwd: target,
|
|
276
|
+
tool: args.tool || "all",
|
|
277
|
+
limit: Number(args.limit) || 10,
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
|
|
213
281
|
throw new Error(`Unknown MCP tool: ${name}`);
|
|
214
282
|
}
|
|
215
283
|
|
|
@@ -309,6 +377,11 @@ async function runMcpDoctor(deps) {
|
|
|
309
377
|
"prismo_firewall",
|
|
310
378
|
"prismo_cc_timeline",
|
|
311
379
|
"prismo_cursor_sessions",
|
|
380
|
+
"prismo_receipt",
|
|
381
|
+
"prismo_instructions_audit",
|
|
382
|
+
"prismo_timeline",
|
|
383
|
+
"prismo_replay",
|
|
384
|
+
"prismo_boundaries",
|
|
312
385
|
];
|
|
313
386
|
const toolNames = tools.map((tool) => tool.name);
|
|
314
387
|
const missingTools = requiredTools.filter((name) => !toolNames.includes(name));
|