@codacy/verity-cli 0.29.4-experimental.f2e812c → 0.30.0-experimental.43e7755

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;
@@ -10342,6 +10342,10 @@ function repoRoot() {
10342
10342
  }
10343
10343
  return _repoRoot;
10344
10344
  }
10345
+ function _resetRepoRoot() {
10346
+ _repoRoot = null;
10347
+ _mainRoot = void 0;
10348
+ }
10345
10349
  var _mainRoot;
10346
10350
  function mainWorktreeRoot() {
10347
10351
  if (_mainRoot !== void 0) return _mainRoot;
@@ -10502,6 +10506,7 @@ var GITHUB_APP_INSTALL_URL = `https://github.com/apps/${GITHUB_APP_SLUG}/install
10502
10506
  function githubAppInstallUrl(accountId) {
10503
10507
  return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
10504
10508
  }
10509
+ var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
10505
10510
 
10506
10511
  // src/lib/output.ts
10507
10512
  var RED = "\x1B[0;31m";
@@ -10775,7 +10780,7 @@ async function foldLegacyLocalCredential(remoteArg) {
10775
10780
  const existing = await readGlobalCredential(remote);
10776
10781
  const merged = {
10777
10782
  token: existing?.token ?? local.token,
10778
- serviceUrl: existing?.serviceUrl ?? local.serviceUrl,
10783
+ serviceUrl: existing?.serviceUrl,
10779
10784
  userId: existing?.userId ?? local.userId,
10780
10785
  email: existing?.email ?? local.email
10781
10786
  };
@@ -10869,9 +10874,6 @@ function getChangedFiles() {
10869
10874
  const filtered = Array.from(sets).filter((f) => !isVerityOwnedPath(f));
10870
10875
  return { files: filtered, hasRecentCommitFiles };
10871
10876
  }
10872
- function getStagedFiles() {
10873
- return splitLines(execGit("git diff --cached --name-only")).filter((f) => !isVerityOwnedPath(f));
10874
- }
10875
10877
  function getDirtyFiles() {
10876
10878
  const set = /* @__PURE__ */ new Set();
10877
10879
  for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
@@ -10892,33 +10894,6 @@ function showContentAtRef(ref, repoRelPath) {
10892
10894
  return null;
10893
10895
  }
10894
10896
  }
10895
- function getPushRangeFiles() {
10896
- const diff = (range) => splitLines(execGit(`git diff --name-only ${range}`)).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
10897
- 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,
10900
- () => {
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;
10903
- }
10904
- ];
10905
- for (const resolve2 of resolvers) {
10906
- const range = resolve2();
10907
- if (range) return { files: diff(range), range };
10908
- }
10909
- const baseline = readBaselineSha();
10910
- if (baseline) {
10911
- const files = diff(`${baseline}..HEAD`);
10912
- if (files.length > 0) return { files, range: `${baseline}..HEAD` };
10913
- }
10914
- const last = diff("HEAD~1..HEAD");
10915
- return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
10916
- }
10917
- function getPushRangeMessages() {
10918
- const { range } = getPushRangeFiles();
10919
- if (!range) return "";
10920
- return execGit(`git log ${range} --format=%B%x00`).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
10921
- }
10922
10897
  function filterAnalyzable(files) {
10923
10898
  return files.filter((f) => {
10924
10899
  const ext = (0, import_node_path3.extname)(f).slice(1);
@@ -10951,7 +10926,7 @@ function getCurrentBranch() {
10951
10926
  function commitResolves(sha) {
10952
10927
  if (!sha) return false;
10953
10928
  try {
10954
- (0, import_node_child_process3.execSync)(`git merge-base --is-ancestor ${JSON.stringify(sha)} HEAD`, {
10929
+ (0, import_node_child_process3.execFileSync)("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
10955
10930
  stdio: ["pipe", "pipe", "pipe"]
10956
10931
  });
10957
10932
  return true;
@@ -10962,8 +10937,9 @@ function commitResolves(sha) {
10962
10937
  function commitsSincePaths(sha, paths) {
10963
10938
  if (!sha) return null;
10964
10939
  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}`, {
10940
+ const args = ["rev-list", "--count", `${sha}..HEAD`];
10941
+ if (paths.length > 0) args.push("--", ...paths.slice(0, 50));
10942
+ const out = (0, import_node_child_process3.execFileSync)("git", args, {
10967
10943
  encoding: "utf-8",
10968
10944
  stdio: ["pipe", "pipe", "pipe"]
10969
10945
  }).trim();
@@ -11245,7 +11221,8 @@ async function serviceUrlFromVerityMd() {
11245
11221
  }
11246
11222
  return null;
11247
11223
  }
11248
- async function resolveServiceUrlDetailed(flagUrl) {
11224
+ async function resolveServiceUrlDetailed(flagUrl, opts = {}) {
11225
+ const allowVerityMd = opts.allowVerityMd ?? true;
11249
11226
  if (flagUrl) {
11250
11227
  return { ok: true, data: { url: flagUrl, source: "flag" } };
11251
11228
  }
@@ -11257,9 +11234,11 @@ async function resolveServiceUrlDetailed(flagUrl) {
11257
11234
  if (credsUrl) {
11258
11235
  return { ok: true, data: { url: credsUrl, source: "credentials" } };
11259
11236
  }
11260
- const mdUrl = await serviceUrlFromVerityMd();
11261
- if (mdUrl) {
11262
- return { ok: true, data: { url: mdUrl, source: "verity_md" } };
11237
+ if (allowVerityMd) {
11238
+ const mdUrl = await serviceUrlFromVerityMd();
11239
+ if (mdUrl) {
11240
+ return { ok: true, data: { url: mdUrl, source: "verity_md" } };
11241
+ }
11263
11242
  }
11264
11243
  return {
11265
11244
  ok: false,
@@ -11267,7 +11246,7 @@ async function resolveServiceUrlDetailed(flagUrl) {
11267
11246
  };
11268
11247
  }
11269
11248
  async function resolveServiceUrlForAuth(flagUrl) {
11270
- const strict = await resolveServiceUrlDetailed(flagUrl);
11249
+ const strict = await resolveServiceUrlDetailed(flagUrl, { allowVerityMd: false });
11271
11250
  if (strict.ok) return strict.data;
11272
11251
  return { url: DEFAULT_SERVICE_URL, source: "default" };
11273
11252
  }
@@ -11405,7 +11384,7 @@ var readline = __toESM(require("node:readline/promises"));
11405
11384
  var import_node_os = require("node:os");
11406
11385
 
11407
11386
  // src/lib/provider-auth.ts
11408
- var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
11387
+ var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
11409
11388
  var form = (fields) => new URLSearchParams(fields).toString();
11410
11389
  async function githubAccountId(owner) {
11411
11390
  try {
@@ -13161,9 +13140,38 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
13161
13140
 
13162
13141
  // src/lib/memory-sync.ts
13163
13142
  var import_promises8 = require("node:fs/promises");
13143
+ var import_node_fs9 = require("node:fs");
13144
+ var import_node_path10 = require("node:path");
13145
+ var import_node_crypto3 = require("node:crypto");
13146
+
13147
+ // src/lib/safe-path.ts
13164
13148
  var import_node_fs8 = require("node:fs");
13165
13149
  var import_node_path9 = require("node:path");
13166
- var import_node_crypto3 = require("node:crypto");
13150
+ function resolveInside(baseDir, candidate) {
13151
+ if (typeof candidate !== "string" || candidate.length === 0) return null;
13152
+ if ((0, import_node_path9.isAbsolute)(candidate)) return null;
13153
+ const baseAbs = (0, import_node_path9.resolve)(baseDir);
13154
+ const full = (0, import_node_path9.resolve)(baseAbs, candidate);
13155
+ const baseSep = baseAbs.endsWith(import_node_path9.sep) ? baseAbs : baseAbs + import_node_path9.sep;
13156
+ if (full !== baseAbs && !full.startsWith(baseSep)) return null;
13157
+ try {
13158
+ if ((0, import_node_fs8.existsSync)(baseAbs)) {
13159
+ const realBase = (0, import_node_fs8.realpathSync)(baseAbs);
13160
+ const realBaseSep = realBase.endsWith(import_node_path9.sep) ? realBase : realBase + import_node_path9.sep;
13161
+ let probe = full;
13162
+ while (!(0, import_node_fs8.existsSync)(probe)) {
13163
+ const parent = (0, import_node_path9.dirname)(probe);
13164
+ if (parent === probe) break;
13165
+ probe = parent;
13166
+ }
13167
+ const realProbe = (0, import_node_fs8.realpathSync)(probe);
13168
+ if (realProbe !== realBase && !realProbe.startsWith(realBaseSep)) return null;
13169
+ }
13170
+ } catch {
13171
+ return null;
13172
+ }
13173
+ return full;
13174
+ }
13167
13175
 
13168
13176
  // src/lib/glob-match.ts
13169
13177
  function globToRegex(glob) {
@@ -13233,32 +13241,32 @@ var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
13233
13241
  async function ensureMemoryDir() {
13234
13242
  await (0, import_promises8.mkdir)(memoryDir2(), { recursive: true });
13235
13243
  for (const domain of DOMAINS2) {
13236
- await (0, import_promises8.mkdir)((0, import_node_path9.join)(memoryDir2(), domain), { recursive: true });
13244
+ await (0, import_promises8.mkdir)((0, import_node_path10.join)(memoryDir2(), domain), { recursive: true });
13237
13245
  }
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);
13246
+ if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"))) {
13247
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
13240
13248
  }
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");
13249
+ if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "index.md"))) {
13250
+ 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
13251
  }
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");
13252
+ if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md"))) {
13253
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
13246
13254
  }
13247
13255
  }
13248
13256
  async function buildManifest() {
13249
- if (!(0, import_node_fs8.existsSync)(memoryDir2())) {
13257
+ if (!(0, import_node_fs9.existsSync)(memoryDir2())) {
13250
13258
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
13251
13259
  }
13252
13260
  const nodes = [];
13253
13261
  for (const domain of DOMAINS2) {
13254
- const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
13255
- if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
13262
+ const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13263
+ if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13256
13264
  try {
13257
13265
  const files = await (0, import_promises8.readdir)(domainDir);
13258
13266
  for (const file of files) {
13259
13267
  if (!file.endsWith(".md")) continue;
13260
13268
  const filePath = `${domain}/${file}`;
13261
- const fullPath = (0, import_node_path9.join)(memoryDir2(), filePath);
13269
+ const fullPath = (0, import_node_path10.join)(memoryDir2(), filePath);
13262
13270
  try {
13263
13271
  const content = await (0, import_promises8.readFile)(fullPath, "utf-8");
13264
13272
  const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
@@ -13271,13 +13279,13 @@ async function buildManifest() {
13271
13279
  }
13272
13280
  let indexHash = null;
13273
13281
  try {
13274
- const indexContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "index.md"), "utf-8");
13282
+ const indexContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "index.md"), "utf-8");
13275
13283
  indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
13276
13284
  } catch {
13277
13285
  }
13278
13286
  let logLength = 0;
13279
13287
  try {
13280
- const logContent = await (0, import_promises8.readFile)((0, import_node_path9.join)(memoryDir2(), "log.md"), "utf-8");
13288
+ const logContent = await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8");
13281
13289
  logLength = logContent.split("\n").length;
13282
13290
  } catch {
13283
13291
  }
@@ -13288,15 +13296,15 @@ function hashContent(content) {
13288
13296
  }
13289
13297
  async function readOnDiskNodes() {
13290
13298
  const out = /* @__PURE__ */ new Map();
13291
- if (!(0, import_node_fs8.existsSync)(memoryDir2())) return out;
13299
+ if (!(0, import_node_fs9.existsSync)(memoryDir2())) return out;
13292
13300
  for (const domain of DOMAINS2) {
13293
- const domainDir = (0, import_node_path9.join)(memoryDir2(), domain);
13294
- if (!(0, import_node_fs8.existsSync)(domainDir)) continue;
13301
+ const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13302
+ if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13295
13303
  try {
13296
13304
  for (const file of await (0, import_promises8.readdir)(domainDir)) {
13297
13305
  if (!file.endsWith(".md")) continue;
13298
13306
  try {
13299
- out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8")));
13307
+ out.set(`${domain}/${file}`, hashContent(await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8")));
13300
13308
  } catch {
13301
13309
  }
13302
13310
  }
@@ -13342,8 +13350,8 @@ async function computeEditedNodeUploads() {
13342
13350
  const uploads = [];
13343
13351
  for (const [path, prevHash] of prev) {
13344
13352
  if (prevHash == null) continue;
13345
- const full = (0, import_node_path9.join)(memoryDir2(), path);
13346
- if (!(0, import_node_fs8.existsSync)(full)) continue;
13353
+ const full = (0, import_node_path10.join)(memoryDir2(), path);
13354
+ if (!(0, import_node_fs9.existsSync)(full)) continue;
13347
13355
  let content;
13348
13356
  try {
13349
13357
  content = await (0, import_promises8.readFile)(full, "utf-8");
@@ -13379,16 +13387,19 @@ async function applyMemoryWrites(writes, opts = {}) {
13379
13387
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
13380
13388
  for (const n of notes) logLines.push(` - ${n}`);
13381
13389
  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");
13390
+ 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";
13391
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
13384
13392
  } catch {
13385
13393
  }
13386
13394
  await recordSyncedNodePaths();
13387
13395
  return count;
13388
13396
  }
13389
13397
  async function applyOneWrite(write, treePaths) {
13390
- const fullPath = (0, import_node_path9.join)(memoryDir2(), write.path);
13398
+ const fullPath = resolveInside(memoryDir2(), write.path);
13391
13399
  const notes = [];
13400
+ if (!fullPath) {
13401
+ return { written: false, notes: [`${String(write.path)}: rejected \u2014 path escapes the memory directory`] };
13402
+ }
13392
13403
  let content = write.content;
13393
13404
  if (treePaths && treePaths.length > 0) {
13394
13405
  const grounded = groundFileGlobs(content, treePaths);
@@ -13397,7 +13408,7 @@ async function applyOneWrite(write, treePaths) {
13397
13408
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
13398
13409
  }
13399
13410
  }
13400
- if ((0, import_node_fs8.existsSync)(fullPath)) {
13411
+ if ((0, import_node_fs9.existsSync)(fullPath)) {
13401
13412
  let existing = "";
13402
13413
  try {
13403
13414
  existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
@@ -13409,7 +13420,7 @@ async function applyOneWrite(write, treePaths) {
13409
13420
  return { written: false, notes };
13410
13421
  }
13411
13422
  }
13412
- await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(fullPath), { recursive: true });
13423
+ await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(fullPath), { recursive: true });
13413
13424
  await (0, import_promises8.writeFile)(fullPath, content);
13414
13425
  return { written: true, notes };
13415
13426
  }
@@ -13450,8 +13461,8 @@ async function regenerateIndex() {
13450
13461
  ];
13451
13462
  let totalNodes = 0;
13452
13463
  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;
13464
+ const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13465
+ if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13455
13466
  try {
13456
13467
  const files = await (0, import_promises8.readdir)(domainDir);
13457
13468
  const mdFiles = files.filter((f) => f.endsWith(".md"));
@@ -13461,7 +13472,7 @@ async function regenerateIndex() {
13461
13472
  for (const file of mdFiles.sort()) {
13462
13473
  const slug = file.replace(/\.md$/, "");
13463
13474
  try {
13464
- const content = await (0, import_promises8.readFile)((0, import_node_path9.join)(domainDir, file), "utf-8");
13475
+ const content = await (0, import_promises8.readFile)((0, import_node_path10.join)(domainDir, file), "utf-8");
13465
13476
  const title = pickFrontmatter(content, "title") ?? slug;
13466
13477
  const kind = pickFrontmatter(content, "kind") ?? "-";
13467
13478
  const confidence = pickFrontmatter(content, "confidence");
@@ -13485,7 +13496,7 @@ async function regenerateIndex() {
13485
13496
  lines.push("No nodes yet. Run an analysis to start building the knowledge graph.");
13486
13497
  }
13487
13498
  const next = lines.join("\n") + "\n";
13488
- const indexPath = (0, import_node_path9.join)(memoryDir2(), "index.md");
13499
+ const indexPath = (0, import_node_path10.join)(memoryDir2(), "index.md");
13489
13500
  let existing = null;
13490
13501
  try {
13491
13502
  existing = await (0, import_promises8.readFile)(indexPath, "utf-8");
@@ -13567,9 +13578,9 @@ function hasLegacyMemoryBlock(text) {
13567
13578
  return findMarker(text, LEGACY_MD_START) !== -1;
13568
13579
  }
13569
13580
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
13570
- const claudeMdPath = (0, import_node_path9.join)(cwd, "CLAUDE.md");
13581
+ const claudeMdPath = (0, import_node_path10.join)(cwd, "CLAUDE.md");
13571
13582
  let existing = "";
13572
- if ((0, import_node_fs8.existsSync)(claudeMdPath)) {
13583
+ if ((0, import_node_fs9.existsSync)(claudeMdPath)) {
13573
13584
  existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
13574
13585
  }
13575
13586
  let startTag = CLAUDE_MD_START;
@@ -13699,9 +13710,9 @@ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
13699
13710
  `;
13700
13711
 
13701
13712
  // src/lib/dossier-session.ts
13702
- var import_node_fs13 = require("node:fs");
13713
+ var import_node_fs14 = require("node:fs");
13703
13714
  var import_node_crypto7 = require("node:crypto");
13704
- var import_node_path12 = require("node:path");
13715
+ var import_node_path13 = require("node:path");
13705
13716
 
13706
13717
  // src/lib/skip-detection.ts
13707
13718
  function isBareAckPrompt(prompt) {
@@ -13865,8 +13876,8 @@ var DEFAULT_MEMORY_BUDGET_BYTES = 4096;
13865
13876
 
13866
13877
  // src/lib/dossier/log.ts
13867
13878
  var import_node_crypto4 = require("node:crypto");
13868
- var import_node_fs9 = require("node:fs");
13869
- var import_node_path10 = require("node:path");
13879
+ var import_node_fs10 = require("node:fs");
13880
+ var import_node_path11 = require("node:path");
13870
13881
  var CRC_TABLE = (() => {
13871
13882
  const t = new Int32Array(256);
13872
13883
  for (let i = 0; i < 256; i++) {
@@ -13885,13 +13896,13 @@ function crc32(s) {
13885
13896
  function openDossier(identity) {
13886
13897
  try {
13887
13898
  const dir = dossierDir(identity);
13888
- (0, import_node_fs9.mkdirSync)(dir, { recursive: true, mode: 448 });
13899
+ (0, import_node_fs10.mkdirSync)(dir, { recursive: true, mode: 448 });
13889
13900
  return {
13890
13901
  dir,
13891
13902
  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")
13903
+ eventsPath: (0, import_node_path11.join)(dir, "events.jsonl"),
13904
+ foldPath: (0, import_node_path11.join)(dir, "fold.json"),
13905
+ rotatedDir: (0, import_node_path11.join)(dir, "rotated")
13895
13906
  };
13896
13907
  } catch {
13897
13908
  return null;
@@ -13953,7 +13964,7 @@ function appendEvent(d, ev) {
13953
13964
  at: ev.at ?? (/* @__PURE__ */ new Date()).toISOString(),
13954
13965
  ...ev
13955
13966
  });
13956
- (0, import_node_fs9.appendFileSync)(d.eventsPath, line, { mode: 384 });
13967
+ (0, import_node_fs10.appendFileSync)(d.eventsPath, line, { mode: 384 });
13957
13968
  return true;
13958
13969
  } catch {
13959
13970
  return false;
@@ -13961,14 +13972,14 @@ function appendEvent(d, ev) {
13961
13972
  }
13962
13973
  function rotateIfNeeded2(d) {
13963
13974
  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();
13975
+ if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return;
13976
+ if ((0, import_node_fs10.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
13977
+ (0, import_node_fs10.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
13978
+ (0, import_node_fs10.renameSync)(d.eventsPath, (0, import_node_path11.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
13979
+ const kept = (0, import_node_fs10.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
13969
13980
  for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
13970
13981
  try {
13971
- (0, import_node_fs9.renameSync)((0, import_node_path10.join)(d.rotatedDir, stale), (0, import_node_path10.join)(d.rotatedDir, `${stale}.pruned`));
13982
+ (0, import_node_fs10.renameSync)((0, import_node_path11.join)(d.rotatedDir, stale), (0, import_node_path11.join)(d.rotatedDir, `${stale}.pruned`));
13972
13983
  } catch {
13973
13984
  }
13974
13985
  }
@@ -13978,8 +13989,8 @@ function rotateIfNeeded2(d) {
13978
13989
 
13979
13990
  // src/lib/dossier/fold-dossier.ts
13980
13991
  var import_node_crypto5 = require("node:crypto");
13981
- var import_node_fs10 = require("node:fs");
13982
- var import_node_path11 = require("node:path");
13992
+ var import_node_fs11 = require("node:fs");
13993
+ var import_node_path12 = require("node:path");
13983
13994
  var EMPTY_CAPABILITIES = () => ({
13984
13995
  human_reachable: { value: "unknown", tier: "unknown" },
13985
13996
  authorship_observability: { value: "unknown", tier: "unknown" },
@@ -14030,12 +14041,12 @@ function foldDossier(d, opts = {}) {
14030
14041
  }
14031
14042
  };
14032
14043
  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();
14044
+ if ((0, import_node_fs11.existsSync)(d.rotatedDir)) {
14045
+ const files = (0, import_node_fs11.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14035
14046
  state.meta.rotations = files.length;
14036
14047
  for (const f of files) {
14037
14048
  try {
14038
- ingest((0, import_node_fs10.readFileSync)((0, import_node_path11.join)(d.rotatedDir, f), "utf8"));
14049
+ ingest((0, import_node_fs11.readFileSync)((0, import_node_path12.join)(d.rotatedDir, f), "utf8"));
14039
14050
  } catch {
14040
14051
  state.meta.dropped_lines++;
14041
14052
  }
@@ -14044,9 +14055,9 @@ function foldDossier(d, opts = {}) {
14044
14055
  } catch {
14045
14056
  }
14046
14057
  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"));
14058
+ if ((0, import_node_fs11.existsSync)(d.eventsPath)) {
14059
+ state.meta.upto_offset = (0, import_node_fs11.statSync)(d.eventsPath).size;
14060
+ ingest((0, import_node_fs11.readFileSync)(d.eventsPath, "utf8"));
14050
14061
  }
14051
14062
  } catch {
14052
14063
  }
@@ -14316,7 +14327,7 @@ function applyBounds(state, input) {
14316
14327
  }
14317
14328
 
14318
14329
  // src/lib/dossier/cache.ts
14319
- var import_node_fs11 = require("node:fs");
14330
+ var import_node_fs12 = require("node:fs");
14320
14331
  function compactState(s) {
14321
14332
  const ms = (iso) => Date.parse(iso) || 0;
14322
14333
  return {
@@ -14445,20 +14456,20 @@ function encodeState(s) {
14445
14456
  function writeFoldCache(d, state) {
14446
14457
  try {
14447
14458
  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);
14459
+ (0, import_node_fs12.writeFileSync)(tmp, encodeState(state), { mode: 384 });
14460
+ (0, import_node_fs12.renameSync)(tmp, d.foldPath);
14450
14461
  } catch {
14451
14462
  }
14452
14463
  }
14453
14464
  function readFoldCache(d) {
14454
14465
  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"));
14466
+ if (!(0, import_node_fs12.existsSync)(d.foldPath)) return null;
14467
+ const raw = JSON.parse((0, import_node_fs12.readFileSync)(d.foldPath, "utf8"));
14457
14468
  if (raw?.v !== 1) return null;
14458
14469
  const cached2 = expandState(raw);
14459
14470
  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;
14471
+ const size = (0, import_node_fs12.existsSync)(d.eventsPath) ? (0, import_node_fs12.statSync)(d.eventsPath).size : 0;
14472
+ const rotations = (0, import_node_fs12.existsSync)(d.rotatedDir) ? (0, import_node_fs12.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).length : 0;
14462
14473
  if (cached2.meta.upto_offset !== size || cached2.meta.rotations !== rotations) return null;
14463
14474
  return cached2;
14464
14475
  } catch {
@@ -14509,13 +14520,13 @@ function assessContinuity(i) {
14509
14520
 
14510
14521
  // src/lib/dossier/reanchor.ts
14511
14522
  var import_node_crypto6 = require("node:crypto");
14512
- var import_node_fs12 = require("node:fs");
14523
+ var import_node_fs13 = require("node:fs");
14513
14524
  function lineSha(text) {
14514
14525
  return (0, import_node_crypto6.createHash)("sha256").update(text.trim()).digest("hex").slice(0, HASH_WIDTH);
14515
14526
  }
14516
14527
  function fileHash(path) {
14517
14528
  try {
14518
- return (0, import_node_crypto6.createHash)("sha256").update((0, import_node_fs12.readFileSync)(path)).digest("hex").slice(0, HASH_WIDTH);
14529
+ return (0, import_node_crypto6.createHash)("sha256").update((0, import_node_fs13.readFileSync)(path)).digest("hex").slice(0, HASH_WIDTH);
14519
14530
  } catch {
14520
14531
  return null;
14521
14532
  }
@@ -14887,20 +14898,20 @@ function foreignAuthoredPaths(identity, opts = {}) {
14887
14898
  let sessions = 0;
14888
14899
  try {
14889
14900
  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 })) {
14901
+ if (!(0, import_node_fs14.existsSync)(dir)) return { paths: [], sessions: 0 };
14902
+ for (const entry of (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true })) {
14892
14903
  if (!entry.isDirectory()) continue;
14893
14904
  if (entry.name === identity.sessionKey) continue;
14894
- const log = (0, import_node_path12.join)(dir, entry.name, "events.jsonl");
14905
+ const log = (0, import_node_path13.join)(dir, entry.name, "events.jsonl");
14895
14906
  try {
14896
- if (!(0, import_node_fs13.existsSync)(log)) continue;
14897
- if (now - (0, import_node_fs13.statSync)(log).mtimeMs > windowMs) continue;
14907
+ if (!(0, import_node_fs14.existsSync)(log)) continue;
14908
+ if (now - (0, import_node_fs14.statSync)(log).mtimeMs > windowMs) continue;
14898
14909
  const sib = {
14899
- dir: (0, import_node_path12.join)(dir, entry.name),
14910
+ dir: (0, import_node_path13.join)(dir, entry.name),
14900
14911
  identity,
14901
14912
  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")
14913
+ foldPath: (0, import_node_path13.join)(dir, entry.name, "fold.json"),
14914
+ rotatedDir: (0, import_node_path13.join)(dir, entry.name, "rotated")
14904
14915
  };
14905
14916
  const state = readFoldCache(sib) ?? foldDossier(sib);
14906
14917
  sessions++;
@@ -14928,25 +14939,25 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
14928
14939
  let removed = 0;
14929
14940
  try {
14930
14941
  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;
14942
+ const userDir = (0, import_node_path13.dirname)((0, import_node_path13.dirname)(mine));
14943
+ if (!(0, import_node_fs14.existsSync)(userDir)) return 0;
14933
14944
  const cutoff = Date.now() - maxAgeMs;
14934
- for (const tree of (0, import_node_fs13.readdirSync)(userDir, { withFileTypes: true })) {
14945
+ for (const tree of (0, import_node_fs14.readdirSync)(userDir, { withFileTypes: true })) {
14935
14946
  if (!tree.isDirectory()) continue;
14936
- const treePath = (0, import_node_path12.join)(userDir, tree.name);
14947
+ const treePath = (0, import_node_path13.join)(userDir, tree.name);
14937
14948
  let live = 0;
14938
- for (const entry of (0, import_node_fs13.readdirSync)(treePath, { withFileTypes: true })) {
14949
+ for (const entry of (0, import_node_fs14.readdirSync)(treePath, { withFileTypes: true })) {
14939
14950
  if (!entry.isDirectory()) continue;
14940
- const dir = (0, import_node_path12.join)(treePath, entry.name);
14951
+ const dir = (0, import_node_path13.join)(treePath, entry.name);
14941
14952
  if (dir === mine) {
14942
14953
  live++;
14943
14954
  continue;
14944
14955
  }
14945
14956
  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;
14957
+ const log = (0, import_node_path13.join)(dir, "events.jsonl");
14958
+ const at = (0, import_node_fs14.existsSync)(log) ? (0, import_node_fs14.statSync)(log).mtimeMs : (0, import_node_fs14.statSync)(dir).mtimeMs;
14948
14959
  if (at < cutoff) {
14949
- (0, import_node_fs13.rmSync)(dir, { recursive: true, force: true });
14960
+ (0, import_node_fs14.rmSync)(dir, { recursive: true, force: true });
14950
14961
  removed++;
14951
14962
  } else {
14952
14963
  live++;
@@ -14956,7 +14967,7 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
14956
14967
  }
14957
14968
  if (live === 0) {
14958
14969
  try {
14959
- (0, import_node_fs13.rmSync)(treePath, { recursive: false, force: false });
14970
+ (0, import_node_fs14.rmSync)(treePath, { recursive: false, force: false });
14960
14971
  } catch {
14961
14972
  }
14962
14973
  }
@@ -14973,8 +14984,8 @@ function sessionDossier(token, sessionId) {
14973
14984
  }
14974
14985
  function hasActiveGoal(d) {
14975
14986
  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"');
14987
+ if (!(0, import_node_fs14.existsSync)(d.eventsPath)) return false;
14988
+ return (0, import_node_fs14.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
14978
14989
  } catch {
14979
14990
  return false;
14980
14991
  }
@@ -14998,7 +15009,7 @@ function recordTurn(d, t) {
14998
15009
  for (const a of t.authored) {
14999
15010
  const origin = a.owner === "subagent" ? "subagent" : "edit_tool";
15000
15011
  const prior = t.known?.authored?.get(a.p);
15001
- const hash = fileHash((0, import_node_path12.join)(root, a.p));
15012
+ const hash = fileHash((0, import_node_path13.join)(root, a.p));
15002
15013
  const hunks = Math.max(0, a.h - (prior?.hunks ?? 0));
15003
15014
  const adds = Math.max(0, a.a - (prior?.adds ?? 0));
15004
15015
  const dels = Math.max(0, a.d - (prior?.dels ?? 0));
@@ -15031,7 +15042,7 @@ function recordTurn(d, t) {
15031
15042
  }
15032
15043
  const seenDivergence = t.known?.divergence ?? /* @__PURE__ */ new Set();
15033
15044
  for (const u of t.unobserved) {
15034
- const hash = fileHash((0, import_node_path12.join)(root, u.p));
15045
+ const hash = fileHash((0, import_node_path13.join)(root, u.p));
15035
15046
  if (seenDivergence.has(divergenceKey(u.p, hash))) continue;
15036
15047
  appendEvent(d, { k: "divergence", kind: "external_mutation", path: u.p, hash });
15037
15048
  }
@@ -15060,12 +15071,14 @@ function recordVerdict(d, v) {
15060
15071
  if (summary) appendEvent(d, { k: "goal_delivered", summary });
15061
15072
  }
15062
15073
  const lines = /* @__PURE__ */ new Map();
15074
+ const sent = new Set(v.sentPaths);
15063
15075
  for (const f of v.findings) {
15064
15076
  if (!f.file || typeof f.line !== "number" || !f.pattern_id) continue;
15077
+ if (!sent.has(f.file)) continue;
15065
15078
  if (!lines.has(f.file)) {
15066
15079
  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);
15080
+ const abs = (0, import_node_path13.join)(root, f.file);
15081
+ lines.set(f.file, (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null);
15069
15082
  } catch {
15070
15083
  lines.set(f.file, null);
15071
15084
  }
@@ -15165,8 +15178,8 @@ function recallMemory(d, identity, opts) {
15165
15178
  budgetBytes: opts.budgetBytes,
15166
15179
  readFileLines: (file) => {
15167
15180
  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;
15181
+ const abs = (0, import_node_path13.join)(root, file);
15182
+ return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
15170
15183
  } catch {
15171
15184
  return null;
15172
15185
  }
@@ -15304,22 +15317,22 @@ async function fireClassify(prompt, sessionId, globals) {
15304
15317
  }
15305
15318
 
15306
15319
  // src/commands/lifecycle.ts
15307
- var import_node_fs17 = require("node:fs");
15308
- var import_node_path16 = require("node:path");
15320
+ var import_node_fs18 = require("node:fs");
15321
+ var import_node_path17 = require("node:path");
15309
15322
 
15310
15323
  // src/lib/baseline.ts
15311
- var import_node_fs16 = require("node:fs");
15312
- var import_node_path15 = require("node:path");
15324
+ var import_node_fs17 = require("node:fs");
15325
+ var import_node_path16 = require("node:path");
15313
15326
  var import_node_crypto9 = require("node:crypto");
15314
15327
 
15315
15328
  // src/lib/snapshot.ts
15316
- var import_node_fs15 = require("node:fs");
15317
- var import_node_path14 = require("node:path");
15329
+ var import_node_fs16 = require("node:fs");
15330
+ var import_node_path15 = require("node:path");
15318
15331
  var import_node_child_process6 = require("node:child_process");
15319
15332
 
15320
15333
  // src/lib/files.ts
15321
- var import_node_fs14 = require("node:fs");
15322
- var import_node_path13 = require("node:path");
15334
+ var import_node_fs15 = require("node:fs");
15335
+ var import_node_path14 = require("node:path");
15323
15336
  var LANG_MAP = {
15324
15337
  // Analyzable (static analysis + Gemini)
15325
15338
  ts: "typescript",
@@ -15387,7 +15400,7 @@ var LANG_MAP = {
15387
15400
  mk: "make"
15388
15401
  };
15389
15402
  function detectLanguage(filepath) {
15390
- const ext = (0, import_node_path13.extname)(filepath).slice(1);
15403
+ const ext = (0, import_node_path14.extname)(filepath).slice(1);
15391
15404
  return LANG_MAP[ext] ?? ext;
15392
15405
  }
15393
15406
  function sortByMtime(files) {
@@ -15395,7 +15408,7 @@ function sortByMtime(files) {
15395
15408
  const resolved = resolveFile(f);
15396
15409
  if (!resolved) return null;
15397
15410
  try {
15398
- const stat3 = (0, import_node_fs14.statSync)(resolved);
15411
+ const stat3 = (0, import_node_fs15.statSync)(resolved);
15399
15412
  return { path: f, resolved, mtime: stat3.mtimeMs };
15400
15413
  } catch {
15401
15414
  return null;
@@ -15428,7 +15441,7 @@ function collectCodeDelta(files, opts) {
15428
15441
  }
15429
15442
  let size;
15430
15443
  try {
15431
- size = (0, import_node_fs14.statSync)(resolved).size;
15444
+ size = (0, import_node_fs15.statSync)(resolved).size;
15432
15445
  } catch {
15433
15446
  exclude(filepath, "not-stattable");
15434
15447
  continue;
@@ -15445,7 +15458,7 @@ function collectCodeDelta(files, opts) {
15445
15458
  }
15446
15459
  let content;
15447
15460
  try {
15448
- content = (0, import_node_fs14.readFileSync)(resolved, "utf-8");
15461
+ content = (0, import_node_fs15.readFileSync)(resolved, "utf-8");
15449
15462
  } catch {
15450
15463
  exclude(filepath, "not-readable");
15451
15464
  continue;
@@ -15484,15 +15497,16 @@ function collectCodeDelta(files, opts) {
15484
15497
 
15485
15498
  // src/lib/snapshot.ts
15486
15499
  function generateSnapshotDiffs(files) {
15487
- if (!(0, import_node_fs15.existsSync)(SNAPSHOT_DIR)) {
15500
+ if (!(0, import_node_fs16.existsSync)(SNAPSHOT_DIR)) {
15488
15501
  return { diffs: [], has_snapshots: false };
15489
15502
  }
15490
15503
  const diffs = [];
15491
15504
  for (const file of files) {
15492
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
15505
+ if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
15506
+ const snapshotPath = (0, import_node_path15.join)(SNAPSHOT_DIR, file.path);
15493
15507
  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");
15508
+ if ((0, import_node_fs16.existsSync)(snapshotPath)) {
15509
+ const oldContent = (0, import_node_fs16.readFileSync)(snapshotPath, "utf-8");
15496
15510
  if (oldContent === file.content) continue;
15497
15511
  const diff = computeDiff(oldContent, file.content, file.path);
15498
15512
  if (diff) {
@@ -15513,23 +15527,51 @@ ${addedLines}`,
15513
15527
  }
15514
15528
  return { diffs, has_snapshots: true };
15515
15529
  }
15530
+ function ensureSnapshotGitignored() {
15531
+ let content = "";
15532
+ try {
15533
+ content = (0, import_node_fs16.readFileSync)(".gitignore", "utf-8");
15534
+ } catch {
15535
+ }
15536
+ let ignored = null;
15537
+ try {
15538
+ (0, import_node_child_process6.execSync)("git check-ignore -q -- .verity/.snapshot/__probe__", { stdio: "pipe" });
15539
+ ignored = true;
15540
+ } catch (err) {
15541
+ ignored = err.status === 1 ? false : null;
15542
+ }
15543
+ if (ignored === true) return "covered";
15544
+ if (ignored === null) {
15545
+ const lines = content.split("\n").map((l) => l.trim());
15546
+ const covering = [".verity/.snapshot/", ".verity/.snapshot", ".verity/", ".verity", ".verity/*"];
15547
+ if (lines.some((l) => covering.includes(l))) return "covered";
15548
+ }
15549
+ try {
15550
+ const block = "# Verity \u2014 snapshots of analyzed files (machine state, never commit)\n.verity/.snapshot/\n";
15551
+ (0, import_node_fs16.writeFileSync)(".gitignore", content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block);
15552
+ return "added";
15553
+ } catch {
15554
+ return "failed";
15555
+ }
15556
+ }
15516
15557
  function saveSnapshots(files) {
15517
15558
  const snapshotPaths = /* @__PURE__ */ new Set();
15518
15559
  for (const file of files) {
15519
- const snapshotPath = (0, import_node_path14.join)(SNAPSHOT_DIR, file.path);
15560
+ if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
15561
+ const snapshotPath = (0, import_node_path15.join)(SNAPSHOT_DIR, file.path);
15520
15562
  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);
15563
+ (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(snapshotPath), { recursive: true });
15564
+ (0, import_node_fs16.writeFileSync)(snapshotPath, file.content);
15523
15565
  }
15524
15566
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
15525
15567
  }
15526
15568
  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");
15569
+ const tmpOld = (0, import_node_path15.join)(SNAPSHOT_DIR, ".diff-old.tmp");
15570
+ const tmpNew = (0, import_node_path15.join)(SNAPSHOT_DIR, ".diff-new.tmp");
15529
15571
  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);
15572
+ (0, import_node_fs16.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
15573
+ (0, import_node_fs16.writeFileSync)(tmpOld, oldContent);
15574
+ (0, import_node_fs16.writeFileSync)(tmpNew, newContent);
15533
15575
  const result = (0, import_node_child_process6.execSync)(
15534
15576
  `git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
15535
15577
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
@@ -15543,32 +15585,32 @@ function computeDiff(oldContent, newContent, filePath) {
15543
15585
  return null;
15544
15586
  } finally {
15545
15587
  try {
15546
- (0, import_node_fs15.unlinkSync)(tmpOld);
15588
+ (0, import_node_fs16.unlinkSync)(tmpOld);
15547
15589
  } catch {
15548
15590
  }
15549
15591
  try {
15550
- (0, import_node_fs15.unlinkSync)(tmpNew);
15592
+ (0, import_node_fs16.unlinkSync)(tmpNew);
15551
15593
  } catch {
15552
15594
  }
15553
15595
  }
15554
15596
  }
15555
15597
  function cleanStaleSnapshots(dir, keepSet) {
15556
- if (!(0, import_node_fs15.existsSync)(dir)) return;
15598
+ if (!(0, import_node_fs16.existsSync)(dir)) return;
15557
15599
  try {
15558
- const entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
15600
+ const entries = (0, import_node_fs16.readdirSync)(dir, { withFileTypes: true });
15559
15601
  for (const entry of entries) {
15560
- if (entry.name.startsWith(".")) continue;
15561
- const fullPath = (0, import_node_path14.join)(dir, entry.name);
15602
+ if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
15603
+ const fullPath = (0, import_node_path15.join)(dir, entry.name);
15562
15604
  if (entry.isDirectory()) {
15563
15605
  cleanStaleSnapshots(fullPath, keepSet);
15564
15606
  try {
15565
- const remaining = (0, import_node_fs15.readdirSync)(fullPath);
15566
- if (remaining.length === 0) (0, import_node_fs15.rmdirSync)(fullPath);
15607
+ const remaining = (0, import_node_fs16.readdirSync)(fullPath);
15608
+ if (remaining.length === 0) (0, import_node_fs16.rmdirSync)(fullPath);
15567
15609
  } catch {
15568
15610
  }
15569
15611
  } else if (!keepSet.has(fullPath)) {
15570
15612
  try {
15571
- (0, import_node_fs15.unlinkSync)(fullPath);
15613
+ (0, import_node_fs16.unlinkSync)(fullPath);
15572
15614
  } catch {
15573
15615
  }
15574
15616
  }
@@ -15587,20 +15629,20 @@ function sessionKey(sessionId) {
15587
15629
  return (0, import_node_crypto9.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
15588
15630
  }
15589
15631
  function sessionDir(key) {
15590
- return (0, import_node_path15.join)(projectPath(BASELINE_DIR), key);
15632
+ return (0, import_node_path16.join)(projectPath(BASELINE_DIR), key);
15591
15633
  }
15592
15634
  function manifestPath(dir) {
15593
- return (0, import_node_path15.join)(dir, "manifest.json");
15635
+ return (0, import_node_path16.join)(dir, "manifest.json");
15594
15636
  }
15595
15637
  function mirrorPath(dir, repoRelPath) {
15596
- return (0, import_node_path15.join)(dir, "files", repoRelPath);
15638
+ return (0, import_node_path16.join)(dir, "files", repoRelPath);
15597
15639
  }
15598
15640
  var CARRY_FILE = `${BASELINE_DIR}/.carry`;
15599
15641
  var CARRY_WINDOW_MS = 12e4;
15600
15642
  function writeCarry(sessionId, headSha) {
15601
15643
  try {
15602
- (0, import_node_fs16.mkdirSync)(projectPath(BASELINE_DIR), { recursive: true });
15603
- (0, import_node_fs16.writeFileSync)(
15644
+ (0, import_node_fs17.mkdirSync)(projectPath(BASELINE_DIR), { recursive: true });
15645
+ (0, import_node_fs17.writeFileSync)(
15604
15646
  projectPath(CARRY_FILE),
15605
15647
  JSON.stringify({ from_key: sessionKey(sessionId), head_sha: headSha, ts: Date.now() })
15606
15648
  );
@@ -15610,10 +15652,10 @@ function writeCarry(sessionId, headSha) {
15610
15652
  function claimCarry(newKey) {
15611
15653
  const carryPath = projectPath(CARRY_FILE);
15612
15654
  try {
15613
- if (!(0, import_node_fs16.existsSync)(carryPath)) return null;
15614
- const carry = JSON.parse((0, import_node_fs16.readFileSync)(carryPath, "utf-8"));
15655
+ if (!(0, import_node_fs17.existsSync)(carryPath)) return null;
15656
+ const carry = JSON.parse((0, import_node_fs17.readFileSync)(carryPath, "utf-8"));
15615
15657
  try {
15616
- (0, import_node_fs16.rmSync)(carryPath, { force: true });
15658
+ (0, import_node_fs17.rmSync)(carryPath, { force: true });
15617
15659
  } catch {
15618
15660
  }
15619
15661
  if (!carry?.from_key || typeof carry.ts !== "number") return null;
@@ -15624,11 +15666,11 @@ function claimCarry(newKey) {
15624
15666
  if (!prior) return null;
15625
15667
  const toDir = sessionDir(newKey);
15626
15668
  try {
15627
- (0, import_node_fs16.rmSync)(toDir, { recursive: true, force: true });
15669
+ (0, import_node_fs17.rmSync)(toDir, { recursive: true, force: true });
15628
15670
  } catch {
15629
15671
  }
15630
- (0, import_node_fs16.renameSync)(fromDir, toDir);
15631
- (0, import_node_fs16.writeFileSync)(manifestPath(toDir), JSON.stringify({ ...prior, session_id: newKey }) + "\n");
15672
+ (0, import_node_fs17.renameSync)(fromDir, toDir);
15673
+ (0, import_node_fs17.writeFileSync)(manifestPath(toDir), JSON.stringify({ ...prior, session_id: newKey }) + "\n");
15632
15674
  return readManifest(toDir);
15633
15675
  } catch {
15634
15676
  return null;
@@ -15652,21 +15694,21 @@ function captureBaseline(opts = {}) {
15652
15694
  const head_sha = getCurrentCommit();
15653
15695
  const dirty = getDirtyFiles();
15654
15696
  try {
15655
- (0, import_node_fs16.rmSync)(dir, { recursive: true, force: true });
15697
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
15656
15698
  } catch {
15657
15699
  }
15658
- const filesDir = (0, import_node_path15.join)(dir, "files");
15700
+ const filesDir = (0, import_node_path16.join)(dir, "files");
15659
15701
  const mirrored = [];
15660
15702
  try {
15661
- (0, import_node_fs16.mkdirSync)(filesDir, { recursive: true });
15703
+ (0, import_node_fs17.mkdirSync)(filesDir, { recursive: true });
15662
15704
  for (const p of dirty) {
15663
15705
  if (p.includes("..")) continue;
15664
15706
  const content = safeReadForMirror(projectPath(p));
15665
15707
  if (content === null) continue;
15666
15708
  const dest = mirrorPath(dir, p);
15667
15709
  try {
15668
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(dest), { recursive: true });
15669
- (0, import_node_fs16.writeFileSync)(dest, content);
15710
+ (0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
15711
+ (0, import_node_fs17.writeFileSync)(dest, content);
15670
15712
  mirrored.push(p);
15671
15713
  } catch {
15672
15714
  }
@@ -15681,8 +15723,8 @@ function captureBaseline(opts = {}) {
15681
15723
  version: BASELINE_VERSION
15682
15724
  };
15683
15725
  try {
15684
- (0, import_node_fs16.mkdirSync)(dir, { recursive: true });
15685
- (0, import_node_fs16.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
15726
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
15727
+ (0, import_node_fs17.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
15686
15728
  } catch {
15687
15729
  }
15688
15730
  pruneOldBaselines();
@@ -15693,9 +15735,9 @@ function readBaseline(sessionId) {
15693
15735
  }
15694
15736
  function readManifest(dir) {
15695
15737
  const mp = manifestPath(dir);
15696
- if (!(0, import_node_fs16.existsSync)(mp)) return null;
15738
+ if (!(0, import_node_fs17.existsSync)(mp)) return null;
15697
15739
  try {
15698
- const parsed = JSON.parse((0, import_node_fs16.readFileSync)(mp, "utf-8"));
15740
+ const parsed = JSON.parse((0, import_node_fs17.readFileSync)(mp, "utf-8"));
15699
15741
  if (typeof parsed.head_sha !== "string" || typeof parsed.captured_at !== "number" || !Array.isArray(parsed.dirty_paths) || parsed.version !== BASELINE_VERSION) {
15700
15742
  return null;
15701
15743
  }
@@ -15726,9 +15768,9 @@ function preImage(repoRelPath, baseline) {
15726
15768
  function resolvePreImage(repoRelPath, baseline) {
15727
15769
  if (baseline.dirty_paths.includes(repoRelPath)) {
15728
15770
  const mp = mirrorPath(sessionDir(sessionKey(baseline.session_id)), repoRelPath);
15729
- if ((0, import_node_fs16.existsSync)(mp)) {
15771
+ if ((0, import_node_fs17.existsSync)(mp)) {
15730
15772
  try {
15731
- return { content: (0, import_node_fs16.readFileSync)(mp, "utf-8"), existed: true };
15773
+ return { content: (0, import_node_fs17.readFileSync)(mp, "utf-8"), existed: true };
15732
15774
  } catch {
15733
15775
  }
15734
15776
  }
@@ -15773,8 +15815,8 @@ function absorbIntoBaseline(paths, sessionId) {
15773
15815
  const content = safeReadForMirror(projectPath(p));
15774
15816
  if (content === null) continue;
15775
15817
  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);
15818
+ (0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
15819
+ (0, import_node_fs17.writeFileSync)(dest, content);
15778
15820
  dirty.add(p);
15779
15821
  adopted++;
15780
15822
  } catch {
@@ -15783,7 +15825,7 @@ function absorbIntoBaseline(paths, sessionId) {
15783
15825
  if (adopted === 0) return 0;
15784
15826
  try {
15785
15827
  const updated = { ...baseline, dirty_paths: [...dirty] };
15786
- (0, import_node_fs16.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
15828
+ (0, import_node_fs17.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
15787
15829
  preImageCache.delete(baseline);
15788
15830
  } catch {
15789
15831
  return 0;
@@ -15794,7 +15836,7 @@ function changedSinceBaseline(repoRelPath, baseline) {
15794
15836
  const pre = preImage(repoRelPath, baseline);
15795
15837
  let current;
15796
15838
  try {
15797
- current = (0, import_node_fs16.readFileSync)(projectPath(repoRelPath), "utf-8");
15839
+ current = (0, import_node_fs17.readFileSync)(projectPath(repoRelPath), "utf-8");
15798
15840
  } catch {
15799
15841
  return pre.existed;
15800
15842
  }
@@ -15803,8 +15845,8 @@ function changedSinceBaseline(repoRelPath, baseline) {
15803
15845
  }
15804
15846
  function safeReadForMirror(absPath) {
15805
15847
  try {
15806
- if ((0, import_node_fs16.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
15807
- const buf = (0, import_node_fs16.readFileSync)(absPath);
15848
+ if ((0, import_node_fs17.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
15849
+ const buf = (0, import_node_fs17.readFileSync)(absPath);
15808
15850
  if (buf.includes(0)) return null;
15809
15851
  return buf.toString("utf-8");
15810
15852
  } catch {
@@ -15815,18 +15857,18 @@ function pruneOldBaselines() {
15815
15857
  const root = projectPath(BASELINE_DIR);
15816
15858
  let entries;
15817
15859
  try {
15818
- entries = (0, import_node_fs16.readdirSync)(root);
15860
+ entries = (0, import_node_fs17.readdirSync)(root);
15819
15861
  } catch {
15820
15862
  return;
15821
15863
  }
15822
15864
  const now = Date.now();
15823
15865
  for (const name of entries) {
15824
- const dir = (0, import_node_path15.join)(root, name);
15866
+ const dir = (0, import_node_path16.join)(root, name);
15825
15867
  const manifest = readManifest(dir);
15826
15868
  if (!manifest) {
15827
15869
  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 });
15870
+ if (now - (0, import_node_fs17.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
15871
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
15830
15872
  }
15831
15873
  } catch {
15832
15874
  }
@@ -15834,7 +15876,7 @@ function pruneOldBaselines() {
15834
15876
  }
15835
15877
  if (now - manifest.captured_at <= BASELINE_TTL_MS) continue;
15836
15878
  try {
15837
- (0, import_node_fs16.rmSync)(dir, { recursive: true, force: true });
15879
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
15838
15880
  } catch {
15839
15881
  }
15840
15882
  }
@@ -16007,8 +16049,8 @@ function buildCompactionContext(session) {
16007
16049
  commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
16008
16050
  readFileLines: (file) => {
16009
16051
  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;
16052
+ const abs = (0, import_node_path17.join)(root, file);
16053
+ return (0, import_node_fs18.existsSync)(abs) ? (0, import_node_fs18.readFileSync)(abs, "utf8").split("\n") : null;
16012
16054
  } catch {
16013
16055
  return null;
16014
16056
  }
@@ -16044,17 +16086,17 @@ async function readHookStdin() {
16044
16086
  try {
16045
16087
  if (process.stdin.isTTY) return {};
16046
16088
  const chunks = [];
16047
- const timeout = new Promise((resolve2) => setTimeout(() => resolve2({}), 500));
16048
- const read = new Promise((resolve2) => {
16089
+ const timeout = new Promise((resolve4) => setTimeout(() => resolve4({}), 500));
16090
+ const read = new Promise((resolve4) => {
16049
16091
  process.stdin.on("data", (c) => chunks.push(c));
16050
16092
  process.stdin.on("end", () => {
16051
16093
  try {
16052
- resolve2(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
16094
+ resolve4(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
16053
16095
  } catch {
16054
- resolve2({});
16096
+ resolve4({});
16055
16097
  }
16056
16098
  });
16057
- process.stdin.on("error", () => resolve2({}));
16099
+ process.stdin.on("error", () => resolve4({}));
16058
16100
  process.stdin.resume();
16059
16101
  });
16060
16102
  return await Promise.race([read, timeout]);
@@ -16572,6 +16614,7 @@ function createRun(opts, globals) {
16572
16614
  phaseReached: "",
16573
16615
  phasesCompleted: [],
16574
16616
  skipReason: null,
16617
+ treeFrame: null,
16575
16618
  turnId: "",
16576
16619
  // The value `resolveReachability` itself returns when every rung declines.
16577
16620
  reachability: { human_reachable: "unknown", human_reachable_source: "inferred", rung: "none" },
@@ -16640,7 +16683,7 @@ function createRun(opts, globals) {
16640
16683
  }
16641
16684
 
16642
16685
  // src/lib/stderr-log.ts
16643
- var import_node_fs18 = require("node:fs");
16686
+ var import_node_fs19 = require("node:fs");
16644
16687
  var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
16645
16688
  var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
16646
16689
  function scrub(s) {
@@ -16653,9 +16696,9 @@ function append(text) {
16653
16696
  try {
16654
16697
  const dir = projectPath(DEBUG_LOG_DIR);
16655
16698
  const file = projectPath(STDERR_LOG_FILE);
16656
- (0, import_node_fs18.mkdirSync)(dir, { recursive: true });
16699
+ (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
16657
16700
  rotateIfNeeded(file);
16658
- (0, import_node_fs18.appendFileSync)(file, text);
16701
+ (0, import_node_fs19.appendFileSync)(file, text);
16659
16702
  } catch {
16660
16703
  }
16661
16704
  }
@@ -16711,13 +16754,17 @@ function formatRunEvidence(run, startedAt) {
16711
16754
  `;
16712
16755
  out += row("turn", `${run.turnId || "(unminted)"}${run.sessionId ? ` \xB7 session ${run.sessionId}` : ""}`);
16713
16756
  out += row("reached", `${run.phaseReached || "(none)"}${run.skipReason ? ` \xB7 SKIPPED: ${run.skipReason}` : ""} \xB7 ${ms}ms`);
16757
+ if (run.treeFrame) {
16758
+ const f = run.treeFrame;
16759
+ out += row("tree", f.worktreeRoot ? `${f.worktreeRoot}${f.isLinkedWorktree ? " \xB7 linked worktree" : ""}${f.branch ? ` \xB7 branch ${f.branch}` : " \xB7 detached"}` : `(unresolved: ${f.refusal ?? "unknown"})`);
16760
+ }
16714
16761
  out += row("changed", `${run.changedUniverse.length} from git \xB7 analyzable ${run.analyzable.length} \xB7 reviewable ${run.reviewable.length} \xB7 security ${run.securityFiles.length} \xB7 forReview ${run.allForReview.length}`);
16715
16762
  const done = (phase) => run.phasesCompleted.includes(phase);
16716
16763
  const ifDone = (phase, value) => done(phase) ? value : "?";
16717
16764
  const md = run.modeDecision;
16718
16765
  if (md) {
16719
16766
  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"}`);
16767
+ 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
16768
  } else {
16722
16769
  out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
16723
16770
  }
@@ -16807,6 +16854,301 @@ function installRunEvidence(run) {
16807
16854
  });
16808
16855
  }
16809
16856
 
16857
+ // src/lib/git-frame.ts
16858
+ var import_node_child_process7 = require("node:child_process");
16859
+ var import_node_fs20 = require("node:fs");
16860
+ var import_node_os3 = require("node:os");
16861
+ var import_node_path18 = require("node:path");
16862
+ var import_node_path19 = require("node:path");
16863
+ var VALUE_TOKEN = `(?:'[^']*'|"[^"]*"|\\S+)`;
16864
+ var GIT_GLOBAL_OPTS = `(?:\\s+(?:-[Cc]\\s+${VALUE_TOKEN}|--?[\\w-]+(?:=\\S+)?))*`;
16865
+ var COMMIT_HEAD = `git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`;
16866
+ var PUSH_HEAD = `git${GIT_GLOBAL_OPTS}\\s+push\\b`;
16867
+ var GH_PR_HEAD = `gh${GIT_GLOBAL_OPTS}\\s+pr\\s+create\\b`;
16868
+ var COMMIT_RE = new RegExp(`(?:^|[\\s;&|(])${COMMIT_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${COMMIT_HEAD}`);
16869
+ var PUSH_RE = new RegExp(`(?:^|[\\s;&|(])${PUSH_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${PUSH_HEAD}`);
16870
+ var GH_PR_RE = new RegExp(`(?:^|[\\s;&|(])${GH_PR_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${GH_PR_HEAD}`);
16871
+ function splitSegments(command) {
16872
+ return (command ?? "").split(/&&|\|\||;|\n/);
16873
+ }
16874
+ function findMomentSegment(command, on) {
16875
+ const segments = splitSegments(command);
16876
+ let commitIdx = -1;
16877
+ let pushIdx = -1;
16878
+ for (let i = 0; i < segments.length; i++) {
16879
+ const seg = segments[i];
16880
+ if (/--dry-run\b/.test(seg)) continue;
16881
+ if (commitIdx === -1 && COMMIT_RE.test(seg)) commitIdx = i;
16882
+ if (pushIdx === -1 && (PUSH_RE.test(seg) || GH_PR_RE.test(seg))) pushIdx = i;
16883
+ }
16884
+ if (commitIdx !== -1 && on.includes("commit")) return { moment: "pre-commit", segmentIndex: commitIdx };
16885
+ if (pushIdx !== -1 && on.includes("push")) return { moment: "pre-push", segmentIndex: pushIdx };
16886
+ return null;
16887
+ }
16888
+ function classifyCommand(command, on) {
16889
+ return findMomentSegment(command, on)?.moment ?? null;
16890
+ }
16891
+ function unquote(token) {
16892
+ if (token.length >= 2) {
16893
+ const first = token[0];
16894
+ if ((first === "'" || first === '"') && token.endsWith(first)) return token.slice(1, -1);
16895
+ }
16896
+ return token;
16897
+ }
16898
+ var SHELL_DYNAMIC = /[$`\\]/;
16899
+ function extractCommandTarget(command, segmentIndex, baseDir) {
16900
+ const segments = splitSegments(command);
16901
+ let dir = baseDir;
16902
+ let named = false;
16903
+ for (let i = 0; i < segmentIndex; i++) {
16904
+ const m = segments[i].match(new RegExp(`^\\s*cd(?:\\s+(${VALUE_TOKEN}))?\\s*$`));
16905
+ if (!m) continue;
16906
+ named = true;
16907
+ if (m[1] === void 0) {
16908
+ dir = (0, import_node_os3.homedir)();
16909
+ continue;
16910
+ }
16911
+ const raw = unquote(m[1]);
16912
+ if (SHELL_DYNAMIC.test(raw) || raw === "-") {
16913
+ return { dir: null, named: true, unresolvable: `cd target not statically resolvable: ${raw}` };
16914
+ }
16915
+ const expanded = raw === "~" ? (0, import_node_os3.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os3.homedir)(), raw.slice(2)) : raw;
16916
+ dir = (0, import_node_path18.isAbsolute)(expanded) ? expanded : (0, import_node_path18.resolve)(dir, expanded);
16917
+ }
16918
+ const seg = segments[segmentIndex];
16919
+ const overrideMatch = /--(?:git-dir|work-tree)(?:=|\s)|\bGIT_(?:DIR|WORK_TREE|INDEX_FILE)=/.exec(seg);
16920
+ if (overrideMatch) {
16921
+ return { dir: null, named: true, unresolvable: `git-dir/work-tree/index override in command: ${overrideMatch[0].trim()}` };
16922
+ }
16923
+ const gitMatch = seg.match(new RegExp(`(?:^|[\\s;&|(])(?:[^\\s;&|()'"]*\\/)?git(${GIT_GLOBAL_OPTS})\\s`));
16924
+ if (gitMatch) {
16925
+ const optsRegion = gitMatch[1] ?? "";
16926
+ const cRe = new RegExp(`-C\\s+(${VALUE_TOKEN})`, "g");
16927
+ let cm;
16928
+ while ((cm = cRe.exec(optsRegion)) !== null) {
16929
+ named = true;
16930
+ const raw = unquote(cm[1]);
16931
+ if (SHELL_DYNAMIC.test(raw)) {
16932
+ return { dir: null, named: true, unresolvable: `-C target not statically resolvable: ${raw}` };
16933
+ }
16934
+ const expanded = raw === "~" ? (0, import_node_os3.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os3.homedir)(), raw.slice(2)) : raw;
16935
+ dir = (0, import_node_path18.isAbsolute)(expanded) ? expanded : (0, import_node_path18.resolve)(dir, expanded);
16936
+ }
16937
+ }
16938
+ return { dir, named, unresolvable: null };
16939
+ }
16940
+ var PUSH_VALUE_FLAGS = /* @__PURE__ */ new Set(["-o", "--push-option", "--receive-pack", "--exec"]);
16941
+ function parsePushTarget(segment) {
16942
+ const none = { remote: null, srcRef: null, dstRef: null, isDelete: false };
16943
+ const m = PUSH_RE.exec(segment);
16944
+ if (!m) return none;
16945
+ const rest = segment.slice(m.index + m[0].length);
16946
+ const tokens = (rest.match(new RegExp(`'[^']*'|"[^"]*"|\\S+`, "g")) ?? []).map(unquote);
16947
+ let remote = null;
16948
+ let refspec = null;
16949
+ let isDelete = false;
16950
+ for (let i = 0; i < tokens.length; i++) {
16951
+ const t = tokens[i];
16952
+ if (t === "-d" || t === "--delete") {
16953
+ isDelete = true;
16954
+ continue;
16955
+ }
16956
+ if (t.startsWith("--repo=")) {
16957
+ remote = t.slice("--repo=".length);
16958
+ continue;
16959
+ }
16960
+ if (t === "--repo") {
16961
+ remote = tokens[++i] ?? null;
16962
+ continue;
16963
+ }
16964
+ if (PUSH_VALUE_FLAGS.has(t)) {
16965
+ i++;
16966
+ continue;
16967
+ }
16968
+ if (t.startsWith("-")) continue;
16969
+ if (SHELL_DYNAMIC.test(t)) return none;
16970
+ if (remote === null) {
16971
+ remote = t;
16972
+ continue;
16973
+ }
16974
+ if (refspec === null) {
16975
+ refspec = t;
16976
+ continue;
16977
+ }
16978
+ break;
16979
+ }
16980
+ if (refspec === null) return { remote, srcRef: null, dstRef: null, isDelete };
16981
+ const spec = refspec.startsWith("+") ? refspec.slice(1) : refspec;
16982
+ const colon = spec.indexOf(":");
16983
+ if (colon === -1) return { remote, srcRef: spec, dstRef: null, isDelete };
16984
+ const src = spec.slice(0, colon);
16985
+ const dst = spec.slice(colon + 1);
16986
+ if (src === "") return { remote, srcRef: null, dstRef: dst || null, isDelete: true };
16987
+ return { remote, srcRef: src, dstRef: dst || null, isDelete };
16988
+ }
16989
+ function gitAt(dir, args) {
16990
+ try {
16991
+ return (0, import_node_child_process7.execFileSync)("git", args, { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
16992
+ } catch {
16993
+ return "";
16994
+ }
16995
+ }
16996
+ function realpathOr(p) {
16997
+ try {
16998
+ return import_node_fs20.realpathSync.native(p);
16999
+ } catch {
17000
+ return (0, import_node_path18.resolve)(p);
17001
+ }
17002
+ }
17003
+ function resolveFrame(input) {
17004
+ const found = findMomentSegment(input.command, input.on);
17005
+ const hookDirUsable = !!input.hookCwd && (0, import_node_fs20.existsSync)(input.hookCwd);
17006
+ const baseDir = hookDirUsable ? input.hookCwd : process.cwd();
17007
+ let anchor = hookDirUsable ? "hook-cwd" : "process-cwd";
17008
+ const refuse = (refusal) => ({
17009
+ moment: found?.moment ?? null,
17010
+ frame: { worktreeRoot: null, gitDir: null, commonDir: null, isLinkedWorktree: false, branch: null, anchor, refusal }
17011
+ });
17012
+ let dir = baseDir;
17013
+ if (found) {
17014
+ const segments = splitSegments(input.command);
17015
+ const kindRes = found.moment === "pre-commit" ? [COMMIT_RE] : [PUSH_RE, GH_PR_RE];
17016
+ const dirs = /* @__PURE__ */ new Set();
17017
+ for (let i = 0; i < segments.length; i++) {
17018
+ if (/--dry-run\b/.test(segments[i])) continue;
17019
+ if (!kindRes.some((re) => re.test(segments[i]))) continue;
17020
+ const target = extractCommandTarget(input.command, i, baseDir);
17021
+ if (target.named) anchor = "command-target";
17022
+ if (target.unresolvable) return refuse(`target:${target.unresolvable}`);
17023
+ dirs.add(target.dir ?? baseDir);
17024
+ }
17025
+ if (dirs.size > 1) return refuse(`target:multiple ${found.moment} targets in one command`);
17026
+ const targetDir = dirs.size === 1 ? [...dirs][0] : baseDir;
17027
+ if (targetDir !== baseDir) {
17028
+ if (!(0, import_node_fs20.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
17029
+ dir = targetDir;
17030
+ }
17031
+ }
17032
+ const toplevel = gitAt(dir, ["rev-parse", "--show-toplevel"]);
17033
+ if (!toplevel) {
17034
+ return refuse(anchor === "command-target" ? `target:not a git repository: ${dir}` : `anchor:not a git repository: ${dir}`);
17035
+ }
17036
+ const gitDirRaw = gitAt(dir, ["rev-parse", "--absolute-git-dir"]);
17037
+ const commonRaw = gitAt(dir, ["rev-parse", "--git-common-dir"]);
17038
+ const gitDir = gitDirRaw ? realpathOr(gitDirRaw) : null;
17039
+ const commonDir = commonRaw ? realpathOr((0, import_node_path18.isAbsolute)(commonRaw) ? commonRaw : (0, import_node_path18.resolve)(dir, commonRaw)) : null;
17040
+ const branchRaw = gitAt(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
17041
+ return {
17042
+ moment: found?.moment ?? null,
17043
+ frame: {
17044
+ worktreeRoot: realpathOr(toplevel),
17045
+ gitDir,
17046
+ commonDir,
17047
+ // The one honest definition: a linked worktree's own git dir differs from
17048
+ // the shared one. No path convention involved — see the header.
17049
+ isLinkedWorktree: !!gitDir && !!commonDir && gitDir !== commonDir,
17050
+ branch: !branchRaw || branchRaw === "HEAD" ? null : branchRaw,
17051
+ anchor,
17052
+ refusal: null
17053
+ }
17054
+ };
17055
+ }
17056
+ function frameGit(frame, args) {
17057
+ if (!frame.worktreeRoot) return "";
17058
+ return gitAt(frame.worktreeRoot, args);
17059
+ }
17060
+ function refResolves(frame, ref) {
17061
+ return frameGit(frame, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]) !== "";
17062
+ }
17063
+ var SHA_RE2 = /^[0-9a-f]{40}$/;
17064
+ function baselineShaAt(frame) {
17065
+ if (!frame.worktreeRoot) return null;
17066
+ try {
17067
+ const sha = (0, import_node_fs20.readFileSync)((0, import_node_path19.join)(frame.worktreeRoot, BASELINE_SHA_FILE), "utf-8").trim();
17068
+ if (!SHA_RE2.test(sha)) return null;
17069
+ return refResolves(frame, sha) ? sha : null;
17070
+ } catch {
17071
+ return null;
17072
+ }
17073
+ }
17074
+ function stagedRange() {
17075
+ return { kind: "staged", base: "HEAD", head: "INDEX", via: "index" };
17076
+ }
17077
+ function resolvePushRange(frame, command, on) {
17078
+ const nothing = (via) => ({ kind: "nothing", base: null, head: "HEAD", via });
17079
+ if (!frame.worktreeRoot) return nothing("refused");
17080
+ const found = findMomentSegment(command, on);
17081
+ const segment = found ? splitSegments(command)[found.segmentIndex] : "";
17082
+ const target = parsePushTarget(segment);
17083
+ if (target.isDelete) return nothing("deletion");
17084
+ const head = target.srcRef ?? "HEAD";
17085
+ if (!refResolves(frame, head)) return nothing(`src-unresolvable:${head}`);
17086
+ const srcName = target.srcRef;
17087
+ const branchForRemote = srcName ?? frame.branch;
17088
+ const candidates = [];
17089
+ if (target.remote && (target.dstRef ?? srcName)) {
17090
+ const dstName = (target.dstRef ?? srcName).replace(/^refs\/heads\//, "");
17091
+ candidates.push({ ref: `refs/remotes/${target.remote}/${dstName}`, via: `refspec:${target.remote}/${dstName}` });
17092
+ }
17093
+ if (target.remote && !srcName && !target.dstRef && frame.branch) {
17094
+ candidates.push({ ref: `refs/remotes/${target.remote}/${frame.branch}`, via: `remote:${target.remote}/${frame.branch}` });
17095
+ }
17096
+ candidates.push({ ref: srcName ? `${srcName}@{push}` : "@{push}", via: "@{push}" });
17097
+ candidates.push({ ref: srcName ? `${srcName}@{upstream}` : "@{upstream}", via: "@{upstream}" });
17098
+ if (branchForRemote) {
17099
+ candidates.push({
17100
+ ref: `refs/remotes/origin/${branchForRemote.replace(/^refs\/heads\//, "")}`,
17101
+ via: `origin/${branchForRemote.replace(/^refs\/heads\//, "")}`
17102
+ });
17103
+ }
17104
+ for (const c of candidates) {
17105
+ if (!refResolves(frame, c.ref)) continue;
17106
+ const mergeBase = frameGit(frame, ["merge-base", c.ref, head]);
17107
+ if (SHA_RE2.test(mergeBase)) return { kind: "push", base: mergeBase, head, via: c.via };
17108
+ }
17109
+ const baseline = baselineShaAt(frame);
17110
+ if (baseline) return { kind: "baseline", base: baseline, head, via: "review-baseline" };
17111
+ if (refResolves(frame, `${head}~1`)) return { kind: "last-commit", base: `${head}~1`, head, via: `${head}~1` };
17112
+ return nothing("no-parent");
17113
+ }
17114
+ function rangeFiles(frame, range) {
17115
+ let out;
17116
+ switch (range.kind) {
17117
+ case "staged":
17118
+ out = frameGit(frame, ["diff", "--cached", "--name-only"]);
17119
+ break;
17120
+ case "push":
17121
+ case "baseline":
17122
+ case "last-commit":
17123
+ out = frameGit(frame, ["diff", "--name-only", range.base, range.head === "INDEX" ? "HEAD" : range.head]);
17124
+ break;
17125
+ case "nothing":
17126
+ return [];
17127
+ }
17128
+ return out.split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
17129
+ }
17130
+ function rangeMessages(frame, range) {
17131
+ if (range.kind === "staged" || range.kind === "nothing" || !range.base) return "";
17132
+ return frameGit(frame, ["log", `${range.base}..${range.head === "INDEX" ? "HEAD" : range.head}`, "--format=%B%x00"]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
17133
+ }
17134
+ function frameTelemetry(frame, range, divergence) {
17135
+ const t = {
17136
+ anchor: frame.anchor,
17137
+ linked_worktree: frame.isLinkedWorktree,
17138
+ range_via: range?.via ?? null,
17139
+ refusal: frame.refusal
17140
+ };
17141
+ if (divergence) {
17142
+ t.root_differs = !!frame.worktreeRoot && !!divergence.actualRoot && realpathOr(frame.worktreeRoot) !== realpathOr(divergence.actualRoot);
17143
+ const a = [...divergence.actualFiles].sort().join("\n");
17144
+ const b = [...divergence.frameFiles].sort().join("\n");
17145
+ t.files_differ = a !== b;
17146
+ t.frame_file_count = divergence.frameFiles.length;
17147
+ t.actual_file_count = divergence.actualFiles.length;
17148
+ }
17149
+ return t;
17150
+ }
17151
+
16810
17152
  // src/lib/reachability.ts
16811
17153
  function resolveReachability(input = {}) {
16812
17154
  const env = input.env ?? {};
@@ -16835,7 +17177,7 @@ function truthy(v) {
16835
17177
  }
16836
17178
 
16837
17179
  // src/lib/transcript.ts
16838
- var import_node_fs19 = require("node:fs");
17180
+ var import_node_fs21 = require("node:fs");
16839
17181
  var MAX_READ_BYTES = 256 * 1024;
16840
17182
  var SMALL_FILE_BYTES = 64 * 1024;
16841
17183
  var MAX_FILES_LIST = 20;
@@ -16859,7 +17201,7 @@ async function extractActionSummary(transcriptPath) {
16859
17201
  function readTurnLines(transcriptPath) {
16860
17202
  let size;
16861
17203
  try {
16862
- size = (0, import_node_fs19.statSync)(transcriptPath).size;
17204
+ size = (0, import_node_fs21.statSync)(transcriptPath).size;
16863
17205
  } catch {
16864
17206
  return null;
16865
17207
  }
@@ -16867,7 +17209,7 @@ function readTurnLines(transcriptPath) {
16867
17209
  let raw;
16868
17210
  let windowed = false;
16869
17211
  if (size <= SMALL_FILE_BYTES) {
16870
- raw = (0, import_node_fs19.readFileSync)(transcriptPath, "utf-8");
17212
+ raw = (0, import_node_fs21.readFileSync)(transcriptPath, "utf-8");
16871
17213
  } else {
16872
17214
  windowed = true;
16873
17215
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
@@ -17047,11 +17389,11 @@ function sanitizeCommand(rawCmd) {
17047
17389
  let cmd = rawCmd.split("\n")[0];
17048
17390
  let cut = -1;
17049
17391
  let marker = "";
17050
- for (const sep of [" | ", " > ", " >> ", " 2>", " && ", " ; "]) {
17051
- const idx = cmd.indexOf(sep);
17392
+ for (const sep2 of [" | ", " > ", " >> ", " 2>", " && ", " ; "]) {
17393
+ const idx = cmd.indexOf(sep2);
17052
17394
  if (idx > 0 && (cut === -1 || idx < cut)) {
17053
17395
  cut = idx;
17054
- marker = sep.trim();
17396
+ marker = sep2.trim();
17055
17397
  }
17056
17398
  }
17057
17399
  if (cut > -1) cmd = cmd.slice(0, cut);
@@ -17075,28 +17417,28 @@ async function readStopHookStdin() {
17075
17417
  try {
17076
17418
  if (process.stdin.isTTY) return empty;
17077
17419
  const chunks = [];
17078
- const timeout = new Promise((resolve2) => setTimeout(() => resolve2(empty), 500));
17079
- const read = new Promise((resolve2) => {
17420
+ const timeout = new Promise((resolve4) => setTimeout(() => resolve4(empty), 500));
17421
+ const read = new Promise((resolve4) => {
17080
17422
  process.stdin.on("data", (chunk) => chunks.push(chunk));
17081
17423
  process.stdin.on("end", () => {
17082
17424
  const raw = Buffer.concat(chunks).toString("utf-8").trim();
17083
17425
  if (!raw) {
17084
- resolve2(empty);
17426
+ resolve4(empty);
17085
17427
  return;
17086
17428
  }
17087
17429
  try {
17088
17430
  const data = JSON.parse(raw);
17089
- resolve2({
17431
+ resolve4({
17090
17432
  assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
17091
17433
  stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
17092
17434
  transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
17093
17435
  sessionId: typeof data.session_id === "string" ? data.session_id : null
17094
17436
  });
17095
17437
  } catch {
17096
- resolve2(empty);
17438
+ resolve4(empty);
17097
17439
  }
17098
17440
  });
17099
- process.stdin.on("error", () => resolve2(empty));
17441
+ process.stdin.on("error", () => resolve4(empty));
17100
17442
  process.stdin.resume();
17101
17443
  });
17102
17444
  return await Promise.race([read, timeout]);
@@ -17110,6 +17452,7 @@ async function bootstrap(run) {
17110
17452
  process.chdir(repoRoot());
17111
17453
  } catch {
17112
17454
  }
17455
+ run.treeFrame = resolveFrame({ command: "", on: [], hookCwd: null }).frame;
17113
17456
  const turnId = `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
17114
17457
  let reachability = resolveReachability({
17115
17458
  autonomousFlag: process.env.VERITY_AUTONOMOUS === "1" || opts.mode === "autonomous",
@@ -17146,6 +17489,39 @@ async function bootstrap(run) {
17146
17489
  Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
17147
17490
  }
17148
17491
 
17492
+ // src/lib/self-scope.ts
17493
+ var LEGACY_GATE_SKILLS = /* @__PURE__ */ new Set([
17494
+ "gate-setup",
17495
+ "gate-analyze",
17496
+ "gate-review",
17497
+ "gate-status",
17498
+ "gate-feedback",
17499
+ "gate-insights",
17500
+ "gate-learn",
17501
+ "gate-memory",
17502
+ "gate-reflect"
17503
+ ]);
17504
+ function isVerityOwned(path) {
17505
+ const segments = path.replace(/\\/g, "/").split("/");
17506
+ for (let i = 0; i < segments.length; i++) {
17507
+ const seg = segments[i];
17508
+ if (seg === ".verity" || seg === ".codacy") return true;
17509
+ if (i === segments.length - 1 && (seg === "VERITY.md" || seg === "GATE.md")) return true;
17510
+ if (seg === ".claude" && segments[i + 1] === "skills" && typeof segments[i + 2] === "string") {
17511
+ const skill = segments[i + 2];
17512
+ if (skill.startsWith("verity-") || LEGACY_GATE_SKILLS.has(skill)) return true;
17513
+ }
17514
+ if (seg === ".claude" && segments[i + 1] === "settings.json") return true;
17515
+ }
17516
+ return false;
17517
+ }
17518
+ function partitionVerityOwned(paths) {
17519
+ const kept = [];
17520
+ const owned = [];
17521
+ for (const p of paths) (isVerityOwned(p) ? owned : kept).push(p);
17522
+ return { kept, owned };
17523
+ }
17524
+
17149
17525
  // src/lib/channel.ts
17150
17526
  var MAX_AGENT_CONTEXT_CHARS = 1500;
17151
17527
  var MAX_AGENT_ITEMS = 5;
@@ -17155,6 +17531,29 @@ function renderItem(label2, text, patternId, file, line) {
17155
17531
  const id = patternId ? ` [${patternId}]` : "";
17156
17532
  return `- ${label2}${text}${where}${id}`;
17157
17533
  }
17534
+ function channelInputFrom(response, intentRepeat = 0, priorPendingFingerprints = []) {
17535
+ const metadata = response.metadata ?? {};
17536
+ const intent = response.intent_alignment ?? {};
17537
+ return {
17538
+ intentRepeat,
17539
+ priorPendingFingerprints,
17540
+ gateDecision: String(response.gate_decision ?? ""),
17541
+ findings: response.findings ?? [],
17542
+ pendingItems: response.pending_items ?? [],
17543
+ reviewStatus: metadata.review_status,
17544
+ coverage: metadata.coverage,
17545
+ intentVerdict: intent.verdict,
17546
+ intentGaps: intent.gaps
17547
+ };
17548
+ }
17549
+ function classifyChannelContent(input) {
17550
+ const refusal = input.reviewStatus === "not_reviewed" || input.reviewStatus === "no_authorship_evidence";
17551
+ const intentFlag = input.intentVerdict === "misaligned" || input.intentVerdict === "partial";
17552
+ const advisory = (input.findings ?? []).some((f) => f.scope !== "pre-existing") || (input.pendingItems ?? []).some(
17553
+ (p) => p.pattern_id !== "intent-misalignment" && !!(p.description ?? p.title ?? p.reason)
17554
+ );
17555
+ return { refusal, intentFlag, advisory };
17556
+ }
17158
17557
  function buildAgentContext(input) {
17159
17558
  const lines = [];
17160
17559
  if (input.reviewStatus === "not_reviewed") {
@@ -17240,7 +17639,7 @@ function channelSilence(input) {
17240
17639
  // src/lib/cli-version.ts
17241
17640
  function cliVersion() {
17242
17641
  try {
17243
- return true ? "0.29.4-experimental.f2e812c" : "dev";
17642
+ return true ? "0.30.0-experimental.43e7755" : "dev";
17244
17643
  } catch {
17245
17644
  return "dev";
17246
17645
  }
@@ -17280,8 +17679,8 @@ async function sendSkipBeacon(ctx, reason) {
17280
17679
  }
17281
17680
 
17282
17681
  // src/lib/static-analysis.ts
17283
- var import_node_child_process7 = require("node:child_process");
17284
- var import_node_fs20 = require("node:fs");
17682
+ var import_node_child_process8 = require("node:child_process");
17683
+ var import_node_fs22 = require("node:fs");
17285
17684
  var SEVERITY_ORDER = {
17286
17685
  Error: 0,
17287
17686
  Critical: 0,
@@ -17293,7 +17692,7 @@ var SEVERITY_ORDER = {
17293
17692
  };
17294
17693
  function isCodacyAvailable() {
17295
17694
  try {
17296
- (0, import_node_child_process7.execSync)("which codacy-analysis", { stdio: "pipe" });
17695
+ (0, import_node_child_process8.execSync)("which codacy-analysis", { stdio: "pipe" });
17297
17696
  return true;
17298
17697
  } catch {
17299
17698
  return false;
@@ -17329,13 +17728,13 @@ function runCodacyAnalysis(files) {
17329
17728
  if (files.length === 0) return empty;
17330
17729
  const existingFiles = files.filter((f) => {
17331
17730
  try {
17332
- return (0, import_node_fs20.existsSync)(f);
17731
+ return (0, import_node_fs22.existsSync)(f);
17333
17732
  } catch {
17334
17733
  return false;
17335
17734
  }
17336
17735
  });
17337
17736
  if (existingFiles.length === 0) return empty;
17338
- const proc = (0, import_node_child_process7.spawnSync)("codacy-analysis", buildAnalyzerArgv(existingFiles), {
17737
+ const proc = (0, import_node_child_process8.spawnSync)("codacy-analysis", buildAnalyzerArgv(existingFiles), {
17339
17738
  encoding: "utf-8",
17340
17739
  maxBuffer: 10 * 1024 * 1024
17341
17740
  });
@@ -17562,9 +17961,10 @@ async function scope(run) {
17562
17961
  const { assistantResponse } = run;
17563
17962
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
17564
17963
  run.changedUniverse = allChanged;
17565
- const analyzable = filterAnalyzable(allChanged);
17566
- const reviewable = filterReviewable(allChanged);
17567
- const securityFiles = filterSecurity(allChanged);
17964
+ const { kept: external } = partitionVerityOwned(allChanged);
17965
+ const analyzable = filterAnalyzable(external);
17966
+ const reviewable = filterReviewable(external);
17967
+ const securityFiles = filterSecurity(external);
17568
17968
  const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
17569
17969
  if (noFilesChanged && !assistantResponse) {
17570
17970
  await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
@@ -17574,8 +17974,8 @@ async function scope(run) {
17574
17974
  }
17575
17975
 
17576
17976
  // src/lib/specs.ts
17577
- var import_node_fs21 = require("node:fs");
17578
- var import_node_path17 = require("node:path");
17977
+ var import_node_fs23 = require("node:fs");
17978
+ var import_node_path20 = require("node:path");
17579
17979
  var SPEC_CANDIDATES = [
17580
17980
  "CLAUDE.md",
17581
17981
  "AGENTS.md",
@@ -17606,16 +18006,16 @@ function discoverSpecs(consulted = []) {
17606
18006
  const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
17607
18007
  if (totalBytes >= totalCap) return false;
17608
18008
  if (seen.has(specPath)) return true;
17609
- if (!(0, import_node_fs21.existsSync)(specPath)) return true;
18009
+ if (!(0, import_node_fs23.existsSync)(specPath)) return true;
17610
18010
  seen.add(specPath);
17611
18011
  const remaining = totalCap - totalBytes;
17612
18012
  const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
17613
18013
  const readBytes = Math.min(fileCap, remaining);
17614
18014
  try {
17615
18015
  const buf = Buffer.alloc(readBytes);
17616
- const fd = (0, import_node_fs21.openSync)(specPath, "r");
17617
- const bytesRead = (0, import_node_fs21.readSync)(fd, buf, 0, readBytes, 0);
17618
- (0, import_node_fs21.closeSync)(fd);
18016
+ const fd = (0, import_node_fs23.openSync)(specPath, "r");
18017
+ const bytesRead = (0, import_node_fs23.readSync)(fd, buf, 0, readBytes, 0);
18018
+ (0, import_node_fs23.closeSync)(fd);
17619
18019
  const content = buf.slice(0, bytesRead).toString("utf-8");
17620
18020
  if (!content) return true;
17621
18021
  result.push({ path: specPath, content });
@@ -17631,7 +18031,7 @@ function discoverSpecs(consulted = []) {
17631
18031
  if (!addSpec(candidate)) break;
17632
18032
  }
17633
18033
  for (const dir of ["spec", "docs"]) {
17634
- if (!(0, import_node_fs21.existsSync)(dir)) continue;
18034
+ if (!(0, import_node_fs23.existsSync)(dir)) continue;
17635
18035
  try {
17636
18036
  const mdFiles = findMdFiles(dir, 2).sort();
17637
18037
  for (const mdFile of mdFiles) {
@@ -17646,9 +18046,9 @@ function findMdFiles(dir, maxDepth, depth = 0) {
17646
18046
  if (depth >= maxDepth) return [];
17647
18047
  const result = [];
17648
18048
  try {
17649
- const entries = (0, import_node_fs21.readdirSync)(dir, { withFileTypes: true });
18049
+ const entries = (0, import_node_fs23.readdirSync)(dir, { withFileTypes: true });
17650
18050
  for (const entry of entries) {
17651
- const fullPath = (0, import_node_path17.join)(dir, entry.name);
18051
+ const fullPath = (0, import_node_path20.join)(dir, entry.name);
17652
18052
  if (entry.isFile() && entry.name.endsWith(".md")) {
17653
18053
  result.push(fullPath);
17654
18054
  } else if (entry.isDirectory() && depth < maxDepth - 1) {
@@ -17660,19 +18060,19 @@ function findMdFiles(dir, maxDepth, depth = 0) {
17660
18060
  return result;
17661
18061
  }
17662
18062
  function discoverPlans() {
17663
- const homePlansDir = (0, import_node_path17.join)(process.env.HOME ?? "", ".claude", "plans");
18063
+ const homePlansDir = (0, import_node_path20.join)(process.env.HOME ?? "", ".claude", "plans");
17664
18064
  const localPlansDir = ".claude/plans";
17665
18065
  const candidates = [];
17666
18066
  const seen = /* @__PURE__ */ new Set();
17667
18067
  for (const plansDir of [localPlansDir, homePlansDir]) {
17668
- if (!(0, import_node_fs21.existsSync)(plansDir)) continue;
18068
+ if (!(0, import_node_fs23.existsSync)(plansDir)) continue;
17669
18069
  try {
17670
- for (const f of (0, import_node_fs21.readdirSync)(plansDir)) {
18070
+ for (const f of (0, import_node_fs23.readdirSync)(plansDir)) {
17671
18071
  if (!f.endsWith(".md") || seen.has(f)) continue;
17672
18072
  seen.add(f);
17673
- const fullPath = (0, import_node_path17.join)(plansDir, f);
18073
+ const fullPath = (0, import_node_path20.join)(plansDir, f);
17674
18074
  try {
17675
- const stat3 = (0, import_node_fs21.statSync)(fullPath);
18075
+ const stat3 = (0, import_node_fs23.statSync)(fullPath);
17676
18076
  candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
17677
18077
  } catch {
17678
18078
  }
@@ -17685,7 +18085,7 @@ function discoverPlans() {
17685
18085
  for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
17686
18086
  if (entry.size > MAX_PLAN_FILE_BYTES) continue;
17687
18087
  try {
17688
- const content = (0, import_node_fs21.readFileSync)(entry.path, "utf-8");
18088
+ const content = (0, import_node_fs23.readFileSync)(entry.path, "utf-8");
17689
18089
  result.push({ name: entry.name, content });
17690
18090
  } catch {
17691
18091
  }
@@ -17960,7 +18360,7 @@ async function mode(run) {
17960
18360
  }
17961
18361
 
17962
18362
  // src/lib/debounce.ts
17963
- var import_node_fs22 = require("node:fs");
18363
+ var import_node_fs24 = require("node:fs");
17964
18364
  var import_node_crypto10 = require("node:crypto");
17965
18365
  function scopedFile(base, sessionId) {
17966
18366
  if (!sessionId) return base;
@@ -17968,9 +18368,9 @@ function scopedFile(base, sessionId) {
17968
18368
  }
17969
18369
  function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
17970
18370
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
17971
- if (!(0, import_node_fs22.existsSync)(file)) return null;
18371
+ if (!(0, import_node_fs24.existsSync)(file)) return null;
17972
18372
  try {
17973
- const lastTs = parseInt((0, import_node_fs22.readFileSync)(file, "utf-8").trim(), 10);
18373
+ const lastTs = parseInt((0, import_node_fs24.readFileSync)(file, "utf-8").trim(), 10);
17974
18374
  const nowTs = Math.floor(Date.now() / 1e3);
17975
18375
  const elapsed = nowTs - lastTs;
17976
18376
  if (elapsed < debounceSeconds) {
@@ -17983,10 +18383,10 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
17983
18383
  function checkMtime(files, bypassForRecentCommits, sessionId) {
17984
18384
  if (bypassForRecentCommits) return null;
17985
18385
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
17986
- if (!(0, import_node_fs22.existsSync)(file)) return null;
18386
+ if (!(0, import_node_fs24.existsSync)(file)) return null;
17987
18387
  let debounceTime;
17988
18388
  try {
17989
- debounceTime = (0, import_node_fs22.statSync)(file).mtimeMs;
18389
+ debounceTime = (0, import_node_fs24.statSync)(file).mtimeMs;
17990
18390
  } catch {
17991
18391
  return null;
17992
18392
  }
@@ -17994,7 +18394,7 @@ function checkMtime(files, bypassForRecentCommits, sessionId) {
17994
18394
  const resolved = resolveFile(f);
17995
18395
  if (!resolved) continue;
17996
18396
  try {
17997
- const stat3 = (0, import_node_fs22.statSync)(resolved);
18397
+ const stat3 = (0, import_node_fs24.statSync)(resolved);
17998
18398
  if (stat3.mtimeMs > debounceTime) {
17999
18399
  return null;
18000
18400
  }
@@ -18010,8 +18410,8 @@ function computeContentHash(files) {
18010
18410
  for (const f of sorted) {
18011
18411
  const resolved = resolveFile(f) ?? f;
18012
18412
  try {
18013
- if ((0, import_node_fs22.existsSync)(resolved)) {
18014
- hash.update((0, import_node_fs22.readFileSync)(resolved));
18413
+ if ((0, import_node_fs24.existsSync)(resolved)) {
18414
+ hash.update((0, import_node_fs24.readFileSync)(resolved));
18015
18415
  }
18016
18416
  } catch {
18017
18417
  }
@@ -18021,9 +18421,9 @@ function computeContentHash(files) {
18021
18421
  function checkContentHash(files, sessionId) {
18022
18422
  const hash = computeContentHash(files);
18023
18423
  const file = scopedFile(HASH_FILE, sessionId);
18024
- if ((0, import_node_fs22.existsSync)(file)) {
18424
+ if ((0, import_node_fs24.existsSync)(file)) {
18025
18425
  try {
18026
- const storedHash = (0, import_node_fs22.readFileSync)(file, "utf-8").trim();
18426
+ const storedHash = (0, import_node_fs24.readFileSync)(file, "utf-8").trim();
18027
18427
  if (hash === storedHash) {
18028
18428
  return { skip: "No source changes since last analysis", hash };
18029
18429
  }
@@ -18033,50 +18433,74 @@ function checkContentHash(files, sessionId) {
18033
18433
  return { skip: null, hash };
18034
18434
  }
18035
18435
  function recordAnalysisStart(sessionId) {
18036
- (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18037
- (0, import_node_fs22.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
18436
+ (0, import_node_fs24.mkdirSync)(VERITY_DIR, { recursive: true });
18437
+ (0, import_node_fs24.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
18038
18438
  }
18039
18439
  function recordPassHash(hash, sessionId) {
18040
- (0, import_node_fs22.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
18440
+ (0, import_node_fs24.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
18041
18441
  }
18042
18442
  function narrowToRecent(files, sessionId) {
18043
18443
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
18044
- if (!(0, import_node_fs22.existsSync)(file)) return files;
18444
+ if (!(0, import_node_fs24.existsSync)(file)) return files;
18045
18445
  let debounceTime;
18046
18446
  try {
18047
- debounceTime = (0, import_node_fs22.statSync)(file).mtimeMs;
18447
+ debounceTime = (0, import_node_fs24.statSync)(file).mtimeMs;
18048
18448
  } catch {
18049
18449
  return files;
18050
18450
  }
18051
18451
  const recent = files.filter((f) => {
18052
18452
  try {
18053
- return (0, import_node_fs22.existsSync)(f) && (0, import_node_fs22.statSync)(f).mtimeMs > debounceTime;
18453
+ return (0, import_node_fs24.existsSync)(f) && (0, import_node_fs24.statSync)(f).mtimeMs > debounceTime;
18054
18454
  } catch {
18055
18455
  return false;
18056
18456
  }
18057
18457
  });
18058
18458
  return recent.length > 0 ? recent : files;
18059
18459
  }
18060
- function readIterationState(currentCommit) {
18061
- if (!(0, import_node_fs22.existsSync)(ITERATION_FILE)) return { iteration: 1, fingerprint: null };
18460
+ function readIteration(currentCommit, _contentHash) {
18461
+ return Math.max(1, readBlockState(currentCommit).attempts);
18462
+ }
18463
+ var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
18464
+ function readBlockState(currentCommit, opts) {
18465
+ if (opts?.newUserPrompt) return NO_BLOCKS;
18466
+ if (!(0, import_node_fs24.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
18062
18467
  try {
18063
- const stored = (0, import_node_fs22.readFileSync)(ITERATION_FILE, "utf-8").trim();
18064
- const parts = stored.split(":");
18065
- const iter = parseInt(parts[0], 10);
18066
- const storedCommit = parts[1] ?? "";
18067
- const storedTimestamp = parseInt(parts[2] ?? "0", 10);
18068
- const fingerprint = parts.slice(3).join(":") || null;
18069
- if (isNaN(iter)) return { iteration: 1, fingerprint: null };
18070
- if (storedCommit !== currentCommit) return { iteration: 1, fingerprint: null };
18071
- if (storedTimestamp > 0) {
18072
- const elapsed = Math.floor(Date.now() / 1e3) - storedTimestamp;
18073
- if (elapsed > 600) return { iteration: 1, fingerprint: null };
18074
- }
18075
- return { iteration: iter, fingerprint };
18468
+ const stored = (0, import_node_fs24.readFileSync)(ITERATION_FILE, "utf-8").trim();
18469
+ const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
18470
+ if (!parsed) return NO_BLOCKS;
18471
+ if (parsed.commit !== currentCommit) return NO_BLOCKS;
18472
+ if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
18473
+ return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
18076
18474
  } catch {
18077
- return { iteration: 1, fingerprint: null };
18475
+ return NO_BLOCKS;
18078
18476
  }
18079
18477
  }
18478
+ function parseJsonState(raw) {
18479
+ const o = JSON.parse(raw);
18480
+ const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
18481
+ if (isNaN(attempts)) return null;
18482
+ return {
18483
+ attempts,
18484
+ blocks: typeof o.blocks === "number" ? o.blocks : attempts,
18485
+ fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
18486
+ commit: typeof o.commit === "string" ? o.commit : "",
18487
+ ts: typeof o.ts === "number" ? o.ts : 0
18488
+ };
18489
+ }
18490
+ function parseLegacyState(raw) {
18491
+ const parts = raw.split(":");
18492
+ const n = parseInt(parts[0], 10);
18493
+ if (isNaN(n)) return null;
18494
+ return {
18495
+ attempts: n,
18496
+ // The old file has no separate block count; the old counter is the closest
18497
+ // honest answer, and it errs toward releasing sooner rather than later.
18498
+ blocks: n,
18499
+ fingerprint: parts.slice(3).join(":") || null,
18500
+ commit: parts[1] ?? "",
18501
+ ts: parseInt(parts[2] ?? "0", 10)
18502
+ };
18503
+ }
18080
18504
  function findingsFingerprint(findings) {
18081
18505
  const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
18082
18506
  return [...new Set(keys)].sort().join(",");
@@ -18086,16 +18510,27 @@ function isSameProblem(previous, current) {
18086
18510
  const prev = new Set(previous.split(","));
18087
18511
  return current.split(",").some((k) => prev.has(k));
18088
18512
  }
18089
- function writeIteration(iteration, commit, _contentHash, fingerprint) {
18090
- (0, import_node_fs22.mkdirSync)(VERITY_DIR, { recursive: true });
18091
- const ts = Math.floor(Date.now() / 1e3);
18092
- const fp = fingerprint ? `:${fingerprint}` : "";
18093
- (0, import_node_fs22.writeFileSync)(ITERATION_FILE, `${iteration}:${commit}:${ts}${fp}`);
18513
+ function writeBlockState(commit, state) {
18514
+ (0, import_node_fs24.mkdirSync)(VERITY_DIR, { recursive: true });
18515
+ (0, import_node_fs24.writeFileSync)(
18516
+ ITERATION_FILE,
18517
+ JSON.stringify({
18518
+ v: 2,
18519
+ attempts: state.attempts,
18520
+ blocks: state.blocks,
18521
+ commit,
18522
+ ts: Math.floor(Date.now() / 1e3),
18523
+ fingerprint: state.fingerprint ?? void 0
18524
+ })
18525
+ );
18526
+ }
18527
+ function resetBlockState(commit) {
18528
+ writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
18094
18529
  }
18095
18530
 
18096
18531
  // src/lib/fold.ts
18097
- var import_node_fs23 = require("node:fs");
18098
- var import_node_path18 = require("node:path");
18532
+ var import_node_fs25 = require("node:fs");
18533
+ var import_node_path21 = require("node:path");
18099
18534
  var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
18100
18535
  "user",
18101
18536
  "assistant",
@@ -18133,7 +18568,7 @@ var COMMAND_CLASSES = [
18133
18568
  [/\btsc\b|\bmypy\b|\btypecheck\b/, "typecheck"],
18134
18569
  [/^git\s/, "git"]
18135
18570
  ];
18136
- function classifyCommand(cmd) {
18571
+ function classifyCommand2(cmd) {
18137
18572
  for (const [re, cls] of COMMAND_CLASSES) if (re.test(cmd)) return cls;
18138
18573
  return "other";
18139
18574
  }
@@ -18232,7 +18667,7 @@ function candidateRoots(repoRoot2) {
18232
18667
  const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
18233
18668
  const out = [norm];
18234
18669
  try {
18235
- const real = import_node_fs23.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
18670
+ const real = import_node_fs25.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
18236
18671
  if (real !== norm) out.push(real);
18237
18672
  } catch {
18238
18673
  }
@@ -18282,8 +18717,10 @@ function fold(transcriptPath, opts = {}) {
18282
18717
  subagentSkipped: 0,
18283
18718
  compactions: 0,
18284
18719
  complete: false
18285
- }
18720
+ },
18721
+ planApproval: { approvals: 0, activeSinceLastPrompt: false }
18286
18722
  };
18723
+ const flow = { seq: 0, lastPrompt: -1, lastApproval: -1, approvals: 0 };
18287
18724
  const byPath = /* @__PURE__ */ new Map();
18288
18725
  const commandStats = /* @__PURE__ */ new Map();
18289
18726
  const pendingByToolUse = /* @__PURE__ */ new Map();
@@ -18309,36 +18746,40 @@ function fold(transcriptPath, opts = {}) {
18309
18746
  if (type === "system" && record.subtype === "compact_boundary") {
18310
18747
  result.coverage.compactions++;
18311
18748
  }
18312
- if (type === "user" && hasUserText(record)) result.coverage.userMessages++;
18313
- collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage);
18749
+ if (type === "user" && hasUserText(record)) {
18750
+ result.coverage.userMessages++;
18751
+ if (owner === "agent") flow.lastPrompt = flow.seq;
18752
+ }
18753
+ if (owner === "agent") flow.seq++;
18754
+ collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, opts.repoRoot, result.coverage, flow);
18314
18755
  }
18315
18756
  };
18316
18757
  try {
18317
- if (!(0, import_node_fs23.existsSync)(transcriptPath)) return result;
18318
- ingest((0, import_node_fs23.readFileSync)(transcriptPath, "utf8"), "agent");
18758
+ if (!(0, import_node_fs25.existsSync)(transcriptPath)) return result;
18759
+ ingest((0, import_node_fs25.readFileSync)(transcriptPath, "utf8"), "agent");
18319
18760
  result.coverage.complete = true;
18320
18761
  } catch {
18321
18762
  return result;
18322
18763
  }
18323
18764
  try {
18324
- const sidecarDir = (0, import_node_path18.join)(
18325
- (0, import_node_path18.dirname)(transcriptPath),
18326
- (0, import_node_path18.basename)(transcriptPath).replace(/\.jsonl$/, ""),
18765
+ const sidecarDir = (0, import_node_path21.join)(
18766
+ (0, import_node_path21.dirname)(transcriptPath),
18767
+ (0, import_node_path21.basename)(transcriptPath).replace(/\.jsonl$/, ""),
18327
18768
  "subagents"
18328
18769
  );
18329
- if ((0, import_node_fs23.existsSync)(sidecarDir)) {
18770
+ if ((0, import_node_fs25.existsSync)(sidecarDir)) {
18330
18771
  const maxFiles = opts.maxSidecars ?? 200;
18331
18772
  const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
18332
18773
  const found = [];
18333
18774
  const walk = (d, depth) => {
18334
18775
  if (depth > 4) return;
18335
- for (const e of (0, import_node_fs23.readdirSync)(d, { withFileTypes: true })) {
18336
- const p = (0, import_node_path18.join)(d, e.name);
18776
+ for (const e of (0, import_node_fs25.readdirSync)(d, { withFileTypes: true })) {
18777
+ const p = (0, import_node_path21.join)(d, e.name);
18337
18778
  if (e.isDirectory()) {
18338
18779
  walk(p, depth + 1);
18339
18780
  } else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
18340
18781
  try {
18341
- const st = (0, import_node_fs23.statSync)(p);
18782
+ const st = (0, import_node_fs25.statSync)(p);
18342
18783
  found.push({ path: p, size: st.size, mtimeMs: st.mtimeMs });
18343
18784
  } catch {
18344
18785
  result.coverage.malformed++;
@@ -18355,7 +18796,7 @@ function fold(transcriptPath, opts = {}) {
18355
18796
  continue;
18356
18797
  }
18357
18798
  try {
18358
- ingest((0, import_node_fs23.readFileSync)(f.path, "utf8"), "subagent");
18799
+ ingest((0, import_node_fs25.readFileSync)(f.path, "utf8"), "subagent");
18359
18800
  bytes += f.size;
18360
18801
  result.coverage.subagentFiles++;
18361
18802
  } catch {
@@ -18382,11 +18823,15 @@ function fold(transcriptPath, opts = {}) {
18382
18823
  if (!p || authoredPaths.has(p)) continue;
18383
18824
  result.unobserved.push({ p, cause: classifyUnobserved(raw) });
18384
18825
  }
18826
+ result.planApproval = {
18827
+ approvals: flow.approvals,
18828
+ activeSinceLastPrompt: flow.lastApproval >= 0 && flow.lastApproval > flow.lastPrompt
18829
+ };
18385
18830
  return result;
18386
18831
  }
18387
18832
  function classifyUnobserved(path) {
18388
18833
  try {
18389
- const st = (0, import_node_fs23.statSync)(path);
18834
+ const st = (0, import_node_fs25.statSync)(path);
18390
18835
  if (!st.isFile()) return "unreadable";
18391
18836
  } catch {
18392
18837
  return "unreadable";
@@ -18394,7 +18839,7 @@ function classifyUnobserved(path) {
18394
18839
  if (/\.(png|jpg|jpeg|gif|pdf|zip|woff2?|ico|mp4)$/i.test(path)) return "binary";
18395
18840
  return "no_edit_record";
18396
18841
  }
18397
- function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally) {
18842
+ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse, toolStats, pendingToolName, taskById, pendingTaskName, repoRoot2, tally, flow) {
18398
18843
  const message = record.message;
18399
18844
  const content = message?.content ?? record.content;
18400
18845
  const blocks = Array.isArray(content) ? content : content && typeof content === "object" ? [content] : [];
@@ -18452,7 +18897,7 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18452
18897
  if (name === "Bash") {
18453
18898
  const cmd = typeof input.command === "string" ? input.command : "";
18454
18899
  if (cmd) {
18455
- const cls = classifyCommand(cmd);
18900
+ const cls = classifyCommand2(cmd);
18456
18901
  const prev = commandStats.get(cls) ?? { last_status: null, runs: 0, head: "" };
18457
18902
  commandStats.set(cls, {
18458
18903
  // UNKNOWN until this command's OWN result arrives. Inheriting the
@@ -18478,6 +18923,13 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
18478
18923
  }
18479
18924
  }
18480
18925
  const toolName = id ? pendingToolName.get(id) : void 0;
18926
+ if (toolName === "ExitPlanMode" && flow && block.is_error !== true) {
18927
+ const body = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => typeof c.text === "string" ? c.text : "").join(" ") : "";
18928
+ if (/approved your plan/i.test(body)) {
18929
+ flow.lastApproval = flow.seq;
18930
+ flow.approvals += 1;
18931
+ }
18932
+ }
18481
18933
  if (toolName) {
18482
18934
  pendingToolName.delete(id);
18483
18935
  const prevTool = toolStats.get(toolName);
@@ -18535,8 +18987,13 @@ function checkConservation(changedFiles, result, repoRoot2) {
18535
18987
  // src/commands/analyze/phases/06-evidence.ts
18536
18988
  async function evidence(run) {
18537
18989
  const { opts } = run;
18538
- const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath } = run;
18990
+ const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run;
18539
18991
  let { analysisMode, earlyFold } = run;
18992
+ const recordFlip = (stage) => {
18993
+ if (run.modeDecision) run.modeDecision = { ...run.modeDecision, resolved: "plan", flip: stage };
18994
+ logEvent("mode_flipped", { stage, to: "plan" });
18995
+ };
18996
+ const planWorthy = !!assistantResponse && !turnAuthoredCode;
18540
18997
  let staticResults = {
18541
18998
  tool: "@codacy/analysis-cli",
18542
18999
  findings: [],
@@ -18556,8 +19013,9 @@ async function evidence(run) {
18556
19013
  const debounceSeconds = parseInt(opts.debounce, 10);
18557
19014
  const debounceSkip = checkDebounce(debounceSeconds, baselineSessionId);
18558
19015
  if (debounceSkip) {
18559
- if (assistantResponse) {
19016
+ if (planWorthy) {
18560
19017
  analysisMode = "plan";
19018
+ recordFlip("debounce");
18561
19019
  } else {
18562
19020
  await passAndExit(run, debounceSkip, "debounce");
18563
19021
  }
@@ -18566,8 +19024,9 @@ async function evidence(run) {
18566
19024
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18567
19025
  const mtimeSkip = checkMtime(allCheckable, hasRecentCommitFiles, baselineSessionId);
18568
19026
  if (mtimeSkip) {
18569
- if (assistantResponse) {
19027
+ if (planWorthy) {
18570
19028
  analysisMode = "plan";
19029
+ recordFlip("mtime");
18571
19030
  } else {
18572
19031
  await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
18573
19032
  }
@@ -18578,8 +19037,9 @@ async function evidence(run) {
18578
19037
  const allCheckable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable, ...securityFiles]));
18579
19038
  const hashResult = checkContentHash(allCheckable, baselineSessionId);
18580
19039
  if (hashResult.skip) {
18581
- if (assistantResponse) {
19040
+ if (planWorthy) {
18582
19041
  analysisMode = "plan";
19042
+ recordFlip("content-hash");
18583
19043
  } else {
18584
19044
  await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
18585
19045
  }
@@ -18641,8 +19101,9 @@ async function evidence(run) {
18641
19101
  maxTotalBytes: parseInt(opts.maxTotalSize, 10)
18642
19102
  });
18643
19103
  if (codeDelta.files.length === 0 && staticResults.findings.length === 0) {
18644
- if (assistantResponse) {
19104
+ if (planWorthy) {
18645
19105
  analysisMode = "plan";
19106
+ recordFlip("empty-after-scoping");
18646
19107
  } else {
18647
19108
  await passAndExit(
18648
19109
  run,
@@ -18662,32 +19123,32 @@ async function evidence(run) {
18662
19123
  snapshotResult = generateSnapshotDiffs(codeDelta.files);
18663
19124
  }
18664
19125
  currentCommit = getCurrentCommit();
18665
- iteration = readIterationState(currentCommit).iteration;
19126
+ iteration = readIteration(currentCommit);
18666
19127
  }
18667
19128
  }
18668
19129
  if (analysisMode === "plan") {
18669
19130
  recordAnalysisStart();
18670
19131
  currentCommit = getCurrentCommit();
18671
- iteration = readIterationState(currentCommit).iteration;
19132
+ iteration = readIteration(currentCommit);
18672
19133
  }
18673
19134
  Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
18674
19135
  }
18675
19136
 
18676
19137
  // src/lib/cache-cleanup.ts
18677
- var import_node_fs24 = require("node:fs");
18678
- var import_node_path19 = require("node:path");
19138
+ var import_node_fs26 = require("node:fs");
19139
+ var import_node_path22 = require("node:path");
18679
19140
  var CACHE_TTL_DAYS = 7;
18680
19141
  function pruneStaleCache() {
18681
19142
  try {
18682
19143
  const dir = projectPath(CACHE_DIR);
18683
19144
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
18684
- for (const entry of (0, import_node_fs24.readdirSync)(dir)) {
19145
+ for (const entry of (0, import_node_fs26.readdirSync)(dir)) {
18685
19146
  if (!entry.startsWith("pending-")) continue;
18686
- const path = (0, import_node_path19.join)(dir, entry);
19147
+ const path = (0, import_node_path22.join)(dir, entry);
18687
19148
  try {
18688
- const stat3 = (0, import_node_fs24.statSync)(path);
19149
+ const stat3 = (0, import_node_fs26.statSync)(path);
18689
19150
  if (stat3.mtimeMs < cutoff) {
18690
- (0, import_node_fs24.unlinkSync)(path);
19151
+ (0, import_node_fs26.unlinkSync)(path);
18691
19152
  logEvent("cache_entry_pruned", {
18692
19153
  path: entry,
18693
19154
  age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
@@ -18701,7 +19162,7 @@ function pruneStaleCache() {
18701
19162
  }
18702
19163
 
18703
19164
  // src/lib/context-files.ts
18704
- var import_node_fs25 = require("node:fs");
19165
+ var import_node_fs27 = require("node:fs");
18705
19166
  var MAX_CONTEXT_FILES = 10;
18706
19167
  var MAX_CONTEXT_FILE_BYTES = 10240;
18707
19168
  var MAX_CONTEXT_TOTAL_BYTES = 51200;
@@ -18716,8 +19177,13 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18716
19177
  logEvent("context_file_skipped", { path: filePath, reason: "verity_owned" });
18717
19178
  continue;
18718
19179
  }
19180
+ const safePath = resolveInside(process.cwd(), filePath);
19181
+ if (!safePath) {
19182
+ logEvent("context_file_skipped", { path: filePath, reason: "outside_repo" });
19183
+ continue;
19184
+ }
18719
19185
  try {
18720
- const content = (0, import_node_fs25.readFileSync)(filePath, "utf8");
19186
+ const content = (0, import_node_fs27.readFileSync)(safePath, "utf8");
18721
19187
  const bytes = Buffer.byteLength(content);
18722
19188
  if (bytes > MAX_CONTEXT_FILE_BYTES) {
18723
19189
  logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
@@ -18764,7 +19230,8 @@ function gatherContextFiles(contextPaths, deltaFiles) {
18764
19230
  // src/commands/analyze/phases/07-context-files.ts
18765
19231
  async function contextFiles(run) {
18766
19232
  const { codeDelta, contextFilePaths } = run;
18767
- const contextFiles2 = gatherContextFiles(contextFilePaths, codeDelta.files);
19233
+ const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
19234
+ const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
18768
19235
  for (const f of codeDelta.files) {
18769
19236
  f.role = "delta";
18770
19237
  }
@@ -18778,8 +19245,8 @@ async function contextFiles(run) {
18778
19245
 
18779
19246
  // src/lib/seed-runner.ts
18780
19247
  var import_promises11 = require("node:fs/promises");
18781
- var import_node_fs26 = require("node:fs");
18782
- var import_node_path20 = require("node:path");
19248
+ var import_node_fs28 = require("node:fs");
19249
+ var import_node_path23 = require("node:path");
18783
19250
  var import_yaml2 = __toESM(require_dist());
18784
19251
 
18785
19252
  // src/lib/seed.ts
@@ -19018,7 +19485,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
19018
19485
  return fm;
19019
19486
  }
19020
19487
  async function runSeed(opts) {
19021
- if (!(0, import_node_fs26.existsSync)(STANDARD_FILE)) {
19488
+ if (!(0, import_node_fs28.existsSync)(STANDARD_FILE)) {
19022
19489
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
19023
19490
  }
19024
19491
  let standardDoc;
@@ -19030,7 +19497,7 @@ async function runSeed(opts) {
19030
19497
  }
19031
19498
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
19032
19499
  let readmeContent;
19033
- if ((0, import_node_fs26.existsSync)("README.md")) {
19500
+ if ((0, import_node_fs28.existsSync)("README.md")) {
19034
19501
  try {
19035
19502
  readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
19036
19503
  } catch {
@@ -19038,7 +19505,7 @@ async function runSeed(opts) {
19038
19505
  }
19039
19506
  let claudeMdContent;
19040
19507
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
19041
- if ((0, import_node_fs26.existsSync)(p)) {
19508
+ if ((0, import_node_fs28.existsSync)(p)) {
19042
19509
  try {
19043
19510
  claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
19044
19511
  break;
@@ -19061,8 +19528,8 @@ async function runSeed(opts) {
19061
19528
  if (candidates.length === 0) {
19062
19529
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
19063
19530
  }
19064
- const overviewPath = (0, import_node_path20.join)(MEMORY_DIR, "domain", "project-overview.md");
19065
- if ((0, import_node_fs26.existsSync)(overviewPath) && !opts.force) {
19531
+ const overviewPath = (0, import_node_path23.join)(MEMORY_DIR, "domain", "project-overview.md");
19532
+ if ((0, import_node_fs28.existsSync)(overviewPath) && !opts.force) {
19066
19533
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
19067
19534
  }
19068
19535
  if (opts.dryRun) {
@@ -19097,9 +19564,14 @@ async function runSeed(opts) {
19097
19564
  }
19098
19565
  const nodeId = res.data.node_id;
19099
19566
  const filePathRel = res.data.file_path;
19100
- const targetPath = (0, import_node_path20.join)(MEMORY_DIR, filePathRel);
19567
+ const targetPath = resolveInside(MEMORY_DIR, filePathRel);
19568
+ if (!targetPath) {
19569
+ opts.onFailed?.(c, `Server returned an out-of-bounds file_path (${String(filePathRel)}); refusing to write outside the memory directory.`);
19570
+ failed++;
19571
+ continue;
19572
+ }
19101
19573
  try {
19102
- await (0, import_promises11.mkdir)((0, import_node_path20.dirname)(targetPath), { recursive: true });
19574
+ await (0, import_promises11.mkdir)((0, import_node_path23.dirname)(targetPath), { recursive: true });
19103
19575
  await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
19104
19576
  created++;
19105
19577
  opts.onCreated?.(nodeId, filePathRel, c);
@@ -19112,8 +19584,8 @@ async function runSeed(opts) {
19112
19584
  }
19113
19585
 
19114
19586
  // src/commands/analyze/phases/08-memory-manifest.ts
19115
- var import_node_fs27 = require("node:fs");
19116
- var import_node_path21 = require("node:path");
19587
+ var import_node_fs29 = require("node:fs");
19588
+ var import_node_path24 = require("node:path");
19117
19589
  async function memoryManifest(run) {
19118
19590
  const { globals } = run;
19119
19591
  const { serviceUrl, token } = run;
@@ -19123,9 +19595,9 @@ async function memoryManifest(run) {
19123
19595
  let autoSeedNotice = null;
19124
19596
  try {
19125
19597
  await ensureMemoryDir();
19126
- const seedMarker = (0, import_node_path21.join)(VERITY_DIR, ".seeded");
19127
- const hasStandard = (0, import_node_fs27.existsSync)(STANDARD_FILE);
19128
- const alreadyTried = (0, import_node_fs27.existsSync)(seedMarker);
19598
+ const seedMarker = (0, import_node_path24.join)(VERITY_DIR, ".seeded");
19599
+ const hasStandard = (0, import_node_fs29.existsSync)(STANDARD_FILE);
19600
+ const alreadyTried = (0, import_node_fs29.existsSync)(seedMarker);
19129
19601
  if (hasStandard && !alreadyTried) {
19130
19602
  const preManifest = await buildManifest();
19131
19603
  if (preManifest.nodes.length === 0) {
@@ -19138,7 +19610,7 @@ async function memoryManifest(run) {
19138
19610
  dryRun: false
19139
19611
  });
19140
19612
  if (seedResult.created > 0) {
19141
- (0, import_node_fs27.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
19613
+ (0, import_node_fs29.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
19142
19614
  `);
19143
19615
  autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
19144
19616
  logEvent("auto_seed_ran", {
@@ -19146,7 +19618,7 @@ async function memoryManifest(run) {
19146
19618
  failed: seedResult.failed
19147
19619
  });
19148
19620
  } else if (seedResult.skipped === "already_seeded") {
19149
- (0, import_node_fs27.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
19621
+ (0, import_node_fs29.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
19150
19622
  `);
19151
19623
  } else {
19152
19624
  logEvent("auto_seed_noop", {
@@ -19241,7 +19713,7 @@ function computeIncrement(reviewedPaths, hashOf, priorAuthored) {
19241
19713
  }
19242
19714
 
19243
19715
  // src/commands/analyze/phases/10-working-memory.ts
19244
- var import_node_path22 = require("node:path");
19716
+ var import_node_path25 = require("node:path");
19245
19717
  async function workingMemory(run) {
19246
19718
  const { opts } = run;
19247
19719
  const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run;
@@ -19253,7 +19725,7 @@ async function workingMemory(run) {
19253
19725
  const priorState = foldForMarks(memorySession.d);
19254
19726
  incrementReport = computeIncrement(
19255
19727
  allForReview,
19256
- (p) => fileHash((0, import_node_path22.join)(repoRoot(), p)),
19728
+ (p) => fileHash((0, import_node_path25.join)(repoRoot(), p)),
19257
19729
  priorState.authored_all.map((a) => ({
19258
19730
  path: a.path,
19259
19731
  hash_at_last_verdict: a.hash_at_last_verdict
@@ -19334,6 +19806,54 @@ async function workingMemory(run) {
19334
19806
  Object.assign(run, { incrementReport, memory, memorySession, reachability });
19335
19807
  }
19336
19808
 
19809
+ // src/lib/note-budget.ts
19810
+ var import_node_fs30 = require("node:fs");
19811
+ var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
19812
+ var EPISODE_STALE_SECONDS = 30 * 60;
19813
+ var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
19814
+ function resolveEpisode(prev, signals) {
19815
+ if (!prev) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19816
+ if (signals.humanSpoke) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19817
+ if (signals.rawFail) return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19818
+ if (prev.tasksCompleted !== signals.tasksCompleted) {
19819
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19820
+ }
19821
+ if (prev.ts > 0 && signals.now - prev.ts > EPISODE_STALE_SECONDS) {
19822
+ return { ...FRESH, tasksCompleted: signals.tasksCompleted, ts: signals.now };
19823
+ }
19824
+ return prev;
19825
+ }
19826
+ function advisoryBudgetSpent(episode, rawDecision) {
19827
+ const budget = ADVISORY_BUDGET[rawDecision] ?? ADVISORY_BUDGET.WARN;
19828
+ return episode.delivered >= budget;
19829
+ }
19830
+ function readAdvisoryEpisode(sessionId) {
19831
+ const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
19832
+ if (!(0, import_node_fs30.existsSync)(file)) return null;
19833
+ try {
19834
+ const o = JSON.parse((0, import_node_fs30.readFileSync)(file, "utf-8")) ?? {};
19835
+ const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
19836
+ if (isNaN(delivered)) return null;
19837
+ return {
19838
+ delivered,
19839
+ tasksCompleted: typeof o.tasksCompleted === "number" ? o.tasksCompleted : 0,
19840
+ ts: typeof o.ts === "number" ? o.ts : 0
19841
+ };
19842
+ } catch {
19843
+ return null;
19844
+ }
19845
+ }
19846
+ function writeAdvisoryEpisode(episode, sessionId) {
19847
+ try {
19848
+ (0, import_node_fs30.mkdirSync)(VERITY_DIR, { recursive: true });
19849
+ (0, import_node_fs30.writeFileSync)(
19850
+ scopedFile(ADVISORY_EPISODE_FILE, sessionId),
19851
+ JSON.stringify({ v: 1, ...episode })
19852
+ );
19853
+ } catch {
19854
+ }
19855
+ }
19856
+
19337
19857
  // src/lib/run-mode.ts
19338
19858
  function parseAutonomousEnv(raw) {
19339
19859
  if (raw === void 0) return void 0;
@@ -19356,7 +19876,7 @@ function isExplicitlyAutonomous(env = process.env) {
19356
19876
  }
19357
19877
 
19358
19878
  // src/lib/task-context.ts
19359
- var import_node_child_process8 = require("node:child_process");
19879
+ var import_node_child_process9 = require("node:child_process");
19360
19880
  var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
19361
19881
  var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
19362
19882
  function parseLinkedIssue(sources) {
@@ -19372,7 +19892,7 @@ function parseLinkedIssue(sources) {
19372
19892
  }
19373
19893
  function safeExec(cmd, timeout) {
19374
19894
  try {
19375
- return (0, import_node_child_process8.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
19895
+ return (0, import_node_child_process9.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
19376
19896
  } catch {
19377
19897
  return "";
19378
19898
  }
@@ -19427,7 +19947,13 @@ async function buildRequest(run) {
19427
19947
  excluded_by_reason: excludedByReason,
19428
19948
  // was the transcript itself truncated? The 256 KB window means "this turn"
19429
19949
  // can quietly mean "the last 256 KB of it".
19430
- transcript_windowed: actionSummary?.transcript_windowed ?? null
19950
+ transcript_windowed: actionSummary?.transcript_windowed ?? null,
19951
+ // The advisory budget's fleet counter-metric (note-budget.ts): deliveries in
19952
+ // the episode as of the PREVIOUS turn — this runs before phase 13 updates
19953
+ // the state, so the number is one turn lagged by construction. The
19954
+ // degenerate win for the budget is a dead channel that looks like clean
19955
+ // code; this is what makes "did delivery rate collapse" a query.
19956
+ advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
19431
19957
  };
19432
19958
  const requestBody = {
19433
19959
  coverage_telemetry: coverageTelemetry,
@@ -19566,7 +20092,8 @@ async function buildRequest(run) {
19566
20092
  }
19567
20093
  const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
19568
20094
  const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
19569
- const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task;
20095
+ const planApprovalActive = foldResult?.planApproval?.activeSinceLastPrompt === true;
20096
+ const hasIntent = (conversation?.prompts?.length ?? 0) > 0 || specs.length > 0 || plans.length > 0 || !!assistantResponse || !!w4Task || planApprovalActive;
19570
20097
  if (hasIntent) {
19571
20098
  const intentContext = {};
19572
20099
  if (conversation && conversation.prompts.length > 0) {
@@ -19598,6 +20125,10 @@ async function buildRequest(run) {
19598
20125
  intentContext.user_prompt = w4Task.goal;
19599
20126
  logEvent("w4_issue_anchor", { issue: w4Task.number, via: w4Task.via });
19600
20127
  }
20128
+ if (planApprovalActive) {
20129
+ intentContext.plan_approved = true;
20130
+ logEvent("plan_approval_carried", { approvals: foldResult.planApproval.approvals });
20131
+ }
19601
20132
  if (assistantResponse) {
19602
20133
  const cap = analysisMode === "plan" ? MAX_ASSISTANT_RESPONSE_CHARS_PLAN : MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT;
19603
20134
  intentContext.assistant_response = assistantResponse.length > cap ? assistantResponse.slice(0, cap) : assistantResponse;
@@ -19617,14 +20148,14 @@ async function buildRequest(run) {
19617
20148
  }
19618
20149
 
19619
20150
  // src/lib/offline.ts
19620
- var import_node_fs28 = require("node:fs");
20151
+ var import_node_fs31 = require("node:fs");
19621
20152
  var import_node_crypto11 = require("node:crypto");
19622
20153
  function cacheRequest(body) {
19623
20154
  try {
19624
- (0, import_node_fs28.mkdirSync)(CACHE_DIR, { recursive: true });
20155
+ (0, import_node_fs31.mkdirSync)(CACHE_DIR, { recursive: true });
19625
20156
  const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
19626
20157
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
19627
- (0, import_node_fs28.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
20158
+ (0, import_node_fs31.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
19628
20159
  } catch {
19629
20160
  }
19630
20161
  }
@@ -19743,10 +20274,10 @@ async function transmit(run) {
19743
20274
  }
19744
20275
 
19745
20276
  // src/commands/analyze/phases/13-reconcile.ts
19746
- var import_node_fs29 = require("node:fs");
19747
- var import_node_path23 = require("node:path");
20277
+ var import_node_fs32 = require("node:fs");
20278
+ var import_node_path26 = require("node:path");
19748
20279
  async function reconcile(run) {
19749
- const { actionSummary, allChanged, analyzable, baseline, codeDelta, contentHash, conversation, decision, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
20280
+ const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
19750
20281
  const sentPaths = codeDelta.files.map((f) => f.path);
19751
20282
  let openElsewhere = [];
19752
20283
  if (memorySession) {
@@ -19754,7 +20285,7 @@ async function reconcile(run) {
19754
20285
  const st = foldDossier(memorySession.d);
19755
20286
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
19756
20287
  try {
19757
- const src = (0, import_node_fs29.readFileSync)((0, import_node_path23.join)(repoRoot(), file), "utf8").split("\n");
20288
+ const src = (0, import_node_fs32.readFileSync)((0, import_node_path26.join)(repoRoot(), file), "utf8").split("\n");
19758
20289
  const at = src[line - 1];
19759
20290
  return at === void 0 ? null : lineSha(at);
19760
20291
  } catch {
@@ -19764,6 +20295,7 @@ async function reconcile(run) {
19764
20295
  } catch {
19765
20296
  }
19766
20297
  }
20298
+ const { kept: externalChanged, owned: verityOwned } = partitionVerityOwned(allChanged);
19767
20299
  const reviewCoverage = {
19768
20300
  reviewed: sentPaths,
19769
20301
  // Declared drops from the stages that DO report themselves today. The other
@@ -19805,11 +20337,20 @@ async function reconcile(run) {
19805
20337
  stage: "baseline-scoping",
19806
20338
  kind: "policy"
19807
20339
  })),
20340
+ // ⚠ VERITY'S OWN FILES, named as such — not laundered into the
20341
+ // extension bucket below, where "we do not review our own installer's
20342
+ // dirt" would read as "a changed README". See self-scope.ts.
20343
+ ...verityOwned.map((path) => ({
20344
+ path,
20345
+ reason: "verity-owned",
20346
+ stage: "self-scope",
20347
+ kind: "policy"
20348
+ })),
19808
20349
  // The extension allowlist. POLICY: a changed README was never going to be
19809
20350
  // reviewed, and calling that a coverage gap would downgrade nearly every
19810
20351
  // PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
19811
20352
  // so "what did Verity ignore entirely" is answerable.
19812
- ...allChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
20353
+ ...externalChanged.filter((p) => !analyzable.includes(p) && !reviewable.includes(p) && !securityFiles.includes(p)).map((path) => ({
19813
20354
  path,
19814
20355
  reason: "not-a-reviewed-file-type",
19815
20356
  stage: "extension-allowlist",
@@ -19846,6 +20387,29 @@ async function reconcile(run) {
19846
20387
  decision
19847
20388
  });
19848
20389
  }
20390
+ const episodeSignals = {
20391
+ humanSpoke: (conversation?.prompts?.length ?? 0) > 0,
20392
+ rawFail: decision === "FAIL",
20393
+ tasksCompleted: (foldResult?.tasks ?? []).filter((t) => t.status === "completed").length,
20394
+ now: Math.floor(Date.now() / 1e3)
20395
+ };
20396
+ let episode = resolveEpisode(readAdvisoryEpisode(baselineSessionId), episodeSignals);
20397
+ const contentClass = classifyChannelContent(channelInputFrom(response));
20398
+ const wouldCarryAdvisory = contentClass.advisory || openElsewhere.length > 0;
20399
+ if (decision !== "FAIL" && !silenced && wouldCarryAdvisory && !contentClass.refusal && !contentClass.intentFlag && advisoryBudgetSpent(episode, decision)) {
20400
+ silenced = "note-budget";
20401
+ logEvent("channel_silenced", {
20402
+ reason: silenced,
20403
+ run_id: response.run_id ?? turnId,
20404
+ decision,
20405
+ episode_delivered: episode.delivered
20406
+ });
20407
+ }
20408
+ const deliveringAdvisory = decision !== "FAIL" && !silenced && wouldCarryAdvisory;
20409
+ writeAdvisoryEpisode(
20410
+ { ...episode, delivered: episode.delivered + (deliveringAdvisory ? 1 : 0), ts: episodeSignals.now },
20411
+ baselineSessionId
20412
+ );
19849
20413
  let intentRepeatCount = 0;
19850
20414
  const priorPendingFingerprints = memorySession ? (() => {
19851
20415
  try {
@@ -19861,6 +20425,11 @@ async function reconcile(run) {
19861
20425
  decision,
19862
20426
  branch: getCurrentBranch(),
19863
20427
  watermarkSha: watermarkIsPartial ? null : watermarkHash,
20428
+ // The byte witness — the same "only honest definition of reviewed" the
20429
+ // coverage column uses. A finding on a path outside this set records no
20430
+ // statement (plan-mode prose anchored to unsent files must not become
20431
+ // "STILL OPEN … the tree is not clean").
20432
+ sentPaths,
19864
20433
  findings: response.findings?.map((f) => ({
19865
20434
  file: f.file,
19866
20435
  line: f.line,
@@ -19927,6 +20496,39 @@ ${YELLOW2}${note}${NC2}
19927
20496
  return exit(0);
19928
20497
  }
19929
20498
 
20499
+ // src/lib/may-block.ts
20500
+ var HARD_BLOCK_CEILING = 5;
20501
+ function mayBlock(input) {
20502
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20503
+ if (input.reviewedFileCount === 0 && input.staticFindingCount === 0) {
20504
+ return { block: false, release: "no-code-reviewed" };
20505
+ }
20506
+ if (input.cycleCutFired) {
20507
+ return { block: false, release: "nothing-moved" };
20508
+ }
20509
+ if (input.attempts > input.maxIterations) {
20510
+ return { block: false, release: "same-problem-cap" };
20511
+ }
20512
+ if (input.blocks > ceiling) {
20513
+ return { block: false, release: "block-ceiling" };
20514
+ }
20515
+ return { block: true, release: null };
20516
+ }
20517
+ function describeRelease(release, input) {
20518
+ const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
20519
+ 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.";
20520
+ switch (release) {
20521
+ case "no-code-reviewed":
20522
+ 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}`;
20523
+ case "nothing-moved":
20524
+ return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
20525
+ case "same-problem-cap":
20526
+ return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
20527
+ case "block-ceiling":
20528
+ return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
20529
+ }
20530
+ }
20531
+
19930
20532
  // src/lib/remediation-guard.ts
19931
20533
  var TOOL_CONFIG_PATTERNS = [
19932
20534
  /(^|\/)\.codacy\//,
@@ -19969,19 +20571,7 @@ function screenRemediation(fix, findingFile) {
19969
20571
 
19970
20572
  // src/commands/analyze/phases/14-render.ts
19971
20573
  function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
19972
- const metadata = response.metadata ?? {};
19973
- const intent = response.intent_alignment ?? {};
19974
- return buildAgentContext({
19975
- intentRepeat,
19976
- priorPendingFingerprints,
19977
- gateDecision: String(response.gate_decision ?? ""),
19978
- findings: response.findings ?? [],
19979
- pendingItems: response.pending_items ?? [],
19980
- reviewStatus: metadata.review_status,
19981
- coverage: metadata.coverage,
19982
- intentVerdict: intent.verdict,
19983
- intentGaps: intent.gaps
19984
- });
20574
+ return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
19985
20575
  }
19986
20576
  async function render(run) {
19987
20577
  const { opts, globals } = run;
@@ -20075,38 +20665,63 @@ async function render(run) {
20075
20665
  reverify_by: response.reverify_by
20076
20666
  });
20077
20667
  const grantNudge = grantWarning ? ` \u26A0\uFE0F ${grantWarning}` : "";
20078
- let capReleased = false;
20668
+ let release = null;
20079
20669
  let effectiveDecision = decision;
20080
20670
  if (decision === "FAIL") {
20081
- const blocking = (response.findings ?? []).filter((f) => {
20671
+ const findings = response.findings ?? [];
20672
+ const blocking = findings.filter((f) => {
20082
20673
  const sev = String(f.severity ?? "").toLowerCase();
20083
20674
  return sev === "critical" || sev === "high";
20084
20675
  });
20085
20676
  const fingerprint = findingsFingerprint(blocking);
20086
- const prior = readIterationState(currentCommit);
20677
+ const prior = readBlockState(currentCommit, {
20678
+ newUserPrompt: (conversation?.prompts?.length ?? 0) > 0
20679
+ });
20087
20680
  const sameProblem = isSameProblem(prior.fingerprint, fingerprint);
20088
- const nextIteration = sameProblem ? prior.iteration + 1 : 1;
20089
20681
  const maxIterations = parseInt(opts.maxIterations, 10);
20090
- writeIteration(nextIteration, currentCommit, contentHash ?? void 0, fingerprint);
20091
- iteration = nextIteration;
20092
- if (nextIteration > maxIterations) {
20093
- capReleased = true;
20682
+ const attempts = sameProblem ? prior.attempts + 1 : 1;
20683
+ const blocks = prior.blocks + 1;
20684
+ const decisionNow = mayBlock({
20685
+ reviewedFileCount: codeDelta.files.length,
20686
+ staticFindingCount: run.staticResults?.findings?.length ?? 0,
20687
+ cycleCutFired: silenced !== null,
20688
+ attempts,
20689
+ blocks,
20690
+ maxIterations
20691
+ });
20692
+ if (decisionNow.block) {
20693
+ writeBlockState(currentCommit, { attempts, blocks, fingerprint });
20694
+ iteration = attempts;
20695
+ } else {
20696
+ release = decisionNow.release;
20094
20697
  effectiveDecision = "WARN";
20095
- logEvent("iteration_cap_released", { iteration: nextIteration, fingerprint });
20698
+ logEvent("block_released", {
20699
+ reason: release,
20700
+ attempts,
20701
+ blocks,
20702
+ reviewed_files: codeDelta.files.length,
20703
+ cycle_cut: silenced,
20704
+ fingerprint
20705
+ });
20096
20706
  }
20097
20707
  }
20098
- if (capReleased) {
20708
+ if (release) {
20099
20709
  const findings = response.findings ?? [];
20100
20710
  const lines = findings.slice(0, 5).map((f) => ` [${String(f.severity ?? "?").toUpperCase()}] ${String(f.title ?? f.message ?? "")} (${String(f.file ?? "?")}:${String(f.line ?? "?")})`);
20711
+ const summary = describeRelease(release, {
20712
+ findingCount: findings.length,
20713
+ maxIterations: parseInt(opts.maxIterations, 10)
20714
+ });
20101
20715
  emitVerdict({
20102
20716
  proposed: "WARN",
20103
20717
  changed: run.changedUniverse,
20104
20718
  coverage: reviewCoverage,
20105
- 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.
20106
- ${lines.join("\n")}`,
20719
+ userSummary: lines.length > 0 ? `${summary}
20720
+ ${lines.join("\n")}` : summary,
20107
20721
  agentContext: null,
20108
20722
  silenced: true
20109
20723
  });
20724
+ return;
20110
20725
  }
20111
20726
  switch (effectiveDecision) {
20112
20727
  case "FAIL": {
@@ -20205,7 +20820,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20205
20820
  break;
20206
20821
  }
20207
20822
  case "PASS": {
20208
- writeIteration(1, currentCommit, contentHash ?? void 0);
20823
+ resetBlockState(currentCommit);
20209
20824
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20210
20825
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20211
20826
  let userSummary = response.user_summary ?? "Verity: PASS";
@@ -20226,6 +20841,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
20226
20841
  break;
20227
20842
  }
20228
20843
  case "WARN": {
20844
+ if (decision !== "FAIL") resetBlockState(currentCommit);
20229
20845
  if (watermarkHash) recordPassHash(watermarkHash, baselineSessionId);
20230
20846
  if (!watermarkIsPartial && currentCommit && currentCommit !== "no-git") writeBaselineSha(currentCommit);
20231
20847
  let userSummary = response.user_summary ?? "Verity: WARN";
@@ -20324,7 +20940,7 @@ async function runAnalyze(opts, globals) {
20324
20940
  }
20325
20941
 
20326
20942
  // src/commands/baseline.ts
20327
- var import_node_fs30 = require("node:fs");
20943
+ var import_node_fs33 = require("node:fs");
20328
20944
  function registerBaselineCommands(program2) {
20329
20945
  const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
20330
20946
  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) => {
@@ -20333,7 +20949,7 @@ function registerBaselineCommands(program2) {
20333
20949
  process.chdir(repoRoot());
20334
20950
  } catch {
20335
20951
  }
20336
- if (!(0, import_node_fs30.existsSync)(VERITY_DIR)) {
20952
+ if (!(0, import_node_fs33.existsSync)(VERITY_DIR)) {
20337
20953
  process.exit(0);
20338
20954
  }
20339
20955
  let sessionId = opts.sessionId;
@@ -20373,7 +20989,7 @@ async function readStdin() {
20373
20989
  }
20374
20990
 
20375
20991
  // src/commands/review.ts
20376
- var import_node_fs31 = require("node:fs");
20992
+ var import_node_fs34 = require("node:fs");
20377
20993
  function registerReviewCommand(program2) {
20378
20994
  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) => {
20379
20995
  const globals = program2.opts();
@@ -20392,7 +21008,7 @@ async function runReview(opts, globals) {
20392
21008
  const securityFiles = filterSecurity(allFiles);
20393
21009
  let staticResults;
20394
21010
  if (isCodacyAvailable()) {
20395
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs31.existsSync)(f) || resolveFile(f) !== null);
21011
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs34.existsSync)(f) || resolveFile(f) !== null);
20396
21012
  staticResults = runCodacyAnalysis(scannable);
20397
21013
  } else {
20398
21014
  staticResults = {
@@ -20418,10 +21034,10 @@ async function runReview(opts, globals) {
20418
21034
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
20419
21035
  specs = [];
20420
21036
  for (const p of specPaths) {
20421
- if (!(0, import_node_fs31.existsSync)(p)) continue;
21037
+ if (!(0, import_node_fs34.existsSync)(p)) continue;
20422
21038
  try {
20423
- const { readFileSync: readFileSync18 } = await import("node:fs");
20424
- const content = readFileSync18(p, "utf-8");
21039
+ const { readFileSync: readFileSync20 } = await import("node:fs");
21040
+ const content = readFileSync20(p, "utf-8");
20425
21041
  specs.push({ path: p, content: content.slice(0, 10240) });
20426
21042
  } catch {
20427
21043
  }
@@ -20478,15 +21094,15 @@ async function runReview(opts, globals) {
20478
21094
  }
20479
21095
 
20480
21096
  // src/commands/guard.ts
20481
- var import_node_fs32 = require("node:fs");
20482
- var import_node_path24 = require("node:path");
21097
+ var import_node_fs35 = require("node:fs");
21098
+ var import_node_path27 = require("node:path");
20483
21099
  var GUARD_BLOCK_CAP = 2;
20484
- var GUARD_ITER_FILE = (0, import_node_path24.join)(VERITY_DIR, ".guard-iteration");
21100
+ var GUARD_ITER_FILE = (0, import_node_path27.join)(VERITY_DIR, ".guard-iteration");
20485
21101
  function readPreToolUseStdin() {
20486
21102
  const empty = { command: "", cwd: null, sessionId: null };
20487
- return new Promise((resolve2) => {
21103
+ return new Promise((resolve4) => {
20488
21104
  try {
20489
- if (process.stdin.isTTY) return resolve2(empty);
21105
+ if (process.stdin.isTTY) return resolve4(empty);
20490
21106
  const chunks = [];
20491
21107
  let timer;
20492
21108
  let settled = false;
@@ -20499,7 +21115,7 @@ function readPreToolUseStdin() {
20499
21115
  process.stdin.removeListener("end", onEnd);
20500
21116
  process.stdin.removeListener("error", onError);
20501
21117
  process.stdin.pause();
20502
- resolve2(value);
21118
+ resolve4(value);
20503
21119
  };
20504
21120
  const onEnd = () => {
20505
21121
  try {
@@ -20520,32 +21136,13 @@ function readPreToolUseStdin() {
20520
21136
  process.stdin.on("error", onError);
20521
21137
  process.stdin.resume();
20522
21138
  } catch {
20523
- resolve2(empty);
21139
+ resolve4(empty);
20524
21140
  }
20525
21141
  });
20526
21142
  }
20527
- function buildCommandRe(head) {
20528
- return new RegExp(`(?:^|[\\s;&|(])${head}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${head}`);
20529
- }
20530
- var GIT_GLOBAL_OPTS = "(?:\\s+(?:-[Cc]\\s+\\S+|--?[\\w-]+(?:=\\S+)?))*";
20531
- var COMMIT_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`);
20532
- var PUSH_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+push\\b`);
20533
- var GH_PR_RE = buildCommandRe(`gh${GIT_GLOBAL_OPTS}\\s+pr\\s+create\\b`);
20534
- function classifyCommand2(command, on) {
20535
- let commit = false;
20536
- let push = false;
20537
- for (const seg of (command ?? "").split(/&&|\|\||;|\n/)) {
20538
- if (/--dry-run\b/.test(seg)) continue;
20539
- if (COMMIT_RE.test(seg)) commit = true;
20540
- if (PUSH_RE.test(seg) || GH_PR_RE.test(seg)) push = true;
20541
- }
20542
- if (commit && on.includes("commit")) return "pre-commit";
20543
- if (push && on.includes("push")) return "pre-push";
20544
- return null;
20545
- }
20546
21143
  function readIterMap() {
20547
21144
  try {
20548
- const raw = JSON.parse((0, import_node_fs32.readFileSync)(GUARD_ITER_FILE, "utf-8"));
21145
+ const raw = JSON.parse((0, import_node_fs35.readFileSync)(GUARD_ITER_FILE, "utf-8"));
20549
21146
  if (raw && typeof raw === "object") {
20550
21147
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
20551
21148
  return { [raw.moment]: raw.count };
@@ -20565,10 +21162,10 @@ function readIter(moment) {
20565
21162
  }
20566
21163
  function writeIter(moment, count) {
20567
21164
  try {
20568
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
21165
+ (0, import_node_fs35.mkdirSync)(VERITY_DIR, { recursive: true });
20569
21166
  const map = readIterMap();
20570
21167
  map[moment] = count;
20571
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
21168
+ (0, import_node_fs35.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20572
21169
  } catch {
20573
21170
  }
20574
21171
  }
@@ -20578,10 +21175,10 @@ function resetIter(moment) {
20578
21175
  if (!(moment in map)) return;
20579
21176
  delete map[moment];
20580
21177
  if (Object.keys(map).length === 0) {
20581
- if ((0, import_node_fs32.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs32.unlinkSync)(GUARD_ITER_FILE);
21178
+ if ((0, import_node_fs35.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs35.unlinkSync)(GUARD_ITER_FILE);
20582
21179
  } else {
20583
- (0, import_node_fs32.mkdirSync)(VERITY_DIR, { recursive: true });
20584
- (0, import_node_fs32.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
21180
+ (0, import_node_fs35.mkdirSync)(VERITY_DIR, { recursive: true });
21181
+ (0, import_node_fs35.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
20585
21182
  }
20586
21183
  } catch {
20587
21184
  }
@@ -20596,8 +21193,14 @@ function registerGuardCommand(program2) {
20596
21193
  }
20597
21194
  });
20598
21195
  }
20599
- function getMomentFiles(moment) {
20600
- return moment === "pre-commit" ? getStagedFiles() : getPushRangeFiles().files;
21196
+ function resolveMomentRange(moment, frame, command, on) {
21197
+ return moment === "pre-commit" ? stagedRange() : resolvePushRange(frame, command, on);
21198
+ }
21199
+ function describeRange(range) {
21200
+ if (range.kind === "staged") return "staged";
21201
+ if (range.kind === "nothing" || !range.base) return null;
21202
+ const base = /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
21203
+ return `${base}..${range.head} via ${range.via}`;
20601
21204
  }
20602
21205
  function matchFlagValue(command, flags) {
20603
21206
  const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
@@ -20632,26 +21235,24 @@ function isSubstantiveIntent(text) {
20632
21235
  if (SHELL_PLUMBING.test(t)) return false;
20633
21236
  return true;
20634
21237
  }
20635
- function extractStatedIntent(moment, command) {
20636
- const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? (getPushRangeMessages() || null);
21238
+ function extractStatedIntent(moment, command, pushedMessages = null) {
21239
+ const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
20637
21240
  return isSubstantiveIntent(text) ? text : null;
20638
21241
  }
20639
21242
  function hasBlockingFinding(response) {
20640
21243
  const findings = response.findings ?? [];
20641
21244
  return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()));
20642
21245
  }
20643
- function buildGuardRequest(moment, files, iter, sessionId, command) {
21246
+ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
20644
21247
  const analyzable = filterAnalyzable(files);
20645
21248
  const securityFiles = filterSecurity(files);
20646
21249
  let staticResults;
20647
21250
  if (isCodacyAvailable()) {
20648
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs32.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
21251
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs35.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
20649
21252
  staticResults = runCodacyAnalysis(scannable);
20650
21253
  } else {
20651
21254
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
20652
21255
  }
20653
- const codeDelta = collectCodeDelta(files);
20654
- if (codeDelta.total_files === 0) return null;
20655
21256
  const trigger = moment === "pre-commit" ? "hook:pre-commit" : "hook:pre-push";
20656
21257
  const requestBody = {
20657
21258
  static_results: staticResults,
@@ -20668,9 +21269,9 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
20668
21269
  iteration: iter + 1
20669
21270
  }
20670
21271
  };
21272
+ if (coverageTelemetry) requestBody.coverage_telemetry = coverageTelemetry;
20671
21273
  const specs = discoverSpecs();
20672
21274
  const plans = discoverPlans();
20673
- const statedIntent = extractStatedIntent(moment, command);
20674
21275
  if (specs.length > 0 || plans.length > 0 || statedIntent) {
20675
21276
  const intentContext = {};
20676
21277
  if (statedIntent) intentContext.user_prompt = statedIntent;
@@ -20680,6 +21281,39 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
20680
21281
  }
20681
21282
  return requestBody;
20682
21283
  }
21284
+ function buildGuardCoverage(files, codeDelta, frame, frameRange) {
21285
+ const byReason = {};
21286
+ for (const e of codeDelta.excluded) byReason[e.reason] = (byReason[e.reason] ?? 0) + 1;
21287
+ return {
21288
+ changed_all: files.length,
21289
+ analyzable: filterAnalyzable(files).length,
21290
+ reviewable: filterReviewable(files).length,
21291
+ security: filterSecurity(files).length,
21292
+ for_review: files.length,
21293
+ sent: codeDelta.total_files,
21294
+ capped_out: codeDelta.truncated?.dropped ?? 0,
21295
+ excluded: codeDelta.excluded.length,
21296
+ excluded_by_reason: byReason,
21297
+ transcript_windowed: null,
21298
+ guard_frame: frameTelemetry(frame, frameRange)
21299
+ };
21300
+ }
21301
+ function coverageSummary(c) {
21302
+ const range = c.range ? ` @ ${c.range}` : "";
21303
+ return `reviewed ${c.sent.length} file(s)${range}`;
21304
+ }
21305
+ function coverageBlock(c) {
21306
+ const lines = [];
21307
+ const tree = c.root ? `${c.root}${c.linked ? " (linked worktree)" : ""}${c.branch ? ` \xB7 branch ${c.branch}` : ""}` : "(no tree resolved)";
21308
+ lines.push(`Reviewed (${c.moment}): ${c.sent.length} file(s)${c.range ? ` @ ${c.range}` : ""}`);
21309
+ lines.push(` Tree: ${tree}`);
21310
+ for (const f of c.sent) lines.push(` - ${f}`);
21311
+ if (c.excluded.length > 0) {
21312
+ lines.push(` Excluded (${c.excluded.length}):`);
21313
+ for (const e of c.excluded) lines.push(` - ${e.path} (${e.reason})`);
21314
+ }
21315
+ return lines.join("\n");
21316
+ }
20683
21317
  function emitAllowNotice(userMsg, agentMsg) {
20684
21318
  process.stdout.write(JSON.stringify({
20685
21319
  systemMessage: userMsg,
@@ -20690,15 +21324,24 @@ function emitAllowNotice(userMsg, agentMsg) {
20690
21324
  async function runGuard(opts, globals) {
20691
21325
  const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
20692
21326
  const { command, cwd, sessionId } = await readPreToolUseStdin();
20693
- if (cwd && (0, import_node_fs32.existsSync)(cwd)) {
20694
- try {
20695
- process.chdir(cwd);
20696
- } catch {
20697
- }
20698
- }
20699
- const moment = classifyCommand2(command, on);
21327
+ const moment = classifyCommand(command, on);
20700
21328
  if (!moment) process.exit(0);
20701
21329
  const verb = moment === "pre-commit" ? "commit" : "push";
21330
+ const { frame } = resolveFrame({ command, on, hookCwd: cwd });
21331
+ if (frame.refusal || !frame.worktreeRoot) {
21332
+ logEvent("guard_frame", { moment, ...frameTelemetry(frame, null) });
21333
+ if ((frame.refusal ?? "").startsWith("anchor:")) process.exit(0);
21334
+ emitAllowNotice(
21335
+ `\u26A0 Verity ${moment}: could not resolve the tree this ${verb} targets \u2014 ${verb}ed WITHOUT review`,
21336
+ `Verity ${moment}: the target tree could not be resolved (${frame.refusal}); the ${verb} was allowed WITHOUT a Verity review.`
21337
+ );
21338
+ }
21339
+ try {
21340
+ process.chdir(frame.worktreeRoot);
21341
+ } catch {
21342
+ process.exit(0);
21343
+ }
21344
+ _resetRepoRoot();
20702
21345
  const iter = readIter(moment);
20703
21346
  if (iter >= GUARD_BLOCK_CAP) {
20704
21347
  resetIter(moment);
@@ -20707,13 +21350,39 @@ async function runGuard(opts, globals) {
20707
21350
  `Verity ${moment}: review-cycle cap (${GUARD_BLOCK_CAP}) reached; the ${verb} was allowed without a further block.`
20708
21351
  );
20709
21352
  }
20710
- const files = getMomentFiles(moment);
21353
+ const range = resolveMomentRange(moment, frame, command, on);
21354
+ const files = rangeFiles(frame, range);
20711
21355
  if (files.length === 0) process.exit(0);
20712
21356
  const tokenResult = await resolveToken(globals.token);
20713
21357
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
20714
21358
  if (!tokenResult.ok || !urlResult.ok) process.exit(0);
20715
- const requestBody = buildGuardRequest(moment, files, iter, sessionId, command);
20716
- if (!requestBody) process.exit(0);
21359
+ logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
21360
+ const codeDelta = collectCodeDelta(files);
21361
+ if (codeDelta.total_files === 0) process.exit(0);
21362
+ const statedIntent = extractStatedIntent(
21363
+ moment,
21364
+ command,
21365
+ moment === "pre-push" ? rangeMessages(frame, range) || null : null
21366
+ );
21367
+ const requestBody = buildGuardRequest(
21368
+ moment,
21369
+ files,
21370
+ codeDelta,
21371
+ iter,
21372
+ sessionId,
21373
+ statedIntent,
21374
+ buildGuardCoverage(files, codeDelta, frame, range)
21375
+ );
21376
+ const coverage = {
21377
+ moment,
21378
+ root: frame.worktreeRoot,
21379
+ branch: frame.branch,
21380
+ linked: frame.isLinkedWorktree,
21381
+ range: describeRange(range),
21382
+ sent: codeDelta.files.map((f) => f.path),
21383
+ excluded: codeDelta.excluded.map((e) => ({ path: e.path, reason: e.reason }))
21384
+ };
21385
+ logToFileOnly(coverageBlock(coverage));
20717
21386
  const result = await analyzeRequest({
20718
21387
  serviceUrl: urlResult.data,
20719
21388
  token: tokenResult.data.token,
@@ -20734,31 +21403,39 @@ async function runGuard(opts, globals) {
20734
21403
  const decision = response.gate_decision ?? "(unrecognised)";
20735
21404
  const viewUrl = response.view_url ?? "";
20736
21405
  const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
21406
+ const covLine = coverageSummary(coverage);
21407
+ const covDetail = coverageBlock(coverage);
20737
21408
  if (decision === "FAIL" && hasBlockingFinding(response)) {
20738
21409
  writeIter(moment, iter + 1);
20739
- writeBlockMessage(moment, response);
21410
+ writeBlockMessage(moment, response, covDetail);
20740
21411
  process.exit(2);
20741
21412
  }
20742
21413
  resetIter(moment);
20743
21414
  if (decision === "FAIL") {
20744
21415
  const narrative = response.assessment?.narrative ?? "";
20745
21416
  emitAllowNotice(
20746
- `\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding${link}`,
20747
- `Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}${viewUrl ? ` Report: ${viewUrl}` : ""}`
21417
+ `\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding (${covLine})${link}`,
21418
+ `Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}
21419
+ ${covDetail}${viewUrl ? `
21420
+ Report: ${viewUrl}` : ""}`
20748
21421
  );
20749
21422
  }
20750
21423
  if (decision === "WARN") {
20751
21424
  emitAllowNotice(
20752
- `\u26A0 Verity ${moment}: WARN \u2014 proceeding${link}`,
20753
- `Verity ${moment} review: WARN (proceeding).${viewUrl ? ` Report: ${viewUrl}` : ""}`
21425
+ `\u26A0 Verity ${moment}: WARN \u2014 proceeding (${covLine})${link}`,
21426
+ `Verity ${moment} review: WARN (proceeding).
21427
+ ${covDetail}${viewUrl ? `
21428
+ Report: ${viewUrl}` : ""}`
20754
21429
  );
20755
21430
  }
20756
21431
  emitAllowNotice(
20757
- `\u2713 Verity ${moment}: PASS${link}`,
20758
- `Verity ${moment} review: PASS.${viewUrl ? ` Report: ${viewUrl}` : ""}`
21432
+ `\u2713 Verity ${moment}: PASS (${covLine})${link}`,
21433
+ `Verity ${moment} review: PASS.
21434
+ ${covDetail}${viewUrl ? `
21435
+ Report: ${viewUrl}` : ""}`
20759
21436
  );
20760
21437
  }
20761
- function writeBlockMessage(moment, response) {
21438
+ function writeBlockMessage(moment, response, covDetail) {
20762
21439
  const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
20763
21440
  const verb = moment === "pre-commit" ? "commit" : "push";
20764
21441
  const assessment = response.assessment;
@@ -20788,6 +21465,9 @@ function writeBlockMessage(moment, response) {
20788
21465
  `);
20789
21466
  }
20790
21467
  }
21468
+ process.stderr.write(`${DIM}${covDetail}${NC}
21469
+
21470
+ `);
20791
21471
  if (viewUrl) process.stderr.write(`${CYAN}Full report: ${viewUrl}${NC}
20792
21472
 
20793
21473
  `);
@@ -20796,16 +21476,16 @@ function writeBlockMessage(moment, response) {
20796
21476
  }
20797
21477
 
20798
21478
  // src/commands/init.ts
20799
- var import_node_fs34 = require("node:fs");
21479
+ var import_node_fs37 = require("node:fs");
20800
21480
  var import_promises13 = require("node:fs/promises");
20801
- var import_node_path26 = require("node:path");
20802
- var import_node_child_process10 = require("node:child_process");
21481
+ var import_node_path29 = require("node:path");
21482
+ var import_node_child_process11 = require("node:child_process");
20803
21483
  var readline2 = __toESM(require("node:readline/promises"));
20804
21484
 
20805
21485
  // src/commands/migrate.ts
20806
- var import_node_fs33 = require("node:fs");
20807
- var import_node_path25 = require("node:path");
20808
- var import_node_child_process9 = require("node:child_process");
21486
+ var import_node_fs36 = require("node:fs");
21487
+ var import_node_path28 = require("node:path");
21488
+ var import_node_child_process10 = require("node:child_process");
20809
21489
 
20810
21490
  // src/lib/telemetry.ts
20811
21491
  var import_promises12 = require("node:fs/promises");
@@ -20900,11 +21580,11 @@ async function uninstallTelemetry() {
20900
21580
  // src/commands/migrate.ts
20901
21581
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
20902
21582
  function defaultNpmRemover(pkg) {
20903
- (0, import_node_child_process9.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
21583
+ (0, import_node_child_process10.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
20904
21584
  }
20905
21585
  function isGitTracked(cwd, relPath) {
20906
21586
  try {
20907
- (0, import_node_child_process9.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
21587
+ (0, import_node_child_process10.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
20908
21588
  return true;
20909
21589
  } catch {
20910
21590
  return false;
@@ -20912,7 +21592,7 @@ function isGitTracked(cwd, relPath) {
20912
21592
  }
20913
21593
  function isGitRepo(cwd) {
20914
21594
  try {
20915
- (0, import_node_child_process9.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
21595
+ (0, import_node_child_process10.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
20916
21596
  return true;
20917
21597
  } catch {
20918
21598
  return false;
@@ -20933,12 +21613,12 @@ async function runMigration(opts = {}) {
20933
21613
  return { actions, migrated: actions.length > 0 };
20934
21614
  }
20935
21615
  function migrateProjectDir(root, actions) {
20936
- const gateDir = (0, import_node_path25.join)(root, ".gate");
20937
- const verityDir = (0, import_node_path25.join)(root, ".verity");
20938
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) {
21616
+ const gateDir = (0, import_node_path28.join)(root, ".gate");
21617
+ const verityDir = (0, import_node_path28.join)(root, ".verity");
21618
+ if ((0, import_node_fs36.existsSync)(gateDir) && !(0, import_node_fs36.existsSync)(verityDir)) {
20939
21619
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
20940
21620
  }
20941
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21621
+ if ((0, import_node_fs36.existsSync)(gateDir) && (0, import_node_fs36.existsSync)(verityDir)) {
20942
21622
  return migrateProjectDirCarry(gateDir, verityDir, actions);
20943
21623
  }
20944
21624
  return false;
@@ -20952,20 +21632,20 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
20952
21632
  );
20953
21633
  }
20954
21634
  try {
20955
- (0, import_node_child_process9.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
21635
+ (0, import_node_child_process10.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
20956
21636
  actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
20957
21637
  moved = true;
20958
21638
  } catch {
20959
21639
  }
20960
21640
  }
20961
21641
  if (moved) {
20962
- if ((0, import_node_fs33.existsSync)(gateDir)) {
21642
+ if ((0, import_node_fs36.existsSync)(gateDir)) {
20963
21643
  const carried = carryLegacyContents(gateDir, verityDir);
20964
21644
  if (carried > 0) {
20965
21645
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
20966
21646
  }
20967
21647
  try {
20968
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21648
+ (0, import_node_fs36.rmSync)(gateDir, { recursive: true, force: true });
20969
21649
  } catch {
20970
21650
  }
20971
21651
  }
@@ -20981,18 +21661,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
20981
21661
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
20982
21662
  }
20983
21663
  try {
20984
- (0, import_node_fs33.rmSync)(gateDir, { recursive: true, force: true });
21664
+ (0, import_node_fs36.rmSync)(gateDir, { recursive: true, force: true });
20985
21665
  } catch {
20986
21666
  }
20987
21667
  return carried > 0;
20988
21668
  }
20989
21669
  function migrateGlobalCredentials(home, actions) {
20990
21670
  if (!home) return;
20991
- const gateCreds = (0, import_node_path25.join)(home, ".gate", "credentials");
20992
- const verityCreds = (0, import_node_path25.join)(home, ".verity", "credentials");
20993
- if (!(0, import_node_fs33.existsSync)(gateCreds)) return;
20994
- if (!(0, import_node_fs33.existsSync)(verityCreds)) {
20995
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.join)(home, ".verity"), { recursive: true });
21671
+ const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
21672
+ const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
21673
+ if (!(0, import_node_fs36.existsSync)(gateCreds)) return;
21674
+ if (!(0, import_node_fs36.existsSync)(verityCreds)) {
21675
+ (0, import_node_fs36.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
20996
21676
  moveFile(gateCreds, verityCreds);
20997
21677
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
20998
21678
  return;
@@ -21014,8 +21694,8 @@ async function migrateLegacyHooks(root, actions) {
21014
21694
  }
21015
21695
  }
21016
21696
  async function migrateClaudeMd(root, actions) {
21017
- const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21018
- const hadLegacyBlock = (0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21697
+ const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
21698
+ const hadLegacyBlock = (0, import_node_fs36.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
21019
21699
  if (!hadLegacyBlock) return;
21020
21700
  try {
21021
21701
  await ensureClaudeMdPointer(root);
@@ -21025,13 +21705,13 @@ async function migrateClaudeMd(root, actions) {
21025
21705
  }
21026
21706
  }
21027
21707
  function migrateStandardFile(root, actions) {
21028
- const gateMd = (0, import_node_path25.join)(root, "GATE.md");
21029
- const verityMd = (0, import_node_path25.join)(root, "VERITY.md");
21030
- if (!(0, import_node_fs33.existsSync)(gateMd) || (0, import_node_fs33.existsSync)(verityMd)) return;
21708
+ const gateMd = (0, import_node_path28.join)(root, "GATE.md");
21709
+ const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
21710
+ if (!(0, import_node_fs36.existsSync)(gateMd) || (0, import_node_fs36.existsSync)(verityMd)) return;
21031
21711
  let moved = false;
21032
21712
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
21033
21713
  try {
21034
- (0, import_node_child_process9.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
21714
+ (0, import_node_child_process10.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
21035
21715
  moved = true;
21036
21716
  } catch {
21037
21717
  }
@@ -21039,12 +21719,12 @@ function migrateStandardFile(root, actions) {
21039
21719
  if (!moved) moveFile(gateMd, verityMd);
21040
21720
  const content = readFileSyncSafe(verityMd);
21041
21721
  const refreshed = content.split("GATE.md").join("VERITY.md");
21042
- if (refreshed !== content) (0, import_node_fs33.writeFileSync)(verityMd, refreshed);
21722
+ if (refreshed !== content) (0, import_node_fs36.writeFileSync)(verityMd, refreshed);
21043
21723
  actions.push("Renamed GATE.md \u2192 VERITY.md");
21044
21724
  }
21045
21725
  async function migrateTelemetryHeaders(root, actions) {
21046
- const file = (0, import_node_path25.join)(root, ".claude", "settings.local.json");
21047
- if (!(0, import_node_fs33.existsSync)(file)) return;
21726
+ const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
21727
+ if (!(0, import_node_fs36.existsSync)(file)) return;
21048
21728
  let settings;
21049
21729
  try {
21050
21730
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -21091,22 +21771,22 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
21091
21771
  toAppend.push(line.replace(/\r$/, ""));
21092
21772
  }
21093
21773
  if (toAppend.length > 0) {
21094
- const sep = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
21095
- (0, import_node_fs33.writeFileSync)(verityCreds, verityContent + sep + toAppend.join("\n") + "\n");
21774
+ const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
21775
+ (0, import_node_fs36.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
21096
21776
  }
21097
- (0, import_node_fs33.rmSync)(gateCreds, { force: true });
21777
+ (0, import_node_fs36.rmSync)(gateCreds, { force: true });
21098
21778
  return toAppend.length;
21099
21779
  }
21100
21780
  function readFileSyncSafe(path) {
21101
21781
  try {
21102
- return (0, import_node_fs33.readFileSync)(path, "utf-8");
21782
+ return (0, import_node_fs36.readFileSync)(path, "utf-8");
21103
21783
  } catch {
21104
21784
  return "";
21105
21785
  }
21106
21786
  }
21107
21787
  function hasStagedChanges(root) {
21108
21788
  try {
21109
- (0, import_node_child_process9.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
21789
+ (0, import_node_child_process10.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
21110
21790
  return false;
21111
21791
  } catch {
21112
21792
  return true;
@@ -21114,35 +21794,35 @@ function hasStagedChanges(root) {
21114
21794
  }
21115
21795
  function moveDir(from, to) {
21116
21796
  try {
21117
- (0, import_node_fs33.renameSync)(from, to);
21797
+ (0, import_node_fs36.renameSync)(from, to);
21118
21798
  } catch (err) {
21119
21799
  if (err.code !== "EXDEV") throw err;
21120
- (0, import_node_fs33.cpSync)(from, to, { recursive: true });
21121
- (0, import_node_fs33.rmSync)(from, { recursive: true, force: true });
21800
+ (0, import_node_fs36.cpSync)(from, to, { recursive: true });
21801
+ (0, import_node_fs36.rmSync)(from, { recursive: true, force: true });
21122
21802
  }
21123
21803
  }
21124
21804
  function moveFile(from, to) {
21125
21805
  try {
21126
- (0, import_node_fs33.renameSync)(from, to);
21806
+ (0, import_node_fs36.renameSync)(from, to);
21127
21807
  } catch (err) {
21128
21808
  if (err.code !== "EXDEV") throw err;
21129
- (0, import_node_fs33.cpSync)(from, to);
21130
- (0, import_node_fs33.rmSync)(from, { force: true });
21809
+ (0, import_node_fs36.cpSync)(from, to);
21810
+ (0, import_node_fs36.rmSync)(from, { force: true });
21131
21811
  }
21132
21812
  }
21133
21813
  function carryLegacyContents(gateDir, verityDir) {
21134
21814
  let copied = 0;
21135
21815
  const walk = (relDir) => {
21136
- const srcDir = (0, import_node_path25.join)(gateDir, relDir);
21137
- for (const entry of (0, import_node_fs33.readdirSync)(srcDir)) {
21138
- const rel = relDir ? (0, import_node_path25.join)(relDir, entry) : entry;
21139
- const src = (0, import_node_path25.join)(gateDir, rel);
21140
- const dest = (0, import_node_path25.join)(verityDir, rel);
21141
- if ((0, import_node_fs33.statSync)(src).isDirectory()) {
21816
+ const srcDir = (0, import_node_path28.join)(gateDir, relDir);
21817
+ for (const entry of (0, import_node_fs36.readdirSync)(srcDir)) {
21818
+ const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
21819
+ const src = (0, import_node_path28.join)(gateDir, rel);
21820
+ const dest = (0, import_node_path28.join)(verityDir, rel);
21821
+ if ((0, import_node_fs36.statSync)(src).isDirectory()) {
21142
21822
  walk(rel);
21143
- } else if (!(0, import_node_fs33.existsSync)(dest)) {
21144
- (0, import_node_fs33.mkdirSync)((0, import_node_path25.dirname)(dest), { recursive: true });
21145
- (0, import_node_fs33.cpSync)(src, dest);
21823
+ } else if (!(0, import_node_fs36.existsSync)(dest)) {
21824
+ (0, import_node_fs36.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
21825
+ (0, import_node_fs36.cpSync)(src, dest);
21146
21826
  copied++;
21147
21827
  }
21148
21828
  }
@@ -21151,22 +21831,22 @@ function carryLegacyContents(gateDir, verityDir) {
21151
21831
  return copied;
21152
21832
  }
21153
21833
  async function needsMigration(root = repoRoot()) {
21154
- const gateDir = (0, import_node_path25.join)(root, ".gate");
21155
- const verityDir = (0, import_node_path25.join)(root, ".verity");
21156
- if ((0, import_node_fs33.existsSync)(gateDir) && !(0, import_node_fs33.existsSync)(verityDir)) return true;
21157
- if ((0, import_node_fs33.existsSync)(gateDir) && (0, import_node_fs33.existsSync)(verityDir)) {
21158
- 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"))) {
21834
+ const gateDir = (0, import_node_path28.join)(root, ".gate");
21835
+ const verityDir = (0, import_node_path28.join)(root, ".verity");
21836
+ if ((0, import_node_fs36.existsSync)(gateDir) && !(0, import_node_fs36.existsSync)(verityDir)) return true;
21837
+ if ((0, import_node_fs36.existsSync)(gateDir) && (0, import_node_fs36.existsSync)(verityDir)) {
21838
+ if ((0, import_node_fs36.existsSync)((0, import_node_path28.join)(gateDir, "credentials")) && !(0, import_node_fs36.existsSync)((0, import_node_path28.join)(verityDir, "credentials"))) {
21159
21839
  return true;
21160
21840
  }
21161
- 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"))) {
21841
+ if ((0, import_node_fs36.existsSync)((0, import_node_path28.join)(gateDir, "memory")) && !(0, import_node_fs36.existsSync)((0, import_node_path28.join)(verityDir, "memory"))) {
21162
21842
  return true;
21163
21843
  }
21164
21844
  }
21165
- const claudeMd = (0, import_node_path25.join)(root, "CLAUDE.md");
21166
- if ((0, import_node_fs33.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21845
+ const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
21846
+ if ((0, import_node_fs36.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
21167
21847
  return true;
21168
21848
  }
21169
- 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"))) {
21849
+ if ((0, import_node_fs36.existsSync)((0, import_node_path28.join)(root, "GATE.md")) && !(0, import_node_fs36.existsSync)((0, import_node_path28.join)(root, "VERITY.md"))) {
21170
21850
  return true;
21171
21851
  }
21172
21852
  if (await hasLegacyHooksAt(root)) return true;
@@ -21248,6 +21928,8 @@ async function confirmExistingLogin(serviceUrl, remote, opts) {
21248
21928
  async function runOptionalAuth(resolution, opts = {}) {
21249
21929
  if (resolution.source === "default") {
21250
21930
  printInfo(`No Verity service configured on this machine \u2014 using the default: ${resolution.url}`);
21931
+ } else {
21932
+ printInfo(`Authenticating against ${resolution.url} (source: ${resolution.source}).`);
21251
21933
  }
21252
21934
  const heal = await maybeHealServiceUrl(resolution, opts.verbose);
21253
21935
  const serviceUrl = heal.serviceUrl;
@@ -21257,7 +21939,7 @@ async function runOptionalAuth(resolution, opts = {}) {
21257
21939
  }
21258
21940
  let remote = "";
21259
21941
  try {
21260
- remote = (0, import_node_child_process10.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
21942
+ remote = (0, import_node_child_process11.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
21261
21943
  } catch {
21262
21944
  }
21263
21945
  if (!healed) {
@@ -21300,15 +21982,15 @@ async function runOptionalAuth(resolution, opts = {}) {
21300
21982
  }
21301
21983
  function resolveDataDir() {
21302
21984
  const candidates = [
21303
- (0, import_node_path26.join)(__dirname, "..", "data"),
21985
+ (0, import_node_path29.join)(__dirname, "..", "data"),
21304
21986
  // installed: node_modules/@codacy/verity-cli/data
21305
- (0, import_node_path26.join)(__dirname, "..", "..", "data"),
21987
+ (0, import_node_path29.join)(__dirname, "..", "..", "data"),
21306
21988
  // edge case: nested resolution
21307
- (0, import_node_path26.join)(process.cwd(), "cli", "data")
21989
+ (0, import_node_path29.join)(process.cwd(), "cli", "data")
21308
21990
  // local dev: running from repo root
21309
21991
  ];
21310
21992
  for (const candidate of candidates) {
21311
- if ((0, import_node_fs34.existsSync)((0, import_node_path26.join)(candidate, "skills"))) {
21993
+ if ((0, import_node_fs37.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
21312
21994
  return candidate;
21313
21995
  }
21314
21996
  }
@@ -21324,7 +22006,7 @@ function registerInitCommand(program2) {
21324
22006
  program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
21325
22007
  const force = opts.force ?? false;
21326
22008
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
21327
- const isProject = projectMarkers.some((m) => (0, import_node_fs34.existsSync)(m));
22009
+ const isProject = projectMarkers.some((m) => (0, import_node_fs37.existsSync)(m));
21328
22010
  if (!isProject) {
21329
22011
  printError("No project detected in the current directory.");
21330
22012
  printInfo('Run "verity init" from your project root.');
@@ -21352,30 +22034,30 @@ function registerInitCommand(program2) {
21352
22034
  }
21353
22035
  printInfo(` Node.js ${nodeVersion} \u2713`);
21354
22036
  try {
21355
- const gitVersion = (0, import_node_child_process10.execSync)("git --version", { encoding: "utf-8" }).trim();
22037
+ const gitVersion = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8" }).trim();
21356
22038
  printInfo(` ${gitVersion} \u2713`);
21357
22039
  } catch {
21358
22040
  printError("git is required but not installed. Install from https://git-scm.com");
21359
22041
  process.exit(1);
21360
22042
  }
21361
22043
  try {
21362
- (0, import_node_child_process10.execSync)("which claude", { encoding: "utf-8" });
22044
+ (0, import_node_child_process11.execSync)("which claude", { encoding: "utf-8" });
21363
22045
  printInfo(" Claude Code \u2713");
21364
22046
  } catch {
21365
22047
  printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
21366
22048
  }
21367
22049
  try {
21368
- (0, import_node_child_process10.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
22050
+ (0, import_node_child_process11.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
21369
22051
  printInfo(" @codacy/analysis-cli \u2713");
21370
22052
  } catch {
21371
22053
  printInfo(" Installing @codacy/analysis-cli...");
21372
22054
  try {
21373
- (0, import_node_child_process10.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
22055
+ (0, import_node_child_process11.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
21374
22056
  printInfo(" @codacy/analysis-cli installed \u2713");
21375
22057
  } catch {
21376
22058
  try {
21377
22059
  printWarn(" Retrying with sudo...");
21378
- (0, import_node_child_process10.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
22060
+ (0, import_node_child_process11.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
21379
22061
  printInfo(" @codacy/analysis-cli installed \u2713");
21380
22062
  } catch {
21381
22063
  printWarn(" Could not install @codacy/analysis-cli automatically.");
@@ -21387,21 +22069,21 @@ function registerInitCommand(program2) {
21387
22069
  console.log("");
21388
22070
  printInfo("Installing skills...");
21389
22071
  const dataDir = resolveDataDir();
21390
- const skillsSource = (0, import_node_path26.join)(dataDir, "skills");
22072
+ const skillsSource = (0, import_node_path29.join)(dataDir, "skills");
21391
22073
  const skillsDest = ".claude/skills";
21392
22074
  const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
21393
22075
  let skillsInstalled = 0;
21394
22076
  for (const skill of skills) {
21395
- const src = (0, import_node_path26.join)(skillsSource, skill);
21396
- const dest = (0, import_node_path26.join)(skillsDest, skill);
21397
- if (!(0, import_node_fs34.existsSync)(src)) {
22077
+ const src = (0, import_node_path29.join)(skillsSource, skill);
22078
+ const dest = (0, import_node_path29.join)(skillsDest, skill);
22079
+ if (!(0, import_node_fs37.existsSync)(src)) {
21398
22080
  printWarn(` Skill data not found: ${skill}`);
21399
22081
  continue;
21400
22082
  }
21401
- if ((0, import_node_fs34.existsSync)(dest) && !force) {
21402
- const srcSkill = (0, import_node_path26.join)(src, "SKILL.md");
21403
- const destSkill = (0, import_node_path26.join)(dest, "SKILL.md");
21404
- if ((0, import_node_fs34.existsSync)(destSkill)) {
22083
+ if ((0, import_node_fs37.existsSync)(dest) && !force) {
22084
+ const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
22085
+ const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
22086
+ if ((0, import_node_fs37.existsSync)(destSkill)) {
21405
22087
  try {
21406
22088
  const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
21407
22089
  const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
@@ -21432,13 +22114,19 @@ function registerInitCommand(program2) {
21432
22114
  }
21433
22115
  await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
21434
22116
  await ensureMemoryDir();
22117
+ const ignoreResult = ensureSnapshotGitignored();
22118
+ if (ignoreResult === "failed") {
22119
+ printWarn(" .gitignore: could not add .verity/.snapshot/ \u2014 add it manually (it holds copies of analyzed files)");
22120
+ } else {
22121
+ printInfo(` .gitignore: .verity/.snapshot/ ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
22122
+ }
21435
22123
  try {
21436
22124
  await ensureClaudeMdPointer();
21437
22125
  printInfo(" CLAUDE.md memory pointer \u2713");
21438
22126
  } catch (err) {
21439
22127
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
21440
22128
  }
21441
- const globalVerityDir = (0, import_node_path26.join)(process.env.HOME ?? "", ".verity");
22129
+ const globalVerityDir = (0, import_node_path29.join)(process.env.HOME ?? "", ".verity");
21442
22130
  await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
21443
22131
  console.log("");
21444
22132
  try {
@@ -21473,8 +22161,8 @@ function registerInitCommand(program2) {
21473
22161
  }
21474
22162
 
21475
22163
  // src/commands/uninstall.ts
21476
- var import_node_fs35 = require("node:fs");
21477
- var import_node_path27 = require("node:path");
22164
+ var import_node_fs38 = require("node:fs");
22165
+ var import_node_path30 = require("node:path");
21478
22166
  var SKILL_NAMES = [
21479
22167
  "verity-setup",
21480
22168
  "verity-analyze",
@@ -21493,11 +22181,11 @@ function registerUninstallCommand(program2) {
21493
22181
  const actions = [];
21494
22182
  const skillsRoot = projectPath(".claude/skills");
21495
22183
  for (const name of SKILL_NAMES) {
21496
- const dir = (0, import_node_path27.join)(skillsRoot, name);
21497
- if ((0, import_node_fs35.existsSync)(dir)) {
22184
+ const dir = (0, import_node_path30.join)(skillsRoot, name);
22185
+ if ((0, import_node_fs38.existsSync)(dir)) {
21498
22186
  actions.push({
21499
22187
  label: `Remove .claude/skills/${name}/`,
21500
- apply: () => (0, import_node_fs35.rmSync)(dir, { recursive: true, force: true })
22188
+ apply: () => (0, import_node_fs38.rmSync)(dir, { recursive: true, force: true })
21501
22189
  });
21502
22190
  }
21503
22191
  }
@@ -21511,24 +22199,24 @@ function registerUninstallCommand(program2) {
21511
22199
  });
21512
22200
  }
21513
22201
  const verityDir = projectPath(VERITY_DIR);
21514
- if ((0, import_node_fs35.existsSync)(verityDir)) {
22202
+ if ((0, import_node_fs38.existsSync)(verityDir)) {
21515
22203
  actions.push({
21516
22204
  label: `Remove ${VERITY_DIR}/`,
21517
- apply: () => (0, import_node_fs35.rmSync)(verityDir, { recursive: true, force: true })
22205
+ apply: () => (0, import_node_fs38.rmSync)(verityDir, { recursive: true, force: true })
21518
22206
  });
21519
22207
  }
21520
22208
  if (!keepVerityMd) {
21521
22209
  const verityMd = projectPath(VERITY_MD_FILE);
21522
- if ((0, import_node_fs35.existsSync)(verityMd)) {
22210
+ if ((0, import_node_fs38.existsSync)(verityMd)) {
21523
22211
  actions.push({
21524
22212
  label: `Remove ${VERITY_MD_FILE}`,
21525
- apply: () => (0, import_node_fs35.rmSync)(verityMd, { force: true })
22213
+ apply: () => (0, import_node_fs38.rmSync)(verityMd, { force: true })
21526
22214
  });
21527
22215
  }
21528
22216
  }
21529
22217
  const cleanupEmptyDir = (path) => {
21530
- if ((0, import_node_fs35.existsSync)(path) && (0, import_node_fs35.statSync)(path).isDirectory() && (0, import_node_fs35.readdirSync)(path).length === 0) {
21531
- (0, import_node_fs35.rmdirSync)(path);
22218
+ if ((0, import_node_fs38.existsSync)(path) && (0, import_node_fs38.statSync)(path).isDirectory() && (0, import_node_fs38.readdirSync)(path).length === 0) {
22219
+ (0, import_node_fs38.rmdirSync)(path);
21532
22220
  }
21533
22221
  };
21534
22222
  actions.push({
@@ -21539,11 +22227,11 @@ function registerUninstallCommand(program2) {
21539
22227
  }
21540
22228
  });
21541
22229
  const home = process.env.HOME ?? "";
21542
- const globalVerityDir = (0, import_node_path27.join)(home, ".verity");
21543
- if (purgeGlobal && (0, import_node_fs35.existsSync)(globalVerityDir)) {
22230
+ const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
22231
+ if (purgeGlobal && (0, import_node_fs38.existsSync)(globalVerityDir)) {
21544
22232
  actions.push({
21545
22233
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
21546
- apply: () => (0, import_node_fs35.rmSync)(globalVerityDir, { recursive: true, force: true })
22234
+ apply: () => (0, import_node_fs38.rmSync)(globalVerityDir, { recursive: true, force: true })
21547
22235
  });
21548
22236
  }
21549
22237
  if (actions.length === 0) {
@@ -21737,8 +22425,8 @@ function registerTaskCommands(program2) {
21737
22425
  }
21738
22426
 
21739
22427
  // src/commands/reset.ts
21740
- var import_node_fs36 = require("node:fs");
21741
- var import_node_path28 = require("node:path");
22428
+ var import_node_fs39 = require("node:fs");
22429
+ var import_node_path31 = require("node:path");
21742
22430
  function registerResetCommand(program2) {
21743
22431
  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) => {
21744
22432
  const globals = program2.opts();
@@ -21775,11 +22463,11 @@ function registerResetCommand(program2) {
21775
22463
  }
21776
22464
  const cacheDir = projectPath(CACHE_DIR);
21777
22465
  let purged = 0;
21778
- if ((0, import_node_fs36.existsSync)(cacheDir)) {
21779
- for (const entry of (0, import_node_fs36.readdirSync)(cacheDir)) {
22466
+ if ((0, import_node_fs39.existsSync)(cacheDir)) {
22467
+ for (const entry of (0, import_node_fs39.readdirSync)(cacheDir)) {
21780
22468
  if (entry.startsWith("pending-")) {
21781
22469
  try {
21782
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(cacheDir, entry));
22470
+ (0, import_node_fs39.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
21783
22471
  purged++;
21784
22472
  } catch {
21785
22473
  }
@@ -21794,19 +22482,19 @@ function registerResetCommand(program2) {
21794
22482
  projectPath(`${VERITY_DIR}/.last-analysis`)
21795
22483
  ];
21796
22484
  for (const file of filesToClear) {
21797
- if ((0, import_node_fs36.existsSync)(file)) {
22485
+ if ((0, import_node_fs39.existsSync)(file)) {
21798
22486
  try {
21799
- (0, import_node_fs36.writeFileSync)(file, "");
22487
+ (0, import_node_fs39.writeFileSync)(file, "");
21800
22488
  } catch {
21801
22489
  }
21802
22490
  }
21803
22491
  }
21804
22492
  if (opts.all) {
21805
22493
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
21806
- if ((0, import_node_fs36.existsSync)(logsDir)) {
21807
- for (const entry of (0, import_node_fs36.readdirSync)(logsDir)) {
22494
+ if ((0, import_node_fs39.existsSync)(logsDir)) {
22495
+ for (const entry of (0, import_node_fs39.readdirSync)(logsDir)) {
21808
22496
  try {
21809
- (0, import_node_fs36.unlinkSync)((0, import_node_path28.join)(logsDir, entry));
22497
+ (0, import_node_fs39.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
21810
22498
  } catch {
21811
22499
  }
21812
22500
  }
@@ -22114,8 +22802,8 @@ function registerTelemetryCommands(program2) {
22114
22802
  }
22115
22803
 
22116
22804
  // src/cli.ts
22117
- program.name("verity").description("CLI for Verity quality gate service").version("0.29.4-experimental.f2e812c").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) => {
22118
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.29.4-experimental.f2e812c");
22805
+ program.name("verity").description("CLI for Verity quality gate service").version("0.30.0-experimental.43e7755").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) => {
22806
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.30.0-experimental.43e7755");
22119
22807
  setUserNamedServiceUrl(program.opts().serviceUrl);
22120
22808
  try {
22121
22809
  await foldLegacyLocalCredential();