@drewpayment/mink 0.12.0-beta.4 → 0.12.0-beta.5

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.
Files changed (46) hide show
  1. package/dashboard/out/404.html +1 -1
  2. package/dashboard/out/action-log.html +1 -1
  3. package/dashboard/out/action-log.txt +1 -1
  4. package/dashboard/out/activity.html +1 -1
  5. package/dashboard/out/activity.txt +1 -1
  6. package/dashboard/out/bugs.html +1 -1
  7. package/dashboard/out/bugs.txt +1 -1
  8. package/dashboard/out/capture.html +1 -1
  9. package/dashboard/out/capture.txt +1 -1
  10. package/dashboard/out/config.html +1 -1
  11. package/dashboard/out/config.txt +1 -1
  12. package/dashboard/out/daemon.html +1 -1
  13. package/dashboard/out/daemon.txt +1 -1
  14. package/dashboard/out/design.html +1 -1
  15. package/dashboard/out/design.txt +1 -1
  16. package/dashboard/out/discord.html +1 -1
  17. package/dashboard/out/discord.txt +1 -1
  18. package/dashboard/out/file-index.html +1 -1
  19. package/dashboard/out/file-index.txt +1 -1
  20. package/dashboard/out/index.html +1 -1
  21. package/dashboard/out/index.txt +1 -1
  22. package/dashboard/out/insights.html +1 -1
  23. package/dashboard/out/insights.txt +1 -1
  24. package/dashboard/out/learning.html +1 -1
  25. package/dashboard/out/learning.txt +1 -1
  26. package/dashboard/out/overview.html +1 -1
  27. package/dashboard/out/overview.txt +1 -1
  28. package/dashboard/out/scheduler.html +1 -1
  29. package/dashboard/out/scheduler.txt +1 -1
  30. package/dashboard/out/sync.html +1 -1
  31. package/dashboard/out/sync.txt +1 -1
  32. package/dashboard/out/tokens.html +1 -1
  33. package/dashboard/out/tokens.txt +1 -1
  34. package/dashboard/out/waste.html +1 -1
  35. package/dashboard/out/waste.txt +1 -1
  36. package/dashboard/out/wiki.html +1 -1
  37. package/dashboard/out/wiki.txt +1 -1
  38. package/dist/cli.bun.js +116 -54
  39. package/dist/cli.node.js +116 -54
  40. package/package.json +1 -1
  41. package/src/commands/post-read.ts +94 -9
  42. package/src/core/framework-advisor/generate.ts +11 -1
  43. package/src/core/note-linker.ts +12 -7
  44. package/src/types/hook-input.ts +10 -0
  45. /package/dashboard/out/_next/static/{i9-16JmUxsS4K70sSYdYA → eZlC6TEe7TWUABN2-Ho0J}/_buildManifest.js +0 -0
  46. /package/dashboard/out/_next/static/{i9-16JmUxsS4K70sSYdYA → eZlC6TEe7TWUABN2-Ho0J}/_ssgManifest.js +0 -0
package/dist/cli.node.js CHANGED
@@ -2681,15 +2681,8 @@ function addBacklink(targetNotePath, sourceTitle) {
2681
2681
  }
2682
2682
  }
2683
2683
  function updateMasterIndex(vaultRootPath) {
2684
- const now = new Date().toISOString().split("T")[0];
2685
2684
  const sections = [
2686
- `---`,
2687
- `updated: "${new Date().toISOString()}"`,
2688
- `---`,
2689
- ``,
2690
2685
  `# Knowledge Base`,
2691
- ``,
2692
- `> Last updated: ${now}`,
2693
2686
  ``
2694
2687
  ];
2695
2688
  const categories = [
@@ -2722,6 +2715,13 @@ function updateMasterIndex(vaultRootPath) {
2722
2715
  }
2723
2716
  sections.push("");
2724
2717
  }
2718
+ const nowIso = new Date().toISOString();
2719
+ const nowDate = nowIso.split("T")[0];
2720
+ sections.push(`---`);
2721
+ sections.push(``);
2722
+ sections.push(`<!-- mink:footer (volatile — keep at end of file) -->`);
2723
+ sections.push(`> Last updated: ${nowDate} (${nowIso})`);
2724
+ sections.push(``);
2725
2725
  const indexPath = vaultMasterIndexPath();
2726
2726
  atomicWriteText(indexPath, sections.join(`
2727
2727
  `));
@@ -7098,20 +7098,46 @@ var init_pre_read = __esm(() => {
7098
7098
  var exports_post_read = {};
7099
7099
  __export(exports_post_read, {
7100
7100
  postRead: () => postRead,
7101
+ extractContent: () => extractContent,
7101
7102
  analyzePostRead: () => analyzePostRead
7102
7103
  });
7103
7104
  import { relative as relative5 } from "path";
7105
+ import { readFileSync as readFileSync19 } from "fs";
7104
7106
  function analyzePostRead(filePath, content, index) {
7105
7107
  if (isBinaryFile(filePath, content ?? undefined)) {
7106
7108
  const entry = index ? index.lookupEntry(filePath) : null;
7107
- return { estimatedTokens: 0, indexHit: !!entry, source: "none" };
7109
+ return {
7110
+ estimatedTokens: 0,
7111
+ indexHit: !!entry,
7112
+ source: "none",
7113
+ indexEntry: null
7114
+ };
7108
7115
  }
7109
7116
  if (content !== null && content.length > 0) {
7110
7117
  const entry = index ? index.lookupEntry(filePath) : null;
7118
+ const tokens = estimateTokens2(content, filePath);
7119
+ let indexEntry = null;
7120
+ if (!entry) {
7121
+ let description = "";
7122
+ try {
7123
+ description = extractDescription(filePath, content);
7124
+ } catch {
7125
+ description = "";
7126
+ }
7127
+ const now = new Date().toISOString();
7128
+ indexEntry = {
7129
+ filePath,
7130
+ description,
7131
+ estimatedTokens: tokens,
7132
+ lastModified: now,
7133
+ lastIndexed: now
7134
+ };
7135
+ }
7111
7136
  return {
7112
- estimatedTokens: estimateTokens2(content, filePath),
7137
+ estimatedTokens: tokens,
7113
7138
  indexHit: !!entry,
7114
- source: "content"
7139
+ source: "content",
7140
+ indexEntry
7115
7141
  };
7116
7142
  }
7117
7143
  if (index) {
@@ -7120,11 +7146,17 @@ function analyzePostRead(filePath, content, index) {
7120
7146
  return {
7121
7147
  estimatedTokens: entry.estimatedTokens,
7122
7148
  indexHit: true,
7123
- source: "index-fallback"
7149
+ source: "index-fallback",
7150
+ indexEntry: null
7124
7151
  };
7125
7152
  }
7126
7153
  }
7127
- return { estimatedTokens: 0, indexHit: false, source: "none" };
7154
+ return {
7155
+ estimatedTokens: 0,
7156
+ indexHit: false,
7157
+ source: "none",
7158
+ indexEntry: null
7159
+ };
7128
7160
  }
7129
7161
  function isPostToolUseInput(value) {
7130
7162
  if (value === null || typeof value !== "object")
@@ -7137,9 +7169,22 @@ function isPostToolUseInput(value) {
7137
7169
  return true;
7138
7170
  }
7139
7171
  function extractContent(input) {
7140
- if (!input.tool_output)
7141
- return null;
7142
- if (typeof input.tool_output.content === "string") {
7172
+ const tr = input.tool_response;
7173
+ if (tr) {
7174
+ if (typeof tr.content === "string")
7175
+ return tr.content;
7176
+ if (Array.isArray(tr.content)) {
7177
+ const parts = tr.content.map((p) => p && typeof p.text === "string" ? p.text : "").filter((s) => s.length > 0);
7178
+ if (parts.length > 0)
7179
+ return parts.join("");
7180
+ }
7181
+ if (tr.file && typeof tr.file.content === "string") {
7182
+ return tr.file.content;
7183
+ }
7184
+ if (typeof tr.text === "string")
7185
+ return tr.text;
7186
+ }
7187
+ if (input.tool_output && typeof input.tool_output.content === "string") {
7143
7188
  return input.tool_output.content;
7144
7189
  }
7145
7190
  return null;
@@ -7159,8 +7204,19 @@ async function postRead(cwd) {
7159
7204
  const rawState = safeReadJson(sessionPath(cwd));
7160
7205
  const state = isSessionState(rawState) ? rawState : createSessionState();
7161
7206
  const repo = FileIndexRepo.for(cwd);
7162
- const content = extractContent(input);
7207
+ let content = null;
7208
+ try {
7209
+ content = readFileSync19(absolutePath, "utf-8");
7210
+ } catch {}
7211
+ if (content === null) {
7212
+ content = extractContent(input);
7213
+ }
7163
7214
  const result = analyzePostRead(filePath, content, repo);
7215
+ if (result.indexEntry) {
7216
+ try {
7217
+ repo.upsert(result.indexEntry);
7218
+ } catch {}
7219
+ }
7164
7220
  recordRead(state, filePath, result.estimatedTokens, result.indexHit);
7165
7221
  try {
7166
7222
  const logWriter = createActionLogWriter(actionLogShardPath(cwd, getOrCreateDeviceId()));
@@ -7177,6 +7233,7 @@ var init_post_read = __esm(() => {
7177
7233
  init_session();
7178
7234
  init_file_index_repo();
7179
7235
  init_token_estimate();
7236
+ init_description();
7180
7237
  init_action_log();
7181
7238
  init_device();
7182
7239
  });
@@ -7377,7 +7434,7 @@ __export(exports_post_write, {
7377
7434
  analyzePostWrite: () => analyzePostWrite
7378
7435
  });
7379
7436
  import { relative as relative7 } from "path";
7380
- import { readFileSync as readFileSync19 } from "fs";
7437
+ import { readFileSync as readFileSync20 } from "fs";
7381
7438
  function analyzePostWrite(filePath, fileContent, index) {
7382
7439
  if (isWriteExcluded(filePath)) {
7383
7440
  return {
@@ -7441,7 +7498,7 @@ async function postWrite(cwd) {
7441
7498
  const filePath = relative7(cwd, absolutePath);
7442
7499
  let fileContent = null;
7443
7500
  try {
7444
- fileContent = readFileSync19(absolutePath, "utf-8");
7501
+ fileContent = readFileSync20(absolutePath, "utf-8");
7445
7502
  } catch {}
7446
7503
  const rawState = safeReadJson(sessionPath(cwd));
7447
7504
  const state = isSessionState(rawState) ? rawState : createSessionState();
@@ -7794,7 +7851,7 @@ __export(exports_self_update, {
7794
7851
  PACKAGE_NAME: () => PACKAGE_NAME
7795
7852
  });
7796
7853
  import { spawnSync as spawnSync2 } from "child_process";
7797
- import { existsSync as existsSync26, readFileSync as readFileSync20 } from "fs";
7854
+ import { existsSync as existsSync26, readFileSync as readFileSync21 } from "fs";
7798
7855
  import { dirname as dirname11 } from "path";
7799
7856
  import { join as join23 } from "path";
7800
7857
  function parseSemver(input) {
@@ -7861,7 +7918,7 @@ function getInstallInfo() {
7861
7918
  }
7862
7919
  let currentVersion = "0.0.0";
7863
7920
  try {
7864
- const pkg = JSON.parse(readFileSync20(packageJsonPath, "utf-8"));
7921
+ const pkg = JSON.parse(readFileSync21(packageJsonPath, "utf-8"));
7865
7922
  if (typeof pkg.version === "string")
7866
7923
  currentVersion = pkg.version;
7867
7924
  } catch {}
@@ -7934,7 +7991,7 @@ function appendLogEntry(entry) {
7934
7991
  }
7935
7992
  function rotateLogIfNeeded(path) {
7936
7993
  try {
7937
- const content = readFileSync20(path, "utf-8");
7994
+ const content = readFileSync21(path, "utf-8");
7938
7995
  const lines = content.split(`
7939
7996
  `);
7940
7997
  if (lines.length <= LOG_MAX_LINES + 1)
@@ -8037,7 +8094,7 @@ async function runSelfUpgradeInner(opts) {
8037
8094
  }
8038
8095
  let verifiedVersion = latest;
8039
8096
  try {
8040
- const pkg = JSON.parse(readFileSync20(info.packageJsonPath, "utf-8"));
8097
+ const pkg = JSON.parse(readFileSync21(info.packageJsonPath, "utf-8"));
8041
8098
  if (typeof pkg.version === "string")
8042
8099
  verifiedVersion = pkg.version;
8043
8100
  } catch {}
@@ -8143,10 +8200,10 @@ async function executeTask(taskId, projectCwd) {
8143
8200
  if (task.actionType === "ai-cli") {
8144
8201
  try {
8145
8202
  const { learningMemoryPath: learningMemoryPath5 } = await Promise.resolve().then(() => (init_paths(), exports_paths));
8146
- const { readFileSync: readFileSync21 } = await import("fs");
8203
+ const { readFileSync: readFileSync22 } = await import("fs");
8147
8204
  let memoryContent;
8148
8205
  try {
8149
- memoryContent = readFileSync21(learningMemoryPath5(projectCwd), "utf-8");
8206
+ memoryContent = readFileSync22(learningMemoryPath5(projectCwd), "utf-8");
8150
8207
  } catch {
8151
8208
  console.log("[mink] no learning memory found, skipping reflection");
8152
8209
  return;
@@ -8711,7 +8768,7 @@ var init_cron = __esm(() => {
8711
8768
 
8712
8769
  // src/core/vault-templates.ts
8713
8770
  import { join as join24 } from "path";
8714
- import { existsSync as existsSync27, writeFileSync as writeFileSync9, readFileSync as readFileSync21, mkdirSync as mkdirSync13 } from "fs";
8771
+ import { existsSync as existsSync27, writeFileSync as writeFileSync9, readFileSync as readFileSync22, mkdirSync as mkdirSync13 } from "fs";
8715
8772
  function seedTemplates(templatesDir) {
8716
8773
  mkdirSync13(templatesDir, { recursive: true });
8717
8774
  for (const [name, content] of Object.entries(DEFAULT_TEMPLATES)) {
@@ -8725,7 +8782,7 @@ function loadTemplate(templatesDir, templateName, vars) {
8725
8782
  const filePath = join24(templatesDir, `${templateName}.md`);
8726
8783
  let content;
8727
8784
  if (existsSync27(filePath)) {
8728
- content = readFileSync21(filePath, "utf-8");
8785
+ content = readFileSync22(filePath, "utf-8");
8729
8786
  } else if (DEFAULT_TEMPLATES[templateName]) {
8730
8787
  content = DEFAULT_TEMPLATES[templateName];
8731
8788
  } else {
@@ -8879,7 +8936,7 @@ category: resources
8879
8936
 
8880
8937
  // src/core/note-writer.ts
8881
8938
  import { join as join25 } from "path";
8882
- import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
8939
+ import { existsSync as existsSync28, readFileSync as readFileSync23 } from "fs";
8883
8940
  import { createHash as createHash4 } from "crypto";
8884
8941
  function sha256(content) {
8885
8942
  return createHash4("sha256").update(content).digest("hex");
@@ -8904,7 +8961,7 @@ function resolveUniqueNotePath(dir, baseSlug, content) {
8904
8961
  }
8905
8962
  function sameContent(filePath, expectedHash) {
8906
8963
  try {
8907
- return sha256(readFileSync22(filePath, "utf-8")) === expectedHash;
8964
+ return sha256(readFileSync23(filePath, "utf-8")) === expectedHash;
8908
8965
  } catch {
8909
8966
  return false;
8910
8967
  }
@@ -9013,7 +9070,7 @@ ${content}
9013
9070
  return filePath;
9014
9071
  }
9015
9072
  function ingestFile(sourcePath, meta) {
9016
- const raw = readFileSync22(sourcePath, "utf-8");
9073
+ const raw = readFileSync23(sourcePath, "utf-8");
9017
9074
  const now = new Date().toISOString();
9018
9075
  const headingMatch = raw.match(/^#\s+(.+)$/m);
9019
9076
  const title = headingMatch?.[1] ?? sourcePath.split("/").pop().replace(/\.md$/, "");
@@ -9085,7 +9142,7 @@ var init_design_eval = __esm(() => {
9085
9142
  });
9086
9143
 
9087
9144
  // src/core/dashboard-api.ts
9088
- import { existsSync as existsSync29, readFileSync as readFileSync23 } from "fs";
9145
+ import { existsSync as existsSync29, readFileSync as readFileSync24 } from "fs";
9089
9146
  import { readdirSync as readdirSync9, readFileSync as readFileSyncFS, existsSync as fsExistsSync } from "fs";
9090
9147
  import { join as join26, resolve as resolve5, normalize, sep } from "path";
9091
9148
  import { execSync as execSync6 } from "child_process";
@@ -9113,7 +9170,7 @@ function checkTextFile2(name, filePath) {
9113
9170
  if (!existsSync29(filePath))
9114
9171
  return { name, status: "missing" };
9115
9172
  try {
9116
- readFileSync23(filePath, "utf-8");
9173
+ readFileSync24(filePath, "utf-8");
9117
9174
  return { name, status: "ok" };
9118
9175
  } catch {
9119
9176
  return { name, status: "corrupt" };
@@ -9123,7 +9180,7 @@ function checkDbFile2(name, filePath) {
9123
9180
  if (!existsSync29(filePath))
9124
9181
  return { name, status: "missing" };
9125
9182
  try {
9126
- const header = readFileSync23(filePath).slice(0, 16).toString("utf-8");
9183
+ const header = readFileSync24(filePath).slice(0, 16).toString("utf-8");
9127
9184
  return header.startsWith("SQLite format 3") ? { name, status: "ok" } : { name, status: "corrupt" };
9128
9185
  } catch {
9129
9186
  return { name, status: "corrupt" };
@@ -10785,7 +10842,7 @@ var exports_daemon = {};
10785
10842
  __export(exports_daemon, {
10786
10843
  daemon: () => daemon
10787
10844
  });
10788
- import { readFileSync as readFileSync24, existsSync as existsSync34 } from "fs";
10845
+ import { readFileSync as readFileSync25, existsSync as existsSync34 } from "fs";
10789
10846
  async function daemon(cwd, args) {
10790
10847
  const subcommand = args[0];
10791
10848
  switch (subcommand) {
@@ -10806,7 +10863,7 @@ async function daemon(cwd, args) {
10806
10863
  return;
10807
10864
  }
10808
10865
  try {
10809
- const content = readFileSync24(logPath, "utf-8");
10866
+ const content = readFileSync25(logPath, "utf-8");
10810
10867
  const lines = content.split(`
10811
10868
  `);
10812
10869
  const tail = lines.slice(-50).join(`
@@ -11387,7 +11444,7 @@ var init_restore = __esm(() => {
11387
11444
  });
11388
11445
 
11389
11446
  // src/core/design-eval/server-detect.ts
11390
- import { readFileSync as readFileSync25 } from "fs";
11447
+ import { readFileSync as readFileSync26 } from "fs";
11391
11448
  import { join as join30 } from "path";
11392
11449
  async function probePort(port) {
11393
11450
  try {
@@ -11410,7 +11467,7 @@ async function findRunningServer(ports = DEFAULT_PROBE_PORTS) {
11410
11467
  }
11411
11468
  function detectDevCommand(cwd) {
11412
11469
  try {
11413
- const raw = readFileSync25(join30(cwd, "package.json"), "utf-8");
11470
+ const raw = readFileSync26(join30(cwd, "package.json"), "utf-8");
11414
11471
  const pkg = JSON.parse(raw);
11415
11472
  const scripts = pkg.scripts;
11416
11473
  if (!scripts || typeof scripts !== "object")
@@ -83779,7 +83836,7 @@ var init_fileUtil = __esm(() => {
83779
83836
  // node_modules/@puppeteer/browsers/lib/esm/install.js
83780
83837
  import assert2 from "node:assert";
83781
83838
  import { spawnSync as spawnSync4 } from "node:child_process";
83782
- import { existsSync as existsSync36, readFileSync as readFileSync26 } from "node:fs";
83839
+ import { existsSync as existsSync36, readFileSync as readFileSync27 } from "node:fs";
83783
83840
  import { mkdir as mkdir2, unlink } from "node:fs/promises";
83784
83841
  import os5 from "node:os";
83785
83842
  import path8 from "node:path";
@@ -83869,7 +83926,7 @@ async function installDeps(installedBrowser) {
83869
83926
  debugInstall(`deb.deps file was not found at ${depsPath}`);
83870
83927
  return;
83871
83928
  }
83872
- const data = readFileSync26(depsPath, "utf-8").split(`
83929
+ const data = readFileSync27(depsPath, "utf-8").split(`
83873
83930
  `).join(",");
83874
83931
  if (process.getuid?.() !== 0) {
83875
83932
  throw new Error("Installing system dependencies requires root privileges");
@@ -85394,14 +85451,14 @@ var init_yerror = __esm(() => {
85394
85451
  });
85395
85452
 
85396
85453
  // node_modules/y18n/build/lib/platform-shims/node.js
85397
- import { readFileSync as readFileSync27, statSync as statSync14, writeFile } from "fs";
85454
+ import { readFileSync as readFileSync28, statSync as statSync14, writeFile } from "fs";
85398
85455
  import { format as format2 } from "util";
85399
85456
  import { resolve as resolve12 } from "path";
85400
85457
  var node_default;
85401
85458
  var init_node = __esm(() => {
85402
85459
  node_default = {
85403
85460
  fs: {
85404
- readFileSync: readFileSync27,
85461
+ readFileSync: readFileSync28,
85405
85462
  writeFile
85406
85463
  },
85407
85464
  format: format2,
@@ -85586,7 +85643,7 @@ var init_y18n = __esm(() => {
85586
85643
  // node_modules/yargs/lib/platform-shims/esm.mjs
85587
85644
  import { notStrictEqual, strictEqual } from "assert";
85588
85645
  import { inspect } from "util";
85589
- import { readFileSync as readFileSync28 } from "fs";
85646
+ import { readFileSync as readFileSync29 } from "fs";
85590
85647
  import { fileURLToPath } from "url";
85591
85648
  import { basename as basename9, dirname as dirname16, extname as extname3, relative as relative9, resolve as resolve13 } from "path";
85592
85649
  var REQUIRE_ERROR = "require is not supported by ESM", REQUIRE_DIRECTORY_ERROR = "loading a directory of commands is not supported yet for ESM", __dirname2, mainFilename, esm_default;
@@ -85635,7 +85692,7 @@ var init_esm = __esm(() => {
85635
85692
  nextTick: process.nextTick,
85636
85693
  stdColumns: typeof process.stdout.columns !== "undefined" ? process.stdout.columns : null
85637
85694
  },
85638
- readFileSync: readFileSync28,
85695
+ readFileSync: readFileSync29,
85639
85696
  require: () => {
85640
85697
  throw new YError(REQUIRE_ERROR);
85641
85698
  },
@@ -91894,7 +91951,7 @@ function generateKnowledgeMarkdown(k) {
91894
91951
  const parts = [];
91895
91952
  parts.push(`# Framework Advisor Knowledge Base`);
91896
91953
  parts.push("");
91897
- parts.push(`> Generated: ${k.generatedAt} | Version: ${k.version} | Frameworks: ${k.frameworks.length}`);
91954
+ parts.push(`> Version: ${k.version} | Frameworks: ${k.frameworks.length}`);
91898
91955
  parts.push("");
91899
91956
  parts.push("## Comparison Matrix");
91900
91957
  parts.push("");
@@ -91950,6 +92007,11 @@ function generateKnowledgeMarkdown(k) {
91950
92007
  parts.push("");
91951
92008
  }
91952
92009
  }
92010
+ parts.push(`---`);
92011
+ parts.push(``);
92012
+ parts.push(`<!-- mink:footer (volatile — keep at end of file) -->`);
92013
+ parts.push(`> Generated: ${k.generatedAt}`);
92014
+ parts.push(``);
91953
92015
  return parts.join(`
91954
92016
  `);
91955
92017
  }
@@ -92425,7 +92487,7 @@ __export(exports_note, {
92425
92487
  note: () => note
92426
92488
  });
92427
92489
  import { resolve as resolve15 } from "path";
92428
- import { existsSync as existsSync40, readFileSync as readFileSync29 } from "fs";
92490
+ import { existsSync as existsSync40, readFileSync as readFileSync30 } from "fs";
92429
92491
  async function note(cwd, args) {
92430
92492
  if (!isWikiEnabled()) {
92431
92493
  console.error("[mink] wiki feature is disabled");
@@ -92450,7 +92512,7 @@ async function note(cwd, args) {
92450
92512
  const date = new Date().toISOString().split("T")[0];
92451
92513
  const content = parsed.positional || parsed.body || "";
92452
92514
  const filePath = appendToDaily(date, content);
92453
- updateVaultIndexForFile(filePath, readFileSync29(filePath, "utf-8"));
92515
+ updateVaultIndexForFile(filePath, readFileSync30(filePath, "utf-8"));
92454
92516
  console.log(`[mink] daily note: ${filePath}`);
92455
92517
  return;
92456
92518
  }
@@ -92783,7 +92845,7 @@ import { homedir as homedir7 } from "os";
92783
92845
  import {
92784
92846
  existsSync as existsSync42,
92785
92847
  mkdirSync as mkdirSync18,
92786
- readFileSync as readFileSync30,
92848
+ readFileSync as readFileSync31,
92787
92849
  writeFileSync as writeFileSync11
92788
92850
  } from "fs";
92789
92851
  import { createHash as createHash5 } from "crypto";
@@ -92807,7 +92869,7 @@ function getMinkVersion() {
92807
92869
  const pkgPath = join36(dir, "package.json");
92808
92870
  if (existsSync42(pkgPath)) {
92809
92871
  try {
92810
- const pkg = JSON.parse(readFileSync30(pkgPath, "utf-8"));
92872
+ const pkg = JSON.parse(readFileSync31(pkgPath, "utf-8"));
92811
92873
  if (pkg.name && pkg.version)
92812
92874
  return pkg.version;
92813
92875
  } catch {}
@@ -92845,7 +92907,7 @@ function installAgentDefinition(opts) {
92845
92907
  if (opts.skip && existsSync42(installed)) {
92846
92908
  return { action: "skipped", path: installed };
92847
92909
  }
92848
- const template = readFileSync30(templatePath, "utf-8");
92910
+ const template = readFileSync31(templatePath, "utf-8");
92849
92911
  const rendered = renderTemplate(template, {
92850
92912
  MINK_ROOT: minkRoot(),
92851
92913
  VAULT_PATH: resolveVaultPath(),
@@ -92853,7 +92915,7 @@ function installAgentDefinition(opts) {
92853
92915
  });
92854
92916
  const exists = existsSync42(installed);
92855
92917
  if (!opts.force && exists) {
92856
- const current = readFileSync30(installed, "utf-8");
92918
+ const current = readFileSync31(installed, "utf-8");
92857
92919
  if (sha2562(current) === sha2562(rendered)) {
92858
92920
  return { action: "unchanged", path: installed };
92859
92921
  }
@@ -92981,7 +93043,7 @@ var init_agent = __esm(() => {
92981
93043
  });
92982
93044
 
92983
93045
  // src/core/sync-merge-drivers.ts
92984
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, copyFileSync as copyFileSync2, unlinkSync as unlinkSync7 } from "fs";
93046
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, copyFileSync as copyFileSync2, unlinkSync as unlinkSync7 } from "fs";
92985
93047
  import { join as join37 } from "path";
92986
93048
  function logWarning(driver, args, err) {
92987
93049
  try {
@@ -92992,14 +93054,14 @@ function logWarning(driver, args, err) {
92992
93054
  }
92993
93055
  function readJsonOrNull(path12) {
92994
93056
  try {
92995
- return JSON.parse(readFileSync31(path12, "utf-8"));
93057
+ return JSON.parse(readFileSync32(path12, "utf-8"));
92996
93058
  } catch {
92997
93059
  return null;
92998
93060
  }
92999
93061
  }
93000
93062
  function readTextOrEmpty(path12) {
93001
93063
  try {
93002
- return readFileSync31(path12, "utf-8");
93064
+ return readFileSync32(path12, "utf-8");
93003
93065
  } catch {
93004
93066
  return "";
93005
93067
  }
@@ -93889,9 +93951,9 @@ switch (command2) {
93889
93951
  const { resolve: resolve18, dirname: dirname20, basename: basename10 } = await import("path");
93890
93952
  const bundlePath = new URL(import.meta.url).pathname;
93891
93953
  const cliPath = resolve18(dirname20(bundlePath));
93892
- const { readFileSync: readFileSync32 } = await import("fs");
93954
+ const { readFileSync: readFileSync33 } = await import("fs");
93893
93955
  try {
93894
- const pkg = JSON.parse(readFileSync32(resolve18(cliPath, "../package.json"), "utf-8"));
93956
+ const pkg = JSON.parse(readFileSync33(resolve18(cliPath, "../package.json"), "utf-8"));
93895
93957
  console.log(`mink ${pkg.version}`);
93896
93958
  } catch {
93897
93959
  console.log("mink (unknown version)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drewpayment/mink",
3
- "version": "0.12.0-beta.4",
3
+ "version": "0.12.0-beta.5",
4
4
  "description": "A hidden presence that moves alongside the developer — token efficiency and cross-project wiki for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,20 +1,26 @@
1
1
  import { relative } from "path";
2
+ import { readFileSync } from "fs";
2
3
  import { readStdinJson } from "../core/stdin";
3
4
  import { sessionPath, actionLogShardPath } from "../core/paths";
4
5
  import { safeReadJson, atomicWriteJson } from "../core/fs-utils";
5
6
  import { createSessionState, isSessionState, recordRead } from "../core/session";
6
7
  import { FileIndexRepo } from "../repositories/file-index-repo";
7
8
  import { estimateTokens, isBinaryFile } from "../core/token-estimate";
9
+ import { extractDescription } from "../core/description";
8
10
  import { createActionLogWriter } from "../core/action-log";
9
11
  import { getOrCreateDeviceId } from "../core/device";
10
12
  import type { SessionState } from "../types/session";
11
- import type { IndexLookup } from "../types/file-index";
13
+ import type { FileIndexEntry, IndexLookup } from "../types/file-index";
12
14
  import type { PostToolUseInput } from "../types/hook-input";
13
15
 
14
16
  export interface PostReadResult {
15
17
  estimatedTokens: number;
16
18
  indexHit: boolean;
17
19
  source: "content" | "index-fallback" | "none";
20
+ // Populated when content was available and the file was not already in
21
+ // the index — lets the caller seed the index lazily so that read-only
22
+ // browsing sessions don't accumulate zero index hits.
23
+ indexEntry: FileIndexEntry | null;
18
24
  }
19
25
 
20
26
  export function analyzePostRead(
@@ -25,16 +31,42 @@ export function analyzePostRead(
25
31
  // Binary file — skip token estimation
26
32
  if (isBinaryFile(filePath, content ?? undefined)) {
27
33
  const entry = index ? index.lookupEntry(filePath) : null;
28
- return { estimatedTokens: 0, indexHit: !!entry, source: "none" };
34
+ return {
35
+ estimatedTokens: 0,
36
+ indexHit: !!entry,
37
+ source: "none",
38
+ indexEntry: null,
39
+ };
29
40
  }
30
41
 
31
42
  // Content available — estimate from actual content
32
43
  if (content !== null && content.length > 0) {
33
44
  const entry = index ? index.lookupEntry(filePath) : null;
45
+ const tokens = estimateTokens(content, filePath);
46
+ // On miss, build a seed entry so the index grows from reads, not just
47
+ // writes and scans. Description failures must never throw out the read.
48
+ let indexEntry: FileIndexEntry | null = null;
49
+ if (!entry) {
50
+ let description = "";
51
+ try {
52
+ description = extractDescription(filePath, content);
53
+ } catch {
54
+ description = "";
55
+ }
56
+ const now = new Date().toISOString();
57
+ indexEntry = {
58
+ filePath,
59
+ description,
60
+ estimatedTokens: tokens,
61
+ lastModified: now,
62
+ lastIndexed: now,
63
+ };
64
+ }
34
65
  return {
35
- estimatedTokens: estimateTokens(content, filePath),
66
+ estimatedTokens: tokens,
36
67
  indexHit: !!entry,
37
68
  source: "content",
69
+ indexEntry,
38
70
  };
39
71
  }
40
72
 
@@ -46,11 +78,17 @@ export function analyzePostRead(
46
78
  estimatedTokens: entry.estimatedTokens,
47
79
  indexHit: true,
48
80
  source: "index-fallback",
81
+ indexEntry: null,
49
82
  };
50
83
  }
51
84
  }
52
85
 
53
- return { estimatedTokens: 0, indexHit: false, source: "none" };
86
+ return {
87
+ estimatedTokens: 0,
88
+ indexHit: false,
89
+ source: "none",
90
+ indexEntry: null,
91
+ };
54
92
  }
55
93
 
56
94
  function isPostToolUseInput(value: unknown): value is PostToolUseInput {
@@ -61,9 +99,32 @@ function isPostToolUseInput(value: unknown): value is PostToolUseInput {
61
99
  return true;
62
100
  }
63
101
 
64
- function extractContent(input: PostToolUseInput): string | null {
65
- if (!input.tool_output) return null;
66
- if (typeof input.tool_output.content === "string") {
102
+ // Pull file content out of the PostToolUse payload. Claude Code has shipped
103
+ // at least two payload shapes for the Read tool:
104
+ // • legacy: `tool_output.content` is a plain string
105
+ // • current: `tool_response` carries the content — either as a string, as
106
+ // a Content[]-style array (`{ type: "text", text: "..." }`), or nested
107
+ // under `tool_response.file.content`
108
+ // We accept any of them so a hook contract drift can't silently zero out
109
+ // token estimation again.
110
+ export function extractContent(input: PostToolUseInput): string | null {
111
+ // Current shape — tool_response
112
+ const tr = input.tool_response;
113
+ if (tr) {
114
+ if (typeof tr.content === "string") return tr.content;
115
+ if (Array.isArray(tr.content)) {
116
+ const parts = tr.content
117
+ .map((p) => (p && typeof p.text === "string" ? p.text : ""))
118
+ .filter((s) => s.length > 0);
119
+ if (parts.length > 0) return parts.join("");
120
+ }
121
+ if (tr.file && typeof tr.file.content === "string") {
122
+ return tr.file.content;
123
+ }
124
+ if (typeof tr.text === "string") return tr.text;
125
+ }
126
+ // Legacy shape — tool_output
127
+ if (input.tool_output && typeof input.tool_output.content === "string") {
67
128
  return input.tool_output.content;
68
129
  }
69
130
  return null;
@@ -92,11 +153,35 @@ export async function postRead(cwd: string): Promise<void> {
92
153
  // File index repository — one-key lookup, no whole-index load.
93
154
  const repo = FileIndexRepo.for(cwd);
94
155
 
95
- // Extract content from tool output
96
- const content = extractContent(input);
156
+ // Primary path: read content from disk by file path. This is the
157
+ // cleanest source because it doesn't depend on Claude Code's evolving
158
+ // hook payload schema (which has silently dropped `tool_output.content`
159
+ // in favor of nested `tool_response` shapes, breaking token
160
+ // measurement). Mirrors post-write.ts's approach.
161
+ let content: string | null = null;
162
+ try {
163
+ content = readFileSync(absolutePath, "utf-8");
164
+ } catch {
165
+ // File unreadable (permissions, deleted between read and hook) —
166
+ // fall back to whatever the payload carries.
167
+ }
168
+ if (content === null) {
169
+ content = extractContent(input);
170
+ }
97
171
 
98
172
  const result = analyzePostRead(filePath, content, repo);
99
173
 
174
+ // Seed the file index on a miss. Read-only browsing sessions otherwise
175
+ // accumulate zero index hits because the index only grows via
176
+ // `mink scan` (capped) or post-write.
177
+ if (result.indexEntry) {
178
+ try {
179
+ repo.upsert(result.indexEntry);
180
+ } catch {
181
+ // Never crash the hook over an index upsert failure.
182
+ }
183
+ }
184
+
100
185
  // Record the read in session state
101
186
  recordRead(state, filePath, result.estimatedTokens, result.indexHit);
102
187
 
@@ -24,10 +24,13 @@ export function generateKnowledgeMarkdown(
24
24
  ): string {
25
25
  const parts: string[] = [];
26
26
 
27
+ // Prompt-cache stability: keep the title and stable summary line (counts
28
+ // only — no timestamps) at the top. The volatile `generatedAt` timestamp
29
+ // lives in the footer so regeneration doesn't bust LLM prefix caches.
27
30
  parts.push(`# Framework Advisor Knowledge Base`);
28
31
  parts.push("");
29
32
  parts.push(
30
- `> Generated: ${k.generatedAt} | Version: ${k.version} | Frameworks: ${k.frameworks.length}`
33
+ `> Version: ${k.version} | Frameworks: ${k.frameworks.length}`
31
34
  );
32
35
  parts.push("");
33
36
 
@@ -111,6 +114,13 @@ export function generateKnowledgeMarkdown(
111
114
  }
112
115
  }
113
116
 
117
+ // ── Footer (volatile — must stay at end for prompt-cache stability) ──
118
+ parts.push(`---`);
119
+ parts.push(``);
120
+ parts.push(`<!-- mink:footer (volatile — keep at end of file) -->`);
121
+ parts.push(`> Generated: ${k.generatedAt}`);
122
+ parts.push(``);
123
+
114
124
  return parts.join("\n");
115
125
  }
116
126