getprismo 0.1.44 → 0.1.46
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 +75 -1130
- package/docs/README.md +11 -0
- package/docs/manual.md +1203 -0
- package/lib/prismo-dev/agent.js +33 -0
- package/lib/prismo-dev/cli.js +52 -2
- package/lib/prismo-dev/enforce.js +29 -1
- package/lib/prismo-dev/help.js +13 -0
- package/lib/prismo-dev-scan.js +7 -0
- package/package.json +1 -1
package/lib/prismo-dev/agent.js
CHANGED
|
@@ -215,6 +215,38 @@ module.exports = function createAgent(deps) {
|
|
|
215
215
|
return sent;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
async function publishClaudeContextBlocks(config, rootDir, repo, options = {}) {
|
|
219
|
+
const state = safeReadJson(enforceStatePath(rootDir));
|
|
220
|
+
const blocks = Array.isArray(state?.contextBlocks) ? state.contextBlocks : [];
|
|
221
|
+
if (!blocks.length) return 0;
|
|
222
|
+
let sent = 0;
|
|
223
|
+
for (const block of blocks.slice(0, 10)) {
|
|
224
|
+
const ok = await sendLiveEvent(config, {
|
|
225
|
+
eventId: block.eventId || `claude-context-block-${block.at || Date.now()}`,
|
|
226
|
+
phase: "fixed",
|
|
227
|
+
eventType: "context_blocked",
|
|
228
|
+
severity: "success",
|
|
229
|
+
headline: "Blocked a wasteful context read",
|
|
230
|
+
detail: `${block.target} (rule: ${block.rule}) was denied before any tokens were spent.`,
|
|
231
|
+
repo,
|
|
232
|
+
targetCause: "generated-artifacts",
|
|
233
|
+
tokensPrevented: Number(block.estimatedTokensSaved || 0),
|
|
234
|
+
occurredAt: block.at || new Date().toISOString(),
|
|
235
|
+
payload: {
|
|
236
|
+
tool: "claude-code",
|
|
237
|
+
rule: block.rule,
|
|
238
|
+
sessionId: block.sessionId || null,
|
|
239
|
+
rawPrompts: false,
|
|
240
|
+
rawCode: false,
|
|
241
|
+
rawStdout: false,
|
|
242
|
+
rawStderr: false,
|
|
243
|
+
},
|
|
244
|
+
}, options);
|
|
245
|
+
if (ok) sent += 1;
|
|
246
|
+
}
|
|
247
|
+
return sent;
|
|
248
|
+
}
|
|
249
|
+
|
|
218
250
|
async function publishAgentLoopSignals(config, rootDir, repo, options = {}) {
|
|
219
251
|
if (!getUsageSummary) return 0;
|
|
220
252
|
let summary = null;
|
|
@@ -552,6 +584,7 @@ module.exports = function createAgent(deps) {
|
|
|
552
584
|
repo,
|
|
553
585
|
}, options);
|
|
554
586
|
await publishClaudeLoopStops(config, rootDir, repo, options);
|
|
587
|
+
await publishClaudeContextBlocks(config, rootDir, repo, options);
|
|
555
588
|
await publishAgentLoopSignals(config, rootDir, repo, options);
|
|
556
589
|
|
|
557
590
|
let autoDetectResult = null;
|
package/lib/prismo-dev/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ const VALID_COMMANDS = new Set([
|
|
|
6
6
|
"connect", "sync", "status", "disconnect", "agent", "connector", "setup", "scan", "digest",
|
|
7
7
|
"optimize", "context", "cc", "cursor", "receipt", "instructions",
|
|
8
8
|
"timeline", "replay", "boundaries", "usage", "guard", "watch", "demo", "repair",
|
|
9
|
-
"enforce", "bridge", "hook",
|
|
9
|
+
"enforce", "bridge", "hook", "protect",
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
12
|
function parseTokenBudget(value) {
|
|
@@ -151,7 +151,7 @@ function createCli(deps) {
|
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
153
|
if (!VALID_COMMANDS.has(command)) {
|
|
154
|
-
throw new Error(`Unknown command: ${command}. Try: prismo connect, prismo connector, prismo bridge, prismo agent, prismo guard, prismo sync, prismo doctor, prismo watch, prismo receipt, prismo benchmark, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, prismo cursor, or prismo usage`);
|
|
154
|
+
throw new Error(`Unknown command: ${command}. Try: prismo protect, prismo connect, prismo connector, prismo bridge, prismo agent, prismo guard, prismo sync, prismo doctor, prismo watch, prismo receipt, prismo benchmark, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, prismo cursor, or prismo usage`);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
if (command === "demo") {
|
|
@@ -837,6 +837,56 @@ function createCli(deps) {
|
|
|
837
837
|
return;
|
|
838
838
|
}
|
|
839
839
|
|
|
840
|
+
if (command === "protect") {
|
|
841
|
+
const json = rest.includes("--json");
|
|
842
|
+
const target = getPositionals(rest, new Set())[0] || process.cwd();
|
|
843
|
+
const steps = [];
|
|
844
|
+
|
|
845
|
+
const doctor = runDoctor(target, { applySuggestions: true, json: true });
|
|
846
|
+
steps.push({
|
|
847
|
+
step: "doctor",
|
|
848
|
+
ok: true,
|
|
849
|
+
detail: `Score ${doctor.after?.score ?? doctor.before?.score ?? "?"}/100; ${(doctor.fixActions || []).length} fix action(s), ${(doctor.generatedFiles || []).length} context file(s).`,
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
const enforce = runEnforceInstall(target);
|
|
853
|
+
steps.push({
|
|
854
|
+
step: "enforce",
|
|
855
|
+
ok: true,
|
|
856
|
+
detail: `Runtime enforcement on: ${enforce.blockedRules} blocked-context rule(s), loop breaking armed.`,
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
const config = loadConfig();
|
|
860
|
+
if (config?.token) {
|
|
861
|
+
const connector = runConnectorStatus();
|
|
862
|
+
if (connector.running) {
|
|
863
|
+
steps.push({ step: "connector", ok: true, detail: "Background connector already running." });
|
|
864
|
+
} else {
|
|
865
|
+
runConnectorInstall(target, { mode: "autopilot" });
|
|
866
|
+
steps.push({ step: "connector", ok: true, detail: "Background connector installed and started in autopilot." });
|
|
867
|
+
}
|
|
868
|
+
} else {
|
|
869
|
+
steps.push({
|
|
870
|
+
step: "connector",
|
|
871
|
+
ok: false,
|
|
872
|
+
detail: `Not connected to Prismo Cloud. Local protection is on; for autonomous repairs and verified savings run: ${NPX_COMMAND} connect --token <your Prismo API key>`,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const result = { schemaVersion: 1, command: "protect", steps, generatedAt: new Date().toISOString() };
|
|
877
|
+
if (json) {
|
|
878
|
+
console.log(JSON.stringify(result, null, 2));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
console.log("");
|
|
882
|
+
console.log("PrismoDev Protect");
|
|
883
|
+
console.log("");
|
|
884
|
+
steps.forEach((item) => console.log(`${item.ok ? "[on]" : "[--]"} ${item.step}: ${item.detail}`));
|
|
885
|
+
console.log("");
|
|
886
|
+
console.log("Your AI coding sessions in this repo are now scoped, guarded, and repaired automatically.");
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
840
890
|
if (command === "enforce") {
|
|
841
891
|
const json = rest.includes("--json");
|
|
842
892
|
const subcommand = (getPositionals(rest, new Set())[0] || "status").toLowerCase();
|
|
@@ -112,6 +112,26 @@ module.exports = function createEnforce(deps) {
|
|
|
112
112
|
writeState(root, state);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Like loopStops: a publishable record of blocked context reads so the
|
|
116
|
+
// connector can surface "prevented before spend" on the dashboard.
|
|
117
|
+
function recordContextBlock(root, state, payload) {
|
|
118
|
+
const contextBlocks = Array.isArray(state.contextBlocks) ? state.contextBlocks : [];
|
|
119
|
+
const at = new Date().toISOString();
|
|
120
|
+
const target = String(payload.target || "").slice(0, 240);
|
|
121
|
+
const sessionId = payload.sessionId || "unknown";
|
|
122
|
+
const eventId = `claude-context-block-${sessionId}-${Buffer.from(`${payload.rule}:${target}`).toString("base64").replace(/[^a-z0-9]/gi, "").slice(0, 24)}-${at.slice(0, 16)}`;
|
|
123
|
+
state.contextBlocks = [{
|
|
124
|
+
eventId,
|
|
125
|
+
at,
|
|
126
|
+
tool: "claude-code",
|
|
127
|
+
target,
|
|
128
|
+
rule: String(payload.rule || "").slice(0, 120),
|
|
129
|
+
estimatedTokensSaved: Math.max(0, Math.round(payload.estimatedTokens || 0)),
|
|
130
|
+
sessionId,
|
|
131
|
+
}, ...contextBlocks].slice(0, DENIAL_LOG_LIMIT);
|
|
132
|
+
writeState(root, state);
|
|
133
|
+
}
|
|
134
|
+
|
|
115
135
|
function estimateBlockedFileTokens(root, target) {
|
|
116
136
|
try {
|
|
117
137
|
const fullPath = path.isAbsolute(target) ? target : path.join(root, target);
|
|
@@ -179,7 +199,15 @@ module.exports = function createEnforce(deps) {
|
|
|
179
199
|
const patterns = readBlockedPatterns(root);
|
|
180
200
|
const hit = patterns.find((pattern) => matchesBlocked(relPath, pattern));
|
|
181
201
|
if (hit) {
|
|
182
|
-
|
|
202
|
+
const state = readState(root);
|
|
203
|
+
const estimatedTokens = estimateBlockedFileTokens(root, target);
|
|
204
|
+
recordDenial(root, state, "blocked-context", relPath, estimatedTokens);
|
|
205
|
+
recordContextBlock(root, state, {
|
|
206
|
+
target: relPath,
|
|
207
|
+
rule: hit,
|
|
208
|
+
estimatedTokens,
|
|
209
|
+
sessionId: String(event.session_id || "unknown"),
|
|
210
|
+
});
|
|
183
211
|
return deny(
|
|
184
212
|
`Prismo context firewall: "${relPath}" is blocked context (rule: ${hit}). `
|
|
185
213
|
+ "It is generated output that wastes agent tokens. Use the .prismo/ context packs instead, "
|
package/lib/prismo-dev/help.js
CHANGED
|
@@ -36,6 +36,7 @@ Usage:
|
|
|
36
36
|
prismo usage [codex|claude|cursor|all] [--json] [--limit N] [path]
|
|
37
37
|
prismo guard [codex|claude|cursor|all] [--json] [--watch] [--once] [--no-sync] [--dry-run] [--limit N] [--budget N] [--interval N] [path]
|
|
38
38
|
prismo repair <cause|auto> [--json] [--dry-run] [--tier mild|aggressive] [--limit N] [--budget N] [--scope SCOPE] [path] [-- <command ...>]
|
|
39
|
+
prismo protect [--json] [path]
|
|
39
40
|
prismo enforce [status|install|uninstall] [--json] [path]
|
|
40
41
|
prismo watch [codex|claude|cursor|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
41
42
|
prismo demo
|
|
@@ -69,6 +70,7 @@ Commands:
|
|
|
69
70
|
usage Read local Codex/Claude Code/Cursor session logs and summarize token usage.
|
|
70
71
|
guard Run proactive local guardrails and sync prevention events to Prismo.
|
|
71
72
|
repair Run the cause-specific repair for a detected waste cause; "auto" lets the planner pick.
|
|
73
|
+
protect One command for full protection: safe fixes + context packs + runtime enforcement + background connector.
|
|
72
74
|
enforce Enforce the context firewall at runtime via Claude Code hooks (block blocked-context reads and command loops).
|
|
73
75
|
watch Refresh local session usage in the terminal.
|
|
74
76
|
demo Show sample output without needing a messy repo.
|
|
@@ -327,6 +329,17 @@ Output:
|
|
|
327
329
|
--dry-run with auto prints the planner decision without executing.
|
|
328
330
|
--tier aggressive forces the stronger repair; cloud-queued actions carry it automatically after a no-change/regressed verdict.
|
|
329
331
|
Repairs only write .prismo/ files and append ignore rules with backups; they never overwrite CLAUDE.md, AGENTS.md, .gitignore, or source code.`,
|
|
332
|
+
protect: `PrismoDev Protect
|
|
333
|
+
|
|
334
|
+
Usage:
|
|
335
|
+
prismo protect [--json] [path]
|
|
336
|
+
|
|
337
|
+
One command that turns on the full stack for this repo:
|
|
338
|
+
1. doctor --apply-suggestions safe ignore rules + compact context packs
|
|
339
|
+
2. enforce install runtime enforcement via Claude Code hooks
|
|
340
|
+
3. connector background autonomous repairs (when connected to Prismo Cloud)
|
|
341
|
+
|
|
342
|
+
If this machine is not connected yet, protect still enables all local protection and prints the connect command for the autonomous half.`,
|
|
330
343
|
enforce: `PrismoDev Enforce
|
|
331
344
|
|
|
332
345
|
Usage:
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -178,6 +178,13 @@ const { appendIgnoreSuggestions, applyFixes } = require("./prismo-dev/fixes")({
|
|
|
178
178
|
function writeGeneratedFile(root, relPath, contents) {
|
|
179
179
|
const fullPath = path.join(root, relPath);
|
|
180
180
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
181
|
+
// Periodic regenerations (connector auto-detect every few minutes) must
|
|
182
|
+
// not churn backups when nothing changed.
|
|
183
|
+
try {
|
|
184
|
+
if (fs.existsSync(fullPath) && fs.readFileSync(fullPath, "utf8") === contents) {
|
|
185
|
+
return { path: relPath, backupPath: null, unchanged: true };
|
|
186
|
+
}
|
|
187
|
+
} catch {}
|
|
181
188
|
const backupPath = backupIfExists(fullPath);
|
|
182
189
|
fs.writeFileSync(fullPath, contents, "utf8");
|
|
183
190
|
return { path: relPath, backupPath };
|
package/package.json
CHANGED