polymath-society 0.2.18 → 0.2.19
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/dist/cli.js +83 -21
- package/dist/index.js +66 -4
- package/dist/pipeline/coding-gap.js +9 -2
- package/dist/web/app.js +50 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -33,9 +33,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
));
|
|
34
34
|
|
|
35
35
|
// src/dataHome.ts
|
|
36
|
-
import { existsSync } from "fs";
|
|
36
|
+
import { existsSync, readFileSync } from "fs";
|
|
37
37
|
import os from "os";
|
|
38
38
|
import path from "path";
|
|
39
|
+
function hasCompletedAnalysis(root) {
|
|
40
|
+
try {
|
|
41
|
+
const a = JSON.parse(readFileSync(path.join(root, "coding", "aggregate.json"), "utf-8"));
|
|
42
|
+
return !!String(a.bigPicture ?? "").trim();
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
39
47
|
function electronAppData() {
|
|
40
48
|
const candidates = [
|
|
41
49
|
"/Applications/Polymath.app/Contents/Resources/standalone/.data",
|
|
@@ -44,14 +52,18 @@ function electronAppData() {
|
|
|
44
52
|
for (const c of candidates) if (existsSync(path.join(c, "coding"))) return c;
|
|
45
53
|
return null;
|
|
46
54
|
}
|
|
47
|
-
function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData) {
|
|
55
|
+
function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData, home = os.homedir()) {
|
|
48
56
|
const fromEnv = env.POLYMATH_DATA_DIR;
|
|
49
57
|
if (fromEnv && fromEnv.trim()) return path.resolve(fromEnv);
|
|
50
58
|
const local = path.join(cwd, ".data");
|
|
51
|
-
|
|
59
|
+
const dflt = path.join(home, ".polymath-society", "data");
|
|
60
|
+
if (existsSync(local) && hasCompletedAnalysis(local)) return local;
|
|
52
61
|
const fromApp = appData();
|
|
62
|
+
if (fromApp && hasCompletedAnalysis(fromApp)) return fromApp;
|
|
63
|
+
if (hasCompletedAnalysis(dflt)) return dflt;
|
|
64
|
+
if (existsSync(local)) return local;
|
|
53
65
|
if (fromApp) return fromApp;
|
|
54
|
-
return
|
|
66
|
+
return dflt;
|
|
55
67
|
}
|
|
56
68
|
var init_dataHome = __esm({
|
|
57
69
|
"src/dataHome.ts"() {
|
|
@@ -2042,7 +2054,7 @@ __export(plan_exports, {
|
|
|
2042
2054
|
tierFromStrings: () => tierFromStrings
|
|
2043
2055
|
});
|
|
2044
2056
|
import { promises as fs34 } from "fs";
|
|
2045
|
-
import
|
|
2057
|
+
import os10 from "os";
|
|
2046
2058
|
import path39 from "path";
|
|
2047
2059
|
function tierFromStrings(...hints) {
|
|
2048
2060
|
for (const h of hints) {
|
|
@@ -2063,7 +2075,7 @@ function tierFromChatgptPlan(planType) {
|
|
|
2063
2075
|
if (s === "free") return "gpt_free";
|
|
2064
2076
|
return "unknown";
|
|
2065
2077
|
}
|
|
2066
|
-
async function detectCodexPlan(home =
|
|
2078
|
+
async function detectCodexPlan(home = os10.homedir()) {
|
|
2067
2079
|
try {
|
|
2068
2080
|
const raw = JSON.parse(await fs34.readFile(path39.join(home, ".codex", "auth.json"), "utf8"));
|
|
2069
2081
|
for (const tok of [raw.tokens?.id_token, raw.tokens?.access_token]) {
|
|
@@ -2079,7 +2091,7 @@ async function detectCodexPlan(home = os9.homedir()) {
|
|
|
2079
2091
|
}
|
|
2080
2092
|
return "unknown";
|
|
2081
2093
|
}
|
|
2082
|
-
async function detectPlan(home =
|
|
2094
|
+
async function detectPlan(home = os10.homedir()) {
|
|
2083
2095
|
let tier = "unknown";
|
|
2084
2096
|
try {
|
|
2085
2097
|
const raw = JSON.parse(await fs34.readFile(path39.join(home, ".claude.json"), "utf8"));
|
|
@@ -2173,7 +2185,7 @@ __export(wizard_exports, {
|
|
|
2173
2185
|
sayExportLinks: () => sayExportLinks
|
|
2174
2186
|
});
|
|
2175
2187
|
import { promises as fs35 } from "fs";
|
|
2176
|
-
import
|
|
2188
|
+
import os11 from "os";
|
|
2177
2189
|
import path40 from "path";
|
|
2178
2190
|
import * as readline from "node:readline/promises";
|
|
2179
2191
|
async function scanJsonlDir(label, dir) {
|
|
@@ -2313,7 +2325,7 @@ async function runWizard() {
|
|
|
2313
2325
|
for (let i = 0; i < agentScans.length; i++) {
|
|
2314
2326
|
const s = agentScans[i];
|
|
2315
2327
|
const idx = present.indexOf(agentIds[i]);
|
|
2316
|
-
say(s.files > 0 ? ` ${idx + 1}. ${green("\u2713")} ${bold(s.label)} \u2014 ${s.files.toLocaleString()} log files \xB7 newest ${s.newest} ${dim(s.dir.replace(
|
|
2328
|
+
say(s.files > 0 ? ` ${idx + 1}. ${green("\u2713")} ${bold(s.label)} \u2014 ${s.files.toLocaleString()} log files \xB7 newest ${s.newest} ${dim(s.dir.replace(os11.homedir(), "~"))}` : ` ${dim("\xB7")} ${dim(`${s.label} \u2014 none found`)} ${dim(s.dir.replace(os11.homedir(), "~"))}`);
|
|
2317
2329
|
}
|
|
2318
2330
|
say(dim(" (log files include forks and agent runs \u2014 the report separates your real sessions out)"));
|
|
2319
2331
|
if (present.length === 0) {
|
|
@@ -2376,9 +2388,9 @@ async function runWizard() {
|
|
|
2376
2388
|
}
|
|
2377
2389
|
};
|
|
2378
2390
|
say(dim(" Scanning for known export formats (Spotlight + the usual folders, a few seconds)..."));
|
|
2379
|
-
for (const c of await deterministicDiscover(
|
|
2380
|
-
for (const f of await scanForExports(path40.join(
|
|
2381
|
-
for (const f of await scanForObsidianVaults(
|
|
2391
|
+
for (const c of await deterministicDiscover(os11.homedir())) add(await identifyPath(c));
|
|
2392
|
+
for (const f of await scanForExports(path40.join(os11.homedir(), "Downloads"))) add(f);
|
|
2393
|
+
for (const f of await scanForObsidianVaults(os11.homedir())) add(f);
|
|
2382
2394
|
for (const f of [...previous?.exports.included ?? [], ...previous?.exports.excluded ?? []]) add(f);
|
|
2383
2395
|
if (!found.length) {
|
|
2384
2396
|
say(dim(" Nothing found in the usual places."));
|
|
@@ -2386,7 +2398,7 @@ async function runWizard() {
|
|
|
2386
2398
|
try {
|
|
2387
2399
|
const { invokeAgent: invokeAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
2388
2400
|
say(dim(" Searching (your agent writes its own filesystem searches; nothing leaves this machine; 90s max)..."));
|
|
2389
|
-
const candidates = await agentDiscoverCandidates((inv) => invokeAgent3(inv),
|
|
2401
|
+
const candidates = await agentDiscoverCandidates((inv) => invokeAgent3(inv), os11.homedir(), []);
|
|
2390
2402
|
for (const c of candidates) add(await identifyPath(c));
|
|
2391
2403
|
} catch (e) {
|
|
2392
2404
|
say(dim(` Agent search unavailable (${e instanceof Error ? e.message.slice(0, 80) : "failed"}).`));
|
|
@@ -2394,7 +2406,7 @@ async function runWizard() {
|
|
|
2394
2406
|
}
|
|
2395
2407
|
if (!found.length) {
|
|
2396
2408
|
const folder = await ask(` Got them in a specific folder? ${dim("(path, or Enter to skip)")}: `);
|
|
2397
|
-
if (folder) for (const f of await scanForExports(folder.replace(/^~(?=$|\/)/,
|
|
2409
|
+
if (folder) for (const f of await scanForExports(folder.replace(/^~(?=$|\/)/, os11.homedir()))) add(f);
|
|
2398
2410
|
}
|
|
2399
2411
|
}
|
|
2400
2412
|
if (found.length) {
|
|
@@ -2419,7 +2431,7 @@ async function runWizard() {
|
|
|
2419
2431
|
approvedExports = manifest.exports.included;
|
|
2420
2432
|
await writeManifest(dataDir, manifest);
|
|
2421
2433
|
const nIn = manifest.exports.included.length;
|
|
2422
|
-
say(` ${green("\u2713")} Saved to ${path40.join(dataDir, "exports-manifest.json").replace(
|
|
2434
|
+
say(` ${green("\u2713")} Saved to ${path40.join(dataDir, "exports-manifest.json").replace(os11.homedir(), "~")} \u2014 ${nIn} source${nIn === 1 ? "" : "s"} approved, ${manifest.exports.excluded.length} excluded (re-run anytime to change).`);
|
|
2423
2435
|
for (const f of manifest.exports.excluded) say(` ${dim(`\u2717 ${path40.basename(f.path)} \u2014 excluded, never read past identification.`)}`);
|
|
2424
2436
|
if (manifest.exports.included.some((f) => f.kind !== "unknown")) {
|
|
2425
2437
|
say(` ${dim("These are ingested and analyzed locally when you pick the full analysis below.")}`);
|
|
@@ -4447,7 +4459,7 @@ import { promises as fs14 } from "fs";
|
|
|
4447
4459
|
import path19 from "path";
|
|
4448
4460
|
|
|
4449
4461
|
// ../../lib/agents/shared/agent.ts
|
|
4450
|
-
import { readFileSync } from "fs";
|
|
4462
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
4451
4463
|
import path13 from "path";
|
|
4452
4464
|
|
|
4453
4465
|
// ../../lib/agents/shared/cliAdapter.ts
|
|
@@ -19895,7 +19907,7 @@ var ADAPTERS2 = {
|
|
|
19895
19907
|
};
|
|
19896
19908
|
function preferredEngine() {
|
|
19897
19909
|
try {
|
|
19898
|
-
const p = JSON.parse(
|
|
19910
|
+
const p = JSON.parse(readFileSync2(path13.join(process.cwd(), ".data", "profile.json"), "utf-8"));
|
|
19899
19911
|
const e = p?.preferredEngine;
|
|
19900
19912
|
return e === "cli" || e === "codex" || e === "cursor" || e === "api" ? e : null;
|
|
19901
19913
|
} catch {
|
|
@@ -23066,6 +23078,8 @@ init_exportLinks();
|
|
|
23066
23078
|
// src/publicCodingPageApi.ts
|
|
23067
23079
|
import { promises as fs28 } from "fs";
|
|
23068
23080
|
import path32 from "path";
|
|
23081
|
+
import os7 from "os";
|
|
23082
|
+
import crypto3 from "crypto";
|
|
23069
23083
|
|
|
23070
23084
|
// ../../lib/agents/coding/publicPage.ts
|
|
23071
23085
|
import { promises as fs27 } from "fs";
|
|
@@ -24465,6 +24479,49 @@ async function handleAnalysisShare(dataDir, kind, full) {
|
|
|
24465
24479
|
return { status: 502, json: { error: e.message.slice(0, 200) } };
|
|
24466
24480
|
}
|
|
24467
24481
|
}
|
|
24482
|
+
function computeMachineUnion(machines) {
|
|
24483
|
+
const counts = /* @__PURE__ */ new Map();
|
|
24484
|
+
for (const m of Object.values(machines)) for (const s of m.sessions ?? []) counts.set(s.id, (counts.get(s.id) ?? 0) + 1);
|
|
24485
|
+
return {
|
|
24486
|
+
machines: Object.entries(machines).map(([id, m]) => ({ id, label: m.label ?? id, sessions: (m.sessions ?? []).length, syncedAt: m.syncedAt ?? null })),
|
|
24487
|
+
uniqueSessions: counts.size,
|
|
24488
|
+
overlappingSessions: [...counts.values()].filter((n) => n > 1).length
|
|
24489
|
+
};
|
|
24490
|
+
}
|
|
24491
|
+
async function handleAnalysisSync(dataDir) {
|
|
24492
|
+
const cfg = centralConfig();
|
|
24493
|
+
if (!cfg.configured) return { status: 503, json: { error: "central services are disabled in this build" } };
|
|
24494
|
+
const auth = await getAccessToken(dataDir);
|
|
24495
|
+
if (!auth) return { status: 401, json: { error: "sign in with Google (top right) first" } };
|
|
24496
|
+
const mFile = path32.join(dataDir, "machine.json");
|
|
24497
|
+
let machine = await readJson4(mFile, null);
|
|
24498
|
+
if (!machine?.id) {
|
|
24499
|
+
machine = { id: crypto3.randomBytes(8).toString("hex"), label: os7.hostname().replace(/\.local$/, "") };
|
|
24500
|
+
await fs28.writeFile(mFile, JSON.stringify(machine, null, 2));
|
|
24501
|
+
}
|
|
24502
|
+
const rows = await readJson4(
|
|
24503
|
+
path32.join(dataDir, "coding", "sessions.json"),
|
|
24504
|
+
[]
|
|
24505
|
+
);
|
|
24506
|
+
const sessions = rows.filter((r) => r.klass === "interactive" && !r.duplicateOf && r.sessionId).map((r) => ({ id: r.sessionId, start: r.start?.slice(0, 10) ?? null, chars: r.humanChars ?? 0, project: r.project ?? null, source: r.source ?? null }));
|
|
24507
|
+
if (!sessions.length) return { status: 409, json: { error: "nothing to sync yet \u2014 run the analysis first" } };
|
|
24508
|
+
const prior = await readOwnReportContent(cfg, auth.token, auth.identity.userId);
|
|
24509
|
+
try {
|
|
24510
|
+
const machines = prior.machines && typeof prior.machines === "object" ? prior.machines : {};
|
|
24511
|
+
machines[machine.id] = { label: machine.label, syncedAt: (/* @__PURE__ */ new Date()).toISOString(), sessions };
|
|
24512
|
+
const combined = computeMachineUnion(machines);
|
|
24513
|
+
const merged = { format: "coding-pr1", ...prior, machines, combined };
|
|
24514
|
+
const up = await fetch(`${cfg.supabaseUrl}/rest/v1/rpc/submit_report`, {
|
|
24515
|
+
method: "POST",
|
|
24516
|
+
headers: { apikey: cfg.supabaseAnonKey, authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
|
|
24517
|
+
body: JSON.stringify({ p_session_id: null, p_content: JSON.stringify(merged) })
|
|
24518
|
+
});
|
|
24519
|
+
if (!up.ok) return { status: 502, json: { error: `sync failed (HTTP ${up.status})` } };
|
|
24520
|
+
return { status: 200, json: { ok: true, machine: machine.label, ...combined } };
|
|
24521
|
+
} catch (e) {
|
|
24522
|
+
return { status: 502, json: { error: e.message.slice(0, 200) } };
|
|
24523
|
+
}
|
|
24524
|
+
}
|
|
24468
24525
|
|
|
24469
24526
|
// src/server.ts
|
|
24470
24527
|
var __dirname = path33.dirname(fileURLToPath2(import.meta.url));
|
|
@@ -24792,6 +24849,11 @@ function startServer(opts) {
|
|
|
24792
24849
|
json(res, out.status, out.json);
|
|
24793
24850
|
return;
|
|
24794
24851
|
}
|
|
24852
|
+
if (req.method === "POST" && route === "/api/analysis/sync") {
|
|
24853
|
+
const out = await handleAnalysisSync(dataDir);
|
|
24854
|
+
json(res, out.status, out.json);
|
|
24855
|
+
return;
|
|
24856
|
+
}
|
|
24795
24857
|
if (req.method === "POST" && route === "/api/analysis/share") {
|
|
24796
24858
|
const body = JSON.parse(await readBody(req) || "{}");
|
|
24797
24859
|
const out = await handleAnalysisShare(dataDir, body.kind === "chat" ? "chat" : "coding", {
|
|
@@ -24855,7 +24917,7 @@ function startServer(opts) {
|
|
|
24855
24917
|
|
|
24856
24918
|
// src/pipeline.ts
|
|
24857
24919
|
import { spawn as spawn7 } from "child_process";
|
|
24858
|
-
import
|
|
24920
|
+
import os9 from "os";
|
|
24859
24921
|
import path36 from "path";
|
|
24860
24922
|
import { existsSync as existsSync5, promises as fs31 } from "fs";
|
|
24861
24923
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -24867,10 +24929,10 @@ import { promises as fs30 } from "fs";
|
|
|
24867
24929
|
import path35 from "path";
|
|
24868
24930
|
|
|
24869
24931
|
// ../../lib/agents/shared/paths.ts
|
|
24870
|
-
import
|
|
24932
|
+
import os8 from "os";
|
|
24871
24933
|
import path34 from "path";
|
|
24872
24934
|
function polymathHome() {
|
|
24873
|
-
return process.env.POLYMATH_HOME || path34.join(
|
|
24935
|
+
return process.env.POLYMATH_HOME || path34.join(os8.homedir(), ".polymath-society");
|
|
24874
24936
|
}
|
|
24875
24937
|
function agentDir(agent) {
|
|
24876
24938
|
return path34.join(polymathHome(), agent);
|
|
@@ -25029,7 +25091,7 @@ async function startRunLog(clientInfo = {}, opts = {}) {
|
|
|
25029
25091
|
|
|
25030
25092
|
// src/pipeline.ts
|
|
25031
25093
|
var MODULE_DIR2 = path36.dirname(fileURLToPath3(import.meta.url));
|
|
25032
|
-
function machineBudget(totalMemBytes =
|
|
25094
|
+
function machineBudget(totalMemBytes = os9.totalmem(), cpus = os9.availableParallelism?.() ?? os9.cpus().length, envOverride = process.env.POLYMATH_CONC) {
|
|
25033
25095
|
const envB = Number(envOverride);
|
|
25034
25096
|
if (Number.isFinite(envB) && envB >= 1) return Math.min(32, Math.floor(envB));
|
|
25035
25097
|
const byMem = Math.floor(totalMemBytes / 2 ** 30 * 0.35 / 0.45);
|
package/dist/index.js
CHANGED
|
@@ -21785,9 +21785,17 @@ Apply the user's LAST instruction and return the complete updated payload.`;
|
|
|
21785
21785
|
}
|
|
21786
21786
|
|
|
21787
21787
|
// src/dataHome.ts
|
|
21788
|
-
import { existsSync as existsSync4 } from "fs";
|
|
21788
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
21789
21789
|
import os6 from "os";
|
|
21790
21790
|
import path26 from "path";
|
|
21791
|
+
function hasCompletedAnalysis(root) {
|
|
21792
|
+
try {
|
|
21793
|
+
const a = JSON.parse(readFileSync2(path26.join(root, "coding", "aggregate.json"), "utf-8"));
|
|
21794
|
+
return !!String(a.bigPicture ?? "").trim();
|
|
21795
|
+
} catch {
|
|
21796
|
+
return false;
|
|
21797
|
+
}
|
|
21798
|
+
}
|
|
21791
21799
|
function electronAppData() {
|
|
21792
21800
|
const candidates = [
|
|
21793
21801
|
"/Applications/Polymath.app/Contents/Resources/standalone/.data",
|
|
@@ -21796,14 +21804,18 @@ function electronAppData() {
|
|
|
21796
21804
|
for (const c of candidates) if (existsSync4(path26.join(c, "coding"))) return c;
|
|
21797
21805
|
return null;
|
|
21798
21806
|
}
|
|
21799
|
-
function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData) {
|
|
21807
|
+
function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData, home = os6.homedir()) {
|
|
21800
21808
|
const fromEnv = env.POLYMATH_DATA_DIR;
|
|
21801
21809
|
if (fromEnv && fromEnv.trim()) return path26.resolve(fromEnv);
|
|
21802
21810
|
const local = path26.join(cwd, ".data");
|
|
21803
|
-
|
|
21811
|
+
const dflt = path26.join(home, ".polymath-society", "data");
|
|
21812
|
+
if (existsSync4(local) && hasCompletedAnalysis(local)) return local;
|
|
21804
21813
|
const fromApp = appData();
|
|
21814
|
+
if (fromApp && hasCompletedAnalysis(fromApp)) return fromApp;
|
|
21815
|
+
if (hasCompletedAnalysis(dflt)) return dflt;
|
|
21816
|
+
if (existsSync4(local)) return local;
|
|
21805
21817
|
if (fromApp) return fromApp;
|
|
21806
|
-
return
|
|
21818
|
+
return dflt;
|
|
21807
21819
|
}
|
|
21808
21820
|
|
|
21809
21821
|
// src/manifest.ts
|
|
@@ -21879,6 +21891,8 @@ var EXPORT_LINKS = [
|
|
|
21879
21891
|
// src/publicCodingPageApi.ts
|
|
21880
21892
|
import { promises as fs28 } from "fs";
|
|
21881
21893
|
import path32 from "path";
|
|
21894
|
+
import os7 from "os";
|
|
21895
|
+
import crypto3 from "crypto";
|
|
21882
21896
|
|
|
21883
21897
|
// ../../lib/agents/coding/publicPage.ts
|
|
21884
21898
|
import { promises as fs27 } from "fs";
|
|
@@ -23278,6 +23292,49 @@ async function handleAnalysisShare(dataDir, kind, full) {
|
|
|
23278
23292
|
return { status: 502, json: { error: e.message.slice(0, 200) } };
|
|
23279
23293
|
}
|
|
23280
23294
|
}
|
|
23295
|
+
function computeMachineUnion(machines) {
|
|
23296
|
+
const counts = /* @__PURE__ */ new Map();
|
|
23297
|
+
for (const m of Object.values(machines)) for (const s of m.sessions ?? []) counts.set(s.id, (counts.get(s.id) ?? 0) + 1);
|
|
23298
|
+
return {
|
|
23299
|
+
machines: Object.entries(machines).map(([id, m]) => ({ id, label: m.label ?? id, sessions: (m.sessions ?? []).length, syncedAt: m.syncedAt ?? null })),
|
|
23300
|
+
uniqueSessions: counts.size,
|
|
23301
|
+
overlappingSessions: [...counts.values()].filter((n) => n > 1).length
|
|
23302
|
+
};
|
|
23303
|
+
}
|
|
23304
|
+
async function handleAnalysisSync(dataDir) {
|
|
23305
|
+
const cfg = centralConfig();
|
|
23306
|
+
if (!cfg.configured) return { status: 503, json: { error: "central services are disabled in this build" } };
|
|
23307
|
+
const auth = await getAccessToken(dataDir);
|
|
23308
|
+
if (!auth) return { status: 401, json: { error: "sign in with Google (top right) first" } };
|
|
23309
|
+
const mFile = path32.join(dataDir, "machine.json");
|
|
23310
|
+
let machine = await readJson4(mFile, null);
|
|
23311
|
+
if (!machine?.id) {
|
|
23312
|
+
machine = { id: crypto3.randomBytes(8).toString("hex"), label: os7.hostname().replace(/\.local$/, "") };
|
|
23313
|
+
await fs28.writeFile(mFile, JSON.stringify(machine, null, 2));
|
|
23314
|
+
}
|
|
23315
|
+
const rows = await readJson4(
|
|
23316
|
+
path32.join(dataDir, "coding", "sessions.json"),
|
|
23317
|
+
[]
|
|
23318
|
+
);
|
|
23319
|
+
const sessions = rows.filter((r) => r.klass === "interactive" && !r.duplicateOf && r.sessionId).map((r) => ({ id: r.sessionId, start: r.start?.slice(0, 10) ?? null, chars: r.humanChars ?? 0, project: r.project ?? null, source: r.source ?? null }));
|
|
23320
|
+
if (!sessions.length) return { status: 409, json: { error: "nothing to sync yet \u2014 run the analysis first" } };
|
|
23321
|
+
const prior = await readOwnReportContent(cfg, auth.token, auth.identity.userId);
|
|
23322
|
+
try {
|
|
23323
|
+
const machines = prior.machines && typeof prior.machines === "object" ? prior.machines : {};
|
|
23324
|
+
machines[machine.id] = { label: machine.label, syncedAt: (/* @__PURE__ */ new Date()).toISOString(), sessions };
|
|
23325
|
+
const combined = computeMachineUnion(machines);
|
|
23326
|
+
const merged = { format: "coding-pr1", ...prior, machines, combined };
|
|
23327
|
+
const up = await fetch(`${cfg.supabaseUrl}/rest/v1/rpc/submit_report`, {
|
|
23328
|
+
method: "POST",
|
|
23329
|
+
headers: { apikey: cfg.supabaseAnonKey, authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
|
|
23330
|
+
body: JSON.stringify({ p_session_id: null, p_content: JSON.stringify(merged) })
|
|
23331
|
+
});
|
|
23332
|
+
if (!up.ok) return { status: 502, json: { error: `sync failed (HTTP ${up.status})` } };
|
|
23333
|
+
return { status: 200, json: { ok: true, machine: machine.label, ...combined } };
|
|
23334
|
+
} catch (e) {
|
|
23335
|
+
return { status: 502, json: { error: e.message.slice(0, 200) } };
|
|
23336
|
+
}
|
|
23337
|
+
}
|
|
23281
23338
|
|
|
23282
23339
|
// src/server.ts
|
|
23283
23340
|
var __dirname = path33.dirname(fileURLToPath2(import.meta.url));
|
|
@@ -23605,6 +23662,11 @@ function startServer(opts) {
|
|
|
23605
23662
|
json(res, out.status, out.json);
|
|
23606
23663
|
return;
|
|
23607
23664
|
}
|
|
23665
|
+
if (req.method === "POST" && route === "/api/analysis/sync") {
|
|
23666
|
+
const out = await handleAnalysisSync(dataDir);
|
|
23667
|
+
json(res, out.status, out.json);
|
|
23668
|
+
return;
|
|
23669
|
+
}
|
|
23608
23670
|
if (req.method === "POST" && route === "/api/analysis/share") {
|
|
23609
23671
|
const body = JSON.parse(await readBody(req) || "{}");
|
|
23610
23672
|
const out = await handleAnalysisShare(dataDir, body.kind === "chat" ? "chat" : "coding", {
|
|
@@ -17191,6 +17191,9 @@ async function main() {
|
|
|
17191
17191
|
return m ? `--- "${s.title}" \xB7 ${(s.start || "").slice(0, 10)} ---
|
|
17192
17192
|
${m.text.slice(0, 3e3)}` : "";
|
|
17193
17193
|
}))).filter(Boolean);
|
|
17194
|
+
const digestDays = (await fs11.readFile(path13.join(CODING, "day-digest.json"), "utf-8").then(JSON.parse).catch(() => ({ days: [] }))).days ?? [];
|
|
17195
|
+
const digestLines = digestDays.slice().sort((a, b) => a.date < b.date ? -1 : 1).slice(-21).map((d) => `${d.date}: ${d.overall ?? ""}
|
|
17196
|
+
${(d.conversations ?? []).slice(0, 4).map((c) => ` - ${c.title ?? ""}: ${c.summary ?? ""}`).join("\n")}`).join("\n").slice(0, 12e3);
|
|
17194
17197
|
const SYS = "You are a Claude Code power-user coach. You judge ONE person against the COMPLETE catalog of best ways to use Claude Code (the rubric below, sourced from Boris Cherny + first-party Claude Code features) and write a comprehensive, honest gap analysis, ORDERED most-difference-making \u2192 least. Rules: (1) Go through the whole catalog and surface the techniques they are NOT doing (or under-using) that would genuinely help \u2014 respecting each tip's [EMPHASIZE]/[DE-EMPHASIZE]/[GATE] curation. (2) ORDER by differenceRank (1 = biggest unlock for THIS person). Follow the rubric's PRIME DIRECTIVE: if usage.parallelism.alreadyParallel is FALSE \u2192 'Do more in parallel' is rank 1; else \u2192 'Invest in your CLAUDE.md / tune your context' is rank 1. Then Plan mode, then 'Give Claude test cases so it can auto-verify and run longer' (Boris's 'most important thing' \u2014 rank it high, right after plan mode), then subagents, and the rest per the rubric order, skipping any that don't apply. (3) GATE the conditional tips on the STRUCTURAL usage facts, NOT on keywords \u2014 and a tip whose gate FAILS is OMITTED from recommendations entirely, never included as a softened/conditional card (recommending something their data says doesn't apply, or that they already do, reads as not having looked): loops require real PR/branch volume in usage.collaboration, else OMIT; the Chrome tip requires ~zero browser-driving in usage.chrome, else OMIT and credit in alreadyStrong; forking uses the RECENT count in usage.forking \u2014 if they fork in the recent window they already do it, OMIT and credit; remote/mobile/EC2 fires only when usage.surface.workstationBound is TRUE; plan mode by usage.planMode; subagents by usage.subagents. If they already do a thing, it goes in alreadyStrong, NOT in recommendations. (4) \u26D4 CRITICAL \u2014 this person BUILDS Claude Code tooling, so their transcripts are full of the words 'worktree', 'pull request', 'fork', 'plan mode', 'by the way', 'teleport'. A keyword in the transcript is NOT evidence they use the feature. Rely on the STRUCTURAL facts only. (5) Ground every recommendation in a RECEIPT \u2014 a structural fact from their usage or a concrete session pain point \u2014 and CONVINCE with a specific case for how it helps THIS person, not a generic benefit. (6) Tailor every action to the Claude Desktop Mac app; NEVER give terminal-only `claude -p` advice. (7) Honor config.deliberateChoices (they stay on `main` on purpose \u2014 do NOT tell them to hand-juggle worktrees; parallel SESSIONS instead) and config.claudeAutoTools (never tell them to 'use' a tool Claude drives itself; for 'explain with visuals' the user just ASKS for a visual \u2014 do not name the tool). (8) Also name what they ALREADY do well. (9) SELF-CONTAINED but not verbose \u2014 lead with the PROBLEM, then the SOLUTION, then HOW to use it in their context. Plain language, no jargon, no enumerating multiple dates. An essay-like rec is a failed rec; so is a cryptic one. Follow this writing rubric strictly:\n\n" + writing + sourceLookupNote();
|
|
17195
17198
|
const prompt = `THE RUBRIC \u2014 the complete catalog of best ways to use Claude Code. Judge the person against ALL of it, and ORDER by differenceRank per its PRIME DIRECTIVE:
|
|
17196
17199
|
|
|
@@ -17200,6 +17203,9 @@ ${techniques}
|
|
|
17200
17203
|
WHAT THIS PERSON ACTUALLY DOES (STRUCTURAL usage facts + config \u2014 ground truth; trust these over anything in the transcripts):
|
|
17201
17204
|
${JSON.stringify(usage, null, 1)}
|
|
17202
17205
|
|
|
17206
|
+
THEIR RECENT DAYS, task by task (dated ground material \u2014 draw applyItHere moments from HERE and from the excerpts, never from imagination):
|
|
17207
|
+
${digestLines}
|
|
17208
|
+
|
|
17203
17209
|
SESSION EXCERPTS (for pain points / friction / grounding ONLY \u2014 never as proof they USE a feature; ">> " = the person, "[AI]" = the model):
|
|
17204
17210
|
${excerpts.join("\n\n").slice(0, 6e4)}
|
|
17205
17211
|
|
|
@@ -17207,11 +17213,12 @@ ${excerpts.join("\n\n").slice(0, 6e4)}
|
|
|
17207
17213
|
Produce the gap analysis. Be thorough: cover every relevant catalog item they're missing, ordered most-difference-making first. Output a SINGLE fenced json block (every string a complete, punctuated, no-fluff sentence):
|
|
17208
17214
|
\`\`\`json
|
|
17209
17215
|
{
|
|
17210
|
-
"recommendations": [ { "title": "Boris's OWN / the feature's general wording for the tip (e.g. 'Invest in your CLAUDE.md', 'Start every complex task in Plan mode', 'Give Claude test cases so it can auto-verify and run longer', 'Code from anywhere with a remote instance', 'Fork your session', 'Use /btw for side queries') \u2014 NOT a hyper-specific name. For the verification tip the title MUST contain 'verify its own work' or 'check its own work' so the report picks the right icon.", "category": "rubric tip", "status": "not-doing|under-using", "differenceRank": 1, "gap": "LEAD WITH THE PROBLEM in plain language \u2014 what it's currently costing them, grounded in a STRUCTURAL fact. One clear, self-contained sentence (~12-18 words), no jargon. If they already do it somewhat, frame it as 'you do X; the gap is pushing it further'.", "whyItHelps": "The SOLUTION, then HOW to use it in THEIR context with a concrete example. Plain language, self-contained, ~30-45 words. Two crisp beats: fix, then their-context application. HARD RULE: no jargon, and NEVER name a Claude-auto tool. EXAMPLE (plan mode): 'Skipping the plan makes Claude pause to ask and drift. Fix: iterate the whole approach in Plan mode first, and drop in concrete expected results as test cases so Claude self-verifies and runs autonomously far longer.'", "payoff": 5 } ],
|
|
17216
|
+
"recommendations": [ { "title": "Boris's OWN / the feature's general wording for the tip (e.g. 'Invest in your CLAUDE.md', 'Start every complex task in Plan mode', 'Give Claude test cases so it can auto-verify and run longer', 'Code from anywhere with a remote instance', 'Fork your session', 'Use /btw for side queries') \u2014 NOT a hyper-specific name. For the verification tip the title MUST contain 'verify its own work' or 'check its own work' so the report picks the right icon.", "category": "rubric tip", "status": "not-doing|under-using", "differenceRank": 1, "gap": "LEAD WITH THE PROBLEM in plain language \u2014 what it's currently costing them, grounded in a STRUCTURAL fact. One clear, self-contained sentence (~12-18 words), no jargon. If they already do it somewhat, frame it as 'you do X; the gap is pushing it further'.", "whyItHelps": "The SOLUTION, then HOW to use it in THEIR context with a concrete example. Plain language, self-contained, ~30-45 words. Two crisp beats: fix, then their-context application. HARD RULE: no jargon, and NEVER name a Claude-auto tool. EXAMPLE (plan mode): 'Skipping the plan makes Claude pause to ask and drift. Fix: iterate the whole approach in Plan mode first, and drop in concrete expected results as test cases so Claude self-verifies and runs autonomously far longer.'", "payoff": 5, "applyItHere": [ { "moment": "A SPECIFIC dated moment from THEIR OWN days/excerpts \u2014 name the repo/project and the actual task in their vocabulary (~15-25 words).", "insteadDo": "How this exact technique changes that exact moment \u2014 concrete, imperative, their context (~15-25 words)." } ] } ],
|
|
17211
17217
|
"alreadyStrong": [ { "title": "", "note": "What they already do well, concretely." } ]
|
|
17212
17218
|
}
|
|
17213
17219
|
\`\`\`
|
|
17214
|
-
\`differenceRank\` is a strict 1..N ordering (1 = highest). Assign it per the PRIME DIRECTIVE and the rubric's general order; the report renders recommendations sorted by it. \`payoff\` (1-5) is a secondary strength cue. Recommend ONLY from the catalog
|
|
17220
|
+
\`differenceRank\` is a strict 1..N ordering (1 = highest). Assign it per the PRIME DIRECTIVE and the rubric's general order; the report renders recommendations sorted by it. \`payoff\` (1-5) is a secondary strength cue. Recommend ONLY from the catalog.
|
|
17221
|
+
\`applyItHere\` HARD RULES: 3 entries per recommendation when the material supports 3, minimum 2 \u2014 and every moment must be VISIBLY present in THEIR RECENT DAYS or the excerpts (a real dated task, named the way they name it). NEVER invent, generalize, or reuse the same moment across recommendations. If the material truly supports only one real moment, give that one \u2014 a fabricated example is worse than a short list.`;
|
|
17215
17222
|
const sha = promptSha([SYS, prompt, "sonnet"]);
|
|
17216
17223
|
if (!process.argv.includes("--refresh")) {
|
|
17217
17224
|
const prev = await reusableOutput(path13.join(CODING, "gap.json"), sha, (o) => Array.isArray(o.recommendations) && o.recommendations.length > 0);
|
package/dist/web/app.js
CHANGED
|
@@ -7291,6 +7291,8 @@ var import_jsx_runtime = __toESM(require_jsx_runtime());
|
|
|
7291
7291
|
function ShareAnalysisRow({ kind, label }) {
|
|
7292
7292
|
const [state, setState] = (0, import_react.useState)("idle");
|
|
7293
7293
|
const [err, setErr] = (0, import_react.useState)("");
|
|
7294
|
+
const [sync, setSync] = (0, import_react.useState)("idle");
|
|
7295
|
+
const [syncMsg, setSyncMsg] = (0, import_react.useState)("");
|
|
7294
7296
|
const send = async () => {
|
|
7295
7297
|
setState("sending");
|
|
7296
7298
|
try {
|
|
@@ -7309,23 +7311,55 @@ function ShareAnalysisRow({ kind, label }) {
|
|
|
7309
7311
|
setState("error");
|
|
7310
7312
|
}
|
|
7311
7313
|
};
|
|
7314
|
+
const doSync = async () => {
|
|
7315
|
+
setSync("sending");
|
|
7316
|
+
setSyncMsg("");
|
|
7317
|
+
try {
|
|
7318
|
+
const r = await fetch("/api/analysis/sync", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }).then((r2) => r2.json());
|
|
7319
|
+
if (r.ok) {
|
|
7320
|
+
const n = r.machines?.length ?? 1;
|
|
7321
|
+
const overlap = r.overlappingSessions ? ` \xB7 ${r.overlappingSessions} overlapping, counted once` : "";
|
|
7322
|
+
setSyncMsg(`Synced \u2713 ${r.machine} \u2014 ${r.uniqueSessions.toLocaleString()} unique sessions across ${n} machine${n === 1 ? "" : "s"}${overlap}`);
|
|
7323
|
+
setSync("idle");
|
|
7324
|
+
} else {
|
|
7325
|
+
setSyncMsg(r.error || "couldn't sync");
|
|
7326
|
+
setSync("error");
|
|
7327
|
+
}
|
|
7328
|
+
} catch {
|
|
7329
|
+
setSyncMsg("couldn't sync \u2014 try again");
|
|
7330
|
+
setSync("error");
|
|
7331
|
+
}
|
|
7332
|
+
};
|
|
7312
7333
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5 rounded-xl border border-line bg-paper px-3.5 py-2.5", children: [
|
|
7313
7334
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-[12px] text-muted", children: [
|
|
7314
7335
|
"Optional: share this full ",
|
|
7315
7336
|
label,
|
|
7316
|
-
" as feedback for Polymath (will greatly help us make this better!). Private \u2014 nothing gets a public link."
|
|
7337
|
+
" as feedback for Polymath (will greatly help us make this better!). Private \u2014 nothing gets a public link.",
|
|
7338
|
+
kind === "coding" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mt-1 block", children: "Work on two machines? Sync each to your account and they combine, overlap counted once." })
|
|
7317
7339
|
] }),
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
7340
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex shrink-0 items-center gap-2", children: [
|
|
7341
|
+
syncMsg && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `text-[11px] ${sync === "error" ? "text-amber-700" : "text-ink"}`, children: syncMsg }),
|
|
7342
|
+
kind === "coding" && !syncMsg && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
7321
7343
|
"button",
|
|
7322
7344
|
{
|
|
7323
|
-
onClick:
|
|
7324
|
-
disabled:
|
|
7345
|
+
onClick: doSync,
|
|
7346
|
+
disabled: sync === "sending",
|
|
7325
7347
|
className: "rounded-full border border-line px-3 py-1 text-[12px] text-ink transition-colors hover:border-accent disabled:opacity-50",
|
|
7326
|
-
children:
|
|
7348
|
+
children: sync === "sending" ? "Syncing\u2026" : "Sync to account"
|
|
7327
7349
|
}
|
|
7328
|
-
)
|
|
7350
|
+
),
|
|
7351
|
+
state === "done" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[12px] font-medium text-ink", children: "Shared \u2713" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
7352
|
+
state === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[11px] text-amber-700", children: err }),
|
|
7353
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
7354
|
+
"button",
|
|
7355
|
+
{
|
|
7356
|
+
onClick: send,
|
|
7357
|
+
disabled: state === "sending",
|
|
7358
|
+
className: "rounded-full border border-line px-3 py-1 text-[12px] text-ink transition-colors hover:border-accent disabled:opacity-50",
|
|
7359
|
+
children: state === "sending" ? "Sending\u2026" : "Share it"
|
|
7360
|
+
}
|
|
7361
|
+
)
|
|
7362
|
+
] })
|
|
7329
7363
|
] })
|
|
7330
7364
|
] });
|
|
7331
7365
|
}
|
|
@@ -9640,7 +9674,14 @@ function FrontierPanel({ f, detail, coaching, gap, parallelism }) {
|
|
|
9640
9674
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mt-1 text-[12.5px] leading-relaxed text-ink", children: [
|
|
9641
9675
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "font-semibold text-accent", children: "Why it helps you \u2014 " }),
|
|
9642
9676
|
r.whyItHelps
|
|
9643
|
-
] })
|
|
9677
|
+
] }),
|
|
9678
|
+
r.applyItHere?.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mt-2 border-t border-line pt-2", children: [
|
|
9679
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-faint", children: "Where this would have hit, in your own work" }),
|
|
9680
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "mt-1.5 space-y-2", children: r.applyItHere.map((ex, j) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { className: "leading-snug", children: [
|
|
9681
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-[12px] font-semibold text-ink", children: ex.insteadDo }),
|
|
9682
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mt-0.5 text-[11.5px] text-ink/80", children: ex.moment })
|
|
9683
|
+
] }, j)) })
|
|
9684
|
+
] }) : null
|
|
9644
9685
|
] }),
|
|
9645
9686
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hidden flex-none items-center self-stretch sm:flex", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TipVisual, { title: r.title }) })
|
|
9646
9687
|
] }) }, i)) }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polymath-society",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "Deterministic metrics from your local Claude Code / Codex agent logs — parallelism, flow, throughput, projects. Zero LLM, zero server, on-device.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|