polymath-society 0.2.4 → 0.2.6
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 +3132 -1694
- package/dist/engine/ingest-export.js +14 -8
- package/dist/engine/public-report.js +14 -8
- package/dist/index.js +1506 -57
- package/dist/pipeline/coding-agglomerate.js +439 -26
- package/dist/pipeline/coding-aggregate.js +313 -9
- package/dist/pipeline/coding-build.js +361 -8
- package/dist/pipeline/coding-coaching.js +274 -22
- package/dist/pipeline/coding-day-digest.js +351 -33
- package/dist/pipeline/coding-delegation.js +369 -22
- package/dist/pipeline/coding-expertise.js +456 -49
- package/dist/pipeline/coding-flow.js +308 -4
- package/dist/pipeline/coding-focus.js +16306 -0
- package/dist/pipeline/coding-frontier-detail.js +444 -26
- package/dist/pipeline/coding-frontier.js +356 -1
- package/dist/pipeline/coding-gap-dist.js +308 -4
- package/dist/pipeline/coding-gap.js +141 -40
- package/dist/pipeline/coding-grade.js +216 -26
- package/dist/pipeline/coding-nutshell.js +347 -17
- package/dist/pipeline/coding-projects.js +387 -7
- package/dist/pipeline/coding-walkthrough.js +324 -19
- package/dist/web/app.js +539 -113
- package/dist/web/styles.css +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2154,6 +2154,9 @@ function invokeAgent(inv, opts = {}) {
|
|
|
2154
2154
|
return getAdapter(opts.adapter).run(inv);
|
|
2155
2155
|
}
|
|
2156
2156
|
|
|
2157
|
+
// ../../lib/agents/coding/grade-calibration.ts
|
|
2158
|
+
var GRADE_CALIBRATION = "\n\nSCORE CALIBRATION \u2014 graders systematically COMPRESS to the middle: real peaks get hedged down to 6-7, and routine sessions get polite 5-6s instead of null. Both are failures; fight them explicitly. Work each criterion in EXACTLY this order:\nSTEP 1 \xB7 EVIDENCE GATE \u2014 find the 1-3 verbatim '>> ' quotes that bear on the criterion. INCLUDE a criterion whenever genuine evidence is present (do not MISS quiet evidence \u2014 check every thread of the session, not just the dominant one). But with NO quotable moment the score is null, full stop: a session can be long, competent, and productive while being null on most criteria. Routine hand-holding earns null, never a 5 \u2014 and null is never a deduction.\nSTEP 2 \xB7 LADDER MATCH \u2014 with quotes in hand, read the criterion's rungs and pick the ONE rung whose marker best describes the quoted moment; name that rung in 'why'. AWARD THE RUNG THE EVIDENCE MATCHES: if your own 'why' describes rung N's criteria, give rung N \u2014 never deny it for lacking the NEXT rung's criteria.\nSTEP 3 \xB7 THE SINGLE BEST MOMENT IS THE SCORE \u2014 set the score by the strongest quoted moment, as if it were the whole session. NEVER average a peak down because the surrounding session is routine (the high-water rule), and never raise a score for volume, effort, or topic prestige without a matching moment.\nSTEP 4 \xB7 ANTI-HEDGE \u2014 if the moment genuinely matches an L8-10 anchor (an override the AI conceded, a shipped-and-verified capability, an 'oh shit, they're right' call, a first-principles redefinition that held), the score IS 8-10. Hedging a real L9 to a 7 is exactly as wrong as inventing a 6 for nothing. The middle is NOT a safe default: commit high on anchor-matching evidence, or go null.\nSTEP 5 \xB7 'CONSERVATIVE' \u2260 SUBTRACT \u2014 when a criterion's ladder says to grade conservatively, that means DO NOT EXCEED the rung whose marker the evidence matches; it NEVER means subtracting rungs from matched evidence. Evidence that matches a rung's marker gets that rung under a conservative reading too.";
|
|
2159
|
+
|
|
2157
2160
|
// ../../lib/calibration/fingerprint.ts
|
|
2158
2161
|
import { createHash } from "node:crypto";
|
|
2159
2162
|
function rubricFingerprint(criteria) {
|
|
@@ -2464,7 +2467,7 @@ ${c.rungs.map((r) => ` ${r.level} \u2014 ${r.marker}`).join("\n")}`
|
|
|
2464
2467
|
}
|
|
2465
2468
|
var SYSTEM = "You grade how a person WORKS from one of their coding-agent sessions (Claude Code / Codex). You read mostly THEIR words; the model's code and output are compressed to context and you ignore the mechanical scaffolding. You never flatter: most sessions are routine hand-holding and should score low or null on most criteria. A score is a placement on the ladder backed by VERBATIM quotes of the user's own lines \u2014 never a vibe.\n\nSTANDING RULES (apply to every criterion):\n1. ABSENCE IS NEVER A DEDUCTION. If a criterion had no chance to show, its score is null \u2014 NOT a low. Don't invent a number for routine work.\n2. HIGH-WATER MARK. Taste and abstraction are PEAK measures \u2014 one genuinely brilliant move in a session counts, even surrounded by routine work. Never grade the person at their worst.\n3. LOW-EFFORT-UNTIL-NEEDED IS THE IDEAL. A terse or vague prompt on routine work is correct economy \u2014 never penalize a vague ask, never reward word-count.\n4. DISCOUNT THE LITERAL HARSHNESS OF SELF-TALK. Profanity aimed at self or AI is DRIVE and high standards, not defects.\n5. PER-CONVERSATION CEILING IS 10. Never output 11 \u2014 scores run 2\u201310.\n6. LOG THE SPIKES IN HIGH DETAIL. When a moment is genuinely good, name it CONCRETELY and quote it.";
|
|
2466
2469
|
function gradeSystemPrompt() {
|
|
2467
|
-
return `${SYSTEM}
|
|
2470
|
+
return `${SYSTEM}${GRADE_CALIBRATION}
|
|
2468
2471
|
|
|
2469
2472
|
Grade ONE coding session (provided in the user message) along the criteria below. ">> " lines are the USER's own words (the signal). ALL bracketed lines are machine context, never the user: "[AI: \u2026]" is the model working, "[bg task: \u2026]" / "[cmd output: \u2026]" are harness notifications, "[mode \u2192 \u2026]" marks a mode switch (context only \u2014 do not grade or quote them). ">> (queued \u2026)" is the user queuing work while the AI runs.
|
|
2470
2473
|
|
|
@@ -2647,7 +2650,7 @@ var LocalGradeProvider = class {
|
|
|
2647
2650
|
|
|
2648
2651
|
// ../../lib/calibration/index.ts
|
|
2649
2652
|
var DEFAULT_CALIBRATION = {
|
|
2650
|
-
version: "2026-07-
|
|
2653
|
+
version: "2026-07-14.2",
|
|
2651
2654
|
// = RUBRIC_FINGERPRINT of the shipped npm rubric (packages/coding-analyzer/
|
|
2652
2655
|
// src/grade/criteria.ts). A unit test fails loud when the rubric changes
|
|
2653
2656
|
// without this being updated (and re-pushed to the server).
|
|
@@ -2736,7 +2739,7 @@ var DEFAULT_CALIBRATION = {
|
|
|
2736
2739
|
}
|
|
2737
2740
|
],
|
|
2738
2741
|
codingTiers: {
|
|
2739
|
-
push: { median: 300, sigma:
|
|
2742
|
+
push: { median: 300, sigma: 0.91, tag: "estimated \u2014 log-normal fit", source: "Anthropic 2026 Claude Code study (400k sessions): median engaged user ~8 prompts/day at ~35 words \u2014 ~300 directed words/day; tail: a 1-in-1,000 user AVERAGES ~5k directed words/day (near-daily heavy dictation, clearly below the ~10k single-day record). AVERAGE over substantial days, cumulative not spiky." },
|
|
2740
2743
|
focus: { median: 0.08, sigma: 0.559, tag: "estimated \u2014 proxy-anchored", source: "median: Anthropic 20h/week engaged-user figure implies ~1h/day of true rapid exchange against an 8h working day; ceiling anchored to the ~4h/day deep-work limit (45% share = 1-in-1,000). AVERAGE share, cumulative not spiky." },
|
|
2741
2744
|
orchestration: { tag: "estimated \u2014 behavior bands", source: "no published concurrency distribution exists; ceiling anchored to the Claude Code lead's documented 10-15 parallel sessions" }
|
|
2742
2745
|
},
|
|
@@ -2769,12 +2772,18 @@ var DEFAULT_CALIBRATION = {
|
|
|
2769
2772
|
line: "the Claude Code lead runs 10\u201315 sessions at once; his #1 tip is 3\u20135 git worktrees in parallel"
|
|
2770
2773
|
},
|
|
2771
2774
|
throughput: {
|
|
2772
|
-
//
|
|
2773
|
-
//
|
|
2774
|
-
//
|
|
2775
|
-
//
|
|
2776
|
-
|
|
2777
|
-
|
|
2775
|
+
// Two DIFFERENT kinds of line (2026-07-14.2): topAvg is a PERCENTILE
|
|
2776
|
+
// claim — what a 1-in-1,000 user AVERAGES over substantial days (5k/day:
|
|
2777
|
+
// near-daily heavy dictation; a measured top dictating power user
|
|
2778
|
+
// averages ~3.9k). topPeak is NOT a percentile — it is the CEILING
|
|
2779
|
+
// reference, the wildest single days on record (~10k: a 14-16h
|
|
2780
|
+
// launch-day marathon), rendered as "wildest single days", never
|
|
2781
|
+
// "top X% peak". An avg-percentile and a peak-percentile can't both be
|
|
2782
|
+
// shown at a same-ish value (avg≈peak reads broken — user call), and a
|
|
2783
|
+
// peak PERCENTILE line is unknowable anyway; the record framing is
|
|
2784
|
+
// honest and keeps the visual gap.
|
|
2785
|
+
topAvgWordsPerDay: 5e3,
|
|
2786
|
+
topPeakWordsPerDay: 1e4,
|
|
2778
2787
|
benchLabel: "top 0.1%",
|
|
2779
2788
|
// No published "words typed/day" for heavy users — the real story is OUTPUT.
|
|
2780
2789
|
loopsHeadline: ">1,000,000 lines of Rust at 99.8% tests passing",
|
|
@@ -2938,7 +2947,7 @@ function buildWebProfile(p, detail) {
|
|
|
2938
2947
|
};
|
|
2939
2948
|
}
|
|
2940
2949
|
function hourlyHistogram(p) {
|
|
2941
|
-
const
|
|
2950
|
+
const MIN4 = 6e4;
|
|
2942
2951
|
const tz = p.tz;
|
|
2943
2952
|
const hourMin = new Array(24).fill(0);
|
|
2944
2953
|
const parts = (ms2) => {
|
|
@@ -2953,7 +2962,7 @@ function hourlyHistogram(p) {
|
|
|
2953
2962
|
const { h, m, s } = parts(cur);
|
|
2954
2963
|
const msToNextHour = ((59 - m) * 60 + (60 - s)) * 1e3;
|
|
2955
2964
|
const segEnd = Math.min(b, cur + msToNextHour);
|
|
2956
|
-
hourMin[h] += (segEnd - cur) /
|
|
2965
|
+
hourMin[h] += (segEnd - cur) / MIN4;
|
|
2957
2966
|
cur = segEnd;
|
|
2958
2967
|
}
|
|
2959
2968
|
}
|
|
@@ -2966,7 +2975,7 @@ function buildOperate(p, detail) {
|
|
|
2966
2975
|
const m = (k) => detail.criteria.find((c) => c.key === k)?.mean ?? null;
|
|
2967
2976
|
const exp = m("expertise"), cog = m("abstraction"), fr = m("frontier");
|
|
2968
2977
|
if (exp == null && cog == null && fr == null) return null;
|
|
2969
|
-
const
|
|
2978
|
+
const pick2 = (ladder, score, fallback = "not enough signal yet") => {
|
|
2970
2979
|
if (score == null) return fallback;
|
|
2971
2980
|
const r = Math.round(score);
|
|
2972
2981
|
const keys = Object.keys(ladder).map(Number).sort((a, b) => a - b);
|
|
@@ -2974,14 +2983,14 @@ function buildOperate(p, detail) {
|
|
|
2974
2983
|
for (const k of keys) if (k <= r) chosen = k;
|
|
2975
2984
|
return ladder[chosen];
|
|
2976
2985
|
};
|
|
2977
|
-
const
|
|
2978
|
-
const
|
|
2979
|
-
const
|
|
2980
|
-
const
|
|
2981
|
-
const
|
|
2986
|
+
const TASTE2 = { 4: "mostly rubber-stamps what the AI produces", 6: "rejects the obviously-bad version and iterates toward better", 8: "makes the call the AI couldn't reach on its own \u2014 opinionated, specific, vindicated", 10: "brings a developed vision up front, and knows where not to impose it" };
|
|
2987
|
+
const DELEGATION2 = { 4: "still uneven \u2014 sometimes under-briefs work they had opinions about", 6: "roughly calibrated on when to do the thinking themselves vs. let the AI run", 8: "well-calibrated \u2014 front-loads the specifiable, reserves correction for genuine AI errors", 10: "near-perfect prompt economy \u2014 almost everything knowable is front-loaded" };
|
|
2988
|
+
const GENERATIVE2 = { 4: "reaches workable answers with effort", 6: "real ideas and sound tradeoff reasoning", 8: "fast collapse to the core, plus its own approach and the exact fix", 10: "out-reasons the tool \u2014 lands insights the AI itself missed" };
|
|
2989
|
+
const EXPERTISE2 = { 2: "defers to the AI on system decisions", 4: "opinions are mostly generic best-practice", 6: "knows their own stack well and corrects the AI on real systems facts", 8: "repeatedly catches what the AI misses with specific systems calls", 10: "makes counter-intuitive, deep-operating-knowledge calls the AI would have gotten backwards" };
|
|
2990
|
+
const FRONTIER2 = { 2: "one session at a time, default settings", 4: "occasionally runs two things; no structural tooling", 6: "routinely parallel, queuing work ahead", 8: "real leverage \u2014 overnight/remote runs, subagents, scripts that drive the AI", 10: "builds harnesses and workflows; loops and scales the AI across machines" };
|
|
2982
2991
|
const judgmentBullets = [
|
|
2983
|
-
`Judgment & taste \u2014 ${
|
|
2984
|
-
`Under ambiguity \u2014 ${
|
|
2992
|
+
`Judgment & taste \u2014 ${pick2(TASTE2, m("taste"))}; on delegation, ${pick2(DELEGATION2, m("delegation"))}.`,
|
|
2993
|
+
`Under ambiguity \u2014 ${pick2(GENERATIVE2, cog)}; strongest where there's no ground truth and the task is to invent what to measure.`
|
|
2985
2994
|
];
|
|
2986
2995
|
let archetype;
|
|
2987
2996
|
if (exp == null || cog == null) archetype = "Not enough graded signal yet to place the technical profile.";
|
|
@@ -3002,17 +3011,17 @@ function buildOperate(p, detail) {
|
|
|
3002
3011
|
frontier: fr,
|
|
3003
3012
|
judgmentBullets,
|
|
3004
3013
|
technicalBullets: [
|
|
3005
|
-
`Systems & architecture \u2014 ${
|
|
3006
|
-
`Raw cognition \u2014 ${
|
|
3007
|
-
`Agent fluency \u2014 ${
|
|
3014
|
+
`Systems & architecture \u2014 ${pick2(EXPERTISE2, exp)}.`,
|
|
3015
|
+
`Raw cognition \u2014 ${pick2(GENERATIVE2, cog)}.`,
|
|
3016
|
+
`Agent fluency \u2014 ${pick2(FRONTIER2, fr)} (${sigSentence}).`
|
|
3008
3017
|
]
|
|
3009
3018
|
};
|
|
3010
3019
|
}
|
|
3011
3020
|
|
|
3012
3021
|
// src/server.ts
|
|
3013
3022
|
import http from "http";
|
|
3014
|
-
import { promises as
|
|
3015
|
-
import
|
|
3023
|
+
import { promises as fs29 } from "fs";
|
|
3024
|
+
import path33 from "path";
|
|
3016
3025
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3017
3026
|
|
|
3018
3027
|
// src/auth.ts
|
|
@@ -5154,8 +5163,8 @@ function getErrorMap() {
|
|
|
5154
5163
|
|
|
5155
5164
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
5156
5165
|
var makeIssue = (params) => {
|
|
5157
|
-
const { data, path:
|
|
5158
|
-
const fullPath = [...
|
|
5166
|
+
const { data, path: path34, errorMaps, issueData } = params;
|
|
5167
|
+
const fullPath = [...path34, ...issueData.path || []];
|
|
5159
5168
|
const fullIssue = {
|
|
5160
5169
|
...issueData,
|
|
5161
5170
|
path: fullPath
|
|
@@ -5271,11 +5280,11 @@ var errorUtil;
|
|
|
5271
5280
|
|
|
5272
5281
|
// ../../node_modules/zod/v3/types.js
|
|
5273
5282
|
var ParseInputLazyPath = class {
|
|
5274
|
-
constructor(parent, value,
|
|
5283
|
+
constructor(parent, value, path34, key) {
|
|
5275
5284
|
this._cachedPath = [];
|
|
5276
5285
|
this.parent = parent;
|
|
5277
5286
|
this.data = value;
|
|
5278
|
-
this._path =
|
|
5287
|
+
this._path = path34;
|
|
5279
5288
|
this._key = key;
|
|
5280
5289
|
}
|
|
5281
5290
|
get path() {
|
|
@@ -18062,39 +18071,39 @@ function createOpenAI(options = {}) {
|
|
|
18062
18071
|
});
|
|
18063
18072
|
const createChatModel = (modelId, settings = {}) => new OpenAIChatLanguageModel(modelId, settings, {
|
|
18064
18073
|
provider: `${providerName}.chat`,
|
|
18065
|
-
url: ({ path:
|
|
18074
|
+
url: ({ path: path34 }) => `${baseURL}${path34}`,
|
|
18066
18075
|
headers: getHeaders,
|
|
18067
18076
|
compatibility,
|
|
18068
18077
|
fetch: options.fetch
|
|
18069
18078
|
});
|
|
18070
18079
|
const createCompletionModel = (modelId, settings = {}) => new OpenAICompletionLanguageModel(modelId, settings, {
|
|
18071
18080
|
provider: `${providerName}.completion`,
|
|
18072
|
-
url: ({ path:
|
|
18081
|
+
url: ({ path: path34 }) => `${baseURL}${path34}`,
|
|
18073
18082
|
headers: getHeaders,
|
|
18074
18083
|
compatibility,
|
|
18075
18084
|
fetch: options.fetch
|
|
18076
18085
|
});
|
|
18077
18086
|
const createEmbeddingModel = (modelId, settings = {}) => new OpenAIEmbeddingModel(modelId, settings, {
|
|
18078
18087
|
provider: `${providerName}.embedding`,
|
|
18079
|
-
url: ({ path:
|
|
18088
|
+
url: ({ path: path34 }) => `${baseURL}${path34}`,
|
|
18080
18089
|
headers: getHeaders,
|
|
18081
18090
|
fetch: options.fetch
|
|
18082
18091
|
});
|
|
18083
18092
|
const createImageModel = (modelId, settings = {}) => new OpenAIImageModel(modelId, settings, {
|
|
18084
18093
|
provider: `${providerName}.image`,
|
|
18085
|
-
url: ({ path:
|
|
18094
|
+
url: ({ path: path34 }) => `${baseURL}${path34}`,
|
|
18086
18095
|
headers: getHeaders,
|
|
18087
18096
|
fetch: options.fetch
|
|
18088
18097
|
});
|
|
18089
18098
|
const createTranscriptionModel = (modelId) => new OpenAITranscriptionModel(modelId, {
|
|
18090
18099
|
provider: `${providerName}.transcription`,
|
|
18091
|
-
url: ({ path:
|
|
18100
|
+
url: ({ path: path34 }) => `${baseURL}${path34}`,
|
|
18092
18101
|
headers: getHeaders,
|
|
18093
18102
|
fetch: options.fetch
|
|
18094
18103
|
});
|
|
18095
18104
|
const createSpeechModel = (modelId) => new OpenAISpeechModel(modelId, {
|
|
18096
18105
|
provider: `${providerName}.speech`,
|
|
18097
|
-
url: ({ path:
|
|
18106
|
+
url: ({ path: path34 }) => `${baseURL}${path34}`,
|
|
18098
18107
|
headers: getHeaders,
|
|
18099
18108
|
fetch: options.fetch
|
|
18100
18109
|
});
|
|
@@ -18115,7 +18124,7 @@ function createOpenAI(options = {}) {
|
|
|
18115
18124
|
const createResponsesModel = (modelId) => {
|
|
18116
18125
|
return new OpenAIResponsesLanguageModel(modelId, {
|
|
18117
18126
|
provider: `${providerName}.responses`,
|
|
18118
|
-
url: ({ path:
|
|
18127
|
+
url: ({ path: path34 }) => `${baseURL}${path34}`,
|
|
18119
18128
|
headers: getHeaders,
|
|
18120
18129
|
fetch: options.fetch
|
|
18121
18130
|
});
|
|
@@ -20795,11 +20804,11 @@ function distOf2(takes) {
|
|
|
20795
20804
|
const observed = takes.filter((t) => t.score != null).length;
|
|
20796
20805
|
const mean2 = vals.reduce((s, v) => s + v, 0) / n;
|
|
20797
20806
|
const sorted = [...vals].sort((a, b) => a - b);
|
|
20798
|
-
const
|
|
20807
|
+
const median2 = n % 2 ? sorted[(n - 1) / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2;
|
|
20799
20808
|
const h = /* @__PURE__ */ new Map();
|
|
20800
20809
|
for (const v of vals) h.set(v, (h.get(v) || 0) + 1);
|
|
20801
20810
|
const hist = [...h.keys()].sort((a, b) => b - a).map((level) => ({ level, count: h.get(level) }));
|
|
20802
|
-
return { n, observed, mean: Math.round(mean2 * 100) / 100, median, hist };
|
|
20811
|
+
return { n, observed, mean: Math.round(mean2 * 100) / 100, median: median2, hist };
|
|
20803
20812
|
}
|
|
20804
20813
|
async function readGrowthAgent() {
|
|
20805
20814
|
const j = await fs16.readFile(path20.join(OUT, "growth-latest.json"), "utf8").then(JSON.parse).catch(() => null);
|
|
@@ -21382,7 +21391,7 @@ function hasContent(r) {
|
|
|
21382
21391
|
function coerceReport(raw, radar, level, scoreMap, nameMap, maxMap) {
|
|
21383
21392
|
const j = raw ?? {};
|
|
21384
21393
|
const str = (v, n = 600) => clip2(v, n);
|
|
21385
|
-
const
|
|
21394
|
+
const pct2 = (tag2, literal) => {
|
|
21386
21395
|
const k = typeof tag2 === "string" ? tag2.trim() : "";
|
|
21387
21396
|
if (k && scoreMap && typeof scoreMap[k] === "number") return bandPercentile(scoreMap[k]) ?? void 0;
|
|
21388
21397
|
return literal ? str(literal, 24) : void 0;
|
|
@@ -21395,7 +21404,7 @@ function coerceReport(raw, radar, level, scoreMap, nameMap, maxMap) {
|
|
|
21395
21404
|
const tag = (v) => typeof v === "string" && v.trim() && scoreMap && scoreMap[v.trim()] != null ? v.trim() : void 0;
|
|
21396
21405
|
const trait = (t) => {
|
|
21397
21406
|
const f = tag(t?.facet);
|
|
21398
|
-
const p =
|
|
21407
|
+
const p = pct2(t?.facet, t?.percentile);
|
|
21399
21408
|
const sc = scoreText(f, scoreMap, maxMap);
|
|
21400
21409
|
const personal = t?.fromPersonalLife === true;
|
|
21401
21410
|
return {
|
|
@@ -21413,7 +21422,7 @@ function coerceReport(raw, radar, level, scoreMap, nameMap, maxMap) {
|
|
|
21413
21422
|
};
|
|
21414
21423
|
const spikes = (Array.isArray(j.spikes) ? j.spikes : []).map((s) => {
|
|
21415
21424
|
const f = tag(s?.facet);
|
|
21416
|
-
const p =
|
|
21425
|
+
const p = pct2(s?.facet, s?.percentile);
|
|
21417
21426
|
const sc = scoreText(f, scoreMap, maxMap);
|
|
21418
21427
|
return {
|
|
21419
21428
|
title: str(s?.title, 160),
|
|
@@ -21646,7 +21655,7 @@ async function profilesRow(cfg, token, userId) {
|
|
|
21646
21655
|
const rows = await r.json();
|
|
21647
21656
|
return rows[0] ?? null;
|
|
21648
21657
|
}
|
|
21649
|
-
async function handleVisibility(dataDir, method, body) {
|
|
21658
|
+
async function handleVisibility(dataDir, method, body, pool) {
|
|
21650
21659
|
const cfg = centralConfig();
|
|
21651
21660
|
if (!cfg.configured) return { status: 503, json: { error: "central services are disabled in this build" } };
|
|
21652
21661
|
const auth = await getAccessToken(dataDir);
|
|
@@ -21662,6 +21671,26 @@ async function handleVisibility(dataDir, method, body) {
|
|
|
21662
21671
|
const isPublic = body?.isPublic;
|
|
21663
21672
|
if (typeof isPublic !== "boolean") return { status: 400, json: { error: "bad-input", message: "isPublic must be a boolean." } };
|
|
21664
21673
|
try {
|
|
21674
|
+
if (isPublic) {
|
|
21675
|
+
const report = await loadPublicReport();
|
|
21676
|
+
if (!report || !hasContent(report)) {
|
|
21677
|
+
return { status: 409, json: { error: "nothing to publish yet \u2014 run the full analysis first so the public report exists, then publish" } };
|
|
21678
|
+
}
|
|
21679
|
+
let numbers = body?.numbers;
|
|
21680
|
+
if (numbers && typeof numbers === "object" && !Array.isArray(numbers) && pool) {
|
|
21681
|
+
numbers = Object.fromEntries(Object.entries(numbers).filter(([k, v]) => k in pool && v != null));
|
|
21682
|
+
} else {
|
|
21683
|
+
numbers = pool && Object.keys(pool).length ? pool : void 0;
|
|
21684
|
+
}
|
|
21685
|
+
const up = await fetch(`${cfg.supabaseUrl}/rest/v1/rpc/submit_report`, {
|
|
21686
|
+
method: "POST",
|
|
21687
|
+
headers: { apikey: cfg.supabaseAnonKey, authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
|
|
21688
|
+
body: JSON.stringify({ p_session_id: null, p_content: JSON.stringify({ format: "pr1", report, ...numbers && Object.keys(numbers).length ? { numbers } : {} }) })
|
|
21689
|
+
});
|
|
21690
|
+
if (!up.ok) {
|
|
21691
|
+
return { status: 502, json: { error: `report upload failed (HTTP ${up.status}) \u2014 refusing to publish a link with nothing behind it` } };
|
|
21692
|
+
}
|
|
21693
|
+
}
|
|
21665
21694
|
const row = await profilesRow(cfg, auth.token, auth.identity.userId);
|
|
21666
21695
|
let slug = row?.public_slug ?? null;
|
|
21667
21696
|
const patch = { is_public: isPublic };
|
|
@@ -21681,9 +21710,1366 @@ async function handleVisibility(dataDir, method, body) {
|
|
|
21681
21710
|
}
|
|
21682
21711
|
}
|
|
21683
21712
|
|
|
21713
|
+
// src/shareEdit.ts
|
|
21714
|
+
import { promises as fs21 } from "fs";
|
|
21715
|
+
import os5 from "os";
|
|
21716
|
+
import path25 from "path";
|
|
21717
|
+
var SYSTEM3 = `You edit a SMALL numbers-only JSON payload a person is about to share (their coding working-style summary). They tell you what to hide or change; you return the COMPLETE updated payload every time.
|
|
21718
|
+
ALLOWED EDITS ONLY: remove a whole section; remove a field inside a section ('don't share my words per day' \u2192 delete avgWordsPerDay); round/coarsen a number ('just say ~4000'); shorten or rename a label. FORBIDDEN: adding fields or sections, inventing numbers, increasing any score or percentile, adding text content. If asked for a forbidden edit, keep the payload unchanged and say why in the reply.
|
|
21719
|
+
Output ONE fenced \`\`\`json block: { "sections": { ...the complete updated payload... } } \u2014 then, AFTER the block, ONE short plain sentence describing what you changed (e.g. "Removed words-per-day and the project names.").`;
|
|
21720
|
+
async function handleShareEdit(pool, body) {
|
|
21721
|
+
const sections = body.sections && typeof body.sections === "object" ? body.sections : null;
|
|
21722
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
21723
|
+
const last = messages.filter((m) => m.role === "user").pop();
|
|
21724
|
+
if (!sections || !last?.content?.trim()) return { status: 400, json: { error: "sections + a user instruction are required" } };
|
|
21725
|
+
const convo = messages.slice(-6).map((m) => `${m.role === "user" ? "USER" : "EDITOR"}: ${m.content}`).join("\n");
|
|
21726
|
+
const prompt = `CURRENT PAYLOAD (what would be shared right now):
|
|
21727
|
+
\`\`\`json
|
|
21728
|
+
${JSON.stringify(sections, null, 1)}
|
|
21729
|
+
\`\`\`
|
|
21730
|
+
|
|
21731
|
+
CONVERSATION:
|
|
21732
|
+
${convo}
|
|
21733
|
+
|
|
21734
|
+
Apply the user's LAST instruction and return the complete updated payload.`;
|
|
21735
|
+
const sandbox = await fs21.mkdtemp(path25.join(os5.tmpdir(), "share-edit-"));
|
|
21736
|
+
try {
|
|
21737
|
+
const run3 = await invokeAgent({ prompt, systemPrompt: SYSTEM3, addDir: sandbox, allowedTools: [], maxTurns: 2, timeoutMs: 12e4, model: "haiku" });
|
|
21738
|
+
const j = run3.json ?? null;
|
|
21739
|
+
const raw = j && typeof j === "object" && "sections" in j ? j.sections : j;
|
|
21740
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { status: 502, json: { error: "the edit didn't produce a valid payload \u2014 try rephrasing" } };
|
|
21741
|
+
const out = {};
|
|
21742
|
+
for (const k of Object.keys(raw)) if (k in pool && raw[k] != null) out[k] = raw[k];
|
|
21743
|
+
const reply = (run3.raw ?? "").split("```").pop()?.trim().split("\n").filter(Boolean).pop()?.slice(0, 300) || "Done.";
|
|
21744
|
+
return { status: 200, json: { sections: out, reply } };
|
|
21745
|
+
} catch (e) {
|
|
21746
|
+
return { status: 502, json: { error: e.message.slice(0, 200) } };
|
|
21747
|
+
} finally {
|
|
21748
|
+
await fs21.rm(sandbox, { recursive: true, force: true }).catch(() => {
|
|
21749
|
+
});
|
|
21750
|
+
}
|
|
21751
|
+
}
|
|
21752
|
+
|
|
21753
|
+
// src/dataHome.ts
|
|
21754
|
+
import { existsSync as existsSync4 } from "fs";
|
|
21755
|
+
import os6 from "os";
|
|
21756
|
+
import path26 from "path";
|
|
21757
|
+
function electronAppData() {
|
|
21758
|
+
const candidates = [
|
|
21759
|
+
"/Applications/Polymath.app/Contents/Resources/standalone/.data",
|
|
21760
|
+
path26.join(os6.homedir(), "Applications", "Polymath.app", "Contents", "Resources", "standalone", ".data")
|
|
21761
|
+
];
|
|
21762
|
+
for (const c of candidates) if (existsSync4(path26.join(c, "coding"))) return c;
|
|
21763
|
+
return null;
|
|
21764
|
+
}
|
|
21765
|
+
function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData) {
|
|
21766
|
+
const fromEnv = env.POLYMATH_DATA_DIR;
|
|
21767
|
+
if (fromEnv && fromEnv.trim()) return path26.resolve(fromEnv);
|
|
21768
|
+
const local = path26.join(cwd, ".data");
|
|
21769
|
+
if (existsSync4(local)) return local;
|
|
21770
|
+
const fromApp = appData();
|
|
21771
|
+
if (fromApp) return fromApp;
|
|
21772
|
+
return path26.join(os6.homedir(), ".polymath-society", "data");
|
|
21773
|
+
}
|
|
21774
|
+
|
|
21775
|
+
// src/manifest.ts
|
|
21776
|
+
import { promises as fs22 } from "fs";
|
|
21777
|
+
import path27 from "path";
|
|
21778
|
+
var MANIFEST_BASENAME = "exports-manifest.json";
|
|
21779
|
+
function manifestPath(dataDir) {
|
|
21780
|
+
return path27.join(dataDir, MANIFEST_BASENAME);
|
|
21781
|
+
}
|
|
21782
|
+
async function readManifest(dataDir) {
|
|
21783
|
+
const p = manifestPath(dataDir);
|
|
21784
|
+
let raw;
|
|
21785
|
+
try {
|
|
21786
|
+
raw = await fs22.readFile(p, "utf-8");
|
|
21787
|
+
} catch {
|
|
21788
|
+
return null;
|
|
21789
|
+
}
|
|
21790
|
+
let j;
|
|
21791
|
+
try {
|
|
21792
|
+
j = JSON.parse(raw);
|
|
21793
|
+
} catch (e) {
|
|
21794
|
+
throw new Error(`${p} exists but is not valid JSON (${e.message}) \u2014 fix or delete it, refusing to guess consent`);
|
|
21795
|
+
}
|
|
21796
|
+
const m = j;
|
|
21797
|
+
const approvedAt = typeof m.approvedAt === "string" ? m.approvedAt : (/* @__PURE__ */ new Date(0)).toISOString();
|
|
21798
|
+
const KNOWN = /* @__PURE__ */ new Set(["chatgpt", "claude", "notion", "obsidian", "unknown"]);
|
|
21799
|
+
const arr = (v) => Array.isArray(v) ? v.filter((f) => f && typeof f.path === "string" && KNOWN.has(f.kind)) : [];
|
|
21800
|
+
if (Array.isArray(m.exports)) {
|
|
21801
|
+
return { approvedAt, exports: { included: arr(m.exports), excluded: [] } };
|
|
21802
|
+
}
|
|
21803
|
+
const ex = m.exports ?? {};
|
|
21804
|
+
const ag = m.agents;
|
|
21805
|
+
const agArr = (v) => Array.isArray(v) ? v.filter((x) => x === "claude-code" || x === "codex") : [];
|
|
21806
|
+
return {
|
|
21807
|
+
approvedAt,
|
|
21808
|
+
...ag ? { agents: { included: agArr(ag.included), excluded: agArr(ag.excluded) } } : {},
|
|
21809
|
+
exports: { included: arr(ex.included), excluded: arr(ex.excluded) }
|
|
21810
|
+
};
|
|
21811
|
+
}
|
|
21812
|
+
|
|
21813
|
+
// src/exportLinks.ts
|
|
21814
|
+
var EXPORT_LINKS = [
|
|
21815
|
+
{
|
|
21816
|
+
kind: "chatgpt",
|
|
21817
|
+
label: "ChatGPT",
|
|
21818
|
+
requestUrl: "https://chatgpt.com/#settings/DataControls",
|
|
21819
|
+
buttonPath: "Settings \u2192 Data controls \u2192 Export data \u2192 Confirm export",
|
|
21820
|
+
eta: "a few hours up to ~2 days \u2014 the download link arrives by email"
|
|
21821
|
+
},
|
|
21822
|
+
{
|
|
21823
|
+
kind: "claude",
|
|
21824
|
+
label: "claude.ai",
|
|
21825
|
+
requestUrl: "https://claude.ai/settings/data-privacy-controls",
|
|
21826
|
+
buttonPath: "Privacy settings \u2192 Export data",
|
|
21827
|
+
eta: "~10 minutes \u2014 the download link arrives by email"
|
|
21828
|
+
},
|
|
21829
|
+
{
|
|
21830
|
+
kind: "notion",
|
|
21831
|
+
label: "Notion",
|
|
21832
|
+
requestUrl: "https://www.notion.so/settings",
|
|
21833
|
+
buttonPath: "Settings \u2192 General \u2192 Export all workspace content (Markdown & CSV)",
|
|
21834
|
+
eta: "~15 minutes \u2014 the download link arrives by email"
|
|
21835
|
+
},
|
|
21836
|
+
{
|
|
21837
|
+
kind: "obsidian",
|
|
21838
|
+
label: "Obsidian",
|
|
21839
|
+
requestUrl: null,
|
|
21840
|
+
buttonPath: "local vault \u2014 no export needed; point the wizard at the folder",
|
|
21841
|
+
eta: ""
|
|
21842
|
+
}
|
|
21843
|
+
];
|
|
21844
|
+
|
|
21845
|
+
// src/publicCodingPageApi.ts
|
|
21846
|
+
import { promises as fs28 } from "fs";
|
|
21847
|
+
import path32 from "path";
|
|
21848
|
+
|
|
21849
|
+
// ../../lib/agents/coding/publicPage.ts
|
|
21850
|
+
import { promises as fs27 } from "fs";
|
|
21851
|
+
import path31 from "path";
|
|
21852
|
+
|
|
21853
|
+
// ../../lib/agents/coding/docsDir.ts
|
|
21854
|
+
import { promises as fs23 } from "fs";
|
|
21855
|
+
import path28 from "path";
|
|
21856
|
+
async function writeScoreboard(codingDir, docs) {
|
|
21857
|
+
const gradesDir = path28.join(codingDir, "grades");
|
|
21858
|
+
let files = [];
|
|
21859
|
+
try {
|
|
21860
|
+
files = (await fs23.readdir(gradesDir)).filter((f) => f.endsWith(".json")).sort();
|
|
21861
|
+
} catch {
|
|
21862
|
+
return;
|
|
21863
|
+
}
|
|
21864
|
+
const byCriterion = /* @__PURE__ */ new Map();
|
|
21865
|
+
for (const f of files) {
|
|
21866
|
+
let g;
|
|
21867
|
+
try {
|
|
21868
|
+
g = JSON.parse(await fs23.readFile(path28.join(gradesDir, f), "utf8"));
|
|
21869
|
+
} catch {
|
|
21870
|
+
continue;
|
|
21871
|
+
}
|
|
21872
|
+
for (const c of g.criteria ?? []) {
|
|
21873
|
+
const s = c.score ?? c.bestEstimate;
|
|
21874
|
+
if (s == null) continue;
|
|
21875
|
+
const line = `L${s}${c.score == null ? "~" : ""} \xB7 ${(g.date ?? "").slice(0, 10)} \xB7 ${(g.title ?? g.sessionId ?? "").replace(/\n.*/s, "").slice(0, 60)} \xB7 ${g.sessionId} \u2014 ${(c.instance ?? "").slice(0, 110)}`;
|
|
21876
|
+
(byCriterion.get(c.key) ?? byCriterion.set(c.key, []).get(c.key)).push({ s, line });
|
|
21877
|
+
}
|
|
21878
|
+
}
|
|
21879
|
+
if (!byCriterion.size) return;
|
|
21880
|
+
const blocks = [...byCriterion.entries()].sort(([a], [b]) => a < b ? -1 : 1).map(
|
|
21881
|
+
([key, rows]) => `## ${key}
|
|
21882
|
+
${rows.sort((a, b) => b.s - a.s).map((r) => r.line).join("\n")}`
|
|
21883
|
+
);
|
|
21884
|
+
await fs23.writeFile(
|
|
21885
|
+
path28.join(docs, "_scoreboard.md"),
|
|
21886
|
+
`# Scoreboard \u2014 every graded session per criterion, best first.
|
|
21887
|
+
# Lx~ = estimate. Format: level \xB7 date \xB7 title \xB7 sessionId \u2014 graded instance.
|
|
21888
|
+
|
|
21889
|
+
${blocks.join("\n\n")}
|
|
21890
|
+
`
|
|
21891
|
+
);
|
|
21892
|
+
}
|
|
21893
|
+
async function ensureCodingDocs(codingDir, sessions) {
|
|
21894
|
+
const docs = path28.join(codingDir, "docs");
|
|
21895
|
+
await fs23.mkdir(docs, { recursive: true });
|
|
21896
|
+
const user = sessions.filter((s) => s.sessionId && !s.duplicateOf && (!("klass" in s) || s.klass === "interactive"));
|
|
21897
|
+
let written = 0;
|
|
21898
|
+
for (const s of user) {
|
|
21899
|
+
const out = path28.join(docs, `${s.sessionId}.txt`);
|
|
21900
|
+
try {
|
|
21901
|
+
await fs23.access(out);
|
|
21902
|
+
continue;
|
|
21903
|
+
} catch {
|
|
21904
|
+
}
|
|
21905
|
+
const mat = await materializeSession2(s.file).catch(() => null);
|
|
21906
|
+
if (!mat?.text) continue;
|
|
21907
|
+
await fs23.writeFile(out, mat.text);
|
|
21908
|
+
written++;
|
|
21909
|
+
}
|
|
21910
|
+
const rows = user.sort((a, b) => (a.start ?? "") < (b.start ?? "") ? -1 : 1).map((s) => `${(s.start ?? "").slice(0, 10)} \xB7 ${(s.title ?? "").replace(/\n.*/s, "").slice(0, 80)} \xB7 ${s.sessionId}`);
|
|
21911
|
+
await fs23.writeFile(path28.join(docs, "_map.md"), rows.join("\n") + "\n");
|
|
21912
|
+
await writeScoreboard(codingDir, docs);
|
|
21913
|
+
if (written) console.log(`[docs] materialized ${written} new transcript(s) \u2192 ${docs}`);
|
|
21914
|
+
return docs;
|
|
21915
|
+
}
|
|
21916
|
+
function sourceLookupNote() {
|
|
21917
|
+
return `
|
|
21918
|
+
|
|
21919
|
+
SOURCE LOOKUP \u2014 you have Read/Grep/Glob over the working directory, which holds EVERYTHING known about this person's coding work. MAPS FIRST, grep second (the engine rule): docs/_scoreboard.md ranks every graded session per criterion, best first \u2014 one glance shows where the spikes/flair are, so start there when hunting peaks or checking whether a high mark is consistent or a one-off; day-digest.json is the day-by-day story of what they did in order \u2014 read the relevant days to get the arc before searching; docs/_map.md lists every session (date \xB7 title \xB7 sessionId). Then drill down: walkthroughs/<sessionId>.json is that session's phase-by-phase account (cheap, already distilled), grades/<sessionId>.json its graded findings with quotes, and docs/<sessionId>.txt the FULL transcript (">> " = the person, "[AI]" = the model) \u2014 the ground truth, read it when the distilled layers can't settle it. Other artifacts (flow.json, delegation/, expertise/, aggregate.json) are there too if a claim depends on them. The digest you were given is the spine \u2014 use lookups when a CLAIM-MOVING question cannot be settled from it (a moment you are about to headline, a promotion/depth call, an ambiguous engagement, whether detail was vision or boilerplate). VERIFY every verbatim quote you cite by finding it in the transcript first; if you cannot find it, do not use it. Look up what changes the output \u2014 do not re-read the whole corpus.`;
|
|
21920
|
+
}
|
|
21921
|
+
|
|
21922
|
+
// ../../lib/agents/coding/promptCache.ts
|
|
21923
|
+
import { createHash as createHash3 } from "crypto";
|
|
21924
|
+
import { promises as fs24 } from "fs";
|
|
21925
|
+
function promptSha(parts) {
|
|
21926
|
+
const h = createHash3("sha256");
|
|
21927
|
+
for (const p of parts) {
|
|
21928
|
+
h.update(p ?? "");
|
|
21929
|
+
h.update("\0");
|
|
21930
|
+
}
|
|
21931
|
+
return h.digest("hex").slice(0, 16);
|
|
21932
|
+
}
|
|
21933
|
+
async function reusableOutput(outPath, sha, valid, key = "promptSha") {
|
|
21934
|
+
try {
|
|
21935
|
+
const o = JSON.parse(await fs24.readFile(outPath, "utf8"));
|
|
21936
|
+
if (o[key] === sha && valid(o)) return o;
|
|
21937
|
+
} catch {
|
|
21938
|
+
}
|
|
21939
|
+
return null;
|
|
21940
|
+
}
|
|
21941
|
+
|
|
21942
|
+
// ../../lib/agents/coding/profile.ts
|
|
21943
|
+
import { promises as fs26 } from "fs";
|
|
21944
|
+
import path30 from "path";
|
|
21945
|
+
|
|
21946
|
+
// ../../lib/agents/coding/walkthrough.ts
|
|
21947
|
+
import { promises as fs25 } from "fs";
|
|
21948
|
+
import path29 from "path";
|
|
21949
|
+
var IDLE_CAP_MS = 12 * 6e4;
|
|
21950
|
+
async function readWalkthrough(outDir, sessionId) {
|
|
21951
|
+
return fs25.readFile(path29.join(outDir, `${sessionId}.json`), "utf8").then(JSON.parse).catch(() => null);
|
|
21952
|
+
}
|
|
21953
|
+
|
|
21954
|
+
// ../../lib/agents/coding/workstyle.ts
|
|
21955
|
+
var MIN3 = 6e4;
|
|
21956
|
+
var DAY_CUTOFF_H = 5;
|
|
21957
|
+
var FLOW_THRESHOLD_MIN = 30;
|
|
21958
|
+
var pct = (x) => `${Math.round(x * 100)}%`;
|
|
21959
|
+
var fmtN = (n) => n.toLocaleString("en-US");
|
|
21960
|
+
var round13 = (x) => Math.round(x * 10) / 10;
|
|
21961
|
+
var median = (xs) => xs.length ? [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)] : 0;
|
|
21962
|
+
function fmtDur2(min) {
|
|
21963
|
+
return min >= 60 ? `${round13(min / 60)}h` : `${Math.round(min)}m`;
|
|
21964
|
+
}
|
|
21965
|
+
function clockLabel(h) {
|
|
21966
|
+
if (h === 0) return "midnight";
|
|
21967
|
+
if (h === 12) return "noon";
|
|
21968
|
+
return h < 12 ? `${h}am` : `${h - 12}pm`;
|
|
21969
|
+
}
|
|
21970
|
+
function pick(ladder, score, fallback = "not enough signal yet") {
|
|
21971
|
+
if (score == null) return fallback;
|
|
21972
|
+
const r = Math.round(score);
|
|
21973
|
+
const keys = Object.keys(ladder).map(Number).sort((a, b) => a - b);
|
|
21974
|
+
let chosen = keys[0];
|
|
21975
|
+
for (const k of keys) if (k <= r) chosen = k;
|
|
21976
|
+
return ladder[chosen];
|
|
21977
|
+
}
|
|
21978
|
+
var TASTE = {
|
|
21979
|
+
4: "mostly rubber-stamps what the AI produces",
|
|
21980
|
+
5: "an ordinary reviewer's bar",
|
|
21981
|
+
6: "rejects the obviously-bad version and iterates toward something better",
|
|
21982
|
+
7: "holds a real, specific quality bar and pushes past the AI's first answer",
|
|
21983
|
+
8: "makes the call the AI couldn't reach on its own \u2014 opinionated, specific, vindicated",
|
|
21984
|
+
9: "high, particular taste \u2014 names AI slop the moment it appears",
|
|
21985
|
+
10: "brings a developed vision up front, and knows where not to impose it",
|
|
21986
|
+
11: "world-class taste"
|
|
21987
|
+
};
|
|
21988
|
+
var DELEGATION = {
|
|
21989
|
+
4: "still uneven \u2014 sometimes under-briefs work they had opinions about",
|
|
21990
|
+
6: "roughly calibrated on when to do the thinking themselves vs. let the AI run",
|
|
21991
|
+
8: "well-calibrated \u2014 front-loads the specifiable, reserves correction for genuine AI errors, lets rote run",
|
|
21992
|
+
10: "near-perfect prompt economy \u2014 almost everything knowable is front-loaded"
|
|
21993
|
+
};
|
|
21994
|
+
var OUTCOMES = {
|
|
21995
|
+
4: "sessions often wrap before a real capability lands",
|
|
21996
|
+
6: "one real, confirmed thing shipped is the typical bar",
|
|
21997
|
+
8: "a substantial, verified capability per session is routine",
|
|
21998
|
+
10: "ships end-to-end-checked work that unblocks a lot downstream"
|
|
21999
|
+
};
|
|
22000
|
+
var GENERATIVE = {
|
|
22001
|
+
4: "reaches workable answers with effort",
|
|
22002
|
+
6: "real ideas and sound tradeoff reasoning",
|
|
22003
|
+
7: "collapses messy situations to their essence and reasons from first principles",
|
|
22004
|
+
8: "fast collapse to the core, plus its own approach and the exact fix",
|
|
22005
|
+
9: "generates frameworks and reframes others wouldn't reach",
|
|
22006
|
+
10: "out-reasons the tool \u2014 lands insights the AI itself missed"
|
|
22007
|
+
};
|
|
22008
|
+
var METACOGNITION = {
|
|
22009
|
+
4: "notices friction but mostly just flags it",
|
|
22010
|
+
6: "spots a bottleneck and adjusts how they work for the case at hand",
|
|
22011
|
+
8: "treats their own throughput as an engineering problem \u2014 names recurring friction and restructures to kill it",
|
|
22012
|
+
10: "re-architects the whole way of working mid-stream to get unstuck"
|
|
22013
|
+
};
|
|
22014
|
+
var EXPERTISE = {
|
|
22015
|
+
2: "defers to the AI on system decisions \u2014 no specific knowledge surfaces",
|
|
22016
|
+
4: "opinions are mostly generic best-practice the AI already had",
|
|
22017
|
+
6: "knows their own stack well and corrects the AI on real systems facts",
|
|
22018
|
+
8: "repeatedly catches what the AI misses with specific systems/tooling calls a junior wouldn't make",
|
|
22019
|
+
9: "deep, specific directives about their own infra a junior wouldn't know",
|
|
22020
|
+
10: "makes counter-intuitive, deep-operating-knowledge calls the AI would have gotten backwards"
|
|
22021
|
+
};
|
|
22022
|
+
var FRONTIER = {
|
|
22023
|
+
2: "one session at a time, default settings",
|
|
22024
|
+
4: "occasionally runs two things; no structural tooling",
|
|
22025
|
+
6: "routinely parallel, queuing work ahead and organizing sessions",
|
|
22026
|
+
8: "real leverage \u2014 overnight/remote runs, subagents, scripts that drive the AI",
|
|
22027
|
+
10: "builds harnesses and workflows; loops and scales the AI across machines"
|
|
22028
|
+
};
|
|
22029
|
+
function flowRunsFromSlots(slots) {
|
|
22030
|
+
const fSlots = Object.keys(slots).map(Number).filter((s) => slots[String(s)] === "F").sort((a, b) => a - b);
|
|
22031
|
+
const runs = [];
|
|
22032
|
+
let start = null, prev = null;
|
|
22033
|
+
const flush = () => {
|
|
22034
|
+
if (start !== null && prev !== null) runs.push((prev - start + 1) * 10);
|
|
22035
|
+
};
|
|
22036
|
+
for (const s of fSlots) {
|
|
22037
|
+
if (prev !== null && s === prev + 1) {
|
|
22038
|
+
prev = s;
|
|
22039
|
+
} else {
|
|
22040
|
+
flush();
|
|
22041
|
+
start = s;
|
|
22042
|
+
prev = s;
|
|
22043
|
+
}
|
|
22044
|
+
}
|
|
22045
|
+
flush();
|
|
22046
|
+
return runs;
|
|
22047
|
+
}
|
|
22048
|
+
function buildFlow(runMin, ctx) {
|
|
22049
|
+
const sorted = [...runMin].sort((a, b) => a - b);
|
|
22050
|
+
const BUCKET_DEFS = [["\u2264 20m", 0, 30], ["30m\u20131h", 30, 60], ["1\u20132h", 60, 120], ["2h+", 120, Infinity]];
|
|
22051
|
+
const flowBlocks = BUCKET_DEFS.map(([label, lo, hi]) => {
|
|
22052
|
+
const inB = sorted.filter((x) => x >= lo && x < hi);
|
|
22053
|
+
return { label, lo, hi, count: inB.length, min: round13(inB.reduce((a, b) => a + b, 0)) };
|
|
22054
|
+
});
|
|
22055
|
+
const deepBlocks = sorted.filter((x) => x >= FLOW_THRESHOLD_MIN).length;
|
|
22056
|
+
const flowHoursPerDay = ctx.activeDays ? ctx.flowHoursTotal / ctx.activeDays : 0;
|
|
22057
|
+
const deepBlocksPerDay = ctx.activeDays ? deepBlocks / ctx.activeDays : 0;
|
|
22058
|
+
const flowCadence = ctx.flowFraction >= 0.45 && flowHoursPerDay >= 1.5 ? "frequent" : ctx.flowFraction < 0.18 ? "rare" : "interspersed";
|
|
22059
|
+
const longest = sorted.length ? Math.max(...sorted) : 0;
|
|
22060
|
+
const med2 = median(sorted);
|
|
22061
|
+
const flowBullets = [];
|
|
22062
|
+
flowBullets.push(`${round13(ctx.flowHoursTotal)}h of genuine flow over ${ctx.activeDays} working days \u2014 about ${round13(flowHoursPerDay)}h a day.`);
|
|
22063
|
+
flowBullets.push(`${pct(ctx.flowFraction)} of active working time is true flow (rapid rhythm or dictation); the rest is thinking-pauses and switching.`);
|
|
22064
|
+
if (flowCadence === "frequent") {
|
|
22065
|
+
flowBullets.push(`Deep flow is a daily norm \u2014 ${deepBlocks} runs of ${FLOW_THRESHOLD_MIN}m+, longest ${fmtDur2(longest)}; highly productive once locked in.`);
|
|
22066
|
+
} else if (flowCadence === "interspersed") {
|
|
22067
|
+
flowBullets.push(`Flow comes in solid bursts \u2014 ${med2}\u2013${longest}m at a time, ${deepBlocks} runs of ${FLOW_THRESHOLD_MIN}m+ \u2014 interspersed with pauses and rapid context-switching across parallel sessions.`);
|
|
22068
|
+
} else {
|
|
22069
|
+
flowBullets.push(`Flow is sporadic \u2014 mostly short focused windows rather than long unbroken runs.`);
|
|
22070
|
+
}
|
|
22071
|
+
if (ctx.peakFlowDay) flowBullets.push(`Best day: ${round13(ctx.peakFlowDay.hours)}h in flow (${pct(ctx.peakFlowDay.fraction)} of that day's working time).`);
|
|
22072
|
+
return {
|
|
22073
|
+
flowSource: ctx.source,
|
|
22074
|
+
flowThresholdMin: FLOW_THRESHOLD_MIN,
|
|
22075
|
+
flowBlocks,
|
|
22076
|
+
blockCount: sorted.length,
|
|
22077
|
+
deepBlocks,
|
|
22078
|
+
longestBlockMin: round13(longest),
|
|
22079
|
+
medianBlockMin: round13(med2),
|
|
22080
|
+
flowFraction: Math.round(ctx.flowFraction * 100) / 100,
|
|
22081
|
+
flowHoursTotal: round13(ctx.flowHoursTotal),
|
|
22082
|
+
flowHoursPerDay: round13(flowHoursPerDay),
|
|
22083
|
+
deepBlocksPerDay: round13(deepBlocksPerDay),
|
|
22084
|
+
flowCadence,
|
|
22085
|
+
peakFlowDay: ctx.peakFlowDay,
|
|
22086
|
+
flowBullets
|
|
22087
|
+
};
|
|
22088
|
+
}
|
|
22089
|
+
function computeWorkstyle(inp) {
|
|
22090
|
+
const { tz, idleGapMin } = inp;
|
|
22091
|
+
const idleGapMs = idleGapMin * MIN3;
|
|
22092
|
+
const all = [];
|
|
22093
|
+
for (const day of inp.dayChart) for (const c of day.conversations) for (const iv of c.intervals) all.push(iv);
|
|
22094
|
+
all.sort((a, b) => a[0] - b[0]);
|
|
22095
|
+
const parts = (ms2) => {
|
|
22096
|
+
const [h, m2, s] = new Date(ms2).toLocaleTimeString("en-GB", { timeZone: tz, hour12: false }).split(":").map(Number);
|
|
22097
|
+
return { h, m: m2, s };
|
|
22098
|
+
};
|
|
22099
|
+
const localDate3 = (ms2) => new Date(ms2).toLocaleDateString("en-CA", { timeZone: tz });
|
|
22100
|
+
const sinceCutoff = (ms2) => {
|
|
22101
|
+
const { h, m: m2 } = parts(ms2);
|
|
22102
|
+
let x = (h - DAY_CUTOFF_H) * 60 + m2;
|
|
22103
|
+
if (x < 0) x += 1440;
|
|
22104
|
+
return x;
|
|
22105
|
+
};
|
|
22106
|
+
const logicalDate = (ms2) => localDate3(ms2 - DAY_CUTOFF_H * 36e5);
|
|
22107
|
+
const fromCutoff = (x) => {
|
|
22108
|
+
const t = (x + DAY_CUTOFF_H * 60) % 1440;
|
|
22109
|
+
return `${String(Math.floor(t / 60)).padStart(2, "0")}:${String(Math.round(t % 60)).padStart(2, "0")}`;
|
|
22110
|
+
};
|
|
22111
|
+
const hourMin = new Array(24).fill(0);
|
|
22112
|
+
for (const [a, b] of all) {
|
|
22113
|
+
let cur = a;
|
|
22114
|
+
while (cur < b) {
|
|
22115
|
+
const { h, m: m2, s } = parts(cur);
|
|
22116
|
+
const msToNextHour = ((59 - m2) * 60 + (60 - s)) * 1e3;
|
|
22117
|
+
const segEnd = Math.min(b, cur + msToNextHour);
|
|
22118
|
+
hourMin[h] += (segEnd - cur) / MIN3;
|
|
22119
|
+
cur = segEnd;
|
|
22120
|
+
}
|
|
22121
|
+
}
|
|
22122
|
+
const hourly = hourMin.map((min, hour) => ({ hour, min: round13(min) }));
|
|
22123
|
+
const totalMin = hourMin.reduce((a, b) => a + b, 0) || 1;
|
|
22124
|
+
const sumHours = (hs) => hs.reduce((a, h) => a + hourMin[h], 0);
|
|
22125
|
+
const shares = {
|
|
22126
|
+
morning: sumHours([5, 6, 7, 8, 9, 10, 11]) / totalMin,
|
|
22127
|
+
afternoon: sumHours([12, 13, 14, 15, 16, 17]) / totalMin,
|
|
22128
|
+
evening: sumHours([18, 19, 20, 21]) / totalMin,
|
|
22129
|
+
lateNight: sumHours([22, 23, 0, 1, 2, 3, 4]) / totalMin
|
|
22130
|
+
};
|
|
22131
|
+
const peakHours = [...hourMin.keys()].filter((h) => hourMin[h] > 0).sort((a, b) => hourMin[b] - hourMin[a]).slice(0, 3);
|
|
22132
|
+
const peak0 = peakHours[0] ?? 12;
|
|
22133
|
+
const isNightOwl = shares.lateNight >= 0.2 || peak0 >= 22 || peak0 <= 3;
|
|
22134
|
+
let lunchDip = null;
|
|
22135
|
+
const dipFloor = totalMin / 40;
|
|
22136
|
+
for (const h of [12, 13, 14]) {
|
|
22137
|
+
if (hourMin[h] < hourMin[h - 1] * 0.7 && hourMin[h] < hourMin[h + 1] * 0.7 && hourMin[h - 1] > dipFloor && hourMin[h + 1] > dipFloor) {
|
|
22138
|
+
if (lunchDip == null || hourMin[h] < hourMin[lunchDip]) lunchDip = h;
|
|
22139
|
+
}
|
|
22140
|
+
}
|
|
22141
|
+
const env = /* @__PURE__ */ new Map();
|
|
22142
|
+
for (const [a, b] of all) {
|
|
22143
|
+
const ld = logicalDate(a);
|
|
22144
|
+
let s = sinceCutoff(a), e = sinceCutoff(b);
|
|
22145
|
+
if (e < s) e += 1440;
|
|
22146
|
+
const cur = env.get(ld) ?? { first: Infinity, last: -Infinity };
|
|
22147
|
+
cur.first = Math.min(cur.first, s);
|
|
22148
|
+
cur.last = Math.max(cur.last, e);
|
|
22149
|
+
env.set(ld, cur);
|
|
22150
|
+
}
|
|
22151
|
+
const startsM = [...env.values()].map((v) => v.first);
|
|
22152
|
+
const endsM = [...env.values()].map((v) => v.last);
|
|
22153
|
+
const daysObserved = env.size;
|
|
22154
|
+
const typicalStart = startsM.length ? fromCutoff(median(startsM)) : "\u2014";
|
|
22155
|
+
const typicalEnd = endsM.length ? fromCutoff(median(endsM)) : "\u2014";
|
|
22156
|
+
const earliestStart = startsM.length ? fromCutoff(Math.min(...startsM)) : "\u2014";
|
|
22157
|
+
const latestEnd = endsM.length ? fromCutoff(Math.max(...endsM)) : "\u2014";
|
|
22158
|
+
let flow;
|
|
22159
|
+
if (inp.flow && inp.flow.days.length) {
|
|
22160
|
+
const runs = [];
|
|
22161
|
+
for (const d of inp.flow.days) runs.push(...flowRunsFromSlots(d.slots));
|
|
22162
|
+
const peak = [...inp.flow.days].sort((a, b) => b.flowHours - a.flowHours)[0];
|
|
22163
|
+
flow = buildFlow(runs, {
|
|
22164
|
+
source: "flow.json",
|
|
22165
|
+
activeDays: inp.flow.totals.activeDays,
|
|
22166
|
+
flowFraction: inp.flow.totals.flowFraction,
|
|
22167
|
+
flowHoursTotal: inp.flow.totals.flowSlots / 6,
|
|
22168
|
+
peakFlowDay: peak ? { date: peak.date, hours: peak.flowHours, fraction: peak.flowFraction } : null
|
|
22169
|
+
});
|
|
22170
|
+
} else {
|
|
22171
|
+
const merged = [];
|
|
22172
|
+
for (const [a, b] of all) {
|
|
22173
|
+
const last = merged[merged.length - 1];
|
|
22174
|
+
if (last && a <= last[1] + idleGapMs) last[1] = Math.max(last[1], b);
|
|
22175
|
+
else merged.push([a, b]);
|
|
22176
|
+
}
|
|
22177
|
+
const blocks = merged.map(([a, b]) => (b - a) / MIN3);
|
|
22178
|
+
const totalBlockMin = blocks.reduce((a, b) => a + b, 0) || 1;
|
|
22179
|
+
const deepMin = blocks.filter((x) => x >= FLOW_THRESHOLD_MIN).reduce((a, b) => a + b, 0);
|
|
22180
|
+
flow = buildFlow(blocks, {
|
|
22181
|
+
source: "intervals",
|
|
22182
|
+
activeDays: daysObserved,
|
|
22183
|
+
flowFraction: deepMin / totalBlockMin,
|
|
22184
|
+
flowHoursTotal: deepMin / 60,
|
|
22185
|
+
peakFlowDay: null
|
|
22186
|
+
});
|
|
22187
|
+
}
|
|
22188
|
+
const m = (k) => inp.criteriaMean[k] ?? { mean: null, n: 0 };
|
|
22189
|
+
const judgment = [
|
|
22190
|
+
{ key: "taste", label: "Taste", score: m("taste").mean, n: m("taste").n },
|
|
22191
|
+
{ key: "delegation", label: "Calibration", score: m("delegation").mean, n: m("delegation").n },
|
|
22192
|
+
{ key: "generative", label: "Under ambiguity", score: m("generative").mean, n: m("generative").n },
|
|
22193
|
+
{ key: "metacognition", label: "Self-direction", score: m("metacognition").mean, n: m("metacognition").n },
|
|
22194
|
+
{ key: "outcomes", label: "Outcomes", score: m("outcomes").mean, n: m("outcomes").n }
|
|
22195
|
+
];
|
|
22196
|
+
const rhythmBullets = [];
|
|
22197
|
+
const lateTail = /^(0[0-5]|2[3])/.test(latestEnd);
|
|
22198
|
+
rhythmBullets.push(`Works roughly ${typicalStart}\u2013${typicalEnd} on a typical day${lateTail ? `, with sessions running as late as ${latestEnd}` : ""}.`);
|
|
22199
|
+
if (isNightOwl) {
|
|
22200
|
+
rhythmBullets.push(`A night owl \u2014 ${pct(shares.lateNight)} of the work lands after 10pm or in the small hours.`);
|
|
22201
|
+
} else if (shares.morning >= shares.afternoon && shares.morning >= shares.evening) {
|
|
22202
|
+
rhythmBullets.push(`An early bird \u2014 most of the work happens before noon (${pct(shares.morning)} of active time).`);
|
|
22203
|
+
} else if (shares.afternoon >= shares.evening) {
|
|
22204
|
+
rhythmBullets.push(`An afternoon worker \u2014 ${pct(shares.afternoon)} of active time falls between noon and 6pm, only ${pct(shares.lateNight)} in the small hours.`);
|
|
22205
|
+
} else {
|
|
22206
|
+
rhythmBullets.push(`An evening worker \u2014 work concentrates after 6pm (${pct(shares.evening)} of active time).`);
|
|
22207
|
+
}
|
|
22208
|
+
{
|
|
22209
|
+
let txt = `Productivity peaks around ${clockLabel(peak0)}`;
|
|
22210
|
+
const p1 = peakHours.find((h) => Math.abs(h - peak0) >= 3);
|
|
22211
|
+
if (p1 != null) txt += `, with a second push around ${clockLabel(p1)}`;
|
|
22212
|
+
rhythmBullets.push(txt + ".");
|
|
22213
|
+
}
|
|
22214
|
+
if (lunchDip != null) rhythmBullets.push(`Takes a visible midday breather around ${clockLabel(lunchDip)}.`);
|
|
22215
|
+
const judgmentBullets = [];
|
|
22216
|
+
judgmentBullets.push(`Judgment & taste \u2014 ${pick(TASTE, m("taste").mean)}; on delegation, ${pick(DELEGATION, m("delegation").mean)}.`);
|
|
22217
|
+
{
|
|
22218
|
+
const cad = inp.outcomeCadence ? `ships something meaningful ~${round13(inp.outcomeCadence.perWeek)}\xD7/week (${pct(inp.outcomeCadence.fraction)} of working days), and ` : "";
|
|
22219
|
+
judgmentBullets.push(`High-outcome work \u2014 ${cad}${pick(OUTCOMES, m("outcomes").mean)}.`);
|
|
22220
|
+
}
|
|
22221
|
+
judgmentBullets.push(`Under ambiguity \u2014 ${pick(GENERATIVE, m("generative").mean)}; strongest where there's no ground truth and the task is to invent what to measure.`);
|
|
22222
|
+
judgmentBullets.push(`Prioritizing the work \u2014 ${pick(METACOGNITION, m("metacognition").mean)}.`);
|
|
22223
|
+
const exp = m("expertise").mean, cog = m("generative").mean, fr = m("frontier").mean;
|
|
22224
|
+
const sig = inp.signals;
|
|
22225
|
+
let archetype;
|
|
22226
|
+
if (exp == null || cog == null) {
|
|
22227
|
+
archetype = "Not enough graded signal yet to place the technical profile.";
|
|
22228
|
+
} else if (cog - exp >= 1.2) {
|
|
22229
|
+
archetype = "Cognition-forward \u2014 reasoning runs ahead of hands-on systems depth: thinks like a senior, with some stack-specific knowledge still leveling up.";
|
|
22230
|
+
} else if (exp - cog >= 1.2) {
|
|
22231
|
+
archetype = "Specialist \u2014 deep, specific systems and architecture knowledge is the anchor, ahead of raw abstraction.";
|
|
22232
|
+
} else if (exp >= 8 && cog >= 8) {
|
|
22233
|
+
archetype = "Deep and sharp \u2014 strong systems knowledge matched by top-tier reasoning.";
|
|
22234
|
+
} else {
|
|
22235
|
+
archetype = "Balanced operator \u2014 solid systems knowledge and reasoning move in step, both clearly competent.";
|
|
22236
|
+
}
|
|
22237
|
+
const gap = exp != null && cog != null ? cog > exp + 0.8 ? "noticeably ahead of the stack-specific knowledge" : exp > cog + 0.8 ? "with the systems depth running slightly ahead" : "roughly in step with the systems depth" : "";
|
|
22238
|
+
const sigBits = [];
|
|
22239
|
+
if (sig.maxConcurrency >= 2) sigBits.push(`up to \xD7${sig.maxConcurrency} sessions at once`);
|
|
22240
|
+
if (sig.subagents > 0) sigBits.push(`${fmtN(sig.subagents)} subagent spawns`);
|
|
22241
|
+
if (sig.queueOps > 0) sigBits.push(`${fmtN(sig.queueOps)} queue-ahead ops`);
|
|
22242
|
+
if (sig.mcpTools > 0) sigBits.push(`${sig.mcpTools} MCP tool${sig.mcpTools > 1 ? "s" : ""}`);
|
|
22243
|
+
const sigSentence = sigBits.length ? sigBits.join(", ") : "mostly single-session work";
|
|
22244
|
+
const experienced = sig.maxConcurrency >= 3 && (sig.queueOps > 50 || sig.subagents > 0 || sig.mcpTools > 0);
|
|
22245
|
+
const technical = {
|
|
22246
|
+
expertise: exp,
|
|
22247
|
+
cognition: cog,
|
|
22248
|
+
frontier: fr,
|
|
22249
|
+
signals: sig,
|
|
22250
|
+
archetype,
|
|
22251
|
+
bullets: [
|
|
22252
|
+
`Systems & architecture \u2014 ${pick(EXPERTISE, exp)}.`,
|
|
22253
|
+
`Raw cognition \u2014 ${pick(GENERATIVE, cog)}${gap ? `, ${gap}` : ""}.`,
|
|
22254
|
+
`Agent fluency \u2014 ${experienced ? "clearly an experienced agent operator; " : ""}${pick(FRONTIER, fr)} (${sigSentence}).`
|
|
22255
|
+
]
|
|
22256
|
+
};
|
|
22257
|
+
return {
|
|
22258
|
+
tz,
|
|
22259
|
+
daysObserved,
|
|
22260
|
+
activeHours: round13(totalMin / 60),
|
|
22261
|
+
typicalStart,
|
|
22262
|
+
typicalEnd,
|
|
22263
|
+
earliestStart,
|
|
22264
|
+
latestEnd,
|
|
22265
|
+
hourly,
|
|
22266
|
+
peakHours,
|
|
22267
|
+
shares,
|
|
22268
|
+
isNightOwl,
|
|
22269
|
+
lunchDip,
|
|
22270
|
+
rhythmBullets,
|
|
22271
|
+
...flow,
|
|
22272
|
+
judgment,
|
|
22273
|
+
judgmentBullets,
|
|
22274
|
+
technical
|
|
22275
|
+
};
|
|
22276
|
+
}
|
|
22277
|
+
|
|
22278
|
+
// ../../lib/agents/coding/benchmarks.ts
|
|
22279
|
+
var BEST = DEFAULT_CALIBRATION.codingBenchmarks;
|
|
22280
|
+
|
|
22281
|
+
// ../../lib/agents/coding/profile.ts
|
|
22282
|
+
var ROOT2 = process.cwd();
|
|
22283
|
+
var CODING_DIR2 = process.env.POLYMATH_DATA_DIR ? path30.join(process.env.POLYMATH_DATA_DIR, "coding") : path30.join(ROOT2, ".data", "coding");
|
|
22284
|
+
var UMETA = /* @__PURE__ */ new Map();
|
|
22285
|
+
async function userMeta(sessionId, file2) {
|
|
22286
|
+
const hit = UMETA.get(sessionId);
|
|
22287
|
+
if (hit) return hit;
|
|
22288
|
+
const msgs = await extractUserMessages(file2).catch(() => []);
|
|
22289
|
+
const meta = msgs.map((m) => ({ t: m.t, words: typedWords(m.text, m.gapMs), key: m.uuid ?? `${m.t}|${m.text}` }));
|
|
22290
|
+
UMETA.set(sessionId, meta);
|
|
22291
|
+
return meta;
|
|
22292
|
+
}
|
|
22293
|
+
var GRADES2 = path30.join(CODING_DIR2, "grades");
|
|
22294
|
+
async function readJson2(p, fallback) {
|
|
22295
|
+
return fs26.readFile(p, "utf8").then((s) => JSON.parse(s)).catch(() => fallback);
|
|
22296
|
+
}
|
|
22297
|
+
function cadenceScore(fraction, multiRatio) {
|
|
22298
|
+
let s;
|
|
22299
|
+
if (fraction >= 0.85) s = 10;
|
|
22300
|
+
else if (fraction >= 0.6) s = 9;
|
|
22301
|
+
else if (fraction >= 0.45) s = 8;
|
|
22302
|
+
else if (fraction >= 0.3) s = 7;
|
|
22303
|
+
else if (fraction >= 0.2) s = 6;
|
|
22304
|
+
else if (fraction >= 0.12) s = 5;
|
|
22305
|
+
else if (fraction >= 0.06) s = 4;
|
|
22306
|
+
else s = 3;
|
|
22307
|
+
if (s >= 10 && multiRatio >= 0.3) s = 11;
|
|
22308
|
+
return s;
|
|
22309
|
+
}
|
|
22310
|
+
function distOf3(takes) {
|
|
22311
|
+
const vals = takes.map((t) => t.score ?? t.best).filter((v) => v != null);
|
|
22312
|
+
const n = vals.length;
|
|
22313
|
+
if (!n) return { n: 0, observed: 0, mean: null, median: null, hist: [] };
|
|
22314
|
+
const observed = takes.filter((t) => t.score != null).length;
|
|
22315
|
+
const mean2 = Math.round(vals.reduce((a, b) => a + b, 0) / n * 100) / 100;
|
|
22316
|
+
const sorted = [...vals].sort((a, b) => a - b);
|
|
22317
|
+
const median2 = n % 2 ? sorted[(n - 1) / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2;
|
|
22318
|
+
const h = /* @__PURE__ */ new Map();
|
|
22319
|
+
for (const v of vals) h.set(Math.round(v), (h.get(Math.round(v)) ?? 0) + 1);
|
|
22320
|
+
const hist = [...h.keys()].sort((a, b) => b - a).map((level) => ({ level, count: h.get(level) }));
|
|
22321
|
+
return { n, observed, mean: mean2, median: median2, hist };
|
|
22322
|
+
}
|
|
22323
|
+
async function compileCodingProfile() {
|
|
22324
|
+
const allRecords = await readJson2(path30.join(CODING_DIR2, "sessions.json"), []);
|
|
22325
|
+
const dupIds = new Set(allRecords.filter((s) => s.duplicateOf).map((s) => s.sessionId));
|
|
22326
|
+
const sessions = allRecords.filter((s) => !s.duplicateOf);
|
|
22327
|
+
const conc = await readJson2(path30.join(CODING_DIR2, "concurrency.json"), null);
|
|
22328
|
+
const renames = await readJson2(path30.join(CODING_DIR2, "renames.json"), []);
|
|
22329
|
+
const byId = new Map(sessions.map((s) => [s.sessionId, s]));
|
|
22330
|
+
let gradeFiles = [];
|
|
22331
|
+
try {
|
|
22332
|
+
gradeFiles = (await fs26.readdir(GRADES2)).filter((f) => f.endsWith(".json"));
|
|
22333
|
+
} catch {
|
|
22334
|
+
}
|
|
22335
|
+
const grades = [];
|
|
22336
|
+
for (const f of gradeFiles) {
|
|
22337
|
+
const g = await readJson2(path30.join(GRADES2, f), null);
|
|
22338
|
+
if (g && byId.has(g.sessionId)) grades.push(g);
|
|
22339
|
+
}
|
|
22340
|
+
const takesByCriterion = /* @__PURE__ */ new Map();
|
|
22341
|
+
for (const g of grades) {
|
|
22342
|
+
for (const c of g.criteria) {
|
|
22343
|
+
const t = {
|
|
22344
|
+
sessionId: g.sessionId,
|
|
22345
|
+
title: g.title || byId.get(g.sessionId)?.title || g.sessionId,
|
|
22346
|
+
date: (g.date || "").slice(0, 10),
|
|
22347
|
+
score: c.score,
|
|
22348
|
+
best: c.bestEstimate,
|
|
22349
|
+
instance: c.instance,
|
|
22350
|
+
why: c.why,
|
|
22351
|
+
quotes: c.quotes,
|
|
22352
|
+
confidence: c.confidence
|
|
22353
|
+
};
|
|
22354
|
+
if (c.score == null && c.bestEstimate == null) continue;
|
|
22355
|
+
(takesByCriterion.get(c.key) ?? takesByCriterion.set(c.key, []).get(c.key)).push(t);
|
|
22356
|
+
}
|
|
22357
|
+
}
|
|
22358
|
+
const criteria = CODING_CRITERIA.map((c) => {
|
|
22359
|
+
const takes = (takesByCriterion.get(c.key) ?? []).sort((a, b) => (b.score ?? b.best ?? 0) - (a.score ?? a.best ?? 0));
|
|
22360
|
+
return { key: c.key, label: c.label, graded: c.graded, definition: c.definition, dist: distOf3(takes), takes };
|
|
22361
|
+
});
|
|
22362
|
+
const tz = conc?.tz || "America/Los_Angeles";
|
|
22363
|
+
const localDate3 = (iso) => iso ? new Date(Date.parse(iso)).toLocaleDateString("en-CA", { timeZone: tz }) : "";
|
|
22364
|
+
let outcomeCadence = null;
|
|
22365
|
+
{
|
|
22366
|
+
const activeDates = new Set(sessions.map((s) => localDate3(s.start)).filter(Boolean));
|
|
22367
|
+
const meaningfulPerDay = /* @__PURE__ */ new Map();
|
|
22368
|
+
for (const g of grades) {
|
|
22369
|
+
const date = localDate3(byId.get(g.sessionId)?.start || g.date || "");
|
|
22370
|
+
if (!date) continue;
|
|
22371
|
+
const out = g.criteria.find((c) => c.key === "outcomes")?.score ?? null;
|
|
22372
|
+
const meta = g.criteria.find((c) => c.key === "metacognition")?.score ?? null;
|
|
22373
|
+
if (out != null && out >= 6 || meta != null && meta >= 8) meaningfulPerDay.set(date, (meaningfulPerDay.get(date) ?? 0) + 1);
|
|
22374
|
+
}
|
|
22375
|
+
const activeDays = activeDates.size;
|
|
22376
|
+
const meaningfulDays = meaningfulPerDay.size;
|
|
22377
|
+
const multiMeaningfulDays = [...meaningfulPerDay.values()].filter((n) => n >= 2).length;
|
|
22378
|
+
if (activeDays > 0) {
|
|
22379
|
+
const dates2 = [...activeDates].sort();
|
|
22380
|
+
const spanDays = Math.max(1, (Date.parse(dates2[dates2.length - 1]) - Date.parse(dates2[0])) / 864e5 + 1);
|
|
22381
|
+
const fraction = meaningfulDays / activeDays;
|
|
22382
|
+
outcomeCadence = {
|
|
22383
|
+
activeDays,
|
|
22384
|
+
meaningfulDays,
|
|
22385
|
+
multiMeaningfulDays,
|
|
22386
|
+
fraction: Math.round(fraction * 100) / 100,
|
|
22387
|
+
perWeek: Math.round(meaningfulDays / (spanDays / 7) * 10) / 10,
|
|
22388
|
+
score: cadenceScore(fraction, meaningfulDays ? multiMeaningfulDays / meaningfulDays : 0)
|
|
22389
|
+
};
|
|
22390
|
+
}
|
|
22391
|
+
}
|
|
22392
|
+
const perDayMeta = new Map((conc?.perDay ?? []).map((d) => [d.date, d]));
|
|
22393
|
+
const gradeById = new Map(grades.map((g) => [g.sessionId, g]));
|
|
22394
|
+
const dayMap = /* @__PURE__ */ new Map();
|
|
22395
|
+
for (const s of sessions) {
|
|
22396
|
+
const date = localDate3(s.start);
|
|
22397
|
+
if (!date) continue;
|
|
22398
|
+
let day = dayMap.get(date);
|
|
22399
|
+
if (!day) {
|
|
22400
|
+
const m = perDayMeta.get(date);
|
|
22401
|
+
day = { date, peakConcurrency: m?.peakConcurrency ?? 1, sessionsStarted: m?.sessionsStarted ?? 0, activeMin: m?.activeMin ?? 0, sessions: [] };
|
|
22402
|
+
dayMap.set(date, day);
|
|
22403
|
+
}
|
|
22404
|
+
const g = gradeById.get(s.sessionId);
|
|
22405
|
+
const findings = {};
|
|
22406
|
+
if (g) {
|
|
22407
|
+
for (const c of g.criteria) if (c.instance && (c.score != null || c.bestEstimate != null)) findings[c.key] = { score: c.score ?? c.bestEstimate, instance: c.instance, why: c.why || "", quotes: c.quotes || [] };
|
|
22408
|
+
}
|
|
22409
|
+
day.sessions.push({ sessionId: s.sessionId, title: s.title, gradable: !!g, findings });
|
|
22410
|
+
}
|
|
22411
|
+
const timeline = [...dayMap.values()].sort((a, b) => b.date.localeCompare(a.date));
|
|
22412
|
+
const levelHist = conc?.levelHistogramMin ?? {};
|
|
22413
|
+
const sumFrom = (min) => Object.entries(levelHist).filter(([k]) => Number(k) >= min).reduce((a, [, v]) => a + v, 0);
|
|
22414
|
+
const topWindows = (conc?.segments ?? []).slice().sort((a, b) => b.level - a.level || b.minutes - a.minutes).slice(0, 12).map((s) => ({ start: s.start, end: s.end, minutes: s.minutes, level: s.level, titles: [...new Set(s.sessions.map((x) => x.title))] }));
|
|
22415
|
+
const parallelism = {
|
|
22416
|
+
maxConcurrency: conc?.maxConcurrency ?? 0,
|
|
22417
|
+
levelHistogramMin: levelHist,
|
|
22418
|
+
minutesAt2plus: Math.round(sumFrom(2)),
|
|
22419
|
+
minutesAt6plus: Math.round(sumFrom(6)),
|
|
22420
|
+
topWindows,
|
|
22421
|
+
queueOps: sessions.reduce((a, s) => a + s.queueOps, 0),
|
|
22422
|
+
renames,
|
|
22423
|
+
renamedSessions: sessions.filter((s) => s.renamed).length,
|
|
22424
|
+
tz: conc?.tz ?? "",
|
|
22425
|
+
idleGapMin: conc?.idleGapMin ?? 5
|
|
22426
|
+
};
|
|
22427
|
+
const dates = sessions.map((s) => s.start).filter(Boolean).sort();
|
|
22428
|
+
const models = /* @__PURE__ */ new Map();
|
|
22429
|
+
for (const s of sessions) for (const [m, n] of Object.entries(s.models)) models.set(m, (models.get(m) ?? 0) + n);
|
|
22430
|
+
const topDays = (conc?.perDay ?? []).slice().sort((a, b) => b.activeMin - a.activeMin).slice(0, 12).map((d) => ({ date: d.date, activeMin: d.activeMin, sessionsStarted: d.sessionsStarted, peakConcurrency: d.peakConcurrency, firstStart: d.firstStart, lastEnd: d.lastEnd, spanMin: d.spanMin }));
|
|
22431
|
+
const lateNight = (conc?.perDay ?? []).filter((d) => {
|
|
22432
|
+
const h = Number(d.firstStart.slice(0, 2));
|
|
22433
|
+
const e = Number(d.lastEnd.slice(0, 2));
|
|
22434
|
+
return e >= 22 || e < 5 || h < 5;
|
|
22435
|
+
}).length;
|
|
22436
|
+
const throughput = {
|
|
22437
|
+
sessions: sessions.length,
|
|
22438
|
+
activeHours: Math.round(sessions.reduce((a, s) => a + s.activeMin, 0) / 60 * 10) / 10,
|
|
22439
|
+
dateRange: { from: (dates[0] || "").slice(0, 10), to: (dates[dates.length - 1] || "").slice(0, 10) },
|
|
22440
|
+
topDays,
|
|
22441
|
+
toolCalls: sessions.reduce((a, s) => a + s.toolCalls, 0),
|
|
22442
|
+
subagents: sessions.reduce((a, s) => a + s.subagentSpawns, 0),
|
|
22443
|
+
models: Object.fromEntries([...models.entries()].sort((a, b) => b[1] - a[1])),
|
|
22444
|
+
lateNightSessions: lateNight
|
|
22445
|
+
};
|
|
22446
|
+
const agglomeration = await readJson2(path30.join(CODING_DIR2, "agglomerate.json"), null);
|
|
22447
|
+
const dayDigest = await readJson2(path30.join(CODING_DIR2, "day-digest.json"), null);
|
|
22448
|
+
if (dayDigest?.days) dayDigest.days = dayDigest.days.map((day) => {
|
|
22449
|
+
const convs = day.conversations.filter((c) => byId.has(c.sessionId));
|
|
22450
|
+
return { ...day, conversations: convs, conversationsTouched: convs.length, totalMessages: convs.reduce((a, c) => a + c.messageCount, 0) };
|
|
22451
|
+
}).filter((day) => day.conversations.length > 0);
|
|
22452
|
+
const dayChart = await readJson2(path30.join(CODING_DIR2, "day-chart.json"), []);
|
|
22453
|
+
const intervalsBy = /* @__PURE__ */ new Map();
|
|
22454
|
+
const titleBy = /* @__PURE__ */ new Map();
|
|
22455
|
+
const fileById = new Map(sessions.map((s) => [s.sessionId, s.file]));
|
|
22456
|
+
for (const day of dayChart) for (const c of day.conversations) {
|
|
22457
|
+
const arr = intervalsBy.get(c.sessionId) ?? [];
|
|
22458
|
+
arr.push(...c.intervals);
|
|
22459
|
+
intervalsBy.set(c.sessionId, arr);
|
|
22460
|
+
titleBy.set(c.sessionId, c.title);
|
|
22461
|
+
}
|
|
22462
|
+
const listOverlapMs = (A, B) => {
|
|
22463
|
+
let ms2 = 0;
|
|
22464
|
+
for (const [a0, a1] of A) for (const [b0, b1] of B) {
|
|
22465
|
+
const lo = Math.max(a0, b0), hi = Math.min(a1, b1);
|
|
22466
|
+
if (hi > lo) ms2 += hi - lo;
|
|
22467
|
+
}
|
|
22468
|
+
return ms2;
|
|
22469
|
+
};
|
|
22470
|
+
const walkthroughs = {};
|
|
22471
|
+
const wdir = path30.join(CODING_DIR2, "walkthroughs");
|
|
22472
|
+
for (const f of await fs26.readdir(wdir).catch(() => [])) {
|
|
22473
|
+
if (!f.endsWith(".json")) continue;
|
|
22474
|
+
const w = await readWalkthrough(wdir, f.replace(".json", ""));
|
|
22475
|
+
if (!w || !byId.has(w.sessionId)) continue;
|
|
22476
|
+
const own = intervalsBy.get(w.sessionId) ?? [];
|
|
22477
|
+
const ownTitle = titleBy.get(w.sessionId) ?? w.title;
|
|
22478
|
+
const um = await userMeta(w.sessionId, fileById.get(w.sessionId) ?? "");
|
|
22479
|
+
const sessionEnd = own.length ? Math.max(...own.map((iv) => iv[1])) : w.phases[w.phases.length - 1]?.fromMs ?? 0;
|
|
22480
|
+
const phases = w.phases.map((p, i) => {
|
|
22481
|
+
const w0 = p.fromMs;
|
|
22482
|
+
const w1 = i + 1 < w.phases.length ? w.phases[i + 1].fromMs : sessionEnd + 1;
|
|
22483
|
+
const inPhase = um.filter((m) => m.t >= w0 && m.t < w1);
|
|
22484
|
+
const userPrompts = inPhase.length;
|
|
22485
|
+
const words = inPhase.reduce((s, m) => s + m.words, 0);
|
|
22486
|
+
const wordsPerMsg = userPrompts ? Math.round(words / userPrompts) : 0;
|
|
22487
|
+
const ownInWin = own.map(([a, b]) => [Math.max(a, w0), Math.min(b, w1)]).filter(([a, b]) => b > a);
|
|
22488
|
+
const durationMin = Math.round(ownInWin.reduce((s, [a, b]) => s + (b - a), 0) / 6e4);
|
|
22489
|
+
const byTitle = /* @__PURE__ */ new Map();
|
|
22490
|
+
for (const [sid, ivs] of intervalsBy) {
|
|
22491
|
+
if (sid === w.sessionId) continue;
|
|
22492
|
+
const t = titleBy.get(sid) ?? "(untitled)";
|
|
22493
|
+
if (t === ownTitle) continue;
|
|
22494
|
+
const ms2 = listOverlapMs(ownInWin, ivs);
|
|
22495
|
+
if (ms2 >= 6e4) byTitle.set(t, (byTitle.get(t) ?? 0) + ms2);
|
|
22496
|
+
}
|
|
22497
|
+
const parallel = [...byTitle.entries()].map(([title, ms2]) => ({ title, overlapMin: Math.round(ms2 / 6e4) })).sort((a, b) => b.overlapMin - a.overlapMin).slice(0, 6);
|
|
22498
|
+
return { what: p.what, durationMin, fromMs: w0, toMs: w1, userPrompts, words, wordsPerMsg, parallel };
|
|
22499
|
+
});
|
|
22500
|
+
walkthroughs[w.sessionId] = { phases, totalActiveMin: w.totalActiveMin, userTurns: w.userTurns };
|
|
22501
|
+
}
|
|
22502
|
+
const rubric2 = Object.fromEntries(
|
|
22503
|
+
CODING_CRITERIA.filter((c) => c.graded).map((c) => [c.key, { label: c.label, definition: c.definition, read: c.read, rungs: c.rungs ?? [] }])
|
|
22504
|
+
);
|
|
22505
|
+
await Promise.all(sessions.map((s) => userMeta(s.sessionId, s.file)));
|
|
22506
|
+
const wordsByDay = {};
|
|
22507
|
+
const seenMsg = /* @__PURE__ */ new Set();
|
|
22508
|
+
for (const s of sessions) for (const m of UMETA.get(s.sessionId) ?? []) {
|
|
22509
|
+
if (seenMsg.has(m.key)) continue;
|
|
22510
|
+
seenMsg.add(m.key);
|
|
22511
|
+
const d = new Date(m.t).toLocaleDateString("en-CA", { timeZone: tz });
|
|
22512
|
+
wordsByDay[d] = (wordsByDay[d] ?? 0) + m.words;
|
|
22513
|
+
}
|
|
22514
|
+
const dayThroughput = {};
|
|
22515
|
+
for (const [d, n] of Object.entries(wordsByDay)) dayThroughput[d] = throughputScore(n);
|
|
22516
|
+
const tCeiling = throughputCeiling(Object.values(wordsByDay));
|
|
22517
|
+
const criteriaMean = {};
|
|
22518
|
+
for (const c of criteria) criteriaMean[c.key] = { mean: c.dist.mean, n: c.dist.n };
|
|
22519
|
+
const flow = await readJson2(path30.join(CODING_DIR2, "flow.json"), null);
|
|
22520
|
+
const focus = await readJson2(path30.join(CODING_DIR2, "focus.json"), null);
|
|
22521
|
+
const expertiseMap = await readJson2(path30.join(CODING_DIR2, "expertise.json"), null);
|
|
22522
|
+
const frontierArsenal = await readJson2(path30.join(CODING_DIR2, "frontier.json"), null);
|
|
22523
|
+
const frontierDetail = await readJson2(path30.join(CODING_DIR2, "frontier-detail.json"), null);
|
|
22524
|
+
const coaching = await readJson2(path30.join(CODING_DIR2, "coaching.json"), null);
|
|
22525
|
+
const gap = await readJson2(path30.join(CODING_DIR2, "gap.json"), null);
|
|
22526
|
+
const aggregate = await readJson2(path30.join(CODING_DIR2, "aggregate.json"), null);
|
|
22527
|
+
const delegationRollup = await readJson2(path30.join(CODING_DIR2, "delegation-rollup.json"), null);
|
|
22528
|
+
const gapDist = await readJson2(path30.join(CODING_DIR2, "gap-dist.json"), null);
|
|
22529
|
+
const projects = await readJson2(path30.join(CODING_DIR2, "projects.json"), null);
|
|
22530
|
+
const workstyle = computeWorkstyle({
|
|
22531
|
+
dayChart,
|
|
22532
|
+
tz,
|
|
22533
|
+
idleGapMin: conc?.idleGapMin ?? 5,
|
|
22534
|
+
criteriaMean,
|
|
22535
|
+
outcomeCadence: outcomeCadence ? { perWeek: outcomeCadence.perWeek, fraction: outcomeCadence.fraction } : null,
|
|
22536
|
+
flow,
|
|
22537
|
+
signals: {
|
|
22538
|
+
maxConcurrency: parallelism.maxConcurrency,
|
|
22539
|
+
subagents: throughput.subagents,
|
|
22540
|
+
queueOps: parallelism.queueOps,
|
|
22541
|
+
mcpTools: new Set(sessions.flatMap((s) => s.mcpTools ?? [])).size,
|
|
22542
|
+
parallelHours: Math.round(parallelism.minutesAt2plus / 60 * 10) / 10
|
|
22543
|
+
}
|
|
22544
|
+
});
|
|
22545
|
+
const pctOf = (sc) => {
|
|
22546
|
+
if (sc == null) return null;
|
|
22547
|
+
const label = bandLabel(sc, DEFAULT_CALIBRATION);
|
|
22548
|
+
const m = /([\d.]+)\s*%/.exec(label);
|
|
22549
|
+
return m ? 100 - parseFloat(m[1]) : null;
|
|
22550
|
+
};
|
|
22551
|
+
const ceilOf = (key) => {
|
|
22552
|
+
const c = criteria.find((x) => x.key === key);
|
|
22553
|
+
const scores = (c?.takes ?? []).map((t) => t.score ?? t.best).filter((x) => x != null).sort((a, b) => b - a);
|
|
22554
|
+
return scores.length ? scores[Math.min(3, scores.length) - 1] : null;
|
|
22555
|
+
};
|
|
22556
|
+
const proud = (key, fallback) => {
|
|
22557
|
+
const t = (criteria.find((x) => x.key === key)?.takes ?? [])[0];
|
|
22558
|
+
if (!t?.instance) return fallback;
|
|
22559
|
+
let h = t.instance.split(/:\s|\s[—–]\s|\swith an?\s/)[0].trim().replace(/\s+/g, " ");
|
|
22560
|
+
h = h.replace(/^The user\b/, "You").replace(/\bThe user\b/g, "you").replace(/([.?!]['"’”)\]]*\s+)you\b/g, (_m, p) => p + "You");
|
|
22561
|
+
if (h.length > 150) h = h.slice(0, 148).replace(/\s+\S*$/, "") + "\u2026";
|
|
22562
|
+
return h;
|
|
22563
|
+
};
|
|
22564
|
+
const nTk = (key) => criteria.find((x) => x.key === key)?.takes.length ?? 0;
|
|
22565
|
+
const peakWords = Object.values(wordsByDay).length ? Math.max(...Object.values(wordsByDay)) : 0;
|
|
22566
|
+
const pHist = parallelism.levelHistogramMin || {};
|
|
22567
|
+
const soloMin = pHist["1"] ?? 0;
|
|
22568
|
+
const parMin = Object.entries(pHist).reduce((a, [k, v]) => Number(k) >= 2 ? a + v : a, 0);
|
|
22569
|
+
const soloPct = soloMin + parMin ? Math.round(soloMin / (soloMin + parMin) * 100) : null;
|
|
22570
|
+
const flowPctDay = aggregate?.flow?.flow?.pctOfWorkDay ?? null;
|
|
22571
|
+
const flowHrsDay = workstyle.flowHoursPerDay ? Math.round(workstyle.flowHoursPerDay * 10) / 10 : null;
|
|
22572
|
+
const focusScore = flowPctDay == null ? null : Math.max(1, Math.min(10, Math.round(2 + flowPctDay / BEST.flow.topPctOfDay * 7)));
|
|
22573
|
+
const out2 = ceilOf("outcomes");
|
|
22574
|
+
const unb = ceilOf("unblocking");
|
|
22575
|
+
const agency = out2 != null && unb != null ? Math.round((out2 + unb) / 2) : out2 ?? unb;
|
|
22576
|
+
const sRow = (label, score, gap2) => ({ label, score, percentile: pctOf(score), gap: gap2 });
|
|
22577
|
+
const tiers = DEFAULT_CALIBRATION.codingTiers;
|
|
22578
|
+
const phi = (z) => {
|
|
22579
|
+
const t = 1 / (1 + 0.2316419 * Math.abs(z));
|
|
22580
|
+
const d = 0.3989423 * Math.exp(-z * z / 2);
|
|
22581
|
+
let pr = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
|
|
22582
|
+
if (z > 0) pr = 1 - pr;
|
|
22583
|
+
return pr;
|
|
22584
|
+
};
|
|
22585
|
+
const lognPct = (x, c) => x == null || !c || x <= 0 ? null : Math.round(phi(Math.log(x / c.median) / c.sigma) * 1e3) / 10;
|
|
22586
|
+
const avgWords = aggregate?.throughput?.avgWordsPerDay ?? null;
|
|
22587
|
+
const pushPctl = lognPct(avgWords, tiers?.push);
|
|
22588
|
+
const focusPctl = lognPct(flowPctDay, tiers?.focus);
|
|
22589
|
+
const hoursAt = (min) => Object.entries(pHist).reduce((a, [k, v]) => Number(k) >= min ? a + v : a, 0) / 60;
|
|
22590
|
+
const orchPctl = hoursAt(5) >= 5 || hoursAt(3) >= 10 && parallelism.queueOps >= 1e3 ? 99.5 : hoursAt(3) >= 10 ? 99 : hoursAt(2) >= 0.1 * (soloMin + parMin) / 60 ? 97 : hoursAt(2) >= 2 ? 90 : 70;
|
|
22591
|
+
const stackUp = [
|
|
22592
|
+
sRow(
|
|
22593
|
+
"How hard you push",
|
|
22594
|
+
aggregate?.throughput?.score ?? tCeiling,
|
|
22595
|
+
`you peak at ~${peakWords.toLocaleString()} words of pure direction in a single day \u2014 the very top sustain ~${BEST.throughput.topAvgWordsPerDay.toLocaleString()} every day, week after week; the wildest single days on record push ~${BEST.throughput.topPeakWordsPerDay.toLocaleString()}`
|
|
22596
|
+
),
|
|
22597
|
+
sRow(
|
|
22598
|
+
"How well you use AI",
|
|
22599
|
+
ceilOf("delegation"),
|
|
22600
|
+
soloPct != null ? `~${soloPct}% of your hours you drive a single agent (${Math.round(soloMin / 60)}h solo vs ${Math.round(parMin / 60)}h with 2+ live) \u2014 the Claude Code lead keeps 10\u201315 going at once, so this is the single biggest speed-up still on the table` : "you brief the AI sharply, but mostly drive one agent at a time \u2014 the top run 10\u201315 in parallel, a several-fold speed-up"
|
|
22601
|
+
),
|
|
22602
|
+
sRow(
|
|
22603
|
+
"How well you focus",
|
|
22604
|
+
focusScore,
|
|
22605
|
+
flowHrsDay != null ? `only ~${flowHrsDay}h of your day is true deep flow \u2014 the best hold ~4h in one unbroken block; yours fragments across parallel sessions, so the gain is directing your attention at one thread for longer` : "your attention scatters across parallel sessions \u2014 directing it at one thread for longer is the unlock"
|
|
22606
|
+
),
|
|
22607
|
+
// The four estimates LEAD with a concrete moment from their own sessions, then an
|
|
22608
|
+
// HONEST caveat — how many graded sessions back the read. No invented weakness, no
|
|
22609
|
+
// claim about what they "didn't" do (false specifics churn a real user); it's an estimate.
|
|
22610
|
+
sRow(
|
|
22611
|
+
"Agency",
|
|
22612
|
+
agency,
|
|
22613
|
+
`${proud("outcomes", "you ship working systems and unblock yourself")} \u2014 one of ${nTk("outcomes")} sessions behind this read; a strong, consistent signal, still an estimate from your logs`
|
|
22614
|
+
),
|
|
22615
|
+
// Judgement/Taste use the SAME numbers their ability cards prove (aggregate
|
|
22616
|
+
// peak-with-consistency over near-peak instances) — a chip saying "top 1%"
|
|
22617
|
+
// above a card proving 10/Top 0.1% with ten receipts reads as a bug.
|
|
22618
|
+
sRow(
|
|
22619
|
+
"Judgement",
|
|
22620
|
+
aggregate?.ability?.abstraction?.score ?? ceilOf("generative"),
|
|
22621
|
+
`${proud("generative", "you make sharp, non-obvious calls on what actually matters")} \u2014 one of ${nTk("generative")} sessions we've graded for this; clear so far, not yet a final verdict`
|
|
22622
|
+
),
|
|
22623
|
+
sRow(
|
|
22624
|
+
"Taste",
|
|
22625
|
+
aggregate?.ability?.taste?.score ?? ceilOf("taste"),
|
|
22626
|
+
`${proud("taste", "you hold a high, opinionated quality bar")} \u2014 one of ${nTk("taste")} sessions behind this read; a real signal, still an estimate`
|
|
22627
|
+
)
|
|
22628
|
+
// Expertise: NO chip — the scored criterion is deprecated; the real judge is
|
|
22629
|
+
// the qualitative domains map (expertise-floor), and a chip must never say
|
|
22630
|
+
// "top X%" while that section shows no qualifying domains.
|
|
22631
|
+
];
|
|
22632
|
+
for (const r of stackUp) {
|
|
22633
|
+
if (r.label === "How hard you push" && pushPctl != null) {
|
|
22634
|
+
r.percentile = pushPctl;
|
|
22635
|
+
r.metric = avgWords;
|
|
22636
|
+
r.curve = "push";
|
|
22637
|
+
}
|
|
22638
|
+
if (r.label === "How well you focus" && focusPctl != null) {
|
|
22639
|
+
r.percentile = focusPctl;
|
|
22640
|
+
r.metric = flowPctDay;
|
|
22641
|
+
r.curve = "focus";
|
|
22642
|
+
}
|
|
22643
|
+
if (r.label === "How well you use AI") r.percentile = orchPctl;
|
|
22644
|
+
}
|
|
22645
|
+
return { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), gradedSessions: grades.length, agglomeration, outcomeCadence, dayDigest, dayChart, timeline, walkthroughs, rubric: rubric2, wordsByDay, dayThroughput, throughputCeiling: tCeiling, stackUp, criteria, parallelism, throughput, workstyle, flow, focus, expertiseMap, frontierArsenal, frontierDetail, coaching, gap, aggregate, delegationRollup, gapDist, projects };
|
|
22646
|
+
}
|
|
22647
|
+
|
|
22648
|
+
// ../../lib/agents/coding/publicPage.ts
|
|
22649
|
+
var readJson3 = async (f, d) => fs27.readFile(f, "utf8").then((t) => JSON.parse(t)).catch(() => d);
|
|
22650
|
+
var thirdPerson = (s) => s;
|
|
22651
|
+
function weeklyCadence(wordsByDay) {
|
|
22652
|
+
const days = Object.keys(wordsByDay).sort();
|
|
22653
|
+
const weeks = /* @__PURE__ */ new Map();
|
|
22654
|
+
for (const d of days) {
|
|
22655
|
+
const dt = /* @__PURE__ */ new Date(d + "T00:00:00Z");
|
|
22656
|
+
const jan1 = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1));
|
|
22657
|
+
const wk = `${dt.getUTCFullYear()}-W${String(Math.ceil(((+dt - +jan1) / 864e5 + jan1.getUTCDay() + 1) / 7)).padStart(2, "0")}`;
|
|
22658
|
+
weeks.set(wk, (weeks.get(wk) ?? 0) + 1);
|
|
22659
|
+
}
|
|
22660
|
+
const full = [...weeks.values()].slice(1, -1);
|
|
22661
|
+
const sevenStreak = (() => {
|
|
22662
|
+
let best = 0, cur = 0;
|
|
22663
|
+
for (const n of full) {
|
|
22664
|
+
cur = n >= 7 ? cur + 1 : 0;
|
|
22665
|
+
best = Math.max(best, cur);
|
|
22666
|
+
}
|
|
22667
|
+
return best;
|
|
22668
|
+
})();
|
|
22669
|
+
const recent = full.slice(-6);
|
|
22670
|
+
const recentAvg = recent.length ? Math.round(recent.reduce((a, b) => a + b, 0) / recent.length * 10) / 10 : 0;
|
|
22671
|
+
const first = full.slice(0, 3), firstAvg = first.length ? Math.round(first.reduce((a, b) => a + b, 0) / first.length * 10) / 10 : 0;
|
|
22672
|
+
return { recentAvg, firstAvg, sevenStreak };
|
|
22673
|
+
}
|
|
22674
|
+
function rhythmFacts(wordsByDay, workstyle, flow) {
|
|
22675
|
+
const days = Object.keys(wordsByDay).sort();
|
|
22676
|
+
const { recentAvg, firstAvg, sevenStreak } = weeklyCadence(wordsByDay);
|
|
22677
|
+
const facts = [];
|
|
22678
|
+
facts.push(recentAvg >= 6.5 ? { big: "7 days a week", rest: sevenStreak >= 2 ? `${sevenStreak} consecutive weeks without a single day off` : "in recent weeks" } : { big: `${recentAvg} days a week`, rest: "averaged over the recent weeks" });
|
|
22679
|
+
if (flow?.workDayHours) facts.push({ big: `${flow.workDayHours}-hour`, rest: "working days, typically" });
|
|
22680
|
+
if (firstAvg && recentAvg > firstAvg + 1.5) facts.push({ big: `${Math.round(firstAvg)} \u2192 ${Math.round(recentAvg)} days/week`, rest: "the ramp from the first weeks to now" });
|
|
22681
|
+
if (workstyle?.activeHours) facts.push({ big: `${Math.round(workstyle.activeHours)} hours`, rest: `hands-on across ${workstyle.daysObserved ?? days.length} observed days${flow?.flowHoursPerDay ? `, ${flow.flowHoursPerDay}h of it in deep flow daily` : ""}` });
|
|
22682
|
+
return facts.slice(0, 4);
|
|
22683
|
+
}
|
|
22684
|
+
function detailedReportCards(profile) {
|
|
22685
|
+
const cards = [];
|
|
22686
|
+
const t = profile?.throughput;
|
|
22687
|
+
if (t?.sessions) {
|
|
22688
|
+
cards.push({ section: "Throughput", stats: [
|
|
22689
|
+
{ big: t.sessions.toLocaleString(), lab: "sessions" },
|
|
22690
|
+
{ big: `${Math.round(t.activeHours ?? 0)}h`, lab: "active time" },
|
|
22691
|
+
{ big: (t.toolCalls ?? 0).toLocaleString(), lab: `tool calls (${(t.subagents ?? 0).toLocaleString()} subagents)` },
|
|
22692
|
+
{ big: String(t.lateNightSessions ?? 0), lab: "late-night days (ends 22:00\u201305:00)" }
|
|
22693
|
+
] });
|
|
22694
|
+
}
|
|
22695
|
+
const ws = profile?.workstyle, af = profile?.aggregate?.flow;
|
|
22696
|
+
const stats = [];
|
|
22697
|
+
if (af?.workWindow?.typicalStart) stats.push({ big: `${af.workWindow.typicalStart}\u2013${af.workWindow.typicalEnd}`, lab: "work window, typical day" });
|
|
22698
|
+
if (af?.flow?.pctOfWorkDay != null) stats.push({ big: `${Math.round(af.flow.pctOfWorkDay * 100)}%`, lab: `in flow, of the work day (${af.flow.flowHoursPerDay}h/day)` });
|
|
22699
|
+
if (af?.peakWindow) stats.push({ big: String(af.peakWindow), lab: "peak flow" });
|
|
22700
|
+
if (af?.parallelism?.maxConcurrent) stats.push({ big: `\xD7${af.parallelism.maxConcurrent}`, lab: "max parallel" });
|
|
22701
|
+
if (ws?.blockCount) stats.push({ big: `${Math.round((ws.flowHoursTotal ?? 0) * 60 / ws.blockCount)} min`, lab: `mean unbroken stretch (${ws.blockCount} runs, ${ws.deepBlocks} \u2265${ws.flowThresholdMin ?? 30}m)` });
|
|
22702
|
+
if (ws?.longestBlockMin) stats.push({ big: `${ws.longestBlockMin} min`, lab: "longest flow run, unbroken rapid exchange" });
|
|
22703
|
+
const focusDays = profile?.focus?.days ?? [];
|
|
22704
|
+
const rngMin = (rngs) => (rngs ?? []).reduce((a, [x, y]) => a + (y - x), 0);
|
|
22705
|
+
const totalAct = focusDays.reduce((a, d) => a + rngMin(d.ranges?.act ?? []), 0);
|
|
22706
|
+
const totalBrk = focusDays.reduce((a, d) => a + rngMin(d.ranges?.brk ?? []), 0);
|
|
22707
|
+
if (totalAct + totalBrk > 0) stats.push({ big: `${Math.round(100 * totalBrk / (totalAct + totalBrk))}%`, lab: "of the working span spent in breaks" });
|
|
22708
|
+
if (stats.length) cards.push({ section: "Flow & rhythm", stats, note: af?.insight ? String(af.insight) : void 0 });
|
|
22709
|
+
return cards;
|
|
22710
|
+
}
|
|
22711
|
+
async function buildSkeleton(codingDir, stackUp) {
|
|
22712
|
+
const agg = await readJson3(path31.join(codingDir, "aggregate.json"), {});
|
|
22713
|
+
const coaching = await readJson3(path31.join(codingDir, "coaching.json"), {});
|
|
22714
|
+
const agglom = await readJson3(path31.join(codingDir, "agglomerate.json"), {});
|
|
22715
|
+
const projectsDoc = await readJson3(path31.join(codingDir, "projects.json"), {});
|
|
22716
|
+
const focus = await readJson3(path31.join(codingDir, "focus.json"), {});
|
|
22717
|
+
const sessions = await readJson3(path31.join(codingDir, "sessions.json"), []);
|
|
22718
|
+
const profile = await compileCodingProfile().catch(() => null);
|
|
22719
|
+
const wordsByDay = agg?.throughput?.wordsByDay ?? {};
|
|
22720
|
+
const wbd = Object.keys(wordsByDay).length ? wordsByDay : Object.fromEntries((focus?.days ?? []).map((d) => [d.date, d.activeMin ?? 1]));
|
|
22721
|
+
const pct2 = (label) => {
|
|
22722
|
+
const r = (stackUp ?? []).find((x) => x.label.toLowerCase().includes(label));
|
|
22723
|
+
return r?.percentile != null ? Math.round((100 - r.percentile) * 100) / 100 : null;
|
|
22724
|
+
};
|
|
22725
|
+
const gradedOn = (key) => (agglom?.criteria ?? []).length ? 0 : 0;
|
|
22726
|
+
const interactive = sessions.filter((s) => s.klass === "interactive" && !s.duplicateOf);
|
|
22727
|
+
const flow = agg?.flow?.flow ?? {};
|
|
22728
|
+
const thr = agg?.throughput ?? {};
|
|
22729
|
+
const par = { max: 0, hours2plus: 0, queueOps: 0 };
|
|
22730
|
+
try {
|
|
22731
|
+
const conc = await readJson3(path31.join(codingDir, "concurrency.json"), {});
|
|
22732
|
+
par.max = conc?.maxConcurrency ?? 0;
|
|
22733
|
+
par.hours2plus = Math.round((conc?.minutesAt2plus ?? 0) / 60);
|
|
22734
|
+
par.queueOps = conc?.queueOps ?? 0;
|
|
22735
|
+
} catch {
|
|
22736
|
+
}
|
|
22737
|
+
const wk = weeklyCadence(wbd);
|
|
22738
|
+
const workWindow = agg?.flow?.workWindow;
|
|
22739
|
+
const axes = [
|
|
22740
|
+
{ key: "judgement", label: "Judgement", topPct: pct2("judgement"), sub: "AI-graded on an anchored rubric", claim: "", read: "", numbers: [], moments: [] },
|
|
22741
|
+
{ key: "agency", label: "Agency", topPct: pct2("agency"), sub: "full ship arcs, verified", claim: "", read: "", numbers: [], moments: [] },
|
|
22742
|
+
{ key: "taste", label: "Taste", topPct: pct2("taste"), sub: "AI-graded on an anchored rubric", claim: "", read: "", numbers: [], moments: [] },
|
|
22743
|
+
{ key: "push", label: "How hard they push", topPct: pct2("push"), sub: "counted, not graded", claim: "", read: "", numbers: [
|
|
22744
|
+
...wk.recentAvg ? [{ big: wk.recentAvg >= 6.5 ? "7 days a week" : `${wk.recentAvg} days a week`, lab: "worked, averaged over recent weeks" }] : [],
|
|
22745
|
+
...workWindow?.typicalStart ? [{ big: `${workWindow.typicalStart}\u2013${workWindow.typicalEnd}`, lab: "work window, typical day" }] : [],
|
|
22746
|
+
...thr.avgWordsPerDay ? [{ big: String(Math.round(thr.avgWordsPerDay).toLocaleString()), lab: "typed/dictated words per day, averaged (~300 for the median active user)" }] : [],
|
|
22747
|
+
...thr.substantialDays ? [{ big: `${thr.substantialDays}/${thr.totalCodingDays}`, lab: "coding days were substantial (3h+ span)" }] : []
|
|
22748
|
+
], moments: [] },
|
|
22749
|
+
{ key: "ai", label: "How they use AI", topPct: pct2("use ai"), sub: "orchestration, measured", claim: "", read: "", numbers: [
|
|
22750
|
+
...par.max ? [{ big: String(par.max), lab: "agents at once, peak" }] : [],
|
|
22751
|
+
...par.hours2plus ? [{ big: `${par.hours2plus}h`, lab: "working 2+ live agents" }] : [],
|
|
22752
|
+
...par.queueOps ? [{ big: par.queueOps.toLocaleString(), lab: "instructions queued ahead" }] : []
|
|
22753
|
+
], moments: [] },
|
|
22754
|
+
{ key: "focus", label: "Focus", topPct: pct2("focus"), sub: "strict definition", claim: "", read: "", numbers: [
|
|
22755
|
+
...flow.pctOfWorkDay != null ? [{ big: `${Math.round(flow.pctOfWorkDay * 100)}%`, lab: "of the work day in strict deep flow (gaps under 6 min)" }] : [],
|
|
22756
|
+
...flow.flowHoursPerDay ? [{ big: `${flow.flowHoursPerDay}h`, lab: "deep flow per day" }] : [],
|
|
22757
|
+
...flow.totalFlowHours ? [{ big: `${Math.round(flow.totalFlowHours)}h`, lab: "total flow across the period" }] : []
|
|
22758
|
+
], moments: [] }
|
|
22759
|
+
];
|
|
22760
|
+
const projects = (Array.isArray(projectsDoc?.projects) ? projectsDoc.projects : Array.isArray(projectsDoc) ? projectsDoc : []).filter((p) => (p.hours ?? 0) >= 1).slice(0, 6).map((p) => ({ name: String(p.project ?? p.name ?? ""), meta: `${p.days ?? "?"} days \xB7 ${Math.round(p.hours ?? 0)}h` }));
|
|
22761
|
+
const moreNumbers = detailedReportCards(profile);
|
|
22762
|
+
const page = {
|
|
22763
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22764
|
+
verdict: thirdPerson(String(coaching?.whatYouAre?.line ?? "")),
|
|
22765
|
+
provenance: { sessions: interactive.length, days: Object.keys(wbd).length, graded: 0 },
|
|
22766
|
+
rhythm: { facts: rhythmFacts(wbd, profile?.workstyle ?? {}, flow) },
|
|
22767
|
+
missionArc: "",
|
|
22768
|
+
overall: "",
|
|
22769
|
+
axes,
|
|
22770
|
+
projects,
|
|
22771
|
+
moreNumbers,
|
|
22772
|
+
howToWork: [],
|
|
22773
|
+
nutshell: String(agg?.bigPicture ?? "")
|
|
22774
|
+
};
|
|
22775
|
+
const evidence = [
|
|
22776
|
+
`PLACEMENT VERDICT (rewrite to third person, keep the punch): ${coaching?.whatYouAre?.line ?? ""}
|
|
22777
|
+
EVIDENCE BEHIND IT: ${coaching?.whatYouAre?.evidence ?? ""}`,
|
|
22778
|
+
`OVERALL READ (compress to 2-4 sentences, keep the behavioral signature): ${String(agglom?.overall ?? "").slice(0, 2200)}`,
|
|
22779
|
+
`PER-CRITERION INSIGHTS:
|
|
22780
|
+
${(agglom?.criteria ?? []).map((c) => `[${c.key}] ${c.headline} \u2014 ${String(c.insight ?? "").slice(0, 400)}`).join("\n")}`,
|
|
22781
|
+
`SIGNATURE MOVES (the moment pool \u2014 verify quotes at the source before using):
|
|
22782
|
+
${(agglom?.signatureMoves ?? []).map((m) => `- ${m.what} \xB7 ${m.session} \xB7 quote: "${m.quote}"`).join("\n")}`,
|
|
22783
|
+
`FEEL-SEEN LINES (material, not to copy verbatim): ${JSON.stringify(coaching?.feelSeen ?? {})}`
|
|
22784
|
+
].join("\n\n");
|
|
22785
|
+
return { page, evidence };
|
|
22786
|
+
}
|
|
22787
|
+
var SYSTEM4 = "You write the COPY for a public, hiring-manager-facing coding profile page. The skeleton (axes, numbers, percentiles) is already computed; you write the words: per-axis claims, reads, and dated concrete moments; the mission arc; the overall read; flow patterns; how-to-work-with bullets. REGISTER: third person throughout (the reader is a hiring manager, not the person). Claims are CLAIMS, not category names ('Kills the wrong metric before it gets built', never 'Strong judgement'). Every moment is dated, concrete, self-contained, and carries the person's VERBATIM words when a quote earns its place \u2014 you MUST verify every quote by finding it in the transcript (docs/<sessionId>.txt) before citing it; can't find it, don't use it. MOMENTS: every axis gets 3-4, and DIVERSE ones: different sessions, different projects, different weeks wherever the evidence allows \u2014 more distinct concrete verified moments is strictly better than fewer, but never pad with a weak restatement of a story already told on that axis. For graded axes pick the sharpest verified moments (the scoreboard ranks candidates). For counted axes (push/focus) turn the person's real peak DAYS into moments: what the day's number was and what they were building (day-digest.json tells you). The AGENCY axis must include one SELF-CONTAINED mission arc moment: what they took on, the insight that redirected it, what shipped \u2014 a stranger should get the whole story from that one entry. MISSION ARC (separate field): the same story at 2-3 sentences, self-contained, concrete. FLOW PATTERNS: from the data (focus.json day ranges, break clusters, day-digest), name what reliably precedes their longest flow runs (inFlow) and what reliably precedes breaks (outFlow) \u2014 one sentence each, evidence-grounded, no speculation. HOW TO WORK WITH THEM: 3-4 bullets derived from the measured behavioral signatures (delegation style, verification habits, standing-rule habit) \u2014 each a practical instruction to a future teammate, not praise. STYLE (hard rules): NO EM DASHES (\u2014) anywhere; use commas, periods, colons. Plain words, no jargon, no consultant-speak, no hedging. Never invent a number, date, quote, or outcome. Numbers you cite must come from the skeleton or the artifacts. Honest edges are stated plainly where the data shows them (the focus axis carries one when flow fragments). BE ECONOMICAL WITH TURNS: verify only the quotes you actually cite, batch several verifications into one Grep call (multiple patterns / one file read covers many quotes), never narrate what you are about to do, and reserve your last turns for emitting the final json block.";
|
|
22788
|
+
function copyPrompt(skeleton) {
|
|
22789
|
+
return `THE SKELETON (numbers + axes already computed, copy fields empty):
|
|
22790
|
+
\`\`\`json
|
|
22791
|
+
${JSON.stringify(skeleton.page, null, 1)}
|
|
22792
|
+
\`\`\`
|
|
22793
|
+
|
|
22794
|
+
THE DISTILLED EVIDENCE:
|
|
22795
|
+
${skeleton.evidence}
|
|
22796
|
+
|
|
22797
|
+
Write the copy. Output ONE fenced json block with EXACTLY these fields:
|
|
22798
|
+
\`\`\`json
|
|
22799
|
+
{
|
|
22800
|
+
"verdict": "third-person rewrite of the placement line",
|
|
22801
|
+
"overall": "2-4 sentences: how they actually work",
|
|
22802
|
+
"missionArc": "2-3 sentences, self-contained story",
|
|
22803
|
+
"inFlow": "what reliably precedes their longest flow runs",
|
|
22804
|
+
"outFlow": "what reliably precedes their breaks",
|
|
22805
|
+
"howToWork": ["3-4 bullets"],
|
|
22806
|
+
"axes": { "judgement": { "claim": "", "read": "", "moments": [ { "date": "YYYY-MM-DD or a range", "what": "", "quote": "verbatim, verified, omit if none earns it", "sessionId": "when known" } ], "edge": "optional honest footnote" }, "agency": {}, "taste": {}, "push": {}, "ai": {}, "focus": {} }
|
|
22807
|
+
}
|
|
22808
|
+
\`\`\``;
|
|
22809
|
+
}
|
|
22810
|
+
async function verifyQuotes(codingDir, axes) {
|
|
22811
|
+
const docs = path31.join(codingDir, "docs");
|
|
22812
|
+
let files = null;
|
|
22813
|
+
const norm2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
22814
|
+
const inDoc = async (file2, q) => norm2(await fs27.readFile(path31.join(docs, file2), "utf8").catch(() => "")).includes(norm2(q).slice(0, 80));
|
|
22815
|
+
let dropped = 0;
|
|
22816
|
+
for (const ax of Object.values(axes ?? {})) {
|
|
22817
|
+
for (const m of ax?.moments ?? []) {
|
|
22818
|
+
if (!m.quote) continue;
|
|
22819
|
+
let ok = false;
|
|
22820
|
+
if (m.sessionId) ok = await inDoc(`${m.sessionId}.txt`, m.quote);
|
|
22821
|
+
if (!ok) {
|
|
22822
|
+
files = files ?? (await fs27.readdir(docs).catch(() => [])).filter((f) => f.endsWith(".txt"));
|
|
22823
|
+
for (const f of files) {
|
|
22824
|
+
if (await inDoc(f, m.quote)) {
|
|
22825
|
+
ok = true;
|
|
22826
|
+
break;
|
|
22827
|
+
}
|
|
22828
|
+
}
|
|
22829
|
+
}
|
|
22830
|
+
if (!ok) {
|
|
22831
|
+
console.log(`[public-page] DROPPED unverifiable quote: "${m.quote.slice(0, 60)}\u2026"`);
|
|
22832
|
+
delete m.quote;
|
|
22833
|
+
dropped++;
|
|
22834
|
+
}
|
|
22835
|
+
}
|
|
22836
|
+
}
|
|
22837
|
+
return { dropped };
|
|
22838
|
+
}
|
|
22839
|
+
async function generatePublicPage(opts) {
|
|
22840
|
+
const outFile = path31.join(opts.codingDir, "coding-public.json");
|
|
22841
|
+
const skeleton = await buildSkeleton(opts.codingDir, opts.stackUp);
|
|
22842
|
+
if (!skeleton.page.verdict && !skeleton.evidence.includes("[")) {
|
|
22843
|
+
console.log("[public-page] no upstream artifacts yet \u2014 run the analysis first");
|
|
22844
|
+
return null;
|
|
22845
|
+
}
|
|
22846
|
+
const system = SYSTEM4 + sourceLookupNote();
|
|
22847
|
+
const prompt = copyPrompt(skeleton);
|
|
22848
|
+
const sha = promptSha([system, prompt, opts.model ?? "sonnet"]);
|
|
22849
|
+
if (!opts.refresh) {
|
|
22850
|
+
const prev = await reusableOutput(outFile, sha, (o) => !!o.verdict && (o.axes ?? []).length === 6);
|
|
22851
|
+
if (prev) {
|
|
22852
|
+
console.log(`[public-page] inputs unchanged \u2014 reusing page from ${prev.generatedAt} (--refresh to regenerate)`);
|
|
22853
|
+
return prev;
|
|
22854
|
+
}
|
|
22855
|
+
}
|
|
22856
|
+
await ensureCodingDocs(opts.codingDir, opts.sessions);
|
|
22857
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
22858
|
+
const run3 = await invokeAgent2({
|
|
22859
|
+
prompt: attempt === 1 ? prompt : prompt + "\n\nREMINDER: your previous attempt contained an em dash (\u2014). That is a hard style violation. Rewrite with ZERO em dashes.",
|
|
22860
|
+
systemPrompt: system,
|
|
22861
|
+
// 40 turns: quote verification is turn-hungry (one grep per citation) and
|
|
22862
|
+
// the run DIED at 14 twice on 2026-07-14, always mid-verification. Batch
|
|
22863
|
+
// instruction in SYSTEM keeps it economical; the engine's corpus agents
|
|
22864
|
+
// get 80 for the same reason.
|
|
22865
|
+
addDir: opts.codingDir,
|
|
22866
|
+
allowedTools: ["Read", "Grep", "Glob"],
|
|
22867
|
+
maxTurns: 40,
|
|
22868
|
+
timeoutMs: 6e5,
|
|
22869
|
+
model: opts.model ?? "sonnet"
|
|
22870
|
+
});
|
|
22871
|
+
const j = run3.json;
|
|
22872
|
+
if (!j?.verdict || !j?.axes) {
|
|
22873
|
+
console.log(`[public-page] attempt ${attempt}: no usable copy`);
|
|
22874
|
+
continue;
|
|
22875
|
+
}
|
|
22876
|
+
if (JSON.stringify(j).includes("\u2014")) {
|
|
22877
|
+
console.log(`[public-page] attempt ${attempt}: em dash in output, retrying once`);
|
|
22878
|
+
continue;
|
|
22879
|
+
}
|
|
22880
|
+
await verifyQuotes(opts.codingDir, j.axes);
|
|
22881
|
+
const page = skeleton.page;
|
|
22882
|
+
page.verdict = String(j.verdict).slice(0, 500);
|
|
22883
|
+
page.overall = String(j.overall ?? "").slice(0, 900);
|
|
22884
|
+
page.missionArc = String(j.missionArc ?? "").slice(0, 700);
|
|
22885
|
+
page.rhythm.inFlow = String(j.inFlow ?? "").slice(0, 300) || void 0;
|
|
22886
|
+
page.rhythm.outFlow = String(j.outFlow ?? "").slice(0, 300) || void 0;
|
|
22887
|
+
page.howToWork = (Array.isArray(j.howToWork) ? j.howToWork : []).map((x) => String(x).slice(0, 250)).slice(0, 5);
|
|
22888
|
+
for (const ax of page.axes) {
|
|
22889
|
+
const c = j.axes[ax.key];
|
|
22890
|
+
if (!c) continue;
|
|
22891
|
+
ax.claim = String(c.claim ?? "").slice(0, 200);
|
|
22892
|
+
ax.read = String(c.read ?? "").slice(0, 500);
|
|
22893
|
+
ax.edge = c.edge ? String(c.edge).slice(0, 350) : void 0;
|
|
22894
|
+
ax.moments = (Array.isArray(c.moments) ? c.moments : []).slice(0, 4).map((m) => ({
|
|
22895
|
+
date: String(m.date ?? "").slice(0, 30),
|
|
22896
|
+
what: String(m.what ?? "").slice(0, 500),
|
|
22897
|
+
...m.quote ? { quote: String(m.quote).slice(0, 300) } : {},
|
|
22898
|
+
...m.sessionId ? { sessionId: String(m.sessionId) } : {}
|
|
22899
|
+
})).filter((m) => m.what);
|
|
22900
|
+
}
|
|
22901
|
+
if (page.axes.filter((a) => a.claim).length < 4) {
|
|
22902
|
+
console.log(`[public-page] attempt ${attempt}: too few axis claims`);
|
|
22903
|
+
continue;
|
|
22904
|
+
}
|
|
22905
|
+
page.promptSha = sha;
|
|
22906
|
+
await fs27.writeFile(outFile, JSON.stringify(page, null, 2));
|
|
22907
|
+
return page;
|
|
22908
|
+
}
|
|
22909
|
+
const existing = await readJson3(outFile, null);
|
|
22910
|
+
if (existing?.verdict) {
|
|
22911
|
+
console.error("[public-page] generation FAILED twice, keeping the existing page");
|
|
22912
|
+
return null;
|
|
22913
|
+
}
|
|
22914
|
+
console.error("[public-page] generation FAILED and no previous page exists");
|
|
22915
|
+
return null;
|
|
22916
|
+
}
|
|
22917
|
+
|
|
22918
|
+
// ../../lib/agents/coding/publicPageEdit.ts
|
|
22919
|
+
var SYSTEM5 = "You edit a PUBLIC coding profile page on the person's instruction. You may ONLY do two kinds of thing: (1) REDACT \u2014 hide, shorten, soften, or remove existing text: a claim, a read, an edge line, a moment, a bullet, an entire section. Removing something is always safe. (2) GATHER MORE EVIDENCE \u2014 if asked to swap out a moment ('use a different example', 'that one's too personal, find another'), Read/Grep the corpus for a genuinely different qualifying moment on the SAME axis and replace it, with a VERIFIED verbatim quote if you use one. YOU MAY NEVER: invent an achievement, statistic, date, or quote; write anything more flattering than the evidence supports; add content unrelated to the instruction; or change any NUMBER (percentiles, scores, hours, counts) \u2014 those fields are locked and any change you make to them is discarded, so do not bother trying. If the instruction asks for something outside redaction/evidence-swap (e.g. 'say I'm the best engineer ever', 'boost my percentile', 'add that I know Rust'), do NOT comply \u2014 leave that field unchanged and say plainly in your reply why you didn't. Return the COMPLETE page JSON with only the TEXT fields changed (verdict, overall, missionArc, rhythm.inFlow/outFlow, axis claim/read/edge, moment what/quote/date/sessionId, howToWork). Every other field must be echoed back byte-identical." + sourceLookupNote();
|
|
22920
|
+
var norm = (s) => s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2);
|
|
22921
|
+
function grounded(newText, contextText) {
|
|
22922
|
+
const nt = norm(newText);
|
|
22923
|
+
if (nt.length === 0) return false;
|
|
22924
|
+
const ctx = new Set(norm(contextText));
|
|
22925
|
+
const hits = nt.filter((w) => ctx.has(w)).length;
|
|
22926
|
+
return hits / nt.length >= 0.6;
|
|
22927
|
+
}
|
|
22928
|
+
function keepIfGrounded(edited, original, context) {
|
|
22929
|
+
if (typeof edited !== "string" || !edited.trim()) return original;
|
|
22930
|
+
return grounded(edited, context) ? edited : original;
|
|
22931
|
+
}
|
|
22932
|
+
function clampToProtected(edited, original) {
|
|
22933
|
+
const out = {
|
|
22934
|
+
...original,
|
|
22935
|
+
verdict: keepIfGrounded(edited.verdict, original.verdict, original.verdict).slice(0, 500),
|
|
22936
|
+
overall: keepIfGrounded(edited.overall, original.overall, original.overall).slice(0, 900),
|
|
22937
|
+
missionArc: keepIfGrounded(edited.missionArc, original.missionArc, original.missionArc).slice(0, 700),
|
|
22938
|
+
// each bullet is grounded against the WHOLE original list — reordering and
|
|
22939
|
+
// rewording within the same set of ideas is fine; a wholly new bullet isn't
|
|
22940
|
+
howToWork: (Array.isArray(edited.howToWork) ? edited.howToWork : original.howToWork).map((x) => keepIfGrounded(x, "", original.howToWork.join(" "))).filter((x) => !!x).map((x) => x.slice(0, 250)).slice(0, 5) || original.howToWork,
|
|
22941
|
+
rhythm: {
|
|
22942
|
+
facts: original.rhythm.facts,
|
|
22943
|
+
// counters: NEVER editable
|
|
22944
|
+
inFlow: keepIfGrounded(edited.rhythm?.inFlow, original.rhythm.inFlow ?? "", original.rhythm.inFlow ?? "").slice(0, 300) || void 0,
|
|
22945
|
+
outFlow: keepIfGrounded(edited.rhythm?.outFlow, original.rhythm.outFlow ?? "", original.rhythm.outFlow ?? "").slice(0, 300) || void 0
|
|
22946
|
+
},
|
|
22947
|
+
// PROTECTED, always restored: provenance, projects, moreNumbers (raw stats)
|
|
22948
|
+
provenance: original.provenance,
|
|
22949
|
+
projects: original.projects,
|
|
22950
|
+
moreNumbers: original.moreNumbers,
|
|
22951
|
+
nutshell: original.nutshell
|
|
22952
|
+
// the local report owns this; not editable here
|
|
22953
|
+
};
|
|
22954
|
+
const editedAxes = new Map((edited.axes ?? []).map((a) => [a.key, a]));
|
|
22955
|
+
out.axes = original.axes.map((orig) => {
|
|
22956
|
+
const e = editedAxes.get(orig.key);
|
|
22957
|
+
const axisCtx = `${orig.claim} ${orig.read} ${orig.moments.map((m) => m.what).join(" ")}`;
|
|
22958
|
+
const base2 = {
|
|
22959
|
+
...orig,
|
|
22960
|
+
// topPct, sub, numbers: PROTECTED, always from orig
|
|
22961
|
+
claim: keepIfGrounded(e?.claim, orig.claim, axisCtx).slice(0, 200),
|
|
22962
|
+
read: keepIfGrounded(e?.read, orig.read, axisCtx).slice(0, 500),
|
|
22963
|
+
edge: e?.edge === void 0 ? orig.edge : e.edge ? keepIfGrounded(e.edge, orig.edge ?? "", `${orig.edge ?? ""} ${axisCtx}`).slice(0, 350) || void 0 : void 0
|
|
22964
|
+
};
|
|
22965
|
+
const origById = new Map(orig.moments.filter((m) => m.sessionId).map((m) => [m.sessionId, m]));
|
|
22966
|
+
base2.moments = (Array.isArray(e?.moments) ? e.moments : orig.moments).slice(0, 3).map((m) => {
|
|
22967
|
+
const matched = m?.sessionId ? origById.get(String(m.sessionId)) : void 0;
|
|
22968
|
+
if (matched) return matched;
|
|
22969
|
+
return {
|
|
22970
|
+
date: String(m?.date ?? "").slice(0, 30),
|
|
22971
|
+
what: String(m?.what ?? "").slice(0, 500),
|
|
22972
|
+
...m?.quote ? { quote: String(m.quote).slice(0, 300) } : {},
|
|
22973
|
+
...m?.sessionId ? { sessionId: String(m.sessionId) } : {}
|
|
22974
|
+
};
|
|
22975
|
+
}).filter((m) => m.what && (origById.has(m.sessionId ?? "") || m.quote));
|
|
22976
|
+
return base2;
|
|
22977
|
+
});
|
|
22978
|
+
return out;
|
|
22979
|
+
}
|
|
22980
|
+
async function editPublicPage(opts) {
|
|
22981
|
+
const last = opts.messages.filter((m) => m.role === "user").pop();
|
|
22982
|
+
if (!last?.content?.trim()) return { status: 400, json: { error: "an instruction is required" } };
|
|
22983
|
+
const convo = opts.messages.slice(-6).map((m) => `${m.role === "user" ? "USER" : "EDITOR"}: ${m.content}`).join("\n");
|
|
22984
|
+
const prompt = `CURRENT PAGE:
|
|
22985
|
+
\`\`\`json
|
|
22986
|
+
${JSON.stringify(opts.page, null, 1)}
|
|
22987
|
+
\`\`\`
|
|
22988
|
+
|
|
22989
|
+
CONVERSATION:
|
|
22990
|
+
${convo}
|
|
22991
|
+
|
|
22992
|
+
Apply the user's LAST instruction within your allowed actions (redact, or gather-more-evidence). Output ONE fenced json block: { "page": <complete page, text fields updated>, "reply": "one short plain sentence on what you did or why you declined" }`;
|
|
22993
|
+
try {
|
|
22994
|
+
const run3 = await invokeAgent2({
|
|
22995
|
+
prompt,
|
|
22996
|
+
systemPrompt: SYSTEM5,
|
|
22997
|
+
addDir: opts.codingDir,
|
|
22998
|
+
allowedTools: ["Read", "Grep", "Glob"],
|
|
22999
|
+
maxTurns: 10,
|
|
23000
|
+
timeoutMs: 24e4,
|
|
23001
|
+
model: opts.model ?? "sonnet"
|
|
23002
|
+
});
|
|
23003
|
+
const j = run3.json;
|
|
23004
|
+
if (!j?.page) return { status: 502, json: { error: "the edit didn't produce a valid page \u2014 try rephrasing" } };
|
|
23005
|
+
const clamped = clampToProtected(j.page, opts.page);
|
|
23006
|
+
const originalIds = new Set(opts.page.axes.flatMap((a) => a.moments.map((m) => m.sessionId).filter(Boolean)));
|
|
23007
|
+
await verifyQuotes(opts.codingDir, clamped.axes);
|
|
23008
|
+
for (const ax of clamped.axes) ax.moments = ax.moments.filter((m) => m.sessionId && originalIds.has(m.sessionId) || m.quote);
|
|
23009
|
+
return { status: 200, json: { page: clamped, reply: String(j.reply ?? "Done.").slice(0, 300) } };
|
|
23010
|
+
} catch (e) {
|
|
23011
|
+
return { status: 502, json: { error: e.message.slice(0, 200) } };
|
|
23012
|
+
}
|
|
23013
|
+
}
|
|
23014
|
+
|
|
23015
|
+
// src/publicCodingPageApi.ts
|
|
23016
|
+
var readJson4 = async (f, d) => fs28.readFile(f, "utf8").then((t) => JSON.parse(t)).catch(() => d);
|
|
23017
|
+
async function localChatReport() {
|
|
23018
|
+
const rep = await loadPublicReport().catch(() => null);
|
|
23019
|
+
return rep && hasContent(rep) ? rep : null;
|
|
23020
|
+
}
|
|
23021
|
+
async function handlePublicCodingPageGet(dataDir) {
|
|
23022
|
+
const page = await readJson4(path32.join(dataDir, "coding", "coding-public.json"), null);
|
|
23023
|
+
const chatReport = await localChatReport();
|
|
23024
|
+
return { status: 200, json: { page, chatReport } };
|
|
23025
|
+
}
|
|
23026
|
+
async function handlePublicCodingPageGenerate(dataDir, liveProfile) {
|
|
23027
|
+
const codingDir = path32.join(dataDir, "coding");
|
|
23028
|
+
const stackUp = liveProfile?.stackUp ?? null;
|
|
23029
|
+
const sessions = await readJson4(path32.join(codingDir, "sessions.json"), []);
|
|
23030
|
+
try {
|
|
23031
|
+
const page = await generatePublicPage({ codingDir, stackUp, sessions });
|
|
23032
|
+
if (!page) return { status: 502, json: { error: "generation failed \u2014 see the server log, or run the analysis first" } };
|
|
23033
|
+
return { status: 200, json: { page } };
|
|
23034
|
+
} catch (e) {
|
|
23035
|
+
return { status: 502, json: { error: e.message.slice(0, 300) } };
|
|
23036
|
+
}
|
|
23037
|
+
}
|
|
23038
|
+
async function handlePublicCodingPageEdit(dataDir, body) {
|
|
23039
|
+
const page = body.page;
|
|
23040
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
23041
|
+
if (!page || !Array.isArray(page.axes)) return { status: 400, json: { error: "page + messages are required" } };
|
|
23042
|
+
const out = await editPublicPage({ page, messages, codingDir: path32.join(dataDir, "coding") });
|
|
23043
|
+
return out;
|
|
23044
|
+
}
|
|
23045
|
+
async function handlePublicCodingPageShare(dataDir, body) {
|
|
23046
|
+
const cfg = centralConfig();
|
|
23047
|
+
if (!cfg.configured) return { status: 503, json: { error: "central services are disabled in this build" } };
|
|
23048
|
+
const auth = await getAccessToken(dataDir);
|
|
23049
|
+
if (!auth) return { status: 401, json: { error: "sign in with Google (top right) to share with Polymath" } };
|
|
23050
|
+
const page = body.page;
|
|
23051
|
+
if (!page?.verdict || !Array.isArray(page.axes) || page.axes.length < 4) {
|
|
23052
|
+
return { status: 409, json: { error: "nothing to share yet \u2014 generate your page first" } };
|
|
23053
|
+
}
|
|
23054
|
+
const chatReport = body.includeChat === false ? null : await localChatReport();
|
|
23055
|
+
try {
|
|
23056
|
+
const payload = JSON.stringify({ format: "coding-pr1", page, ...chatReport ? { chatReport } : {} });
|
|
23057
|
+
const up = await fetch(`${cfg.supabaseUrl}/rest/v1/rpc/submit_report`, {
|
|
23058
|
+
method: "POST",
|
|
23059
|
+
headers: { apikey: cfg.supabaseAnonKey, authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
|
|
23060
|
+
body: JSON.stringify({ p_session_id: null, p_content: payload })
|
|
23061
|
+
});
|
|
23062
|
+
if (!up.ok) return { status: 502, json: { error: `share failed (HTTP ${up.status})` } };
|
|
23063
|
+
const verification = String(await up.json().catch(() => "unverified"));
|
|
23064
|
+
return { status: 200, json: { ok: true, includedChat: !!chatReport, verification } };
|
|
23065
|
+
} catch (e) {
|
|
23066
|
+
return { status: 502, json: { error: e.message.slice(0, 200) } };
|
|
23067
|
+
}
|
|
23068
|
+
}
|
|
23069
|
+
|
|
21684
23070
|
// src/server.ts
|
|
21685
|
-
var __dirname =
|
|
21686
|
-
var UI_DIR =
|
|
23071
|
+
var __dirname = path33.dirname(fileURLToPath2(import.meta.url));
|
|
23072
|
+
var UI_DIR = path33.join(__dirname, "web");
|
|
21687
23073
|
var DEFAULT_SHARE_URL = "https://polymathsociety.us/api/coding-share";
|
|
21688
23074
|
var MIME = {
|
|
21689
23075
|
".html": "text/html; charset=utf-8",
|
|
@@ -21714,19 +23100,19 @@ async function readBody(req, maxBytes = 8e6) {
|
|
|
21714
23100
|
async function serveStatic(res, urlPath) {
|
|
21715
23101
|
const clean = urlPath.split("?")[0];
|
|
21716
23102
|
const rel = clean === "/" ? "index.html" : clean.replace(/^\/+/, "");
|
|
21717
|
-
const full =
|
|
23103
|
+
const full = path33.normalize(path33.join(UI_DIR, rel));
|
|
21718
23104
|
if (!full.startsWith(UI_DIR)) {
|
|
21719
23105
|
res.writeHead(403).end("forbidden");
|
|
21720
23106
|
return;
|
|
21721
23107
|
}
|
|
21722
23108
|
try {
|
|
21723
|
-
const data = await
|
|
21724
|
-
res.writeHead(200, { "content-type": MIME[
|
|
23109
|
+
const data = await fs29.readFile(full);
|
|
23110
|
+
res.writeHead(200, { "content-type": MIME[path33.extname(full)] ?? "application/octet-stream" });
|
|
21725
23111
|
res.end(data);
|
|
21726
23112
|
} catch {
|
|
21727
|
-
if (!
|
|
23113
|
+
if (!path33.extname(full)) {
|
|
21728
23114
|
try {
|
|
21729
|
-
const idx = await
|
|
23115
|
+
const idx = await fs29.readFile(path33.join(UI_DIR, "index.html"));
|
|
21730
23116
|
res.writeHead(200, { "content-type": MIME[".html"] }).end(idx);
|
|
21731
23117
|
return;
|
|
21732
23118
|
} catch {
|
|
@@ -21751,11 +23137,17 @@ async function handleShare(res, body, opts, dataDir) {
|
|
|
21751
23137
|
res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ ok: false, error: "bad JSON" }));
|
|
21752
23138
|
return;
|
|
21753
23139
|
}
|
|
21754
|
-
const all = opts.profile?.sections ?? {};
|
|
21755
|
-
const wanted = parsed.all ? Object.keys(all) : Array.isArray(parsed.sections) ? parsed.sections : [];
|
|
23140
|
+
const all = opts.shareSections ?? opts.profile?.sections ?? {};
|
|
21756
23141
|
const sections = {};
|
|
21757
|
-
|
|
21758
|
-
|
|
23142
|
+
if (parsed.sectionsData && typeof parsed.sectionsData === "object" && !Array.isArray(parsed.sectionsData)) {
|
|
23143
|
+
for (const key of Object.keys(parsed.sectionsData)) {
|
|
23144
|
+
if (key in all && parsed.sectionsData[key] != null) sections[key] = parsed.sectionsData[key];
|
|
23145
|
+
}
|
|
23146
|
+
} else {
|
|
23147
|
+
const wanted = parsed.all ? Object.keys(all) : Array.isArray(parsed.sections) ? parsed.sections : [];
|
|
23148
|
+
for (const key of wanted) {
|
|
23149
|
+
if (key in all && all[key] != null) sections[key] = all[key];
|
|
23150
|
+
}
|
|
21759
23151
|
}
|
|
21760
23152
|
if (Object.keys(sections).length === 0) {
|
|
21761
23153
|
res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ ok: false, error: "no shareable sections selected" }));
|
|
@@ -21791,8 +23183,11 @@ async function handleShare(res, body, opts, dataDir) {
|
|
|
21791
23183
|
}
|
|
21792
23184
|
}
|
|
21793
23185
|
function startServer(opts) {
|
|
23186
|
+
let liveProfile = opts.profile;
|
|
23187
|
+
let liveProgress = {};
|
|
23188
|
+
let profileRev = 0;
|
|
21794
23189
|
const host = opts.host ?? "127.0.0.1";
|
|
21795
|
-
const dataDir = opts.dataDir ??
|
|
23190
|
+
const dataDir = opts.dataDir ?? resolveDataRoot();
|
|
21796
23191
|
let serverUrl = "";
|
|
21797
23192
|
const json = (res, status, body) => res.writeHead(status, { "content-type": MIME[".json"] }).end(JSON.stringify(body));
|
|
21798
23193
|
const server = http.createServer(async (req, res) => {
|
|
@@ -21800,7 +23195,17 @@ function startServer(opts) {
|
|
|
21800
23195
|
const url = req.url ?? "/";
|
|
21801
23196
|
const route = url.split("?")[0];
|
|
21802
23197
|
if (req.method === "GET" && (url === "/api/coding" || url === "/profile.json" || url.startsWith("/profile.json?"))) {
|
|
21803
|
-
res.writeHead(200, { "content-type": MIME[".json"] }).end(JSON.stringify(
|
|
23198
|
+
res.writeHead(200, { "content-type": MIME[".json"] }).end(JSON.stringify(liveProfile));
|
|
23199
|
+
return;
|
|
23200
|
+
}
|
|
23201
|
+
if (req.method === "GET" && url === "/api/progress") {
|
|
23202
|
+
res.writeHead(200, { "content-type": MIME[".json"] }).end(JSON.stringify({ ...liveProgress, profileRev }));
|
|
23203
|
+
return;
|
|
23204
|
+
}
|
|
23205
|
+
if (req.method === "GET" && route === "/api/exports-manifest") {
|
|
23206
|
+
const m = await readManifest(dataDir).catch(() => null);
|
|
23207
|
+
const kinds = new Set((m?.exports?.included ?? []).map((e) => e.kind));
|
|
23208
|
+
json(res, 200, { connectedKinds: [...kinds], links: EXPORT_LINKS });
|
|
21804
23209
|
return;
|
|
21805
23210
|
}
|
|
21806
23211
|
if (req.method === "GET" && route === "/meta.json") {
|
|
@@ -21818,6 +23223,10 @@ function startServer(opts) {
|
|
|
21818
23223
|
submit: central.configured,
|
|
21819
23224
|
auth: central.configured,
|
|
21820
23225
|
share: true,
|
|
23226
|
+
shareEdit: true,
|
|
23227
|
+
// the review modal's chat editor (/api/share/edit)
|
|
23228
|
+
publicCodingPage: true,
|
|
23229
|
+
// the hiring-manager page (/api/public-coding-page*)
|
|
21821
23230
|
person: true,
|
|
21822
23231
|
// the chat-analysis tab's /api/person* routes
|
|
21823
23232
|
publicReport: true
|
|
@@ -21934,13 +23343,14 @@ function startServer(opts) {
|
|
|
21934
23343
|
}
|
|
21935
23344
|
if (route === "/api/profile/visibility" && (req.method === "GET" || req.method === "POST")) {
|
|
21936
23345
|
const body = req.method === "POST" ? JSON.parse(await readBody(req) || "{}") : void 0;
|
|
21937
|
-
const
|
|
23346
|
+
const pool = opts.shareSections ?? opts.profile?.sections ?? {};
|
|
23347
|
+
const out = await handleVisibility(dataDir, req.method, body, pool);
|
|
21938
23348
|
json(res, out.status, out.json);
|
|
21939
23349
|
return;
|
|
21940
23350
|
}
|
|
21941
23351
|
if (route === "/api/coding/chat" && req.method === "POST") {
|
|
21942
23352
|
const body = JSON.parse(await readBody(req) || "{}");
|
|
21943
|
-
await handleCodingChat(
|
|
23353
|
+
await handleCodingChat(liveProfile, dataDir, body, res);
|
|
21944
23354
|
return;
|
|
21945
23355
|
}
|
|
21946
23356
|
if (req.method === "POST" && url === "/share") {
|
|
@@ -21948,6 +23358,35 @@ function startServer(opts) {
|
|
|
21948
23358
|
await handleShare(res, body, opts, dataDir);
|
|
21949
23359
|
return;
|
|
21950
23360
|
}
|
|
23361
|
+
if (req.method === "POST" && route === "/api/share/edit") {
|
|
23362
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
23363
|
+
const pool = opts.shareSections ?? opts.profile?.sections ?? {};
|
|
23364
|
+
const out = await handleShareEdit(pool, body);
|
|
23365
|
+
json(res, out.status, out.json);
|
|
23366
|
+
return;
|
|
23367
|
+
}
|
|
23368
|
+
if (req.method === "GET" && route === "/api/public-coding-page") {
|
|
23369
|
+
const out = await handlePublicCodingPageGet(dataDir);
|
|
23370
|
+
json(res, out.status, out.json);
|
|
23371
|
+
return;
|
|
23372
|
+
}
|
|
23373
|
+
if (req.method === "POST" && route === "/api/public-coding-page/generate") {
|
|
23374
|
+
const out = await handlePublicCodingPageGenerate(dataDir, liveProfile);
|
|
23375
|
+
json(res, out.status, out.json);
|
|
23376
|
+
return;
|
|
23377
|
+
}
|
|
23378
|
+
if (req.method === "POST" && route === "/api/public-coding-page/edit") {
|
|
23379
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
23380
|
+
const out = await handlePublicCodingPageEdit(dataDir, body);
|
|
23381
|
+
json(res, out.status, out.json);
|
|
23382
|
+
return;
|
|
23383
|
+
}
|
|
23384
|
+
if (req.method === "POST" && route === "/api/public-coding-page/share") {
|
|
23385
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
23386
|
+
const out = await handlePublicCodingPageShare(dataDir, body);
|
|
23387
|
+
json(res, out.status, out.json);
|
|
23388
|
+
return;
|
|
23389
|
+
}
|
|
21951
23390
|
if (route.startsWith("/api/")) {
|
|
21952
23391
|
json(res, 404, { error: `this viewer doesn't serve ${route}` });
|
|
21953
23392
|
return;
|
|
@@ -21976,7 +23415,17 @@ function startServer(opts) {
|
|
|
21976
23415
|
resolve2({
|
|
21977
23416
|
url: serverUrl,
|
|
21978
23417
|
port,
|
|
21979
|
-
close: () => new Promise((r) => server.close(() => r()))
|
|
23418
|
+
close: () => new Promise((r) => server.close(() => r())),
|
|
23419
|
+
setProfile: (p) => {
|
|
23420
|
+
liveProfile = p;
|
|
23421
|
+
profileRev++;
|
|
23422
|
+
},
|
|
23423
|
+
setShareSections: (s) => {
|
|
23424
|
+
opts.shareSections = s;
|
|
23425
|
+
},
|
|
23426
|
+
setProgress: (p) => {
|
|
23427
|
+
liveProgress = p;
|
|
23428
|
+
}
|
|
21980
23429
|
});
|
|
21981
23430
|
});
|
|
21982
23431
|
});
|