getprismo 0.1.58 → 0.1.60

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/docs/manual.md CHANGED
@@ -1055,6 +1055,15 @@ npx getprismo context backend
1055
1055
 
1056
1056
  use these as the starting point for coding sessions instead of letting agents explore the whole repo.
1057
1057
 
1058
+ Each generated context pack also gets a machine-readable receipt next to it, for example:
1059
+
1060
+ ```text
1061
+ .prismo/frontend-context.md
1062
+ .prismo/frontend-context.receipt.json
1063
+ ```
1064
+
1065
+ Receipts record the repo ref, intended agent surfaces, source roots, omitted context classes, a digest of the source-file slice, estimated pack tokens, stale-after policy, and the command to regenerate/verify the pack. They do not include raw prompts or source contents; the digest is an audit handle so later sessions and the dashboard can tell what a pack represents without treating it as opaque markdown.
1066
+
1058
1067
  ---
1059
1068
 
1060
1069
  ## tracking modes
@@ -1081,8 +1090,13 @@ no api keys. no intercepted prompts. no data uploaded.
1081
1090
  ├── architecture-summary.md
1082
1091
  ├── backend-summary.md
1083
1092
  ├── frontend-summary.md
1093
+ ├── architecture-summary.receipt.json
1094
+ ├── backend-summary.receipt.json
1095
+ ├── frontend-summary.receipt.json
1084
1096
  ├── frontend-context.md
1097
+ ├── frontend-context.receipt.json
1085
1098
  ├── backend-context.md
1099
+ ├── backend-context.receipt.json
1086
1100
  ├── recommended-CLAUDE.boilerplate.md
1087
1101
  ├── recommended-AGENTS.boilerplate.md
1088
1102
  ├── recommended-.claudeignore
@@ -980,6 +980,7 @@ module.exports = function createAgent(deps) {
980
980
  openWorkspace,
981
981
  parseCommand,
982
982
  renderAgentTerminal,
983
+ registerSelfRepair,
983
984
  reportAutoDetect,
984
985
  reportProgress,
985
986
  runAgent,
@@ -135,6 +135,7 @@ function createCli(deps) {
135
135
  buildMultiSessionTimeline: _timeline,
136
136
  buildSyncPayload,
137
137
  loadConfig,
138
+ registerSelfRepair,
138
139
  buildReceipt: _receipt,
139
140
  buildReplay: _replay,
140
141
  runFirewall: _firewall,
@@ -841,15 +842,37 @@ function createCli(deps) {
841
842
  else console.log(renderPlannerTerminal(result));
842
843
  return;
843
844
  }
845
+ const tier = (tierIndex >= 0 ? ownArgs[tierIndex + 1] : null) || "mild";
844
846
  const result = await runRepair(target, cause, {
845
847
  limit: parsePositiveInt(limitIndex >= 0 ? ownArgs[limitIndex + 1] : null, 5),
846
848
  tokenBudget: parseTokenBudget(budgetIndex >= 0 ? ownArgs[budgetIndex + 1] : null),
847
849
  scope: scopeIndex >= 0 ? ownArgs[scopeIndex + 1] : null,
848
- tier: tierIndex >= 0 ? ownArgs[tierIndex + 1] : null,
850
+ tier,
849
851
  commandArgs,
850
852
  });
851
- if (json) console.log(JSON.stringify(result, null, 2));
852
- else console.log(renderRepairTerminal(result));
853
+ // Register a completed manual repair with the cloud so the backend
854
+ // verification loop measures it like a dashboard-queued repair —
855
+ // otherwise hand-run repairs never become verified savings.
856
+ let registered = false;
857
+ if (result.status === "completed" && typeof registerSelfRepair === "function") {
858
+ const config = loadConfig();
859
+ if (config && config.token) {
860
+ registered = await registerSelfRepair(config, {
861
+ generatedAt: result.generatedAt,
862
+ decision: { cause: result.cause, tier },
863
+ outcome: {
864
+ status: result.status,
865
+ statusMessage: result.statusMessage,
866
+ generatedFiles: (result.result && result.result.generatedFiles) || [],
867
+ },
868
+ });
869
+ }
870
+ }
871
+ if (json) console.log(JSON.stringify({ ...result, registered }, null, 2));
872
+ else {
873
+ console.log(renderRepairTerminal(result));
874
+ if (registered) console.log("\nReported to Prismo Cloud — savings will verify as new sessions come in.");
875
+ }
853
876
  if (result.status === "failed") process.exitCode = 1;
854
877
  return;
855
878
  }
@@ -386,7 +386,9 @@ module.exports = function createCloudSync(deps) {
386
386
  }
387
387
 
388
388
  async function runSync(rootDir = process.cwd(), options = {}) {
389
- const config = loadConfig();
389
+ // Allow an embedding host (the editor extension) to inject its own
390
+ // connection instead of reading the CLI's on-disk config.
391
+ const config = options.config || loadConfig();
390
392
  const payload = buildSyncPayload(rootDir, {
391
393
  limit: options.limit || config?.sync?.defaultLimit || 20,
392
394
  tool: options.tool || "all",
@@ -1,4 +1,6 @@
1
1
  module.exports = function createContextOptimize(deps) {
2
+ const crypto = require("crypto");
3
+ const { spawnSync } = require("child_process");
2
4
  const {
3
5
  fs,
4
6
  path,
@@ -125,6 +127,111 @@ function proseList(items, empty = "none detected") {
125
127
  return items && items.length ? items.join(", ") : empty;
126
128
  }
127
129
 
130
+ function estimateTokens(text) {
131
+ return Math.ceil(String(text || "").length / 4);
132
+ }
133
+
134
+ function sha256(value) {
135
+ return `sha256:${crypto.createHash("sha256").update(String(value)).digest("hex")}`;
136
+ }
137
+
138
+ function gitValue(root, args) {
139
+ try {
140
+ const result = spawnSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
141
+ return result.status === 0 ? result.stdout.trim() || null : null;
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ function repoRef(root) {
148
+ return {
149
+ branch: gitValue(root, ["rev-parse", "--abbrev-ref", "HEAD"]),
150
+ commit: gitValue(root, ["rev-parse", "--short=12", "HEAD"]),
151
+ };
152
+ }
153
+
154
+ function isContextPackFile(name) {
155
+ return name === "architecture-summary.md" ||
156
+ name === "backend-summary.md" ||
157
+ name === "frontend-summary.md" ||
158
+ name.endsWith("-context.md");
159
+ }
160
+
161
+ function rootsFromPaths(paths) {
162
+ return Array.from(new Set((paths || [])
163
+ .map((p) => String(p || "").replace(/\\/g, "/").split("/").filter(Boolean).slice(0, 2).join("/"))
164
+ .filter(Boolean)))
165
+ .sort();
166
+ }
167
+
168
+ function sourceRootsForPack(ctx, name) {
169
+ if (name === "backend-summary.md") {
170
+ return rootsFromPaths([
171
+ ...ctx.backend.api,
172
+ ...ctx.backend.services,
173
+ ...ctx.backend.models,
174
+ ...ctx.backend.db,
175
+ ...ctx.backend.auth,
176
+ ...ctx.backend.config,
177
+ ]);
178
+ }
179
+ if (name === "frontend-summary.md") {
180
+ return rootsFromPaths([
181
+ ...ctx.frontend.app,
182
+ ...ctx.frontend.components,
183
+ ...ctx.frontend.apiClient,
184
+ ...ctx.frontend.state,
185
+ ...ctx.frontend.styling,
186
+ ]);
187
+ }
188
+ if (name.endsWith("-context.md")) {
189
+ return rootsFromPaths(scopedRelevantFiles(ctx, name.replace(/-context\.md$/, "")));
190
+ }
191
+ return ctx.folders.slice(0, 30);
192
+ }
193
+
194
+ function matchingSourceFiles(ctx, roots) {
195
+ if (!roots || !roots.length) return ctx.scan.files.slice(0, 250);
196
+ return ctx.scan.files.filter((file) => roots.some((root) => file.path === root || file.path.startsWith(`${root}/`)));
197
+ }
198
+
199
+ function renderPackReceipt(ctx, name, contents) {
200
+ const packPath = `.prismo/${name}`;
201
+ const packId = name.replace(/\.md$/, "");
202
+ const sourceRoots = sourceRootsForPack(ctx, name);
203
+ const sourceFiles = matchingSourceFiles(ctx, sourceRoots)
204
+ .filter((file) => file.kind !== "binary")
205
+ .map((file) => ({
206
+ path: file.path,
207
+ size: file.size,
208
+ kind: file.kind,
209
+ tokens: file.tokens,
210
+ }))
211
+ .sort((a, b) => a.path.localeCompare(b.path));
212
+ return JSON.stringify({
213
+ schema_version: 1,
214
+ pack_id: packId,
215
+ pack_path: packPath,
216
+ receipt_path: `.prismo/${packId}.receipt.json`,
217
+ generated_at: ctx.generatedAt,
218
+ prismo_version: require("../../package.json").version,
219
+ repo_ref: repoRef(ctx.root),
220
+ target_agents: ["claude-code", "cursor", "codex", "mcp"],
221
+ source_roots: sourceRoots,
222
+ source_globs_digest: sha256(JSON.stringify({
223
+ pack_id: packId,
224
+ source_roots: sourceRoots,
225
+ files: sourceFiles,
226
+ })),
227
+ omitted_classes: ["lockfiles", "build-output", "large-logs", "coverage", "dependency-vendor", "binary-media"],
228
+ token_budget: 12000,
229
+ estimated_pack_tokens: estimateTokens(contents),
230
+ stale_if_older_than_ms: 86400000,
231
+ verify_command: `${NPX_COMMAND} optimize${ctx.scope ? ` ${ctx.scope}` : ""}`,
232
+ }, null, 2) + "\n";
233
+ }
234
+
128
235
  function renderArchitectureSummary(ctx) {
129
236
  const apiLayer = ctx.backend.api.slice(0, 6);
130
237
  const dbLayer = ctx.backend.db.slice(0, 6);
@@ -429,9 +536,9 @@ function writeOptimizeGeneratedFile(root, relPath, contents) {
429
536
  return { path: relPath, backupPath: null, replaced: existed };
430
537
  }
431
538
 
432
- function renderScopedContext(ctx, scope) {
539
+ function scopedRelevantFiles(ctx, scope) {
433
540
  const scopeLower = scope.toLowerCase();
434
- const relevant = ctx.scan.files
541
+ return ctx.scan.files
435
542
  .filter((file) => {
436
543
  const rel = file.path.toLowerCase();
437
544
  if (scopeLower === "frontend") return rel.includes("frontend/") || rel.includes("src/app/") || rel.includes("src/components/");
@@ -442,6 +549,10 @@ function renderScopedContext(ctx, scope) {
442
549
  .filter((file) => file.kind !== "binary")
443
550
  .slice(0, 60)
444
551
  .map((file) => file.path);
552
+ }
553
+
554
+ function renderScopedContext(ctx, scope) {
555
+ const relevant = scopedRelevantFiles(ctx, scope);
445
556
 
446
557
  return [
447
558
  `# ${scope.charAt(0).toUpperCase()}${scope.slice(1)} Context`,
@@ -544,7 +655,11 @@ function runOptimize(rootDir = process.cwd(), options = {}) {
544
655
  const pending = getOptimizePendingFiles(ctx);
545
656
 
546
657
  if (options.dryRun) {
547
- const generatedFiles = pending.map(([name]) => path.join(".prismo", name));
658
+ const generatedFiles = pending.flatMap(([name]) => {
659
+ const files = [path.join(".prismo", name)];
660
+ if (isContextPackFile(name)) files.push(path.join(".prismo", `${name.replace(/\.md$/, "")}.receipt.json`));
661
+ return files;
662
+ });
548
663
  generatedFiles.push(".prismo/optimize-report.md");
549
664
  return {
550
665
  root: ctx.root,
@@ -564,6 +679,12 @@ function runOptimize(rootDir = process.cwd(), options = {}) {
564
679
  for (const [name, contents] of pending) {
565
680
  const written = writeOptimizeGeneratedFile(ctx.root, path.join(".prismo", name), contents);
566
681
  generated.push(written.path);
682
+ if (isContextPackFile(name)) {
683
+ const receipt = renderPackReceipt(ctx, name, contents);
684
+ const receiptName = `${name.replace(/\.md$/, "")}.receipt.json`;
685
+ const writtenReceipt = writeOptimizeGeneratedFile(ctx.root, path.join(".prismo", receiptName), receipt);
686
+ generated.push(writtenReceipt.path);
687
+ }
567
688
  }
568
689
  const report = renderOptimizeReport(ctx, [...generated, ".prismo/optimize-report.md"]);
569
690
  const writtenReport = writeOptimizeReport(ctx.root, path.join(".prismo", "optimize-report.md"), report);
@@ -383,6 +383,7 @@ const {
383
383
  const {
384
384
  renderAgentTerminal,
385
385
  runAgent,
386
+ registerSelfRepair,
386
387
  VALID_MODES: AGENT_VALID_MODES,
387
388
  } = require("./prismo-dev/agent")({
388
389
  fs,
@@ -479,6 +480,7 @@ const { runCli } = require("./prismo-dev/cli")({
479
480
  NPX_COMMAND,
480
481
  DEFAULT_PRISMO_PROXY_URL,
481
482
  AGENT_VALID_MODES,
483
+ registerSelfRepair,
482
484
  openUrl,
483
485
  printStep,
484
486
  getPositionals,
@@ -586,7 +588,6 @@ const { runCli } = require("./prismo-dev/cli")({
586
588
  buildMultiAgentView,
587
589
  buildSyncPayload,
588
590
  loadConfig,
589
- runFirewall,
590
591
  });
591
592
 
592
593
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
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",