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.
@@ -134,9 +134,8 @@ var require_secure_json_parse = __commonJS({
134
134
  });
135
135
 
136
136
  // ../../scripts/coding-gap.mts
137
- import { promises as fs9 } from "fs";
138
- import os2 from "os";
139
- import path12 from "path";
137
+ import { promises as fs11 } from "fs";
138
+ import path13 from "path";
140
139
 
141
140
  // ../../lib/agents/shared/agent.ts
142
141
  import { readFileSync } from "fs";
@@ -2151,8 +2150,8 @@ function getErrorMap() {
2151
2150
 
2152
2151
  // ../../node_modules/zod/v3/helpers/parseUtil.js
2153
2152
  var makeIssue = (params) => {
2154
- const { data, path: path13, errorMaps, issueData } = params;
2155
- const fullPath = [...path13, ...issueData.path || []];
2153
+ const { data, path: path14, errorMaps, issueData } = params;
2154
+ const fullPath = [...path14, ...issueData.path || []];
2156
2155
  const fullIssue = {
2157
2156
  ...issueData,
2158
2157
  path: fullPath
@@ -2268,11 +2267,11 @@ var errorUtil;
2268
2267
 
2269
2268
  // ../../node_modules/zod/v3/types.js
2270
2269
  var ParseInputLazyPath = class {
2271
- constructor(parent, value, path13, key) {
2270
+ constructor(parent, value, path14, key) {
2272
2271
  this._cachedPath = [];
2273
2272
  this.parent = parent;
2274
2273
  this.data = value;
2275
- this._path = path13;
2274
+ this._path = path14;
2276
2275
  this._key = key;
2277
2276
  }
2278
2277
  get path() {
@@ -15059,39 +15058,39 @@ function createOpenAI(options = {}) {
15059
15058
  });
15060
15059
  const createChatModel = (modelId, settings = {}) => new OpenAIChatLanguageModel(modelId, settings, {
15061
15060
  provider: `${providerName}.chat`,
15062
- url: ({ path: path13 }) => `${baseURL}${path13}`,
15061
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
15063
15062
  headers: getHeaders,
15064
15063
  compatibility,
15065
15064
  fetch: options.fetch
15066
15065
  });
15067
15066
  const createCompletionModel = (modelId, settings = {}) => new OpenAICompletionLanguageModel(modelId, settings, {
15068
15067
  provider: `${providerName}.completion`,
15069
- url: ({ path: path13 }) => `${baseURL}${path13}`,
15068
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
15070
15069
  headers: getHeaders,
15071
15070
  compatibility,
15072
15071
  fetch: options.fetch
15073
15072
  });
15074
15073
  const createEmbeddingModel = (modelId, settings = {}) => new OpenAIEmbeddingModel(modelId, settings, {
15075
15074
  provider: `${providerName}.embedding`,
15076
- url: ({ path: path13 }) => `${baseURL}${path13}`,
15075
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
15077
15076
  headers: getHeaders,
15078
15077
  fetch: options.fetch
15079
15078
  });
15080
15079
  const createImageModel = (modelId, settings = {}) => new OpenAIImageModel(modelId, settings, {
15081
15080
  provider: `${providerName}.image`,
15082
- url: ({ path: path13 }) => `${baseURL}${path13}`,
15081
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
15083
15082
  headers: getHeaders,
15084
15083
  fetch: options.fetch
15085
15084
  });
15086
15085
  const createTranscriptionModel = (modelId) => new OpenAITranscriptionModel(modelId, {
15087
15086
  provider: `${providerName}.transcription`,
15088
- url: ({ path: path13 }) => `${baseURL}${path13}`,
15087
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
15089
15088
  headers: getHeaders,
15090
15089
  fetch: options.fetch
15091
15090
  });
15092
15091
  const createSpeechModel = (modelId) => new OpenAISpeechModel(modelId, {
15093
15092
  provider: `${providerName}.speech`,
15094
- url: ({ path: path13 }) => `${baseURL}${path13}`,
15093
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
15095
15094
  headers: getHeaders,
15096
15095
  fetch: options.fetch
15097
15096
  });
@@ -15112,7 +15111,7 @@ function createOpenAI(options = {}) {
15112
15111
  const createResponsesModel = (modelId) => {
15113
15112
  return new OpenAIResponsesLanguageModel(modelId, {
15114
15113
  provider: `${providerName}.responses`,
15115
- url: ({ path: path13 }) => `${baseURL}${path13}`,
15114
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
15116
15115
  headers: getHeaders,
15117
15116
  fetch: options.fetch
15118
15117
  });
@@ -16271,7 +16270,7 @@ function computeWorkstyle(inp) {
16271
16270
 
16272
16271
  // ../../lib/calibration/index.ts
16273
16272
  var DEFAULT_CALIBRATION = {
16274
- version: "2026-07-12.2",
16273
+ version: "2026-07-14.2",
16275
16274
  // = RUBRIC_FINGERPRINT of the shipped npm rubric (packages/coding-analyzer/
16276
16275
  // src/grade/criteria.ts). A unit test fails loud when the rubric changes
16277
16276
  // without this being updated (and re-pushed to the server).
@@ -16360,7 +16359,7 @@ var DEFAULT_CALIBRATION = {
16360
16359
  }
16361
16360
  ],
16362
16361
  codingTiers: {
16363
- push: { median: 300, sigma: 1.121, 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 shape from its actions-per-prompt distribution. AVERAGE over substantial days, cumulative not spiky." },
16362
+ 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." },
16364
16363
  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." },
16365
16364
  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" }
16366
16365
  },
@@ -16393,12 +16392,18 @@ var DEFAULT_CALIBRATION = {
16393
16392
  line: "the Claude Code lead runs 10\u201315 sessions at once; his #1 tip is 3\u20135 git worktrees in parallel"
16394
16393
  },
16395
16394
  throughput: {
16396
- // Top-0.1% words/day reference lines, anchored to the throughput ladder
16397
- // (THROUGHPUT_LADDER in throughput.ts): score 10 "world-class sustained" =
16398
- // 9k/day, score 11 "superhuman ceiling" = 12k/day. Calibrated to the person's
16399
- // own anchors, not a wild guess but still a target, not a measured population.
16400
- topAvgWordsPerDay: 9e3,
16401
- topPeakWordsPerDay: 12e3,
16395
+ // Two DIFFERENT kinds of line (2026-07-14.2): topAvg is a PERCENTILE
16396
+ // claim — what a 1-in-1,000 user AVERAGES over substantial days (5k/day:
16397
+ // near-daily heavy dictation; a measured top dictating power user
16398
+ // averages ~3.9k). topPeak is NOT a percentileit is the CEILING
16399
+ // reference, the wildest single days on record (~10k: a 14-16h
16400
+ // launch-day marathon), rendered as "wildest single days", never
16401
+ // "top X% peak". An avg-percentile and a peak-percentile can't both be
16402
+ // shown at a same-ish value (avg≈peak reads broken — user call), and a
16403
+ // peak PERCENTILE line is unknowable anyway; the record framing is
16404
+ // honest and keeps the visual gap.
16405
+ topAvgWordsPerDay: 5e3,
16406
+ topPeakWordsPerDay: 1e4,
16402
16407
  benchLabel: "top 0.1%",
16403
16408
  // No published "words typed/day" for heavy users — the real story is OUTPUT.
16404
16409
  loopsHeadline: ">1,000,000 lines of Rust at 99.8% tests passing",
@@ -16733,7 +16738,7 @@ async function compileCodingProfile() {
16733
16738
  sRow(
16734
16739
  "How hard you push",
16735
16740
  aggregate?.throughput?.score ?? tCeiling,
16736
- `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, and crest past ${BEST.throughput.topPeakWordsPerDay.toLocaleString()}`
16741
+ `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()}`
16737
16742
  ),
16738
16743
  sRow(
16739
16744
  "How well you use AI",
@@ -16809,6 +16814,10 @@ async function readCodingAsset(name17) {
16809
16814
  );
16810
16815
  }
16811
16816
 
16817
+ // ../../lib/agents/coding/docsDir.ts
16818
+ import { promises as fs9 } from "fs";
16819
+ import path12 from "path";
16820
+
16812
16821
  // ../../lib/agents/coding/materialize.ts
16813
16822
  import { promises as fs8 } from "fs";
16814
16823
  var SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
@@ -16945,8 +16954,95 @@ async function materializeSession(file2) {
16945
16954
  return { text: lines.join("\n"), humanTurns, humanChars };
16946
16955
  }
16947
16956
 
16957
+ // ../../lib/agents/coding/docsDir.ts
16958
+ async function writeScoreboard(codingDir, docs) {
16959
+ const gradesDir = path12.join(codingDir, "grades");
16960
+ let files = [];
16961
+ try {
16962
+ files = (await fs9.readdir(gradesDir)).filter((f) => f.endsWith(".json")).sort();
16963
+ } catch {
16964
+ return;
16965
+ }
16966
+ const byCriterion = /* @__PURE__ */ new Map();
16967
+ for (const f of files) {
16968
+ let g;
16969
+ try {
16970
+ g = JSON.parse(await fs9.readFile(path12.join(gradesDir, f), "utf8"));
16971
+ } catch {
16972
+ continue;
16973
+ }
16974
+ for (const c of g.criteria ?? []) {
16975
+ const s = c.score ?? c.bestEstimate;
16976
+ if (s == null) continue;
16977
+ 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)}`;
16978
+ (byCriterion.get(c.key) ?? byCriterion.set(c.key, []).get(c.key)).push({ s, line });
16979
+ }
16980
+ }
16981
+ if (!byCriterion.size) return;
16982
+ const blocks = [...byCriterion.entries()].sort(([a], [b]) => a < b ? -1 : 1).map(
16983
+ ([key, rows]) => `## ${key}
16984
+ ${rows.sort((a, b) => b.s - a.s).map((r) => r.line).join("\n")}`
16985
+ );
16986
+ await fs9.writeFile(
16987
+ path12.join(docs, "_scoreboard.md"),
16988
+ `# Scoreboard \u2014 every graded session per criterion, best first.
16989
+ # Lx~ = estimate. Format: level \xB7 date \xB7 title \xB7 sessionId \u2014 graded instance.
16990
+
16991
+ ${blocks.join("\n\n")}
16992
+ `
16993
+ );
16994
+ }
16995
+ async function ensureCodingDocs(codingDir, sessions) {
16996
+ const docs = path12.join(codingDir, "docs");
16997
+ await fs9.mkdir(docs, { recursive: true });
16998
+ const user = sessions.filter((s) => s.sessionId && !s.duplicateOf && (!("klass" in s) || s.klass === "interactive"));
16999
+ let written = 0;
17000
+ for (const s of user) {
17001
+ const out = path12.join(docs, `${s.sessionId}.txt`);
17002
+ try {
17003
+ await fs9.access(out);
17004
+ continue;
17005
+ } catch {
17006
+ }
17007
+ const mat = await materializeSession(s.file).catch(() => null);
17008
+ if (!mat?.text) continue;
17009
+ await fs9.writeFile(out, mat.text);
17010
+ written++;
17011
+ }
17012
+ 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}`);
17013
+ await fs9.writeFile(path12.join(docs, "_map.md"), rows.join("\n") + "\n");
17014
+ await writeScoreboard(codingDir, docs);
17015
+ if (written) console.log(`[docs] materialized ${written} new transcript(s) \u2192 ${docs}`);
17016
+ return docs;
17017
+ }
17018
+ function sourceLookupNote() {
17019
+ return `
17020
+
17021
+ 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.`;
17022
+ }
17023
+
17024
+ // ../../lib/agents/coding/promptCache.ts
17025
+ import { createHash as createHash2 } from "crypto";
17026
+ import { promises as fs10 } from "fs";
17027
+ function promptSha(parts) {
17028
+ const h = createHash2("sha256");
17029
+ for (const p of parts) {
17030
+ h.update(p ?? "");
17031
+ h.update("\0");
17032
+ }
17033
+ return h.digest("hex").slice(0, 16);
17034
+ }
17035
+ async function reusableOutput(outPath, sha, valid, key = "promptSha") {
17036
+ try {
17037
+ const o = JSON.parse(await fs10.readFile(outPath, "utf8"));
17038
+ if (o[key] === sha && valid(o)) return o;
17039
+ } catch {
17040
+ }
17041
+ return null;
17042
+ }
17043
+
16948
17044
  // ../../scripts/coding-gap.mts
16949
- var CODING = ".data/coding";
17045
+ var CODING = CODING_DIR;
16950
17046
  var CONFIG = {
16951
17047
  interface: "Claude Code INSIDE the Claude Desktop Mac app (NOT a raw terminal) \u2014 so terminal-only advice like `claude -p` does not fit; give desktop-app actions.",
16952
17048
  permissions: "`--dangerously-skip-permissions` is ON. They are NOT worried about destructive actions (never happened; they just revert). Do NOT raise auto mode or any permissions change \u2014 it is not in the catalog.",
@@ -16958,7 +17054,7 @@ var CLAUDE_AUTO_CAP = /* @__PURE__ */ new Set(["queue-ahead", "deferred-tools",
16958
17054
  var CLAUDE_AUTO_MCP = /^(visualize|Claude[_ ]?Preview|ccd[_ ]?session|ccd[_ ]?session[_ ]?mgmt|mcp[_ ]?registry)$/i;
16959
17055
  async function entrypointOf(file2) {
16960
17056
  try {
16961
- const head = (await fs9.readFile(file2, "utf8")).slice(0, 2e4);
17057
+ const head = (await fs11.readFile(file2, "utf8")).slice(0, 2e4);
16962
17058
  if (/[\/."]\.?conductor/i.test(head)) return "conductor";
16963
17059
  const m = /"entrypoint":"([^"]+)"/.exec(head);
16964
17060
  return m ? m[1] : "unknown";
@@ -16980,14 +17076,14 @@ var MOBILE_WEB = /^claude-(web|mobile|ios|android)$/;
16980
17076
  function sumTool(recs, name17) {
16981
17077
  return recs.reduce((a, s) => a + (s.toolCounts?.[name17] ?? 0), 0);
16982
17078
  }
16983
- var LOCK = path12.join(CODING, ".gap.lock");
17079
+ var LOCK = path13.join(CODING, ".gap.lock");
16984
17080
  var lockHeld = false;
16985
17081
  async function acquireLock() {
16986
17082
  try {
16987
- await fs9.writeFile(LOCK, String(process.pid), { flag: "wx" });
17083
+ await fs11.writeFile(LOCK, String(process.pid), { flag: "wx" });
16988
17084
  lockHeld = true;
16989
17085
  } catch {
16990
- const pid = await fs9.readFile(LOCK, "utf8").catch(() => "?");
17086
+ const pid = await fs11.readFile(LOCK, "utf8").catch(() => "?");
16991
17087
  throw new Error(`another coding-gap run appears active (pid ${pid}, ${LOCK}). Wait for it or delete the lock if it's stale \u2014 concurrent runs overwrite each other's gap.json.`);
16992
17088
  }
16993
17089
  }
@@ -16996,9 +17092,9 @@ async function main() {
16996
17092
  const p = await compileCodingProfile();
16997
17093
  const techniquesAsset = await readCodingAsset("TECHNIQUES.md");
16998
17094
  const techniques = techniquesAsset.text;
16999
- console.log(` rubric: ${techniquesAsset.path} (${(await fs9.stat(techniquesAsset.path)).mtime.toISOString()})`);
17095
+ console.log(` rubric: ${techniquesAsset.path} (${(await fs11.stat(techniquesAsset.path)).mtime.toISOString()})`);
17000
17096
  const writing = (await readCodingAsset("WRITING.md")).text;
17001
- const all = JSON.parse(await fs9.readFile(path12.join(CODING, "sessions.json"), "utf8"));
17097
+ const all = JSON.parse(await fs11.readFile(path13.join(CODING, "sessions.json"), "utf8"));
17002
17098
  const interactive = all.filter((s) => s.klass === "interactive" && s.start);
17003
17099
  const latestMs = Math.max(...interactive.map((s) => Date.parse(s.start)));
17004
17100
  const cutoffMs = latestMs - 14 * 864e5;
@@ -17055,7 +17151,7 @@ async function main() {
17055
17151
  return m ? `--- "${s.title}" \xB7 ${(s.start || "").slice(0, 10)} ---
17056
17152
  ${m.text.slice(0, 3e3)}` : "";
17057
17153
  }))).filter(Boolean);
17058
- const SYS = "You are a Claude Code power-user coach. You judge ONE person against the COMPLETE catalog of best ways to use Claude Code (the rubric below, sourced from Boris Cherny + first-party Claude Code features) and write a comprehensive, honest gap analysis, ORDERED most-difference-making \u2192 least. Rules: (1) Go through the whole catalog and surface the techniques they are NOT doing (or under-using) that would genuinely help \u2014 respecting each tip's [EMPHASIZE]/[DE-EMPHASIZE]/[GATE] curation. (2) ORDER by differenceRank (1 = biggest unlock for THIS person). Follow the rubric's PRIME DIRECTIVE: if usage.parallelism.alreadyParallel is FALSE \u2192 'Do more in parallel' is rank 1; else \u2192 'Invest in your CLAUDE.md / tune your context' is rank 1. Then Plan mode, then 'Give Claude test cases so it can auto-verify and run longer' (Boris's 'most important thing' \u2014 rank it high, right after plan mode), then subagents, and the rest per the rubric order, skipping any that don't apply. (3) GATE the conditional tips on the STRUCTURAL usage facts, NOT on keywords \u2014 and a tip whose gate FAILS is OMITTED from recommendations entirely, never included as a softened/conditional card (recommending something their data says doesn't apply, or that they already do, reads as not having looked): loops require real PR/branch volume in usage.collaboration, else OMIT; the Chrome tip requires ~zero browser-driving in usage.chrome, else OMIT and credit in alreadyStrong; forking uses the RECENT count in usage.forking \u2014 if they fork in the recent window they already do it, OMIT and credit; remote/mobile/EC2 fires only when usage.surface.workstationBound is TRUE; plan mode by usage.planMode; subagents by usage.subagents. If they already do a thing, it goes in alreadyStrong, NOT in recommendations. (4) \u26D4 CRITICAL \u2014 this person BUILDS Claude Code tooling, so their transcripts are full of the words 'worktree', 'pull request', 'fork', 'plan mode', 'by the way', 'teleport'. A keyword in the transcript is NOT evidence they use the feature. Rely on the STRUCTURAL facts only. (5) Ground every recommendation in a RECEIPT \u2014 a structural fact from their usage or a concrete session pain point \u2014 and CONVINCE with a specific case for how it helps THIS person, not a generic benefit. (6) Tailor every action to the Claude Desktop Mac app; NEVER give terminal-only `claude -p` advice. (7) Honor config.deliberateChoices (they stay on `main` on purpose \u2014 do NOT tell them to hand-juggle worktrees; parallel SESSIONS instead) and config.claudeAutoTools (never tell them to 'use' a tool Claude drives itself; for 'explain with visuals' the user just ASKS for a visual \u2014 do not name the tool). (8) Also name what they ALREADY do well. (9) SELF-CONTAINED but not verbose \u2014 lead with the PROBLEM, then the SOLUTION, then HOW to use it in their context. Plain language, no jargon, no enumerating multiple dates. An essay-like rec is a failed rec; so is a cryptic one. Follow this writing rubric strictly:\n\n" + writing;
17154
+ const SYS = "You are a Claude Code power-user coach. You judge ONE person against the COMPLETE catalog of best ways to use Claude Code (the rubric below, sourced from Boris Cherny + first-party Claude Code features) and write a comprehensive, honest gap analysis, ORDERED most-difference-making \u2192 least. Rules: (1) Go through the whole catalog and surface the techniques they are NOT doing (or under-using) that would genuinely help \u2014 respecting each tip's [EMPHASIZE]/[DE-EMPHASIZE]/[GATE] curation. (2) ORDER by differenceRank (1 = biggest unlock for THIS person). Follow the rubric's PRIME DIRECTIVE: if usage.parallelism.alreadyParallel is FALSE \u2192 'Do more in parallel' is rank 1; else \u2192 'Invest in your CLAUDE.md / tune your context' is rank 1. Then Plan mode, then 'Give Claude test cases so it can auto-verify and run longer' (Boris's 'most important thing' \u2014 rank it high, right after plan mode), then subagents, and the rest per the rubric order, skipping any that don't apply. (3) GATE the conditional tips on the STRUCTURAL usage facts, NOT on keywords \u2014 and a tip whose gate FAILS is OMITTED from recommendations entirely, never included as a softened/conditional card (recommending something their data says doesn't apply, or that they already do, reads as not having looked): loops require real PR/branch volume in usage.collaboration, else OMIT; the Chrome tip requires ~zero browser-driving in usage.chrome, else OMIT and credit in alreadyStrong; forking uses the RECENT count in usage.forking \u2014 if they fork in the recent window they already do it, OMIT and credit; remote/mobile/EC2 fires only when usage.surface.workstationBound is TRUE; plan mode by usage.planMode; subagents by usage.subagents. If they already do a thing, it goes in alreadyStrong, NOT in recommendations. (4) \u26D4 CRITICAL \u2014 this person BUILDS Claude Code tooling, so their transcripts are full of the words 'worktree', 'pull request', 'fork', 'plan mode', 'by the way', 'teleport'. A keyword in the transcript is NOT evidence they use the feature. Rely on the STRUCTURAL facts only. (5) Ground every recommendation in a RECEIPT \u2014 a structural fact from their usage or a concrete session pain point \u2014 and CONVINCE with a specific case for how it helps THIS person, not a generic benefit. (6) Tailor every action to the Claude Desktop Mac app; NEVER give terminal-only `claude -p` advice. (7) Honor config.deliberateChoices (they stay on `main` on purpose \u2014 do NOT tell them to hand-juggle worktrees; parallel SESSIONS instead) and config.claudeAutoTools (never tell them to 'use' a tool Claude drives itself; for 'explain with visuals' the user just ASKS for a visual \u2014 do not name the tool). (8) Also name what they ALREADY do well. (9) SELF-CONTAINED but not verbose \u2014 lead with the PROBLEM, then the SOLUTION, then HOW to use it in their context. Plain language, no jargon, no enumerating multiple dates. An essay-like rec is a failed rec; so is a cryptic one. Follow this writing rubric strictly:\n\n" + writing + sourceLookupNote();
17059
17155
  const prompt = `THE RUBRIC \u2014 the complete catalog of best ways to use Claude Code. Judge the person against ALL of it, and ORDER by differenceRank per its PRIME DIRECTIVE:
17060
17156
 
17061
17157
  ${techniques}
@@ -17076,18 +17172,23 @@ Produce the gap analysis. Be thorough: cover every relevant catalog item they're
17076
17172
  }
17077
17173
  \`\`\`
17078
17174
  \`differenceRank\` is a strict 1..N ordering (1 = highest). Assign it per the PRIME DIRECTIVE and the rubric's general order; the report renders recommendations sorted by it. \`payoff\` (1-5) is a secondary strength cue. Recommend ONLY from the catalog.`;
17079
- console.log("gap \u2014 1 Sonnet pass over the full TECHNIQUES.md catalog + structural usage");
17175
+ const sha = promptSha([SYS, prompt, "sonnet"]);
17176
+ if (!process.argv.includes("--refresh")) {
17177
+ const prev = await reusableOutput(path13.join(CODING, "gap.json"), sha, (o) => Array.isArray(o.recommendations) && o.recommendations.length > 0);
17178
+ if (prev) {
17179
+ console.log(`[gap] inputs unchanged \u2014 reusing output from ${prev.generatedAt} (--refresh to regenerate)`);
17180
+ return;
17181
+ }
17182
+ }
17183
+ await ensureCodingDocs(CODING, all);
17184
+ console.log("gap \u2014 1 Sonnet pass over the full TECHNIQUES.md catalog + structural usage (+source lookups)");
17080
17185
  console.log(` signals: alreadyParallel=${alreadyParallel} planModeCalls=${planModeUses} forks=${forkCopies}/${all.length} featureBranches=${featureBranches.length} workstationBound=${workstationBound} chrome=${chromeToolUse}`);
17081
- const sandbox = await fs9.mkdtemp(path12.join(os2.tmpdir(), "gap-"));
17082
17186
  let out = { recommendations: [], alreadyStrong: [] };
17083
17187
  try {
17084
- const run2 = await invokeAgent({ prompt, systemPrompt: SYS, addDir: sandbox, allowedTools: ["Read"], maxTurns: 4, timeoutMs: 6e5, model: "sonnet" });
17188
+ const run2 = await invokeAgent({ prompt, systemPrompt: SYS, addDir: CODING, allowedTools: ["Read", "Grep", "Glob"], maxTurns: 10, timeoutMs: 6e5, model: "sonnet" });
17085
17189
  out = run2.json || out;
17086
17190
  } catch (e) {
17087
17191
  console.log("ERR", e.message.slice(0, 160));
17088
- } finally {
17089
- await fs9.rm(sandbox, { recursive: true, force: true }).catch(() => {
17090
- });
17091
17192
  }
17092
17193
  if (Array.isArray(out.recommendations)) {
17093
17194
  out.recommendations = out.recommendations.map((r, i) => ({ r, i })).sort((a, b) => (a.r.differenceRank ?? 99) - (b.r.differenceRank ?? 99) || (b.r.payoff ?? 0) - (a.r.payoff ?? 0) || a.i - b.i).map((x) => x.r);
@@ -17095,14 +17196,14 @@ Produce the gap analysis. Be thorough: cover every relevant catalog item they're
17095
17196
  if (!Array.isArray(out.recommendations) || out.recommendations.length === 0) {
17096
17197
  throw new Error("gap run produced 0 recommendations \u2014 refusing to overwrite the existing gap.json. Re-run the builder.");
17097
17198
  }
17098
- const result = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...out };
17099
- await fs9.writeFile(path12.join(CODING, "gap.json"), JSON.stringify(result, null, 2));
17199
+ const result = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), promptSha: sha, ...out };
17200
+ await fs11.writeFile(path13.join(CODING, "gap.json"), JSON.stringify(result, null, 2));
17100
17201
  console.log(`WROTE ${CODING}/gap.json \u2014 recommendations:${(out.recommendations || []).length} \xB7 alreadyStrong:${(out.alreadyStrong || []).length}`);
17101
17202
  }
17102
17203
  main().catch((e) => {
17103
17204
  console.error(e);
17104
17205
  process.exitCode = 1;
17105
17206
  }).finally(() => {
17106
- if (lockHeld) fs9.rm(LOCK, { force: true }).catch(() => {
17207
+ if (lockHeld) fs11.rm(LOCK, { force: true }).catch(() => {
17107
17208
  });
17108
17209
  });