getprismo 0.1.51 → 0.1.52
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/privacy-telemetry.md +2 -0
- package/docs/validation-week.md +5 -0
- package/lib/prismo-dev/agent.js +4 -1
- package/lib/prismo-dev/cli.js +10 -2
- package/lib/prismo-dev/cloud-sync.js +40 -3
- package/lib/prismo-dev/help.js +3 -3
- package/lib/prismo-dev/usage-sessions.js +35 -6
- package/package.json +1 -1
|
@@ -15,6 +15,8 @@ This page lists **exactly** what the connector sends after you connect, field by
|
|
|
15
15
|
|
|
16
16
|
## What session sync sends (`prismo sync`, and the connector on an interval)
|
|
17
17
|
|
|
18
|
+
The background connector captures recent agent sessions from **every repo** on this machine (each attributed to its own repo), so all your agent activity is visible in one place — not just the repo the connector was started in. A one-off `prismo sync` covers only the current repo unless you pass `--all-repos`. Either way, only the numbers and labels below are sent; never code or prompts.
|
|
19
|
+
|
|
18
20
|
Per machine:
|
|
19
21
|
|
|
20
22
|
| field | example | note |
|
package/docs/validation-week.md
CHANGED
|
@@ -8,3 +8,8 @@ Paper cuts, observations, and metric notes from the measurement window. Fix bugs
|
|
|
8
8
|
- 2026-06-12: follow-up dogfood found backend-summary.md could churn backups because generated .prismo context files influenced load-bearing text-reference counts. Excluded .prismo generated context from the reference corpus and added a regression test; repeated optimize on the SaaS repo now produces 0 new .bak files after the one-time old-report replacement.
|
|
9
9
|
- 2026-06-12: connector dogfood found scoped `optimize frontend` and unscoped `optimize` could alternate optimize-report.md's Generated Files list and create metadata-only backups. Changed optimize-report metadata-only changes to update without backups and added a scoped/unscoped regression test.
|
|
10
10
|
- 2026-06-12: connector dogfood found legitimate generated-context changes still left timestamped `.prismo/*.bak` files, which made the repo look dirty during validation. Changed `prismo optimize` context files to update in place because they are reproducible generated artifacts; user-authored files still keep backups.
|
|
11
|
+
- 2026-06-14: THREE real bugs found via dogfooding, all fixed + shipped (v0.1.47-0.1.51):
|
|
12
|
+
1. Digest counted auto-detect health scans as interventions (186 fake vs 3 real).
|
|
13
|
+
2. Verification baseline poisoned by Cursor's blind 0-signals -> now compares like-for-like across tools.
|
|
14
|
+
3. CRITICAL: Claude Code telemetry silently dropped when repo path has a space/dot ("Code Projects") -> path encoding now matches Claude's. Was hiding the strongest signal for any user with a spaced path.
|
|
15
|
+
- 2026-06-14: KNOWN BUG (not yet fixed): `prismo protect` reinstalls/repoints the single connector even when one is already running for another repo, silently hijacking which repo syncs. Connector is single-repo; working across multiple repos means only the watched one syncs to the dashboard. Post-freeze: make protect not move a running connector, and consider syncing all recent repos.
|
package/lib/prismo-dev/agent.js
CHANGED
|
@@ -752,7 +752,10 @@ module.exports = function createAgent(deps) {
|
|
|
752
752
|
let syncResult = null;
|
|
753
753
|
if (options.syncTelemetry) {
|
|
754
754
|
try {
|
|
755
|
-
|
|
755
|
+
// The connector is the always-on capturer: observe every repo's agent
|
|
756
|
+
// activity, not just the one it was started in (each session is
|
|
757
|
+
// attributed to its own repo). Opt out with allRepos: false.
|
|
758
|
+
const result = await runSync(rootDir, { limit: options.syncLimit || 20, allRepos: options.allRepos !== false });
|
|
756
759
|
syncResult = {
|
|
757
760
|
synced: Boolean(result.synced),
|
|
758
761
|
sessions: Number(result.aggregate?.sessions || 0),
|
package/lib/prismo-dev/cli.js
CHANGED
|
@@ -417,6 +417,7 @@ function createCli(deps) {
|
|
|
417
417
|
preview: rest.includes("--preview"),
|
|
418
418
|
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
|
|
419
419
|
tool: toolIndex >= 0 ? rest[toolIndex + 1] : "all",
|
|
420
|
+
allRepos: rest.includes("--all-repos"),
|
|
420
421
|
endpoint: apiUrlIndex >= 0 ? `${String(rest[apiUrlIndex + 1] || "").replace(/\/$/, "")}/v1/dev/sessions/sync` : null,
|
|
421
422
|
};
|
|
422
423
|
if (rest.includes("--watch")) {
|
|
@@ -859,8 +860,15 @@ function createCli(deps) {
|
|
|
859
860
|
const config = loadConfig();
|
|
860
861
|
if (config?.token) {
|
|
861
862
|
const connector = runConnectorStatus();
|
|
862
|
-
if (connector.
|
|
863
|
-
|
|
863
|
+
if (connector.installed) {
|
|
864
|
+
// The connector already runs and now observes every repo's agent
|
|
865
|
+
// activity (all-repos sync), so do NOT repoint it to this repo —
|
|
866
|
+
// that would hijack which repo the dashboard treats as primary.
|
|
867
|
+
const watchedRoot = connector.state && connector.state.root;
|
|
868
|
+
const watched = watchedRoot && path.resolve(watchedRoot) !== path.resolve(target)
|
|
869
|
+
? ` (watching ${path.basename(watchedRoot)}; it captures this repo too)`
|
|
870
|
+
: "";
|
|
871
|
+
steps.push({ step: "connector", ok: true, detail: `Background connector already installed${watched}.` });
|
|
864
872
|
} else {
|
|
865
873
|
runConnectorInstall(target, { mode: "autopilot" });
|
|
866
874
|
steps.push({ step: "connector", ok: true, detail: "Background connector installed and started in autopilot." });
|
|
@@ -86,7 +86,18 @@ module.exports = function createCloudSync(deps) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function
|
|
89
|
+
function sameResolvedPath(a, b) {
|
|
90
|
+
if (!a || !b) return false;
|
|
91
|
+
try {
|
|
92
|
+
const ra = fs.existsSync(a) ? fs.realpathSync(a) : path.resolve(a);
|
|
93
|
+
const rb = fs.existsSync(b) ? fs.realpathSync(b) : path.resolve(b);
|
|
94
|
+
return ra === rb;
|
|
95
|
+
} catch {
|
|
96
|
+
return path.resolve(a) === path.resolve(b);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function repoIdentity(root, idOptions = {}) {
|
|
90
101
|
const resolved = path.resolve(root || process.cwd());
|
|
91
102
|
const remote = runGit(resolved, ["config", "--get", "remote.origin.url"]);
|
|
92
103
|
const branch = runGit(resolved, ["branch", "--show-current"]);
|
|
@@ -96,7 +107,9 @@ module.exports = function createCloudSync(deps) {
|
|
|
96
107
|
remote: redactRemote(remote),
|
|
97
108
|
branch: branch || null,
|
|
98
109
|
commit: commit || null,
|
|
99
|
-
|
|
110
|
+
// PR detection shells out to `gh` per repo; skip it for secondary repos
|
|
111
|
+
// in all-repos sync so a 60s poll doesn't fan out gh calls everywhere.
|
|
112
|
+
pr: idOptions.detectPr === false ? null : detectPullRequest(resolved),
|
|
100
113
|
};
|
|
101
114
|
}
|
|
102
115
|
|
|
@@ -176,11 +189,13 @@ module.exports = function createCloudSync(deps) {
|
|
|
176
189
|
|
|
177
190
|
function buildSyncPayload(rootDir = process.cwd(), options = {}) {
|
|
178
191
|
const root = path.resolve(rootDir);
|
|
192
|
+
const allRepos = Boolean(options.allRepos);
|
|
179
193
|
const repo = repoIdentity(root);
|
|
180
194
|
const usage = getUsageSummary({
|
|
181
195
|
cwd: root,
|
|
182
196
|
tool: options.tool || "all",
|
|
183
197
|
limit: options.limit || 20,
|
|
198
|
+
allRepos,
|
|
184
199
|
});
|
|
185
200
|
let scan = null;
|
|
186
201
|
try {
|
|
@@ -188,7 +203,28 @@ module.exports = function createCloudSync(deps) {
|
|
|
188
203
|
} catch {
|
|
189
204
|
scan = null;
|
|
190
205
|
}
|
|
191
|
-
|
|
206
|
+
|
|
207
|
+
// In all-repos mode each session is attributed to its own repo, derived
|
|
208
|
+
// from the session's recorded cwd (git lookups cached per directory), so
|
|
209
|
+
// the dashboard groups activity by the repo it actually happened in.
|
|
210
|
+
const repoCache = new Map();
|
|
211
|
+
function repoForSession(session) {
|
|
212
|
+
if (!allRepos) return repo;
|
|
213
|
+
const sessionCwd = session && session.cwd ? path.resolve(session.cwd) : null;
|
|
214
|
+
if (!sessionCwd) return repo;
|
|
215
|
+
if (sameResolvedPath(sessionCwd, root)) return repo;
|
|
216
|
+
if (repoCache.has(sessionCwd)) return repoCache.get(sessionCwd);
|
|
217
|
+
let id;
|
|
218
|
+
try {
|
|
219
|
+
id = repoIdentity(sessionCwd, { detectPr: false });
|
|
220
|
+
} catch {
|
|
221
|
+
id = repo;
|
|
222
|
+
}
|
|
223
|
+
repoCache.set(sessionCwd, id);
|
|
224
|
+
return id;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sessions = (usage.sessions || []).map((session) => sanitizeSession(session, repoForSession(session)));
|
|
192
228
|
const aggregate = sessions.reduce((acc, session) => {
|
|
193
229
|
acc.sessions += 1;
|
|
194
230
|
acc.displayTokens += session.tokens.display;
|
|
@@ -347,6 +383,7 @@ module.exports = function createCloudSync(deps) {
|
|
|
347
383
|
const payload = buildSyncPayload(rootDir, {
|
|
348
384
|
limit: options.limit || config?.sync?.defaultLimit || 20,
|
|
349
385
|
tool: options.tool || "all",
|
|
386
|
+
allRepos: Boolean(options.allRepos),
|
|
350
387
|
});
|
|
351
388
|
if (options.dryRun || options.preview) {
|
|
352
389
|
return {
|
package/lib/prismo-dev/help.js
CHANGED
|
@@ -15,7 +15,7 @@ Usage:
|
|
|
15
15
|
prismo connect [--json] [--token TOKEN] [--api-url URL] [--org ORG] [--user USER] [--device NAME]
|
|
16
16
|
prismo connector [status|install|start|stop|uninstall] [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
|
|
17
17
|
prismo bridge [--json] [path]
|
|
18
|
-
prismo sync [--json] [--dry-run] [--watch] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
18
|
+
prismo sync [--json] [--dry-run] [--watch] [--all-repos] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
19
19
|
prismo status [--json]
|
|
20
20
|
prismo digest [--json] [--days N]
|
|
21
21
|
prismo disconnect [--json]
|
|
@@ -485,7 +485,7 @@ Output:
|
|
|
485
485
|
Usage:
|
|
486
486
|
prismo connect [--json] [--token TOKEN] [--api-url URL] [--org ORG] [--user USER] [--device NAME]
|
|
487
487
|
prismo connector [status|install|start|stop|uninstall] [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
|
|
488
|
-
prismo sync [--json] [--dry-run] [--watch] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
488
|
+
prismo sync [--json] [--dry-run] [--watch] [--all-repos] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
489
489
|
prismo status [--json]
|
|
490
490
|
prismo disconnect [--json]
|
|
491
491
|
|
|
@@ -556,7 +556,7 @@ Privacy:
|
|
|
556
556
|
sync: `PrismoDev Sync
|
|
557
557
|
|
|
558
558
|
Usage:
|
|
559
|
-
prismo sync [--json] [--dry-run] [--watch] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
559
|
+
prismo sync [--json] [--dry-run] [--watch] [--all-repos] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
560
560
|
|
|
561
561
|
Examples:
|
|
562
562
|
prismo sync --dry-run
|
|
@@ -257,24 +257,53 @@ function sameResolvedPath(a, b) {
|
|
|
257
257
|
return false;
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
function safeMtimeMs(filePath) {
|
|
261
|
+
try {
|
|
262
|
+
return fs.statSync(filePath).mtimeMs;
|
|
263
|
+
} catch {
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
260
268
|
function getUsageSummary(options = {}) {
|
|
261
269
|
const tool = options.tool || "all";
|
|
262
270
|
const limit = options.limit || 5;
|
|
263
271
|
const cwd = options.cwd || process.cwd();
|
|
272
|
+
// allRepos: capture the most recent sessions across every repo on this
|
|
273
|
+
// machine (each retains its own cwd for per-repo attribution), so a single
|
|
274
|
+
// always-on connector observes all of the user's agent activity rather than
|
|
275
|
+
// only the one repo it was started in. Cursor is skipped here because it
|
|
276
|
+
// exposes no per-session signal and cannot be attributed per repo.
|
|
277
|
+
const allRepos = Boolean(options.allRepos);
|
|
264
278
|
const sessions = [];
|
|
265
279
|
if (tool === "all" || tool === "codex") {
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
280
|
+
if (allRepos) {
|
|
281
|
+
const recent = getCodexSessionFiles()
|
|
282
|
+
.map((file) => ({ file, m: safeMtimeMs(file) }))
|
|
283
|
+
.sort((a, b) => b.m - a.m)
|
|
284
|
+
.slice(0, Math.max(limit, 30));
|
|
285
|
+
for (const { file } of recent) sessions.push(analyzeSessionFile(file, "codex"));
|
|
286
|
+
} else {
|
|
287
|
+
for (const file of getCodexSessionFiles().slice(0, Math.max(limit * 8, 20))) {
|
|
288
|
+
const session = analyzeSessionFile(file, "codex");
|
|
289
|
+
if (!session.cwd || sameResolvedPath(session.cwd, cwd)) sessions.push(session);
|
|
290
|
+
if (sessions.filter((item) => item.tool === "codex").length >= limit) break;
|
|
291
|
+
}
|
|
270
292
|
}
|
|
271
293
|
}
|
|
272
294
|
if (tool === "all" || tool === "claude") {
|
|
273
|
-
|
|
295
|
+
const claudeFiles = allRepos
|
|
296
|
+
? getAllClaudeSessionFiles()
|
|
297
|
+
.map((file) => ({ file, m: safeMtimeMs(file) }))
|
|
298
|
+
.sort((a, b) => b.m - a.m)
|
|
299
|
+
.slice(0, Math.max(limit, 30))
|
|
300
|
+
.map((x) => x.file)
|
|
301
|
+
: getClaudeSessionFiles(cwd).slice(0, limit);
|
|
302
|
+
for (const file of claudeFiles) {
|
|
274
303
|
sessions.push(analyzeSessionFile(file, "claude-code"));
|
|
275
304
|
}
|
|
276
305
|
}
|
|
277
|
-
if (tool === "all" || tool === "cursor") {
|
|
306
|
+
if (!allRepos && (tool === "all" || tool === "cursor")) {
|
|
278
307
|
try {
|
|
279
308
|
const cursorData = analyzeCursorSessions({ limit, cwd });
|
|
280
309
|
if (cursorData.sessions.length) {
|
package/package.json
CHANGED