@codacy/verity-cli 0.29.4-experimental.5fcba03 → 0.29.4-experimental.694bcef

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
@@ -6974,10 +6974,10 @@ var require_resolve_block_map = __commonJS({
6974
6974
  let offset = bm.offset;
6975
6975
  let commentEnd = null;
6976
6976
  for (const collItem of bm.items) {
6977
- const { start, key, sep, value } = collItem;
6977
+ const { start, key, sep: sep2, value } = collItem;
6978
6978
  const keyProps = resolveProps.resolveProps(start, {
6979
6979
  indicator: "explicit-key-ind",
6980
- next: key ?? sep?.[0],
6980
+ next: key ?? sep2?.[0],
6981
6981
  offset,
6982
6982
  onError,
6983
6983
  parentIndent: bm.indent,
@@ -6991,7 +6991,7 @@ var require_resolve_block_map = __commonJS({
6991
6991
  else if ("indent" in key && key.indent !== bm.indent)
6992
6992
  onError(offset, "BAD_INDENT", startColMsg);
6993
6993
  }
6994
- if (!keyProps.anchor && !keyProps.tag && !sep) {
6994
+ if (!keyProps.anchor && !keyProps.tag && !sep2) {
6995
6995
  commentEnd = keyProps.end;
6996
6996
  if (keyProps.comment) {
6997
6997
  if (map.comment)
@@ -7015,7 +7015,7 @@ var require_resolve_block_map = __commonJS({
7015
7015
  ctx.atKey = false;
7016
7016
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
7017
7017
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
7018
- const valueProps = resolveProps.resolveProps(sep ?? [], {
7018
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
7019
7019
  indicator: "map-value-ind",
7020
7020
  next: value,
7021
7021
  offset: keyNode.range[2],
@@ -7031,7 +7031,7 @@ var require_resolve_block_map = __commonJS({
7031
7031
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
7032
7032
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
7033
7033
  }
7034
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep, null, valueProps, onError);
7034
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep2, null, valueProps, onError);
7035
7035
  if (ctx.schema.compat)
7036
7036
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
7037
7037
  offset = valueNode.range[2];
@@ -7122,7 +7122,7 @@ var require_resolve_end = __commonJS({
7122
7122
  let comment = "";
7123
7123
  if (end) {
7124
7124
  let hasSpace = false;
7125
- let sep = "";
7125
+ let sep2 = "";
7126
7126
  for (const token of end) {
7127
7127
  const { source, type } = token;
7128
7128
  switch (type) {
@@ -7136,13 +7136,13 @@ var require_resolve_end = __commonJS({
7136
7136
  if (!comment)
7137
7137
  comment = cb;
7138
7138
  else
7139
- comment += sep + cb;
7140
- sep = "";
7139
+ comment += sep2 + cb;
7140
+ sep2 = "";
7141
7141
  break;
7142
7142
  }
7143
7143
  case "newline":
7144
7144
  if (comment)
7145
- sep += source;
7145
+ sep2 += source;
7146
7146
  hasSpace = true;
7147
7147
  break;
7148
7148
  default:
@@ -7185,18 +7185,18 @@ var require_resolve_flow_collection = __commonJS({
7185
7185
  let offset = fc.offset + fc.start.source.length;
7186
7186
  for (let i = 0; i < fc.items.length; ++i) {
7187
7187
  const collItem = fc.items[i];
7188
- const { start, key, sep, value } = collItem;
7188
+ const { start, key, sep: sep2, value } = collItem;
7189
7189
  const props = resolveProps.resolveProps(start, {
7190
7190
  flow: fcName,
7191
7191
  indicator: "explicit-key-ind",
7192
- next: key ?? sep?.[0],
7192
+ next: key ?? sep2?.[0],
7193
7193
  offset,
7194
7194
  onError,
7195
7195
  parentIndent: fc.indent,
7196
7196
  startOnNewline: false
7197
7197
  });
7198
7198
  if (!props.found) {
7199
- if (!props.anchor && !props.tag && !sep && !value) {
7199
+ if (!props.anchor && !props.tag && !sep2 && !value) {
7200
7200
  if (i === 0 && props.comma)
7201
7201
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
7202
7202
  else if (i < fc.items.length - 1)
@@ -7250,8 +7250,8 @@ var require_resolve_flow_collection = __commonJS({
7250
7250
  }
7251
7251
  }
7252
7252
  }
7253
- if (!isMap && !sep && !props.found) {
7254
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError);
7253
+ if (!isMap && !sep2 && !props.found) {
7254
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep2, null, props, onError);
7255
7255
  coll.items.push(valueNode);
7256
7256
  offset = valueNode.range[2];
7257
7257
  if (isBlock(value))
@@ -7263,7 +7263,7 @@ var require_resolve_flow_collection = __commonJS({
7263
7263
  if (isBlock(key))
7264
7264
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
7265
7265
  ctx.atKey = false;
7266
- const valueProps = resolveProps.resolveProps(sep ?? [], {
7266
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
7267
7267
  flow: fcName,
7268
7268
  indicator: "map-value-ind",
7269
7269
  next: value,
@@ -7274,8 +7274,8 @@ var require_resolve_flow_collection = __commonJS({
7274
7274
  });
7275
7275
  if (valueProps.found) {
7276
7276
  if (!isMap && !props.found && ctx.options.strict) {
7277
- if (sep)
7278
- for (const st of sep) {
7277
+ if (sep2)
7278
+ for (const st of sep2) {
7279
7279
  if (st === valueProps.found)
7280
7280
  break;
7281
7281
  if (st.type === "newline") {
@@ -7292,7 +7292,7 @@ var require_resolve_flow_collection = __commonJS({
7292
7292
  else
7293
7293
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
7294
7294
  }
7295
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) : null;
7295
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep2, null, valueProps, onError) : null;
7296
7296
  if (valueNode) {
7297
7297
  if (isBlock(value))
7298
7298
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -7472,7 +7472,7 @@ var require_resolve_block_scalar = __commonJS({
7472
7472
  chompStart = i + 1;
7473
7473
  }
7474
7474
  let value = "";
7475
- let sep = "";
7475
+ let sep2 = "";
7476
7476
  let prevMoreIndented = false;
7477
7477
  for (let i = 0; i < contentStart; ++i)
7478
7478
  value += lines[i][0].slice(trimIndent) + "\n";
@@ -7489,24 +7489,24 @@ var require_resolve_block_scalar = __commonJS({
7489
7489
  indent = "";
7490
7490
  }
7491
7491
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
7492
- value += sep + indent.slice(trimIndent) + content;
7493
- sep = "\n";
7492
+ value += sep2 + indent.slice(trimIndent) + content;
7493
+ sep2 = "\n";
7494
7494
  } else if (indent.length > trimIndent || content[0] === " ") {
7495
- if (sep === " ")
7496
- sep = "\n";
7497
- else if (!prevMoreIndented && sep === "\n")
7498
- sep = "\n\n";
7499
- value += sep + indent.slice(trimIndent) + content;
7500
- sep = "\n";
7495
+ if (sep2 === " ")
7496
+ sep2 = "\n";
7497
+ else if (!prevMoreIndented && sep2 === "\n")
7498
+ sep2 = "\n\n";
7499
+ value += sep2 + indent.slice(trimIndent) + content;
7500
+ sep2 = "\n";
7501
7501
  prevMoreIndented = true;
7502
7502
  } else if (content === "") {
7503
- if (sep === "\n")
7503
+ if (sep2 === "\n")
7504
7504
  value += "\n";
7505
7505
  else
7506
- sep = "\n";
7506
+ sep2 = "\n";
7507
7507
  } else {
7508
- value += sep + content;
7509
- sep = " ";
7508
+ value += sep2 + content;
7509
+ sep2 = " ";
7510
7510
  prevMoreIndented = false;
7511
7511
  }
7512
7512
  }
@@ -7684,25 +7684,25 @@ var require_resolve_flow_scalar = __commonJS({
7684
7684
  if (!match)
7685
7685
  return source;
7686
7686
  let res = match[1];
7687
- let sep = " ";
7687
+ let sep2 = " ";
7688
7688
  let pos = first.lastIndex;
7689
7689
  line.lastIndex = pos;
7690
7690
  while (match = line.exec(source)) {
7691
7691
  if (match[1] === "") {
7692
- if (sep === "\n")
7693
- res += sep;
7692
+ if (sep2 === "\n")
7693
+ res += sep2;
7694
7694
  else
7695
- sep = "\n";
7695
+ sep2 = "\n";
7696
7696
  } else {
7697
- res += sep + match[1];
7698
- sep = " ";
7697
+ res += sep2 + match[1];
7698
+ sep2 = " ";
7699
7699
  }
7700
7700
  pos = line.lastIndex;
7701
7701
  }
7702
7702
  const last = /[ \t]*(.*)/sy;
7703
7703
  last.lastIndex = pos;
7704
7704
  match = last.exec(source);
7705
- return res + sep + (match?.[1] ?? "");
7705
+ return res + sep2 + (match?.[1] ?? "");
7706
7706
  }
7707
7707
  function doubleQuotedValue(source, onError) {
7708
7708
  let res = "";
@@ -8509,14 +8509,14 @@ var require_cst_stringify = __commonJS({
8509
8509
  }
8510
8510
  }
8511
8511
  }
8512
- function stringifyItem({ start, key, sep, value }) {
8512
+ function stringifyItem({ start, key, sep: sep2, value }) {
8513
8513
  let res = "";
8514
8514
  for (const st of start)
8515
8515
  res += st.source;
8516
8516
  if (key)
8517
8517
  res += stringifyToken(key);
8518
- if (sep)
8519
- for (const st of sep)
8518
+ if (sep2)
8519
+ for (const st of sep2)
8520
8520
  res += st.source;
8521
8521
  if (value)
8522
8522
  res += stringifyToken(value);
@@ -9659,18 +9659,18 @@ var require_parser = __commonJS({
9659
9659
  if (this.type === "map-value-ind") {
9660
9660
  const prev = getPrevProps(this.peek(2));
9661
9661
  const start = getFirstKeyStartProps(prev);
9662
- let sep;
9662
+ let sep2;
9663
9663
  if (scalar.end) {
9664
- sep = scalar.end;
9665
- sep.push(this.sourceToken);
9664
+ sep2 = scalar.end;
9665
+ sep2.push(this.sourceToken);
9666
9666
  delete scalar.end;
9667
9667
  } else
9668
- sep = [this.sourceToken];
9668
+ sep2 = [this.sourceToken];
9669
9669
  const map = {
9670
9670
  type: "block-map",
9671
9671
  offset: scalar.offset,
9672
9672
  indent: scalar.indent,
9673
- items: [{ start, key: scalar, sep }]
9673
+ items: [{ start, key: scalar, sep: sep2 }]
9674
9674
  };
9675
9675
  this.onKeyLine = true;
9676
9676
  this.stack[this.stack.length - 1] = map;
@@ -9822,15 +9822,15 @@ var require_parser = __commonJS({
9822
9822
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
9823
9823
  const start2 = getFirstKeyStartProps(it.start);
9824
9824
  const key = it.key;
9825
- const sep = it.sep;
9826
- sep.push(this.sourceToken);
9825
+ const sep2 = it.sep;
9826
+ sep2.push(this.sourceToken);
9827
9827
  delete it.key;
9828
9828
  delete it.sep;
9829
9829
  this.stack.push({
9830
9830
  type: "block-map",
9831
9831
  offset: this.offset,
9832
9832
  indent: this.indent,
9833
- items: [{ start: start2, key, sep }]
9833
+ items: [{ start: start2, key, sep: sep2 }]
9834
9834
  });
9835
9835
  } else if (start.length > 0) {
9836
9836
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -10024,13 +10024,13 @@ var require_parser = __commonJS({
10024
10024
  const prev = getPrevProps(parent);
10025
10025
  const start = getFirstKeyStartProps(prev);
10026
10026
  fixFlowSeqItems(fc);
10027
- const sep = fc.end.splice(1, fc.end.length);
10028
- sep.push(this.sourceToken);
10027
+ const sep2 = fc.end.splice(1, fc.end.length);
10028
+ sep2.push(this.sourceToken);
10029
10029
  const map = {
10030
10030
  type: "block-map",
10031
10031
  offset: fc.offset,
10032
10032
  indent: fc.indent,
10033
- items: [{ start, key: fc, sep }]
10033
+ items: [{ start, key: fc, sep: sep2 }]
10034
10034
  };
10035
10035
  this.onKeyLine = true;
10036
10036
  this.stack[this.stack.length - 1] = map;
@@ -10390,11 +10390,11 @@ var MAX_DELTA_BYTES = 194560;
10390
10390
  var MAX_FILES = 40;
10391
10391
  var MAX_FILE_BYTES = 51200;
10392
10392
  var DEBOUNCE_SECONDS = 30;
10393
- var MAX_SPEC_FILES = 10;
10394
- var MAX_SPEC_FILE_BYTES = 10240;
10395
- var MAX_TOTAL_SPEC_BYTES = 30720;
10393
+ var MAX_SPEC_FILES = 6;
10394
+ var MAX_SPEC_FILE_BYTES = 512e3;
10395
+ var MAX_TOTAL_SPEC_BYTES = 512e3;
10396
10396
  var MAX_PLAN_FILES = 3;
10397
- var MAX_PLAN_FILE_BYTES = 10240;
10397
+ var MAX_PLAN_FILE_BYTES = 512e3;
10398
10398
  var MAX_INTENT_CHARS = 2e3;
10399
10399
  var SNAPSHOT_DIR = `${VERITY_DIR}/.snapshot`;
10400
10400
  var BASELINE_DIR = `${VERITY_DIR}/.baseline`;
@@ -10502,6 +10502,7 @@ var GITHUB_APP_INSTALL_URL = `https://github.com/apps/${GITHUB_APP_SLUG}/install
10502
10502
  function githubAppInstallUrl(accountId) {
10503
10503
  return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
10504
10504
  }
10505
+ var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
10505
10506
 
10506
10507
  // src/lib/output.ts
10507
10508
  var RED = "\x1B[0;31m";
@@ -10775,7 +10776,7 @@ async function foldLegacyLocalCredential(remoteArg) {
10775
10776
  const existing = await readGlobalCredential(remote);
10776
10777
  const merged = {
10777
10778
  token: existing?.token ?? local.token,
10778
- serviceUrl: existing?.serviceUrl ?? local.serviceUrl,
10779
+ serviceUrl: existing?.serviceUrl,
10779
10780
  userId: existing?.userId ?? local.userId,
10780
10781
  email: existing?.email ?? local.email
10781
10782
  };
@@ -10803,6 +10804,13 @@ function execGit(cmd) {
10803
10804
  return "";
10804
10805
  }
10805
10806
  }
10807
+ function execGitArgs(args) {
10808
+ try {
10809
+ return (0, import_node_child_process3.execFileSync)("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10810
+ } catch {
10811
+ return "";
10812
+ }
10813
+ }
10806
10814
  function splitLines(s) {
10807
10815
  return s.split("\n").filter((l) => l.length > 0);
10808
10816
  }
@@ -10893,17 +10901,17 @@ function showContentAtRef(ref, repoRelPath) {
10893
10901
  }
10894
10902
  }
10895
10903
  function getPushRangeFiles() {
10896
- const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10904
+ const diff = (range) => splitLines(execGitArgs(["diff", "--name-only", range])).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10897
10905
  const resolvers = [
10898
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{push}") ? "@{push}..HEAD" : null,
10899
- () => execGit("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}") ? "@{upstream}..HEAD" : null,
10906
+ () => execGitArgs(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{push}"]) ? "@{push}..HEAD" : null,
10907
+ () => execGitArgs(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]) ? "@{upstream}..HEAD" : null,
10900
10908
  () => {
10901
- const branch = execGit("git rev-parse --abbrev-ref HEAD");
10902
- return branch && branch !== "HEAD" && execGit(`git rev-parse --verify -q origin/${branch}`) ? `origin/${branch}..HEAD` : null;
10909
+ const branch = execGitArgs(["rev-parse", "--abbrev-ref", "HEAD"]);
10910
+ return branch && branch !== "HEAD" && execGitArgs(["rev-parse", "--verify", "-q", `origin/${branch}`]) ? `origin/${branch}..HEAD` : null;
10903
10911
  }
10904
10912
  ];
10905
- for (const resolve2 of resolvers) {
10906
- const range = resolve2();
10913
+ for (const resolve3 of resolvers) {
10914
+ const range = resolve3();
10907
10915
  if (range) return { files: diff(range), range };
10908
10916
  }
10909
10917
  const baseline = readBaselineSha();
@@ -10917,7 +10925,7 @@ function getPushRangeFiles() {
10917
10925
  function getPushRangeMessages() {
10918
10926
  const { range } = getPushRangeFiles();
10919
10927
  if (!range) return "";
10920
- return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
10928
+ return execGitArgs(["log", range, "--format=%B%x00"]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
10921
10929
  }
10922
10930
  function filterAnalyzable(files) {
10923
10931
  return files.filter((f) => {
@@ -10951,7 +10959,7 @@ function getCurrentBranch() {
10951
10959
  function commitResolves(sha) {
10952
10960
  if (!sha) return false;
10953
10961
  try {
10954
- (0, import_node_child_process3.execSync)(`git merge-base --is-ancestor ${JSON.stringify(sha)} HEAD`, {
10962
+ (0, import_node_child_process3.execFileSync)("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
10955
10963
  stdio: ["pipe", "pipe", "pipe"]
10956
10964
  });
10957
10965
  return true;
@@ -10962,8 +10970,9 @@ function commitResolves(sha) {
10962
10970
  function commitsSincePaths(sha, paths) {
10963
10971
  if (!sha) return null;
10964
10972
  try {
10965
- const scope2 = paths.length > 0 ? ` -- ${paths.slice(0, 50).map((p) => JSON.stringify(p)).join(" ")}` : "";
10966
- const out = (0, import_node_child_process3.execSync)(`git rev-list --count ${JSON.stringify(sha)}..HEAD${scope2}`, {
10973
+ const args = ["rev-list", "--count", `${sha}..HEAD`];
10974
+ if (paths.length > 0) args.push("--", ...paths.slice(0, 50));
10975
+ const out = (0, import_node_child_process3.execFileSync)("git", args, {
10967
10976
  encoding: "utf-8",
10968
10977
  stdio: ["pipe", "pipe", "pipe"]
10969
10978
  }).trim();
@@ -11245,7 +11254,8 @@ async function serviceUrlFromVerityMd() {
11245
11254
  }
11246
11255
  return null;
11247
11256
  }
11248
- async function resolveServiceUrlDetailed(flagUrl) {
11257
+ async function resolveServiceUrlDetailed(flagUrl, opts = {}) {
11258
+ const allowVerityMd = opts.allowVerityMd ?? true;
11249
11259
  if (flagUrl) {
11250
11260
  return { ok: true, data: { url: flagUrl, source: "flag" } };
11251
11261
  }
@@ -11257,9 +11267,11 @@ async function resolveServiceUrlDetailed(flagUrl) {
11257
11267
  if (credsUrl) {
11258
11268
  return { ok: true, data: { url: credsUrl, source: "credentials" } };
11259
11269
  }
11260
- const mdUrl = await serviceUrlFromVerityMd();
11261
- if (mdUrl) {
11262
- return { ok: true, data: { url: mdUrl, source: "verity_md" } };
11270
+ if (allowVerityMd) {
11271
+ const mdUrl = await serviceUrlFromVerityMd();
11272
+ if (mdUrl) {
11273
+ return { ok: true, data: { url: mdUrl, source: "verity_md" } };
11274
+ }
11263
11275
  }
11264
11276
  return {
11265
11277
  ok: false,
@@ -11267,7 +11279,7 @@ async function resolveServiceUrlDetailed(flagUrl) {
11267
11279
  };
11268
11280
  }
11269
11281
  async function resolveServiceUrlForAuth(flagUrl) {
11270
- const strict = await resolveServiceUrlDetailed(flagUrl);
11282
+ const strict = await resolveServiceUrlDetailed(flagUrl, { allowVerityMd: false });
11271
11283
  if (strict.ok) return strict.data;
11272
11284
  return { url: DEFAULT_SERVICE_URL, source: "default" };
11273
11285
  }
@@ -11405,7 +11417,7 @@ var readline = __toESM(require("node:readline/promises"));
11405
11417
  var import_node_os = require("node:os");
11406
11418
 
11407
11419
  // src/lib/provider-auth.ts
11408
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
11420
+ var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
11409
11421
  var form = (fields) => new URLSearchParams(fields).toString();
11410
11422
  async function githubAccountId(owner) {
11411
11423
  try {
@@ -13161,9 +13173,38 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
13161
13173
 
13162
13174
  // src/lib/memory-sync.ts
13163
13175
  var import_promises8 = require("node:fs/promises");
13176
+ var import_node_fs9 = require("node:fs");
13177
+ var import_node_path10 = require("node:path");
13178
+ var import_node_crypto3 = require("node:crypto");
13179
+
13180
+ // src/lib/safe-path.ts
13164
13181
  var import_node_fs8 = require("node:fs");
13165
13182
  var import_node_path9 = require("node:path");
13166
- var import_node_crypto3 = require("node:crypto");
13183
+ function resolveInside(baseDir, candidate) {
13184
+ if (typeof candidate !== "string" || candidate.length === 0) return null;
13185
+ if ((0, import_node_path9.isAbsolute)(candidate)) return null;
13186
+ const baseAbs = (0, import_node_path9.resolve)(baseDir);
13187
+ const full = (0, import_node_path9.resolve)(baseAbs, candidate);
13188
+ const baseSep = baseAbs.endsWith(import_node_path9.sep) ? baseAbs : baseAbs + import_node_path9.sep;
13189
+ if (full !== baseAbs && !full.startsWith(baseSep)) return null;
13190
+ try {
13191
+ if ((0, import_node_fs8.existsSync)(baseAbs)) {
13192
+ const realBase = (0, import_node_fs8.realpathSync)(baseAbs);
13193
+ const realBaseSep = realBase.endsWith(import_node_path9.sep) ? realBase : realBase + import_node_path9.sep;
13194
+ let probe = full;
13195
+ while (!(0, import_node_fs8.existsSync)(probe)) {
13196
+ const parent = (0, import_node_path9.dirname)(probe);
13197
+ if (parent === probe) break;
13198
+ probe = parent;
13199
+ }
13200
+ const realProbe = (0, import_node_fs8.realpathSync)(probe);
13201
+ if (realProbe !== realBase && !realProbe.startsWith(realBaseSep)) return null;
13202
+ }
13203
+ } catch {
13204
+ return null;
13205
+ }
13206
+ return full;
13207
+ }
13167
13208
 
13168
13209
  // src/lib/glob-match.ts
13169
13210
  function globToRegex(glob) {
@@ -13233,32 +13274,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
13233
13274
  async function ensureMemoryDir() {
13234
13275
  await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
13235
13276
  for (const domain of DOMAINS2) {
13236
- await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
13277
+ await (0, import_promises8.mkdir)((0, import_node_path10.join)(memoryDir2(), domain), { recursive: true });
13237
13278
  }
13238
- if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"))) {
13239
- await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
13279
+ if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"))) {
13280
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
13240
13281
  }
13241
- if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "index.md"))) {
13242
- await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
13282
+ if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "index.md"))) {
13283
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
13243
13284
  }
13244
- if (!(0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md"))) {
13245
- await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
13285
+ if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md"))) {
13286
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
13246
13287
  }
13247
13288
  }
13248
13289
  async function buildManifest() {
13249
- if (!(0, import_node_fs8.existsSync)(memoryDir2())) {
13290
+ if (!(0, import_node_fs9.existsSync)(memoryDir2())) {
13250
13291
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
13251
13292
  }
13252
13293
  const nodes = [];
13253
13294
  for (const domain of DOMAINS2) {
13254
- const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
13255
- if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
13295
+ const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13296
+ if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13256
13297
  try {
13257
13298
  const files = await (0, import_promises8.readdir)(domainDir);
13258
13299
  for (const file of files) {
13259
13300
  if (!file.endsWith(".md")) continue;
13260
13301
  const filePath = `${domain}/${file}`;
13261
- const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
13302
+ const fullPath = (0, import_node_path10.join)(memoryDir2(), filePath);
13262
13303
  try {
13263
13304
  const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
13264
13305
  const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
@@ -13271,13 +13312,13 @@ async function buildManifest() {
13271
13312
  }
13272
13313
  let indexHash = null;
13273
13314
  try {
13274
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
13315
+ const indexContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "index.md"), "utf-8");
13275
13316
  indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
13276
13317
  } catch {
13277
13318
  }
13278
13319
  let logLength = 0;
13279
13320
  try {
13280
- const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
13321
+ const logContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8");
13281
13322
  logLength = logContent.split("\n").length;
13282
13323
  } catch {
13283
13324
  }
@@ -13288,15 +13329,15 @@ function hashContent(content) {
13288
13329
  }
13289
13330
  async function readOnDiskNodes() {
13290
13331
  const out = /* @__PURE__ */ new Map();
13291
- if (!(0, import_node_fs8.existsSync)(memoryDir2())) return out;
13332
+ if (!(0, import_node_fs9.existsSync)(memoryDir2())) return out;
13292
13333
  for (const domain of DOMAINS2) {
13293
- const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
13294
- if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
13334
+ const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13335
+ if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13295
13336
  try {
13296
13337
  for (const file of await (0, import_promises8.readdir)(domainDir)) {
13297
13338
  if (!file.endsWith(".md")) continue;
13298
13339
  try {
13299
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
13340
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8")));
13300
13341
  } catch {
13301
13342
  }
13302
13343
  }
@@ -13342,8 +13383,8 @@ async function computeEditedNodeUploads() {
13342
13383
  const uploads = [];
13343
13384
  for (const [path, prevHash] of prev) {
13344
13385
  if (prevHash == null) continue;
13345
- const full = (0, import_node_path9.join)(memoryDir2(), path);
13346
- if (!(0, import_node_fs8.existsSync)(full)) continue;
13386
+ const full = (0, import_node_path10.join)(memoryDir2(), path);
13387
+ if (!(0, import_node_fs9.existsSync)(full)) continue;
13347
13388
  let content;
13348
13389
  try {
13349
13390
  content = await (0, import_promises8.readFile)(full, "utf-8");
@@ -13379,16 +13420,19 @@ async function applyMemoryWrites(writes, opts = {}) {
13379
13420
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
13380
13421
  for (const n of notes) logLines.push(` - ${n}`);
13381
13422
  try {
13382
- const existing = (0, import_node_fs8.existsSync)((0, import_node_path9.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
13383
- await (0, import_promises8.writeFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
13423
+ const existing = (0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
13424
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
13384
13425
  } catch {
13385
13426
  }
13386
13427
  await recordSyncedNodePaths();
13387
13428
  return count;
13388
13429
  }
13389
13430
  async function applyOneWrite(write, treePaths) {
13390
- const fullPath = (0, import_node_path9.join)(memoryDir2(), write.path);
13431
+ const fullPath = resolveInside(memoryDir2(), write.path);
13391
13432
  const notes = [];
13433
+ if (!fullPath) {
13434
+ return { written: false, notes: [`${String(write.path)}: rejected \u2014 path escapes the memory directory`] };
13435
+ }
13392
13436
  let content = write.content;
13393
13437
  if (treePaths && treePaths.length > 0) {
13394
13438
  const grounded = groundFileGlobs(content, treePaths);
@@ -13397,7 +13441,7 @@ async function applyOneWrite(write, treePaths) {
13397
13441
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
13398
13442
  }
13399
13443
  }
13400
- if ((0, import_node_fs8.existsSync)(fullPath)) {
13444
+ if ((0, import_node_fs9.existsSync)(fullPath)) {
13401
13445
  let existing = "";
13402
13446
  try {
13403
13447
  existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
@@ -13409,7 +13453,7 @@ async function applyOneWrite(write, treePaths) {
13409
13453
  return { written: false, notes };
13410
13454
  }
13411
13455
  }
13412
- await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
13456
+ await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(fullPath), { recursive: true });
13413
13457
  await (0, import_promises8.writeFile)(fullPath, content);
13414
13458
  return { written: true, notes };
13415
13459
  }
@@ -13450,8 +13494,8 @@ async function regenerateIndex() {
13450
13494
  ];
13451
13495
  let totalNodes = 0;
13452
13496
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
13453
- const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
13454
- if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
13497
+ const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13498
+ if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13455
13499
  try {
13456
13500
  const files = await (0, import_promises8.readdir)(domainDir);
13457
13501
  const mdFiles = files.filter((f) => f.endsWith(".md"));
@@ -13461,7 +13505,7 @@ async function regenerateIndex() {
13461
13505
  for (const file of mdFiles.sort()) {
13462
13506
  const slug = file.replace(/\.md$/, "");
13463
13507
  try {
13464
- const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
13508
+ const content = await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8");
13465
13509
  const title = pickFrontmatter(content, "title") ?? slug;
13466
13510
  const kind = pickFrontmatter(content, "kind") ?? "-";
13467
13511
  const confidence = pickFrontmatter(content, "confidence");
@@ -13485,7 +13529,7 @@ async function regenerateIndex() {
13485
13529
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
13486
13530
  }
13487
13531
  const next = lines.join("\n") + "\n";
13488
- const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
13532
+ const indexPath = (0, import_node_path10.join)(memoryDir2(), "index.md");
13489
13533
  let existing = null;
13490
13534
  try {
13491
13535
  existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
@@ -13567,9 +13611,9 @@ function hasLegacyMemoryBlock(text) {
13567
13611
  return findMarker(text, LEGACY_MD_START) !== -1;
13568
13612
  }
13569
13613
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
13570
- const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
13614
+ const claudeMdPath = (0, import_node_path10.join)(cwd, "CLAUDE.md");
13571
13615
  let existing = "";
13572
- if ((0, import_node_fs8.existsSync)(claudeMdPath)) {
13616
+ if ((0, import_node_fs9.existsSync)(claudeMdPath)) {
13573
13617
  existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
13574
13618
  }
13575
13619
  let startTag = CLAUDE_MD_START;
@@ -13699,9 +13743,9 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
13699
13743
  `;
13700
13744
 
13701
13745
  // src/lib/dossier-session.ts
13702
- var import_node_fs13 = require("node:fs");
13746
+ var import_node_fs14 = require("node:fs");
13703
13747
  var import_node_crypto7 = require("node:crypto");
13704
- var import_node_path12 = require("node:path");
13748
+ var import_node_path13 = require("node:path");
13705
13749
 
13706
13750
  // src/lib/skip-detection.ts
13707
13751
  function isBareAckPrompt(prompt) {
@@ -13865,8 +13909,8 @@ var DEFAULT_MEMORY_BUDGET_BYTES = 4096;
13865
13909
 
13866
13910
  // src/lib/dossier/log.ts
13867
13911
  var import_node_crypto4 = require("node:crypto");
13868
- var import_node_fs9 = require("node:fs");
13869
- var import_node_path10 = require("node:path");
13912
+ var import_node_fs10 = require("node:fs");
13913
+ var import_node_path11 = require("node:path");
13870
13914
  var CRC_TABLE = (() => {
13871
13915
  const t = new Int32Array(256);
13872
13916
  for (let i = 0; i < 256; i++) {
@@ -13885,13 +13929,13 @@ function crc32(s) {
13885
13929
  function openDossier(identity) {
13886
13930
  try {
13887
13931
  const dir = dossierDir(identity);
13888
- (0, import_node_fs9.mkdirSync)(dir, { recursive: true, mode: 448 });
13932
+ (0, import_node_fs10.mkdirSync)(dir, { recursive: true, mode: 448 });
13889
13933
  return {
13890
13934
  dir,
13891
13935
  identity,
13892
- eventsPath: (0, import_node_path10.join)(dir, "events.jsonl"),
13893
- foldPath: (0, import_node_path10.join)(dir, "fold.json"),
13894
- rotatedDir: (0, import_node_path10.join)(dir, "rotated")
13936
+ eventsPath: (0, import_node_path11.join)(dir, "events.jsonl"),
13937
+ foldPath: (0, import_node_path11.join)(dir, "fold.json"),
13938
+ rotatedDir: (0, import_node_path11.join)(dir, "rotated")
13895
13939
  };
13896
13940
  } catch {
13897
13941
  return null;
@@ -13953,7 +13997,7 @@ function appendEvent(d, ev) {
13953
13997
  at: ev.at ?? (/* @__PURE__ */ new Date()).toISOString(),
13954
13998
  ...ev
13955
13999
  });
13956
- (0, import_node_fs9.appendFileSync)(d.eventsPath, line, { mode: 384 });
14000
+ (0, import_node_fs10.appendFileSync)(d.eventsPath, line, { mode: 384 });
13957
14001
  return true;
13958
14002
  } catch {
13959
14003
  return false;
@@ -13961,14 +14005,14 @@ function appendEvent(d, ev) {
13961
14005
  }
13962
14006
  function rotateIfNeeded2(d) {
13963
14007
  try {
13964
- if (!(0, import_node_fs9.existsSync)(d.eventsPath)) return;
13965
- if ((0, import_node_fs9.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
13966
- (0, import_node_fs9.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
13967
- (0, import_node_fs9.renameSync)(d.eventsPath, (0, import_node_path10.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
13968
- const kept = (0, import_node_fs9.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14008
+ if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return;
14009
+ if ((0, import_node_fs10.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
14010
+ (0, import_node_fs10.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
14011
+ (0, import_node_fs10.renameSync)(d.eventsPath, (0, import_node_path11.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
14012
+ const kept = (0, import_node_fs10.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
13969
14013
  for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
13970
14014
  try {
13971
- (0, import_node_fs9.renameSync)((0, import_node_path10.join)(d.rotatedDir, stale), (0, import_node_path10.join)(d.rotatedDir, `${stale}.pruned`));
14015
+ (0, import_node_fs10.renameSync)((0, import_node_path11.join)(d.rotatedDir, stale), (0, import_node_path11.join)(d.rotatedDir, `${stale}.pruned`));
13972
14016
  } catch {
13973
14017
  }
13974
14018
  }
@@ -13978,8 +14022,8 @@ function rotateIfNeeded2(d) {
13978
14022
 
13979
14023
  // src/lib/dossier/fold-dossier.ts
13980
14024
  var import_node_crypto5 = require("node:crypto");
13981
- var import_node_fs10 = require("node:fs");
13982
- var import_node_path11 = require("node:path");
14025
+ var import_node_fs11 = require("node:fs");
14026
+ var import_node_path12 = require("node:path");
13983
14027
  var EMPTY_CAPABILITIES = () => ({
13984
14028
  human_reachable: { value: "unknown", tier: "unknown" },
13985
14029
  authorship_observability: { value: "unknown", tier: "unknown" },
@@ -14030,12 +14074,12 @@ function foldDossier(d, opts = {}) {
14030
14074
  }
14031
14075
  };
14032
14076
  try {
14033
- if ((0, import_node_fs10.existsSync)(d.rotatedDir)) {
14034
- const files = (0, import_node_fs10.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14077
+ if ((0, import_node_fs11.existsSync)(d.rotatedDir)) {
14078
+ const files = (0, import_node_fs11.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14035
14079
  state.meta.rotations = files.length;
14036
14080
  for (const f of files) {
14037
14081
  try {
14038
- ingest((0, import_node_fs10.readFileSync)((0, import_node_path11.join)(d.rotatedDir, f), "utf8"));
14082
+ ingest((0, import_node_fs11.readFileSync)((0, import_node_path12.join)(d.rotatedDir, f), "utf8"));
14039
14083
  } catch {
14040
14084
  state.meta.dropped_lines++;
14041
14085
  }
@@ -14044,9 +14088,9 @@ function foldDossier(d, opts = {}) {
14044
14088
  } catch {
14045
14089
  }
14046
14090
  try {
14047
- if ((0, import_node_fs10.existsSync)(d.eventsPath)) {
14048
- state.meta.upto_offset = (0, import_node_fs10.statSync)(d.eventsPath).size;
14049
- ingest((0, import_node_fs10.readFileSync)(d.eventsPath, "utf8"));
14091
+ if ((0, import_node_fs11.existsSync)(d.eventsPath)) {
14092
+ state.meta.upto_offset = (0, import_node_fs11.statSync)(d.eventsPath).size;
14093
+ ingest((0, import_node_fs11.readFileSync)(d.eventsPath, "utf8"));
14050
14094
  }
14051
14095
  } catch {
14052
14096
  }
@@ -14316,7 +14360,7 @@ function applyBounds(state, input) {
14316
14360
  }
14317
14361
 
14318
14362
  // src/lib/dossier/cache.ts
14319
- var import_node_fs11 = require("node:fs");
14363
+ var import_node_fs12 = require("node:fs");
14320
14364
  function compactState(s) {
14321
14365
  const ms = (iso) => Date.parse(iso) || 0;
14322
14366
  return {
@@ -14445,20 +14489,20 @@ function encodeState(s) {
14445
14489
  function writeFoldCache(d, state) {
14446
14490
  try {
14447
14491
  const tmp = `${d.foldPath}.${process.pid}.tmp`;
14448
- (0, import_node_fs11.writeFileSync)(tmp, encodeState(state), { mode: 384 });
14449
- (0, import_node_fs11.renameSync)(tmp, d.foldPath);
14492
+ (0, import_node_fs12.writeFileSync)(tmp, encodeState(state), { mode: 384 });
14493
+ (0, import_node_fs12.renameSync)(tmp, d.foldPath);
14450
14494
  } catch {
14451
14495
  }
14452
14496
  }
14453
14497
  function readFoldCache(d) {
14454
14498
  try {
14455
- if (!(0, import_node_fs11.existsSync)(d.foldPath)) return null;
14456
- const raw = JSON.parse((0, import_node_fs11.readFileSync)(d.foldPath, "utf8"));
14499
+ if (!(0, import_node_fs12.existsSync)(d.foldPath)) return null;
14500
+ const raw = JSON.parse((0, import_node_fs12.readFileSync)(d.foldPath, "utf8"));
14457
14501
  if (raw?.v !== 1) return null;
14458
14502
  const cached2 = expandState(raw);
14459
14503
  if (!cached2?.meta) return null;
14460
- const size = (0, import_node_fs11.existsSync)(d.eventsPath) ? (0, import_node_fs11.statSync)(d.eventsPath).size : 0;
14461
- const rotations = (0, import_node_fs11.existsSync)(d.rotatedDir) ? (0, import_node_fs11.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).length : 0;
14504
+ const size = (0, import_node_fs12.existsSync)(d.eventsPath) ? (0, import_node_fs12.statSync)(d.eventsPath).size : 0;
14505
+ const rotations = (0, import_node_fs12.existsSync)(d.rotatedDir) ? (0, import_node_fs12.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).length : 0;
14462
14506
  if (cached2.meta.upto_offset !== size || cached2.meta.rotations !== rotations) return null;
14463
14507
  return cached2;
14464
14508
  } catch {
@@ -14509,13 +14553,13 @@ function assessContinuity(i) {
14509
14553
 
14510
14554
  // src/lib/dossier/reanchor.ts
14511
14555
  var import_node_crypto6 = require("node:crypto");
14512
- var import_node_fs12 = require("node:fs");
14556
+ var import_node_fs13 = require("node:fs");
14513
14557
  function lineSha(text) {
14514
14558
  return (0, import_node_crypto6.createHash)("sha256").update(text.trim()).digest("hex").slice(0, HASH_WIDTH);
14515
14559
  }
14516
14560
  function fileHash(path) {
14517
14561
  try {
14518
- return (0, import_node_crypto6.createHash)("sha256").update((0, import_node_fs12.readFileSync)(path)).digest("hex").slice(0, HASH_WIDTH);
14562
+ return (0, import_node_crypto6.createHash)("sha256").update((0, import_node_fs13.readFileSync)(path)).digest("hex").slice(0, HASH_WIDTH);
14519
14563
  } catch {
14520
14564
  return null;
14521
14565
  }
@@ -14887,20 +14931,20 @@ function foreignAuthoredPaths(identity, opts = {}) {
14887
14931
  let sessions = 0;
14888
14932
  try {
14889
14933
  const dir = treeDir(identity);
14890
- if (!(0, import_node_fs13.existsSync)(dir)) return { paths: [], sessions: 0 };
14891
- for (const entry of (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true })) {
14934
+ if (!(0, import_node_fs14.existsSync)(dir)) return { paths: [], sessions: 0 };
14935
+ for (const entry of (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true })) {
14892
14936
  if (!entry.isDirectory()) continue;
14893
14937
  if (entry.name === identity.sessionKey) continue;
14894
- const log = (0, import_node_path12.join)(dir, entry.name, "events.jsonl");
14938
+ const log = (0, import_node_path13.join)(dir, entry.name, "events.jsonl");
14895
14939
  try {
14896
- if (!(0, import_node_fs13.existsSync)(log)) continue;
14897
- if (now - (0, import_node_fs13.statSync)(log).mtimeMs > windowMs) continue;
14940
+ if (!(0, import_node_fs14.existsSync)(log)) continue;
14941
+ if (now - (0, import_node_fs14.statSync)(log).mtimeMs > windowMs) continue;
14898
14942
  const sib = {
14899
- dir: (0, import_node_path12.join)(dir, entry.name),
14943
+ dir: (0, import_node_path13.join)(dir, entry.name),
14900
14944
  identity,
14901
14945
  eventsPath: log,
14902
- foldPath: (0, import_node_path12.join)(dir, entry.name, "fold.json"),
14903
- rotatedDir: (0, import_node_path12.join)(dir, entry.name, "rotated")
14946
+ foldPath: (0, import_node_path13.join)(dir, entry.name, "fold.json"),
14947
+ rotatedDir: (0, import_node_path13.join)(dir, entry.name, "rotated")
14904
14948
  };
14905
14949
  const state = readFoldCache(sib) ?? foldDossier(sib);
14906
14950
  sessions++;
@@ -14928,25 +14972,25 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
14928
14972
  let removed = 0;
14929
14973
  try {
14930
14974
  const mine = dossierDir(identity);
14931
- const userDir = (0, import_node_path12.dirname)((0, import_node_path12.dirname)(mine));
14932
- if (!(0, import_node_fs13.existsSync)(userDir)) return 0;
14975
+ const userDir = (0, import_node_path13.dirname)((0, import_node_path13.dirname)(mine));
14976
+ if (!(0, import_node_fs14.existsSync)(userDir)) return 0;
14933
14977
  const cutoff = Date.now() - maxAgeMs;
14934
- for (const tree of (0, import_node_fs13.readdirSync)(userDir, { withFileTypes: true })) {
14978
+ for (const tree of (0, import_node_fs14.readdirSync)(userDir, { withFileTypes: true })) {
14935
14979
  if (!tree.isDirectory()) continue;
14936
- const treePath = (0, import_node_path12.join)(userDir, tree.name);
14980
+ const treePath = (0, import_node_path13.join)(userDir, tree.name);
14937
14981
  let live = 0;
14938
- for (const entry of (0, import_node_fs13.readdirSync)(treePath, { withFileTypes: true })) {
14982
+ for (const entry of (0, import_node_fs14.readdirSync)(treePath, { withFileTypes: true })) {
14939
14983
  if (!entry.isDirectory()) continue;
14940
- const dir = (0, import_node_path12.join)(treePath, entry.name);
14984
+ const dir = (0, import_node_path13.join)(treePath, entry.name);
14941
14985
  if (dir === mine) {
14942
14986
  live++;
14943
14987
  continue;
14944
14988
  }
14945
14989
  try {
14946
- const log = (0, import_node_path12.join)(dir, "events.jsonl");
14947
- const at = (0, import_node_fs13.existsSync)(log) ? (0, import_node_fs13.statSync)(log).mtimeMs : (0, import_node_fs13.statSync)(dir).mtimeMs;
14990
+ const log = (0, import_node_path13.join)(dir, "events.jsonl");
14991
+ const at = (0, import_node_fs14.existsSync)(log) ? (0, import_node_fs14.statSync)(log).mtimeMs : (0, import_node_fs14.statSync)(dir).mtimeMs;
14948
14992
  if (at < cutoff) {
14949
- (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
14993
+ (0, import_node_fs14.rmSync)(dir, { recursive: true, force: true });
14950
14994
  removed++;
14951
14995
  } else {
14952
14996
  live++;
@@ -14956,7 +15000,7 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
14956
15000
  }
14957
15001
  if (live === 0) {
14958
15002
  try {
14959
- (0, import_node_fs13.rmSync)(treePath, { recursive: false, force: false });
15003
+ (0, import_node_fs14.rmSync)(treePath, { recursive: false, force: false });
14960
15004
  } catch {
14961
15005
  }
14962
15006
  }
@@ -14973,8 +15017,8 @@ function sessionDossier(token, sessionId) {
14973
15017
  }
14974
15018
  function hasActiveGoal(d) {
14975
15019
  try {
14976
- if (!(0, import_node_fs13.existsSync)(d.eventsPath)) return false;
14977
- return (0, import_node_fs13.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
15020
+ if (!(0, import_node_fs14.existsSync)(d.eventsPath)) return false;
15021
+ return (0, import_node_fs14.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
14978
15022
  } catch {
14979
15023
  return false;
14980
15024
  }
@@ -14998,7 +15042,7 @@ function recordTurn(d, t) {
14998
15042
  for (const a of t.authored) {
14999
15043
  const origin = a.owner === "subagent" ? "subagent" : "edit_tool";
15000
15044
  const prior = t.known?.authored?.get(a.p);
15001
- const hash = fileHash((0, import_node_path12.join)(root, a.p));
15045
+ const hash = fileHash((0, import_node_path13.join)(root, a.p));
15002
15046
  const hunks = Math.max(0, a.h - (prior?.hunks ?? 0));
15003
15047
  const adds = Math.max(0, a.a - (prior?.adds ?? 0));
15004
15048
  const dels = Math.max(0, a.d - (prior?.dels ?? 0));
@@ -15031,7 +15075,7 @@ function recordTurn(d, t) {
15031
15075
  }
15032
15076
  const seenDivergence = t.known?.divergence ?? /* @__PURE__ */ new Set();
15033
15077
  for (const u of t.unobserved) {
15034
- const hash = fileHash((0, import_node_path12.join)(root, u.p));
15078
+ const hash = fileHash((0, import_node_path13.join)(root, u.p));
15035
15079
  if (seenDivergence.has(divergenceKey(u.p, hash))) continue;
15036
15080
  appendEvent(d, { k: "divergence", kind: "external_mutation", path: u.p, hash });
15037
15081
  }
@@ -15060,12 +15104,14 @@ function recordVerdict(d, v) {
15060
15104
  if (summary) appendEvent(d, { k: "goal_delivered", summary });
15061
15105
  }
15062
15106
  const lines = /* @__PURE__ */ new Map();
15107
+ const sent = new Set(v.sentPaths);
15063
15108
  for (const f of v.findings) {
15064
15109
  if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
15110
+ if (!sent.has(f.file)) continue;
15065
15111
  if (!lines.has(f.file)) {
15066
15112
  try {
15067
- const abs = (0, import_node_path12.join)(root, f.file);
15068
- lines.set(f.file, (0, import_node_fs13.existsSync)(abs) ? (0, import_node_fs13.readFileSync)(abs, "utf8").split("\n") : null);
15113
+ const abs = (0, import_node_path13.join)(root, f.file);
15114
+ lines.set(f.file, (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null);
15069
15115
  } catch {
15070
15116
  lines.set(f.file, null);
15071
15117
  }
@@ -15165,8 +15211,8 @@ function recallMemory(d, identity, opts) {
15165
15211
  budgetBytes: opts.budgetBytes,
15166
15212
  readFileLines: (file) => {
15167
15213
  try {
15168
- const abs = (0, import_node_path12.join)(root, file);
15169
- return (0, import_node_fs13.existsSync)(abs) ? (0, import_node_fs13.readFileSync)(abs, "utf8").split("\n") : null;
15214
+ const abs = (0, import_node_path13.join)(root, file);
15215
+ return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
15170
15216
  } catch {
15171
15217
  return null;
15172
15218
  }
@@ -15304,22 +15350,22 @@ async function fireClassify(prompt, sessionId, globals) {
15304
15350
  }
15305
15351
 
15306
15352
  // src/commands/lifecycle.ts
15307
- var import_node_fs17 = require("node:fs");
15308
- var import_node_path16 = require("node:path");
15353
+ var import_node_fs18 = require("node:fs");
15354
+ var import_node_path17 = require("node:path");
15309
15355
 
15310
15356
  // src/lib/baseline.ts
15311
- var import_node_fs16 = require("node:fs");
15312
- var import_node_path15 = require("node:path");
15357
+ var import_node_fs17 = require("node:fs");
15358
+ var import_node_path16 = require("node:path");
15313
15359
  var import_node_crypto9 = require("node:crypto");
15314
15360
 
15315
15361
  // src/lib/snapshot.ts
15316
- var import_node_fs15 = require("node:fs");
15317
- var import_node_path14 = require("node:path");
15362
+ var import_node_fs16 = require("node:fs");
15363
+ var import_node_path15 = require("node:path");
15318
15364
  var import_node_child_process6 = require("node:child_process");
15319
15365
 
15320
15366
  // src/lib/files.ts
15321
- var import_node_fs14 = require("node:fs");
15322
- var import_node_path13 = require("node:path");
15367
+ var import_node_fs15 = require("node:fs");
15368
+ var import_node_path14 = require("node:path");
15323
15369
  var LANG_MAP = {
15324
15370
  // Analyzable (static analysis + Gemini)
15325
15371
  ts: "typescript",
@@ -15387,7 +15433,7 @@ var LANG_MAP = {
15387
15433
  mk: "make"
15388
15434
  };
15389
15435
  function detectLanguage(filepath) {
15390
- const ext = (0, import_node_path13.extname)(filepath).slice(1);
15436
+ const ext = (0, import_node_path14.extname)(filepath).slice(1);
15391
15437
  return LANG_MAP[ext] ?? ext;
15392
15438
  }
15393
15439
  function sortByMtime(files) {
@@ -15395,7 +15441,7 @@ function sortByMtime(files) {
15395
15441
  const resolved = resolveFile(f);
15396
15442
  if (!resolved) return null;
15397
15443
  try {
15398
- const stat3 = (0, import_node_fs14.statSync)(resolved);
15444
+ const stat3 = (0, import_node_fs15.statSync)(resolved);
15399
15445
  return { path: f, resolved, mtime: stat3.mtimeMs };
15400
15446
  } catch {
15401
15447
  return null;
@@ -15428,7 +15474,7 @@ function collectCodeDelta(files, opts) {
15428
15474
  }
15429
15475
  let size;
15430
15476
  try {
15431
- size = (0, import_node_fs14.statSync)(resolved).size;
15477
+ size = (0, import_node_fs15.statSync)(resolved).size;
15432
15478
  } catch {
15433
15479
  exclude(filepath, "not-stattable");
15434
15480
  continue;
@@ -15445,7 +15491,7 @@ function collectCodeDelta(files, opts) {
15445
15491
  }
15446
15492
  let content;
15447
15493
  try {
15448
- content = (0, import_node_fs14.readFileSync)(resolved, "utf-8");
15494
+ content = (0, import_node_fs15.readFileSync)(resolved, "utf-8");
15449
15495
  } catch {
15450
15496
  exclude(filepath, "not-readable");
15451
15497
  continue;
@@ -15484,15 +15530,16 @@ function collectCodeDelta(files, opts) {
15484
15530
 
15485
15531
  // src/lib/snapshot.ts
15486
15532
  function generateSnapshotDiffs(files) {
15487
- if (!(0, import_node_fs15.existsSync)(SNAPSHOT_DIR)) {
15533
+ if (!(0, import_node_fs16.existsSync)(SNAPSHOT_DIR)) {
15488
15534
  return { diffs: [], has_snapshots: false };
15489
15535
  }
15490
15536
  const diffs = [];
15491
15537
  for (const file of files) {
15492
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
15538
+ if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
15539
+ const snapshotPath = (0, import_node_path15.join)(SNAPSHOT_DIR, file.path);
15493
15540
  const language = file.language ?? detectLanguage(file.path);
15494
- if ((0, import_node_fs15.existsSync)(snapshotPath)) {
15495
- const oldContent = (0, import_node_fs15.readFileSync)(snapshotPath, "utf-8");
15541
+ if ((0, import_node_fs16.existsSync)(snapshotPath)) {
15542
+ const oldContent = (0, import_node_fs16.readFileSync)(snapshotPath, "utf-8");
15496
15543
  if (oldContent === file.content) continue;
15497
15544
  const diff = computeDiff(oldContent, file.content, file.path);
15498
15545
  if (diff) {
@@ -15513,23 +15560,51 @@ ${addedLines}`,
15513
15560
  }
15514
15561
  return { diffs, has_snapshots: true };
15515
15562
  }
15563
+ function ensureSnapshotGitignored() {
15564
+ let content = "";
15565
+ try {
15566
+ content = (0, import_node_fs16.readFileSync)(".gitignore", "utf-8");
15567
+ } catch {
15568
+ }
15569
+ let ignored = null;
15570
+ try {
15571
+ (0, import_node_child_process6.execSync)("git check-ignore -q -- .verity/.snapshot/__probe__", { stdio: "pipe" });
15572
+ ignored = true;
15573
+ } catch (err) {
15574
+ ignored = err.status === 1 ? false : null;
15575
+ }
15576
+ if (ignored === true) return "covered";
15577
+ if (ignored === null) {
15578
+ const lines = content.split("\n").map((l) => l.trim());
15579
+ const covering = [".verity/.snapshot/", ".verity/.snapshot", ".verity/", ".verity", ".verity/*"];
15580
+ if (lines.some((l) => covering.includes(l))) return "covered";
15581
+ }
15582
+ try {
15583
+ const block = "# Verity \u2014 snapshots of analyzed files (machine state, never commit)\n.verity/.snapshot/\n";
15584
+ (0, import_node_fs16.writeFileSync)(".gitignore", content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block);
15585
+ return "added";
15586
+ } catch {
15587
+ return "failed";
15588
+ }
15589
+ }
15516
15590
  function saveSnapshots(files) {
15517
15591
  const snapshotPaths = /* @__PURE__ */ new Set();
15518
15592
  for (const file of files) {
15519
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
15593
+ if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
15594
+ const snapshotPath = (0, import_node_path15.join)(SNAPSHOT_DIR, file.path);
15520
15595
  snapshotPaths.add(snapshotPath);
15521
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(snapshotPath), { recursive: true });
15522
- (0, import_node_fs15.writeFileSync)(snapshotPath, file.content);
15596
+ (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(snapshotPath), { recursive: true });
15597
+ (0, import_node_fs16.writeFileSync)(snapshotPath, file.content);
15523
15598
  }
15524
15599
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
15525
15600
  }
15526
15601
  function computeDiff(oldContent, newContent, filePath) {
15527
- const tmpOld = (0, import_node_path14.join)(SNAPSHOT_DIR, ".diff-old.tmp");
15528
- const tmpNew = (0, import_node_path14.join)(SNAPSHOT_DIR, ".diff-new.tmp");
15602
+ const tmpOld = (0, import_node_path15.join)(SNAPSHOT_DIR, ".diff-old.tmp");
15603
+ const tmpNew = (0, import_node_path15.join)(SNAPSHOT_DIR, ".diff-new.tmp");
15529
15604
  try {
15530
- (0, import_node_fs15.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
15531
- (0, import_node_fs15.writeFileSync)(tmpOld, oldContent);
15532
- (0, import_node_fs15.writeFileSync)(tmpNew, newContent);
15605
+ (0, import_node_fs16.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
15606
+ (0, import_node_fs16.writeFileSync)(tmpOld, oldContent);
15607
+ (0, import_node_fs16.writeFileSync)(tmpNew, newContent);
15533
15608
  const result = (0, import_node_child_process6.execSync)(
15534
15609
  `git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
15535
15610
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
@@ -15543,32 +15618,32 @@ function computeDiff(oldContent, newContent, filePath) {
15543
15618
  return null;
15544
15619
  } finally {
15545
15620
  try {
15546
- (0, import_node_fs15.unlinkSync)(tmpOld);
15621
+ (0, import_node_fs16.unlinkSync)(tmpOld);
15547
15622
  } catch {
15548
15623
  }
15549
15624
  try {
15550
- (0, import_node_fs15.unlinkSync)(tmpNew);
15625
+ (0, import_node_fs16.unlinkSync)(tmpNew);
15551
15626
  } catch {
15552
15627
  }
15553
15628
  }
15554
15629
  }
15555
15630
  function cleanStaleSnapshots(dir, keepSet) {
15556
- if (!(0, import_node_fs15.existsSync)(dir)) return;
15631
+ if (!(0, import_node_fs16.existsSync)(dir)) return;
15557
15632
  try {
15558
- const entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
15633
+ const entries = (0, import_node_fs16.readdirSync)(dir, { withFileTypes: true });
15559
15634
  for (const entry of entries) {
15560
- if (entry.name.startsWith(".")) continue;
15561
- const fullPath = (0, import_node_path14.join)(dir, entry.name);
15635
+ if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
15636
+ const fullPath = (0, import_node_path15.join)(dir, entry.name);
15562
15637
  if (entry.isDirectory()) {
15563
15638
  cleanStaleSnapshots(fullPath, keepSet);
15564
15639
  try {
15565
- const remaining = (0, import_node_fs15.readdirSync)(fullPath);
15566
- if (remaining.length === 0) (0, import_node_fs15.rmdirSync)(fullPath);
15640
+ const remaining = (0, import_node_fs16.readdirSync)(fullPath);
15641
+ if (remaining.length === 0) (0, import_node_fs16.rmdirSync)(fullPath);
15567
15642
  } catch {
15568
15643
  }
15569
15644
  } else if (!keepSet.has(fullPath)) {
15570
15645
  try {
15571
- (0, import_node_fs15.unlinkSync)(fullPath);
15646
+ (0, import_node_fs16.unlinkSync)(fullPath);
15572
15647
  } catch {
15573
15648
  }
15574
15649
  }
@@ -15587,20 +15662,20 @@ function sessionKey(sessionId) {
15587
15662
  return (0, import_node_crypto9.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
15588
15663
  }
15589
15664
  function sessionDir(key) {
15590
- return (0, import_node_path15.join)(projectPath(BASELINE_DIR), key);
15665
+ return (0, import_node_path16.join)(projectPath(BASELINE_DIR), key);
15591
15666
  }
15592
15667
  function manifestPath(dir) {
15593
- return (0, import_node_path15.join)(dir, "manifest.json");
15668
+ return (0, import_node_path16.join)(dir, "manifest.json");
15594
15669
  }
15595
15670
  function mirrorPath(dir, repoRelPath) {
15596
- return (0, import_node_path15.join)(dir, "files", repoRelPath);
15671
+ return (0, import_node_path16.join)(dir, "files", repoRelPath);
15597
15672
  }
15598
15673
  var CARRY_FILE = `${BASELINE_DIR}/.carry`;
15599
15674
  var CARRY_WINDOW_MS = 12e4;
15600
15675
  function writeCarry(sessionId, headSha) {
15601
15676
  try {
15602
- (0, import_node_fs16.mkdirSync)(projectPath(BASELINE_DIR), { recursive: true });
15603
- (0, import_node_fs16.writeFileSync)(
15677
+ (0, import_node_fs17.mkdirSync)(projectPath(BASELINE_DIR), { recursive: true });
15678
+ (0, import_node_fs17.writeFileSync)(
15604
15679
  projectPath(CARRY_FILE),
15605
15680
  JSON.stringify({ from_key: sessionKey(sessionId), head_sha: headSha, ts: Date.now() })
15606
15681
  );
@@ -15610,10 +15685,10 @@ function writeCarry(sessionId, headSha) {
15610
15685
  function claimCarry(newKey) {
15611
15686
  const carryPath = projectPath(CARRY_FILE);
15612
15687
  try {
15613
- if (!(0, import_node_fs16.existsSync)(carryPath)) return null;
15614
- const carry = JSON.parse((0, import_node_fs16.readFileSync)(carryPath, "utf-8"));
15688
+ if (!(0, import_node_fs17.existsSync)(carryPath)) return null;
15689
+ const carry = JSON.parse((0, import_node_fs17.readFileSync)(carryPath, "utf-8"));
15615
15690
  try {
15616
- (0, import_node_fs16.rmSync)(carryPath, { force: true });
15691
+ (0, import_node_fs17.rmSync)(carryPath, { force: true });
15617
15692
  } catch {
15618
15693
  }
15619
15694
  if (!carry?.from_key || typeof carry.ts !== "number") return null;
@@ -15624,11 +15699,11 @@ function claimCarry(newKey) {
15624
15699
  if (!prior) return null;
15625
15700
  const toDir = sessionDir(newKey);
15626
15701
  try {
15627
- (0, import_node_fs16.rmSync)(toDir, { recursive: true, force: true });
15702
+ (0, import_node_fs17.rmSync)(toDir, { recursive: true, force: true });
15628
15703
  } catch {
15629
15704
  }
15630
- (0, import_node_fs16.renameSync)(fromDir, toDir);
15631
- (0, import_node_fs16.writeFileSync)(manifestPath(toDir), JSON.stringify({ ...prior, session_id: newKey }) + "\n");
15705
+ (0, import_node_fs17.renameSync)(fromDir, toDir);
15706
+ (0, import_node_fs17.writeFileSync)(manifestPath(toDir), JSON.stringify({ ...prior, session_id: newKey }) + "\n");
15632
15707
  return readManifest(toDir);
15633
15708
  } catch {
15634
15709
  return null;
@@ -15652,21 +15727,21 @@ function captureBaseline(opts = {}) {
15652
15727
  const head_sha = getCurrentCommit();
15653
15728
  const dirty = getDirtyFiles();
15654
15729
  try {
15655
- (0, import_node_fs16.rmSync)(dir, { recursive: true, force: true });
15730
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
15656
15731
  } catch {
15657
15732
  }
15658
- const filesDir = (0, import_node_path15.join)(dir, "files");
15733
+ const filesDir = (0, import_node_path16.join)(dir, "files");
15659
15734
  const mirrored = [];
15660
15735
  try {
15661
- (0, import_node_fs16.mkdirSync)(filesDir, { recursive: true });
15736
+ (0, import_node_fs17.mkdirSync)(filesDir, { recursive: true });
15662
15737
  for (const p of dirty) {
15663
15738
  if (p.includes("..")) continue;
15664
15739
  const content = safeReadForMirror(projectPath(p));
15665
15740
  if (content === null) continue;
15666
15741
  const dest = mirrorPath(dir, p);
15667
15742
  try {
15668
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15669
- (0, import_node_fs16.writeFileSync)(dest, content);
15743
+ (0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
15744
+ (0, import_node_fs17.writeFileSync)(dest, content);
15670
15745
  mirrored.push(p);
15671
15746
  } catch {
15672
15747
  }
@@ -15681,8 +15756,8 @@ function captureBaseline(opts = {}) {
15681
15756
  version: BASELINE_VERSION
15682
15757
  };
15683
15758
  try {
15684
- (0, import_node_fs16.mkdirSync)(dir, { recursive: true });
15685
- (0, import_node_fs16.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
15759
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
15760
+ (0, import_node_fs17.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
15686
15761
  } catch {
15687
15762
  }
15688
15763
  pruneOldBaselines();
@@ -15693,9 +15768,9 @@ function readBaseline(sessionId) {
15693
15768
  }
15694
15769
  function readManifest(dir) {
15695
15770
  const mp = manifestPath(dir);
15696
- if (!(0, import_node_fs16.existsSync)(mp)) return null;
15771
+ if (!(0, import_node_fs17.existsSync)(mp)) return null;
15697
15772
  try {
15698
- const parsed = JSON.parse((0, import_node_fs16.readFileSync)(mp, "utf-8"));
15773
+ const parsed = JSON.parse((0, import_node_fs17.readFileSync)(mp, "utf-8"));
15699
15774
  if (typeof parsed.head_sha !== "string" || typeof parsed.captured_at !== "number" || !Array.isArray(parsed.dirty_paths) || parsed.version !== BASELINE_VERSION) {
15700
15775
  return null;
15701
15776
  }
@@ -15726,9 +15801,9 @@ function preImage(repoRelPath, baseline) {
15726
15801
  function resolvePreImage(repoRelPath, baseline) {
15727
15802
  if (baseline.dirty_paths.includes(repoRelPath)) {
15728
15803
  const mp = mirrorPath(sessionDir(sessionKey(baseline.session_id)), repoRelPath);
15729
- if ((0, import_node_fs16.existsSync)(mp)) {
15804
+ if ((0, import_node_fs17.existsSync)(mp)) {
15730
15805
  try {
15731
- return { content: (0, import_node_fs16.readFileSync)(mp, "utf-8"), existed: true };
15806
+ return { content: (0, import_node_fs17.readFileSync)(mp, "utf-8"), existed: true };
15732
15807
  } catch {
15733
15808
  }
15734
15809
  }
@@ -15773,8 +15848,8 @@ function absorbIntoBaseline(paths, sessionId) {
15773
15848
  const content = safeReadForMirror(projectPath(p));
15774
15849
  if (content === null) continue;
15775
15850
  const dest = mirrorPath(dir, p);
15776
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15777
- (0, import_node_fs16.writeFileSync)(dest, content);
15851
+ (0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
15852
+ (0, import_node_fs17.writeFileSync)(dest, content);
15778
15853
  dirty.add(p);
15779
15854
  adopted++;
15780
15855
  } catch {
@@ -15783,7 +15858,7 @@ function absorbIntoBaseline(paths, sessionId) {
15783
15858
  if (adopted === 0) return 0;
15784
15859
  try {
15785
15860
  const updated = { ...baseline, dirty_paths: [...dirty] };
15786
- (0, import_node_fs16.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
15861
+ (0, import_node_fs17.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
15787
15862
  preImageCache.delete(baseline);
15788
15863
  } catch {
15789
15864
  return 0;
@@ -15794,7 +15869,7 @@ function changedSinceBaseline(repoRelPath, baseline) {
15794
15869
  const pre = preImage(repoRelPath, baseline);
15795
15870
  let current;
15796
15871
  try {
15797
- current = (0, import_node_fs16.readFileSync)(projectPath(repoRelPath), "utf-8");
15872
+ current = (0, import_node_fs17.readFileSync)(projectPath(repoRelPath), "utf-8");
15798
15873
  } catch {
15799
15874
  return pre.existed;
15800
15875
  }
@@ -15803,8 +15878,8 @@ function changedSinceBaseline(repoRelPath, baseline) {
15803
15878
  }
15804
15879
  function safeReadForMirror(absPath) {
15805
15880
  try {
15806
- if ((0, import_node_fs16.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
15807
- const buf = (0, import_node_fs16.readFileSync)(absPath);
15881
+ if ((0, import_node_fs17.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
15882
+ const buf = (0, import_node_fs17.readFileSync)(absPath);
15808
15883
  if (buf.includes(0)) return null;
15809
15884
  return buf.toString("utf-8");
15810
15885
  } catch {
@@ -15815,18 +15890,18 @@ function pruneOldBaselines() {
15815
15890
  const root = projectPath(BASELINE_DIR);
15816
15891
  let entries;
15817
15892
  try {
15818
- entries = (0, import_node_fs16.readdirSync)(root);
15893
+ entries = (0, import_node_fs17.readdirSync)(root);
15819
15894
  } catch {
15820
15895
  return;
15821
15896
  }
15822
15897
  const now = Date.now();
15823
15898
  for (const name of entries) {
15824
- const dir = (0, import_node_path15.join)(root, name);
15899
+ const dir = (0, import_node_path16.join)(root, name);
15825
15900
  const manifest = readManifest(dir);
15826
15901
  if (!manifest) {
15827
15902
  try {
15828
- if (now - (0, import_node_fs16.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
15829
- (0, import_node_fs16.rmSync)(dir, { recursive: true, force: true });
15903
+ if (now - (0, import_node_fs17.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
15904
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
15830
15905
  }
15831
15906
  } catch {
15832
15907
  }
@@ -15834,7 +15909,7 @@ function pruneOldBaselines() {
15834
15909
  }
15835
15910
  if (now - manifest.captured_at <= BASELINE_TTL_MS) continue;
15836
15911
  try {
15837
- (0, import_node_fs16.rmSync)(dir, { recursive: true, force: true });
15912
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
15838
15913
  } catch {
15839
15914
  }
15840
15915
  }
@@ -16007,8 +16082,8 @@ function buildCompactionContext(session) {
16007
16082
  commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
16008
16083
  readFileLines: (file) => {
16009
16084
  try {
16010
- const abs = (0, import_node_path16.join)(root, file);
16011
- return (0, import_node_fs17.existsSync)(abs) ? (0, import_node_fs17.readFileSync)(abs, "utf8").split("\n") : null;
16085
+ const abs = (0, import_node_path17.join)(root, file);
16086
+ return (0, import_node_fs18.existsSync)(abs) ? (0, import_node_fs18.readFileSync)(abs, "utf8").split("\n") : null;
16012
16087
  } catch {
16013
16088
  return null;
16014
16089
  }
@@ -16044,17 +16119,17 @@ async function readHookStdin() {
16044
16119
  try {
16045
16120
  if (process.stdin.isTTY) return {};
16046
16121
  const chunks = [];
16047
- const timeout = new Promise((resolve2) => setTimeout(() => resolve2({}), 500));
16048
- const read = new Promise((resolve2) => {
16122
+ const timeout = new Promise((resolve3) => setTimeout(() => resolve3({}), 500));
16123
+ const read = new Promise((resolve3) => {
16049
16124
  process.stdin.on("data", (c) => chunks.push(c));
16050
16125
  process.stdin.on("end", () => {
16051
16126
  try {
16052
- resolve2(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
16127
+ resolve3(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
16053
16128
  } catch {
16054
- resolve2({});
16129
+ resolve3({});
16055
16130
  }
16056
16131
  });
16057
- process.stdin.on("error", () => resolve2({}));
16132
+ process.stdin.on("error", () => resolve3({}));
16058
16133
  process.stdin.resume();
16059
16134
  });
16060
16135
  return await Promise.race([read, timeout]);
@@ -16640,7 +16715,7 @@ function createRun(opts, globals) {
16640
16715
  }
16641
16716
 
16642
16717
  // src/lib/stderr-log.ts
16643
- var import_node_fs18 = require("node:fs");
16718
+ var import_node_fs19 = require("node:fs");
16644
16719
  var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
16645
16720
  var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
16646
16721
  function scrub(s) {
@@ -16653,9 +16728,9 @@ function append(text) {
16653
16728
  try {
16654
16729
  const dir = projectPath(DEBUG_LOG_DIR);
16655
16730
  const file = projectPath(STDERR_LOG_FILE);
16656
- (0, import_node_fs18.mkdirSync)(dir, { recursive: true });
16731
+ (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
16657
16732
  rotateIfNeeded(file);
16658
- (0, import_node_fs18.appendFileSync)(file, text);
16733
+ (0, import_node_fs19.appendFileSync)(file, text);
16659
16734
  } catch {
16660
16735
  }
16661
16736
  }
@@ -16717,7 +16792,7 @@ function formatRunEvidence(run, startedAt) {
16717
16792
  const md = run.modeDecision;
16718
16793
  if (md) {
16719
16794
  const how = md.forced ? "forced by --mode" : `predicted=${md.predicted ?? "none"} \u2192 ${md.resolved}`;
16720
- out += row("mode", `${md.resolved} \xB7 ${how} \xB7 authored=${md.authored ? "yes" : "no"} \xB7 investigated=${md.investigated ? "yes" : "no"}`);
16795
+ out += row("mode", `${md.resolved} \xB7 ${how}` + (md.flip ? ` (flipped to plan: no delta at ${md.flip})` : "") + ` \xB7 authored=${md.authored ? "yes" : "no"} \xB7 investigated=${md.investigated ? "yes" : "no"}`);
16721
16796
  } else {
16722
16797
  out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
16723
16798
  }
@@ -16765,6 +16840,30 @@ function formatRunEvidence(run, startedAt) {
16765
16840
  out += row("", `${cov.outsideRepo} authored path(s) refused as outside the repo`);
16766
16841
  }
16767
16842
  }
16843
+ if (run.foldResult?.tools?.length) {
16844
+ const shown = run.foldResult.tools.slice(0, 6).map((t) => {
16845
+ const outcome = t.failed > 0 ? `${t.failed} failed` : t.last_status === 0 ? "ok" : "?";
16846
+ const where = t.targets.length > 0 ? ` \u2192 ${t.targets.slice(0, 2).join(", ")}` : "";
16847
+ return `${t.runs}\xD7 ${t.name} (${outcome})${where}`;
16848
+ });
16849
+ const more = run.foldResult.tools.length > 6 ? ` \u2026 +${run.foldResult.tools.length - 6} more` : "";
16850
+ out += row("tools", shown.join(" \xB7 ") + more);
16851
+ if (run.foldResult.coverage.toolNamesDropped > 0) {
16852
+ out += row("", `\u26A0 ${run.foldResult.coverage.toolNamesDropped} tool name(s) refused by the cap`);
16853
+ }
16854
+ }
16855
+ if (run.foldResult?.tasks?.length) {
16856
+ const t = run.foldResult.tasks;
16857
+ const done2 = t.filter((x) => x.status === "completed").length;
16858
+ out += row("tasks", `${t.length} \xB7 ${done2} completed \xB7 ` + list(t.slice(0, 4).map((x) => `#${x.id} ${x.name} [${x.status}]`), 4));
16859
+ }
16860
+ if (run.specs?.length) {
16861
+ const readThisSession = new Set(run.actionSummary?.files_read ?? []);
16862
+ const labelled = run.specs.map(
16863
+ (s) => `${s.path}${readThisSession.has(s.path) ? " (read)" : " (positional)"}`
16864
+ );
16865
+ out += row("specs", `${run.specs.length} \xB7 ${list(labelled, 5)}`);
16866
+ }
16768
16867
  if (run.staticResults.findings.length > 0) {
16769
16868
  out += row("static", `${run.staticResults.findings.length} finding(s) from ${run.staticResults.summary.tools_run.join(", ") || "no tools"}`);
16770
16869
  }
@@ -16811,7 +16910,7 @@ function truthy(v) {
16811
16910
  }
16812
16911
 
16813
16912
  // src/lib/transcript.ts
16814
- var import_node_fs19 = require("node:fs");
16913
+ var import_node_fs20 = require("node:fs");
16815
16914
  var MAX_READ_BYTES = 256 * 1024;
16816
16915
  var SMALL_FILE_BYTES = 64 * 1024;
16817
16916
  var MAX_FILES_LIST = 20;
@@ -16835,7 +16934,7 @@ async function extractActionSummary(transcriptPath) {
16835
16934
  function readTurnLines(transcriptPath) {
16836
16935
  let size;
16837
16936
  try {
16838
- size = (0, import_node_fs19.statSync)(transcriptPath).size;
16937
+ size = (0, import_node_fs20.statSync)(transcriptPath).size;
16839
16938
  } catch {
16840
16939
  return null;
16841
16940
  }
@@ -16843,7 +16942,7 @@ function readTurnLines(transcriptPath) {
16843
16942
  let raw;
16844
16943
  let windowed = false;
16845
16944
  if (size <= SMALL_FILE_BYTES) {
16846
- raw = (0, import_node_fs19.readFileSync)(transcriptPath, "utf-8");
16945
+ raw = (0, import_node_fs20.readFileSync)(transcriptPath, "utf-8");
16847
16946
  } else {
16848
16947
  windowed = true;
16849
16948
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
@@ -17023,11 +17122,11 @@ function sanitizeCommand(rawCmd) {
17023
17122
  let cmd = rawCmd.split("\n")[0];
17024
17123
  let cut = -1;
17025
17124
  let marker = "";
17026
- for (const sep of [" | ", " > ", " >> ", " 2>", " && ", " ; "]) {
17027
- const idx = cmd.indexOf(sep);
17125
+ for (const sep2 of [" | ", " > ", " >> ", " 2>", " && ", " ; "]) {
17126
+ const idx = cmd.indexOf(sep2);
17028
17127
  if (idx > 0 && (cut === -1 || idx < cut)) {
17029
17128
  cut = idx;
17030
- marker = sep.trim();
17129
+ marker = sep2.trim();
17031
17130
  }
17032
17131
  }
17033
17132
  if (cut > -1) cmd = cmd.slice(0, cut);
@@ -17051,28 +17150,28 @@ async function readStopHookStdin() {
17051
17150
  try {
17052
17151
  if (process.stdin.isTTY) return empty;
17053
17152
  const chunks = [];
17054
- const timeout = new Promise((resolve2) => setTimeout(() => resolve2(empty), 500));
17055
- const read = new Promise((resolve2) => {
17153
+ const timeout = new Promise((resolve3) => setTimeout(() => resolve3(empty), 500));
17154
+ const read = new Promise((resolve3) => {
17056
17155
  process.stdin.on("data", (chunk) => chunks.push(chunk));
17057
17156
  process.stdin.on("end", () => {
17058
17157
  const raw = Buffer.concat(chunks).toString("utf-8").trim();
17059
17158
  if (!raw) {
17060
- resolve2(empty);
17159
+ resolve3(empty);
17061
17160
  return;
17062
17161
  }
17063
17162
  try {
17064
17163
  const data = JSON.parse(raw);
17065
- resolve2({
17164
+ resolve3({
17066
17165
  assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
17067
17166
  stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
17068
17167
  transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
17069
17168
  sessionId: typeof data.session_id === "string" ? data.session_id : null
17070
17169
  });
17071
17170
  } catch {
17072
- resolve2(empty);
17171
+ resolve3(empty);
17073
17172
  }
17074
17173
  });
17075
- process.stdin.on("error", () => resolve2(empty));
17174
+ process.stdin.on("error", () => resolve3(empty));
17076
17175
  process.stdin.resume();
17077
17176
  });
17078
17177
  return await Promise.race([read, timeout]);
@@ -17122,6 +17221,39 @@ async function bootstrap(run) {
17122
17221
  Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
17123
17222
  }
17124
17223
 
17224
+ // src/lib/self-scope.ts
17225
+ var LEGACY_GATE_SKILLS = /* @__PURE__ */ new Set([
17226
+ "gate-setup",
17227
+ "gate-analyze",
17228
+ "gate-review",
17229
+ "gate-status",
17230
+ "gate-feedback",
17231
+ "gate-insights",
17232
+ "gate-learn",
17233
+ "gate-memory",
17234
+ "gate-reflect"
17235
+ ]);
17236
+ function isVerityOwned(path) {
17237
+ const segments = path.replace(/\\/g, "/").split("/");
17238
+ for (let i = 0; i < segments.length; i++) {
17239
+ const seg = segments[i];
17240
+ if (seg === ".verity" || seg === ".codacy") return true;
17241
+ if (i === segments.length - 1 && (seg === "VERITY.md" || seg === "GATE.md")) return true;
17242
+ if (seg === ".claude" && segments[i + 1] === "skills" && typeof segments[i + 2] === "string") {
17243
+ const skill = segments[i + 2];
17244
+ if (skill.startsWith("verity-") || LEGACY_GATE_SKILLS.has(skill)) return true;
17245
+ }
17246
+ if (seg === ".claude" && segments[i + 1] === "settings.json") return true;
17247
+ }
17248
+ return false;
17249
+ }
17250
+ function partitionVerityOwned(paths) {
17251
+ const kept = [];
17252
+ const owned = [];
17253
+ for (const p of paths) (isVerityOwned(p) ? owned : kept).push(p);
17254
+ return { kept, owned };
17255
+ }
17256
+
17125
17257
  // src/lib/channel.ts
17126
17258
  var MAX_AGENT_CONTEXT_CHARS = 1500;
17127
17259
  var MAX_AGENT_ITEMS = 5;
@@ -17131,6 +17263,29 @@ function renderItem(label2, text, patternId, file, line) {
17131
17263
  const id = patternId ? ` [${patternId}]` : "";
17132
17264
  return `- ${label2}${text}${where}${id}`;
17133
17265
  }
17266
+ function channelInputFrom(response, intentRepeat = 0, priorPendingFingerprints = []) {
17267
+ const metadata = response.metadata ?? {};
17268
+ const intent = response.intent_alignment ?? {};
17269
+ return {
17270
+ intentRepeat,
17271
+ priorPendingFingerprints,
17272
+ gateDecision: String(response.gate_decision ?? ""),
17273
+ findings: response.findings ?? [],
17274
+ pendingItems: response.pending_items ?? [],
17275
+ reviewStatus: metadata.review_status,
17276
+ coverage: metadata.coverage,
17277
+ intentVerdict: intent.verdict,
17278
+ intentGaps: intent.gaps
17279
+ };
17280
+ }
17281
+ function classifyChannelContent(input) {
17282
+ const refusal = input.reviewStatus === "not_reviewed" || input.reviewStatus === "no_authorship_evidence";
17283
+ const intentFlag = input.intentVerdict === "misaligned" || input.intentVerdict === "partial";
17284
+ const advisory = (input.findings ?? []).some((f) => f.scope !== "pre-existing") || (input.pendingItems ?? []).some(
17285
+ (p) => p.pattern_id !== "intent-misalignment" && !!(p.description ?? p.title ?? p.reason)
17286
+ );
17287
+ return { refusal, intentFlag, advisory };
17288
+ }
17134
17289
  function buildAgentContext(input) {
17135
17290
  const lines = [];
17136
17291
  if (input.reviewStatus === "not_reviewed") {
@@ -17216,7 +17371,7 @@ function channelSilence(input) {
17216
17371
  // src/lib/cli-version.ts
17217
17372
  function cliVersion() {
17218
17373
  try {
17219
- return true ? "0.29.4-experimental.5fcba03" : "dev";
17374
+ return true ? "0.29.4-experimental.694bcef" : "dev";
17220
17375
  } catch {
17221
17376
  return "dev";
17222
17377
  }
@@ -17257,7 +17412,7 @@ async function sendSkipBeacon(ctx, reason) {
17257
17412
 
17258
17413
  // src/lib/static-analysis.ts
17259
17414
  var import_node_child_process7 = require("node:child_process");
17260
- var import_node_fs20 = require("node:fs");
17415
+ var import_node_fs21 = require("node:fs");
17261
17416
  var SEVERITY_ORDER = {
17262
17417
  Error: 0,
17263
17418
  Critical: 0,
@@ -17305,7 +17460,7 @@ function runCodacyAnalysis(files) {
17305
17460
  if (files.length === 0) return empty;
17306
17461
  const existingFiles = files.filter((f) => {
17307
17462
  try {
17308
- return (0, import_node_fs20.existsSync)(f);
17463
+ return (0, import_node_fs21.existsSync)(f);
17309
17464
  } catch {
17310
17465
  return false;
17311
17466
  }
@@ -17538,9 +17693,10 @@ async function scope(run) {
17538
17693
  const { assistantResponse } = run;
17539
17694
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
17540
17695
  run.changedUniverse = allChanged;
17541
- const analyzable = filterAnalyzable(allChanged);
17542
- const reviewable = filterReviewable(allChanged);
17543
- const securityFiles = filterSecurity(allChanged);
17696
+ const { kept: external } = partitionVerityOwned(allChanged);
17697
+ const analyzable = filterAnalyzable(external);
17698
+ const reviewable = filterReviewable(external);
17699
+ const securityFiles = filterSecurity(external);
17544
17700
  const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
17545
17701
  if (noFilesChanged && !assistantResponse) {
17546
17702
  await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
@@ -17550,8 +17706,8 @@ async function scope(run) {
17550
17706
  }
17551
17707
 
17552
17708
  // src/lib/specs.ts
17553
- var import_node_fs21 = require("node:fs");
17554
- var import_node_path17 = require("node:path");
17709
+ var import_node_fs22 = require("node:fs");
17710
+ var import_node_path18 = require("node:path");
17555
17711
  var SPEC_CANDIDATES = [
17556
17712
  "CLAUDE.md",
17557
17713
  "AGENTS.md",
@@ -17567,23 +17723,31 @@ var SPEC_CANDIDATES = [
17567
17723
  "docs/API.md",
17568
17724
  "spec/ARCHITECTURE.md"
17569
17725
  ];
17570
- function discoverSpecs() {
17726
+ var DOC_EXT = /\.(md|mdx|ya?ml|txt|rst|adoc)$/i;
17727
+ var UNCONSULTED_FILE_BYTES = 10240;
17728
+ var UNCONSULTED_TOTAL_BYTES = 30720;
17729
+ function discoverSpecs(consulted = []) {
17571
17730
  const result = [];
17572
17731
  const seen = /* @__PURE__ */ new Set();
17573
17732
  let totalBytes = 0;
17574
- const addSpec = (specPath) => {
17733
+ const consultedDocs = new Set(
17734
+ consulted.filter((p) => DOC_EXT.test(p) && !p.startsWith("/") && !p.includes(".."))
17735
+ );
17736
+ const addSpec = (specPath, relevant = false) => {
17575
17737
  if (result.length >= MAX_SPEC_FILES) return false;
17576
- if (totalBytes >= MAX_TOTAL_SPEC_BYTES) return false;
17738
+ const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
17739
+ if (totalBytes >= totalCap) return false;
17577
17740
  if (seen.has(specPath)) return true;
17578
- if (!(0, import_node_fs21.existsSync)(specPath)) return true;
17741
+ if (!(0, import_node_fs22.existsSync)(specPath)) return true;
17579
17742
  seen.add(specPath);
17580
- const remaining = MAX_TOTAL_SPEC_BYTES - totalBytes;
17581
- const readBytes = Math.min(MAX_SPEC_FILE_BYTES, remaining);
17743
+ const remaining = totalCap - totalBytes;
17744
+ const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
17745
+ const readBytes = Math.min(fileCap, remaining);
17582
17746
  try {
17583
17747
  const buf = Buffer.alloc(readBytes);
17584
- const fd = (0, import_node_fs21.openSync)(specPath, "r");
17585
- const bytesRead = (0, import_node_fs21.readSync)(fd, buf, 0, readBytes, 0);
17586
- (0, import_node_fs21.closeSync)(fd);
17748
+ const fd = (0, import_node_fs22.openSync)(specPath, "r");
17749
+ const bytesRead = (0, import_node_fs22.readSync)(fd, buf, 0, readBytes, 0);
17750
+ (0, import_node_fs22.closeSync)(fd);
17587
17751
  const content = buf.slice(0, bytesRead).toString("utf-8");
17588
17752
  if (!content) return true;
17589
17753
  result.push({ path: specPath, content });
@@ -17592,11 +17756,14 @@ function discoverSpecs() {
17592
17756
  }
17593
17757
  return true;
17594
17758
  };
17759
+ for (const doc of consultedDocs) {
17760
+ if (!addSpec(doc, true)) break;
17761
+ }
17595
17762
  for (const candidate of SPEC_CANDIDATES) {
17596
17763
  if (!addSpec(candidate)) break;
17597
17764
  }
17598
17765
  for (const dir of ["spec", "docs"]) {
17599
- if (!(0, import_node_fs21.existsSync)(dir)) continue;
17766
+ if (!(0, import_node_fs22.existsSync)(dir)) continue;
17600
17767
  try {
17601
17768
  const mdFiles = findMdFiles(dir, 2).sort();
17602
17769
  for (const mdFile of mdFiles) {
@@ -17611,9 +17778,9 @@ function findMdFiles(dir, maxDepth, depth = 0) {
17611
17778
  if (depth >= maxDepth) return [];
17612
17779
  const result = [];
17613
17780
  try {
17614
- const entries = (0, import_node_fs21.readdirSync)(dir, { withFileTypes: true });
17781
+ const entries = (0, import_node_fs22.readdirSync)(dir, { withFileTypes: true });
17615
17782
  for (const entry of entries) {
17616
- const fullPath = (0, import_node_path17.join)(dir, entry.name);
17783
+ const fullPath = (0, import_node_path18.join)(dir, entry.name);
17617
17784
  if (entry.isFile() && entry.name.endsWith(".md")) {
17618
17785
  result.push(fullPath);
17619
17786
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -17625,19 +17792,19 @@ function findMdFiles(dir, maxDepth, depth = 0) {
17625
17792
  return result;
17626
17793
  }
17627
17794
  function discoverPlans() {
17628
- const homePlansDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".claude", "plans");
17795
+ const homePlansDir = (0, import_node_path18.join)(process.env.HOME ?? "", ".claude", "plans");
17629
17796
  const localPlansDir = ".claude/plans";
17630
17797
  const candidates = [];
17631
17798
  const seen = /* @__PURE__ */ new Set();
17632
17799
  for (const plansDir of [localPlansDir, homePlansDir]) {
17633
- if (!(0, import_node_fs21.existsSync)(plansDir)) continue;
17800
+ if (!(0, import_node_fs22.existsSync)(plansDir)) continue;
17634
17801
  try {
17635
- for (const f of (0, import_node_fs21.readdirSync)(plansDir)) {
17802
+ for (const f of (0, import_node_fs22.readdirSync)(plansDir)) {
17636
17803
  if (!f.endsWith(".md") || seen.has(f)) continue;
17637
17804
  seen.add(f);
17638
- const fullPath = (0, import_node_path17.join)(plansDir, f);
17805
+ const fullPath = (0, import_node_path18.join)(plansDir, f);
17639
17806
  try {
17640
- const stat3 = (0, import_node_fs21.statSync)(fullPath);
17807
+ const stat3 = (0, import_node_fs22.statSync)(fullPath);
17641
17808
  candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
17642
17809
  } catch {
17643
17810
  }
@@ -17650,7 +17817,7 @@ function discoverPlans() {
17650
17817
  for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
17651
17818
  if (entry.size > MAX_PLAN_FILE_BYTES) continue;
17652
17819
  try {
17653
- const content = (0, import_node_fs21.readFileSync)(entry.path, "utf-8");
17820
+ const content = (0, import_node_fs22.readFileSync)(entry.path, "utf-8");
17654
17821
  result.push({ name: entry.name, content });
17655
17822
  } catch {
17656
17823
  }
@@ -17662,7 +17829,7 @@ function discoverPlans() {
17662
17829
  async function intentInputs(run) {
17663
17830
  const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run;
17664
17831
  const conversation = await readAndClearConversationBuffer(baselineSessionId);
17665
- const specs = discoverSpecs();
17832
+ const specs = discoverSpecs(actionSummary?.files_read ?? []);
17666
17833
  const plans = discoverPlans();
17667
17834
  const agentAuthoredCodeThisTurn = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0));
17668
17835
  const turnAuthoredCode = agentAuthoredCodeThisTurn || !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
@@ -17925,7 +18092,7 @@ async function mode(run) {
17925
18092
  }
17926
18093
 
17927
18094
  // src/lib/debounce.ts
17928
- var import_node_fs22 = require("node:fs");
18095
+ var import_node_fs23 = require("node:fs");
17929
18096
  var import_node_crypto10 = require("node:crypto");
17930
18097
  function scopedFile(base, sessionId) {
17931
18098
  if (!sessionId) return base;
@@ -17933,9 +18100,9 @@ function scopedFile(base, sessionId) {
17933
18100
  }
17934
18101
  function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
17935
18102
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
17936
- if (!(0, import_node_fs22.existsSync)(file)) return null;
18103
+ if (!(0, import_node_fs23.existsSync)(file)) return null;
17937
18104
  try {
17938
- const lastTs = parseInt((0, import_node_fs22.readFileSync)(file, "utf-8").trim(), 10);
18105
+ const lastTs = parseInt((0, import_node_fs23.readFileSync)(file, "utf-8").trim(), 10);
17939
18106
  const nowTs = Math.floor(Date.now() / 1e3);
17940
18107
  const elapsed = nowTs - lastTs;
17941
18108
  if (elapsed < debounceSeconds) {
@@ -17948,10 +18115,10 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
17948
18115
  function checkMtime(files, bypassForRecentCommits, sessionId) {
17949
18116
  if (bypassForRecentCommits) return null;
17950
18117
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
17951
- if (!(0, import_node_fs22.existsSync)(file)) return null;
18118
+ if (!(0, import_node_fs23.existsSync)(file)) return null;
17952
18119
  let debounceTime;
17953
18120
  try {
17954
- debounceTime = (0, import_node_fs22.statSync)(file).mtimeMs;
18121
+ debounceTime = (0, import_node_fs23.statSync)(file).mtimeMs;
17955
18122
  } catch {
17956
18123
  return null;
17957
18124
  }
@@ -17959,7 +18126,7 @@ function checkMtime(files, bypassForRecentCommits, sessionId) {
17959
18126
  const resolved = resolveFile(f);
17960
18127
  if (!resolved) continue;
17961
18128
  try {
17962
- const stat3 = (0, import_node_fs22.statSync)(resolved);
18129
+ const stat3 = (0, import_node_fs23.statSync)(resolved);
17963
18130
  if (stat3.mtimeMs > debounceTime) {
17964
18131
  return null;
17965
18132
  }
@@ -17975,8 +18142,8 @@ function computeContentHash(files) {
17975
18142
  for (const f of sorted) {
17976
18143
  const resolved = resolveFile(f) ?? f;
17977
18144
  try {
17978
- if ((0, import_node_fs22.existsSync)(resolved)) {
17979
- hash.update((0, import_node_fs22.readFileSync)(resolved));
18145
+ if ((0, import_node_fs23.existsSync)(resolved)) {
18146
+ hash.update((0, import_node_fs23.readFileSync)(resolved));
17980
18147
  }
17981
18148
  } catch {
17982
18149
  }
@@ -17986,9 +18153,9 @@ function computeContentHash(files) {
17986
18153
  function checkContentHash(files, sessionId) {
17987
18154
  const hash = computeContentHash(files);
17988
18155
  const file = scopedFile(HASH_FILE, sessionId);
17989
- if ((0, import_node_fs22.existsSync)(file)) {
18156
+ if ((0, import_node_fs23.existsSync)(file)) {
17990
18157
  try {
17991
- const storedHash = (0, import_node_fs22.readFileSync)(file, "utf-8").trim();
18158
+ const storedHash = (0, import_node_fs23.readFileSync)(file, "utf-8").trim();
17992
18159
  if (hash === storedHash) {
17993
18160
  return { skip: "No source changes since last analysis", hash };
17994
18161
  }
@@ -17998,50 +18165,74 @@ function checkContentHash(files, sessionId) {
17998
18165
  return { skip: null, hash };
17999
18166
  }
18000
18167
  function recordAnalysisStart(sessionId) {
18001
- (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18002
- (0, import_node_fs22.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
18168
+ (0, import_node_fs23.mkdirSync)(VERITY_DIR, { recursive: true });
18169
+ (0, import_node_fs23.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
18003
18170
  }
18004
18171
  function recordPassHash(hash, sessionId) {
18005
- (0, import_node_fs22.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
18172
+ (0, import_node_fs23.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
18006
18173
  }
18007
18174
  function narrowToRecent(files, sessionId) {
18008
18175
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
18009
- if (!(0, import_node_fs22.existsSync)(file)) return files;
18176
+ if (!(0, import_node_fs23.existsSync)(file)) return files;
18010
18177
  let debounceTime;
18011
18178
  try {
18012
- debounceTime = (0, import_node_fs22.statSync)(file).mtimeMs;
18179
+ debounceTime = (0, import_node_fs23.statSync)(file).mtimeMs;
18013
18180
  } catch {
18014
18181
  return files;
18015
18182
  }
18016
18183
  const recent = files.filter((f) => {
18017
18184
  try {
18018
- return (0, import_node_fs22.existsSync)(f) && (0, import_node_fs22.statSync)(f).mtimeMs > debounceTime;
18185
+ return (0, import_node_fs23.existsSync)(f) && (0, import_node_fs23.statSync)(f).mtimeMs > debounceTime;
18019
18186
  } catch {
18020
18187
  return false;
18021
18188
  }
18022
18189
  });
18023
18190
  return recent.length > 0 ? recent : files;
18024
18191
  }
18025
- function readIterationState(currentCommit) {
18026
- if (!(0, import_node_fs22.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
18192
+ function readIteration(currentCommit, _contentHash) {
18193
+ return Math.max(1, readBlockState(currentCommit).attempts);
18194
+ }
18195
+ var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
18196
+ function readBlockState(currentCommit, opts) {
18197
+ if (opts?.newUserPrompt) return NO_BLOCKS;
18198
+ if (!(0, import_node_fs23.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
18027
18199
  try {
18028
- const stored = (0, import_node_fs22.readFileSync)(ITERATION_FILE, "utf-8").trim();
18029
- const parts = stored.split(":");
18030
- const iter = parseInt(parts[0], 10);
18031
- const storedCommit = parts[1] ?? "";
18032
- const storedTimestamp = parseInt(parts[2] ?? "0", 10);
18033
- const fingerprint = parts.slice(3).join(":") || null;
18034
- if (isNaN(iter)) return { iteration: 1, fingerprint: null };
18035
- if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
18036
- if (storedTimestamp > 0) {
18037
- const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
18038
- if (elapsed > 600) return { iteration: 1, fingerprint: null };
18039
- }
18040
- return { iteration: iter, fingerprint };
18200
+ const stored = (0, import_node_fs23.readFileSync)(ITERATION_FILE, "utf-8").trim();
18201
+ const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
18202
+ if (!parsed) return NO_BLOCKS;
18203
+ if (parsed.commit !== currentCommit) return NO_BLOCKS;
18204
+ if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
18205
+ return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
18041
18206
  } catch {
18042
- return { iteration: 1, fingerprint: null };
18207
+ return NO_BLOCKS;
18043
18208
  }
18044
18209
  }
18210
+ function parseJsonState(raw) {
18211
+ const o = JSON.parse(raw);
18212
+ const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
18213
+ if (isNaN(attempts)) return null;
18214
+ return {
18215
+ attempts,
18216
+ blocks: typeof o.blocks === "number" ? o.blocks : attempts,
18217
+ fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
18218
+ commit: typeof o.commit === "string" ? o.commit : "",
18219
+ ts: typeof o.ts === "number" ? o.ts : 0
18220
+ };
18221
+ }
18222
+ function parseLegacyState(raw) {
18223
+ const parts = raw.split(":");
18224
+ const n = parseInt(parts[0], 10);
18225
+ if (isNaN(n)) return null;
18226
+ return {
18227
+ attempts: n,
18228
+ // The old file has no separate block count; the old counter is the closest
18229
+ // honest answer, and it errs toward releasing sooner rather than later.
18230
+ blocks: n,
18231
+ fingerprint: parts.slice(3).join(":") || null,
18232
+ commit: parts[1] ?? "",
18233
+ ts: parseInt(parts[2] ?? "0", 10)
18234
+ };
18235
+ }
18045
18236
  function findingsFingerprint(findings) {
18046
18237
  const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
18047
18238
  return [...new Set(keys)].sort().join(",");
@@ -18051,16 +18242,27 @@ function isSameProblem(previous, current) {
18051
18242
  const prev = new Set(previous.split(","));
18052
18243
  return current.split(",").some((k) => prev.has(k));
18053
18244
  }
18054
- function writeIteration(iteration, commit, _contentHash, fingerprint) {
18055
- (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18056
- const ts = Math.floor(Date.now() / 1e3);
18057
- const fp = fingerprint ? `:${fingerprint}` : "";
18058
- (0, import_node_fs22.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
18245
+ function writeBlockState(commit, state) {
18246
+ (0, import_node_fs23.mkdirSync)(VERITY_DIR, { recursive: true });
18247
+ (0, import_node_fs23.writeFileSync)(
18248
+ ITERATION_FILE,
18249
+ JSON.stringify({
18250
+ v: 2,
18251
+ attempts: state.attempts,
18252
+ blocks: state.blocks,
18253
+ commit,
18254
+ ts: Math.floor(Date.now() / 1e3),
18255
+ fingerprint: state.fingerprint ?? void 0
18256
+ })
18257
+ );
18258
+ }
18259
+ function resetBlockState(commit) {
18260
+ writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
18059
18261
  }
18060
18262
 
18061
18263
  // src/lib/fold.ts
18062
- var import_node_fs23 = require("node:fs");
18063
- var import_node_path18 = require("node:path");
18264
+ var import_node_fs24 = require("node:fs");
18265
+ var import_node_path19 = require("node:path");
18064
18266
  var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
18065
18267
  "user",
18066
18268
  "assistant",
@@ -18178,6 +18380,18 @@ function commandShape(cmd) {
18178
18380
  return out.join(" ").slice(0, COMMAND_HEAD_CHARS);
18179
18381
  }
18180
18382
  var COMMAND_HEAD_CHARS = 80;
18383
+ var MAX_TOOL_NAMES = 64;
18384
+ var MAX_TOOL_TARGETS = 3;
18385
+ var MAX_TASKS = 64;
18386
+ var TASK_NAME_CHARS = 120;
18387
+ function toolTarget(name, input) {
18388
+ if (name === "Bash") return null;
18389
+ for (const key of ["file_path", "notebook_path", "path", "filePath"]) {
18390
+ const v = input[key];
18391
+ if (typeof v === "string" && v) return v.slice(0, 120);
18392
+ }
18393
+ return null;
18394
+ }
18181
18395
  var rootCandidateCache = /* @__PURE__ */ new Map();
18182
18396
  function candidateRoots(repoRoot2) {
18183
18397
  const cached2 = rootCandidateCache.get(repoRoot2);
@@ -18185,7 +18399,7 @@ function candidateRoots(repoRoot2) {
18185
18399
  const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
18186
18400
  const out = [norm];
18187
18401
  try {
18188
- const real = import_node_fs23.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
18402
+ const real = import_node_fs24.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
18189
18403
  if (real !== norm) out.push(real);
18190
18404
  } catch {
18191
18405
  }
@@ -18208,15 +18422,20 @@ function toRepoRelative(path, repoRoot2) {
18208
18422
  }
18209
18423
  function isInsideRepo(path, repoRoot2) {
18210
18424
  const p = path.replace(/\\/g, "/");
18211
- if (!p.startsWith("/")) return true;
18425
+ if (!isAbsolutePath(p)) return true;
18212
18426
  if (!repoRoot2) return true;
18213
18427
  return candidateRoots(repoRoot2).some((root) => p === root || p.startsWith(root + "/"));
18214
18428
  }
18429
+ function isAbsolutePath(p) {
18430
+ return p.startsWith("/") || /^[A-Za-z]:\//.test(p);
18431
+ }
18215
18432
  function fold(transcriptPath, opts = {}) {
18216
18433
  const result = {
18217
18434
  authored: [],
18218
18435
  unobserved: [],
18219
18436
  commands: [],
18437
+ tools: [],
18438
+ tasks: [],
18220
18439
  unknownTypes: [],
18221
18440
  coverage: {
18222
18441
  recordCounts: {},
@@ -18224,16 +18443,23 @@ function fold(transcriptPath, opts = {}) {
18224
18443
  malformed: 0,
18225
18444
  subagentFiles: 0,
18226
18445
  outsideRepo: 0,
18446
+ toolNamesDropped: 0,
18227
18447
  dispatched: 0,
18228
18448
  userMessages: 0,
18229
18449
  subagentSkipped: 0,
18230
18450
  compactions: 0,
18231
18451
  complete: false
18232
- }
18452
+ },
18453
+ planApproval: { approvals: 0, activeSinceLastPrompt: false }
18233
18454
  };
18455
+ const flow = { seq: 0, lastPrompt: -1, lastApproval: -1, approvals: 0 };
18234
18456
  const byPath = /* @__PURE__ */ new Map();
18235
18457
  const commandStats = /* @__PURE__ */ new Map();
18236
18458
  const pendingByToolUse = /* @__PURE__ */ new Map();
18459
+ const toolStats = /* @__PURE__ */ new Map();
18460
+ const pendingToolName = /* @__PURE__ */ new Map();
18461
+ const taskById = /* @__PURE__ */ new Map();
18462
+ const pendingTaskName = /* @__PURE__ */ new Map();
18237
18463
  const unknown = /* @__PURE__ */ new Set();
18238
18464
  const ingest = (raw, owner) => {
18239
18465
  for (const line of raw.split("\n")) {
@@ -18252,36 +18478,40 @@ function fold(transcriptPath, opts = {}) {
18252
18478
  if (type === "system" && record.subtype === "compact_boundary") {
18253
18479
  result.coverage.compactions++;
18254
18480
  }
18255
- if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
18256
- collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, opts.repoRoot, result.coverage);
18481
+ if (type === "user" && hasUserText(record)) {
18482
+ result.coverage.userMessages++;
18483
+ if (owner === "agent") flow.lastPrompt = flow.seq;
18484
+ }
18485
+ if (owner === "agent") flow.seq++;
18486
+ collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage, flow);
18257
18487
  }
18258
18488
  };
18259
18489
  try {
18260
- if (!(0, import_node_fs23.existsSync)(transcriptPath)) return result;
18261
- ingest((0, import_node_fs23.readFileSync)(transcriptPath, "utf8"), "agent");
18490
+ if (!(0, import_node_fs24.existsSync)(transcriptPath)) return result;
18491
+ ingest((0, import_node_fs24.readFileSync)(transcriptPath, "utf8"), "agent");
18262
18492
  result.coverage.complete = true;
18263
18493
  } catch {
18264
18494
  return result;
18265
18495
  }
18266
18496
  try {
18267
- const sidecarDir = (0, import_node_path18.join)(
18268
- (0, import_node_path18.dirname)(transcriptPath),
18269
- (0, import_node_path18.basename)(transcriptPath).replace(/\.jsonl$/, ""),
18497
+ const sidecarDir = (0, import_node_path19.join)(
18498
+ (0, import_node_path19.dirname)(transcriptPath),
18499
+ (0, import_node_path19.basename)(transcriptPath).replace(/\.jsonl$/, ""),
18270
18500
  "subagents"
18271
18501
  );
18272
- if ((0, import_node_fs23.existsSync)(sidecarDir)) {
18502
+ if ((0, import_node_fs24.existsSync)(sidecarDir)) {
18273
18503
  const maxFiles = opts.maxSidecars ?? 200;
18274
18504
  const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
18275
18505
  const found = [];
18276
18506
  const walk = (d, depth) => {
18277
18507
  if (depth > 4) return;
18278
- for (const e of (0, import_node_fs23.readdirSync)(d, { withFileTypes: true })) {
18279
- const p = (0, import_node_path18.join)(d, e.name);
18508
+ for (const e of (0, import_node_fs24.readdirSync)(d, { withFileTypes: true })) {
18509
+ const p = (0, import_node_path19.join)(d, e.name);
18280
18510
  if (e.isDirectory()) {
18281
18511
  walk(p, depth + 1);
18282
18512
  } else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
18283
18513
  try {
18284
- const st = (0, import_node_fs23.statSync)(p);
18514
+ const st = (0, import_node_fs24.statSync)(p);
18285
18515
  found.push({ path: p, size: st.size, mtimeMs: st.mtimeMs });
18286
18516
  } catch {
18287
18517
  result.coverage.malformed++;
@@ -18298,7 +18528,7 @@ function fold(transcriptPath, opts = {}) {
18298
18528
  continue;
18299
18529
  }
18300
18530
  try {
18301
- ingest((0, import_node_fs23.readFileSync)(f.path, "utf8"), "subagent");
18531
+ ingest((0, import_node_fs24.readFileSync)(f.path, "utf8"), "subagent");
18302
18532
  bytes += f.size;
18303
18533
  result.coverage.subagentFiles++;
18304
18534
  } catch {
@@ -18311,17 +18541,29 @@ function fold(transcriptPath, opts = {}) {
18311
18541
  result.authored = [...byPath.values()].sort((a, b) => a.p.localeCompare(b.p));
18312
18542
  result.unknownTypes = [...unknown].sort();
18313
18543
  result.commands = [...commandStats.entries()].map(([cls, s]) => ({ class: cls, ...s })).sort((a, b) => a.class.localeCompare(b.class));
18544
+ result.tools = [...toolStats.entries()].map(([name, s]) => ({
18545
+ name,
18546
+ runs: s.runs,
18547
+ failed: s.failed,
18548
+ last_status: s.last_status,
18549
+ targets: [...s.targets].map((t) => toRepoRelative(t, opts.repoRoot)).sort()
18550
+ })).sort((a, b) => b.runs - a.runs || a.name.localeCompare(b.name));
18551
+ result.tasks = [...taskById.entries()].map(([id, t]) => ({ id, name: t.name, status: t.status })).sort((a, b) => (Number(a.id) || 0) - (Number(b.id) || 0));
18314
18552
  const authoredPaths = new Set(result.authored.map((a) => a.p));
18315
18553
  for (const raw of opts.changedFiles ?? []) {
18316
18554
  const p = toRepoRelative(raw, opts.repoRoot);
18317
18555
  if (!p || authoredPaths.has(p)) continue;
18318
18556
  result.unobserved.push({ p, cause: classifyUnobserved(raw) });
18319
18557
  }
18558
+ result.planApproval = {
18559
+ approvals: flow.approvals,
18560
+ activeSinceLastPrompt: flow.lastApproval >= 0 && flow.lastApproval > flow.lastPrompt
18561
+ };
18320
18562
  return result;
18321
18563
  }
18322
18564
  function classifyUnobserved(path) {
18323
18565
  try {
18324
- const st = (0, import_node_fs23.statSync)(path);
18566
+ const st = (0, import_node_fs24.statSync)(path);
18325
18567
  if (!st.isFile()) return "unreadable";
18326
18568
  } catch {
18327
18569
  return "unreadable";
@@ -18329,7 +18571,7 @@ function classifyUnobserved(path) {
18329
18571
  if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
18330
18572
  return "no_edit_record";
18331
18573
  }
18332
- function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, repoRoot2, tally) {
18574
+ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally, flow) {
18333
18575
  const message = record.message;
18334
18576
  const content = message?.content ?? record.content;
18335
18577
  const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
@@ -18338,6 +18580,32 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18338
18580
  if (blockType === "tool_use") {
18339
18581
  const name = String(block.name ?? "");
18340
18582
  const input = block.input ?? {};
18583
+ if (name && toolStats.size < MAX_TOOL_NAMES) {
18584
+ const prev = toolStats.get(name) ?? { runs: 0, failed: 0, last_status: null, targets: /* @__PURE__ */ new Set() };
18585
+ prev.targets = prev.targets ?? /* @__PURE__ */ new Set();
18586
+ if (prev.targets.size < MAX_TOOL_TARGETS) {
18587
+ const target = toolTarget(name, input);
18588
+ if (target) prev.targets.add(target);
18589
+ }
18590
+ toolStats.set(name, { runs: prev.runs + 1, failed: prev.failed, last_status: null, targets: prev.targets });
18591
+ const toolId = typeof block.id === "string" ? block.id : null;
18592
+ if (toolId) pendingToolName.set(toolId, name);
18593
+ } else if (name && tally) {
18594
+ tally.toolNamesDropped += 1;
18595
+ }
18596
+ if (name === "TaskCreate") {
18597
+ const subject = typeof input.subject === "string" ? input.subject : "";
18598
+ const taskId = typeof block.id === "string" ? block.id : null;
18599
+ if (subject && taskId) pendingTaskName.set(taskId, subject.slice(0, TASK_NAME_CHARS));
18600
+ }
18601
+ if (name === "TaskUpdate") {
18602
+ const id = typeof input.taskId === "string" ? input.taskId : "";
18603
+ const status = typeof input.status === "string" ? input.status : "";
18604
+ if (id && status) {
18605
+ const entry = taskById.get(id);
18606
+ taskById.set(id, { name: entry?.name ?? `#${id}`, status });
18607
+ }
18608
+ }
18341
18609
  if (EDIT_TOOLS.has(name)) {
18342
18610
  const rawPath = typeof input.notebook_path === "string" ? input.notebook_path : typeof input.file_path === "string" ? input.file_path : null;
18343
18611
  if (rawPath && !isInsideRepo(rawPath, repoRoot2)) {
@@ -18377,6 +18645,38 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18377
18645
  }
18378
18646
  if (blockType === "tool_result") {
18379
18647
  const id = typeof block.tool_use_id === "string" ? block.tool_use_id : null;
18648
+ const taskName = id ? pendingTaskName.get(id) : void 0;
18649
+ if (taskName) {
18650
+ pendingTaskName.delete(id);
18651
+ const body = typeof block.content === "string" ? block.content : "";
18652
+ const created = /Task #(\d+)/.exec(body);
18653
+ if (created && taskById.size < MAX_TASKS) {
18654
+ taskById.set(created[1], { name: taskName, status: taskById.get(created[1])?.status ?? "created" });
18655
+ }
18656
+ }
18657
+ const toolName = id ? pendingToolName.get(id) : void 0;
18658
+ if (toolName === "ExitPlanMode" && flow && block.is_error !== true) {
18659
+ const body = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => typeof c.text === "string" ? c.text : "").join(" ") : "";
18660
+ if (/approved your plan/i.test(body)) {
18661
+ flow.lastApproval = flow.seq;
18662
+ flow.approvals += 1;
18663
+ }
18664
+ }
18665
+ if (toolName) {
18666
+ pendingToolName.delete(id);
18667
+ const prevTool = toolStats.get(toolName);
18668
+ if (prevTool) {
18669
+ const failed = block.is_error === true;
18670
+ toolStats.set(toolName, {
18671
+ runs: prevTool.runs,
18672
+ failed: prevTool.failed + (failed ? 1 : 0),
18673
+ last_status: block.is_error === true ? 1 : block.is_error === false ? 0 : null,
18674
+ // Carried, not rebuilt: the targets were collected on the `tool_use`
18675
+ // side and dropping them here would empty the ledger on every result.
18676
+ targets: prevTool.targets
18677
+ });
18678
+ }
18679
+ }
18380
18680
  const cls = id ? pendingByToolUse.get(id) : void 0;
18381
18681
  if (!cls) continue;
18382
18682
  pendingByToolUse.delete(id);
@@ -18419,8 +18719,13 @@ function checkConservation(changedFiles, result, repoRoot2) {
18419
18719
  // src/commands/analyze/phases/06-evidence.ts
18420
18720
  async function evidence(run) {
18421
18721
  const { opts } = run;
18422
- const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath } = run;
18722
+ const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run;
18423
18723
  let { analysisMode, earlyFold } = run;
18724
+ const recordFlip = (stage) => {
18725
+ if (run.modeDecision) run.modeDecision = { ...run.modeDecision, resolved: "plan", flip: stage };
18726
+ logEvent("mode_flipped", { stage, to: "plan" });
18727
+ };
18728
+ const planWorthy = !!assistantResponse && !turnAuthoredCode;
18424
18729
  let staticResults = {
18425
18730
  tool: "@codacy/analysis-cli",
18426
18731
  findings: [],
@@ -18440,8 +18745,9 @@ async function evidence(run) {
18440
18745
  const debounceSeconds = parseInt(opts.debounce, 10);
18441
18746
  const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
18442
18747
  if (debounceSkip) {
18443
- if (assistantResponse) {
18748
+ if (planWorthy) {
18444
18749
  analysisMode = "plan";
18750
+ recordFlip("debounce");
18445
18751
  } else {
18446
18752
  await passAndExit(run, debounceSkip, "debounce");
18447
18753
  }
@@ -18450,8 +18756,9 @@ async function evidence(run) {
18450
18756
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18451
18757
  const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
18452
18758
  if (mtimeSkip) {
18453
- if (assistantResponse) {
18759
+ if (planWorthy) {
18454
18760
  analysisMode = "plan";
18761
+ recordFlip("mtime");
18455
18762
  } else {
18456
18763
  await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
18457
18764
  }
@@ -18462,8 +18769,9 @@ async function evidence(run) {
18462
18769
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18463
18770
  const hashResult = checkContentHash(allCheckable, baselineSessionId);
18464
18771
  if (hashResult.skip) {
18465
- if (assistantResponse) {
18772
+ if (planWorthy) {
18466
18773
  analysisMode = "plan";
18774
+ recordFlip("content-hash");
18467
18775
  } else {
18468
18776
  await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
18469
18777
  }
@@ -18525,8 +18833,9 @@ async function evidence(run) {
18525
18833
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
18526
18834
  });
18527
18835
  if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
18528
- if (assistantResponse) {
18836
+ if (planWorthy) {
18529
18837
  analysisMode = "plan";
18838
+ recordFlip("empty-after-scoping");
18530
18839
  } else {
18531
18840
  await passAndExit(
18532
18841
  run,
@@ -18546,32 +18855,32 @@ async function evidence(run) {
18546
18855
  snapshotResult = generateSnapshotDiffs(codeDelta.files);
18547
18856
  }
18548
18857
  currentCommit = getCurrentCommit();
18549
- iteration = readIterationState(currentCommit).iteration;
18858
+ iteration = readIteration(currentCommit);
18550
18859
  }
18551
18860
  }
18552
18861
  if (analysisMode === "plan") {
18553
18862
  recordAnalysisStart();
18554
18863
  currentCommit = getCurrentCommit();
18555
- iteration = readIterationState(currentCommit).iteration;
18864
+ iteration = readIteration(currentCommit);
18556
18865
  }
18557
18866
  Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
18558
18867
  }
18559
18868
 
18560
18869
  // src/lib/cache-cleanup.ts
18561
- var import_node_fs24 = require("node:fs");
18562
- var import_node_path19 = require("node:path");
18870
+ var import_node_fs25 = require("node:fs");
18871
+ var import_node_path20 = require("node:path");
18563
18872
  var CACHE_TTL_DAYS = 7;
18564
18873
  function pruneStaleCache() {
18565
18874
  try {
18566
18875
  const dir = projectPath(CACHE_DIR);
18567
18876
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
18568
- for (const entry of (0, import_node_fs24.readdirSync)(dir)) {
18877
+ for (const entry of (0, import_node_fs25.readdirSync)(dir)) {
18569
18878
  if (!entry.startsWith("pending-")) continue;
18570
- const path = (0, import_node_path19.join)(dir, entry);
18879
+ const path = (0, import_node_path20.join)(dir, entry);
18571
18880
  try {
18572
- const stat3 = (0, import_node_fs24.statSync)(path);
18881
+ const stat3 = (0, import_node_fs25.statSync)(path);
18573
18882
  if (stat3.mtimeMs < cutoff) {
18574
- (0, import_node_fs24.unlinkSync)(path);
18883
+ (0, import_node_fs25.unlinkSync)(path);
18575
18884
  logEvent("cache_entry_pruned", {
18576
18885
  path: entry,
18577
18886
  age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
@@ -18585,7 +18894,7 @@ function pruneStaleCache() {
18585
18894
  }
18586
18895
 
18587
18896
  // src/lib/context-files.ts
18588
- var import_node_fs25 = require("node:fs");
18897
+ var import_node_fs26 = require("node:fs");
18589
18898
  var MAX_CONTEXT_FILES = 10;
18590
18899
  var MAX_CONTEXT_FILE_BYTES = 10240;
18591
18900
  var MAX_CONTEXT_TOTAL_BYTES = 51200;
@@ -18600,8 +18909,13 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18600
18909
  logEvent("context_file_skipped", { path: filePath, reason: "verity_owned" });
18601
18910
  continue;
18602
18911
  }
18912
+ const safePath = resolveInside(process.cwd(), filePath);
18913
+ if (!safePath) {
18914
+ logEvent("context_file_skipped", { path: filePath, reason: "outside_repo" });
18915
+ continue;
18916
+ }
18603
18917
  try {
18604
- const content = (0, import_node_fs25.readFileSync)(filePath, "utf8");
18918
+ const content = (0, import_node_fs26.readFileSync)(safePath, "utf8");
18605
18919
  const bytes = Buffer.byteLength(content);
18606
18920
  if (bytes > MAX_CONTEXT_FILE_BYTES) {
18607
18921
  logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
@@ -18648,7 +18962,8 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18648
18962
  // src/commands/analyze/phases/07-context-files.ts
18649
18963
  async function contextFiles(run) {
18650
18964
  const { codeDelta, contextFilePaths } = run;
18651
- const contextFiles2 = gatherContextFiles(contextFilePaths, codeDelta.files);
18965
+ const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
18966
+ const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
18652
18967
  for (const f of codeDelta.files) {
18653
18968
  f.role = "delta";
18654
18969
  }
@@ -18662,8 +18977,8 @@ async function contextFiles(run) {
18662
18977
 
18663
18978
  // src/lib/seed-runner.ts
18664
18979
  var import_promises11 = require("node:fs/promises");
18665
- var import_node_fs26 = require("node:fs");
18666
- var import_node_path20 = require("node:path");
18980
+ var import_node_fs27 = require("node:fs");
18981
+ var import_node_path21 = require("node:path");
18667
18982
  var import_yaml2 = __toESM(require_dist());
18668
18983
 
18669
18984
  // src/lib/seed.ts
@@ -18902,7 +19217,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
18902
19217
  return fm;
18903
19218
  }
18904
19219
  async function runSeed(opts) {
18905
- if (!(0, import_node_fs26.existsSync)(STANDARD_FILE)) {
19220
+ if (!(0, import_node_fs27.existsSync)(STANDARD_FILE)) {
18906
19221
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
18907
19222
  }
18908
19223
  let standardDoc;
@@ -18914,7 +19229,7 @@ async function runSeed(opts) {
18914
19229
  }
18915
19230
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
18916
19231
  let readmeContent;
18917
- if ((0, import_node_fs26.existsSync)("README.md")) {
19232
+ if ((0, import_node_fs27.existsSync)("README.md")) {
18918
19233
  try {
18919
19234
  readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
18920
19235
  } catch {
@@ -18922,7 +19237,7 @@ async function runSeed(opts) {
18922
19237
  }
18923
19238
  let claudeMdContent;
18924
19239
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
18925
- if ((0, import_node_fs26.existsSync)(p)) {
19240
+ if ((0, import_node_fs27.existsSync)(p)) {
18926
19241
  try {
18927
19242
  claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
18928
19243
  break;
@@ -18945,8 +19260,8 @@ async function runSeed(opts) {
18945
19260
  if (candidates.length === 0) {
18946
19261
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
18947
19262
  }
18948
- const overviewPath = (0, import_node_path20.join)(MEMORY_DIR, "domain", "project-overview.md");
18949
- if ((0, import_node_fs26.existsSync)(overviewPath) && !opts.force) {
19263
+ const overviewPath = (0, import_node_path21.join)(MEMORY_DIR, "domain", "project-overview.md");
19264
+ if ((0, import_node_fs27.existsSync)(overviewPath) && !opts.force) {
18950
19265
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
18951
19266
  }
18952
19267
  if (opts.dryRun) {
@@ -18981,9 +19296,14 @@ async function runSeed(opts) {
18981
19296
  }
18982
19297
  const nodeId = res.data.node_id;
18983
19298
  const filePathRel = res.data.file_path;
18984
- const targetPath = (0, import_node_path20.join)(MEMORY_DIR, filePathRel);
19299
+ const targetPath = resolveInside(MEMORY_DIR, filePathRel);
19300
+ if (!targetPath) {
19301
+ opts.onFailed?.(c, `Server returned an out-of-bounds file_path (${String(filePathRel)}); refusing to write outside the memory directory.`);
19302
+ failed++;
19303
+ continue;
19304
+ }
18985
19305
  try {
18986
- await (0, import_promises11.mkdir)((0, import_node_path20.dirname)(targetPath), { recursive: true });
19306
+ await (0, import_promises11.mkdir)((0, import_node_path21.dirname)(targetPath), { recursive: true });
18987
19307
  await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
18988
19308
  created++;
18989
19309
  opts.onCreated?.(nodeId, filePathRel, c);
@@ -18996,8 +19316,8 @@ async function runSeed(opts) {
18996
19316
  }
18997
19317
 
18998
19318
  // src/commands/analyze/phases/08-memory-manifest.ts
18999
- var import_node_fs27 = require("node:fs");
19000
- var import_node_path21 = require("node:path");
19319
+ var import_node_fs28 = require("node:fs");
19320
+ var import_node_path22 = require("node:path");
19001
19321
  async function memoryManifest(run) {
19002
19322
  const { globals } = run;
19003
19323
  const { serviceUrl, token } = run;
@@ -19007,9 +19327,9 @@ async function memoryManifest(run) {
19007
19327
  let autoSeedNotice = null;
19008
19328
  try {
19009
19329
  await ensureMemoryDir();
19010
- const seedMarker = (0, import_node_path21.join)(VERITY_DIR, ".seeded");
19011
- const hasStandard = (0, import_node_fs27.existsSync)(STANDARD_FILE);
19012
- const alreadyTried = (0, import_node_fs27.existsSync)(seedMarker);
19330
+ const seedMarker = (0, import_node_path22.join)(VERITY_DIR, ".seeded");
19331
+ const hasStandard = (0, import_node_fs28.existsSync)(STANDARD_FILE);
19332
+ const alreadyTried = (0, import_node_fs28.existsSync)(seedMarker);
19013
19333
  if (hasStandard && !alreadyTried) {
19014
19334
  const preManifest = await buildManifest();
19015
19335
  if (preManifest.nodes.length === 0) {
@@ -19022,7 +19342,7 @@ async function memoryManifest(run) {
19022
19342
  dryRun: false
19023
19343
  });
19024
19344
  if (seedResult.created > 0) {
19025
- (0, import_node_fs27.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
19345
+ (0, import_node_fs28.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
19026
19346
  `);
19027
19347
  autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
19028
19348
  logEvent("auto_seed_ran", {
@@ -19030,7 +19350,7 @@ async function memoryManifest(run) {
19030
19350
  failed: seedResult.failed
19031
19351
  });
19032
19352
  } else if (seedResult.skipped === "already_seeded") {
19033
- (0, import_node_fs27.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
19353
+ (0, import_node_fs28.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
19034
19354
  `);
19035
19355
  } else {
19036
19356
  logEvent("auto_seed_noop", {
@@ -19125,7 +19445,7 @@ function computeIncrement(reviewedPaths, hashOf, priorAuthored) {
19125
19445
  }
19126
19446
 
19127
19447
  // src/commands/analyze/phases/10-working-memory.ts
19128
- var import_node_path22 = require("node:path");
19448
+ var import_node_path23 = require("node:path");
19129
19449
  async function workingMemory(run) {
19130
19450
  const { opts } = run;
19131
19451
  const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run;
@@ -19137,7 +19457,7 @@ async function workingMemory(run) {
19137
19457
  const priorState = foldForMarks(memorySession.d);
19138
19458
  incrementReport = computeIncrement(
19139
19459
  allForReview,
19140
- (p) => fileHash((0, import_node_path22.join)(repoRoot(), p)),
19460
+ (p) => fileHash((0, import_node_path23.join)(repoRoot(), p)),
19141
19461
  priorState.authored_all.map((a) => ({
19142
19462
  path: a.path,
19143
19463
  hash_at_last_verdict: a.hash_at_last_verdict
@@ -19218,6 +19538,54 @@ async function workingMemory(run) {
19218
19538
  Object.assign(run, { incrementReport, memory, memorySession, reachability });
19219
19539
  }
19220
19540
 
19541
+ // src/lib/note-budget.ts
19542
+ var import_node_fs29 = require("node:fs");
19543
+ var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
19544
+ var EPISODE_STALE_SECONDS = 30 * 60;
19545
+ var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
19546
+ function resolveEpisode(prev, signals) {
19547
+ if (!prev) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19548
+ if (signals.humanSpoke) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19549
+ if (signals.rawFail) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19550
+ if (prev.tasksCompleted !== signals.tasksCompleted) {
19551
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19552
+ }
19553
+ if (prev.ts > 0 && signals.now - prev.ts > EPISODE_STALE_SECONDS) {
19554
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19555
+ }
19556
+ return prev;
19557
+ }
19558
+ function advisoryBudgetSpent(episode, rawDecision) {
19559
+ const budget = ADVISORY_BUDGET[rawDecision] ?? ADVISORY_BUDGET.WARN;
19560
+ return episode.delivered >= budget;
19561
+ }
19562
+ function readAdvisoryEpisode(sessionId) {
19563
+ const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
19564
+ if (!(0, import_node_fs29.existsSync)(file)) return null;
19565
+ try {
19566
+ const o = JSON.parse((0, import_node_fs29.readFileSync)(file, "utf-8")) ?? {};
19567
+ const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
19568
+ if (isNaN(delivered)) return null;
19569
+ return {
19570
+ delivered,
19571
+ tasksCompleted: typeof o.tasksCompleted === "number" ? o.tasksCompleted : 0,
19572
+ ts: typeof o.ts === "number" ? o.ts : 0
19573
+ };
19574
+ } catch {
19575
+ return null;
19576
+ }
19577
+ }
19578
+ function writeAdvisoryEpisode(episode, sessionId) {
19579
+ try {
19580
+ (0, import_node_fs29.mkdirSync)(VERITY_DIR, { recursive: true });
19581
+ (0, import_node_fs29.writeFileSync)(
19582
+ scopedFile(ADVISORY_EPISODE_FILE, sessionId),
19583
+ JSON.stringify({ v: 1, ...episode })
19584
+ );
19585
+ } catch {
19586
+ }
19587
+ }
19588
+
19221
19589
  // src/lib/run-mode.ts
19222
19590
  function parseAutonomousEnv(raw) {
19223
19591
  if (raw === void 0) return void 0;
@@ -19311,7 +19679,13 @@ async function buildRequest(run) {
19311
19679
  excluded_by_reason: excludedByReason,
19312
19680
  // was the transcript itself truncated? The 256 KB window means "this turn"
19313
19681
  // can quietly mean "the last 256 KB of it".
19314
- transcript_windowed: actionSummary?.transcript_windowed ?? null
19682
+ transcript_windowed: actionSummary?.transcript_windowed ?? null,
19683
+ // The advisory budget's fleet counter-metric (note-budget.ts): deliveries in
19684
+ // the episode as of the PREVIOUS turn — this runs before phase 13 updates
19685
+ // the state, so the number is one turn lagged by construction. The
19686
+ // degenerate win for the budget is a dead channel that looks like clean
19687
+ // code; this is what makes "did delivery rate collapse" a query.
19688
+ advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
19315
19689
  };
19316
19690
  const requestBody = {
19317
19691
  coverage_telemetry: coverageTelemetry,
@@ -19370,6 +19744,8 @@ async function buildRequest(run) {
19370
19744
  authored_since_verdict: memorySession ? foldDossier(memorySession.d).authored_all.filter((a) => a.hash_now !== a.hash_at_last_verdict).map((a) => a.path) : [],
19371
19745
  unobserved: foldResult?.unobserved ?? [],
19372
19746
  commands: foldResult?.commands ?? [],
19747
+ tools: foldResult?.tools ?? [],
19748
+ tasks: foldResult?.tasks ?? [],
19373
19749
  unknown_types: foldResult?.unknownTypes ?? [],
19374
19750
  coverage: foldResult?.coverage ?? { recordCounts: {}, totalRecords: 0, malformed: 0, subagentFiles: 0, subagentSkipped: 0, complete: false },
19375
19751
  // INV-19, computed client-side and sent so a conservation failure is
@@ -19448,7 +19824,8 @@ async function buildRequest(run) {
19448
19824
  }
19449
19825
  const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
19450
19826
  const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
19451
- const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task;
19827
+ const planApprovalActive = foldResult?.planApproval?.activeSinceLastPrompt === true;
19828
+ const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task || planApprovalActive;
19452
19829
  if (hasIntent) {
19453
19830
  const intentContext = {};
19454
19831
  if (conversation && conversation.prompts.length > 0) {
@@ -19480,6 +19857,10 @@ async function buildRequest(run) {
19480
19857
  intentContext.user_prompt = w4Task.goal;
19481
19858
  logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
19482
19859
  }
19860
+ if (planApprovalActive) {
19861
+ intentContext.plan_approved = true;
19862
+ logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
19863
+ }
19483
19864
  if (assistantResponse) {
19484
19865
  const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
19485
19866
  intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
@@ -19499,14 +19880,14 @@ async function buildRequest(run) {
19499
19880
  }
19500
19881
 
19501
19882
  // src/lib/offline.ts
19502
- var import_node_fs28 = require("node:fs");
19883
+ var import_node_fs30 = require("node:fs");
19503
19884
  var import_node_crypto11 = require("node:crypto");
19504
19885
  function cacheRequest(body) {
19505
19886
  try {
19506
- (0, import_node_fs28.mkdirSync)(CACHE_DIR, { recursive: true });
19887
+ (0, import_node_fs30.mkdirSync)(CACHE_DIR, { recursive: true });
19507
19888
  const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
19508
19889
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
19509
- (0, import_node_fs28.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
19890
+ (0, import_node_fs30.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
19510
19891
  } catch {
19511
19892
  }
19512
19893
  }
@@ -19625,10 +20006,10 @@ async function transmit(run) {
19625
20006
  }
19626
20007
 
19627
20008
  // src/commands/analyze/phases/13-reconcile.ts
19628
- var import_node_fs29 = require("node:fs");
19629
- var import_node_path23 = require("node:path");
20009
+ var import_node_fs31 = require("node:fs");
20010
+ var import_node_path24 = require("node:path");
19630
20011
  async function reconcile(run) {
19631
- const { actionSummary, allChanged, analyzable, baseline, codeDelta, contentHash, conversation, decision, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
20012
+ const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
19632
20013
  const sentPaths = codeDelta.files.map((f) => f.path);
19633
20014
  let openElsewhere = [];
19634
20015
  if (memorySession) {
@@ -19636,7 +20017,7 @@ async function reconcile(run) {
19636
20017
  const st = foldDossier(memorySession.d);
19637
20018
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
19638
20019
  try {
19639
- const src = (0, import_node_fs29.readFileSync)((0, import_node_path23.join)(repoRoot(), file), "utf8").split("\n");
20020
+ const src = (0, import_node_fs31.readFileSync)((0, import_node_path24.join)(repoRoot(), file), "utf8").split("\n");
19640
20021
  const at = src[line - 1];
19641
20022
  return at === void 0 ? null : lineSha(at);
19642
20023
  } catch {
@@ -19646,6 +20027,7 @@ async function reconcile(run) {
19646
20027
  } catch {
19647
20028
  }
19648
20029
  }
20030
+ const { kept: externalChanged, owned: verityOwned } = partitionVerityOwned(allChanged);
19649
20031
  const reviewCoverage = {
19650
20032
  reviewed: sentPaths,
19651
20033
  // Declared drops from the stages that DO report themselves today. The other
@@ -19687,11 +20069,20 @@ async function reconcile(run) {
19687
20069
  stage: "baseline-scoping",
19688
20070
  kind: "policy"
19689
20071
  })),
20072
+ // ⚠ VERITY'S OWN FILES, named as such — not laundered into the
20073
+ // extension bucket below, where "we do not review our own installer's
20074
+ // dirt" would read as "a changed README". See self-scope.ts.
20075
+ ...verityOwned.map((path) => ({
20076
+ path,
20077
+ reason: "verity-owned",
20078
+ stage: "self-scope",
20079
+ kind: "policy"
20080
+ })),
19690
20081
  // The extension allowlist. POLICY: a changed README was never going to be
19691
20082
  // reviewed, and calling that a coverage gap would downgrade nearly every
19692
20083
  // PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
19693
20084
  // so "what did Verity ignore entirely" is answerable.
19694
- ...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
20085
+ ...externalChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
19695
20086
  path,
19696
20087
  reason: "not-a-reviewed-file-type",
19697
20088
  stage: "extension-allowlist",
@@ -19728,6 +20119,29 @@ async function reconcile(run) {
19728
20119
  decision
19729
20120
  });
19730
20121
  }
20122
+ const episodeSignals = {
20123
+ humanSpoke: (conversation?.prompts?.length ?? 0) > 0,
20124
+ rawFail: decision === "FAIL",
20125
+ tasksCompleted: (foldResult?.tasks ?? []).filter((t) => t.status === "completed").length,
20126
+ now: Math.floor(Date.now() / 1e3)
20127
+ };
20128
+ let episode = resolveEpisode(readAdvisoryEpisode(baselineSessionId), episodeSignals);
20129
+ const contentClass = classifyChannelContent(channelInputFrom(response));
20130
+ const wouldCarryAdvisory = contentClass.advisory || openElsewhere.length > 0;
20131
+ if (decision !== "FAIL" && !silenced && wouldCarryAdvisory && !contentClass.refusal && !contentClass.intentFlag && advisoryBudgetSpent(episode, decision)) {
20132
+ silenced = "note-budget";
20133
+ logEvent("channel_silenced", {
20134
+ reason: silenced,
20135
+ run_id: response.run_id ?? turnId,
20136
+ decision,
20137
+ episode_delivered: episode.delivered
20138
+ });
20139
+ }
20140
+ const deliveringAdvisory = decision !== "FAIL" && !silenced && wouldCarryAdvisory;
20141
+ writeAdvisoryEpisode(
20142
+ { ...episode, delivered: episode.delivered + (deliveringAdvisory ? 1 : 0), ts: episodeSignals.now },
20143
+ baselineSessionId
20144
+ );
19731
20145
  let intentRepeatCount = 0;
19732
20146
  const priorPendingFingerprints = memorySession ? (() => {
19733
20147
  try {
@@ -19743,6 +20157,11 @@ async function reconcile(run) {
19743
20157
  decision,
19744
20158
  branch: getCurrentBranch(),
19745
20159
  watermarkSha: watermarkIsPartial ? null : watermarkHash,
20160
+ // The byte witness — the same "only honest definition of reviewed" the
20161
+ // coverage column uses. A finding on a path outside this set records no
20162
+ // statement (plan-mode prose anchored to unsent files must not become
20163
+ // "STILL OPEN … the tree is not clean").
20164
+ sentPaths,
19746
20165
  findings: response.findings?.map((f) => ({
19747
20166
  file: f.file,
19748
20167
  line: f.line,
@@ -19809,6 +20228,39 @@ ${YELLOW2}${note}${NC2}
19809
20228
  return exit(0);
19810
20229
  }
19811
20230
 
20231
+ // src/lib/may-block.ts
20232
+ var HARD_BLOCK_CEILING = 5;
20233
+ function mayBlock(input) {
20234
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20235
+ if (input.reviewedFileCount === 0 && input.staticFindingCount === 0) {
20236
+ return { block: false, release: "no-code-reviewed" };
20237
+ }
20238
+ if (input.cycleCutFired) {
20239
+ return { block: false, release: "nothing-moved" };
20240
+ }
20241
+ if (input.attempts > input.maxIterations) {
20242
+ return { block: false, release: "same-problem-cap" };
20243
+ }
20244
+ if (input.blocks > ceiling) {
20245
+ return { block: false, release: "block-ceiling" };
20246
+ }
20247
+ return { block: true, release: null };
20248
+ }
20249
+ function describeRelease(release, input) {
20250
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20251
+ 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.";
20252
+ switch (release) {
20253
+ case "no-code-reviewed":
20254
+ 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}`;
20255
+ case "nothing-moved":
20256
+ return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
20257
+ case "same-problem-cap":
20258
+ return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
20259
+ case "block-ceiling":
20260
+ return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
20261
+ }
20262
+ }
20263
+
19812
20264
  // src/lib/remediation-guard.ts
19813
20265
  var TOOL_CONFIG_PATTERNS = [
19814
20266
  /(^|\/)\.codacy\//,
@@ -19851,19 +20303,7 @@ function screenRemediation(fix, findingFile) {
19851
20303
 
19852
20304
  // src/commands/analyze/phases/14-render.ts
19853
20305
  function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
19854
- const metadata = response.metadata ?? {};
19855
- const intent = response.intent_alignment ?? {};
19856
- return buildAgentContext({
19857
- intentRepeat,
19858
- priorPendingFingerprints,
19859
- gateDecision: String(response.gate_decision ?? ""),
19860
- findings: response.findings ?? [],
19861
- pendingItems: response.pending_items ?? [],
19862
- reviewStatus: metadata.review_status,
19863
- coverage: metadata.coverage,
19864
- intentVerdict: intent.verdict,
19865
- intentGaps: intent.gaps
19866
- });
20306
+ return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
19867
20307
  }
19868
20308
  async function render(run) {
19869
20309
  const { opts, globals } = run;
@@ -19957,38 +20397,63 @@ async function render(run) {
19957
20397
  reverify_by: response.reverify_by
19958
20398
  });
19959
20399
  const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
19960
- let capReleased = false;
20400
+ let release = null;
19961
20401
  let effectiveDecision = decision;
19962
20402
  if (decision === "FAIL") {
19963
- const blocking = (response.findings ?? []).filter((f) => {
20403
+ const findings = response.findings ?? [];
20404
+ const blocking = findings.filter((f) => {
19964
20405
  const sev = String(f.severity ?? "").toLowerCase();
19965
20406
  return sev === "critical" || sev === "high";
19966
20407
  });
19967
20408
  const fingerprint = findingsFingerprint(blocking);
19968
- const prior = readIterationState(currentCommit);
20409
+ const prior = readBlockState(currentCommit, {
20410
+ newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
20411
+ });
19969
20412
  const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
19970
- const nextIteration = sameProblem ? prior.iteration + 1 : 1;
19971
20413
  const maxIterations = parseInt(opts.maxIterations, 10);
19972
- writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
19973
- iteration = nextIteration;
19974
- if (nextIteration > maxIterations) {
19975
- capReleased = true;
20414
+ const attempts = sameProblem ? prior.attempts + 1 : 1;
20415
+ const blocks = prior.blocks + 1;
20416
+ const decisionNow = mayBlock({
20417
+ reviewedFileCount: codeDelta.files.length,
20418
+ staticFindingCount: run.staticResults?.findings?.length ?? 0,
20419
+ cycleCutFired: silenced !== null,
20420
+ attempts,
20421
+ blocks,
20422
+ maxIterations
20423
+ });
20424
+ if (decisionNow.block) {
20425
+ writeBlockState(currentCommit, { attempts, blocks, fingerprint });
20426
+ iteration = attempts;
20427
+ } else {
20428
+ release = decisionNow.release;
19976
20429
  effectiveDecision = "WARN";
19977
- logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
20430
+ logEvent("block_released", {
20431
+ reason: release,
20432
+ attempts,
20433
+ blocks,
20434
+ reviewed_files: codeDelta.files.length,
20435
+ cycle_cut: silenced,
20436
+ fingerprint
20437
+ });
19978
20438
  }
19979
20439
  }
19980
- if (capReleased) {
20440
+ if (release) {
19981
20441
  const findings = response.findings ?? [];
19982
20442
  const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
20443
+ const summary = describeRelease(release, {
20444
+ findingCount: findings.length,
20445
+ maxIterations: parseInt(opts.maxIterations, 10)
20446
+ });
19983
20447
  emitVerdict({
19984
20448
  proposed: "WARN",
19985
20449
  changed: run.changedUniverse,
19986
20450
  coverage: reviewCoverage,
19987
- userSummary: `Verity: WARN \u2014 self-healing limit (${opts.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${findings.length} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.
19988
- ${lines.join("\n")}`,
20451
+ userSummary: lines.length > 0 ? `${summary}
20452
+ ${lines.join("\n")}` : summary,
19989
20453
  agentContext: null,
19990
20454
  silenced: true
19991
20455
  });
20456
+ return;
19992
20457
  }
19993
20458
  switch (effectiveDecision) {
19994
20459
  case "FAIL": {
@@ -20087,7 +20552,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20087
20552
  break;
20088
20553
  }
20089
20554
  case "PASS": {
20090
- writeIteration(1, currentCommit, contentHash ?? void 0);
20555
+ resetBlockState(currentCommit);
20091
20556
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20092
20557
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20093
20558
  let userSummary = response.user_summary ?? "Verity: PASS";
@@ -20108,6 +20573,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20108
20573
  break;
20109
20574
  }
20110
20575
  case "WARN": {
20576
+ if (decision !== "FAIL") resetBlockState(currentCommit);
20111
20577
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20112
20578
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20113
20579
  let userSummary = response.user_summary ?? "Verity: WARN";
@@ -20206,7 +20672,7 @@ async function runAnalyze(opts, globals) {
20206
20672
  }
20207
20673
 
20208
20674
  // src/commands/baseline.ts
20209
- var import_node_fs30 = require("node:fs");
20675
+ var import_node_fs32 = require("node:fs");
20210
20676
  function registerBaselineCommands(program2) {
20211
20677
  const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
20212
20678
  baseline.command("capture").description("Snapshot the working tree at task start (used by SessionStart hook)").option("--session-id <id>", "Session id (overrides any value from stdin)").option("--source <source>", "Lifecycle hint: startup|resume|clear|compact").action(async (opts) => {
@@ -20215,7 +20681,7 @@ function registerBaselineCommands(program2) {
20215
20681
  process.chdir(repoRoot());
20216
20682
  } catch {
20217
20683
  }
20218
- if (!(0, import_node_fs30.existsSync)(VERITY_DIR)) {
20684
+ if (!(0, import_node_fs32.existsSync)(VERITY_DIR)) {
20219
20685
  process.exit(0);
20220
20686
  }
20221
20687
  let sessionId = opts.sessionId;
@@ -20255,7 +20721,7 @@ async function readStdin() {
20255
20721
  }
20256
20722
 
20257
20723
  // src/commands/review.ts
20258
- var import_node_fs31 = require("node:fs");
20724
+ var import_node_fs33 = require("node:fs");
20259
20725
  function registerReviewCommand(program2) {
20260
20726
  program2.command("review").description("Run on-demand Verity analysis (advisory, never blocks)").requiredOption("--files <paths>", "Comma-separated file list").option("--changed <paths>", "Subset of --files that were modified").option("--intent <text>", "User intent description (max 2000 chars)").option("--specs <paths>", "Comma-separated spec file paths").option("--json", "Output raw JSON response").action(async (opts) => {
20261
20727
  const globals = program2.opts();
@@ -20274,7 +20740,7 @@ async function runReview(opts, globals) {
20274
20740
  const securityFiles = filterSecurity(allFiles);
20275
20741
  let staticResults;
20276
20742
  if (isCodacyAvailable()) {
20277
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs31.existsSync)(f) || resolveFile(f) !== null);
20743
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs33.existsSync)(f) || resolveFile(f) !== null);
20278
20744
  staticResults = runCodacyAnalysis(scannable);
20279
20745
  } else {
20280
20746
  staticResults = {
@@ -20300,10 +20766,10 @@ async function runReview(opts, globals) {
20300
20766
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
20301
20767
  specs = [];
20302
20768
  for (const p of specPaths) {
20303
- if (!(0, import_node_fs31.existsSync)(p)) continue;
20769
+ if (!(0, import_node_fs33.existsSync)(p)) continue;
20304
20770
  try {
20305
- const { readFileSync: readFileSync18 } = await import("node:fs");
20306
- const content = readFileSync18(p, "utf-8");
20771
+ const { readFileSync: readFileSync19 } = await import("node:fs");
20772
+ const content = readFileSync19(p, "utf-8");
20307
20773
  specs.push({ path: p, content: content.slice(0, 10240) });
20308
20774
  } catch {
20309
20775
  }
@@ -20360,15 +20826,15 @@ async function runReview(opts, globals) {
20360
20826
  }
20361
20827
 
20362
20828
  // src/commands/guard.ts
20363
- var import_node_fs32 = require("node:fs");
20364
- var import_node_path24 = require("node:path");
20829
+ var import_node_fs34 = require("node:fs");
20830
+ var import_node_path25 = require("node:path");
20365
20831
  var GUARD_BLOCK_CAP = 2;
20366
- var GUARD_ITER_FILE = (0, import_node_path24.join)(VERITY_DIR, ".guard-iteration");
20832
+ var GUARD_ITER_FILE = (0, import_node_path25.join)(VERITY_DIR, ".guard-iteration");
20367
20833
  function readPreToolUseStdin() {
20368
20834
  const empty = { command: "", cwd: null, sessionId: null };
20369
- return new Promise((resolve2) => {
20835
+ return new Promise((resolve3) => {
20370
20836
  try {
20371
- if (process.stdin.isTTY) return resolve2(empty);
20837
+ if (process.stdin.isTTY) return resolve3(empty);
20372
20838
  const chunks = [];
20373
20839
  let timer;
20374
20840
  let settled = false;
@@ -20381,7 +20847,7 @@ function readPreToolUseStdin() {
20381
20847
  process.stdin.removeListener("end", onEnd);
20382
20848
  process.stdin.removeListener("error", onError);
20383
20849
  process.stdin.pause();
20384
- resolve2(value);
20850
+ resolve3(value);
20385
20851
  };
20386
20852
  const onEnd = () => {
20387
20853
  try {
@@ -20402,7 +20868,7 @@ function readPreToolUseStdin() {
20402
20868
  process.stdin.on("error", onError);
20403
20869
  process.stdin.resume();
20404
20870
  } catch {
20405
- resolve2(empty);
20871
+ resolve3(empty);
20406
20872
  }
20407
20873
  });
20408
20874
  }
@@ -20427,7 +20893,7 @@ function classifyCommand2(command, on) {
20427
20893
  }
20428
20894
  function readIterMap() {
20429
20895
  try {
20430
- const raw = JSON.parse((0, import_node_fs32.readFileSync)(GUARD_ITER_FILE, "utf-8"));
20896
+ const raw = JSON.parse((0, import_node_fs34.readFileSync)(GUARD_ITER_FILE, "utf-8"));
20431
20897
  if (raw && typeof raw === "object") {
20432
20898
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
20433
20899
  return { [raw.moment]: raw.count };
@@ -20447,10 +20913,10 @@ function readIter(moment) {
20447
20913
  }
20448
20914
  function writeIter(moment, count) {
20449
20915
  try {
20450
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20916
+ (0, import_node_fs34.mkdirSync)(VERITY_DIR, { recursive: true });
20451
20917
  const map = readIterMap();
20452
20918
  map[moment] = count;
20453
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20919
+ (0, import_node_fs34.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20454
20920
  } catch {
20455
20921
  }
20456
20922
  }
@@ -20460,10 +20926,10 @@ function resetIter(moment) {
20460
20926
  if (!(moment in map)) return;
20461
20927
  delete map[moment];
20462
20928
  if (Object.keys(map).length === 0) {
20463
- if ((0, import_node_fs32.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs32.unlinkSync)(GUARD_ITER_FILE);
20929
+ if ((0, import_node_fs34.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs34.unlinkSync)(GUARD_ITER_FILE);
20464
20930
  } else {
20465
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20466
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20931
+ (0, import_node_fs34.mkdirSync)(VERITY_DIR, { recursive: true });
20932
+ (0, import_node_fs34.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20467
20933
  }
20468
20934
  } catch {
20469
20935
  }
@@ -20527,7 +20993,7 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
20527
20993
  const securityFiles = filterSecurity(files);
20528
20994
  let staticResults;
20529
20995
  if (isCodacyAvailable()) {
20530
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs32.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
20996
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs34.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
20531
20997
  staticResults = runCodacyAnalysis(scannable);
20532
20998
  } else {
20533
20999
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -20572,7 +21038,7 @@ function emitAllowNotice(userMsg, agentMsg) {
20572
21038
  async function runGuard(opts, globals) {
20573
21039
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
20574
21040
  const { command, cwd, sessionId } = await readPreToolUseStdin();
20575
- if (cwd && (0, import_node_fs32.existsSync)(cwd)) {
21041
+ if (cwd && (0, import_node_fs34.existsSync)(cwd)) {
20576
21042
  try {
20577
21043
  process.chdir(cwd);
20578
21044
  } catch {
@@ -20678,15 +21144,15 @@ function writeBlockMessage(moment, response) {
20678
21144
  }
20679
21145
 
20680
21146
  // src/commands/init.ts
20681
- var import_node_fs34 = require("node:fs");
21147
+ var import_node_fs36 = require("node:fs");
20682
21148
  var import_promises13 = require("node:fs/promises");
20683
- var import_node_path26 = require("node:path");
21149
+ var import_node_path27 = require("node:path");
20684
21150
  var import_node_child_process10 = require("node:child_process");
20685
21151
  var readline2 = __toESM(require("node:readline/promises"));
20686
21152
 
20687
21153
  // src/commands/migrate.ts
20688
- var import_node_fs33 = require("node:fs");
20689
- var import_node_path25 = require("node:path");
21154
+ var import_node_fs35 = require("node:fs");
21155
+ var import_node_path26 = require("node:path");
20690
21156
  var import_node_child_process9 = require("node:child_process");
20691
21157
 
20692
21158
  // src/lib/telemetry.ts
@@ -20815,12 +21281,12 @@ async function runMigration(opts = {}) {
20815
21281
  return { actions, migrated: actions.length > 0 };
20816
21282
  }
20817
21283
  function migrateProjectDir(root, actions) {
20818
- const gateDir = (0, import_node_path25.join)(root, ".gate");
20819
- const verityDir = (0, import_node_path25.join)(root, ".verity");
20820
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) {
21284
+ const gateDir = (0, import_node_path26.join)(root, ".gate");
21285
+ const verityDir = (0, import_node_path26.join)(root, ".verity");
21286
+ if ((0, import_node_fs35.existsSync)(gateDir) && !(0, import_node_fs35.existsSync)(verityDir)) {
20821
21287
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
20822
21288
  }
20823
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21289
+ if ((0, import_node_fs35.existsSync)(gateDir) && (0, import_node_fs35.existsSync)(verityDir)) {
20824
21290
  return migrateProjectDirCarry(gateDir, verityDir, actions);
20825
21291
  }
20826
21292
  return false;
@@ -20841,13 +21307,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
20841
21307
  }
20842
21308
  }
20843
21309
  if (moved) {
20844
- if ((0, import_node_fs33.existsSync)(gateDir)) {
21310
+ if ((0, import_node_fs35.existsSync)(gateDir)) {
20845
21311
  const carried = carryLegacyContents(gateDir, verityDir);
20846
21312
  if (carried > 0) {
20847
21313
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
20848
21314
  }
20849
21315
  try {
20850
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21316
+ (0, import_node_fs35.rmSync)(gateDir, { recursive: true, force: true });
20851
21317
  } catch {
20852
21318
  }
20853
21319
  }
@@ -20863,18 +21329,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
20863
21329
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
20864
21330
  }
20865
21331
  try {
20866
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21332
+ (0, import_node_fs35.rmSync)(gateDir, { recursive: true, force: true });
20867
21333
  } catch {
20868
21334
  }
20869
21335
  return carried > 0;
20870
21336
  }
20871
21337
  function migrateGlobalCredentials(home, actions) {
20872
21338
  if (!home) return;
20873
- const gateCreds = (0, import_node_path25.join)(home, ".gate", "credentials");
20874
- const verityCreds = (0, import_node_path25.join)(home, ".verity", "credentials");
20875
- if (!(0, import_node_fs33.existsSync)(gateCreds)) return;
20876
- if (!(0, import_node_fs33.existsSync)(verityCreds)) {
20877
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.join)(home, ".verity"), { recursive: true });
21339
+ const gateCreds = (0, import_node_path26.join)(home, ".gate", "credentials");
21340
+ const verityCreds = (0, import_node_path26.join)(home, ".verity", "credentials");
21341
+ if (!(0, import_node_fs35.existsSync)(gateCreds)) return;
21342
+ if (!(0, import_node_fs35.existsSync)(verityCreds)) {
21343
+ (0, import_node_fs35.mkdirSync)((0, import_node_path26.join)(home, ".verity"), { recursive: true });
20878
21344
  moveFile(gateCreds, verityCreds);
20879
21345
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
20880
21346
  return;
@@ -20896,8 +21362,8 @@ async function migrateLegacyHooks(root, actions) {
20896
21362
  }
20897
21363
  }
20898
21364
  async function migrateClaudeMd(root, actions) {
20899
- const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
20900
- const hadLegacyBlock = (0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21365
+ const claudeMd = (0, import_node_path26.join)(root, "CLAUDE.md");
21366
+ const hadLegacyBlock = (0, import_node_fs35.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
20901
21367
  if (!hadLegacyBlock) return;
20902
21368
  try {
20903
21369
  await ensureClaudeMdPointer(root);
@@ -20907,9 +21373,9 @@ async function migrateClaudeMd(root, actions) {
20907
21373
  }
20908
21374
  }
20909
21375
  function migrateStandardFile(root, actions) {
20910
- const gateMd = (0, import_node_path25.join)(root, "GATE.md");
20911
- const verityMd = (0, import_node_path25.join)(root, "VERITY.md");
20912
- if (!(0, import_node_fs33.existsSync)(gateMd) || (0, import_node_fs33.existsSync)(verityMd)) return;
21376
+ const gateMd = (0, import_node_path26.join)(root, "GATE.md");
21377
+ const verityMd = (0, import_node_path26.join)(root, "VERITY.md");
21378
+ if (!(0, import_node_fs35.existsSync)(gateMd) || (0, import_node_fs35.existsSync)(verityMd)) return;
20913
21379
  let moved = false;
20914
21380
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
20915
21381
  try {
@@ -20921,12 +21387,12 @@ function migrateStandardFile(root, actions) {
20921
21387
  if (!moved) moveFile(gateMd, verityMd);
20922
21388
  const content = readFileSyncSafe(verityMd);
20923
21389
  const refreshed = content.split("GATE.md").join("VERITY.md");
20924
- if (refreshed !== content) (0, import_node_fs33.writeFileSync)(verityMd, refreshed);
21390
+ if (refreshed !== content) (0, import_node_fs35.writeFileSync)(verityMd, refreshed);
20925
21391
  actions.push("Renamed GATE.md \u2192 VERITY.md");
20926
21392
  }
20927
21393
  async function migrateTelemetryHeaders(root, actions) {
20928
- const file = (0, import_node_path25.join)(root, ".claude", "settings.local.json");
20929
- if (!(0, import_node_fs33.existsSync)(file)) return;
21394
+ const file = (0, import_node_path26.join)(root, ".claude", "settings.local.json");
21395
+ if (!(0, import_node_fs35.existsSync)(file)) return;
20930
21396
  let settings;
20931
21397
  try {
20932
21398
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -20973,15 +21439,15 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
20973
21439
  toAppend.push(line.replace(/\r$/, ""));
20974
21440
  }
20975
21441
  if (toAppend.length > 0) {
20976
- const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
20977
- (0, import_node_fs33.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
21442
+ const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
21443
+ (0, import_node_fs35.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
20978
21444
  }
20979
- (0, import_node_fs33.rmSync)(gateCreds, { force: true });
21445
+ (0, import_node_fs35.rmSync)(gateCreds, { force: true });
20980
21446
  return toAppend.length;
20981
21447
  }
20982
21448
  function readFileSyncSafe(path) {
20983
21449
  try {
20984
- return (0, import_node_fs33.readFileSync)(path, "utf-8");
21450
+ return (0, import_node_fs35.readFileSync)(path, "utf-8");
20985
21451
  } catch {
20986
21452
  return "";
20987
21453
  }
@@ -20996,35 +21462,35 @@ function hasStagedChanges(root) {
20996
21462
  }
20997
21463
  function moveDir(from, to) {
20998
21464
  try {
20999
- (0, import_node_fs33.renameSync)(from, to);
21465
+ (0, import_node_fs35.renameSync)(from, to);
21000
21466
  } catch (err) {
21001
21467
  if (err.code !== "EXDEV") throw err;
21002
- (0, import_node_fs33.cpSync)(from, to, { recursive: true });
21003
- (0, import_node_fs33.rmSync)(from, { recursive: true, force: true });
21468
+ (0, import_node_fs35.cpSync)(from, to, { recursive: true });
21469
+ (0, import_node_fs35.rmSync)(from, { recursive: true, force: true });
21004
21470
  }
21005
21471
  }
21006
21472
  function moveFile(from, to) {
21007
21473
  try {
21008
- (0, import_node_fs33.renameSync)(from, to);
21474
+ (0, import_node_fs35.renameSync)(from, to);
21009
21475
  } catch (err) {
21010
21476
  if (err.code !== "EXDEV") throw err;
21011
- (0, import_node_fs33.cpSync)(from, to);
21012
- (0, import_node_fs33.rmSync)(from, { force: true });
21477
+ (0, import_node_fs35.cpSync)(from, to);
21478
+ (0, import_node_fs35.rmSync)(from, { force: true });
21013
21479
  }
21014
21480
  }
21015
21481
  function carryLegacyContents(gateDir, verityDir) {
21016
21482
  let copied = 0;
21017
21483
  const walk = (relDir) => {
21018
- const srcDir = (0, import_node_path25.join)(gateDir, relDir);
21019
- for (const entry of (0, import_node_fs33.readdirSync)(srcDir)) {
21020
- const rel = relDir ? (0, import_node_path25.join)(relDir, entry) : entry;
21021
- const src = (0, import_node_path25.join)(gateDir, rel);
21022
- const dest = (0, import_node_path25.join)(verityDir, rel);
21023
- if ((0, import_node_fs33.statSync)(src).isDirectory()) {
21484
+ const srcDir = (0, import_node_path26.join)(gateDir, relDir);
21485
+ for (const entry of (0, import_node_fs35.readdirSync)(srcDir)) {
21486
+ const rel = relDir ? (0, import_node_path26.join)(relDir, entry) : entry;
21487
+ const src = (0, import_node_path26.join)(gateDir, rel);
21488
+ const dest = (0, import_node_path26.join)(verityDir, rel);
21489
+ if ((0, import_node_fs35.statSync)(src).isDirectory()) {
21024
21490
  walk(rel);
21025
- } else if (!(0, import_node_fs33.existsSync)(dest)) {
21026
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
21027
- (0, import_node_fs33.cpSync)(src, dest);
21491
+ } else if (!(0, import_node_fs35.existsSync)(dest)) {
21492
+ (0, import_node_fs35.mkdirSync)((0, import_node_path26.dirname)(dest), { recursive: true });
21493
+ (0, import_node_fs35.cpSync)(src, dest);
21028
21494
  copied++;
21029
21495
  }
21030
21496
  }
@@ -21033,22 +21499,22 @@ function carryLegacyContents(gateDir, verityDir) {
21033
21499
  return copied;
21034
21500
  }
21035
21501
  async function needsMigration(root = repoRoot()) {
21036
- const gateDir = (0, import_node_path25.join)(root, ".gate");
21037
- const verityDir = (0, import_node_path25.join)(root, ".verity");
21038
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) return true;
21039
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21040
- if ((0, import_node_fs33.existsSync)((0, import_node_path25.join)(gateDir, "credentials")) && !(0, import_node_fs33.existsSync)((0, import_node_path25.join)(verityDir, "credentials"))) {
21502
+ const gateDir = (0, import_node_path26.join)(root, ".gate");
21503
+ const verityDir = (0, import_node_path26.join)(root, ".verity");
21504
+ if ((0, import_node_fs35.existsSync)(gateDir) && !(0, import_node_fs35.existsSync)(verityDir)) return true;
21505
+ if ((0, import_node_fs35.existsSync)(gateDir) && (0, import_node_fs35.existsSync)(verityDir)) {
21506
+ if ((0, import_node_fs35.existsSync)((0, import_node_path26.join)(gateDir, "credentials")) && !(0, import_node_fs35.existsSync)((0, import_node_path26.join)(verityDir, "credentials"))) {
21041
21507
  return true;
21042
21508
  }
21043
- if ((0, import_node_fs33.existsSync)((0, import_node_path25.join)(gateDir, "memory")) && !(0, import_node_fs33.existsSync)((0, import_node_path25.join)(verityDir, "memory"))) {
21509
+ if ((0, import_node_fs35.existsSync)((0, import_node_path26.join)(gateDir, "memory")) && !(0, import_node_fs35.existsSync)((0, import_node_path26.join)(verityDir, "memory"))) {
21044
21510
  return true;
21045
21511
  }
21046
21512
  }
21047
- const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21048
- if ((0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21513
+ const claudeMd = (0, import_node_path26.join)(root, "CLAUDE.md");
21514
+ if ((0, import_node_fs35.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21049
21515
  return true;
21050
21516
  }
21051
- if ((0, import_node_fs33.existsSync)((0, import_node_path25.join)(root, "GATE.md")) && !(0, import_node_fs33.existsSync)((0, import_node_path25.join)(root, "VERITY.md"))) {
21517
+ if ((0, import_node_fs35.existsSync)((0, import_node_path26.join)(root, "GATE.md")) && !(0, import_node_fs35.existsSync)((0, import_node_path26.join)(root, "VERITY.md"))) {
21052
21518
  return true;
21053
21519
  }
21054
21520
  if (await hasLegacyHooksAt(root)) return true;
@@ -21130,6 +21596,8 @@ async function confirmExistingLogin(serviceUrl, remote, opts) {
21130
21596
  async function runOptionalAuth(resolution, opts = {}) {
21131
21597
  if (resolution.source === "default") {
21132
21598
  printInfo(`No Verity service configured on this machine \u2014 using the default: ${resolution.url}`);
21599
+ } else {
21600
+ printInfo(`Authenticating against ${resolution.url} (source: ${resolution.source}).`);
21133
21601
  }
21134
21602
  const heal = await maybeHealServiceUrl(resolution, opts.verbose);
21135
21603
  const serviceUrl = heal.serviceUrl;
@@ -21182,15 +21650,15 @@ async function runOptionalAuth(resolution, opts = {}) {
21182
21650
  }
21183
21651
  function resolveDataDir() {
21184
21652
  const candidates = [
21185
- (0, import_node_path26.join)(__dirname, "..", "data"),
21653
+ (0, import_node_path27.join)(__dirname, "..", "data"),
21186
21654
  // installed: node_modules/@codacy/verity-cli/data
21187
- (0, import_node_path26.join)(__dirname, "..", "..", "data"),
21655
+ (0, import_node_path27.join)(__dirname, "..", "..", "data"),
21188
21656
  // edge case: nested resolution
21189
- (0, import_node_path26.join)(process.cwd(), "cli", "data")
21657
+ (0, import_node_path27.join)(process.cwd(), "cli", "data")
21190
21658
  // local dev: running from repo root
21191
21659
  ];
21192
21660
  for (const candidate of candidates) {
21193
- if ((0, import_node_fs34.existsSync)((0, import_node_path26.join)(candidate, "skills"))) {
21661
+ if ((0, import_node_fs36.existsSync)((0, import_node_path27.join)(candidate, "skills"))) {
21194
21662
  return candidate;
21195
21663
  }
21196
21664
  }
@@ -21206,7 +21674,7 @@ function registerInitCommand(program2) {
21206
21674
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
21207
21675
  const force = opts.force ?? false;
21208
21676
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
21209
- const isProject = projectMarkers.some((m) => (0, import_node_fs34.existsSync)(m));
21677
+ const isProject = projectMarkers.some((m) => (0, import_node_fs36.existsSync)(m));
21210
21678
  if (!isProject) {
21211
21679
  printError("No project detected in the current directory.");
21212
21680
  printInfo('Run "verity init" from your project root.');
@@ -21269,21 +21737,21 @@ function registerInitCommand(program2) {
21269
21737
  console.log("");
21270
21738
  printInfo("Installing skills...");
21271
21739
  const dataDir = resolveDataDir();
21272
- const skillsSource = (0, import_node_path26.join)(dataDir, "skills");
21740
+ const skillsSource = (0, import_node_path27.join)(dataDir, "skills");
21273
21741
  const skillsDest = ".claude/skills";
21274
21742
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
21275
21743
  let skillsInstalled = 0;
21276
21744
  for (const skill of skills) {
21277
- const src = (0, import_node_path26.join)(skillsSource, skill);
21278
- const dest = (0, import_node_path26.join)(skillsDest, skill);
21279
- if (!(0, import_node_fs34.existsSync)(src)) {
21745
+ const src = (0, import_node_path27.join)(skillsSource, skill);
21746
+ const dest = (0, import_node_path27.join)(skillsDest, skill);
21747
+ if (!(0, import_node_fs36.existsSync)(src)) {
21280
21748
  printWarn(` Skill data not found: ${skill}`);
21281
21749
  continue;
21282
21750
  }
21283
- if ((0, import_node_fs34.existsSync)(dest) && !force) {
21284
- const srcSkill = (0, import_node_path26.join)(src, "SKILL.md");
21285
- const destSkill = (0, import_node_path26.join)(dest, "SKILL.md");
21286
- if ((0, import_node_fs34.existsSync)(destSkill)) {
21751
+ if ((0, import_node_fs36.existsSync)(dest) && !force) {
21752
+ const srcSkill = (0, import_node_path27.join)(src, "SKILL.md");
21753
+ const destSkill = (0, import_node_path27.join)(dest, "SKILL.md");
21754
+ if ((0, import_node_fs36.existsSync)(destSkill)) {
21287
21755
  try {
21288
21756
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
21289
21757
  const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
@@ -21314,13 +21782,19 @@ function registerInitCommand(program2) {
21314
21782
  }
21315
21783
  await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
21316
21784
  await ensureMemoryDir();
21785
+ const ignoreResult = ensureSnapshotGitignored();
21786
+ if (ignoreResult === "failed") {
21787
+ printWarn(" .gitignore: could not add .verity/.snapshot/ \u2014 add it manually (it holds copies of analyzed files)");
21788
+ } else {
21789
+ printInfo(` .gitignore: .verity/.snapshot/ ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
21790
+ }
21317
21791
  try {
21318
21792
  await ensureClaudeMdPointer();
21319
21793
  printInfo(" CLAUDE.md memory pointer \u2713");
21320
21794
  } catch (err) {
21321
21795
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
21322
21796
  }
21323
- const globalVerityDir = (0, import_node_path26.join)(process.env.HOME ?? "", ".verity");
21797
+ const globalVerityDir = (0, import_node_path27.join)(process.env.HOME ?? "", ".verity");
21324
21798
  await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
21325
21799
  console.log("");
21326
21800
  try {
@@ -21355,8 +21829,8 @@ function registerInitCommand(program2) {
21355
21829
  }
21356
21830
 
21357
21831
  // src/commands/uninstall.ts
21358
- var import_node_fs35 = require("node:fs");
21359
- var import_node_path27 = require("node:path");
21832
+ var import_node_fs37 = require("node:fs");
21833
+ var import_node_path28 = require("node:path");
21360
21834
  var SKILL_NAMES = [
21361
21835
  "verity-setup",
21362
21836
  "verity-analyze",
@@ -21375,11 +21849,11 @@ function registerUninstallCommand(program2) {
21375
21849
  const actions = [];
21376
21850
  const skillsRoot = projectPath(".claude/skills");
21377
21851
  for (const name of SKILL_NAMES) {
21378
- const dir = (0, import_node_path27.join)(skillsRoot, name);
21379
- if ((0, import_node_fs35.existsSync)(dir)) {
21852
+ const dir = (0, import_node_path28.join)(skillsRoot, name);
21853
+ if ((0, import_node_fs37.existsSync)(dir)) {
21380
21854
  actions.push({
21381
21855
  label: `Remove .claude/skills/${name}/`,
21382
- apply: () => (0, import_node_fs35.rmSync)(dir, { recursive: true, force: true })
21856
+ apply: () => (0, import_node_fs37.rmSync)(dir, { recursive: true, force: true })
21383
21857
  });
21384
21858
  }
21385
21859
  }
@@ -21393,24 +21867,24 @@ function registerUninstallCommand(program2) {
21393
21867
  });
21394
21868
  }
21395
21869
  const verityDir = projectPath(VERITY_DIR);
21396
- if ((0, import_node_fs35.existsSync)(verityDir)) {
21870
+ if ((0, import_node_fs37.existsSync)(verityDir)) {
21397
21871
  actions.push({
21398
21872
  label: `Remove ${VERITY_DIR}/`,
21399
- apply: () => (0, import_node_fs35.rmSync)(verityDir, { recursive: true, force: true })
21873
+ apply: () => (0, import_node_fs37.rmSync)(verityDir, { recursive: true, force: true })
21400
21874
  });
21401
21875
  }
21402
21876
  if (!keepVerityMd) {
21403
21877
  const verityMd = projectPath(VERITY_MD_FILE);
21404
- if ((0, import_node_fs35.existsSync)(verityMd)) {
21878
+ if ((0, import_node_fs37.existsSync)(verityMd)) {
21405
21879
  actions.push({
21406
21880
  label: `Remove ${VERITY_MD_FILE}`,
21407
- apply: () => (0, import_node_fs35.rmSync)(verityMd, { force: true })
21881
+ apply: () => (0, import_node_fs37.rmSync)(verityMd, { force: true })
21408
21882
  });
21409
21883
  }
21410
21884
  }
21411
21885
  const cleanupEmptyDir = (path) => {
21412
- if ((0, import_node_fs35.existsSync)(path) && (0, import_node_fs35.statSync)(path).isDirectory() && (0, import_node_fs35.readdirSync)(path).length === 0) {
21413
- (0, import_node_fs35.rmdirSync)(path);
21886
+ if ((0, import_node_fs37.existsSync)(path) && (0, import_node_fs37.statSync)(path).isDirectory() && (0, import_node_fs37.readdirSync)(path).length === 0) {
21887
+ (0, import_node_fs37.rmdirSync)(path);
21414
21888
  }
21415
21889
  };
21416
21890
  actions.push({
@@ -21421,11 +21895,11 @@ function registerUninstallCommand(program2) {
21421
21895
  }
21422
21896
  });
21423
21897
  const home = process.env.HOME ?? "";
21424
- const globalVerityDir = (0, import_node_path27.join)(home, ".verity");
21425
- if (purgeGlobal && (0, import_node_fs35.existsSync)(globalVerityDir)) {
21898
+ const globalVerityDir = (0, import_node_path28.join)(home, ".verity");
21899
+ if (purgeGlobal && (0, import_node_fs37.existsSync)(globalVerityDir)) {
21426
21900
  actions.push({
21427
21901
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
21428
- apply: () => (0, import_node_fs35.rmSync)(globalVerityDir, { recursive: true, force: true })
21902
+ apply: () => (0, import_node_fs37.rmSync)(globalVerityDir, { recursive: true, force: true })
21429
21903
  });
21430
21904
  }
21431
21905
  if (actions.length === 0) {
@@ -21619,8 +22093,8 @@ function registerTaskCommands(program2) {
21619
22093
  }
21620
22094
 
21621
22095
  // src/commands/reset.ts
21622
- var import_node_fs36 = require("node:fs");
21623
- var import_node_path28 = require("node:path");
22096
+ var import_node_fs38 = require("node:fs");
22097
+ var import_node_path29 = require("node:path");
21624
22098
  function registerResetCommand(program2) {
21625
22099
  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) => {
21626
22100
  const globals = program2.opts();
@@ -21657,11 +22131,11 @@ function registerResetCommand(program2) {
21657
22131
  }
21658
22132
  const cacheDir = projectPath(CACHE_DIR);
21659
22133
  let purged = 0;
21660
- if ((0, import_node_fs36.existsSync)(cacheDir)) {
21661
- for (const entry of (0, import_node_fs36.readdirSync)(cacheDir)) {
22134
+ if ((0, import_node_fs38.existsSync)(cacheDir)) {
22135
+ for (const entry of (0, import_node_fs38.readdirSync)(cacheDir)) {
21662
22136
  if (entry.startsWith("pending-")) {
21663
22137
  try {
21664
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(cacheDir, entry));
22138
+ (0, import_node_fs38.unlinkSync)((0, import_node_path29.join)(cacheDir, entry));
21665
22139
  purged++;
21666
22140
  } catch {
21667
22141
  }
@@ -21676,19 +22150,19 @@ function registerResetCommand(program2) {
21676
22150
  projectPath(`${VERITY_DIR}/.last-analysis`)
21677
22151
  ];
21678
22152
  for (const file of filesToClear) {
21679
- if ((0, import_node_fs36.existsSync)(file)) {
22153
+ if ((0, import_node_fs38.existsSync)(file)) {
21680
22154
  try {
21681
- (0, import_node_fs36.writeFileSync)(file, "");
22155
+ (0, import_node_fs38.writeFileSync)(file, "");
21682
22156
  } catch {
21683
22157
  }
21684
22158
  }
21685
22159
  }
21686
22160
  if (opts.all) {
21687
22161
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
21688
- if ((0, import_node_fs36.existsSync)(logsDir)) {
21689
- for (const entry of (0, import_node_fs36.readdirSync)(logsDir)) {
22162
+ if ((0, import_node_fs38.existsSync)(logsDir)) {
22163
+ for (const entry of (0, import_node_fs38.readdirSync)(logsDir)) {
21690
22164
  try {
21691
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(logsDir, entry));
22165
+ (0, import_node_fs38.unlinkSync)((0, import_node_path29.join)(logsDir, entry));
21692
22166
  } catch {
21693
22167
  }
21694
22168
  }
@@ -21996,8 +22470,8 @@ function registerTelemetryCommands(program2) {
21996
22470
  }
21997
22471
 
21998
22472
  // src/cli.ts
21999
- program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.5fcba03").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) => {
22000
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.5fcba03");
22473
+ program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.694bcef").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) => {
22474
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.694bcef");
22001
22475
  setUserNamedServiceUrl(program.opts().serviceUrl);
22002
22476
  try {
22003
22477
  await foldLegacyLocalCredential();