qodewk 0.9.2 → 0.10.0

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 (2) hide show
  1. package/dist/index.cjs +654 -219
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1157,8 +1157,8 @@ var require_command = __commonJS({
1157
1157
  "use strict";
1158
1158
  var EventEmitter2 = require("events").EventEmitter;
1159
1159
  var childProcess = require("child_process");
1160
- var path16 = require("path");
1161
- var fs15 = require("fs");
1160
+ var path17 = require("path");
1161
+ var fs16 = require("fs");
1162
1162
  var process3 = require("process");
1163
1163
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1164
1164
  var { CommanderError: CommanderError2 } = require_error();
@@ -2139,7 +2139,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2139
2139
  * @param {string} subcommandName
2140
2140
  */
2141
2141
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2142
- if (fs15.existsSync(executableFile)) return;
2142
+ if (fs16.existsSync(executableFile)) return;
2143
2143
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2144
2144
  const executableMissing = `'${executableFile}' does not exist
2145
2145
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2157,11 +2157,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2157
2157
  let launchWithNode = false;
2158
2158
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2159
2159
  function findFile(baseDir, baseName) {
2160
- const localBin = path16.resolve(baseDir, baseName);
2161
- if (fs15.existsSync(localBin)) return localBin;
2162
- if (sourceExt.includes(path16.extname(baseName))) return void 0;
2160
+ const localBin = path17.resolve(baseDir, baseName);
2161
+ if (fs16.existsSync(localBin)) return localBin;
2162
+ if (sourceExt.includes(path17.extname(baseName))) return void 0;
2163
2163
  const foundExt = sourceExt.find(
2164
- (ext) => fs15.existsSync(`${localBin}${ext}`)
2164
+ (ext) => fs16.existsSync(`${localBin}${ext}`)
2165
2165
  );
2166
2166
  if (foundExt) return `${localBin}${foundExt}`;
2167
2167
  return void 0;
@@ -2173,21 +2173,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2173
2173
  if (this._scriptPath) {
2174
2174
  let resolvedScriptPath;
2175
2175
  try {
2176
- resolvedScriptPath = fs15.realpathSync(this._scriptPath);
2176
+ resolvedScriptPath = fs16.realpathSync(this._scriptPath);
2177
2177
  } catch {
2178
2178
  resolvedScriptPath = this._scriptPath;
2179
2179
  }
2180
- executableDir = path16.resolve(
2181
- path16.dirname(resolvedScriptPath),
2180
+ executableDir = path17.resolve(
2181
+ path17.dirname(resolvedScriptPath),
2182
2182
  executableDir
2183
2183
  );
2184
2184
  }
2185
2185
  if (executableDir) {
2186
2186
  let localFile = findFile(executableDir, executableFile);
2187
2187
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2188
- const legacyName = path16.basename(
2188
+ const legacyName = path17.basename(
2189
2189
  this._scriptPath,
2190
- path16.extname(this._scriptPath)
2190
+ path17.extname(this._scriptPath)
2191
2191
  );
2192
2192
  if (legacyName !== this._name) {
2193
2193
  localFile = findFile(
@@ -2198,7 +2198,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2198
2198
  }
2199
2199
  executableFile = localFile || executableFile;
2200
2200
  }
2201
- launchWithNode = sourceExt.includes(path16.extname(executableFile));
2201
+ launchWithNode = sourceExt.includes(path17.extname(executableFile));
2202
2202
  let proc;
2203
2203
  if (process3.platform !== "win32") {
2204
2204
  if (launchWithNode) {
@@ -3045,7 +3045,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3045
3045
  * @return {Command}
3046
3046
  */
3047
3047
  nameFromFilename(filename) {
3048
- this._name = path16.basename(filename, path16.extname(filename));
3048
+ this._name = path17.basename(filename, path17.extname(filename));
3049
3049
  return this;
3050
3050
  }
3051
3051
  /**
@@ -3059,9 +3059,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3059
3059
  * @param {string} [path]
3060
3060
  * @return {(string|null|Command)}
3061
3061
  */
3062
- executableDir(path17) {
3063
- if (path17 === void 0) return this._executableDir;
3064
- this._executableDir = path17;
3062
+ executableDir(path18) {
3063
+ if (path18 === void 0) return this._executableDir;
3064
+ this._executableDir = path18;
3065
3065
  return this;
3066
3066
  }
3067
3067
  /**
@@ -4207,10 +4207,10 @@ var require_src2 = __commonJS({
4207
4207
  var fs_1 = require("fs");
4208
4208
  var debug_1 = __importDefault(require_src());
4209
4209
  var log = debug_1.default("@kwsites/file-exists");
4210
- function check(path16, isFile, isDirectory) {
4211
- log(`checking %s`, path16);
4210
+ function check(path17, isFile, isDirectory) {
4211
+ log(`checking %s`, path17);
4212
4212
  try {
4213
- const stat = fs_1.statSync(path16);
4213
+ const stat = fs_1.statSync(path17);
4214
4214
  if (stat.isFile() && isFile) {
4215
4215
  log(`[OK] path represents a file`);
4216
4216
  return true;
@@ -4230,8 +4230,8 @@ var require_src2 = __commonJS({
4230
4230
  throw e;
4231
4231
  }
4232
4232
  }
4233
- function exists2(path16, type = exports2.READABLE) {
4234
- return check(path16, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
4233
+ function exists2(path17, type = exports2.READABLE) {
4234
+ return check(path17, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
4235
4235
  }
4236
4236
  exports2.exists = exists2;
4237
4237
  exports2.FILE = 1;
@@ -4842,8 +4842,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
4842
4842
  function forEachLineWithContent(input, callback) {
4843
4843
  return toLinesWithContent(input, true).map((line) => callback(line));
4844
4844
  }
4845
- function folderExists(path16) {
4846
- return (0, import_file_exists.exists)(path16, import_file_exists.FOLDER);
4845
+ function folderExists(path17) {
4846
+ return (0, import_file_exists.exists)(path17, import_file_exists.FOLDER);
4847
4847
  }
4848
4848
  function append(target, item) {
4849
4849
  if (Array.isArray(target)) {
@@ -5034,8 +5034,8 @@ function checkIsRepoRootTask() {
5034
5034
  commands,
5035
5035
  format: "utf-8",
5036
5036
  onError,
5037
- parser(path16) {
5038
- return /^\.(git)?$/.test(path16.trim());
5037
+ parser(path17) {
5038
+ return /^\.(git)?$/.test(path17.trim());
5039
5039
  }
5040
5040
  };
5041
5041
  }
@@ -5291,11 +5291,11 @@ function parseGrep(grep) {
5291
5291
  const paths = /* @__PURE__ */ new Set();
5292
5292
  const results = {};
5293
5293
  forEachLineWithContent(grep, (input) => {
5294
- const [path16, line, preview] = input.split(NULL);
5295
- paths.add(path16);
5296
- (results[path16] = results[path16] || []).push({
5294
+ const [path17, line, preview] = input.split(NULL);
5295
+ paths.add(path17);
5296
+ (results[path17] = results[path17] || []).push({
5297
5297
  line: asNumber(line),
5298
- path: path16,
5298
+ path: path17,
5299
5299
  preview
5300
5300
  });
5301
5301
  });
@@ -5592,14 +5592,14 @@ function hashObjectTask(filePath, write) {
5592
5592
  }
5593
5593
  return straightThroughStringTask(commands, true);
5594
5594
  }
5595
- function parseInit(bare, path16, text) {
5595
+ function parseInit(bare, path17, text) {
5596
5596
  const response = String(text).trim();
5597
5597
  let result;
5598
5598
  if (result = initResponseRegex.exec(response)) {
5599
- return new InitSummary(bare, path16, false, result[1]);
5599
+ return new InitSummary(bare, path17, false, result[1]);
5600
5600
  }
5601
5601
  if (result = reInitResponseRegex.exec(response)) {
5602
- return new InitSummary(bare, path16, true, result[1]);
5602
+ return new InitSummary(bare, path17, true, result[1]);
5603
5603
  }
5604
5604
  let gitDir = "";
5605
5605
  const tokens = response.split(" ");
@@ -5610,12 +5610,12 @@ function parseInit(bare, path16, text) {
5610
5610
  break;
5611
5611
  }
5612
5612
  }
5613
- return new InitSummary(bare, path16, /^re/i.test(response), gitDir);
5613
+ return new InitSummary(bare, path17, /^re/i.test(response), gitDir);
5614
5614
  }
5615
5615
  function hasBareCommand(command) {
5616
5616
  return command.includes(bareCommand);
5617
5617
  }
5618
- function initTask(bare = false, path16, customArgs) {
5618
+ function initTask(bare = false, path17, customArgs) {
5619
5619
  const commands = ["init", ...customArgs];
5620
5620
  if (bare && !hasBareCommand(commands)) {
5621
5621
  commands.splice(1, 0, bareCommand);
@@ -5624,7 +5624,7 @@ function initTask(bare = false, path16, customArgs) {
5624
5624
  commands,
5625
5625
  format: "utf-8",
5626
5626
  parser(text) {
5627
- return parseInit(commands.includes("--bare"), path16, text);
5627
+ return parseInit(commands.includes("--bare"), path17, text);
5628
5628
  }
5629
5629
  };
5630
5630
  }
@@ -5903,14 +5903,14 @@ function splitLine(result, lineStr) {
5903
5903
  default:
5904
5904
  return;
5905
5905
  }
5906
- function data(index, workingDir, path16) {
5906
+ function data(index, workingDir, path17) {
5907
5907
  const raw = `${index}${workingDir}`;
5908
5908
  const handler = parsers6.get(raw);
5909
5909
  if (handler) {
5910
- handler(result, path16);
5910
+ handler(result, path17);
5911
5911
  }
5912
5912
  if (raw !== "##" && raw !== "!!") {
5913
- result.files.push(new FileStatusSummary(path16, index, workingDir));
5913
+ result.files.push(new FileStatusSummary(path17, index, workingDir));
5914
5914
  }
5915
5915
  }
5916
5916
  }
@@ -6097,8 +6097,8 @@ function deleteBranchTask(branch, forceDelete = false) {
6097
6097
  return task;
6098
6098
  }
6099
6099
  function toPath(input) {
6100
- const path16 = input.trim().replace(/^["']|["']$/g, "");
6101
- return path16 && (0, import_node_path.normalize)(path16);
6100
+ const path17 = input.trim().replace(/^["']|["']$/g, "");
6101
+ return path17 && (0, import_node_path.normalize)(path17);
6102
6102
  }
6103
6103
  function checkIgnoreTask(paths) {
6104
6104
  return {
@@ -6237,8 +6237,8 @@ function stashListTask(opt = {}, customArgs) {
6237
6237
  parser: parser4
6238
6238
  };
6239
6239
  }
6240
- function addSubModuleTask(repo, path16) {
6241
- return subModuleTask(["add", repo, path16]);
6240
+ function addSubModuleTask(repo, path17) {
6241
+ return subModuleTask(["add", repo, path17]);
6242
6242
  }
6243
6243
  function initSubModuleTask(customArgs) {
6244
6244
  return subModuleTask(["init", ...customArgs]);
@@ -6646,12 +6646,12 @@ function gitP(...args) {
6646
6646
  );
6647
6647
  }
6648
6648
  return chain.then(function() {
6649
- return new Promise(function(resolve5, reject) {
6649
+ return new Promise(function(resolve6, reject) {
6650
6650
  const callback = (err, result) => {
6651
6651
  if (err) {
6652
6652
  return reject(toError(err));
6653
6653
  }
6654
- resolve5(result);
6654
+ resolve6(result);
6655
6655
  };
6656
6656
  args2.push(callback);
6657
6657
  git2[fn].apply(git2, args2);
@@ -7570,9 +7570,9 @@ var init_esm = __esm({
7570
7570
  "src/lib/responses/InitSummary.ts"() {
7571
7571
  "use strict";
7572
7572
  InitSummary = class {
7573
- constructor(bare, path16, existing, gitDir) {
7573
+ constructor(bare, path17, existing, gitDir) {
7574
7574
  this.bare = bare;
7575
- this.path = path16;
7575
+ this.path = path17;
7576
7576
  this.existing = existing;
7577
7577
  this.gitDir = gitDir;
7578
7578
  }
@@ -8105,12 +8105,12 @@ var init_esm = __esm({
8105
8105
  "use strict";
8106
8106
  fromPathRegex = /^(.+)\0(.+)$/;
8107
8107
  FileStatusSummary = class {
8108
- constructor(path16, index, working_dir) {
8109
- this.path = path16;
8108
+ constructor(path17, index, working_dir) {
8109
+ this.path = path17;
8110
8110
  this.index = index;
8111
8111
  this.working_dir = working_dir;
8112
8112
  if (index === "R" || working_dir === "R") {
8113
- const detail = fromPathRegex.exec(path16) || [null, path16, path16];
8113
+ const detail = fromPathRegex.exec(path17) || [null, path17, path17];
8114
8114
  this.from = detail[2] || "";
8115
8115
  this.path = detail[1] || "";
8116
8116
  }
@@ -8372,9 +8372,9 @@ var init_esm = __esm({
8372
8372
  next
8373
8373
  );
8374
8374
  }
8375
- hashObject(path16, write) {
8375
+ hashObject(path17, write) {
8376
8376
  return this._runTask(
8377
- hashObjectTask(path16, write === true),
8377
+ hashObjectTask(path17, write === true),
8378
8378
  trailingFunctionArgument(arguments)
8379
8379
  );
8380
8380
  }
@@ -9011,8 +9011,8 @@ var init_esm = __esm({
9011
9011
  }
9012
9012
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
9013
9013
  };
9014
- Git2.prototype.submoduleAdd = function(repo, path16, then) {
9015
- return this._runTask(addSubModuleTask2(repo, path16), trailingFunctionArgument2(arguments));
9014
+ Git2.prototype.submoduleAdd = function(repo, path17, then) {
9015
+ return this._runTask(addSubModuleTask2(repo, path17), trailingFunctionArgument2(arguments));
9016
9016
  };
9017
9017
  Git2.prototype.submoduleUpdate = function(args, then) {
9018
9018
  return this._runTask(
@@ -9318,8 +9318,8 @@ var init_esm = __esm({
9318
9318
  });
9319
9319
 
9320
9320
  // src/index.ts
9321
- var fs14 = __toESM(require("fs"), 1);
9322
- var path15 = __toESM(require("path"), 1);
9321
+ var fs15 = __toESM(require("fs"), 1);
9322
+ var path16 = __toESM(require("path"), 1);
9323
9323
 
9324
9324
  // ../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/esm.mjs
9325
9325
  var import_index = __toESM(require_commander(), 1);
@@ -9339,7 +9339,7 @@ var {
9339
9339
  } = import_index.default;
9340
9340
 
9341
9341
  // src/index.ts
9342
- var import_picocolors3 = __toESM(require_picocolors(), 1);
9342
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
9343
9343
 
9344
9344
  // ../core/dist/index.js
9345
9345
  var crypto2 = __toESM(require("crypto"), 1);
@@ -9822,8 +9822,8 @@ function getErrorMap() {
9822
9822
 
9823
9823
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
9824
9824
  var makeIssue = (params) => {
9825
- const { data, path: path16, errorMaps, issueData } = params;
9826
- const fullPath = [...path16, ...issueData.path || []];
9825
+ const { data, path: path17, errorMaps, issueData } = params;
9826
+ const fullPath = [...path17, ...issueData.path || []];
9827
9827
  const fullIssue = {
9828
9828
  ...issueData,
9829
9829
  path: fullPath
@@ -9939,11 +9939,11 @@ var errorUtil;
9939
9939
 
9940
9940
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
9941
9941
  var ParseInputLazyPath = class {
9942
- constructor(parent, value, path16, key) {
9942
+ constructor(parent, value, path17, key) {
9943
9943
  this._cachedPath = [];
9944
9944
  this.parent = parent;
9945
9945
  this.data = value;
9946
- this._path = path16;
9946
+ this._path = path17;
9947
9947
  this._key = key;
9948
9948
  }
9949
9949
  get path() {
@@ -14496,8 +14496,8 @@ function harvestAntigravityFootprints(repoPath, sinceDate) {
14496
14496
  const DatabaseSync2 = getSqliteDatabase();
14497
14497
  if (!DatabaseSync2)
14498
14498
  return footprints;
14499
- const homedir11 = os3.homedir();
14500
- const antigravityDir = process.env.ANTIGRAVITY_APP_DATA_DIR || path3.join(homedir11, ".gemini", "antigravity");
14499
+ const homedir12 = os3.homedir();
14500
+ const antigravityDir = process.env.ANTIGRAVITY_APP_DATA_DIR || path3.join(homedir12, ".gemini", "antigravity");
14501
14501
  const dbPath = path3.join(antigravityDir, "conversation_summaries.db");
14502
14502
  if (!fs2.existsSync(dbPath)) {
14503
14503
  return footprints;
@@ -14599,8 +14599,8 @@ var path4 = __toESM(require("path"), 1);
14599
14599
  var os4 = __toESM(require("os"), 1);
14600
14600
  function harvestClaudeFootprints(repoPath, projectAlias, sinceDate) {
14601
14601
  const footprints = [];
14602
- const homedir11 = os4.homedir();
14603
- const claudeProjectsDir = path4.join(homedir11, ".claude", "projects");
14602
+ const homedir12 = os4.homedir();
14603
+ const claudeProjectsDir = path4.join(homedir12, ".claude", "projects");
14604
14604
  if (!fs3.existsSync(claudeProjectsDir)) {
14605
14605
  return footprints;
14606
14606
  }
@@ -15175,9 +15175,9 @@ function resolveClineStorageDirs(customDir) {
15175
15175
  if (process.env.ROO_STORAGE_DIR && fs7.existsSync(process.env.ROO_STORAGE_DIR)) {
15176
15176
  dirs.push(process.env.ROO_STORAGE_DIR);
15177
15177
  }
15178
- const homedir11 = os7.homedir();
15178
+ const homedir12 = os7.homedir();
15179
15179
  if (process.platform === "win32") {
15180
- const appData = process.env.APPDATA || path8.join(homedir11, "AppData", "Roaming");
15180
+ const appData = process.env.APPDATA || path8.join(homedir12, "AppData", "Roaming");
15181
15181
  const ideRoots = ["Code", "Code - Insiders", "Cursor", "Windsurf"];
15182
15182
  const extensionIds = [
15183
15183
  "saoudrizwan.claude-dev",
@@ -15191,7 +15191,7 @@ function resolveClineStorageDirs(customDir) {
15191
15191
  }
15192
15192
  }
15193
15193
  } else if (process.platform === "darwin") {
15194
- const appSupport = path8.join(homedir11, "Library", "Application Support");
15194
+ const appSupport = path8.join(homedir12, "Library", "Application Support");
15195
15195
  const ideRoots = ["Code", "Code - Insiders", "Cursor", "Windsurf"];
15196
15196
  const extensionIds = [
15197
15197
  "saoudrizwan.claude-dev",
@@ -15205,7 +15205,7 @@ function resolveClineStorageDirs(customDir) {
15205
15205
  }
15206
15206
  }
15207
15207
  } else {
15208
- const configRoot = process.env.XDG_CONFIG_HOME || path8.join(homedir11, ".config");
15208
+ const configRoot = process.env.XDG_CONFIG_HOME || path8.join(homedir12, ".config");
15209
15209
  const ideRoots = ["Code", "Code - Insiders", "Cursor", "Windsurf"];
15210
15210
  const extensionIds = [
15211
15211
  "saoudrizwan.claude-dev",
@@ -16063,6 +16063,289 @@ function formatMarkdownReceipt(receipt, publicUrl) {
16063
16063
  return lines.join("\n");
16064
16064
  }
16065
16065
 
16066
+ // ../core/dist/doctor.js
16067
+ var fs12 = __toESM(require("fs"), 1);
16068
+ var path13 = __toESM(require("path"), 1);
16069
+ var os12 = __toESM(require("os"), 1);
16070
+ var import_node_child_process = require("child_process");
16071
+ var HOOK_MARKER_BEGIN = "# --- BEGIN QODEWK HOOK ---";
16072
+ function auditAgentEnvironments(repoPath = process.cwd()) {
16073
+ const homedir12 = os12.homedir();
16074
+ const results = [];
16075
+ const agEnv = process.env.ANTIGRAVITY_APP_DATA_DIR;
16076
+ const agHome = path13.join(homedir12, ".gemini", "antigravity");
16077
+ const agPath = agEnv && fs12.existsSync(agEnv) ? agEnv : fs12.existsSync(agHome) ? agHome : void 0;
16078
+ results.push({
16079
+ id: "antigravity",
16080
+ name: "Google Antigravity",
16081
+ status: agPath ? "active" : "not_detected",
16082
+ path: agPath,
16083
+ details: agPath ? "Antigravity brain telemetry detected" : "No active session brain found"
16084
+ });
16085
+ const claudeProjects = path13.join(homedir12, ".claude", "projects");
16086
+ const claudeExists = fs12.existsSync(claudeProjects);
16087
+ results.push({
16088
+ id: "claude",
16089
+ name: "Claude Code CLI",
16090
+ status: claudeExists ? "active" : "not_detected",
16091
+ path: claudeExists ? claudeProjects : void 0,
16092
+ details: claudeExists ? "Claude project session directory found" : "No ~/.claude/projects directory found"
16093
+ });
16094
+ let cursorPath;
16095
+ if (process.platform === "win32") {
16096
+ const c3 = path13.join(process.env.APPDATA || path13.join(homedir12, "AppData", "Roaming"), "Cursor", "User", "workspaceStorage");
16097
+ if (fs12.existsSync(c3))
16098
+ cursorPath = c3;
16099
+ } else if (process.platform === "darwin") {
16100
+ const c3 = path13.join(homedir12, "Library", "Application Support", "Cursor", "User", "workspaceStorage");
16101
+ if (fs12.existsSync(c3))
16102
+ cursorPath = c3;
16103
+ } else {
16104
+ const c3 = path13.join(homedir12, ".config", "Cursor", "User", "workspaceStorage");
16105
+ if (fs12.existsSync(c3))
16106
+ cursorPath = c3;
16107
+ }
16108
+ results.push({
16109
+ id: "cursor",
16110
+ name: "Cursor IDE",
16111
+ status: cursorPath ? "active" : "not_detected",
16112
+ path: cursorPath,
16113
+ details: cursorPath ? "Cursor workspace storage detected" : "Cursor IDE storage not detected"
16114
+ });
16115
+ let windsurfPath;
16116
+ if (process.platform === "win32") {
16117
+ const w = path13.join(process.env.APPDATA || path13.join(homedir12, "AppData", "Roaming"), "Windsurf", "User", "workspaceStorage");
16118
+ if (fs12.existsSync(w))
16119
+ windsurfPath = w;
16120
+ } else if (process.platform === "darwin") {
16121
+ const w = path13.join(homedir12, "Library", "Application Support", "Windsurf", "User", "workspaceStorage");
16122
+ if (fs12.existsSync(w))
16123
+ windsurfPath = w;
16124
+ } else {
16125
+ const w = path13.join(homedir12, ".config", "Windsurf", "User", "workspaceStorage");
16126
+ if (fs12.existsSync(w))
16127
+ windsurfPath = w;
16128
+ }
16129
+ results.push({
16130
+ id: "windsurf",
16131
+ name: "Windsurf IDE",
16132
+ status: windsurfPath ? "active" : "not_detected",
16133
+ path: windsurfPath,
16134
+ details: windsurfPath ? "Cascade session workspace storage detected" : "Windsurf IDE storage not detected"
16135
+ });
16136
+ let clinePath;
16137
+ const localCline = path13.join(repoPath, ".cline");
16138
+ const localRoo = path13.join(repoPath, ".roo");
16139
+ if (fs12.existsSync(localCline)) {
16140
+ clinePath = localCline;
16141
+ } else if (fs12.existsSync(localRoo)) {
16142
+ clinePath = localRoo;
16143
+ } else {
16144
+ const appData = process.platform === "win32" ? process.env.APPDATA || path13.join(homedir12, "AppData", "Roaming") : process.platform === "darwin" ? path13.join(homedir12, "Library", "Application Support") : path13.join(homedir12, ".config");
16145
+ const clineExt = path13.join(appData, "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "tasks");
16146
+ const rooExt = path13.join(appData, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline", "tasks");
16147
+ if (fs12.existsSync(clineExt))
16148
+ clinePath = clineExt;
16149
+ else if (fs12.existsSync(rooExt))
16150
+ clinePath = rooExt;
16151
+ }
16152
+ results.push({
16153
+ id: "cline",
16154
+ name: "Cline / Roo Code",
16155
+ status: clinePath ? "active" : "not_detected",
16156
+ path: clinePath,
16157
+ details: clinePath ? "VS Code autonomous agent task storage detected" : "No active Cline or Roo Code tasks found"
16158
+ });
16159
+ const aiderPath = path13.join(repoPath, ".aider.chat.history.md");
16160
+ const aiderExists = fs12.existsSync(aiderPath);
16161
+ results.push({
16162
+ id: "aider",
16163
+ name: "Aider CLI",
16164
+ status: aiderExists ? "active" : "not_detected",
16165
+ path: aiderExists ? aiderPath : void 0,
16166
+ details: aiderExists ? "Repository pairing transcript found" : "No repo-local .aider.chat.history.md found"
16167
+ });
16168
+ const localOpenCode = path13.join(repoPath, ".opencode");
16169
+ const homeOpenCode = path13.join(homedir12, ".opencode", "sessions");
16170
+ const opencodePath = fs12.existsSync(localOpenCode) ? localOpenCode : fs12.existsSync(homeOpenCode) ? homeOpenCode : void 0;
16171
+ results.push({
16172
+ id: "opencode",
16173
+ name: "OpenCode CLI",
16174
+ status: opencodePath ? "active" : "not_detected",
16175
+ path: opencodePath,
16176
+ details: opencodePath ? "OpenCode session history detected" : "No OpenCode session files found"
16177
+ });
16178
+ return results;
16179
+ }
16180
+ function resolveHooksDir(cwd = process.cwd()) {
16181
+ const gitPath = path13.join(cwd, ".git");
16182
+ if (!fs12.existsSync(gitPath))
16183
+ return null;
16184
+ try {
16185
+ const stat = fs12.statSync(gitPath);
16186
+ if (stat.isDirectory()) {
16187
+ return path13.join(gitPath, "hooks");
16188
+ }
16189
+ const content = fs12.readFileSync(gitPath, "utf-8");
16190
+ const match = content.match(/gitdir:\s*(.+)/i);
16191
+ if (!match?.[1])
16192
+ return null;
16193
+ let gitDir = match[1].trim();
16194
+ if (!path13.isAbsolute(gitDir)) {
16195
+ gitDir = path13.resolve(cwd, gitDir);
16196
+ }
16197
+ const commonFile = path13.join(gitDir, "commondir");
16198
+ if (fs12.existsSync(commonFile)) {
16199
+ let common = fs12.readFileSync(commonFile, "utf-8").trim();
16200
+ if (!path13.isAbsolute(common))
16201
+ common = path13.resolve(gitDir, common);
16202
+ return path13.join(common, "hooks");
16203
+ } else if (path13.basename(path13.dirname(gitDir)) === "worktrees") {
16204
+ return path13.join(path13.dirname(path13.dirname(gitDir)), "hooks");
16205
+ }
16206
+ return path13.join(gitDir, "hooks");
16207
+ } catch {
16208
+ return null;
16209
+ }
16210
+ }
16211
+ function auditGitHooks(repoPath = process.cwd()) {
16212
+ const hooksDir = resolveHooksDir(repoPath);
16213
+ if (!hooksDir || !fs12.existsSync(hooksDir)) {
16214
+ return {
16215
+ isGitRepo: Boolean(hooksDir),
16216
+ hooksDir,
16217
+ postCommitInstalled: false,
16218
+ postRewriteInstalled: false,
16219
+ markerFound: false
16220
+ };
16221
+ }
16222
+ const postCommit = path13.join(hooksDir, "post-commit");
16223
+ const postRewrite = path13.join(hooksDir, "post-rewrite");
16224
+ const hasMarker = (filePath) => {
16225
+ if (!fs12.existsSync(filePath))
16226
+ return false;
16227
+ try {
16228
+ return fs12.readFileSync(filePath, "utf-8").includes(HOOK_MARKER_BEGIN);
16229
+ } catch {
16230
+ return false;
16231
+ }
16232
+ };
16233
+ const commitOk = hasMarker(postCommit);
16234
+ const rewriteOk = hasMarker(postRewrite);
16235
+ return {
16236
+ isGitRepo: true,
16237
+ hooksDir,
16238
+ postCommitInstalled: commitOk,
16239
+ postRewriteInstalled: rewriteOk,
16240
+ markerFound: commitOk || rewriteOk
16241
+ };
16242
+ }
16243
+ async function benchmarkHookLatency(trials = 3) {
16244
+ const samples = [];
16245
+ for (let i2 = 0; i2 < trials; i2++) {
16246
+ const start = process.hrtime.bigint();
16247
+ try {
16248
+ const child = (0, import_node_child_process.spawn)(process.execPath, ["-e", "process.exit(0)"], {
16249
+ detached: true,
16250
+ stdio: "ignore",
16251
+ windowsHide: true
16252
+ });
16253
+ child.unref();
16254
+ const end = process.hrtime.bigint();
16255
+ const diffMs = Number(end - start) / 1e6;
16256
+ samples.push(diffMs);
16257
+ } catch {
16258
+ samples.push(99);
16259
+ }
16260
+ }
16261
+ const minMs = samples.length > 0 ? Math.min(...samples) : 99;
16262
+ const roundedMs = Number(minMs.toFixed(2));
16263
+ if (roundedMs < 5) {
16264
+ return {
16265
+ executionMs: roundedMs,
16266
+ passedInvariant: true,
16267
+ status: "pass",
16268
+ message: `Detached in ${roundedMs} ms (passed < 5ms invariant)`
16269
+ };
16270
+ } else if (roundedMs < 45) {
16271
+ return {
16272
+ executionMs: roundedMs,
16273
+ passedInvariant: true,
16274
+ status: "warn",
16275
+ message: `Detached in ${roundedMs} ms (acceptable cold spawn, target is < 5ms)`
16276
+ };
16277
+ } else {
16278
+ return {
16279
+ executionMs: roundedMs,
16280
+ passedInvariant: false,
16281
+ status: "fail",
16282
+ message: `Detached in ${roundedMs} ms (exceeded non-blocking threshold)`
16283
+ };
16284
+ }
16285
+ }
16286
+ function auditStorageHealth() {
16287
+ const dbPath = path13.join(os12.homedir(), ".qodewk", "state.db");
16288
+ try {
16289
+ const db = new LocalStateDB();
16290
+ const stats = db.getStats();
16291
+ db.close();
16292
+ let sizeBytes = 0;
16293
+ if (fs12.existsSync(dbPath)) {
16294
+ sizeBytes = fs12.statSync(dbPath).size;
16295
+ }
16296
+ return {
16297
+ healthy: true,
16298
+ dbPath,
16299
+ receiptsCount: stats.totalReceipts,
16300
+ footprintsCount: stats.totalFootprints,
16301
+ sizeBytes
16302
+ };
16303
+ } catch {
16304
+ return {
16305
+ healthy: false,
16306
+ dbPath,
16307
+ receiptsCount: 0,
16308
+ footprintsCount: 0
16309
+ };
16310
+ }
16311
+ }
16312
+ function auditPricingSync() {
16313
+ const modelsCount = Object.keys(RATE_CARDS).length;
16314
+ const cachedJson = path13.join(os12.homedir(), ".qodewk", "pricing.json");
16315
+ const hasCached = fs12.existsSync(cachedJson);
16316
+ return {
16317
+ modelsLoaded: modelsCount,
16318
+ cachedRegistryPresent: hasCached,
16319
+ status: modelsCount > 0 ? "active" : "offline"
16320
+ };
16321
+ }
16322
+ async function runDiagnostics(options = {}) {
16323
+ const repoPath = options.repoPath || process.cwd();
16324
+ const agents = auditAgentEnvironments(repoPath);
16325
+ const hooks = auditGitHooks(repoPath);
16326
+ const benchmark = await benchmarkHookLatency();
16327
+ const storage = auditStorageHealth();
16328
+ const pricing = auditPricingSync();
16329
+ const activeAgents = agents.filter((a) => a.status === "active").length;
16330
+ let overallStatus = "healthy";
16331
+ if (!storage.healthy || !hooks.isGitRepo) {
16332
+ overallStatus = "error";
16333
+ } else if (!hooks.markerFound || activeAgents === 0) {
16334
+ overallStatus = "warning";
16335
+ }
16336
+ return {
16337
+ overallStatus,
16338
+ version: "0.10.0",
16339
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
16340
+ repoPath,
16341
+ agents,
16342
+ hooks,
16343
+ benchmark,
16344
+ storage,
16345
+ pricing
16346
+ };
16347
+ }
16348
+
16066
16349
  // ../core/dist/index.js
16067
16350
  function sanitizeReceiptForShare(receipt) {
16068
16351
  const sessions = receipt.ai.sessions?.map((s) => ({
@@ -16194,7 +16477,7 @@ async function generateReceipt(options = {}) {
16194
16477
  }
16195
16478
 
16196
16479
  // src/receipt-view.ts
16197
- var fs12 = __toESM(require("fs"), 1);
16480
+ var fs13 = __toESM(require("fs"), 1);
16198
16481
  var import_picocolors = __toESM(require_picocolors(), 1);
16199
16482
 
16200
16483
  // src/barcode.ts
@@ -16608,7 +16891,7 @@ async function outputReceipt(receipt, options, publicUrl) {
16608
16891
  }
16609
16892
  if (options.out) {
16610
16893
  const fileContent = format === "terminal" ? stripAnsi(content) : content;
16611
- fs12.writeFileSync(options.out, fileContent.endsWith("\n") ? fileContent : fileContent + "\n", "utf-8");
16894
+ fs13.writeFileSync(options.out, fileContent.endsWith("\n") ? fileContent : fileContent + "\n", "utf-8");
16612
16895
  console.log(import_picocolors.default.green(`\u2713 Receipt written to ${options.out}`));
16613
16896
  }
16614
16897
  if (!options.out) {
@@ -16621,12 +16904,12 @@ async function outputReceipt(receipt, options, publicUrl) {
16621
16904
  }
16622
16905
 
16623
16906
  // src/hooks.ts
16624
- var fs13 = __toESM(require("fs"), 1);
16625
- var path13 = __toESM(require("path"), 1);
16626
- var HOOK_MARKER_BEGIN = "# --- BEGIN QODEWK HOOK ---";
16907
+ var fs14 = __toESM(require("fs"), 1);
16908
+ var path14 = __toESM(require("path"), 1);
16909
+ var HOOK_MARKER_BEGIN2 = "# --- BEGIN QODEWK HOOK ---";
16627
16910
  var HOOK_MARKER_END = "# --- END QODEWK HOOK ---";
16628
16911
  var HOOK_SNIPPET = `
16629
- ${HOOK_MARKER_BEGIN}
16912
+ ${HOOK_MARKER_BEGIN2}
16630
16913
  # Non-blocking Qodewk background recorder (< 5ms)
16631
16914
  if command -v qodewk >/dev/null 2>&1 || command -v pnpm >/dev/null 2>&1 || [ -f "./node_modules/.bin/qodewk" ]; then
16632
16915
  ( ( qodewk record || pnpm qodewk record || npx qodewk record ) >/dev/null 2>&1 & )
@@ -16634,37 +16917,37 @@ fi
16634
16917
  ${HOOK_MARKER_END}
16635
16918
  `;
16636
16919
  function resolveGitHooksDir(cwd = process.cwd()) {
16637
- const gitPath = path13.join(cwd, ".git");
16638
- if (!fs13.existsSync(gitPath)) return null;
16920
+ const gitPath = path14.join(cwd, ".git");
16921
+ if (!fs14.existsSync(gitPath)) return null;
16639
16922
  let gitCommonDir;
16640
- const stat = fs13.statSync(gitPath);
16923
+ const stat = fs14.statSync(gitPath);
16641
16924
  if (stat.isDirectory()) {
16642
16925
  gitCommonDir = gitPath;
16643
16926
  } else {
16644
- const content = fs13.readFileSync(gitPath, "utf-8");
16927
+ const content = fs14.readFileSync(gitPath, "utf-8");
16645
16928
  const match = content.match(/gitdir:\s*(.+)/i);
16646
16929
  if (!match?.[1]) return null;
16647
16930
  let gitDir = match[1].trim();
16648
- if (!path13.isAbsolute(gitDir)) {
16649
- gitDir = path13.resolve(cwd, gitDir);
16931
+ if (!path14.isAbsolute(gitDir)) {
16932
+ gitDir = path14.resolve(cwd, gitDir);
16650
16933
  }
16651
- const commonFile = path13.join(gitDir, "commondir");
16652
- if (fs13.existsSync(commonFile)) {
16653
- let common = fs13.readFileSync(commonFile, "utf-8").trim();
16654
- if (!path13.isAbsolute(common)) {
16655
- common = path13.resolve(gitDir, common);
16934
+ const commonFile = path14.join(gitDir, "commondir");
16935
+ if (fs14.existsSync(commonFile)) {
16936
+ let common = fs14.readFileSync(commonFile, "utf-8").trim();
16937
+ if (!path14.isAbsolute(common)) {
16938
+ common = path14.resolve(gitDir, common);
16656
16939
  }
16657
16940
  gitCommonDir = common;
16658
- } else if (path13.basename(path13.dirname(gitDir)) === "worktrees") {
16659
- gitCommonDir = path13.dirname(path13.dirname(gitDir));
16941
+ } else if (path14.basename(path14.dirname(gitDir)) === "worktrees") {
16942
+ gitCommonDir = path14.dirname(path14.dirname(gitDir));
16660
16943
  } else {
16661
16944
  gitCommonDir = gitDir;
16662
16945
  }
16663
16946
  }
16664
- return path13.join(gitCommonDir, "hooks");
16947
+ return path14.join(gitCommonDir, "hooks");
16665
16948
  }
16666
16949
  function checkHookStatus(hooksDir) {
16667
- if (!hooksDir || !fs13.existsSync(hooksDir)) {
16950
+ if (!hooksDir || !fs14.existsSync(hooksDir)) {
16668
16951
  return {
16669
16952
  isGit: Boolean(hooksDir),
16670
16953
  hooksDir,
@@ -16672,13 +16955,13 @@ function checkHookStatus(hooksDir) {
16672
16955
  postRewriteInstalled: false
16673
16956
  };
16674
16957
  }
16675
- const postCommit = path13.join(hooksDir, "post-commit");
16676
- const postRewrite = path13.join(hooksDir, "post-rewrite");
16958
+ const postCommit = path14.join(hooksDir, "post-commit");
16959
+ const postRewrite = path14.join(hooksDir, "post-rewrite");
16677
16960
  const hasHook = (p2) => {
16678
- if (!fs13.existsSync(p2)) return false;
16961
+ if (!fs14.existsSync(p2)) return false;
16679
16962
  try {
16680
- const content = fs13.readFileSync(p2, "utf-8");
16681
- return content.includes(HOOK_MARKER_BEGIN);
16963
+ const content = fs14.readFileSync(p2, "utf-8");
16964
+ return content.includes(HOOK_MARKER_BEGIN2);
16682
16965
  } catch {
16683
16966
  return false;
16684
16967
  }
@@ -16692,31 +16975,118 @@ function checkHookStatus(hooksDir) {
16692
16975
  }
16693
16976
  function installHookFile(hookFile) {
16694
16977
  let content = "";
16695
- if (fs13.existsSync(hookFile)) {
16696
- content = fs13.readFileSync(hookFile, "utf-8");
16978
+ if (fs14.existsSync(hookFile)) {
16979
+ content = fs14.readFileSync(hookFile, "utf-8");
16697
16980
  } else {
16698
16981
  content = "#!/bin/sh\n";
16699
16982
  }
16700
- if (content.includes(HOOK_MARKER_BEGIN)) {
16983
+ if (content.includes(HOOK_MARKER_BEGIN2)) {
16701
16984
  return "exists";
16702
16985
  }
16703
- fs13.writeFileSync(hookFile, content + HOOK_SNIPPET, { mode: 493 });
16986
+ fs14.writeFileSync(hookFile, content + HOOK_SNIPPET, { mode: 493 });
16704
16987
  return "installed";
16705
16988
  }
16706
16989
  function uninstallHookFile(hookFile) {
16707
- if (!fs13.existsSync(hookFile)) return "absent";
16708
- const content = fs13.readFileSync(hookFile, "utf-8");
16709
- if (!content.includes(HOOK_MARKER_BEGIN)) return "missing";
16710
- const regex = new RegExp(`\\n?${HOOK_MARKER_BEGIN}[\\s\\S]*?${HOOK_MARKER_END}\\n?`, "g");
16990
+ if (!fs14.existsSync(hookFile)) return "absent";
16991
+ const content = fs14.readFileSync(hookFile, "utf-8");
16992
+ if (!content.includes(HOOK_MARKER_BEGIN2)) return "missing";
16993
+ const regex = new RegExp(`\\n?${HOOK_MARKER_BEGIN2}[\\s\\S]*?${HOOK_MARKER_END}\\n?`, "g");
16711
16994
  const updated = content.replace(regex, "");
16712
- fs13.writeFileSync(hookFile, updated, { mode: 493 });
16995
+ fs14.writeFileSync(hookFile, updated, { mode: 493 });
16713
16996
  return "removed";
16714
16997
  }
16715
16998
 
16716
16999
  // src/menu.ts
16717
17000
  var readline = __toESM(require("readline"), 1);
16718
- var path14 = __toESM(require("path"), 1);
17001
+ var path15 = __toESM(require("path"), 1);
17002
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
17003
+
17004
+ // src/doctor-view.ts
16719
17005
  var import_picocolors2 = __toESM(require_picocolors(), 1);
17006
+ function formatDoctorReport(report) {
17007
+ const lines = [];
17008
+ const push = (l) => lines.push(l);
17009
+ push("");
17010
+ const badge = bg.teal(import_picocolors2.default.bold(colors.ink(" qodewk doctor ")));
17011
+ push(` ${colors.mutedSoft("\u250C")} ${badge} ${import_picocolors2.default.dim(`v${report.version}`)}`);
17012
+ push(` ${colors.mutedSoft("\u2502")}`);
17013
+ push(` ${colors.teal("\u25C7")} ${import_picocolors2.default.bold(import_picocolors2.default.white("AI Agent Environment"))}`);
17014
+ push(` ${colors.mutedSoft("\u2502")}`);
17015
+ for (const agent of report.agents) {
17016
+ const isAct = agent.status === "active";
17017
+ const icon = isAct ? colors.green("\u2713") : import_picocolors2.default.dim("-");
17018
+ const namePad = agent.name.padEnd(20, ".");
17019
+ const statusText = isAct ? colors.green("Active") : import_picocolors2.default.dim("Not detected");
17020
+ const detail = agent.path ? import_picocolors2.default.dim(` (${agent.path})`) : "";
17021
+ push(` ${colors.mutedSoft("\u2502")} ${icon} ${import_picocolors2.default.white(namePad)} ${statusText}${detail}`);
17022
+ }
17023
+ push(` ${colors.mutedSoft("\u2502")}`);
17024
+ push(` ${colors.teal("\u25C7")} ${import_picocolors2.default.bold(import_picocolors2.default.white("Git Hook Telemetry"))}`);
17025
+ push(` ${colors.mutedSoft("\u2502")}`);
17026
+ const hookDirText = report.hooks.hooksDir ? import_picocolors2.default.dim(` (${report.hooks.hooksDir})`) : "";
17027
+ const pcIcon = report.hooks.postCommitInstalled ? colors.green("\u2713") : colors.red("\xD7");
17028
+ const pcStatus = report.hooks.postCommitInstalled ? colors.green("Installed") : colors.red("Missing");
17029
+ push(` ${colors.mutedSoft("\u2502")} ${pcIcon} ${import_picocolors2.default.white("post-commit hook ...")} ${pcStatus}${hookDirText}`);
17030
+ const prIcon = report.hooks.postRewriteInstalled ? colors.green("\u2713") : colors.red("\xD7");
17031
+ const prStatus = report.hooks.postRewriteInstalled ? colors.green("Installed") : colors.red("Missing");
17032
+ push(` ${colors.mutedSoft("\u2502")} ${prIcon} ${import_picocolors2.default.white("post-rewrite hook ..")} ${prStatus}`);
17033
+ const bench = report.benchmark;
17034
+ const benchIcon = bench.status === "pass" ? colors.green("\u2713") : bench.status === "warn" ? colors.amber("!") : colors.red("\xD7");
17035
+ const benchColor = bench.status === "pass" ? colors.green : bench.status === "warn" ? colors.amber : colors.red;
17036
+ push(` ${colors.mutedSoft("\u2502")} ${benchIcon} ${import_picocolors2.default.white("Hook spawn latency .")} ${benchColor(`${bench.executionMs} ms`)} ${import_picocolors2.default.dim(`(${bench.message})`)}`);
17037
+ push(` ${colors.mutedSoft("\u2502")}`);
17038
+ push(` ${colors.teal("\u25C7")} ${import_picocolors2.default.bold(import_picocolors2.default.white("Local Storage & Pricing"))}`);
17039
+ push(` ${colors.mutedSoft("\u2502")}`);
17040
+ const dbIcon = report.storage.healthy ? colors.green("\u2713") : colors.red("\xD7");
17041
+ const dbStatus = report.storage.healthy ? colors.green("Healthy") : colors.red("Failed");
17042
+ const dbDetail = report.storage.healthy ? import_picocolors2.default.dim(` (${report.storage.receiptsCount} receipts, ${report.storage.footprintsCount} footprints)`) : "";
17043
+ push(` ${colors.mutedSoft("\u2502")} ${dbIcon} ${import_picocolors2.default.white("SQLite Database ....")} ${dbStatus}${dbDetail}`);
17044
+ const prcIcon = report.pricing.status === "active" ? colors.green("\u2713") : colors.red("\xD7");
17045
+ const prcStatus = report.pricing.status === "active" ? colors.green("Active") : colors.red("Offline");
17046
+ push(` ${colors.mutedSoft("\u2502")} ${prcIcon} ${import_picocolors2.default.white("Rate Card Registry .")} ${prcStatus} ${import_picocolors2.default.dim(`(${report.pricing.modelsLoaded} model rate cards loaded)`)}`);
17047
+ push(` ${colors.mutedSoft("\u2502")}`);
17048
+ if (report.overallStatus === "healthy") {
17049
+ push(` ${colors.coral("\u25C6")} ${import_picocolors2.default.bold(colors.green("All Systems Operational \u2014 Telemetry Ready"))}`);
17050
+ } else if (report.overallStatus === "warning") {
17051
+ push(` ${colors.amber("\u25C6")} ${import_picocolors2.default.bold(colors.amber("Operational with Warnings \u2014 Run `qodewk hook install` to enable automatic recording"))}`);
17052
+ } else {
17053
+ push(` ${colors.red("\u25C6")} ${import_picocolors2.default.bold(colors.red("Diagnostic Issues Detected \u2014 Check Git repository or database status"))}`);
17054
+ }
17055
+ push(` ${colors.mutedSoft("\u2514")}`);
17056
+ push("");
17057
+ return lines.join("\n");
17058
+ }
17059
+ function formatHookTestReport(hooks, benchmark) {
17060
+ const lines = [];
17061
+ const push = (l) => lines.push(l);
17062
+ push("");
17063
+ const badge = bg.teal(import_picocolors2.default.bold(colors.ink(" qodewk hook test ")));
17064
+ push(` ${colors.mutedSoft("\u250C")} ${badge}`);
17065
+ push(` ${colors.mutedSoft("\u2502")}`);
17066
+ const pcIcon = hooks.postCommitInstalled ? colors.green("\u2713") : colors.red("\xD7");
17067
+ const pcStatus = hooks.postCommitInstalled ? colors.green("Installed") : colors.red("Missing");
17068
+ push(` ${colors.mutedSoft("\u2502")} ${pcIcon} post-commit hook: ${pcStatus}`);
17069
+ const prIcon = hooks.postRewriteInstalled ? colors.green("\u2713") : colors.red("\xD7");
17070
+ const prStatus = hooks.postRewriteInstalled ? colors.green("Installed") : colors.red("Missing");
17071
+ push(` ${colors.mutedSoft("\u2502")} ${prIcon} post-rewrite hook: ${prStatus}`);
17072
+ const benchIcon = benchmark.status === "pass" ? colors.green("\u2713") : benchmark.status === "warn" ? colors.amber("!") : colors.red("\xD7");
17073
+ const benchColor = benchmark.status === "pass" ? colors.green : benchmark.status === "warn" ? colors.amber : colors.red;
17074
+ push(` ${colors.mutedSoft("\u2502")} ${benchIcon} Detachment Latency: ${benchColor(`${benchmark.executionMs} ms`)}`);
17075
+ push(` ${colors.mutedSoft("\u2502")} ${import_picocolors2.default.dim(benchmark.message)}`);
17076
+ push(` ${colors.mutedSoft("\u2502")}`);
17077
+ if (benchmark.passedInvariant && hooks.markerFound) {
17078
+ push(` ${colors.coral("\u25C6")} ${import_picocolors2.default.bold(colors.green("Non-blocking Hook Verified (< 5ms Invariant Passed)"))}`);
17079
+ } else if (!hooks.markerFound) {
17080
+ push(` ${colors.amber("\u25C6")} ${import_picocolors2.default.bold(colors.amber("Hooks Not Installed \u2014 Run `qodewk hook install`"))}`);
17081
+ } else {
17082
+ push(` ${colors.red("\u25C6")} ${import_picocolors2.default.bold(colors.red("Latency Threshold Exceeded \u2014 Reinstall recommended"))}`);
17083
+ }
17084
+ push(` ${colors.mutedSoft("\u2514")}`);
17085
+ push("");
17086
+ return lines.join("\n");
17087
+ }
17088
+
17089
+ // src/menu.ts
16720
17090
  var SHARE_PAYLOAD_MAX_BYTES = 5e4;
16721
17091
  var BANNER_LINES = [
16722
17092
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
@@ -16763,6 +17133,12 @@ var MENU_ITEMS = [
16763
17133
  label: "Git Notes Management",
16764
17134
  description: "Inspect & attach receipts to refs/notes/qodewk"
16765
17135
  },
17136
+ {
17137
+ id: "doctor",
17138
+ key: "7",
17139
+ label: "System Health & Diagnostics",
17140
+ description: "Audit AI agents, Git hooks, SQLite & pricing registry (doctor)"
17141
+ },
16766
17142
  {
16767
17143
  id: "exit",
16768
17144
  key: "0",
@@ -16808,7 +17184,7 @@ var RECEIPT_HORIZON_ITEMS = [
16808
17184
  description: "Return to previous screen"
16809
17185
  }
16810
17186
  ];
16811
- function buildMenuFrame(selectedIndex, items = MENU_ITEMS, title = "Telemetry Control Panel", subtitle = "\u2191\u2193 move \xB7 enter select \xB7 0-6 quick jump \xB7 q quit", options = {}) {
17187
+ function buildMenuFrame(selectedIndex, items = MENU_ITEMS, title = "Telemetry Control Panel", subtitle = "\u2191\u2193 move \xB7 enter select \xB7 0-7 quick jump \xB7 q quit", options = {}) {
16812
17188
  const lines = [];
16813
17189
  const push = (line) => lines.push(line);
16814
17190
  if (options.showBanner ?? true) {
@@ -16819,21 +17195,21 @@ function buildMenuFrame(selectedIndex, items = MENU_ITEMS, title = "Telemetry Co
16819
17195
  }
16820
17196
  }
16821
17197
  push("");
16822
- const badge = bg.teal(import_picocolors2.default.bold(colors.ink(" qodewk ")));
16823
- push(` ${colors.mutedSoft("\u250C")} ${badge} ${import_picocolors2.default.dim("v0.9.2")}`);
17198
+ const badge = bg.teal(import_picocolors3.default.bold(colors.ink(" qodewk ")));
17199
+ push(` ${colors.mutedSoft("\u250C")} ${badge} ${import_picocolors3.default.dim("v0.10.0")}`);
16824
17200
  push(` ${colors.mutedSoft("\u2502")}`);
16825
17201
  if (options.actionContext) {
16826
- push(` ${colors.teal("\u25C7")} ${import_picocolors2.default.dim("Action:")} ${import_picocolors2.default.white(options.actionContext)}`);
17202
+ push(` ${colors.teal("\u25C7")} ${import_picocolors3.default.dim("Action:")} ${import_picocolors3.default.white(options.actionContext)}`);
16827
17203
  push(` ${colors.mutedSoft("\u2502")}`);
16828
17204
  } else {
16829
17205
  const repo = options.repoContext?.alias || "Qodewk";
16830
17206
  const branch = options.repoContext?.branch ? `(${options.repoContext.branch})` : "";
16831
- push(` ${colors.teal("\u25C7")} ${import_picocolors2.default.dim("Repository:")} ${import_picocolors2.default.white(repo)} ${import_picocolors2.default.dim(branch)}`);
17207
+ push(` ${colors.teal("\u25C7")} ${import_picocolors3.default.dim("Repository:")} ${import_picocolors3.default.white(repo)} ${import_picocolors3.default.dim(branch)}`);
16832
17208
  push(` ${colors.mutedSoft("\u2502")}`);
16833
- push(` ${colors.teal("\u25C7")} ${import_picocolors2.default.dim("Telemetry:")} ${import_picocolors2.default.white("Claude Warm Editorial Rate Cards")}`);
17209
+ push(` ${colors.teal("\u25C7")} ${import_picocolors3.default.dim("Telemetry:")} ${import_picocolors3.default.white("Claude Warm Editorial Rate Cards")}`);
16834
17210
  push(` ${colors.mutedSoft("\u2502")}`);
16835
17211
  }
16836
- push(` ${colors.coral("\u25C6")} ${import_picocolors2.default.bold(import_picocolors2.default.white(title))}`);
17212
+ push(` ${colors.coral("\u25C6")} ${import_picocolors3.default.bold(import_picocolors3.default.white(title))}`);
16837
17213
  push(` ${colors.mutedSoft("\u2502")}`);
16838
17214
  const selectedItem = items[selectedIndex] || items[0];
16839
17215
  for (let i2 = 0; i2 < items.length; i2++) {
@@ -16841,44 +17217,44 @@ function buildMenuFrame(selectedIndex, items = MENU_ITEMS, title = "Telemetry Co
16841
17217
  const isSelected = i2 === selectedIndex;
16842
17218
  if (isSelected) {
16843
17219
  push(
16844
- ` ${colors.mutedSoft("\u2502")} ${import_picocolors2.default.bold(colors.coral("\u203A"))} ${import_picocolors2.default.bold(colors.coral(item.key))} ${import_picocolors2.default.bold(import_picocolors2.default.white(item.label))}`
17220
+ ` ${colors.mutedSoft("\u2502")} ${import_picocolors3.default.bold(colors.coral("\u203A"))} ${import_picocolors3.default.bold(colors.coral(item.key))} ${import_picocolors3.default.bold(import_picocolors3.default.white(item.label))}`
16845
17221
  );
16846
17222
  } else {
16847
17223
  push(
16848
- ` ${colors.mutedSoft("\u2502")} ${import_picocolors2.default.dim(item.key)} ${import_picocolors2.default.dim(import_picocolors2.default.white(item.label))}`
17224
+ ` ${colors.mutedSoft("\u2502")} ${import_picocolors3.default.dim(item.key)} ${import_picocolors3.default.dim(import_picocolors3.default.white(item.label))}`
16849
17225
  );
16850
17226
  }
16851
17227
  }
16852
17228
  const divider = colors.mutedSoft("\u2500".repeat(58));
16853
17229
  push(` ${colors.mutedSoft("\u2502")}`);
16854
17230
  push(` ${colors.mutedSoft("\u2502")} ${divider}`);
16855
- push(` ${colors.mutedSoft("\u2502")} ${import_picocolors2.default.bold(import_picocolors2.default.dim("Description"))}`);
17231
+ push(` ${colors.mutedSoft("\u2502")} ${import_picocolors3.default.bold(import_picocolors3.default.dim("Description"))}`);
16856
17232
  push(` ${colors.mutedSoft("\u2502")} ${colors.amber(selectedItem.description)}`);
16857
17233
  push(` ${colors.mutedSoft("\u2502")} ${divider}`);
16858
17234
  push(` ${colors.mutedSoft("\u2502")}`);
16859
- push(` ${colors.mutedSoft("\u2502")} ${import_picocolors2.default.dim(subtitle)}`);
16860
- push(` ${colors.mutedSoft("\u2502")} ${colors.green("[\u2713]")} ${import_picocolors2.default.dim("Source code was never uploaded to Qodewk")}`);
17235
+ push(` ${colors.mutedSoft("\u2502")} ${import_picocolors3.default.dim(subtitle)}`);
17236
+ push(` ${colors.mutedSoft("\u2502")} ${colors.green("[\u2713]")} ${import_picocolors3.default.dim("Source code was never uploaded to Qodewk")}`);
16861
17237
  push(` ${colors.mutedSoft("\u2514")}`);
16862
17238
  push("");
16863
17239
  return lines.join("\n");
16864
17240
  }
16865
17241
  function askLine(promptText) {
16866
- return new Promise((resolve5) => {
17242
+ return new Promise((resolve6) => {
16867
17243
  const rl = readline.createInterface({
16868
17244
  input: process.stdin,
16869
17245
  output: process.stdout
16870
17246
  });
16871
17247
  rl.question(promptText, (answer) => {
16872
17248
  rl.close();
16873
- resolve5(answer.trim());
17249
+ resolve6(answer.trim());
16874
17250
  });
16875
17251
  });
16876
17252
  }
16877
17253
  function waitForKeyToReturn() {
16878
- return new Promise((resolve5) => {
16879
- console.log(import_picocolors2.default.dim("\n Press any key or Enter to return to menu..."));
17254
+ return new Promise((resolve6) => {
17255
+ console.log(import_picocolors3.default.dim("\n Press any key or Enter to return to menu..."));
16880
17256
  if (!process.stdin.isTTY) {
16881
- resolve5();
17257
+ resolve6();
16882
17258
  return;
16883
17259
  }
16884
17260
  const wasRaw = process.stdin.isRaw;
@@ -16887,7 +17263,7 @@ function waitForKeyToReturn() {
16887
17263
  const onKey = () => {
16888
17264
  process.stdin.removeListener("data", onKey);
16889
17265
  if (wasRaw !== void 0) process.stdin.setRawMode(wasRaw);
16890
- resolve5();
17266
+ resolve6();
16891
17267
  };
16892
17268
  process.stdin.once("data", onKey);
16893
17269
  });
@@ -16905,7 +17281,7 @@ async function getRepoContext() {
16905
17281
  }
16906
17282
  const branchSummary = await git.branch();
16907
17283
  const branch = branchSummary.current || "HEAD";
16908
- const alias = path14.basename(process.cwd());
17284
+ const alias = path15.basename(process.cwd());
16909
17285
  cachedRepoContext = { alias, branch };
16910
17286
  return cachedRepoContext;
16911
17287
  } catch {
@@ -16916,7 +17292,7 @@ async function getRepoContext() {
16916
17292
  async function runInteractiveMenu() {
16917
17293
  if (!process.stdin.isTTY) {
16918
17294
  console.log(buildMenuFrame(0));
16919
- console.log(import_picocolors2.default.yellow("Interactive menu requires a TTY terminal. Use `qodewk --help` for commands."));
17295
+ console.log(import_picocolors3.default.yellow("Interactive menu requires a TTY terminal. Use `qodewk --help` for commands."));
16920
17296
  return;
16921
17297
  }
16922
17298
  const repoContext = await getRepoContext();
@@ -16928,7 +17304,7 @@ async function runInteractiveMenu() {
16928
17304
  const render = () => {
16929
17305
  const items = getActiveItems();
16930
17306
  const title = currentScreen === "main" ? "Telemetry Control Panel" : "Generate Local Receipt";
16931
- const subtitle = currentScreen === "main" ? "\u2191\u2193 move \xB7 enter select \xB7 0-6 quick jump \xB7 q quit" : "\u2191\u2193 move \xB7 enter select \xB7 0-5 quick jump \xB7 esc / 0 back";
17307
+ const subtitle = currentScreen === "main" ? "\u2191\u2193 move \xB7 enter select \xB7 0-7 quick jump \xB7 q quit" : "\u2191\u2193 move \xB7 enter select \xB7 0-5 quick jump \xB7 esc / 0 back";
16932
17308
  const actionContext = currentScreen === "receipt_horizon" ? "Generate Local Receipt" : void 0;
16933
17309
  const showBanner = currentScreen === "main";
16934
17310
  const frame = buildMenuFrame(selectedIndex, items, title, subtitle, {
@@ -16969,15 +17345,15 @@ async function runInteractiveMenu() {
16969
17345
  since = "7d";
16970
17346
  label = "past 7 days weekly sprint (--since 7d)";
16971
17347
  } else if (item.id === "custom") {
16972
- console.log(import_picocolors2.default.bold(import_picocolors2.default.white(" Custom Telemetry Horizon")));
16973
- console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17348
+ console.log(import_picocolors3.default.bold(import_picocolors3.default.white(" Custom Telemetry Horizon")));
17349
+ console.log(import_picocolors3.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
16974
17350
  const input = await askLine(" \u203A Enter duration or date (e.g. 12h, 3d, 2026-10-01): ");
16975
17351
  if (input) {
16976
17352
  since = input;
16977
17353
  label = `custom range: ${input}`;
16978
17354
  }
16979
17355
  }
16980
- console.log(import_picocolors2.default.dim(` [\xB7] Generating local receipt for ${label}...`));
17356
+ console.log(import_picocolors3.default.dim(` [\xB7] Generating local receipt for ${label}...`));
16981
17357
  try {
16982
17358
  const receipt = await generateReceipt({ since, isPublic: false });
16983
17359
  const db = new LocalStateDB();
@@ -16986,7 +17362,7 @@ async function runInteractiveMenu() {
16986
17362
  await renderTerminalReceipt(receipt);
16987
17363
  } catch (err) {
16988
17364
  const message = err instanceof Error ? err.message : String(err);
16989
- console.error(import_picocolors2.default.red(`
17365
+ console.error(import_picocolors3.default.red(`
16990
17366
  Failed to generate receipt: ${message}`));
16991
17367
  }
16992
17368
  await waitForKeyToReturn();
@@ -17000,8 +17376,8 @@ async function runInteractiveMenu() {
17000
17376
  const selectedItem = MENU_ITEMS[selectedIndex];
17001
17377
  switch (selectedItem.id) {
17002
17378
  case "audit": {
17003
- console.log(import_picocolors2.default.bold(import_picocolors2.default.white("\n Audit Branch or Revision Diff")));
17004
- console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17379
+ console.log(import_picocolors3.default.bold(import_picocolors3.default.white("\n Audit Branch or Revision Diff")));
17380
+ console.log(import_picocolors3.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17005
17381
  let defaultBase = "origin/main";
17006
17382
  try {
17007
17383
  defaultBase = await detectDefaultBaseBranch(process.cwd());
@@ -17013,7 +17389,7 @@ async function runInteractiveMenu() {
17013
17389
  const head = await askLine(
17014
17390
  ` \u203A Head git ref or commit SHA [default: HEAD]: `
17015
17391
  );
17016
- console.log(import_picocolors2.default.dim("\n [\xB7] Auditing git revision range..."));
17392
+ console.log(import_picocolors3.default.dim("\n [\xB7] Auditing git revision range..."));
17017
17393
  try {
17018
17394
  const receipt = await generateReceipt({
17019
17395
  baseSha: base || defaultBase,
@@ -17026,18 +17402,18 @@ async function runInteractiveMenu() {
17026
17402
  await renderTerminalReceipt(receipt);
17027
17403
  } catch (err) {
17028
17404
  const message = err instanceof Error ? err.message : String(err);
17029
- console.error(import_picocolors2.default.red(`
17405
+ console.error(import_picocolors3.default.red(`
17030
17406
  Audit failed: ${message}`));
17031
17407
  }
17032
17408
  await waitForKeyToReturn();
17033
17409
  break;
17034
17410
  }
17035
17411
  case "share": {
17036
- console.log(import_picocolors2.default.dim(" [\xB7] Generating and sanitizing receipt for cloud publishing..."));
17412
+ console.log(import_picocolors3.default.dim(" [\xB7] Generating and sanitizing receipt for cloud publishing..."));
17037
17413
  try {
17038
17414
  const receipt = await generateReceipt({ isPublic: false });
17039
17415
  if (process.env.QODEWK_TELEMETRY === "off") {
17040
- console.log(import_picocolors2.default.yellow("\n Cloud publishing is disabled by QODEWK_TELEMETRY=off."));
17416
+ console.log(import_picocolors3.default.yellow("\n Cloud publishing is disabled by QODEWK_TELEMETRY=off."));
17041
17417
  await renderTerminalReceipt(receipt);
17042
17418
  await waitForKeyToReturn();
17043
17419
  break;
@@ -17052,7 +17428,7 @@ async function runInteractiveMenu() {
17052
17428
  body = JSON.stringify(trimmed2);
17053
17429
  }
17054
17430
  const endpoint = resolveApiUrl();
17055
- console.log(import_picocolors2.default.dim(` [\xB7] Uploading metadata to ${endpoint}...`));
17431
+ console.log(import_picocolors3.default.dim(` [\xB7] Uploading metadata to ${endpoint}...`));
17056
17432
  const response = await fetch(endpoint, {
17057
17433
  method: "POST",
17058
17434
  headers: {
@@ -17064,11 +17440,11 @@ async function runInteractiveMenu() {
17064
17440
  if (!response.ok) {
17065
17441
  const errText = await response.text().catch(() => "");
17066
17442
  if (response.status === 503) {
17067
- console.log(import_picocolors2.default.yellow("\n Notice: Remote server requires durable storage (Supabase or PostgreSQL) in production"));
17068
- console.log(import_picocolors2.default.dim(" to protect receipts from serverless cold-start data loss."));
17069
- console.log(import_picocolors2.default.dim(` Endpoint: ${endpoint}`));
17443
+ console.log(import_picocolors3.default.yellow("\n Notice: Remote server requires durable storage (Supabase or PostgreSQL) in production"));
17444
+ console.log(import_picocolors3.default.dim(" to protect receipts from serverless cold-start data loss."));
17445
+ console.log(import_picocolors3.default.dim(` Endpoint: ${endpoint}`));
17070
17446
  console.log(colors.green("\n [\u2713] Your receipt is safely saved locally in: ~/.qodewk/state.db"));
17071
- console.log(import_picocolors2.default.dim(" You can also save it as a local Git note: qodewk --notes\n"));
17447
+ console.log(import_picocolors3.default.dim(" You can also save it as a local Git note: qodewk --notes\n"));
17072
17448
  await renderTerminalReceipt(sanitized);
17073
17449
  await waitForKeyToReturn();
17074
17450
  break;
@@ -17081,13 +17457,13 @@ async function runInteractiveMenu() {
17081
17457
  db.close();
17082
17458
  console.log("");
17083
17459
  console.log(colors.green(" [\u2713] Published successfully."));
17084
- console.log(` ${import_picocolors2.default.dim("Public URL:")} ${import_picocolors2.default.underline(import_picocolors2.default.cyan(data.url))}`);
17085
- console.log(` ${import_picocolors2.default.bold(colors.amber("Claim token:"))} ${colors.amber(data.claimToken)}`);
17086
- console.log(import_picocolors2.default.dim(" (Saved to ~/.qodewk/state.db for future authorship proofs)\n"));
17460
+ console.log(` ${import_picocolors3.default.dim("Public URL:")} ${import_picocolors3.default.underline(import_picocolors3.default.cyan(data.url))}`);
17461
+ console.log(` ${import_picocolors3.default.bold(colors.amber("Claim token:"))} ${colors.amber(data.claimToken)}`);
17462
+ console.log(import_picocolors3.default.dim(" (Saved to ~/.qodewk/state.db for future authorship proofs)\n"));
17087
17463
  await renderTerminalReceipt(sanitized, data.url);
17088
17464
  } catch (err) {
17089
17465
  const message = err instanceof Error ? err.message : String(err);
17090
- console.error(import_picocolors2.default.red(`
17466
+ console.error(import_picocolors3.default.red(`
17091
17467
  Share failed: ${message}`));
17092
17468
  }
17093
17469
  await waitForKeyToReturn();
@@ -17105,10 +17481,14 @@ async function runInteractiveMenu() {
17105
17481
  await manageGitNotesSubmenu();
17106
17482
  break;
17107
17483
  }
17484
+ case "doctor": {
17485
+ await runDoctorDiagnosticsAction();
17486
+ break;
17487
+ }
17108
17488
  case "exit": {
17109
17489
  active = false;
17110
17490
  cleanup();
17111
- console.log(import_picocolors2.default.dim(" Exited Qodewk.\n"));
17491
+ console.log(import_picocolors3.default.dim(" Exited Qodewk.\n"));
17112
17492
  process.exit(0);
17113
17493
  return;
17114
17494
  }
@@ -17117,31 +17497,32 @@ async function runInteractiveMenu() {
17117
17497
  const manageHooksSubmenu = async () => {
17118
17498
  const hooksDir = resolveGitHooksDir();
17119
17499
  const status = checkHookStatus(hooksDir);
17120
- console.log(import_picocolors2.default.bold(import_picocolors2.default.white("\n Qodewk Git Hooks Management")));
17121
- console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17500
+ console.log(import_picocolors3.default.bold(import_picocolors3.default.white("\n Qodewk Git Hooks Management")));
17501
+ console.log(import_picocolors3.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17122
17502
  if (!status.isGit) {
17123
- console.log(import_picocolors2.default.yellow(" Not inside a Git repository. Hooks cannot be configured."));
17503
+ console.log(import_picocolors3.default.yellow(" Not inside a Git repository. Hooks cannot be configured."));
17124
17504
  await waitForKeyToReturn();
17125
17505
  return;
17126
17506
  }
17127
- console.log(` Hooks directory: ${import_picocolors2.default.dim(status.hooksDir || "None")}`);
17507
+ console.log(` Hooks directory: ${import_picocolors3.default.dim(status.hooksDir || "None")}`);
17128
17508
  console.log(
17129
- ` post-commit: ${status.postCommitInstalled ? colors.green("[\u2713 Installed]") : import_picocolors2.default.dim("[\xB7 Not installed]")}`
17509
+ ` post-commit: ${status.postCommitInstalled ? colors.green("[\u2713 Installed]") : import_picocolors3.default.dim("[\xB7 Not installed]")}`
17130
17510
  );
17131
17511
  console.log(
17132
- ` post-rewrite: ${status.postRewriteInstalled ? colors.green("[\u2713 Installed]") : import_picocolors2.default.dim("[\xB7 Not installed]")}`
17512
+ ` post-rewrite: ${status.postRewriteInstalled ? colors.green("[\u2713 Installed]") : import_picocolors3.default.dim("[\xB7 Not installed]")}`
17133
17513
  );
17134
17514
  console.log("");
17135
17515
  console.log(" [1] Install non-blocking hooks (< 5ms background recorder)");
17136
17516
  console.log(" [2] Uninstall Qodewk hooks");
17517
+ console.log(" [3] Test & benchmark hook latency (< 5ms invariant)");
17137
17518
  console.log(" [0] Back to main menu");
17138
17519
  console.log("");
17139
- const choice = await askLine(" \u203A Select option [0-2]: ");
17520
+ const choice = await askLine(" \u203A Select option [0-3]: ");
17140
17521
  if (choice === "1") {
17141
17522
  if (status.hooksDir) {
17142
17523
  const targets = ["post-commit", "post-rewrite"];
17143
17524
  for (const t2 of targets) {
17144
- installHookFile(path14.join(status.hooksDir, t2));
17525
+ installHookFile(path15.join(status.hooksDir, t2));
17145
17526
  }
17146
17527
  console.log(colors.green("\n [\u2713] Non-blocking hooks installed successfully."));
17147
17528
  }
@@ -17149,16 +17530,20 @@ async function runInteractiveMenu() {
17149
17530
  if (status.hooksDir) {
17150
17531
  const targets = ["post-commit", "post-rewrite"];
17151
17532
  for (const t2 of targets) {
17152
- uninstallHookFile(path14.join(status.hooksDir, t2));
17533
+ uninstallHookFile(path15.join(status.hooksDir, t2));
17153
17534
  }
17154
17535
  console.log(colors.green("\n [\u2713] Qodewk hooks removed."));
17155
17536
  }
17537
+ } else if (choice === "3") {
17538
+ const hooks = auditGitHooks();
17539
+ const bench = await benchmarkHookLatency();
17540
+ console.log(formatHookTestReport(hooks, bench));
17156
17541
  }
17157
17542
  await waitForKeyToReturn();
17158
17543
  };
17159
17544
  const manageGitNotesSubmenu = async () => {
17160
- console.log(import_picocolors2.default.bold(import_picocolors2.default.white("\n Qodewk Git Notes Management (refs/notes/qodewk)")));
17161
- console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17545
+ console.log(import_picocolors3.default.bold(import_picocolors3.default.white("\n Qodewk Git Notes Management (refs/notes/qodewk)")));
17546
+ console.log(import_picocolors3.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17162
17547
  console.log(" [1] List commits with attached Qodewk notes");
17163
17548
  console.log(" [2] Show receipt note on a commit (default: HEAD)");
17164
17549
  console.log(" [3] Attach receipt note to a Git commit (default: HEAD)");
@@ -17169,17 +17554,17 @@ async function runInteractiveMenu() {
17169
17554
  try {
17170
17555
  const shas = await listGitReceiptNotes();
17171
17556
  if (shas.length === 0) {
17172
- console.log(import_picocolors2.default.yellow("\n No Qodewk Git notes found in repository."));
17557
+ console.log(import_picocolors3.default.yellow("\n No Qodewk Git notes found in repository."));
17173
17558
  } else {
17174
- console.log(import_picocolors2.default.bold(`
17559
+ console.log(import_picocolors3.default.bold(`
17175
17560
  Found ${shas.length} commit note(s) in refs/notes/qodewk:`));
17176
17561
  for (const sha of shas) {
17177
- console.log(` ${import_picocolors2.default.cyan(sha.slice(0, 10))} ${import_picocolors2.default.dim(sha)}`);
17562
+ console.log(` ${import_picocolors3.default.cyan(sha.slice(0, 10))} ${import_picocolors3.default.dim(sha)}`);
17178
17563
  }
17179
17564
  }
17180
17565
  } catch (err) {
17181
17566
  const message = err instanceof Error ? err.message : String(err);
17182
- console.error(import_picocolors2.default.red(`
17567
+ console.error(import_picocolors3.default.red(`
17183
17568
  Error listing Git notes: ${message}`));
17184
17569
  }
17185
17570
  } else if (choice === "2") {
@@ -17187,20 +17572,20 @@ async function runInteractiveMenu() {
17187
17572
  try {
17188
17573
  const receipt = await readGitReceiptNote(commit);
17189
17574
  if (!receipt) {
17190
- console.log(import_picocolors2.default.yellow(`
17575
+ console.log(import_picocolors3.default.yellow(`
17191
17576
  No Qodewk receipt note found on commit '${commit}'.`));
17192
17577
  } else {
17193
17578
  await renderTerminalReceipt(receipt);
17194
17579
  }
17195
17580
  } catch (err) {
17196
17581
  const message = err instanceof Error ? err.message : String(err);
17197
- console.error(import_picocolors2.default.red(`
17582
+ console.error(import_picocolors3.default.red(`
17198
17583
  Error reading Git note: ${message}`));
17199
17584
  }
17200
17585
  } else if (choice === "3") {
17201
17586
  const commit = await askLine(" \u203A Commit SHA to attach note [default: HEAD]: ") || "HEAD";
17202
17587
  try {
17203
- console.log(import_picocolors2.default.dim(`
17588
+ console.log(import_picocolors3.default.dim(`
17204
17589
  [\xB7] Generating receipt for ${commit}...`));
17205
17590
  const receipt = await generateReceipt({ headSha: commit });
17206
17591
  await writeGitReceiptNote(receipt.repository.headSha, receipt);
@@ -17210,30 +17595,30 @@ async function runInteractiveMenu() {
17210
17595
  );
17211
17596
  } catch (err) {
17212
17597
  const message = err instanceof Error ? err.message : String(err);
17213
- console.error(import_picocolors2.default.red(`
17598
+ console.error(import_picocolors3.default.red(`
17214
17599
  Error writing Git note: ${message}`));
17215
17600
  }
17216
17601
  }
17217
17602
  await waitForKeyToReturn();
17218
17603
  };
17219
17604
  const showStorageStatus = async () => {
17220
- console.log(import_picocolors2.default.bold(import_picocolors2.default.white("\n Qodewk Local Database & Storage Status")));
17221
- console.log(import_picocolors2.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17605
+ console.log(import_picocolors3.default.bold(import_picocolors3.default.white("\n Qodewk Local Database & Storage Status")));
17606
+ console.log(import_picocolors3.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17222
17607
  try {
17223
17608
  const db = new LocalStateDB();
17224
17609
  const stats = db.getStats();
17225
17610
  const recent = db.getRecentReceipts(5);
17226
17611
  db.close();
17227
- console.log(` Database path: ${import_picocolors2.default.dim("~/.qodewk/state.db")}`);
17228
- console.log(` Total saved receipts: ${import_picocolors2.default.bold(import_picocolors2.default.white(String(stats.totalReceipts)))}`);
17229
- console.log(` Total agent footprints: ${import_picocolors2.default.bold(import_picocolors2.default.white(String(stats.totalFootprints)))}`);
17612
+ console.log(` Database path: ${import_picocolors3.default.dim("~/.qodewk/state.db")}`);
17613
+ console.log(` Total saved receipts: ${import_picocolors3.default.bold(import_picocolors3.default.white(String(stats.totalReceipts)))}`);
17614
+ console.log(` Total agent footprints: ${import_picocolors3.default.bold(import_picocolors3.default.white(String(stats.totalFootprints)))}`);
17230
17615
  console.log("");
17231
17616
  if (recent.length === 0) {
17232
- console.log(import_picocolors2.default.dim(" No stored receipts yet. Run 'Generate Local Receipt' to record your first."));
17617
+ console.log(import_picocolors3.default.dim(" No stored receipts yet. Run 'Generate Local Receipt' to record your first."));
17233
17618
  } else {
17234
- console.log(import_picocolors2.default.dim(" Recent Receipts:"));
17235
- console.log(import_picocolors2.default.dim(" " + "ID".padEnd(26) + "DATE".padEnd(12) + "BRANCH".padEnd(18) + "COST"));
17236
- console.log(import_picocolors2.default.dim(" " + "\u2500".repeat(60)));
17619
+ console.log(import_picocolors3.default.dim(" Recent Receipts:"));
17620
+ console.log(import_picocolors3.default.dim(" " + "ID".padEnd(26) + "DATE".padEnd(12) + "BRANCH".padEnd(18) + "COST"));
17621
+ console.log(import_picocolors3.default.dim(" " + "\u2500".repeat(60)));
17237
17622
  for (const item of recent) {
17238
17623
  const id = item.receipt.receipt.id.slice(0, 24).padEnd(26);
17239
17624
  const date = item.receipt.receipt.createdAt.slice(0, 10).padEnd(12);
@@ -17243,10 +17628,23 @@ async function runInteractiveMenu() {
17243
17628
  }
17244
17629
  }
17245
17630
  console.log("");
17246
- console.log(` ${colors.green("[\u2713]")} ${import_picocolors2.default.dim("Privacy guarantee: raw source code is never stored in SQLite")}`);
17631
+ console.log(` ${colors.green("[\u2713]")} ${import_picocolors3.default.dim("Privacy guarantee: raw source code is never stored in SQLite")}`);
17632
+ } catch (err) {
17633
+ const message = err instanceof Error ? err.message : String(err);
17634
+ console.error(import_picocolors3.default.red(` Failed to inspect storage: ${message}`));
17635
+ }
17636
+ await waitForKeyToReturn();
17637
+ };
17638
+ const runDoctorDiagnosticsAction = async () => {
17639
+ console.log(import_picocolors3.default.bold(import_picocolors3.default.white("\n Qodewk System Health & Diagnostics (Doctor)")));
17640
+ console.log(import_picocolors3.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
17641
+ console.log(import_picocolors3.default.dim(" [\xB7] Running diagnostic audit across agents, hooks, storage, and pricing...\n"));
17642
+ try {
17643
+ const report = await runDiagnostics();
17644
+ console.log(formatDoctorReport(report));
17247
17645
  } catch (err) {
17248
17646
  const message = err instanceof Error ? err.message : String(err);
17249
- console.error(import_picocolors2.default.red(` Failed to inspect storage: ${message}`));
17647
+ console.error(import_picocolors3.default.red(` Diagnostic check failed: ${message}`));
17250
17648
  }
17251
17649
  await waitForKeyToReturn();
17252
17650
  };
@@ -17271,7 +17669,7 @@ async function runInteractiveMenu() {
17271
17669
  return;
17272
17670
  }
17273
17671
  cleanup();
17274
- console.log(import_picocolors2.default.dim("\n Exited Qodewk.\n"));
17672
+ console.log(import_picocolors3.default.dim("\n Exited Qodewk.\n"));
17275
17673
  process.exit(0);
17276
17674
  }
17277
17675
  if (key.name === "up" || key.name === "k") {
@@ -17348,19 +17746,19 @@ loadQodewkEnv();
17348
17746
  var SHARE_PAYLOAD_MAX_BYTES2 = 5e4;
17349
17747
  function resolveCliVersion() {
17350
17748
  try {
17351
- const argv1 = process.argv[1] ? path15.dirname(path15.resolve(process.argv[1])) : process.cwd();
17749
+ const argv1 = process.argv[1] ? path16.dirname(path16.resolve(process.argv[1])) : process.cwd();
17352
17750
  const candidates = [
17353
- path15.join(argv1, "..", "package.json"),
17354
- path15.join(argv1, "package.json")
17751
+ path16.join(argv1, "..", "package.json"),
17752
+ path16.join(argv1, "package.json")
17355
17753
  ];
17356
17754
  for (const candidate of candidates) {
17357
- if (!fs14.existsSync(candidate)) continue;
17358
- const pkg = JSON.parse(fs14.readFileSync(candidate, "utf-8"));
17755
+ if (!fs15.existsSync(candidate)) continue;
17756
+ const pkg = JSON.parse(fs15.readFileSync(candidate, "utf-8"));
17359
17757
  if (pkg.name === "qodewk" && pkg.version) return pkg.version;
17360
17758
  }
17361
17759
  } catch {
17362
17760
  }
17363
- return "0.9.2";
17761
+ return "0.10.0";
17364
17762
  }
17365
17763
  function resolveSince(options) {
17366
17764
  return options.today ? "today" : options.since;
@@ -17398,21 +17796,39 @@ program2.name("qodewk").description("Universal telemetry and digital receipt gen
17398
17796
  if (options.notes) {
17399
17797
  try {
17400
17798
  await writeGitReceiptNote(receipt.repository.headSha, receipt);
17401
- console.log(import_picocolors3.default.green(`\u2713 Attached receipt note to ${receipt.repository.headSha.slice(0, 7)} (refs/notes/qodewk)`));
17799
+ console.log(import_picocolors4.default.green(`\u2713 Attached receipt note to ${receipt.repository.headSha.slice(0, 7)} (refs/notes/qodewk)`));
17402
17800
  } catch (noteErr) {
17403
- console.warn(import_picocolors3.default.yellow(`Warning: Could not write Git note: ${noteErr.message}`));
17801
+ console.warn(import_picocolors4.default.yellow(`Warning: Could not write Git note: ${noteErr.message}`));
17404
17802
  }
17405
17803
  }
17406
17804
  await outputReceipt(receipt, options);
17407
17805
  } catch (err) {
17408
17806
  const message = err instanceof Error ? err.message : String(err);
17409
- console.error(import_picocolors3.default.red(`Error generating receipt: ${message}`));
17807
+ console.error(import_picocolors4.default.red(`Error generating receipt: ${message}`));
17410
17808
  process.exit(1);
17411
17809
  }
17412
17810
  });
17413
17811
  program2.command("menu").description("Launch interactive terminal control panel").action(async () => {
17414
17812
  await runInteractiveMenu();
17415
17813
  });
17814
+ program2.command("doctor").description("Run complete diagnostic health check of AI agents, git hooks, and local telemetry").option("-j, --json", "Output diagnostic report in machine-readable JSON format").action(async (options) => {
17815
+ try {
17816
+ const isJson = Boolean(options.json || program2.opts().json);
17817
+ const report = await runDiagnostics();
17818
+ if (isJson) {
17819
+ console.log(JSON.stringify(report, null, 2));
17820
+ } else {
17821
+ console.log(formatDoctorReport(report));
17822
+ }
17823
+ if (report.overallStatus === "error") {
17824
+ process.exit(1);
17825
+ }
17826
+ } catch (err) {
17827
+ const message = err instanceof Error ? err.message : String(err);
17828
+ console.error(import_picocolors4.default.red(`Error running diagnostics: ${message}`));
17829
+ process.exit(1);
17830
+ }
17831
+ });
17416
17832
  program2.command("audit").description("Audit PR or branch diff against a target base commit").option("-b, --base <base>", "Base git ref or commit SHA (e.g. origin/main)").option("-H, --head <head>", "Head git ref or commit SHA").option("-f, --format <format>", "Output format (terminal, json, markdown)", "terminal").option("-o, --out <path>", "Write receipt output to specified file path").option("-p, --provider <provider>", "Specify AI provider").option("-m, --model <model>", "Specify AI model").option("-s, --since <duration>", "Harvest agent footprints since duration").option("--today", "Harvest agent footprints for today").option("--platform <platform>", "Filter agent platform").option("--anon", "Anonymize branch name in output").option("--barcode", "Render classic 1D thermal barcode (always on; kept for compatibility)").action(async (options) => {
17417
17833
  try {
17418
17834
  const receipt = await generateReceipt({
@@ -17429,7 +17845,7 @@ program2.command("audit").description("Audit PR or branch diff against a target
17429
17845
  await outputReceipt(receipt, options);
17430
17846
  } catch (err) {
17431
17847
  const message = err instanceof Error ? err.message : String(err);
17432
- console.error(import_picocolors3.default.red(`Error auditing git range: ${message}`));
17848
+ console.error(import_picocolors4.default.red(`Error auditing git range: ${message}`));
17433
17849
  process.exit(1);
17434
17850
  }
17435
17851
  });
@@ -17446,12 +17862,12 @@ program2.command("share").description("Publish privacy-safe receipt to Qodewk cl
17446
17862
  const localFlag = program2.opts().local === true;
17447
17863
  if (process.env.QODEWK_TELEMETRY === "off" || localFlag) {
17448
17864
  console.log(
17449
- import_picocolors3.default.yellow(
17865
+ import_picocolors4.default.yellow(
17450
17866
  `
17451
17867
  Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"}).`
17452
17868
  )
17453
17869
  );
17454
- console.log(import_picocolors3.default.dim("Telemetry remains strictly stored in local SQLite (~/.qodewk/state.db).\n"));
17870
+ console.log(import_picocolors4.default.dim("Telemetry remains strictly stored in local SQLite (~/.qodewk/state.db).\n"));
17455
17871
  persistReceipt(receipt);
17456
17872
  await renderTerminalReceipt(receipt);
17457
17873
  return;
@@ -17466,11 +17882,11 @@ Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"})
17466
17882
  body = JSON.stringify(trimmed2);
17467
17883
  }
17468
17884
  if (Buffer.byteLength(body, "utf-8") > SHARE_PAYLOAD_MAX_BYTES2) {
17469
- console.error(import_picocolors3.default.red("Sanitized receipt still exceeds the 50 KB cloud payload limit."));
17885
+ console.error(import_picocolors4.default.red("Sanitized receipt still exceeds the 50 KB cloud payload limit."));
17470
17886
  process.exit(1);
17471
17887
  }
17472
17888
  const endpoint = resolveApiUrl();
17473
- console.log(import_picocolors3.default.dim(`Publishing receipt ${sanitized.receipt.id} to ${endpoint}...`));
17889
+ console.log(import_picocolors4.default.dim(`Publishing receipt ${sanitized.receipt.id} to ${endpoint}...`));
17474
17890
  try {
17475
17891
  const response = await fetch(endpoint, {
17476
17892
  method: "POST",
@@ -17487,11 +17903,11 @@ Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"})
17487
17903
  const data = await response.json();
17488
17904
  persistReceipt(sanitized, data.claimToken);
17489
17905
  console.log("");
17490
- console.log(import_picocolors3.default.green(" Published successfully."));
17491
- console.log(` ${import_picocolors3.default.dim("Public URL:")} ${import_picocolors3.default.underline(import_picocolors3.default.cyan(data.url))}`);
17492
- console.log(` ${import_picocolors3.default.bold(import_picocolors3.default.yellow(" Claim token (store securely \u2014 shown once):"))}`);
17493
- console.log(` ${import_picocolors3.default.yellow(data.claimToken)}`);
17494
- console.log(import_picocolors3.default.dim(" Saved to ~/.qodewk/state.db \u2014 required to prove authorship later."));
17906
+ console.log(import_picocolors4.default.green(" Published successfully."));
17907
+ console.log(` ${import_picocolors4.default.dim("Public URL:")} ${import_picocolors4.default.underline(import_picocolors4.default.cyan(data.url))}`);
17908
+ console.log(` ${import_picocolors4.default.bold(import_picocolors4.default.yellow(" Claim token (store securely \u2014 shown once):"))}`);
17909
+ console.log(` ${import_picocolors4.default.yellow(data.claimToken)}`);
17910
+ console.log(import_picocolors4.default.dim(" Saved to ~/.qodewk/state.db \u2014 required to prove authorship later."));
17495
17911
  console.log("");
17496
17912
  await renderTerminalReceipt(sanitized, data.url);
17497
17913
  if (options.json) {
@@ -17499,14 +17915,14 @@ Cloud publishing is disabled (${localFlag ? "--local" : "QODEWK_TELEMETRY=off"})
17499
17915
  }
17500
17916
  } catch (networkErr) {
17501
17917
  const message = networkErr instanceof Error ? networkErr.message : String(networkErr);
17502
- console.log(import_picocolors3.default.yellow(`
17918
+ console.log(import_picocolors4.default.yellow(`
17503
17919
  Could not reach cloud API (${message}). Rendered locally:`));
17504
17920
  persistReceipt(receipt);
17505
17921
  await renderTerminalReceipt(receipt);
17506
17922
  }
17507
17923
  } catch (err) {
17508
17924
  const message = err instanceof Error ? err.message : String(err);
17509
- console.error(import_picocolors3.default.red(`Error sharing receipt: ${message}`));
17925
+ console.error(import_picocolors4.default.red(`Error sharing receipt: ${message}`));
17510
17926
  process.exit(1);
17511
17927
  }
17512
17928
  });
@@ -17524,35 +17940,35 @@ hookCommand.command("install").description("Install non-blocking post-commit and
17524
17940
  try {
17525
17941
  const hooksDir = resolveGitHooksDir();
17526
17942
  if (!hooksDir) {
17527
- console.error(import_picocolors3.default.red("Error: Current directory is not a Git repository (.git not found)."));
17943
+ console.error(import_picocolors4.default.red("Error: Current directory is not a Git repository (.git not found)."));
17528
17944
  process.exit(1);
17529
17945
  }
17530
- if (!fs14.existsSync(hooksDir)) {
17531
- fs14.mkdirSync(hooksDir, { recursive: true });
17946
+ if (!fs15.existsSync(hooksDir)) {
17947
+ fs15.mkdirSync(hooksDir, { recursive: true });
17532
17948
  }
17533
17949
  const targets = ["post-commit", "post-rewrite"];
17534
17950
  let installed = 0;
17535
17951
  let existing = 0;
17536
17952
  for (const name of targets) {
17537
- const result = installHookFile(path15.join(hooksDir, name));
17953
+ const result = installHookFile(path16.join(hooksDir, name));
17538
17954
  if (result === "installed") installed++;
17539
17955
  else existing++;
17540
17956
  }
17541
17957
  if (installed === 0 && existing > 0) {
17542
- console.log(import_picocolors3.default.yellow("Qodewk Git hooks are already installed (post-commit, post-rewrite)."));
17958
+ console.log(import_picocolors4.default.yellow("Qodewk Git hooks are already installed (post-commit, post-rewrite)."));
17543
17959
  return;
17544
17960
  }
17545
17961
  console.log(
17546
- import_picocolors3.default.green(
17962
+ import_picocolors4.default.green(
17547
17963
  `\u2713 Non-blocking Qodewk hooks installed in ${hooksDir} (post-commit${installed > 1 || existing > 0 ? ", post-rewrite" : installed === 1 && targets.length === 2 ? " + post-rewrite" : ""})`
17548
17964
  )
17549
17965
  );
17550
17966
  if (existing > 0) {
17551
- console.log(import_picocolors3.default.dim(` (${existing} hook file(s) already contained the Qodewk marker)`));
17967
+ console.log(import_picocolors4.default.dim(` (${existing} hook file(s) already contained the Qodewk marker)`));
17552
17968
  }
17553
17969
  } catch (err) {
17554
17970
  const message = err instanceof Error ? err.message : String(err);
17555
- console.error(import_picocolors3.default.red(`Failed to install Git hook: ${message}`));
17971
+ console.error(import_picocolors4.default.red(`Failed to install Git hook: ${message}`));
17556
17972
  process.exit(1);
17557
17973
  }
17558
17974
  });
@@ -17560,23 +17976,42 @@ hookCommand.command("uninstall").description("Remove Qodewk post-commit and post
17560
17976
  try {
17561
17977
  const hooksDir = resolveGitHooksDir();
17562
17978
  if (!hooksDir) {
17563
- console.log(import_picocolors3.default.yellow("No Git repository found."));
17979
+ console.log(import_picocolors4.default.yellow("No Git repository found."));
17564
17980
  return;
17565
17981
  }
17566
17982
  const targets = ["post-commit", "post-rewrite"];
17567
17983
  let removed = 0;
17568
17984
  for (const name of targets) {
17569
- const result = uninstallHookFile(path15.join(hooksDir, name));
17985
+ const result = uninstallHookFile(path16.join(hooksDir, name));
17570
17986
  if (result === "removed") removed++;
17571
17987
  }
17572
17988
  if (removed === 0) {
17573
- console.log(import_picocolors3.default.yellow("Qodewk hooks are not installed."));
17989
+ console.log(import_picocolors4.default.yellow("Qodewk hooks are not installed."));
17574
17990
  return;
17575
17991
  }
17576
- console.log(import_picocolors3.default.green(`\u2713 Qodewk hook markers removed from ${removed} file(s).`));
17992
+ console.log(import_picocolors4.default.green(`\u2713 Qodewk hook markers removed from ${removed} file(s).`));
17993
+ } catch (err) {
17994
+ const message = err instanceof Error ? err.message : String(err);
17995
+ console.error(import_picocolors4.default.red(`Failed to uninstall hook: ${message}`));
17996
+ process.exit(1);
17997
+ }
17998
+ });
17999
+ hookCommand.command("test").aliases(["verify", "check"]).description("Verify non-blocking Git hook installation and benchmark latency (< 5ms invariant)").option("-j, --json", "Output benchmark in JSON format").action(async (options) => {
18000
+ try {
18001
+ const hooks = auditGitHooks();
18002
+ const benchmark = await benchmarkHookLatency();
18003
+ const isJson = Boolean(options.json || program2.opts().json);
18004
+ if (isJson) {
18005
+ console.log(JSON.stringify({ hooks, benchmark }, null, 2));
18006
+ } else {
18007
+ console.log(formatHookTestReport(hooks, benchmark));
18008
+ }
18009
+ if (!benchmark.passedInvariant) {
18010
+ process.exit(1);
18011
+ }
17577
18012
  } catch (err) {
17578
18013
  const message = err instanceof Error ? err.message : String(err);
17579
- console.error(import_picocolors3.default.red(`Failed to uninstall hook: ${message}`));
18014
+ console.error(import_picocolors4.default.red(`Error testing hooks: ${message}`));
17580
18015
  process.exit(1);
17581
18016
  }
17582
18017
  });
@@ -17585,7 +18020,7 @@ notesCmd.command("show [commit]").description("Display Qodewk receipt attached t
17585
18020
  try {
17586
18021
  const receipt = await readGitReceiptNote(commit);
17587
18022
  if (!receipt) {
17588
- console.log(import_picocolors3.default.yellow(`No Qodewk receipt note found on commit '${commit}'.`));
18023
+ console.log(import_picocolors4.default.yellow(`No Qodewk receipt note found on commit '${commit}'.`));
17589
18024
  return;
17590
18025
  }
17591
18026
  if (opts.json) {
@@ -17595,7 +18030,7 @@ notesCmd.command("show [commit]").description("Display Qodewk receipt attached t
17595
18030
  }
17596
18031
  } catch (err) {
17597
18032
  const message = err instanceof Error ? err.message : String(err);
17598
- console.error(import_picocolors3.default.red(`Error reading Git note: ${message}`));
18033
+ console.error(import_picocolors4.default.red(`Error reading Git note: ${message}`));
17599
18034
  process.exit(1);
17600
18035
  }
17601
18036
  });
@@ -17604,11 +18039,11 @@ notesCmd.command("write [commit]").description("Generate and attach receipt to a
17604
18039
  const receipt = await generateReceipt({ headSha: commit });
17605
18040
  await writeGitReceiptNote(receipt.repository.headSha, receipt);
17606
18041
  console.log(
17607
- import_picocolors3.default.green(`\u2713 Attached receipt note to ${receipt.repository.headSha.slice(0, 7)} (refs/notes/qodewk)`)
18042
+ import_picocolors4.default.green(`\u2713 Attached receipt note to ${receipt.repository.headSha.slice(0, 7)} (refs/notes/qodewk)`)
17608
18043
  );
17609
18044
  } catch (err) {
17610
18045
  const message = err instanceof Error ? err.message : String(err);
17611
- console.error(import_picocolors3.default.red(`Error writing Git note: ${message}`));
18046
+ console.error(import_picocolors4.default.red(`Error writing Git note: ${message}`));
17612
18047
  process.exit(1);
17613
18048
  }
17614
18049
  });
@@ -17616,16 +18051,16 @@ notesCmd.command("list").description("List all commits with attached Qodewk rece
17616
18051
  try {
17617
18052
  const shas = await listGitReceiptNotes();
17618
18053
  if (shas.length === 0) {
17619
- console.log(import_picocolors3.default.yellow("No Qodewk Git notes found in repository."));
18054
+ console.log(import_picocolors4.default.yellow("No Qodewk Git notes found in repository."));
17620
18055
  return;
17621
18056
  }
17622
- console.log(import_picocolors3.default.bold(`Found ${shas.length} commit note(s) in refs/notes/qodewk:`));
18057
+ console.log(import_picocolors4.default.bold(`Found ${shas.length} commit note(s) in refs/notes/qodewk:`));
17623
18058
  for (const sha of shas) {
17624
- console.log(` ${import_picocolors3.default.cyan(sha.slice(0, 10))} ${import_picocolors3.default.dim(sha)}`);
18059
+ console.log(` ${import_picocolors4.default.cyan(sha.slice(0, 10))} ${import_picocolors4.default.dim(sha)}`);
17625
18060
  }
17626
18061
  } catch (err) {
17627
18062
  const message = err instanceof Error ? err.message : String(err);
17628
- console.error(import_picocolors3.default.red(`Error listing Git notes: ${message}`));
18063
+ console.error(import_picocolors4.default.red(`Error listing Git notes: ${message}`));
17629
18064
  process.exit(1);
17630
18065
  }
17631
18066
  });