getprismo 0.1.59 → 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
@@ -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);
@@ -588,7 +588,6 @@ const { runCli } = require("./prismo-dev/cli")({
588
588
  buildMultiAgentView,
589
589
  buildSyncPayload,
590
590
  loadConfig,
591
- runFirewall,
592
591
  });
593
592
 
594
593
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.59",
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",