getprismo 0.1.53 → 0.1.54

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.
@@ -475,9 +475,13 @@ Tools exposed:
475
475
  prismo_firewall
476
476
  prismo_cc_timeline
477
477
  prismo_cursor_sessions
478
+ prismo_should_shield (agent-native: should I shield this command?)
479
+ prismo_loop_check (agent-native: am I looping?)
480
+ prismo_context_guard (agent-native: what repo am I in / what to avoid rereading?)
478
481
 
479
482
  Output:
480
483
  Starts a local JSON-RPC MCP server over stdio. Use it from MCP-compatible clients so agents can scan context waste, search shielded command output, and request scoped context without loading huge logs into chat.
484
+ The agent-native tools answer single mid-session questions directly so Claude Code, Codex, and Cursor can call Prismo while they work, not just after.
481
485
 
482
486
  prismo mcp doctor validates the local MCP tool surface and prints a ready-to-use client config snippet.`,
483
487
  connect: `PrismoDev Connect
@@ -1,3 +1,40 @@
1
+ const path = require("path");
2
+
3
+ // Commands whose output reliably floods agent context and should run through
4
+ // shield (full output to disk, compact summary to the model).
5
+ const NOISY_COMMAND_PATTERNS = [
6
+ { re: /(^|\s)(jest|vitest|mocha|pytest|rspec|phpunit|ava|tap)(\s|$)|(\b(npm|yarn|pnpm|bun)\s+(run\s+)?test\b)|\b(go|cargo)\s+test\b/, why: "test runs emit large pass/fail output" },
7
+ { re: /(^|\s)(webpack|rollup|esbuild|vite|tsc)(\s|$)|\b(next|nuxt|astro)\s+build\b|\b(npm|yarn|pnpm|bun)\s+(run\s+)?build\b|\b(go|cargo)\s+build\b|(^|\s)make(\s|$)/, why: "builds emit long compiler/bundler output" },
8
+ { re: /\b(npm|pnpm|bun)\s+(install|ci|i)\b|\byarn(\s+install)?\b|\bpip\s+install\b|\bbundle\s+install\b/, why: "installs print large dependency trees" },
9
+ { re: /(^|\s)(eslint|ruff|flake8|mypy|pylint)(\s|$)|\b(npm|yarn|pnpm)\s+(run\s+)?lint\b|tsc\s+--noEmit/, why: "linters/type-checkers emit many diagnostics" },
10
+ { re: /\b(playwright|cypress|e2e)\b/, why: "e2e runs dump verbose logs" },
11
+ { re: /\bdocker\s+build\b|\bterraform\b|\bkubectl\s+logs\b|\bcoverage\b|--verbose\b/, why: "infra/coverage/verbose commands flood output" },
12
+ ];
13
+ const QUICK_COMMAND_PATTERNS = [
14
+ /(^|\s)(git\s+(status|log|diff|branch|show)|ls|pwd|whoami|which|echo|cat|head|tail|wc|env|date|node\s+-v|--version)(\s|$)/,
15
+ ];
16
+
17
+ function classifyCommandForShield(command, repeatedCommands = []) {
18
+ const cmd = String(command || "").trim();
19
+ if (!cmd) return { shouldShield: false, confidence: "low", reason: "No command provided." };
20
+ const normalized = cmd.replace(/\s+/g, " ");
21
+ const repeated = (repeatedCommands || []).some((item) => {
22
+ const value = String(item.value || "").replace(/\s+/g, " ");
23
+ return value && (normalized.includes(value) || value.includes(normalized)) && Number(item.count || 0) >= 3;
24
+ });
25
+ if (repeated) {
26
+ return { shouldShield: true, confidence: "high", reason: "This command has already repeated several times in recent sessions; shield it so each retry costs a summary, not full output." };
27
+ }
28
+ const noisy = NOISY_COMMAND_PATTERNS.find((entry) => entry.re.test(normalized));
29
+ if (noisy) {
30
+ return { shouldShield: true, confidence: "high", reason: `Shield recommended: ${noisy.why}.` };
31
+ }
32
+ if (QUICK_COMMAND_PATTERNS.some((re) => re.test(normalized))) {
33
+ return { shouldShield: false, confidence: "high", reason: "Quick, low-output command; shielding is unnecessary." };
34
+ }
35
+ return { shouldShield: false, confidence: "low", reason: "No known flooding pattern; shield only if you expect large output." };
36
+ }
37
+
1
38
  function createTextResult(payload) {
2
39
  const text = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
3
40
  return {
@@ -147,6 +184,20 @@ function createMcpTools(deps) {
147
184
  tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
148
185
  limit: limitProperty,
149
186
  }),
187
+ // Agent-native, mid-loop decision tools: tight yes/no answers an agent can
188
+ // act on during a session, not analysis dumps.
189
+ makeTool("prismo_should_shield", "Decide whether a shell command should be run through Prismo shield (full output to disk, compact summary to context) before you run it. Call this before any command that might print a lot.", {
190
+ command: { type: "string", description: "The exact shell command you are about to run." },
191
+ path: pathProperty,
192
+ }, ["command"]),
193
+ makeTool("prismo_loop_check", "Check whether the current coding session is stuck in a loop (repeating the same command or failing repeatedly). Call this when you have retried something more than once.", {
194
+ path: pathProperty,
195
+ tool: { type: "string", enum: ["all", "codex", "claude", "cursor"], description: "Which local session logs to inspect." },
196
+ }),
197
+ makeTool("prismo_context_guard", "Get what repo you are in, which paths to avoid rereading, files you have already read repeatedly, and the compact context pack to start from. Call this at the start of a session or before broad exploration.", {
198
+ path: pathProperty,
199
+ scope: scopeProperty,
200
+ }),
150
201
  ];
151
202
 
152
203
  function resolveRoot(args) {
@@ -292,6 +343,75 @@ function createMcpTools(deps) {
292
343
  }));
293
344
  }
294
345
 
346
+ if (name === "prismo_should_shield") {
347
+ let repeatedCommands = [];
348
+ try {
349
+ const summary = getUsageSummary({ cwd: target, limit: 3, tool: "all" });
350
+ const latest = (summary.sessions || [])[0];
351
+ repeatedCommands = (latest && latest.repeatedCommands) || [];
352
+ } catch { /* command classification still works without session context */ }
353
+ const decision = classifyCommandForShield(args.command, repeatedCommands);
354
+ return createTextResult({
355
+ schemaVersion: 1,
356
+ command: args.command,
357
+ shouldShield: decision.shouldShield,
358
+ confidence: decision.confidence,
359
+ reason: decision.reason,
360
+ recommended: decision.shouldShield
361
+ ? `npx -y getprismo@latest shield -- ${String(args.command || "").trim()}`
362
+ : null,
363
+ note: "Shield stores full stdout/stderr locally and returns a compact summary, so the output never floods context.",
364
+ });
365
+ }
366
+
367
+ if (name === "prismo_loop_check") {
368
+ const summary = getUsageSummary({ cwd: target, limit: 3, tool: args.tool || "all" });
369
+ const latest = (summary.sessions || [])[0] || null;
370
+ const repeatedCommands = (latest && latest.repeatedCommands) || [];
371
+ const looping = Boolean(latest && latest.loopSuspicion) || repeatedCommands.some((c) => Number(c.count || 0) >= 4);
372
+ const topRepeat = repeatedCommands[0] || null;
373
+ return createTextResult({
374
+ schemaVersion: 1,
375
+ looping,
376
+ confidence: latest ? (latest.loopConfidence || (looping ? "medium" : "low")) : "low",
377
+ signals: {
378
+ repeatedCommands: repeatedCommands.slice(0, 5),
379
+ failureMentions: latest ? Number(latest.failureMentions || 0) : 0,
380
+ turns: latest ? Number(latest.turns || 0) : 0,
381
+ },
382
+ advice: looping
383
+ ? `You appear to be looping${topRepeat ? ` on \`${topRepeat.value}\` (${topRepeat.count}x)` : ""}. Stop retrying, change the approach, and capture the command once with \`npx -y getprismo@latest shield -- <command>\` instead of re-running it.`
384
+ : "No loop detected. Keep going.",
385
+ });
386
+ }
387
+
388
+ if (name === "prismo_context_guard") {
389
+ const scope = args.scope || null;
390
+ const scan = scanRepo(target, { includeUsage: true, usageLimit: 3 });
391
+ const ctx = createOptimizeContext(target, scope);
392
+ const usage = scan.realUsage && scan.realUsage.sessions ? scan.realUsage.sessions : [];
393
+ const repeatedlyRead = [];
394
+ for (const session of usage) {
395
+ for (const item of session.repeatedPathMentions || []) {
396
+ if (Number(item.count || 0) >= 4) repeatedlyRead.push({ path: item.value, reads: item.count });
397
+ }
398
+ }
399
+ const blocked = (scan.recommendedClaudeIgnore || []).slice(0, 24);
400
+ return createTextResult({
401
+ schemaVersion: 1,
402
+ repo: path.basename(path.resolve(target)),
403
+ avoidRereading: {
404
+ blockedContext: blocked,
405
+ alreadyReadRepeatedly: repeatedlyRead.slice(0, 12),
406
+ },
407
+ startFrom: {
408
+ contextFile: scope ? `.prismo/${scope}-summary.md` : ".prismo/architecture-summary.md",
409
+ starterPrompt: renderStarterPrompt(ctx, scope),
410
+ },
411
+ advice: "Read the start-from context pack first. Do not reread blocked paths or files you have already read several times; quote what you already saw instead.",
412
+ });
413
+ }
414
+
295
415
  throw new Error(`Unknown MCP tool: ${name}`);
296
416
  }
297
417
 
@@ -397,6 +517,9 @@ async function runMcpDoctor(deps) {
397
517
  "prismo_timeline",
398
518
  "prismo_replay",
399
519
  "prismo_boundaries",
520
+ "prismo_should_shield",
521
+ "prismo_loop_check",
522
+ "prismo_context_guard",
400
523
  ];
401
524
  const toolNames = tools.map((tool) => tool.name);
402
525
  const missingTools = requiredTools.filter((name) => !toolNames.includes(name));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
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",