getprismo 0.1.59 → 0.1.61
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
|
-
|
|
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,14 +1,17 @@
|
|
|
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,
|
|
5
7
|
NPX_COMMAND,
|
|
8
|
+
PACKAGE_VERSION,
|
|
9
|
+
estimateTokens,
|
|
6
10
|
scanRepo,
|
|
7
11
|
safeReadJson,
|
|
8
12
|
readIfText,
|
|
9
13
|
formatBytes,
|
|
10
14
|
color,
|
|
11
|
-
writeGeneratedFile,
|
|
12
15
|
} = deps;
|
|
13
16
|
|
|
14
17
|
const {
|
|
@@ -125,6 +128,145 @@ function proseList(items, empty = "none detected") {
|
|
|
125
128
|
return items && items.length ? items.join(", ") : empty;
|
|
126
129
|
}
|
|
127
130
|
|
|
131
|
+
function sha256(value) {
|
|
132
|
+
return `sha256:${crypto.createHash("sha256").update(String(value)).digest("hex")}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function gitValue(root, args) {
|
|
136
|
+
try {
|
|
137
|
+
const result = spawnSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
138
|
+
return result.status === 0 ? result.stdout.trim() || null : null;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function workingTreeDirty(root) {
|
|
145
|
+
try {
|
|
146
|
+
const result = spawnSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
147
|
+
if (result.status !== 0) return null;
|
|
148
|
+
return result.stdout.trim().length > 0;
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function repoRef(root) {
|
|
155
|
+
const branch = gitValue(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
156
|
+
const commit = gitValue(root, ["rev-parse", "--short=12", "HEAD"]);
|
|
157
|
+
// A pack derived from uncommitted files has a different staleness boundary
|
|
158
|
+
// than one derived from a clean commit, so record the working-tree state.
|
|
159
|
+
// null when this is not a git repo.
|
|
160
|
+
const inRepo = Boolean(branch || commit);
|
|
161
|
+
return {
|
|
162
|
+
branch,
|
|
163
|
+
commit,
|
|
164
|
+
working_tree_dirty: inRepo ? workingTreeDirty(root) : null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Directory roots that are intentionally kept out of context grounding (they
|
|
169
|
+
// map to the receipt's omitted_classes). Used to separate roots that actually
|
|
170
|
+
// grounded the pack from roots that were merely present and avoided.
|
|
171
|
+
const EXCLUDED_ROOT_DIRS = new Set([
|
|
172
|
+
"node_modules", "vendor", ".venv", "venv",
|
|
173
|
+
"dist", "build", "out", "target", ".next", ".nuxt", ".output", ".svelte-kit",
|
|
174
|
+
"coverage", ".nyc_output",
|
|
175
|
+
"logs", "log",
|
|
176
|
+
".cache", "__pycache__", ".git",
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
function isExcludedRoot(root) {
|
|
180
|
+
return String(root || "").split("/").some((seg) => EXCLUDED_ROOT_DIRS.has(seg.toLowerCase()));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isContextPackFile(name) {
|
|
184
|
+
return name === "architecture-summary.md" ||
|
|
185
|
+
name === "backend-summary.md" ||
|
|
186
|
+
name === "frontend-summary.md" ||
|
|
187
|
+
name.endsWith("-context.md");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function rootsFromPaths(paths) {
|
|
191
|
+
return Array.from(new Set((paths || [])
|
|
192
|
+
.map((p) => String(p || "").replace(/\\/g, "/").split("/").filter(Boolean).slice(0, 2).join("/"))
|
|
193
|
+
.filter(Boolean)))
|
|
194
|
+
.sort();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function sourceRootsForPack(ctx, name) {
|
|
198
|
+
if (name === "backend-summary.md") {
|
|
199
|
+
return rootsFromPaths([
|
|
200
|
+
...ctx.backend.api,
|
|
201
|
+
...ctx.backend.services,
|
|
202
|
+
...ctx.backend.models,
|
|
203
|
+
...ctx.backend.db,
|
|
204
|
+
...ctx.backend.auth,
|
|
205
|
+
...ctx.backend.config,
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
if (name === "frontend-summary.md") {
|
|
209
|
+
return rootsFromPaths([
|
|
210
|
+
...ctx.frontend.app,
|
|
211
|
+
...ctx.frontend.components,
|
|
212
|
+
...ctx.frontend.apiClient,
|
|
213
|
+
...ctx.frontend.state,
|
|
214
|
+
...ctx.frontend.styling,
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
if (name.endsWith("-context.md")) {
|
|
218
|
+
return rootsFromPaths(scopedRelevantFiles(ctx, name.replace(/-context\.md$/, "")));
|
|
219
|
+
}
|
|
220
|
+
return ctx.folders.slice(0, 30);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function matchingSourceFiles(ctx, roots) {
|
|
224
|
+
if (!roots || !roots.length) return ctx.scan.files.slice(0, 250);
|
|
225
|
+
return ctx.scan.files.filter((file) => roots.some((root) => file.path === root || file.path.startsWith(`${root}/`)));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function renderPackReceipt(ctx, name, contents) {
|
|
229
|
+
const packPath = `.prismo/${name}`;
|
|
230
|
+
const packId = name.replace(/\.md$/, "");
|
|
231
|
+
const candidateRoots = sourceRootsForPack(ctx, name);
|
|
232
|
+
// Separate roots that actually grounded the pack from roots that were present
|
|
233
|
+
// but deliberately kept out (build output, logs, vendored deps). Without this,
|
|
234
|
+
// a later reader could assume the pack was grounded in e.g. dist/ or logs/.
|
|
235
|
+
const includedSourceRoots = candidateRoots.filter((root) => !isExcludedRoot(root));
|
|
236
|
+
const excludedRoots = candidateRoots.filter((root) => isExcludedRoot(root));
|
|
237
|
+
const sourceFiles = matchingSourceFiles(ctx, includedSourceRoots)
|
|
238
|
+
.filter((file) => file.kind !== "binary")
|
|
239
|
+
.map((file) => ({
|
|
240
|
+
path: file.path,
|
|
241
|
+
size: file.size,
|
|
242
|
+
kind: file.kind,
|
|
243
|
+
tokens: file.tokens,
|
|
244
|
+
}))
|
|
245
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
246
|
+
return JSON.stringify({
|
|
247
|
+
schema_version: 1,
|
|
248
|
+
pack_id: packId,
|
|
249
|
+
pack_path: packPath,
|
|
250
|
+
receipt_path: `.prismo/${packId}.receipt.json`,
|
|
251
|
+
generated_at: ctx.generatedAt,
|
|
252
|
+
prismo_version: PACKAGE_VERSION,
|
|
253
|
+
repo_ref: repoRef(ctx.root),
|
|
254
|
+
target_agents: ["claude-code", "cursor", "codex", "mcp"],
|
|
255
|
+
included_source_roots: includedSourceRoots,
|
|
256
|
+
excluded_roots: excludedRoots,
|
|
257
|
+
source_globs_digest: sha256(JSON.stringify({
|
|
258
|
+
pack_id: packId,
|
|
259
|
+
source_roots: includedSourceRoots,
|
|
260
|
+
files: sourceFiles,
|
|
261
|
+
})),
|
|
262
|
+
omitted_classes: ["lockfiles", "build-output", "large-logs", "coverage", "dependency-vendor", "binary-media"],
|
|
263
|
+
token_budget: 12000,
|
|
264
|
+
estimated_pack_tokens: estimateTokens(contents),
|
|
265
|
+
stale_if_older_than_ms: 86400000,
|
|
266
|
+
verify_command: `${NPX_COMMAND} optimize${ctx.scope ? ` ${ctx.scope}` : ""}`,
|
|
267
|
+
}, null, 2) + "\n";
|
|
268
|
+
}
|
|
269
|
+
|
|
128
270
|
function renderArchitectureSummary(ctx) {
|
|
129
271
|
const apiLayer = ctx.backend.api.slice(0, 6);
|
|
130
272
|
const dbLayer = ctx.backend.db.slice(0, 6);
|
|
@@ -429,9 +571,9 @@ function writeOptimizeGeneratedFile(root, relPath, contents) {
|
|
|
429
571
|
return { path: relPath, backupPath: null, replaced: existed };
|
|
430
572
|
}
|
|
431
573
|
|
|
432
|
-
function
|
|
574
|
+
function scopedRelevantFiles(ctx, scope) {
|
|
433
575
|
const scopeLower = scope.toLowerCase();
|
|
434
|
-
|
|
576
|
+
return ctx.scan.files
|
|
435
577
|
.filter((file) => {
|
|
436
578
|
const rel = file.path.toLowerCase();
|
|
437
579
|
if (scopeLower === "frontend") return rel.includes("frontend/") || rel.includes("src/app/") || rel.includes("src/components/");
|
|
@@ -442,6 +584,10 @@ function renderScopedContext(ctx, scope) {
|
|
|
442
584
|
.filter((file) => file.kind !== "binary")
|
|
443
585
|
.slice(0, 60)
|
|
444
586
|
.map((file) => file.path);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function renderScopedContext(ctx, scope) {
|
|
590
|
+
const relevant = scopedRelevantFiles(ctx, scope);
|
|
445
591
|
|
|
446
592
|
return [
|
|
447
593
|
`# ${scope.charAt(0).toUpperCase()}${scope.slice(1)} Context`,
|
|
@@ -544,7 +690,11 @@ function runOptimize(rootDir = process.cwd(), options = {}) {
|
|
|
544
690
|
const pending = getOptimizePendingFiles(ctx);
|
|
545
691
|
|
|
546
692
|
if (options.dryRun) {
|
|
547
|
-
const generatedFiles = pending.
|
|
693
|
+
const generatedFiles = pending.flatMap(([name]) => {
|
|
694
|
+
const files = [path.join(".prismo", name)];
|
|
695
|
+
if (isContextPackFile(name)) files.push(path.join(".prismo", `${name.replace(/\.md$/, "")}.receipt.json`));
|
|
696
|
+
return files;
|
|
697
|
+
});
|
|
548
698
|
generatedFiles.push(".prismo/optimize-report.md");
|
|
549
699
|
return {
|
|
550
700
|
root: ctx.root,
|
|
@@ -564,6 +714,12 @@ function runOptimize(rootDir = process.cwd(), options = {}) {
|
|
|
564
714
|
for (const [name, contents] of pending) {
|
|
565
715
|
const written = writeOptimizeGeneratedFile(ctx.root, path.join(".prismo", name), contents);
|
|
566
716
|
generated.push(written.path);
|
|
717
|
+
if (isContextPackFile(name)) {
|
|
718
|
+
const receipt = renderPackReceipt(ctx, name, contents);
|
|
719
|
+
const receiptName = `${name.replace(/\.md$/, "")}.receipt.json`;
|
|
720
|
+
const writtenReceipt = writeOptimizeGeneratedFile(ctx.root, path.join(".prismo", receiptName), receipt);
|
|
721
|
+
generated.push(writtenReceipt.path);
|
|
722
|
+
}
|
|
567
723
|
}
|
|
568
724
|
const report = renderOptimizeReport(ctx, [...generated, ".prismo/optimize-report.md"]);
|
|
569
725
|
const writtenReport = writeOptimizeReport(ctx.root, path.join(".prismo", "optimize-report.md"), report);
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -53,12 +53,13 @@ const {
|
|
|
53
53
|
fs,
|
|
54
54
|
path,
|
|
55
55
|
NPX_COMMAND,
|
|
56
|
+
PACKAGE_VERSION,
|
|
57
|
+
estimateTokens,
|
|
56
58
|
scanRepo: (...args) => scanRepo(...args),
|
|
57
59
|
safeReadJson,
|
|
58
60
|
readIfText,
|
|
59
61
|
formatBytes: (...args) => formatBytes(...args),
|
|
60
62
|
color,
|
|
61
|
-
writeGeneratedFile: (...args) => writeGeneratedFile(...args),
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
const {
|
|
@@ -588,7 +589,6 @@ const { runCli } = require("./prismo-dev/cli")({
|
|
|
588
589
|
buildMultiAgentView,
|
|
589
590
|
buildSyncPayload,
|
|
590
591
|
loadConfig,
|
|
591
|
-
runFirewall,
|
|
592
592
|
});
|
|
593
593
|
|
|
594
594
|
module.exports = {
|
package/package.json
CHANGED