@codacy/verity-cli 0.32.7 → 0.33.0-experimental.34db3a4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/verity.js CHANGED
@@ -6975,10 +6975,10 @@ var require_resolve_block_map = __commonJS({
6975
6975
  let offset = bm.offset;
6976
6976
  let commentEnd = null;
6977
6977
  for (const collItem of bm.items) {
6978
- const { start, key, sep: sep3, value } = collItem;
6978
+ const { start, key, sep: sep4, value } = collItem;
6979
6979
  const keyProps = resolveProps.resolveProps(start, {
6980
6980
  indicator: "explicit-key-ind",
6981
- next: key ?? sep3?.[0],
6981
+ next: key ?? sep4?.[0],
6982
6982
  offset,
6983
6983
  onError,
6984
6984
  parentIndent: bm.indent,
@@ -6992,7 +6992,7 @@ var require_resolve_block_map = __commonJS({
6992
6992
  else if ("indent" in key && key.indent !== bm.indent)
6993
6993
  onError(offset, "BAD_INDENT", startColMsg);
6994
6994
  }
6995
- if (!keyProps.anchor && !keyProps.tag && !sep3) {
6995
+ if (!keyProps.anchor && !keyProps.tag && !sep4) {
6996
6996
  commentEnd = keyProps.end;
6997
6997
  if (keyProps.comment) {
6998
6998
  if (map.comment)
@@ -7016,7 +7016,7 @@ var require_resolve_block_map = __commonJS({
7016
7016
  ctx.atKey = false;
7017
7017
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
7018
7018
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
7019
- const valueProps = resolveProps.resolveProps(sep3 ?? [], {
7019
+ const valueProps = resolveProps.resolveProps(sep4 ?? [], {
7020
7020
  indicator: "map-value-ind",
7021
7021
  next: value,
7022
7022
  offset: keyNode.range[2],
@@ -7032,7 +7032,7 @@ var require_resolve_block_map = __commonJS({
7032
7032
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
7033
7033
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
7034
7034
  }
7035
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep3, null, valueProps, onError);
7035
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep4, null, valueProps, onError);
7036
7036
  if (ctx.schema.compat)
7037
7037
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
7038
7038
  offset = valueNode.range[2];
@@ -7123,7 +7123,7 @@ var require_resolve_end = __commonJS({
7123
7123
  let comment = "";
7124
7124
  if (end) {
7125
7125
  let hasSpace = false;
7126
- let sep3 = "";
7126
+ let sep4 = "";
7127
7127
  for (const token of end) {
7128
7128
  const { source, type } = token;
7129
7129
  switch (type) {
@@ -7137,13 +7137,13 @@ var require_resolve_end = __commonJS({
7137
7137
  if (!comment)
7138
7138
  comment = cb;
7139
7139
  else
7140
- comment += sep3 + cb;
7141
- sep3 = "";
7140
+ comment += sep4 + cb;
7141
+ sep4 = "";
7142
7142
  break;
7143
7143
  }
7144
7144
  case "newline":
7145
7145
  if (comment)
7146
- sep3 += source;
7146
+ sep4 += source;
7147
7147
  hasSpace = true;
7148
7148
  break;
7149
7149
  default:
@@ -7186,18 +7186,18 @@ var require_resolve_flow_collection = __commonJS({
7186
7186
  let offset = fc.offset + fc.start.source.length;
7187
7187
  for (let i = 0; i < fc.items.length; ++i) {
7188
7188
  const collItem = fc.items[i];
7189
- const { start, key, sep: sep3, value } = collItem;
7189
+ const { start, key, sep: sep4, value } = collItem;
7190
7190
  const props = resolveProps.resolveProps(start, {
7191
7191
  flow: fcName,
7192
7192
  indicator: "explicit-key-ind",
7193
- next: key ?? sep3?.[0],
7193
+ next: key ?? sep4?.[0],
7194
7194
  offset,
7195
7195
  onError,
7196
7196
  parentIndent: fc.indent,
7197
7197
  startOnNewline: false
7198
7198
  });
7199
7199
  if (!props.found) {
7200
- if (!props.anchor && !props.tag && !sep3 && !value) {
7200
+ if (!props.anchor && !props.tag && !sep4 && !value) {
7201
7201
  if (i === 0 && props.comma)
7202
7202
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
7203
7203
  else if (i < fc.items.length - 1)
@@ -7251,8 +7251,8 @@ var require_resolve_flow_collection = __commonJS({
7251
7251
  }
7252
7252
  }
7253
7253
  }
7254
- if (!isMap && !sep3 && !props.found) {
7255
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep3, null, props, onError);
7254
+ if (!isMap && !sep4 && !props.found) {
7255
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep4, null, props, onError);
7256
7256
  coll.items.push(valueNode);
7257
7257
  offset = valueNode.range[2];
7258
7258
  if (isBlock(value))
@@ -7264,7 +7264,7 @@ var require_resolve_flow_collection = __commonJS({
7264
7264
  if (isBlock(key))
7265
7265
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
7266
7266
  ctx.atKey = false;
7267
- const valueProps = resolveProps.resolveProps(sep3 ?? [], {
7267
+ const valueProps = resolveProps.resolveProps(sep4 ?? [], {
7268
7268
  flow: fcName,
7269
7269
  indicator: "map-value-ind",
7270
7270
  next: value,
@@ -7275,8 +7275,8 @@ var require_resolve_flow_collection = __commonJS({
7275
7275
  });
7276
7276
  if (valueProps.found) {
7277
7277
  if (!isMap && !props.found && ctx.options.strict) {
7278
- if (sep3)
7279
- for (const st of sep3) {
7278
+ if (sep4)
7279
+ for (const st of sep4) {
7280
7280
  if (st === valueProps.found)
7281
7281
  break;
7282
7282
  if (st.type === "newline") {
@@ -7293,7 +7293,7 @@ var require_resolve_flow_collection = __commonJS({
7293
7293
  else
7294
7294
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
7295
7295
  }
7296
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep3, null, valueProps, onError) : null;
7296
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep4, null, valueProps, onError) : null;
7297
7297
  if (valueNode) {
7298
7298
  if (isBlock(value))
7299
7299
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -7473,7 +7473,7 @@ var require_resolve_block_scalar = __commonJS({
7473
7473
  chompStart = i + 1;
7474
7474
  }
7475
7475
  let value = "";
7476
- let sep3 = "";
7476
+ let sep4 = "";
7477
7477
  let prevMoreIndented = false;
7478
7478
  for (let i = 0; i < contentStart; ++i)
7479
7479
  value += lines[i][0].slice(trimIndent) + "\n";
@@ -7490,24 +7490,24 @@ var require_resolve_block_scalar = __commonJS({
7490
7490
  indent = "";
7491
7491
  }
7492
7492
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
7493
- value += sep3 + indent.slice(trimIndent) + content;
7494
- sep3 = "\n";
7493
+ value += sep4 + indent.slice(trimIndent) + content;
7494
+ sep4 = "\n";
7495
7495
  } else if (indent.length > trimIndent || content[0] === " ") {
7496
- if (sep3 === " ")
7497
- sep3 = "\n";
7498
- else if (!prevMoreIndented && sep3 === "\n")
7499
- sep3 = "\n\n";
7500
- value += sep3 + indent.slice(trimIndent) + content;
7501
- sep3 = "\n";
7496
+ if (sep4 === " ")
7497
+ sep4 = "\n";
7498
+ else if (!prevMoreIndented && sep4 === "\n")
7499
+ sep4 = "\n\n";
7500
+ value += sep4 + indent.slice(trimIndent) + content;
7501
+ sep4 = "\n";
7502
7502
  prevMoreIndented = true;
7503
7503
  } else if (content === "") {
7504
- if (sep3 === "\n")
7504
+ if (sep4 === "\n")
7505
7505
  value += "\n";
7506
7506
  else
7507
- sep3 = "\n";
7507
+ sep4 = "\n";
7508
7508
  } else {
7509
- value += sep3 + content;
7510
- sep3 = " ";
7509
+ value += sep4 + content;
7510
+ sep4 = " ";
7511
7511
  prevMoreIndented = false;
7512
7512
  }
7513
7513
  }
@@ -7689,25 +7689,25 @@ var require_resolve_flow_scalar = __commonJS({
7689
7689
  if (!match)
7690
7690
  return source;
7691
7691
  let res = match[1];
7692
- let sep3 = " ";
7692
+ let sep4 = " ";
7693
7693
  let pos = first.lastIndex;
7694
7694
  line.lastIndex = pos;
7695
7695
  while (match = line.exec(source)) {
7696
7696
  if (match[1] === "") {
7697
- if (sep3 === "\n")
7698
- res += sep3;
7697
+ if (sep4 === "\n")
7698
+ res += sep4;
7699
7699
  else
7700
- sep3 = "\n";
7700
+ sep4 = "\n";
7701
7701
  } else {
7702
- res += sep3 + match[1];
7703
- sep3 = " ";
7702
+ res += sep4 + match[1];
7703
+ sep4 = " ";
7704
7704
  }
7705
7705
  pos = line.lastIndex;
7706
7706
  }
7707
7707
  const last = /[ \t]*(.*)/sy;
7708
7708
  last.lastIndex = pos;
7709
7709
  match = last.exec(source);
7710
- return res + sep3 + (match?.[1] ?? "");
7710
+ return res + sep4 + (match?.[1] ?? "");
7711
7711
  }
7712
7712
  function doubleQuotedValue(source, onError) {
7713
7713
  let res = "";
@@ -8514,14 +8514,14 @@ var require_cst_stringify = __commonJS({
8514
8514
  }
8515
8515
  }
8516
8516
  }
8517
- function stringifyItem({ start, key, sep: sep3, value }) {
8517
+ function stringifyItem({ start, key, sep: sep4, value }) {
8518
8518
  let res = "";
8519
8519
  for (const st of start)
8520
8520
  res += st.source;
8521
8521
  if (key)
8522
8522
  res += stringifyToken(key);
8523
- if (sep3)
8524
- for (const st of sep3)
8523
+ if (sep4)
8524
+ for (const st of sep4)
8525
8525
  res += st.source;
8526
8526
  if (value)
8527
8527
  res += stringifyToken(value);
@@ -9671,18 +9671,18 @@ var require_parser = __commonJS({
9671
9671
  if (this.type === "map-value-ind") {
9672
9672
  const prev = getPrevProps(this.peek(2));
9673
9673
  const start = getFirstKeyStartProps(prev);
9674
- let sep3;
9674
+ let sep4;
9675
9675
  if (scalar.end) {
9676
- sep3 = scalar.end;
9677
- sep3.push(this.sourceToken);
9676
+ sep4 = scalar.end;
9677
+ sep4.push(this.sourceToken);
9678
9678
  delete scalar.end;
9679
9679
  } else
9680
- sep3 = [this.sourceToken];
9680
+ sep4 = [this.sourceToken];
9681
9681
  const map = {
9682
9682
  type: "block-map",
9683
9683
  offset: scalar.offset,
9684
9684
  indent: scalar.indent,
9685
- items: [{ start, key: scalar, sep: sep3 }]
9685
+ items: [{ start, key: scalar, sep: sep4 }]
9686
9686
  };
9687
9687
  this.onKeyLine = true;
9688
9688
  this.stack[this.stack.length - 1] = map;
@@ -9835,15 +9835,15 @@ var require_parser = __commonJS({
9835
9835
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
9836
9836
  const start2 = getFirstKeyStartProps(it.start);
9837
9837
  const key = it.key;
9838
- const sep3 = it.sep;
9839
- sep3.push(this.sourceToken);
9838
+ const sep4 = it.sep;
9839
+ sep4.push(this.sourceToken);
9840
9840
  delete it.key;
9841
9841
  delete it.sep;
9842
9842
  this.stack.push({
9843
9843
  type: "block-map",
9844
9844
  offset: this.offset,
9845
9845
  indent: this.indent,
9846
- items: [{ start: start2, key, sep: sep3 }]
9846
+ items: [{ start: start2, key, sep: sep4 }]
9847
9847
  });
9848
9848
  } else if (start.length > 0) {
9849
9849
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -10037,13 +10037,13 @@ var require_parser = __commonJS({
10037
10037
  const prev = getPrevProps(parent);
10038
10038
  const start = getFirstKeyStartProps(prev);
10039
10039
  fixFlowSeqItems(fc);
10040
- const sep3 = fc.end.splice(1, fc.end.length);
10041
- sep3.push(this.sourceToken);
10040
+ const sep4 = fc.end.splice(1, fc.end.length);
10041
+ sep4.push(this.sourceToken);
10042
10042
  const map = {
10043
10043
  type: "block-map",
10044
10044
  offset: fc.offset,
10045
10045
  indent: fc.indent,
10046
- items: [{ start, key: fc, sep: sep3 }]
10046
+ items: [{ start, key: fc, sep: sep4 }]
10047
10047
  };
10048
10048
  this.onKeyLine = true;
10049
10049
  this.stack[this.stack.length - 1] = map;
@@ -10413,7 +10413,6 @@ var MAX_ITERATIONS = 2;
10413
10413
  var MAX_SPEC_FILES = 6;
10414
10414
  var MAX_SPEC_FILE_BYTES = 512e3;
10415
10415
  var MAX_TOTAL_SPEC_BYTES = 512e3;
10416
- var MAX_EXPLICIT_SPEC_FILE_BYTES = 10240;
10417
10416
  var MAX_PLAN_FILES = 3;
10418
10417
  var MAX_PLAN_FILE_BYTES = 512e3;
10419
10418
  var MAX_INTENT_CHARS = 2e3;
@@ -10524,7 +10523,7 @@ var SECURITY_PATTERNS = [
10524
10523
  /Dockerfile/
10525
10524
  ];
10526
10525
  var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10527
- var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
10526
+ var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
10528
10527
  var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
10529
10528
  var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
10530
10529
  var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
@@ -10970,8 +10969,8 @@ function filterReviewable(files) {
10970
10969
  const ext = (0, import_node_path3.extname)(f).slice(1);
10971
10970
  if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
10972
10971
  if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
10973
- const basename5 = f.split("/").pop() ?? "";
10974
- if (REVIEWABLE_FILENAMES.has(basename5)) return true;
10972
+ const basename4 = f.split("/").pop() ?? "";
10973
+ if (REVIEWABLE_FILENAMES.has(basename4)) return true;
10975
10974
  if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
10976
10975
  return false;
10977
10976
  });
@@ -11534,7 +11533,7 @@ function startSpinner(label2, opts = {}) {
11534
11533
  }
11535
11534
 
11536
11535
  // src/lib/provider-auth.ts
11537
- var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
11536
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
11538
11537
  var form = (fields) => new URLSearchParams(fields).toString();
11539
11538
  async function githubAccountId(owner) {
11540
11539
  try {
@@ -12801,50 +12800,12 @@ function resolveGuardMoments(explicit) {
12801
12800
  }
12802
12801
 
12803
12802
  // src/lib/plugin-ownership.ts
12804
- var import_node_fs7 = require("node:fs");
12805
-
12806
- // src/lib/which.ts
12807
- var import_node_fs5 = require("node:fs");
12808
- var import_node_path7 = require("node:path");
12809
- function executableExtensions(platform, pathext) {
12810
- if (platform !== "win32") return [""];
12811
- const raw = (pathext ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
12812
- const out = [];
12813
- for (const ext of raw) {
12814
- const lower = ext.toLowerCase();
12815
- if (!out.includes(lower)) out.push(lower);
12816
- if (!out.includes(ext)) out.push(ext);
12817
- }
12818
- return out;
12819
- }
12820
- function whichSync(bin, opts = {}) {
12821
- const platform = opts.platform ?? process.platform;
12822
- const rawPath = opts.path ?? process.env.PATH ?? "";
12823
- if (!rawPath) return null;
12824
- const exts = executableExtensions(platform, opts.pathext ?? process.env.PATHEXT);
12825
- const real = opts.real ?? true;
12826
- for (const dir of rawPath.split(import_node_path7.delimiter)) {
12827
- if (!dir) continue;
12828
- for (const ext of exts) {
12829
- const candidate = (0, import_node_path7.join)(dir, bin + ext);
12830
- try {
12831
- if (!(0, import_node_fs5.statSync)(candidate).isFile()) continue;
12832
- if (platform !== "win32") (0, import_node_fs5.accessSync)(candidate, import_node_fs5.constants.X_OK);
12833
- return real ? (0, import_node_fs5.realpathSync)(candidate) : candidate;
12834
- } catch {
12835
- continue;
12836
- }
12837
- }
12838
- }
12839
- return null;
12840
- }
12841
-
12842
- // src/lib/plugin-ownership.ts
12803
+ var import_node_fs6 = require("node:fs");
12843
12804
  var import_node_os2 = require("node:os");
12844
- var import_node_path8 = require("node:path");
12805
+ var import_node_path7 = require("node:path");
12845
12806
 
12846
12807
  // src/lib/stderr-log.ts
12847
- var import_node_fs6 = require("node:fs");
12808
+ var import_node_fs5 = require("node:fs");
12848
12809
  var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
12849
12810
  var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
12850
12811
  function scrub(s) {
@@ -12857,9 +12818,9 @@ function append(text) {
12857
12818
  try {
12858
12819
  const dir = projectPath(DEBUG_LOG_DIR);
12859
12820
  const file = projectPath(STDERR_LOG_FILE);
12860
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
12821
+ (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
12861
12822
  rotateIfNeeded(file);
12862
- (0, import_node_fs6.appendFileSync)(file, text);
12823
+ (0, import_node_fs5.appendFileSync)(file, text);
12863
12824
  } catch {
12864
12825
  }
12865
12826
  }
@@ -12907,8 +12868,8 @@ function markerPath() {
12907
12868
  }
12908
12869
  function readMarker() {
12909
12870
  try {
12910
- if (!(0, import_node_fs7.existsSync)(markerPath())) return null;
12911
- const raw = JSON.parse((0, import_node_fs7.readFileSync)(markerPath(), "utf-8"));
12871
+ if (!(0, import_node_fs6.existsSync)(markerPath())) return null;
12872
+ const raw = JSON.parse((0, import_node_fs6.readFileSync)(markerPath(), "utf-8"));
12912
12873
  const pluginRoot = typeof raw.plugin_root === "string" ? raw.plugin_root : "";
12913
12874
  if (!pluginRoot || CONTROL_CHARS.test(pluginRoot)) return null;
12914
12875
  return {
@@ -12925,23 +12886,23 @@ function recordPluginOwnership(sessionId) {
12925
12886
  const pluginRoot = process.env.VERITY_PLUGIN_ROOT;
12926
12887
  if (!pluginRoot) return;
12927
12888
  try {
12928
- (0, import_node_fs7.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
12889
+ (0, import_node_fs6.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
12929
12890
  const marker = {
12930
12891
  session_id: sessionId,
12931
12892
  plugin_root: pluginRoot,
12932
12893
  version: process.env.VERITY_PLUGIN_VERSION || null,
12933
12894
  ts: Math.floor(Date.now() / 1e3)
12934
12895
  };
12935
- (0, import_node_fs7.writeFileSync)(markerPath(), JSON.stringify(marker));
12896
+ (0, import_node_fs6.writeFileSync)(markerPath(), JSON.stringify(marker));
12936
12897
  } catch {
12937
12898
  }
12938
12899
  }
12939
12900
  function claudeConfigDir() {
12940
- return process.env.CLAUDE_CONFIG_DIR || (0, import_node_path8.join)((0, import_node_os2.homedir)(), ".claude");
12901
+ return process.env.CLAUDE_CONFIG_DIR || (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".claude");
12941
12902
  }
12942
12903
  function readJsonFile(path) {
12943
12904
  try {
12944
- const parsed = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf-8"));
12905
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf-8"));
12945
12906
  return parsed && typeof parsed === "object" ? parsed : null;
12946
12907
  } catch {
12947
12908
  return null;
@@ -12949,9 +12910,9 @@ function readJsonFile(path) {
12949
12910
  }
12950
12911
  function enabledPluginSetting(key) {
12951
12912
  const files = [
12952
- projectPath((0, import_node_path8.join)(".claude", "settings.local.json")),
12953
- projectPath((0, import_node_path8.join)(".claude", "settings.json")),
12954
- (0, import_node_path8.join)(claudeConfigDir(), "settings.json")
12913
+ projectPath((0, import_node_path7.join)(".claude", "settings.local.json")),
12914
+ projectPath((0, import_node_path7.join)(".claude", "settings.json")),
12915
+ (0, import_node_path7.join)(claudeConfigDir(), "settings.json")
12955
12916
  ];
12956
12917
  for (const file of files) {
12957
12918
  const map = readJsonFile(file)?.enabledPlugins;
@@ -12963,16 +12924,27 @@ function enabledPluginSetting(key) {
12963
12924
  }
12964
12925
  function marketplaceLocations() {
12965
12926
  const out = /* @__PURE__ */ new Map();
12966
- const known = readJsonFile((0, import_node_path8.join)(claudeConfigDir(), "plugins", "known_marketplaces.json"));
12927
+ const known = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "known_marketplaces.json"));
12967
12928
  if (!known) return out;
12968
12929
  for (const [name, entry] of Object.entries(known)) {
12969
12930
  const loc2 = entry?.installLocation;
12970
- if (typeof loc2 === "string" && loc2) out.set(name, (0, import_node_path8.resolve)(loc2));
12931
+ if (typeof loc2 === "string" && loc2) out.set(name, (0, import_node_path7.resolve)(loc2));
12971
12932
  }
12972
12933
  return out;
12973
12934
  }
12974
12935
  function verityPathEntry() {
12975
- return whichSync("verity", { real: false });
12936
+ const path = process.env.PATH;
12937
+ if (!path) return null;
12938
+ for (const dir of path.split(import_node_path7.delimiter)) {
12939
+ if (!dir) continue;
12940
+ const candidate = (0, import_node_path7.join)(dir, "verity");
12941
+ try {
12942
+ (0, import_node_fs6.accessSync)(candidate, import_node_fs6.constants.X_OK);
12943
+ return candidate;
12944
+ } catch {
12945
+ }
12946
+ }
12947
+ return null;
12976
12948
  }
12977
12949
  function verityOnPath() {
12978
12950
  return verityPathEntry() !== null;
@@ -12981,7 +12953,7 @@ function globalVerityVersion() {
12981
12953
  const entry = verityPathEntry();
12982
12954
  if (!entry) return null;
12983
12955
  try {
12984
- const pkg = readJsonFile((0, import_node_path8.join)((0, import_node_path8.dirname)((0, import_node_fs7.realpathSync)(entry)), "..", "package.json"));
12956
+ const pkg = readJsonFile((0, import_node_path7.join)((0, import_node_path7.dirname)((0, import_node_fs6.realpathSync)(entry)), "..", "package.json"));
12985
12957
  if (pkg?.name !== "@codacy/verity-cli" || typeof pkg.version !== "string") return null;
12986
12958
  return pkg.version;
12987
12959
  } catch {
@@ -12993,7 +12965,7 @@ function pluginCliInvocation() {
12993
12965
  if (!root) return null;
12994
12966
  const onPath = globalVerityVersion();
12995
12967
  if (onPath !== null && onPath === activePluginVersion()) return null;
12996
- return `node ${JSON.stringify((0, import_node_path8.join)(root, "scripts", "verity.mjs"))}`;
12968
+ return `node ${JSON.stringify((0, import_node_path7.join)(root, "scripts", "verity.mjs"))}`;
12997
12969
  }
12998
12970
  function cliVersionSkew() {
12999
12971
  if (!process.env.VERITY_PLUGIN_ROOT) return null;
@@ -13012,7 +12984,7 @@ var VERITY_MARKETPLACE_REPO = "codacy/verity";
13012
12984
  function marketplaceConflict() {
13013
12985
  const wanted = VERITY_MARKETPLACE;
13014
12986
  for (const file of ["settings.json", "settings.local.json"]) {
13015
- const path = (0, import_node_path8.join)(claudeConfigDir(), file);
12987
+ const path = (0, import_node_path7.join)(claudeConfigDir(), file);
13016
12988
  const declared = readJsonFile(path)?.extraKnownMarketplaces ?? null;
13017
12989
  const entry2 = declared && typeof declared === "object" ? declared[wanted] : void 0;
13018
12990
  if (entry2 && typeof entry2 === "object") {
@@ -13022,7 +12994,7 @@ function marketplaceConflict() {
13022
12994
  }
13023
12995
  }
13024
12996
  }
13025
- const known = readJsonFile((0, import_node_path8.join)(claudeConfigDir(), "plugins", "known_marketplaces.json"));
12997
+ const known = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "known_marketplaces.json"));
13026
12998
  const entry = known?.[wanted];
13027
12999
  if (entry && typeof entry === "object" && !pointsAtVerity(entry.source)) {
13028
13000
  return { name: wanted, declaredAs: describeSource(entry.source) };
@@ -13043,7 +13015,7 @@ function describeSource(src) {
13043
13015
  return target ? `${kind} \u2192 ${target}` : kind;
13044
13016
  }
13045
13017
  function legacyMarketplaceInstall() {
13046
- const plugins = readJsonFile((0, import_node_path8.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
13018
+ const plugins = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
13047
13019
  if (!plugins || typeof plugins !== "object") return null;
13048
13020
  for (const key of Object.keys(plugins)) {
13049
13021
  const at = key.lastIndexOf("@");
@@ -13055,13 +13027,13 @@ function legacyMarketplaceInstall() {
13055
13027
  }
13056
13028
  function realpathOr(p) {
13057
13029
  try {
13058
- return import_node_fs7.realpathSync.native(p);
13030
+ return import_node_fs6.realpathSync.native(p);
13059
13031
  } catch {
13060
- return (0, import_node_path8.resolve)(p);
13032
+ return (0, import_node_path7.resolve)(p);
13061
13033
  }
13062
13034
  }
13063
13035
  function isWithin(want, dir) {
13064
- return want === dir || want.startsWith(dir + import_node_path8.sep);
13036
+ return want === dir || want.startsWith(dir + import_node_path7.sep);
13065
13037
  }
13066
13038
  function entryAppliesHere(entry, here) {
13067
13039
  const e = entry;
@@ -13071,9 +13043,9 @@ function entryAppliesHere(entry, here) {
13071
13043
  return forProject === here;
13072
13044
  }
13073
13045
  function registrySays(pluginRoot) {
13074
- const plugins = readJsonFile((0, import_node_path8.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
13046
+ const plugins = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
13075
13047
  if (!plugins || typeof plugins !== "object") return "unverified";
13076
- const want = (0, import_node_path8.resolve)(pluginRoot);
13048
+ const want = (0, import_node_path7.resolve)(pluginRoot);
13077
13049
  const here = realpathOr(repoRoot());
13078
13050
  const markets = marketplaceLocations();
13079
13051
  for (const [key, value] of Object.entries(plugins)) {
@@ -13082,15 +13054,15 @@ function registrySays(pluginRoot) {
13082
13054
  const applicable = (Array.isArray(value) ? value : []).filter((entry) => entryAppliesHere(entry, here));
13083
13055
  const claims = applicable.some((entry) => {
13084
13056
  const installPath = entry?.installPath;
13085
- return typeof installPath === "string" && (0, import_node_path8.resolve)(installPath) === want;
13057
+ return typeof installPath === "string" && (0, import_node_path7.resolve)(installPath) === want;
13086
13058
  }) || source !== void 0 && applicable.length > 0 && isWithin(want, source);
13087
13059
  if (claims) {
13088
13060
  if (enabledPluginSetting(key) === false) return "gone";
13089
- return (0, import_node_fs7.existsSync)(pluginRoot) ? "live" : "gone";
13061
+ return (0, import_node_fs6.existsSync)(pluginRoot) ? "live" : "gone";
13090
13062
  }
13091
13063
  }
13092
- const managed = (0, import_node_path8.resolve)((0, import_node_path8.join)(claudeConfigDir(), "plugins", "cache"));
13093
- return want === managed || want.startsWith(managed + import_node_path8.sep) ? "gone" : "unverified";
13064
+ const managed = (0, import_node_path7.resolve)((0, import_node_path7.join)(claudeConfigDir(), "plugins", "cache"));
13065
+ return want === managed || want.startsWith(managed + import_node_path7.sep) ? "gone" : "unverified";
13094
13066
  }
13095
13067
  var _live = /* @__PURE__ */ new Map();
13096
13068
  function pluginLiveness(pluginRoot) {
@@ -13104,7 +13076,7 @@ function clearStalePluginMarker() {
13104
13076
  const marker = readMarker();
13105
13077
  if (!marker || pluginLiveness(marker.plugin_root) !== "gone") return null;
13106
13078
  try {
13107
- (0, import_node_fs7.rmSync)(markerPath(), { force: true });
13079
+ (0, import_node_fs6.rmSync)(markerPath(), { force: true });
13108
13080
  } catch {
13109
13081
  return null;
13110
13082
  }
@@ -13132,7 +13104,7 @@ function pluginActiveHere() {
13132
13104
  return activePluginInstall() !== null;
13133
13105
  }
13134
13106
  function registeredVerityPlugin() {
13135
- const plugins = readJsonFile((0, import_node_path8.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
13107
+ const plugins = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
13136
13108
  if (!plugins || typeof plugins !== "object") return null;
13137
13109
  const here = realpathOr(repoRoot());
13138
13110
  for (const [key, value] of Object.entries(plugins)) {
@@ -13142,7 +13114,7 @@ function registeredVerityPlugin() {
13142
13114
  for (const entry of Array.isArray(value) ? value : []) {
13143
13115
  const e = entry;
13144
13116
  const installPath = typeof e.installPath === "string" ? e.installPath : "";
13145
- if (!installPath || !(0, import_node_fs7.existsSync)(installPath)) continue;
13117
+ if (!installPath || !(0, import_node_fs6.existsSync)(installPath)) continue;
13146
13118
  if (!entryAppliesHere(entry, here)) continue;
13147
13119
  return { pluginRoot: installPath, version: typeof e.version === "string" ? e.version : null };
13148
13120
  }
@@ -13327,7 +13299,7 @@ var import_node_crypto8 = require("node:crypto");
13327
13299
 
13328
13300
  // src/lib/conversation-buffer.ts
13329
13301
  var import_promises5 = require("node:fs/promises");
13330
- var import_node_fs8 = require("node:fs");
13302
+ var import_node_fs7 = require("node:fs");
13331
13303
  var import_node_child_process5 = require("node:child_process");
13332
13304
  var import_node_crypto = require("node:crypto");
13333
13305
  function stripImageReferences(text) {
@@ -13363,7 +13335,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
13363
13335
  }
13364
13336
  async function readAndClearConversationBuffer(currentSessionId) {
13365
13337
  try {
13366
- if ((0, import_node_fs8.existsSync)(CONVERSATION_BUFFER_FILE)) {
13338
+ if ((0, import_node_fs7.existsSync)(CONVERSATION_BUFFER_FILE)) {
13367
13339
  const entries = await readBufferEntries();
13368
13340
  let mine = entries;
13369
13341
  let others = [];
@@ -13387,7 +13359,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
13387
13359
  };
13388
13360
  }
13389
13361
  }
13390
- if ((0, import_node_fs8.existsSync)(INTENT_FILE)) {
13362
+ if ((0, import_node_fs7.existsSync)(INTENT_FILE)) {
13391
13363
  try {
13392
13364
  const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
13393
13365
  await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
@@ -13680,9 +13652,9 @@ function isCommandOnlyTurn(input) {
13680
13652
 
13681
13653
  // src/lib/context-identity.ts
13682
13654
  var import_node_crypto2 = require("node:crypto");
13683
- var import_node_fs9 = require("node:fs");
13655
+ var import_node_fs8 = require("node:fs");
13684
13656
  var import_node_os3 = require("node:os");
13685
- var import_node_path9 = require("node:path");
13657
+ var import_node_path8 = require("node:path");
13686
13658
  var SHARED_SENTINELS = /* @__PURE__ */ new Set([
13687
13659
  "",
13688
13660
  "-",
@@ -13720,7 +13692,7 @@ function contextIdentity(input) {
13720
13692
  if (rawTree && !isSharedSentinel(rawTree)) {
13721
13693
  let resolved = rawTree;
13722
13694
  try {
13723
- resolved = import_node_fs9.realpathSync.native(rawTree);
13695
+ resolved = import_node_fs8.realpathSync.native(rawTree);
13724
13696
  } catch {
13725
13697
  }
13726
13698
  treeKey = (0, import_node_crypto2.createHash)("sha256").update(resolved).digest("hex").slice(0, 12);
@@ -13736,13 +13708,13 @@ function contextIdentity(input) {
13736
13708
  }
13737
13709
  function verityHome() {
13738
13710
  const override = process.env.VERITY_HOME;
13739
- return override && override.trim() ? (0, import_node_path9.resolve)(override) : (0, import_node_path9.join)((0, import_node_os3.homedir)(), ".verity");
13711
+ return override && override.trim() ? (0, import_node_path8.resolve)(override) : (0, import_node_path8.join)((0, import_node_os3.homedir)(), ".verity");
13740
13712
  }
13741
13713
  function dossierDir(identity) {
13742
- return (0, import_node_path9.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
13714
+ return (0, import_node_path8.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
13743
13715
  }
13744
13716
  function treeDir(identity) {
13745
- return (0, import_node_path9.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
13717
+ return (0, import_node_path8.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
13746
13718
  }
13747
13719
  function scopeIdentity(token, sessionId) {
13748
13720
  const t = (token ?? "").trim();
@@ -13757,8 +13729,8 @@ function sessionScopeKey(token, sessionId) {
13757
13729
 
13758
13730
  // src/lib/task-context-buffer.ts
13759
13731
  var import_promises6 = require("node:fs/promises");
13760
- var import_node_fs10 = require("node:fs");
13761
- var import_node_path10 = require("node:path");
13732
+ var import_node_fs9 = require("node:fs");
13733
+ var import_node_path9 = require("node:path");
13762
13734
  var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
13763
13735
  var MAX_BUFFER_BYTES = 500 * 1024;
13764
13736
  var MAX_PROMPT_CHARS = 2e3;
@@ -13797,7 +13769,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
13797
13769
  }
13798
13770
  async function readTaskContextBuffer(taskId) {
13799
13771
  const filePath = bufferPath(taskId);
13800
- if (!(0, import_node_fs10.existsSync)(filePath)) return null;
13772
+ if (!(0, import_node_fs9.existsSync)(filePath)) return null;
13801
13773
  try {
13802
13774
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
13803
13775
  if (!content.trim()) return null;
@@ -13831,12 +13803,12 @@ async function readTaskContextBuffer(taskId) {
13831
13803
  }
13832
13804
  async function cleanupTaskContextBuffers() {
13833
13805
  try {
13834
- if (!(0, import_node_fs10.existsSync)(TASK_CONTEXT_DIR)) return;
13806
+ if (!(0, import_node_fs9.existsSync)(TASK_CONTEXT_DIR)) return;
13835
13807
  const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
13836
13808
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
13837
13809
  for (const file of files) {
13838
13810
  if (!file.endsWith(".jsonl")) continue;
13839
- const filePath = (0, import_node_path10.join)(TASK_CONTEXT_DIR, file);
13811
+ const filePath = (0, import_node_path9.join)(TASK_CONTEXT_DIR, file);
13840
13812
  try {
13841
13813
  const stats = await (0, import_promises6.stat)(filePath);
13842
13814
  if (stats.mtimeMs < cutoffMs) {
@@ -13850,13 +13822,13 @@ async function cleanupTaskContextBuffers() {
13850
13822
  }
13851
13823
  function bufferPath(taskId) {
13852
13824
  const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
13853
- return (0, import_node_path10.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
13825
+ return (0, import_node_path9.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
13854
13826
  }
13855
13827
  async function appendEntry(taskId, entry) {
13856
13828
  try {
13857
13829
  await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
13858
13830
  const filePath = bufferPath(taskId);
13859
- if ((0, import_node_fs10.existsSync)(filePath)) {
13831
+ if ((0, import_node_fs9.existsSync)(filePath)) {
13860
13832
  const stats = await (0, import_promises6.stat)(filePath);
13861
13833
  if (stats.size >= MAX_BUFFER_BYTES) {
13862
13834
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
@@ -13867,21 +13839,213 @@ async function appendEntry(taskId, entry) {
13867
13839
  }
13868
13840
  }
13869
13841
  const line = JSON.stringify(entry) + "\n";
13870
- const existing = (0, import_node_fs10.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
13842
+ const existing = (0, import_node_fs9.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
13871
13843
  await (0, import_promises6.writeFile)(filePath, existing + line);
13872
13844
  } catch {
13873
13845
  }
13874
13846
  }
13875
13847
 
13876
13848
  // src/lib/memory-retrieval.ts
13877
- var import_promises7 = require("node:fs/promises");
13849
+ var import_promises8 = require("node:fs/promises");
13878
13850
  var import_node_fs11 = require("node:fs");
13879
13851
  var import_node_path11 = require("node:path");
13852
+
13853
+ // src/lib/org-mirror.ts
13854
+ var import_node_fs10 = require("node:fs");
13855
+ var import_promises7 = require("node:fs/promises");
13856
+ var import_node_path10 = require("node:path");
13857
+ var ORG_KINDS = ["decision", "security", "gotcha", "pattern", "domain", "integration"];
13858
+ var STATE_FILE = ".org-pull-state.json";
13859
+ var BODY_MAX = 8192;
13860
+ function orgMirrorDir(remote = requestRemote()) {
13861
+ const parsed = parseRemote(remote);
13862
+ if (!parsed) return null;
13863
+ return (0, import_node_path10.join)(verityHome(), "orgs", parsed.host, parsed.owner.toLowerCase(), "memory");
13864
+ }
13865
+ function slugify(s) {
13866
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
13867
+ }
13868
+ function orgNodePath(node) {
13869
+ const kind = ORG_KINDS.includes(node.kind) ? node.kind : "domain";
13870
+ return `${kind}/${slugify(node.title) || "claim"}-${node.tag_id.replace(/-/g, "").slice(0, 8)}.md`;
13871
+ }
13872
+ function renderOrgNode(node, orgName) {
13873
+ const fm = [
13874
+ "---",
13875
+ `id: ${JSON.stringify(node.tag_id)}`,
13876
+ `node_id: ${JSON.stringify(node.node_id)}`,
13877
+ 'scope: "org"',
13878
+ `org: ${JSON.stringify(orgName)}`,
13879
+ `kind: ${JSON.stringify(node.kind)}`,
13880
+ `title: ${JSON.stringify(node.title)}`,
13881
+ `origin: ${JSON.stringify(node.origin)}`,
13882
+ `signal: ${JSON.stringify(node.signal)}`,
13883
+ `tier: ${node.tier === null ? "null" : JSON.stringify(node.tier)}`,
13884
+ `applies_to: ${JSON.stringify(node.applies_to)}`,
13885
+ `source_repo_count: ${JSON.stringify(node.source_repo_count)}`,
13886
+ `promoted_at: ${JSON.stringify(node.promoted_at)}`,
13887
+ 'status: "active"',
13888
+ "---"
13889
+ ];
13890
+ return `${fm.join("\n")}
13891
+
13892
+ # ${node.title}
13893
+
13894
+ ${node.body.trim()}
13895
+ `;
13896
+ }
13897
+ function parseOrgNode(content) {
13898
+ const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
13899
+ if (!m) return null;
13900
+ const fm = {};
13901
+ for (const line of m[1].split("\n")) {
13902
+ const kv = line.match(/^([a-z_]+):\s*(.*)$/);
13903
+ if (!kv) continue;
13904
+ try {
13905
+ fm[kv[1]] = JSON.parse(kv[2]);
13906
+ } catch {
13907
+ fm[kv[1]] = kv[2];
13908
+ }
13909
+ }
13910
+ if (fm.scope !== "org" || typeof fm.id !== "string" || typeof fm.title !== "string") return null;
13911
+ if (fm.status && fm.status !== "active") return null;
13912
+ const body = m[2].replace(/^\s*#[^\n]*\n/, "").trim();
13913
+ return {
13914
+ tag_id: fm.id,
13915
+ node_id: typeof fm.node_id === "string" ? fm.node_id : "",
13916
+ kind: typeof fm.kind === "string" ? fm.kind : "domain",
13917
+ title: fm.title,
13918
+ body,
13919
+ origin: typeof fm.origin === "string" ? fm.origin : "",
13920
+ signal: typeof fm.signal === "string" ? fm.signal : "",
13921
+ tier: typeof fm.tier === "number" ? fm.tier : null,
13922
+ applies_to: Array.isArray(fm.applies_to) ? fm.applies_to.filter((v) => typeof v === "string") : [],
13923
+ source_repo_count: typeof fm.source_repo_count === "number" ? fm.source_repo_count : 1,
13924
+ promoted_at: typeof fm.promoted_at === "string" ? fm.promoted_at : ""
13925
+ };
13926
+ }
13927
+ async function readState(dir) {
13928
+ try {
13929
+ const parsed = JSON.parse(await (0, import_promises7.readFile)((0, import_node_path10.join)(dir, STATE_FILE), "utf-8"));
13930
+ return {
13931
+ version: typeof parsed?.version === "string" ? parsed.version : null,
13932
+ organization: parsed?.organization && typeof parsed.organization.id === "string" ? parsed.organization : null,
13933
+ files: Array.isArray(parsed?.files) ? parsed.files.filter((f) => typeof f === "string") : []
13934
+ };
13935
+ } catch {
13936
+ return { version: null, organization: null, files: [] };
13937
+ }
13938
+ }
13939
+ async function writeState(dir, state) {
13940
+ await (0, import_promises7.mkdir)(dir, { recursive: true });
13941
+ await (0, import_promises7.writeFile)((0, import_node_path10.join)(dir, STATE_FILE), JSON.stringify({
13942
+ ...state.version ? { version: state.version } : {},
13943
+ organization: state.organization,
13944
+ pulled_at: (/* @__PURE__ */ new Date()).toISOString(),
13945
+ files: state.files
13946
+ }, null, 2) + "\n");
13947
+ }
13948
+ function renderIndex(orgName, nodes) {
13949
+ const lines = [
13950
+ `# Org Knowledge \u2014 ${orgName}`,
13951
+ "",
13952
+ "*Auto-generated by verity CLI from what this organization's repositories learned. Do not hand-edit; `verity memory pull` overwrites it.*",
13953
+ "",
13954
+ '> These claims hold across the organization\'s repositories, not only this one. Each names where it came from. A claim that is wrong for this repository is demoted with `verity memory demote <id> --reason "\u2026"`, for everyone.',
13955
+ ""
13956
+ ];
13957
+ const byKind = /* @__PURE__ */ new Map();
13958
+ for (const n of nodes) byKind.set(n.kind, [...byKind.get(n.kind) ?? [], n]);
13959
+ for (const kind of ORG_KINDS) {
13960
+ const list2 = byKind.get(kind);
13961
+ if (!list2?.length) continue;
13962
+ lines.push(`## ${kind}/ (${list2.length})`);
13963
+ for (const n of list2) lines.push(`- [[${orgNodePath(n).split("/")[1].replace(/\.md$/, "")}]] \u2014 ${n.title} \xB7 ${n.origin}`);
13964
+ lines.push("");
13965
+ }
13966
+ if (nodes.length === 0) lines.push("No org knowledge yet.", "");
13967
+ return lines.join("\n");
13968
+ }
13969
+ async function pullOrgKnowledge(opts) {
13970
+ const dir = orgMirrorDir(opts.remote ?? requestRemote());
13971
+ if (!dir) return { ok: true, status: "no_remote", received: 0, dir: null };
13972
+ const state = await readState(dir);
13973
+ const params = new URLSearchParams({ for: "agent" });
13974
+ if (!opts.force && state.version) params.set("if_version", state.version);
13975
+ const res = await apiRequest({
13976
+ method: "GET",
13977
+ path: `/memory/org?${params.toString()}`,
13978
+ serviceUrl: opts.serviceUrl,
13979
+ token: opts.token,
13980
+ verbose: opts.verbose,
13981
+ timeout: opts.timeoutMs ?? 3e4,
13982
+ cmd: "memory-org-pull"
13983
+ });
13984
+ if (!res.ok) return { ok: false, error: res.error, category: res.category };
13985
+ const page = res.data;
13986
+ if (page.unchanged) return { ok: true, status: "unchanged", received: 0, dir };
13987
+ const org = page.organization;
13988
+ const nodes = org ? (page.nodes ?? []).flatMap((n) => typeof n.tag_id === "string" && typeof n.title === "string" && typeof n.body === "string" ? [{
13989
+ tag_id: n.tag_id,
13990
+ node_id: typeof n.node_id === "string" ? n.node_id : "",
13991
+ kind: typeof n.kind === "string" ? n.kind : "domain",
13992
+ title: n.title,
13993
+ body: n.body.slice(0, BODY_MAX),
13994
+ origin: typeof n.origin === "string" ? n.origin : "",
13995
+ signal: typeof n.signal === "string" ? n.signal : "",
13996
+ tier: typeof n.tier === "number" ? n.tier : null,
13997
+ applies_to: Array.isArray(n.applies_to) ? n.applies_to.filter((v) => typeof v === "string") : [],
13998
+ source_repo_count: typeof n.source_repo_count === "number" ? n.source_repo_count : 1,
13999
+ promoted_at: typeof n.promoted_at === "string" ? n.promoted_at : ""
14000
+ }] : []) : [];
14001
+ const written = /* @__PURE__ */ new Set();
14002
+ await (0, import_promises7.mkdir)(dir, { recursive: true });
14003
+ for (const n of nodes) {
14004
+ const rel = orgNodePath(n);
14005
+ if (written.has(rel)) continue;
14006
+ await (0, import_promises7.mkdir)((0, import_node_path10.join)(dir, rel.split("/")[0]), { recursive: true });
14007
+ await (0, import_promises7.writeFile)((0, import_node_path10.join)(dir, rel), renderOrgNode(n, org?.name ?? ""));
14008
+ written.add(rel);
14009
+ }
14010
+ for (const rel of state.files) {
14011
+ if (!written.has(rel)) await (0, import_promises7.rm)((0, import_node_path10.join)(dir, rel), { force: true });
14012
+ }
14013
+ await (0, import_promises7.writeFile)((0, import_node_path10.join)(dir, "index.md"), renderIndex(org?.name ?? "no organization", nodes));
14014
+ await writeState(dir, { version: org ? page.version : null, organization: org, files: [...written].sort() });
14015
+ return { ok: true, status: org ? "pulled" : "none", received: nodes.length, dir };
14016
+ }
14017
+ async function readOrgMirror(remote = requestRemote()) {
14018
+ const dir = orgMirrorDir(remote);
14019
+ if (!dir || !(0, import_node_fs10.existsSync)(dir)) return [];
14020
+ const out = [];
14021
+ for (const kind of ORG_KINDS) {
14022
+ const kindDir = (0, import_node_path10.join)(dir, kind);
14023
+ if (!(0, import_node_fs10.existsSync)(kindDir)) continue;
14024
+ let files;
14025
+ try {
14026
+ files = await (0, import_promises7.readdir)(kindDir);
14027
+ } catch {
14028
+ continue;
14029
+ }
14030
+ for (const file of files) {
14031
+ if (!file.endsWith(".md")) continue;
14032
+ try {
14033
+ const node = parseOrgNode(await (0, import_promises7.readFile)((0, import_node_path10.join)(kindDir, file), "utf-8"));
14034
+ if (node) out.push(node);
14035
+ } catch {
14036
+ }
14037
+ }
14038
+ }
14039
+ return out;
14040
+ }
14041
+
14042
+ // src/lib/memory-retrieval.ts
13880
14043
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
13881
14044
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
13882
14045
  var DEFAULT_BUDGET_TOKENS = 2e3;
13883
14046
  var MAX_BUDGET_TOKENS = 4e3;
13884
14047
  var MIN_SCORE = 0.5;
14048
+ var ORG_MIN_SHARED_TERMS = 2;
13885
14049
  function tokenize(text) {
13886
14050
  return new Set(
13887
14051
  text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
@@ -13894,6 +14058,26 @@ function jaccardKeywords(a, b) {
13894
14058
  const union = a.size + b.size - inter;
13895
14059
  return union === 0 ? 0 : inter / union;
13896
14060
  }
14061
+ function sharedTerms(a, b) {
14062
+ let n = 0;
14063
+ for (const w of a) if (b.has(w)) n++;
14064
+ return n;
14065
+ }
14066
+ function orgNodeToLocal(n) {
14067
+ return {
14068
+ path: `org:${n.tag_id}`,
14069
+ nodeId: n.tag_id,
14070
+ kind: n.kind,
14071
+ title: n.title,
14072
+ body: n.body.slice(0, 2e3),
14073
+ fileGlobs: [],
14074
+ confidence: 0.5,
14075
+ citedCount: 0,
14076
+ tokenEstimate: Math.ceil((n.title.length + n.body.length) / 4) + 20,
14077
+ scope: "org",
14078
+ origin: n.origin
14079
+ };
14080
+ }
13897
14081
  function globMatch(glob, filePath) {
13898
14082
  if (glob === filePath) return true;
13899
14083
  const suffixMatch = glob.match(/^\*\*\/\*(.+)$/);
@@ -13969,19 +14153,19 @@ function parseFrontmatter(content) {
13969
14153
  return { fm, body: match[2].trim() };
13970
14154
  }
13971
14155
  async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
13972
- if (!(0, import_node_fs11.existsSync)(memoryDir())) return null;
14156
+ const hasRepoGraph = (0, import_node_fs11.existsSync)(memoryDir());
13973
14157
  const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
13974
14158
  const promptTokens = tokenize(promptText);
13975
14159
  const nodes = [];
13976
- for (const domain of DOMAINS) {
14160
+ for (const domain of hasRepoGraph ? DOMAINS : []) {
13977
14161
  const domainDir = (0, import_node_path11.join)(memoryDir(), domain);
13978
14162
  if (!(0, import_node_fs11.existsSync)(domainDir)) continue;
13979
14163
  try {
13980
- const files = await (0, import_promises7.readdir)(domainDir);
14164
+ const files = await (0, import_promises8.readdir)(domainDir);
13981
14165
  for (const file of files) {
13982
14166
  if (!file.endsWith(".md")) continue;
13983
14167
  try {
13984
- const content = await (0, import_promises7.readFile)((0, import_node_path11.join)(domainDir, file), "utf-8");
14168
+ const content = await (0, import_promises8.readFile)((0, import_node_path11.join)(domainDir, file), "utf-8");
13985
14169
  const { fm, body } = parseFrontmatter(content);
13986
14170
  if (fm.status && fm.status !== "active") continue;
13987
14171
  nodes.push({
@@ -14001,6 +14185,13 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
14001
14185
  } catch {
14002
14186
  }
14003
14187
  }
14188
+ try {
14189
+ for (const claim of await readOrgMirror()) {
14190
+ const local = orgNodeToLocal(claim);
14191
+ if (sharedTerms(tokenize(`${local.title} ${local.body}`), promptTokens) >= ORG_MIN_SHARED_TERMS) nodes.push(local);
14192
+ }
14193
+ } catch {
14194
+ }
14004
14195
  if (nodes.length === 0) return null;
14005
14196
  const scored = nodes.map((n) => ({
14006
14197
  ...n,
@@ -14019,12 +14210,13 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
14019
14210
  const lines = [
14020
14211
  "## Project Knowledge (auto-injected)",
14021
14212
  "",
14022
- `*${selected.length} node(s) relevant to your current task. See \`.verity/memory/index.md\` for the full knowledge base.*`,
14213
+ `*${selected.length} node(s) relevant to your current task. See \`.verity/memory/index.md\` for the full knowledge base${selected.some((n) => n.scope === "org") ? ", and `~/.verity/orgs/` for what the organization's other repositories learned" : ""}.*`,
14023
14214
  ""
14024
14215
  ];
14025
14216
  for (const node of selected) {
14026
14217
  lines.push(`### ${node.title}`);
14027
- lines.push(`*${node.kind}* \xB7 confidence ${Math.round(node.confidence * 100)}%`);
14218
+ if (node.scope === "org") lines.push(`*org \xB7 ${node.origin}* \xB7 ${node.kind}`);
14219
+ else lines.push(`*${node.kind}* \xB7 confidence ${Math.round(node.confidence * 100)}%`);
14028
14220
  lines.push("");
14029
14221
  lines.push(node.body.slice(0, 800));
14030
14222
  lines.push("");
@@ -14039,7 +14231,7 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
14039
14231
  }
14040
14232
 
14041
14233
  // src/lib/memory-sync.ts
14042
- var import_promises8 = require("node:fs/promises");
14234
+ var import_promises9 = require("node:fs/promises");
14043
14235
  var import_node_fs14 = require("node:fs");
14044
14236
  var import_node_path13 = require("node:path");
14045
14237
  var import_node_crypto3 = require("node:crypto");
@@ -14154,8 +14346,8 @@ function ensureVerityGitignore() {
14154
14346
  }
14155
14347
  let text = next.join("\n");
14156
14348
  if (!hasMarker) {
14157
- const sep3 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
14158
- text = text + sep3 + VERITY_GITIGNORE_BLOCK;
14349
+ const sep4 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
14350
+ text = text + sep4 + VERITY_GITIGNORE_BLOCK;
14159
14351
  }
14160
14352
  if (text !== content) (0, import_node_fs12.writeFileSync)(".gitignore", text);
14161
14353
  return verified(
@@ -14185,8 +14377,8 @@ function writeFencedBlock() {
14185
14377
  lines = fenceMemoryLines(lines);
14186
14378
  let text = lines.join("\n");
14187
14379
  if (!hasMarker) {
14188
- const sep3 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
14189
- text = text + sep3 + VERITY_GITIGNORE_BLOCK;
14380
+ const sep4 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
14381
+ text = text + sep4 + VERITY_GITIGNORE_BLOCK;
14190
14382
  }
14191
14383
  try {
14192
14384
  (0, import_node_fs12.writeFileSync)(".gitignore", text);
@@ -14236,8 +14428,8 @@ function keepMemoryTracked() {
14236
14428
  }
14237
14429
  if (content.includes(MEMORY_OPT_OUT_MARKER)) return "already";
14238
14430
  try {
14239
- const sep3 = content === "" ? "" : content.endsWith("\n") ? "\n" : "\n\n";
14240
- (0, import_node_fs12.writeFileSync)(".gitignore", content + sep3 + MEMORY_OPT_OUT_STANZA);
14431
+ const sep4 = content === "" ? "" : content.endsWith("\n") ? "\n" : "\n\n";
14432
+ (0, import_node_fs12.writeFileSync)(".gitignore", content + sep4 + MEMORY_OPT_OUT_STANZA);
14241
14433
  } catch {
14242
14434
  return "failed";
14243
14435
  }
@@ -14299,42 +14491,6 @@ function resolveInside(baseDir, candidate) {
14299
14491
  }
14300
14492
  return full;
14301
14493
  }
14302
- var O_NOFOLLOW = typeof import_node_fs13.constants.O_NOFOLLOW === "number" ? import_node_fs13.constants.O_NOFOLLOW : 0;
14303
- function readFileInside(baseDir, candidate, maxBytes) {
14304
- const full = resolveInside(baseDir, candidate);
14305
- if (!full) return null;
14306
- let realParent;
14307
- let realBase;
14308
- try {
14309
- realParent = (0, import_node_fs13.realpathSync)((0, import_node_path12.dirname)(full));
14310
- realBase = (0, import_node_fs13.realpathSync)((0, import_node_path12.resolve)(baseDir));
14311
- } catch {
14312
- return null;
14313
- }
14314
- const realBaseSep = realBase.endsWith(import_node_path12.sep) ? realBase : realBase + import_node_path12.sep;
14315
- if (realParent !== realBase && !realParent.startsWith(realBaseSep)) return null;
14316
- const target = (0, import_node_path12.join)(realParent, (0, import_node_path12.basename)(full));
14317
- let fd = null;
14318
- try {
14319
- fd = (0, import_node_fs13.openSync)(target, import_node_fs13.constants.O_RDONLY | O_NOFOLLOW);
14320
- const st = (0, import_node_fs13.fstatSync)(fd);
14321
- if (!st.isFile()) return null;
14322
- const cap = Math.min(maxBytes, st.size);
14323
- if (cap <= 0) return "";
14324
- const buf = Buffer.alloc(cap);
14325
- const bytesRead = (0, import_node_fs13.readSync)(fd, buf, 0, cap, 0);
14326
- return buf.subarray(0, bytesRead).toString("utf-8");
14327
- } catch {
14328
- return null;
14329
- } finally {
14330
- if (fd !== null) {
14331
- try {
14332
- (0, import_node_fs13.closeSync)(fd);
14333
- } catch {
14334
- }
14335
- }
14336
- }
14337
- }
14338
14494
 
14339
14495
  // src/lib/glob-match.ts
14340
14496
  function globToRegex(glob) {
@@ -14384,6 +14540,14 @@ function globToRegex(glob) {
14384
14540
  out = out.replace(/(?:\.\*)+/g, ".*");
14385
14541
  return new RegExp(`^${out}$`);
14386
14542
  }
14543
+ function globMatch2(glob, filePath) {
14544
+ if (glob === filePath) return true;
14545
+ try {
14546
+ return globToRegex(glob).test(filePath);
14547
+ } catch {
14548
+ return false;
14549
+ }
14550
+ }
14387
14551
  function anyPathMatches(glob, treePaths) {
14388
14552
  let regex;
14389
14553
  try {
@@ -14402,18 +14566,18 @@ var memoryDir2 = () => projectPath(`${VERITY_DIR}/memory`);
14402
14566
  var DOMAINS2 = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations", "_archive"];
14403
14567
  var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
14404
14568
  async function ensureMemoryDir() {
14405
- await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
14569
+ await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
14406
14570
  for (const domain of DOMAINS2) {
14407
- await (0, import_promises8.mkdir)((0, import_node_path13.join)(memoryDir2(), domain), { recursive: true });
14571
+ await (0, import_promises9.mkdir)((0, import_node_path13.join)(memoryDir2(), domain), { recursive: true });
14408
14572
  }
14409
14573
  if (!(0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "SCHEMA.md"))) {
14410
- await (0, import_promises8.writeFile)((0, import_node_path13.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
14574
+ await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
14411
14575
  }
14412
14576
  if (!(0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "index.md"))) {
14413
- await (0, import_promises8.writeFile)((0, import_node_path13.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
14577
+ await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
14414
14578
  }
14415
14579
  if (!(0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "log.md"))) {
14416
- await (0, import_promises8.writeFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
14580
+ await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
14417
14581
  }
14418
14582
  }
14419
14583
  async function buildManifest() {
@@ -14425,13 +14589,13 @@ async function buildManifest() {
14425
14589
  const domainDir = (0, import_node_path13.join)(memoryDir2(), domain);
14426
14590
  if (!(0, import_node_fs14.existsSync)(domainDir)) continue;
14427
14591
  try {
14428
- const files = await (0, import_promises8.readdir)(domainDir);
14592
+ const files = await (0, import_promises9.readdir)(domainDir);
14429
14593
  for (const file of files) {
14430
14594
  if (!file.endsWith(".md")) continue;
14431
14595
  const filePath = `${domain}/${file}`;
14432
14596
  const fullPath = (0, import_node_path13.join)(memoryDir2(), filePath);
14433
14597
  try {
14434
- const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
14598
+ const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
14435
14599
  const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
14436
14600
  nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
14437
14601
  } catch {
@@ -14442,13 +14606,13 @@ async function buildManifest() {
14442
14606
  }
14443
14607
  let indexHash = null;
14444
14608
  try {
14445
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path13.join)(memoryDir2(), "index.md"), "utf-8");
14609
+ const indexContent = await (0, import_promises9.readFile)((0, import_node_path13.join)(memoryDir2(), "index.md"), "utf-8");
14446
14610
  indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
14447
14611
  } catch {
14448
14612
  }
14449
14613
  let logLength = 0;
14450
14614
  try {
14451
- const logContent = await (0, import_promises8.readFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "utf-8");
14615
+ const logContent = await (0, import_promises9.readFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "utf-8");
14452
14616
  logLength = logContent.split("\n").length;
14453
14617
  } catch {
14454
14618
  }
@@ -14464,10 +14628,10 @@ async function readOnDiskNodes() {
14464
14628
  const domainDir = (0, import_node_path13.join)(memoryDir2(), domain);
14465
14629
  if (!(0, import_node_fs14.existsSync)(domainDir)) continue;
14466
14630
  try {
14467
- for (const file of await (0, import_promises8.readdir)(domainDir)) {
14631
+ for (const file of await (0, import_promises9.readdir)(domainDir)) {
14468
14632
  if (!file.endsWith(".md")) continue;
14469
14633
  try {
14470
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path13.join)(domainDir, file), "utf-8")));
14634
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path13.join)(domainDir, file), "utf-8")));
14471
14635
  } catch {
14472
14636
  }
14473
14637
  }
@@ -14479,7 +14643,7 @@ async function readOnDiskNodes() {
14479
14643
  async function readSyncBaseline() {
14480
14644
  const out = /* @__PURE__ */ new Map();
14481
14645
  try {
14482
- const parsed = JSON.parse(await (0, import_promises8.readFile)(syncStateFile(), "utf-8"));
14646
+ const parsed = JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
14483
14647
  if (Array.isArray(parsed?.nodes)) {
14484
14648
  for (const n of parsed.nodes) if (n?.path) out.set(n.path, n.hash ?? null);
14485
14649
  } else if (Array.isArray(parsed?.paths)) {
@@ -14495,15 +14659,20 @@ async function recordSyncedNodePaths() {
14495
14659
  const next = JSON.stringify({ schema: 2, nodes }) + "\n";
14496
14660
  let existing = "";
14497
14661
  try {
14498
- existing = await (0, import_promises8.readFile)(syncStateFile(), "utf-8");
14662
+ existing = await (0, import_promises9.readFile)(syncStateFile(), "utf-8");
14499
14663
  } catch {
14500
14664
  }
14501
14665
  if (existing === next) return;
14502
- await (0, import_promises8.mkdir)(projectPath(VERITY_DIR), { recursive: true });
14503
- await (0, import_promises8.writeFile)(syncStateFile(), next);
14666
+ await writeFileAtomic(syncStateFile(), next);
14504
14667
  } catch {
14505
14668
  }
14506
14669
  }
14670
+ async function writeFileAtomic(path, content) {
14671
+ await (0, import_promises9.mkdir)((0, import_node_path13.dirname)(path), { recursive: true });
14672
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
14673
+ await (0, import_promises9.writeFile)(tmp, content);
14674
+ await (0, import_promises9.rename)(tmp, path);
14675
+ }
14507
14676
  async function recordSyncBaseline() {
14508
14677
  await recordSyncedNodePaths();
14509
14678
  }
@@ -14517,7 +14686,7 @@ async function computeEditedNodeUploads() {
14517
14686
  if (!(0, import_node_fs14.existsSync)(full)) continue;
14518
14687
  let content;
14519
14688
  try {
14520
- content = await (0, import_promises8.readFile)(full, "utf-8");
14689
+ content = await (0, import_promises9.readFile)(full, "utf-8");
14521
14690
  } catch {
14522
14691
  continue;
14523
14692
  }
@@ -14538,31 +14707,56 @@ async function applyMemoryWrites(writes, opts = {}) {
14538
14707
  let count = 0;
14539
14708
  const notes = [];
14540
14709
  const treePaths = opts.treePaths;
14710
+ const synced = /* @__PURE__ */ new Map();
14541
14711
  for (const write of writes) {
14542
- const { written, notes: writeNotes } = await applyOneWrite(write, treePaths);
14543
- if (written) count++;
14544
- notes.push(...writeNotes);
14712
+ const outcome = await applyOneWrite(write, treePaths, opts.lastServed);
14713
+ if (outcome.written) count++;
14714
+ if (outcome.syncedHash) {
14715
+ synced.set(write.path, outcome.syncedHash);
14716
+ opts.served?.set(write.path, outcome.syncedHash);
14717
+ }
14718
+ notes.push(...outcome.notes);
14545
14719
  }
14546
14720
  await regenerateIndex();
14547
- try {
14548
- await ensureClaudeMdPointer();
14549
- } catch {
14721
+ if (opts.claudeMdPointer !== false) {
14722
+ try {
14723
+ await ensureClaudeMdPointer();
14724
+ } catch {
14725
+ }
14550
14726
  }
14551
14727
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
14552
14728
  for (const n of notes) logLines.push(` - ${n}`);
14553
14729
  try {
14554
- const existing = (0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
14555
- await (0, import_promises8.writeFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
14730
+ const existing = (0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
14731
+ await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
14556
14732
  } catch {
14557
14733
  }
14558
- await recordSyncedNodePaths();
14734
+ if (opts.baseline === "written") await mergeSyncBaseline(synced);
14735
+ else if (opts.recordBaseline !== false) await recordSyncedNodePaths();
14559
14736
  return count;
14560
14737
  }
14561
- async function applyOneWrite(write, treePaths) {
14738
+ async function mergeSyncBaseline(entries) {
14739
+ if (entries.size === 0) return;
14740
+ try {
14741
+ if ((0, import_node_fs14.existsSync)(syncStateFile())) {
14742
+ try {
14743
+ JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
14744
+ } catch {
14745
+ return;
14746
+ }
14747
+ }
14748
+ const merged = await readSyncBaseline();
14749
+ for (const [path, hash] of entries) merged.set(path, hash);
14750
+ const nodes = [...merged].map(([path, hash]) => ({ path, hash }));
14751
+ await writeFileAtomic(syncStateFile(), JSON.stringify({ schema: 2, nodes }) + "\n");
14752
+ } catch {
14753
+ }
14754
+ }
14755
+ async function applyOneWrite(write, treePaths, lastServed) {
14562
14756
  const fullPath = resolveInside(memoryDir2(), write.path);
14563
14757
  const notes = [];
14564
14758
  if (!fullPath) {
14565
- return { written: false, notes: [`${String(write.path)}: rejected \u2014 path escapes the memory directory`] };
14759
+ return { written: false, syncedHash: null, notes: [`${String(write.path)}: rejected \u2014 path escapes the memory directory`] };
14566
14760
  }
14567
14761
  let content = write.content;
14568
14762
  if (treePaths && treePaths.length > 0) {
@@ -14575,18 +14769,22 @@ async function applyOneWrite(write, treePaths) {
14575
14769
  if ((0, import_node_fs14.existsSync)(fullPath)) {
14576
14770
  let existing = "";
14577
14771
  try {
14578
- existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
14772
+ existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
14579
14773
  } catch {
14580
14774
  }
14581
- if (existing === content) return { written: false, notes };
14775
+ if (existing === content) return { written: false, syncedHash: hashContent(content), notes };
14582
14776
  if (existing !== "") {
14583
- notes.push(`${write.path}: kept local edit (differs from server)`);
14584
- return { written: false, notes };
14777
+ const served = lastServed?.get(write.path);
14778
+ if (served == null || served !== hashContent(existing)) {
14779
+ notes.push(`${write.path}: kept local edit (differs from server)`);
14780
+ return { written: false, syncedHash: null, notes };
14781
+ }
14782
+ notes.push(`${write.path}: updated from server (untouched here since the server delivered it)`);
14585
14783
  }
14586
14784
  }
14587
- await (0, import_promises8.mkdir)((0, import_node_path13.dirname)(fullPath), { recursive: true });
14588
- await (0, import_promises8.writeFile)(fullPath, content);
14589
- return { written: true, notes };
14785
+ await (0, import_promises9.mkdir)((0, import_node_path13.dirname)(fullPath), { recursive: true });
14786
+ await (0, import_promises9.writeFile)(fullPath, content);
14787
+ return { written: true, syncedHash: hashContent(content), notes };
14590
14788
  }
14591
14789
  function groundFileGlobs(content, treePaths) {
14592
14790
  const globs = pickFrontmatterArray(content, "file_globs");
@@ -14628,7 +14826,7 @@ async function regenerateIndex() {
14628
14826
  const domainDir = (0, import_node_path13.join)(memoryDir2(), domain);
14629
14827
  if (!(0, import_node_fs14.existsSync)(domainDir)) continue;
14630
14828
  try {
14631
- const files = await (0, import_promises8.readdir)(domainDir);
14829
+ const files = await (0, import_promises9.readdir)(domainDir);
14632
14830
  const mdFiles = files.filter((f) => f.endsWith(".md"));
14633
14831
  if (mdFiles.length === 0) continue;
14634
14832
  lines.push(`## ${domain}/ (${mdFiles.length})`);
@@ -14636,7 +14834,7 @@ async function regenerateIndex() {
14636
14834
  for (const file of mdFiles.sort()) {
14637
14835
  const slug = file.replace(/\.md$/, "");
14638
14836
  try {
14639
- const content = await (0, import_promises8.readFile)((0, import_node_path13.join)(domainDir, file), "utf-8");
14837
+ const content = await (0, import_promises9.readFile)((0, import_node_path13.join)(domainDir, file), "utf-8");
14640
14838
  const title = pickFrontmatter(content, "title") ?? slug;
14641
14839
  const kind = pickFrontmatter(content, "kind") ?? "-";
14642
14840
  const confidence = pickFrontmatter(content, "confidence");
@@ -14663,11 +14861,11 @@ async function regenerateIndex() {
14663
14861
  const indexPath = (0, import_node_path13.join)(memoryDir2(), "index.md");
14664
14862
  let existing = null;
14665
14863
  try {
14666
- existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
14864
+ existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
14667
14865
  } catch {
14668
14866
  }
14669
14867
  if (existing === next) return;
14670
- await (0, import_promises8.writeFile)(indexPath, next);
14868
+ await (0, import_promises9.writeFile)(indexPath, next);
14671
14869
  }
14672
14870
  function pickFrontmatter(content, key) {
14673
14871
  const re = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "m");
@@ -14694,6 +14892,169 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
14694
14892
  var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
14695
14893
  var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
14696
14894
  var CLAUDE_MD_PROSE = [
14895
+ "## Project Memory",
14896
+ "",
14897
+ "This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
14898
+ "non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
14899
+ "that may apply to the change you are about to make. Open specific node files via",
14900
+ "the Read tool when the title or scope suggests relevance.",
14901
+ "",
14902
+ "The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
14903
+ "superseded \u2014 ignore them unless investigating history.",
14904
+ "",
14905
+ "Knowledge the organization's other repositories learned is mirrored outside the",
14906
+ "repo at `~/.verity/orgs/<host>/<owner>/memory/` (`verity memory org` lists it).",
14907
+ "Each claim says where it came from; one that is wrong here is demoted for",
14908
+ 'everyone with `verity memory demote <id> --reason "\u2026"`.',
14909
+ "",
14910
+ "## Quality gate: accepted risks",
14911
+ "",
14912
+ "When the Verity pre-commit/pre-push gate FAILs, fix the findings \u2014 that is the",
14913
+ 'default. Use `verity waive <pattern-id> --file <path> --reason "\u2026"` ONLY to relay',
14914
+ "a risk a human has explicitly accepted: a named code-review finding, an ADR, or",
14915
+ "the user saying so in this conversation. The --reason must cite that source.",
14916
+ "",
14917
+ "Never waive on your own judgment, to get past a block, or pre-emptively. A waive",
14918
+ "binds to the file's current bytes and voids automatically when the file changes,",
14919
+ "and every waive is recorded in the run ledger. For a pattern-level false positive",
14920
+ "use `verity feedback finding <run-id> <pattern-id> false_positive` instead.",
14921
+ "",
14922
+ "## Post-task reflection",
14923
+ "",
14924
+ `When a task is complete (you've created a PR, the user says "done" or "ship it",`,
14925
+ "or the work is clearly finished), **draft the reflection yourself** \u2014 1\u20133",
14926
+ "concrete things worth remembering (a decision, a gotcha, or a pattern), each",
14927
+ "cited with the files / PR / commands / error-signatures it came from. Skip",
14928
+ "entirely if nothing non-obvious happened \u2014 that judgement is the ONLY filter,",
14929
+ "because nothing reviews the reflection before it lands.",
14930
+ "",
14931
+ "Then record it straight away. There is no confirm step, in any environment:",
14932
+ "",
14933
+ "```bash",
14934
+ 'verity reflect --user-input "<your draft>" --kind <kind>',
14935
+ "```",
14936
+ "",
14937
+ "Add `--confirmed` ONLY when the user authored or dictated the words. Without",
14938
+ "it the node is stored as `source: agent` \u2014 Verity thought this, nobody checked",
14939
+ "it. With it, `source: user` at full confidence \u2014 a person stands behind this.",
14940
+ "Never claim the second for your own draft, however good it is.",
14941
+ "",
14942
+ "**Name the files in the text.** Verity scopes the reflection to the paths it",
14943
+ "cites, and a reflection that names no file in this repo is never retrieved for",
14944
+ "a later review \u2014 it is recorded and then invisible. The command says so when it",
14945
+ 'happens; `--file-globs "<path or glob>"` is the fix when the prose cannot carry',
14946
+ "the paths.",
14947
+ "",
14948
+ "Then tell the user, in one line, what you recorded and where: the command",
14949
+ "prints the node id, the path under `.verity/memory/`, and a dashboard link.",
14950
+ "They did not agree to it in advance, so say it happened \u2014 editing or deleting",
14951
+ "that file is how they correct it.",
14952
+ "",
14953
+ "> Durable, hand-curated guidance goes in the preserve region below (it survives",
14954
+ "> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
14955
+ "> markers is tool-owned and overwritten on each run.",
14956
+ "",
14957
+ "## Housekeeping Turns",
14958
+ "",
14959
+ "When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
14960
+ "rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
14961
+ "",
14962
+ "```bash",
14963
+ 'verity ignore --turn --agent --reason "pulling latest before starting"',
14964
+ "```",
14965
+ "",
14966
+ "This skips the review for that turn, which saves the turn Verity would",
14967
+ "otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
14968
+ "`--turn` when a single piece of housekeeping spans several turns.",
14969
+ "",
14970
+ "**It is a claim about the turn, not a way to silence review.** The declaration",
14971
+ "is checked against what the turn actually did: if anything is authored \u2014 by you,",
14972
+ "by a subagent, or by a shell command that can write files \u2014 it voids, the review",
14973
+ "runs anyway, and the broken declaration is reported. So declare housekeeping you",
14974
+ "are about to do, never work you have already done, and never as a way to get past",
14975
+ "a finding. Declarations are budgeted per session and every one is recorded with",
14976
+ "its reason."
14977
+ ].join("\n");
14978
+ var CLAUDE_MD_PROSE_PRE_ORG = [
14979
+ "## Project Memory",
14980
+ "",
14981
+ "This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
14982
+ "non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
14983
+ "that may apply to the change you are about to make. Open specific node files via",
14984
+ "the Read tool when the title or scope suggests relevance.",
14985
+ "",
14986
+ "The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
14987
+ "superseded \u2014 ignore them unless investigating history.",
14988
+ "",
14989
+ "## Quality gate: accepted risks",
14990
+ "",
14991
+ "When the Verity pre-commit/pre-push gate FAILs, fix the findings \u2014 that is the",
14992
+ 'default. Use `verity waive <pattern-id> --file <path> --reason "\u2026"` ONLY to relay',
14993
+ "a risk a human has explicitly accepted: a named code-review finding, an ADR, or",
14994
+ "the user saying so in this conversation. The --reason must cite that source.",
14995
+ "",
14996
+ "Never waive on your own judgment, to get past a block, or pre-emptively. A waive",
14997
+ "binds to the file's current bytes and voids automatically when the file changes,",
14998
+ "and every waive is recorded in the run ledger. For a pattern-level false positive",
14999
+ "use `verity feedback finding <run-id> <pattern-id> false_positive` instead.",
15000
+ "",
15001
+ "## Post-task reflection",
15002
+ "",
15003
+ `When a task is complete (you've created a PR, the user says "done" or "ship it",`,
15004
+ "or the work is clearly finished), **draft the reflection yourself** \u2014 1\u20133",
15005
+ "concrete things worth remembering (a decision, a gotcha, or a pattern), each",
15006
+ "cited with the files / PR / commands / error-signatures it came from. Skip",
15007
+ "entirely if nothing non-obvious happened \u2014 that judgement is the ONLY filter,",
15008
+ "because nothing reviews the reflection before it lands.",
15009
+ "",
15010
+ "Then record it straight away. There is no confirm step, in any environment:",
15011
+ "",
15012
+ "```bash",
15013
+ 'verity reflect --user-input "<your draft>" --kind <kind>',
15014
+ "```",
15015
+ "",
15016
+ "Add `--confirmed` ONLY when the user authored or dictated the words. Without",
15017
+ "it the node is stored as `source: agent` \u2014 Verity thought this, nobody checked",
15018
+ "it. With it, `source: user` at full confidence \u2014 a person stands behind this.",
15019
+ "Never claim the second for your own draft, however good it is.",
15020
+ "",
15021
+ "**Name the files in the text.** Verity scopes the reflection to the paths it",
15022
+ "cites, and a reflection that names no file in this repo is never retrieved for",
15023
+ "a later review \u2014 it is recorded and then invisible. The command says so when it",
15024
+ 'happens; `--file-globs "<path or glob>"` is the fix when the prose cannot carry',
15025
+ "the paths.",
15026
+ "",
15027
+ "Then tell the user, in one line, what you recorded and where: the command",
15028
+ "prints the node id, the path under `.verity/memory/`, and a dashboard link.",
15029
+ "They did not agree to it in advance, so say it happened \u2014 editing or deleting",
15030
+ "that file is how they correct it.",
15031
+ "",
15032
+ "> Durable, hand-curated guidance goes in the preserve region below (it survives",
15033
+ "> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
15034
+ "> markers is tool-owned and overwritten on each run.",
15035
+ "",
15036
+ "## Housekeeping Turns",
15037
+ "",
15038
+ "When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
15039
+ "rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
15040
+ "",
15041
+ "```bash",
15042
+ 'verity ignore --turn --agent --reason "pulling latest before starting"',
15043
+ "```",
15044
+ "",
15045
+ "This skips the review for that turn, which saves the turn Verity would",
15046
+ "otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
15047
+ "`--turn` when a single piece of housekeeping spans several turns.",
15048
+ "",
15049
+ "**It is a claim about the turn, not a way to silence review.** The declaration",
15050
+ "is checked against what the turn actually did: if anything is authored \u2014 by you,",
15051
+ "by a subagent, or by a shell command that can write files \u2014 it voids, the review",
15052
+ "runs anyway, and the broken declaration is reported. So declare housekeeping you",
15053
+ "are about to do, never work you have already done, and never as a way to get past",
15054
+ "a finding. Declarations are budgeted per session and every one is recorded with",
15055
+ "its reason."
15056
+ ].join("\n");
15057
+ var CLAUDE_MD_PROSE_PRE_AUTORECORD = [
14697
15058
  "## Project Memory",
14698
15059
  "",
14699
15060
  "This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
@@ -14897,7 +15258,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
14897
15258
  const claudeMdPath = (0, import_node_path13.join)(cwd, "CLAUDE.md");
14898
15259
  let existing = "";
14899
15260
  if ((0, import_node_fs14.existsSync)(claudeMdPath)) {
14900
- existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
15261
+ existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
14901
15262
  }
14902
15263
  let startTag = CLAUDE_MD_START;
14903
15264
  let endTag = CLAUDE_MD_END;
@@ -14953,7 +15314,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
14953
15314
  next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
14954
15315
  }
14955
15316
  if (next === existing) return;
14956
- await (0, import_promises8.writeFile)(claudeMdPath, next);
15317
+ await (0, import_promises9.writeFile)(claudeMdPath, next);
14957
15318
  }
14958
15319
  function extractPreserveContent(interior) {
14959
15320
  for (const [start, end] of [
@@ -14972,6 +15333,8 @@ function stripKnownProse(interior) {
14972
15333
  const trimmed = interior.replace(/^\n+/, "");
14973
15334
  for (const prose of [
14974
15335
  CLAUDE_MD_PROSE,
15336
+ CLAUDE_MD_PROSE_PRE_ORG,
15337
+ CLAUDE_MD_PROSE_PRE_AUTORECORD,
14975
15338
  CLAUDE_MD_PROSE_PRE_REFLECT,
14976
15339
  CLAUDE_MD_PROSE_PRE_WAIVE,
14977
15340
  CLAUDE_MD_PROSE_PRE_IGNORE,
@@ -16591,21 +16954,112 @@ async function fireClassify(prompt, sessionId, globals) {
16591
16954
 
16592
16955
  // src/commands/lifecycle.ts
16593
16956
  var import_node_fs23 = require("node:fs");
16594
- var import_node_path20 = require("node:path");
16957
+ var import_node_path21 = require("node:path");
16595
16958
 
16596
16959
  // src/lib/baseline.ts
16597
16960
  var import_node_fs22 = require("node:fs");
16598
- var import_node_path19 = require("node:path");
16961
+ var import_node_path20 = require("node:path");
16599
16962
  var import_node_crypto9 = require("node:crypto");
16600
16963
 
16601
16964
  // src/lib/snapshot.ts
16602
16965
  var import_node_fs21 = require("node:fs");
16603
- var import_node_path18 = require("node:path");
16966
+ var import_node_path19 = require("node:path");
16604
16967
  var import_node_child_process7 = require("node:child_process");
16605
16968
 
16606
16969
  // src/lib/files.ts
16970
+ var import_node_path18 = require("node:path");
16971
+
16972
+ // src/lib/safe-read.ts
16607
16973
  var import_node_fs20 = require("node:fs");
16608
16974
  var import_node_path17 = require("node:path");
16975
+ var FLAGS = import_node_fs20.constants;
16976
+ var NOFOLLOW = FLAGS.O_NOFOLLOW;
16977
+ var NONBLOCK = FLAGS.O_NONBLOCK;
16978
+ function closeOpened(fd) {
16979
+ try {
16980
+ (0, import_node_fs20.closeSync)(fd);
16981
+ } catch {
16982
+ }
16983
+ }
16984
+ function isInsideRoot(realRoot, realPath) {
16985
+ return realPath === realRoot || realPath.startsWith(realRoot.endsWith(import_node_path17.sep) ? realRoot : realRoot + import_node_path17.sep);
16986
+ }
16987
+ function sameOpenedFile(opened, current, nofollowAvailable) {
16988
+ if (!nofollowAvailable && (opened.ino === 0n || current.ino === 0n)) return false;
16989
+ return opened.ino === current.ino && opened.dev === current.dev;
16990
+ }
16991
+ function openRegularInRoot(root, path) {
16992
+ const full = (0, import_node_path17.isAbsolute)(path) ? path : (0, import_node_path17.join)(root, path);
16993
+ let fd;
16994
+ try {
16995
+ fd = (0, import_node_fs20.openSync)(full, import_node_fs20.constants.O_RDONLY | (NOFOLLOW ?? 0) | (NONBLOCK ?? 0));
16996
+ } catch (err) {
16997
+ const code = err.code;
16998
+ if (code === "ELOOP" || code === "EMLINK" || code === "EFTYPE") return { ok: false, reason: "symlink" };
16999
+ if (code === "ENOENT" || code === "ENOTDIR") return { ok: false, reason: "missing" };
17000
+ return { ok: false, reason: "unreadable" };
17001
+ }
17002
+ try {
17003
+ const opened = (0, import_node_fs20.fstatSync)(fd, { bigint: true });
17004
+ if (!opened.isFile()) {
17005
+ closeOpened(fd);
17006
+ return { ok: false, reason: "not-regular" };
17007
+ }
17008
+ const realRoot = import_node_fs20.realpathSync.native(root);
17009
+ const realPath = import_node_fs20.realpathSync.native(full);
17010
+ if (!isInsideRoot(realRoot, realPath)) {
17011
+ closeOpened(fd);
17012
+ return { ok: false, reason: "outside-root" };
17013
+ }
17014
+ const current = (0, import_node_fs20.statSync)(realPath, { bigint: true });
17015
+ if (!sameOpenedFile(opened, current, NOFOLLOW !== void 0)) {
17016
+ closeOpened(fd);
17017
+ return { ok: false, reason: NOFOLLOW === void 0 ? "unreadable" : "outside-root" };
17018
+ }
17019
+ return { ok: true, fd, size: Number(opened.size), mtimeMs: Number(opened.mtimeMs) };
17020
+ } catch {
17021
+ closeOpened(fd);
17022
+ return { ok: false, reason: "unreadable" };
17023
+ }
17024
+ }
17025
+ function statRegularInRoot(root, path) {
17026
+ const opened = openRegularInRoot(root, path);
17027
+ if (!opened.ok) return opened;
17028
+ closeOpened(opened.fd);
17029
+ return { ok: true, size: opened.size, mtimeMs: opened.mtimeMs };
17030
+ }
17031
+ function readOpened(fd, size) {
17032
+ try {
17033
+ const buffer = Buffer.alloc(size);
17034
+ let offset = 0;
17035
+ while (offset < size) {
17036
+ const n = (0, import_node_fs20.readSync)(fd, buffer, offset, size - offset, offset);
17037
+ if (n === 0) break;
17038
+ offset += n;
17039
+ }
17040
+ return buffer.subarray(0, offset);
17041
+ } finally {
17042
+ closeOpened(fd);
17043
+ }
17044
+ }
17045
+ function readOpenedUtf8(fd, size) {
17046
+ return readOpened(fd, size).toString("utf-8");
17047
+ }
17048
+ function readRegularFileInRoot(root, path, maxBytes) {
17049
+ const opened = openRegularInRoot(root, path);
17050
+ if (!opened.ok) return opened;
17051
+ if (opened.size > maxBytes) {
17052
+ closeOpened(opened.fd);
17053
+ return { ok: false, reason: "too-large", size: opened.size };
17054
+ }
17055
+ try {
17056
+ return { ok: true, content: readOpenedUtf8(opened.fd, opened.size), size: opened.size };
17057
+ } catch {
17058
+ return { ok: false, reason: "unreadable" };
17059
+ }
17060
+ }
17061
+
17062
+ // src/lib/files.ts
16609
17063
  var LANG_MAP = {
16610
17064
  // Analyzable (static analysis + Gemini)
16611
17065
  ts: "typescript",
@@ -16673,19 +17127,14 @@ var LANG_MAP = {
16673
17127
  mk: "make"
16674
17128
  };
16675
17129
  function detectLanguage(filepath) {
16676
- const ext = (0, import_node_path17.extname)(filepath).slice(1);
17130
+ const ext = (0, import_node_path18.extname)(filepath).slice(1);
16677
17131
  return LANG_MAP[ext] ?? ext;
16678
17132
  }
16679
17133
  function sortByMtime(files) {
16680
17134
  const withMtime = files.map((f) => {
16681
- const resolved = resolveFile(f);
16682
- if (!resolved) return null;
16683
- try {
16684
- const stat3 = (0, import_node_fs20.statSync)(resolved);
16685
- return { path: f, resolved, mtime: stat3.mtimeMs };
16686
- } catch {
16687
- return null;
16688
- }
17135
+ const stat3 = statRegularInRoot(process.cwd(), f);
17136
+ if (stat3.ok) return { path: f, mtime: stat3.mtimeMs };
17137
+ return stat3.reason === "missing" ? null : { path: f, mtime: Number.NEGATIVE_INFINITY };
16689
17138
  }).filter((x) => x !== null);
16690
17139
  withMtime.sort((a, b) => b.mtime - a.mtime);
16691
17140
  return withMtime.map((x) => x.path);
@@ -16707,23 +17156,22 @@ function collectCodeDelta(files, opts) {
16707
17156
  droppedPaths.push(filepath);
16708
17157
  continue;
16709
17158
  }
16710
- const resolved = resolveFile(filepath);
16711
- if (!resolved) {
16712
- exclude(filepath, "path-not-resolvable");
16713
- continue;
16714
- }
16715
- let size;
16716
- try {
16717
- size = (0, import_node_fs20.statSync)(resolved).size;
16718
- } catch {
16719
- exclude(filepath, "not-stattable");
17159
+ const opened = openRegularInRoot(process.cwd(), filepath);
17160
+ if (!opened.ok) {
17161
+ exclude(
17162
+ filepath,
17163
+ opened.reason === "symlink" || opened.reason === "outside-root" ? "symlink" : opened.reason === "missing" ? "path-not-resolvable" : "not-stattable"
17164
+ );
16720
17165
  continue;
16721
17166
  }
17167
+ const size = opened.size;
16722
17168
  if (size > maxFileBytes) {
17169
+ closeOpened(opened.fd);
16723
17170
  exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
16724
17171
  continue;
16725
17172
  }
16726
17173
  if (totalSize + size > maxTotalBytes) {
17174
+ closeOpened(opened.fd);
16727
17175
  truncationReason ??= "max_total_bytes";
16728
17176
  const idx = sorted.indexOf(filepath);
16729
17177
  droppedPaths.push(...sorted.slice(idx));
@@ -16731,7 +17179,7 @@ function collectCodeDelta(files, opts) {
16731
17179
  }
16732
17180
  let content;
16733
17181
  try {
16734
- content = (0, import_node_fs20.readFileSync)(resolved, "utf-8");
17182
+ content = readOpenedUtf8(opened.fd, size);
16735
17183
  } catch {
16736
17184
  exclude(filepath, "not-readable");
16737
17185
  continue;
@@ -16776,7 +17224,7 @@ function generateSnapshotDiffs(files) {
16776
17224
  const diffs = [];
16777
17225
  for (const file of files) {
16778
17226
  if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
16779
- const snapshotPath = (0, import_node_path18.join)(SNAPSHOT_DIR, file.path);
17227
+ const snapshotPath = (0, import_node_path19.join)(SNAPSHOT_DIR, file.path);
16780
17228
  const language = file.language ?? detectLanguage(file.path);
16781
17229
  if ((0, import_node_fs21.existsSync)(snapshotPath)) {
16782
17230
  const oldContent = (0, import_node_fs21.readFileSync)(snapshotPath, "utf-8");
@@ -16804,16 +17252,16 @@ function saveSnapshots(files) {
16804
17252
  const snapshotPaths = /* @__PURE__ */ new Set();
16805
17253
  for (const file of files) {
16806
17254
  if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
16807
- const snapshotPath = (0, import_node_path18.join)(SNAPSHOT_DIR, file.path);
17255
+ const snapshotPath = (0, import_node_path19.join)(SNAPSHOT_DIR, file.path);
16808
17256
  snapshotPaths.add(snapshotPath);
16809
- (0, import_node_fs21.mkdirSync)((0, import_node_path18.dirname)(snapshotPath), { recursive: true });
17257
+ (0, import_node_fs21.mkdirSync)((0, import_node_path19.dirname)(snapshotPath), { recursive: true });
16810
17258
  (0, import_node_fs21.writeFileSync)(snapshotPath, file.content);
16811
17259
  }
16812
17260
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
16813
17261
  }
16814
17262
  function computeDiff(oldContent, newContent, filePath) {
16815
- const tmpOld = (0, import_node_path18.join)(SNAPSHOT_DIR, ".diff-old.tmp");
16816
- const tmpNew = (0, import_node_path18.join)(SNAPSHOT_DIR, ".diff-new.tmp");
17263
+ const tmpOld = (0, import_node_path19.join)(SNAPSHOT_DIR, ".diff-old.tmp");
17264
+ const tmpNew = (0, import_node_path19.join)(SNAPSHOT_DIR, ".diff-new.tmp");
16817
17265
  try {
16818
17266
  (0, import_node_fs21.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
16819
17267
  (0, import_node_fs21.writeFileSync)(tmpOld, oldContent);
@@ -16846,7 +17294,7 @@ function cleanStaleSnapshots(dir, keepSet) {
16846
17294
  const entries = (0, import_node_fs21.readdirSync)(dir, { withFileTypes: true });
16847
17295
  for (const entry of entries) {
16848
17296
  if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
16849
- const fullPath = (0, import_node_path18.join)(dir, entry.name);
17297
+ const fullPath = (0, import_node_path19.join)(dir, entry.name);
16850
17298
  if (entry.isDirectory()) {
16851
17299
  cleanStaleSnapshots(fullPath, keepSet);
16852
17300
  try {
@@ -16875,13 +17323,13 @@ function sessionKey(sessionId) {
16875
17323
  return (0, import_node_crypto9.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
16876
17324
  }
16877
17325
  function sessionDir(key) {
16878
- return (0, import_node_path19.join)(projectPath(BASELINE_DIR), key);
17326
+ return (0, import_node_path20.join)(projectPath(BASELINE_DIR), key);
16879
17327
  }
16880
17328
  function manifestPath(dir) {
16881
- return (0, import_node_path19.join)(dir, "manifest.json");
17329
+ return (0, import_node_path20.join)(dir, "manifest.json");
16882
17330
  }
16883
17331
  function mirrorPath(dir, repoRelPath) {
16884
- return (0, import_node_path19.join)(dir, "files", repoRelPath);
17332
+ return (0, import_node_path20.join)(dir, "files", repoRelPath);
16885
17333
  }
16886
17334
  var CARRY_FILE = `${BASELINE_DIR}/.carry`;
16887
17335
  var CARRY_WINDOW_MS = 12e4;
@@ -16943,7 +17391,7 @@ function captureBaseline(opts = {}) {
16943
17391
  (0, import_node_fs22.rmSync)(dir, { recursive: true, force: true });
16944
17392
  } catch {
16945
17393
  }
16946
- const filesDir = (0, import_node_path19.join)(dir, "files");
17394
+ const filesDir = (0, import_node_path20.join)(dir, "files");
16947
17395
  const mirrored = [];
16948
17396
  try {
16949
17397
  (0, import_node_fs22.mkdirSync)(filesDir, { recursive: true });
@@ -16953,7 +17401,7 @@ function captureBaseline(opts = {}) {
16953
17401
  if (content === null) continue;
16954
17402
  const dest = mirrorPath(dir, p);
16955
17403
  try {
16956
- (0, import_node_fs22.mkdirSync)((0, import_node_path19.dirname)(dest), { recursive: true });
17404
+ (0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(dest), { recursive: true });
16957
17405
  (0, import_node_fs22.writeFileSync)(dest, content);
16958
17406
  mirrored.push(p);
16959
17407
  } catch {
@@ -17061,7 +17509,7 @@ function absorbIntoBaseline(paths, sessionId) {
17061
17509
  const content = safeReadForMirror(projectPath(p));
17062
17510
  if (content === null) continue;
17063
17511
  const dest = mirrorPath(dir, p);
17064
- (0, import_node_fs22.mkdirSync)((0, import_node_path19.dirname)(dest), { recursive: true });
17512
+ (0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(dest), { recursive: true });
17065
17513
  (0, import_node_fs22.writeFileSync)(dest, content);
17066
17514
  dirty.add(p);
17067
17515
  adopted++;
@@ -17109,7 +17557,7 @@ function pruneOldBaselines() {
17109
17557
  }
17110
17558
  const now = Date.now();
17111
17559
  for (const name of entries) {
17112
- const dir = (0, import_node_path19.join)(root, name);
17560
+ const dir = (0, import_node_path20.join)(root, name);
17113
17561
  const manifest = readManifest(dir);
17114
17562
  if (!manifest) {
17115
17563
  try {
@@ -17297,7 +17745,7 @@ function buildCompactionContext(session) {
17297
17745
  commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
17298
17746
  readFileLines: (file) => {
17299
17747
  try {
17300
- const abs = (0, import_node_path20.join)(root, file);
17748
+ const abs = (0, import_node_path21.join)(root, file);
17301
17749
  return (0, import_node_fs23.existsSync)(abs) ? (0, import_node_fs23.readFileSync)(abs, "utf8").split("\n") : null;
17302
17750
  } catch {
17303
17751
  return null;
@@ -17334,17 +17782,17 @@ async function readHookStdin() {
17334
17782
  try {
17335
17783
  if (process.stdin.isTTY) return {};
17336
17784
  const chunks = [];
17337
- const timeout = new Promise((resolve5) => setTimeout(() => resolve5({}), 500));
17338
- const read = new Promise((resolve5) => {
17785
+ const timeout = new Promise((resolve6) => setTimeout(() => resolve6({}), 500));
17786
+ const read = new Promise((resolve6) => {
17339
17787
  process.stdin.on("data", (c) => chunks.push(c));
17340
17788
  process.stdin.on("end", () => {
17341
17789
  try {
17342
- resolve5(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
17790
+ resolve6(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
17343
17791
  } catch {
17344
- resolve5({});
17792
+ resolve6({});
17345
17793
  }
17346
17794
  });
17347
- process.stdin.on("error", () => resolve5({}));
17795
+ process.stdin.on("error", () => resolve6({}));
17348
17796
  process.stdin.resume();
17349
17797
  });
17350
17798
  return await Promise.race([read, timeout]);
@@ -17354,25 +17802,25 @@ async function readHookStdin() {
17354
17802
  }
17355
17803
 
17356
17804
  // src/commands/standard.ts
17357
- var import_promises12 = require("node:fs/promises");
17805
+ var import_promises13 = require("node:fs/promises");
17358
17806
  var import_node_fs30 = require("node:fs");
17359
17807
  var import_yaml3 = __toESM(require_dist());
17360
17808
 
17361
17809
  // src/lib/synthesize.ts
17362
17810
  var import_node_child_process9 = require("node:child_process");
17363
17811
  var import_node_fs26 = require("node:fs");
17364
- var import_promises9 = require("node:fs/promises");
17365
- var import_node_path23 = require("node:path");
17812
+ var import_promises10 = require("node:fs/promises");
17813
+ var import_node_path24 = require("node:path");
17366
17814
  var import_yaml = __toESM(require_dist());
17367
17815
 
17368
17816
  // src/lib/data-dir.ts
17369
17817
  var import_node_fs24 = require("node:fs");
17370
- var import_node_path21 = require("node:path");
17818
+ var import_node_path22 = require("node:path");
17371
17819
  function resolveDataDir() {
17372
17820
  const candidates2 = [
17373
- (0, import_node_path21.join)(__dirname, "..", "data"),
17821
+ (0, import_node_path22.join)(__dirname, "..", "data"),
17374
17822
  // installed: node_modules/@codacy/verity-cli/data
17375
- (0, import_node_path21.join)(__dirname, "..", "..", "data"),
17823
+ (0, import_node_path22.join)(__dirname, "..", "..", "data"),
17376
17824
  // edge case: nested resolution
17377
17825
  // THE COMMITTED SOURCE, for a source checkout that has not been built.
17378
17826
  // cli/data/skills/ is a BUILD ARTIFACT (scripts/build.js copies client/skills
@@ -17381,14 +17829,14 @@ function resolveDataDir() {
17381
17829
  // without this the synthesizer throws "Could not find Verity skill data"
17382
17830
  // for every test and every `verity` run from source. Resolved from this
17383
17831
  // module's own location, never the cwd: see the warning below.
17384
- (0, import_node_path21.join)(__dirname, "..", "..", "client"),
17832
+ (0, import_node_path22.join)(__dirname, "..", "..", "client"),
17385
17833
  // bundled: cli/bin/ → ../../client
17386
- (0, import_node_path21.join)(__dirname, "..", "..", "..", "client"),
17834
+ (0, import_node_path22.join)(__dirname, "..", "..", "..", "client"),
17387
17835
  // tsx: cli/src/lib/ → ../../../client
17388
17836
  ...process.env.VERITY_DEV_DATA_DIR ? [process.env.VERITY_DEV_DATA_DIR] : []
17389
17837
  ];
17390
17838
  for (const candidate of candidates2) {
17391
- if ((0, import_node_fs24.existsSync)((0, import_node_path21.join)(candidate, "skills"))) {
17839
+ if ((0, import_node_fs24.existsSync)((0, import_node_path22.join)(candidate, "skills"))) {
17392
17840
  return candidate;
17393
17841
  }
17394
17842
  }
@@ -17397,13 +17845,13 @@ function resolveDataDir() {
17397
17845
  );
17398
17846
  }
17399
17847
  function setupDataPath(file) {
17400
- return (0, import_node_path21.join)(resolveDataDir(), "skills", "verity-setup", file);
17848
+ return (0, import_node_path22.join)(resolveDataDir(), "skills", "verity-setup", file);
17401
17849
  }
17402
17850
 
17403
17851
  // src/lib/detect.ts
17404
17852
  var import_node_child_process8 = require("node:child_process");
17405
17853
  var import_node_fs25 = require("node:fs");
17406
- var import_node_path22 = require("node:path");
17854
+ var import_node_path23 = require("node:path");
17407
17855
  var TOOLED_LANGUAGES = /* @__PURE__ */ new Set([
17408
17856
  "typescript",
17409
17857
  "javascript",
@@ -17467,18 +17915,18 @@ function walk(root) {
17467
17915
  for (const entry of entries) {
17468
17916
  if (found.length >= WALK_MAX_FILES) return;
17469
17917
  if (IGNORED_SEGMENTS.includes(entry.name)) continue;
17470
- const full = (0, import_node_path22.join)(dir, entry.name);
17918
+ const full = (0, import_node_path23.join)(dir, entry.name);
17471
17919
  if (entry.isDirectory()) visit(full, depth + 1);
17472
- else if (entry.isFile()) found.push((0, import_node_path22.relative)(root, full));
17920
+ else if (entry.isFile()) found.push((0, import_node_path23.relative)(root, full));
17473
17921
  }
17474
17922
  };
17475
17923
  visit(root, 0);
17476
17924
  return found;
17477
17925
  }
17478
17926
  function languageOf(path) {
17479
- const name = (0, import_node_path22.basename)(path);
17927
+ const name = (0, import_node_path23.basename)(path);
17480
17928
  if (/^Dockerfile(\..+)?$/i.test(name)) return "dockerfile";
17481
- if (!(0, import_node_path22.extname)(name)) return null;
17929
+ if (!(0, import_node_path23.extname)(name)) return null;
17482
17930
  const lang = detectLanguage(path);
17483
17931
  return lang || null;
17484
17932
  }
@@ -17560,12 +18008,12 @@ function declaredDependencies(root, files) {
17560
18008
  if (deps && typeof deps === "object") names2.push(...Object.keys(deps));
17561
18009
  }
17562
18010
  };
17563
- readPackageJson((0, import_node_path22.join)(root, "package.json"));
17564
- const nested = files.filter((f) => f.includes("/") && (0, import_node_path22.basename)(f) === "package.json").slice(0, NESTED_MANIFEST_LIMIT);
17565
- for (const rel of nested) readPackageJson((0, import_node_path22.join)(root, rel));
18011
+ readPackageJson((0, import_node_path23.join)(root, "package.json"));
18012
+ const nested = files.filter((f) => f.includes("/") && (0, import_node_path23.basename)(f) === "package.json").slice(0, NESTED_MANIFEST_LIMIT);
18013
+ for (const rel of nested) readPackageJson((0, import_node_path23.join)(root, rel));
17566
18014
  const pythonManifests = [
17567
- ...["pyproject.toml", "requirements.txt", "Pipfile", "setup.py"].map((f) => (0, import_node_path22.join)(root, f)),
17568
- ...files.filter((f) => f.includes("/") && /(^|\/)(pyproject\.toml|requirements\.txt)$/.test(f)).slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path22.join)(root, f))
18015
+ ...["pyproject.toml", "requirements.txt", "Pipfile", "setup.py"].map((f) => (0, import_node_path23.join)(root, f)),
18016
+ ...files.filter((f) => f.includes("/") && /(^|\/)(pyproject\.toml|requirements\.txt)$/.test(f)).slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path23.join)(root, f))
17569
18017
  ];
17570
18018
  for (const path of pythonManifests) {
17571
18019
  if (!(0, import_node_fs25.existsSync)(path)) continue;
@@ -17580,8 +18028,8 @@ function declaredDependencies(root, files) {
17580
18028
  }
17581
18029
  }
17582
18030
  const goMods = [
17583
- (0, import_node_path22.join)(root, "go.mod"),
17584
- ...files.filter((f) => f.includes("/") && (0, import_node_path22.basename)(f) === "go.mod").slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path22.join)(root, f))
18031
+ (0, import_node_path23.join)(root, "go.mod"),
18032
+ ...files.filter((f) => f.includes("/") && (0, import_node_path23.basename)(f) === "go.mod").slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path23.join)(root, f))
17585
18033
  ];
17586
18034
  for (const path of goMods) {
17587
18035
  if (!(0, import_node_fs25.existsSync)(path)) continue;
@@ -17594,7 +18042,7 @@ function declaredDependencies(root, files) {
17594
18042
  }
17595
18043
  }
17596
18044
  for (const file of ["pom.xml", "build.gradle", "build.gradle.kts", "Gemfile", "Cargo.toml"]) {
17597
- const path = (0, import_node_path22.join)(root, file);
18045
+ const path = (0, import_node_path23.join)(root, file);
17598
18046
  if (!(0, import_node_fs25.existsSync)(path)) continue;
17599
18047
  try {
17600
18048
  const text = (0, import_node_fs25.readFileSync)(path, "utf-8");
@@ -17605,7 +18053,7 @@ function declaredDependencies(root, files) {
17605
18053
  return names2;
17606
18054
  }
17607
18055
  function detectBuildSystem(root, files) {
17608
- const has = (f) => (0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, f)) || files.some((p) => (0, import_node_path22.basename)(p) === f);
18056
+ const has = (f) => (0, import_node_fs25.existsSync)((0, import_node_path23.join)(root, f)) || files.some((p) => (0, import_node_path23.basename)(p) === f);
17609
18057
  if (has("pnpm-lock.yaml")) return "pnpm";
17610
18058
  if (has("yarn.lock")) return "yarn";
17611
18059
  if (has("bun.lock") || has("bun.lockb")) return "bun";
@@ -17622,8 +18070,8 @@ function detectBuildSystem(root, files) {
17622
18070
  }
17623
18071
  function detectArchitecture(root, files) {
17624
18072
  const workspaceMarkers = ["lerna.json", "pnpm-workspace.yaml", "nx.json", "turbo.json", "rush.json"];
17625
- if (workspaceMarkers.some((m) => (0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, m)))) return "monorepo";
17626
- const pkg = readJson((0, import_node_path22.join)(root, "package.json"));
18073
+ if (workspaceMarkers.some((m) => (0, import_node_fs25.existsSync)((0, import_node_path23.join)(root, m)))) return "monorepo";
18074
+ const pkg = readJson((0, import_node_path23.join)(root, "package.json"));
17627
18075
  if (pkg && "workspaces" in pkg) return "monorepo";
17628
18076
  const manifests = files.filter((f) => /(^|\/)(package\.json|go\.mod|pyproject\.toml|Cargo\.toml|pom\.xml)$/.test(f));
17629
18077
  const nested = manifests.filter((f) => f.includes("/"));
@@ -17645,7 +18093,7 @@ function measureAvgFileLength(root, files, languages) {
17645
18093
  let total = 0;
17646
18094
  let counted = 0;
17647
18095
  for (let i = 0; i < candidates2.length; i += stride) {
17648
- const path = (0, import_node_path22.join)(root, candidates2[i]);
18096
+ const path = (0, import_node_path23.join)(root, candidates2[i]);
17649
18097
  try {
17650
18098
  if ((0, import_node_fs25.statSync)(path).size > 2 * 1024 * 1024) continue;
17651
18099
  total += (0, import_node_fs25.readFileSync)(path, "utf-8").split("\n").length;
@@ -17678,14 +18126,14 @@ function detectProject(root = repoRoot()) {
17678
18126
  const existingToolConfigs = [];
17679
18127
  for (const [tool, markers] of TOOL_CONFIG_MARKERS) {
17680
18128
  for (const marker of markers) {
17681
- if ((0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, marker))) {
18129
+ if ((0, import_node_fs25.existsSync)((0, import_node_path23.join)(root, marker))) {
17682
18130
  existingToolConfigs.push({ tool, path: `./${marker}` });
17683
18131
  break;
17684
18132
  }
17685
18133
  }
17686
18134
  }
17687
18135
  return {
17688
- projectName: (0, import_node_path22.basename)(root),
18136
+ projectName: (0, import_node_path23.basename)(root),
17689
18137
  languages,
17690
18138
  languageCounts,
17691
18139
  frameworks: matchAll(dependencies, FRAMEWORK_BY_DEPENDENCY),
@@ -18129,10 +18577,10 @@ async function correctVerityMdVersion(opts) {
18129
18577
  origin: { kind: "synthesized", tools: opts.tools }
18130
18578
  }));
18131
18579
  }
18132
- async function writeFileTo(relative3, body) {
18133
- const target = projectPath(relative3);
18134
- await (0, import_promises9.mkdir)((0, import_node_path23.dirname)(target), { recursive: true });
18135
- await (0, import_promises9.writeFile)(target, body);
18580
+ async function writeFileTo(relative2, body) {
18581
+ const target = projectPath(relative2);
18582
+ await (0, import_promises10.mkdir)((0, import_node_path24.dirname)(target), { recursive: true });
18583
+ await (0, import_promises10.writeFile)(target, body);
18136
18584
  }
18137
18585
  async function deriveConfigForStandard(standard) {
18138
18586
  const spec = standard.knowledge_spec ?? {};
@@ -18189,14 +18637,14 @@ ${validation.detail}`);
18189
18637
  }
18190
18638
 
18191
18639
  // src/lib/setup-state.ts
18192
- var import_promises10 = require("node:fs/promises");
18640
+ var import_promises11 = require("node:fs/promises");
18193
18641
  var import_node_fs27 = require("node:fs");
18194
18642
  var SETUP_STATE_FILE = `${VERITY_DIR}/setup.json`;
18195
18643
  async function readSetupState() {
18196
18644
  const path = projectPath(SETUP_STATE_FILE);
18197
18645
  if (!(0, import_node_fs27.existsSync)(path)) return null;
18198
18646
  try {
18199
- const parsed = JSON.parse(await (0, import_promises10.readFile)(path, "utf-8"));
18647
+ const parsed = JSON.parse(await (0, import_promises11.readFile)(path, "utf-8"));
18200
18648
  return parsed && typeof parsed === "object" ? parsed : null;
18201
18649
  } catch {
18202
18650
  return null;
@@ -18211,7 +18659,7 @@ async function writeSetupState(patch) {
18211
18659
 
18212
18660
  // src/lib/push-setup.ts
18213
18661
  var import_node_fs29 = require("node:fs");
18214
- var import_promises11 = require("node:fs/promises");
18662
+ var import_promises12 = require("node:fs/promises");
18215
18663
  var import_yaml2 = __toESM(require_dist());
18216
18664
 
18217
18665
  // src/lib/verityignore.ts
@@ -18380,7 +18828,7 @@ async function pushStandardAndConfig(globals, what = {}) {
18380
18828
  const standardPath = projectPath(STANDARD_FILE);
18381
18829
  if (pushStandard && (0, import_node_fs29.existsSync)(standardPath)) {
18382
18830
  try {
18383
- const content = (0, import_yaml2.parse)(await (0, import_promises11.readFile)(standardPath, "utf-8"));
18831
+ const content = (0, import_yaml2.parse)(await (0, import_promises12.readFile)(standardPath, "utf-8"));
18384
18832
  const upload = buildStandardUpload(content, readVerityIgnoreRaw());
18385
18833
  if (upload.warning) lines.push(upload.warning);
18386
18834
  const result = await apiRequest({
@@ -18405,7 +18853,7 @@ async function pushStandardAndConfig(globals, what = {}) {
18405
18853
  const configPath = projectPath(CODACY_CONFIG_FILE);
18406
18854
  if (pushConfig && (0, import_node_fs29.existsSync)(configPath)) {
18407
18855
  try {
18408
- const content = JSON.parse(await (0, import_promises11.readFile)(configPath, "utf-8"));
18856
+ const content = JSON.parse(await (0, import_promises12.readFile)(configPath, "utf-8"));
18409
18857
  const result = await apiRequest({
18410
18858
  method: "POST",
18411
18859
  path: "/analysis-configs",
@@ -18439,7 +18887,7 @@ function registerStandardCommands(program2) {
18439
18887
  printError(`No ${STANDARD_FILE} here \u2014 run "verity standard synthesize" to create one.`);
18440
18888
  process.exit(1);
18441
18889
  }
18442
- const content = (0, import_yaml3.parse)(await (0, import_promises12.readFile)(standardPath, "utf-8"));
18890
+ const content = (0, import_yaml3.parse)(await (0, import_promises13.readFile)(standardPath, "utf-8"));
18443
18891
  const derived = await deriveConfigForStandard(content);
18444
18892
  for (const path of derived.written) printInfo(` ${path} \u2713`);
18445
18893
  for (const note of derived.notes) printWarn(` ${note}`);
@@ -18495,7 +18943,7 @@ function registerStandardCommands(program2) {
18495
18943
  }
18496
18944
  let yamlContent;
18497
18945
  try {
18498
- yamlContent = await (0, import_promises12.readFile)(opts.file, "utf-8");
18946
+ yamlContent = await (0, import_promises13.readFile)(opts.file, "utf-8");
18499
18947
  } catch {
18500
18948
  printError(`Cannot read ${opts.file}`);
18501
18949
  process.exit(1);
@@ -18592,7 +19040,7 @@ function parseIntensity(value) {
18592
19040
  }
18593
19041
 
18594
19042
  // src/commands/config.ts
18595
- var import_promises13 = require("node:fs/promises");
19043
+ var import_promises14 = require("node:fs/promises");
18596
19044
  function registerConfigCommands(program2) {
18597
19045
  const config = program2.command("config").description("Manage analysis configuration");
18598
19046
  config.command("service-url").description("Print the resolved service URL (used by the shipped skills)").action(async () => {
@@ -18638,7 +19086,7 @@ function registerConfigCommands(program2) {
18638
19086
  }
18639
19087
  let content;
18640
19088
  try {
18641
- const raw = await (0, import_promises13.readFile)(opts.file, "utf-8");
19089
+ const raw = await (0, import_promises14.readFile)(opts.file, "utf-8");
18642
19090
  content = JSON.parse(raw);
18643
19091
  } catch {
18644
19092
  printError(`Cannot read or parse ${opts.file}`);
@@ -18747,7 +19195,9 @@ function formatRunDetail(run2) {
18747
19195
  if (f.description && f.description !== title) lines.push(` ${f.description}`);
18748
19196
  const fix = f.fix?.description ?? f.suggestion;
18749
19197
  if (fix) lines.push(` \u21B3 fix: ${fix}`);
18750
- if (f.scope === "pre-existing") lines.push(" (pre-existing)");
19198
+ if (f.scope === "pre-existing") {
19199
+ lines.push(f.provenance === "caused-elsewhere" ? " (caused elsewhere \u2014 not by this change)" : " (pre-existing)");
19200
+ }
18751
19201
  }
18752
19202
  }
18753
19203
  const pending = run2.pending_items ?? [];
@@ -19447,6 +19897,10 @@ function rgInvocations(env = process.env) {
19447
19897
  out.push({ cmd: `${(0, import_node_os4.homedir)()}/.local/bin/claude`, argv0: "rg" });
19448
19898
  return out;
19449
19899
  }
19900
+ function ripgrepEnv(base = process.env) {
19901
+ const { RIPGREP_CONFIG_PATH: _dropped, ...rest } = base;
19902
+ return rest;
19903
+ }
19450
19904
  var MAX_SYMBOLS = 12;
19451
19905
  var MAX_SITES = 24;
19452
19906
  var MAX_SITES_PER_FILE = 3;
@@ -19943,6 +20397,112 @@ function partitionSites(rgLines, symbols, opts) {
19943
20397
  while (callers.length + tests.length > MAX_SITES) callers.pop();
19944
20398
  return { callers, tests, dropped };
19945
20399
  }
20400
+ var MAX_IMPORTERS = 12;
20401
+ var MAX_IMPORTERS_PER_FILE = 2;
20402
+ function moduleKey(path) {
20403
+ return path.replace(/\.(ts|tsx|js|jsx|mjs|cjs|mts|cts)$/i, "").replace(/\/index$/, "");
20404
+ }
20405
+ function importSpecifier(text) {
20406
+ const m = /(?:\bfrom|\brequire\s*\(|\bimport\s*\(|^\s*import)\s*['"]([^'"\n]+)['"]/.exec(text);
20407
+ return m?.[1] ?? null;
20408
+ }
20409
+ function resolveSpecifier(fromFile, spec) {
20410
+ if (!spec.startsWith(".")) return null;
20411
+ const dir = fromFile.includes("/") ? fromFile.slice(0, fromFile.lastIndexOf("/")) : "";
20412
+ const out = [];
20413
+ for (const part of (dir ? dir.split("/") : []).concat(spec.split("/"))) {
20414
+ if (part === "" || part === ".") continue;
20415
+ if (part === "..") {
20416
+ if (out.length === 0) return null;
20417
+ out.pop();
20418
+ continue;
20419
+ }
20420
+ out.push(part);
20421
+ }
20422
+ return out.length > 0 ? moduleKey(out.join("/")) : null;
20423
+ }
20424
+ function partitionImporters(rgLines, changedPaths, opts) {
20425
+ const changedByKey = /* @__PURE__ */ new Map();
20426
+ for (const p of changedPaths) changedByKey.set(moduleKey(p), p);
20427
+ const hits = rgLines.map(parseRgLine).filter((h) => h !== null);
20428
+ hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
20429
+ const perFile = /* @__PURE__ */ new Map();
20430
+ const out = [];
20431
+ for (const h of hits) {
20432
+ if (out.length >= MAX_IMPORTERS) break;
20433
+ if (opts.sentPaths.has(h.file)) continue;
20434
+ if (opts.isExcluded(h.file)) continue;
20435
+ if (!isCodeSiteFile(h.file)) continue;
20436
+ const spec = importSpecifier(h.text);
20437
+ if (!spec) continue;
20438
+ const resolved = resolveSpecifier(h.file, spec);
20439
+ if (!resolved) continue;
20440
+ const changed = changedByKey.get(resolved);
20441
+ if (!changed) continue;
20442
+ const used = perFile.get(h.file) ?? 0;
20443
+ if (used >= MAX_IMPORTERS_PER_FILE) continue;
20444
+ perFile.set(h.file, used + 1);
20445
+ out.push({ file: h.file, line: h.line, text: h.text.trim().slice(0, SITE_TEXT_MAX), symbol: changed });
20446
+ }
20447
+ return out;
20448
+ }
20449
+ function runRg(args, cwd, timeoutMs) {
20450
+ let res = null;
20451
+ for (const inv of rgInvocations()) {
20452
+ res = (0, import_node_child_process10.spawnSync)(inv.cmd, args, {
20453
+ ...inv.argv0 ? { argv0: inv.argv0 } : {},
20454
+ cwd,
20455
+ env: ripgrepEnv(),
20456
+ timeout: timeoutMs,
20457
+ maxBuffer: 4 * 1024 * 1024,
20458
+ encoding: "utf8"
20459
+ });
20460
+ if (res.error?.code !== "ENOENT") break;
20461
+ }
20462
+ if (!res || res.error?.code === "ENOENT") {
20463
+ return { ok: false, reason: "no-tool" };
20464
+ }
20465
+ if (res.error) {
20466
+ const code = res.error.code;
20467
+ return { ok: false, reason: code === "ETIMEDOUT" ? "timeout" : "error" };
20468
+ }
20469
+ if (res.signal) return { ok: false, reason: "timeout" };
20470
+ if (res.status !== 0 && res.status !== 1) return { ok: false, reason: "error" };
20471
+ return { ok: true, lines: (res.stdout ?? "").split("\n").map((l) => l.replace(/^\.\//, "")).filter(Boolean) };
20472
+ }
20473
+ function findImporters(input) {
20474
+ const basenames = [
20475
+ ...new Set(input.changedPaths.map((p) => moduleKey(p).split("/").pop() ?? "").filter((b) => b.length > 0))
20476
+ ].slice(0, MAX_SYMBOLS);
20477
+ if (basenames.length === 0) return [];
20478
+ const res = runRg(
20479
+ [
20480
+ "--no-config",
20481
+ "-n",
20482
+ "-w",
20483
+ "-F",
20484
+ "--no-heading",
20485
+ "--color",
20486
+ "never",
20487
+ "-m",
20488
+ "8",
20489
+ "--max-columns",
20490
+ "300",
20491
+ "--max-columns-preview",
20492
+ ...basenames.flatMap((b) => ["-e", b]),
20493
+ "-g",
20494
+ "!**/{dist,build,out,vendor,node_modules,.git,coverage,target,__pycache__}/**",
20495
+ "./"
20496
+ ],
20497
+ input.cwd,
20498
+ input.timeoutMs
20499
+ );
20500
+ if (!res.ok) return [];
20501
+ return partitionImporters(res.lines, input.changedPaths, {
20502
+ sentPaths: input.sentPaths,
20503
+ isExcluded: input.isExcluded
20504
+ });
20505
+ }
19946
20506
  function buildRepoContext(input) {
19947
20507
  const started = Date.now();
19948
20508
  let signalsByPath;
@@ -19977,7 +20537,18 @@ function buildRepoContext(input) {
19977
20537
  const unsupportedExts = [...unsupported].sort().slice(0, 8);
19978
20538
  const audit = unsupportedExts.length > 0 ? { unsupported_exts: unsupportedExts } : {};
19979
20539
  const symbols = rankSymbols([...new Set(collected)]);
20540
+ const changedPaths = [.../* @__PURE__ */ new Set([...signalsByPath.keys(), ...input.deltaFiles.map((f) => f.path)])];
20541
+ const importers = findImporters({
20542
+ changedPaths,
20543
+ sentPaths: input.sentPaths,
20544
+ isExcluded: input.isExcluded,
20545
+ cwd: input.cwd ?? process.cwd(),
20546
+ timeoutMs: input.timeoutMs ?? RG_TIMEOUT_MS
20547
+ });
19980
20548
  if (symbols.length === 0) {
20549
+ if (importers.length > 0) {
20550
+ return { state: "ok", symbols: [], ...audit, callers: [], tests: [], importers, elapsed_ms: Date.now() - started };
20551
+ }
19981
20552
  const everySupportedFileFoundNothing = unsupportedExts.length > 0;
19982
20553
  return {
19983
20554
  state: "absent",
@@ -19986,6 +20557,9 @@ function buildRepoContext(input) {
19986
20557
  };
19987
20558
  }
19988
20559
  const args = [
20560
+ // `--no-config` + RIPGREP_CONFIG_PATH scrubbed below: a config file can add
20561
+ // `--pre=<command>`, which runs a program per searched file.
20562
+ "--no-config",
19989
20563
  "-n",
19990
20564
  "-w",
19991
20565
  "-F",
@@ -20013,33 +20587,19 @@ function buildRepoContext(input) {
20013
20587
  "!**/{dist,build,out,vendor,node_modules,.git,coverage,target,__pycache__}/**",
20014
20588
  "./"
20015
20589
  ];
20016
- let res = null;
20017
- for (const inv of rgInvocations()) {
20018
- res = (0, import_node_child_process10.spawnSync)(inv.cmd, args, {
20019
- ...inv.argv0 ? { argv0: inv.argv0 } : {},
20020
- cwd: input.cwd ?? process.cwd(),
20021
- timeout: input.timeoutMs ?? RG_TIMEOUT_MS,
20022
- maxBuffer: 4 * 1024 * 1024,
20023
- encoding: "utf8"
20024
- });
20025
- if (res.error?.code !== "ENOENT") break;
20026
- }
20027
- if (!res || res.error?.code === "ENOENT") {
20028
- return { state: "absent", reason: "no-tool", symbols, ...audit };
20029
- }
20030
- if (res.error) {
20031
- const code = res.error.code;
20032
- if (code === "ETIMEDOUT") return { state: "absent", reason: "timeout", symbols, ...audit };
20033
- return { state: "absent", reason: "error", symbols, ...audit };
20590
+ const res = runRg(args, input.cwd ?? process.cwd(), input.timeoutMs ?? RG_TIMEOUT_MS);
20591
+ if (!res.ok) {
20592
+ if (importers.length > 0) {
20593
+ return { state: "ok", symbols, ...audit, callers: [], tests: [], importers, elapsed_ms: Date.now() - started };
20594
+ }
20595
+ return { state: "absent", reason: res.reason, symbols, ...audit };
20034
20596
  }
20035
- if (res.signal) return { state: "absent", reason: "timeout", symbols, ...audit };
20036
- if (res.status !== 0 && res.status !== 1) return { state: "absent", reason: "error", symbols, ...audit };
20037
- const lines = (res.stdout ?? "").split("\n").map((l) => l.replace(/^\.\//, "")).filter(Boolean);
20597
+ const lines = res.lines;
20038
20598
  const { callers, tests, dropped } = partitionSites(lines, symbols, {
20039
20599
  sentPaths: input.sentPaths,
20040
20600
  isExcluded: input.isExcluded
20041
20601
  });
20042
- if (callers.length === 0 && tests.length === 0) {
20602
+ if (callers.length === 0 && tests.length === 0 && importers.length === 0) {
20043
20603
  return {
20044
20604
  state: "absent",
20045
20605
  reason: "no-sites",
@@ -20056,6 +20616,7 @@ function buildRepoContext(input) {
20056
20616
  ...audit,
20057
20617
  callers,
20058
20618
  tests,
20619
+ ...importers.length > 0 ? { importers } : {},
20059
20620
  elapsed_ms: Date.now() - started
20060
20621
  };
20061
20622
  }
@@ -20123,12 +20684,17 @@ function upgradeToExcerpts(rc, opts) {
20123
20684
  }
20124
20685
  function describeRepoContext(rc) {
20125
20686
  if (rc.state !== "ok") {
20126
- const exts = rc.unsupported_exts?.length ? ` \xB7 no rules for: ${rc.unsupported_exts.join(", ")}` : "";
20127
- return `absent (${rc.reason ?? "unknown"})${exts}`;
20687
+ const bits = [
20688
+ rc.symbols?.length ? `searched: ${rc.symbols.join(", ")}` : "",
20689
+ rc.dropped_symbols?.length ? `dropped too-common: ${rc.dropped_symbols.join(", ")}` : "",
20690
+ rc.unsupported_exts?.length ? `no rules for: ${rc.unsupported_exts.join(", ")}` : ""
20691
+ ].filter(Boolean);
20692
+ return `absent (${rc.reason ?? "unknown"})${bits.length > 0 ? ` \xB7 ${bits.join(" \xB7 ")}` : ""}`;
20128
20693
  }
20129
20694
  const parts = [
20130
20695
  `${rc.symbols?.length ?? 0} symbol(s) \u2192 ${rc.callers?.length ?? 0} caller(s) \xB7 ${rc.tests?.length ?? 0} test(s)`
20131
20696
  ];
20697
+ if (rc.importers?.length) parts.push(`${rc.importers.length} importer(s)`);
20132
20698
  if (rc.excerpts?.length) parts.push(`${rc.excerpts.length} excerpt(s)`);
20133
20699
  if (rc.dropped_symbols?.length) parts.push(`dropped too-common: ${rc.dropped_symbols.join(", ")}`);
20134
20700
  if (rc.unsupported_exts?.length) parts.push(`no rules for: ${rc.unsupported_exts.join(", ")}`);
@@ -20256,11 +20822,85 @@ function installRunEvidence(run2) {
20256
20822
  }
20257
20823
 
20258
20824
  // src/lib/git-frame.ts
20259
- var import_node_child_process11 = require("node:child_process");
20260
20825
  var import_node_fs33 = require("node:fs");
20261
20826
  var import_node_os5 = require("node:os");
20262
- var import_node_path24 = require("node:path");
20263
20827
  var import_node_path25 = require("node:path");
20828
+ var import_node_path26 = require("node:path");
20829
+
20830
+ // src/lib/hardened-git.ts
20831
+ var import_node_child_process11 = require("node:child_process");
20832
+ var HARDENED_CONFIG_ARGS = [
20833
+ "-c",
20834
+ "core.fsmonitor=false",
20835
+ "-c",
20836
+ "core.pager=cat",
20837
+ "-c",
20838
+ "log.showSignature=false"
20839
+ ];
20840
+ var DIFF_READ_FLAGS = ["--no-ext-diff", "--no-textconv"];
20841
+ var DEFAULT_GIT_TIMEOUT_MS = 6e4;
20842
+ var DEFAULT_GIT_MAX_BUFFER = 64 * 1024 * 1024;
20843
+ function hardenedGitEnv(base = process.env) {
20844
+ const env = {};
20845
+ for (const [key, value] of Object.entries(base)) {
20846
+ if (key.startsWith("GIT_")) continue;
20847
+ env[key] = value;
20848
+ }
20849
+ env.GIT_OPTIONAL_LOCKS = "0";
20850
+ env.GIT_TERMINAL_PROMPT = "0";
20851
+ return env;
20852
+ }
20853
+ function hardenedGitArgv(args) {
20854
+ return [...HARDENED_CONFIG_ARGS, ...args];
20855
+ }
20856
+ function classifyError(err) {
20857
+ const e = err;
20858
+ const stderr = typeof e?.stderr === "string" ? e.stderr : e?.stderr ? e.stderr.toString("utf-8") : "";
20859
+ if (e?.code === "ETIMEDOUT" || e?.killed && e?.signal === "SIGTERM") return { ok: false, error: "timeout", stderr };
20860
+ if (e?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return { ok: false, error: "too_large", stderr };
20861
+ return { ok: false, error: "failed", stderr };
20862
+ }
20863
+ function runGitSync(cwd, args, opts = {}) {
20864
+ try {
20865
+ const stdout = (0, import_node_child_process11.execFileSync)("git", hardenedGitArgv(args), {
20866
+ cwd,
20867
+ env: hardenedGitEnv(),
20868
+ encoding: "utf-8",
20869
+ stdio: ["ignore", "pipe", "pipe"],
20870
+ timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
20871
+ maxBuffer: opts.maxBuffer ?? DEFAULT_GIT_MAX_BUFFER
20872
+ });
20873
+ return { ok: true, stdout };
20874
+ } catch (err) {
20875
+ return classifyError(err);
20876
+ }
20877
+ }
20878
+ function runGit(cwd, args, opts = {}) {
20879
+ return new Promise((resolvePromise) => {
20880
+ (0, import_node_child_process11.execFile)(
20881
+ "git",
20882
+ hardenedGitArgv(args),
20883
+ {
20884
+ cwd,
20885
+ env: hardenedGitEnv(),
20886
+ encoding: "utf-8",
20887
+ timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
20888
+ maxBuffer: opts.maxBuffer ?? DEFAULT_GIT_MAX_BUFFER
20889
+ },
20890
+ (err, stdout, stderr) => {
20891
+ if (err) {
20892
+ const e = err;
20893
+ e.stderr = stderr;
20894
+ resolvePromise(classifyError(e));
20895
+ return;
20896
+ }
20897
+ resolvePromise({ ok: true, stdout });
20898
+ }
20899
+ );
20900
+ });
20901
+ }
20902
+
20903
+ // src/lib/git-frame.ts
20264
20904
  var VALUE_TOKEN = `(?:'[^']*'|"[^"]*"|\\S+)`;
20265
20905
  var GIT_GLOBAL_OPTS = `(?:\\s+(?:-[Cc]\\s+${VALUE_TOKEN}|--?[\\w-]+(?:=\\S+)?))*`;
20266
20906
  var COMMIT_HEAD = `git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`;
@@ -20270,7 +20910,41 @@ var COMMIT_RE = new RegExp(`(?:^|[\\s;&|(])${COMMIT_HEAD}|(?:^|[;&|(])\\s*[^\\s;
20270
20910
  var PUSH_RE = new RegExp(`(?:^|[\\s;&|(])${PUSH_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${PUSH_HEAD}`);
20271
20911
  var GH_PR_RE = new RegExp(`(?:^|[\\s;&|(])${GH_PR_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${GH_PR_HEAD}`);
20272
20912
  function splitSegments(command) {
20273
- return (command ?? "").split(/&&|\|\||;|\n/);
20913
+ return stripHeredocBodies(command ?? "").split(/&&|\|\||;|\n/);
20914
+ }
20915
+ var HEREDOC_OPERATOR = /(^|[^<])<<(-?)[ \t]*(?:'([^'\n]+)'|"([^"\n]+)"|\\?([A-Za-z_][A-Za-z0-9_]*))/g;
20916
+ var SHELL_CONSUMER = /(^|[\s;&|(])(bash|sh|zsh|dash|ksh|fish|ssh|eval|exec|sudo|su|xargs|env|nohup|time|docker|kubectl|podman)(\s|$)/;
20917
+ function stripHeredocBodies(command) {
20918
+ const lines = command.split("\n");
20919
+ const out = [];
20920
+ let i = 0;
20921
+ while (i < lines.length) {
20922
+ const line = lines[i];
20923
+ out.push(line);
20924
+ i++;
20925
+ const words = [];
20926
+ HEREDOC_OPERATOR.lastIndex = 0;
20927
+ let m;
20928
+ while ((m = HEREDOC_OPERATOR.exec(line)) !== null) {
20929
+ words.push({ word: m[3] ?? m[4] ?? m[5], dash: m[2] === "-" });
20930
+ }
20931
+ if (words.length === 0 || SHELL_CONSUMER.test(line)) continue;
20932
+ let cursor = i;
20933
+ let end = -1;
20934
+ for (const { word, dash } of words) {
20935
+ let k = cursor;
20936
+ while (k < lines.length && (dash ? lines[k].replace(/^\t+/, "") : lines[k]) !== word) k++;
20937
+ if (k >= lines.length) {
20938
+ end = -1;
20939
+ break;
20940
+ }
20941
+ end = k;
20942
+ cursor = k + 1;
20943
+ }
20944
+ if (end === -1) continue;
20945
+ i = end + 1;
20946
+ }
20947
+ return out.join("\n");
20274
20948
  }
20275
20949
  function findMomentSegment(command, on) {
20276
20950
  const segments = splitSegments(command);
@@ -20313,8 +20987,8 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
20313
20987
  if (SHELL_DYNAMIC.test(raw) || raw === "-") {
20314
20988
  return { dir: null, named: true, unresolvable: `cd target not statically resolvable: ${raw}` };
20315
20989
  }
20316
- const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0, import_node_path25.join)((0, import_node_os5.homedir)(), raw.slice(2)) : raw;
20317
- dir = (0, import_node_path24.isAbsolute)(expanded) ? expanded : (0, import_node_path24.resolve)(dir, expanded);
20990
+ const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0, import_node_path26.join)((0, import_node_os5.homedir)(), raw.slice(2)) : raw;
20991
+ dir = (0, import_node_path25.isAbsolute)(expanded) ? expanded : (0, import_node_path25.resolve)(dir, expanded);
20318
20992
  }
20319
20993
  const seg = segments[segmentIndex];
20320
20994
  const overrideMatch = /--(?:git-dir|work-tree)(?:=|\s)|\bGIT_(?:DIR|WORK_TREE|INDEX_FILE)=/.exec(seg);
@@ -20332,8 +21006,8 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
20332
21006
  if (SHELL_DYNAMIC.test(raw)) {
20333
21007
  return { dir: null, named: true, unresolvable: `-C target not statically resolvable: ${raw}` };
20334
21008
  }
20335
- const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0, import_node_path25.join)((0, import_node_os5.homedir)(), raw.slice(2)) : raw;
20336
- dir = (0, import_node_path24.isAbsolute)(expanded) ? expanded : (0, import_node_path24.resolve)(dir, expanded);
21009
+ const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0, import_node_path26.join)((0, import_node_os5.homedir)(), raw.slice(2)) : raw;
21010
+ dir = (0, import_node_path25.isAbsolute)(expanded) ? expanded : (0, import_node_path25.resolve)(dir, expanded);
20337
21011
  }
20338
21012
  }
20339
21013
  return { dir, named, unresolvable: null };
@@ -20388,17 +21062,14 @@ function parsePushTarget(segment) {
20388
21062
  return { remote, srcRef: src, dstRef: dst || null, isDelete };
20389
21063
  }
20390
21064
  function gitAt(dir, args) {
20391
- try {
20392
- return (0, import_node_child_process11.execFileSync)("git", args, { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
20393
- } catch {
20394
- return "";
20395
- }
21065
+ const res = runGitSync(dir, args);
21066
+ return res.ok ? res.stdout.trim() : "";
20396
21067
  }
20397
21068
  function realpathOr2(p) {
20398
21069
  try {
20399
21070
  return import_node_fs33.realpathSync.native(p);
20400
21071
  } catch {
20401
- return (0, import_node_path24.resolve)(p);
21072
+ return (0, import_node_path25.resolve)(p);
20402
21073
  }
20403
21074
  }
20404
21075
  function resolveFrame(input) {
@@ -20437,7 +21108,7 @@ function resolveFrame(input) {
20437
21108
  const gitDirRaw = gitAt(dir, ["rev-parse", "--absolute-git-dir"]);
20438
21109
  const commonRaw = gitAt(dir, ["rev-parse", "--git-common-dir"]);
20439
21110
  const gitDir = gitDirRaw ? realpathOr2(gitDirRaw) : null;
20440
- const commonDir = commonRaw ? realpathOr2((0, import_node_path24.isAbsolute)(commonRaw) ? commonRaw : (0, import_node_path24.resolve)(dir, commonRaw)) : null;
21111
+ const commonDir = commonRaw ? realpathOr2((0, import_node_path25.isAbsolute)(commonRaw) ? commonRaw : (0, import_node_path25.resolve)(dir, commonRaw)) : null;
20441
21112
  const branchRaw = gitAt(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
20442
21113
  return {
20443
21114
  moment: found?.moment ?? null,
@@ -20612,7 +21283,7 @@ function stagedRange(frame, command) {
20612
21283
  if (plan.kind === "unpredictable") {
20613
21284
  return { kind: "staged", base: "HEAD", head: "INDEX", via: "staged-in-command", refusal: plan.reason };
20614
21285
  }
20615
- const mergeHead = frame.gitDir ? (0, import_node_path25.join)(frame.gitDir, "MERGE_HEAD") : null;
21286
+ const mergeHead = frame.gitDir ? (0, import_node_path26.join)(frame.gitDir, "MERGE_HEAD") : null;
20616
21287
  if (mergeHead && (0, import_node_fs33.existsSync)(mergeHead)) {
20617
21288
  const vsHead = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "HEAD"]).split("\n").filter(Boolean));
20618
21289
  const vsMerge = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "MERGE_HEAD"]).split("\n").filter(Boolean));
@@ -20665,7 +21336,7 @@ function rangeChangeSignals(frame, range, paths) {
20665
21336
  const out = /* @__PURE__ */ new Map();
20666
21337
  if (range.kind === "nothing" || paths.length === 0) return out;
20667
21338
  if (range.kind === "push" && !range.base) return out;
20668
- const args = range.kind === "staged" || range.kind === "merge" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
21339
+ const args = range.kind === "staged" || range.kind === "merge" ? ["diff", ...DIFF_READ_FLAGS, "--cached", "--unified=0"] : ["diff", ...DIFF_READ_FLAGS, "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
20669
21340
  const diff = frameGit(frame, [...args, "--", ...paths]);
20670
21341
  let current = null;
20671
21342
  let oldSide = null;
@@ -21028,7 +21699,7 @@ function sanitizeCommandWithLoss(rawCmd) {
21028
21699
  const lost = lines.slice(1).some((l) => l.trim().length > 0);
21029
21700
  const first = lines[0];
21030
21701
  const cmd = sanitizeCommand(first);
21031
- const hadSeparator = SEPARATORS.some((sep3) => first.indexOf(sep3) > 0);
21702
+ const hadSeparator = SEPARATORS.some((sep4) => first.indexOf(sep4) > 0);
21032
21703
  return { cmd, lost: lost || !hadSeparator && first.length > MAX_COMMAND_CHARS };
21033
21704
  }
21034
21705
  var SEPARATORS = [" | ", " > ", " >> ", " 2>", " && ", " ; "];
@@ -21037,11 +21708,11 @@ function sanitizeCommand(rawCmd) {
21037
21708
  let cmd = rawCmd.split("\n")[0];
21038
21709
  let cut = -1;
21039
21710
  let marker = "";
21040
- for (const sep3 of SEPARATORS) {
21041
- const idx = cmd.indexOf(sep3);
21711
+ for (const sep4 of SEPARATORS) {
21712
+ const idx = cmd.indexOf(sep4);
21042
21713
  if (idx > 0 && (cut === -1 || idx < cut)) {
21043
21714
  cut = idx;
21044
- marker = sep3.trim();
21715
+ marker = sep4.trim();
21045
21716
  }
21046
21717
  }
21047
21718
  if (cut > -1) cmd = cmd.slice(0, cut);
@@ -21065,28 +21736,28 @@ async function readStopHookStdin() {
21065
21736
  try {
21066
21737
  if (process.stdin.isTTY) return empty;
21067
21738
  const chunks = [];
21068
- const timeout = new Promise((resolve5) => setTimeout(() => resolve5(empty), 500));
21069
- const read = new Promise((resolve5) => {
21739
+ const timeout = new Promise((resolve6) => setTimeout(() => resolve6(empty), 500));
21740
+ const read = new Promise((resolve6) => {
21070
21741
  process.stdin.on("data", (chunk) => chunks.push(chunk));
21071
21742
  process.stdin.on("end", () => {
21072
21743
  const raw = Buffer.concat(chunks).toString("utf-8").trim();
21073
21744
  if (!raw) {
21074
- resolve5(empty);
21745
+ resolve6(empty);
21075
21746
  return;
21076
21747
  }
21077
21748
  try {
21078
21749
  const data = JSON.parse(raw);
21079
- resolve5({
21750
+ resolve6({
21080
21751
  assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
21081
21752
  stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
21082
21753
  transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
21083
21754
  sessionId: typeof data.session_id === "string" ? data.session_id : null
21084
21755
  });
21085
21756
  } catch {
21086
- resolve5(empty);
21757
+ resolve6(empty);
21087
21758
  }
21088
21759
  });
21089
- process.stdin.on("error", () => resolve5(empty));
21760
+ process.stdin.on("error", () => resolve6(empty));
21090
21761
  process.stdin.resume();
21091
21762
  });
21092
21763
  return await Promise.race([read, timeout]);
@@ -21290,7 +21961,7 @@ function channelSilence(input) {
21290
21961
  // src/lib/cli-version.ts
21291
21962
  function cliVersion() {
21292
21963
  try {
21293
- return true ? "0.32.7" : "dev";
21964
+ return true ? "0.33.0-experimental.34db3a4" : "dev";
21294
21965
  } catch {
21295
21966
  return "dev";
21296
21967
  }
@@ -21342,10 +22013,12 @@ var SEVERITY_ORDER = {
21342
22013
  Low: 3
21343
22014
  };
21344
22015
  function isCodacyAvailable() {
21345
- return codacyAnalysisPath() !== null;
21346
- }
21347
- function codacyAnalysisPath() {
21348
- return whichSync("codacy-analysis");
22016
+ try {
22017
+ (0, import_node_child_process12.execSync)("which codacy-analysis", { stdio: "pipe" });
22018
+ return true;
22019
+ } catch {
22020
+ return false;
22021
+ }
21349
22022
  }
21350
22023
  function buildAnalyzerArgv(files) {
21351
22024
  return [
@@ -21372,16 +22045,6 @@ function withFailure(kind, detail) {
21372
22045
  summary: { ...EMPTY_RESULT.summary, failure: { kind, detail: detail.slice(0, 300) } }
21373
22046
  };
21374
22047
  }
21375
- function runCodacyAnalysisIfAvailable(files) {
21376
- if (files.length === 0) return EMPTY_RESULT;
21377
- if (!isCodacyAvailable()) {
21378
- return withFailure(
21379
- "analyzer_unavailable",
21380
- "@codacy/analysis-cli was not found on PATH \u2014 no static analysis ran"
21381
- );
21382
- }
21383
- return runCodacyAnalysis(files);
21384
- }
21385
22048
  function runCodacyAnalysis(files) {
21386
22049
  const empty = EMPTY_RESULT;
21387
22050
  if (files.length === 0) return empty;
@@ -21393,8 +22056,7 @@ function runCodacyAnalysis(files) {
21393
22056
  }
21394
22057
  });
21395
22058
  if (existingFiles.length === 0) return empty;
21396
- const analyzer = codacyAnalysisPath() ?? "codacy-analysis";
21397
- const proc = (0, import_node_child_process12.spawnSync)(analyzer, buildAnalyzerArgv(existingFiles), {
22059
+ const proc = (0, import_node_child_process12.spawnSync)("codacy-analysis", buildAnalyzerArgv(existingFiles), {
21398
22060
  encoding: "utf-8",
21399
22061
  maxBuffer: 10 * 1024 * 1024
21400
22062
  });
@@ -21562,10 +22224,11 @@ var EMPTY_STATIC = {
21562
22224
  summary: { total_findings: 0, by_severity: {}, tools_run: [] }
21563
22225
  };
21564
22226
  function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
21565
- if (skipStatic) return EMPTY_STATIC;
22227
+ if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
21566
22228
  let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
21567
22229
  if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
21568
- return runCodacyAnalysisIfAvailable(scannable);
22230
+ if (scannable.length === 0) return EMPTY_STATIC;
22231
+ return runCodacyAnalysis(scannable);
21569
22232
  }
21570
22233
  function localOnlyAndExit(staticResults) {
21571
22234
  printJsonCompact({
@@ -21657,7 +22320,7 @@ async function scope(run2) {
21657
22320
 
21658
22321
  // src/lib/specs.ts
21659
22322
  var import_node_fs36 = require("node:fs");
21660
- var import_node_path26 = require("node:path");
22323
+ var import_node_path27 = require("node:path");
21661
22324
  var SPEC_CANDIDATES = [
21662
22325
  "CLAUDE.md",
21663
22326
  "AGENTS.md",
@@ -21676,16 +22339,6 @@ var SPEC_CANDIDATES = [
21676
22339
  var DOC_EXT = /\.(md|mdx|ya?ml|txt|rst|adoc)$/i;
21677
22340
  var UNCONSULTED_FILE_BYTES = 10240;
21678
22341
  var UNCONSULTED_TOTAL_BYTES = 30720;
21679
- function readSpecFiles(specsOpt, root) {
21680
- const out = [];
21681
- for (const raw of specsOpt.split(",").map((f) => f.trim()).filter(Boolean)) {
21682
- const candidate = (0, import_node_path26.isAbsolute)(raw) ? (0, import_node_path26.relative)(root, raw) : raw;
21683
- const content = readFileInside(root, candidate, MAX_EXPLICIT_SPEC_FILE_BYTES);
21684
- if (content === null) continue;
21685
- out.push({ path: raw, content });
21686
- }
21687
- return out;
21688
- }
21689
22342
  function discoverSpecs(consulted = []) {
21690
22343
  const result = [];
21691
22344
  const seen = /* @__PURE__ */ new Set();
@@ -21703,15 +22356,13 @@ function discoverSpecs(consulted = []) {
21703
22356
  const remaining = totalCap - totalBytes;
21704
22357
  const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
21705
22358
  const readBytes = Math.min(fileCap, remaining);
22359
+ const opened = openRegularInRoot(process.cwd(), specPath);
22360
+ if (!opened.ok) return true;
21706
22361
  try {
21707
- const buf = Buffer.alloc(readBytes);
21708
- const fd = (0, import_node_fs36.openSync)(specPath, "r");
21709
- const bytesRead = (0, import_node_fs36.readSync)(fd, buf, 0, readBytes, 0);
21710
- (0, import_node_fs36.closeSync)(fd);
21711
- const content = buf.slice(0, bytesRead).toString("utf-8");
21712
- if (!content) return true;
21713
- result.push({ path: specPath, content });
21714
- totalBytes += content.length;
22362
+ const bytes = readOpened(opened.fd, Math.min(opened.size, readBytes));
22363
+ if (bytes.length === 0) return true;
22364
+ result.push({ path: specPath, content: bytes.toString("utf-8") });
22365
+ totalBytes += bytes.length;
21715
22366
  } catch {
21716
22367
  }
21717
22368
  return true;
@@ -21740,7 +22391,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
21740
22391
  try {
21741
22392
  const entries = (0, import_node_fs36.readdirSync)(dir, { withFileTypes: true });
21742
22393
  for (const entry of entries) {
21743
- const fullPath = (0, import_node_path26.join)(dir, entry.name);
22394
+ const fullPath = (0, import_node_path27.join)(dir, entry.name);
21744
22395
  if (entry.isFile() && entry.name.endsWith(".md")) {
21745
22396
  result.push(fullPath);
21746
22397
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -21752,37 +22403,51 @@ function findMdFiles(dir, maxDepth, depth = 0) {
21752
22403
  return result;
21753
22404
  }
21754
22405
  function discoverPlans() {
21755
- const homePlansDir = (0, import_node_path26.join)(process.env.HOME ?? "", ".claude", "plans");
21756
- const localPlansDir = ".claude/plans";
22406
+ const home = process.env.HOME ?? "";
22407
+ const homePlansDir = (0, import_node_path27.join)(home, ".claude", "plans");
22408
+ const sources = [];
22409
+ if (isRealDirectoryChain(process.cwd(), [".claude", "plans"])) {
22410
+ sources.push({ root: process.cwd(), prefix: (0, import_node_path27.join)(".claude", "plans") });
22411
+ }
22412
+ if (home && (0, import_node_fs36.existsSync)(homePlansDir)) sources.push({ root: homePlansDir, prefix: "" });
21757
22413
  const candidates2 = [];
21758
22414
  const seen = /* @__PURE__ */ new Set();
21759
- for (const plansDir of [localPlansDir, homePlansDir]) {
21760
- if (!(0, import_node_fs36.existsSync)(plansDir)) continue;
22415
+ for (const source of sources) {
22416
+ let names2;
21761
22417
  try {
21762
- for (const f of (0, import_node_fs36.readdirSync)(plansDir)) {
21763
- if (!f.endsWith(".md") || seen.has(f)) continue;
21764
- seen.add(f);
21765
- const fullPath = (0, import_node_path26.join)(plansDir, f);
21766
- try {
21767
- const stat3 = (0, import_node_fs36.statSync)(fullPath);
21768
- candidates2.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
21769
- } catch {
21770
- }
21771
- }
22418
+ names2 = (0, import_node_fs36.readdirSync)(source.prefix ? (0, import_node_path27.join)(source.root, source.prefix) : source.root);
21772
22419
  } catch {
22420
+ continue;
22421
+ }
22422
+ for (const f of names2) {
22423
+ if (!f.endsWith(".md") || seen.has(f)) continue;
22424
+ seen.add(f);
22425
+ const relPath = source.prefix ? (0, import_node_path27.join)(source.prefix, f) : f;
22426
+ const stat3 = statRegularInRoot(source.root, relPath);
22427
+ if (!stat3.ok) continue;
22428
+ candidates2.push({ name: f, root: source.root, relPath, mtime: stat3.mtimeMs, size: stat3.size });
21773
22429
  }
21774
22430
  }
21775
22431
  candidates2.sort((a, b) => b.mtime - a.mtime);
21776
22432
  const result = [];
21777
22433
  for (const entry of candidates2.slice(0, MAX_PLAN_FILES)) {
21778
22434
  if (entry.size > MAX_PLAN_FILE_BYTES) continue;
22435
+ const read = readRegularFileInRoot(entry.root, entry.relPath, MAX_PLAN_FILE_BYTES);
22436
+ if (read.ok) result.push({ name: entry.name, content: read.content });
22437
+ }
22438
+ return result;
22439
+ }
22440
+ function isRealDirectoryChain(root, parts) {
22441
+ let current = root;
22442
+ for (const part of parts) {
22443
+ current = (0, import_node_path27.join)(current, part);
21779
22444
  try {
21780
- const content = (0, import_node_fs36.readFileSync)(entry.path, "utf-8");
21781
- result.push({ name: entry.name, content });
22445
+ if (!(0, import_node_fs36.lstatSync)(current).isDirectory()) return false;
21782
22446
  } catch {
22447
+ return false;
21783
22448
  }
21784
22449
  }
21785
- return result;
22450
+ return true;
21786
22451
  }
21787
22452
  var GUARD_DOC_EXT = /\.(md|mdx|txt|rst|adoc)$/i;
21788
22453
  function discoverGuardDocs(rangeFiles2) {
@@ -21793,16 +22458,21 @@ function discoverGuardDocs(rangeFiles2) {
21793
22458
  if (!GUARD_DOC_EXT.test(path)) continue;
21794
22459
  if (path.startsWith("/") || path.includes("..")) continue;
21795
22460
  if (!(0, import_node_fs36.existsSync)(path)) continue;
22461
+ const opened = openRegularInRoot(process.cwd(), path);
22462
+ if (!opened.ok) continue;
22463
+ if (opened.size > MAX_PLAN_FILE_BYTES || totalBytes + opened.size > MAX_TOTAL_SPEC_BYTES) {
22464
+ closeOpened(opened.fd);
22465
+ continue;
22466
+ }
22467
+ let bytes;
21796
22468
  try {
21797
- const stat3 = (0, import_node_fs36.statSync)(path);
21798
- if (stat3.size > MAX_PLAN_FILE_BYTES) continue;
21799
- if (totalBytes + stat3.size > MAX_TOTAL_SPEC_BYTES) continue;
21800
- const content = (0, import_node_fs36.readFileSync)(path, "utf-8");
21801
- if (!content) continue;
21802
- result.push({ name: path, content });
21803
- totalBytes += content.length;
22469
+ bytes = readOpened(opened.fd, opened.size);
21804
22470
  } catch {
22471
+ continue;
21805
22472
  }
22473
+ if (bytes.length === 0) continue;
22474
+ result.push({ name: path, content: bytes.toString("utf-8") });
22475
+ totalBytes += bytes.length;
21806
22476
  }
21807
22477
  return result;
21808
22478
  }
@@ -21974,7 +22644,7 @@ async function mode(run2) {
21974
22644
 
21975
22645
  // src/lib/fold.ts
21976
22646
  var import_node_fs37 = require("node:fs");
21977
- var import_node_path27 = require("node:path");
22647
+ var import_node_path28 = require("node:path");
21978
22648
  var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
21979
22649
  "user",
21980
22650
  "assistant",
@@ -22206,9 +22876,9 @@ function fold(transcriptPath, opts = {}) {
22206
22876
  return result;
22207
22877
  }
22208
22878
  try {
22209
- const sidecarDir = (0, import_node_path27.join)(
22210
- (0, import_node_path27.dirname)(transcriptPath),
22211
- (0, import_node_path27.basename)(transcriptPath).replace(/\.jsonl$/, ""),
22879
+ const sidecarDir = (0, import_node_path28.join)(
22880
+ (0, import_node_path28.dirname)(transcriptPath),
22881
+ (0, import_node_path28.basename)(transcriptPath).replace(/\.jsonl$/, ""),
22212
22882
  "subagents"
22213
22883
  );
22214
22884
  if ((0, import_node_fs37.existsSync)(sidecarDir)) {
@@ -22218,7 +22888,7 @@ function fold(transcriptPath, opts = {}) {
22218
22888
  const walk2 = (d, depth) => {
22219
22889
  if (depth > 4) return;
22220
22890
  for (const e of (0, import_node_fs37.readdirSync)(d, { withFileTypes: true })) {
22221
- const p = (0, import_node_path27.join)(d, e.name);
22891
+ const p = (0, import_node_path28.join)(d, e.name);
22222
22892
  if (e.isDirectory()) {
22223
22893
  walk2(p, depth + 1);
22224
22894
  } else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
@@ -22542,12 +23212,14 @@ async function evidence(run2) {
22542
23212
  authorshipWasObservable
22543
23213
  });
22544
23214
  const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
22545
- if (!opts.skipStatic) {
23215
+ if (!opts.skipStatic && isCodacyAvailable()) {
22546
23216
  let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
22547
23217
  if (baseline) {
22548
23218
  allScannable = allScannable.filter((f) => changedSinceBaseline(f, baseline));
22549
23219
  }
22550
- staticResults = runCodacyAnalysisIfAvailable(allScannable);
23220
+ if (allScannable.length > 0) {
23221
+ staticResults = runCodacyAnalysis(allScannable);
23222
+ }
22551
23223
  }
22552
23224
  const deltaSet = baseline ? recentForReview.filter((f) => changedSinceBaseline(f, baseline)) : recentForReview;
22553
23225
  codeDelta = collectCodeDelta(deltaSet, {
@@ -22591,7 +23263,7 @@ async function evidence(run2) {
22591
23263
 
22592
23264
  // src/lib/cache-cleanup.ts
22593
23265
  var import_node_fs38 = require("node:fs");
22594
- var import_node_path28 = require("node:path");
23266
+ var import_node_path29 = require("node:path");
22595
23267
  var CACHE_TTL_DAYS = 7;
22596
23268
  function pruneStaleCache() {
22597
23269
  try {
@@ -22599,7 +23271,7 @@ function pruneStaleCache() {
22599
23271
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
22600
23272
  for (const entry of (0, import_node_fs38.readdirSync)(dir)) {
22601
23273
  if (!entry.startsWith("pending-")) continue;
22602
- const path = (0, import_node_path28.join)(dir, entry);
23274
+ const path = (0, import_node_path29.join)(dir, entry);
22603
23275
  try {
22604
23276
  const stat3 = (0, import_node_fs38.statSync)(path);
22605
23277
  if (stat3.mtimeMs < cutoff) {
@@ -22760,14 +23432,15 @@ async function repoContext(run2) {
22760
23432
  dropped: run2.repoContext.dropped_symbols?.length ?? 0,
22761
23433
  callers: run2.repoContext.callers?.length ?? 0,
22762
23434
  tests: run2.repoContext.tests?.length ?? 0,
23435
+ importers: run2.repoContext.importers?.length ?? 0,
22763
23436
  elapsed_ms: run2.repoContext.elapsed_ms ?? null
22764
23437
  });
22765
23438
  }
22766
23439
 
22767
23440
  // src/lib/seed-runner.ts
22768
- var import_promises14 = require("node:fs/promises");
23441
+ var import_promises15 = require("node:fs/promises");
22769
23442
  var import_node_fs40 = require("node:fs");
22770
- var import_node_path29 = require("node:path");
23443
+ var import_node_path30 = require("node:path");
22771
23444
  var import_yaml4 = __toESM(require_dist());
22772
23445
 
22773
23446
  // src/lib/seed.ts
@@ -22843,7 +23516,7 @@ function classifyClaudeSection(heading) {
22843
23516
  if (hasAny(["integration", "webhook", "third-party", "third party", "provider"])) return "integration";
22844
23517
  return "domain";
22845
23518
  }
22846
- function slugify(s) {
23519
+ function slugify2(s) {
22847
23520
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
22848
23521
  }
22849
23522
  var FRAMEWORK_GLOBS = [
@@ -22940,7 +23613,7 @@ _Extracted from README on setup. Update when project direction changes._`,
22940
23613
  for (const fwRaw of frameworks) {
22941
23614
  const fw = fwRaw.trim();
22942
23615
  if (!fw) continue;
22943
- const slug = slugify(fw);
23616
+ const slug = slugify2(fw);
22944
23617
  if (!slug || seenFrameworkSlugs.has(slug)) continue;
22945
23618
  seenFrameworkSlugs.add(slug);
22946
23619
  out.push({
@@ -23011,7 +23684,7 @@ async function runSeed(opts) {
23011
23684
  }
23012
23685
  let standardDoc;
23013
23686
  try {
23014
- const raw = await (0, import_promises14.readFile)(STANDARD_FILE, "utf-8");
23687
+ const raw = await (0, import_promises15.readFile)(STANDARD_FILE, "utf-8");
23015
23688
  standardDoc = (0, import_yaml4.parse)(raw);
23016
23689
  } catch {
23017
23690
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
@@ -23020,7 +23693,7 @@ async function runSeed(opts) {
23020
23693
  let readmeContent;
23021
23694
  if ((0, import_node_fs40.existsSync)("README.md")) {
23022
23695
  try {
23023
- readmeContent = await (0, import_promises14.readFile)("README.md", "utf-8");
23696
+ readmeContent = await (0, import_promises15.readFile)("README.md", "utf-8");
23024
23697
  } catch {
23025
23698
  }
23026
23699
  }
@@ -23028,7 +23701,7 @@ async function runSeed(opts) {
23028
23701
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
23029
23702
  if ((0, import_node_fs40.existsSync)(p)) {
23030
23703
  try {
23031
- claudeMdContent = await (0, import_promises14.readFile)(p, "utf-8");
23704
+ claudeMdContent = await (0, import_promises15.readFile)(p, "utf-8");
23032
23705
  break;
23033
23706
  } catch {
23034
23707
  }
@@ -23049,7 +23722,7 @@ async function runSeed(opts) {
23049
23722
  if (candidates2.length === 0) {
23050
23723
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
23051
23724
  }
23052
- const overviewPath = (0, import_node_path29.join)(MEMORY_DIR, "domain", "project-overview.md");
23725
+ const overviewPath = (0, import_node_path30.join)(MEMORY_DIR, "domain", "project-overview.md");
23053
23726
  if ((0, import_node_fs40.existsSync)(overviewPath) && !opts.force) {
23054
23727
  return { created: 0, failed: 0, skipped: "already_seeded", candidates: candidates2 };
23055
23728
  }
@@ -23092,8 +23765,8 @@ async function runSeed(opts) {
23092
23765
  continue;
23093
23766
  }
23094
23767
  try {
23095
- await (0, import_promises14.mkdir)((0, import_node_path29.dirname)(targetPath), { recursive: true });
23096
- await (0, import_promises14.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
23768
+ await (0, import_promises15.mkdir)((0, import_node_path30.dirname)(targetPath), { recursive: true });
23769
+ await (0, import_promises15.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
23097
23770
  created++;
23098
23771
  opts.onCreated?.(nodeId, filePathRel, c);
23099
23772
  } catch (err) {
@@ -23106,7 +23779,7 @@ async function runSeed(opts) {
23106
23779
 
23107
23780
  // src/commands/analyze/phases/08-memory-manifest.ts
23108
23781
  var import_node_fs41 = require("node:fs");
23109
- var import_node_path30 = require("node:path");
23782
+ var import_node_path31 = require("node:path");
23110
23783
  async function memoryManifest(run2) {
23111
23784
  const { globals } = run2;
23112
23785
  const { serviceUrl, token } = run2;
@@ -23116,7 +23789,7 @@ async function memoryManifest(run2) {
23116
23789
  let autoSeedNotice = null;
23117
23790
  try {
23118
23791
  await ensureMemoryDir();
23119
- const seedMarker = (0, import_node_path30.join)(VERITY_DIR, ".seeded");
23792
+ const seedMarker = (0, import_node_path31.join)(VERITY_DIR, ".seeded");
23120
23793
  const hasStandard = (0, import_node_fs41.existsSync)(STANDARD_FILE);
23121
23794
  const alreadyTried = (0, import_node_fs41.existsSync)(seedMarker);
23122
23795
  if (hasStandard && !alreadyTried) {
@@ -23234,7 +23907,7 @@ function computeIncrement(reviewedPaths, hashOf, priorAuthored) {
23234
23907
  }
23235
23908
 
23236
23909
  // src/commands/analyze/phases/10-working-memory.ts
23237
- var import_node_path31 = require("node:path");
23910
+ var import_node_path32 = require("node:path");
23238
23911
  async function workingMemory(run2) {
23239
23912
  const { opts } = run2;
23240
23913
  const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run2;
@@ -23246,7 +23919,7 @@ async function workingMemory(run2) {
23246
23919
  const priorState = foldForMarks(memorySession.d);
23247
23920
  incrementReport = computeIncrement(
23248
23921
  allForReview,
23249
- (p) => fileHash((0, import_node_path31.join)(repoRoot(), p)),
23922
+ (p) => fileHash((0, import_node_path32.join)(repoRoot(), p)),
23250
23923
  priorState.authored_all.map((a) => ({
23251
23924
  path: a.path,
23252
23925
  hash_at_last_verdict: a.hash_at_last_verdict
@@ -23383,15 +24056,6 @@ function parseAutonomousEnv(raw) {
23383
24056
  if (v === "0" || v === "false" || v === "off" || v === "no") return false;
23384
24057
  return true;
23385
24058
  }
23386
- function resolveRunMode(inputs = {}) {
23387
- if (inputs.autonomousFlag === true) return "autonomous";
23388
- if (inputs.autonomousFlag === false) return "interactive";
23389
- const env = inputs.env ?? process.env;
23390
- const envDecision = parseAutonomousEnv(env.VERITY_AUTONOMOUS);
23391
- if (envDecision !== void 0) return envDecision ? "autonomous" : "interactive";
23392
- const isTTY = inputs.isTTY ?? Boolean(process.stdin?.isTTY);
23393
- return isTTY ? "interactive" : "autonomous";
23394
- }
23395
24059
  function isExplicitlyAutonomous(env = process.env) {
23396
24060
  return parseAutonomousEnv(env.VERITY_AUTONOMOUS) === true || parseAutonomousEnv(env.CI) === true || parseAutonomousEnv(env.GITHUB_ACTIONS) === true;
23397
24061
  }
@@ -23811,7 +24475,7 @@ async function transmit(run2) {
23811
24475
 
23812
24476
  // src/commands/analyze/phases/13-reconcile.ts
23813
24477
  var import_node_fs44 = require("node:fs");
23814
- var import_node_path32 = require("node:path");
24478
+ var import_node_path33 = require("node:path");
23815
24479
  async function reconcile(run2) {
23816
24480
  const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run2;
23817
24481
  const sentPaths = codeDelta.files.map((f) => f.path);
@@ -23840,7 +24504,7 @@ async function reconcile(run2) {
23840
24504
  const st = foldDossier(memorySession.d);
23841
24505
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
23842
24506
  try {
23843
- const src = (0, import_node_fs44.readFileSync)((0, import_node_path32.join)(repoRoot(), file), "utf8").split("\n");
24507
+ const src = (0, import_node_fs44.readFileSync)((0, import_node_path33.join)(repoRoot(), file), "utf8").split("\n");
23844
24508
  const at = src[line - 1];
23845
24509
  return at === void 0 ? null : lineSha(at);
23846
24510
  } catch {
@@ -24083,23 +24747,163 @@ function mayBlock(input) {
24083
24747
  if (input.attempts > input.maxIterations) {
24084
24748
  return { block: false, release: "same-problem-cap" };
24085
24749
  }
24086
- if (input.blocks > ceiling) {
24087
- return { block: false, release: "block-ceiling" };
24750
+ if (input.blocks > ceiling) {
24751
+ return { block: false, release: "block-ceiling" };
24752
+ }
24753
+ return { block: true, release: null };
24754
+ }
24755
+ function describeRelease(release, input) {
24756
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
24757
+ const open = input.findingCount > 0 ? `${input.findingCount} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.` : "Human review required before deploying.";
24758
+ switch (release) {
24759
+ case "no-code-reviewed":
24760
+ return `Verity: WARN \u2014 NOT BLOCKING: no code was reviewed on this turn, so there is nothing here to fix. Reported as advice instead. ${open}`;
24761
+ case "nothing-moved":
24762
+ return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
24763
+ case "same-problem-cap":
24764
+ return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
24765
+ case "block-ceiling":
24766
+ return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
24767
+ }
24768
+ }
24769
+
24770
+ // src/lib/memory-pull.ts
24771
+ var import_node_child_process14 = require("node:child_process");
24772
+ var import_promises16 = require("node:fs/promises");
24773
+ var pullStateFile = () => projectPath(`${VERITY_DIR}/.memory-pull-state.json`);
24774
+ var PAGE_SIZE = 200;
24775
+ var MAX_PAGES = 500;
24776
+ var memoryIsTracked = () => committedMemoryFiles().length > 0;
24777
+ async function pullRepoMemory(opts) {
24778
+ if (memoryIsTracked()) return { ok: true, status: "tracked", received: 0, written: 0 };
24779
+ const state = await readPullState();
24780
+ const known = opts.force ? null : state.version;
24781
+ let treePaths;
24782
+ try {
24783
+ treePaths = listTrackedFiles();
24784
+ } catch {
24785
+ treePaths = void 0;
24786
+ }
24787
+ const served = /* @__PURE__ */ new Map();
24788
+ let received = 0;
24789
+ let written = 0;
24790
+ let version = null;
24791
+ let cursor = null;
24792
+ let pages = 0;
24793
+ const remember = (complete) => writePullState({
24794
+ version: complete ? version : null,
24795
+ // A complete pull saw every file the server holds, so its ledger replaces the
24796
+ // old one (a path that stopped arriving was archived). A partial one only adds.
24797
+ served: complete ? served : new Map([...state.served, ...served])
24798
+ });
24799
+ do {
24800
+ const params = new URLSearchParams({ limit: String(PAGE_SIZE) });
24801
+ if (cursor) params.set("cursor", cursor);
24802
+ else if (known) params.set("if_version", known);
24803
+ const res = await apiRequest({
24804
+ method: "GET",
24805
+ path: `/memory/files?${params.toString()}`,
24806
+ serviceUrl: opts.serviceUrl,
24807
+ token: opts.token,
24808
+ verbose: opts.verbose,
24809
+ timeout: opts.timeoutMs ?? 3e4,
24810
+ cmd: "memory-pull"
24811
+ });
24812
+ if (!res.ok) {
24813
+ if (pages > 0) await remember(false);
24814
+ return { ok: false, error: res.error, category: res.category };
24815
+ }
24816
+ const page = res.data;
24817
+ if (page.unchanged) return { ok: true, status: "unchanged", received: 0, written: 0 };
24818
+ if (pages === 0) version = page.version;
24819
+ else if (page.version !== version) version = null;
24820
+ const files = (page.files ?? []).map((f) => ({
24821
+ path: f.path,
24822
+ content: f.content,
24823
+ node_id: f.node_id,
24824
+ op: "create"
24825
+ }));
24826
+ received += files.length;
24827
+ try {
24828
+ written += await applyMemoryWrites(files, {
24829
+ treePaths,
24830
+ baseline: "written",
24831
+ lastServed: state.served,
24832
+ served,
24833
+ claudeMdPointer: false
24834
+ });
24835
+ } catch (err) {
24836
+ await remember(false);
24837
+ throw err;
24838
+ }
24839
+ cursor = page.next_cursor;
24840
+ pages++;
24841
+ } while (cursor && pages < MAX_PAGES);
24842
+ await remember(!cursor);
24843
+ return { ok: true, status: "pulled", received, written };
24844
+ }
24845
+ async function readServedLedger() {
24846
+ return (await readPullState()).served;
24847
+ }
24848
+ async function recordServedFiles(served) {
24849
+ if (served.size === 0) return;
24850
+ const state = await readPullState();
24851
+ for (const [path, hash] of served) state.served.set(path, hash);
24852
+ await writePullState(state);
24853
+ }
24854
+ async function readPullState() {
24855
+ try {
24856
+ const parsed = JSON.parse(await (0, import_promises16.readFile)(pullStateFile(), "utf-8"));
24857
+ const served = /* @__PURE__ */ new Map();
24858
+ if (parsed?.served && typeof parsed.served === "object") {
24859
+ for (const [path, hash] of Object.entries(parsed.served)) {
24860
+ if (typeof hash === "string") served.set(path, hash);
24861
+ }
24862
+ }
24863
+ return { version: typeof parsed?.version === "string" ? parsed.version : null, served };
24864
+ } catch {
24865
+ return { version: null, served: /* @__PURE__ */ new Map() };
24866
+ }
24867
+ }
24868
+ async function writePullState(state) {
24869
+ try {
24870
+ await (0, import_promises16.mkdir)(projectPath(VERITY_DIR), { recursive: true });
24871
+ await (0, import_promises16.writeFile)(pullStateFile(), JSON.stringify({
24872
+ ...state.version ? { version: state.version } : {},
24873
+ pulled_at: (/* @__PURE__ */ new Date()).toISOString(),
24874
+ served: Object.fromEntries(state.served)
24875
+ }) + "\n");
24876
+ } catch {
24088
24877
  }
24089
- return { block: true, release: null };
24090
24878
  }
24091
- function describeRelease(release, input) {
24092
- const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
24093
- const open = input.findingCount > 0 ? `${input.findingCount} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.` : "Human review required before deploying.";
24094
- switch (release) {
24095
- case "no-code-reviewed":
24096
- return `Verity: WARN \u2014 NOT BLOCKING: no code was reviewed on this turn, so there is nothing here to fix. Reported as advice instead. ${open}`;
24097
- case "nothing-moved":
24098
- return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
24099
- case "same-problem-cap":
24100
- return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
24101
- case "block-ceiling":
24102
- return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
24879
+ function pullsAtSessionStart(auth2, source) {
24880
+ if (source === "compact") return false;
24881
+ if (auth2.source === "env" || auth2.source === "flag") return true;
24882
+ return auth2.userId != null;
24883
+ }
24884
+ function spawnBackgroundPull(deps = {}) {
24885
+ const d = {
24886
+ spawn: import_node_child_process14.spawn,
24887
+ execPath: process.execPath,
24888
+ script: process.argv[1],
24889
+ cwd: process.cwd(),
24890
+ env: process.env,
24891
+ ...deps
24892
+ };
24893
+ if (d.env.VERITY_NO_BACKGROUND_PULL === "1" || !d.script) return false;
24894
+ try {
24895
+ const child = d.spawn(d.execPath, [d.script, "memory", "pull", "--quiet"], {
24896
+ cwd: d.cwd,
24897
+ env: d.env,
24898
+ detached: true,
24899
+ stdio: "ignore"
24900
+ });
24901
+ child.on("error", () => {
24902
+ });
24903
+ child.unref();
24904
+ return true;
24905
+ } catch {
24906
+ return false;
24103
24907
  }
24104
24908
  }
24105
24909
 
@@ -24201,7 +25005,9 @@ async function render(run2) {
24201
25005
  if (memoryWritesRaw && memoryWritesRaw.length > 0) {
24202
25006
  try {
24203
25007
  const treePaths = listTrackedFiles();
24204
- await applyMemoryWrites(memoryWritesRaw, { treePaths });
25008
+ const served = /* @__PURE__ */ new Map();
25009
+ await applyMemoryWrites(memoryWritesRaw, { treePaths, lastServed: await readServedLedger(), served });
25010
+ await recordServedFiles(served);
24205
25011
  } catch (err) {
24206
25012
  process.stderr.write(`verity: memory sync write failed: ${err.message}
24207
25013
  `);
@@ -24530,7 +25336,7 @@ var import_node_fs47 = require("node:fs");
24530
25336
 
24531
25337
  // src/lib/project-skills.ts
24532
25338
  var import_node_fs46 = require("node:fs");
24533
- var import_node_path33 = require("node:path");
25339
+ var import_node_path34 = require("node:path");
24534
25340
  var PROJECT_SKILL_NAMES = [
24535
25341
  "verity-setup",
24536
25342
  "verity-analyze",
@@ -24556,13 +25362,13 @@ var ALL = [...PROJECT_SKILL_NAMES, ...LEGACY_SKILL_NAMES];
24556
25362
  function staleProjectSkills() {
24557
25363
  const root = projectPath(".claude/skills");
24558
25364
  if (!(0, import_node_fs46.existsSync)(root)) return [];
24559
- return ALL.filter((name) => (0, import_node_fs46.existsSync)((0, import_node_path33.join)(root, name)));
25365
+ return ALL.filter((name) => (0, import_node_fs46.existsSync)((0, import_node_path34.join)(root, name)));
24560
25366
  }
24561
25367
  function removeProjectSkills() {
24562
25368
  const root = projectPath(".claude/skills");
24563
25369
  const removed = [];
24564
25370
  for (const name of staleProjectSkills()) {
24565
- (0, import_node_fs46.rmSync)((0, import_node_path33.join)(root, name), { recursive: true, force: true });
25371
+ (0, import_node_fs46.rmSync)((0, import_node_path34.join)(root, name), { recursive: true, force: true });
24566
25372
  removed.push(name);
24567
25373
  }
24568
25374
  return removed;
@@ -24688,6 +25494,7 @@ function registerBaselineCommands(program2) {
24688
25494
  }
24689
25495
  const authForScope = await resolveToken(program2.opts().token);
24690
25496
  const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
25497
+ if (authForScope.ok && pullsAtSessionStart(authForScope.data, source)) spawnBackgroundPull();
24691
25498
  const scopeSession = session;
24692
25499
  const result = captureBaseline({ sessionId: sessionScopeKey(scopeToken, scopeSession), source });
24693
25500
  logEvent("baseline_capture", {
@@ -24750,22 +25557,45 @@ async function runReview(opts, globals) {
24750
25557
  const changedFiles = opts.changed ? opts.changed.split(",").map((f) => f.trim()).filter(Boolean) : allFiles;
24751
25558
  const analyzable = filterAnalyzable(allFiles);
24752
25559
  const securityFiles = filterSecurity(allFiles);
24753
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs48.existsSync)(f) || resolveFile(f) !== null);
24754
- const staticResults = runCodacyAnalysisIfAvailable(scannable);
25560
+ let staticResults;
25561
+ if (isCodacyAvailable()) {
25562
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs48.existsSync)(f) || resolveFile(f) !== null);
25563
+ staticResults = runCodacyAnalysis(scannable);
25564
+ } else {
25565
+ staticResults = {
25566
+ tool: "@codacy/analysis-cli",
25567
+ findings: [],
25568
+ summary: { total_findings: 0, by_severity: {}, tools_run: [] }
25569
+ };
25570
+ }
24755
25571
  const codeDelta = collectCodeDelta(allFiles);
24756
25572
  const tokenResult = await resolveToken(globals.token);
24757
25573
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
24758
25574
  if (!tokenResult.ok || !urlResult.ok) {
24759
- const staticFailure = staticResults.summary.failure;
24760
25575
  printJsonCompact({
24761
- gate_decision: "WARN",
24762
- systemMessage: staticFailure ? `Verity: not authenticated, and local static analysis did not run (${staticFailure.kind}) \u2014 nothing was examined. Run \`verity init\` to authenticate and unlock the full review.` : "Verity: not authenticated \u2014 showing local static results only (no deep review, no upload). Run `verity init` to authenticate and unlock the full review.",
25576
+ gate_decision: "PASS",
25577
+ systemMessage: "Verity: not authenticated \u2014 showing local static results only (no deep review, no upload). Run `verity init` to authenticate and unlock the full review.",
24763
25578
  unauthenticated: true,
24764
25579
  static_results: staticResults
24765
25580
  });
24766
25581
  process.exit(0);
24767
25582
  }
24768
- const specs = opts.specs ? readSpecFiles(opts.specs, repoRoot()) : discoverSpecs();
25583
+ let specs;
25584
+ if (opts.specs) {
25585
+ const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
25586
+ specs = [];
25587
+ for (const p of specPaths) {
25588
+ if (!(0, import_node_fs48.existsSync)(p)) continue;
25589
+ try {
25590
+ const { readFileSync: readFileSync26 } = await import("node:fs");
25591
+ const content = readFileSync26(p, "utf-8");
25592
+ specs.push({ path: p, content: content.slice(0, 10240) });
25593
+ } catch {
25594
+ }
25595
+ }
25596
+ } else {
25597
+ specs = discoverSpecs();
25598
+ }
24769
25599
  const plans = discoverPlans();
24770
25600
  const requestBody = {
24771
25601
  static_results: staticResults,
@@ -24798,10 +25628,9 @@ async function runReview(opts, globals) {
24798
25628
  });
24799
25629
  if (!result.ok) {
24800
25630
  printError(`Service error: ${result.error}`);
24801
- const staticFailure = staticResults.summary.failure;
24802
25631
  printJsonCompact({
24803
- gate_decision: "WARN",
24804
- systemMessage: staticFailure ? `Verity: service unavailable, and local static analysis did not run (${staticFailure.kind}) \u2014 nothing was examined` : "Verity: service unavailable \u2014 showing local static results only",
25632
+ gate_decision: "PASS",
25633
+ systemMessage: "Verity: Service unavailable \u2014 showing static results only",
24805
25634
  offline: true,
24806
25635
  static_results: staticResults
24807
25636
  });
@@ -24817,14 +25646,387 @@ async function runReview(opts, globals) {
24817
25646
 
24818
25647
  // src/commands/guard.ts
24819
25648
  var import_node_fs49 = require("node:fs");
24820
- var import_node_path34 = require("node:path");
25649
+ var import_node_path35 = require("node:path");
25650
+
25651
+ // src/lib/terminal-text.ts
25652
+ var CONTROL = /[\x00-\x08\x0b-\x1f\x7f-\x9f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g;
25653
+ function terminalText(value, max = 4e3) {
25654
+ if (typeof value !== "string") return "";
25655
+ const clean2 = value.replace(CONTROL, "");
25656
+ return clean2.length > max ? `${clean2.slice(0, max - 1)}\u2026` : clean2;
25657
+ }
25658
+ function terminalUrl(value) {
25659
+ const clean2 = terminalText(value, 2e3);
25660
+ return /^https?:\/\/\S+$/.test(clean2) ? clean2 : "";
25661
+ }
25662
+
25663
+ // src/lib/agent-driver.ts
25664
+ var AGENT_DEADLINE_MS = {
25665
+ "pre-commit": 9e4,
25666
+ "pre-push": 18e4
25667
+ };
25668
+ var MIN_AGENT_START_MS = 25e3;
25669
+ var MIN_TURN_MS = 12e3;
25670
+ var FINISH_TIMEOUT_MS = 15e3;
25671
+ var CANCEL_TIMEOUT_MS = 3e3;
25672
+ var MAX_TURNS = 16;
25673
+ var ONE_SHOT_FINDING_FIELDS = ["severity", "category", "file", "line", "title", "description", "scope", "pattern_id"];
25674
+ function summarizeFindings(response) {
25675
+ const findings = Array.isArray(response.findings) ? response.findings : [];
25676
+ return findings.slice(0, 40).map((f) => {
25677
+ const out = {};
25678
+ const finding = f ?? {};
25679
+ for (const key of ONE_SHOT_FINDING_FIELDS) if (finding[key] !== void 0) out[key] = finding[key];
25680
+ return out;
25681
+ });
25682
+ }
25683
+ function finishOneShot(settled) {
25684
+ if (!settled) return { status: "pending" };
25685
+ if (!settled.ok) return { status: "failed" };
25686
+ const r = settled.response;
25687
+ return {
25688
+ status: "arrived",
25689
+ ...typeof r.agent_attestation === "string" ? { attestation: r.agent_attestation } : {},
25690
+ ...typeof r.run_id === "string" ? { run_id: r.run_id } : {},
25691
+ ...typeof r.gate_decision === "string" ? { gate_decision: r.gate_decision } : {}
25692
+ };
25693
+ }
25694
+ async function runAgentReview(options) {
25695
+ const now = options.now ?? Date.now;
25696
+ const remaining = () => options.deadlineMs - (now() - options.startedAt);
25697
+ let settled = null;
25698
+ const oneShotDone = options.oneShot.then(
25699
+ (outcome) => {
25700
+ settled = outcome;
25701
+ },
25702
+ () => {
25703
+ settled = { ok: false };
25704
+ }
25705
+ );
25706
+ if (remaining() < MIN_AGENT_START_MS) return { kind: "skipped", reason: "not enough time left after prep" };
25707
+ const started = await options.http.post(
25708
+ "/agent-review/start",
25709
+ { ...options.start, remaining_ms: remaining() },
25710
+ Math.max(1, remaining())
25711
+ );
25712
+ if (!started.ok) return { kind: "failed", reason: `start: ${started.error}` };
25713
+ if (!started.data.enabled) return { kind: "disabled", reason: started.data.reason };
25714
+ let state = started.data.state;
25715
+ let calls = started.data.calls ?? [];
25716
+ let done = started.data.done;
25717
+ let sentOneShot = "pending";
25718
+ const cancel = async () => {
25719
+ await options.http.post("/agent-review/cancel", { state }, CANCEL_TIMEOUT_MS);
25720
+ };
25721
+ for (let turn = 0; !done; turn++) {
25722
+ if (turn >= MAX_TURNS || remaining() < MIN_TURN_MS) break;
25723
+ const results = await options.executeTools(calls);
25724
+ let oneShot;
25725
+ const current = settled;
25726
+ if (current && sentOneShot === "pending") {
25727
+ oneShot = current.ok ? { status: "arrived", findings: summarizeFindings(current.response) } : { status: "failed" };
25728
+ sentOneShot = oneShot.status;
25729
+ }
25730
+ const turnRequest = { state, remaining_ms: remaining(), results, ...oneShot ? { one_shot: oneShot } : {} };
25731
+ const next = await options.http.post("/agent-review/turn", turnRequest, Math.max(1, remaining()));
25732
+ if (!next.ok) {
25733
+ await cancel();
25734
+ return { kind: "failed", reason: `turn: ${next.error}` };
25735
+ }
25736
+ state = next.data.state;
25737
+ calls = next.data.calls ?? [];
25738
+ done = next.data.done;
25739
+ }
25740
+ if (!settled) {
25741
+ const wait = Math.max(0, remaining() - FINISH_TIMEOUT_MS);
25742
+ await Promise.race([oneShotDone, new Promise((resolve6) => setTimeout(resolve6, wait))]);
25743
+ }
25744
+ const finished = await options.http.post(
25745
+ "/agent-review/finish",
25746
+ { state, session_id: options.sessionId, one_shot: finishOneShot(settled) },
25747
+ FINISH_TIMEOUT_MS
25748
+ );
25749
+ if (!finished.ok) {
25750
+ await cancel();
25751
+ return { kind: "failed", reason: `finish: ${finished.error}` };
25752
+ }
25753
+ return { kind: "finished", response: finished.data };
25754
+ }
25755
+
25756
+ // src/lib/agent-tools.ts
25757
+ var TOOL_LIMITS = {
25758
+ outputChars: 16e3,
25759
+ readLines: 400,
25760
+ blameLines: 200,
25761
+ searchHits: 50,
25762
+ searchLineChars: 300,
25763
+ listEntries: 200,
25764
+ logCommits: 8,
25765
+ textChars: 200,
25766
+ pathChars: 400,
25767
+ fileBytes: 4 * 1024 * 1024,
25768
+ gitTimeoutMs: 2e3,
25769
+ diffTimeoutMs: 1e4,
25770
+ concurrency: 4
25771
+ };
25772
+ var DENIED_PATHS = [
25773
+ /(^|\/)\.env(\.[^/]*)?$/i,
25774
+ /\.(pem|key|p12|pfx|jks|keystore|asc|gpg|kdbx)$/i,
25775
+ /(^|\/)id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/i,
25776
+ /(^|\/)\.(npmrc|netrc|pypirc|pgpass|git-credentials)$/i,
25777
+ /(^|\/)(credentials|secrets?)(\.[^/]*)?$/i,
25778
+ /(^|\/)\.(ssh|aws|gnupg|docker)(\/|$)/i,
25779
+ /(^|\/)\.git(\/|$)/
25780
+ ];
25781
+ function isDeniedPath(path) {
25782
+ return DENIED_PATHS.some((pattern) => pattern.test(path)) || isVerityOwnedPath(path);
25783
+ }
25784
+ function validateRepoPath(value, { allowRoot = false } = {}) {
25785
+ if (typeof value !== "string") return { ok: false, error: "path must be a string" };
25786
+ let path = value.replace(/\\/g, "/").replace(/^\.\/+/, "");
25787
+ if (allowRoot && (path === "" || path === ".")) return { ok: true, value: "" };
25788
+ path = path.replace(/\/+$/, "");
25789
+ if (!path || path.length > TOOL_LIMITS.pathChars) return { ok: false, error: "path is empty or too long" };
25790
+ if (/[-]/.test(path)) return { ok: false, error: "path contains control characters" };
25791
+ if (path.startsWith("/") || /^[A-Za-z]:/.test(path)) return { ok: false, error: "path must be relative to the repository root" };
25792
+ if (path.split("/").some((segment) => segment === "..")) return { ok: false, error: "path must not leave the repository" };
25793
+ if (path.startsWith("-") || path.startsWith(":")) return { ok: false, error: 'path must not start with "-" or ":"' };
25794
+ if (isDeniedPath(path)) return { ok: false, error: "reading this path is not allowed" };
25795
+ return { ok: true, value: path };
25796
+ }
25797
+ function lineNumber(value, fallback) {
25798
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : fallback;
25799
+ }
25800
+ function capOutput(text) {
25801
+ return text.length > TOOL_LIMITS.outputChars ? `${text.slice(0, TOOL_LIMITS.outputChars)}
25802
+ [truncated at ${TOOL_LIMITS.outputChars} characters]` : text;
25803
+ }
25804
+ function git(ctx, args, opts = {}) {
25805
+ return runGit(ctx.root, args, { timeoutMs: opts.timeoutMs ?? TOOL_LIMITS.gitTimeoutMs, maxBuffer: opts.maxBuffer ?? TOOL_LIMITS.fileBytes });
25806
+ }
25807
+ function gitFailure(res, what) {
25808
+ if (res.error === "timeout") return `${what} timed out`;
25809
+ if (res.error === "too_large") return `${what} is too large`;
25810
+ const detail = res.stderr.trim().split("\n")[0]?.slice(0, 160);
25811
+ return detail ? `${what} failed: ${detail}` : `${what} failed`;
25812
+ }
25813
+ var INDEX = Symbol("index");
25814
+ function changeRevision(range) {
25815
+ return range.kind === "push" ? range.head : INDEX;
25816
+ }
25817
+ function baseRevision(range) {
25818
+ return range.kind === "push" ? range.base : "HEAD";
25819
+ }
25820
+ function objectSpec(revision, path) {
25821
+ return revision === INDEX ? `:${path}` : `${revision}:${path}`;
25822
+ }
25823
+ async function readBlob(ctx, revision, path) {
25824
+ const res = await git(ctx, ["cat-file", "blob", objectSpec(revision, path)]);
25825
+ if (!res.ok) {
25826
+ if (res.error === "failed") return { ok: false, error: `${path} is not a file in ${revision === INDEX ? "the staged version" : "that version"}` };
25827
+ return { ok: false, error: gitFailure(res, `reading ${path}`) };
25828
+ }
25829
+ if (res.stdout.slice(0, 8e3).includes("\0")) return { ok: false, error: `${path} is a binary file` };
25830
+ return { ok: true, value: res.stdout };
25831
+ }
25832
+ function numberedRange(content, startLine, endLine, maxLines) {
25833
+ const lines = content.split("\n");
25834
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
25835
+ const total = lines.length;
25836
+ const from = Math.min(Math.max(1, startLine), Math.max(1, total));
25837
+ const to = Math.min(total, Math.max(from, endLine), from + maxLines - 1);
25838
+ const width = String(to).length;
25839
+ const body = lines.slice(from - 1, to).map((line, i) => `${String(from + i).padStart(width, " ")}| ${line}`).join("\n");
25840
+ return `lines ${total === 0 ? 0 : from}-${to} of ${total}
25841
+ ${body}`;
25842
+ }
25843
+ async function resolveRevision(ctx, value) {
25844
+ if (value === "head") return { ok: true, value: changeRevision(ctx.range) };
25845
+ if (value === "base") {
25846
+ const base = baseRevision(ctx.range);
25847
+ return base ? { ok: true, value: base } : { ok: false, error: "this change has no base version" };
25848
+ }
25849
+ if (typeof value === "string" && /^[0-9a-f]{7,40}$/.test(value)) {
25850
+ const res = await git(ctx, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${value}^{commit}`]);
25851
+ if (res.ok && res.stdout.trim()) return { ok: true, value: res.stdout.trim() };
25852
+ return { ok: false, error: `unknown commit ${value}` };
25853
+ }
25854
+ return { ok: false, error: 'revision must be "base", "head", or a commit hash from git_log' };
25855
+ }
25856
+ var readFileTool = async (ctx, args) => {
25857
+ const path = validateRepoPath(args.path);
25858
+ if (!path.ok) return path;
25859
+ const blob = await readBlob(ctx, changeRevision(ctx.range), path.value);
25860
+ if (!blob.ok) return blob;
25861
+ const start = lineNumber(args.start_line, 1);
25862
+ const end = lineNumber(args.end_line, start + TOOL_LIMITS.readLines - 1);
25863
+ return { ok: true, value: `${path.value} (${ctx.range.kind === "push" ? "pushed version" : "staged version"}), ${numberedRange(blob.value, start, end, TOOL_LIMITS.readLines)}` };
25864
+ };
25865
+ var gitShowTool = async (ctx, args) => {
25866
+ const path = validateRepoPath(args.path);
25867
+ if (!path.ok) return path;
25868
+ const revision = await resolveRevision(ctx, args.revision);
25869
+ if (!revision.ok) return revision;
25870
+ const blob = await readBlob(ctx, revision.value, path.value);
25871
+ if (!blob.ok) return blob;
25872
+ const start = lineNumber(args.start_line, 1);
25873
+ const end = lineNumber(args.end_line, start + TOOL_LIMITS.readLines - 1);
25874
+ return { ok: true, value: `${path.value} at ${String(args.revision)}, ${numberedRange(blob.value, start, end, TOOL_LIMITS.readLines)}` };
25875
+ };
25876
+ var gitDiffTool = async (ctx, args) => {
25877
+ const path = validateRepoPath(args.path);
25878
+ if (!path.ok) return path;
25879
+ const range = ctx.range;
25880
+ let diffArgs;
25881
+ if (range.kind === "push") {
25882
+ if (!range.base) return { ok: false, error: "this push has no base to diff against" };
25883
+ diffArgs = ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", range.base, range.head];
25884
+ } else {
25885
+ diffArgs = ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", "--cached"];
25886
+ }
25887
+ const res = await git(ctx, ["--literal-pathspecs", ...diffArgs, "--", path.value]);
25888
+ if (!res.ok) return { ok: false, error: gitFailure(res, "diff") };
25889
+ return { ok: true, value: res.stdout.trim() ? capOutput(res.stdout) : `no changes to ${path.value} in the change under review` };
25890
+ };
25891
+ var gitLogTool = async (ctx, args) => {
25892
+ const path = validateRepoPath(args.path);
25893
+ if (!path.ok) return path;
25894
+ const count = Math.min(TOOL_LIMITS.logCommits, lineNumber(args.max_count, TOOL_LIMITS.logCommits));
25895
+ const revision = ctx.range.kind === "push" ? ctx.range.head : "HEAD";
25896
+ const res = await git(ctx, [
25897
+ "--literal-pathspecs",
25898
+ "log",
25899
+ "--no-show-signature",
25900
+ "--no-color",
25901
+ "-n",
25902
+ String(count),
25903
+ "--date=short",
25904
+ "--format=%h %ad %s",
25905
+ revision,
25906
+ "--",
25907
+ path.value
25908
+ ]);
25909
+ if (!res.ok) return { ok: false, error: gitFailure(res, "log") };
25910
+ return { ok: true, value: res.stdout.trim() ? capOutput(res.stdout.trim()) : `no commits touch ${path.value}` };
25911
+ };
25912
+ var gitBlameTool = async (ctx, args) => {
25913
+ const path = validateRepoPath(args.path);
25914
+ if (!path.ok) return path;
25915
+ const start = lineNumber(args.start_line, 1);
25916
+ const end = Math.min(lineNumber(args.end_line, start), start + TOOL_LIMITS.blameLines - 1);
25917
+ const revision = ctx.range.kind === "push" ? ctx.range.head : "HEAD";
25918
+ const res = await git(ctx, ["blame", "--no-textconv", "-s", "-L", `${start},${Math.max(start, end)}`, revision, "--", path.value]);
25919
+ if (!res.ok) return { ok: false, error: gitFailure(res, "blame") };
25920
+ return { ok: true, value: capOutput(res.stdout.trimEnd()) };
25921
+ };
25922
+ var listFilesTool = async (ctx, args) => {
25923
+ const dir = validateRepoPath(args.dir, { allowRoot: true });
25924
+ if (!dir.ok) return dir;
25925
+ const glob = typeof args.glob === "string" && args.glob.length > 0 && args.glob.length <= 200 ? args.glob : null;
25926
+ const scope2 = dir.value || ".";
25927
+ const res = ctx.range.kind === "push" ? await git(ctx, ["--literal-pathspecs", "ls-tree", "-r", "--name-only", "-z", ctx.range.head, "--", scope2]) : await git(ctx, ["--literal-pathspecs", "ls-files", "-z", "--cached", "--", scope2]);
25928
+ if (!res.ok) return { ok: false, error: gitFailure(res, "listing") };
25929
+ const paths = res.stdout.split("\0").filter((p) => p && !isDeniedPath(p) && (!glob || globMatch2(glob, p)));
25930
+ const shown = paths.slice(0, TOOL_LIMITS.listEntries);
25931
+ const more = paths.length - shown.length;
25932
+ return { ok: true, value: shown.length ? `${shown.join("\n")}${more > 0 ? `
25933
+ \u2026 ${more} more` : ""}` : "no matching files" };
25934
+ };
25935
+ var searchTool = async (ctx, args) => {
25936
+ const text = args.text;
25937
+ if (typeof text !== "string" || text.length === 0 || text.length > TOOL_LIMITS.textChars || /[\n\r]/.test(text)) {
25938
+ return { ok: false, error: `text must be 1-${TOOL_LIMITS.textChars} characters on one line` };
25939
+ }
25940
+ const glob = typeof args.path_glob === "string" && args.path_glob.length > 0 ? args.path_glob : null;
25941
+ if (glob && (glob.length > 200 || /[-]/.test(glob) || glob.startsWith("-"))) {
25942
+ return { ok: false, error: "path_glob is not a valid glob" };
25943
+ }
25944
+ const pathspec = glob ? [`:(glob)${glob}`] : [];
25945
+ const revision = ctx.range.kind === "push" ? [ctx.range.head] : ["--cached"];
25946
+ const res = await git(ctx, ["grep", "-n", "-I", "-F", "--no-color", ...revision.filter((r) => r === "--cached"), "-e", text, ...revision.filter((r) => r !== "--cached"), "--", ...pathspec]);
25947
+ if (!res.ok) {
25948
+ if (res.error === "failed" && res.stderr.trim() === "") return { ok: true, value: "no matches" };
25949
+ return { ok: false, error: gitFailure(res, "search") };
25950
+ }
25951
+ const prefix = ctx.range.kind === "push" ? `${ctx.range.head}:` : "";
25952
+ const hits = [];
25953
+ for (const raw of res.stdout.split("\n")) {
25954
+ if (!raw) continue;
25955
+ const line = prefix && raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
25956
+ const file = line.slice(0, line.indexOf(":"));
25957
+ if (isDeniedPath(file)) continue;
25958
+ hits.push(line.length > TOOL_LIMITS.searchLineChars ? `${line.slice(0, TOOL_LIMITS.searchLineChars)}\u2026` : line);
25959
+ if (hits.length >= TOOL_LIMITS.searchHits) break;
25960
+ }
25961
+ return { ok: true, value: hits.length ? hits.join("\n") : "no matches" };
25962
+ };
25963
+ var TOOLS = {
25964
+ read_file: readFileTool,
25965
+ search: searchTool,
25966
+ list_files: listFilesTool,
25967
+ git_log: gitLogTool,
25968
+ git_blame: gitBlameTool,
25969
+ git_show: gitShowTool,
25970
+ git_diff: gitDiffTool
25971
+ };
25972
+ function makeToolExecutor(ctx) {
25973
+ return async (calls) => {
25974
+ const results = new Array(calls.length);
25975
+ let next = 0;
25976
+ const worker = async () => {
25977
+ while (next < calls.length) {
25978
+ const index = next++;
25979
+ const call = calls[index];
25980
+ const tool = TOOLS[call.name];
25981
+ if (!tool) {
25982
+ results[index] = { call_id: call.call_id, ok: false, error: `unknown tool ${call.name}` };
25983
+ continue;
25984
+ }
25985
+ try {
25986
+ const outcome = await tool(ctx, call.arguments ?? {});
25987
+ results[index] = outcome.ok ? { call_id: call.call_id, ok: true, output: capOutput(outcome.value) } : { call_id: call.call_id, ok: false, error: outcome.error };
25988
+ } catch (err) {
25989
+ results[index] = { call_id: call.call_id, ok: false, error: `tool failed: ${String(err?.message ?? err).slice(0, 160)}` };
25990
+ }
25991
+ }
25992
+ };
25993
+ await Promise.all(Array.from({ length: Math.min(TOOL_LIMITS.concurrency, calls.length) }, worker));
25994
+ return results;
25995
+ };
25996
+ }
25997
+ async function buildAgentDiffs(ctx, paths, limits = { perFileChars: 24e3, totalChars: 12e4 }) {
25998
+ const valid = paths.filter((p) => validateRepoPath(p).ok);
25999
+ if (valid.length === 0 || ctx.range.kind === "nothing") return [];
26000
+ if (ctx.range.kind === "push" && !ctx.range.base) return [];
26001
+ const args = ctx.range.kind === "push" ? ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", ctx.range.base, ctx.range.head] : ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", "--cached"];
26002
+ const res = await git(ctx, ["--literal-pathspecs", ...args, "--", ...valid], { timeoutMs: TOOL_LIMITS.diffTimeoutMs, maxBuffer: 16 * 1024 * 1024 });
26003
+ if (!res.ok) return [];
26004
+ const out = [];
26005
+ let total = 0;
26006
+ for (const chunk of res.stdout.split(/^(?=diff --git )/m)) {
26007
+ if (!chunk.startsWith("diff --git ")) continue;
26008
+ const plus = /^\+\+\+ b\/(.+)$/m.exec(chunk);
26009
+ const minus = /^--- a\/(.+)$/m.exec(chunk);
26010
+ const path = plus?.[1] ?? minus?.[1] ?? /^diff --git a\/(\S+)/.exec(chunk)?.[1];
26011
+ if (!path || isDeniedPath(path)) continue;
26012
+ const room = Math.min(limits.perFileChars, limits.totalChars - total);
26013
+ if (room <= 0) break;
26014
+ const diff = chunk.length > room ? chunk.slice(0, room) : chunk;
26015
+ total += diff.length;
26016
+ out.push({ path, diff });
26017
+ }
26018
+ return out;
26019
+ }
26020
+
26021
+ // src/commands/guard.ts
26022
+ var EXCERPT_SOURCE_MAX_BYTES = 2 * 1024 * 1024;
24821
26023
  var GUARD_BLOCK_CAP = 2;
24822
- var GUARD_ITER_FILE = (0, import_node_path34.join)(VERITY_DIR, ".guard-iteration");
26024
+ var GUARD_ITER_FILE = (0, import_node_path35.join)(VERITY_DIR, ".guard-iteration");
24823
26025
  function readPreToolUseStdin() {
24824
26026
  const empty = { command: "", cwd: null, sessionId: null };
24825
- return new Promise((resolve5) => {
26027
+ return new Promise((resolve6) => {
24826
26028
  try {
24827
- if (process.stdin.isTTY) return resolve5(empty);
26029
+ if (process.stdin.isTTY) return resolve6(empty);
24828
26030
  const chunks = [];
24829
26031
  let timer;
24830
26032
  let settled = false;
@@ -24837,7 +26039,7 @@ function readPreToolUseStdin() {
24837
26039
  process.stdin.removeListener("end", onEnd);
24838
26040
  process.stdin.removeListener("error", onError);
24839
26041
  process.stdin.pause();
24840
- resolve5(value);
26042
+ resolve6(value);
24841
26043
  };
24842
26044
  const onEnd = () => {
24843
26045
  try {
@@ -24858,7 +26060,7 @@ function readPreToolUseStdin() {
24858
26060
  process.stdin.on("error", onError);
24859
26061
  process.stdin.resume();
24860
26062
  } catch {
24861
- resolve5(empty);
26063
+ resolve6(empty);
24862
26064
  }
24863
26065
  });
24864
26066
  }
@@ -24970,8 +26172,13 @@ function hasBlockingFinding(response, sentFiles) {
24970
26172
  function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
24971
26173
  const analyzable = filterAnalyzable(files);
24972
26174
  const securityFiles = filterSecurity(files);
24973
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs49.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
24974
- const staticResults = runCodacyAnalysisIfAvailable(scannable);
26175
+ let staticResults;
26176
+ if (isCodacyAvailable()) {
26177
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs49.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
26178
+ staticResults = runCodacyAnalysis(scannable);
26179
+ } else {
26180
+ staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
26181
+ }
24975
26182
  const trigger = moment === "pre-commit" ? "hook:pre-commit" : "hook:pre-push";
24976
26183
  const requestBody = {
24977
26184
  static_results: staticResults,
@@ -25034,12 +26241,103 @@ function coverageBlock(c) {
25034
26241
  }
25035
26242
  function emitAllowNotice(userMsg, agentMsg) {
25036
26243
  process.stdout.write(JSON.stringify({
25037
- systemMessage: userMsg,
25038
- hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: agentMsg }
26244
+ systemMessage: terminalText(userMsg, 8e3),
26245
+ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: terminalText(agentMsg, 4e4) }
25039
26246
  }) + "\n");
25040
26247
  process.exit(0);
25041
26248
  }
26249
+ var GATES = ["PASS", "WARN", "FAIL"];
26250
+ function worstGate(...gates) {
26251
+ const rank2 = (gate) => {
26252
+ const index = GATES.indexOf(String(gate ?? "").toUpperCase());
26253
+ return index === -1 ? 1 : index;
26254
+ };
26255
+ return GATES[Math.max(...gates.map(rank2))];
26256
+ }
26257
+ function oneShotLostOutcome(input) {
26258
+ return input.agentGate === "FAIL" && input.agentBlocking ? "block" : "allow-without-review";
26259
+ }
26260
+ function withAgentVerdict(oneShot, agent) {
26261
+ if (agent.one_shot_source === "none" && typeof oneShot.gate_decision === "string") {
26262
+ const added = agent.findings.filter((f) => f.agent_added === true);
26263
+ return {
26264
+ ...oneShot,
26265
+ gate_decision: worstGate(oneShot.gate_decision, agent.gate_decision ?? "WARN", "WARN"),
26266
+ findings: [...Array.isArray(oneShot.findings) ? oneShot.findings : [], ...added],
26267
+ agent_review: {
26268
+ ...agent.agent_review,
26269
+ gate_decision_before: oneShot.gate_decision,
26270
+ one_shot_source: "none",
26271
+ cleared: [],
26272
+ downgraded: []
26273
+ }
26274
+ };
26275
+ }
26276
+ return {
26277
+ ...oneShot,
26278
+ gate_decision: agent.gate_decision,
26279
+ findings: agent.findings,
26280
+ agent_review: {
26281
+ ...agent.agent_review,
26282
+ gate_decision_before: agent.gate_decision_before,
26283
+ one_shot_source: agent.one_shot_source,
26284
+ cleared: agent.cleared,
26285
+ downgraded: agent.downgraded ?? []
26286
+ }
26287
+ };
26288
+ }
26289
+ function describeAgentOutcome(outcome) {
26290
+ if (!outcome || outcome.kind === "disabled") return null;
26291
+ if (outcome.kind !== "finished") return `Agent review: not used (${outcome.reason})`;
26292
+ const { agent_review: r, gate_decision: after, gate_decision_before: before } = outcome.response;
26293
+ const checks = Number(r.code_runtime_checks ?? 0) + Number(r.container_checks ?? 0);
26294
+ const verdict = before && after && before !== after ? `, verdict changed from ${before} to ${after}` : "";
26295
+ return `Agent review: ${r.outcome}, ${r.turns} turn(s), ${r.local_calls} read(s), ${checks} code check(s), ${r.cleared} cleared, ${Number(r.downgraded ?? 0)} downgraded, ${r.added} added${verdict}`;
26296
+ }
26297
+ function startAgentReview(input) {
26298
+ if (process.env.VERITY_GIT_MOMENT_AGENT === "off") return null;
26299
+ const ctx = { root: input.root, range: input.range };
26300
+ const http = {
26301
+ async post(path, body, timeoutMs) {
26302
+ const res = await apiRequest({
26303
+ method: "POST",
26304
+ path,
26305
+ serviceUrl: input.serviceUrl,
26306
+ token: input.token,
26307
+ body,
26308
+ timeout: timeoutMs,
26309
+ cmd: "guard-agent",
26310
+ verbose: input.verbose,
26311
+ encodeBody: true
26312
+ });
26313
+ return res.ok ? { ok: true, data: res.data } : { ok: false, error: res.error };
26314
+ }
26315
+ };
26316
+ const sentPaths = input.codeDelta.files.map((f) => f.path);
26317
+ const run2 = async () => runAgentReview({
26318
+ http,
26319
+ executeTools: makeToolExecutor(ctx),
26320
+ startedAt: input.hookStartedAt,
26321
+ deadlineMs: AGENT_DEADLINE_MS[input.moment],
26322
+ start: {
26323
+ moment: input.moment,
26324
+ iteration: input.iteration,
26325
+ session_id: input.sessionId,
26326
+ stated_intent: input.statedIntent,
26327
+ changed_files: input.files,
26328
+ sent_paths: sentPaths,
26329
+ diffs: await buildAgentDiffs(ctx, sentPaths),
26330
+ repo_context: input.requestBody.repo_context,
26331
+ static_results: input.requestBody.static_results,
26332
+ one_shot_chars: input.codeDelta.files.reduce((n, f) => n + f.content.length, 0)
26333
+ },
26334
+ oneShot: input.oneShot.then((r) => r.ok ? { ok: true, response: r.data } : { ok: false }),
26335
+ sessionId: input.sessionId
26336
+ });
26337
+ return run2().catch((err) => ({ kind: "failed", reason: String(err?.message ?? err) }));
26338
+ }
25042
26339
  async function runGuard(opts, globals) {
26340
+ const hookStartedAt = Date.now();
25043
26341
  const on = resolveGuardMoments(opts.on);
25044
26342
  if (on.length === 0) process.exit(0);
25045
26343
  const { command, cwd, sessionId } = await readPreToolUseStdin();
@@ -25111,12 +26409,12 @@ async function runGuard(opts, globals) {
25111
26409
  cwd: frame.worktreeRoot ?? process.cwd()
25112
26410
  });
25113
26411
  upgradeToExcerpts(repoContext2, {
26412
+ // Rooted, no-follow read from the validated descriptor (safe-read.ts):
26413
+ // site paths come from a search, and a site under a symlinked directory
26414
+ // must not pull a file from outside the tree into the request.
25114
26415
  readFile: (rel) => {
25115
- try {
25116
- return (0, import_node_fs49.readFileSync)((0, import_node_path34.join)(frame.worktreeRoot ?? process.cwd(), rel), "utf8");
25117
- } catch {
25118
- return null;
25119
- }
26416
+ const read = readRegularFileInRoot(frame.worktreeRoot ?? process.cwd(), rel, EXCERPT_SOURCE_MAX_BYTES);
26417
+ return read.ok ? read.content : null;
25120
26418
  }
25121
26419
  });
25122
26420
  requestBody.repo_context = repoContext2;
@@ -25127,6 +26425,7 @@ async function runGuard(opts, globals) {
25127
26425
  symbols: repoContext2.symbols?.length ?? 0,
25128
26426
  callers: repoContext2.callers?.length ?? 0,
25129
26427
  tests: repoContext2.tests?.length ?? 0,
26428
+ importers: repoContext2.importers?.length ?? 0,
25130
26429
  excerpts: repoContext2.excerpts?.length ?? 0,
25131
26430
  elapsed_ms: repoContext2.elapsed_ms ?? null
25132
26431
  });
@@ -25145,7 +26444,7 @@ async function runGuard(opts, globals) {
25145
26444
  };
25146
26445
  logToFileOnly(coverageBlock(coverage));
25147
26446
  const reviewStart = Date.now();
25148
- const result = await analyzeRequest({
26447
+ const oneShot = analyzeRequest({
25149
26448
  serviceUrl: urlResult.data,
25150
26449
  token: tokenResult.data.token,
25151
26450
  body: requestBody,
@@ -25153,22 +26452,51 @@ async function runGuard(opts, globals) {
25153
26452
  timeout: 29e4,
25154
26453
  cmd: "guard"
25155
26454
  });
26455
+ const agent = startAgentReview({
26456
+ serviceUrl: urlResult.data,
26457
+ token: tokenResult.data.token,
26458
+ verbose: globals.verbose,
26459
+ hookStartedAt,
26460
+ moment,
26461
+ root: frame.worktreeRoot,
26462
+ range,
26463
+ iteration: iter + 1,
26464
+ sessionId,
26465
+ statedIntent,
26466
+ files,
26467
+ codeDelta,
26468
+ requestBody,
26469
+ oneShot
26470
+ });
26471
+ const result = await oneShot;
26472
+ const agentOutcome = agent ? await agent : null;
26473
+ const agentVerdict = agentOutcome?.kind === "finished" && agentOutcome.response.gate_decision ? agentOutcome.response : null;
25156
26474
  const reviewSecs = Math.max(1, Math.round((Date.now() - reviewStart) / 1e3));
26475
+ const oneShotResponse = result.ok ? result.data : {};
26476
+ const response = agentVerdict ? withAgentVerdict(oneShotResponse, agentVerdict) : oneShotResponse;
26477
+ if (opts.json) process.stderr.write(JSON.stringify(response) + "\n");
26478
+ const decision = response.gate_decision ?? "(unrecognised)";
26479
+ const viewUrl = response.view_url ?? "";
26480
+ const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
26481
+ const covLine = `${coverageSummary(coverage)} in ${reviewSecs}s`;
26482
+ const agentLine = describeAgentOutcome(agentOutcome);
26483
+ const covDetail = agentLine ? `${coverageBlock(coverage)}
26484
+ ${agentLine}` : coverageBlock(coverage);
26485
+ const witnessed = [...codeDelta.files.map((f) => f.path), ...agentVerdict?.witnessed_files ?? []];
25157
26486
  if (!result.ok) {
25158
26487
  const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : result.error.startsWith("INVALID_TOKEN") ? " Your Verity login expired or was revoked \u2014 run `verity login` to sign in again." : "";
26488
+ if (oneShotLostOutcome({ agentGate: decision, agentBlocking: hasBlockingFinding(response, witnessed) }) === "block") {
26489
+ writeIter(moment, iter + 1);
26490
+ writeBlockMessage(moment, response, covDetail);
26491
+ process.exit(2);
26492
+ }
26493
+ const agentNote = agentLine ? ` ${agentLine}, but it reviewed only part of the change.` : "";
25159
26494
  emitAllowNotice(
25160
- `\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "service offline"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
25161
- `Verity ${moment}: ${authRemedy ? "not authorized" : "service unavailable"} (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${authRemedy}`
26495
+ `\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "no single-pass review"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
26496
+ `Verity ${moment}: the single-pass review did not come back (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${agentNote}${authRemedy}`
25162
26497
  );
25163
26498
  }
25164
- if (opts.json) process.stderr.write(JSON.stringify(result.data) + "\n");
25165
- const response = result.data;
25166
- const decision = response.gate_decision ?? "(unrecognised)";
25167
- const viewUrl = response.view_url ?? "";
25168
- const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
25169
- const covLine = `${coverageSummary(coverage)} in ${reviewSecs}s`;
25170
- const covDetail = coverageBlock(coverage);
25171
- if (decision === "FAIL" && hasBlockingFinding(response, codeDelta.files.map((f) => f.path))) {
26499
+ if (decision === "FAIL" && hasBlockingFinding(response, witnessed)) {
25172
26500
  writeIter(moment, iter + 1);
25173
26501
  writeBlockMessage(moment, response, covDetail);
25174
26502
  process.exit(2);
@@ -25187,12 +26515,16 @@ async function runGuard(opts, globals) {
25187
26515
  emitAllowNotice(notice.user, notice.agent);
25188
26516
  }
25189
26517
  function verdictNotice(ctx) {
25190
- const { moment, verb, covLine, covDetail, link, viewUrl } = ctx;
25191
- const decision = ctx.decision ?? "(unrecognised)";
26518
+ const { moment, verb } = ctx;
26519
+ const covLine = terminalText(ctx.covLine, 500);
26520
+ const covDetail = terminalText(ctx.covDetail, 2e4);
26521
+ const link = terminalText(ctx.link, 2100);
26522
+ const viewUrl = terminalUrl(ctx.viewUrl);
26523
+ const decision = ctx.decision == null ? "(unrecognised)" : terminalText(ctx.decision, 60);
25192
26524
  const report = viewUrl ? `
25193
26525
  Report: ${viewUrl}` : "";
25194
26526
  if (decision === "FAIL") {
25195
- const narrative = ctx.narrative ?? "";
26527
+ const narrative = terminalText(ctx.narrative, 4e3);
25196
26528
  return {
25197
26529
  user: `\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding (${covLine})${link}`,
25198
26530
  agent: `Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}
@@ -25223,9 +26555,9 @@ function writeBlockMessage(moment, response, covDetail) {
25223
26555
  const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
25224
26556
  const verb = moment === "pre-commit" ? "commit" : "push";
25225
26557
  const assessment = response.assessment;
25226
- const narrative = assessment?.narrative ?? "";
25227
- const findings = response.findings ?? [];
25228
- const viewUrl = response.view_url ?? "";
26558
+ const narrative = terminalText(assessment?.narrative, 4e3);
26559
+ const findings = Array.isArray(response.findings) ? response.findings : [];
26560
+ const viewUrl = terminalUrl(response.view_url);
25229
26561
  process.stderr.write(`${RED}${BOLD}\u2501\u2501\u2501 Verity ${label2} gate: FAIL \u2501\u2501\u2501${NC}
25230
26562
 
25231
26563
  `);
@@ -25238,18 +26570,18 @@ function writeBlockMessage(moment, response, covDetail) {
25238
26570
 
25239
26571
  `);
25240
26572
  for (const f of agentFindings) {
25241
- const severity = (f.severity ?? "").toUpperCase();
26573
+ const severity = terminalText(f.severity, 20).toUpperCase();
25242
26574
  const fix = f.fix;
25243
- process.stderr.write(`[${severity}] ${f.title ?? ""}
26575
+ process.stderr.write(`[${severity}] ${terminalText(f.title, 300)}
25244
26576
  `);
25245
- process.stderr.write(` File: ${f.file ?? ""}:${f.line ?? ""}
26577
+ process.stderr.write(` File: ${terminalText(f.file, 400)}:${terminalText(String(f.line ?? ""), 12)}
25246
26578
  `);
25247
- process.stderr.write(` Fix: ${fix?.description ?? "See description"}
26579
+ process.stderr.write(` Fix: ${terminalText(fix?.description, 2e3) || "See description"}
25248
26580
 
25249
26581
  `);
25250
26582
  }
25251
26583
  }
25252
- process.stderr.write(`${DIM}${covDetail}${NC}
26584
+ process.stderr.write(`${DIM}${terminalText(covDetail, 2e4)}${NC}
25253
26585
 
25254
26586
  `);
25255
26587
  if (viewUrl) process.stderr.write(`${CYAN}Full report: ${viewUrl}${NC}
@@ -25410,10 +26742,10 @@ function registerWaiveCommand(program2) {
25410
26742
 
25411
26743
  // src/commands/init.ts
25412
26744
  var import_node_fs54 = require("node:fs");
25413
- var import_promises17 = require("node:fs/promises");
26745
+ var import_promises19 = require("node:fs/promises");
25414
26746
  var import_yaml6 = __toESM(require_dist());
25415
- var import_node_path37 = require("node:path");
25416
- var import_node_child_process16 = require("node:child_process");
26747
+ var import_node_path38 = require("node:path");
26748
+ var import_node_child_process17 = require("node:child_process");
25417
26749
 
25418
26750
  // src/lib/banner.ts
25419
26751
  var WORDMARK = [
@@ -25494,10 +26826,15 @@ function printPhase(n, of, title, subtitle) {
25494
26826
  var import_node_fs51 = require("node:fs");
25495
26827
 
25496
26828
  // src/lib/prereqs.ts
25497
- var import_node_child_process14 = require("node:child_process");
26829
+ var import_node_child_process15 = require("node:child_process");
25498
26830
  var MIN_NODE_MAJOR = 20;
25499
26831
  function which(bin) {
25500
- return whichSync(bin, { real: false });
26832
+ try {
26833
+ const out = (0, import_node_child_process15.execSync)(`command -v ${bin}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
26834
+ return out || null;
26835
+ } catch {
26836
+ return null;
26837
+ }
25501
26838
  }
25502
26839
  function checkNode() {
25503
26840
  const version = process.version;
@@ -25515,7 +26852,7 @@ function checkNode() {
25515
26852
  function checkGit() {
25516
26853
  let detail = "";
25517
26854
  try {
25518
- detail = (0, import_node_child_process14.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
26855
+ detail = (0, import_node_child_process15.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
25519
26856
  } catch {
25520
26857
  return {
25521
26858
  id: "git",
@@ -25552,13 +26889,13 @@ function checkAnalysisCli() {
25552
26889
  }
25553
26890
  var INSTALL_TIMEOUT_MS = 12e4;
25554
26891
  function run(command, args, opts = {}) {
25555
- return new Promise((resolve5) => {
25556
- const child = (0, import_node_child_process14.spawn)(command, args, {
26892
+ return new Promise((resolve6) => {
26893
+ const child = (0, import_node_child_process15.spawn)(command, args, {
25557
26894
  stdio: opts.inherit ? "inherit" : "pipe",
25558
26895
  timeout: INSTALL_TIMEOUT_MS
25559
26896
  });
25560
- child.on("error", () => resolve5(false));
25561
- child.on("close", (code) => resolve5(code === 0));
26897
+ child.on("error", () => resolve6(false));
26898
+ child.on("close", (code) => resolve6(code === 0));
25562
26899
  });
25563
26900
  }
25564
26901
  async function installAnalysisCli() {
@@ -25602,7 +26939,7 @@ async function checkPrereqs(opts = {}) {
25602
26939
  }
25603
26940
 
25604
26941
  // src/lib/telemetry.ts
25605
- var import_promises15 = require("node:fs/promises");
26942
+ var import_promises17 = require("node:fs/promises");
25606
26943
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
25607
26944
  var GITIGNORE_FILE = ".gitignore";
25608
26945
  var GITIGNORE_ENTRY = SETTINGS_LOCAL_IGNORE_ENTRY;
@@ -25632,7 +26969,7 @@ function buildTelemetryEnv(serviceUrl) {
25632
26969
  var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv(""));
25633
26970
  async function readSettingsLocal() {
25634
26971
  try {
25635
- return JSON.parse(await (0, import_promises15.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
26972
+ return JSON.parse(await (0, import_promises17.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
25636
26973
  } catch {
25637
26974
  return {};
25638
26975
  }
@@ -25644,7 +26981,7 @@ async function ensureGitignore() {
25644
26981
  const file = projectPath(GITIGNORE_FILE);
25645
26982
  let content = "";
25646
26983
  try {
25647
- content = await (0, import_promises15.readFile)(file, "utf-8");
26984
+ content = await (0, import_promises17.readFile)(file, "utf-8");
25648
26985
  } catch {
25649
26986
  }
25650
26987
  const lines = content.split("\n").map((l) => l.trim());
@@ -25653,7 +26990,7 @@ async function ensureGitignore() {
25653
26990
  }
25654
26991
  const block = "# Verity telemetry \u2014 machine-local Claude Code settings\n" + GITIGNORE_ENTRY + "\n";
25655
26992
  const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
25656
- await (0, import_promises15.writeFile)(file, next);
26993
+ await (0, import_promises17.writeFile)(file, next);
25657
26994
  }
25658
26995
  async function installTelemetry(serviceUrl) {
25659
26996
  const env = buildTelemetryEnv(serviceUrl);
@@ -25834,15 +27171,15 @@ function registerDoctorCommand(program2) {
25834
27171
 
25835
27172
  // src/commands/migrate.ts
25836
27173
  var import_node_fs52 = require("node:fs");
25837
- var import_node_path35 = require("node:path");
25838
- var import_node_child_process15 = require("node:child_process");
27174
+ var import_node_path36 = require("node:path");
27175
+ var import_node_child_process16 = require("node:child_process");
25839
27176
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
25840
27177
  function defaultNpmRemover(pkg) {
25841
- (0, import_node_child_process15.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
27178
+ (0, import_node_child_process16.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
25842
27179
  }
25843
27180
  function isGitTracked(cwd, relPath) {
25844
27181
  try {
25845
- (0, import_node_child_process15.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
27182
+ (0, import_node_child_process16.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
25846
27183
  return true;
25847
27184
  } catch {
25848
27185
  return false;
@@ -25850,7 +27187,7 @@ function isGitTracked(cwd, relPath) {
25850
27187
  }
25851
27188
  function isGitRepo(cwd) {
25852
27189
  try {
25853
- (0, import_node_child_process15.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
27190
+ (0, import_node_child_process16.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
25854
27191
  return true;
25855
27192
  } catch {
25856
27193
  return false;
@@ -25871,8 +27208,8 @@ async function runMigration(opts = {}) {
25871
27208
  return { actions, migrated: actions.length > 0 };
25872
27209
  }
25873
27210
  function migrateProjectDir(root, actions) {
25874
- const gateDir = (0, import_node_path35.join)(root, ".gate");
25875
- const verityDir = (0, import_node_path35.join)(root, ".verity");
27211
+ const gateDir = (0, import_node_path36.join)(root, ".gate");
27212
+ const verityDir = (0, import_node_path36.join)(root, ".verity");
25876
27213
  if ((0, import_node_fs52.existsSync)(gateDir) && !(0, import_node_fs52.existsSync)(verityDir)) {
25877
27214
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
25878
27215
  }
@@ -25890,7 +27227,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
25890
27227
  );
25891
27228
  }
25892
27229
  try {
25893
- (0, import_node_child_process15.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
27230
+ (0, import_node_child_process16.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
25894
27231
  actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
25895
27232
  moved = true;
25896
27233
  } catch {
@@ -25926,11 +27263,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
25926
27263
  }
25927
27264
  function migrateGlobalCredentials(home, actions) {
25928
27265
  if (!home) return;
25929
- const gateCreds = (0, import_node_path35.join)(home, ".gate", "credentials");
25930
- const verityCreds = (0, import_node_path35.join)(home, ".verity", "credentials");
27266
+ const gateCreds = (0, import_node_path36.join)(home, ".gate", "credentials");
27267
+ const verityCreds = (0, import_node_path36.join)(home, ".verity", "credentials");
25931
27268
  if (!(0, import_node_fs52.existsSync)(gateCreds)) return;
25932
27269
  if (!(0, import_node_fs52.existsSync)(verityCreds)) {
25933
- (0, import_node_fs52.mkdirSync)((0, import_node_path35.join)(home, ".verity"), { recursive: true });
27270
+ (0, import_node_fs52.mkdirSync)((0, import_node_path36.join)(home, ".verity"), { recursive: true });
25934
27271
  moveFile(gateCreds, verityCreds);
25935
27272
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
25936
27273
  return;
@@ -25952,7 +27289,7 @@ async function migrateLegacyHooks(root, actions) {
25952
27289
  }
25953
27290
  }
25954
27291
  async function migrateClaudeMd(root, actions) {
25955
- const claudeMd = (0, import_node_path35.join)(root, "CLAUDE.md");
27292
+ const claudeMd = (0, import_node_path36.join)(root, "CLAUDE.md");
25956
27293
  const hadLegacyBlock = (0, import_node_fs52.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
25957
27294
  if (!hadLegacyBlock) return;
25958
27295
  try {
@@ -25963,13 +27300,13 @@ async function migrateClaudeMd(root, actions) {
25963
27300
  }
25964
27301
  }
25965
27302
  function migrateStandardFile(root, actions) {
25966
- const gateMd = (0, import_node_path35.join)(root, "GATE.md");
25967
- const verityMd = (0, import_node_path35.join)(root, "VERITY.md");
27303
+ const gateMd = (0, import_node_path36.join)(root, "GATE.md");
27304
+ const verityMd = (0, import_node_path36.join)(root, "VERITY.md");
25968
27305
  if (!(0, import_node_fs52.existsSync)(gateMd) || (0, import_node_fs52.existsSync)(verityMd)) return;
25969
27306
  let moved = false;
25970
27307
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
25971
27308
  try {
25972
- (0, import_node_child_process15.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
27309
+ (0, import_node_child_process16.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
25973
27310
  moved = true;
25974
27311
  } catch {
25975
27312
  }
@@ -25981,7 +27318,7 @@ function migrateStandardFile(root, actions) {
25981
27318
  actions.push("Renamed GATE.md \u2192 VERITY.md");
25982
27319
  }
25983
27320
  async function migrateTelemetryHeaders(root, actions) {
25984
- const file = (0, import_node_path35.join)(root, ".claude", "settings.local.json");
27321
+ const file = (0, import_node_path36.join)(root, ".claude", "settings.local.json");
25985
27322
  if (!(0, import_node_fs52.existsSync)(file)) return;
25986
27323
  let settings;
25987
27324
  try {
@@ -26029,8 +27366,8 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
26029
27366
  toAppend.push(line.replace(/\r$/, ""));
26030
27367
  }
26031
27368
  if (toAppend.length > 0) {
26032
- const sep3 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
26033
- (0, import_node_fs52.writeFileSync)(verityCreds, verityContent + sep3 + toAppend.join("\n") + "\n");
27369
+ const sep4 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
27370
+ (0, import_node_fs52.writeFileSync)(verityCreds, verityContent + sep4 + toAppend.join("\n") + "\n");
26034
27371
  }
26035
27372
  (0, import_node_fs52.rmSync)(gateCreds, { force: true });
26036
27373
  return toAppend.length;
@@ -26044,7 +27381,7 @@ function readFileSyncSafe(path) {
26044
27381
  }
26045
27382
  function hasStagedChanges(root) {
26046
27383
  try {
26047
- (0, import_node_child_process15.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
27384
+ (0, import_node_child_process16.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
26048
27385
  return false;
26049
27386
  } catch {
26050
27387
  return true;
@@ -26071,15 +27408,15 @@ function moveFile(from, to) {
26071
27408
  function carryLegacyContents(gateDir, verityDir) {
26072
27409
  let copied = 0;
26073
27410
  const walk2 = (relDir) => {
26074
- const srcDir = (0, import_node_path35.join)(gateDir, relDir);
27411
+ const srcDir = (0, import_node_path36.join)(gateDir, relDir);
26075
27412
  for (const entry of (0, import_node_fs52.readdirSync)(srcDir)) {
26076
- const rel = relDir ? (0, import_node_path35.join)(relDir, entry) : entry;
26077
- const src = (0, import_node_path35.join)(gateDir, rel);
26078
- const dest = (0, import_node_path35.join)(verityDir, rel);
27413
+ const rel = relDir ? (0, import_node_path36.join)(relDir, entry) : entry;
27414
+ const src = (0, import_node_path36.join)(gateDir, rel);
27415
+ const dest = (0, import_node_path36.join)(verityDir, rel);
26079
27416
  if ((0, import_node_fs52.statSync)(src).isDirectory()) {
26080
27417
  walk2(rel);
26081
27418
  } else if (!(0, import_node_fs52.existsSync)(dest)) {
26082
- (0, import_node_fs52.mkdirSync)((0, import_node_path35.dirname)(dest), { recursive: true });
27419
+ (0, import_node_fs52.mkdirSync)((0, import_node_path36.dirname)(dest), { recursive: true });
26083
27420
  (0, import_node_fs52.cpSync)(src, dest);
26084
27421
  copied++;
26085
27422
  }
@@ -26089,22 +27426,22 @@ function carryLegacyContents(gateDir, verityDir) {
26089
27426
  return copied;
26090
27427
  }
26091
27428
  async function needsMigration(root = repoRoot()) {
26092
- const gateDir = (0, import_node_path35.join)(root, ".gate");
26093
- const verityDir = (0, import_node_path35.join)(root, ".verity");
27429
+ const gateDir = (0, import_node_path36.join)(root, ".gate");
27430
+ const verityDir = (0, import_node_path36.join)(root, ".verity");
26094
27431
  if ((0, import_node_fs52.existsSync)(gateDir) && !(0, import_node_fs52.existsSync)(verityDir)) return true;
26095
27432
  if ((0, import_node_fs52.existsSync)(gateDir) && (0, import_node_fs52.existsSync)(verityDir)) {
26096
- if ((0, import_node_fs52.existsSync)((0, import_node_path35.join)(gateDir, "credentials")) && !(0, import_node_fs52.existsSync)((0, import_node_path35.join)(verityDir, "credentials"))) {
27433
+ if ((0, import_node_fs52.existsSync)((0, import_node_path36.join)(gateDir, "credentials")) && !(0, import_node_fs52.existsSync)((0, import_node_path36.join)(verityDir, "credentials"))) {
26097
27434
  return true;
26098
27435
  }
26099
- if ((0, import_node_fs52.existsSync)((0, import_node_path35.join)(gateDir, "memory")) && !(0, import_node_fs52.existsSync)((0, import_node_path35.join)(verityDir, "memory"))) {
27436
+ if ((0, import_node_fs52.existsSync)((0, import_node_path36.join)(gateDir, "memory")) && !(0, import_node_fs52.existsSync)((0, import_node_path36.join)(verityDir, "memory"))) {
26100
27437
  return true;
26101
27438
  }
26102
27439
  }
26103
- const claudeMd = (0, import_node_path35.join)(root, "CLAUDE.md");
27440
+ const claudeMd = (0, import_node_path36.join)(root, "CLAUDE.md");
26104
27441
  if ((0, import_node_fs52.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
26105
27442
  return true;
26106
27443
  }
26107
- if ((0, import_node_fs52.existsSync)((0, import_node_path35.join)(root, "GATE.md")) && !(0, import_node_fs52.existsSync)((0, import_node_path35.join)(root, "VERITY.md"))) {
27444
+ if ((0, import_node_fs52.existsSync)((0, import_node_path36.join)(root, "GATE.md")) && !(0, import_node_fs52.existsSync)((0, import_node_path36.join)(root, "VERITY.md"))) {
26108
27445
  return true;
26109
27446
  }
26110
27447
  if (await hasLegacyHooksAt(root)) return true;
@@ -26233,7 +27570,7 @@ function runSelect(opts) {
26233
27570
  cursor: initialIdx[0] ?? 0,
26234
27571
  chosen: new Set(mode2 === "single" ? [initialIdx[0] ?? 0] : initialIdx)
26235
27572
  };
26236
- return new Promise((resolve5) => {
27573
+ return new Promise((resolve6) => {
26237
27574
  let painted = 0;
26238
27575
  let settled = false;
26239
27576
  const draw = () => {
@@ -26281,7 +27618,7 @@ function runSelect(opts) {
26281
27618
  if (settled) return;
26282
27619
  settled = true;
26283
27620
  restore();
26284
- resolve5(value);
27621
+ resolve6(value);
26285
27622
  };
26286
27623
  readline2.emitKeypressEvents(input);
26287
27624
  input.setRawMode(true);
@@ -26302,9 +27639,9 @@ async function askLine(question, io = {}) {
26302
27639
  output: io.output ?? process.stdout
26303
27640
  });
26304
27641
  try {
26305
- return await new Promise((resolve5) => {
26306
- rl.question(question).then((a) => resolve5(a.trim())).catch(() => resolve5(null));
26307
- rl.once("close", () => setImmediate(() => resolve5(null)));
27642
+ return await new Promise((resolve6) => {
27643
+ rl.question(question).then((a) => resolve6(a.trim())).catch(() => resolve6(null));
27644
+ rl.once("close", () => setImmediate(() => resolve6(null)));
26308
27645
  });
26309
27646
  } finally {
26310
27647
  rl.close();
@@ -26386,8 +27723,8 @@ async function promptMultiSelect(question, choices, fallback) {
26386
27723
 
26387
27724
  // src/lib/remote-config.ts
26388
27725
  var import_node_fs53 = require("node:fs");
26389
- var import_promises16 = require("node:fs/promises");
26390
- var import_node_path36 = require("node:path");
27726
+ var import_promises18 = require("node:fs/promises");
27727
+ var import_node_path37 = require("node:path");
26391
27728
  var import_yaml5 = __toESM(require_dist());
26392
27729
  var IGNORE_RIDER = "verityignore";
26393
27730
  async function fetchRemoteSetup(opts) {
@@ -26436,7 +27773,7 @@ async function adoptRemoteSetup(found, opts) {
26436
27773
  await writeOut(VERITYIGNORE_FILE, rider);
26437
27774
  written.push(VERITYIGNORE_FILE);
26438
27775
  } else {
26439
- const local = await (0, import_promises16.readFile)(localIgnore, "utf-8").catch(() => null);
27776
+ const local = await (0, import_promises18.readFile)(localIgnore, "utf-8").catch(() => null);
26440
27777
  if (local !== null && local !== rider) {
26441
27778
  notes.push(`${VERITYIGNORE_FILE} already exists here and differs from the pushed copy \u2014 kept yours.`);
26442
27779
  }
@@ -26463,10 +27800,10 @@ async function adoptRemoteSetup(found, opts) {
26463
27800
  written.push(VERITY_MD_FILE);
26464
27801
  return { written, notes };
26465
27802
  }
26466
- async function writeOut(relative3, body) {
26467
- const target = projectPath(relative3);
26468
- await (0, import_promises16.mkdir)((0, import_node_path36.dirname)(target), { recursive: true });
26469
- await (0, import_promises16.writeFile)(target, body);
27803
+ async function writeOut(relative2, body) {
27804
+ const target = projectPath(relative2);
27805
+ await (0, import_promises18.mkdir)((0, import_node_path37.dirname)(target), { recursive: true });
27806
+ await (0, import_promises18.writeFile)(target, body);
26470
27807
  }
26471
27808
  function describeRemote(found) {
26472
27809
  const when = found.standard.createdAt.slice(0, 10);
@@ -26536,7 +27873,7 @@ async function runOptionalAuth(resolution, opts = {}) {
26536
27873
  }
26537
27874
  let remote = "";
26538
27875
  try {
26539
- remote = (0, import_node_child_process16.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
27876
+ remote = (0, import_node_child_process17.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
26540
27877
  } catch {
26541
27878
  }
26542
27879
  if (!healed) {
@@ -26582,15 +27919,15 @@ async function runOptionalAuth(resolution, opts = {}) {
26582
27919
  }
26583
27920
  function resolveDataDir2() {
26584
27921
  const candidates2 = [
26585
- (0, import_node_path37.join)(__dirname, "..", "data"),
27922
+ (0, import_node_path38.join)(__dirname, "..", "data"),
26586
27923
  // installed: node_modules/@codacy/verity-cli/data
26587
- (0, import_node_path37.join)(__dirname, "..", "..", "data"),
27924
+ (0, import_node_path38.join)(__dirname, "..", "..", "data"),
26588
27925
  // edge case: nested resolution
26589
- (0, import_node_path37.join)(process.cwd(), "cli", "data")
27926
+ (0, import_node_path38.join)(process.cwd(), "cli", "data")
26590
27927
  // local dev: running from repo root
26591
27928
  ];
26592
27929
  for (const candidate of candidates2) {
26593
- if ((0, import_node_fs54.existsSync)((0, import_node_path37.join)(candidate, "skills"))) {
27930
+ if ((0, import_node_fs54.existsSync)((0, import_node_path38.join)(candidate, "skills"))) {
26594
27931
  return candidate;
26595
27932
  }
26596
27933
  }
@@ -26599,8 +27936,8 @@ function resolveDataDir2() {
26599
27936
  );
26600
27937
  }
26601
27938
  async function copyDir(src, dest) {
26602
- await (0, import_promises17.mkdir)(dest, { recursive: true });
26603
- await (0, import_promises17.cp)(src, dest, { recursive: true, force: true });
27939
+ await (0, import_promises19.mkdir)(dest, { recursive: true });
27940
+ await (0, import_promises19.cp)(src, dest, { recursive: true, force: true });
26604
27941
  }
26605
27942
  async function skillIsCurrent(src, dest) {
26606
27943
  const list2 = (dir) => {
@@ -26608,7 +27945,7 @@ async function skillIsCurrent(src, dest) {
26608
27945
  const walk2 = (d, prefix) => {
26609
27946
  for (const e of (0, import_node_fs54.readdirSync)(d, { withFileTypes: true })) {
26610
27947
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
26611
- if (e.isDirectory()) walk2((0, import_node_path37.join)(d, e.name), rel);
27948
+ if (e.isDirectory()) walk2((0, import_node_path38.join)(d, e.name), rel);
26612
27949
  else if (e.isFile()) out.push(rel);
26613
27950
  }
26614
27951
  };
@@ -26619,8 +27956,8 @@ async function skillIsCurrent(src, dest) {
26619
27956
  const shipped = list2(src);
26620
27957
  if (JSON.stringify(shipped) !== JSON.stringify(list2(dest))) return false;
26621
27958
  for (const rel of shipped) {
26622
- const a = await (0, import_promises17.readFile)((0, import_node_path37.join)(src, rel), "utf-8");
26623
- const b = await (0, import_promises17.readFile)((0, import_node_path37.join)(dest, rel), "utf-8");
27959
+ const a = await (0, import_promises19.readFile)((0, import_node_path38.join)(src, rel), "utf-8");
27960
+ const b = await (0, import_promises19.readFile)((0, import_node_path38.join)(dest, rel), "utf-8");
26624
27961
  if (a !== b) return false;
26625
27962
  }
26626
27963
  return true;
@@ -26807,7 +28144,7 @@ async function healStaleAnalysisConfig(globals) {
26807
28144
  printWarn(" Your analysis config names pattern ids that no longer resolve \u2014 those tools were");
26808
28145
  printWarn(" running silently with nothing enabled. Re-deriving it from your Standard\u2026");
26809
28146
  try {
26810
- const content = (0, import_yaml6.parse)(await (0, import_promises17.readFile)(standardPath, "utf-8"));
28147
+ const content = (0, import_yaml6.parse)(await (0, import_promises19.readFile)(standardPath, "utf-8"));
26811
28148
  const derived = await deriveConfigForStandard(content);
26812
28149
  for (const path of derived.written) printInfo(` ${path} \u2713 (re-derived)`);
26813
28150
  for (const note of derived.notes) printWarn(` ${note}`);
@@ -26888,7 +28225,7 @@ async function handoffToSetup(enabled, claudeInstalled) {
26888
28225
  console.log(" Usually a minute or two. Quit any time \u2014 re-running /verity-setup resumes.");
26889
28226
  console.log("");
26890
28227
  const startedAt = Date.now();
26891
- const run2 = (0, import_node_child_process16.spawnSync)("claude", ["/verity-setup"], { stdio: "inherit" });
28228
+ const run2 = (0, import_node_child_process17.spawnSync)("claude", ["/verity-setup"], { stdio: "inherit" });
26892
28229
  if (run2.error) {
26893
28230
  printWarn(`Could not start Claude Code: ${run2.error.message}`);
26894
28231
  return instruct("start it yourself and run the skill there");
@@ -26898,12 +28235,12 @@ async function handoffToSetup(enabled, claudeInstalled) {
26898
28235
  async function installSkills(force, step) {
26899
28236
  step("Installing skills");
26900
28237
  const dataDir = resolveDataDir2();
26901
- const skillsSource = (0, import_node_path37.join)(dataDir, "skills");
28238
+ const skillsSource = (0, import_node_path38.join)(dataDir, "skills");
26902
28239
  const skillsDest = ".claude/skills";
26903
28240
  let skillsInstalled = 0;
26904
28241
  for (const skill of SKILLS) {
26905
- const src = (0, import_node_path37.join)(skillsSource, skill);
26906
- const dest = (0, import_node_path37.join)(skillsDest, skill);
28242
+ const src = (0, import_node_path38.join)(skillsSource, skill);
28243
+ const dest = (0, import_node_path38.join)(skillsDest, skill);
26907
28244
  if (!(0, import_node_fs54.existsSync)(src)) {
26908
28245
  printWarn(` Skill data not found: ${skill}`);
26909
28246
  continue;
@@ -26972,7 +28309,7 @@ async function checkPrerequisites(step) {
26972
28309
  }
26973
28310
  async function scaffoldProject(step, defaultsOnly) {
26974
28311
  step("Knowledge base, .gitignore and CLAUDE.md");
26975
- await (0, import_promises17.mkdir)(VERITY_DIR, { recursive: true });
28312
+ await (0, import_promises19.mkdir)(VERITY_DIR, { recursive: true });
26976
28313
  await ensureMemoryDir();
26977
28314
  const ignoreResult = ensureVerityGitignore();
26978
28315
  if (ignoreResult === "failed") {
@@ -27137,8 +28474,8 @@ function registerInitCommand(program2) {
27137
28474
  printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
27138
28475
  }
27139
28476
  await scaffoldProject(step, defaultsOnly);
27140
- const globalVerityDir = (0, import_node_path37.join)(process.env.HOME ?? "", ".verity");
27141
- await (0, import_promises17.mkdir)(globalVerityDir, { recursive: true });
28477
+ const globalVerityDir = (0, import_node_path38.join)(process.env.HOME ?? "", ".verity");
28478
+ await (0, import_promises19.mkdir)(globalVerityDir, { recursive: true });
27142
28479
  console.log("");
27143
28480
  step("Wiring Claude Code hooks");
27144
28481
  const gitMoments = [
@@ -27236,7 +28573,7 @@ function registerInitCommand(program2) {
27236
28573
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
27237
28574
  init: {
27238
28575
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
27239
- cli_version: true ? "0.32.7" : "dev"
28576
+ cli_version: true ? "0.33.0-experimental.34db3a4" : "dev"
27240
28577
  }
27241
28578
  });
27242
28579
  } catch (err) {
@@ -27287,7 +28624,7 @@ function registerInitCommand(program2) {
27287
28624
 
27288
28625
  // src/commands/uninstall.ts
27289
28626
  var import_node_fs55 = require("node:fs");
27290
- var import_node_path38 = require("node:path");
28627
+ var import_node_path39 = require("node:path");
27291
28628
  function registerUninstallCommand(program2) {
27292
28629
  program2.command("uninstall").description("Remove Verity from this project (skills, hooks, .verity/, VERITY.md)").option("--dry-run", "Show what would be removed without doing it").option("--purge-global", "Also remove ~/.verity/ (deletes saved tokens \u2014 reconnect requires re-registration)").option("--keep-verity-md", "Keep the project root VERITY.md file").action(async (opts) => {
27293
28630
  const dryRun = opts.dryRun ?? false;
@@ -27296,7 +28633,7 @@ function registerUninstallCommand(program2) {
27296
28633
  const actions = [];
27297
28634
  const skillsRoot = projectPath(".claude/skills");
27298
28635
  for (const name of PROJECT_SKILL_NAMES) {
27299
- const dir = (0, import_node_path38.join)(skillsRoot, name);
28636
+ const dir = (0, import_node_path39.join)(skillsRoot, name);
27300
28637
  if ((0, import_node_fs55.existsSync)(dir)) {
27301
28638
  actions.push({
27302
28639
  label: `Remove .claude/skills/${name}/`,
@@ -27342,7 +28679,7 @@ function registerUninstallCommand(program2) {
27342
28679
  }
27343
28680
  });
27344
28681
  const home = process.env.HOME ?? "";
27345
- const globalVerityDir = (0, import_node_path38.join)(home, ".verity");
28682
+ const globalVerityDir = (0, import_node_path39.join)(home, ".verity");
27346
28683
  if (purgeGlobal && (0, import_node_fs55.existsSync)(globalVerityDir)) {
27347
28684
  actions.push({
27348
28685
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
@@ -27541,7 +28878,7 @@ function registerTaskCommands(program2) {
27541
28878
 
27542
28879
  // src/commands/reset.ts
27543
28880
  var import_node_fs56 = require("node:fs");
27544
- var import_node_path39 = require("node:path");
28881
+ var import_node_path40 = require("node:path");
27545
28882
  function registerResetCommand(program2) {
27546
28883
  program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
27547
28884
  const globals = program2.opts();
@@ -27582,7 +28919,7 @@ function registerResetCommand(program2) {
27582
28919
  for (const entry of (0, import_node_fs56.readdirSync)(cacheDir)) {
27583
28920
  if (entry.startsWith("pending-")) {
27584
28921
  try {
27585
- (0, import_node_fs56.unlinkSync)((0, import_node_path39.join)(cacheDir, entry));
28922
+ (0, import_node_fs56.unlinkSync)((0, import_node_path40.join)(cacheDir, entry));
27586
28923
  purged++;
27587
28924
  } catch {
27588
28925
  }
@@ -27609,7 +28946,7 @@ function registerResetCommand(program2) {
27609
28946
  if ((0, import_node_fs56.existsSync)(logsDir)) {
27610
28947
  for (const entry of (0, import_node_fs56.readdirSync)(logsDir)) {
27611
28948
  try {
27612
- (0, import_node_fs56.unlinkSync)((0, import_node_path39.join)(logsDir, entry));
28949
+ (0, import_node_fs56.unlinkSync)((0, import_node_path40.join)(logsDir, entry));
27613
28950
  } catch {
27614
28951
  }
27615
28952
  }
@@ -27621,10 +28958,52 @@ function registerResetCommand(program2) {
27621
28958
  }
27622
28959
 
27623
28960
  // src/commands/reflect.ts
28961
+ var import_node_fs57 = require("node:fs");
28962
+
28963
+ // src/lib/reflection-globs.ts
28964
+ var MAX_GLOBS = 6;
28965
+ var PATH_TOKEN = /[A-Za-z0-9_./-]*[A-Za-z0-9_-]\.[A-Za-z0-9]{1,8}(?::\d+(?::\d+)?)?/g;
28966
+ function clean(token) {
28967
+ return token.replace(/:\d+(?::\d+)?$/, "").replace(/^[`'"(\[]+/, "").replace(/[`'"),\].;!?]+$/, "").replace(/^\.\//, "");
28968
+ }
28969
+ function resolve5(token, treePaths) {
28970
+ if (treePaths.includes(token)) return token;
28971
+ const suffix = token.startsWith("/") ? token : `/${token}`;
28972
+ const matches = treePaths.filter((p) => p.endsWith(suffix));
28973
+ return matches.length === 1 ? matches[0] : null;
28974
+ }
28975
+ function deriveFileGlobs(text, treePaths) {
28976
+ if (!text || treePaths.length === 0) return [];
28977
+ const out = [];
28978
+ const seen = /* @__PURE__ */ new Set();
28979
+ for (const raw of text.match(PATH_TOKEN) ?? []) {
28980
+ const token = clean(raw);
28981
+ if (!token) continue;
28982
+ const path = resolve5(token, treePaths);
28983
+ if (!path || seen.has(path)) continue;
28984
+ seen.add(path);
28985
+ out.push(path);
28986
+ if (out.length >= MAX_GLOBS) break;
28987
+ }
28988
+ return out;
28989
+ }
28990
+
28991
+ // src/commands/reflect.ts
28992
+ async function writeNodeToDisk(args) {
28993
+ try {
28994
+ const written = await applyMemoryWrites(
28995
+ [{ path: args.filePath, content: args.content, node_id: args.nodeId, op: "create" }],
28996
+ { treePaths: listTrackedFiles(), recordBaseline: false }
28997
+ );
28998
+ if (written === 0) return false;
28999
+ return (0, import_node_fs57.existsSync)(projectPath(`${VERITY_DIR}/memory/${args.filePath}`));
29000
+ } catch {
29001
+ return false;
29002
+ }
29003
+ }
27624
29004
  function registerReflectCommand(program2) {
27625
- program2.command("reflect").description("Capture learnings \u2014 auto-extract or submit a human reflection").option("--user-input <text>", "The reflection to record (the agent-drafted or user-confirmed text)").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").option("--autonomous", "Record the drafted reflection without a confirm step (auto-detected from TTY / VERITY_AUTONOMOUS when omitted)").action(async (opts) => {
29005
+ program2.command("reflect").description("Capture learnings \u2014 auto-extract or record a reflection").option("--user-input <text>", "The reflection to record (the agent-drafted or user-dictated text)").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").option("--confirmed", "The USER authored or confirmed these exact words \u2014 records as human-authored (source: user, confidence 1.0)").option("--file-globs <glob...>", "Files this reflection is about. Without any, the paths cited in the text are used; a reflection matching no file never surfaces in a review").option("--autonomous", "(deprecated, ignored) reflections are always recorded without a confirm step").action(async (opts) => {
27626
29006
  const globals = program2.opts();
27627
- const mode2 = resolveRunMode({ autonomousFlag: opts.autonomous });
27628
29007
  await ensureMemoryDir();
27629
29008
  const tokenResult = await resolveToken(globals.token);
27630
29009
  if (!tokenResult.ok) {
@@ -27689,9 +29068,14 @@ function registerReflectCommand(program2) {
27689
29068
  if (userInput) {
27690
29069
  const validKinds = ["decision", "gotcha", "pattern", "security", "quality", "intent", "domain", "integration"];
27691
29070
  const kind = validKinds.includes(opts.kind) ? opts.kind : "gotcha";
29071
+ const confirmed = opts.confirmed === true;
29072
+ const source = confirmed ? "user" : "agent";
29073
+ const explicitGlobs = Array.isArray(opts.fileGlobs) ? opts.fileGlobs.map((g) => g.trim()).filter(Boolean) : [];
29074
+ const derivedGlobs = explicitGlobs.length > 0 ? [] : deriveFileGlobs(userInput, listTrackedFiles());
29075
+ const fileGlobs = explicitGlobs.length > 0 ? explicitGlobs : derivedGlobs.length > 0 ? derivedGlobs : inheritedGlobs;
27692
29076
  const result = await apiRequest({
27693
29077
  method: "POST",
27694
- path: "/compound/memory/nodes",
29078
+ path: "/compound/reflect",
27695
29079
  serviceUrl,
27696
29080
  token,
27697
29081
  body: {
@@ -27699,14 +29083,23 @@ function registerReflectCommand(program2) {
27699
29083
  title: userInput.slice(0, 200),
27700
29084
  body: userInput,
27701
29085
  domains: [],
27702
- // Inherit globs from the current task's context_files so the
27703
- // reflection is actually reachable by reviewer injection.
27704
- // Falls back to [] when there's no task context — user can
27705
- // edit the markdown later to tighten retrieval.
27706
- file_globs: inheritedGlobs
29086
+ file_globs: fileGlobs,
29087
+ source,
29088
+ created_by: "reflect",
29089
+ // The server VERIFIES this against the project before writing it
29090
+ // an id it cannot verify is dropped, never trusted and never an
29091
+ // error. Sending it is what lets an auto-recorded reflection carry
29092
+ // the same attribution a dashboard-written one does; without it the
29093
+ // unreviewed node is the one that cannot say where it came from.
29094
+ ...taskId && { task_id: taskId },
29095
+ // Server-side default for each source; sent explicitly so an older
29096
+ // server (which knows 'user'/'extractor'/'linter' only, and would
29097
+ // silently coerce an unknown source to 'user' at 1.0) still records
29098
+ // an honest confidence for a draft nobody confirmed.
29099
+ confidence: confirmed ? 1 : 0.7
27707
29100
  },
27708
29101
  verbose: globals.verbose,
27709
- cmd: "reflect_user"
29102
+ cmd: confirmed ? "reflect_user" : "reflect_agent"
27710
29103
  });
27711
29104
  if (!result.ok) {
27712
29105
  printError(`Failed to save reflection: ${result.error}`);
@@ -27714,12 +29107,30 @@ function registerReflectCommand(program2) {
27714
29107
  }
27715
29108
  const nodeId = result.data.node_id;
27716
29109
  const filePath = result.data.file_path;
27717
- printInfo(`Queued: ${nodeId} (will sync to .verity/memory/${filePath} on next analysis).`);
29110
+ const viewUrl = typeof result.data.view_url === "string" ? result.data.view_url : null;
29111
+ const recordedSource = typeof result.data.source === "string" ? result.data.source : source;
29112
+ const recordedAsUser = recordedSource === "user";
29113
+ const content = typeof result.data.content === "string" ? result.data.content : null;
29114
+ const synced = content !== null && await writeNodeToDisk({ filePath, content, nodeId });
29115
+ printInfo(
29116
+ synced ? `Recorded ${nodeId} \u2192 .verity/memory/${filePath}` : `Recorded ${nodeId} (syncs to .verity/memory/${filePath} on the next analysis).`
29117
+ );
29118
+ if (viewUrl) printInfo(` ${viewUrl}`);
27718
29119
  printInfo(
27719
- mode2 === "autonomous" ? "Recorded autonomously (no confirm step) into the project knowledge base." : "Recorded your confirmed reflection into the project knowledge base."
29120
+ recordedAsUser ? " Recorded as yours (source: user)." : " Auto-recorded from this task \u2014 nobody reviewed it. Edit or delete the file if it is wrong."
27720
29121
  );
29122
+ if (recordedSource !== source) {
29123
+ printWarn(
29124
+ `Provenance mismatch: sent source "${source}", the service recorded "${recordedSource}". Correct ${nodeId} on the dashboard \u2014 a draft stored as human-authored cannot be told apart later.`
29125
+ );
29126
+ }
29127
+ if (fileGlobs.length === 0) {
29128
+ printWarn(
29129
+ 'No files matched \u2014 this reflection will not surface in future reviews. Cite a path in the text, or pass --file-globs "<path or glob>".'
29130
+ );
29131
+ }
27721
29132
  if (taskResolution === "none") {
27722
- printInfo("(No task context \u2014 reflection lives at the project level.)");
29133
+ printInfo(" (No task context \u2014 reflection lives at the project level.)");
27723
29134
  }
27724
29135
  } else {
27725
29136
  if (!taskId) {
@@ -27806,6 +29217,121 @@ function registerMemoryCommand(program2) {
27806
29217
  }
27807
29218
  process.exit(result.failed === 0 ? 0 : 1);
27808
29219
  });
29220
+ memory.command("pull").description("Download this repo's knowledge graph into .verity/memory/ (the session-start hook runs it)").option("--force", "Fetch every node even if nothing changed since the last pull").option("--quiet", "Print nothing and always exit 0 (for the session-start hook)").action(async (opts) => {
29221
+ const globals = program2.opts();
29222
+ const quiet = !!opts.quiet;
29223
+ const fail = (message) => {
29224
+ if (!quiet) printError(message);
29225
+ process.exit(quiet ? 0 : 1);
29226
+ };
29227
+ try {
29228
+ process.chdir(repoRoot());
29229
+ } catch {
29230
+ }
29231
+ const tokenResult = await resolveToken(globals.token);
29232
+ if (!tokenResult.ok) return fail(tokenResult.error);
29233
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
29234
+ if (!urlResult.ok) return fail(urlResult.error);
29235
+ let result;
29236
+ try {
29237
+ result = await pullRepoMemory({
29238
+ serviceUrl: urlResult.data,
29239
+ token: tokenResult.data.token,
29240
+ verbose: globals.verbose,
29241
+ force: !!opts.force
29242
+ });
29243
+ } catch (err) {
29244
+ return fail(`Could not pull the knowledge graph: ${err.message}`);
29245
+ }
29246
+ logEvent("memory_pull", result.ok ? { status: result.status, received: result.received, written: result.written } : { status: "failed", category: result.category ?? null });
29247
+ if (!result.ok) return fail(`Could not pull the knowledge graph: ${result.error}`);
29248
+ if (!quiet) {
29249
+ printInfo(result.status === "unchanged" ? "Knowledge graph is up to date." : result.status === "tracked" ? "Skipped: .verity/memory/ is tracked by git here, and the pull never rewrites tracked files. Run `verity memory untrack` to make the graph machine-local." : `Pulled ${result.received} node(s); ${result.written} new or updated file(s) written to .verity/memory/.`);
29250
+ }
29251
+ let org;
29252
+ try {
29253
+ org = await pullOrgKnowledge({
29254
+ serviceUrl: urlResult.data,
29255
+ token: tokenResult.data.token,
29256
+ verbose: globals.verbose,
29257
+ force: !!opts.force
29258
+ });
29259
+ } catch (err) {
29260
+ org = { ok: false, error: err.message };
29261
+ }
29262
+ logEvent("memory_org_pull", org.ok ? { status: org.status, received: org.received } : { status: "failed", category: org.category ?? null });
29263
+ if (!quiet) {
29264
+ if (!org.ok) printWarn(`Could not refresh the org knowledge mirror: ${org.error}`);
29265
+ else if (org.status === "pulled") printInfo(`Org knowledge: ${org.received} claim(s) mirrored at ${org.dir}.`);
29266
+ else if (org.status === "unchanged") printInfo("Org knowledge mirror is up to date.");
29267
+ }
29268
+ process.exit(0);
29269
+ });
29270
+ memory.command("org").description("List the org knowledge mirrored for this repository's organization").action(async () => {
29271
+ try {
29272
+ process.chdir(repoRoot());
29273
+ } catch {
29274
+ }
29275
+ const dir = orgMirrorDir();
29276
+ if (!dir) {
29277
+ printError("Not a repository with an origin remote, so there is no organization to list.");
29278
+ process.exit(1);
29279
+ }
29280
+ const claims = await readOrgMirror();
29281
+ if (claims.length === 0) {
29282
+ printInfo(`No org knowledge mirrored yet (${dir}). Run \`verity memory pull\`.`);
29283
+ process.exit(0);
29284
+ }
29285
+ for (const c of claims) console.log(`${c.tag_id} ${c.kind.padEnd(11)} ${c.title} \xB7 ${c.origin}`);
29286
+ printInfo(`${claims.length} claim(s) at ${dir}. Demote one with \`verity memory demote <id> --reason "\u2026"\`.`);
29287
+ process.exit(0);
29288
+ });
29289
+ memory.command("demote <claim-id>").description("Demote an org knowledge claim for the whole organization, with a reason").requiredOption("--reason <text>", "Why this claim should no longer be shared (1\u2013500 characters)").action(async (claimId, opts) => {
29290
+ const globals = program2.opts();
29291
+ try {
29292
+ process.chdir(repoRoot());
29293
+ } catch {
29294
+ }
29295
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(claimId)) {
29296
+ printError("The claim id is the `id` in the mirrored file, a uuid. `verity memory org` lists them.");
29297
+ process.exit(1);
29298
+ }
29299
+ const reason = opts.reason.trim();
29300
+ if (reason.length === 0 || reason.length > 500) {
29301
+ printError("--reason is 1 to 500 characters.");
29302
+ process.exit(1);
29303
+ }
29304
+ const tokenResult = await resolveToken(globals.token);
29305
+ if (!tokenResult.ok) {
29306
+ printError(tokenResult.error);
29307
+ process.exit(1);
29308
+ }
29309
+ const urlResult = await resolveServiceUrl(globals.serviceUrl);
29310
+ if (!urlResult.ok) {
29311
+ printError(urlResult.error);
29312
+ process.exit(1);
29313
+ }
29314
+ const res = await apiRequest({
29315
+ method: "POST",
29316
+ path: `/memory/org/claims/${claimId}/demote`,
29317
+ serviceUrl: urlResult.data,
29318
+ token: tokenResult.data.token,
29319
+ body: { reason },
29320
+ verbose: globals.verbose,
29321
+ cmd: "memory-demote",
29322
+ extraHeaders: { "X-Verity-Via": "cli" }
29323
+ });
29324
+ if (!res.ok) {
29325
+ printError(`Could not demote the claim: ${res.error}`);
29326
+ process.exit(1);
29327
+ }
29328
+ printInfo(`Demoted ${claimId} for the whole organization. Restore it from the dashboard if that was wrong.`);
29329
+ try {
29330
+ await pullOrgKnowledge({ serviceUrl: urlResult.data, token: tokenResult.data.token, verbose: globals.verbose, force: true });
29331
+ } catch {
29332
+ }
29333
+ process.exit(0);
29334
+ });
27809
29335
  memory.command("untrack").description("Stop committing .verity/memory/ \u2014 the graph stays on disk and leaves your diffs").action(() => {
27810
29336
  try {
27811
29337
  process.chdir(repoRoot());
@@ -27969,8 +29495,8 @@ function registerTelemetryCommands(program2) {
27969
29495
  }
27970
29496
 
27971
29497
  // src/cli.ts
27972
- program.name("verity").description("CLI for Verity quality gate service").version("0.32.7").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
27973
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.7");
29498
+ program.name("verity").description("CLI for Verity quality gate service").version("0.33.0-experimental.34db3a4").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
29499
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.0-experimental.34db3a4");
27974
29500
  setUserNamedServiceUrl(program.opts().serviceUrl);
27975
29501
  try {
27976
29502
  await foldLegacyLocalCredential();