@codacy/verity-cli 0.32.0-experimental.f0746f7 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/verity.js CHANGED
@@ -10405,6 +10405,8 @@ var MAX_INTENT_CHARS = 2e3;
10405
10405
  var SNAPSHOT_DIR = `${VERITY_DIR}/.snapshot`;
10406
10406
  var BASELINE_DIR = `${VERITY_DIR}/.baseline`;
10407
10407
  var CONVERSATION_BUFFER_FILE = `${VERITY_DIR}/.conversation-buffer`;
10408
+ var PLUGIN_MARKER_FILE = `${VERITY_DIR}/.plugin-active`;
10409
+ var PROJECT_CONFIG_FILE = `${VERITY_DIR}/config.json`;
10408
10410
  var CONVERSATION_MAX_ENTRIES = 10;
10409
10411
  var CONVERSATION_WINDOW_MINUTES = 15;
10410
10412
  var MAX_FINDINGS = 25;
@@ -10507,7 +10509,7 @@ var SECURITY_PATTERNS = [
10507
10509
  /Dockerfile/
10508
10510
  ];
10509
10511
  var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
10510
- var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
10512
+ var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
10511
10513
  var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
10512
10514
  var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
10513
10515
  var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
@@ -10831,16 +10833,9 @@ function splitLines(s) {
10831
10833
  }
10832
10834
  var SHA_RE = /^[0-9a-f]{40}$/;
10833
10835
  function readBaselineSha() {
10834
- if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
10835
- let sha;
10836
- try {
10837
- sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10838
- } catch {
10839
- return null;
10840
- }
10841
- if (!SHA_RE.test(sha)) return null;
10842
- const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10843
- if (!reachable) {
10836
+ const sha = readRawBaselineSha();
10837
+ if (sha === null) return null;
10838
+ if (!commitResolves(sha)) {
10844
10839
  try {
10845
10840
  (0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
10846
10841
  } catch {
@@ -10849,6 +10844,16 @@ function readBaselineSha() {
10849
10844
  }
10850
10845
  return sha;
10851
10846
  }
10847
+ function readRawBaselineSha() {
10848
+ if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
10849
+ let sha;
10850
+ try {
10851
+ sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
10852
+ } catch {
10853
+ return null;
10854
+ }
10855
+ return SHA_RE.test(sha) ? sha : null;
10856
+ }
10852
10857
  function writeBaselineSha(sha) {
10853
10858
  if (!SHA_RE.test(sha)) return;
10854
10859
  try {
@@ -10857,6 +10862,25 @@ function writeBaselineSha(sha) {
10857
10862
  } catch {
10858
10863
  }
10859
10864
  }
10865
+ function commitObjectExists(sha) {
10866
+ if (!SHA_RE.test(sha)) return false;
10867
+ return execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
10868
+ }
10869
+ function committedSinceRewrite(oldSha) {
10870
+ if (!SHA_RE.test(oldSha)) return [];
10871
+ const newLocal = new Set(splitLines(execGit(`git rev-list HEAD --not ${oldSha} --remotes`)));
10872
+ if (newLocal.size === 0) return [];
10873
+ const files = /* @__PURE__ */ new Set();
10874
+ for (const line of splitLines(execGit(`git cherry ${oldSha} HEAD`))) {
10875
+ const sp = line.indexOf(" ");
10876
+ if (sp < 0) continue;
10877
+ const mark = line.slice(0, sp);
10878
+ const sha = line.slice(sp + 1).trim();
10879
+ if (mark !== "+" || !newLocal.has(sha)) continue;
10880
+ for (const f of splitLines(execGit(`git diff-tree --no-commit-id --name-only -r ${sha}`))) files.add(f);
10881
+ }
10882
+ return [...files].filter((f) => !isVerityOwnedPath(f));
10883
+ }
10860
10884
  var VERITY_OWNED_PREFIXES = [".verity/", ".gate/", ".codacy/"];
10861
10885
  var VERITY_OWNED_FILES = ["VERITY.md"];
10862
10886
  function isVerityOwnedPath(file) {
@@ -10869,6 +10893,7 @@ function getChangedFiles() {
10869
10893
  for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
10870
10894
  for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
10871
10895
  for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
10896
+ const rawBaseline = readRawBaselineSha();
10872
10897
  const baseline = readBaselineSha();
10873
10898
  if (baseline) {
10874
10899
  const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
@@ -10876,6 +10901,13 @@ function getChangedFiles() {
10876
10901
  hasRecentCommitFiles = true;
10877
10902
  for (const f of committed) sets.add(f);
10878
10903
  }
10904
+ } else if (rawBaseline && commitObjectExists(rawBaseline)) {
10905
+ const committed = committedSinceRewrite(rawBaseline);
10906
+ logEvent("baseline_rewritten", { recovered: committed.length });
10907
+ if (committed.length > 0) {
10908
+ hasRecentCommitFiles = true;
10909
+ for (const f of committed) sets.add(f);
10910
+ }
10879
10911
  } else {
10880
10912
  const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
10881
10913
  const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
@@ -12701,9 +12733,155 @@ async function applyMomentSelection(moments) {
12701
12733
  return settings;
12702
12734
  }
12703
12735
 
12736
+ // src/lib/project-config.ts
12737
+ var import_node_fs4 = require("node:fs");
12738
+ var DEFAULTS = { git_moments: [] };
12739
+ function isMoment(value) {
12740
+ return value === "commit" || value === "push";
12741
+ }
12742
+ function parseMoments(raw) {
12743
+ return [...new Set(raw.split(",").map((s) => s.trim()).filter(isMoment))];
12744
+ }
12745
+ function readProjectConfig() {
12746
+ try {
12747
+ if (!(0, import_node_fs4.existsSync)(PROJECT_CONFIG_FILE)) return DEFAULTS;
12748
+ const raw = JSON.parse((0, import_node_fs4.readFileSync)(PROJECT_CONFIG_FILE, "utf-8"));
12749
+ const moments = Array.isArray(raw.git_moments) ? raw.git_moments.filter(isMoment) : [];
12750
+ return { git_moments: [...new Set(moments)] };
12751
+ } catch {
12752
+ return DEFAULTS;
12753
+ }
12754
+ }
12755
+ function writeProjectConfig(patch) {
12756
+ const next = { ...readProjectConfig(), ...patch };
12757
+ (0, import_node_fs4.mkdirSync)(VERITY_DIR, { recursive: true });
12758
+ (0, import_node_fs4.writeFileSync)(PROJECT_CONFIG_FILE, JSON.stringify(next, null, 2) + "\n");
12759
+ return next;
12760
+ }
12761
+ function resolveGuardMoments(explicit) {
12762
+ if (explicit !== void 0) return parseMoments(explicit);
12763
+ return readProjectConfig().git_moments;
12764
+ }
12765
+
12766
+ // src/lib/plugin-ownership.ts
12767
+ var import_node_fs6 = require("node:fs");
12768
+
12769
+ // src/lib/stderr-log.ts
12770
+ var import_node_fs5 = require("node:fs");
12771
+ var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
12772
+ var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
12773
+ function scrub(s) {
12774
+ return s.replace(TOKEN_RE2, "verity_***REDACTED***").replace(ANSI_RE, "");
12775
+ }
12776
+ var installed = false;
12777
+ var wroteBanner = false;
12778
+ var banner = "";
12779
+ function append(text) {
12780
+ try {
12781
+ const dir = projectPath(DEBUG_LOG_DIR);
12782
+ const file = projectPath(STDERR_LOG_FILE);
12783
+ (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
12784
+ rotateIfNeeded(file);
12785
+ (0, import_node_fs5.appendFileSync)(file, text);
12786
+ } catch {
12787
+ }
12788
+ }
12789
+ function ensureBanner() {
12790
+ if (wroteBanner) return;
12791
+ wroteBanner = true;
12792
+ append(banner);
12793
+ }
12794
+ function installStderrLog(cmd, argv, version) {
12795
+ if (installed || !isDebugEnabled()) return;
12796
+ installed = true;
12797
+ banner = `
12798
+ \u2501\u2501 verity ${cmd} \xB7 ${(/* @__PURE__ */ new Date()).toISOString()} \xB7 pid ${process.pid}
12799
+ v${version} \xB7 ${process.cwd()}
12800
+ argv: ${scrub(argv.join(" "))}
12801
+ `;
12802
+ const original = process.stderr.write.bind(process.stderr);
12803
+ const tee = (...args) => {
12804
+ const result = original(...args);
12805
+ try {
12806
+ const chunk = args[0];
12807
+ const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
12808
+ ensureBanner();
12809
+ append(scrub(text));
12810
+ } catch {
12811
+ }
12812
+ return result;
12813
+ };
12814
+ process.stderr.write = tee;
12815
+ }
12816
+ function logToFileOnly(text) {
12817
+ if (!installed) return;
12818
+ ensureBanner();
12819
+ append(text.endsWith("\n") ? text : `${text}
12820
+ `);
12821
+ }
12822
+
12823
+ // src/lib/plugin-ownership.ts
12824
+ var MARKER_TTL_SECONDS = 24 * 60 * 60;
12825
+ function isPluginInvocation() {
12826
+ return !!process.env.VERITY_PLUGIN_ROOT;
12827
+ }
12828
+ function readMarker() {
12829
+ try {
12830
+ if (!(0, import_node_fs6.existsSync)(PLUGIN_MARKER_FILE)) return null;
12831
+ const raw = JSON.parse((0, import_node_fs6.readFileSync)(PLUGIN_MARKER_FILE, "utf-8"));
12832
+ const pluginRoot = typeof raw.plugin_root === "string" ? raw.plugin_root : "";
12833
+ if (!pluginRoot) return null;
12834
+ return {
12835
+ session_id: typeof raw.session_id === "string" ? raw.session_id : null,
12836
+ plugin_root: pluginRoot,
12837
+ version: typeof raw.version === "string" ? raw.version : null,
12838
+ ts: typeof raw.ts === "number" ? raw.ts : 0
12839
+ };
12840
+ } catch {
12841
+ return null;
12842
+ }
12843
+ }
12844
+ function recordPluginOwnership(sessionId) {
12845
+ const pluginRoot = process.env.VERITY_PLUGIN_ROOT;
12846
+ if (!pluginRoot) return;
12847
+ try {
12848
+ (0, import_node_fs6.mkdirSync)(VERITY_DIR, { recursive: true });
12849
+ const marker = {
12850
+ session_id: sessionId,
12851
+ plugin_root: pluginRoot,
12852
+ version: process.env.VERITY_PLUGIN_VERSION || null,
12853
+ ts: Math.floor(Date.now() / 1e3)
12854
+ };
12855
+ (0, import_node_fs6.writeFileSync)(PLUGIN_MARKER_FILE, JSON.stringify(marker));
12856
+ } catch {
12857
+ }
12858
+ }
12859
+ function shouldDeferToPlugin(sessionId) {
12860
+ if (isPluginInvocation()) {
12861
+ recordPluginOwnership(sessionId);
12862
+ return false;
12863
+ }
12864
+ const marker = readMarker();
12865
+ if (!marker) return false;
12866
+ if (!(0, import_node_fs6.existsSync)(marker.plugin_root)) return false;
12867
+ if (sessionId && marker.session_id) return sessionId === marker.session_id;
12868
+ return Math.floor(Date.now() / 1e3) - marker.ts < MARKER_TTL_SECONDS;
12869
+ }
12870
+ function pluginActiveHere() {
12871
+ const marker = readMarker();
12872
+ return !!marker && (0, import_node_fs6.existsSync)(marker.plugin_root);
12873
+ }
12874
+ function deferredToPlugin(command, sessionId) {
12875
+ if (!shouldDeferToPlugin(sessionId)) return false;
12876
+ logToFileOnly(
12877
+ `${command}: the Verity Claude Code plugin owns this session's hooks \u2014 standing down so the turn is not gated twice. Remove the duplicate settings.json hooks with \`verity init --plugin-mode\`.`
12878
+ );
12879
+ return true;
12880
+ }
12881
+
12704
12882
  // src/commands/hooks.ts
12705
12883
  var ALL_MOMENTS = ["stop", "pre-commit", "pre-push"];
12706
- function parseMoments(raw) {
12884
+ function parseMoments2(raw) {
12707
12885
  const seen = /* @__PURE__ */ new Set();
12708
12886
  for (const part of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
12709
12887
  if (ALL_MOMENTS.includes(part)) seen.add(part);
@@ -12715,7 +12893,22 @@ function registerHooksCommands(program2) {
12715
12893
  hooks.command("install").description("Install Verity hooks into Claude Code settings").option("--force", "Overwrite existing Verity hooks").option("--moments <list>", "Reconcile to exactly these moments: stop,pre-commit,pre-push").action(async (opts) => {
12716
12894
  const force = opts.force ?? false;
12717
12895
  if (opts.moments != null) {
12718
- const moments = parseMoments(opts.moments);
12896
+ const moments = parseMoments2(opts.moments);
12897
+ const gitMoments = [
12898
+ ...moments.includes("pre-commit") ? ["commit"] : [],
12899
+ ...moments.includes("pre-push") ? ["push"] : []
12900
+ ];
12901
+ writeProjectConfig({ git_moments: gitMoments });
12902
+ if (pluginActiveHere()) {
12903
+ printInfo("The Verity plugin wires the hooks; recorded your selection in .verity/config.json:");
12904
+ printInfo(` Stop (analysis on every turn): ${moments.includes("stop") ? "on" : "off \u2014 the plugin still wires it; see below"}`);
12905
+ printInfo(` Pre-commit gate: ${gitMoments.includes("commit") ? "on" : "off"}`);
12906
+ printInfo(` Pre-push/PR gate: ${gitMoments.includes("push") ? "on" : "off"}`);
12907
+ if (!moments.includes("stop")) {
12908
+ printWarn(" Turning the Stop review off is not yet supported under the plugin \u2014 it stays on.");
12909
+ }
12910
+ return;
12911
+ }
12719
12912
  await applyMomentSelection(moments);
12720
12913
  const status = await checkAllVerityHooks();
12721
12914
  printInfo("Verity hooks reconciled in .claude/settings.json:");
@@ -12762,6 +12955,24 @@ function registerHooksCommands(program2) {
12762
12955
  });
12763
12956
  hooks.command("check").description("Check if Verity hooks are installed").action(async () => {
12764
12957
  const status = await checkAllVerityHooks();
12958
+ if (pluginActiveHere()) {
12959
+ const moments = readProjectConfig().git_moments;
12960
+ printInfo("Wired by the Verity Claude Code plugin (not .claude/settings.json):");
12961
+ printInfo(" Stop hook (verity analyze): installed");
12962
+ printInfo(" Intent hook (verity intent capture): installed");
12963
+ printInfo(" Baseline hook (verity baseline capture): installed");
12964
+ printInfo(
12965
+ ` Git-moment gate (verity guard): ${moments.length ? `installed [${moments.join(", ")}]` : "wired but gating nothing"}`
12966
+ );
12967
+ if (!moments.length) {
12968
+ printInfo(' Enable it with "verity config git-moments commit,push".');
12969
+ }
12970
+ if (status.stop || status.intent || status.baseline || status.guard) {
12971
+ printWarn(" Duplicate hooks also exist in .claude/settings.json. They stand down at run time,");
12972
+ printWarn(' but remove them with "verity init --plugin-mode" so the wiring says what it does.');
12973
+ }
12974
+ return;
12975
+ }
12765
12976
  printInfo(`Stop hook (verity analyze): ${status.stop ? "installed" : "not installed"}`);
12766
12977
  printInfo(`Intent hook (verity intent capture): ${status.intent ? "installed" : "not installed"}`);
12767
12978
  printInfo(`Baseline hook (verity baseline capture): ${status.baseline ? "installed" : "not installed"}`);
@@ -12787,7 +12998,7 @@ var import_node_crypto8 = require("node:crypto");
12787
12998
 
12788
12999
  // src/lib/conversation-buffer.ts
12789
13000
  var import_promises5 = require("node:fs/promises");
12790
- var import_node_fs4 = require("node:fs");
13001
+ var import_node_fs7 = require("node:fs");
12791
13002
  var import_node_child_process5 = require("node:child_process");
12792
13003
  var import_node_crypto = require("node:crypto");
12793
13004
  function stripImageReferences(text) {
@@ -12823,7 +13034,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
12823
13034
  }
12824
13035
  async function readAndClearConversationBuffer(currentSessionId) {
12825
13036
  try {
12826
- if ((0, import_node_fs4.existsSync)(CONVERSATION_BUFFER_FILE)) {
13037
+ if ((0, import_node_fs7.existsSync)(CONVERSATION_BUFFER_FILE)) {
12827
13038
  const entries = await readBufferEntries();
12828
13039
  let mine = entries;
12829
13040
  let others = [];
@@ -12847,7 +13058,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
12847
13058
  };
12848
13059
  }
12849
13060
  }
12850
- if ((0, import_node_fs4.existsSync)(INTENT_FILE)) {
13061
+ if ((0, import_node_fs7.existsSync)(INTENT_FILE)) {
12851
13062
  try {
12852
13063
  const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
12853
13064
  await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
@@ -12902,88 +13113,324 @@ function getRecentCommitMessages() {
12902
13113
  }
12903
13114
  }
12904
13115
 
12905
- // src/lib/context-identity.ts
12906
- var import_node_crypto2 = require("node:crypto");
12907
- var import_node_fs5 = require("node:fs");
12908
- var import_node_os2 = require("node:os");
12909
- var import_node_path6 = require("node:path");
12910
- var SHARED_SENTINELS = /* @__PURE__ */ new Set([
12911
- "",
12912
- "-",
12913
- "n/a",
12914
- "na",
12915
- "none",
12916
- "null",
12917
- "undefined",
12918
- "unknown",
12919
- "anon",
12920
- "anonymous",
12921
- "default",
12922
- "_default"
12923
- ]);
12924
- function isSharedSentinel(value) {
12925
- if (value == null) return true;
12926
- return SHARED_SENTINELS.has(value.trim().toLowerCase());
12927
- }
12928
- var ephemeral = /* @__PURE__ */ new Map();
12929
- function ephemeralId(slot, width) {
12930
- let id = ephemeral.get(slot);
12931
- if (!id) {
12932
- id = `eph-${(0, import_node_crypto2.randomBytes)(Math.ceil(width / 2)).toString("hex").slice(0, width - 4)}`;
12933
- ephemeral.set(slot, id);
12934
- }
12935
- return id;
12936
- }
12937
- function contextIdentity(input) {
12938
- const tokenUsable = !isSharedSentinel(input.token ?? null);
12939
- const sessionUsable = !isSharedSentinel(input.sessionId ?? null);
12940
- const userKey = tokenUsable ? (0, import_node_crypto2.createHash)("sha256").update(String(input.token)).digest("hex").slice(0, 12) : ephemeralId("user", 12);
12941
- const sessionKey2 = sessionUsable ? (0, import_node_crypto2.createHash)("sha256").update(String(input.sessionId)).digest("hex").slice(0, 16) : ephemeralId("session", 16);
12942
- let treeKey = "no-tree";
12943
- const rawTree = input.treeRoot;
12944
- if (rawTree && !isSharedSentinel(rawTree)) {
12945
- let resolved = rawTree;
12946
- try {
12947
- resolved = import_node_fs5.realpathSync.native(rawTree);
12948
- } catch {
12949
- }
12950
- treeKey = (0, import_node_crypto2.createHash)("sha256").update(resolved).digest("hex").slice(0, 12);
13116
+ // src/lib/analysis-mode.ts
13117
+ var DEBUG_PHRASES = [
13118
+ "not working",
13119
+ "doesn't work",
13120
+ "doesn't work",
13121
+ "does not work",
13122
+ "isn't working",
13123
+ "is not working",
13124
+ "can't figure out",
13125
+ "stack trace"
13126
+ ];
13127
+ var DEBUG_WORDS = [
13128
+ "fix",
13129
+ "bug",
13130
+ "broken",
13131
+ "crash",
13132
+ "crashing",
13133
+ "failing",
13134
+ "debug",
13135
+ "debugging",
13136
+ "investigate",
13137
+ "troubleshoot",
13138
+ "regression",
13139
+ "wrong"
13140
+ ];
13141
+ var DEBUG_PATTERN = new RegExp(
13142
+ [
13143
+ ...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
13144
+ ...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
13145
+ ].join("|"),
13146
+ "i"
13147
+ );
13148
+ var FALSE_POSITIVE_PATTERNS = [
13149
+ /\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
13150
+ /\berror\s+handling\b/i,
13151
+ /\berror\s+boundar(?:y|ies)\b/i,
13152
+ /\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
13153
+ /\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
13154
+ ];
13155
+ function hasDebugIntent(prompt) {
13156
+ if (!DEBUG_PATTERN.test(prompt)) return false;
13157
+ for (const fp of FALSE_POSITIVE_PATTERNS) {
13158
+ if (fp.test(prompt)) return false;
12951
13159
  }
12952
- return {
12953
- userKey,
12954
- treeKey,
12955
- sessionKey: sessionKey2,
12956
- userKeySource: tokenUsable ? "token" : "ephemeral",
12957
- sessionKeySource: sessionUsable ? "session_id" : "ephemeral",
12958
- ephemeral: !tokenUsable || !sessionUsable
12959
- };
12960
- }
12961
- function verityHome() {
12962
- const override = process.env.VERITY_HOME;
12963
- return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".verity");
12964
- }
12965
- function dossierDir(identity) {
12966
- return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
12967
- }
12968
- function treeDir(identity) {
12969
- return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
13160
+ return true;
12970
13161
  }
12971
- function scopeIdentity(token, sessionId) {
12972
- const t = (token ?? "").trim();
12973
- const s = (sessionId ?? "").trim();
12974
- const userKey = t.length > 0 ? (0, import_node_crypto2.createHash)("sha256").update(t).digest("hex").slice(0, 12) : "anon";
12975
- const sessionKey2 = s.length > 0 ? (0, import_node_crypto2.createHash)("sha256").update(s).digest("hex").slice(0, 16) : "_default";
12976
- return { userKey, sessionKey: sessionKey2, bucket: `${userKey}/${sessionKey2}` };
13162
+ var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
13163
+ var CODE_AUTHORING_PATTERN = /\b(add|create|implement|build|write|fix|update|change|refactor|modify|remove|delete|move|rename)\b.*\b(function|component|feature|endpoint|test|file|module|class|type|interface|hook|page|route|style|migration|code|bug|error|issue)\b/i;
13164
+ function isGitOnlyPrompt(prompt) {
13165
+ if (!GIT_ONLY_PATTERN.test(prompt)) return false;
13166
+ if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
13167
+ return true;
12977
13168
  }
12978
- function sessionScopeKey(token, sessionId) {
12979
- return scopeIdentity(token, sessionId).bucket;
13169
+ function reconcileAnalysisMode(predictedMode, signals) {
13170
+ const mode2 = resolveAnalysisMode(predictedMode, signals);
13171
+ if (mode2 !== "skip") return mode2;
13172
+ const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
13173
+ if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
13174
+ return mode2;
12980
13175
  }
12981
-
12982
- // src/lib/task-context-buffer.ts
12983
- var import_promises6 = require("node:fs/promises");
12984
- var import_node_fs6 = require("node:fs");
12985
- var import_node_path7 = require("node:path");
12986
- var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
13176
+ function resolveAnalysisMode(predictedMode, signals) {
13177
+ if (!predictedMode || !isValidMode(predictedMode)) {
13178
+ return detectAnalysisMode(
13179
+ signals.noFilesChanged,
13180
+ signals.assistantResponse,
13181
+ signals.conversationPrompts,
13182
+ signals.actionSummary,
13183
+ signals.sessionAuthoredCode
13184
+ );
13185
+ }
13186
+ const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
13187
+ const agentInvestigated = didAgentInvestigate(signals.actionSummary);
13188
+ switch (predictedMode) {
13189
+ case "skip":
13190
+ if (agentAuthoredCode) return "standard";
13191
+ return "skip";
13192
+ case "plan":
13193
+ if (agentAuthoredCode) return "standard";
13194
+ return "plan";
13195
+ case "debug":
13196
+ return "debug";
13197
+ case "standard":
13198
+ if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
13199
+ return agentInvestigated ? "plan" : "skip";
13200
+ }
13201
+ return "standard";
13202
+ }
13203
+ }
13204
+ function didAgentInvestigate(summary) {
13205
+ if (!summary) return false;
13206
+ return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
13207
+ }
13208
+ function isValidMode(mode2) {
13209
+ return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
13210
+ }
13211
+ function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
13212
+ const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
13213
+ if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
13214
+ if (!agentAuthoredCode) return "skip";
13215
+ }
13216
+ if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
13217
+ return "plan";
13218
+ }
13219
+ if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
13220
+ return didAgentInvestigate(actionSummary) ? "plan" : "skip";
13221
+ }
13222
+ for (const prompt of conversationPrompts) {
13223
+ if (hasDebugIntent(prompt)) {
13224
+ return "debug";
13225
+ }
13226
+ }
13227
+ return "standard";
13228
+ }
13229
+ var FILE_MUTATE_RE = /(?:^|[\s|&;(`])(?:sed\s+-i|perl\s+-i|awk\b|tee\b|dd\b|cp\b|mv\b|ln\b|install\b|touch\b|patch\b|git\s+(?:apply|am)\b|cargo\s+build|go\s+generate|make\b|--write\b|--fix\b|--in-place\b)|>>?(?![&>])/i;
13230
+ var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
13231
+ var READ_ONLY_RE = /^\s*(?:git\s+(?:status|diff|log|show|branch|remote|config|rev-parse|ls-files|blame|describe)|ls|cat|head|tail|less|grep|rg|find|pwd|echo|printf|wc|which|type|tree|stat|file|env|printenv|date|whoami)\b/i;
13232
+ var CHAIN_RE = /&&|\||;|\$\(|\x60/;
13233
+ function isNonAuthoringCommand(cmd) {
13234
+ if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
13235
+ if (FILE_MUTATE_RE.test(cmd)) return false;
13236
+ if (CHAIN_RE.test(cmd)) return false;
13237
+ return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
13238
+ }
13239
+ function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
13240
+ if (!actionSummary) return sessionAuthoredCode;
13241
+ if ((actionSummary.subagents ?? 0) > 0) return true;
13242
+ if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
13243
+ const commands = actionSummary.commands ?? [];
13244
+ if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
13245
+ if (sessionAuthoredCode) {
13246
+ const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
13247
+ if (!allSafe) return true;
13248
+ }
13249
+ return false;
13250
+ }
13251
+ function scopeToAuthored(files, actionSummary) {
13252
+ if (!actionSummary) return { files, signal: "no-transcript" };
13253
+ const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
13254
+ if (touched.length === 0) return { files: [], signal: "none-authored" };
13255
+ return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
13256
+ }
13257
+ function chooseReviewScope(input) {
13258
+ if (input.narrowingIsTrustworthy) return input.scopedFiles;
13259
+ if (input.recoveredScope.length > 0) return input.recoveredScope;
13260
+ return input.authorshipWasObservable ? [] : input.allForReview;
13261
+ }
13262
+ function narrowToAgentAuthored(files, actionSummary) {
13263
+ if (!actionSummary) return files;
13264
+ const touched = [
13265
+ ...actionSummary.files_edited,
13266
+ ...actionSummary.files_created
13267
+ ];
13268
+ if (touched.length === 0) return files;
13269
+ return files.filter((f) => {
13270
+ const suffix = "/" + f;
13271
+ return touched.some((t) => t === f || t.endsWith(suffix));
13272
+ });
13273
+ }
13274
+
13275
+ // src/lib/skip-detection.ts
13276
+ function isBareAckPrompt(prompt) {
13277
+ if (typeof prompt !== "string") return false;
13278
+ const trimmed = prompt.trim();
13279
+ if (trimmed.length === 0) return false;
13280
+ if (trimmed.length > 20) return false;
13281
+ const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
13282
+ return bareAckPattern.test(trimmed);
13283
+ }
13284
+ function isContinuationPrompt(prompt) {
13285
+ if (typeof prompt !== "string") return false;
13286
+ const trimmed = prompt.trim();
13287
+ if (trimmed.length === 0) return false;
13288
+ if (trimmed.length > 24) return false;
13289
+ const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
13290
+ return continuation.test(trimmed) || isBareAckPrompt(trimmed);
13291
+ }
13292
+ function isSlashCommand(prompt) {
13293
+ if (typeof prompt !== "string") return false;
13294
+ return /^\s*\/[A-Za-z][\w-]*(\s|$)/.test(prompt);
13295
+ }
13296
+ function resolveGoalPrompt(prompts) {
13297
+ if (prompts.length === 0) return null;
13298
+ const latest = prompts[prompts.length - 1];
13299
+ if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
13300
+ for (let i = prompts.length - 2; i >= 0; i--) {
13301
+ if (!isContinuationPrompt(prompts[i].prompt)) {
13302
+ return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
13303
+ }
13304
+ }
13305
+ return { entry: latest, turnsBack: 0 };
13306
+ }
13307
+ function isReflectionQuestion(response) {
13308
+ if (!response || typeof response !== "string") return false;
13309
+ const markers = [
13310
+ /reflection\s+for\s+future\s+agents/i,
13311
+ /what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
13312
+ /say\s+['"]?skip['"]?\s+to\s+skip/i,
13313
+ /quick\s+reflection\s+question/i,
13314
+ // Post-flip (VRT-21): the agent drafts the reflection itself and, when
13315
+ // interactive, asks the user to confirm/correct before recording. That
13316
+ // turn authors no code either, so it's still a reflection turn.
13317
+ /reflection\s+draft/i,
13318
+ /confirm,?\s+correct,?\s+or\s+add/i
13319
+ ];
13320
+ return markers.some((m) => m.test(response));
13321
+ }
13322
+ function isMetaTaskLabel(label2) {
13323
+ if (label2 === null || label2 === void 0) return false;
13324
+ if (typeof label2 !== "string") return false;
13325
+ const trimmed = label2.trim();
13326
+ if (trimmed.length === 0) return true;
13327
+ const metaPatterns = [
13328
+ /^verity\s+[\w-]+\s+response$/i,
13329
+ // "Verity reflect response"
13330
+ /^simple user response$/i,
13331
+ /^verity\s+command$/i,
13332
+ // "Verity command"
13333
+ /^user\s+(question|reply|response|ack)$/i
13334
+ ];
13335
+ return metaPatterns.some((p) => p.test(trimmed));
13336
+ }
13337
+ function shouldSkipForBareAck(input) {
13338
+ if (!isBareAckPrompt(input.prompt)) return false;
13339
+ if (input.turnAuthoredCode) return false;
13340
+ return input.canSeeTurnAuthorship;
13341
+ }
13342
+ function isCommandOnlyTurn(input) {
13343
+ if (!input.authorshipIsObservable) return false;
13344
+ if (input.userCommandsTruncated) return false;
13345
+ const commands = input.userCommands ?? [];
13346
+ if (commands.length === 0) return false;
13347
+ if (input.agentAuthoredFiles > 0) return false;
13348
+ if (input.agentToolCalls > 0) return false;
13349
+ return commands.every(isNonAuthoringCommand);
13350
+ }
13351
+
13352
+ // src/lib/context-identity.ts
13353
+ var import_node_crypto2 = require("node:crypto");
13354
+ var import_node_fs8 = require("node:fs");
13355
+ var import_node_os2 = require("node:os");
13356
+ var import_node_path6 = require("node:path");
13357
+ var SHARED_SENTINELS = /* @__PURE__ */ new Set([
13358
+ "",
13359
+ "-",
13360
+ "n/a",
13361
+ "na",
13362
+ "none",
13363
+ "null",
13364
+ "undefined",
13365
+ "unknown",
13366
+ "anon",
13367
+ "anonymous",
13368
+ "default",
13369
+ "_default"
13370
+ ]);
13371
+ function isSharedSentinel(value) {
13372
+ if (value == null) return true;
13373
+ return SHARED_SENTINELS.has(value.trim().toLowerCase());
13374
+ }
13375
+ var ephemeral = /* @__PURE__ */ new Map();
13376
+ function ephemeralId(slot, width) {
13377
+ let id = ephemeral.get(slot);
13378
+ if (!id) {
13379
+ id = `eph-${(0, import_node_crypto2.randomBytes)(Math.ceil(width / 2)).toString("hex").slice(0, width - 4)}`;
13380
+ ephemeral.set(slot, id);
13381
+ }
13382
+ return id;
13383
+ }
13384
+ function contextIdentity(input) {
13385
+ const tokenUsable = !isSharedSentinel(input.token ?? null);
13386
+ const sessionUsable = !isSharedSentinel(input.sessionId ?? null);
13387
+ const userKey = tokenUsable ? (0, import_node_crypto2.createHash)("sha256").update(String(input.token)).digest("hex").slice(0, 12) : ephemeralId("user", 12);
13388
+ const sessionKey2 = sessionUsable ? (0, import_node_crypto2.createHash)("sha256").update(String(input.sessionId)).digest("hex").slice(0, 16) : ephemeralId("session", 16);
13389
+ let treeKey = "no-tree";
13390
+ const rawTree = input.treeRoot;
13391
+ if (rawTree && !isSharedSentinel(rawTree)) {
13392
+ let resolved = rawTree;
13393
+ try {
13394
+ resolved = import_node_fs8.realpathSync.native(rawTree);
13395
+ } catch {
13396
+ }
13397
+ treeKey = (0, import_node_crypto2.createHash)("sha256").update(resolved).digest("hex").slice(0, 12);
13398
+ }
13399
+ return {
13400
+ userKey,
13401
+ treeKey,
13402
+ sessionKey: sessionKey2,
13403
+ userKeySource: tokenUsable ? "token" : "ephemeral",
13404
+ sessionKeySource: sessionUsable ? "session_id" : "ephemeral",
13405
+ ephemeral: !tokenUsable || !sessionUsable
13406
+ };
13407
+ }
13408
+ function verityHome() {
13409
+ const override = process.env.VERITY_HOME;
13410
+ return override && override.trim() ? (0, import_node_path6.resolve)(override) : (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".verity");
13411
+ }
13412
+ function dossierDir(identity) {
13413
+ return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
13414
+ }
13415
+ function treeDir(identity) {
13416
+ return (0, import_node_path6.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
13417
+ }
13418
+ function scopeIdentity(token, sessionId) {
13419
+ const t = (token ?? "").trim();
13420
+ const s = (sessionId ?? "").trim();
13421
+ const userKey = t.length > 0 ? (0, import_node_crypto2.createHash)("sha256").update(t).digest("hex").slice(0, 12) : "anon";
13422
+ const sessionKey2 = s.length > 0 ? (0, import_node_crypto2.createHash)("sha256").update(s).digest("hex").slice(0, 16) : "_default";
13423
+ return { userKey, sessionKey: sessionKey2, bucket: `${userKey}/${sessionKey2}` };
13424
+ }
13425
+ function sessionScopeKey(token, sessionId) {
13426
+ return scopeIdentity(token, sessionId).bucket;
13427
+ }
13428
+
13429
+ // src/lib/task-context-buffer.ts
13430
+ var import_promises6 = require("node:fs/promises");
13431
+ var import_node_fs9 = require("node:fs");
13432
+ var import_node_path7 = require("node:path");
13433
+ var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
12987
13434
  var MAX_BUFFER_BYTES = 500 * 1024;
12988
13435
  var MAX_PROMPT_CHARS = 2e3;
12989
13436
  var MAX_RESPONSE_CHARS = 4e3;
@@ -13021,7 +13468,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
13021
13468
  }
13022
13469
  async function readTaskContextBuffer(taskId) {
13023
13470
  const filePath = bufferPath(taskId);
13024
- if (!(0, import_node_fs6.existsSync)(filePath)) return null;
13471
+ if (!(0, import_node_fs9.existsSync)(filePath)) return null;
13025
13472
  try {
13026
13473
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
13027
13474
  if (!content.trim()) return null;
@@ -13055,7 +13502,7 @@ async function readTaskContextBuffer(taskId) {
13055
13502
  }
13056
13503
  async function cleanupTaskContextBuffers() {
13057
13504
  try {
13058
- if (!(0, import_node_fs6.existsSync)(TASK_CONTEXT_DIR)) return;
13505
+ if (!(0, import_node_fs9.existsSync)(TASK_CONTEXT_DIR)) return;
13059
13506
  const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
13060
13507
  const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
13061
13508
  for (const file of files) {
@@ -13080,7 +13527,7 @@ async function appendEntry(taskId, entry) {
13080
13527
  try {
13081
13528
  await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
13082
13529
  const filePath = bufferPath(taskId);
13083
- if ((0, import_node_fs6.existsSync)(filePath)) {
13530
+ if ((0, import_node_fs9.existsSync)(filePath)) {
13084
13531
  const stats = await (0, import_promises6.stat)(filePath);
13085
13532
  if (stats.size >= MAX_BUFFER_BYTES) {
13086
13533
  const content = await (0, import_promises6.readFile)(filePath, "utf-8");
@@ -13091,7 +13538,7 @@ async function appendEntry(taskId, entry) {
13091
13538
  }
13092
13539
  }
13093
13540
  const line = JSON.stringify(entry) + "\n";
13094
- const existing = (0, import_node_fs6.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
13541
+ const existing = (0, import_node_fs9.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
13095
13542
  await (0, import_promises6.writeFile)(filePath, existing + line);
13096
13543
  } catch {
13097
13544
  }
@@ -13099,7 +13546,7 @@ async function appendEntry(taskId, entry) {
13099
13546
 
13100
13547
  // src/lib/memory-retrieval.ts
13101
13548
  var import_promises7 = require("node:fs/promises");
13102
- var import_node_fs7 = require("node:fs");
13549
+ var import_node_fs10 = require("node:fs");
13103
13550
  var import_node_path8 = require("node:path");
13104
13551
  var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
13105
13552
  var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
@@ -13193,13 +13640,13 @@ function parseFrontmatter(content) {
13193
13640
  return { fm, body: match[2].trim() };
13194
13641
  }
13195
13642
  async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
13196
- if (!(0, import_node_fs7.existsSync)(memoryDir())) return null;
13643
+ if (!(0, import_node_fs10.existsSync)(memoryDir())) return null;
13197
13644
  const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
13198
13645
  const promptTokens = tokenize(promptText);
13199
13646
  const nodes = [];
13200
13647
  for (const domain of DOMAINS) {
13201
13648
  const domainDir = (0, import_node_path8.join)(memoryDir(), domain);
13202
- if (!(0, import_node_fs7.existsSync)(domainDir)) continue;
13649
+ if (!(0, import_node_fs10.existsSync)(domainDir)) continue;
13203
13650
  try {
13204
13651
  const files = await (0, import_promises7.readdir)(domainDir);
13205
13652
  for (const file of files) {
@@ -13264,12 +13711,12 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
13264
13711
 
13265
13712
  // src/lib/memory-sync.ts
13266
13713
  var import_promises8 = require("node:fs/promises");
13267
- var import_node_fs9 = require("node:fs");
13714
+ var import_node_fs12 = require("node:fs");
13268
13715
  var import_node_path10 = require("node:path");
13269
13716
  var import_node_crypto3 = require("node:crypto");
13270
13717
 
13271
13718
  // src/lib/safe-path.ts
13272
- var import_node_fs8 = require("node:fs");
13719
+ var import_node_fs11 = require("node:fs");
13273
13720
  var import_node_path9 = require("node:path");
13274
13721
  function resolveInside(baseDir, candidate) {
13275
13722
  if (typeof candidate !== "string" || candidate.length === 0) return null;
@@ -13279,16 +13726,16 @@ function resolveInside(baseDir, candidate) {
13279
13726
  const baseSep = baseAbs.endsWith(import_node_path9.sep) ? baseAbs : baseAbs + import_node_path9.sep;
13280
13727
  if (full !== baseAbs && !full.startsWith(baseSep)) return null;
13281
13728
  try {
13282
- if ((0, import_node_fs8.existsSync)(baseAbs)) {
13283
- const realBase = (0, import_node_fs8.realpathSync)(baseAbs);
13729
+ if ((0, import_node_fs11.existsSync)(baseAbs)) {
13730
+ const realBase = (0, import_node_fs11.realpathSync)(baseAbs);
13284
13731
  const realBaseSep = realBase.endsWith(import_node_path9.sep) ? realBase : realBase + import_node_path9.sep;
13285
13732
  let probe = full;
13286
- while (!(0, import_node_fs8.existsSync)(probe)) {
13733
+ while (!(0, import_node_fs11.existsSync)(probe)) {
13287
13734
  const parent = (0, import_node_path9.dirname)(probe);
13288
13735
  if (parent === probe) break;
13289
13736
  probe = parent;
13290
13737
  }
13291
- const realProbe = (0, import_node_fs8.realpathSync)(probe);
13738
+ const realProbe = (0, import_node_fs11.realpathSync)(probe);
13292
13739
  if (realProbe !== realBase && !realProbe.startsWith(realBaseSep)) return null;
13293
13740
  }
13294
13741
  } catch {
@@ -13367,24 +13814,24 @@ async function ensureMemoryDir() {
13367
13814
  for (const domain of DOMAINS2) {
13368
13815
  await (0, import_promises8.mkdir)((0, import_node_path10.join)(memoryDir2(), domain), { recursive: true });
13369
13816
  }
13370
- if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"))) {
13817
+ if (!(0, import_node_fs12.existsSync)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"))) {
13371
13818
  await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
13372
13819
  }
13373
- if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "index.md"))) {
13820
+ if (!(0, import_node_fs12.existsSync)((0, import_node_path10.join)(memoryDir2(), "index.md"))) {
13374
13821
  await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
13375
13822
  }
13376
- if (!(0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md"))) {
13823
+ if (!(0, import_node_fs12.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md"))) {
13377
13824
  await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
13378
13825
  }
13379
13826
  }
13380
13827
  async function buildManifest() {
13381
- if (!(0, import_node_fs9.existsSync)(memoryDir2())) {
13828
+ if (!(0, import_node_fs12.existsSync)(memoryDir2())) {
13382
13829
  return { schema_version: 1, nodes: [], index_hash: null, log_length: 0 };
13383
13830
  }
13384
13831
  const nodes = [];
13385
13832
  for (const domain of DOMAINS2) {
13386
13833
  const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13387
- if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13834
+ if (!(0, import_node_fs12.existsSync)(domainDir)) continue;
13388
13835
  try {
13389
13836
  const files = await (0, import_promises8.readdir)(domainDir);
13390
13837
  for (const file of files) {
@@ -13420,10 +13867,10 @@ function hashContent(content) {
13420
13867
  }
13421
13868
  async function readOnDiskNodes() {
13422
13869
  const out = /* @__PURE__ */ new Map();
13423
- if (!(0, import_node_fs9.existsSync)(memoryDir2())) return out;
13870
+ if (!(0, import_node_fs12.existsSync)(memoryDir2())) return out;
13424
13871
  for (const domain of DOMAINS2) {
13425
13872
  const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13426
- if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
13873
+ if (!(0, import_node_fs12.existsSync)(domainDir)) continue;
13427
13874
  try {
13428
13875
  for (const file of await (0, import_promises8.readdir)(domainDir)) {
13429
13876
  if (!file.endsWith(".md")) continue;
@@ -13475,7 +13922,7 @@ async function computeEditedNodeUploads() {
13475
13922
  for (const [path, prevHash] of prev) {
13476
13923
  if (prevHash == null) continue;
13477
13924
  const full = (0, import_node_path10.join)(memoryDir2(), path);
13478
- if (!(0, import_node_fs9.existsSync)(full)) continue;
13925
+ if (!(0, import_node_fs12.existsSync)(full)) continue;
13479
13926
  let content;
13480
13927
  try {
13481
13928
  content = await (0, import_promises8.readFile)(full, "utf-8");
@@ -13511,7 +13958,7 @@ async function applyMemoryWrites(writes, opts = {}) {
13511
13958
  const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
13512
13959
  for (const n of notes) logLines.push(` - ${n}`);
13513
13960
  try {
13514
- const existing = (0, import_node_fs9.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
13961
+ const existing = (0, import_node_fs12.existsSync)((0, import_node_path10.join)(memoryDir2(), "log.md")) ? await (0, import_promises8.readFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
13515
13962
  await (0, import_promises8.writeFile)((0, import_node_path10.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
13516
13963
  } catch {
13517
13964
  }
@@ -13532,7 +13979,7 @@ async function applyOneWrite(write, treePaths) {
13532
13979
  notes.push(`${write.path}: dropped unmatched file_globs [${grounded.dropped.join(", ")}]`);
13533
13980
  }
13534
13981
  }
13535
- if ((0, import_node_fs9.existsSync)(fullPath)) {
13982
+ if ((0, import_node_fs12.existsSync)(fullPath)) {
13536
13983
  let existing = "";
13537
13984
  try {
13538
13985
  existing = await (0, import_promises8.readFile)(fullPath, "utf-8");
@@ -13586,7 +14033,7 @@ async function regenerateIndex() {
13586
14033
  let totalNodes = 0;
13587
14034
  for (const domain of DOMAINS2.filter((d) => d !== "_archive")) {
13588
14035
  const domainDir = (0, import_node_path10.join)(memoryDir2(), domain);
13589
- if (!(0, import_node_fs9.existsSync)(domainDir)) continue;
14036
+ if (!(0, import_node_fs12.existsSync)(domainDir)) continue;
13590
14037
  try {
13591
14038
  const files = await (0, import_promises8.readdir)(domainDir);
13592
14039
  const mdFiles = files.filter((f) => f.endsWith(".md"));
@@ -13856,7 +14303,7 @@ function hasLegacyMemoryBlock(text) {
13856
14303
  async function ensureClaudeMdPointer(cwd = repoRoot()) {
13857
14304
  const claudeMdPath = (0, import_node_path10.join)(cwd, "CLAUDE.md");
13858
14305
  let existing = "";
13859
- if ((0, import_node_fs9.existsSync)(claudeMdPath)) {
14306
+ if ((0, import_node_fs12.existsSync)(claudeMdPath)) {
13860
14307
  existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
13861
14308
  }
13862
14309
  let startTag = CLAUDE_MD_START;
@@ -13963,266 +14410,39 @@ source: extractor | user | imported
13963
14410
 
13964
14411
  # Title
13965
14412
 
13966
- Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
13967
- \`\`\`
13968
-
13969
- ## Edge types
13970
-
13971
- | Edge | Meaning |
13972
- |------|---------|
13973
- | related | Loose association |
13974
- | supersedes | A replaces B |
13975
- | contradicts | A and B disagree |
13976
- | caused_by | Something in B led to A |
13977
- | example_of | A is an instance of B |
13978
-
13979
- ## Domains
13980
-
13981
- | Directory | Purpose |
13982
- |-----------|---------|
13983
- | decisions/ | Architectural choices (ADR-style) |
13984
- | quality/ | Quality patterns |
13985
- | security/ | Security constraints |
13986
- | intent/ | Intent templates |
13987
- | gotchas/ | Footguns and surprises |
13988
- | patterns/ | Code conventions |
13989
- | domain/ | Business logic concepts |
13990
- | integrations/ | External system knowledge |
13991
- | _archive/ | Superseded nodes |
13992
- `;
13993
-
13994
- // src/lib/dossier-session.ts
13995
- var import_node_fs14 = require("node:fs");
13996
- var import_node_crypto7 = require("node:crypto");
13997
- var import_node_path13 = require("node:path");
13998
-
13999
- // src/lib/analysis-mode.ts
14000
- var DEBUG_PHRASES = [
14001
- "not working",
14002
- "doesn't work",
14003
- "doesn't work",
14004
- "does not work",
14005
- "isn't working",
14006
- "is not working",
14007
- "can't figure out",
14008
- "stack trace"
14009
- ];
14010
- var DEBUG_WORDS = [
14011
- "fix",
14012
- "bug",
14013
- "broken",
14014
- "crash",
14015
- "crashing",
14016
- "failing",
14017
- "debug",
14018
- "debugging",
14019
- "investigate",
14020
- "troubleshoot",
14021
- "regression",
14022
- "wrong"
14023
- ];
14024
- var DEBUG_PATTERN = new RegExp(
14025
- [
14026
- ...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
14027
- ...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
14028
- ].join("|"),
14029
- "i"
14030
- );
14031
- var FALSE_POSITIVE_PATTERNS = [
14032
- /\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
14033
- /\berror\s+handling\b/i,
14034
- /\berror\s+boundar(?:y|ies)\b/i,
14035
- /\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
14036
- /\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
14037
- ];
14038
- function hasDebugIntent(prompt) {
14039
- if (!DEBUG_PATTERN.test(prompt)) return false;
14040
- for (const fp of FALSE_POSITIVE_PATTERNS) {
14041
- if (fp.test(prompt)) return false;
14042
- }
14043
- return true;
14044
- }
14045
- var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
14046
- var CODE_AUTHORING_PATTERN = /\b(add|create|implement|build|write|fix|update|change|refactor|modify|remove|delete|move|rename)\b.*\b(function|component|feature|endpoint|test|file|module|class|type|interface|hook|page|route|style|migration|code|bug|error|issue)\b/i;
14047
- function isGitOnlyPrompt(prompt) {
14048
- if (!GIT_ONLY_PATTERN.test(prompt)) return false;
14049
- if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
14050
- return true;
14051
- }
14052
- function reconcileAnalysisMode(predictedMode, signals) {
14053
- const mode2 = resolveAnalysisMode(predictedMode, signals);
14054
- if (mode2 !== "skip") return mode2;
14055
- const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
14056
- if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
14057
- return mode2;
14058
- }
14059
- function resolveAnalysisMode(predictedMode, signals) {
14060
- if (!predictedMode || !isValidMode(predictedMode)) {
14061
- return detectAnalysisMode(
14062
- signals.noFilesChanged,
14063
- signals.assistantResponse,
14064
- signals.conversationPrompts,
14065
- signals.actionSummary,
14066
- signals.sessionAuthoredCode
14067
- );
14068
- }
14069
- const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
14070
- const agentInvestigated = didAgentInvestigate(signals.actionSummary);
14071
- switch (predictedMode) {
14072
- case "skip":
14073
- if (agentAuthoredCode) return "standard";
14074
- return "skip";
14075
- case "plan":
14076
- if (agentAuthoredCode) return "standard";
14077
- return "plan";
14078
- case "debug":
14079
- return "debug";
14080
- case "standard":
14081
- if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
14082
- return agentInvestigated ? "plan" : "skip";
14083
- }
14084
- return "standard";
14085
- }
14086
- }
14087
- function didAgentInvestigate(summary) {
14088
- if (!summary) return false;
14089
- return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
14090
- }
14091
- function isValidMode(mode2) {
14092
- return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
14093
- }
14094
- function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
14095
- const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
14096
- if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
14097
- if (!agentAuthoredCode) return "skip";
14098
- }
14099
- if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
14100
- return "plan";
14101
- }
14102
- if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
14103
- return didAgentInvestigate(actionSummary) ? "plan" : "skip";
14104
- }
14105
- for (const prompt of conversationPrompts) {
14106
- if (hasDebugIntent(prompt)) {
14107
- return "debug";
14108
- }
14109
- }
14110
- return "standard";
14111
- }
14112
- var FILE_MUTATE_RE = /(?:^|[\s|&;(`])(?:sed\s+-i|perl\s+-i|awk\b|tee\b|dd\b|cp\b|mv\b|ln\b|install\b|touch\b|patch\b|git\s+(?:apply|am)\b|cargo\s+build|go\s+generate|make\b|--write\b|--fix\b|--in-place\b)|>>?(?![&>])/i;
14113
- var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
14114
- var READ_ONLY_RE = /^\s*(?:git\s+(?:status|diff|log|show|branch|remote|config|rev-parse|ls-files|blame|describe)|ls|cat|head|tail|less|grep|rg|find|pwd|echo|printf|wc|which|type|tree|stat|file|env|printenv|date|whoami)\b/i;
14115
- var CHAIN_RE = /&&|\||;|\$\(|\x60/;
14116
- function isNonAuthoringCommand(cmd) {
14117
- if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
14118
- if (FILE_MUTATE_RE.test(cmd)) return false;
14119
- if (CHAIN_RE.test(cmd)) return false;
14120
- return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
14121
- }
14122
- function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
14123
- if (!actionSummary) return sessionAuthoredCode;
14124
- if ((actionSummary.subagents ?? 0) > 0) return true;
14125
- if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
14126
- const commands = actionSummary.commands ?? [];
14127
- if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
14128
- if (sessionAuthoredCode) {
14129
- const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
14130
- if (!allSafe) return true;
14131
- }
14132
- return false;
14133
- }
14134
- function scopeToAuthored(files, actionSummary) {
14135
- if (!actionSummary) return { files, signal: "no-transcript" };
14136
- const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
14137
- if (touched.length === 0) return { files: [], signal: "none-authored" };
14138
- return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
14139
- }
14140
- function narrowToAgentAuthored(files, actionSummary) {
14141
- if (!actionSummary) return files;
14142
- const touched = [
14143
- ...actionSummary.files_edited,
14144
- ...actionSummary.files_created
14145
- ];
14146
- if (touched.length === 0) return files;
14147
- return files.filter((f) => {
14148
- const suffix = "/" + f;
14149
- return touched.some((t) => t === f || t.endsWith(suffix));
14150
- });
14151
- }
14152
-
14153
- // src/lib/skip-detection.ts
14154
- function isBareAckPrompt(prompt) {
14155
- if (typeof prompt !== "string") return false;
14156
- const trimmed = prompt.trim();
14157
- if (trimmed.length === 0) return false;
14158
- if (trimmed.length > 20) return false;
14159
- const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
14160
- return bareAckPattern.test(trimmed);
14161
- }
14162
- function isContinuationPrompt(prompt) {
14163
- if (typeof prompt !== "string") return false;
14164
- const trimmed = prompt.trim();
14165
- if (trimmed.length === 0) return false;
14166
- if (trimmed.length > 24) return false;
14167
- const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
14168
- return continuation.test(trimmed) || isBareAckPrompt(trimmed);
14169
- }
14170
- function resolveGoalPrompt(prompts) {
14171
- if (prompts.length === 0) return null;
14172
- const latest = prompts[prompts.length - 1];
14173
- if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
14174
- for (let i = prompts.length - 2; i >= 0; i--) {
14175
- if (!isContinuationPrompt(prompts[i].prompt)) {
14176
- return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
14177
- }
14178
- }
14179
- return { entry: latest, turnsBack: 0 };
14180
- }
14181
- function isReflectionQuestion(response) {
14182
- if (!response || typeof response !== "string") return false;
14183
- const markers = [
14184
- /reflection\s+for\s+future\s+agents/i,
14185
- /what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
14186
- /say\s+['"]?skip['"]?\s+to\s+skip/i,
14187
- /quick\s+reflection\s+question/i,
14188
- // Post-flip (VRT-21): the agent drafts the reflection itself and, when
14189
- // interactive, asks the user to confirm/correct before recording. That
14190
- // turn authors no code either, so it's still a reflection turn.
14191
- /reflection\s+draft/i,
14192
- /confirm,?\s+correct,?\s+or\s+add/i
14193
- ];
14194
- return markers.some((m) => m.test(response));
14195
- }
14196
- function isMetaTaskLabel(label2) {
14197
- if (label2 === null || label2 === void 0) return false;
14198
- if (typeof label2 !== "string") return false;
14199
- const trimmed = label2.trim();
14200
- if (trimmed.length === 0) return true;
14201
- const metaPatterns = [
14202
- /^verity\s+[\w-]+\s+response$/i,
14203
- // "Verity reflect response"
14204
- /^simple user response$/i,
14205
- /^verity\s+command$/i,
14206
- // "Verity command"
14207
- /^user\s+(question|reply|response|ack)$/i
14208
- ];
14209
- return metaPatterns.some((p) => p.test(trimmed));
14210
- }
14211
- function shouldSkipForBareAck(input) {
14212
- if (!isBareAckPrompt(input.prompt)) return false;
14213
- if (input.turnAuthoredCode) return false;
14214
- return input.canSeeTurnAuthorship;
14215
- }
14216
- function isCommandOnlyTurn(input) {
14217
- if (!input.authorshipIsObservable) return false;
14218
- if (input.userCommandsTruncated) return false;
14219
- const commands = input.userCommands ?? [];
14220
- if (commands.length === 0) return false;
14221
- if (input.agentAuthoredFiles > 0) return false;
14222
- if (input.agentToolCalls > 0) return false;
14223
- return commands.every(isNonAuthoringCommand);
14224
- }
14225
-
14413
+ Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
14414
+ \`\`\`
14415
+
14416
+ ## Edge types
14417
+
14418
+ | Edge | Meaning |
14419
+ |------|---------|
14420
+ | related | Loose association |
14421
+ | supersedes | A replaces B |
14422
+ | contradicts | A and B disagree |
14423
+ | caused_by | Something in B led to A |
14424
+ | example_of | A is an instance of B |
14425
+
14426
+ ## Domains
14427
+
14428
+ | Directory | Purpose |
14429
+ |-----------|---------|
14430
+ | decisions/ | Architectural choices (ADR-style) |
14431
+ | quality/ | Quality patterns |
14432
+ | security/ | Security constraints |
14433
+ | intent/ | Intent templates |
14434
+ | gotchas/ | Footguns and surprises |
14435
+ | patterns/ | Code conventions |
14436
+ | domain/ | Business logic concepts |
14437
+ | integrations/ | External system knowledge |
14438
+ | _archive/ | Superseded nodes |
14439
+ `;
14440
+
14441
+ // src/lib/dossier-session.ts
14442
+ var import_node_fs17 = require("node:fs");
14443
+ var import_node_crypto7 = require("node:crypto");
14444
+ var import_node_path13 = require("node:path");
14445
+
14226
14446
  // src/lib/pending-repeat.ts
14227
14447
  var STOP = /* @__PURE__ */ new Set([
14228
14448
  "the",
@@ -14326,7 +14546,7 @@ function statementAnchorKey(file, patternId) {
14326
14546
 
14327
14547
  // src/lib/dossier/log.ts
14328
14548
  var import_node_crypto4 = require("node:crypto");
14329
- var import_node_fs10 = require("node:fs");
14549
+ var import_node_fs13 = require("node:fs");
14330
14550
  var import_node_path11 = require("node:path");
14331
14551
  var CRC_TABLE = (() => {
14332
14552
  const t = new Int32Array(256);
@@ -14346,7 +14566,7 @@ function crc32(s) {
14346
14566
  function openDossier(identity) {
14347
14567
  try {
14348
14568
  const dir = dossierDir(identity);
14349
- (0, import_node_fs10.mkdirSync)(dir, { recursive: true, mode: 448 });
14569
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true, mode: 448 });
14350
14570
  return {
14351
14571
  dir,
14352
14572
  identity,
@@ -14414,7 +14634,7 @@ function appendEvent(d, ev) {
14414
14634
  at: ev.at ?? (/* @__PURE__ */ new Date()).toISOString(),
14415
14635
  ...ev
14416
14636
  });
14417
- (0, import_node_fs10.appendFileSync)(d.eventsPath, line, { mode: 384 });
14637
+ (0, import_node_fs13.appendFileSync)(d.eventsPath, line, { mode: 384 });
14418
14638
  return true;
14419
14639
  } catch {
14420
14640
  return false;
@@ -14422,14 +14642,14 @@ function appendEvent(d, ev) {
14422
14642
  }
14423
14643
  function rotateIfNeeded2(d) {
14424
14644
  try {
14425
- if (!(0, import_node_fs10.existsSync)(d.eventsPath)) return;
14426
- if ((0, import_node_fs10.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
14427
- (0, import_node_fs10.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
14428
- (0, import_node_fs10.renameSync)(d.eventsPath, (0, import_node_path11.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
14429
- const kept = (0, import_node_fs10.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14645
+ if (!(0, import_node_fs13.existsSync)(d.eventsPath)) return;
14646
+ if ((0, import_node_fs13.statSync)(d.eventsPath).size < ROTATE_BYTES) return;
14647
+ (0, import_node_fs13.mkdirSync)(d.rotatedDir, { recursive: true, mode: 448 });
14648
+ (0, import_node_fs13.renameSync)(d.eventsPath, (0, import_node_path11.join)(d.rotatedDir, `events.${Date.now()}.jsonl`));
14649
+ const kept = (0, import_node_fs13.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14430
14650
  for (const stale of kept.slice(0, Math.max(0, kept.length - ROTATE_KEEP))) {
14431
14651
  try {
14432
- (0, import_node_fs10.renameSync)((0, import_node_path11.join)(d.rotatedDir, stale), (0, import_node_path11.join)(d.rotatedDir, `${stale}.pruned`));
14652
+ (0, import_node_fs13.renameSync)((0, import_node_path11.join)(d.rotatedDir, stale), (0, import_node_path11.join)(d.rotatedDir, `${stale}.pruned`));
14433
14653
  } catch {
14434
14654
  }
14435
14655
  }
@@ -14439,7 +14659,7 @@ function rotateIfNeeded2(d) {
14439
14659
 
14440
14660
  // src/lib/dossier/fold-dossier.ts
14441
14661
  var import_node_crypto5 = require("node:crypto");
14442
- var import_node_fs11 = require("node:fs");
14662
+ var import_node_fs14 = require("node:fs");
14443
14663
  var import_node_path12 = require("node:path");
14444
14664
  var EMPTY_CAPABILITIES = () => ({
14445
14665
  human_reachable: { value: "unknown", tier: "unknown" },
@@ -14491,12 +14711,12 @@ function foldDossier(d, opts = {}) {
14491
14711
  }
14492
14712
  };
14493
14713
  try {
14494
- if ((0, import_node_fs11.existsSync)(d.rotatedDir)) {
14495
- const files = (0, import_node_fs11.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14714
+ if ((0, import_node_fs14.existsSync)(d.rotatedDir)) {
14715
+ const files = (0, import_node_fs14.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).sort();
14496
14716
  state.meta.rotations = files.length;
14497
14717
  for (const f of files) {
14498
14718
  try {
14499
- ingest((0, import_node_fs11.readFileSync)((0, import_node_path12.join)(d.rotatedDir, f), "utf8"));
14719
+ ingest((0, import_node_fs14.readFileSync)((0, import_node_path12.join)(d.rotatedDir, f), "utf8"));
14500
14720
  } catch {
14501
14721
  state.meta.dropped_lines++;
14502
14722
  }
@@ -14505,9 +14725,9 @@ function foldDossier(d, opts = {}) {
14505
14725
  } catch {
14506
14726
  }
14507
14727
  try {
14508
- if ((0, import_node_fs11.existsSync)(d.eventsPath)) {
14509
- state.meta.upto_offset = (0, import_node_fs11.statSync)(d.eventsPath).size;
14510
- ingest((0, import_node_fs11.readFileSync)(d.eventsPath, "utf8"));
14728
+ if ((0, import_node_fs14.existsSync)(d.eventsPath)) {
14729
+ state.meta.upto_offset = (0, import_node_fs14.statSync)(d.eventsPath).size;
14730
+ ingest((0, import_node_fs14.readFileSync)(d.eventsPath, "utf8"));
14511
14731
  }
14512
14732
  } catch {
14513
14733
  }
@@ -14778,7 +14998,7 @@ function applyBounds(state, input) {
14778
14998
  }
14779
14999
 
14780
15000
  // src/lib/dossier/cache.ts
14781
- var import_node_fs12 = require("node:fs");
15001
+ var import_node_fs15 = require("node:fs");
14782
15002
  function compactState(s) {
14783
15003
  const ms = (iso) => Date.parse(iso) || 0;
14784
15004
  return {
@@ -14907,20 +15127,20 @@ function encodeState(s) {
14907
15127
  function writeFoldCache(d, state) {
14908
15128
  try {
14909
15129
  const tmp = `${d.foldPath}.${process.pid}.tmp`;
14910
- (0, import_node_fs12.writeFileSync)(tmp, encodeState(state), { mode: 384 });
14911
- (0, import_node_fs12.renameSync)(tmp, d.foldPath);
15130
+ (0, import_node_fs15.writeFileSync)(tmp, encodeState(state), { mode: 384 });
15131
+ (0, import_node_fs15.renameSync)(tmp, d.foldPath);
14912
15132
  } catch {
14913
15133
  }
14914
15134
  }
14915
15135
  function readFoldCache(d) {
14916
15136
  try {
14917
- if (!(0, import_node_fs12.existsSync)(d.foldPath)) return null;
14918
- const raw = JSON.parse((0, import_node_fs12.readFileSync)(d.foldPath, "utf8"));
15137
+ if (!(0, import_node_fs15.existsSync)(d.foldPath)) return null;
15138
+ const raw = JSON.parse((0, import_node_fs15.readFileSync)(d.foldPath, "utf8"));
14919
15139
  if (raw?.v !== 1) return null;
14920
15140
  const cached2 = expandState(raw);
14921
15141
  if (!cached2?.meta) return null;
14922
- const size = (0, import_node_fs12.existsSync)(d.eventsPath) ? (0, import_node_fs12.statSync)(d.eventsPath).size : 0;
14923
- const rotations = (0, import_node_fs12.existsSync)(d.rotatedDir) ? (0, import_node_fs12.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).length : 0;
15142
+ const size = (0, import_node_fs15.existsSync)(d.eventsPath) ? (0, import_node_fs15.statSync)(d.eventsPath).size : 0;
15143
+ const rotations = (0, import_node_fs15.existsSync)(d.rotatedDir) ? (0, import_node_fs15.readdirSync)(d.rotatedDir).filter((f) => f.endsWith(".jsonl")).length : 0;
14924
15144
  if (cached2.meta.upto_offset !== size || cached2.meta.rotations !== rotations) return null;
14925
15145
  return cached2;
14926
15146
  } catch {
@@ -14971,13 +15191,13 @@ function assessContinuity(i) {
14971
15191
 
14972
15192
  // src/lib/dossier/reanchor.ts
14973
15193
  var import_node_crypto6 = require("node:crypto");
14974
- var import_node_fs13 = require("node:fs");
15194
+ var import_node_fs16 = require("node:fs");
14975
15195
  function lineSha(text) {
14976
15196
  return (0, import_node_crypto6.createHash)("sha256").update(text.trim()).digest("hex").slice(0, HASH_WIDTH);
14977
15197
  }
14978
15198
  function fileHash(path) {
14979
15199
  try {
14980
- return (0, import_node_crypto6.createHash)("sha256").update((0, import_node_fs13.readFileSync)(path)).digest("hex").slice(0, HASH_WIDTH);
15200
+ return (0, import_node_crypto6.createHash)("sha256").update((0, import_node_fs16.readFileSync)(path)).digest("hex").slice(0, HASH_WIDTH);
14981
15201
  } catch {
14982
15202
  return null;
14983
15203
  }
@@ -15349,14 +15569,14 @@ function foreignAuthoredPaths(identity, opts = {}) {
15349
15569
  let sessions = 0;
15350
15570
  try {
15351
15571
  const dir = treeDir(identity);
15352
- if (!(0, import_node_fs14.existsSync)(dir)) return { paths: [], sessions: 0 };
15353
- for (const entry of (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true })) {
15572
+ if (!(0, import_node_fs17.existsSync)(dir)) return { paths: [], sessions: 0 };
15573
+ for (const entry of (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true })) {
15354
15574
  if (!entry.isDirectory()) continue;
15355
15575
  if (entry.name === identity.sessionKey) continue;
15356
15576
  const log = (0, import_node_path13.join)(dir, entry.name, "events.jsonl");
15357
15577
  try {
15358
- if (!(0, import_node_fs14.existsSync)(log)) continue;
15359
- if (now - (0, import_node_fs14.statSync)(log).mtimeMs > windowMs) continue;
15578
+ if (!(0, import_node_fs17.existsSync)(log)) continue;
15579
+ if (now - (0, import_node_fs17.statSync)(log).mtimeMs > windowMs) continue;
15360
15580
  const sib = {
15361
15581
  dir: (0, import_node_path13.join)(dir, entry.name),
15362
15582
  identity,
@@ -15391,13 +15611,13 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
15391
15611
  try {
15392
15612
  const mine = dossierDir(identity);
15393
15613
  const userDir = (0, import_node_path13.dirname)((0, import_node_path13.dirname)(mine));
15394
- if (!(0, import_node_fs14.existsSync)(userDir)) return 0;
15614
+ if (!(0, import_node_fs17.existsSync)(userDir)) return 0;
15395
15615
  const cutoff = Date.now() - maxAgeMs;
15396
- for (const tree of (0, import_node_fs14.readdirSync)(userDir, { withFileTypes: true })) {
15616
+ for (const tree of (0, import_node_fs17.readdirSync)(userDir, { withFileTypes: true })) {
15397
15617
  if (!tree.isDirectory()) continue;
15398
15618
  const treePath = (0, import_node_path13.join)(userDir, tree.name);
15399
15619
  let live = 0;
15400
- for (const entry of (0, import_node_fs14.readdirSync)(treePath, { withFileTypes: true })) {
15620
+ for (const entry of (0, import_node_fs17.readdirSync)(treePath, { withFileTypes: true })) {
15401
15621
  if (!entry.isDirectory()) continue;
15402
15622
  const dir = (0, import_node_path13.join)(treePath, entry.name);
15403
15623
  if (dir === mine) {
@@ -15406,9 +15626,9 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
15406
15626
  }
15407
15627
  try {
15408
15628
  const log = (0, import_node_path13.join)(dir, "events.jsonl");
15409
- const at = (0, import_node_fs14.existsSync)(log) ? (0, import_node_fs14.statSync)(log).mtimeMs : (0, import_node_fs14.statSync)(dir).mtimeMs;
15629
+ const at = (0, import_node_fs17.existsSync)(log) ? (0, import_node_fs17.statSync)(log).mtimeMs : (0, import_node_fs17.statSync)(dir).mtimeMs;
15410
15630
  if (at < cutoff) {
15411
- (0, import_node_fs14.rmSync)(dir, { recursive: true, force: true });
15631
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
15412
15632
  removed++;
15413
15633
  } else {
15414
15634
  live++;
@@ -15418,7 +15638,7 @@ function pruneOldDossiers(identity, maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
15418
15638
  }
15419
15639
  if (live === 0) {
15420
15640
  try {
15421
- (0, import_node_fs14.rmSync)(treePath, { recursive: false, force: false });
15641
+ (0, import_node_fs17.rmSync)(treePath, { recursive: false, force: false });
15422
15642
  } catch {
15423
15643
  }
15424
15644
  }
@@ -15435,8 +15655,8 @@ function sessionDossier(token, sessionId) {
15435
15655
  }
15436
15656
  function hasActiveGoal(d) {
15437
15657
  try {
15438
- if (!(0, import_node_fs14.existsSync)(d.eventsPath)) return false;
15439
- return (0, import_node_fs14.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
15658
+ if (!(0, import_node_fs17.existsSync)(d.eventsPath)) return false;
15659
+ return (0, import_node_fs17.readFileSync)(d.eventsPath, "utf8").includes('"k":"goal"');
15440
15660
  } catch {
15441
15661
  return false;
15442
15662
  }
@@ -15529,7 +15749,7 @@ function recordVerdict(d, v) {
15529
15749
  if (!lines.has(f.file)) {
15530
15750
  try {
15531
15751
  const abs = (0, import_node_path13.join)(root, f.file);
15532
- lines.set(f.file, (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null);
15752
+ lines.set(f.file, (0, import_node_fs17.existsSync)(abs) ? (0, import_node_fs17.readFileSync)(abs, "utf8").split("\n") : null);
15533
15753
  } catch {
15534
15754
  lines.set(f.file, null);
15535
15755
  }
@@ -15630,7 +15850,7 @@ function recallMemory(d, identity, opts) {
15630
15850
  readFileLines: (file) => {
15631
15851
  try {
15632
15852
  const abs = (0, import_node_path13.join)(root, file);
15633
- return (0, import_node_fs14.existsSync)(abs) ? (0, import_node_fs14.readFileSync)(abs, "utf8").split("\n") : null;
15853
+ return (0, import_node_fs17.existsSync)(abs) ? (0, import_node_fs17.readFileSync)(abs, "utf8").split("\n") : null;
15634
15854
  } catch {
15635
15855
  return null;
15636
15856
  }
@@ -15679,15 +15899,21 @@ function registerIntentCommands(program2) {
15679
15899
  if (!prompt) {
15680
15900
  process.exit(0);
15681
15901
  }
15902
+ if (deferredToPlugin("intent capture", event.session_id ?? process.env.CLAUDE_SESSION_ID ?? null)) {
15903
+ process.exit(0);
15904
+ }
15905
+ const isPrimitive = isSlashCommand(prompt);
15682
15906
  const authForScope = await resolveToken(program2.opts().token);
15683
15907
  const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
15684
15908
  const scopeSession = event.session_id || process.env.CLAUDE_SESSION_ID || "";
15685
- await appendToConversationBuffer(prompt, sessionScopeKey(scopeToken, scopeSession));
15686
- try {
15687
- const tok = await resolveToken(globals.token);
15688
- const session = sessionDossier(tok.ok ? tok.data.token : null, event.session_id ?? null);
15689
- if (session) recordGoal(session.d, prompt);
15690
- } catch {
15909
+ if (!isPrimitive) await appendToConversationBuffer(prompt, sessionScopeKey(scopeToken, scopeSession));
15910
+ if (!isPrimitive) {
15911
+ try {
15912
+ const tok = await resolveToken(globals.token);
15913
+ const session = sessionDossier(tok.ok ? tok.data.token : null, event.session_id ?? null);
15914
+ if (session) recordGoal(session.d, prompt);
15915
+ } catch {
15916
+ }
15691
15917
  }
15692
15918
  try {
15693
15919
  await ensureMemoryDir();
@@ -15768,21 +15994,21 @@ async function fireClassify(prompt, sessionId, globals) {
15768
15994
  }
15769
15995
 
15770
15996
  // src/commands/lifecycle.ts
15771
- var import_node_fs18 = require("node:fs");
15997
+ var import_node_fs21 = require("node:fs");
15772
15998
  var import_node_path17 = require("node:path");
15773
15999
 
15774
16000
  // src/lib/baseline.ts
15775
- var import_node_fs17 = require("node:fs");
16001
+ var import_node_fs20 = require("node:fs");
15776
16002
  var import_node_path16 = require("node:path");
15777
16003
  var import_node_crypto9 = require("node:crypto");
15778
16004
 
15779
16005
  // src/lib/snapshot.ts
15780
- var import_node_fs16 = require("node:fs");
16006
+ var import_node_fs19 = require("node:fs");
15781
16007
  var import_node_path15 = require("node:path");
15782
16008
  var import_node_child_process6 = require("node:child_process");
15783
16009
 
15784
16010
  // src/lib/files.ts
15785
- var import_node_fs15 = require("node:fs");
16011
+ var import_node_fs18 = require("node:fs");
15786
16012
  var import_node_path14 = require("node:path");
15787
16013
  var LANG_MAP = {
15788
16014
  // Analyzable (static analysis + Gemini)
@@ -15859,7 +16085,7 @@ function sortByMtime(files) {
15859
16085
  const resolved = resolveFile(f);
15860
16086
  if (!resolved) return null;
15861
16087
  try {
15862
- const stat3 = (0, import_node_fs15.statSync)(resolved);
16088
+ const stat3 = (0, import_node_fs18.statSync)(resolved);
15863
16089
  return { path: f, resolved, mtime: stat3.mtimeMs };
15864
16090
  } catch {
15865
16091
  return null;
@@ -15892,7 +16118,7 @@ function collectCodeDelta(files, opts) {
15892
16118
  }
15893
16119
  let size;
15894
16120
  try {
15895
- size = (0, import_node_fs15.statSync)(resolved).size;
16121
+ size = (0, import_node_fs18.statSync)(resolved).size;
15896
16122
  } catch {
15897
16123
  exclude(filepath, "not-stattable");
15898
16124
  continue;
@@ -15909,7 +16135,7 @@ function collectCodeDelta(files, opts) {
15909
16135
  }
15910
16136
  let content;
15911
16137
  try {
15912
- content = (0, import_node_fs15.readFileSync)(resolved, "utf-8");
16138
+ content = (0, import_node_fs18.readFileSync)(resolved, "utf-8");
15913
16139
  } catch {
15914
16140
  exclude(filepath, "not-readable");
15915
16141
  continue;
@@ -15948,7 +16174,7 @@ function collectCodeDelta(files, opts) {
15948
16174
 
15949
16175
  // src/lib/snapshot.ts
15950
16176
  function generateSnapshotDiffs(files) {
15951
- if (!(0, import_node_fs16.existsSync)(SNAPSHOT_DIR)) {
16177
+ if (!(0, import_node_fs19.existsSync)(SNAPSHOT_DIR)) {
15952
16178
  return { diffs: [], has_snapshots: false };
15953
16179
  }
15954
16180
  const diffs = [];
@@ -15956,8 +16182,8 @@ function generateSnapshotDiffs(files) {
15956
16182
  if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
15957
16183
  const snapshotPath = (0, import_node_path15.join)(SNAPSHOT_DIR, file.path);
15958
16184
  const language = file.language ?? detectLanguage(file.path);
15959
- if ((0, import_node_fs16.existsSync)(snapshotPath)) {
15960
- const oldContent = (0, import_node_fs16.readFileSync)(snapshotPath, "utf-8");
16185
+ if ((0, import_node_fs19.existsSync)(snapshotPath)) {
16186
+ const oldContent = (0, import_node_fs19.readFileSync)(snapshotPath, "utf-8");
15961
16187
  if (oldContent === file.content) continue;
15962
16188
  const diff = computeDiff(oldContent, file.content, file.path);
15963
16189
  if (diff) {
@@ -15984,8 +16210,8 @@ function saveSnapshots(files) {
15984
16210
  if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
15985
16211
  const snapshotPath = (0, import_node_path15.join)(SNAPSHOT_DIR, file.path);
15986
16212
  snapshotPaths.add(snapshotPath);
15987
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(snapshotPath), { recursive: true });
15988
- (0, import_node_fs16.writeFileSync)(snapshotPath, file.content);
16213
+ (0, import_node_fs19.mkdirSync)((0, import_node_path15.dirname)(snapshotPath), { recursive: true });
16214
+ (0, import_node_fs19.writeFileSync)(snapshotPath, file.content);
15989
16215
  }
15990
16216
  cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
15991
16217
  }
@@ -15993,9 +16219,9 @@ function computeDiff(oldContent, newContent, filePath) {
15993
16219
  const tmpOld = (0, import_node_path15.join)(SNAPSHOT_DIR, ".diff-old.tmp");
15994
16220
  const tmpNew = (0, import_node_path15.join)(SNAPSHOT_DIR, ".diff-new.tmp");
15995
16221
  try {
15996
- (0, import_node_fs16.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
15997
- (0, import_node_fs16.writeFileSync)(tmpOld, oldContent);
15998
- (0, import_node_fs16.writeFileSync)(tmpNew, newContent);
16222
+ (0, import_node_fs19.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
16223
+ (0, import_node_fs19.writeFileSync)(tmpOld, oldContent);
16224
+ (0, import_node_fs19.writeFileSync)(tmpNew, newContent);
15999
16225
  const result = (0, import_node_child_process6.execSync)(
16000
16226
  `git diff --no-index --unified=10 -- "${tmpOld}" "${tmpNew}"`,
16001
16227
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
@@ -16009,32 +16235,32 @@ function computeDiff(oldContent, newContent, filePath) {
16009
16235
  return null;
16010
16236
  } finally {
16011
16237
  try {
16012
- (0, import_node_fs16.unlinkSync)(tmpOld);
16238
+ (0, import_node_fs19.unlinkSync)(tmpOld);
16013
16239
  } catch {
16014
16240
  }
16015
16241
  try {
16016
- (0, import_node_fs16.unlinkSync)(tmpNew);
16242
+ (0, import_node_fs19.unlinkSync)(tmpNew);
16017
16243
  } catch {
16018
16244
  }
16019
16245
  }
16020
16246
  }
16021
16247
  function cleanStaleSnapshots(dir, keepSet) {
16022
- if (!(0, import_node_fs16.existsSync)(dir)) return;
16248
+ if (!(0, import_node_fs19.existsSync)(dir)) return;
16023
16249
  try {
16024
- const entries = (0, import_node_fs16.readdirSync)(dir, { withFileTypes: true });
16250
+ const entries = (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true });
16025
16251
  for (const entry of entries) {
16026
16252
  if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
16027
16253
  const fullPath = (0, import_node_path15.join)(dir, entry.name);
16028
16254
  if (entry.isDirectory()) {
16029
16255
  cleanStaleSnapshots(fullPath, keepSet);
16030
16256
  try {
16031
- const remaining = (0, import_node_fs16.readdirSync)(fullPath);
16032
- if (remaining.length === 0) (0, import_node_fs16.rmdirSync)(fullPath);
16257
+ const remaining = (0, import_node_fs19.readdirSync)(fullPath);
16258
+ if (remaining.length === 0) (0, import_node_fs19.rmdirSync)(fullPath);
16033
16259
  } catch {
16034
16260
  }
16035
16261
  } else if (!keepSet.has(fullPath)) {
16036
16262
  try {
16037
- (0, import_node_fs16.unlinkSync)(fullPath);
16263
+ (0, import_node_fs19.unlinkSync)(fullPath);
16038
16264
  } catch {
16039
16265
  }
16040
16266
  }
@@ -16065,8 +16291,8 @@ var CARRY_FILE = `${BASELINE_DIR}/.carry`;
16065
16291
  var CARRY_WINDOW_MS = 12e4;
16066
16292
  function writeCarry(sessionId, headSha) {
16067
16293
  try {
16068
- (0, import_node_fs17.mkdirSync)(projectPath(BASELINE_DIR), { recursive: true });
16069
- (0, import_node_fs17.writeFileSync)(
16294
+ (0, import_node_fs20.mkdirSync)(projectPath(BASELINE_DIR), { recursive: true });
16295
+ (0, import_node_fs20.writeFileSync)(
16070
16296
  projectPath(CARRY_FILE),
16071
16297
  JSON.stringify({ from_key: sessionKey(sessionId), head_sha: headSha, ts: Date.now() })
16072
16298
  );
@@ -16076,10 +16302,10 @@ function writeCarry(sessionId, headSha) {
16076
16302
  function claimCarry(newKey) {
16077
16303
  const carryPath = projectPath(CARRY_FILE);
16078
16304
  try {
16079
- if (!(0, import_node_fs17.existsSync)(carryPath)) return null;
16080
- const carry = JSON.parse((0, import_node_fs17.readFileSync)(carryPath, "utf-8"));
16305
+ if (!(0, import_node_fs20.existsSync)(carryPath)) return null;
16306
+ const carry = JSON.parse((0, import_node_fs20.readFileSync)(carryPath, "utf-8"));
16081
16307
  try {
16082
- (0, import_node_fs17.rmSync)(carryPath, { force: true });
16308
+ (0, import_node_fs20.rmSync)(carryPath, { force: true });
16083
16309
  } catch {
16084
16310
  }
16085
16311
  if (!carry?.from_key || typeof carry.ts !== "number") return null;
@@ -16090,11 +16316,11 @@ function claimCarry(newKey) {
16090
16316
  if (!prior) return null;
16091
16317
  const toDir = sessionDir(newKey);
16092
16318
  try {
16093
- (0, import_node_fs17.rmSync)(toDir, { recursive: true, force: true });
16319
+ (0, import_node_fs20.rmSync)(toDir, { recursive: true, force: true });
16094
16320
  } catch {
16095
16321
  }
16096
- (0, import_node_fs17.renameSync)(fromDir, toDir);
16097
- (0, import_node_fs17.writeFileSync)(manifestPath(toDir), JSON.stringify({ ...prior, session_id: newKey }) + "\n");
16322
+ (0, import_node_fs20.renameSync)(fromDir, toDir);
16323
+ (0, import_node_fs20.writeFileSync)(manifestPath(toDir), JSON.stringify({ ...prior, session_id: newKey }) + "\n");
16098
16324
  return readManifest(toDir);
16099
16325
  } catch {
16100
16326
  return null;
@@ -16118,21 +16344,21 @@ function captureBaseline(opts = {}) {
16118
16344
  const head_sha = getCurrentCommit();
16119
16345
  const dirty = getDirtyFiles();
16120
16346
  try {
16121
- (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
16347
+ (0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
16122
16348
  } catch {
16123
16349
  }
16124
16350
  const filesDir = (0, import_node_path16.join)(dir, "files");
16125
16351
  const mirrored = [];
16126
16352
  try {
16127
- (0, import_node_fs17.mkdirSync)(filesDir, { recursive: true });
16353
+ (0, import_node_fs20.mkdirSync)(filesDir, { recursive: true });
16128
16354
  for (const p of dirty) {
16129
16355
  if (p.includes("..")) continue;
16130
16356
  const content = safeReadForMirror(projectPath(p));
16131
16357
  if (content === null) continue;
16132
16358
  const dest = mirrorPath(dir, p);
16133
16359
  try {
16134
- (0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
16135
- (0, import_node_fs17.writeFileSync)(dest, content);
16360
+ (0, import_node_fs20.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
16361
+ (0, import_node_fs20.writeFileSync)(dest, content);
16136
16362
  mirrored.push(p);
16137
16363
  } catch {
16138
16364
  }
@@ -16147,8 +16373,8 @@ function captureBaseline(opts = {}) {
16147
16373
  version: BASELINE_VERSION
16148
16374
  };
16149
16375
  try {
16150
- (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
16151
- (0, import_node_fs17.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
16376
+ (0, import_node_fs20.mkdirSync)(dir, { recursive: true });
16377
+ (0, import_node_fs20.writeFileSync)(manifestPath(dir), JSON.stringify(baseline));
16152
16378
  } catch {
16153
16379
  }
16154
16380
  pruneOldBaselines();
@@ -16159,9 +16385,9 @@ function readBaseline(sessionId) {
16159
16385
  }
16160
16386
  function readManifest(dir) {
16161
16387
  const mp = manifestPath(dir);
16162
- if (!(0, import_node_fs17.existsSync)(mp)) return null;
16388
+ if (!(0, import_node_fs20.existsSync)(mp)) return null;
16163
16389
  try {
16164
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(mp, "utf-8"));
16390
+ const parsed = JSON.parse((0, import_node_fs20.readFileSync)(mp, "utf-8"));
16165
16391
  if (typeof parsed.head_sha !== "string" || typeof parsed.captured_at !== "number" || !Array.isArray(parsed.dirty_paths) || parsed.version !== BASELINE_VERSION) {
16166
16392
  return null;
16167
16393
  }
@@ -16192,9 +16418,9 @@ function preImage(repoRelPath, baseline) {
16192
16418
  function resolvePreImage(repoRelPath, baseline) {
16193
16419
  if (baseline.dirty_paths.includes(repoRelPath)) {
16194
16420
  const mp = mirrorPath(sessionDir(sessionKey(baseline.session_id)), repoRelPath);
16195
- if ((0, import_node_fs17.existsSync)(mp)) {
16421
+ if ((0, import_node_fs20.existsSync)(mp)) {
16196
16422
  try {
16197
- return { content: (0, import_node_fs17.readFileSync)(mp, "utf-8"), existed: true };
16423
+ return { content: (0, import_node_fs20.readFileSync)(mp, "utf-8"), existed: true };
16198
16424
  } catch {
16199
16425
  }
16200
16426
  }
@@ -16239,8 +16465,8 @@ function absorbIntoBaseline(paths, sessionId) {
16239
16465
  const content = safeReadForMirror(projectPath(p));
16240
16466
  if (content === null) continue;
16241
16467
  const dest = mirrorPath(dir, p);
16242
- (0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
16243
- (0, import_node_fs17.writeFileSync)(dest, content);
16468
+ (0, import_node_fs20.mkdirSync)((0, import_node_path16.dirname)(dest), { recursive: true });
16469
+ (0, import_node_fs20.writeFileSync)(dest, content);
16244
16470
  dirty.add(p);
16245
16471
  adopted++;
16246
16472
  } catch {
@@ -16249,7 +16475,7 @@ function absorbIntoBaseline(paths, sessionId) {
16249
16475
  if (adopted === 0) return 0;
16250
16476
  try {
16251
16477
  const updated = { ...baseline, dirty_paths: [...dirty] };
16252
- (0, import_node_fs17.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
16478
+ (0, import_node_fs20.writeFileSync)(manifestPath(dir), JSON.stringify(updated));
16253
16479
  preImageCache.delete(baseline);
16254
16480
  } catch {
16255
16481
  return 0;
@@ -16260,7 +16486,7 @@ function changedSinceBaseline(repoRelPath, baseline) {
16260
16486
  const pre = preImage(repoRelPath, baseline);
16261
16487
  let current;
16262
16488
  try {
16263
- current = (0, import_node_fs17.readFileSync)(projectPath(repoRelPath), "utf-8");
16489
+ current = (0, import_node_fs20.readFileSync)(projectPath(repoRelPath), "utf-8");
16264
16490
  } catch {
16265
16491
  return pre.existed;
16266
16492
  }
@@ -16269,8 +16495,8 @@ function changedSinceBaseline(repoRelPath, baseline) {
16269
16495
  }
16270
16496
  function safeReadForMirror(absPath) {
16271
16497
  try {
16272
- if ((0, import_node_fs17.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
16273
- const buf = (0, import_node_fs17.readFileSync)(absPath);
16498
+ if ((0, import_node_fs20.statSync)(absPath).size > MIRROR_MAX_BYTES) return null;
16499
+ const buf = (0, import_node_fs20.readFileSync)(absPath);
16274
16500
  if (buf.includes(0)) return null;
16275
16501
  return buf.toString("utf-8");
16276
16502
  } catch {
@@ -16281,7 +16507,7 @@ function pruneOldBaselines() {
16281
16507
  const root = projectPath(BASELINE_DIR);
16282
16508
  let entries;
16283
16509
  try {
16284
- entries = (0, import_node_fs17.readdirSync)(root);
16510
+ entries = (0, import_node_fs20.readdirSync)(root);
16285
16511
  } catch {
16286
16512
  return;
16287
16513
  }
@@ -16291,8 +16517,8 @@ function pruneOldBaselines() {
16291
16517
  const manifest = readManifest(dir);
16292
16518
  if (!manifest) {
16293
16519
  try {
16294
- if (now - (0, import_node_fs17.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
16295
- (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
16520
+ if (now - (0, import_node_fs20.statSync)(dir).mtimeMs > BASELINE_TTL_MS) {
16521
+ (0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
16296
16522
  }
16297
16523
  } catch {
16298
16524
  }
@@ -16300,7 +16526,7 @@ function pruneOldBaselines() {
16300
16526
  }
16301
16527
  if (now - manifest.captured_at <= BASELINE_TTL_MS) continue;
16302
16528
  try {
16303
- (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
16529
+ (0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
16304
16530
  } catch {
16305
16531
  }
16306
16532
  }
@@ -16394,6 +16620,7 @@ function registerLifecycleCommands(program2) {
16394
16620
  if (!verityConfigured()) process.exit(0);
16395
16621
  const event = await readHookStdin();
16396
16622
  const sessionId = opts.sessionId ?? event.session_id ?? process.env.CLAUDE_SESSION_ID ?? null;
16623
+ if (deferredToPlugin("compact", sessionId)) process.exit(0);
16397
16624
  const globals = program2.opts();
16398
16625
  const tok = await resolveToken(globals.token);
16399
16626
  const session2 = sessionDossier(tok.ok ? tok.data.token : null, sessionId);
@@ -16422,6 +16649,7 @@ function registerLifecycleCommands(program2) {
16422
16649
  if (!verityConfigured()) process.exit(0);
16423
16650
  const event = await readHookStdin();
16424
16651
  const sessionId = opts.sessionId ?? event.session_id ?? process.env.CLAUDE_SESSION_ID ?? null;
16652
+ if (deferredToPlugin("session end", sessionId)) process.exit(0);
16425
16653
  const reason = opts.reason ?? event.session_end_reason ?? event.reason ?? "other";
16426
16654
  if (reason === "clear") {
16427
16655
  writeCarry(sessionId ?? void 0, getCurrentCommit());
@@ -16474,7 +16702,7 @@ function buildCompactionContext(session) {
16474
16702
  readFileLines: (file) => {
16475
16703
  try {
16476
16704
  const abs = (0, import_node_path17.join)(root, file);
16477
- return (0, import_node_fs18.existsSync)(abs) ? (0, import_node_fs18.readFileSync)(abs, "utf8").split("\n") : null;
16705
+ return (0, import_node_fs21.existsSync)(abs) ? (0, import_node_fs21.readFileSync)(abs, "utf8").split("\n") : null;
16478
16706
  } catch {
16479
16707
  return null;
16480
16708
  }
@@ -16531,18 +16759,18 @@ async function readHookStdin() {
16531
16759
 
16532
16760
  // src/commands/standard.ts
16533
16761
  var import_promises12 = require("node:fs/promises");
16534
- var import_node_fs25 = require("node:fs");
16762
+ var import_node_fs28 = require("node:fs");
16535
16763
  var import_yaml3 = __toESM(require_dist());
16536
16764
 
16537
16765
  // src/lib/synthesize.ts
16538
16766
  var import_node_child_process8 = require("node:child_process");
16539
- var import_node_fs21 = require("node:fs");
16767
+ var import_node_fs24 = require("node:fs");
16540
16768
  var import_promises9 = require("node:fs/promises");
16541
16769
  var import_node_path20 = require("node:path");
16542
16770
  var import_yaml = __toESM(require_dist());
16543
16771
 
16544
16772
  // src/lib/data-dir.ts
16545
- var import_node_fs19 = require("node:fs");
16773
+ var import_node_fs22 = require("node:fs");
16546
16774
  var import_node_path18 = require("node:path");
16547
16775
  function resolveDataDir() {
16548
16776
  const candidates = [
@@ -16550,10 +16778,21 @@ function resolveDataDir() {
16550
16778
  // installed: node_modules/@codacy/verity-cli/data
16551
16779
  (0, import_node_path18.join)(__dirname, "..", "..", "data"),
16552
16780
  // edge case: nested resolution
16781
+ // THE COMMITTED SOURCE, for a source checkout that has not been built.
16782
+ // cli/data/skills/ is a BUILD ARTIFACT (scripts/build.js copies client/skills
16783
+ // into it) and is gitignored, because skills have one committed source — so
16784
+ // in a fresh clone the packaged candidates above do not exist at all, and
16785
+ // without this the synthesizer throws "Could not find Verity skill data"
16786
+ // for every test and every `verity` run from source. Resolved from this
16787
+ // module's own location, never the cwd: see the warning below.
16788
+ (0, import_node_path18.join)(__dirname, "..", "..", "client"),
16789
+ // bundled: cli/bin/ → ../../client
16790
+ (0, import_node_path18.join)(__dirname, "..", "..", "..", "client"),
16791
+ // tsx: cli/src/lib/ → ../../../client
16553
16792
  ...process.env.VERITY_DEV_DATA_DIR ? [process.env.VERITY_DEV_DATA_DIR] : []
16554
16793
  ];
16555
16794
  for (const candidate of candidates) {
16556
- if ((0, import_node_fs19.existsSync)((0, import_node_path18.join)(candidate, "skills"))) {
16795
+ if ((0, import_node_fs22.existsSync)((0, import_node_path18.join)(candidate, "skills"))) {
16557
16796
  return candidate;
16558
16797
  }
16559
16798
  }
@@ -16567,7 +16806,7 @@ function setupDataPath(file) {
16567
16806
 
16568
16807
  // src/lib/detect.ts
16569
16808
  var import_node_child_process7 = require("node:child_process");
16570
- var import_node_fs20 = require("node:fs");
16809
+ var import_node_fs23 = require("node:fs");
16571
16810
  var import_node_path19 = require("node:path");
16572
16811
  var TOOLED_LANGUAGES = /* @__PURE__ */ new Set([
16573
16812
  "typescript",
@@ -16624,7 +16863,7 @@ function walk(root) {
16624
16863
  if (depth > WALK_MAX_DEPTH || found.length >= WALK_MAX_FILES) return;
16625
16864
  let entries;
16626
16865
  try {
16627
- entries = (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true });
16866
+ entries = (0, import_node_fs23.readdirSync)(dir, { withFileTypes: true });
16628
16867
  } catch {
16629
16868
  return;
16630
16869
  }
@@ -16699,7 +16938,7 @@ var TOOL_CONFIG_MARKERS = [
16699
16938
  ];
16700
16939
  function readJson(path) {
16701
16940
  try {
16702
- return JSON.parse((0, import_node_fs20.readFileSync)(path, "utf-8"));
16941
+ return JSON.parse((0, import_node_fs23.readFileSync)(path, "utf-8"));
16703
16942
  } catch {
16704
16943
  return null;
16705
16944
  }
@@ -16732,9 +16971,9 @@ function declaredDependencies(root, files) {
16732
16971
  ...files.filter((f) => f.includes("/") && /(^|\/)(pyproject\.toml|requirements\.txt)$/.test(f)).slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path19.join)(root, f))
16733
16972
  ];
16734
16973
  for (const path of pythonManifests) {
16735
- if (!(0, import_node_fs20.existsSync)(path)) continue;
16974
+ if (!(0, import_node_fs23.existsSync)(path)) continue;
16736
16975
  try {
16737
- const text = (0, import_node_fs20.readFileSync)(path, "utf-8");
16976
+ const text = (0, import_node_fs23.readFileSync)(path, "utf-8");
16738
16977
  for (const m of text.matchAll(/^\s*["']?([A-Za-z][A-Za-z0-9._-]+)/gm)) names2.push(m[1]);
16739
16978
  for (const line of text.split("\n")) {
16740
16979
  if (!/dependencies\s*=/.test(line)) continue;
@@ -16748,9 +16987,9 @@ function declaredDependencies(root, files) {
16748
16987
  ...files.filter((f) => f.includes("/") && (0, import_node_path19.basename)(f) === "go.mod").slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path19.join)(root, f))
16749
16988
  ];
16750
16989
  for (const path of goMods) {
16751
- if (!(0, import_node_fs20.existsSync)(path)) continue;
16990
+ if (!(0, import_node_fs23.existsSync)(path)) continue;
16752
16991
  try {
16753
- const text = (0, import_node_fs20.readFileSync)(path, "utf-8");
16992
+ const text = (0, import_node_fs23.readFileSync)(path, "utf-8");
16754
16993
  for (const m of text.matchAll(/^\s+([\w.-]+\/[\w./-]+)\s+v/gm)) {
16755
16994
  names2.push(m[1].replace(/^github\.com\//, ""));
16756
16995
  }
@@ -16759,9 +16998,9 @@ function declaredDependencies(root, files) {
16759
16998
  }
16760
16999
  for (const file of ["pom.xml", "build.gradle", "build.gradle.kts", "Gemfile", "Cargo.toml"]) {
16761
17000
  const path = (0, import_node_path19.join)(root, file);
16762
- if (!(0, import_node_fs20.existsSync)(path)) continue;
17001
+ if (!(0, import_node_fs23.existsSync)(path)) continue;
16763
17002
  try {
16764
- const text = (0, import_node_fs20.readFileSync)(path, "utf-8");
17003
+ const text = (0, import_node_fs23.readFileSync)(path, "utf-8");
16765
17004
  for (const m of text.matchAll(/["'<]([A-Za-z][A-Za-z0-9._-]{2,})["'>]/g)) names2.push(m[1]);
16766
17005
  } catch {
16767
17006
  }
@@ -16769,7 +17008,7 @@ function declaredDependencies(root, files) {
16769
17008
  return names2;
16770
17009
  }
16771
17010
  function detectBuildSystem(root, files) {
16772
- const has = (f) => (0, import_node_fs20.existsSync)((0, import_node_path19.join)(root, f)) || files.some((p) => (0, import_node_path19.basename)(p) === f);
17011
+ const has = (f) => (0, import_node_fs23.existsSync)((0, import_node_path19.join)(root, f)) || files.some((p) => (0, import_node_path19.basename)(p) === f);
16773
17012
  if (has("pnpm-lock.yaml")) return "pnpm";
16774
17013
  if (has("yarn.lock")) return "yarn";
16775
17014
  if (has("bun.lock") || has("bun.lockb")) return "bun";
@@ -16786,7 +17025,7 @@ function detectBuildSystem(root, files) {
16786
17025
  }
16787
17026
  function detectArchitecture(root, files) {
16788
17027
  const workspaceMarkers = ["lerna.json", "pnpm-workspace.yaml", "nx.json", "turbo.json", "rush.json"];
16789
- if (workspaceMarkers.some((m) => (0, import_node_fs20.existsSync)((0, import_node_path19.join)(root, m)))) return "monorepo";
17028
+ if (workspaceMarkers.some((m) => (0, import_node_fs23.existsSync)((0, import_node_path19.join)(root, m)))) return "monorepo";
16790
17029
  const pkg = readJson((0, import_node_path19.join)(root, "package.json"));
16791
17030
  if (pkg && "workspaces" in pkg) return "monorepo";
16792
17031
  const manifests = files.filter((f) => /(^|\/)(package\.json|go\.mod|pyproject\.toml|Cargo\.toml|pom\.xml)$/.test(f));
@@ -16811,8 +17050,8 @@ function measureAvgFileLength(root, files, languages) {
16811
17050
  for (let i = 0; i < candidates.length; i += stride) {
16812
17051
  const path = (0, import_node_path19.join)(root, candidates[i]);
16813
17052
  try {
16814
- if ((0, import_node_fs20.statSync)(path).size > 2 * 1024 * 1024) continue;
16815
- total += (0, import_node_fs20.readFileSync)(path, "utf-8").split("\n").length;
17053
+ if ((0, import_node_fs23.statSync)(path).size > 2 * 1024 * 1024) continue;
17054
+ total += (0, import_node_fs23.readFileSync)(path, "utf-8").split("\n").length;
16816
17055
  counted++;
16817
17056
  } catch {
16818
17057
  }
@@ -16837,7 +17076,7 @@ function detectProject(root = repoRoot()) {
16837
17076
  const existingToolConfigs = [];
16838
17077
  for (const [tool, markers] of TOOL_CONFIG_MARKERS) {
16839
17078
  for (const marker of markers) {
16840
- if ((0, import_node_fs20.existsSync)((0, import_node_path19.join)(root, marker))) {
17079
+ if ((0, import_node_fs23.existsSync)((0, import_node_path19.join)(root, marker))) {
16841
17080
  existingToolConfigs.push({ tool, path: `./${marker}` });
16842
17081
  break;
16843
17082
  }
@@ -16942,8 +17181,8 @@ ${closingNote(input.origin)}
16942
17181
 
16943
17182
  // src/lib/synthesize.ts
16944
17183
  function loadCatalog() {
16945
- const catalog = (0, import_yaml.parse)((0, import_node_fs21.readFileSync)(setupDataPath("patterns-reference.yaml"), "utf-8"));
16946
- const template = (0, import_yaml.parse)((0, import_node_fs21.readFileSync)(setupDataPath("standard-template.yaml"), "utf-8"));
17184
+ const catalog = (0, import_yaml.parse)((0, import_node_fs24.readFileSync)(setupDataPath("patterns-reference.yaml"), "utf-8"));
17185
+ const template = (0, import_yaml.parse)((0, import_node_fs24.readFileSync)(setupDataPath("standard-template.yaml"), "utf-8"));
16947
17186
  return { catalog, template };
16948
17187
  }
16949
17188
  function selectTools(languages, intensity, catalog) {
@@ -17221,7 +17460,7 @@ function validatePatternIds() {
17221
17460
  }
17222
17461
  async function runSynthesis(opts) {
17223
17462
  const standardPath = projectPath(STANDARD_FILE);
17224
- if ((0, import_node_fs21.existsSync)(standardPath) && !opts.force) {
17463
+ if ((0, import_node_fs24.existsSync)(standardPath) && !opts.force) {
17225
17464
  return { refused: `${STANDARD_FILE} already exists \u2014 pass --force to replace it.` };
17226
17465
  }
17227
17466
  const detected = opts.detected ?? detectProject();
@@ -17349,11 +17588,11 @@ ${validation.detail}`);
17349
17588
 
17350
17589
  // src/lib/setup-state.ts
17351
17590
  var import_promises10 = require("node:fs/promises");
17352
- var import_node_fs22 = require("node:fs");
17591
+ var import_node_fs25 = require("node:fs");
17353
17592
  var SETUP_STATE_FILE = `${VERITY_DIR}/setup.json`;
17354
17593
  async function readSetupState() {
17355
17594
  const path = projectPath(SETUP_STATE_FILE);
17356
- if (!(0, import_node_fs22.existsSync)(path)) return null;
17595
+ if (!(0, import_node_fs25.existsSync)(path)) return null;
17357
17596
  try {
17358
17597
  const parsed = JSON.parse(await (0, import_promises10.readFile)(path, "utf-8"));
17359
17598
  return parsed && typeof parsed === "object" ? parsed : null;
@@ -17369,12 +17608,12 @@ async function writeSetupState(patch) {
17369
17608
  }
17370
17609
 
17371
17610
  // src/lib/push-setup.ts
17372
- var import_node_fs24 = require("node:fs");
17611
+ var import_node_fs27 = require("node:fs");
17373
17612
  var import_promises11 = require("node:fs/promises");
17374
17613
  var import_yaml2 = __toESM(require_dist());
17375
17614
 
17376
17615
  // src/lib/verityignore.ts
17377
- var import_node_fs23 = require("node:fs");
17616
+ var import_node_fs26 = require("node:fs");
17378
17617
  var EMPTY = { rules: [], securityOverlap: [], problems: [] };
17379
17618
  var SECURITY_PROBES = [
17380
17619
  ".env",
@@ -17467,9 +17706,9 @@ function isIgnored2(ig, path) {
17467
17706
  }
17468
17707
  function loadVerityIgnore() {
17469
17708
  const file = projectPath(VERITYIGNORE_FILE);
17470
- if (!(0, import_node_fs23.existsSync)(file)) return EMPTY;
17709
+ if (!(0, import_node_fs26.existsSync)(file)) return EMPTY;
17471
17710
  try {
17472
- return parseVerityIgnore((0, import_node_fs23.readFileSync)(file, "utf-8"));
17711
+ return parseVerityIgnore((0, import_node_fs26.readFileSync)(file, "utf-8"));
17473
17712
  } catch {
17474
17713
  return EMPTY;
17475
17714
  }
@@ -17507,9 +17746,9 @@ function buildStandardUpload(standard, ignoreRaw) {
17507
17746
  }
17508
17747
  function readVerityIgnoreRaw() {
17509
17748
  const file = projectPath(VERITYIGNORE_FILE);
17510
- if (!(0, import_node_fs23.existsSync)(file)) return null;
17749
+ if (!(0, import_node_fs26.existsSync)(file)) return null;
17511
17750
  try {
17512
- return (0, import_node_fs23.readFileSync)(file, "utf-8");
17751
+ return (0, import_node_fs26.readFileSync)(file, "utf-8");
17513
17752
  } catch {
17514
17753
  return null;
17515
17754
  }
@@ -17537,7 +17776,7 @@ async function pushStandardAndConfig(globals, what = {}) {
17537
17776
  }
17538
17777
  let standardVersion = null;
17539
17778
  const standardPath = projectPath(STANDARD_FILE);
17540
- if (pushStandard && (0, import_node_fs24.existsSync)(standardPath)) {
17779
+ if (pushStandard && (0, import_node_fs27.existsSync)(standardPath)) {
17541
17780
  try {
17542
17781
  const content = (0, import_yaml2.parse)(await (0, import_promises11.readFile)(standardPath, "utf-8"));
17543
17782
  const upload = buildStandardUpload(content, readVerityIgnoreRaw());
@@ -17562,7 +17801,7 @@ async function pushStandardAndConfig(globals, what = {}) {
17562
17801
  }
17563
17802
  let configPushed = false;
17564
17803
  const configPath = projectPath(CODACY_CONFIG_FILE);
17565
- if (pushConfig && (0, import_node_fs24.existsSync)(configPath)) {
17804
+ if (pushConfig && (0, import_node_fs27.existsSync)(configPath)) {
17566
17805
  try {
17567
17806
  const content = JSON.parse(await (0, import_promises11.readFile)(configPath, "utf-8"));
17568
17807
  const result = await apiRequest({
@@ -17594,7 +17833,7 @@ function registerStandardCommands(program2) {
17594
17833
  const state = await readSetupState();
17595
17834
  if (opts.configOnly) {
17596
17835
  const standardPath = projectPath(STANDARD_FILE);
17597
- if (!(0, import_node_fs25.existsSync)(standardPath)) {
17836
+ if (!(0, import_node_fs28.existsSync)(standardPath)) {
17598
17837
  printError(`No ${STANDARD_FILE} here \u2014 run "verity standard synthesize" to create one.`);
17599
17838
  process.exit(1);
17600
17839
  }
@@ -17763,6 +18002,22 @@ function registerConfigCommands(program2) {
17763
18002
  }
17764
18003
  process.stdout.write(urlResult.data + "\n");
17765
18004
  });
18005
+ config.command("git-moments [moments]").description('Get or set the git moments the guard reviews: commit,push \u2014 or "none"').action((moments) => {
18006
+ if (moments === void 0) {
18007
+ const current = readProjectConfig().git_moments;
18008
+ process.stdout.write((current.length ? current.join(",") : "none") + "\n");
18009
+ return;
18010
+ }
18011
+ const next = moments === "none" ? [] : parseMoments(moments);
18012
+ if (moments !== "none" && next.length === 0) {
18013
+ printError(`Unrecognised moments: ${moments}. Use "commit", "push", "commit,push", or "none".`);
18014
+ process.exit(1);
18015
+ }
18016
+ writeProjectConfig({ git_moments: next });
18017
+ printInfo(
18018
+ next.length ? `Git-moment review enabled for: ${next.join(", ")}` : "Git-moment review disabled \u2014 commits and pushes are no longer gated."
18019
+ );
18020
+ });
17766
18021
  config.command("push").description("Upload the analysis config to the service").option("--file <path>", "Path to config file", CODACY_CONFIG_FILE).action(async (opts) => {
17767
18022
  const globals = program2.opts();
17768
18023
  const tokenResult = await resolveToken(globals.token);
@@ -17905,10 +18160,10 @@ function formatRunDetail(run2) {
17905
18160
  }
17906
18161
 
17907
18162
  // src/lib/ignore-declaration.ts
17908
- var import_node_fs27 = require("node:fs");
18163
+ var import_node_fs30 = require("node:fs");
17909
18164
 
17910
18165
  // src/lib/debounce.ts
17911
- var import_node_fs26 = require("node:fs");
18166
+ var import_node_fs29 = require("node:fs");
17912
18167
  var import_node_crypto10 = require("node:crypto");
17913
18168
  function scopedFile(base, sessionId) {
17914
18169
  if (!sessionId) return base;
@@ -17916,9 +18171,9 @@ function scopedFile(base, sessionId) {
17916
18171
  }
17917
18172
  function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
17918
18173
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
17919
- if (!(0, import_node_fs26.existsSync)(file)) return null;
18174
+ if (!(0, import_node_fs29.existsSync)(file)) return null;
17920
18175
  try {
17921
- const lastTs = parseInt((0, import_node_fs26.readFileSync)(file, "utf-8").trim(), 10);
18176
+ const lastTs = parseInt((0, import_node_fs29.readFileSync)(file, "utf-8").trim(), 10);
17922
18177
  const nowTs = Math.floor(Date.now() / 1e3);
17923
18178
  const elapsed = nowTs - lastTs;
17924
18179
  if (elapsed < debounceSeconds) {
@@ -17931,10 +18186,10 @@ function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
17931
18186
  function checkMtime(files, bypassForRecentCommits, sessionId) {
17932
18187
  if (bypassForRecentCommits) return null;
17933
18188
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
17934
- if (!(0, import_node_fs26.existsSync)(file)) return null;
18189
+ if (!(0, import_node_fs29.existsSync)(file)) return null;
17935
18190
  let debounceTime;
17936
18191
  try {
17937
- debounceTime = (0, import_node_fs26.statSync)(file).mtimeMs;
18192
+ debounceTime = (0, import_node_fs29.statSync)(file).mtimeMs;
17938
18193
  } catch {
17939
18194
  return null;
17940
18195
  }
@@ -17942,7 +18197,7 @@ function checkMtime(files, bypassForRecentCommits, sessionId) {
17942
18197
  const resolved = resolveFile(f);
17943
18198
  if (!resolved) continue;
17944
18199
  try {
17945
- const stat3 = (0, import_node_fs26.statSync)(resolved);
18200
+ const stat3 = (0, import_node_fs29.statSync)(resolved);
17946
18201
  if (stat3.mtimeMs > debounceTime) {
17947
18202
  return null;
17948
18203
  }
@@ -17958,8 +18213,8 @@ function computeContentHash(files) {
17958
18213
  for (const f of sorted) {
17959
18214
  const resolved = resolveFile(f) ?? f;
17960
18215
  try {
17961
- if ((0, import_node_fs26.existsSync)(resolved)) {
17962
- hash.update((0, import_node_fs26.readFileSync)(resolved));
18216
+ if ((0, import_node_fs29.existsSync)(resolved)) {
18217
+ hash.update((0, import_node_fs29.readFileSync)(resolved));
17963
18218
  }
17964
18219
  } catch {
17965
18220
  }
@@ -17969,9 +18224,9 @@ function computeContentHash(files) {
17969
18224
  function checkContentHash(files, sessionId) {
17970
18225
  const hash = computeContentHash(files);
17971
18226
  const file = scopedFile(HASH_FILE, sessionId);
17972
- if ((0, import_node_fs26.existsSync)(file)) {
18227
+ if ((0, import_node_fs29.existsSync)(file)) {
17973
18228
  try {
17974
- const storedHash = (0, import_node_fs26.readFileSync)(file, "utf-8").trim();
18229
+ const storedHash = (0, import_node_fs29.readFileSync)(file, "utf-8").trim();
17975
18230
  if (hash === storedHash) {
17976
18231
  return { skip: "No source changes since last analysis", hash };
17977
18232
  }
@@ -17981,24 +18236,24 @@ function checkContentHash(files, sessionId) {
17981
18236
  return { skip: null, hash };
17982
18237
  }
17983
18238
  function recordAnalysisStart(sessionId) {
17984
- (0, import_node_fs26.mkdirSync)(VERITY_DIR, { recursive: true });
17985
- (0, import_node_fs26.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
18239
+ (0, import_node_fs29.mkdirSync)(VERITY_DIR, { recursive: true });
18240
+ (0, import_node_fs29.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
17986
18241
  }
17987
18242
  function recordPassHash(hash, sessionId) {
17988
- (0, import_node_fs26.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
18243
+ (0, import_node_fs29.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
17989
18244
  }
17990
18245
  function narrowToRecent(files, sessionId) {
17991
18246
  const file = scopedFile(DEBOUNCE_FILE, sessionId);
17992
- if (!(0, import_node_fs26.existsSync)(file)) return files;
18247
+ if (!(0, import_node_fs29.existsSync)(file)) return files;
17993
18248
  let debounceTime;
17994
18249
  try {
17995
- debounceTime = (0, import_node_fs26.statSync)(file).mtimeMs;
18250
+ debounceTime = (0, import_node_fs29.statSync)(file).mtimeMs;
17996
18251
  } catch {
17997
18252
  return files;
17998
18253
  }
17999
18254
  const recent = files.filter((f) => {
18000
18255
  try {
18001
- return (0, import_node_fs26.existsSync)(f) && (0, import_node_fs26.statSync)(f).mtimeMs > debounceTime;
18256
+ return (0, import_node_fs29.existsSync)(f) && (0, import_node_fs29.statSync)(f).mtimeMs > debounceTime;
18002
18257
  } catch {
18003
18258
  return false;
18004
18259
  }
@@ -18011,9 +18266,9 @@ function readIteration(currentCommit, _contentHash) {
18011
18266
  var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
18012
18267
  function readBlockState(currentCommit, opts) {
18013
18268
  if (opts?.newUserPrompt) return NO_BLOCKS;
18014
- if (!(0, import_node_fs26.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
18269
+ if (!(0, import_node_fs29.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
18015
18270
  try {
18016
- const stored = (0, import_node_fs26.readFileSync)(ITERATION_FILE, "utf-8").trim();
18271
+ const stored = (0, import_node_fs29.readFileSync)(ITERATION_FILE, "utf-8").trim();
18017
18272
  const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
18018
18273
  if (!parsed) return NO_BLOCKS;
18019
18274
  if (parsed.commit !== currentCommit) return NO_BLOCKS;
@@ -18059,8 +18314,8 @@ function isSameProblem(previous, current) {
18059
18314
  return current.split(",").some((k) => prev.has(k));
18060
18315
  }
18061
18316
  function writeBlockState(commit, state) {
18062
- (0, import_node_fs26.mkdirSync)(VERITY_DIR, { recursive: true });
18063
- (0, import_node_fs26.writeFileSync)(
18317
+ (0, import_node_fs29.mkdirSync)(VERITY_DIR, { recursive: true });
18318
+ (0, import_node_fs29.writeFileSync)(
18064
18319
  ITERATION_FILE,
18065
18320
  JSON.stringify({
18066
18321
  v: 2,
@@ -18165,9 +18420,9 @@ function resolveIgnoreState(keys) {
18165
18420
  }
18166
18421
  function readIgnoreState(sessionId) {
18167
18422
  const file = stateFile(sessionId);
18168
- if (!(0, import_node_fs27.existsSync)(file)) return null;
18423
+ if (!(0, import_node_fs30.existsSync)(file)) return null;
18169
18424
  try {
18170
- const o = JSON.parse((0, import_node_fs27.readFileSync)(file, "utf-8")) ?? {};
18425
+ const o = JSON.parse((0, import_node_fs30.readFileSync)(file, "utf-8")) ?? {};
18171
18426
  const spent = typeof o.spent === "number" ? o.spent : 0;
18172
18427
  const raw = o.active;
18173
18428
  let active = null;
@@ -18191,8 +18446,8 @@ function readIgnoreState(sessionId) {
18191
18446
  }
18192
18447
  function writeIgnoreState(state, sessionId) {
18193
18448
  try {
18194
- (0, import_node_fs27.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
18195
- (0, import_node_fs27.writeFileSync)(stateFile(sessionId), JSON.stringify({ v: 1, active: state.active, spent: state.spent }));
18449
+ (0, import_node_fs30.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
18450
+ (0, import_node_fs30.writeFileSync)(stateFile(sessionId), JSON.stringify({ v: 1, active: state.active, spent: state.spent }));
18196
18451
  } catch {
18197
18452
  }
18198
18453
  }
@@ -18566,59 +18821,8 @@ function createRun(opts, globals) {
18566
18821
  };
18567
18822
  }
18568
18823
 
18569
- // src/lib/stderr-log.ts
18570
- var import_node_fs28 = require("node:fs");
18571
- var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
18572
- var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
18573
- function scrub(s) {
18574
- return s.replace(TOKEN_RE2, "verity_***REDACTED***").replace(ANSI_RE, "");
18575
- }
18576
- var installed = false;
18577
- var wroteBanner = false;
18578
- var banner = "";
18579
- function append(text) {
18580
- try {
18581
- const dir = projectPath(DEBUG_LOG_DIR);
18582
- const file = projectPath(STDERR_LOG_FILE);
18583
- (0, import_node_fs28.mkdirSync)(dir, { recursive: true });
18584
- rotateIfNeeded(file);
18585
- (0, import_node_fs28.appendFileSync)(file, text);
18586
- } catch {
18587
- }
18588
- }
18589
- function ensureBanner() {
18590
- if (wroteBanner) return;
18591
- wroteBanner = true;
18592
- append(banner);
18593
- }
18594
- function installStderrLog(cmd, argv, version) {
18595
- if (installed || !isDebugEnabled()) return;
18596
- installed = true;
18597
- banner = `
18598
- \u2501\u2501 verity ${cmd} \xB7 ${(/* @__PURE__ */ new Date()).toISOString()} \xB7 pid ${process.pid}
18599
- v${version} \xB7 ${process.cwd()}
18600
- argv: ${scrub(argv.join(" "))}
18601
- `;
18602
- const original = process.stderr.write.bind(process.stderr);
18603
- const tee = (...args) => {
18604
- const result = original(...args);
18605
- try {
18606
- const chunk = args[0];
18607
- const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
18608
- ensureBanner();
18609
- append(scrub(text));
18610
- } catch {
18611
- }
18612
- return result;
18613
- };
18614
- process.stderr.write = tee;
18615
- }
18616
- function logToFileOnly(text) {
18617
- if (!installed) return;
18618
- ensureBanner();
18619
- append(text.endsWith("\n") ? text : `${text}
18620
- `);
18621
- }
18824
+ // src/commands/analyze/index.ts
18825
+ var import_node_fs43 = require("node:fs");
18622
18826
 
18623
18827
  // src/lib/repo-context.ts
18624
18828
  var import_node_child_process9 = require("node:child_process");
@@ -19439,7 +19643,7 @@ function installRunEvidence(run2) {
19439
19643
 
19440
19644
  // src/lib/git-frame.ts
19441
19645
  var import_node_child_process10 = require("node:child_process");
19442
- var import_node_fs29 = require("node:fs");
19646
+ var import_node_fs31 = require("node:fs");
19443
19647
  var import_node_os4 = require("node:os");
19444
19648
  var import_node_path21 = require("node:path");
19445
19649
  var import_node_path22 = require("node:path");
@@ -19578,14 +19782,14 @@ function gitAt(dir, args) {
19578
19782
  }
19579
19783
  function realpathOr(p) {
19580
19784
  try {
19581
- return import_node_fs29.realpathSync.native(p);
19785
+ return import_node_fs31.realpathSync.native(p);
19582
19786
  } catch {
19583
19787
  return (0, import_node_path21.resolve)(p);
19584
19788
  }
19585
19789
  }
19586
19790
  function resolveFrame(input) {
19587
19791
  const found = findMomentSegment(input.command, input.on);
19588
- const hookDirUsable = !!input.hookCwd && (0, import_node_fs29.existsSync)(input.hookCwd);
19792
+ const hookDirUsable = !!input.hookCwd && (0, import_node_fs31.existsSync)(input.hookCwd);
19589
19793
  const baseDir = hookDirUsable ? input.hookCwd : process.cwd();
19590
19794
  let anchor = hookDirUsable ? "hook-cwd" : "process-cwd";
19591
19795
  const refuse = (refusal) => ({
@@ -19608,7 +19812,7 @@ function resolveFrame(input) {
19608
19812
  if (dirs.size > 1) return refuse(`target:multiple ${found.moment} targets in one command`);
19609
19813
  const targetDir = dirs.size === 1 ? [...dirs][0] : baseDir;
19610
19814
  if (targetDir !== baseDir) {
19611
- if (!(0, import_node_fs29.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
19815
+ if (!(0, import_node_fs31.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
19612
19816
  dir = targetDir;
19613
19817
  }
19614
19818
  }
@@ -19644,21 +19848,19 @@ function refResolves(frame, ref) {
19644
19848
  return frameGit(frame, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]) !== "";
19645
19849
  }
19646
19850
  var SHA_RE2 = /^[0-9a-f]{40}$/;
19647
- function baselineShaAt(frame) {
19648
- if (!frame.worktreeRoot) return null;
19649
- try {
19650
- const sha = (0, import_node_fs29.readFileSync)((0, import_node_path22.join)(frame.worktreeRoot, BASELINE_SHA_FILE), "utf-8").trim();
19651
- if (!SHA_RE2.test(sha)) return null;
19652
- return refResolves(frame, sha) ? sha : null;
19653
- } catch {
19654
- return null;
19851
+ function stagedRange(frame) {
19852
+ if (!frame.worktreeRoot) return { kind: "nothing", base: null, head: "INDEX", via: "refused" };
19853
+ const mergeHead = frame.gitDir ? (0, import_node_path22.join)(frame.gitDir, "MERGE_HEAD") : null;
19854
+ if (mergeHead && (0, import_node_fs31.existsSync)(mergeHead)) {
19855
+ const vsHead = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "HEAD"]).split("\n").filter(Boolean));
19856
+ const vsMerge = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "MERGE_HEAD"]).split("\n").filter(Boolean));
19857
+ const resolutions = [...vsHead].filter((f) => vsMerge.has(f) && !isVerityOwnedPath(f));
19858
+ return { kind: "merge", base: "HEAD", head: "INDEX", via: "merge-resolutions", files: resolutions };
19655
19859
  }
19656
- }
19657
- function stagedRange() {
19658
19860
  return { kind: "staged", base: "HEAD", head: "INDEX", via: "index" };
19659
19861
  }
19660
19862
  function resolvePushRange(frame, command, on) {
19661
- const nothing = (via) => ({ kind: "nothing", base: null, head: "HEAD", via });
19863
+ const nothing = (via2) => ({ kind: "nothing", base: null, head: "HEAD", via: via2 });
19662
19864
  if (!frame.worktreeRoot) return nothing("refused");
19663
19865
  const found = findMomentSegment(command, on);
19664
19866
  const segment = found ? splitSegments(command)[found.segmentIndex] : "";
@@ -19666,54 +19868,32 @@ function resolvePushRange(frame, command, on) {
19666
19868
  if (target.isDelete) return nothing("deletion");
19667
19869
  const head = target.srcRef ?? "HEAD";
19668
19870
  if (!refResolves(frame, head)) return nothing(`src-unresolvable:${head}`);
19669
- const srcName = target.srcRef;
19670
- const branchForRemote = srcName ?? frame.branch;
19671
- const candidates = [];
19672
- if (target.remote && (target.dstRef ?? srcName)) {
19673
- const dstName = (target.dstRef ?? srcName).replace(/^refs\/heads\//, "");
19674
- candidates.push({ ref: `refs/remotes/${target.remote}/${dstName}`, via: `refspec:${target.remote}/${dstName}` });
19675
- }
19676
- if (target.remote && !srcName && !target.dstRef && frame.branch) {
19677
- candidates.push({ ref: `refs/remotes/${target.remote}/${frame.branch}`, via: `remote:${target.remote}/${frame.branch}` });
19678
- }
19679
- candidates.push({ ref: srcName ? `${srcName}@{push}` : "@{push}", via: "@{push}" });
19680
- candidates.push({ ref: srcName ? `${srcName}@{upstream}` : "@{upstream}", via: "@{upstream}" });
19681
- if (branchForRemote) {
19682
- candidates.push({
19683
- ref: `refs/remotes/origin/${branchForRemote.replace(/^refs\/heads\//, "")}`,
19684
- via: `origin/${branchForRemote.replace(/^refs\/heads\//, "")}`
19685
- });
19686
- }
19687
- for (const c of candidates) {
19688
- if (!refResolves(frame, c.ref)) continue;
19689
- const mergeBase = frameGit(frame, ["merge-base", c.ref, head]);
19690
- if (SHA_RE2.test(mergeBase)) return { kind: "push", base: mergeBase, head, via: c.via };
19871
+ const remotePattern = target.remote ? `--remotes=${target.remote}` : "--remotes";
19872
+ const via = target.remote ? `publication:${target.remote}` : "publication";
19873
+ const commits = frameGit(frame, ["rev-list", head, "--not", remotePattern]).split("\n").filter(Boolean);
19874
+ if (commits.length === 0) return { kind: "nothing", base: null, head, via: "already-published" };
19875
+ const boundary = frameGit(frame, ["rev-list", head, "--not", remotePattern, "--boundary"]).split("\n").filter((l) => l.startsWith("-")).map((l) => l.slice(1));
19876
+ const base = boundary.find((s) => SHA_RE2.test(s)) ?? null;
19877
+ const files = /* @__PURE__ */ new Set();
19878
+ for (const sha of commits) {
19879
+ for (const f of frameGit(frame, ["diff-tree", "--no-commit-id", "--name-only", "-r", sha]).split("\n")) {
19880
+ if (f && !isVerityOwnedPath(f)) files.add(f);
19881
+ }
19691
19882
  }
19692
- const baseline = baselineShaAt(frame);
19693
- if (baseline) return { kind: "baseline", base: baseline, head, via: "review-baseline" };
19694
- if (refResolves(frame, `${head}~1`)) return { kind: "last-commit", base: `${head}~1`, head, via: `${head}~1` };
19695
- return nothing("no-parent");
19883
+ return { kind: "push", base, head, via, files: [...files], commits };
19696
19884
  }
19697
19885
  function rangeFiles(frame, range) {
19698
- let out;
19699
- switch (range.kind) {
19700
- case "staged":
19701
- out = frameGit(frame, ["diff", "--cached", "--name-only"]);
19702
- break;
19703
- case "push":
19704
- case "baseline":
19705
- case "last-commit":
19706
- out = frameGit(frame, ["diff", "--name-only", range.base, range.head === "INDEX" ? "HEAD" : range.head]);
19707
- break;
19708
- case "nothing":
19709
- return [];
19886
+ if (range.files) return range.files.filter((f) => !isVerityOwnedPath(f));
19887
+ if (range.kind === "staged") {
19888
+ return frameGit(frame, ["diff", "--cached", "--name-only"]).split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
19710
19889
  }
19711
- return out.split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
19890
+ return [];
19712
19891
  }
19713
19892
  function rangeChangeSignals(frame, range, paths) {
19714
19893
  const out = /* @__PURE__ */ new Map();
19715
19894
  if (range.kind === "nothing" || paths.length === 0) return out;
19716
- const args = range.kind === "staged" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
19895
+ if (range.kind === "push" && !range.base) return out;
19896
+ const args = range.kind === "staged" || range.kind === "merge" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
19717
19897
  const diff = frameGit(frame, [...args, "--", ...paths]);
19718
19898
  let current = null;
19719
19899
  let oldSide = null;
@@ -19745,8 +19925,9 @@ function rangeChangeSignals(frame, range, paths) {
19745
19925
  return out;
19746
19926
  }
19747
19927
  function rangeMessages(frame, range) {
19748
- if (range.kind === "staged" || range.kind === "nothing" || !range.base) return "";
19749
- 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");
19928
+ if (range.kind !== "push" || !range.commits || range.commits.length === 0) return "";
19929
+ const commits = range.commits.slice(0, 100);
19930
+ return frameGit(frame, ["show", "-s", "--format=%B%x00", ...commits]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
19750
19931
  }
19751
19932
  function frameTelemetry(frame, range, divergence) {
19752
19933
  const t = {
@@ -19794,7 +19975,7 @@ function truthy(v) {
19794
19975
  }
19795
19976
 
19796
19977
  // src/lib/transcript.ts
19797
- var import_node_fs30 = require("node:fs");
19978
+ var import_node_fs32 = require("node:fs");
19798
19979
  var MAX_READ_BYTES = 256 * 1024;
19799
19980
  var SMALL_FILE_BYTES = 64 * 1024;
19800
19981
  var MAX_FILES_LIST = 20;
@@ -19821,7 +20002,7 @@ async function extractActionSummary(transcriptPath) {
19821
20002
  function readTurnLines(transcriptPath) {
19822
20003
  let size;
19823
20004
  try {
19824
- size = (0, import_node_fs30.statSync)(transcriptPath).size;
20005
+ size = (0, import_node_fs32.statSync)(transcriptPath).size;
19825
20006
  } catch {
19826
20007
  return null;
19827
20008
  }
@@ -19829,7 +20010,7 @@ function readTurnLines(transcriptPath) {
19829
20010
  let raw;
19830
20011
  let windowed = false;
19831
20012
  if (size <= SMALL_FILE_BYTES) {
19832
- raw = (0, import_node_fs30.readFileSync)(transcriptPath, "utf-8");
20013
+ raw = (0, import_node_fs32.readFileSync)(transcriptPath, "utf-8");
19833
20014
  } else {
19834
20015
  windowed = true;
19835
20016
  const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
@@ -20146,6 +20327,9 @@ async function bootstrap(run2) {
20146
20327
  isTTY: process.stdout.isTTY === true
20147
20328
  });
20148
20329
  const { assistantMessage: assistantResponse, stopReason, transcriptPath, sessionId } = await readStopHookStdin();
20330
+ if (deferredToPlugin("analyze", sessionId ?? process.env.CLAUDE_SESSION_ID ?? null)) {
20331
+ process.exit(0);
20332
+ }
20149
20333
  const actionSummary = transcriptPath ? await extractActionSummary(transcriptPath) : null;
20150
20334
  const tokenResult = await resolveToken(globals.token);
20151
20335
  const scopeToken = tokenResult.ok ? tokenResult.data.token : void 0;
@@ -20325,7 +20509,7 @@ function channelSilence(input) {
20325
20509
  // src/lib/cli-version.ts
20326
20510
  function cliVersion() {
20327
20511
  try {
20328
- return true ? "0.32.0-experimental.f0746f7" : "dev";
20512
+ return true ? "0.32.0" : "dev";
20329
20513
  } catch {
20330
20514
  return "dev";
20331
20515
  }
@@ -20366,7 +20550,7 @@ async function sendSkipBeacon(ctx, reason) {
20366
20550
 
20367
20551
  // src/lib/static-analysis.ts
20368
20552
  var import_node_child_process11 = require("node:child_process");
20369
- var import_node_fs31 = require("node:fs");
20553
+ var import_node_fs33 = require("node:fs");
20370
20554
  var SEVERITY_ORDER = {
20371
20555
  Error: 0,
20372
20556
  Critical: 0,
@@ -20414,7 +20598,7 @@ function runCodacyAnalysis(files) {
20414
20598
  if (files.length === 0) return empty;
20415
20599
  const existingFiles = files.filter((f) => {
20416
20600
  try {
20417
- return (0, import_node_fs31.existsSync)(f);
20601
+ return (0, import_node_fs33.existsSync)(f);
20418
20602
  } catch {
20419
20603
  return false;
20420
20604
  }
@@ -20683,7 +20867,7 @@ async function scope(run2) {
20683
20867
  }
20684
20868
 
20685
20869
  // src/lib/specs.ts
20686
- var import_node_fs32 = require("node:fs");
20870
+ var import_node_fs34 = require("node:fs");
20687
20871
  var import_node_path23 = require("node:path");
20688
20872
  var SPEC_CANDIDATES = [
20689
20873
  "CLAUDE.md",
@@ -20715,16 +20899,16 @@ function discoverSpecs(consulted = []) {
20715
20899
  const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
20716
20900
  if (totalBytes >= totalCap) return false;
20717
20901
  if (seen.has(specPath)) return true;
20718
- if (!(0, import_node_fs32.existsSync)(specPath)) return true;
20902
+ if (!(0, import_node_fs34.existsSync)(specPath)) return true;
20719
20903
  seen.add(specPath);
20720
20904
  const remaining = totalCap - totalBytes;
20721
20905
  const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
20722
20906
  const readBytes = Math.min(fileCap, remaining);
20723
20907
  try {
20724
20908
  const buf = Buffer.alloc(readBytes);
20725
- const fd = (0, import_node_fs32.openSync)(specPath, "r");
20726
- const bytesRead = (0, import_node_fs32.readSync)(fd, buf, 0, readBytes, 0);
20727
- (0, import_node_fs32.closeSync)(fd);
20909
+ const fd = (0, import_node_fs34.openSync)(specPath, "r");
20910
+ const bytesRead = (0, import_node_fs34.readSync)(fd, buf, 0, readBytes, 0);
20911
+ (0, import_node_fs34.closeSync)(fd);
20728
20912
  const content = buf.slice(0, bytesRead).toString("utf-8");
20729
20913
  if (!content) return true;
20730
20914
  result.push({ path: specPath, content });
@@ -20740,7 +20924,7 @@ function discoverSpecs(consulted = []) {
20740
20924
  if (!addSpec(candidate)) break;
20741
20925
  }
20742
20926
  for (const dir of ["spec", "docs"]) {
20743
- if (!(0, import_node_fs32.existsSync)(dir)) continue;
20927
+ if (!(0, import_node_fs34.existsSync)(dir)) continue;
20744
20928
  try {
20745
20929
  const mdFiles = findMdFiles(dir, 2).sort();
20746
20930
  for (const mdFile of mdFiles) {
@@ -20755,7 +20939,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
20755
20939
  if (depth >= maxDepth) return [];
20756
20940
  const result = [];
20757
20941
  try {
20758
- const entries = (0, import_node_fs32.readdirSync)(dir, { withFileTypes: true });
20942
+ const entries = (0, import_node_fs34.readdirSync)(dir, { withFileTypes: true });
20759
20943
  for (const entry of entries) {
20760
20944
  const fullPath = (0, import_node_path23.join)(dir, entry.name);
20761
20945
  if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -20774,14 +20958,14 @@ function discoverPlans() {
20774
20958
  const candidates = [];
20775
20959
  const seen = /* @__PURE__ */ new Set();
20776
20960
  for (const plansDir of [localPlansDir, homePlansDir]) {
20777
- if (!(0, import_node_fs32.existsSync)(plansDir)) continue;
20961
+ if (!(0, import_node_fs34.existsSync)(plansDir)) continue;
20778
20962
  try {
20779
- for (const f of (0, import_node_fs32.readdirSync)(plansDir)) {
20963
+ for (const f of (0, import_node_fs34.readdirSync)(plansDir)) {
20780
20964
  if (!f.endsWith(".md") || seen.has(f)) continue;
20781
20965
  seen.add(f);
20782
20966
  const fullPath = (0, import_node_path23.join)(plansDir, f);
20783
20967
  try {
20784
- const stat3 = (0, import_node_fs32.statSync)(fullPath);
20968
+ const stat3 = (0, import_node_fs34.statSync)(fullPath);
20785
20969
  candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
20786
20970
  } catch {
20787
20971
  }
@@ -20794,7 +20978,7 @@ function discoverPlans() {
20794
20978
  for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
20795
20979
  if (entry.size > MAX_PLAN_FILE_BYTES) continue;
20796
20980
  try {
20797
- const content = (0, import_node_fs32.readFileSync)(entry.path, "utf-8");
20981
+ const content = (0, import_node_fs34.readFileSync)(entry.path, "utf-8");
20798
20982
  result.push({ name: entry.name, content });
20799
20983
  } catch {
20800
20984
  }
@@ -20809,12 +20993,12 @@ function discoverGuardDocs(rangeFiles2) {
20809
20993
  if (result.length >= MAX_SPEC_FILES) break;
20810
20994
  if (!GUARD_DOC_EXT.test(path)) continue;
20811
20995
  if (path.startsWith("/") || path.includes("..")) continue;
20812
- if (!(0, import_node_fs32.existsSync)(path)) continue;
20996
+ if (!(0, import_node_fs34.existsSync)(path)) continue;
20813
20997
  try {
20814
- const stat3 = (0, import_node_fs32.statSync)(path);
20998
+ const stat3 = (0, import_node_fs34.statSync)(path);
20815
20999
  if (stat3.size > MAX_PLAN_FILE_BYTES) continue;
20816
21000
  if (totalBytes + stat3.size > MAX_TOTAL_SPEC_BYTES) continue;
20817
- const content = (0, import_node_fs32.readFileSync)(path, "utf-8");
21001
+ const content = (0, import_node_fs34.readFileSync)(path, "utf-8");
20818
21002
  if (!content) continue;
20819
21003
  result.push({ name: path, content });
20820
21004
  totalBytes += content.length;
@@ -20990,7 +21174,7 @@ async function mode(run2) {
20990
21174
  }
20991
21175
 
20992
21176
  // src/lib/fold.ts
20993
- var import_node_fs33 = require("node:fs");
21177
+ var import_node_fs35 = require("node:fs");
20994
21178
  var import_node_path24 = require("node:path");
20995
21179
  var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
20996
21180
  "user",
@@ -21128,7 +21312,7 @@ function candidateRoots(repoRoot2) {
21128
21312
  const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
21129
21313
  const out = [norm];
21130
21314
  try {
21131
- const real = import_node_fs33.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
21315
+ const real = import_node_fs35.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
21132
21316
  if (real !== norm) out.push(real);
21133
21317
  } catch {
21134
21318
  }
@@ -21216,8 +21400,8 @@ function fold(transcriptPath, opts = {}) {
21216
21400
  }
21217
21401
  };
21218
21402
  try {
21219
- if (!(0, import_node_fs33.existsSync)(transcriptPath)) return result;
21220
- ingest((0, import_node_fs33.readFileSync)(transcriptPath, "utf8"), "agent");
21403
+ if (!(0, import_node_fs35.existsSync)(transcriptPath)) return result;
21404
+ ingest((0, import_node_fs35.readFileSync)(transcriptPath, "utf8"), "agent");
21221
21405
  result.coverage.complete = true;
21222
21406
  } catch {
21223
21407
  return result;
@@ -21228,19 +21412,19 @@ function fold(transcriptPath, opts = {}) {
21228
21412
  (0, import_node_path24.basename)(transcriptPath).replace(/\.jsonl$/, ""),
21229
21413
  "subagents"
21230
21414
  );
21231
- if ((0, import_node_fs33.existsSync)(sidecarDir)) {
21415
+ if ((0, import_node_fs35.existsSync)(sidecarDir)) {
21232
21416
  const maxFiles = opts.maxSidecars ?? 200;
21233
21417
  const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
21234
21418
  const found = [];
21235
21419
  const walk2 = (d, depth) => {
21236
21420
  if (depth > 4) return;
21237
- for (const e of (0, import_node_fs33.readdirSync)(d, { withFileTypes: true })) {
21421
+ for (const e of (0, import_node_fs35.readdirSync)(d, { withFileTypes: true })) {
21238
21422
  const p = (0, import_node_path24.join)(d, e.name);
21239
21423
  if (e.isDirectory()) {
21240
21424
  walk2(p, depth + 1);
21241
21425
  } else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
21242
21426
  try {
21243
- const st = (0, import_node_fs33.statSync)(p);
21427
+ const st = (0, import_node_fs35.statSync)(p);
21244
21428
  found.push({ path: p, size: st.size, mtimeMs: st.mtimeMs });
21245
21429
  } catch {
21246
21430
  result.coverage.malformed++;
@@ -21257,7 +21441,7 @@ function fold(transcriptPath, opts = {}) {
21257
21441
  continue;
21258
21442
  }
21259
21443
  try {
21260
- ingest((0, import_node_fs33.readFileSync)(f.path, "utf8"), "subagent");
21444
+ ingest((0, import_node_fs35.readFileSync)(f.path, "utf8"), "subagent");
21261
21445
  bytes += f.size;
21262
21446
  result.coverage.subagentFiles++;
21263
21447
  } catch {
@@ -21292,7 +21476,7 @@ function fold(transcriptPath, opts = {}) {
21292
21476
  }
21293
21477
  function classifyUnobserved(path) {
21294
21478
  try {
21295
- const st = (0, import_node_fs33.statSync)(path);
21479
+ const st = (0, import_node_fs35.statSync)(path);
21296
21480
  if (!st.isFile()) return "unreadable";
21297
21481
  } catch {
21298
21482
  return "unreadable";
@@ -21544,7 +21728,20 @@ async function evidence(run2) {
21544
21728
  widened_to: allForReview.length
21545
21729
  });
21546
21730
  }
21547
- const baseForReview = narrowingIsTrustworthy ? scoped.files : recoveredScope.length > 0 ? recoveredScope : allForReview;
21731
+ const authorshipWasObservable = scoped.signal === "authored" && actionSummary?.transcript_windowed !== "orphaned";
21732
+ if (!narrowingIsTrustworthy && recoveredScope.length === 0 && authorshipWasObservable) {
21733
+ logEvent("authored_nothing_reviewable", {
21734
+ touched: (actionSummary?.files_edited?.length ?? 0) + (actionSummary?.files_created?.length ?? 0),
21735
+ candidates: allForReview.length
21736
+ });
21737
+ }
21738
+ const baseForReview = chooseReviewScope({
21739
+ narrowingIsTrustworthy,
21740
+ scopedFiles: scoped.files,
21741
+ recoveredScope,
21742
+ allForReview,
21743
+ authorshipWasObservable
21744
+ });
21548
21745
  const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
21549
21746
  if (!opts.skipStatic && isCodacyAvailable()) {
21550
21747
  let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
@@ -21596,20 +21793,20 @@ async function evidence(run2) {
21596
21793
  }
21597
21794
 
21598
21795
  // src/lib/cache-cleanup.ts
21599
- var import_node_fs34 = require("node:fs");
21796
+ var import_node_fs36 = require("node:fs");
21600
21797
  var import_node_path25 = require("node:path");
21601
21798
  var CACHE_TTL_DAYS = 7;
21602
21799
  function pruneStaleCache() {
21603
21800
  try {
21604
21801
  const dir = projectPath(CACHE_DIR);
21605
21802
  const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
21606
- for (const entry of (0, import_node_fs34.readdirSync)(dir)) {
21803
+ for (const entry of (0, import_node_fs36.readdirSync)(dir)) {
21607
21804
  if (!entry.startsWith("pending-")) continue;
21608
21805
  const path = (0, import_node_path25.join)(dir, entry);
21609
21806
  try {
21610
- const stat3 = (0, import_node_fs34.statSync)(path);
21807
+ const stat3 = (0, import_node_fs36.statSync)(path);
21611
21808
  if (stat3.mtimeMs < cutoff) {
21612
- (0, import_node_fs34.unlinkSync)(path);
21809
+ (0, import_node_fs36.unlinkSync)(path);
21613
21810
  logEvent("cache_entry_pruned", {
21614
21811
  path: entry,
21615
21812
  age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
@@ -21623,7 +21820,7 @@ function pruneStaleCache() {
21623
21820
  }
21624
21821
 
21625
21822
  // src/lib/context-files.ts
21626
- var import_node_fs35 = require("node:fs");
21823
+ var import_node_fs37 = require("node:fs");
21627
21824
  var import_node_os5 = require("node:os");
21628
21825
  var MAX_CONTEXT_FILES = 10;
21629
21826
  var MAX_CONTEXT_FILE_BYTES = 10240;
@@ -21681,7 +21878,7 @@ function gatherContextFiles(contextPaths, deltaFiles, opts) {
21681
21878
  continue;
21682
21879
  }
21683
21880
  try {
21684
- const content = (0, import_node_fs35.readFileSync)(safePath, "utf8");
21881
+ const content = (0, import_node_fs37.readFileSync)(safePath, "utf8");
21685
21882
  const bytes = Buffer.byteLength(content);
21686
21883
  if (bytes > MAX_CONTEXT_FILE_BYTES) {
21687
21884
  logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
@@ -21772,7 +21969,7 @@ async function repoContext(run2) {
21772
21969
 
21773
21970
  // src/lib/seed-runner.ts
21774
21971
  var import_promises14 = require("node:fs/promises");
21775
- var import_node_fs36 = require("node:fs");
21972
+ var import_node_fs38 = require("node:fs");
21776
21973
  var import_node_path26 = require("node:path");
21777
21974
  var import_yaml4 = __toESM(require_dist());
21778
21975
 
@@ -22012,7 +22209,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
22012
22209
  return fm;
22013
22210
  }
22014
22211
  async function runSeed(opts) {
22015
- if (!(0, import_node_fs36.existsSync)(STANDARD_FILE)) {
22212
+ if (!(0, import_node_fs38.existsSync)(STANDARD_FILE)) {
22016
22213
  return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
22017
22214
  }
22018
22215
  let standardDoc;
@@ -22024,7 +22221,7 @@ async function runSeed(opts) {
22024
22221
  }
22025
22222
  const knowledgeSpec = standardDoc.knowledge_spec ?? {};
22026
22223
  let readmeContent;
22027
- if ((0, import_node_fs36.existsSync)("README.md")) {
22224
+ if ((0, import_node_fs38.existsSync)("README.md")) {
22028
22225
  try {
22029
22226
  readmeContent = await (0, import_promises14.readFile)("README.md", "utf-8");
22030
22227
  } catch {
@@ -22032,7 +22229,7 @@ async function runSeed(opts) {
22032
22229
  }
22033
22230
  let claudeMdContent;
22034
22231
  for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
22035
- if ((0, import_node_fs36.existsSync)(p)) {
22232
+ if ((0, import_node_fs38.existsSync)(p)) {
22036
22233
  try {
22037
22234
  claudeMdContent = await (0, import_promises14.readFile)(p, "utf-8");
22038
22235
  break;
@@ -22056,7 +22253,7 @@ async function runSeed(opts) {
22056
22253
  return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
22057
22254
  }
22058
22255
  const overviewPath = (0, import_node_path26.join)(MEMORY_DIR, "domain", "project-overview.md");
22059
- if ((0, import_node_fs36.existsSync)(overviewPath) && !opts.force) {
22256
+ if ((0, import_node_fs38.existsSync)(overviewPath) && !opts.force) {
22060
22257
  return { created: 0, failed: 0, skipped: "already_seeded", candidates };
22061
22258
  }
22062
22259
  if (opts.dryRun) {
@@ -22111,7 +22308,7 @@ async function runSeed(opts) {
22111
22308
  }
22112
22309
 
22113
22310
  // src/commands/analyze/phases/08-memory-manifest.ts
22114
- var import_node_fs37 = require("node:fs");
22311
+ var import_node_fs39 = require("node:fs");
22115
22312
  var import_node_path27 = require("node:path");
22116
22313
  async function memoryManifest(run2) {
22117
22314
  const { globals } = run2;
@@ -22123,8 +22320,8 @@ async function memoryManifest(run2) {
22123
22320
  try {
22124
22321
  await ensureMemoryDir();
22125
22322
  const seedMarker = (0, import_node_path27.join)(VERITY_DIR, ".seeded");
22126
- const hasStandard = (0, import_node_fs37.existsSync)(STANDARD_FILE);
22127
- const alreadyTried = (0, import_node_fs37.existsSync)(seedMarker);
22323
+ const hasStandard = (0, import_node_fs39.existsSync)(STANDARD_FILE);
22324
+ const alreadyTried = (0, import_node_fs39.existsSync)(seedMarker);
22128
22325
  if (hasStandard && !alreadyTried) {
22129
22326
  const preManifest = await buildManifest();
22130
22327
  if (preManifest.nodes.length === 0) {
@@ -22137,7 +22334,7 @@ async function memoryManifest(run2) {
22137
22334
  dryRun: false
22138
22335
  });
22139
22336
  if (seedResult.created > 0) {
22140
- (0, import_node_fs37.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
22337
+ (0, import_node_fs39.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
22141
22338
  `);
22142
22339
  autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
22143
22340
  logEvent("auto_seed_ran", {
@@ -22145,7 +22342,7 @@ async function memoryManifest(run2) {
22145
22342
  failed: seedResult.failed
22146
22343
  });
22147
22344
  } else if (seedResult.skipped === "already_seeded") {
22148
- (0, import_node_fs37.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
22345
+ (0, import_node_fs39.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
22149
22346
  `);
22150
22347
  } else {
22151
22348
  logEvent("auto_seed_noop", {
@@ -22334,7 +22531,7 @@ async function workingMemory(run2) {
22334
22531
  }
22335
22532
 
22336
22533
  // src/lib/note-budget.ts
22337
- var import_node_fs38 = require("node:fs");
22534
+ var import_node_fs40 = require("node:fs");
22338
22535
  var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
22339
22536
  var EPISODE_STALE_SECONDS = 30 * 60;
22340
22537
  var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
@@ -22356,9 +22553,9 @@ function advisoryBudgetSpent(episode, rawDecision) {
22356
22553
  }
22357
22554
  function readAdvisoryEpisode(sessionId) {
22358
22555
  const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
22359
- if (!(0, import_node_fs38.existsSync)(file)) return null;
22556
+ if (!(0, import_node_fs40.existsSync)(file)) return null;
22360
22557
  try {
22361
- const o = JSON.parse((0, import_node_fs38.readFileSync)(file, "utf-8")) ?? {};
22558
+ const o = JSON.parse((0, import_node_fs40.readFileSync)(file, "utf-8")) ?? {};
22362
22559
  const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
22363
22560
  if (isNaN(delivered)) return null;
22364
22561
  return {
@@ -22372,8 +22569,8 @@ function readAdvisoryEpisode(sessionId) {
22372
22569
  }
22373
22570
  function writeAdvisoryEpisode(episode, sessionId) {
22374
22571
  try {
22375
- (0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
22376
- (0, import_node_fs38.writeFileSync)(
22572
+ (0, import_node_fs40.mkdirSync)(VERITY_DIR, { recursive: true });
22573
+ (0, import_node_fs40.writeFileSync)(
22377
22574
  scopedFile(ADVISORY_EPISODE_FILE, sessionId),
22378
22575
  JSON.stringify({ v: 1, ...episode })
22379
22576
  );
@@ -22690,14 +22887,14 @@ async function buildRequest(run2) {
22690
22887
  }
22691
22888
 
22692
22889
  // src/lib/offline.ts
22693
- var import_node_fs39 = require("node:fs");
22890
+ var import_node_fs41 = require("node:fs");
22694
22891
  var import_node_crypto11 = require("node:crypto");
22695
22892
  function cacheRequest(body) {
22696
22893
  try {
22697
- (0, import_node_fs39.mkdirSync)(CACHE_DIR, { recursive: true });
22894
+ (0, import_node_fs41.mkdirSync)(CACHE_DIR, { recursive: true });
22698
22895
  const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
22699
22896
  const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
22700
- (0, import_node_fs39.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
22897
+ (0, import_node_fs41.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
22701
22898
  } catch {
22702
22899
  }
22703
22900
  }
@@ -22816,7 +23013,7 @@ async function transmit(run2) {
22816
23013
  }
22817
23014
 
22818
23015
  // src/commands/analyze/phases/13-reconcile.ts
22819
- var import_node_fs40 = require("node:fs");
23016
+ var import_node_fs42 = require("node:fs");
22820
23017
  var import_node_path29 = require("node:path");
22821
23018
  async function reconcile(run2) {
22822
23019
  const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run2;
@@ -22846,7 +23043,7 @@ async function reconcile(run2) {
22846
23043
  const st = foldDossier(memorySession.d);
22847
23044
  openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
22848
23045
  try {
22849
- const src = (0, import_node_fs40.readFileSync)((0, import_node_path29.join)(repoRoot(), file), "utf8").split("\n");
23046
+ const src = (0, import_node_fs42.readFileSync)((0, import_node_path29.join)(repoRoot(), file), "utf8").split("\n");
22850
23047
  const at = src[line - 1];
22851
23048
  return at === void 0 ? null : lineSha(at);
22852
23049
  } catch {
@@ -23510,6 +23707,10 @@ function registerAnalyzeCommand(program2) {
23510
23707
  }
23511
23708
  var tracing = () => process.env.VERITY_TRACE_PHASES === "1";
23512
23709
  async function runAnalyze(opts, globals) {
23710
+ if (!verityConfigured()) {
23711
+ (0, import_node_fs43.writeSync)(2, '[verity] not set up in this project \u2014 run "verity init" first.\n');
23712
+ process.exit(0);
23713
+ }
23513
23714
  const run2 = createRun(opts, globals);
23514
23715
  installRunEvidence(run2);
23515
23716
  for (const [name, phase] of PIPELINE) {
@@ -23528,7 +23729,6 @@ async function runAnalyze(opts, globals) {
23528
23729
  }
23529
23730
 
23530
23731
  // src/commands/baseline.ts
23531
- var import_node_fs41 = require("node:fs");
23532
23732
  function registerBaselineCommands(program2) {
23533
23733
  const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
23534
23734
  baseline.command("capture").description("Snapshot the working tree at task start (used by SessionStart hook)").option("--session-id <id>", "Session id (overrides any value from stdin)").option("--source <source>", "Lifecycle hint: startup|resume|clear|compact").action(async (opts) => {
@@ -23537,7 +23737,16 @@ function registerBaselineCommands(program2) {
23537
23737
  process.chdir(repoRoot());
23538
23738
  } catch {
23539
23739
  }
23540
- if (!(0, import_node_fs41.existsSync)(VERITY_DIR)) {
23740
+ if (!verityConfigured()) {
23741
+ const how = isPluginInvocation() ? "Offer to run /verity:setup for the user." : "Offer to run `verity init` (or /verity-setup) for the user.";
23742
+ process.stdout.write(
23743
+ JSON.stringify({
23744
+ hookSpecificOutput: {
23745
+ hookEventName: "SessionStart",
23746
+ additionalContext: `Verity is installed but this project is not set up yet, so the quality gate will not review anything here. ${how}`
23747
+ }
23748
+ }) + "\n"
23749
+ );
23541
23750
  process.exit(0);
23542
23751
  }
23543
23752
  let sessionId = opts.sessionId;
@@ -23553,6 +23762,9 @@ function registerBaselineCommands(program2) {
23553
23762
  }
23554
23763
  }
23555
23764
  }
23765
+ if (deferredToPlugin("baseline capture", sessionId ?? process.env.CLAUDE_SESSION_ID ?? null)) {
23766
+ process.exit(0);
23767
+ }
23556
23768
  const authForScope = await resolveToken(program2.opts().token);
23557
23769
  const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
23558
23770
  const scopeSession = sessionId || process.env.CLAUDE_SESSION_ID || void 0;
@@ -23577,7 +23789,7 @@ async function readStdin() {
23577
23789
  }
23578
23790
 
23579
23791
  // src/commands/review.ts
23580
- var import_node_fs42 = require("node:fs");
23792
+ var import_node_fs44 = require("node:fs");
23581
23793
  function registerReviewCommand(program2) {
23582
23794
  program2.command("review").description("Run on-demand Verity analysis (advisory, never blocks)").requiredOption("--files <paths>", "Comma-separated file list").option("--changed <paths>", "Subset of --files that were modified").option("--intent <text>", "User intent description (max 2000 chars)").option("--specs <paths>", "Comma-separated spec file paths").option("--json", "Output raw JSON response").action(async (opts) => {
23583
23795
  const globals = program2.opts();
@@ -23596,7 +23808,7 @@ async function runReview(opts, globals) {
23596
23808
  const securityFiles = filterSecurity(allFiles);
23597
23809
  let staticResults;
23598
23810
  if (isCodacyAvailable()) {
23599
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs42.existsSync)(f) || resolveFile(f) !== null);
23811
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs44.existsSync)(f) || resolveFile(f) !== null);
23600
23812
  staticResults = runCodacyAnalysis(scannable);
23601
23813
  } else {
23602
23814
  staticResults = {
@@ -23622,10 +23834,10 @@ async function runReview(opts, globals) {
23622
23834
  const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
23623
23835
  specs = [];
23624
23836
  for (const p of specPaths) {
23625
- if (!(0, import_node_fs42.existsSync)(p)) continue;
23837
+ if (!(0, import_node_fs44.existsSync)(p)) continue;
23626
23838
  try {
23627
- const { readFileSync: readFileSync26 } = await import("node:fs");
23628
- const content = readFileSync26(p, "utf-8");
23839
+ const { readFileSync: readFileSync27 } = await import("node:fs");
23840
+ const content = readFileSync27(p, "utf-8");
23629
23841
  specs.push({ path: p, content: content.slice(0, 10240) });
23630
23842
  } catch {
23631
23843
  }
@@ -23682,7 +23894,7 @@ async function runReview(opts, globals) {
23682
23894
  }
23683
23895
 
23684
23896
  // src/commands/guard.ts
23685
- var import_node_fs43 = require("node:fs");
23897
+ var import_node_fs45 = require("node:fs");
23686
23898
  var import_node_path30 = require("node:path");
23687
23899
  var GUARD_BLOCK_CAP = 2;
23688
23900
  var GUARD_ITER_FILE = (0, import_node_path30.join)(VERITY_DIR, ".guard-iteration");
@@ -23730,7 +23942,7 @@ function readPreToolUseStdin() {
23730
23942
  }
23731
23943
  function readIterMap() {
23732
23944
  try {
23733
- const raw = JSON.parse((0, import_node_fs43.readFileSync)(GUARD_ITER_FILE, "utf-8"));
23945
+ const raw = JSON.parse((0, import_node_fs45.readFileSync)(GUARD_ITER_FILE, "utf-8"));
23734
23946
  if (raw && typeof raw === "object") {
23735
23947
  if (typeof raw.moment === "string" && typeof raw.count === "number") {
23736
23948
  return { [raw.moment]: raw.count };
@@ -23750,10 +23962,10 @@ function readIter(moment) {
23750
23962
  }
23751
23963
  function writeIter(moment, count) {
23752
23964
  try {
23753
- (0, import_node_fs43.mkdirSync)(VERITY_DIR, { recursive: true });
23965
+ (0, import_node_fs45.mkdirSync)(VERITY_DIR, { recursive: true });
23754
23966
  const map = readIterMap();
23755
23967
  map[moment] = count;
23756
- (0, import_node_fs43.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
23968
+ (0, import_node_fs45.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
23757
23969
  } catch {
23758
23970
  }
23759
23971
  }
@@ -23763,16 +23975,16 @@ function resetIter(moment) {
23763
23975
  if (!(moment in map)) return;
23764
23976
  delete map[moment];
23765
23977
  if (Object.keys(map).length === 0) {
23766
- if ((0, import_node_fs43.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs43.unlinkSync)(GUARD_ITER_FILE);
23978
+ if ((0, import_node_fs45.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs45.unlinkSync)(GUARD_ITER_FILE);
23767
23979
  } else {
23768
- (0, import_node_fs43.mkdirSync)(VERITY_DIR, { recursive: true });
23769
- (0, import_node_fs43.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
23980
+ (0, import_node_fs45.mkdirSync)(VERITY_DIR, { recursive: true });
23981
+ (0, import_node_fs45.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
23770
23982
  }
23771
23983
  } catch {
23772
23984
  }
23773
23985
  }
23774
23986
  function registerGuardCommand(program2) {
23775
- program2.command("guard").description("Git-moment gate: review staged/to-push changes before a commit/push (PreToolUse hook)").option("--on <moments>", "Which git moments to gate: commit,push", "commit,push").option("--json", "Output raw JSON response (debug)").action(async (opts) => {
23987
+ program2.command("guard").description("Git-moment gate: review staged/to-push changes before a commit/push (PreToolUse hook)").option("--on <moments>", "Which git moments to gate: commit,push (default: the project config)").option("--json", "Output raw JSON response (debug)").action(async (opts) => {
23776
23988
  const globals = program2.opts();
23777
23989
  try {
23778
23990
  await runGuard(opts, globals);
@@ -23782,13 +23994,14 @@ function registerGuardCommand(program2) {
23782
23994
  });
23783
23995
  }
23784
23996
  function resolveMomentRange(moment, frame, command, on) {
23785
- return moment === "pre-commit" ? stagedRange() : resolvePushRange(frame, command, on);
23997
+ return moment === "pre-commit" ? stagedRange(frame) : resolvePushRange(frame, command, on);
23786
23998
  }
23787
23999
  function describeRange(range) {
23788
24000
  if (range.kind === "staged") return "staged";
23789
- if (range.kind === "nothing" || !range.base) return null;
23790
- const base = /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
23791
- return `${base}..${range.head} via ${range.via}`;
24001
+ if (range.kind === "merge") return `merge (${range.via})`;
24002
+ if (range.kind === "nothing") return range.via === "already-published" ? "already published" : null;
24003
+ const base = range.base && /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
24004
+ return base ? `${base}..${range.head} via ${range.via}` : `via ${range.via}`;
23792
24005
  }
23793
24006
  function matchFlagValue(command, flags) {
23794
24007
  const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
@@ -23837,7 +24050,7 @@ function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedInte
23837
24050
  const securityFiles = filterSecurity(files);
23838
24051
  let staticResults;
23839
24052
  if (isCodacyAvailable()) {
23840
- const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs43.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
24053
+ const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs45.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
23841
24054
  staticResults = runCodacyAnalysis(scannable);
23842
24055
  } else {
23843
24056
  staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
@@ -23910,8 +24123,10 @@ function emitAllowNotice(userMsg, agentMsg) {
23910
24123
  process.exit(0);
23911
24124
  }
23912
24125
  async function runGuard(opts, globals) {
23913
- const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
24126
+ const on = resolveGuardMoments(opts.on);
24127
+ if (on.length === 0) process.exit(0);
23914
24128
  const { command, cwd, sessionId } = await readPreToolUseStdin();
24129
+ if (deferredToPlugin("guard", sessionId)) process.exit(0);
23915
24130
  const moment = classifyCommand(command, on);
23916
24131
  if (!moment) process.exit(0);
23917
24132
  const verb = moment === "pre-commit" ? "commit" : "push";
@@ -23974,7 +24189,7 @@ async function runGuard(opts, globals) {
23974
24189
  upgradeToExcerpts(repoContext2, {
23975
24190
  readFile: (rel) => {
23976
24191
  try {
23977
- return (0, import_node_fs43.readFileSync)((0, import_node_path30.join)(frame.worktreeRoot ?? process.cwd(), rel), "utf8");
24192
+ return (0, import_node_fs45.readFileSync)((0, import_node_path30.join)(frame.worktreeRoot ?? process.cwd(), rel), "utf8");
23978
24193
  } catch {
23979
24194
  return null;
23980
24195
  }
@@ -24195,7 +24410,7 @@ function registerIgnoreCommand(program2) {
24195
24410
 
24196
24411
  // src/commands/waive.ts
24197
24412
  var import_node_crypto12 = require("node:crypto");
24198
- var import_node_fs44 = require("node:fs");
24413
+ var import_node_fs46 = require("node:fs");
24199
24414
  function registerWaiveCommand(program2) {
24200
24415
  program2.command("waive <pattern-id>").description("Record an accepted-risk disposition for an open finding (voids when the file changes)").option("--file <path>", "File the finding is anchored to, REPO-RELATIVE (recommended \u2014 narrows the waive)").requiredOption("--reason <text>", "The human disposition this records (reviewer finding, ADR, \u2026)").action(async (patternId, opts) => {
24201
24416
  const globals = program2.opts();
@@ -24224,7 +24439,7 @@ function registerWaiveCommand(program2) {
24224
24439
  if (opts.file) {
24225
24440
  body.file = opts.file;
24226
24441
  try {
24227
- body.file_sha256 = (0, import_node_crypto12.createHash)("sha256").update((0, import_node_fs44.readFileSync)(opts.file)).digest("hex");
24442
+ body.file_sha256 = (0, import_node_crypto12.createHash)("sha256").update((0, import_node_fs46.readFileSync)(opts.file)).digest("hex");
24228
24443
  } catch {
24229
24444
  printError(`Cannot read ${opts.file} \u2014 run from the repo root, or omit --file to waive by pattern.`);
24230
24445
  process.exit(1);
@@ -24249,7 +24464,7 @@ function registerWaiveCommand(program2) {
24249
24464
  }
24250
24465
 
24251
24466
  // src/commands/init.ts
24252
- var import_node_fs49 = require("node:fs");
24467
+ var import_node_fs51 = require("node:fs");
24253
24468
  var import_promises17 = require("node:fs/promises");
24254
24469
  var import_yaml6 = __toESM(require_dist());
24255
24470
  var import_node_path33 = require("node:path");
@@ -24331,7 +24546,7 @@ function printPhase(n, of, title, subtitle) {
24331
24546
  }
24332
24547
 
24333
24548
  // src/commands/doctor.ts
24334
- var import_node_fs46 = require("node:fs");
24549
+ var import_node_fs48 = require("node:fs");
24335
24550
 
24336
24551
  // src/lib/prereqs.ts
24337
24552
  var import_node_child_process13 = require("node:child_process");
@@ -24451,7 +24666,7 @@ var import_promises15 = require("node:fs/promises");
24451
24666
 
24452
24667
  // src/lib/gitignore.ts
24453
24668
  var import_node_child_process14 = require("node:child_process");
24454
- var import_node_fs45 = require("node:fs");
24669
+ var import_node_fs47 = require("node:fs");
24455
24670
  var VERITY_GITIGNORE_MARKER = "# Verity \u2014 machine-local state.";
24456
24671
  var SETTINGS_LOCAL_IGNORE_ENTRY = ".claude/settings.local.json";
24457
24672
  var VERITY_GITIGNORE_BLOCK = [
@@ -24484,7 +24699,7 @@ function semanticsHold() {
24484
24699
  function ensureVerityGitignore() {
24485
24700
  let content = "";
24486
24701
  try {
24487
- content = (0, import_node_fs45.readFileSync)(".gitignore", "utf-8");
24702
+ content = (0, import_node_fs47.readFileSync)(".gitignore", "utf-8");
24488
24703
  } catch {
24489
24704
  }
24490
24705
  const hasMarker = content.includes(VERITY_GITIGNORE_MARKER);
@@ -24505,7 +24720,7 @@ function ensureVerityGitignore() {
24505
24720
  const sep2 = next === "" ? "" : next.endsWith("\n") ? "\n" : "\n\n";
24506
24721
  next = next + sep2 + VERITY_GITIGNORE_BLOCK;
24507
24722
  }
24508
- (0, import_node_fs45.writeFileSync)(".gitignore", next);
24723
+ (0, import_node_fs47.writeFileSync)(".gitignore", next);
24509
24724
  return verified(needsRepair ? "repaired" : "added");
24510
24725
  } catch {
24511
24726
  return "failed";
@@ -24632,11 +24847,11 @@ async function buildReport() {
24632
24847
  const state = await readSetupState();
24633
24848
  const hooks = await checkAllVerityHooks();
24634
24849
  const telemetry = await checkTelemetry();
24635
- const hasConfig = (0, import_node_fs46.existsSync)(projectPath(CODACY_CONFIG_FILE));
24850
+ const hasConfig = (0, import_node_fs48.existsSync)(projectPath(CODACY_CONFIG_FILE));
24636
24851
  const artifacts = {
24637
- standard: (0, import_node_fs46.existsSync)(projectPath(STANDARD_FILE)),
24852
+ standard: (0, import_node_fs48.existsSync)(projectPath(STANDARD_FILE)),
24638
24853
  analysisConfig: hasConfig,
24639
- verityMd: (0, import_node_fs46.existsSync)(projectPath(VERITY_MD_FILE)),
24854
+ verityMd: (0, import_node_fs48.existsSync)(projectPath(VERITY_MD_FILE)),
24640
24855
  analysisConfigIds: hasConfig ? validatePatternIds().status : "absent"
24641
24856
  };
24642
24857
  const next = [];
@@ -24730,7 +24945,7 @@ function registerDoctorCommand(program2) {
24730
24945
  }
24731
24946
 
24732
24947
  // src/commands/migrate.ts
24733
- var import_node_fs47 = require("node:fs");
24948
+ var import_node_fs49 = require("node:fs");
24734
24949
  var import_node_path31 = require("node:path");
24735
24950
  var import_node_child_process15 = require("node:child_process");
24736
24951
  var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
@@ -24770,10 +24985,10 @@ async function runMigration(opts = {}) {
24770
24985
  function migrateProjectDir(root, actions) {
24771
24986
  const gateDir = (0, import_node_path31.join)(root, ".gate");
24772
24987
  const verityDir = (0, import_node_path31.join)(root, ".verity");
24773
- if ((0, import_node_fs47.existsSync)(gateDir) && !(0, import_node_fs47.existsSync)(verityDir)) {
24988
+ if ((0, import_node_fs49.existsSync)(gateDir) && !(0, import_node_fs49.existsSync)(verityDir)) {
24774
24989
  return migrateProjectDirRename(root, gateDir, verityDir, actions);
24775
24990
  }
24776
- if ((0, import_node_fs47.existsSync)(gateDir) && (0, import_node_fs47.existsSync)(verityDir)) {
24991
+ if ((0, import_node_fs49.existsSync)(gateDir) && (0, import_node_fs49.existsSync)(verityDir)) {
24777
24992
  return migrateProjectDirCarry(gateDir, verityDir, actions);
24778
24993
  }
24779
24994
  return false;
@@ -24794,13 +25009,13 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
24794
25009
  }
24795
25010
  }
24796
25011
  if (moved) {
24797
- if ((0, import_node_fs47.existsSync)(gateDir)) {
25012
+ if ((0, import_node_fs49.existsSync)(gateDir)) {
24798
25013
  const carried = carryLegacyContents(gateDir, verityDir);
24799
25014
  if (carried > 0) {
24800
25015
  actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
24801
25016
  }
24802
25017
  try {
24803
- (0, import_node_fs47.rmSync)(gateDir, { recursive: true, force: true });
25018
+ (0, import_node_fs49.rmSync)(gateDir, { recursive: true, force: true });
24804
25019
  } catch {
24805
25020
  }
24806
25021
  }
@@ -24816,7 +25031,7 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
24816
25031
  actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
24817
25032
  }
24818
25033
  try {
24819
- (0, import_node_fs47.rmSync)(gateDir, { recursive: true, force: true });
25034
+ (0, import_node_fs49.rmSync)(gateDir, { recursive: true, force: true });
24820
25035
  } catch {
24821
25036
  }
24822
25037
  return carried > 0;
@@ -24825,9 +25040,9 @@ function migrateGlobalCredentials(home, actions) {
24825
25040
  if (!home) return;
24826
25041
  const gateCreds = (0, import_node_path31.join)(home, ".gate", "credentials");
24827
25042
  const verityCreds = (0, import_node_path31.join)(home, ".verity", "credentials");
24828
- if (!(0, import_node_fs47.existsSync)(gateCreds)) return;
24829
- if (!(0, import_node_fs47.existsSync)(verityCreds)) {
24830
- (0, import_node_fs47.mkdirSync)((0, import_node_path31.join)(home, ".verity"), { recursive: true });
25043
+ if (!(0, import_node_fs49.existsSync)(gateCreds)) return;
25044
+ if (!(0, import_node_fs49.existsSync)(verityCreds)) {
25045
+ (0, import_node_fs49.mkdirSync)((0, import_node_path31.join)(home, ".verity"), { recursive: true });
24831
25046
  moveFile(gateCreds, verityCreds);
24832
25047
  actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
24833
25048
  return;
@@ -24850,7 +25065,7 @@ async function migrateLegacyHooks(root, actions) {
24850
25065
  }
24851
25066
  async function migrateClaudeMd(root, actions) {
24852
25067
  const claudeMd = (0, import_node_path31.join)(root, "CLAUDE.md");
24853
- const hadLegacyBlock = (0, import_node_fs47.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
25068
+ const hadLegacyBlock = (0, import_node_fs49.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
24854
25069
  if (!hadLegacyBlock) return;
24855
25070
  try {
24856
25071
  await ensureClaudeMdPointer(root);
@@ -24862,7 +25077,7 @@ async function migrateClaudeMd(root, actions) {
24862
25077
  function migrateStandardFile(root, actions) {
24863
25078
  const gateMd = (0, import_node_path31.join)(root, "GATE.md");
24864
25079
  const verityMd = (0, import_node_path31.join)(root, "VERITY.md");
24865
- if (!(0, import_node_fs47.existsSync)(gateMd) || (0, import_node_fs47.existsSync)(verityMd)) return;
25080
+ if (!(0, import_node_fs49.existsSync)(gateMd) || (0, import_node_fs49.existsSync)(verityMd)) return;
24866
25081
  let moved = false;
24867
25082
  if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
24868
25083
  try {
@@ -24874,12 +25089,12 @@ function migrateStandardFile(root, actions) {
24874
25089
  if (!moved) moveFile(gateMd, verityMd);
24875
25090
  const content = readFileSyncSafe(verityMd);
24876
25091
  const refreshed = content.split("GATE.md").join("VERITY.md");
24877
- if (refreshed !== content) (0, import_node_fs47.writeFileSync)(verityMd, refreshed);
25092
+ if (refreshed !== content) (0, import_node_fs49.writeFileSync)(verityMd, refreshed);
24878
25093
  actions.push("Renamed GATE.md \u2192 VERITY.md");
24879
25094
  }
24880
25095
  async function migrateTelemetryHeaders(root, actions) {
24881
25096
  const file = (0, import_node_path31.join)(root, ".claude", "settings.local.json");
24882
- if (!(0, import_node_fs47.existsSync)(file)) return;
25097
+ if (!(0, import_node_fs49.existsSync)(file)) return;
24883
25098
  let settings;
24884
25099
  try {
24885
25100
  settings = JSON.parse(readFileSyncSafe(file) || "{}");
@@ -24927,14 +25142,14 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
24927
25142
  }
24928
25143
  if (toAppend.length > 0) {
24929
25144
  const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
24930
- (0, import_node_fs47.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
25145
+ (0, import_node_fs49.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
24931
25146
  }
24932
- (0, import_node_fs47.rmSync)(gateCreds, { force: true });
25147
+ (0, import_node_fs49.rmSync)(gateCreds, { force: true });
24933
25148
  return toAppend.length;
24934
25149
  }
24935
25150
  function readFileSyncSafe(path) {
24936
25151
  try {
24937
- return (0, import_node_fs47.readFileSync)(path, "utf-8");
25152
+ return (0, import_node_fs49.readFileSync)(path, "utf-8");
24938
25153
  } catch {
24939
25154
  return "";
24940
25155
  }
@@ -24949,35 +25164,35 @@ function hasStagedChanges(root) {
24949
25164
  }
24950
25165
  function moveDir(from, to) {
24951
25166
  try {
24952
- (0, import_node_fs47.renameSync)(from, to);
25167
+ (0, import_node_fs49.renameSync)(from, to);
24953
25168
  } catch (err) {
24954
25169
  if (err.code !== "EXDEV") throw err;
24955
- (0, import_node_fs47.cpSync)(from, to, { recursive: true });
24956
- (0, import_node_fs47.rmSync)(from, { recursive: true, force: true });
25170
+ (0, import_node_fs49.cpSync)(from, to, { recursive: true });
25171
+ (0, import_node_fs49.rmSync)(from, { recursive: true, force: true });
24957
25172
  }
24958
25173
  }
24959
25174
  function moveFile(from, to) {
24960
25175
  try {
24961
- (0, import_node_fs47.renameSync)(from, to);
25176
+ (0, import_node_fs49.renameSync)(from, to);
24962
25177
  } catch (err) {
24963
25178
  if (err.code !== "EXDEV") throw err;
24964
- (0, import_node_fs47.cpSync)(from, to);
24965
- (0, import_node_fs47.rmSync)(from, { force: true });
25179
+ (0, import_node_fs49.cpSync)(from, to);
25180
+ (0, import_node_fs49.rmSync)(from, { force: true });
24966
25181
  }
24967
25182
  }
24968
25183
  function carryLegacyContents(gateDir, verityDir) {
24969
25184
  let copied = 0;
24970
25185
  const walk2 = (relDir) => {
24971
25186
  const srcDir = (0, import_node_path31.join)(gateDir, relDir);
24972
- for (const entry of (0, import_node_fs47.readdirSync)(srcDir)) {
25187
+ for (const entry of (0, import_node_fs49.readdirSync)(srcDir)) {
24973
25188
  const rel = relDir ? (0, import_node_path31.join)(relDir, entry) : entry;
24974
25189
  const src = (0, import_node_path31.join)(gateDir, rel);
24975
25190
  const dest = (0, import_node_path31.join)(verityDir, rel);
24976
- if ((0, import_node_fs47.statSync)(src).isDirectory()) {
25191
+ if ((0, import_node_fs49.statSync)(src).isDirectory()) {
24977
25192
  walk2(rel);
24978
- } else if (!(0, import_node_fs47.existsSync)(dest)) {
24979
- (0, import_node_fs47.mkdirSync)((0, import_node_path31.dirname)(dest), { recursive: true });
24980
- (0, import_node_fs47.cpSync)(src, dest);
25193
+ } else if (!(0, import_node_fs49.existsSync)(dest)) {
25194
+ (0, import_node_fs49.mkdirSync)((0, import_node_path31.dirname)(dest), { recursive: true });
25195
+ (0, import_node_fs49.cpSync)(src, dest);
24981
25196
  copied++;
24982
25197
  }
24983
25198
  }
@@ -24988,20 +25203,20 @@ function carryLegacyContents(gateDir, verityDir) {
24988
25203
  async function needsMigration(root = repoRoot()) {
24989
25204
  const gateDir = (0, import_node_path31.join)(root, ".gate");
24990
25205
  const verityDir = (0, import_node_path31.join)(root, ".verity");
24991
- if ((0, import_node_fs47.existsSync)(gateDir) && !(0, import_node_fs47.existsSync)(verityDir)) return true;
24992
- if ((0, import_node_fs47.existsSync)(gateDir) && (0, import_node_fs47.existsSync)(verityDir)) {
24993
- if ((0, import_node_fs47.existsSync)((0, import_node_path31.join)(gateDir, "credentials")) && !(0, import_node_fs47.existsSync)((0, import_node_path31.join)(verityDir, "credentials"))) {
25206
+ if ((0, import_node_fs49.existsSync)(gateDir) && !(0, import_node_fs49.existsSync)(verityDir)) return true;
25207
+ if ((0, import_node_fs49.existsSync)(gateDir) && (0, import_node_fs49.existsSync)(verityDir)) {
25208
+ if ((0, import_node_fs49.existsSync)((0, import_node_path31.join)(gateDir, "credentials")) && !(0, import_node_fs49.existsSync)((0, import_node_path31.join)(verityDir, "credentials"))) {
24994
25209
  return true;
24995
25210
  }
24996
- if ((0, import_node_fs47.existsSync)((0, import_node_path31.join)(gateDir, "memory")) && !(0, import_node_fs47.existsSync)((0, import_node_path31.join)(verityDir, "memory"))) {
25211
+ if ((0, import_node_fs49.existsSync)((0, import_node_path31.join)(gateDir, "memory")) && !(0, import_node_fs49.existsSync)((0, import_node_path31.join)(verityDir, "memory"))) {
24997
25212
  return true;
24998
25213
  }
24999
25214
  }
25000
25215
  const claudeMd = (0, import_node_path31.join)(root, "CLAUDE.md");
25001
- if ((0, import_node_fs47.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
25216
+ if ((0, import_node_fs49.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
25002
25217
  return true;
25003
25218
  }
25004
- if ((0, import_node_fs47.existsSync)((0, import_node_path31.join)(root, "GATE.md")) && !(0, import_node_fs47.existsSync)((0, import_node_path31.join)(root, "VERITY.md"))) {
25219
+ if ((0, import_node_fs49.existsSync)((0, import_node_path31.join)(root, "GATE.md")) && !(0, import_node_fs49.existsSync)((0, import_node_path31.join)(root, "VERITY.md"))) {
25005
25220
  return true;
25006
25221
  }
25007
25222
  if (await hasLegacyHooksAt(root)) return true;
@@ -25276,7 +25491,7 @@ async function promptMultiSelect(question, choices, fallback) {
25276
25491
  }
25277
25492
 
25278
25493
  // src/lib/remote-config.ts
25279
- var import_node_fs48 = require("node:fs");
25494
+ var import_node_fs50 = require("node:fs");
25280
25495
  var import_promises16 = require("node:fs/promises");
25281
25496
  var import_node_path32 = require("node:path");
25282
25497
  var import_yaml5 = __toESM(require_dist());
@@ -25323,7 +25538,7 @@ async function adoptRemoteSetup(found, opts) {
25323
25538
  written.push(STANDARD_FILE);
25324
25539
  if (rider !== null) {
25325
25540
  const localIgnore = projectPath(VERITYIGNORE_FILE);
25326
- if (!(0, import_node_fs48.existsSync)(localIgnore)) {
25541
+ if (!(0, import_node_fs50.existsSync)(localIgnore)) {
25327
25542
  await writeOut(VERITYIGNORE_FILE, rider);
25328
25543
  written.push(VERITYIGNORE_FILE);
25329
25544
  } else {
@@ -25481,7 +25696,7 @@ function resolveDataDir2() {
25481
25696
  // local dev: running from repo root
25482
25697
  ];
25483
25698
  for (const candidate of candidates) {
25484
- if ((0, import_node_fs49.existsSync)((0, import_node_path33.join)(candidate, "skills"))) {
25699
+ if ((0, import_node_fs51.existsSync)((0, import_node_path33.join)(candidate, "skills"))) {
25485
25700
  return candidate;
25486
25701
  }
25487
25702
  }
@@ -25497,7 +25712,7 @@ async function skillIsCurrent(src, dest) {
25497
25712
  const list2 = (dir) => {
25498
25713
  const out = [];
25499
25714
  const walk2 = (d, prefix) => {
25500
- for (const e of (0, import_node_fs49.readdirSync)(d, { withFileTypes: true })) {
25715
+ for (const e of (0, import_node_fs51.readdirSync)(d, { withFileTypes: true })) {
25501
25716
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
25502
25717
  if (e.isDirectory()) walk2((0, import_node_path33.join)(d, e.name), rel);
25503
25718
  else if (e.isFile()) out.push(rel);
@@ -25689,7 +25904,7 @@ async function synthesizeLocally(opts) {
25689
25904
  async function healStaleAnalysisConfig(globals) {
25690
25905
  const configPath = projectPath(CODACY_CONFIG_FILE);
25691
25906
  const standardPath = projectPath(STANDARD_FILE);
25692
- if (!(0, import_node_fs49.existsSync)(configPath) || !(0, import_node_fs49.existsSync)(standardPath)) return;
25907
+ if (!(0, import_node_fs51.existsSync)(configPath) || !(0, import_node_fs51.existsSync)(standardPath)) return;
25693
25908
  const validation = validatePatternIds();
25694
25909
  if (validation.status !== "invalid") return;
25695
25910
  printWarn(" Your analysis config names pattern ids that no longer resolve \u2014 those tools were");
@@ -25783,14 +25998,125 @@ async function handoffToSetup(enabled, claudeInstalled) {
25783
25998
  }
25784
25999
  await reportPhaseTwo(startedAt);
25785
26000
  }
26001
+ async function installSkills(force, step) {
26002
+ step("Installing skills");
26003
+ const dataDir = resolveDataDir2();
26004
+ const skillsSource = (0, import_node_path33.join)(dataDir, "skills");
26005
+ const skillsDest = ".claude/skills";
26006
+ let skillsInstalled = 0;
26007
+ for (const skill of SKILLS) {
26008
+ const src = (0, import_node_path33.join)(skillsSource, skill);
26009
+ const dest = (0, import_node_path33.join)(skillsDest, skill);
26010
+ if (!(0, import_node_fs51.existsSync)(src)) {
26011
+ printWarn(` Skill data not found: ${skill}`);
26012
+ continue;
26013
+ }
26014
+ if ((0, import_node_fs51.existsSync)(dest) && !force && await skillIsCurrent(src, dest)) {
26015
+ skillsInstalled++;
26016
+ continue;
26017
+ }
26018
+ await copyDir(src, dest);
26019
+ skillsInstalled++;
26020
+ }
26021
+ printInfo(` ${skillsInstalled}/${SKILLS.length} skills installed to .claude/skills/ \u2713`);
26022
+ }
26023
+ async function adoptPluginWiring(gitMoments, moments) {
26024
+ const settings = await readSettings();
26025
+ const stripped = removeVerityHooks(settings);
26026
+ const hadAny = JSON.stringify(settings.hooks ?? {}) !== JSON.stringify(stripped.hooks ?? {});
26027
+ if (hadAny) {
26028
+ await writeSettings(stripped);
26029
+ printInfo(" Removed this project's Verity hooks from .claude/settings.json \u2713");
26030
+ printInfo(" The plugin wires them now, so each turn is reviewed once.");
26031
+ } else {
26032
+ printInfo(" Wired by the Verity plugin \u2014 nothing to reconcile here \u2713");
26033
+ }
26034
+ printInfo(` Pre-commit gate: ${gitMoments.includes("commit") ? "on" : "off"}`);
26035
+ printInfo(` Pre-push/PR gate: ${gitMoments.includes("push") ? "on" : "off"}`);
26036
+ printInfo(" Stop + intent + baseline + compact + session-end: always on \u2713");
26037
+ if (!moments.includes("stop")) {
26038
+ printWarn(" Turning the Stop review off is not yet supported under the plugin \u2014 it stays on.");
26039
+ }
26040
+ }
26041
+ async function reconcileOwnWiring(moments) {
26042
+ await applyMomentSelection(moments);
26043
+ const hookStatus = await checkAllVerityHooks();
26044
+ printInfo(` Stop (verity analyze): ${hookStatus.stop ? "on" : "off"}`);
26045
+ printInfo(` Pre-commit gate: ${hookStatus.guardOn.includes("commit") ? "on" : "off"}`);
26046
+ printInfo(` Pre-push/PR gate: ${hookStatus.guardOn.includes("push") ? "on" : "off"}`);
26047
+ printInfo(" Intent + baseline + compact + session-end: always on \u2713");
26048
+ if (!hookStatus.stop && hookStatus.guardOn.length === 0) {
26049
+ printWarn(" No analysis moment is active \u2014 code changes will NOT be reviewed.");
26050
+ printWarn(" Enable one: verity hooks install --moments stop");
26051
+ }
26052
+ }
26053
+ async function checkPrerequisites(step) {
26054
+ step("Checking prerequisites");
26055
+ const prereqs = await checkPrereqs({ install: true });
26056
+ for (const c of prereqs.checks) {
26057
+ if (c.status === "ok") {
26058
+ if (c.justInstalled) continue;
26059
+ printInfo(` ${c.label} ${c.detail} \u2713`);
26060
+ } else {
26061
+ printWarn(` ${c.label}: ${c.detail}`);
26062
+ if (c.remedy) printWarn(` ${c.remedy}`);
26063
+ }
26064
+ }
26065
+ if (prereqs.blocked) {
26066
+ printError("A required prerequisite is missing \u2014 cannot continue.");
26067
+ process.exit(1);
26068
+ }
26069
+ return prereqs.checks.some((c) => c.id === "claude" && c.status === "ok");
26070
+ }
26071
+ async function scaffoldProject(step, defaultsOnly) {
26072
+ step("Knowledge base, .gitignore and CLAUDE.md");
26073
+ await (0, import_promises17.mkdir)(VERITY_DIR, { recursive: true });
26074
+ await ensureMemoryDir();
26075
+ const ignoreResult = ensureVerityGitignore();
26076
+ if (ignoreResult === "failed") {
26077
+ printWarn(" .gitignore: could not write the Verity block \u2014 add it manually");
26078
+ printWarn(" (.verity/ holds copies of analyzed files, including any secret the gate flagged)");
26079
+ } else if (ignoreResult === "conflict") {
26080
+ printWarn(" .gitignore: the Verity block is in place but git still ignores .verity/standard.yaml");
26081
+ printWarn(" Something outside this file covers it \u2014 a global (~/.gitignore) or nested");
26082
+ printWarn(" .gitignore, or a pattern we do not recognise. Check: git check-ignore -v .verity/standard.yaml");
26083
+ printWarn(" Until it is fixed, the Standard and the knowledge graph cannot be committed.");
26084
+ } else if (ignoreResult === "repaired") {
26085
+ printInfo(" .gitignore: rewrote `.verity/` to `.verity/*` so the standard stays committable \u2713");
26086
+ } else {
26087
+ printInfo(` .gitignore: Verity block ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
26088
+ }
26089
+ const tracked = committedVerityState();
26090
+ if (tracked.length > 0) {
26091
+ printWarn(` ${tracked.length} Verity state file(s) are tracked in git (e.g. ${tracked[0]}).`);
26092
+ const untrack = defaultsOnly ? false : await promptYes(" Untrack them now (files stay on disk)? [Y/n] ", { nonInteractive: false });
26093
+ if (untrack) {
26094
+ const result = untrackVerityState();
26095
+ if (result === "untracked") printInfo(" Untracked (staged) \u2014 commit to finish \u2713");
26096
+ else if (result === "failed") printWarn(' Could not untrack \u2014 run "git rm -r --cached .verity" manually');
26097
+ } else {
26098
+ printWarn(" Left tracked. Fix with: git rm -r --cached .verity && git add .verity/standard.yaml .verity/memory");
26099
+ }
26100
+ }
26101
+ try {
26102
+ await ensureClaudeMdPointer();
26103
+ printInfo(" CLAUDE.md instructions \u2713");
26104
+ } catch (err) {
26105
+ printWarn(` Could not update CLAUDE.md: ${err.message}`);
26106
+ }
26107
+ }
25786
26108
  function registerInitCommand(program2) {
25787
- 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").option("--no-adopt", "Don't offer this repository's existing Standard from the service; synthesize a new one").action(async (opts) => {
26109
+ 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").option(
26110
+ "--plugin-mode",
26111
+ "The Claude Code plugin owns the skills and hooks: install neither, and remove any this project already has"
26112
+ ).option("--no-adopt", "Don't offer this repository's existing Standard from the service; synthesize a new one").action(async (opts) => {
25788
26113
  const force = opts.force ?? false;
25789
26114
  const wantsHandoff = opts.setup !== false;
25790
26115
  const wantsAdopt = opts.adopt !== false;
25791
26116
  const defaultsOnly = (opts.yes ?? false) || !interactive();
26117
+ const pluginMode = opts.pluginMode ?? pluginActiveHere();
25792
26118
  const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
25793
- const isProject = projectMarkers.some((m) => (0, import_node_fs49.existsSync)(m));
26119
+ const isProject = projectMarkers.some((m) => (0, import_node_fs51.existsSync)(m));
25794
26120
  if (!isProject) {
25795
26121
  printError("No project detected in the current directory.");
25796
26122
  printInfo('Run "verity init" from your project root.');
@@ -25821,43 +26147,13 @@ function registerInitCommand(program2) {
25821
26147
  }
25822
26148
  console.log("");
25823
26149
  }
25824
- step("Checking prerequisites");
25825
- const prereqs = await checkPrereqs({ install: true });
25826
- for (const c of prereqs.checks) {
25827
- if (c.status === "ok") {
25828
- if (c.justInstalled) continue;
25829
- printInfo(` ${c.label} ${c.detail} \u2713`);
25830
- } else {
25831
- printWarn(` ${c.label}: ${c.detail}`);
25832
- if (c.remedy) printWarn(` ${c.remedy}`);
25833
- }
25834
- }
25835
- if (prereqs.blocked) {
25836
- printError("A required prerequisite is missing \u2014 cannot continue.");
25837
- process.exit(1);
25838
- }
25839
- const claudeInstalled = prereqs.checks.some((c) => c.id === "claude" && c.status === "ok");
26150
+ const claudeInstalled = await checkPrerequisites(step);
25840
26151
  console.log("");
25841
- step("Installing skills");
25842
- const dataDir = resolveDataDir2();
25843
- const skillsSource = (0, import_node_path33.join)(dataDir, "skills");
25844
- const skillsDest = ".claude/skills";
25845
- let skillsInstalled = 0;
25846
- for (const skill of SKILLS) {
25847
- const src = (0, import_node_path33.join)(skillsSource, skill);
25848
- const dest = (0, import_node_path33.join)(skillsDest, skill);
25849
- if (!(0, import_node_fs49.existsSync)(src)) {
25850
- printWarn(` Skill data not found: ${skill}`);
25851
- continue;
25852
- }
25853
- if ((0, import_node_fs49.existsSync)(dest) && !force && await skillIsCurrent(src, dest)) {
25854
- skillsInstalled++;
25855
- continue;
25856
- }
25857
- await copyDir(src, dest);
25858
- skillsInstalled++;
26152
+ if (pluginMode) {
26153
+ printInfo("Skipping skills \u2014 the Verity plugin provides them, namespaced as /verity:<name>.");
26154
+ } else {
26155
+ await installSkills(force, step);
25859
26156
  }
25860
- printInfo(` ${skillsInstalled}/${SKILLS.length} skills installed to .claude/skills/ \u2713`);
25861
26157
  step(defaultsOnly ? "Setup answers (defaults)" : "Your setup answers");
25862
26158
  const previous = await readSetupState();
25863
26159
  const answers = await askSetupQuestions(defaultsOnly, previous);
@@ -25865,54 +26161,20 @@ function registerInitCommand(program2) {
25865
26161
  if (defaultsOnly) {
25866
26162
  printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
25867
26163
  }
25868
- step("Knowledge base, .gitignore and CLAUDE.md");
25869
- await (0, import_promises17.mkdir)(VERITY_DIR, { recursive: true });
25870
- await ensureMemoryDir();
25871
- const ignoreResult = ensureVerityGitignore();
25872
- if (ignoreResult === "failed") {
25873
- printWarn(" .gitignore: could not write the Verity block \u2014 add it manually");
25874
- printWarn(" (.verity/ holds copies of analyzed files, including any secret the gate flagged)");
25875
- } else if (ignoreResult === "conflict") {
25876
- printWarn(" .gitignore: the Verity block is in place but git still ignores .verity/standard.yaml");
25877
- printWarn(" Something outside this file covers it \u2014 a global (~/.gitignore) or nested");
25878
- printWarn(" .gitignore, or a pattern we do not recognise. Check: git check-ignore -v .verity/standard.yaml");
25879
- printWarn(" Until it is fixed, the Standard and the knowledge graph cannot be committed.");
25880
- } else if (ignoreResult === "repaired") {
25881
- printInfo(" .gitignore: rewrote `.verity/` to `.verity/*` so the standard stays committable \u2713");
25882
- } else {
25883
- printInfo(` .gitignore: Verity block ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
25884
- }
25885
- const tracked = committedVerityState();
25886
- if (tracked.length > 0) {
25887
- printWarn(` ${tracked.length} Verity state file(s) are tracked in git (e.g. ${tracked[0]}).`);
25888
- const untrack = defaultsOnly ? false : await promptYes(" Untrack them now (files stay on disk)? [Y/n] ", { nonInteractive: false });
25889
- if (untrack) {
25890
- const result = untrackVerityState();
25891
- if (result === "untracked") printInfo(" Untracked (staged) \u2014 commit to finish \u2713");
25892
- else if (result === "failed") printWarn(' Could not untrack \u2014 run "git rm -r --cached .verity" manually');
25893
- } else {
25894
- printWarn(" Left tracked. Fix with: git rm -r --cached .verity && git add .verity/standard.yaml .verity/memory");
25895
- }
25896
- }
25897
- try {
25898
- await ensureClaudeMdPointer();
25899
- printInfo(" CLAUDE.md instructions \u2713");
25900
- } catch (err) {
25901
- printWarn(` Could not update CLAUDE.md: ${err.message}`);
25902
- }
26164
+ await scaffoldProject(step, defaultsOnly);
25903
26165
  const globalVerityDir = (0, import_node_path33.join)(process.env.HOME ?? "", ".verity");
25904
26166
  await (0, import_promises17.mkdir)(globalVerityDir, { recursive: true });
25905
26167
  console.log("");
25906
26168
  step("Wiring Claude Code hooks");
25907
- await applyMomentSelection(moments);
25908
- const hookStatus = await checkAllVerityHooks();
25909
- printInfo(` Stop (verity analyze): ${hookStatus.stop ? "on" : "off"}`);
25910
- printInfo(` Pre-commit gate: ${hookStatus.guardOn.includes("commit") ? "on" : "off"}`);
25911
- printInfo(` Pre-push/PR gate: ${hookStatus.guardOn.includes("push") ? "on" : "off"}`);
25912
- printInfo(` Intent + baseline + compact + session-end: always on \u2713`);
25913
- if (!hookStatus.stop && hookStatus.guardOn.length === 0) {
25914
- printWarn(" No analysis moment is active \u2014 code changes will NOT be reviewed.");
25915
- printWarn(" Enable one: verity hooks install --moments stop");
26169
+ const gitMoments = [
26170
+ ...moments.includes("pre-commit") ? ["commit"] : [],
26171
+ ...moments.includes("pre-push") ? ["push"] : []
26172
+ ];
26173
+ writeProjectConfig({ git_moments: gitMoments });
26174
+ if (pluginMode) {
26175
+ await adoptPluginWiring(gitMoments, moments);
26176
+ } else {
26177
+ await reconcileOwnWiring(moments);
25916
26178
  }
25917
26179
  console.log("");
25918
26180
  step("Sign in to Verity (optional)");
@@ -25955,7 +26217,7 @@ function registerInitCommand(program2) {
25955
26217
  }
25956
26218
  step("Your project's Standard");
25957
26219
  let haveStandard = false;
25958
- if ((0, import_node_fs49.existsSync)(projectPath(STANDARD_FILE))) {
26220
+ if ((0, import_node_fs51.existsSync)(projectPath(STANDARD_FILE))) {
25959
26221
  printInfo(" This project already has .verity/standard.yaml \u2014 keeping it.");
25960
26222
  haveStandard = true;
25961
26223
  } else {
@@ -25982,7 +26244,7 @@ function registerInitCommand(program2) {
25982
26244
  ...telemetryChoice ? { telemetry: telemetryChoice } : {},
25983
26245
  init: {
25984
26246
  completed_at: (/* @__PURE__ */ new Date()).toISOString(),
25985
- cli_version: true ? "0.32.0-experimental.f0746f7" : "dev"
26247
+ cli_version: true ? "0.32.0" : "dev"
25986
26248
  }
25987
26249
  });
25988
26250
  } catch (err) {
@@ -25991,9 +26253,13 @@ function registerInitCommand(program2) {
25991
26253
  console.log("");
25992
26254
  printInfo("This machine is set up.");
25993
26255
  console.log("");
25994
- console.log(" .claude/skills/verity-*/ 8 skills (setup, analyze, status, feedback,");
25995
- console.log(" learn, memory, insights, reflect)");
25996
- console.log(" .claude/settings.json hooks, reconciled to your chosen moments");
26256
+ if (pluginMode) {
26257
+ console.log(" (skills and hooks come from the Verity plugin, not this project)");
26258
+ } else {
26259
+ console.log(" .claude/skills/verity-*/ 8 skills (setup, analyze, status, feedback,");
26260
+ console.log(" learn, memory, insights, reflect)");
26261
+ console.log(" .claude/settings.json hooks, reconciled to your chosen moments");
26262
+ }
25997
26263
  console.log(" .verity/memory/ knowledge base (commit to git)");
25998
26264
  console.log(" .verity/standard.yaml the Standard the gate enforces");
25999
26265
  console.log(" .codacy/codacy.config.json static-analysis patterns (validated)");
@@ -26017,7 +26283,7 @@ function registerInitCommand(program2) {
26017
26283
  }
26018
26284
 
26019
26285
  // src/commands/uninstall.ts
26020
- var import_node_fs50 = require("node:fs");
26286
+ var import_node_fs52 = require("node:fs");
26021
26287
  var import_node_path34 = require("node:path");
26022
26288
  var SKILL_NAMES = [
26023
26289
  "verity-setup",
@@ -26038,10 +26304,10 @@ function registerUninstallCommand(program2) {
26038
26304
  const skillsRoot = projectPath(".claude/skills");
26039
26305
  for (const name of SKILL_NAMES) {
26040
26306
  const dir = (0, import_node_path34.join)(skillsRoot, name);
26041
- if ((0, import_node_fs50.existsSync)(dir)) {
26307
+ if ((0, import_node_fs52.existsSync)(dir)) {
26042
26308
  actions.push({
26043
26309
  label: `Remove .claude/skills/${name}/`,
26044
- apply: () => (0, import_node_fs50.rmSync)(dir, { recursive: true, force: true })
26310
+ apply: () => (0, import_node_fs52.rmSync)(dir, { recursive: true, force: true })
26045
26311
  });
26046
26312
  }
26047
26313
  }
@@ -26055,24 +26321,24 @@ function registerUninstallCommand(program2) {
26055
26321
  });
26056
26322
  }
26057
26323
  const verityDir = projectPath(VERITY_DIR);
26058
- if ((0, import_node_fs50.existsSync)(verityDir)) {
26324
+ if ((0, import_node_fs52.existsSync)(verityDir)) {
26059
26325
  actions.push({
26060
26326
  label: `Remove ${VERITY_DIR}/`,
26061
- apply: () => (0, import_node_fs50.rmSync)(verityDir, { recursive: true, force: true })
26327
+ apply: () => (0, import_node_fs52.rmSync)(verityDir, { recursive: true, force: true })
26062
26328
  });
26063
26329
  }
26064
26330
  if (!keepVerityMd) {
26065
26331
  const verityMd = projectPath(VERITY_MD_FILE);
26066
- if ((0, import_node_fs50.existsSync)(verityMd)) {
26332
+ if ((0, import_node_fs52.existsSync)(verityMd)) {
26067
26333
  actions.push({
26068
26334
  label: `Remove ${VERITY_MD_FILE}`,
26069
- apply: () => (0, import_node_fs50.rmSync)(verityMd, { force: true })
26335
+ apply: () => (0, import_node_fs52.rmSync)(verityMd, { force: true })
26070
26336
  });
26071
26337
  }
26072
26338
  }
26073
26339
  const cleanupEmptyDir = (path) => {
26074
- if ((0, import_node_fs50.existsSync)(path) && (0, import_node_fs50.statSync)(path).isDirectory() && (0, import_node_fs50.readdirSync)(path).length === 0) {
26075
- (0, import_node_fs50.rmdirSync)(path);
26340
+ if ((0, import_node_fs52.existsSync)(path) && (0, import_node_fs52.statSync)(path).isDirectory() && (0, import_node_fs52.readdirSync)(path).length === 0) {
26341
+ (0, import_node_fs52.rmdirSync)(path);
26076
26342
  }
26077
26343
  };
26078
26344
  actions.push({
@@ -26084,10 +26350,10 @@ function registerUninstallCommand(program2) {
26084
26350
  });
26085
26351
  const home = process.env.HOME ?? "";
26086
26352
  const globalVerityDir = (0, import_node_path34.join)(home, ".verity");
26087
- if (purgeGlobal && (0, import_node_fs50.existsSync)(globalVerityDir)) {
26353
+ if (purgeGlobal && (0, import_node_fs52.existsSync)(globalVerityDir)) {
26088
26354
  actions.push({
26089
26355
  label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
26090
- apply: () => (0, import_node_fs50.rmSync)(globalVerityDir, { recursive: true, force: true })
26356
+ apply: () => (0, import_node_fs52.rmSync)(globalVerityDir, { recursive: true, force: true })
26091
26357
  });
26092
26358
  }
26093
26359
  if (actions.length === 0) {
@@ -26281,7 +26547,7 @@ function registerTaskCommands(program2) {
26281
26547
  }
26282
26548
 
26283
26549
  // src/commands/reset.ts
26284
- var import_node_fs51 = require("node:fs");
26550
+ var import_node_fs53 = require("node:fs");
26285
26551
  var import_node_path35 = require("node:path");
26286
26552
  function registerResetCommand(program2) {
26287
26553
  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) => {
@@ -26319,11 +26585,11 @@ function registerResetCommand(program2) {
26319
26585
  }
26320
26586
  const cacheDir = projectPath(CACHE_DIR);
26321
26587
  let purged = 0;
26322
- if ((0, import_node_fs51.existsSync)(cacheDir)) {
26323
- for (const entry of (0, import_node_fs51.readdirSync)(cacheDir)) {
26588
+ if ((0, import_node_fs53.existsSync)(cacheDir)) {
26589
+ for (const entry of (0, import_node_fs53.readdirSync)(cacheDir)) {
26324
26590
  if (entry.startsWith("pending-")) {
26325
26591
  try {
26326
- (0, import_node_fs51.unlinkSync)((0, import_node_path35.join)(cacheDir, entry));
26592
+ (0, import_node_fs53.unlinkSync)((0, import_node_path35.join)(cacheDir, entry));
26327
26593
  purged++;
26328
26594
  } catch {
26329
26595
  }
@@ -26338,19 +26604,19 @@ function registerResetCommand(program2) {
26338
26604
  projectPath(`${VERITY_DIR}/.last-analysis`)
26339
26605
  ];
26340
26606
  for (const file of filesToClear) {
26341
- if ((0, import_node_fs51.existsSync)(file)) {
26607
+ if ((0, import_node_fs53.existsSync)(file)) {
26342
26608
  try {
26343
- (0, import_node_fs51.writeFileSync)(file, "");
26609
+ (0, import_node_fs53.writeFileSync)(file, "");
26344
26610
  } catch {
26345
26611
  }
26346
26612
  }
26347
26613
  }
26348
26614
  if (opts.all) {
26349
26615
  const logsDir = projectPath(`${VERITY_DIR}/.logs`);
26350
- if ((0, import_node_fs51.existsSync)(logsDir)) {
26351
- for (const entry of (0, import_node_fs51.readdirSync)(logsDir)) {
26616
+ if ((0, import_node_fs53.existsSync)(logsDir)) {
26617
+ for (const entry of (0, import_node_fs53.readdirSync)(logsDir)) {
26352
26618
  try {
26353
- (0, import_node_fs51.unlinkSync)((0, import_node_path35.join)(logsDir, entry));
26619
+ (0, import_node_fs53.unlinkSync)((0, import_node_path35.join)(logsDir, entry));
26354
26620
  } catch {
26355
26621
  }
26356
26622
  }
@@ -26658,8 +26924,8 @@ function registerTelemetryCommands(program2) {
26658
26924
  }
26659
26925
 
26660
26926
  // src/cli.ts
26661
- program.name("verity").description("CLI for Verity quality gate service").version("0.32.0-experimental.f0746f7").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) => {
26662
- installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.0-experimental.f0746f7");
26927
+ program.name("verity").description("CLI for Verity quality gate service").version("0.32.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) => {
26928
+ installStderrLog(actionCommand.name(), process.argv.slice(2), "0.32.0");
26663
26929
  setUserNamedServiceUrl(program.opts().serviceUrl);
26664
26930
  try {
26665
26931
  await foldLegacyLocalCredential();