@codacy/verity-cli 0.31.0 → 0.31.1-experimental.68fca47

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
@@ -10395,6 +10395,7 @@ var MAX_DELTA_BYTES = 194560;
10395
10395
  var MAX_FILES = 40;
10396
10396
  var MAX_FILE_BYTES = 51200;
10397
10397
  var DEBOUNCE_SECONDS = 30;
10398
+ var MAX_ITERATIONS = 2;
10398
10399
  var MAX_SPEC_FILES = 6;
10399
10400
  var MAX_SPEC_FILE_BYTES = 512e3;
10400
10401
  var MAX_TOTAL_SPEC_BYTES = 512e3;
@@ -10506,7 +10507,7 @@ var SECURITY_PATTERNS = [
10506
10507
  /Dockerfile/
10507
10508
  ];
10508
10509
  var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10509
- var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
10510
+ var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
10510
10511
  var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
10511
10512
  var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
10512
10513
  var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
@@ -10519,13 +10520,20 @@ var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
10519
10520
  var IGNORE_DECLARATION_FILE = `${VERITY_DIR}/.ignore-declaration`;
10520
10521
 
10521
10522
  // src/lib/output.ts
10522
- var RED = "\x1B[0;31m";
10523
- var YELLOW = "\x1B[1;33m";
10524
- var GREEN = "\x1B[0;32m";
10525
- var CYAN = "\x1B[0;36m";
10526
- var BOLD = "\x1B[1m";
10527
- var DIM = "\x1B[2m";
10528
- var NC = "\x1B[0m";
10523
+ function colorEnabled() {
10524
+ if (process.env.FORCE_COLOR) return true;
10525
+ if (process.env.NO_COLOR !== void 0) return false;
10526
+ if (process.env.TERM === "dumb") return false;
10527
+ return !!process.stderr.isTTY;
10528
+ }
10529
+ var COLOR = colorEnabled();
10530
+ var RED = COLOR ? "\x1B[0;31m" : "";
10531
+ var YELLOW = COLOR ? "\x1B[1;33m" : "";
10532
+ var GREEN = COLOR ? "\x1B[0;32m" : "";
10533
+ var CYAN = COLOR ? "\x1B[0;36m" : "";
10534
+ var BOLD = COLOR ? "\x1B[1m" : "";
10535
+ var DIM = COLOR ? "\x1B[2m" : "";
10536
+ var NC = COLOR ? "\x1B[0m" : "";
10529
10537
  function printJson(data) {
10530
10538
  process.stdout.write(JSON.stringify(data, null, 2) + "\n");
10531
10539
  }
@@ -11252,7 +11260,7 @@ async function resolveServiceUrlDetailed(flagUrl, opts = {}) {
11252
11260
  }
11253
11261
  return {
11254
11262
  ok: false,
11255
- error: 'No Verity service URL found. Run "verity login" to get started, or /verity-setup to configure this project.'
11263
+ error: 'No Verity service URL found. Run "verity login" to get started, or "verity init" to set up this project.'
11256
11264
  };
11257
11265
  }
11258
11266
  async function resolveServiceUrlForAuth(flagUrl) {
@@ -11299,7 +11307,7 @@ async function resolveToken(flagToken) {
11299
11307
  }
11300
11308
  return {
11301
11309
  ok: false,
11302
- error: 'No Verity token found. Run "verity login" to sign in, or /verity-setup to set up this project.'
11310
+ error: 'No Verity token found. Run "verity login" to sign in, or "verity init" to set up this project.'
11303
11311
  };
11304
11312
  }
11305
11313
  async function whoami(token, serviceUrl, verbose) {
@@ -11393,6 +11401,91 @@ async function maybeHealServiceUrl(resolution, verbose) {
11393
11401
  var readline = __toESM(require("node:readline/promises"));
11394
11402
  var import_node_os = require("node:os");
11395
11403
 
11404
+ // src/lib/spinner.ts
11405
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
11406
+ var FRAME_MS = 80;
11407
+ var DIM2 = "\x1B[2m";
11408
+ var GREEN2 = "\x1B[0;32m";
11409
+ var YELLOW2 = "\x1B[1;33m";
11410
+ var RESET = "\x1B[0m";
11411
+ function secondsSince(start) {
11412
+ return `${Math.round((Date.now() - start) / 1e3)}s`;
11413
+ }
11414
+ function startSpinner(label2, opts = {}) {
11415
+ const stream = process.stderr;
11416
+ const color = colorEnabled();
11417
+ const animate = opts.animate ?? !!stream.isTTY;
11418
+ const showElapsed = opts.elapsed ?? true;
11419
+ const start = Date.now();
11420
+ let current = label2;
11421
+ let frame = 0;
11422
+ let timer = null;
11423
+ let done = false;
11424
+ const fit = (label3, clock, columns) => {
11425
+ const budget = columns - 5;
11426
+ if (budget <= 0) return { label: "", clock: "" };
11427
+ if (label3.length + clock.length <= budget) return { label: label3, clock };
11428
+ if (clock.length >= budget) return { label: "", clock: clock.slice(0, budget) };
11429
+ const room = budget - clock.length;
11430
+ return { label: room <= 1 ? label3.slice(0, room) : `${label3.slice(0, room - 1)}\u2026`, clock };
11431
+ };
11432
+ const paint = () => {
11433
+ const glyph = FRAMES[frame++ % FRAMES.length];
11434
+ const columns = stream.columns ?? 80;
11435
+ const { label: label3, clock } = fit(current, showElapsed ? ` ${secondsSince(start)}` : "", columns);
11436
+ const line = color ? ` ${GREEN2}${glyph}${RESET} ${label3}${DIM2}${clock}${RESET}` : ` ${glyph} ${label3}${clock}`;
11437
+ stream.write(`\r\x1B[2K${line}`);
11438
+ };
11439
+ const clearLine = () => {
11440
+ if (animate) stream.write("\r\x1B[2K");
11441
+ };
11442
+ if (animate) {
11443
+ paint();
11444
+ timer = setInterval(paint, FRAME_MS);
11445
+ timer.unref?.();
11446
+ } else {
11447
+ stream.write(` ${current}\u2026
11448
+ `);
11449
+ }
11450
+ const finish = (render2) => {
11451
+ if (done) return;
11452
+ done = true;
11453
+ if (timer) clearInterval(timer);
11454
+ clearLine();
11455
+ render2();
11456
+ };
11457
+ return {
11458
+ update(next) {
11459
+ current = next;
11460
+ if (animate) paint();
11461
+ else stream.write(` ${next}\u2026
11462
+ `);
11463
+ },
11464
+ succeed(message) {
11465
+ finish(() => {
11466
+ const text = message ?? current;
11467
+ const clock = showElapsed ? ` (${secondsSince(start)})` : "";
11468
+ stream.write(
11469
+ color ? ` ${GREEN2}\u2713${RESET} ${text}${DIM2}${clock}${RESET}
11470
+ ` : ` \u2713 ${text}${clock}
11471
+ `
11472
+ );
11473
+ });
11474
+ },
11475
+ warn(message) {
11476
+ finish(() => {
11477
+ stream.write(color ? ` ${YELLOW2}\u26A0${RESET} ${message}
11478
+ ` : ` \u26A0 ${message}
11479
+ `);
11480
+ });
11481
+ },
11482
+ stop() {
11483
+ finish(() => {
11484
+ });
11485
+ }
11486
+ };
11487
+ }
11488
+
11396
11489
  // src/lib/provider-auth.ts
11397
11490
  var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
11398
11491
  var form = (fields) => new URLSearchParams(fields).toString();
@@ -11459,42 +11552,49 @@ async function githubDeviceFlow() {
11459
11552
  printInfo("");
11460
11553
  printInfo(`To authorize Verity, open: ${dc.verification_uri}`);
11461
11554
  printInfo(`And enter the code: ${dc.user_code}`);
11462
- printInfo("Waiting for authorization\u2026");
11555
+ const spinner = startSpinner(`Waiting for you to approve in the browser \xB7 code ${dc.user_code}`);
11463
11556
  const deadline = Date.now() + (dc.expires_in || 900) * 1e3;
11464
11557
  let interval = dc.interval || 5;
11465
- while (Date.now() < deadline) {
11466
- await sleep(interval * 1e3);
11467
- let data;
11468
- try {
11469
- const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
11470
- method: "POST",
11471
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11472
- body: form({
11473
- client_id: GITHUB_CLIENT_ID,
11474
- device_code: dc.device_code,
11475
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
11476
- })
11477
- });
11478
- data = await res.json().catch(() => ({}));
11479
- } catch {
11480
- continue;
11481
- }
11482
- if (data.access_token) return { ok: true, data: data.access_token };
11483
- switch (data.error) {
11484
- case "authorization_pending":
11485
- break;
11486
- case "slow_down":
11487
- interval += 5;
11488
- break;
11489
- case "access_denied":
11490
- return { ok: false, error: "Authorization was denied on GitHub." };
11491
- case "expired_token":
11492
- return { ok: false, error: "The authorization code expired. Re-run register." };
11493
- default:
11494
- if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
11558
+ try {
11559
+ while (Date.now() < deadline) {
11560
+ await sleep(interval * 1e3);
11561
+ let data;
11562
+ try {
11563
+ const res = await fetch(GITHUB_ACCESS_TOKEN_URL, {
11564
+ method: "POST",
11565
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
11566
+ body: form({
11567
+ client_id: GITHUB_CLIENT_ID,
11568
+ device_code: dc.device_code,
11569
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
11570
+ })
11571
+ });
11572
+ data = await res.json().catch(() => ({}));
11573
+ } catch {
11574
+ continue;
11575
+ }
11576
+ if (data.access_token) {
11577
+ spinner.succeed("Authorized on GitHub");
11578
+ return { ok: true, data: data.access_token };
11579
+ }
11580
+ switch (data.error) {
11581
+ case "authorization_pending":
11582
+ break;
11583
+ case "slow_down":
11584
+ interval += 5;
11585
+ break;
11586
+ case "access_denied":
11587
+ return { ok: false, error: "Authorization was denied on GitHub." };
11588
+ case "expired_token":
11589
+ return { ok: false, error: "The authorization code expired. Re-run register." };
11590
+ default:
11591
+ if (data.error) return { ok: false, error: `GitHub auth error: ${data.error}` };
11592
+ }
11495
11593
  }
11594
+ return { ok: false, error: "Timed out waiting for GitHub authorization." };
11595
+ } finally {
11596
+ spinner.stop();
11496
11597
  }
11497
- return { ok: false, error: "Timed out waiting for GitHub authorization." };
11498
11598
  }
11499
11599
 
11500
11600
  // src/lib/register.ts
@@ -12263,12 +12363,20 @@ var VERITY_STOP_RE = /(?:^|[\/\s"'])verity\s+analyze\b/;
12263
12363
  var VERITY_INTENT_RE = /(?:^|[\/\s"'])verity\s+intent\s+capture\b/;
12264
12364
  var VERITY_GUARD_RE = /(?:^|[\/\s"'])verity\s+guard\b/;
12265
12365
  var VERITY_BASELINE_RE = /(?:^|[\/\s"'])verity\s+baseline\s+capture\b/;
12366
+ var VERITY_COMPACT_RE = /(?:^|[\/\s"'])verity\s+compact\b/;
12367
+ var VERITY_SESSION_END_RE = /(?:^|[\/\s"'])verity\s+session\s+end\b/;
12266
12368
  function isVerityGuardHook(entry) {
12267
12369
  return VERITY_GUARD_RE.test(entry.command ?? "");
12268
12370
  }
12269
12371
  function isVerityBaselineHook(entry) {
12270
12372
  return VERITY_BASELINE_RE.test(entry.command ?? "");
12271
12373
  }
12374
+ function isVerityCompactHook(entry) {
12375
+ return VERITY_COMPACT_RE.test(entry.command ?? "");
12376
+ }
12377
+ function isVeritySessionEndHook(entry) {
12378
+ return VERITY_SESSION_END_RE.test(entry.command ?? "");
12379
+ }
12272
12380
  var LEGACY_STOP_RE = /(?:^|[\/\s"'])gate\s+analyze\b/;
12273
12381
  var LEGACY_INTENT_RE = /(?:^|[\/\s"'])gate\s+intent\s+capture\b/;
12274
12382
  function isVerityStopHook(entry) {
@@ -12280,7 +12388,7 @@ function isVerityIntentHook(entry) {
12280
12388
  return VERITY_INTENT_RE.test(c) || LEGACY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh") || c.includes(".gate/hooks/capture-intent.sh");
12281
12389
  }
12282
12390
  function isVerityHook(entry) {
12283
- return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
12391
+ return isVerityStopHook(entry) || isVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry) || isVerityCompactHook(entry) || isVeritySessionEndHook(entry);
12284
12392
  }
12285
12393
  function isCurrentVerityStopHook(entry) {
12286
12394
  const c = entry.command ?? "";
@@ -12291,7 +12399,7 @@ function isCurrentVerityIntentHook(entry) {
12291
12399
  return VERITY_INTENT_RE.test(c) || c.includes(".verity/hooks/capture-intent.sh");
12292
12400
  }
12293
12401
  function isCurrentVerityHook(entry) {
12294
- return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry);
12402
+ return isCurrentVerityStopHook(entry) || isCurrentVerityIntentHook(entry) || isVerityGuardHook(entry) || isVerityBaselineHook(entry) || isVerityCompactHook(entry) || isVeritySessionEndHook(entry);
12295
12403
  }
12296
12404
  function settingsHasLegacyHook(settings) {
12297
12405
  for (const groups of Object.values(settings.hooks ?? {})) {
@@ -12353,6 +12461,8 @@ async function checkAllVerityHooks() {
12353
12461
  async function checkExternalVerityHooks() {
12354
12462
  let stop = false;
12355
12463
  let intent = false;
12464
+ let compact = false;
12465
+ let sessionEnd = false;
12356
12466
  let guardOn = [];
12357
12467
  for (const f of [SETTINGS_LOCAL_FILE, globalSettingsFile()]) {
12358
12468
  let settings;
@@ -12364,9 +12474,11 @@ async function checkExternalVerityHooks() {
12364
12474
  const r = checkVerityHooks(settings);
12365
12475
  stop = stop || r.stop;
12366
12476
  intent = intent || r.intent;
12477
+ compact = compact || r.compact;
12478
+ sessionEnd = sessionEnd || r.sessionEnd;
12367
12479
  if (r.guardOn.length > guardOn.length) guardOn = r.guardOn;
12368
12480
  }
12369
- return { stop, intent, guardOn };
12481
+ return { stop, intent, compact, sessionEnd, guardOn };
12370
12482
  }
12371
12483
  async function checkAllVerityHooksDetailed() {
12372
12484
  let stop = false;
@@ -12446,11 +12558,11 @@ function checkVerityHooks(settings) {
12446
12558
  }
12447
12559
  const compactGroups = hooks["PostCompact"] ?? [];
12448
12560
  const hasCompact = compactGroups.some(
12449
- (g) => g.hooks?.some((h) => h.command?.trim() === "verity compact")
12561
+ (g) => g.hooks?.some((h) => isVerityCompactHook(h))
12450
12562
  );
12451
12563
  const endGroups = hooks["SessionEnd"] ?? [];
12452
12564
  const hasSessionEnd = endGroups.some(
12453
- (g) => g.hooks?.some((h) => h.command?.trim() === "verity session end")
12565
+ (g) => g.hooks?.some((h) => isVeritySessionEndHook(h))
12454
12566
  );
12455
12567
  return {
12456
12568
  stop: hasStop,
@@ -12564,6 +12676,8 @@ function reconcileMomentHooks(settings, moments, externalPresent = {
12564
12676
  };
12565
12677
  if (!externalPresent.intent) push("UserPromptSubmit", { hooks: [VERITY_INTENT_HOOK] });
12566
12678
  push("SessionStart", { hooks: [VERITY_BASELINE_HOOK] });
12679
+ if (!externalPresent.compact) push("PostCompact", { hooks: [VERITY_COMPACT_HOOK] });
12680
+ if (!externalPresent.sessionEnd) push("SessionEnd", { hooks: [VERITY_SESSION_END_HOOK] });
12567
12681
  if (moments.includes("stop") && !externalPresent.stop) {
12568
12682
  push("Stop", { hooks: [VERITY_STOP_HOOK] });
12569
12683
  }
@@ -13540,6 +13654,74 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
13540
13654
  var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
13541
13655
  var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
13542
13656
  var CLAUDE_MD_PROSE = [
13657
+ "## Project Memory",
13658
+ "",
13659
+ "This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
13660
+ "non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
13661
+ "that may apply to the change you are about to make. Open specific node files via",
13662
+ "the Read tool when the title or scope suggests relevance.",
13663
+ "",
13664
+ "The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
13665
+ "superseded \u2014 ignore them unless investigating history.",
13666
+ "",
13667
+ "## Quality gate: accepted risks",
13668
+ "",
13669
+ "When the Verity pre-commit/pre-push gate FAILs, fix the findings \u2014 that is the",
13670
+ 'default. Use `verity waive <pattern-id> --file <path> --reason "\u2026"` ONLY to relay',
13671
+ "a risk a human has explicitly accepted: a named code-review finding, an ADR, or",
13672
+ "the user saying so in this conversation. The --reason must cite that source.",
13673
+ "",
13674
+ "Never waive on your own judgment, to get past a block, or pre-emptively. A waive",
13675
+ "binds to the file's current bytes and voids automatically when the file changes,",
13676
+ "and every waive is recorded in the run ledger. For a pattern-level false positive",
13677
+ "use `verity feedback finding <run-id> <pattern-id> false_positive` instead.",
13678
+ "",
13679
+ "## Post-task reflection",
13680
+ "",
13681
+ `When a task is complete (you've created a PR, the user says "done" or "ship it",`,
13682
+ "or the work is clearly finished), **draft the reflection yourself first** \u2014 1\u20133",
13683
+ "concrete things worth remembering (a decision, a gotcha, or a pattern), each",
13684
+ "cited with the files / PR / commands / error-signatures it came from. Skip",
13685
+ "entirely if nothing non-obvious happened.",
13686
+ "",
13687
+ "Then, depending on how the session is running:",
13688
+ "",
13689
+ "- **Interactive** (a person is at the keyboard): show your draft and ask one",
13690
+ ` question before recording \u2014 "Here's my reflection draft for the project`,
13691
+ " memory: <draft>. Anything to confirm, correct, or add before I record it?",
13692
+ ` (Say 'skip' to drop it.)" Record the final text (with their corrections) via`,
13693
+ ' `verity reflect --user-input "<final reflection>" --kind <kind>`. If they say',
13694
+ ' "skip", do not record.',
13695
+ "- **Autonomous** (headless / CI / cron, or `VERITY_AUTONOMOUS=1`): record your",
13696
+ ' draft directly, never blocking \u2014 `verity reflect --user-input "<your draft>"',
13697
+ " --kind <kind> --autonomous`.",
13698
+ "",
13699
+ "> Durable, hand-curated guidance goes in the preserve region below (it survives",
13700
+ "> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
13701
+ "> markers is tool-owned and overwritten on each run.",
13702
+ "",
13703
+ "## Housekeeping Turns",
13704
+ "",
13705
+ "When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
13706
+ "rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
13707
+ "",
13708
+ "```bash",
13709
+ 'verity ignore --turn --agent --reason "pulling latest before starting"',
13710
+ "```",
13711
+ "",
13712
+ "This skips the review for that turn, which saves the turn Verity would",
13713
+ "otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
13714
+ "`--turn` when a single piece of housekeeping spans several turns.",
13715
+ "",
13716
+ "**It is a claim about the turn, not a way to silence review.** The declaration",
13717
+ "is checked against what the turn actually did: if anything is authored \u2014 by you,",
13718
+ "by a subagent, or by a shell command that can write files \u2014 it voids, the review",
13719
+ "runs anyway, and the broken declaration is reported. So declare housekeeping you",
13720
+ "are about to do, never work you have already done, and never as a way to get past",
13721
+ "a finding. Declarations are budgeted per session and every one is recorded with",
13722
+ "its reason."
13723
+ ].join("\n");
13724
+ var CLAUDE_MD_PROSE_PRE_REFLECT = [
13543
13725
  "## Project Memory",
13544
13726
  "",
13545
13727
  "This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
@@ -13750,6 +13932,7 @@ function stripKnownProse(interior) {
13750
13932
  const trimmed = interior.replace(/^\n+/, "");
13751
13933
  for (const prose of [
13752
13934
  CLAUDE_MD_PROSE,
13935
+ CLAUDE_MD_PROSE_PRE_REFLECT,
13753
13936
  CLAUDE_MD_PROSE_PRE_WAIVE,
13754
13937
  CLAUDE_MD_PROSE_PRE_IGNORE,
13755
13938
  CLAUDE_MD_PROSE_LEGACY
@@ -14136,6 +14319,11 @@ var REANCHOR_WINDOW = 20;
14136
14319
  var STATEMENT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
14137
14320
  var DEFAULT_MEMORY_BUDGET_BYTES = 4096;
14138
14321
 
14322
+ // src/lib/dossier/events.ts
14323
+ function statementAnchorKey(file, patternId) {
14324
+ return `${file}::${patternId}`;
14325
+ }
14326
+
14139
14327
  // src/lib/dossier/log.ts
14140
14328
  var import_node_crypto4 = require("node:crypto");
14141
14329
  var import_node_fs10 = require("node:fs");
@@ -14419,6 +14607,7 @@ function reduce(state, events, now) {
14419
14607
  existing.line_sha = ev.line_sha ?? existing.line_sha;
14420
14608
  existing.register = ev.register;
14421
14609
  if (existing.outcome === "fixed") existing.outcome = "recurred";
14610
+ else if (existing.outcome !== "open") existing.outcome = "open";
14422
14611
  break;
14423
14612
  }
14424
14613
  byAnchor.set(ev.anchor_key, {
@@ -15349,10 +15538,10 @@ function recordVerdict(d, v) {
15349
15538
  const at = src?.[f.line - 1];
15350
15539
  appendEvent(d, {
15351
15540
  k: "statement",
15352
- // Stable across title and line drift 374 of 378 recurring sites (98.9%)
15353
- // carry title drift, so a title-inclusive key fragments the recurrence it
15354
- // exists to detect.
15355
- anchor_key: `${f.file}::${f.pattern_id}`,
15541
+ // ONE OWNER. The server's settled feed is resolved against this exact
15542
+ // key in `13-reconcile.ts`; spelling the template literal twice is how the
15543
+ // two sides drift apart. See `statementAnchorKey`.
15544
+ anchor_key: statementAnchorKey(f.file, f.pattern_id),
15356
15545
  file: f.file,
15357
15546
  line: f.line,
15358
15547
  pattern_id: f.pattern_id,
@@ -15789,33 +15978,6 @@ ${addedLines}`,
15789
15978
  }
15790
15979
  return { diffs, has_snapshots: true };
15791
15980
  }
15792
- function ensureSnapshotGitignored() {
15793
- let content = "";
15794
- try {
15795
- content = (0, import_node_fs16.readFileSync)(".gitignore", "utf-8");
15796
- } catch {
15797
- }
15798
- let ignored = null;
15799
- try {
15800
- (0, import_node_child_process6.execSync)("git check-ignore -q -- .verity/.snapshot/__probe__", { stdio: "pipe" });
15801
- ignored = true;
15802
- } catch (err) {
15803
- ignored = err.status === 1 ? false : null;
15804
- }
15805
- if (ignored === true) return "covered";
15806
- if (ignored === null) {
15807
- const lines = content.split("\n").map((l) => l.trim());
15808
- const covering = [".verity/.snapshot/", ".verity/.snapshot", ".verity/", ".verity", ".verity/*"];
15809
- if (lines.some((l) => covering.includes(l))) return "covered";
15810
- }
15811
- try {
15812
- const block = "# Verity \u2014 snapshots of analyzed files (machine state, never commit)\n.verity/.snapshot/\n";
15813
- (0, import_node_fs16.writeFileSync)(".gitignore", content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block);
15814
- return "added";
15815
- } catch {
15816
- return "failed";
15817
- }
15818
- }
15819
15981
  function saveSnapshots(files) {
15820
15982
  const snapshotPaths = /* @__PURE__ */ new Set();
15821
15983
  for (const file of files) {
@@ -16737,19 +16899,19 @@ function loc(f) {
16737
16899
  if (!f.file) return "";
16738
16900
  return f.line != null ? `${f.file}:${f.line}` : f.file;
16739
16901
  }
16740
- function formatRunDetail(run) {
16902
+ function formatRunDetail(run2) {
16741
16903
  const lines = [];
16742
- const q = run.assessment?.quality_score;
16743
- const s = run.assessment?.security_score;
16904
+ const q = run2.assessment?.quality_score;
16905
+ const s = run2.assessment?.security_score;
16744
16906
  const qStr = q != null ? `${q}` : "-";
16745
16907
  const sStr = s != null ? `${s}` : "-";
16746
- lines.push(`${run.run_id} ${run.gate_decision} Q ${qStr}/10 S ${sStr}/10`);
16908
+ lines.push(`${run2.run_id} ${run2.gate_decision} Q ${qStr}/10 S ${sStr}/10`);
16747
16909
  const meta = [];
16748
- if (run.trigger) meta.push(`trigger: ${run.trigger}`);
16749
- if (run.standard_version != null) meta.push(`standard v${run.standard_version}`);
16750
- if (run.created_at) meta.push(run.created_at.slice(0, 19).replace("T", " "));
16910
+ if (run2.trigger) meta.push(`trigger: ${run2.trigger}`);
16911
+ if (run2.standard_version != null) meta.push(`standard v${run2.standard_version}`);
16912
+ if (run2.created_at) meta.push(run2.created_at.slice(0, 19).replace("T", " "));
16751
16913
  if (meta.length > 0) lines.push(meta.join(" \xB7 "));
16752
- const findings = run.findings ?? [];
16914
+ const findings = run2.findings ?? [];
16753
16915
  if (findings.length === 0) {
16754
16916
  lines.push("");
16755
16917
  lines.push("No findings \u2014 clean.");
@@ -16766,7 +16928,7 @@ function formatRunDetail(run) {
16766
16928
  if (f.scope === "pre-existing") lines.push(" (pre-existing)");
16767
16929
  }
16768
16930
  }
16769
- const pending = run.pending_items ?? [];
16931
+ const pending = run2.pending_items ?? [];
16770
16932
  if (pending.length > 0) {
16771
16933
  lines.push("");
16772
16934
  lines.push(`PENDING (${pending.length})`);
@@ -16774,9 +16936,9 @@ function formatRunDetail(run) {
16774
16936
  lines.push(` [${(p.priority ?? "").toUpperCase()}] ${p.description}`);
16775
16937
  }
16776
16938
  }
16777
- if (run.assessment?.narrative) {
16939
+ if (run2.assessment?.narrative) {
16778
16940
  lines.push("");
16779
- lines.push(run.assessment.narrative);
16941
+ lines.push(run2.assessment.narrative);
16780
16942
  }
16781
16943
  return lines;
16782
16944
  }
@@ -17148,7 +17310,7 @@ function registerStatusCommand(program2) {
17148
17310
  return;
17149
17311
  }
17150
17312
  if (mem?.configured === false) {
17151
- printInfo("Verity is not configured for this project. Run /verity-setup.");
17313
+ printInfo('Verity is not configured for this project. Run "verity init".');
17152
17314
  return;
17153
17315
  }
17154
17316
  printInfo("=== Verity Status ===");
@@ -17180,7 +17342,7 @@ function registerStatusCommand(program2) {
17180
17342
  if (hookStatus.stop) moments.push("stop");
17181
17343
  if (hookStatus.guardOn.includes("commit")) moments.push("pre-commit");
17182
17344
  if (hookStatus.guardOn.includes("push")) moments.push("pre-push/PR");
17183
- printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : "none (run /verity-setup)"}`);
17345
+ printInfo(`Moments: ${moments.length > 0 ? moments.join(", ") : 'none (run "verity init")'}`);
17184
17346
  if (!mem) return;
17185
17347
  if (mem.recent_runs) {
17186
17348
  const r = mem.recent_runs;
@@ -17229,7 +17391,8 @@ function registerStatusCommand(program2) {
17229
17391
  printInfo("");
17230
17392
  printInfo("--- Pending Items ---");
17231
17393
  for (const item of mem.pending_items) {
17232
- printInfo(` [${item.priority.toUpperCase()}] ${item.description}`);
17394
+ const priority = typeof item.priority === "string" && item.priority.length > 0 ? item.priority.toUpperCase() : "UNSPECIFIED";
17395
+ printInfo(` [${priority}] ${item.description ?? "(no description)"}`);
17233
17396
  }
17234
17397
  }
17235
17398
  const recentTasks = mem.recent_tasks;
@@ -17265,12 +17428,12 @@ function registerStatusCommand(program2) {
17265
17428
  printInfo("");
17266
17429
  printInfo("--- Recent Runs ---");
17267
17430
  printInfo(`${"Run ID".padEnd(32)} ${"Decision".padEnd(10)}${"Q".padEnd(4)}${"S".padEnd(4)}${"Findings".padEnd(32)}Date`);
17268
- for (const run of runsResult.data.runs) {
17269
- const q = run.quality_score != null ? `${run.quality_score}` : "-";
17270
- const s = run.security_score != null ? `${run.security_score}` : "-";
17271
- const findings = formatFindingsSummary(run.findings_count);
17272
- const date = run.created_at.slice(0, 19).replace("T", " ");
17273
- printInfo(`${run.run_id.padEnd(32)} ${run.gate_decision.padEnd(10)}${q.padEnd(4)}${s.padEnd(4)}${findings.padEnd(32)}${date}`);
17431
+ for (const run2 of runsResult.data.runs) {
17432
+ const q = run2.quality_score != null ? `${run2.quality_score}` : "-";
17433
+ const s = run2.security_score != null ? `${run2.security_score}` : "-";
17434
+ const findings = formatFindingsSummary(run2.findings_count);
17435
+ const date = run2.created_at.slice(0, 19).replace("T", " ");
17436
+ printInfo(`${run2.run_id.padEnd(32)} ${run2.gate_decision.padEnd(10)}${q.padEnd(4)}${s.padEnd(4)}${findings.padEnd(32)}${date}`);
17274
17437
  }
17275
17438
  }
17276
17439
  }
@@ -17405,7 +17568,6 @@ function createRun(opts, globals) {
17405
17568
  token: "",
17406
17569
  modeDecision: null,
17407
17570
  sessionIdForMemory: "",
17408
- contextFilePaths: [],
17409
17571
  analysisMode: "standard",
17410
17572
  sessionAuthoredCode: false,
17411
17573
  staticResults: {
@@ -17415,6 +17577,7 @@ function createRun(opts, globals) {
17415
17577
  },
17416
17578
  codeDelta: { files: [], total_lines: 0, total_files: 0, excluded: [] },
17417
17579
  snapshotResult: { has_snapshots: false, diffs: [] },
17580
+ repoContext: null,
17418
17581
  contentHash: null,
17419
17582
  iteration: 1,
17420
17583
  currentCommit: "",
@@ -17506,35 +17669,35 @@ function row(label2, value) {
17506
17669
  return ` \u25B8 ${label2.padEnd(10)} ${value}
17507
17670
  `;
17508
17671
  }
17509
- function formatRunEvidence(run, startedAt) {
17672
+ function formatRunEvidence(run2, startedAt) {
17510
17673
  const ms = Date.now() - startedAt;
17511
- const sent = run.codeDelta.files.filter((f) => f.role !== "context").map((f) => f.path);
17512
- const context = run.codeDelta.files.filter((f) => f.role === "context").map((f) => f.path);
17674
+ const sent = run2.codeDelta.files.filter((f) => f.role !== "context").map((f) => f.path);
17675
+ const context = run2.codeDelta.files.filter((f) => f.role === "context").map((f) => f.path);
17513
17676
  let out = ` \u2500\u2500 what verity saw \u2500\u2500
17514
17677
  `;
17515
- out += row("turn", `${run.turnId || "(unminted)"}${run.sessionId ? ` \xB7 session ${run.sessionId}` : ""}`);
17516
- out += row("reached", `${run.phaseReached || "(none)"}${run.skipReason ? ` \xB7 SKIPPED: ${run.skipReason}` : ""} \xB7 ${ms}ms`);
17517
- if (run.treeFrame) {
17518
- const f = run.treeFrame;
17678
+ out += row("turn", `${run2.turnId || "(unminted)"}${run2.sessionId ? ` \xB7 session ${run2.sessionId}` : ""}`);
17679
+ out += row("reached", `${run2.phaseReached || "(none)"}${run2.skipReason ? ` \xB7 SKIPPED: ${run2.skipReason}` : ""} \xB7 ${ms}ms`);
17680
+ if (run2.treeFrame) {
17681
+ const f = run2.treeFrame;
17519
17682
  out += row("tree", f.worktreeRoot ? `${f.worktreeRoot}${f.isLinkedWorktree ? " \xB7 linked worktree" : ""}${f.branch ? ` \xB7 branch ${f.branch}` : " \xB7 detached"}` : `(unresolved: ${f.refusal ?? "unknown"})`);
17520
17683
  }
17521
- 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}`);
17522
- const done = (phase) => run.phasesCompleted.includes(phase);
17684
+ out += row("changed", `${run2.changedUniverse.length} from git \xB7 analyzable ${run2.analyzable.length} \xB7 reviewable ${run2.reviewable.length} \xB7 security ${run2.securityFiles.length} \xB7 forReview ${run2.allForReview.length}`);
17685
+ const done = (phase) => run2.phasesCompleted.includes(phase);
17523
17686
  const ifDone = (phase, value) => done(phase) ? value : "?";
17524
- const md = run.modeDecision;
17687
+ const md = run2.modeDecision;
17525
17688
  if (md) {
17526
17689
  const how = md.forced ? "forced by --mode" : `predicted=${md.predicted ?? "none"} \u2192 ${md.resolved}`;
17527
17690
  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"}`);
17528
17691
  } else {
17529
- out += row("mode", `? (this run stopped in ${run.phaseReached || "no phase"}, before the mode was decided)`);
17692
+ out += row("mode", `? (this run stopped in ${run2.phaseReached || "no phase"}, before the mode was decided)`);
17530
17693
  }
17531
- out += row("signals", `baseline=${ifDone("bootstrap", run.baseline ? "yes" : "no")} \xB7 authored=${ifDone("intentInputs", run.turnAuthoredCode ? "yes" : "no")} \xB7 observable=${ifDone("intentInputs", run.authorshipIsObservable ? "yes" : "no")}` + (run.actionSummary?.transcript_windowed ? ` \xB7 window=${run.actionSummary.transcript_windowed}` : ""));
17694
+ out += row("signals", `baseline=${ifDone("bootstrap", run2.baseline ? "yes" : "no")} \xB7 authored=${ifDone("intentInputs", run2.turnAuthoredCode ? "yes" : "no")} \xB7 observable=${ifDone("intentInputs", run2.authorshipIsObservable ? "yes" : "no")}` + (run2.actionSummary?.transcript_windowed ? ` \xB7 window=${run2.actionSummary.transcript_windowed}` : ""));
17532
17695
  if (!done("intentInputs")) {
17533
- out += row("", `(\`?\` = the phase that determines it did not complete \u2014 this run stopped in ${run.phaseReached})`);
17696
+ out += row("", `(\`?\` = the phase that determines it did not complete \u2014 this run stopped in ${run2.phaseReached})`);
17534
17697
  }
17535
17698
  out += row("sent", `${sent.length} \xB7 ${list(sent)}`);
17536
17699
  if (context.length > 0) out += row("context", `${context.length} \xB7 ${list(context)}`);
17537
- const withheld = run.reviewCoverage.notReviewed;
17700
+ const withheld = run2.reviewCoverage.notReviewed;
17538
17701
  if (withheld.length > 0) {
17539
17702
  const byReason = /* @__PURE__ */ new Map();
17540
17703
  for (const w of withheld) {
@@ -17547,18 +17710,18 @@ function formatRunEvidence(run, startedAt) {
17547
17710
  first = false;
17548
17711
  }
17549
17712
  } else {
17550
- const sentSet = new Set(run.codeDelta.files.map((f) => f.path));
17551
- const notSent = run.changedUniverse.filter((p) => !sentSet.has(p));
17713
+ const sentSet = new Set(run2.codeDelta.files.map((f) => f.path));
17714
+ const notSent = run2.changedUniverse.filter((p) => !sentSet.has(p));
17552
17715
  if (notSent.length > 0) {
17553
17716
  out += row("not sent", `${list(notSent)}`);
17554
- out += row("", `(stage unknown \u2014 the coverage ledger is built in phase 13, and this run reached ${run.phaseReached || "no phase"})`);
17555
- } else if (run.changedUniverse.length > 0) {
17717
+ out += row("", `(stage unknown \u2014 the coverage ledger is built in phase 13, and this run reached ${run2.phaseReached || "no phase"})`);
17718
+ } else if (run2.changedUniverse.length > 0) {
17556
17719
  out += row("withheld", "(nothing \u2014 every changed file was reviewed)");
17557
17720
  }
17558
17721
  }
17559
- const cov = run.foldResult?.coverage;
17722
+ const cov = run2.foldResult?.coverage;
17560
17723
  if (cov) {
17561
- const delegated = run.foldResult.authored.filter((a) => a.owner === "subagent").length;
17724
+ const delegated = run2.foldResult.authored.filter((a) => a.owner === "subagent").length;
17562
17725
  if (cov.dispatched > 0 || cov.subagentFiles > 0 || cov.subagentSkipped > 0) {
17563
17726
  out += row("delegated", `${cov.dispatched} dispatched \xB7 ${cov.subagentFiles} agent log(s) read \xB7 ${delegated} path(s) attributed to subagents`);
17564
17727
  }
@@ -17572,54 +17735,740 @@ function formatRunEvidence(run, startedAt) {
17572
17735
  out += row("", `${cov.outsideRepo} authored path(s) refused as outside the repo`);
17573
17736
  }
17574
17737
  }
17575
- if (run.foldResult?.tools?.length) {
17576
- const shown = run.foldResult.tools.slice(0, 6).map((t) => {
17738
+ if (run2.foldResult?.tools?.length) {
17739
+ const shown = run2.foldResult.tools.slice(0, 6).map((t) => {
17577
17740
  const outcome = t.failed > 0 ? `${t.failed} failed` : t.last_status === 0 ? "ok" : "?";
17578
17741
  const where = t.targets.length > 0 ? ` \u2192 ${t.targets.slice(0, 2).join(", ")}` : "";
17579
17742
  return `${t.runs}\xD7 ${t.name} (${outcome})${where}`;
17580
17743
  });
17581
- const more = run.foldResult.tools.length > 6 ? ` \u2026 +${run.foldResult.tools.length - 6} more` : "";
17744
+ const more = run2.foldResult.tools.length > 6 ? ` \u2026 +${run2.foldResult.tools.length - 6} more` : "";
17582
17745
  out += row("tools", shown.join(" \xB7 ") + more);
17583
- if (run.foldResult.coverage.toolNamesDropped > 0) {
17584
- out += row("", `\u26A0 ${run.foldResult.coverage.toolNamesDropped} tool name(s) refused by the cap`);
17746
+ if (run2.foldResult.coverage.toolNamesDropped > 0) {
17747
+ out += row("", `\u26A0 ${run2.foldResult.coverage.toolNamesDropped} tool name(s) refused by the cap`);
17585
17748
  }
17586
17749
  }
17587
- if (run.foldResult?.tasks?.length) {
17588
- const t = run.foldResult.tasks;
17750
+ if (run2.foldResult?.tasks?.length) {
17751
+ const t = run2.foldResult.tasks;
17589
17752
  const done2 = t.filter((x) => x.status === "completed").length;
17590
17753
  out += row("tasks", `${t.length} \xB7 ${done2} completed \xB7 ` + list(t.slice(0, 4).map((x) => `#${x.id} ${x.name} [${x.status}]`), 4));
17591
17754
  }
17592
- if (run.specs?.length) {
17593
- const readThisSession = new Set(run.actionSummary?.files_read ?? []);
17594
- const labelled = run.specs.map(
17755
+ if (run2.specs?.length) {
17756
+ const readThisSession = new Set(run2.actionSummary?.files_read ?? []);
17757
+ const labelled = run2.specs.map(
17595
17758
  (s) => `${s.path}${readThisSession.has(s.path) ? " (read)" : " (positional)"}`
17596
17759
  );
17597
- out += row("specs", `${run.specs.length} \xB7 ${list(labelled, 5)}`);
17760
+ out += row("specs", `${run2.specs.length} \xB7 ${list(labelled, 5)}`);
17598
17761
  }
17599
- if (run.staticResults.findings.length > 0) {
17600
- out += row("static", `${run.staticResults.findings.length} finding(s) from ${run.staticResults.summary.tools_run.join(", ") || "no tools"}`);
17762
+ if (run2.staticResults.findings.length > 0) {
17763
+ out += row("static", `${run2.staticResults.findings.length} finding(s) from ${run2.staticResults.summary.tools_run.join(", ") || "no tools"}`);
17601
17764
  }
17602
- if (run.decision && run.decision !== "(unrecognised)") {
17603
- out += row("verdict", run.decision + (run.silenced ? ` \xB7 agent channel silenced (${run.silenced})` : ""));
17765
+ if (run2.decision && run2.decision !== "(unrecognised)") {
17766
+ out += row("verdict", run2.decision + (run2.silenced ? ` \xB7 agent channel silenced (${run2.silenced})` : ""));
17604
17767
  }
17605
17768
  return out;
17606
17769
  }
17607
- function installRunEvidence(run) {
17770
+ function installRunEvidence(run2) {
17608
17771
  const startedAt = Date.now();
17609
17772
  process.on("exit", () => {
17610
17773
  try {
17611
- logToFileOnly(formatRunEvidence(run, startedAt));
17774
+ logToFileOnly(formatRunEvidence(run2, startedAt));
17612
17775
  } catch {
17613
17776
  }
17614
17777
  });
17615
17778
  }
17616
17779
 
17617
17780
  // src/lib/git-frame.ts
17618
- var import_node_child_process7 = require("node:child_process");
17781
+ var import_node_child_process8 = require("node:child_process");
17619
17782
  var import_node_fs24 = require("node:fs");
17620
- var import_node_os3 = require("node:os");
17783
+ var import_node_os4 = require("node:os");
17621
17784
  var import_node_path18 = require("node:path");
17622
17785
  var import_node_path19 = require("node:path");
17786
+
17787
+ // src/lib/repo-context.ts
17788
+ var import_node_child_process7 = require("node:child_process");
17789
+ var import_node_os3 = require("node:os");
17790
+ function rgInvocations(env = process.env) {
17791
+ const out = [{ cmd: "rg" }];
17792
+ if (env.CLAUDE_CODE_EXECPATH) out.push({ cmd: env.CLAUDE_CODE_EXECPATH, argv0: "rg" });
17793
+ out.push({ cmd: `${(0, import_node_os3.homedir)()}/.local/bin/claude`, argv0: "rg" });
17794
+ return out;
17795
+ }
17796
+ var MAX_SYMBOLS = 12;
17797
+ var MAX_SITES = 24;
17798
+ var MAX_SITES_PER_FILE = 3;
17799
+ var MAX_HITS_PER_SYMBOL = 50;
17800
+ var MAX_TEST_SLOTS = 8;
17801
+ var SITE_TEXT_MAX = 160;
17802
+ var ENCLOSING_SCAN_LINES = 200;
17803
+ var RG_TIMEOUT_MS = 1500;
17804
+ var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
17805
+ var STOPLIST = /* @__PURE__ */ new Set([
17806
+ // keyword-shaped captures
17807
+ "if",
17808
+ "for",
17809
+ "while",
17810
+ "switch",
17811
+ "catch",
17812
+ "return",
17813
+ "function",
17814
+ "class",
17815
+ "const",
17816
+ "let",
17817
+ "var",
17818
+ "new",
17819
+ "else",
17820
+ "try",
17821
+ "finally",
17822
+ "throw",
17823
+ "await",
17824
+ "async",
17825
+ "yield",
17826
+ "delete",
17827
+ "typeof",
17828
+ "instanceof",
17829
+ "void",
17830
+ "this",
17831
+ "super",
17832
+ "import",
17833
+ "export",
17834
+ "default",
17835
+ "extends",
17836
+ "implements",
17837
+ "interface",
17838
+ "enum",
17839
+ "type",
17840
+ "public",
17841
+ "private",
17842
+ "protected",
17843
+ "static",
17844
+ "get",
17845
+ "set",
17846
+ "constructor",
17847
+ "def",
17848
+ "elif",
17849
+ "lambda",
17850
+ "with",
17851
+ "pass",
17852
+ "self",
17853
+ "cls",
17854
+ "not",
17855
+ "and",
17856
+ "or",
17857
+ "raise",
17858
+ "except",
17859
+ "func",
17860
+ "defer",
17861
+ "chan",
17862
+ "select",
17863
+ "range",
17864
+ "module",
17865
+ "struct",
17866
+ "trait",
17867
+ "impl",
17868
+ "using",
17869
+ "namespace",
17870
+ // universal noise
17871
+ "main",
17872
+ "init",
17873
+ "index",
17874
+ "data",
17875
+ "value",
17876
+ "result",
17877
+ "item",
17878
+ "name",
17879
+ "key",
17880
+ "run",
17881
+ "test",
17882
+ "setup",
17883
+ "update",
17884
+ "create",
17885
+ "handle",
17886
+ "check",
17887
+ "load",
17888
+ "save",
17889
+ "list",
17890
+ "map",
17891
+ "args",
17892
+ "params",
17893
+ "props",
17894
+ "state",
17895
+ "error",
17896
+ "err",
17897
+ "res",
17898
+ "req",
17899
+ "ctx",
17900
+ "config",
17901
+ "options",
17902
+ "util",
17903
+ "utils",
17904
+ "helper",
17905
+ "render",
17906
+ "build",
17907
+ "parse",
17908
+ "format",
17909
+ "apply",
17910
+ "process",
17911
+ "start",
17912
+ "stop"
17913
+ ]);
17914
+ var TS_RULES = [
17915
+ /\b(?:function|class|interface|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/,
17916
+ /\btype\s+([A-Za-z_][A-Za-z0-9_]*)\s*=/,
17917
+ // const foo = (…) => · const foo = x => · const foo = function.
17918
+ // ⚠ REQUIRES the arrow or `function` ON THE LINE. The first version accepted
17919
+ // any `= (` — and `const started = (rows?.[0] as Row)?.at` is a PARENTHESIZED
17920
+ // CAST, not a function. Replayed over 12 real commits, that one shape put
17921
+ // three local variables into the symbol set per commit. A multi-line arrow
17922
+ // is the accepted false negative; a cast is not an accepted false positive.
17923
+ /\b(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::[^=\n]+)?=\s*(?:async\s+)?(?:function\b|\([^)]*\)(?:\s*:[^=\n]+)?\s*=>|[A-Za-z_$][\w$]*\s*=>)/,
17924
+ // method shape: name(…) { — keyword captures die at the stoplist
17925
+ /^\s*(?:(?:public|private|protected|static|readonly|async|override)\s+)*(?:\*\s*)?([A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*(?::[^{;\n]+)?\s*\{/
17926
+ ];
17927
+ var PY_RULES = [
17928
+ /^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)/,
17929
+ /^\s*class\s+([A-Za-z_]\w*)/
17930
+ ];
17931
+ var GO_RULES = [
17932
+ /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/,
17933
+ /^type\s+([A-Za-z_]\w*)/
17934
+ ];
17935
+ var CLIKE_RULES = [
17936
+ /\b(?:class|interface|enum|record|struct)\s+([A-Za-z_]\w*)/,
17937
+ // access-modifier method shape: `public async Task<Foo> BarBaz(…`
17938
+ /(?:public|private|protected|internal|static|final|virtual|override|sealed|abstract)[\w<>[\],?\s]*?\s([A-Za-z_]\w*)\s*\(/
17939
+ ];
17940
+ var RB_RULES = [
17941
+ /^\s*def\s+(?:self\.)?([A-Za-z_]\w*)/,
17942
+ /^\s*(?:class|module)\s+([A-Z]\w*)/
17943
+ ];
17944
+ var RS_RULES = [
17945
+ /\bfn\s+([A-Za-z_]\w*)/,
17946
+ /\b(?:struct|enum|trait)\s+([A-Za-z_]\w*)/
17947
+ ];
17948
+ var PHP_RULES = [
17949
+ /\bfunction\s+([A-Za-z_]\w*)/,
17950
+ /\bclass\s+([A-Za-z_]\w*)/
17951
+ ];
17952
+ var C_RULES = [
17953
+ /^(?:static\s+|inline\s+|extern\s+|constexpr\s+)*(?:struct\s+|enum\s+|union\s+|unsigned\s+|const\s+)*[A-Za-z_]\w*(?:\s*[*&]+\s*|\s+)([A-Za-z_]\w*)\s*\([^;]*$/,
17954
+ /\b(?:struct|enum|union|class)\s+([A-Za-z_]\w*)/,
17955
+ /::\s*~?([A-Za-z_]\w*)\s*\([^;]*$/
17956
+ // out-of-line C++ method definition
17957
+ ];
17958
+ var SH_RULES = [
17959
+ /^\s*(?:function\s+)?([A-Za-z_]\w*)\s*\(\)\s*\{/,
17960
+ /^function\s+([A-Za-z_]\w*)/
17961
+ ];
17962
+ var SQL_RULES = [
17963
+ /\bcreate\s+(?:or\s+replace\s+)?(?:table|view|materialized\s+view|function|procedure|index|trigger|type|policy)\s+(?:if\s+not\s+exists\s+)?(?:[\w".]*\.)?"?([A-Za-z_]\w*)"?/i
17964
+ ];
17965
+ var TF_RULES = [
17966
+ /^\s*(?:resource|data)\s+"[^"]+"\s+"([A-Za-z_]\w*)"/,
17967
+ /^\s*(?:module|variable|output)\s+"([A-Za-z_]\w*)"/
17968
+ ];
17969
+ var SWIFT_RULES = [
17970
+ /\bfunc\s+([A-Za-z_]\w*)/,
17971
+ /\b(?:class|struct|enum|protocol|extension|actor)\s+([A-Za-z_]\w*)/
17972
+ ];
17973
+ var DART_RULES = [
17974
+ /\b(?:class|enum|mixin|extension)\s+([A-Za-z_]\w*)/,
17975
+ /^\s*(?:static\s+)?(?:Future<[^>]*>|Stream<[^>]*>|void|int|double|bool|String|num|dynamic|[A-Z]\w*(?:<[^>]*>)?)\s+([a-z_]\w*)\s*\(/
17976
+ ];
17977
+ var LUA_RULES = [
17978
+ /^\s*(?:local\s+)?function\s+(?:[\w.]+[.:])?([A-Za-z_]\w*)/
17979
+ ];
17980
+ var EX_RULES = [
17981
+ /^\s*def(?:p|macro)?\s+([a-z_]\w*)/,
17982
+ /^\s*defmodule\s+(?:[\w.]*\.)?([A-Z]\w*)/
17983
+ ];
17984
+ var PROTO_RULES = [
17985
+ /^\s*(?:message|service|enum)\s+([A-Za-z_]\w*)/,
17986
+ /^\s*rpc\s+([A-Za-z_]\w*)/
17987
+ ];
17988
+ var GRAPHQL_RULES = [
17989
+ /^\s*(?:type|interface|enum|input|union|scalar)\s+([A-Za-z_]\w*)/
17990
+ ];
17991
+ var RULES_BY_EXT = {
17992
+ ts: TS_RULES,
17993
+ tsx: TS_RULES,
17994
+ js: TS_RULES,
17995
+ jsx: TS_RULES,
17996
+ mjs: TS_RULES,
17997
+ cjs: TS_RULES,
17998
+ svelte: TS_RULES,
17999
+ vue: TS_RULES,
18000
+ // script blocks
18001
+ py: PY_RULES,
18002
+ go: GO_RULES,
18003
+ java: CLIKE_RULES,
18004
+ cs: CLIKE_RULES,
18005
+ kt: CLIKE_RULES,
18006
+ scala: CLIKE_RULES,
18007
+ rb: RB_RULES,
18008
+ rs: RS_RULES,
18009
+ php: PHP_RULES,
18010
+ c: C_RULES,
18011
+ cpp: C_RULES,
18012
+ cc: C_RULES,
18013
+ h: C_RULES,
18014
+ hpp: C_RULES,
18015
+ sh: SH_RULES,
18016
+ bash: SH_RULES,
18017
+ zsh: SH_RULES,
18018
+ sql: SQL_RULES,
18019
+ tf: TF_RULES,
18020
+ hcl: TF_RULES,
18021
+ swift: SWIFT_RULES,
18022
+ dart: DART_RULES,
18023
+ lua: LUA_RULES,
18024
+ ex: EX_RULES,
18025
+ exs: EX_RULES,
18026
+ proto: PROTO_RULES,
18027
+ graphql: GRAPHQL_RULES,
18028
+ gql: GRAPHQL_RULES
18029
+ };
18030
+ var HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
18031
+ function parseDiffSignals(diff) {
18032
+ const addedRanges = [];
18033
+ const touchPoints = [];
18034
+ const deletedLines = [];
18035
+ let newLine = 0;
18036
+ let oldRemaining = 0;
18037
+ let newRemaining = 0;
18038
+ let runStart = -1;
18039
+ let deletionRun = false;
18040
+ const closeAddedRun = () => {
18041
+ if (runStart >= 0) addedRanges.push([runStart, newLine - 1]);
18042
+ runStart = -1;
18043
+ };
18044
+ const closeDeletionRun = () => {
18045
+ if (deletionRun) touchPoints.push(Math.max(1, newLine));
18046
+ deletionRun = false;
18047
+ };
18048
+ for (const line of diff.split("\n")) {
18049
+ const inHunk = oldRemaining > 0 || newRemaining > 0;
18050
+ if (!inHunk) {
18051
+ closeAddedRun();
18052
+ closeDeletionRun();
18053
+ const header = HUNK_HEADER.exec(line);
18054
+ if (header) {
18055
+ newLine = parseInt(header[3], 10);
18056
+ oldRemaining = header[2] === void 0 ? 1 : parseInt(header[2], 10);
18057
+ newRemaining = header[4] === void 0 ? 1 : parseInt(header[4], 10);
18058
+ if (newRemaining === 0) newLine = Math.max(1, newLine);
18059
+ }
18060
+ continue;
18061
+ }
18062
+ if (line.startsWith("\\")) continue;
18063
+ if (line.startsWith("+") && newRemaining > 0) {
18064
+ deletionRun = false;
18065
+ if (runStart < 0) runStart = newLine;
18066
+ newLine++;
18067
+ newRemaining--;
18068
+ continue;
18069
+ }
18070
+ if (line.startsWith("-") && oldRemaining > 0) {
18071
+ closeAddedRun();
18072
+ deletionRun = true;
18073
+ deletedLines.push(line.slice(1));
18074
+ oldRemaining--;
18075
+ continue;
18076
+ }
18077
+ closeAddedRun();
18078
+ closeDeletionRun();
18079
+ newLine++;
18080
+ if (oldRemaining > 0) oldRemaining--;
18081
+ if (newRemaining > 0) newRemaining--;
18082
+ }
18083
+ closeAddedRun();
18084
+ closeDeletionRun();
18085
+ return { addedRanges, touchPoints, deletedLines };
18086
+ }
18087
+ function declNameOn(line, rules) {
18088
+ for (const r of rules) {
18089
+ const m = r.exec(line);
18090
+ if (m?.[1]) return m[1];
18091
+ }
18092
+ return null;
18093
+ }
18094
+ function acceptable(name) {
18095
+ if (!name || !IDENTIFIER.test(name) || STOPLIST.has(name.toLowerCase())) return false;
18096
+ if (name.length < 3) return false;
18097
+ if (name.length === 3 && name === name.toLowerCase() && !name.includes("_")) return false;
18098
+ return true;
18099
+ }
18100
+ function isMultiSegment(name) {
18101
+ return /[a-z][A-Z]/.test(name) || name.includes("_");
18102
+ }
18103
+ function extractFileSymbols(path, content, signals) {
18104
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
18105
+ const rules = RULES_BY_EXT[ext];
18106
+ if (!rules) return [];
18107
+ if (signals.addedRanges.length === 0 && signals.touchPoints.length === 0 && signals.deletedLines.length === 0) return [];
18108
+ const lines = content.split("\n");
18109
+ const found = [];
18110
+ const seen = /* @__PURE__ */ new Set();
18111
+ const add = (name) => {
18112
+ if (acceptable(name) && !seen.has(name)) {
18113
+ seen.add(name);
18114
+ found.push(name);
18115
+ }
18116
+ };
18117
+ for (const deleted of signals.deletedLines) {
18118
+ add(declNameOn(deleted, rules));
18119
+ }
18120
+ for (const [start, end] of signals.addedRanges) {
18121
+ for (let n = start; n <= Math.min(end, lines.length); n++) {
18122
+ add(declNameOn(lines[n - 1] ?? "", rules));
18123
+ }
18124
+ }
18125
+ const scanStarts = [
18126
+ ...signals.addedRanges.map(([start]) => start),
18127
+ ...signals.touchPoints
18128
+ ];
18129
+ for (const start of scanStarts) {
18130
+ const floor = Math.max(1, start - ENCLOSING_SCAN_LINES);
18131
+ for (let n = Math.min(start, lines.length); n >= floor; n--) {
18132
+ const name = declNameOn(lines[n - 1] ?? "", rules);
18133
+ if (acceptable(name)) {
18134
+ add(name);
18135
+ break;
18136
+ }
18137
+ }
18138
+ }
18139
+ return found;
18140
+ }
18141
+ function rankSymbols(symbols) {
18142
+ return symbols.map((s, i) => ({ s, i })).sort((a, b) => {
18143
+ const seg = Number(isMultiSegment(b.s)) - Number(isMultiSegment(a.s));
18144
+ if (seg !== 0) return seg;
18145
+ if (b.s.length !== a.s.length) return b.s.length - a.s.length;
18146
+ return a.i - b.i;
18147
+ }).slice(0, MAX_SYMBOLS).map((x) => x.s);
18148
+ }
18149
+ var TEST_PATH = /(^|\/)(tests?|specs?|__tests__|e2e)(\/|$)/i;
18150
+ var TEST_FILE = /(\.(test|spec|e2e)\.[^./]+|_test\.[^./]+|_spec\.rb)$/i;
18151
+ var TEST_PY_PREFIX = /(^|\/)test_[^/]+\.py$/i;
18152
+ var IMPORT_LINE = /^\s*(import\s|from\s+\S+\s+import\s|const\s+.*=\s*require\s*\(|require\s*\(|using\s+[\w.]+;|#include|export\s+\{[^}]*\}\s+from|export\s+\*\s+from)/;
18153
+ var BARE_MEMBER_LINE = /^(type\s+)?[A-Za-z_$][\w$]*\s*,?$/;
18154
+ var GO_IMPORT_PATH_LINE = /^"[^"]+",?$/;
18155
+ var SITE_CODE_EXTENSIONS = /* @__PURE__ */ new Set([
18156
+ "ts",
18157
+ "tsx",
18158
+ "js",
18159
+ "jsx",
18160
+ "mjs",
18161
+ "cjs",
18162
+ "py",
18163
+ "go",
18164
+ "java",
18165
+ "kt",
18166
+ "rb",
18167
+ "rs",
18168
+ "scala",
18169
+ "c",
18170
+ "cpp",
18171
+ "cc",
18172
+ "h",
18173
+ "hpp",
18174
+ "cs",
18175
+ "php",
18176
+ "swift",
18177
+ "dart",
18178
+ "lua",
18179
+ "sh",
18180
+ "bash",
18181
+ "zsh",
18182
+ "svelte",
18183
+ "vue",
18184
+ "ex",
18185
+ "exs",
18186
+ // sql/tf carry REAL call sites (SELECT my_function(...), module.name) —
18187
+ // excluded in an earlier round because of migration-comment noise, which the
18188
+ // COMMENT_LINE filter now handles on its own.
18189
+ "sql",
18190
+ "tf",
18191
+ "hcl"
18192
+ ]);
18193
+ var COMMENT_LINE = /^(\/\/|#(?!\[)|\*|\/\*|--\s|<!--)/;
18194
+ function isCodeSiteFile(path) {
18195
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
18196
+ return SITE_CODE_EXTENSIONS.has(ext);
18197
+ }
18198
+ function isTestPath(path) {
18199
+ return TEST_PATH.test(path) || TEST_FILE.test(path) || TEST_PY_PREFIX.test(path);
18200
+ }
18201
+ function parseRgLine(line) {
18202
+ const first = line.indexOf(":");
18203
+ if (first <= 0) return null;
18204
+ const second = line.indexOf(":", first + 1);
18205
+ if (second < 0) return null;
18206
+ const n = parseInt(line.slice(first + 1, second), 10);
18207
+ if (!Number.isFinite(n) || n < 1) return null;
18208
+ return { file: line.slice(0, first), line: n, text: line.slice(second + 1) };
18209
+ }
18210
+ var isWordChar = (c) => c !== void 0 && /[A-Za-z0-9_]/.test(c);
18211
+ function wordHit(text, symbol) {
18212
+ let from = 0;
18213
+ for (; ; ) {
18214
+ const at = text.indexOf(symbol, from);
18215
+ if (at < 0) return false;
18216
+ const before = at === 0 ? void 0 : text[at - 1];
18217
+ const after = text[at + symbol.length];
18218
+ if (!isWordChar(before) && !isWordChar(after)) return true;
18219
+ from = at + 1;
18220
+ }
18221
+ }
18222
+ function isContractLine(text, symbol) {
18223
+ for (const kw of ["implements", "extends"]) {
18224
+ let from = 0;
18225
+ for (; ; ) {
18226
+ const at = text.indexOf(kw, from);
18227
+ if (at < 0) break;
18228
+ from = at + 1;
18229
+ const before = at === 0 ? void 0 : text[at - 1];
18230
+ const after = text[at + kw.length];
18231
+ if (isWordChar(before) || isWordChar(after)) continue;
18232
+ let clause = text.slice(at + kw.length);
18233
+ const stop = Math.min(
18234
+ ...[clause.indexOf(";"), clause.indexOf("{")].filter((i) => i >= 0)
18235
+ );
18236
+ if (Number.isFinite(stop)) clause = clause.slice(0, stop);
18237
+ if (wordHit(clause, symbol)) return true;
18238
+ }
18239
+ }
18240
+ return false;
18241
+ }
18242
+ function partitionSites(rgLines, symbols, opts) {
18243
+ const hits = [];
18244
+ const hitCount = /* @__PURE__ */ new Map();
18245
+ for (const line of rgLines) {
18246
+ const hit = parseRgLine(line);
18247
+ if (!hit) continue;
18248
+ const matched = symbols.filter((s) => wordHit(hit.text, s));
18249
+ for (const s of matched) hitCount.set(s, (hitCount.get(s) ?? 0) + 1);
18250
+ if (matched.length === 0) continue;
18251
+ hits.push({ ...hit, symbol: matched[0] });
18252
+ }
18253
+ hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
18254
+ const dropped = symbols.filter((s) => (hitCount.get(s) ?? 0) > MAX_HITS_PER_SYMBOL);
18255
+ const droppedSet = new Set(dropped);
18256
+ const perFile = /* @__PURE__ */ new Map();
18257
+ const callers = [];
18258
+ const tests = [];
18259
+ for (const h of hits) {
18260
+ if (droppedSet.has(h.symbol)) continue;
18261
+ if (opts.sentPaths.has(h.file)) continue;
18262
+ if (opts.isExcluded(h.file)) continue;
18263
+ if (!isCodeSiteFile(h.file)) continue;
18264
+ const text = h.text.trim();
18265
+ if (text.length === 0 || IMPORT_LINE.test(h.text)) continue;
18266
+ if (COMMENT_LINE.test(text)) continue;
18267
+ if (BARE_MEMBER_LINE.test(text) || GO_IMPORT_PATH_LINE.test(text)) continue;
18268
+ const n = perFile.get(h.file) ?? 0;
18269
+ if (n >= MAX_SITES_PER_FILE) continue;
18270
+ const site = {
18271
+ file: h.file,
18272
+ line: h.line,
18273
+ text: text.slice(0, SITE_TEXT_MAX),
18274
+ symbol: h.symbol,
18275
+ // R2 falls out of R1 for free: a word search for `Sym` already matches
18276
+ // `implements Sym` / `extends Sym` lines — classification is all R2 is.
18277
+ ...isContractLine(text, h.symbol) ? { kind: "contract" } : {}
18278
+ };
18279
+ if (isTestPath(h.file)) {
18280
+ if (tests.length < MAX_TEST_SLOTS) {
18281
+ tests.push(site);
18282
+ perFile.set(h.file, n + 1);
18283
+ }
18284
+ } else if (callers.length + tests.length < MAX_SITES) {
18285
+ callers.push(site);
18286
+ perFile.set(h.file, n + 1);
18287
+ }
18288
+ }
18289
+ while (callers.length + tests.length > MAX_SITES) callers.pop();
18290
+ return { callers, tests, dropped };
18291
+ }
18292
+ function buildRepoContext(input) {
18293
+ const started = Date.now();
18294
+ let signalsByPath;
18295
+ if (input.signalsByPath) {
18296
+ if (input.signalsByPath.size === 0) return { state: "absent", reason: "no-diffs" };
18297
+ signalsByPath = input.signalsByPath;
18298
+ } else {
18299
+ if (input.diffs.length === 0) return { state: "absent", reason: "no-diffs" };
18300
+ signalsByPath = /* @__PURE__ */ new Map();
18301
+ for (const d of input.diffs) signalsByPath.set(d.path, parseDiffSignals(d.diff));
18302
+ }
18303
+ const collected = [];
18304
+ const unsupported = /* @__PURE__ */ new Set();
18305
+ const noteUnsupported = (path) => {
18306
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
18307
+ if (ext && !RULES_BY_EXT[ext]) unsupported.add(ext);
18308
+ };
18309
+ const deltaPathSet = new Set(input.deltaFiles.map((f) => f.path));
18310
+ for (const f of input.deltaFiles) {
18311
+ const signals = signalsByPath.get(f.path);
18312
+ if (!signals) continue;
18313
+ noteUnsupported(f.path);
18314
+ collected.push(...extractFileSymbols(f.path, f.content, signals));
18315
+ }
18316
+ for (const [path, signals] of signalsByPath) {
18317
+ if (deltaPathSet.has(path)) continue;
18318
+ if (signals.deletedLines.length > 0) {
18319
+ noteUnsupported(path);
18320
+ collected.push(...extractFileSymbols(path, "", signals));
18321
+ }
18322
+ }
18323
+ const unsupportedExts = [...unsupported].sort().slice(0, 8);
18324
+ const audit = unsupportedExts.length > 0 ? { unsupported_exts: unsupportedExts } : {};
18325
+ const symbols = rankSymbols([...new Set(collected)]);
18326
+ if (symbols.length === 0) {
18327
+ const everySupportedFileFoundNothing = unsupportedExts.length > 0;
18328
+ return {
18329
+ state: "absent",
18330
+ reason: everySupportedFileFoundNothing ? "unsupported-language" : "no-symbols",
18331
+ ...audit
18332
+ };
18333
+ }
18334
+ const args = [
18335
+ "-n",
18336
+ "-w",
18337
+ "-F",
18338
+ "--no-heading",
18339
+ "--color",
18340
+ "never",
18341
+ // ⚠ NO `--sort path` — it single-threads rg, and on a large worktree that
18342
+ // is the difference between 17ms and a timeout. Determinism is restored by
18343
+ // sorting the hits in partitionSites instead.
18344
+ //
18345
+ // `-m 8` bounds output PER FILE (shared across all patterns), so a noisy
18346
+ // repo cannot blow the 4MB read buffer and turn the whole feature into
18347
+ // `absent/error`. Cost, accepted: the frequency gate sees per-file-capped
18348
+ // counts, so a symbol concentrated in a handful of files can slip a gate
18349
+ // a full count would have tripped — but the ≤3-sites-per-file cap already
18350
+ // bounds exactly that shape's damage; the gate exists for the many-file
18351
+ // 'init' shape, which 8-per-file still trips (>6 files ⇒ >50).
18352
+ "-m",
18353
+ "8",
18354
+ "--max-columns",
18355
+ "300",
18356
+ "--max-columns-preview",
18357
+ ...symbols.flatMap((s) => ["-e", s]),
18358
+ "-g",
18359
+ "!**/{dist,build,out,vendor,node_modules,.git,coverage,target,__pycache__}/**",
18360
+ "./"
18361
+ ];
18362
+ let res = null;
18363
+ for (const inv of rgInvocations()) {
18364
+ res = (0, import_node_child_process7.spawnSync)(inv.cmd, args, {
18365
+ ...inv.argv0 ? { argv0: inv.argv0 } : {},
18366
+ cwd: input.cwd ?? process.cwd(),
18367
+ timeout: input.timeoutMs ?? RG_TIMEOUT_MS,
18368
+ maxBuffer: 4 * 1024 * 1024,
18369
+ encoding: "utf8"
18370
+ });
18371
+ if (res.error?.code !== "ENOENT") break;
18372
+ }
18373
+ if (!res || res.error?.code === "ENOENT") {
18374
+ return { state: "absent", reason: "no-tool", symbols, ...audit };
18375
+ }
18376
+ if (res.error) {
18377
+ const code = res.error.code;
18378
+ if (code === "ETIMEDOUT") return { state: "absent", reason: "timeout", symbols, ...audit };
18379
+ return { state: "absent", reason: "error", symbols, ...audit };
18380
+ }
18381
+ if (res.signal) return { state: "absent", reason: "timeout", symbols, ...audit };
18382
+ if (res.status !== 0 && res.status !== 1) return { state: "absent", reason: "error", symbols, ...audit };
18383
+ const lines = (res.stdout ?? "").split("\n").map((l) => l.replace(/^\.\//, "")).filter(Boolean);
18384
+ const { callers, tests, dropped } = partitionSites(lines, symbols, {
18385
+ sentPaths: input.sentPaths,
18386
+ isExcluded: input.isExcluded
18387
+ });
18388
+ if (callers.length === 0 && tests.length === 0) {
18389
+ return {
18390
+ state: "absent",
18391
+ reason: "no-sites",
18392
+ symbols,
18393
+ ...dropped.length > 0 ? { dropped_symbols: dropped } : {},
18394
+ ...audit,
18395
+ elapsed_ms: Date.now() - started
18396
+ };
18397
+ }
18398
+ return {
18399
+ state: "ok",
18400
+ symbols,
18401
+ ...dropped.length > 0 ? { dropped_symbols: dropped } : {},
18402
+ ...audit,
18403
+ callers,
18404
+ tests,
18405
+ elapsed_ms: Date.now() - started
18406
+ };
18407
+ }
18408
+ var MAX_EXCERPTS = 12;
18409
+ var MAX_EXCERPTS_PER_FILE = 2;
18410
+ var EXCERPT_MAX_LINES = 30;
18411
+ var EXCERPT_MAX_CHARS = 2400;
18412
+ var EXCERPT_TOTAL_BYTES = 24576;
18413
+ var EXCERPT_DECL_SCAN = 40;
18414
+ function extractEnclosingExcerpt(content, siteLine, path) {
18415
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
18416
+ const rules = RULES_BY_EXT[ext] ?? [];
18417
+ const lines = content.split("\n");
18418
+ if (siteLine < 1 || siteLine > lines.length) return null;
18419
+ let declStart = null;
18420
+ const floor = Math.max(1, siteLine - EXCERPT_DECL_SCAN);
18421
+ for (let n = siteLine; n >= floor; n--) {
18422
+ if (declNameOn(lines[n - 1] ?? "", rules) !== null) {
18423
+ declStart = n;
18424
+ break;
18425
+ }
18426
+ }
18427
+ let start;
18428
+ if (declStart !== null && siteLine - declStart < EXCERPT_MAX_LINES) {
18429
+ start = declStart;
18430
+ } else {
18431
+ start = Math.max(1, siteLine - (EXCERPT_MAX_LINES - 6));
18432
+ }
18433
+ const end = Math.min(lines.length, start + EXCERPT_MAX_LINES - 1);
18434
+ const text = lines.slice(start - 1, end).join("\n").slice(0, EXCERPT_MAX_CHARS);
18435
+ return { start_line: start, text };
18436
+ }
18437
+ function upgradeToExcerpts(rc, opts) {
18438
+ if (rc.state !== "ok") return;
18439
+ const ranked = [
18440
+ ...(rc.callers ?? []).filter((s) => s.kind === "contract"),
18441
+ ...rc.tests ?? [],
18442
+ ...(rc.callers ?? []).filter((s) => s.kind !== "contract")
18443
+ ];
18444
+ const perFile = /* @__PURE__ */ new Map();
18445
+ const contentCache = /* @__PURE__ */ new Map();
18446
+ const excerpts = [];
18447
+ let totalBytes = 0;
18448
+ for (const site of ranked) {
18449
+ if (excerpts.length >= MAX_EXCERPTS) break;
18450
+ const used = perFile.get(site.file) ?? 0;
18451
+ if (used >= MAX_EXCERPTS_PER_FILE) continue;
18452
+ if (!contentCache.has(site.file)) contentCache.set(site.file, opts.readFile(site.file));
18453
+ const content = contentCache.get(site.file);
18454
+ if (content === null || content === void 0) continue;
18455
+ const ex = extractEnclosingExcerpt(content, site.line, site.file);
18456
+ if (!ex) continue;
18457
+ if (totalBytes + ex.text.length > EXCERPT_TOTAL_BYTES) break;
18458
+ excerpts.push({
18459
+ file: site.file,
18460
+ start_line: ex.start_line,
18461
+ symbol: site.symbol,
18462
+ kind: site.kind === "contract" ? "contract" : isTestPath(site.file) ? "test" : "caller",
18463
+ text: ex.text
18464
+ });
18465
+ totalBytes += ex.text.length;
18466
+ perFile.set(site.file, used + 1);
18467
+ }
18468
+ if (excerpts.length > 0) rc.excerpts = excerpts;
18469
+ }
18470
+
18471
+ // src/lib/git-frame.ts
17623
18472
  var VALUE_TOKEN = `(?:'[^']*'|"[^"]*"|\\S+)`;
17624
18473
  var GIT_GLOBAL_OPTS = `(?:\\s+(?:-[Cc]\\s+${VALUE_TOKEN}|--?[\\w-]+(?:=\\S+)?))*`;
17625
18474
  var COMMIT_HEAD = `git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`;
@@ -17665,14 +18514,14 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
17665
18514
  if (!m) continue;
17666
18515
  named = true;
17667
18516
  if (m[1] === void 0) {
17668
- dir = (0, import_node_os3.homedir)();
18517
+ dir = (0, import_node_os4.homedir)();
17669
18518
  continue;
17670
18519
  }
17671
18520
  const raw = unquote(m[1]);
17672
18521
  if (SHELL_DYNAMIC.test(raw) || raw === "-") {
17673
18522
  return { dir: null, named: true, unresolvable: `cd target not statically resolvable: ${raw}` };
17674
18523
  }
17675
- const expanded = raw === "~" ? (0, import_node_os3.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os3.homedir)(), raw.slice(2)) : raw;
18524
+ const expanded = raw === "~" ? (0, import_node_os4.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os4.homedir)(), raw.slice(2)) : raw;
17676
18525
  dir = (0, import_node_path18.isAbsolute)(expanded) ? expanded : (0, import_node_path18.resolve)(dir, expanded);
17677
18526
  }
17678
18527
  const seg = segments[segmentIndex];
@@ -17691,7 +18540,7 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
17691
18540
  if (SHELL_DYNAMIC.test(raw)) {
17692
18541
  return { dir: null, named: true, unresolvable: `-C target not statically resolvable: ${raw}` };
17693
18542
  }
17694
- const expanded = raw === "~" ? (0, import_node_os3.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os3.homedir)(), raw.slice(2)) : raw;
18543
+ const expanded = raw === "~" ? (0, import_node_os4.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os4.homedir)(), raw.slice(2)) : raw;
17695
18544
  dir = (0, import_node_path18.isAbsolute)(expanded) ? expanded : (0, import_node_path18.resolve)(dir, expanded);
17696
18545
  }
17697
18546
  }
@@ -17748,7 +18597,7 @@ function parsePushTarget(segment) {
17748
18597
  }
17749
18598
  function gitAt(dir, args) {
17750
18599
  try {
17751
- return (0, import_node_child_process7.execFileSync)("git", args, { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
18600
+ return (0, import_node_child_process8.execFileSync)("git", args, { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
17752
18601
  } catch {
17753
18602
  return "";
17754
18603
  }
@@ -17887,17 +18736,51 @@ function rangeFiles(frame, range) {
17887
18736
  }
17888
18737
  return out.split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
17889
18738
  }
17890
- function rangeMessages(frame, range) {
17891
- if (range.kind === "staged" || range.kind === "nothing" || !range.base) return "";
17892
- 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");
17893
- }
17894
- function frameTelemetry(frame, range, divergence) {
17895
- const t = {
17896
- anchor: frame.anchor,
17897
- linked_worktree: frame.isLinkedWorktree,
17898
- range_via: range?.via ?? null,
17899
- refusal: frame.refusal
17900
- };
18739
+ function rangeChangeSignals(frame, range, paths) {
18740
+ const out = /* @__PURE__ */ new Map();
18741
+ if (range.kind === "nothing" || paths.length === 0) return out;
18742
+ const args = range.kind === "staged" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
18743
+ const diff = frameGit(frame, [...args, "--", ...paths]);
18744
+ let current = null;
18745
+ let oldSide = null;
18746
+ let buf = [];
18747
+ const flush = () => {
18748
+ if (current !== null && buf.length > 0) out.set(current, parseDiffSignals(buf.join("\n")));
18749
+ buf = [];
18750
+ };
18751
+ for (const line of diff.split("\n")) {
18752
+ if (line.startsWith("diff --git ")) {
18753
+ flush();
18754
+ current = null;
18755
+ oldSide = null;
18756
+ continue;
18757
+ }
18758
+ const minusM = /^--- (?:a\/)?(.+)$/.exec(line);
18759
+ if (minusM) {
18760
+ oldSide = minusM[1] === "/dev/null" ? null : minusM[1];
18761
+ continue;
18762
+ }
18763
+ const plusM = /^\+\+\+ (?:b\/)?(.+)$/.exec(line);
18764
+ if (plusM) {
18765
+ current = plusM[1] === "/dev/null" ? oldSide : plusM[1];
18766
+ continue;
18767
+ }
18768
+ if (current !== null) buf.push(line);
18769
+ }
18770
+ flush();
18771
+ return out;
18772
+ }
18773
+ function rangeMessages(frame, range) {
18774
+ if (range.kind === "staged" || range.kind === "nothing" || !range.base) return "";
18775
+ 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");
18776
+ }
18777
+ function frameTelemetry(frame, range, divergence) {
18778
+ const t = {
18779
+ anchor: frame.anchor,
18780
+ linked_worktree: frame.isLinkedWorktree,
18781
+ range_via: range?.via ?? null,
18782
+ refusal: frame.refusal
18783
+ };
17901
18784
  if (divergence) {
17902
18785
  t.root_differs = !!frame.worktreeRoot && !!divergence.actualRoot && realpathOr(frame.worktreeRoot) !== realpathOr(divergence.actualRoot);
17903
18786
  const a = [...divergence.actualFiles].sort().join("\n");
@@ -17946,6 +18829,7 @@ var MAX_COMMANDS = 10;
17946
18829
  var MAX_COMMAND_CHARS = 80;
17947
18830
  var MAX_TOOL_BLOCKS = 200;
17948
18831
  var MAX_SUMMARY_BYTES = 4096;
18832
+ var MAX_SEARCHES_DETAIL = 10;
17949
18833
  var HOME = process.env.HOME ?? "";
17950
18834
  var BASH_INPUT_RE = /^\s*<bash-input>([\s\S]*?)<\/bash-input>/;
17951
18835
  var BASH_ECHO_RE = /^\s*<bash-(?:stdout|stderr)>/;
@@ -18031,6 +18915,7 @@ function buildSummary(lines) {
18031
18915
  let userCommandsTruncated = false;
18032
18916
  let commandsTruncated = false;
18033
18917
  let searches = 0;
18918
+ const searchesDetail = [];
18034
18919
  let subagents = 0;
18035
18920
  let webFetches = 0;
18036
18921
  let totalToolCalls = 0;
@@ -18102,9 +18987,19 @@ function buildSummary(lines) {
18102
18987
  break;
18103
18988
  }
18104
18989
  case "Grep":
18105
- case "Glob":
18990
+ case "Glob": {
18106
18991
  searches++;
18992
+ const pattern = typeof input.pattern === "string" ? input.pattern.slice(0, 120) : "";
18993
+ if (pattern && searchesDetail.length < MAX_SEARCHES_DETAIL) {
18994
+ const scopePath = typeof input.path === "string" ? input.path.slice(0, 200) : void 0;
18995
+ searchesDetail.push({
18996
+ tool: toolName,
18997
+ pattern,
18998
+ ...scopePath ? { path: scopePath } : {}
18999
+ });
19000
+ }
18107
19001
  break;
19002
+ }
18108
19003
  case "Agent":
18109
19004
  case "Task":
18110
19005
  case "Workflow":
@@ -18139,6 +19034,7 @@ function buildSummary(lines) {
18139
19034
  ...cappedOut(filesCreated, MAX_CREATED_LIST)
18140
19035
  ],
18141
19036
  searches,
19037
+ ...searchesDetail.length > 0 ? { searches_detail: searchesDetail } : {},
18142
19038
  commands,
18143
19039
  ...commandsTruncated ? { commands_truncated: true } : {},
18144
19040
  user_commands: userCommands,
@@ -18149,6 +19045,9 @@ function buildSummary(lines) {
18149
19045
  turn_messages: turnMessages,
18150
19046
  turn_duration_ms: turnDurationMs
18151
19047
  };
19048
+ if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
19049
+ delete summary.searches_detail;
19050
+ }
18152
19051
  if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
18153
19052
  summary.commands = [];
18154
19053
  summary.commands_truncated = true;
@@ -18259,13 +19158,13 @@ async function readStopHookStdin() {
18259
19158
  return empty;
18260
19159
  }
18261
19160
  }
18262
- async function bootstrap(run) {
18263
- const { opts, globals } = run;
19161
+ async function bootstrap(run2) {
19162
+ const { opts, globals } = run2;
18264
19163
  try {
18265
19164
  process.chdir(repoRoot());
18266
19165
  } catch {
18267
19166
  }
18268
- run.treeFrame = resolveFrame({ command: "", on: [], hookCwd: null }).frame;
19167
+ run2.treeFrame = resolveFrame({ command: "", on: [], hookCwd: null }).frame;
18269
19168
  const turnId = `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
18270
19169
  let reachability = resolveReachability({
18271
19170
  autonomousFlag: process.env.VERITY_AUTONOMOUS === "1" || opts.mode === "autonomous",
@@ -18279,7 +19178,7 @@ async function bootstrap(run) {
18279
19178
  const rawSessionId = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
18280
19179
  const baselineSessionId = sessionScopeKey(scopeToken, rawSessionId);
18281
19180
  if (tokenResult.ok) {
18282
- run.beacon = {
19181
+ run2.beacon = {
18283
19182
  resolveServiceUrl: async () => {
18284
19183
  const u = await resolveServiceUrl(globals.serviceUrl);
18285
19184
  return u.ok ? u.data : null;
@@ -18299,7 +19198,7 @@ async function bootstrap(run) {
18299
19198
  age_ms: Date.now() - baseline.captured_at
18300
19199
  });
18301
19200
  }
18302
- Object.assign(run, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
19201
+ Object.assign(run2, { actionSummary, assistantResponse, baseline, baselineSessionId, reachability, sessionId, stopReason, tokenResult, transcriptPath, turnId });
18303
19202
  }
18304
19203
 
18305
19204
  // src/lib/self-scope.ts
@@ -18452,7 +19351,7 @@ function channelSilence(input) {
18452
19351
  // src/lib/cli-version.ts
18453
19352
  function cliVersion() {
18454
19353
  try {
18455
- return true ? "0.31.0" : "dev";
19354
+ return true ? "0.31.1-experimental.68fca47" : "dev";
18456
19355
  } catch {
18457
19356
  return "dev";
18458
19357
  }
@@ -18492,7 +19391,7 @@ async function sendSkipBeacon(ctx, reason) {
18492
19391
  }
18493
19392
 
18494
19393
  // src/lib/static-analysis.ts
18495
- var import_node_child_process8 = require("node:child_process");
19394
+ var import_node_child_process9 = require("node:child_process");
18496
19395
  var import_node_fs26 = require("node:fs");
18497
19396
  var SEVERITY_ORDER = {
18498
19397
  Error: 0,
@@ -18505,7 +19404,7 @@ var SEVERITY_ORDER = {
18505
19404
  };
18506
19405
  function isCodacyAvailable() {
18507
19406
  try {
18508
- (0, import_node_child_process8.execSync)("which codacy-analysis", { stdio: "pipe" });
19407
+ (0, import_node_child_process9.execSync)("which codacy-analysis", { stdio: "pipe" });
18509
19408
  return true;
18510
19409
  } catch {
18511
19410
  return false;
@@ -18547,7 +19446,7 @@ function runCodacyAnalysis(files) {
18547
19446
  }
18548
19447
  });
18549
19448
  if (existingFiles.length === 0) return empty;
18550
- const proc = (0, import_node_child_process8.spawnSync)("codacy-analysis", buildAnalyzerArgv(existingFiles), {
19449
+ const proc = (0, import_node_child_process9.spawnSync)("codacy-analysis", buildAnalyzerArgv(existingFiles), {
18551
19450
  encoding: "utf-8",
18552
19451
  maxBuffer: 10 * 1024 * 1024
18553
19452
  });
@@ -18558,12 +19457,12 @@ function runCodacyAnalysis(files) {
18558
19457
  spawnError: proc.error?.message
18559
19458
  });
18560
19459
  }
18561
- function interpretAnalyzerRun(run) {
18562
- const output = run.stdout ?? "";
19460
+ function interpretAnalyzerRun(run2) {
19461
+ const output = run2.stdout ?? "";
18563
19462
  if (!output.trim()) {
18564
19463
  return withFailure(
18565
- run.spawnError ? "spawn_failed" : "no_output",
18566
- run.spawnError ?? run.stderr ?? `exit ${run.status}`
19464
+ run2.spawnError ? "spawn_failed" : "no_output",
19465
+ run2.spawnError ?? run2.stderr ?? `exit ${run2.status}`
18567
19466
  );
18568
19467
  }
18569
19468
  let parsed;
@@ -18702,9 +19601,10 @@ function describeOpenElsewhere(open) {
18702
19601
  const lines = open.slice(0, 5).map((o) => ` ${o.file}:${o.line} [${o.pattern_id}]`);
18703
19602
  const more = open.length > 5 ? `
18704
19603
  (+${open.length - 5} more)` : "";
18705
- return `STILL OPEN ELSEWHERE. ${open.length} blocking finding(s) Verity raised earlier are still present in files this run did not review:
19604
+ return `STILL OPEN ELSEWHERE. ${open.length} finding(s) Verity raised earlier are still on disk in files this run did not review:
18706
19605
  ${lines.join("\n")}${more}
18707
- This verdict covers the current change only. The tree is not clean.`;
19606
+ They did not fail this run \u2014 this verdict covers the current change only. The tree is not clean.
19607
+ Fix them, or record a disposition: verity waive <pattern-id> --file <path> --reason "\u2026"`;
18708
19608
  }
18709
19609
 
18710
19610
  // src/commands/analyze/exit.ts
@@ -18729,9 +19629,9 @@ function localOnlyAndExit(staticResults) {
18729
19629
  });
18730
19630
  process.exit(0);
18731
19631
  }
18732
- async function passAndExit(run, reason, skip, kindOverride) {
18733
- run.skipReason = skip;
18734
- const sent = await sendSkipBeacon(run.beacon, skip);
19632
+ async function passAndExit(run2, reason, skip, kindOverride) {
19633
+ run2.skipReason = skip;
19634
+ const sent = await sendSkipBeacon(run2.beacon, skip);
18735
19635
  logEvent("skip", { reason: skip, beacon: sent });
18736
19636
  const POLICY_SKIPS = /* @__PURE__ */ new Set([
18737
19637
  "no-analyzable-files",
@@ -18746,7 +19646,7 @@ async function passAndExit(run, reason, skip, kindOverride) {
18746
19646
  "no-delta-since-last-review"
18747
19647
  ]);
18748
19648
  const skipKind = kindOverride ?? (POLICY_SKIPS.has(skip) ? "policy" : "capacity");
18749
- const changed = run.changedUniverse;
19649
+ const changed = run2.changedUniverse;
18750
19650
  const { coverage, unaccounted } = reconcileCoverage(changed, {
18751
19651
  reviewed: [],
18752
19652
  notReviewed: changed.map((path) => ({ path, reason: skip, stage: "pre-flight", kind: skipKind }))
@@ -18774,10 +19674,10 @@ async function passAndExit(run, reason, skip, kindOverride) {
18774
19674
  }
18775
19675
 
18776
19676
  // src/commands/analyze/phases/02-scope.ts
18777
- async function scope(run) {
18778
- const { assistantResponse } = run;
19677
+ async function scope(run2) {
19678
+ const { assistantResponse } = run2;
18779
19679
  const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
18780
- run.changedUniverse = allChanged;
19680
+ run2.changedUniverse = allChanged;
18781
19681
  const { kept: external } = partitionVerityOwned(allChanged);
18782
19682
  const verityIgnore = loadVerityIgnore();
18783
19683
  const ignored = partitionIgnored(external, verityIgnore);
@@ -18802,10 +19702,10 @@ async function scope(run) {
18802
19702
  const securityFiles = filterSecurity(inScope);
18803
19703
  const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
18804
19704
  if (noFilesChanged && !assistantResponse) {
18805
- await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
19705
+ await passAndExit(run2, "No analyzable files changed", "no-analyzable-files");
18806
19706
  }
18807
19707
  const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
18808
- Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles, verityIgnored: ignored });
19708
+ Object.assign(run2, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles, verityIgnored: ignored });
18809
19709
  }
18810
19710
 
18811
19711
  // src/lib/specs.ts
@@ -18951,8 +19851,8 @@ function discoverGuardDocs(rangeFiles2) {
18951
19851
  }
18952
19852
 
18953
19853
  // src/commands/analyze/phases/03-intent-inputs.ts
18954
- async function intentInputs(run) {
18955
- const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run;
19854
+ async function intentInputs(run2) {
19855
+ const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run2;
18956
19856
  if (isCommandOnlyTurn({
18957
19857
  userCommands: actionSummary?.user_commands,
18958
19858
  userCommandsTruncated: actionSummary?.user_commands_truncated,
@@ -18960,11 +19860,11 @@ async function intentInputs(run) {
18960
19860
  agentToolCalls: actionSummary?.total_tool_calls ?? 0,
18961
19861
  authorshipIsObservable: !!actionSummary && actionSummary.transcript_windowed !== "orphaned"
18962
19862
  })) {
18963
- await passAndExit(run, "User command only \u2014 skipping analysis", "command-only-turn");
19863
+ await passAndExit(run2, "User command only \u2014 skipping analysis", "command-only-turn");
18964
19864
  }
18965
19865
  {
18966
19866
  const ignoreKeys = ignoreStateKeys(
18967
- run.tokenResult.ok ? run.tokenResult.data.token : void 0,
19867
+ run2.tokenResult.ok ? run2.tokenResult.data.token : void 0,
18968
19868
  null
18969
19869
  );
18970
19870
  const found = resolveIgnoreState([baselineSessionId, ...ignoreKeys]);
@@ -18988,7 +19888,7 @@ async function intentInputs(run) {
18988
19888
  if (declaration.scope === "turn" && found) clearActiveDeclaration(found.key);
18989
19889
  logEvent("ignore_honoured", { scope: declaration.scope, origin: declaration.origin });
18990
19890
  await passAndExit(
18991
- run,
19891
+ run2,
18992
19892
  `skipping this turn \u2014 declared housekeeping ("${declaration.reason}")`,
18993
19893
  "declared-ignore"
18994
19894
  );
@@ -19002,7 +19902,7 @@ async function intentInputs(run) {
19002
19902
  const notice = `Verity: the ignore declared for this window ("${declaration.reason}") was voided \u2014 ${outcome.why}. Reviewing normally.`;
19003
19903
  process.stderr.write(`${notice}
19004
19904
  `);
19005
- run.voidedIgnoreNotice = notice;
19905
+ run2.voidedIgnoreNotice = notice;
19006
19906
  }
19007
19907
  }
19008
19908
  }
@@ -19024,34 +19924,33 @@ async function intentInputs(run) {
19024
19924
  const adopted = absorbIntoBaseline(setupAuthored, baselineSessionId);
19025
19925
  logEvent("baseline_absorbed", { skip: "verity-command", offered: setupAuthored.length, adopted });
19026
19926
  }
19027
- await passAndExit(run, "Verity command \u2014 skipping analysis", "verity-command");
19927
+ await passAndExit(run2, "Verity command \u2014 skipping analysis", "verity-command");
19028
19928
  }
19029
19929
  if (shouldSkipForBareAck({ prompt: latestPrompt, turnAuthoredCode, canSeeTurnAuthorship })) {
19030
- await passAndExit(run, "Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
19930
+ await passAndExit(run2, "Bare acknowledgment \u2014 skipping analysis", "bare-acknowledgment");
19031
19931
  }
19032
19932
  if (isReflectionQuestion(assistantResponse) && !turnAuthoredCode && canSeeTurnAuthorship) {
19033
- await passAndExit(run, "Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
19933
+ await passAndExit(run2, "Reflection-prompt turn \u2014 skipping analysis", "reflection-prompt");
19034
19934
  }
19035
- Object.assign(run, { authorshipIsObservable, conversation, earlyFold, plans, specs, turnAuthoredCode });
19935
+ Object.assign(run2, { authorshipIsObservable, conversation, earlyFold, plans, specs, turnAuthoredCode });
19036
19936
  }
19037
19937
 
19038
19938
  // src/commands/analyze/phases/04-connect.ts
19039
- async function connect(run) {
19040
- const { opts, globals } = run;
19041
- const { analyzable, baseline, securityFiles, tokenResult } = run;
19939
+ async function connect(run2) {
19940
+ const { opts, globals } = run2;
19941
+ const { analyzable, baseline, securityFiles, tokenResult } = run2;
19042
19942
  const urlResult = await resolveServiceUrl(globals.serviceUrl);
19043
19943
  if (!tokenResult.ok || !urlResult.ok) {
19044
19944
  localOnlyAndExit(runLocalStatic(analyzable, securityFiles, baseline, !!opts.skipStatic));
19045
19945
  }
19046
- Object.assign(run, { urlResult, serviceUrl: urlResult.data, token: tokenResult.data.token });
19946
+ Object.assign(run2, { urlResult, serviceUrl: urlResult.data, token: tokenResult.data.token });
19047
19947
  }
19048
19948
 
19049
19949
  // src/commands/analyze/phases/05-mode.ts
19050
- async function mode(run) {
19051
- const { opts, globals } = run;
19052
- const { actionSummary, allForReview, assistantResponse, baseline, conversation, memory, noFilesChanged, serviceUrl, sessionId, token, turnAuthoredCode } = run;
19950
+ async function mode(run2) {
19951
+ const { opts, globals } = run2;
19952
+ const { actionSummary, allForReview, assistantResponse, baseline, conversation, memory, noFilesChanged, serviceUrl, sessionId, token, turnAuthoredCode } = run2;
19053
19953
  const sessionIdForMemory = sessionId || process.env.CLAUDE_SESSION_ID || "";
19054
- let contextFilePaths = [];
19055
19954
  let predictedMode;
19056
19955
  try {
19057
19956
  const memoryPath2 = sessionIdForMemory ? `/memory?session_id=${encodeURIComponent(sessionIdForMemory)}` : "/memory";
@@ -19065,9 +19964,6 @@ async function mode(run) {
19065
19964
  cmd: "analyze_context"
19066
19965
  });
19067
19966
  if (memoryResult.ok) {
19068
- if (Array.isArray(memoryResult.data.context_files)) {
19069
- contextFilePaths = memoryResult.data.context_files;
19070
- }
19071
19967
  const rawMode = memoryResult.data.predicted_mode;
19072
19968
  if (rawMode && ["standard", "plan", "debug", "skip"].includes(rawMode)) {
19073
19969
  predictedMode = rawMode;
@@ -19090,7 +19986,7 @@ async function mode(run) {
19090
19986
  );
19091
19987
  }
19092
19988
  const investigated = didAgentInvestigate(actionSummary);
19093
- run.modeDecision = {
19989
+ run2.modeDecision = {
19094
19990
  predicted: predictedMode ?? null,
19095
19991
  resolved: analysisMode,
19096
19992
  authored: turnAuthoredCode,
@@ -19110,13 +20006,13 @@ async function mode(run) {
19110
20006
  });
19111
20007
  if (analysisMode === "skip") {
19112
20008
  await passAndExit(
19113
- run,
20009
+ run2,
19114
20010
  "Skip mode \u2014 no code work to analyze",
19115
20011
  "skip-mode",
19116
20012
  turnAuthoredCode ? "capacity" : void 0
19117
20013
  );
19118
20014
  }
19119
- Object.assign(run, { analysisMode, contextFilePaths, sessionAuthoredCode, sessionIdForMemory });
20015
+ Object.assign(run2, { analysisMode, sessionAuthoredCode, sessionIdForMemory });
19120
20016
  }
19121
20017
 
19122
20018
  // src/lib/fold.ts
@@ -19576,12 +20472,12 @@ function checkConservation(changedFiles, result, repoRoot2) {
19576
20472
  }
19577
20473
 
19578
20474
  // src/commands/analyze/phases/06-evidence.ts
19579
- async function evidence(run) {
19580
- const { opts } = run;
19581
- const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run;
19582
- let { analysisMode, earlyFold } = run;
20475
+ async function evidence(run2) {
20476
+ const { opts } = run2;
20477
+ const { actionSummary, allForReview, analyzable, assistantResponse, authorshipIsObservable, baseline, baselineSessionId, hasRecentCommitFiles, reviewable, securityFiles, sessionAuthoredCode, transcriptPath, turnAuthoredCode } = run2;
20478
+ let { analysisMode, earlyFold } = run2;
19583
20479
  const recordFlip = (stage) => {
19584
- if (run.modeDecision) run.modeDecision = { ...run.modeDecision, resolved: "plan", flip: stage };
20480
+ if (run2.modeDecision) run2.modeDecision = { ...run2.modeDecision, resolved: "plan", flip: stage };
19585
20481
  logEvent("mode_flipped", { stage, to: "plan" });
19586
20482
  };
19587
20483
  const planWorthy = !!assistantResponse && !turnAuthoredCode;
@@ -19608,7 +20504,7 @@ async function evidence(run) {
19608
20504
  analysisMode = "plan";
19609
20505
  recordFlip("debounce");
19610
20506
  } else {
19611
- await passAndExit(run, debounceSkip, "debounce");
20507
+ await passAndExit(run2, debounceSkip, "debounce");
19612
20508
  }
19613
20509
  }
19614
20510
  if (analysisMode !== "plan") {
@@ -19619,7 +20515,7 @@ async function evidence(run) {
19619
20515
  analysisMode = "plan";
19620
20516
  recordFlip("mtime");
19621
20517
  } else {
19622
- await passAndExit(run, mtimeSkip, "no-delta-since-last-review");
20518
+ await passAndExit(run2, mtimeSkip, "no-delta-since-last-review");
19623
20519
  }
19624
20520
  }
19625
20521
  }
@@ -19632,7 +20528,7 @@ async function evidence(run) {
19632
20528
  analysisMode = "plan";
19633
20529
  recordFlip("content-hash");
19634
20530
  } else {
19635
- await passAndExit(run, hashResult.skip, "no-delta-since-last-review");
20531
+ await passAndExit(run2, hashResult.skip, "no-delta-since-last-review");
19636
20532
  }
19637
20533
  }
19638
20534
  contentHash = hashResult.hash;
@@ -19640,7 +20536,7 @@ async function evidence(run) {
19640
20536
  const scoped = scopeToAuthored(allForReview, actionSummary);
19641
20537
  const canTrustNoneAuthored = scoped.signal === "none-authored" && authorshipIsObservable;
19642
20538
  if (canTrustNoneAuthored && !hasNonEditAuthorship(actionSummary, sessionAuthoredCode)) {
19643
- await passAndExit(run, "No agent-authored code this turn \u2014 working-tree changes were not authored by this session", "zero-increment");
20539
+ await passAndExit(run2, "No agent-authored code this turn \u2014 working-tree changes were not authored by this session", "zero-increment");
19644
20540
  }
19645
20541
  if (scoped.signal === "none-authored" && !authorshipIsObservable) {
19646
20542
  logEvent("none_authored_unverifiable", {
@@ -19697,7 +20593,7 @@ async function evidence(run) {
19697
20593
  recordFlip("empty-after-scoping");
19698
20594
  } else {
19699
20595
  await passAndExit(
19700
- run,
20596
+ run2,
19701
20597
  "No files within size limits to analyze",
19702
20598
  "size-limit",
19703
20599
  codeDelta.excluded.length > 0 ? "capacity" : "policy"
@@ -19722,7 +20618,7 @@ async function evidence(run) {
19722
20618
  currentCommit = getCurrentCommit();
19723
20619
  iteration = readIteration(currentCommit);
19724
20620
  }
19725
- Object.assign(run, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
20621
+ Object.assign(run2, { analysisMode, codeDelta, contentHash, currentCommit, earlyFold, iteration, snapshotResult, staticResults });
19726
20622
  }
19727
20623
 
19728
20624
  // src/lib/cache-cleanup.ts
@@ -19754,15 +20650,52 @@ function pruneStaleCache() {
19754
20650
 
19755
20651
  // src/lib/context-files.ts
19756
20652
  var import_node_fs30 = require("node:fs");
20653
+ var import_node_os5 = require("node:os");
19757
20654
  var MAX_CONTEXT_FILES = 10;
19758
20655
  var MAX_CONTEXT_FILE_BYTES = 10240;
19759
- var MAX_CONTEXT_TOTAL_BYTES = 51200;
19760
- function gatherContextFiles(contextPaths, deltaFiles) {
20656
+ var MAX_CONTEXT_TOTAL_BYTES = 24576;
20657
+ function readSetContextPaths(summary, deltaFiles) {
20658
+ const reads = summary?.files_read ?? [];
20659
+ if (reads.length === 0) return [];
20660
+ const root = process.cwd().replace(/\/+$/, "");
20661
+ const home = (0, import_node_os5.homedir)();
20662
+ const toRepoRelative2 = (p) => {
20663
+ if (!p) return null;
20664
+ let abs;
20665
+ if (p.startsWith("/")) {
20666
+ abs = p;
20667
+ } else {
20668
+ const rebuilt = `${home}/${p}`;
20669
+ abs = rebuilt.startsWith(`${root}/`) ? rebuilt : `${root}/${p}`;
20670
+ }
20671
+ if (!abs.startsWith(`${root}/`)) return null;
20672
+ return abs.slice(root.length + 1);
20673
+ };
20674
+ const authored = /* @__PURE__ */ new Set();
20675
+ for (const p of [...summary?.files_edited ?? [], ...summary?.files_created ?? []]) {
20676
+ const rel = toRepoRelative2(p);
20677
+ if (rel) authored.add(rel);
20678
+ }
20679
+ for (const f of deltaFiles) authored.add(f.path);
20680
+ const out = [];
20681
+ const seen = /* @__PURE__ */ new Set();
20682
+ for (const p of reads) {
20683
+ const rel = toRepoRelative2(p);
20684
+ if (!rel || seen.has(rel) || authored.has(rel)) continue;
20685
+ seen.add(rel);
20686
+ const ext = rel.split(".").pop()?.toLowerCase() ?? "";
20687
+ if (!ANALYZABLE_EXTENSIONS.has(ext) && !REVIEWABLE_EXTENSIONS.has(ext)) continue;
20688
+ out.push(rel);
20689
+ }
20690
+ return out;
20691
+ }
20692
+ function gatherContextFiles(contextPaths, deltaFiles, opts) {
20693
+ const fileCap = Math.max(0, Math.min(MAX_CONTEXT_FILES, opts?.maxFiles ?? MAX_CONTEXT_FILES));
19761
20694
  const deltaPaths = new Set(deltaFiles.map((f) => f.path));
19762
20695
  const result = [];
19763
20696
  let totalBytes = 0;
19764
20697
  for (const filePath of contextPaths) {
19765
- if (result.length >= MAX_CONTEXT_FILES) break;
20698
+ if (result.length >= fileCap) break;
19766
20699
  if (deltaPaths.has(filePath)) continue;
19767
20700
  if (isVerityOwnedPath(filePath)) {
19768
20701
  logEvent("context_file_skipped", { path: filePath, reason: "verity_owned" });
@@ -19819,10 +20752,15 @@ function gatherContextFiles(contextPaths, deltaFiles) {
19819
20752
  }
19820
20753
 
19821
20754
  // src/commands/analyze/phases/07-context-files.ts
19822
- async function contextFiles(run) {
19823
- const { codeDelta, contextFilePaths } = run;
19824
- const { kept: externalContext } = partitionVerityOwned(contextFilePaths ?? []);
19825
- const contextFiles2 = gatherContextFiles(externalContext, codeDelta.files);
20755
+ async function contextFiles(run2) {
20756
+ const { codeDelta } = run2;
20757
+ const readSet = readSetContextPaths(run2.actionSummary, codeDelta.files);
20758
+ const { kept: externalContext } = partitionVerityOwned(readSet);
20759
+ const ig = loadVerityIgnore();
20760
+ const unfenced = run2.verityIgnored.suspended ? externalContext : externalContext.filter((p) => !isIgnored(ig, p));
20761
+ const contextFiles2 = gatherContextFiles(unfenced, codeDelta.files, {
20762
+ maxFiles: MAX_FILES - codeDelta.files.length
20763
+ });
19826
20764
  for (const f of codeDelta.files) {
19827
20765
  f.role = "delta";
19828
20766
  }
@@ -19834,6 +20772,30 @@ async function contextFiles(run) {
19834
20772
  });
19835
20773
  }
19836
20774
 
20775
+ // src/commands/analyze/phases/07b-repo-context.ts
20776
+ async function repoContext(run2) {
20777
+ const { codeDelta, snapshotResult } = run2;
20778
+ const deltaFiles = codeDelta.files.filter((f) => f.role !== "context");
20779
+ const sentPaths = new Set(codeDelta.files.map((f) => f.path));
20780
+ const ig = loadVerityIgnore();
20781
+ const isExcluded = (p) => isVerityOwnedPath(p) || !run2.verityIgnored.suspended && isIgnored(ig, p);
20782
+ run2.repoContext = buildRepoContext({
20783
+ deltaFiles,
20784
+ diffs: snapshotResult.diffs,
20785
+ sentPaths,
20786
+ isExcluded
20787
+ });
20788
+ logEvent("repo_context", {
20789
+ state: run2.repoContext.state,
20790
+ reason: run2.repoContext.reason ?? null,
20791
+ symbols: run2.repoContext.symbols?.length ?? 0,
20792
+ dropped: run2.repoContext.dropped_symbols?.length ?? 0,
20793
+ callers: run2.repoContext.callers?.length ?? 0,
20794
+ tests: run2.repoContext.tests?.length ?? 0,
20795
+ elapsed_ms: run2.repoContext.elapsed_ms ?? null
20796
+ });
20797
+ }
20798
+
19837
20799
  // src/lib/seed-runner.ts
19838
20800
  var import_promises11 = require("node:fs/promises");
19839
20801
  var import_node_fs31 = require("node:fs");
@@ -20177,9 +21139,9 @@ async function runSeed(opts) {
20177
21139
  // src/commands/analyze/phases/08-memory-manifest.ts
20178
21140
  var import_node_fs32 = require("node:fs");
20179
21141
  var import_node_path24 = require("node:path");
20180
- async function memoryManifest(run) {
20181
- const { globals } = run;
20182
- const { serviceUrl, token } = run;
21142
+ async function memoryManifest(run2) {
21143
+ const { globals } = run2;
21144
+ const { serviceUrl, token } = run2;
20183
21145
  let memoryManifest2;
20184
21146
  let deletedNodePaths = [];
20185
21147
  let editedUploads = [];
@@ -20231,12 +21193,12 @@ async function memoryManifest(run) {
20231
21193
  editedUploads = await computeEditedNodeUploads();
20232
21194
  } catch {
20233
21195
  }
20234
- Object.assign(run, { autoSeedNotice, deletedNodePaths, editedUploads, memoryManifest: memoryManifest2 });
21196
+ Object.assign(run2, { autoSeedNotice, deletedNodePaths, editedUploads, memoryManifest: memoryManifest2 });
20235
21197
  }
20236
21198
 
20237
21199
  // src/commands/analyze/phases/09-fold-transcript.ts
20238
- async function foldTranscript(run) {
20239
- const { allForReview, earlyFold, transcriptPath } = run;
21200
+ async function foldTranscript(run2) {
21201
+ const { allForReview, earlyFold, transcriptPath } = run2;
20240
21202
  let foldResult = null;
20241
21203
  let foldConservation = null;
20242
21204
  if (transcriptPath) {
@@ -20253,7 +21215,7 @@ async function foldTranscript(run) {
20253
21215
  foldResult = null;
20254
21216
  }
20255
21217
  }
20256
- Object.assign(run, { foldConservation, foldResult });
21218
+ Object.assign(run2, { foldConservation, foldResult });
20257
21219
  }
20258
21220
 
20259
21221
  // src/lib/increment.ts
@@ -20305,10 +21267,10 @@ function computeIncrement(reviewedPaths, hashOf, priorAuthored) {
20305
21267
 
20306
21268
  // src/commands/analyze/phases/10-working-memory.ts
20307
21269
  var import_node_path25 = require("node:path");
20308
- async function workingMemory(run) {
20309
- const { opts } = run;
20310
- const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run;
20311
- let { reachability } = run;
21270
+ async function workingMemory(run2) {
21271
+ const { opts } = run2;
21272
+ const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run2;
21273
+ let { reachability } = run2;
20312
21274
  const memorySession = sessionDossier(token, sessionId ?? process.env.CLAUDE_SESSION_ID ?? null);
20313
21275
  let memory = null;
20314
21276
  let incrementReport = null;
@@ -20394,7 +21356,7 @@ async function workingMemory(run) {
20394
21356
  hasUserPrompt: (conversation?.prompts?.length ?? 0) > 0,
20395
21357
  isTTY: process.stdout.isTTY === true
20396
21358
  });
20397
- Object.assign(run, { incrementReport, memory, memorySession, reachability });
21359
+ Object.assign(run2, { incrementReport, memory, memorySession, reachability });
20398
21360
  }
20399
21361
 
20400
21362
  // src/lib/note-budget.ts
@@ -20467,7 +21429,7 @@ function isExplicitlyAutonomous(env = process.env) {
20467
21429
  }
20468
21430
 
20469
21431
  // src/lib/task-context.ts
20470
- var import_node_child_process9 = require("node:child_process");
21432
+ var import_node_child_process10 = require("node:child_process");
20471
21433
  var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
20472
21434
  var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
20473
21435
  function parseLinkedIssue(sources) {
@@ -20483,7 +21445,7 @@ function parseLinkedIssue(sources) {
20483
21445
  }
20484
21446
  function safeExec(cmd, timeout) {
20485
21447
  try {
20486
- return (0, import_node_child_process9.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
21448
+ return (0, import_node_child_process10.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
20487
21449
  } catch {
20488
21450
  return "";
20489
21451
  }
@@ -20513,8 +21475,8 @@ function resolveTaskContext(opts) {
20513
21475
  // src/commands/analyze/phases/11-build-request.ts
20514
21476
  var MAX_ASSISTANT_RESPONSE_CHARS_PLAN = 32768;
20515
21477
  var MAX_ASSISTANT_RESPONSE_CHARS_DEFAULT = 8e3;
20516
- async function buildRequest(run) {
20517
- const { actionSummary, allChanged, allForReview, analysisMode, analyzable, assistantResponse, codeDelta, conversation, deletedNodePaths, editedUploads, foldConservation, foldResult, incrementReport, iteration, memory, memoryManifest: memoryManifest2, memorySession, plans, reachability, reviewable, securityFiles, sessionId, snapshotResult, specs, staticResults, stopReason, turnId } = run;
21478
+ async function buildRequest(run2) {
21479
+ const { actionSummary, allChanged, allForReview, analysisMode, analyzable, assistantResponse, codeDelta, conversation, deletedNodePaths, editedUploads, foldConservation, foldResult, incrementReport, iteration, memory, memoryManifest: memoryManifest2, memorySession, plans, reachability, reviewable, securityFiles, sessionId, snapshotResult, specs, staticResults, stopReason, turnId } = run2;
20518
21480
  const excludedByReason = {};
20519
21481
  for (const e of codeDelta.excluded ?? []) {
20520
21482
  excludedByReason[e.reason] = (excludedByReason[e.reason] ?? 0) + 1;
@@ -20544,15 +21506,15 @@ async function buildRequest(run) {
20544
21506
  // the state, so the number is one turn lagged by construction. The
20545
21507
  // degenerate win for the budget is a dead channel that looks like clean
20546
21508
  // code; this is what makes "did delivery rate collapse" a query.
20547
- advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0,
21509
+ advisory_delivered_prior: readAdvisoryEpisode(run2.baselineSessionId)?.delivered ?? 0,
20548
21510
  // `.verityignore` — see CoverageTelemetry.verityignore for why the SHARE is
20549
21511
  // the number that matters and why no paths travel with it.
20550
21512
  verityignore: {
20551
- rules: run.verityIgnored.rules,
20552
- excluded: run.verityIgnored.ignored.length,
20553
- share: ignoreShare(run.verityIgnored.kept.length, run.verityIgnored.ignored.length),
20554
- security_excluded: run.verityIgnored.securityExcluded.length,
20555
- suspended: run.verityIgnored.suspended
21513
+ rules: run2.verityIgnored.rules,
21514
+ excluded: run2.verityIgnored.ignored.length,
21515
+ share: ignoreShare(run2.verityIgnored.kept.length, run2.verityIgnored.ignored.length),
21516
+ security_excluded: run2.verityIgnored.securityExcluded.length,
21517
+ suspended: run2.verityIgnored.suspended
20556
21518
  }
20557
21519
  };
20558
21520
  const requestBody = {
@@ -20690,6 +21652,9 @@ async function buildRequest(run) {
20690
21652
  if (snapshotResult.has_snapshots && snapshotResult.diffs.length > 0) {
20691
21653
  requestBody.snapshot_diffs = snapshotResult.diffs;
20692
21654
  }
21655
+ if (run2.repoContext) {
21656
+ requestBody.repo_context = run2.repoContext;
21657
+ }
20693
21658
  const noHumanPrompt = (conversation?.prompts?.length ?? 0) === 0;
20694
21659
  const w4Task = noHumanPrompt && isExplicitlyAutonomous() ? resolveTaskContext() : null;
20695
21660
  const planApprovalActive = foldResult?.planApproval?.activeSinceLastPrompt === true;
@@ -20747,7 +21712,7 @@ async function buildRequest(run) {
20747
21712
  }
20748
21713
  requestBody.intent_context = intentContext;
20749
21714
  }
20750
- Object.assign(run, { requestBody });
21715
+ Object.assign(run2, { requestBody });
20751
21716
  }
20752
21717
 
20753
21718
  // src/lib/offline.ts
@@ -20808,9 +21773,9 @@ function shouldWarmRetryAnalyze(result) {
20808
21773
  }
20809
21774
 
20810
21775
  // src/commands/analyze/phases/12-transmit.ts
20811
- async function transmit(run) {
20812
- const { globals } = run;
20813
- const { codeDelta, requestBody, serviceUrl, staticResults, token } = run;
21776
+ async function transmit(run2) {
21777
+ const { globals } = run2;
21778
+ const { codeDelta, requestBody, serviceUrl, staticResults, token } = run2;
20814
21779
  const ANALYZE_TIMEOUT_MS = 1e5;
20815
21780
  let result = await analyzeRequest({
20816
21781
  serviceUrl,
@@ -20873,15 +21838,34 @@ async function transmit(run) {
20873
21838
  }
20874
21839
  const response = result.data;
20875
21840
  const decision = response.gate_decision ?? "(unrecognised)";
20876
- Object.assign(run, { decision, response });
21841
+ Object.assign(run2, { decision, response });
20877
21842
  }
20878
21843
 
20879
21844
  // src/commands/analyze/phases/13-reconcile.ts
20880
21845
  var import_node_fs35 = require("node:fs");
20881
21846
  var import_node_path26 = require("node:path");
20882
- async function reconcile(run) {
20883
- const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
21847
+ async function reconcile(run2) {
21848
+ const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run2;
20884
21849
  const sentPaths = codeDelta.files.map((f) => f.path);
21850
+ if (memorySession) {
21851
+ try {
21852
+ const settledSites = response.metadata?.settled_sites;
21853
+ if (Array.isArray(settledSites)) {
21854
+ for (const site of settledSites) {
21855
+ const siteRecord = site;
21856
+ const file = typeof siteRecord?.file === "string" ? siteRecord.file : null;
21857
+ const patternId = typeof siteRecord?.pattern_id === "string" ? siteRecord.pattern_id : null;
21858
+ if (!file || !patternId) continue;
21859
+ appendEvent(memorySession.d, {
21860
+ k: "outcome",
21861
+ anchor_key: statementAnchorKey(file, patternId),
21862
+ outcome: "answered"
21863
+ });
21864
+ }
21865
+ }
21866
+ } catch {
21867
+ }
21868
+ }
20885
21869
  let openElsewhere = [];
20886
21870
  if (memorySession) {
20887
21871
  try {
@@ -20962,7 +21946,7 @@ async function reconcile(run) {
20962
21946
  // below is the signal that replaces the noise.
20963
21947
  //
20964
21948
  // Taken from the run, not recomputed — see context.ts `verityIgnored`.
20965
- ...run.verityIgnored.ignored.map((path) => ({
21949
+ ...run2.verityIgnored.ignored.map((path) => ({
20966
21950
  path,
20967
21951
  reason: "verityignore",
20968
21952
  stage: "verityignore",
@@ -21082,11 +22066,11 @@ async function reconcile(run) {
21082
22066
  `
21083
22067
  );
21084
22068
  }
21085
- Object.assign(run, { intentRepeatCount, openElsewhere, priorPendingFingerprints, reviewCoverage, sentPaths, silenced, watermarkHash, watermarkIsPartial });
22069
+ Object.assign(run2, { intentRepeatCount, openElsewhere, priorPendingFingerprints, reviewCoverage, sentPaths, silenced, watermarkHash, watermarkIsPartial });
21086
22070
  }
21087
22071
 
21088
22072
  // src/lib/emit.ts
21089
- var YELLOW2 = "\x1B[33m";
22073
+ var YELLOW3 = "\x1B[33m";
21090
22074
  var NC2 = "\x1B[0m";
21091
22075
  function emitVerdict(input) {
21092
22076
  const exit = input.exit ?? ((code) => process.exit(code));
@@ -21097,7 +22081,7 @@ function emitVerdict(input) {
21097
22081
  const note = [describeCoverage(coverage), describeOpenElsewhere(openElsewhere)].filter(Boolean).join("\n\n") || null;
21098
22082
  if (unaccounted.length > 0) {
21099
22083
  process.stderr.write(
21100
- `${YELLOW2}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
22084
+ `${YELLOW3}Verity: ${unaccounted.length} changed file(s) could not be attributed to any review stage \u2014 counted as unreviewed.${NC2}
21101
22085
  `
21102
22086
  );
21103
22087
  }
@@ -21109,7 +22093,7 @@ ${input.agentContext}
21109
22093
  `);
21110
22094
  }
21111
22095
  if (note && !input.silenced) process.stderr.write(`
21112
- ${YELLOW2}${note}${NC2}
22096
+ ${YELLOW3}${note}${NC2}
21113
22097
  `);
21114
22098
  return exit(2);
21115
22099
  }
@@ -21195,10 +22179,10 @@ function screenRemediation(fix, findingFile) {
21195
22179
  function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints = []) {
21196
22180
  return buildAgentContext(channelInputFrom(response, intentRepeat, priorPendingFingerprints));
21197
22181
  }
21198
- async function render(run) {
21199
- const { opts, globals } = run;
21200
- const { actionSummary, assistantResponse, autoSeedNotice, voidedIgnoreNotice, baselineSessionId, codeDelta, contentHash, conversation, currentCommit, decision, intentRepeatCount, memory, openElsewhere, priorPendingFingerprints, response, reviewCoverage, serviceUrl, sessionIdForMemory, silenced, token, watermarkHash, watermarkIsPartial } = run;
21201
- let { iteration } = run;
22182
+ async function render(run2) {
22183
+ const { opts, globals } = run2;
22184
+ const { actionSummary, assistantResponse, autoSeedNotice, voidedIgnoreNotice, baselineSessionId, codeDelta, contentHash, conversation, currentCommit, decision, intentRepeatCount, memory, openElsewhere, priorPendingFingerprints, response, reviewCoverage, serviceUrl, sessionIdForMemory, silenced, token, watermarkHash, watermarkIsPartial } = run2;
22185
+ let { iteration } = run2;
21202
22186
  const metadata = response.metadata ?? {};
21203
22187
  const intentAmbiguity = metadata.intent_ambiguity;
21204
22188
  if (intentAmbiguity != null && intentAmbiguity > 5) {
@@ -21305,7 +22289,7 @@ async function render(run) {
21305
22289
  const blocks = prior.blocks + 1;
21306
22290
  const decisionNow = mayBlock({
21307
22291
  reviewedFileCount: codeDelta.files.length,
21308
- staticFindingCount: run.staticResults?.findings?.length ?? 0,
22292
+ staticFindingCount: run2.staticResults?.findings?.length ?? 0,
21309
22293
  cycleCutFired: silenced !== null,
21310
22294
  attempts,
21311
22295
  blocks,
@@ -21336,7 +22320,7 @@ async function render(run) {
21336
22320
  });
21337
22321
  emitVerdict({
21338
22322
  proposed: "WARN",
21339
- changed: run.changedUniverse,
22323
+ changed: run2.changedUniverse,
21340
22324
  coverage: reviewCoverage,
21341
22325
  userSummary: lines.length > 0 ? `${summary}
21342
22326
  ${lines.join("\n")}` : summary,
@@ -21428,7 +22412,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
21428
22412
  `);
21429
22413
  emitVerdict({
21430
22414
  proposed: "FAIL",
21431
- changed: run.changedUniverse,
22415
+ changed: run2.changedUniverse,
21432
22416
  coverage: reviewCoverage,
21433
22417
  userSummary: "",
21434
22418
  // Subject to the SAME cycle cut as PASS/WARN. Suppressing here is safe:
@@ -21453,7 +22437,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
21453
22437
  userSummary += loginNudge + grantNudge;
21454
22438
  emitVerdict({
21455
22439
  proposed: "PASS",
21456
- changed: run.changedUniverse,
22440
+ changed: run2.changedUniverse,
21457
22441
  coverage: reviewCoverage,
21458
22442
  userSummary,
21459
22443
  agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
@@ -21475,7 +22459,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
21475
22459
  userSummary += loginNudge + grantNudge;
21476
22460
  emitVerdict({
21477
22461
  proposed: "WARN",
21478
- changed: run.changedUniverse,
22462
+ changed: run2.changedUniverse,
21479
22463
  coverage: reviewCoverage,
21480
22464
  userSummary,
21481
22465
  agentContext: silenced ? null : agentContextFor(response, intentRepeatCount, priorPendingFingerprints),
@@ -21500,7 +22484,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
21500
22484
  process.exit(0);
21501
22485
  }
21502
22486
  }
21503
- Object.assign(run, { iteration });
22487
+ Object.assign(run2, { iteration });
21504
22488
  }
21505
22489
 
21506
22490
  // src/commands/analyze/index.ts
@@ -21519,6 +22503,8 @@ var PIPELINE = [
21519
22503
  // ← THE NARROWING. what gets sent, and why not the rest
21520
22504
  ["contextFiles", contextFiles],
21521
22505
  // supporting files, merged INTO the delta array
22506
+ ["repoContext", repoContext],
22507
+ // R1/R3 — call sites of changed symbols, one line each
21522
22508
  ["memoryManifest", memoryManifest],
21523
22509
  // knowledge-graph manifest + one-time auto-seed
21524
22510
  ["foldTranscript", foldTranscript],
@@ -21535,7 +22521,7 @@ var PIPELINE = [
21535
22521
  // say it — stderr, stdout, disk
21536
22522
  ];
21537
22523
  function registerAnalyzeCommand(program2) {
21538
- program2.command("analyze").description("Run Verity analysis on changed files (stop hook)").option("--debounce <seconds>", "Skip if last analysis was within N seconds", "30").option("--max-iterations <n>", "Force PASS after N FAIL cycles", "2").option("--max-files <n>", "Max files to send for review", "20").option("--max-file-size <bytes>", "Skip files larger than N bytes", "51200").option("--max-total-size <bytes>", "Stop collecting files at N total bytes", "194560").option("--skip-static", "Skip codacy-analysis").option("--mode <mode>", "Force analysis mode (standard|plan|debug|skip)").option("--json", "Output raw JSON response").action(async (opts) => {
22524
+ program2.command("analyze").description("Run Verity analysis on changed files (stop hook)").option("--debounce <seconds>", "Skip if last analysis was within N seconds", String(DEBOUNCE_SECONDS)).option("--max-iterations <n>", "Force PASS after N FAIL cycles", String(MAX_ITERATIONS)).option("--max-files <n>", "Max files to send for review", String(MAX_FILES)).option("--max-file-size <bytes>", "Skip files larger than N bytes", String(MAX_FILE_BYTES)).option("--max-total-size <bytes>", "Stop collecting files at N total bytes", String(MAX_DELTA_BYTES)).option("--skip-static", "Skip codacy-analysis").option("--mode <mode>", "Force analysis mode (standard|plan|debug|skip)").option("--json", "Output raw JSON response").action(async (opts) => {
21539
22525
  const globals = program2.opts();
21540
22526
  try {
21541
22527
  await runAnalyze(opts, globals);
@@ -21550,18 +22536,18 @@ function registerAnalyzeCommand(program2) {
21550
22536
  }
21551
22537
  var tracing = () => process.env.VERITY_TRACE_PHASES === "1";
21552
22538
  async function runAnalyze(opts, globals) {
21553
- const run = createRun(opts, globals);
21554
- installRunEvidence(run);
22539
+ const run2 = createRun(opts, globals);
22540
+ installRunEvidence(run2);
21555
22541
  for (const [name, phase] of PIPELINE) {
21556
- run.phaseReached = name;
22542
+ run2.phaseReached = name;
21557
22543
  if (!tracing()) {
21558
- await phase(run);
21559
- run.phasesCompleted.push(name);
22544
+ await phase(run2);
22545
+ run2.phasesCompleted.push(name);
21560
22546
  continue;
21561
22547
  }
21562
22548
  const started = Date.now();
21563
- await phase(run);
21564
- run.phasesCompleted.push(name);
22549
+ await phase(run2);
22550
+ run2.phasesCompleted.push(name);
21565
22551
  process.stderr.write(`verity\xB7phase ${name} ${Date.now() - started}ms
21566
22552
  `);
21567
22553
  }
@@ -21664,8 +22650,8 @@ async function runReview(opts, globals) {
21664
22650
  for (const p of specPaths) {
21665
22651
  if (!(0, import_node_fs37.existsSync)(p)) continue;
21666
22652
  try {
21667
- const { readFileSync: readFileSync24 } = await import("node:fs");
21668
- const content = readFileSync24(p, "utf-8");
22653
+ const { readFileSync: readFileSync25 } = await import("node:fs");
22654
+ const content = readFileSync25(p, "utf-8");
21669
22655
  specs.push({ path: p, content: content.slice(0, 10240) });
21670
22656
  } catch {
21671
22657
  }
@@ -22000,6 +22986,39 @@ async function runGuard(opts, globals) {
22000
22986
  statedIntent,
22001
22987
  buildGuardCoverage(files, codeDelta, frame, range)
22002
22988
  );
22989
+ try {
22990
+ const ig = loadVerityIgnore();
22991
+ const repoContext2 = buildRepoContext({
22992
+ deltaFiles: codeDelta.files,
22993
+ diffs: [],
22994
+ signalsByPath: rangeChangeSignals(frame, range, codeDelta.files.map((f) => f.path)),
22995
+ sentPaths: new Set(codeDelta.files.map((f) => f.path)),
22996
+ isExcluded: (p) => isVerityOwnedPath(p) || isIgnored(ig, p),
22997
+ cwd: frame.worktreeRoot ?? process.cwd()
22998
+ });
22999
+ upgradeToExcerpts(repoContext2, {
23000
+ readFile: (rel) => {
23001
+ try {
23002
+ return (0, import_node_fs38.readFileSync)((0, import_node_path27.join)(frame.worktreeRoot ?? process.cwd(), rel), "utf8");
23003
+ } catch {
23004
+ return null;
23005
+ }
23006
+ }
23007
+ });
23008
+ requestBody.repo_context = repoContext2;
23009
+ logEvent("repo_context", {
23010
+ moment,
23011
+ state: repoContext2.state,
23012
+ reason: repoContext2.reason ?? null,
23013
+ symbols: repoContext2.symbols?.length ?? 0,
23014
+ callers: repoContext2.callers?.length ?? 0,
23015
+ tests: repoContext2.tests?.length ?? 0,
23016
+ excerpts: repoContext2.excerpts?.length ?? 0,
23017
+ elapsed_ms: repoContext2.elapsed_ms ?? null
23018
+ });
23019
+ } catch (e) {
23020
+ logEvent("repo_context", { moment, state: "absent", reason: "exception", message: e.message });
23021
+ }
22003
23022
  const coverage = {
22004
23023
  moment,
22005
23024
  root: frame.worktreeRoot,
@@ -22254,22 +23273,297 @@ function registerWaiveCommand(program2) {
22254
23273
  }
22255
23274
 
22256
23275
  // src/commands/init.ts
22257
- var import_node_fs41 = require("node:fs");
22258
- var import_promises13 = require("node:fs/promises");
23276
+ var import_node_fs44 = require("node:fs");
23277
+ var import_promises14 = require("node:fs/promises");
22259
23278
  var import_node_path29 = require("node:path");
23279
+ var import_node_child_process14 = require("node:child_process");
23280
+
23281
+ // src/lib/banner.ts
23282
+ var WORDMARK = [
23283
+ "\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
23284
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D",
23285
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D ",
23286
+ "\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D ",
23287
+ " \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
23288
+ " \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "
23289
+ ];
23290
+ var WORDMARK_NARROW = [
23291
+ "\u2588 \u2588 \u2588\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2584 \u2580\u2580\u2588\u2580\u2580 \u2580\u2580\u2588\u2580\u2580 \u2588 \u2588",
23292
+ "\u2588 \u2588 \u2588\u2580\u2580\u2580 \u2588\u2584\u2584\u2584\u2580 \u2588 \u2588 \u2580\u2584\u2580 ",
23293
+ " \u2580\u2584\u2580 \u2588\u2584\u2584\u2584\u2584 \u2588 \u2580\u2584 \u2584\u2584\u2588\u2584\u2584 \u2588 \u2588 "
23294
+ ];
23295
+ var TAGLINE = "reviews every turn, remembers every lesson";
23296
+ var WORDMARK_COLOR = ["\x1B[0;32m"];
23297
+ var DIM3 = "\x1B[2m";
23298
+ var RESET2 = "\x1B[0m";
23299
+ var INDENT = " ";
23300
+ function artWidth(art) {
23301
+ return Math.max(...art.map((r) => r.length)) + INDENT.length;
23302
+ }
23303
+ function canRenderArt() {
23304
+ return !!process.stderr.isTTY;
23305
+ }
23306
+ function printBanner(opts) {
23307
+ if (!opts.interactive) return;
23308
+ const columns = opts.columns ?? process.stderr.columns ?? 80;
23309
+ const color = colorEnabled();
23310
+ const version = `v${cliVersion()}`;
23311
+ const art = [WORDMARK, WORDMARK_NARROW].find((a) => artWidth(a) <= columns) ?? null;
23312
+ const dim = (text) => color ? `${DIM3}${text}${RESET2}` : text;
23313
+ process.stderr.write("\n");
23314
+ if (!art) {
23315
+ const candidates = [`Verity ${version}`, "Verity"];
23316
+ const text = candidates.find((c) => c.length + INDENT.length <= columns);
23317
+ if (text) process.stderr.write(`${INDENT}${dim(text)}
23318
+ `);
23319
+ process.stderr.write("\n");
23320
+ return;
23321
+ }
23322
+ art.forEach((row2, i) => {
23323
+ const tint = color && WORDMARK_COLOR.length > 0 ? WORDMARK_COLOR[Math.min(i, WORDMARK_COLOR.length - 1)] : "";
23324
+ const reset = color && tint ? RESET2 : "";
23325
+ process.stderr.write(`${INDENT}${tint}${row2}${reset}
23326
+ `);
23327
+ });
23328
+ if (TAGLINE.length + INDENT.length <= columns) {
23329
+ process.stderr.write(`${INDENT}${dim(TAGLINE)}
23330
+ `);
23331
+ }
23332
+ if (version.length + INDENT.length <= columns) {
23333
+ process.stderr.write(`${INDENT}${dim(version)}
23334
+ `);
23335
+ }
23336
+ process.stderr.write("\n");
23337
+ }
23338
+ function printPhase(n, of, title, subtitle) {
23339
+ const color = colorEnabled();
23340
+ const columns = process.stderr.columns ?? 72;
23341
+ const width = Math.min(columns, 72);
23342
+ const label2 = `\u2500\u2500 Phase ${n} of ${of} \xB7 ${title} `;
23343
+ const rule = label2 + "\u2500".repeat(Math.max(0, width - label2.length - 2));
23344
+ process.stderr.write("\n");
23345
+ process.stderr.write(color ? ` ${DIM3}${rule}${RESET2}
23346
+ ` : ` ${rule}
23347
+ `);
23348
+ if (subtitle && subtitle.length + INDENT.length <= columns) {
23349
+ process.stderr.write(color ? ` ${DIM3}${subtitle}${RESET2}
23350
+ ` : ` ${subtitle}
23351
+ `);
23352
+ }
23353
+ process.stderr.write("\n");
23354
+ }
23355
+
23356
+ // src/commands/doctor.ts
23357
+ var import_node_fs42 = require("node:fs");
23358
+
23359
+ // src/lib/prereqs.ts
22260
23360
  var import_node_child_process11 = require("node:child_process");
22261
- var readline2 = __toESM(require("node:readline/promises"));
23361
+ var MIN_NODE_MAJOR = 20;
23362
+ function which(bin) {
23363
+ try {
23364
+ const out = (0, import_node_child_process11.execSync)(`command -v ${bin}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
23365
+ return out || null;
23366
+ } catch {
23367
+ return null;
23368
+ }
23369
+ }
23370
+ function checkNode() {
23371
+ const version = process.version;
23372
+ const major = Number.parseInt(version.slice(1), 10);
23373
+ const ok = Number.isFinite(major) && major >= MIN_NODE_MAJOR;
23374
+ return {
23375
+ id: "node",
23376
+ label: "Node.js",
23377
+ status: ok ? "ok" : "missing",
23378
+ detail: version,
23379
+ remedy: ok ? void 0 : `Node.js ${MIN_NODE_MAJOR}+ required \u2014 update from https://nodejs.org`,
23380
+ required: true
23381
+ };
23382
+ }
23383
+ function checkGit() {
23384
+ let detail = "";
23385
+ try {
23386
+ detail = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
23387
+ } catch {
23388
+ return {
23389
+ id: "git",
23390
+ label: "git",
23391
+ status: "missing",
23392
+ detail: "not found",
23393
+ remedy: "Install git from https://git-scm.com",
23394
+ required: true
23395
+ };
23396
+ }
23397
+ return { id: "git", label: "git", status: "ok", detail, required: true };
23398
+ }
23399
+ function checkClaude() {
23400
+ const path = which("claude");
23401
+ return {
23402
+ id: "claude",
23403
+ label: "Claude Code",
23404
+ status: path ? "ok" : "warn",
23405
+ detail: path ?? "not found",
23406
+ remedy: path ? void 0 : "Hooks are wired but need Claude Code to fire \u2014 https://claude.com/claude-code",
23407
+ required: false
23408
+ };
23409
+ }
23410
+ function checkAnalysisCli() {
23411
+ const path = which("codacy-analysis");
23412
+ return {
23413
+ id: "analysis-cli",
23414
+ label: "@codacy/analysis-cli",
23415
+ status: path ? "ok" : "warn",
23416
+ detail: path ?? "not found",
23417
+ remedy: path ? void 0 : "npm install -g @codacy/analysis-cli (static findings are unavailable until then)",
23418
+ required: false
23419
+ };
23420
+ }
23421
+ var INSTALL_TIMEOUT_MS = 12e4;
23422
+ function run(command, args, opts = {}) {
23423
+ return new Promise((resolve4) => {
23424
+ const child = (0, import_node_child_process11.spawn)(command, args, {
23425
+ stdio: opts.inherit ? "inherit" : "pipe",
23426
+ timeout: INSTALL_TIMEOUT_MS
23427
+ });
23428
+ child.on("error", () => resolve4(false));
23429
+ child.on("close", (code) => resolve4(code === 0));
23430
+ });
23431
+ }
23432
+ async function installAnalysisCli() {
23433
+ const spinner = startSpinner("Installing @codacy/analysis-cli");
23434
+ const ok = await run("npm", ["install", "-g", "@codacy/analysis-cli"]);
23435
+ if (ok) {
23436
+ const check = checkAnalysisCli();
23437
+ if (check.status === "ok") {
23438
+ spinner.succeed("@codacy/analysis-cli installed");
23439
+ return { ...check, justInstalled: true };
23440
+ }
23441
+ spinner.warn("npm reported success but codacy-analysis is not on PATH");
23442
+ return check;
23443
+ }
23444
+ spinner.stop();
23445
+ {
23446
+ const failed = {
23447
+ id: "analysis-cli",
23448
+ label: "@codacy/analysis-cli",
23449
+ status: "warn",
23450
+ detail: "install failed",
23451
+ remedy: "Install manually: npm install -g @codacy/analysis-cli",
23452
+ required: false
23453
+ };
23454
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return failed;
23455
+ process.stderr.write(" Retrying with sudo (you may be asked for your password)\u2026\n");
23456
+ const sudoOk = await run("sudo", ["npm", "install", "-g", "@codacy/analysis-cli"], { inherit: true });
23457
+ if (!sudoOk) return failed;
23458
+ const check = checkAnalysisCli();
23459
+ return check.status === "ok" ? { ...check, justInstalled: true } : check;
23460
+ }
23461
+ }
23462
+ async function checkPrereqs(opts = {}) {
23463
+ const checks = [checkNode(), checkGit(), checkClaude()];
23464
+ let analysis = checkAnalysisCli();
23465
+ if (analysis.status !== "ok" && opts.install) {
23466
+ analysis = await installAnalysisCli();
23467
+ }
23468
+ checks.push(analysis);
23469
+ return { checks, blocked: checks.some((c) => c.required && c.status !== "ok") };
23470
+ }
22262
23471
 
22263
- // src/commands/migrate.ts
23472
+ // src/lib/telemetry.ts
23473
+ var import_promises12 = require("node:fs/promises");
23474
+
23475
+ // src/lib/gitignore.ts
23476
+ var import_node_child_process12 = require("node:child_process");
22264
23477
  var import_node_fs40 = require("node:fs");
22265
- var import_node_path28 = require("node:path");
22266
- var import_node_child_process10 = require("node:child_process");
23478
+ var VERITY_GITIGNORE_MARKER = "# Verity \u2014 machine-local state.";
23479
+ var SETTINGS_LOCAL_IGNORE_ENTRY = ".claude/settings.local.json";
23480
+ var VERITY_GITIGNORE_BLOCK = [
23481
+ "# Verity \u2014 machine-local state. Everything in .verity/ is ignored EXCEPT the",
23482
+ "# shared standard and the knowledge graph, which are meant to be committed.",
23483
+ ".verity/*",
23484
+ "!.verity/standard.yaml",
23485
+ "!.verity/memory/",
23486
+ ".verity/memory/log.md",
23487
+ SETTINGS_LOCAL_IGNORE_ENTRY,
23488
+ ""
23489
+ ].join("\n");
23490
+ var BREAKING_ENTRIES = /* @__PURE__ */ new Set([".verity/", ".verity"]);
23491
+ function isIgnored2(path) {
23492
+ try {
23493
+ (0, import_node_child_process12.execSync)(`git check-ignore -q -- "${path}"`, { stdio: "pipe" });
23494
+ return true;
23495
+ } catch (err) {
23496
+ return err.status === 1 ? false : null;
23497
+ }
23498
+ }
23499
+ function semanticsHold() {
23500
+ const snapshot = isIgnored2(".verity/.snapshot/__probe__");
23501
+ const standard = isIgnored2(".verity/standard.yaml");
23502
+ const node = isIgnored2(".verity/memory/domain/__probe__.md");
23503
+ const futureState = isIgnored2(".verity/.__probe-future-state__");
23504
+ if (snapshot === null || standard === null || node === null || futureState === null) return null;
23505
+ return snapshot === true && futureState === true && standard === false && node === false;
23506
+ }
23507
+ function ensureVerityGitignore() {
23508
+ let content = "";
23509
+ try {
23510
+ content = (0, import_node_fs40.readFileSync)(".gitignore", "utf-8");
23511
+ } catch {
23512
+ }
23513
+ const hasMarker = content.includes(VERITY_GITIGNORE_MARKER);
23514
+ const lines = content.split("\n");
23515
+ const breakingCount = lines.filter((l) => BREAKING_ENTRIES.has(l.trim())).length;
23516
+ const needsRepair = breakingCount > 0;
23517
+ const verified = (result) => semanticsHold() === false ? "conflict" : result;
23518
+ if (hasMarker && !needsRepair) return verified("covered");
23519
+ if (!hasMarker && !needsRepair) {
23520
+ if (semanticsHold() === true) return "covered";
23521
+ }
23522
+ try {
23523
+ let next = content;
23524
+ if (needsRepair) {
23525
+ next = lines.map((l) => BREAKING_ENTRIES.has(l.trim()) ? ".verity/*" : l).join("\n");
23526
+ }
23527
+ if (!hasMarker) {
23528
+ const sep2 = next === "" ? "" : next.endsWith("\n") ? "\n" : "\n\n";
23529
+ next = next + sep2 + VERITY_GITIGNORE_BLOCK;
23530
+ }
23531
+ (0, import_node_fs40.writeFileSync)(".gitignore", next);
23532
+ return verified(needsRepair ? "repaired" : "added");
23533
+ } catch {
23534
+ return "failed";
23535
+ }
23536
+ }
23537
+ function committedVerityState() {
23538
+ let out = "";
23539
+ try {
23540
+ out = (0, import_node_child_process12.execSync)("git ls-files -z -- .verity", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
23541
+ } catch {
23542
+ return [];
23543
+ }
23544
+ return out.split("\0").filter(Boolean).filter((p) => p !== ".verity/standard.yaml" && !(p.startsWith(".verity/memory/") && p !== ".verity/memory/log.md"));
23545
+ }
23546
+ function untrackVerityState() {
23547
+ const tracked = committedVerityState();
23548
+ if (tracked.length === 0) return "none";
23549
+ try {
23550
+ (0, import_node_child_process12.execSync)("git rm -r --cached --quiet -- .verity", { stdio: "pipe" });
23551
+ for (const keep of [".verity/standard.yaml", ".verity/memory"]) {
23552
+ try {
23553
+ (0, import_node_child_process12.execSync)(`git add -- "${keep}"`, { stdio: "pipe" });
23554
+ } catch {
23555
+ }
23556
+ }
23557
+ return "untracked";
23558
+ } catch {
23559
+ return "failed";
23560
+ }
23561
+ }
22267
23562
 
22268
23563
  // src/lib/telemetry.ts
22269
- var import_promises12 = require("node:fs/promises");
22270
23564
  var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
22271
23565
  var GITIGNORE_FILE = ".gitignore";
22272
- var GITIGNORE_ENTRY = ".claude/settings.local.json";
23566
+ var GITIGNORE_ENTRY = SETTINGS_LOCAL_IGNORE_ENTRY;
22273
23567
  var OTEL_HEADERS_HELPER_CMD = "verity telemetry headers";
22274
23568
  var LEGACY_TELEMETRY_ENV_KEYS = ["OTEL_EXPORTER_OTLP_HEADERS"];
22275
23569
  function deriveOtlpEndpoint(serviceUrl) {
@@ -22355,14 +23649,119 @@ async function uninstallTelemetry() {
22355
23649
  return { ok: true, data: { removed } };
22356
23650
  }
22357
23651
 
23652
+ // src/lib/setup-state.ts
23653
+ var import_promises13 = require("node:fs/promises");
23654
+ var import_node_fs41 = require("node:fs");
23655
+ var SETUP_STATE_FILE = `${VERITY_DIR}/setup.json`;
23656
+ async function readSetupState() {
23657
+ const path = projectPath(SETUP_STATE_FILE);
23658
+ if (!(0, import_node_fs41.existsSync)(path)) return null;
23659
+ try {
23660
+ const parsed = JSON.parse(await (0, import_promises13.readFile)(path, "utf-8"));
23661
+ return parsed && typeof parsed === "object" ? parsed : null;
23662
+ } catch {
23663
+ return null;
23664
+ }
23665
+ }
23666
+ async function writeSetupState(patch) {
23667
+ const current = await readSetupState() ?? { version: 1 };
23668
+ const next = { ...current, ...patch, version: 1 };
23669
+ await writeJsonFilePreservingStyle(projectPath(SETUP_STATE_FILE), next);
23670
+ return next;
23671
+ }
23672
+
23673
+ // src/commands/doctor.ts
23674
+ async function buildReport() {
23675
+ const prereqs = await checkPrereqs({ install: false });
23676
+ const state = await readSetupState();
23677
+ const hooks = await checkAllVerityHooks();
23678
+ const telemetry = await checkTelemetry();
23679
+ const artifacts = {
23680
+ standard: (0, import_node_fs42.existsSync)(projectPath(STANDARD_FILE)),
23681
+ analysisConfig: (0, import_node_fs42.existsSync)(projectPath(CODACY_CONFIG_FILE)),
23682
+ verityMd: (0, import_node_fs42.existsSync)(projectPath(VERITY_MD_FILE))
23683
+ };
23684
+ const next = [];
23685
+ for (const c of prereqs.checks) {
23686
+ if (c.status !== "ok" && c.remedy) next.push(c.remedy);
23687
+ }
23688
+ if (!state?.init) next.push('Run "verity init" \u2014 the deterministic setup phase has not completed here.');
23689
+ if (!artifacts.standard || !artifacts.analysisConfig || !artifacts.verityMd) {
23690
+ next.push("Run /verity-setup in Claude Code \u2014 the Standard, analysis config, and VERITY.md are synthesized there.");
23691
+ }
23692
+ const noAnalysisMoment = !hooks.stop && hooks.guardOn.length === 0;
23693
+ if (noAnalysisMoment) {
23694
+ next.push('No analysis moment is active \u2014 run "verity hooks install --moments stop" or re-run "verity init".');
23695
+ }
23696
+ if (state?.telemetry === "deferred") {
23697
+ next.push('Telemetry was requested but needs a token \u2014 run "verity login", then "verity telemetry install".');
23698
+ }
23699
+ return {
23700
+ prerequisites: prereqs.checks,
23701
+ blocked: prereqs.blocked,
23702
+ phases: {
23703
+ init: { done: !!state?.init, ...state?.init ?? {} },
23704
+ setup: { done: artifacts.standard && artifacts.analysisConfig && artifacts.verityMd }
23705
+ },
23706
+ answers: {
23707
+ intensity: state?.intensity ?? null,
23708
+ moments: state?.moments ?? null,
23709
+ telemetry: state?.telemetry ?? null
23710
+ },
23711
+ hooks: { ...hooks, noAnalysisMoment },
23712
+ telemetry: { enabled: telemetry.enabled, endpoint: telemetry.endpoint },
23713
+ artifacts,
23714
+ next
23715
+ };
23716
+ }
23717
+ function registerDoctorCommand(program2) {
23718
+ program2.command("doctor").description("Report prerequisites, setup phase, hooks, and what is still missing").option("--json", "Output the full report as JSON (what /verity-setup reads)").action(async (opts) => {
23719
+ const report = await buildReport();
23720
+ if (opts.json) {
23721
+ printJson(report);
23722
+ if (report.blocked) process.exit(1);
23723
+ return;
23724
+ }
23725
+ printInfo("Prerequisites:");
23726
+ for (const c of report.prerequisites) {
23727
+ if (c.status === "ok") printInfo(` ${c.label} ${c.detail} \u2713`);
23728
+ else printWarn(` ${c.label}: ${c.detail}${c.remedy ? ` \u2014 ${c.remedy}` : ""}`);
23729
+ }
23730
+ printInfo("Setup:");
23731
+ printInfo(` verity init: ${report.phases.init.done ? `done ${report.phases.init.completed_at ?? ""}`.trim() : "NOT run here"}`);
23732
+ printInfo(` /verity-setup: ${report.phases.setup.done ? "done" : "not completed"}`);
23733
+ printInfo(` intensity: ${report.answers.intensity ?? "\u2014"} moments: ${report.answers.moments?.join(", ") ?? "\u2014"}`);
23734
+ printInfo("Hooks:");
23735
+ printInfo(` Stop (verity analyze): ${report.hooks.stop ? "on" : "off"}`);
23736
+ printInfo(` Git-moment gate: ${report.hooks.guardOn.length ? report.hooks.guardOn.join(", ") : "off"}`);
23737
+ printInfo(` Infra (intent/baseline/compact/session-end): ${[report.hooks.intent, report.hooks.baseline, report.hooks.compact, report.hooks.sessionEnd].filter(Boolean).length}/4`);
23738
+ printInfo(`Telemetry: ${report.telemetry.enabled ? `enabled \u2192 ${report.telemetry.endpoint}` : "disabled"}`);
23739
+ printInfo("Artifacts:");
23740
+ printInfo(` .verity/standard.yaml: ${report.artifacts.standard ? "\u2713" : "missing"}`);
23741
+ printInfo(` .codacy/codacy.config.json: ${report.artifacts.analysisConfig ? "\u2713" : "missing"}`);
23742
+ printInfo(` VERITY.md: ${report.artifacts.verityMd ? "\u2713" : "missing"}`);
23743
+ if (report.next.length > 0) {
23744
+ console.log("");
23745
+ printWarn("Next:");
23746
+ for (const n of report.next) printWarn(` - ${n}`);
23747
+ } else {
23748
+ printInfo("Setup is complete.");
23749
+ }
23750
+ if (report.blocked) process.exit(1);
23751
+ });
23752
+ }
23753
+
22358
23754
  // src/commands/migrate.ts
23755
+ var import_node_fs43 = require("node:fs");
23756
+ var import_node_path28 = require("node:path");
23757
+ var import_node_child_process13 = require("node:child_process");
22359
23758
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
22360
23759
  function defaultNpmRemover(pkg) {
22361
- (0, import_node_child_process10.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
23760
+ (0, import_node_child_process13.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
22362
23761
  }
22363
23762
  function isGitTracked(cwd, relPath) {
22364
23763
  try {
22365
- (0, import_node_child_process10.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
23764
+ (0, import_node_child_process13.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
22366
23765
  return true;
22367
23766
  } catch {
22368
23767
  return false;
@@ -22370,7 +23769,7 @@ function isGitTracked(cwd, relPath) {
22370
23769
  }
22371
23770
  function isGitRepo(cwd) {
22372
23771
  try {
22373
- (0, import_node_child_process10.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
23772
+ (0, import_node_child_process13.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
22374
23773
  return true;
22375
23774
  } catch {
22376
23775
  return false;
@@ -22393,10 +23792,10 @@ async function runMigration(opts = {}) {
22393
23792
  function migrateProjectDir(root, actions) {
22394
23793
  const gateDir = (0, import_node_path28.join)(root, ".gate");
22395
23794
  const verityDir = (0, import_node_path28.join)(root, ".verity");
22396
- if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) {
23795
+ if ((0, import_node_fs43.existsSync)(gateDir) && !(0, import_node_fs43.existsSync)(verityDir)) {
22397
23796
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
22398
23797
  }
22399
- if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
23798
+ if ((0, import_node_fs43.existsSync)(gateDir) && (0, import_node_fs43.existsSync)(verityDir)) {
22400
23799
  return migrateProjectDirCarry(gateDir, verityDir, actions);
22401
23800
  }
22402
23801
  return false;
@@ -22410,20 +23809,20 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
22410
23809
  );
22411
23810
  }
22412
23811
  try {
22413
- (0, import_node_child_process10.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
23812
+ (0, import_node_child_process13.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
22414
23813
  actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
22415
23814
  moved = true;
22416
23815
  } catch {
22417
23816
  }
22418
23817
  }
22419
23818
  if (moved) {
22420
- if ((0, import_node_fs40.existsSync)(gateDir)) {
23819
+ if ((0, import_node_fs43.existsSync)(gateDir)) {
22421
23820
  const carried = carryLegacyContents(gateDir, verityDir);
22422
23821
  if (carried > 0) {
22423
23822
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
22424
23823
  }
22425
23824
  try {
22426
- (0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
23825
+ (0, import_node_fs43.rmSync)(gateDir, { recursive: true, force: true });
22427
23826
  } catch {
22428
23827
  }
22429
23828
  }
@@ -22439,7 +23838,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
22439
23838
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
22440
23839
  }
22441
23840
  try {
22442
- (0, import_node_fs40.rmSync)(gateDir, { recursive: true, force: true });
23841
+ (0, import_node_fs43.rmSync)(gateDir, { recursive: true, force: true });
22443
23842
  } catch {
22444
23843
  }
22445
23844
  return carried > 0;
@@ -22448,9 +23847,9 @@ function migrateGlobalCredentials(home, actions) {
22448
23847
  if (!home) return;
22449
23848
  const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
22450
23849
  const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
22451
- if (!(0, import_node_fs40.existsSync)(gateCreds)) return;
22452
- if (!(0, import_node_fs40.existsSync)(verityCreds)) {
22453
- (0, import_node_fs40.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
23850
+ if (!(0, import_node_fs43.existsSync)(gateCreds)) return;
23851
+ if (!(0, import_node_fs43.existsSync)(verityCreds)) {
23852
+ (0, import_node_fs43.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
22454
23853
  moveFile(gateCreds, verityCreds);
22455
23854
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
22456
23855
  return;
@@ -22473,7 +23872,7 @@ async function migrateLegacyHooks(root, actions) {
22473
23872
  }
22474
23873
  async function migrateClaudeMd(root, actions) {
22475
23874
  const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
22476
- const hadLegacyBlock = (0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
23875
+ const hadLegacyBlock = (0, import_node_fs43.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
22477
23876
  if (!hadLegacyBlock) return;
22478
23877
  try {
22479
23878
  await ensureClaudeMdPointer(root);
@@ -22485,11 +23884,11 @@ async function migrateClaudeMd(root, actions) {
22485
23884
  function migrateStandardFile(root, actions) {
22486
23885
  const gateMd = (0, import_node_path28.join)(root, "GATE.md");
22487
23886
  const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
22488
- if (!(0, import_node_fs40.existsSync)(gateMd) || (0, import_node_fs40.existsSync)(verityMd)) return;
23887
+ if (!(0, import_node_fs43.existsSync)(gateMd) || (0, import_node_fs43.existsSync)(verityMd)) return;
22489
23888
  let moved = false;
22490
23889
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
22491
23890
  try {
22492
- (0, import_node_child_process10.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
23891
+ (0, import_node_child_process13.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
22493
23892
  moved = true;
22494
23893
  } catch {
22495
23894
  }
@@ -22497,12 +23896,12 @@ function migrateStandardFile(root, actions) {
22497
23896
  if (!moved) moveFile(gateMd, verityMd);
22498
23897
  const content = readFileSyncSafe(verityMd);
22499
23898
  const refreshed = content.split("GATE.md").join("VERITY.md");
22500
- if (refreshed !== content) (0, import_node_fs40.writeFileSync)(verityMd, refreshed);
23899
+ if (refreshed !== content) (0, import_node_fs43.writeFileSync)(verityMd, refreshed);
22501
23900
  actions.push("Renamed GATE.md \u2192 VERITY.md");
22502
23901
  }
22503
23902
  async function migrateTelemetryHeaders(root, actions) {
22504
23903
  const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
22505
- if (!(0, import_node_fs40.existsSync)(file)) return;
23904
+ if (!(0, import_node_fs43.existsSync)(file)) return;
22506
23905
  let settings;
22507
23906
  try {
22508
23907
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -22550,21 +23949,21 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
22550
23949
  }
22551
23950
  if (toAppend.length > 0) {
22552
23951
  const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
22553
- (0, import_node_fs40.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
23952
+ (0, import_node_fs43.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
22554
23953
  }
22555
- (0, import_node_fs40.rmSync)(gateCreds, { force: true });
23954
+ (0, import_node_fs43.rmSync)(gateCreds, { force: true });
22556
23955
  return toAppend.length;
22557
23956
  }
22558
23957
  function readFileSyncSafe(path) {
22559
23958
  try {
22560
- return (0, import_node_fs40.readFileSync)(path, "utf-8");
23959
+ return (0, import_node_fs43.readFileSync)(path, "utf-8");
22561
23960
  } catch {
22562
23961
  return "";
22563
23962
  }
22564
23963
  }
22565
23964
  function hasStagedChanges(root) {
22566
23965
  try {
22567
- (0, import_node_child_process10.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
23966
+ (0, import_node_child_process13.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
22568
23967
  return false;
22569
23968
  } catch {
22570
23969
  return true;
@@ -22572,35 +23971,35 @@ function hasStagedChanges(root) {
22572
23971
  }
22573
23972
  function moveDir(from, to) {
22574
23973
  try {
22575
- (0, import_node_fs40.renameSync)(from, to);
23974
+ (0, import_node_fs43.renameSync)(from, to);
22576
23975
  } catch (err) {
22577
23976
  if (err.code !== "EXDEV") throw err;
22578
- (0, import_node_fs40.cpSync)(from, to, { recursive: true });
22579
- (0, import_node_fs40.rmSync)(from, { recursive: true, force: true });
23977
+ (0, import_node_fs43.cpSync)(from, to, { recursive: true });
23978
+ (0, import_node_fs43.rmSync)(from, { recursive: true, force: true });
22580
23979
  }
22581
23980
  }
22582
23981
  function moveFile(from, to) {
22583
23982
  try {
22584
- (0, import_node_fs40.renameSync)(from, to);
23983
+ (0, import_node_fs43.renameSync)(from, to);
22585
23984
  } catch (err) {
22586
23985
  if (err.code !== "EXDEV") throw err;
22587
- (0, import_node_fs40.cpSync)(from, to);
22588
- (0, import_node_fs40.rmSync)(from, { force: true });
23986
+ (0, import_node_fs43.cpSync)(from, to);
23987
+ (0, import_node_fs43.rmSync)(from, { force: true });
22589
23988
  }
22590
23989
  }
22591
23990
  function carryLegacyContents(gateDir, verityDir) {
22592
23991
  let copied = 0;
22593
23992
  const walk = (relDir) => {
22594
23993
  const srcDir = (0, import_node_path28.join)(gateDir, relDir);
22595
- for (const entry of (0, import_node_fs40.readdirSync)(srcDir)) {
23994
+ for (const entry of (0, import_node_fs43.readdirSync)(srcDir)) {
22596
23995
  const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
22597
23996
  const src = (0, import_node_path28.join)(gateDir, rel);
22598
23997
  const dest = (0, import_node_path28.join)(verityDir, rel);
22599
- if ((0, import_node_fs40.statSync)(src).isDirectory()) {
23998
+ if ((0, import_node_fs43.statSync)(src).isDirectory()) {
22600
23999
  walk(rel);
22601
- } else if (!(0, import_node_fs40.existsSync)(dest)) {
22602
- (0, import_node_fs40.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
22603
- (0, import_node_fs40.cpSync)(src, dest);
24000
+ } else if (!(0, import_node_fs43.existsSync)(dest)) {
24001
+ (0, import_node_fs43.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
24002
+ (0, import_node_fs43.cpSync)(src, dest);
22604
24003
  copied++;
22605
24004
  }
22606
24005
  }
@@ -22611,20 +24010,20 @@ function carryLegacyContents(gateDir, verityDir) {
22611
24010
  async function needsMigration(root = repoRoot()) {
22612
24011
  const gateDir = (0, import_node_path28.join)(root, ".gate");
22613
24012
  const verityDir = (0, import_node_path28.join)(root, ".verity");
22614
- if ((0, import_node_fs40.existsSync)(gateDir) && !(0, import_node_fs40.existsSync)(verityDir)) return true;
22615
- if ((0, import_node_fs40.existsSync)(gateDir) && (0, import_node_fs40.existsSync)(verityDir)) {
22616
- if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(gateDir, "credentials")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(verityDir, "credentials"))) {
24013
+ if ((0, import_node_fs43.existsSync)(gateDir) && !(0, import_node_fs43.existsSync)(verityDir)) return true;
24014
+ if ((0, import_node_fs43.existsSync)(gateDir) && (0, import_node_fs43.existsSync)(verityDir)) {
24015
+ if ((0, import_node_fs43.existsSync)((0, import_node_path28.join)(gateDir, "credentials")) && !(0, import_node_fs43.existsSync)((0, import_node_path28.join)(verityDir, "credentials"))) {
22617
24016
  return true;
22618
24017
  }
22619
- if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(gateDir, "memory")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(verityDir, "memory"))) {
24018
+ if ((0, import_node_fs43.existsSync)((0, import_node_path28.join)(gateDir, "memory")) && !(0, import_node_fs43.existsSync)((0, import_node_path28.join)(verityDir, "memory"))) {
22620
24019
  return true;
22621
24020
  }
22622
24021
  }
22623
24022
  const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
22624
- if ((0, import_node_fs40.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
24023
+ if ((0, import_node_fs43.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
22625
24024
  return true;
22626
24025
  }
22627
- if ((0, import_node_fs40.existsSync)((0, import_node_path28.join)(root, "GATE.md")) && !(0, import_node_fs40.existsSync)((0, import_node_path28.join)(root, "VERITY.md"))) {
24026
+ if ((0, import_node_fs43.existsSync)((0, import_node_path28.join)(root, "GATE.md")) && !(0, import_node_fs43.existsSync)((0, import_node_path28.join)(root, "VERITY.md"))) {
22628
24027
  return true;
22629
24028
  }
22630
24029
  if (await hasLegacyHooksAt(root)) return true;
@@ -22649,17 +24048,96 @@ function registerMigrateCommand(program2) {
22649
24048
  });
22650
24049
  }
22651
24050
 
22652
- // src/commands/init.ts
22653
- async function promptYes(question) {
22654
- if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
22655
- const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
24051
+ // src/lib/prompt.ts
24052
+ var readline2 = __toESM(require("node:readline/promises"));
24053
+ function interactive() {
24054
+ return !!process.stdin.isTTY && !!process.stdout.isTTY;
24055
+ }
24056
+ async function askLine(question, io = {}) {
24057
+ const rl = readline2.createInterface({
24058
+ input: io.input ?? process.stdin,
24059
+ output: io.output ?? process.stdout
24060
+ });
22656
24061
  try {
22657
- const answer = (await rl.question(question)).trim().toLowerCase();
22658
- return answer === "" || answer === "y" || answer === "yes";
24062
+ return await new Promise((resolve4) => {
24063
+ rl.question(question).then((a) => resolve4(a.trim())).catch(() => resolve4(null));
24064
+ rl.once("close", () => setImmediate(() => resolve4(null)));
24065
+ });
22659
24066
  } finally {
22660
24067
  rl.close();
22661
24068
  }
22662
24069
  }
24070
+ var ask = (question) => askLine(question);
24071
+ function parseChoice(answer, choices, fallback) {
24072
+ const a = answer.trim().toLowerCase();
24073
+ if (a === "") return { id: fallback, recognized: true };
24074
+ const byNumber = Number.parseInt(a, 10);
24075
+ if (Number.isInteger(byNumber) && byNumber >= 1 && byNumber <= choices.length) {
24076
+ return { id: choices[byNumber - 1].id, recognized: true };
24077
+ }
24078
+ const byId = choices.find((c) => c.id.toLowerCase() === a || c.label.toLowerCase() === a);
24079
+ if (byId) return { id: byId.id, recognized: true };
24080
+ return { id: fallback, recognized: false };
24081
+ }
24082
+ function parseMultiSelect(answer, choices, fallback) {
24083
+ const a = answer.trim().toLowerCase();
24084
+ if (a === "") return { ids: [...fallback], recognized: true };
24085
+ const picked = /* @__PURE__ */ new Set();
24086
+ for (const part of a.split(",").map((s) => s.trim()).filter(Boolean)) {
24087
+ const n = Number.parseInt(part, 10);
24088
+ if (Number.isInteger(n) && n >= 1 && n <= choices.length) {
24089
+ picked.add(choices[n - 1].id);
24090
+ continue;
24091
+ }
24092
+ const byId = choices.find((c) => c.id.toLowerCase() === part || c.label.toLowerCase() === part);
24093
+ if (byId) picked.add(byId.id);
24094
+ }
24095
+ if (picked.size === 0) return { ids: [...fallback], recognized: false };
24096
+ return { ids: choices.filter((c) => picked.has(c.id)).map((c) => c.id), recognized: true };
24097
+ }
24098
+ async function promptYes(question, opts) {
24099
+ if (!interactive()) return opts.nonInteractive;
24100
+ const answer = await ask(question);
24101
+ if (answer === null) return opts.nonInteractive;
24102
+ const a = answer.toLowerCase();
24103
+ return a === "" || a === "y" || a === "yes";
24104
+ }
24105
+ function printOptions(question, choices) {
24106
+ console.log("");
24107
+ console.log(` ${question}`);
24108
+ choices.forEach((c, i) => {
24109
+ const tag = c.recommended ? " (recommended)" : "";
24110
+ console.log(` ${i + 1}. ${c.label}${tag}${c.hint ? ` \u2014 ${c.hint}` : ""}`);
24111
+ });
24112
+ }
24113
+ async function promptChoice(question, choices, fallback) {
24114
+ if (!interactive()) return fallback;
24115
+ printOptions(question, choices);
24116
+ const defaultIdx = choices.findIndex((c) => c.id === fallback);
24117
+ const answer = await ask(` Choose [${defaultIdx + 1}]: `);
24118
+ if (answer === null) {
24119
+ console.log(` (no answer \u2014 using ${fallback})`);
24120
+ return fallback;
24121
+ }
24122
+ const parsed = parseChoice(answer, choices, fallback);
24123
+ if (!parsed.recognized) console.log(` Unrecognized answer "${answer}" \u2014 using ${fallback}.`);
24124
+ return parsed.id;
24125
+ }
24126
+ async function promptMultiSelect(question, choices, fallback) {
24127
+ if (!interactive()) return [...fallback];
24128
+ printOptions(question, choices);
24129
+ const defaultLabel = choices.map((c, i) => fallback.includes(c.id) ? String(i + 1) : null).filter(Boolean).join(",");
24130
+ const answer = await ask(` Choose one or more, comma-separated [${defaultLabel}]: `);
24131
+ if (answer === null) {
24132
+ console.log(" (no answer \u2014 using the default)");
24133
+ return [...fallback];
24134
+ }
24135
+ const parsed = parseMultiSelect(answer, choices, fallback);
24136
+ if (!parsed.recognized) console.log(` Unrecognized answer "${answer}" \u2014 using the default.`);
24137
+ return parsed.ids;
24138
+ }
24139
+
24140
+ // src/commands/init.ts
22663
24141
  async function confirmExistingLogin(serviceUrl, remote, opts) {
22664
24142
  const existing = await resolveToken(opts.token);
22665
24143
  if (!existing.ok) return "drive-login";
@@ -22717,7 +24195,7 @@ async function runOptionalAuth(resolution, opts = {}) {
22717
24195
  }
22718
24196
  let remote = "";
22719
24197
  try {
22720
- remote = (0, import_node_child_process11.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
24198
+ remote = (0, import_node_child_process14.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
22721
24199
  } catch {
22722
24200
  }
22723
24201
  if (!healed) {
@@ -22728,7 +24206,7 @@ async function runOptionalAuth(resolution, opts = {}) {
22728
24206
  printInfo("Verity runs in local-only mode: the gate still runs and shows static findings, but nothing uploads.");
22729
24207
  printInfo(' Authenticate anytime: run "verity login" (one login covers every repo you can write to).');
22730
24208
  };
22731
- if (process.stdin.isTTY && process.stdout.isTTY) {
24209
+ if (interactive() && !opts.yes) {
22732
24210
  console.log("");
22733
24211
  console.log(" Signing in is optional. What it does:");
22734
24212
  console.log(" - Confirms which repositories you can write to. The GitHub token is");
@@ -22743,9 +24221,12 @@ async function runOptionalAuth(resolution, opts = {}) {
22743
24221
  console.log(" findings, but nothing is uploaded.");
22744
24222
  console.log("");
22745
24223
  }
22746
- const wantsAuth = await promptYes("Authenticate with GitHub now to upload results to Verity? [Y/skip] ");
24224
+ const wantsAuth = opts.yes ? false : await promptYes(
24225
+ "Authenticate with GitHub now to upload results to Verity? [Y/skip] ",
24226
+ { nonInteractive: false }
24227
+ );
22747
24228
  if (!wantsAuth) {
22748
- printInfo("Skipped authentication.");
24229
+ printInfo(opts.yes ? "Skipped authentication (unattended run)." : "Skipped authentication.");
22749
24230
  localOnlyNote();
22750
24231
  return;
22751
24232
  }
@@ -22768,7 +24249,7 @@ function resolveDataDir() {
22768
24249
  // local dev: running from repo root
22769
24250
  ];
22770
24251
  for (const candidate of candidates) {
22771
- if ((0, import_node_fs41.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
24252
+ if ((0, import_node_fs44.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
22772
24253
  return candidate;
22773
24254
  }
22774
24255
  }
@@ -22777,22 +24258,197 @@ function resolveDataDir() {
22777
24258
  );
22778
24259
  }
22779
24260
  async function copyDir(src, dest) {
22780
- await (0, import_promises13.mkdir)(dest, { recursive: true });
22781
- await (0, import_promises13.cp)(src, dest, { recursive: true, force: true });
24261
+ await (0, import_promises14.mkdir)(dest, { recursive: true });
24262
+ await (0, import_promises14.cp)(src, dest, { recursive: true, force: true });
24263
+ }
24264
+ async function skillIsCurrent(src, dest) {
24265
+ const list2 = (dir) => {
24266
+ const out = [];
24267
+ const walk = (d, prefix) => {
24268
+ for (const e of (0, import_node_fs44.readdirSync)(d, { withFileTypes: true })) {
24269
+ const rel = prefix ? `${prefix}/${e.name}` : e.name;
24270
+ if (e.isDirectory()) walk((0, import_node_path29.join)(d, e.name), rel);
24271
+ else if (e.isFile()) out.push(rel);
24272
+ }
24273
+ };
24274
+ walk(dir, "");
24275
+ return out.sort();
24276
+ };
24277
+ try {
24278
+ const shipped = list2(src);
24279
+ if (JSON.stringify(shipped) !== JSON.stringify(list2(dest))) return false;
24280
+ for (const rel of shipped) {
24281
+ const a = await (0, import_promises14.readFile)((0, import_node_path29.join)(src, rel), "utf-8");
24282
+ const b = await (0, import_promises14.readFile)((0, import_node_path29.join)(dest, rel), "utf-8");
24283
+ if (a !== b) return false;
24284
+ }
24285
+ return true;
24286
+ } catch {
24287
+ return false;
24288
+ }
24289
+ }
24290
+ var SKILLS = [
24291
+ "verity-setup",
24292
+ "verity-analyze",
24293
+ "verity-status",
24294
+ "verity-feedback",
24295
+ "verity-learn",
24296
+ "verity-memory",
24297
+ "verity-insights",
24298
+ "verity-reflect"
24299
+ ];
24300
+ var INTENSITY_CHOICES = [
24301
+ { id: "lightweight", label: "lightweight", hint: "critical security only, fastest (~3s)" },
24302
+ { id: "balanced", label: "balanced", hint: "security + quality (~8s)", recommended: true },
24303
+ { id: "thorough", label: "thorough", hint: "all tools, all rules (~15s)" }
24304
+ ];
24305
+ var MOMENT_CHOICES = [
24306
+ { id: "stop", label: "On stop", hint: "after every agent turn \u2014 fast feedback while you work", recommended: true },
24307
+ { id: "pre-commit", label: "Before commit", hint: "reviews the staged diff, blocks the commit on FAIL" },
24308
+ { id: "pre-push", label: "Before push / PR", hint: "reviews the to-be-pushed commits, blocks on FAIL" }
24309
+ ];
24310
+ var DEFAULT_MOMENTS = ["stop"];
24311
+ async function askSetupQuestions(defaultsOnly, previous) {
24312
+ const intensityDefault = previous?.intensity ?? "balanced";
24313
+ const momentsDefault = previous?.moments?.length ? previous.moments : DEFAULT_MOMENTS;
24314
+ if (defaultsOnly) {
24315
+ return { intensity: intensityDefault, moments: momentsDefault, telemetry: "not-asked" };
24316
+ }
24317
+ if (previous?.intensity || previous?.moments) {
24318
+ printInfo(`Current: ${intensityDefault} \xB7 ${momentsDefault.join(", ")} \u2014 press Enter to keep either.`);
24319
+ }
24320
+ const intensity = await promptChoice(
24321
+ "Analysis intensity \u2014 how deeply should Verity review?",
24322
+ INTENSITY_CHOICES,
24323
+ intensityDefault
24324
+ );
24325
+ const moments = await promptMultiSelect(
24326
+ "When should Verity review your code?",
24327
+ MOMENT_CHOICES,
24328
+ momentsDefault
24329
+ );
24330
+ const current = await checkTelemetry();
24331
+ if (current.enabled) {
24332
+ printInfo(`Cost & usage telemetry: already enabled \u2192 ${current.endpoint}`);
24333
+ printInfo(' (turn it off with "verity telemetry uninstall")');
24334
+ return { intensity, moments, telemetry: "already-on" };
24335
+ }
24336
+ console.log("");
24337
+ console.log(" Cost & usage telemetry (opt-in) \u2014 powers the /usage dashboard.");
24338
+ console.log(" Sends Claude Code's own OpenTelemetry metrics and traces only: model names,");
24339
+ console.log(" token counts, USD cost, agent types, session ids. NOT your prompts, code, or");
24340
+ console.log(" tool input/output. Without it, /usage stays empty.");
24341
+ const wants = await promptYes(" Enable cost & usage telemetry? [Y/n] ", { nonInteractive: false });
24342
+ return { intensity, moments, telemetry: wants ? "yes" : "no" };
24343
+ }
24344
+ function insideClaudeCode() {
24345
+ return !!process.env.CLAUDECODE || !!process.env.CLAUDE_SESSION_ID;
24346
+ }
24347
+ function resumeDeferredPending(previous) {
24348
+ return previous?.telemetry === "deferred";
24349
+ }
24350
+ var PHASE_TWO_ARTIFACTS = [
24351
+ {
24352
+ path: ".verity/standard.yaml",
24353
+ what: "the Standard, synthesized from your codebase",
24354
+ // The presence flag travels WITH the row. Read positionally from a parallel
24355
+ // array, a reorder of this list would silently move every ✓ onto the wrong
24356
+ // path — a report that is confidently wrong about what got created.
24357
+ present: (a) => a.standard
24358
+ },
24359
+ {
24360
+ path: ".codacy/codacy.config.json",
24361
+ what: "curated static-analysis patterns",
24362
+ present: (a) => a.analysisConfig
24363
+ },
24364
+ {
24365
+ path: "VERITY.md",
24366
+ what: "project quality overview",
24367
+ present: (a) => a.verityMd
24368
+ }
24369
+ ];
24370
+ async function reportPhaseTwo(startedAt) {
24371
+ const elapsed = Math.round((Date.now() - startedAt) / 1e3);
24372
+ let report = null;
24373
+ try {
24374
+ report = await buildReport();
24375
+ } catch {
24376
+ }
24377
+ console.log("");
24378
+ if (!report) {
24379
+ printWarn('Could not verify what the setup session produced \u2014 run "verity doctor".');
24380
+ return;
24381
+ }
24382
+ const { artifacts } = report;
24383
+ const complete = PHASE_TWO_ARTIFACTS.every((a) => a.present(artifacts));
24384
+ printInfo(complete ? `Setup complete (${elapsed}s).` : `Setup session ended after ${elapsed}s.`);
24385
+ for (const { path, what, present } of PHASE_TWO_ARTIFACTS) {
24386
+ const ok = present(artifacts);
24387
+ console.log(` ${ok ? "\u2713" : "\xB7"} ${path.padEnd(30)} ${ok ? what : `${what} \u2014 NOT created`}`);
24388
+ }
24389
+ if (!complete) {
24390
+ console.log("");
24391
+ printWarn('Setup did not finish. Re-run "/verity-setup" in Claude Code to complete it \u2014');
24392
+ printWarn(" nothing is lost; it picks up from what is already on disk.");
24393
+ }
24394
+ }
24395
+ async function handoffToSetup(enabled, claudeInstalled) {
24396
+ const instruct = (why) => {
24397
+ console.log("");
24398
+ printInfo("Still to do \u2014 this is what /verity-setup does (it needs a model):");
24399
+ for (const { path, what } of PHASE_TWO_ARTIFACTS) {
24400
+ console.log(` ${path.padEnd(30)} ${what}`);
24401
+ }
24402
+ console.log("");
24403
+ printInfo("Next step: run /verity-setup in Claude Code.");
24404
+ printInfo(` (${why})`);
24405
+ };
24406
+ if (!enabled) return instruct("--no-setup was passed");
24407
+ if (insideClaudeCode()) return instruct("you are already in a Claude Code session \u2014 invoke the skill there");
24408
+ if (!interactive()) return instruct("no interactive terminal here");
24409
+ if (!claudeInstalled) return instruct("Claude Code is not installed on this machine yet");
24410
+ printPhase(2, 2, "your Standard", "reading the codebase \xB7 synthesizing the Standard \xB7 curating patterns");
24411
+ console.log(" Claude Code takes over the screen from here. It will:");
24412
+ console.log(" \xB7 read your codebase \u2014 languages, frameworks, architecture");
24413
+ console.log(" \xB7 synthesize .verity/standard.yaml and show it to you");
24414
+ console.log(" \xB7 write the analysis config, then VERITY.md");
24415
+ console.log(" Usually a minute or two. Quit any time \u2014 re-running /verity-setup resumes.");
24416
+ console.log("");
24417
+ const startedAt = Date.now();
24418
+ const run2 = (0, import_node_child_process14.spawnSync)("claude", ["/verity-setup"], { stdio: "inherit" });
24419
+ if (run2.error) {
24420
+ printWarn(`Could not start Claude Code: ${run2.error.message}`);
24421
+ return instruct("start it yourself and run the skill there");
24422
+ }
24423
+ await reportPhaseTwo(startedAt);
22782
24424
  }
22783
24425
  function registerInitCommand(program2) {
22784
- program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
24426
+ program2.command("init").alias("setup").description("Set up Verity in the current project (asks the setup questions, then hands off to /verity-setup)").option("--force", "Reinstall the skills even when they are already up to date (hooks are always reconciled)").option("-y, --yes", "Take the recommended answer for every question (no prompts)").option("--no-setup", "Skip the /verity-setup handoff at the end").action(async (opts) => {
22785
24427
  const force = opts.force ?? false;
24428
+ const wantsHandoff = opts.setup !== false;
24429
+ const defaultsOnly = (opts.yes ?? false) || !interactive();
22786
24430
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
22787
- const isProject = projectMarkers.some((m) => (0, import_node_fs41.existsSync)(m));
24431
+ const isProject = projectMarkers.some((m) => (0, import_node_fs44.existsSync)(m));
22788
24432
  if (!isProject) {
22789
24433
  printError("No project detected in the current directory.");
22790
24434
  printInfo('Run "verity init" from your project root.');
22791
24435
  process.exit(1);
22792
24436
  }
22793
- console.log("");
22794
- printInfo("Initializing Verity in this project...");
22795
- console.log("");
24437
+ const showArt = canRenderArt() && !insideClaudeCode();
24438
+ printBanner({ interactive: showArt });
24439
+ if (!showArt) {
24440
+ console.log("");
24441
+ printInfo("Initializing Verity in this project...");
24442
+ console.log("");
24443
+ } else {
24444
+ printPhase(1, 2, "this machine", "prerequisites \xB7 skills \xB7 hooks \xB7 sign-in");
24445
+ }
24446
+ const TOTAL_STEPS = 8;
24447
+ let stepNo = 0;
24448
+ const step = (label2) => {
24449
+ stepNo++;
24450
+ printInfo(`${DIM}[${stepNo}/${TOTAL_STEPS}]${NC} ${label2}`);
24451
+ };
22796
24452
  if (await needsMigration()) {
22797
24453
  printInfo("Legacy GATE.md install detected \u2014 migrating to Verity...");
22798
24454
  try {
@@ -22803,143 +24459,171 @@ function registerInitCommand(program2) {
22803
24459
  }
22804
24460
  console.log("");
22805
24461
  }
22806
- printInfo("Checking prerequisites...");
22807
- const nodeVersion = process.version;
22808
- const nodeMajor = parseInt(nodeVersion.slice(1), 10);
22809
- if (nodeMajor < 20) {
22810
- printError(`Node.js 20+ required (found ${nodeVersion}). Update from https://nodejs.org`);
22811
- process.exit(1);
24462
+ step("Checking prerequisites");
24463
+ const prereqs = await checkPrereqs({ install: true });
24464
+ for (const c of prereqs.checks) {
24465
+ if (c.status === "ok") {
24466
+ if (c.justInstalled) continue;
24467
+ printInfo(` ${c.label} ${c.detail} \u2713`);
24468
+ } else {
24469
+ printWarn(` ${c.label}: ${c.detail}`);
24470
+ if (c.remedy) printWarn(` ${c.remedy}`);
24471
+ }
22812
24472
  }
22813
- printInfo(` Node.js ${nodeVersion} \u2713`);
22814
- try {
22815
- const gitVersion = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8" }).trim();
22816
- printInfo(` ${gitVersion} \u2713`);
22817
- } catch {
22818
- printError("git is required but not installed. Install from https://git-scm.com");
24473
+ if (prereqs.blocked) {
24474
+ printError("A required prerequisite is missing \u2014 cannot continue.");
22819
24475
  process.exit(1);
22820
24476
  }
22821
- try {
22822
- (0, import_node_child_process11.execSync)("which claude", { encoding: "utf-8" });
22823
- printInfo(" Claude Code \u2713");
22824
- } catch {
22825
- printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
22826
- }
22827
- try {
22828
- (0, import_node_child_process11.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
22829
- printInfo(" @codacy/analysis-cli \u2713");
22830
- } catch {
22831
- printInfo(" Installing @codacy/analysis-cli...");
22832
- try {
22833
- (0, import_node_child_process11.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
22834
- printInfo(" @codacy/analysis-cli installed \u2713");
22835
- } catch {
22836
- try {
22837
- printWarn(" Retrying with sudo...");
22838
- (0, import_node_child_process11.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
22839
- printInfo(" @codacy/analysis-cli installed \u2713");
22840
- } catch {
22841
- printWarn(" Could not install @codacy/analysis-cli automatically.");
22842
- printWarn(" Install manually: npm install -g @codacy/analysis-cli");
22843
- printWarn(" Static analysis will be unavailable until installed.");
22844
- }
22845
- }
22846
- }
24477
+ const claudeInstalled = prereqs.checks.some((c) => c.id === "claude" && c.status === "ok");
22847
24478
  console.log("");
22848
- printInfo("Installing skills...");
24479
+ step("Installing skills");
22849
24480
  const dataDir = resolveDataDir();
22850
24481
  const skillsSource = (0, import_node_path29.join)(dataDir, "skills");
22851
24482
  const skillsDest = ".claude/skills";
22852
- const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
22853
24483
  let skillsInstalled = 0;
22854
- for (const skill of skills) {
24484
+ for (const skill of SKILLS) {
22855
24485
  const src = (0, import_node_path29.join)(skillsSource, skill);
22856
24486
  const dest = (0, import_node_path29.join)(skillsDest, skill);
22857
- if (!(0, import_node_fs41.existsSync)(src)) {
24487
+ if (!(0, import_node_fs44.existsSync)(src)) {
22858
24488
  printWarn(` Skill data not found: ${skill}`);
22859
24489
  continue;
22860
24490
  }
22861
- if ((0, import_node_fs41.existsSync)(dest) && !force) {
22862
- const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
22863
- const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
22864
- if ((0, import_node_fs41.existsSync)(destSkill)) {
22865
- try {
22866
- const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
22867
- const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
22868
- if (srcContent === destContent) {
22869
- skillsInstalled++;
22870
- continue;
22871
- }
22872
- } catch {
22873
- }
22874
- }
24491
+ if ((0, import_node_fs44.existsSync)(dest) && !force && await skillIsCurrent(src, dest)) {
24492
+ skillsInstalled++;
24493
+ continue;
22875
24494
  }
22876
24495
  await copyDir(src, dest);
22877
24496
  skillsInstalled++;
22878
24497
  }
22879
- printInfo(` ${skillsInstalled}/${skills.length} skills installed to .claude/skills/ \u2713`);
22880
- printInfo("Wiring Claude Code hooks...");
22881
- const present = await checkAllVerityHooks();
22882
- const settings = await readSettings();
22883
- const hookResult = installVerityHooks(settings, force, present);
22884
- if (hookResult.ok) {
22885
- await writeSettings(hookResult.data);
22886
- printInfo(" Stop hook: verity analyze \u2713");
22887
- printInfo(" Intent hook: verity intent capture \u2713");
22888
- printInfo(" Baseline hook: verity baseline capture \u2713");
22889
- } else {
22890
- printWarn(` ${hookResult.error}`);
22891
- printInfo(' Run "verity hooks install --force" to overwrite.');
24498
+ printInfo(` ${skillsInstalled}/${SKILLS.length} skills installed to .claude/skills/ \u2713`);
24499
+ step(defaultsOnly ? "Setup answers (defaults)" : "Your setup answers");
24500
+ const previous = await readSetupState();
24501
+ const answers = await askSetupQuestions(defaultsOnly, previous);
24502
+ const { intensity, moments } = answers;
24503
+ if (defaultsOnly) {
24504
+ printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
22892
24505
  }
22893
- await (0, import_promises13.mkdir)(VERITY_DIR, { recursive: true });
24506
+ step("Knowledge base, .gitignore and CLAUDE.md");
24507
+ await (0, import_promises14.mkdir)(VERITY_DIR, { recursive: true });
22894
24508
  await ensureMemoryDir();
22895
- const ignoreResult = ensureSnapshotGitignored();
24509
+ const ignoreResult = ensureVerityGitignore();
22896
24510
  if (ignoreResult === "failed") {
22897
- printWarn(" .gitignore: could not add .verity/.snapshot/ \u2014 add it manually (it holds copies of analyzed files)");
24511
+ printWarn(" .gitignore: could not write the Verity block \u2014 add it manually");
24512
+ printWarn(" (.verity/ holds copies of analyzed files, including any secret the gate flagged)");
24513
+ } else if (ignoreResult === "conflict") {
24514
+ printWarn(" .gitignore: the Verity block is in place but git still ignores .verity/standard.yaml");
24515
+ printWarn(" Something outside this file covers it \u2014 a global (~/.gitignore) or nested");
24516
+ printWarn(" .gitignore, or a pattern we do not recognise. Check: git check-ignore -v .verity/standard.yaml");
24517
+ printWarn(" Until it is fixed, the Standard and the knowledge graph cannot be committed.");
24518
+ } else if (ignoreResult === "repaired") {
24519
+ printInfo(" .gitignore: rewrote `.verity/` to `.verity/*` so the standard stays committable \u2713");
22898
24520
  } else {
22899
- printInfo(` .gitignore: .verity/.snapshot/ ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
24521
+ printInfo(` .gitignore: Verity block ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
24522
+ }
24523
+ const tracked = committedVerityState();
24524
+ if (tracked.length > 0) {
24525
+ printWarn(` ${tracked.length} Verity state file(s) are tracked in git (e.g. ${tracked[0]}).`);
24526
+ const untrack = defaultsOnly ? false : await promptYes(" Untrack them now (files stay on disk)? [Y/n] ", { nonInteractive: false });
24527
+ if (untrack) {
24528
+ const result = untrackVerityState();
24529
+ if (result === "untracked") printInfo(" Untracked (staged) \u2014 commit to finish \u2713");
24530
+ else if (result === "failed") printWarn(' Could not untrack \u2014 run "git rm -r --cached .verity" manually');
24531
+ } else {
24532
+ printWarn(" Left tracked. Fix with: git rm -r --cached .verity && git add .verity/standard.yaml .verity/memory");
24533
+ }
22900
24534
  }
22901
24535
  try {
22902
24536
  await ensureClaudeMdPointer();
22903
- printInfo(" CLAUDE.md memory pointer \u2713");
24537
+ printInfo(" CLAUDE.md instructions \u2713");
22904
24538
  } catch (err) {
22905
24539
  printWarn(` Could not update CLAUDE.md: ${err.message}`);
22906
24540
  }
22907
24541
  const globalVerityDir = (0, import_node_path29.join)(process.env.HOME ?? "", ".verity");
22908
- await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
24542
+ await (0, import_promises14.mkdir)(globalVerityDir, { recursive: true });
22909
24543
  console.log("");
24544
+ step("Wiring Claude Code hooks");
24545
+ await applyMomentSelection(moments);
24546
+ const hookStatus = await checkAllVerityHooks();
24547
+ printInfo(` Stop (verity analyze): ${hookStatus.stop ? "on" : "off"}`);
24548
+ printInfo(` Pre-commit gate: ${hookStatus.guardOn.includes("commit") ? "on" : "off"}`);
24549
+ printInfo(` Pre-push/PR gate: ${hookStatus.guardOn.includes("push") ? "on" : "off"}`);
24550
+ printInfo(` Intent + baseline + compact + session-end: always on \u2713`);
24551
+ if (!hookStatus.stop && hookStatus.guardOn.length === 0) {
24552
+ printWarn(" No analysis moment is active \u2014 code changes will NOT be reviewed.");
24553
+ printWarn(" Enable one: verity hooks install --moments stop");
24554
+ }
24555
+ console.log("");
24556
+ step("Sign in to Verity (optional)");
22910
24557
  try {
22911
24558
  const globals = program2.opts();
22912
24559
  const resolution = await resolveServiceUrlForAuth(globals.serviceUrl);
22913
24560
  await runOptionalAuth(resolution, {
22914
24561
  token: globals.token,
22915
- verbose: globals.verbose
24562
+ verbose: globals.verbose,
24563
+ yes: defaultsOnly
22916
24564
  });
22917
24565
  } catch (err) {
22918
24566
  printWarn(`Authentication step skipped: ${err.message}`);
22919
24567
  }
24568
+ const resumeDeferred = answers.telemetry === "not-asked" && resumeDeferredPending(previous);
24569
+ const wantsTelemetry = answers.telemetry === "yes" || resumeDeferred;
24570
+ step("Cost & usage telemetry");
24571
+ if (answers.telemetry === "not-asked" && !resumeDeferredPending(previous)) {
24572
+ printInfo(' not asked (unattended run) \u2014 enable later with "verity telemetry install"');
24573
+ } else if (answers.telemetry === "no") {
24574
+ printInfo(' declined \u2014 enable later with "verity telemetry install"');
24575
+ }
24576
+ let telemetryChoice = answers.telemetry === "already-on" ? "enabled" : answers.telemetry === "no" ? "declined" : wantsTelemetry ? "deferred" : void 0;
24577
+ if (wantsTelemetry) {
24578
+ const globals = program2.opts();
24579
+ const token = await resolveToken(globals.token);
24580
+ const url = await resolveServiceUrl(globals.serviceUrl);
24581
+ if (token.ok && url.ok) {
24582
+ const installed2 = await installTelemetry(url.data);
24583
+ if (installed2.ok) {
24584
+ telemetryChoice = "enabled";
24585
+ printInfo(`Telemetry enabled \u2192 ${installed2.data.endpoint}`);
24586
+ printInfo(" takes effect on your NEXT Claude Code session; view cost & usage at /usage");
24587
+ } else {
24588
+ printWarn(`Could not enable telemetry: ${installed2.error}`);
24589
+ }
24590
+ } else {
24591
+ printWarn('Telemetry needs a Verity token \u2014 run "verity login", then "verity telemetry install".');
24592
+ }
24593
+ }
24594
+ step("Recording your answers");
24595
+ try {
24596
+ await writeSetupState({
24597
+ intensity,
24598
+ moments,
24599
+ ...telemetryChoice ? { telemetry: telemetryChoice } : {},
24600
+ init: {
24601
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
24602
+ cli_version: true ? "0.31.1-experimental.68fca47" : "dev"
24603
+ }
24604
+ });
24605
+ } catch (err) {
24606
+ printWarn(`Could not record setup answers: ${err.message}`);
24607
+ }
22920
24608
  console.log("");
22921
- printInfo("Verity initialized!");
24609
+ printInfo("This machine is set up.");
22922
24610
  console.log("");
22923
- console.log(" Installed:");
22924
- console.log(" .claude/skills/verity-setup/ \u2014 project configuration");
22925
- console.log(" .claude/skills/verity-analyze/ \u2014 on-demand analysis");
22926
- console.log(" .claude/skills/verity-status/ \u2014 project health");
22927
- console.log(" .claude/skills/verity-feedback/ \u2014 finding feedback + suppressions");
22928
- console.log(" .claude/skills/verity-learn/ \u2014 view project knowledge");
22929
- console.log(" .claude/skills/verity-memory/ \u2014 browse knowledge graph");
22930
- console.log(" .claude/skills/verity-insights/ \u2014 quality metrics + evolution");
22931
- console.log(" .claude/skills/verity-reflect/ \u2014 capture learnings");
22932
- console.log(" .claude/settings.json \u2014 hooks (verity analyze + intent capture)");
22933
- console.log(" .verity/memory/ \u2014 knowledge base (8 domains, commit to git)");
24611
+ console.log(" .claude/skills/verity-*/ 8 skills (setup, analyze, status, feedback,");
24612
+ console.log(" learn, memory, insights, reflect)");
24613
+ console.log(" .claude/settings.json hooks, reconciled to your chosen moments");
24614
+ console.log(" .verity/memory/ knowledge base (commit to git)");
24615
+ console.log(" .verity/setup.json your answers, read by /verity-setup");
24616
+ console.log(" .gitignore Verity block (whitelist form)");
24617
+ console.log(" CLAUDE.md memory pointer, waive policy, reflection");
22934
24618
  console.log("");
22935
- console.log(" Next step: open this project in Claude Code and run /verity-setup");
22936
- console.log(' (Not authenticated? Verity runs in local-only mode until you run "verity login".)');
24619
+ console.log(` Intensity: ${intensity} Moments: ${moments.join(", ") || "none"}`);
24620
+ await handoffToSetup(wantsHandoff, claudeInstalled);
22937
24621
  console.log("");
22938
24622
  });
22939
24623
  }
22940
24624
 
22941
24625
  // src/commands/uninstall.ts
22942
- var import_node_fs42 = require("node:fs");
24626
+ var import_node_fs45 = require("node:fs");
22943
24627
  var import_node_path30 = require("node:path");
22944
24628
  var SKILL_NAMES = [
22945
24629
  "verity-setup",
@@ -22960,10 +24644,10 @@ function registerUninstallCommand(program2) {
22960
24644
  const skillsRoot = projectPath(".claude/skills");
22961
24645
  for (const name of SKILL_NAMES) {
22962
24646
  const dir = (0, import_node_path30.join)(skillsRoot, name);
22963
- if ((0, import_node_fs42.existsSync)(dir)) {
24647
+ if ((0, import_node_fs45.existsSync)(dir)) {
22964
24648
  actions.push({
22965
24649
  label: `Remove .claude/skills/${name}/`,
22966
- apply: () => (0, import_node_fs42.rmSync)(dir, { recursive: true, force: true })
24650
+ apply: () => (0, import_node_fs45.rmSync)(dir, { recursive: true, force: true })
22967
24651
  });
22968
24652
  }
22969
24653
  }
@@ -22977,24 +24661,24 @@ function registerUninstallCommand(program2) {
22977
24661
  });
22978
24662
  }
22979
24663
  const verityDir = projectPath(VERITY_DIR);
22980
- if ((0, import_node_fs42.existsSync)(verityDir)) {
24664
+ if ((0, import_node_fs45.existsSync)(verityDir)) {
22981
24665
  actions.push({
22982
24666
  label: `Remove ${VERITY_DIR}/`,
22983
- apply: () => (0, import_node_fs42.rmSync)(verityDir, { recursive: true, force: true })
24667
+ apply: () => (0, import_node_fs45.rmSync)(verityDir, { recursive: true, force: true })
22984
24668
  });
22985
24669
  }
22986
24670
  if (!keepVerityMd) {
22987
24671
  const verityMd = projectPath(VERITY_MD_FILE);
22988
- if ((0, import_node_fs42.existsSync)(verityMd)) {
24672
+ if ((0, import_node_fs45.existsSync)(verityMd)) {
22989
24673
  actions.push({
22990
24674
  label: `Remove ${VERITY_MD_FILE}`,
22991
- apply: () => (0, import_node_fs42.rmSync)(verityMd, { force: true })
24675
+ apply: () => (0, import_node_fs45.rmSync)(verityMd, { force: true })
22992
24676
  });
22993
24677
  }
22994
24678
  }
22995
24679
  const cleanupEmptyDir = (path) => {
22996
- if ((0, import_node_fs42.existsSync)(path) && (0, import_node_fs42.statSync)(path).isDirectory() && (0, import_node_fs42.readdirSync)(path).length === 0) {
22997
- (0, import_node_fs42.rmdirSync)(path);
24680
+ if ((0, import_node_fs45.existsSync)(path) && (0, import_node_fs45.statSync)(path).isDirectory() && (0, import_node_fs45.readdirSync)(path).length === 0) {
24681
+ (0, import_node_fs45.rmdirSync)(path);
22998
24682
  }
22999
24683
  };
23000
24684
  actions.push({
@@ -23006,10 +24690,10 @@ function registerUninstallCommand(program2) {
23006
24690
  });
23007
24691
  const home = process.env.HOME ?? "";
23008
24692
  const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
23009
- if (purgeGlobal && (0, import_node_fs42.existsSync)(globalVerityDir)) {
24693
+ if (purgeGlobal && (0, import_node_fs45.existsSync)(globalVerityDir)) {
23010
24694
  actions.push({
23011
24695
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
23012
- apply: () => (0, import_node_fs42.rmSync)(globalVerityDir, { recursive: true, force: true })
24696
+ apply: () => (0, import_node_fs45.rmSync)(globalVerityDir, { recursive: true, force: true })
23013
24697
  });
23014
24698
  }
23015
24699
  if (actions.length === 0) {
@@ -23029,7 +24713,7 @@ function registerUninstallCommand(program2) {
23029
24713
  if (!purgeGlobal) {
23030
24714
  printInfo('Saved tokens at ~/.verity/credentials are preserved \u2014 re-run "verity init" to reconnect.');
23031
24715
  } else {
23032
- printWarn("Global credentials wiped \u2014 re-register with /verity-setup to reconnect.");
24716
+ printWarn('Global credentials wiped \u2014 run "verity login" (or "verity init") to reconnect.');
23033
24717
  }
23034
24718
  });
23035
24719
  }
@@ -23203,7 +24887,7 @@ function registerTaskCommands(program2) {
23203
24887
  }
23204
24888
 
23205
24889
  // src/commands/reset.ts
23206
- var import_node_fs43 = require("node:fs");
24890
+ var import_node_fs46 = require("node:fs");
23207
24891
  var import_node_path31 = require("node:path");
23208
24892
  function registerResetCommand(program2) {
23209
24893
  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) => {
@@ -23241,11 +24925,11 @@ function registerResetCommand(program2) {
23241
24925
  }
23242
24926
  const cacheDir = projectPath(CACHE_DIR);
23243
24927
  let purged = 0;
23244
- if ((0, import_node_fs43.existsSync)(cacheDir)) {
23245
- for (const entry of (0, import_node_fs43.readdirSync)(cacheDir)) {
24928
+ if ((0, import_node_fs46.existsSync)(cacheDir)) {
24929
+ for (const entry of (0, import_node_fs46.readdirSync)(cacheDir)) {
23246
24930
  if (entry.startsWith("pending-")) {
23247
24931
  try {
23248
- (0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
24932
+ (0, import_node_fs46.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
23249
24933
  purged++;
23250
24934
  } catch {
23251
24935
  }
@@ -23260,19 +24944,19 @@ function registerResetCommand(program2) {
23260
24944
  projectPath(`${VERITY_DIR}/.last-analysis`)
23261
24945
  ];
23262
24946
  for (const file of filesToClear) {
23263
- if ((0, import_node_fs43.existsSync)(file)) {
24947
+ if ((0, import_node_fs46.existsSync)(file)) {
23264
24948
  try {
23265
- (0, import_node_fs43.writeFileSync)(file, "");
24949
+ (0, import_node_fs46.writeFileSync)(file, "");
23266
24950
  } catch {
23267
24951
  }
23268
24952
  }
23269
24953
  }
23270
24954
  if (opts.all) {
23271
24955
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
23272
- if ((0, import_node_fs43.existsSync)(logsDir)) {
23273
- for (const entry of (0, import_node_fs43.readdirSync)(logsDir)) {
24956
+ if ((0, import_node_fs46.existsSync)(logsDir)) {
24957
+ for (const entry of (0, import_node_fs46.readdirSync)(logsDir)) {
23274
24958
  try {
23275
- (0, import_node_fs43.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
24959
+ (0, import_node_fs46.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
23276
24960
  } catch {
23277
24961
  }
23278
24962
  }
@@ -23580,8 +25264,8 @@ function registerTelemetryCommands(program2) {
23580
25264
  }
23581
25265
 
23582
25266
  // src/cli.ts
23583
- program.name("verity").description("CLI for Verity quality gate service").version("0.31.0").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) => {
23584
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.0");
25267
+ program.name("verity").description("CLI for Verity quality gate service").version("0.31.1-experimental.68fca47").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) => {
25268
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.1-experimental.68fca47");
23585
25269
  setUserNamedServiceUrl(program.opts().serviceUrl);
23586
25270
  try {
23587
25271
  await foldLegacyLocalCredential();
@@ -23607,6 +25291,7 @@ registerGuardCommand(program);
23607
25291
  registerIgnoreCommand(program);
23608
25292
  registerWaiveCommand(program);
23609
25293
  registerInitCommand(program);
25294
+ registerDoctorCommand(program);
23610
25295
  registerUninstallCommand(program);
23611
25296
  registerTaskCommands(program);
23612
25297
  registerResetCommand(program);