@unerr-ai/unerr 0.1.7 → 0.1.8

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.
Files changed (3) hide show
  1. package/README.md +9 -2
  2. package/dist/cli.js +1353 -279
  3. package/package.json +9 -2
package/dist/cli.js CHANGED
@@ -245,16 +245,16 @@ async function getParser(language) {
245
245
  const parser = new TreeSitter();
246
246
  const langFile = `tree-sitter-${language}.wasm`;
247
247
  try {
248
- const { join: join83 } = await import("path");
248
+ const { join: join85 } = await import("path");
249
249
  const { existsSync: existsSync82 } = await import("fs");
250
250
  const possiblePaths = [
251
- join83(
251
+ join85(
252
252
  process.cwd(),
253
253
  "node_modules",
254
254
  `tree-sitter-${language}`,
255
255
  langFile
256
256
  ),
257
- join83(process.cwd(), "node_modules", "web-tree-sitter", langFile)
257
+ join85(process.cwd(), "node_modules", "web-tree-sitter", langFile)
258
258
  ];
259
259
  let wasmPath = null;
260
260
  for (const p of possiblePaths) {
@@ -5770,6 +5770,7 @@ var init_startup_log = __esm({
5770
5770
  // src/config/settings.ts
5771
5771
  var settings_exports = {};
5772
5772
  __export(settings_exports, {
5773
+ FetchUrlConfigSchema: () => FetchUrlConfigSchema,
5773
5774
  LocalLlmConfigSchema: () => LocalLlmConfigSchema,
5774
5775
  SettingsSchema: () => SettingsSchema,
5775
5776
  loadSettings: () => loadSettings,
@@ -5852,7 +5853,7 @@ function loadSettings(cwd) {
5852
5853
  };
5853
5854
  return SettingsSchema.parse(merged);
5854
5855
  }
5855
- var LlmProviderEnum, EndpointConfigSchema, LocalLlmConfigSchema, LlmConfigSchema, SettingsSchema, DEFAULTS;
5856
+ var LlmProviderEnum, EndpointConfigSchema, LocalLlmConfigSchema, LlmConfigSchema, FetchUrlConfigSchema, SettingsSchema, DEFAULTS;
5856
5857
  var init_settings = __esm({
5857
5858
  "src/config/settings.ts"() {
5858
5859
  "use strict";
@@ -5885,6 +5886,17 @@ var init_settings = __esm({
5885
5886
  apiKey: z.string().optional(),
5886
5887
  baseUrl: z.string().optional()
5887
5888
  });
5889
+ FetchUrlConfigSchema = z.object({
5890
+ playwright: z.object({
5891
+ enabled: z.boolean().default(false),
5892
+ timeoutMs: z.number().int().min(1e3).max(6e4).default(15e3),
5893
+ waitUntil: z.enum(["load", "domcontentloaded", "networkidle"]).default("networkidle")
5894
+ }).default(() => ({
5895
+ enabled: false,
5896
+ timeoutMs: 15e3,
5897
+ waitUntil: "networkidle"
5898
+ }))
5899
+ });
5888
5900
  SettingsSchema = z.object({
5889
5901
  /** Default Claude model for interactive sessions */
5890
5902
  model: z.string().default("claude-sonnet-4-20250514"),
@@ -5899,7 +5911,15 @@ var init_settings = __esm({
5899
5911
  /** AI SDK LLM configuration (Sprint D — unified multi-provider) */
5900
5912
  llm: LlmConfigSchema.optional(),
5901
5913
  /** BYO-LLM configuration for local LLM providers */
5902
- localLlm: LocalLlmConfigSchema.optional()
5914
+ localLlm: LocalLlmConfigSchema.optional(),
5915
+ /** fetch_url runtime config (Playwright SPA fallback, etc.) */
5916
+ fetchUrl: FetchUrlConfigSchema.default(() => ({
5917
+ playwright: {
5918
+ enabled: false,
5919
+ timeoutMs: 15e3,
5920
+ waitUntil: "networkidle"
5921
+ }
5922
+ }))
5903
5923
  });
5904
5924
  DEFAULTS = SettingsSchema.parse({});
5905
5925
  }
@@ -6551,6 +6571,19 @@ CREATE TABLE IF NOT EXISTS meta (
6551
6571
  key TEXT PRIMARY KEY,
6552
6572
  value TEXT NOT NULL
6553
6573
  );
6574
+
6575
+ CREATE TABLE IF NOT EXISTS fetch_cache (
6576
+ url TEXT PRIMARY KEY,
6577
+ content_hash TEXT NOT NULL,
6578
+ markdown TEXT NOT NULL,
6579
+ title TEXT NOT NULL,
6580
+ extractor TEXT NOT NULL,
6581
+ raw_bytes INTEGER NOT NULL,
6582
+ compressed_bytes INTEGER NOT NULL,
6583
+ fetched_at INTEGER NOT NULL,
6584
+ hit_count INTEGER NOT NULL DEFAULT 0
6585
+ );
6586
+ CREATE INDEX IF NOT EXISTS idx_fetch_cache_fetched ON fetch_cache(fetched_at);
6554
6587
  `;
6555
6588
  SCHEMA_VERSION = "1";
6556
6589
  MetricsStore = class {
@@ -6660,9 +6693,39 @@ CREATE TABLE IF NOT EXISTS meta (
6660
6693
  `),
6661
6694
  allSessionSummaries: this.db.prepare(`
6662
6695
  SELECT * FROM session_summaries ORDER BY ended_at DESC
6696
+ `),
6697
+ upsertFetchCache: this.db.prepare(`
6698
+ INSERT INTO fetch_cache
6699
+ (url, content_hash, markdown, title, extractor,
6700
+ raw_bytes, compressed_bytes, fetched_at, hit_count)
6701
+ VALUES (@url, @content_hash, @markdown, @title, @extractor,
6702
+ @raw_bytes, @compressed_bytes, @fetched_at, 0)
6703
+ ON CONFLICT(url) DO UPDATE SET
6704
+ content_hash = excluded.content_hash,
6705
+ markdown = excluded.markdown,
6706
+ title = excluded.title,
6707
+ extractor = excluded.extractor,
6708
+ raw_bytes = excluded.raw_bytes,
6709
+ compressed_bytes = excluded.compressed_bytes,
6710
+ fetched_at = excluded.fetched_at
6711
+ `),
6712
+ getFetchCache: this.db.prepare(`
6713
+ SELECT * FROM fetch_cache WHERE url = @url
6714
+ `),
6715
+ bumpFetchCacheHit: this.db.prepare(`
6716
+ UPDATE fetch_cache SET hit_count = hit_count + 1 WHERE url = @url
6663
6717
  `)
6664
6718
  };
6665
6719
  }
6720
+ upsertFetchCacheRow(row) {
6721
+ this.stmt.upsertFetchCache.run(row);
6722
+ }
6723
+ getFetchCacheRow(url) {
6724
+ return this.stmt.getFetchCache.get({ url }) ?? null;
6725
+ }
6726
+ bumpFetchCacheHitFor(url) {
6727
+ this.stmt.bumpFetchCacheHit.run({ url });
6728
+ }
6666
6729
  // ── Writes ──────────────────────────────────────────────────────────
6667
6730
  insertCompression(row) {
6668
6731
  return Number(this.stmt.insertCompression.run(row).lastInsertRowid);
@@ -11178,7 +11241,7 @@ async function resolveJavaBuildTool(projectRoot, _options) {
11178
11241
  };
11179
11242
  writeNeedsInput(projectRoot, [signal]);
11180
11243
  log6.info(
11181
- `Java build tool: ${choice.tool} (auto: ${choice.reason}). Override: unerr daemon config . --java-build-tool=<tool>`
11244
+ `Java build tool: ${choice.tool} (auto: ${choice.reason}). Override: unerr pm config . --java-build-tool=<tool>`
11182
11245
  );
11183
11246
  } else {
11184
11247
  log6.info(`Java build tool: ${choice.tool} (${choice.reason})`);
@@ -11813,6 +11876,7 @@ __export(local_indexer_exports, {
11813
11876
  discoverSourceFiles: () => discoverSourceFiles,
11814
11877
  indexLocalProject: () => indexLocalProject,
11815
11878
  reindexFile: () => reindexFile,
11879
+ removeOrphanedEntities: () => removeOrphanedEntities,
11816
11880
  resolveImportSourceToFile: () => resolveImportSourceToFile,
11817
11881
  runCommunityDetection: () => runCommunityDetection,
11818
11882
  runConventionDetection: () => runConventionDetection
@@ -12830,7 +12894,45 @@ async function removeOrphanedEntities(graphStore, liveKeys) {
12830
12894
  const orphanKeys = existingKeys.filter((k) => !liveKeys.has(k));
12831
12895
  if (orphanKeys.length === 0) return;
12832
12896
  log7.info(`Removing ${orphanKeys.length} orphaned entities from graph`);
12833
- for (const key of orphanKeys) {
12897
+ for (let i = 0; i < orphanKeys.length; i += ORPHAN_BATCH_SIZE) {
12898
+ const chunk = orphanKeys.slice(i, i + ORPHAN_BATCH_SIZE);
12899
+ const keyRows = chunk.map((k) => [k]);
12900
+ try {
12901
+ await db.run(
12902
+ `orphan[k] <- $keys
12903
+ ?[from_key, to_key, type] := orphan[from_key], *edges{from_key, to_key, type}
12904
+ :rm edges {from_key, to_key, type}`,
12905
+ { keys: keyRows }
12906
+ );
12907
+ await db.run(
12908
+ `orphan[k] <- $keys
12909
+ ?[from_key, to_key, type] := orphan[to_key], *edges{from_key, to_key, type}
12910
+ :rm edges {from_key, to_key, type}`,
12911
+ { keys: keyRows }
12912
+ );
12913
+ await db.run(
12914
+ `orphan[k] <- $keys
12915
+ ?[token, entity_key] := orphan[entity_key], *search_tokens[token, entity_key]
12916
+ :rm search_tokens {token, entity_key}`,
12917
+ { keys: keyRows }
12918
+ );
12919
+ await db.run(
12920
+ `orphan[k] <- $keys
12921
+ ?[file_path, entity_key] := orphan[entity_key], *file_index[file_path, entity_key]
12922
+ :rm file_index {file_path, entity_key}`,
12923
+ { keys: keyRows }
12924
+ );
12925
+ await db.run("?[key] <- $keys :rm entities {key}", { keys: keyRows });
12926
+ } catch (err) {
12927
+ log7.info(
12928
+ `Batched orphan removal failed for chunk of ${chunk.length} (${formatUnknownError(err)}); falling back to per-key`
12929
+ );
12930
+ await removeOrphansPerKey(db, chunk);
12931
+ }
12932
+ }
12933
+ }
12934
+ async function removeOrphansPerKey(db, keys) {
12935
+ for (const key of keys) {
12834
12936
  try {
12835
12937
  await db.run(
12836
12938
  "?[from_key, to_key, type] := *edges{from_key, to_key, type}, from_key = $key :rm edges {from_key, to_key, type}",
@@ -12987,7 +13089,7 @@ async function indexDocumentFiles(projectRoot, graphStore) {
12987
13089
  }
12988
13090
  return docKeys;
12989
13091
  }
12990
- var INDEXABLE_EXTENSIONS, EXCLUDED_DIRS2, EXCLUDED_PATH_PREFIXES, MAX_FILE_SIZE, DOCUMENT_EXTENSIONS, DOCUMENT_NAMES, CI_CD_DIRS, DOC_READ_LIMIT, log7, DOC_TOKEN_PATTERNS;
13092
+ var INDEXABLE_EXTENSIONS, EXCLUDED_DIRS2, EXCLUDED_PATH_PREFIXES, MAX_FILE_SIZE, DOCUMENT_EXTENSIONS, DOCUMENT_NAMES, CI_CD_DIRS, DOC_READ_LIMIT, log7, ORPHAN_BATCH_SIZE, DOC_TOKEN_PATTERNS;
12991
13093
  var init_local_indexer = __esm({
12992
13094
  "src/intelligence/local-indexer.ts"() {
12993
13095
  "use strict";
@@ -13136,6 +13238,7 @@ var init_local_indexer = __esm({
13136
13238
  `);
13137
13239
  }
13138
13240
  };
13241
+ ORPHAN_BATCH_SIZE = 1e3;
13139
13242
  DOC_TOKEN_PATTERNS = {
13140
13243
  ".md": /^#{1,6}\s+(.+)$/gm,
13141
13244
  ".mdx": /^#{1,6}\s+(.+)$/gm,
@@ -16881,9 +16984,9 @@ function createEmptyAllTime() {
16881
16984
  function loadStats() {
16882
16985
  const currentWeek = getWeekStart();
16883
16986
  try {
16884
- const { readFileSync: readFileSync70 } = __require("fs");
16987
+ const { readFileSync: readFileSync71 } = __require("fs");
16885
16988
  const raw = JSON.parse(
16886
- readFileSync70(getStatsPath(), "utf-8")
16989
+ readFileSync71(getStatsPath(), "utf-8")
16887
16990
  );
16888
16991
  if (raw.version !== 1) {
16889
16992
  return {
@@ -17601,9 +17704,9 @@ function getCumulativePath() {
17601
17704
  function loadCumulativeStats() {
17602
17705
  const currentWeek = getWeekStart2();
17603
17706
  try {
17604
- const { readFileSync: readFileSync70 } = __require("fs");
17707
+ const { readFileSync: readFileSync71 } = __require("fs");
17605
17708
  const raw = JSON.parse(
17606
- readFileSync70(getCumulativePath(), "utf-8")
17709
+ readFileSync71(getCumulativePath(), "utf-8")
17607
17710
  );
17608
17711
  if (raw.weekStart !== currentWeek) {
17609
17712
  return {
@@ -17664,9 +17767,9 @@ function createEmptyCumulativeLocal(weekStart) {
17664
17767
  function loadCumulativeLocalStats() {
17665
17768
  const currentWeek = getWeekStart2();
17666
17769
  try {
17667
- const { readFileSync: readFileSync70 } = __require("fs");
17770
+ const { readFileSync: readFileSync71 } = __require("fs");
17668
17771
  const raw = JSON.parse(
17669
- readFileSync70(getCumulativeLocalPath(), "utf-8")
17772
+ readFileSync71(getCumulativeLocalPath(), "utf-8")
17670
17773
  );
17671
17774
  if (raw.weekStartDate !== currentWeek) {
17672
17775
  return createEmptyCumulativeLocal(currentWeek);
@@ -19119,6 +19222,24 @@ var init_tool_clusters = __esm({
19119
19222
  "starting",
19120
19223
  "planning"
19121
19224
  ]
19225
+ },
19226
+ {
19227
+ id: "web",
19228
+ name: "Web Fetch",
19229
+ tools: ["fetch_url"],
19230
+ triggerKeywords: [
19231
+ "fetch",
19232
+ "url",
19233
+ "webpage",
19234
+ "web page",
19235
+ "http",
19236
+ "https",
19237
+ "scrape",
19238
+ "docs at",
19239
+ "documentation at",
19240
+ "blog",
19241
+ "article"
19242
+ ]
19122
19243
  }
19123
19244
  ];
19124
19245
  TOOL_TO_CLUSTER = /* @__PURE__ */ new Map();
@@ -19236,6 +19357,11 @@ var init_tool_descriptions = __esm({
19236
19357
  active: "Find callers or callees of an entity across the codebase. Pass direction:'callers' (default) or 'callees'. Catches indirect refs grep misses.",
19237
19358
  locked: "[tier 1 \u2014 always exposed]"
19238
19359
  },
19360
+ fetch_url: {
19361
+ tier: 1,
19362
+ active: "Fetch a web page and return DOM-extracted markdown passages. Strips chrome, converts to ATX-markdown, splits by heading, ranks by BM25 when prompt is set, caches by content hash. Use instead of built-in WebFetch \u2014 5\u201310\xD7 fewer tokens.",
19363
+ locked: "[tier 1 \u2014 always exposed]"
19364
+ },
19239
19365
  // ── Tier 2 — structural unlock ─────────────────────────────────────────
19240
19366
  get_critical_nodes: {
19241
19367
  tier: 2,
@@ -19407,6 +19533,36 @@ var init_tool_definitions = __esm({
19407
19533
  openWorldHint: false
19408
19534
  }
19409
19535
  },
19536
+ fetch_url: {
19537
+ inputSchema: {
19538
+ type: "object",
19539
+ properties: {
19540
+ url: {
19541
+ type: "string",
19542
+ description: "Absolute URL to fetch (http or https). Localhost is allowed without TLS upgrade."
19543
+ },
19544
+ prompt: {
19545
+ type: "string",
19546
+ description: "Optional. When set AND extracted markdown > 8 KB, passages are re-ranked by BM25 relevance to this prompt (default top 20)."
19547
+ },
19548
+ offset: {
19549
+ type: "integer",
19550
+ description: "Passage index to start at for pagination (default 0). Pair with limit to walk long pages without re-fetching."
19551
+ },
19552
+ limit: {
19553
+ type: "integer",
19554
+ description: "Maximum passages to return (default 30, max 300). Also acts as BM25 topK when prompt is set."
19555
+ },
19556
+ token_budget: TOKEN_BUDGET_PROP
19557
+ },
19558
+ required: ["url"]
19559
+ },
19560
+ annotations: {
19561
+ title: "Fetch Web Page (Markdown + BM25)",
19562
+ readOnlyHint: true,
19563
+ openWorldHint: true
19564
+ }
19565
+ },
19410
19566
  file_outline: {
19411
19567
  inputSchema: {
19412
19568
  type: "object",
@@ -23084,6 +23240,12 @@ var init_wire_cap = __esm({
23084
23240
  // statically. The caller should pick from the returned `entities[].name`
23085
23241
  // and re-call file_read with that exact value. A literal `entity:<name>`
23086
23242
  // hint trains the agent to paste the placeholder verbatim.
23243
+ },
23244
+ fetch_url: {
23245
+ arrayKey: "passages",
23246
+ defaultLimit: 30,
23247
+ maxLimit: 300,
23248
+ cursorArg: "limit"
23087
23249
  }
23088
23250
  };
23089
23251
  HARD_BYTE_CAP = 8192;
@@ -24309,6 +24471,785 @@ var init_file_read_protocol = __esm({
24309
24471
  }
24310
24472
  });
24311
24473
 
24474
+ // src/tools/web/bm25-rank.ts
24475
+ function tokenize2(text2) {
24476
+ return text2.toLowerCase().replace(/[^a-z0-9\s]+/g, " ").split(/\s+/).filter((t) => t.length > 1 && !STOPWORDS.has(t));
24477
+ }
24478
+ async function rankPassagesByPrompt(passages, opts) {
24479
+ const prompt = opts.prompt?.trim();
24480
+ if (!prompt || passages.length === 0) return passages;
24481
+ const promptTokens = tokenize2(prompt);
24482
+ if (promptTokens.length === 0) return passages;
24483
+ const mod = await import("wink-bm25-text-search");
24484
+ const engine = mod.default();
24485
+ engine.defineConfig({ fldWeights: { text: 1, heading: 2 } });
24486
+ engine.definePrepTasks([tokenize2]);
24487
+ for (const p of passages) {
24488
+ engine.addDoc(
24489
+ { text: p.text, heading: p.heading ?? "" },
24490
+ String(p.index)
24491
+ );
24492
+ }
24493
+ engine.consolidate();
24494
+ const hits = engine.search(prompt);
24495
+ if (hits.length === 0) return passages;
24496
+ const topK = Math.max(1, opts.topK ?? Math.min(20, passages.length));
24497
+ const ranked = [];
24498
+ const seen = /* @__PURE__ */ new Set();
24499
+ for (const [docId] of hits.slice(0, topK)) {
24500
+ const idx = Number(docId);
24501
+ const p = passages.find((x2) => x2.index === idx);
24502
+ if (p && !seen.has(idx)) {
24503
+ ranked.push(p);
24504
+ seen.add(idx);
24505
+ }
24506
+ }
24507
+ if (ranked.length === 0) return passages;
24508
+ return ranked;
24509
+ }
24510
+ var STOPWORDS;
24511
+ var init_bm25_rank = __esm({
24512
+ "src/tools/web/bm25-rank.ts"() {
24513
+ "use strict";
24514
+ STOPWORDS = /* @__PURE__ */ new Set([
24515
+ "a",
24516
+ "an",
24517
+ "the",
24518
+ "is",
24519
+ "are",
24520
+ "was",
24521
+ "were",
24522
+ "be",
24523
+ "been",
24524
+ "being",
24525
+ "of",
24526
+ "and",
24527
+ "or",
24528
+ "but",
24529
+ "in",
24530
+ "on",
24531
+ "at",
24532
+ "to",
24533
+ "for",
24534
+ "with",
24535
+ "by",
24536
+ "from",
24537
+ "up",
24538
+ "down",
24539
+ "out",
24540
+ "off",
24541
+ "over",
24542
+ "under",
24543
+ "as",
24544
+ "this",
24545
+ "that",
24546
+ "these",
24547
+ "those",
24548
+ "it",
24549
+ "its",
24550
+ "into",
24551
+ "if",
24552
+ "then",
24553
+ "do",
24554
+ "does",
24555
+ "did"
24556
+ ]);
24557
+ }
24558
+ });
24559
+
24560
+ // src/tools/web/diff-cache.ts
24561
+ import { createHash as createHash3 } from "crypto";
24562
+ import { join as join60 } from "path";
24563
+ function hashHtml(html) {
24564
+ return createHash3("sha256").update(html).digest("hex").slice(0, 32);
24565
+ }
24566
+ function lookupFetchCache(cwd, url) {
24567
+ try {
24568
+ const store = openMetricsStore(join60(cwd, ".unerr"));
24569
+ const row = store.getFetchCacheRow(url);
24570
+ if (!row) return { hit: false, prior: null };
24571
+ return { hit: true, prior: row };
24572
+ } catch {
24573
+ return { hit: false, prior: null };
24574
+ }
24575
+ }
24576
+ function storeFetchCache(cwd, row) {
24577
+ try {
24578
+ const store = openMetricsStore(join60(cwd, ".unerr"));
24579
+ store.upsertFetchCacheRow({ ...row, hit_count: 0 });
24580
+ } catch {
24581
+ }
24582
+ }
24583
+ function bumpCacheHit(cwd, url) {
24584
+ try {
24585
+ const store = openMetricsStore(join60(cwd, ".unerr"));
24586
+ store.bumpFetchCacheHitFor(url);
24587
+ } catch {
24588
+ }
24589
+ }
24590
+ function summarizeMarkdownDiff(oldMd, newMd) {
24591
+ if (oldMd === newMd) {
24592
+ return { unchanged: true, changedRegions: 0, addedLines: 0, removedLines: 0 };
24593
+ }
24594
+ const oldLines = new Set(oldMd.split("\n"));
24595
+ const newLines = newMd.split("\n");
24596
+ let added = 0;
24597
+ let regions = 0;
24598
+ let inRegion = false;
24599
+ for (const line of newLines) {
24600
+ if (!oldLines.has(line)) {
24601
+ added++;
24602
+ if (!inRegion) {
24603
+ regions++;
24604
+ inRegion = true;
24605
+ }
24606
+ } else {
24607
+ inRegion = false;
24608
+ }
24609
+ }
24610
+ const newLineSet = new Set(newLines);
24611
+ let removed = 0;
24612
+ for (const line of oldMd.split("\n")) {
24613
+ if (!newLineSet.has(line)) removed++;
24614
+ }
24615
+ return {
24616
+ unchanged: false,
24617
+ changedRegions: regions,
24618
+ addedLines: added,
24619
+ removedLines: removed
24620
+ };
24621
+ }
24622
+ var init_diff_cache = __esm({
24623
+ "src/tools/web/diff-cache.ts"() {
24624
+ "use strict";
24625
+ init_metrics_store();
24626
+ }
24627
+ });
24628
+
24629
+ // src/tools/web/extract.ts
24630
+ async function extractMainContent(html, baseUrl) {
24631
+ const { JSDOM } = await import("jsdom");
24632
+ const dom = new JSDOM(html, { url: baseUrl });
24633
+ const defuddled = await tryDefuddle(dom);
24634
+ if (defuddled && stripTags(defuddled.contentHtml).length >= MIN_USEFUL_CHARS) {
24635
+ return defuddled;
24636
+ }
24637
+ const readabilityResult = await tryReadability(dom);
24638
+ if (readabilityResult && stripTags(readabilityResult.contentHtml).length >= MIN_USEFUL_CHARS) {
24639
+ return readabilityResult;
24640
+ }
24641
+ return rawBodyFallback(dom);
24642
+ }
24643
+ async function tryDefuddle(dom) {
24644
+ try {
24645
+ const mod = await import("defuddle");
24646
+ const DefuddleCtor = mod.default;
24647
+ const doc = dom.window.document;
24648
+ const result = new DefuddleCtor(doc, { markdown: false }).parse();
24649
+ if (!result?.content) return null;
24650
+ return {
24651
+ title: result.title ?? "",
24652
+ contentHtml: result.content,
24653
+ wordCount: result.wordCount ?? wordCountOf(result.content),
24654
+ byline: result.author ?? void 0,
24655
+ excerpt: result.description ?? void 0,
24656
+ extractor: "defuddle"
24657
+ };
24658
+ } catch {
24659
+ return null;
24660
+ }
24661
+ }
24662
+ async function tryReadability(dom) {
24663
+ try {
24664
+ const mod = await import("@mozilla/readability");
24665
+ const doc = dom.window.document;
24666
+ if (mod.isProbablyReaderable && !mod.isProbablyReaderable(doc)) {
24667
+ return null;
24668
+ }
24669
+ const article = new mod.Readability(
24670
+ doc
24671
+ ).parse();
24672
+ if (!article?.content) return null;
24673
+ return {
24674
+ title: article.title ?? "",
24675
+ contentHtml: article.content,
24676
+ wordCount: wordCountOf(article.textContent ?? article.content),
24677
+ byline: article.byline ?? void 0,
24678
+ excerpt: article.excerpt ?? void 0,
24679
+ lang: article.lang ?? void 0,
24680
+ extractor: "readability"
24681
+ };
24682
+ } catch {
24683
+ return null;
24684
+ }
24685
+ }
24686
+ function rawBodyFallback(dom) {
24687
+ const doc = dom.window.document;
24688
+ for (const sel of ["script", "style", "noscript", "iframe", "svg"]) {
24689
+ for (const node of doc.querySelectorAll(sel)) node.remove();
24690
+ }
24691
+ const main = doc.querySelector("main") ?? doc.querySelector("article") ?? doc.body ?? doc.documentElement;
24692
+ const html = main?.innerHTML ?? "";
24693
+ return {
24694
+ title: doc.title ?? "",
24695
+ contentHtml: html,
24696
+ wordCount: wordCountOf(main?.textContent ?? ""),
24697
+ extractor: "raw-body"
24698
+ };
24699
+ }
24700
+ function stripTags(html) {
24701
+ return html.replace(/<[^>]+>/g, "").trim();
24702
+ }
24703
+ function wordCountOf(text2) {
24704
+ return text2.trim().split(/\s+/).filter(Boolean).length;
24705
+ }
24706
+ var MIN_USEFUL_CHARS;
24707
+ var init_extract = __esm({
24708
+ "src/tools/web/extract.ts"() {
24709
+ "use strict";
24710
+ MIN_USEFUL_CHARS = 200;
24711
+ }
24712
+ });
24713
+
24714
+ // src/tools/web/markdown.ts
24715
+ async function htmlToMarkdown(html) {
24716
+ if (!html.trim()) return "";
24717
+ const TurndownModule = await import("turndown");
24718
+ const Turndown = TurndownModule.default;
24719
+ const td = new Turndown({
24720
+ headingStyle: "atx",
24721
+ codeBlockStyle: "fenced",
24722
+ hr: "---",
24723
+ bulletListMarker: "-",
24724
+ emDelimiter: "_"
24725
+ });
24726
+ td.addRule("drop-anchor-only", {
24727
+ filter: (node) => {
24728
+ if (node.nodeName !== "A") return false;
24729
+ const href = node.getAttribute("href") ?? "";
24730
+ const text2 = (node.textContent ?? "").trim();
24731
+ return href.startsWith("#") || href === "" && text2 === "";
24732
+ },
24733
+ replacement: (content) => content
24734
+ });
24735
+ td.addRule("strip-tracking-params", {
24736
+ filter: (node) => node.nodeName === "A",
24737
+ replacement: (content, node) => {
24738
+ const rawHref = node.getAttribute("href") ?? "";
24739
+ const cleanHref = cleanUrl(rawHref);
24740
+ const title = node.getAttribute("title");
24741
+ if (!cleanHref) return content;
24742
+ const titleSuffix = title ? ` "${title}"` : "";
24743
+ return `[${content}](${cleanHref}${titleSuffix})`;
24744
+ }
24745
+ });
24746
+ return td.turndown(html);
24747
+ }
24748
+ function cleanUrl(href) {
24749
+ if (!href || href.startsWith("#") || href.startsWith("javascript:")) {
24750
+ return href;
24751
+ }
24752
+ try {
24753
+ const url = new URL(href, "https://example.invalid");
24754
+ const params = url.searchParams;
24755
+ const drop = [];
24756
+ for (const key of params.keys()) {
24757
+ if (TRACKING_PARAMS.test(key)) drop.push(key);
24758
+ }
24759
+ for (const key of drop) params.delete(key);
24760
+ return url.protocol === "https:" && url.hostname === "example.invalid" ? url.pathname + url.search + url.hash : url.toString();
24761
+ } catch {
24762
+ return href;
24763
+ }
24764
+ }
24765
+ var TRACKING_PARAMS;
24766
+ var init_markdown = __esm({
24767
+ "src/tools/web/markdown.ts"() {
24768
+ "use strict";
24769
+ TRACKING_PARAMS = /^(utm_|mc_|gclid$|fbclid$|yclid$|msclkid$|ref$|ref_)/i;
24770
+ }
24771
+ });
24772
+
24773
+ // src/tools/web/passage-split.ts
24774
+ function splitMarkdownIntoPassages(markdown) {
24775
+ const lines = markdown.split("\n");
24776
+ const passages = [];
24777
+ let currentHeading = null;
24778
+ let buffer = [];
24779
+ let bufferStart = 0;
24780
+ let inFence = false;
24781
+ let fenceMarker = "";
24782
+ const flush = () => {
24783
+ const text2 = buffer.join("\n").trim();
24784
+ if (text2) {
24785
+ passages.push({
24786
+ index: passages.length,
24787
+ heading: currentHeading,
24788
+ text: text2,
24789
+ startLine: bufferStart + 1
24790
+ });
24791
+ }
24792
+ buffer = [];
24793
+ };
24794
+ for (let i = 0; i < lines.length; i++) {
24795
+ const line = lines[i] ?? "";
24796
+ const fenceMatch = line.match(/^(```|~~~)/);
24797
+ if (fenceMatch) {
24798
+ if (!inFence) {
24799
+ inFence = true;
24800
+ fenceMarker = fenceMatch[1] ?? "```";
24801
+ if (buffer.length === 0) bufferStart = i;
24802
+ buffer.push(line);
24803
+ } else if (line.startsWith(fenceMarker)) {
24804
+ buffer.push(line);
24805
+ inFence = false;
24806
+ } else {
24807
+ buffer.push(line);
24808
+ }
24809
+ continue;
24810
+ }
24811
+ if (inFence) {
24812
+ buffer.push(line);
24813
+ continue;
24814
+ }
24815
+ const headingMatch = line.match(/^(#{1,6})\s+(.+?)\s*$/);
24816
+ if (headingMatch) {
24817
+ flush();
24818
+ currentHeading = headingMatch[2] ?? null;
24819
+ bufferStart = i;
24820
+ buffer.push(line);
24821
+ continue;
24822
+ }
24823
+ if (line.trim() === "") {
24824
+ if (buffer.length > 0) flush();
24825
+ continue;
24826
+ }
24827
+ if (buffer.length === 0) bufferStart = i;
24828
+ buffer.push(line);
24829
+ }
24830
+ flush();
24831
+ return passages;
24832
+ }
24833
+ var init_passage_split = __esm({
24834
+ "src/tools/web/passage-split.ts"() {
24835
+ "use strict";
24836
+ }
24837
+ });
24838
+
24839
+ // src/tools/web/post-process.ts
24840
+ import { readdirSync as readdirSync14, readFileSync as readFileSync51 } from "fs";
24841
+ import { join as join61 } from "path";
24842
+ import { fileURLToPath as fileURLToPath3 } from "url";
24843
+ function loadRules() {
24844
+ if (rulesCache) return rulesCache;
24845
+ const map = /* @__PURE__ */ new Map();
24846
+ if (!RULES_DIR) {
24847
+ rulesCache = map;
24848
+ return map;
24849
+ }
24850
+ let entries;
24851
+ try {
24852
+ entries = readdirSync14(RULES_DIR);
24853
+ } catch {
24854
+ rulesCache = map;
24855
+ return map;
24856
+ }
24857
+ for (const name of entries) {
24858
+ if (!name.endsWith(".json")) continue;
24859
+ try {
24860
+ const raw = readFileSync51(join61(RULES_DIR, name), "utf-8");
24861
+ const parsed = JSON.parse(raw);
24862
+ if (parsed.host) map.set(parsed.host.toLowerCase(), parsed);
24863
+ } catch {
24864
+ }
24865
+ }
24866
+ rulesCache = map;
24867
+ return map;
24868
+ }
24869
+ function postProcessMarkdown(markdown, opts = {}) {
24870
+ let out = universalCleanup(markdown);
24871
+ const hostRule = pickHostRule(opts);
24872
+ if (hostRule) out = applyHostRule(out, hostRule);
24873
+ out = universalCleanup(out);
24874
+ return out;
24875
+ }
24876
+ function universalCleanup(md) {
24877
+ return md.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").replace(/^\[\s*\]\([^)]*\)\s*$/gm, "").trim();
24878
+ }
24879
+ function pickHostRule(opts) {
24880
+ if (opts.rules?.length) {
24881
+ return mergeRules(opts.rules);
24882
+ }
24883
+ if (!opts.url) return null;
24884
+ try {
24885
+ const host = new URL(opts.url).hostname.toLowerCase();
24886
+ const rules = loadRules();
24887
+ const exact = rules.get(host);
24888
+ if (exact) return exact;
24889
+ const parts = host.split(".");
24890
+ for (let i = 1; i < parts.length - 1; i++) {
24891
+ const suffix = parts.slice(i).join(".");
24892
+ const match = rules.get(suffix);
24893
+ if (match) return match;
24894
+ }
24895
+ return null;
24896
+ } catch {
24897
+ return null;
24898
+ }
24899
+ }
24900
+ function mergeRules(rules) {
24901
+ return {
24902
+ host: "merged",
24903
+ drop: rules.flatMap((r) => r.drop ?? []),
24904
+ replace: rules.flatMap((r) => r.replace ?? [])
24905
+ };
24906
+ }
24907
+ function applyHostRule(md, rule) {
24908
+ let out = md;
24909
+ if (rule.drop?.length) {
24910
+ for (const pat of rule.drop) {
24911
+ try {
24912
+ const re = new RegExp(pat, "gm");
24913
+ out = out.replace(re, "");
24914
+ } catch {
24915
+ }
24916
+ }
24917
+ }
24918
+ if (rule.replace?.length) {
24919
+ for (const { pattern, with: replacement } of rule.replace) {
24920
+ try {
24921
+ const re = new RegExp(pattern, "gm");
24922
+ out = out.replace(re, replacement);
24923
+ } catch {
24924
+ }
24925
+ }
24926
+ }
24927
+ return out;
24928
+ }
24929
+ var RULES_DIR, rulesCache;
24930
+ var init_post_process = __esm({
24931
+ "src/tools/web/post-process.ts"() {
24932
+ "use strict";
24933
+ RULES_DIR = (() => {
24934
+ try {
24935
+ return join61(fileURLToPath3(new URL(".", import.meta.url)), "rules");
24936
+ } catch {
24937
+ return "";
24938
+ }
24939
+ })();
24940
+ rulesCache = null;
24941
+ }
24942
+ });
24943
+
24944
+ // src/tools/web/spa-render.ts
24945
+ function shouldUsePlaywright(args) {
24946
+ if (!args.config?.playwright?.enabled) return false;
24947
+ if (args.extractor !== "raw-body") return false;
24948
+ return args.wordCount < SPA_EMPTY_WC;
24949
+ }
24950
+ async function renderWithPlaywright(url, config) {
24951
+ const pw = await loadPlaywright();
24952
+ if (!pw) return null;
24953
+ const browser = await pw.chromium.launch({ headless: true });
24954
+ try {
24955
+ const ctx = await browser.newContext({
24956
+ userAgent: "unerr-fetch-url/1.0 (+https://unerr.dev) Mozilla/5.0 compatible"
24957
+ });
24958
+ const page = await ctx.newPage();
24959
+ const response = await page.goto(url, {
24960
+ waitUntil: config.playwright.waitUntil,
24961
+ timeout: config.playwright.timeoutMs
24962
+ });
24963
+ const html = await page.content();
24964
+ return {
24965
+ html,
24966
+ finalUrl: page.url(),
24967
+ status: response?.status() ?? 200
24968
+ };
24969
+ } finally {
24970
+ await browser.close();
24971
+ }
24972
+ }
24973
+ async function loadPlaywright() {
24974
+ try {
24975
+ return await import("playwright");
24976
+ } catch {
24977
+ return null;
24978
+ }
24979
+ }
24980
+ var SPA_EMPTY_WC;
24981
+ var init_spa_render = __esm({
24982
+ "src/tools/web/spa-render.ts"() {
24983
+ "use strict";
24984
+ SPA_EMPTY_WC = 40;
24985
+ }
24986
+ });
24987
+
24988
+ // src/tools/web/telemetry.ts
24989
+ function recordFetchUrlTelemetry(cwd, ev) {
24990
+ const savedPct = ev.rawBytes > 0 ? Math.round((ev.rawBytes - ev.compressedBytes) / ev.rawBytes * 100) : 0;
24991
+ appendCompressionLog(cwd, {
24992
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
24993
+ command: `fetch_url ${ev.url}`,
24994
+ category: "fetch_url",
24995
+ confidence: 1,
24996
+ rawBytes: ev.rawBytes,
24997
+ compressedBytes: ev.compressedBytes,
24998
+ savedPct,
24999
+ omniFallback: ev.extractor === "raw-body",
25000
+ teeFile: ev.cacheHit ? "cache-hit" : void 0
25001
+ });
25002
+ }
25003
+ var init_telemetry = __esm({
25004
+ "src/tools/web/telemetry.ts"() {
25005
+ "use strict";
25006
+ init_shell_compression_log();
25007
+ }
25008
+ });
25009
+
25010
+ // src/tools/web/fetch-url-protocol.ts
25011
+ var fetch_url_protocol_exports = {};
25012
+ __export(fetch_url_protocol_exports, {
25013
+ runFetchUrl: () => runFetchUrl
25014
+ });
25015
+ function shouldRank(args, markdown) {
25016
+ if (!args.prompt || args.prompt.trim().length === 0) return false;
25017
+ return Buffer.byteLength(markdown, "utf-8") > BM25_GATE_BYTES;
25018
+ }
25019
+ async function runFetchUrl(args, ctx) {
25020
+ if (!args.url || typeof args.url !== "string") {
25021
+ throw new Error("fetch_url requires a string `url` argument");
25022
+ }
25023
+ const url = upgradeToHttps(args.url);
25024
+ const started = Date.now();
25025
+ const fetched = await fetchHtml(url, ctx.abortSignal);
25026
+ const contentHash = hashHtml(fetched.html);
25027
+ const cache = lookupFetchCache(ctx.cwd, fetched.finalUrl);
25028
+ let markdown;
25029
+ let title;
25030
+ let extractor;
25031
+ let wordCount;
25032
+ let cacheHit = false;
25033
+ let diff;
25034
+ if (cache.hit && cache.prior && cache.prior.content_hash === contentHash) {
25035
+ markdown = cache.prior.markdown;
25036
+ title = cache.prior.title;
25037
+ extractor = "cache";
25038
+ wordCount = markdown.trim().split(/\s+/).filter(Boolean).length;
25039
+ cacheHit = true;
25040
+ diff = {
25041
+ unchanged: true,
25042
+ changed_regions: 0,
25043
+ added_lines: 0,
25044
+ removed_lines: 0
25045
+ };
25046
+ bumpCacheHit(ctx.cwd, fetched.finalUrl);
25047
+ } else {
25048
+ let extracted = await extractMainContent(fetched.html, fetched.finalUrl);
25049
+ const settings = safeLoadSettings(ctx.cwd);
25050
+ if (shouldUsePlaywright({
25051
+ config: settings?.fetchUrl,
25052
+ extractor: extracted.extractor,
25053
+ wordCount: extracted.wordCount
25054
+ }) && settings) {
25055
+ const rendered = await renderWithPlaywright(
25056
+ fetched.finalUrl,
25057
+ settings.fetchUrl
25058
+ );
25059
+ if (rendered) {
25060
+ fetched.html = rendered.html;
25061
+ fetched.finalUrl = rendered.finalUrl;
25062
+ fetched.status = rendered.status;
25063
+ extracted = await extractMainContent(rendered.html, rendered.finalUrl);
25064
+ }
25065
+ }
25066
+ const rawMarkdown = await htmlToMarkdown(extracted.contentHtml);
25067
+ markdown = postProcessMarkdown(rawMarkdown, { url: fetched.finalUrl });
25068
+ title = extracted.title;
25069
+ extractor = extracted.extractor;
25070
+ wordCount = extracted.wordCount;
25071
+ if (cache.prior) {
25072
+ const summary = summarizeMarkdownDiff(cache.prior.markdown, markdown);
25073
+ diff = {
25074
+ unchanged: summary.unchanged,
25075
+ changed_regions: summary.changedRegions,
25076
+ added_lines: summary.addedLines,
25077
+ removed_lines: summary.removedLines
25078
+ };
25079
+ }
25080
+ storeFetchCache(ctx.cwd, {
25081
+ url: fetched.finalUrl,
25082
+ content_hash: contentHash,
25083
+ markdown,
25084
+ title,
25085
+ extractor,
25086
+ raw_bytes: Buffer.byteLength(fetched.html, "utf-8"),
25087
+ compressed_bytes: Buffer.byteLength(markdown, "utf-8"),
25088
+ fetched_at: Date.now()
25089
+ });
25090
+ }
25091
+ const allPassages = splitMarkdownIntoPassages(markdown);
25092
+ const ranked = shouldRank(args, markdown) ? await rankPassagesByPrompt(allPassages, {
25093
+ prompt: args.prompt ?? "",
25094
+ topK: args.limit ?? BM25_DEFAULT_TOPK
25095
+ }) : allPassages;
25096
+ const offset = Math.max(0, args.offset ?? 0);
25097
+ const sliced = offset > 0 ? ranked.slice(offset) : ranked;
25098
+ const rawBytes = byteLength(fetched.html);
25099
+ const compressedBytes = byteLength(markdown);
25100
+ recordFetchUrlTelemetry(ctx.cwd, {
25101
+ url: fetched.finalUrl,
25102
+ rawBytes,
25103
+ compressedBytes,
25104
+ durationMs: Date.now() - started,
25105
+ extractor: extractor === "cache" ? "raw-body" : extractor,
25106
+ cacheHit
25107
+ });
25108
+ return {
25109
+ url: args.url,
25110
+ final_url: fetched.finalUrl,
25111
+ status: fetched.status,
25112
+ title,
25113
+ extractor,
25114
+ word_count: wordCount,
25115
+ cache_hit: cacheHit,
25116
+ diff,
25117
+ raw_bytes: rawBytes,
25118
+ extracted_bytes: compressedBytes,
25119
+ compression_ratio: rawBytes > 0 ? Math.round((1 - compressedBytes / rawBytes) * 100) / 100 : 0,
25120
+ passages: sliced.map((p) => ({
25121
+ index: p.index,
25122
+ heading: p.heading,
25123
+ text: p.text,
25124
+ start_line: p.startLine
25125
+ })),
25126
+ total: allPassages.length
25127
+ };
25128
+ }
25129
+ function upgradeToHttps(url) {
25130
+ if (!url.startsWith("http://")) return url;
25131
+ try {
25132
+ const host = new URL(url).hostname;
25133
+ if (host === "localhost" || host === "127.0.0.1" || host === "::1") {
25134
+ return url;
25135
+ }
25136
+ } catch {
25137
+ return url;
25138
+ }
25139
+ return "https://" + url.slice(7);
25140
+ }
25141
+ async function fetchHtml(url, abortSignal) {
25142
+ const controller = new AbortController();
25143
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
25144
+ const signal = abortSignal ? composeSignals(controller.signal, abortSignal) : controller.signal;
25145
+ try {
25146
+ const res = await fetch(url, {
25147
+ signal,
25148
+ redirect: "follow",
25149
+ headers: {
25150
+ "user-agent": "unerr-fetch-url/1.0 (+https://unerr.dev) Mozilla/5.0 compatible",
25151
+ accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
25152
+ }
25153
+ });
25154
+ const contentType = res.headers.get("content-type") ?? "";
25155
+ if (!contentType.includes("html") && !contentType.includes("xml")) {
25156
+ throw new Error(
25157
+ `fetch_url expected HTML response, got content-type: ${contentType || "unknown"}`
25158
+ );
25159
+ }
25160
+ const reader = res.body?.getReader();
25161
+ if (!reader) {
25162
+ const text2 = await res.text();
25163
+ return capHtml({
25164
+ html: text2,
25165
+ finalUrl: res.url,
25166
+ status: res.status,
25167
+ contentType
25168
+ });
25169
+ }
25170
+ const chunks = [];
25171
+ let total = 0;
25172
+ for (; ; ) {
25173
+ const { done, value } = await reader.read();
25174
+ if (done) break;
25175
+ if (value) {
25176
+ total += value.byteLength;
25177
+ if (total > MAX_HTML_BYTES) {
25178
+ await reader.cancel();
25179
+ break;
25180
+ }
25181
+ chunks.push(value);
25182
+ }
25183
+ }
25184
+ const merged = concatBytes(chunks);
25185
+ const html = new TextDecoder("utf-8", { fatal: false }).decode(merged);
25186
+ return capHtml({
25187
+ html,
25188
+ finalUrl: res.url,
25189
+ status: res.status,
25190
+ contentType
25191
+ });
25192
+ } finally {
25193
+ clearTimeout(timer);
25194
+ }
25195
+ }
25196
+ function capHtml(fetched) {
25197
+ if (fetched.html.length > MAX_HTML_BYTES) {
25198
+ return { ...fetched, html: fetched.html.slice(0, MAX_HTML_BYTES) };
25199
+ }
25200
+ return fetched;
25201
+ }
25202
+ function composeSignals(a, b) {
25203
+ const controller = new AbortController();
25204
+ const onAbort = () => controller.abort();
25205
+ if (a.aborted || b.aborted) controller.abort();
25206
+ else {
25207
+ a.addEventListener("abort", onAbort, { once: true });
25208
+ b.addEventListener("abort", onAbort, { once: true });
25209
+ }
25210
+ return controller.signal;
25211
+ }
25212
+ function byteLength(s) {
25213
+ return Buffer.byteLength(s, "utf-8");
25214
+ }
25215
+ function safeLoadSettings(cwd) {
25216
+ try {
25217
+ return loadSettings(cwd);
25218
+ } catch {
25219
+ return null;
25220
+ }
25221
+ }
25222
+ function concatBytes(chunks) {
25223
+ let total = 0;
25224
+ for (const c of chunks) total += c.byteLength;
25225
+ const out = new Uint8Array(total);
25226
+ let offset = 0;
25227
+ for (const c of chunks) {
25228
+ out.set(c, offset);
25229
+ offset += c.byteLength;
25230
+ }
25231
+ return out;
25232
+ }
25233
+ var FETCH_TIMEOUT_MS, MAX_HTML_BYTES, BM25_GATE_BYTES, BM25_DEFAULT_TOPK;
25234
+ var init_fetch_url_protocol = __esm({
25235
+ "src/tools/web/fetch-url-protocol.ts"() {
25236
+ "use strict";
25237
+ init_settings();
25238
+ init_bm25_rank();
25239
+ init_diff_cache();
25240
+ init_extract();
25241
+ init_markdown();
25242
+ init_passage_split();
25243
+ init_post_process();
25244
+ init_spa_render();
25245
+ init_telemetry();
25246
+ FETCH_TIMEOUT_MS = 15e3;
25247
+ MAX_HTML_BYTES = 5 * 1024 * 1024;
25248
+ BM25_GATE_BYTES = 8 * 1024;
25249
+ BM25_DEFAULT_TOPK = 20;
25250
+ }
25251
+ });
25252
+
24312
25253
  // src/intelligence/query-router.ts
24313
25254
  var query_router_exports = {};
24314
25255
  __export(query_router_exports, {
@@ -24564,7 +25505,9 @@ var init_query_router = __esm({
24564
25505
  "get_test_coverage",
24565
25506
  // Sprint FE-B: file read protocol
24566
25507
  "file_outline",
24567
- "file_read"
25508
+ "file_read",
25509
+ // Sprint FU-1: web fetch
25510
+ "fetch_url"
24568
25511
  ]);
24569
25512
  ENRICHABLE_TOOLS = /* @__PURE__ */ new Set([
24570
25513
  // Agent-facing tools
@@ -25362,7 +26305,8 @@ var init_query_router = __esm({
25362
26305
  const saved = estimate.tokensWithout - estimate.tokensUsed;
25363
26306
  if (saved > 0) {
25364
26307
  const isFileNav = toolName === "file_read" || toolName === "file_outline" || toolName === "get_file";
25365
- const mechanism = isFileNav ? "file_read" : "graph_query";
26308
+ const isFetchUrl = toolName === "fetch_url";
26309
+ const mechanism = isFetchUrl ? "fetch_url" : isFileNav ? "file_read" : "graph_query";
25366
26310
  enrichTokensSaved = saved;
25367
26311
  enrichSavingsMechanism = mechanism;
25368
26312
  const dollarSavings = calculateDollarSavings(saved);
@@ -26280,11 +27224,11 @@ var init_query_router = __esm({
26280
27224
  const entity = await this.resolveEntityWithOverlay(key);
26281
27225
  if (entity?.file_path && entity.start_line > 0) {
26282
27226
  try {
26283
- const { readFileSync: readFileSync70 } = await import("fs");
27227
+ const { readFileSync: readFileSync71 } = await import("fs");
26284
27228
  const { resolve: resolve6 } = await import("path");
26285
27229
  const cwd = this.projectRoot ?? process.cwd();
26286
27230
  const abs = resolve6(cwd, entity.file_path);
26287
- const lines = readFileSync70(abs, "utf-8").split("\n");
27231
+ const lines = readFileSync71(abs, "utf-8").split("\n");
26288
27232
  const start = entity.start_line - 1;
26289
27233
  const end = entity.end_line ?? lines.length;
26290
27234
  const bodyLines = lines.slice(start, end);
@@ -26522,6 +27466,12 @@ var init_query_router = __esm({
26522
27466
  graph: this.localGraph
26523
27467
  });
26524
27468
  }
27469
+ case "fetch_url": {
27470
+ const { runFetchUrl: runFetchUrl2 } = await Promise.resolve().then(() => (init_fetch_url_protocol(), fetch_url_protocol_exports));
27471
+ return runFetchUrl2(args, {
27472
+ cwd: this.projectRoot ?? process.cwd()
27473
+ });
27474
+ }
26525
27475
  default:
26526
27476
  throw new Error(`Unknown local tool: ${toolName}`);
26527
27477
  }
@@ -27881,8 +28831,8 @@ __export(causal_bridge_exports, {
27881
28831
  assembleCausalChain: () => assembleCausalChain,
27882
28832
  computeDurability: () => computeDurability
27883
28833
  });
27884
- import { existsSync as existsSync59, readFileSync as readFileSync51 } from "fs";
27885
- import { join as join60 } from "path";
28834
+ import { existsSync as existsSync59, readFileSync as readFileSync52 } from "fs";
28835
+ import { join as join62 } from "path";
27886
28836
  function computeAggregateDurability(interactions) {
27887
28837
  if (interactions.length === 0) return 1;
27888
28838
  const survivedCount = interactions.filter((i) => i.survived).length;
@@ -28039,9 +28989,9 @@ var init_causal_bridge = __esm({
28039
28989
  return chains;
28040
28990
  }
28041
28991
  loadEntityLedgerEntries(entityKey2) {
28042
- const ledgerPath = join60(this.unerrDir, "ledger", "shadow.jsonl");
28992
+ const ledgerPath = join62(this.unerrDir, "ledger", "shadow.jsonl");
28043
28993
  if (!existsSync59(ledgerPath)) return [];
28044
- const content = readFileSync51(ledgerPath, "utf-8");
28994
+ const content = readFileSync52(ledgerPath, "utf-8");
28045
28995
  const lines = content.split("\n").filter((l) => l.trim().length > 0);
28046
28996
  const entries = [];
28047
28997
  for (const line of lines) {
@@ -28527,11 +29477,11 @@ var context_ledger_exports = {};
28527
29477
  __export(context_ledger_exports, {
28528
29478
  createContextLedger: () => createContextLedger
28529
29479
  });
28530
- import { existsSync as existsSync60, mkdirSync as mkdirSync32, readFileSync as readFileSync52, writeFileSync as writeFileSync27 } from "fs";
28531
- import { join as join61 } from "path";
29480
+ import { existsSync as existsSync60, mkdirSync as mkdirSync32, readFileSync as readFileSync53, writeFileSync as writeFileSync27 } from "fs";
29481
+ import { join as join63 } from "path";
28532
29482
  function createContextLedger(unerrDir2) {
28533
- const stateDir = join61(unerrDir2, "state");
28534
- const filePath = join61(stateDir, "context-ledger.json");
29483
+ const stateDir = join63(unerrDir2, "state");
29484
+ const filePath = join63(stateDir, "context-ledger.json");
28535
29485
  let records = [];
28536
29486
  let index = /* @__PURE__ */ new Map();
28537
29487
  function ensureDir() {
@@ -28550,7 +29500,7 @@ function createContextLedger(unerrDir2) {
28550
29500
  return /* @__PURE__ */ new Map();
28551
29501
  }
28552
29502
  try {
28553
- const raw = readFileSync52(filePath, "utf-8");
29503
+ const raw = readFileSync53(filePath, "utf-8");
28554
29504
  const parsed = JSON.parse(raw);
28555
29505
  records = Array.isArray(parsed) ? parsed : [];
28556
29506
  } catch {
@@ -29477,11 +30427,11 @@ __export(intent_correlator_exports, {
29477
30427
  import {
29478
30428
  existsSync as existsSync61,
29479
30429
  mkdirSync as mkdirSync33,
29480
- readFileSync as readFileSync53,
30430
+ readFileSync as readFileSync54,
29481
30431
  renameSync as renameSync2,
29482
30432
  writeFileSync as writeFileSync28
29483
30433
  } from "fs";
29484
- import { join as join62 } from "path";
30434
+ import { join as join64 } from "path";
29485
30435
  function extractFiles4(args) {
29486
30436
  const files = args.files;
29487
30437
  if (files && Array.isArray(files)) {
@@ -29529,8 +30479,8 @@ var init_intent_correlator = __esm({
29529
30479
  pendingPath;
29530
30480
  pending = [];
29531
30481
  constructor(unerrDir2) {
29532
- this.ledgerDir = join62(unerrDir2, "ledger");
29533
- this.pendingPath = join62(this.ledgerDir, "pending_correlations.json");
30482
+ this.ledgerDir = join64(unerrDir2, "ledger");
30483
+ this.pendingPath = join64(this.ledgerDir, "pending_correlations.json");
29534
30484
  if (!existsSync61(this.ledgerDir)) {
29535
30485
  mkdirSync33(this.ledgerDir, { recursive: true });
29536
30486
  }
@@ -29625,7 +30575,7 @@ var init_intent_correlator = __esm({
29625
30575
  load() {
29626
30576
  if (!existsSync61(this.pendingPath)) return;
29627
30577
  try {
29628
- const raw = readFileSync53(this.pendingPath, "utf-8");
30578
+ const raw = readFileSync54(this.pendingPath, "utf-8");
29629
30579
  const parsed = JSON.parse(raw);
29630
30580
  if (Array.isArray(parsed)) {
29631
30581
  this.pending = parsed;
@@ -30190,7 +31140,7 @@ var init_session_state = __esm({
30190
31140
 
30191
31141
  // src/proxy/tool-exposure-store.ts
30192
31142
  import { promises as fs6 } from "fs";
30193
- import { dirname as dirname16, join as join63 } from "path";
31143
+ import { dirname as dirname16, join as join65 } from "path";
30194
31144
  var ToolExposureStore;
30195
31145
  var init_tool_exposure_store = __esm({
30196
31146
  "src/proxy/tool-exposure-store.ts"() {
@@ -30200,7 +31150,7 @@ var init_tool_exposure_store = __esm({
30200
31150
  sessionId;
30201
31151
  ensuredDir = false;
30202
31152
  constructor(unerrDir2, sessionId) {
30203
- this.filePath = join63(unerrDir2, "router", "exposure-events.jsonl");
31153
+ this.filePath = join65(unerrDir2, "router", "exposure-events.jsonl");
30204
31154
  this.sessionId = sessionId;
30205
31155
  }
30206
31156
  /**
@@ -30462,11 +31412,11 @@ var init_router_gateway = __esm({
30462
31412
 
30463
31413
  // src/timeline/timeline-store.ts
30464
31414
  import { existsSync as existsSync62, mkdirSync as mkdirSync34 } from "fs";
30465
- import { join as join64 } from "path";
31415
+ import { join as join66 } from "path";
30466
31416
  async function openTimelineDb(projectRoot) {
30467
- const unerrDir2 = join64(projectRoot, ".unerr");
31417
+ const unerrDir2 = join66(projectRoot, ".unerr");
30468
31418
  mkdirSync34(unerrDir2, { recursive: true });
30469
- const dbPath = join64(unerrDir2, TIMELINE_DB_FILENAME);
31419
+ const dbPath = join66(unerrDir2, TIMELINE_DB_FILENAME);
30470
31420
  const isNew = !existsSync62(dbPath);
30471
31421
  const cozoModule = await import("cozo-node");
30472
31422
  const CozoDbConstructor = cozoModule.default ? cozoModule.default.CozoDb : cozoModule.CozoDb;
@@ -31510,23 +32460,23 @@ import {
31510
32460
  appendFileSync as appendFileSync8,
31511
32461
  existsSync as existsSync63,
31512
32462
  mkdirSync as mkdirSync35,
31513
- readFileSync as readFileSync54,
32463
+ readFileSync as readFileSync55,
31514
32464
  renameSync as renameSync3,
31515
32465
  writeFileSync as writeFileSync29
31516
32466
  } from "fs";
31517
- import { join as join65 } from "path";
32467
+ import { join as join67 } from "path";
31518
32468
  import { gzipSync } from "zlib";
31519
32469
  function archiveShadowLedger(unerrDir2, opts = {}) {
31520
- const ledgerDir = join65(unerrDir2, "ledger");
31521
- const filePath = join65(ledgerDir, "shadow.jsonl");
31522
- const archiveDir = join65(ledgerDir, "archive");
32470
+ const ledgerDir = join67(unerrDir2, "ledger");
32471
+ const filePath = join67(ledgerDir, "shadow.jsonl");
32472
+ const archiveDir = join67(ledgerDir, "archive");
31523
32473
  if (!existsSync63(filePath)) {
31524
32474
  return { archived: 0, kept: 0, archivePath: null };
31525
32475
  }
31526
32476
  const retainMs = opts.retainMs ?? DEFAULT_RETAIN_MS;
31527
32477
  const now = opts.nowMs ?? Date.now();
31528
32478
  const cutoff = now - retainMs;
31529
- const raw = readFileSync54(filePath, "utf-8");
32479
+ const raw = readFileSync55(filePath, "utf-8");
31530
32480
  const initialBytes = Buffer.byteLength(raw);
31531
32481
  const lines = raw.split("\n").filter((l) => l.trim().length > 0);
31532
32482
  const keep = [];
@@ -31551,7 +32501,7 @@ function archiveShadowLedger(unerrDir2, opts = {}) {
31551
32501
  }
31552
32502
  mkdirSync35(archiveDir, { recursive: true });
31553
32503
  const dateStamp = new Date(now).toISOString().slice(0, 10);
31554
- const archivePath = join65(archiveDir, `${dateStamp}.jsonl.gz`);
32504
+ const archivePath = join67(archiveDir, `${dateStamp}.jsonl.gz`);
31555
32505
  const payload = `${archive.join("\n")}
31556
32506
  `;
31557
32507
  const gz = gzipSync(Buffer.from(payload, "utf-8"));
@@ -31563,7 +32513,7 @@ function archiveShadowLedger(unerrDir2, opts = {}) {
31563
32513
  const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
31564
32514
  const kept = `${keep.join("\n")}${keep.length > 0 ? "\n" : ""}`;
31565
32515
  writeFileSync29(tmpPath, kept, "utf-8");
31566
- const afterRaw = readFileSync54(filePath, "utf-8");
32516
+ const afterRaw = readFileSync55(filePath, "utf-8");
31567
32517
  const afterBytes = Buffer.byteLength(afterRaw);
31568
32518
  if (afterBytes > initialBytes) {
31569
32519
  const tail = afterRaw.slice(raw.length);
@@ -32072,12 +33022,12 @@ import { randomBytes as randomBytes5 } from "crypto";
32072
33022
  import {
32073
33023
  existsSync as existsSync64,
32074
33024
  mkdirSync as mkdirSync36,
32075
- readFileSync as readFileSync55,
32076
- readdirSync as readdirSync14,
33025
+ readFileSync as readFileSync56,
33026
+ readdirSync as readdirSync15,
32077
33027
  rmSync as rmSync2,
32078
33028
  writeFileSync as writeFileSync30
32079
33029
  } from "fs";
32080
- import { join as join66 } from "path";
33030
+ import { join as join68 } from "path";
32081
33031
  var _log2, MAX_SNAPSHOTS, AUTO_SNAPSHOT_COOLDOWN_MS, WorkingSnapshotStore;
32082
33032
  var init_working_snapshots = __esm({
32083
33033
  "src/tracking/working-snapshots.ts"() {
@@ -32095,7 +33045,7 @@ var init_working_snapshots = __esm({
32095
33045
  unerrDir;
32096
33046
  constructor(unerrDir2) {
32097
33047
  this.unerrDir = unerrDir2;
32098
- this.snapshotDir = join66(unerrDir2, "snapshots");
33048
+ this.snapshotDir = join68(unerrDir2, "snapshots");
32099
33049
  if (!existsSync64(this.snapshotDir)) {
32100
33050
  mkdirSync36(this.snapshotDir, { recursive: true });
32101
33051
  }
@@ -32116,7 +33066,7 @@ var init_working_snapshots = __esm({
32116
33066
  processed: false
32117
33067
  };
32118
33068
  writeFileSync30(
32119
- join66(this.snapshotDir, `${id}.json`),
33069
+ join68(this.snapshotDir, `${id}.json`),
32120
33070
  JSON.stringify(snapshot, null, 2),
32121
33071
  "utf-8"
32122
33072
  );
@@ -32130,10 +33080,10 @@ var init_working_snapshots = __esm({
32130
33080
  * Get a snapshot by ID.
32131
33081
  */
32132
33082
  get(snapshotId) {
32133
- const filePath = join66(this.snapshotDir, `${snapshotId}.json`);
33083
+ const filePath = join68(this.snapshotDir, `${snapshotId}.json`);
32134
33084
  if (!existsSync64(filePath)) return null;
32135
33085
  try {
32136
- return JSON.parse(readFileSync55(filePath, "utf-8"));
33086
+ return JSON.parse(readFileSync56(filePath, "utf-8"));
32137
33087
  } catch {
32138
33088
  return null;
32139
33089
  }
@@ -32143,13 +33093,13 @@ var init_working_snapshots = __esm({
32143
33093
  */
32144
33094
  list() {
32145
33095
  if (!existsSync64(this.snapshotDir)) return [];
32146
- const files = readdirSync14(this.snapshotDir).filter(
33096
+ const files = readdirSync15(this.snapshotDir).filter(
32147
33097
  (f) => f.endsWith(".json")
32148
33098
  );
32149
33099
  const snapshots = [];
32150
33100
  for (const file of files) {
32151
33101
  try {
32152
- const raw = readFileSync55(join66(this.snapshotDir, file), "utf-8");
33102
+ const raw = readFileSync56(join68(this.snapshotDir, file), "utf-8");
32153
33103
  snapshots.push(JSON.parse(raw));
32154
33104
  } catch {
32155
33105
  }
@@ -32190,7 +33140,7 @@ var init_working_snapshots = __esm({
32190
33140
  if (!snapshot) return;
32191
33141
  snapshot.processed = true;
32192
33142
  writeFileSync30(
32193
- join66(this.snapshotDir, `${snapshotId}.json`),
33143
+ join68(this.snapshotDir, `${snapshotId}.json`),
32194
33144
  JSON.stringify(snapshot, null, 2),
32195
33145
  "utf-8"
32196
33146
  );
@@ -32205,7 +33155,7 @@ var init_working_snapshots = __esm({
32205
33155
  * Delete a snapshot.
32206
33156
  */
32207
33157
  delete(snapshotId) {
32208
- const filePath = join66(this.snapshotDir, `${snapshotId}.json`);
33158
+ const filePath = join68(this.snapshotDir, `${snapshotId}.json`);
32209
33159
  if (!existsSync64(filePath)) return false;
32210
33160
  rmSync2(filePath);
32211
33161
  return true;
@@ -32214,10 +33164,10 @@ var init_working_snapshots = __esm({
32214
33164
  * Get the timeline branch counter from branch_context.json.
32215
33165
  */
32216
33166
  getTimelineBranch() {
32217
- const contextPath = join66(this.unerrDir, "branch_context.json");
33167
+ const contextPath = join68(this.unerrDir, "branch_context.json");
32218
33168
  if (!existsSync64(contextPath)) return 0;
32219
33169
  try {
32220
- const ctx = JSON.parse(readFileSync55(contextPath, "utf-8"));
33170
+ const ctx = JSON.parse(readFileSync56(contextPath, "utf-8"));
32221
33171
  return ctx.timelineBranch ?? 0;
32222
33172
  } catch {
32223
33173
  return 0;
@@ -32227,11 +33177,11 @@ var init_working_snapshots = __esm({
32227
33177
  * Increment the timeline branch counter. Returns the new value.
32228
33178
  */
32229
33179
  incrementTimelineBranch() {
32230
- const contextPath = join66(this.unerrDir, "branch_context.json");
33180
+ const contextPath = join68(this.unerrDir, "branch_context.json");
32231
33181
  let ctx = {};
32232
33182
  if (existsSync64(contextPath)) {
32233
33183
  try {
32234
- ctx = JSON.parse(readFileSync55(contextPath, "utf-8"));
33184
+ ctx = JSON.parse(readFileSync56(contextPath, "utf-8"));
32235
33185
  } catch {
32236
33186
  ctx = {};
32237
33187
  }
@@ -32404,8 +33354,8 @@ var quality_signals_exports = {};
32404
33354
  __export(quality_signals_exports, {
32405
33355
  QualitySignalTracker: () => QualitySignalTracker
32406
33356
  });
32407
- import { existsSync as existsSync65, readFileSync as readFileSync56, writeFileSync as writeFileSync31 } from "fs";
32408
- import { join as join67 } from "path";
33357
+ import { existsSync as existsSync65, readFileSync as readFileSync57, writeFileSync as writeFileSync31 } from "fs";
33358
+ import { join as join69 } from "path";
32409
33359
  function computeDurabilityFromAge(survivalMs) {
32410
33360
  if (survivalMs < FRAGILE_THRESHOLD_MS) {
32411
33361
  return 0.1 + survivalMs / FRAGILE_THRESHOLD_MS * 0.2;
@@ -32432,7 +33382,7 @@ var init_quality_signals = __esm({
32432
33382
  /** Maximum corrections to retain in memory/disk. */
32433
33383
  static MAX_CORRECTIONS = 200;
32434
33384
  constructor(unerrDir2) {
32435
- this.signalsPath = join67(unerrDir2, "state", "quality_signals.json");
33385
+ this.signalsPath = join69(unerrDir2, "state", "quality_signals.json");
32436
33386
  this.signals = this.load();
32437
33387
  }
32438
33388
  /**
@@ -32524,7 +33474,7 @@ var init_quality_signals = __esm({
32524
33474
  */
32525
33475
  save() {
32526
33476
  try {
32527
- const dir = join67(this.signalsPath, "..");
33477
+ const dir = join69(this.signalsPath, "..");
32528
33478
  if (!existsSync65(dir)) {
32529
33479
  const { mkdirSync: mkdirSync48 } = __require("fs");
32530
33480
  mkdirSync48(dir, { recursive: true });
@@ -32580,7 +33530,7 @@ var init_quality_signals = __esm({
32580
33530
  }
32581
33531
  try {
32582
33532
  return JSON.parse(
32583
- readFileSync56(this.signalsPath, "utf-8")
33533
+ readFileSync57(this.signalsPath, "utf-8")
32584
33534
  );
32585
33535
  } catch {
32586
33536
  return {
@@ -33599,8 +34549,8 @@ var incomplete_work_exports = {};
33599
34549
  __export(incomplete_work_exports, {
33600
34550
  IncompleteWorkDetector: () => IncompleteWorkDetector
33601
34551
  });
33602
- import { existsSync as existsSync66, mkdirSync as mkdirSync37, readFileSync as readFileSync57, writeFileSync as writeFileSync32 } from "fs";
33603
- import { join as join68 } from "path";
34552
+ import { existsSync as existsSync66, mkdirSync as mkdirSync37, readFileSync as readFileSync58, writeFileSync as writeFileSync32 } from "fs";
34553
+ import { join as join70 } from "path";
33604
34554
  function severityRank(severity) {
33605
34555
  switch (severity) {
33606
34556
  case "high":
@@ -33793,9 +34743,9 @@ var init_incomplete_work = __esm({
33793
34743
  persistItems(items) {
33794
34744
  if (!this.unerrDir) return false;
33795
34745
  try {
33796
- const stateDir = join68(this.unerrDir, "state");
34746
+ const stateDir = join70(this.unerrDir, "state");
33797
34747
  if (!existsSync66(stateDir)) mkdirSync37(stateDir, { recursive: true });
33798
- const filePath = join68(stateDir, PERSISTENCE_FILE);
34748
+ const filePath = join70(stateDir, PERSISTENCE_FILE);
33799
34749
  writeFileSync32(
33800
34750
  filePath,
33801
34751
  JSON.stringify({
@@ -33815,9 +34765,9 @@ var init_incomplete_work = __esm({
33815
34765
  */
33816
34766
  static readPersistedItems(unerrDir2) {
33817
34767
  try {
33818
- const filePath = join68(unerrDir2, "state", PERSISTENCE_FILE);
34768
+ const filePath = join70(unerrDir2, "state", PERSISTENCE_FILE);
33819
34769
  if (!existsSync66(filePath)) return [];
33820
- const data = JSON.parse(readFileSync57(filePath, "utf-8"));
34770
+ const data = JSON.parse(readFileSync58(filePath, "utf-8"));
33821
34771
  return data.items ?? [];
33822
34772
  } catch {
33823
34773
  return [];
@@ -35597,8 +36547,8 @@ __export(git_trailers_exports, {
35597
36547
  parseTrailersFromMessage: () => parseTrailersFromMessage,
35598
36548
  uninstallPrepareCommitMsgHook: () => uninstallPrepareCommitMsgHook
35599
36549
  });
35600
- import { existsSync as existsSync68, readFileSync as readFileSync58, writeFileSync as writeFileSync33 } from "fs";
35601
- import { join as join69 } from "path";
36550
+ import { existsSync as existsSync68, readFileSync as readFileSync59, writeFileSync as writeFileSync33 } from "fs";
36551
+ import { join as join71 } from "path";
35602
36552
  function getCommitTrailers(ledger, timelineBranch, branch) {
35603
36553
  const recent = ledger.getRecentEntries(1);
35604
36554
  if (recent.length === 0) return null;
@@ -35618,15 +36568,15 @@ function formatTrailers(trailers) {
35618
36568
  ].join("\n");
35619
36569
  }
35620
36570
  function installPrepareCommitMsgHook(projectRoot) {
35621
- const hooksDir = join69(projectRoot, ".git", "hooks");
36571
+ const hooksDir = join71(projectRoot, ".git", "hooks");
35622
36572
  if (!existsSync68(hooksDir)) {
35623
36573
  return false;
35624
36574
  }
35625
- const hookPath = join69(hooksDir, "prepare-commit-msg");
36575
+ const hookPath = join71(hooksDir, "prepare-commit-msg");
35626
36576
  const marker = "# unerr-trailer-injection";
35627
36577
  if (existsSync68(hookPath)) {
35628
36578
  try {
35629
- const existing = readFileSync58(hookPath, "utf-8");
36579
+ const existing = readFileSync59(hookPath, "utf-8");
35630
36580
  if (existing.includes(marker)) {
35631
36581
  return true;
35632
36582
  }
@@ -35655,10 +36605,10 @@ ${generateHookScript()}`;
35655
36605
  }
35656
36606
  }
35657
36607
  function uninstallPrepareCommitMsgHook(projectRoot) {
35658
- const hookPath = join69(projectRoot, ".git", "hooks", "prepare-commit-msg");
36608
+ const hookPath = join71(projectRoot, ".git", "hooks", "prepare-commit-msg");
35659
36609
  if (!existsSync68(hookPath)) return true;
35660
36610
  try {
35661
- const content = readFileSync58(hookPath, "utf-8");
36611
+ const content = readFileSync59(hookPath, "utf-8");
35662
36612
  const marker = "# unerr-trailer-injection";
35663
36613
  if (!content.includes(marker)) return true;
35664
36614
  const lines = content.split("\n");
@@ -35825,13 +36775,13 @@ var init_http_transport = __esm({
35825
36775
  import {
35826
36776
  existsSync as existsSync69,
35827
36777
  mkdirSync as mkdirSync39,
35828
- readFileSync as readFileSync59,
35829
- readdirSync as readdirSync15,
36778
+ readFileSync as readFileSync60,
36779
+ readdirSync as readdirSync16,
35830
36780
  rmSync as rmSync3,
35831
36781
  statSync as statSync11,
35832
36782
  writeFileSync as writeFileSync34
35833
36783
  } from "fs";
35834
- import { join as join70 } from "path";
36784
+ import { join as join72 } from "path";
35835
36785
  function sanitizeBranchName(branch) {
35836
36786
  return branch.replace(/\//g, "__").replace(/[^a-zA-Z0-9_.\-]/g, "_");
35837
36787
  }
@@ -35849,7 +36799,7 @@ var init_branch_snapshot = __esm({
35849
36799
  branchDir;
35850
36800
  projectRoot;
35851
36801
  constructor(unerrDir2, projectRoot) {
35852
- this.branchDir = join70(unerrDir2, "drift", "branches");
36802
+ this.branchDir = join72(unerrDir2, "drift", "branches");
35853
36803
  this.projectRoot = projectRoot;
35854
36804
  }
35855
36805
  /**
@@ -35863,7 +36813,7 @@ var init_branch_snapshot = __esm({
35863
36813
  log17.info(`No drift entities/edges to snapshot for branch ${branch}`);
35864
36814
  }
35865
36815
  const dirName = sanitizeBranchName(branch);
35866
- const snapshotDir = join70(this.branchDir, dirName);
36816
+ const snapshotDir = join72(this.branchDir, dirName);
35867
36817
  if (!existsSync69(snapshotDir)) {
35868
36818
  mkdirSync39(snapshotDir, { recursive: true });
35869
36819
  }
@@ -35875,12 +36825,12 @@ var init_branch_snapshot = __esm({
35875
36825
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
35876
36826
  };
35877
36827
  writeFileSync34(
35878
- join70(snapshotDir, OVERLAY_FILE),
36828
+ join72(snapshotDir, OVERLAY_FILE),
35879
36829
  JSON.stringify(snapshot, null, 2),
35880
36830
  "utf-8"
35881
36831
  );
35882
36832
  writeFileSync34(
35883
- join70(snapshotDir, HASHES_FILE),
36833
+ join72(snapshotDir, HASHES_FILE),
35884
36834
  JSON.stringify(fileHashState, null, 2),
35885
36835
  "utf-8"
35886
36836
  );
@@ -35896,14 +36846,14 @@ var init_branch_snapshot = __esm({
35896
36846
  */
35897
36847
  async restoreSnapshot(branch, localGraph) {
35898
36848
  const dirName = sanitizeBranchName(branch);
35899
- const snapshotDir = join70(this.branchDir, dirName);
35900
- const overlayPath = join70(snapshotDir, OVERLAY_FILE);
36849
+ const snapshotDir = join72(this.branchDir, dirName);
36850
+ const overlayPath = join72(snapshotDir, OVERLAY_FILE);
35901
36851
  if (!existsSync69(overlayPath)) {
35902
36852
  log17.info(`No snapshot for branch ${branch} \u2014 first visit`);
35903
36853
  return null;
35904
36854
  }
35905
36855
  try {
35906
- const raw = readFileSync59(overlayPath, "utf-8");
36856
+ const raw = readFileSync60(overlayPath, "utf-8");
35907
36857
  const snapshot = JSON.parse(raw);
35908
36858
  for (const entity of snapshot.entities) {
35909
36859
  await localGraph.upsertDriftEntity(entity);
@@ -35915,7 +36865,7 @@ var init_branch_snapshot = __esm({
35915
36865
  }
35916
36866
  const now = /* @__PURE__ */ new Date();
35917
36867
  writeFileSync34(
35918
- join70(snapshotDir, ".last_access"),
36868
+ join72(snapshotDir, ".last_access"),
35919
36869
  now.toISOString(),
35920
36870
  "utf-8"
35921
36871
  );
@@ -35935,17 +36885,17 @@ var init_branch_snapshot = __esm({
35935
36885
  */
35936
36886
  hasSnapshot(branch) {
35937
36887
  const dirName = sanitizeBranchName(branch);
35938
- return existsSync69(join70(this.branchDir, dirName, OVERLAY_FILE));
36888
+ return existsSync69(join72(this.branchDir, dirName, OVERLAY_FILE));
35939
36889
  }
35940
36890
  /**
35941
36891
  * Get the file hash state from a branch snapshot.
35942
36892
  */
35943
36893
  getSnapshotFileHashes(branch) {
35944
36894
  const dirName = sanitizeBranchName(branch);
35945
- const hashesPath = join70(this.branchDir, dirName, HASHES_FILE);
36895
+ const hashesPath = join72(this.branchDir, dirName, HASHES_FILE);
35946
36896
  if (!existsSync69(hashesPath)) return null;
35947
36897
  try {
35948
- const raw = readFileSync59(hashesPath, "utf-8");
36898
+ const raw = readFileSync60(hashesPath, "utf-8");
35949
36899
  return JSON.parse(raw);
35950
36900
  } catch {
35951
36901
  return null;
@@ -35956,7 +36906,7 @@ var init_branch_snapshot = __esm({
35956
36906
  */
35957
36907
  deleteSnapshot(branch) {
35958
36908
  const dirName = sanitizeBranchName(branch);
35959
- const snapshotDir = join70(this.branchDir, dirName);
36909
+ const snapshotDir = join72(this.branchDir, dirName);
35960
36910
  if (!existsSync69(snapshotDir)) return false;
35961
36911
  rmSync3(snapshotDir, { recursive: true, force: true });
35962
36912
  log17.info(`Deleted branch snapshot: ${branch}`);
@@ -35973,7 +36923,7 @@ var init_branch_snapshot = __esm({
35973
36923
  let removed = 0;
35974
36924
  for (const snapshot of snapshots) {
35975
36925
  if (!gitBranches.has(snapshot.branch)) {
35976
- const snapshotDir = join70(this.branchDir, snapshot.id);
36926
+ const snapshotDir = join72(this.branchDir, snapshot.id);
35977
36927
  rmSync3(snapshotDir, { recursive: true, force: true });
35978
36928
  log17.info(`GC removed snapshot for deleted branch: ${snapshot.branch}`);
35979
36929
  removed++;
@@ -35987,16 +36937,16 @@ var init_branch_snapshot = __esm({
35987
36937
  listSnapshots() {
35988
36938
  if (!existsSync69(this.branchDir)) return [];
35989
36939
  try {
35990
- const entries = readdirSync15(this.branchDir, { withFileTypes: true });
36940
+ const entries = readdirSync16(this.branchDir, { withFileTypes: true });
35991
36941
  const snapshots = [];
35992
36942
  for (const entry of entries) {
35993
36943
  if (!entry.isDirectory()) continue;
35994
- const overlayPath = join70(this.branchDir, entry.name, OVERLAY_FILE);
36944
+ const overlayPath = join72(this.branchDir, entry.name, OVERLAY_FILE);
35995
36945
  if (!existsSync69(overlayPath)) continue;
35996
36946
  try {
35997
- const raw = readFileSync59(overlayPath, "utf-8");
36947
+ const raw = readFileSync60(overlayPath, "utf-8");
35998
36948
  const snapshot = JSON.parse(raw);
35999
- const accessPath = join70(this.branchDir, entry.name, ".last_access");
36949
+ const accessPath = join72(this.branchDir, entry.name, ".last_access");
36000
36950
  let accessedAt;
36001
36951
  if (existsSync69(accessPath)) {
36002
36952
  accessedAt = statSync11(accessPath).mtime;
@@ -36025,7 +36975,7 @@ var init_branch_snapshot = __esm({
36025
36975
  if (snapshots.length <= MAX_BRANCH_SNAPSHOTS) return;
36026
36976
  const toRemove = snapshots.slice(MAX_BRANCH_SNAPSHOTS);
36027
36977
  for (const snapshot of toRemove) {
36028
- const dir = join70(this.branchDir, snapshot.id);
36978
+ const dir = join72(this.branchDir, snapshot.id);
36029
36979
  rmSync3(dir, { recursive: true, force: true });
36030
36980
  log17.info(`LRU evicted branch snapshot: ${snapshot.branch}`);
36031
36981
  }
@@ -36040,17 +36990,17 @@ __export(file_hash_state_exports, {
36040
36990
  FileHashManager: () => FileHashManager,
36041
36991
  contentSha256: () => contentSha256
36042
36992
  });
36043
- import { createHash as createHash3 } from "crypto";
36993
+ import { createHash as createHash4 } from "crypto";
36044
36994
  import {
36045
36995
  existsSync as existsSync70,
36046
36996
  mkdirSync as mkdirSync40,
36047
- readFileSync as readFileSync60,
36997
+ readFileSync as readFileSync61,
36048
36998
  renameSync as renameSync4,
36049
36999
  writeFileSync as writeFileSync35
36050
37000
  } from "fs";
36051
- import { join as join71 } from "path";
37001
+ import { join as join73 } from "path";
36052
37002
  function contentSha256(content) {
36053
- return createHash3("sha256").update(content).digest("hex");
37003
+ return createHash4("sha256").update(content).digest("hex");
36054
37004
  }
36055
37005
  var STATE_FILE2, FileHashManager;
36056
37006
  var init_file_hash_state = __esm({
@@ -36062,8 +37012,8 @@ var init_file_hash_state = __esm({
36062
37012
  statePath;
36063
37013
  state;
36064
37014
  constructor(unerrDir2) {
36065
- this.stateDir = join71(unerrDir2, "state");
36066
- this.statePath = join71(this.stateDir, STATE_FILE2);
37015
+ this.stateDir = join73(unerrDir2, "state");
37016
+ this.statePath = join73(this.stateDir, STATE_FILE2);
36067
37017
  this.state = this.load();
36068
37018
  }
36069
37019
  /**
@@ -36139,7 +37089,7 @@ var init_file_hash_state = __esm({
36139
37089
  return { files: {} };
36140
37090
  }
36141
37091
  try {
36142
- const raw = readFileSync60(this.statePath, "utf-8");
37092
+ const raw = readFileSync61(this.statePath, "utf-8");
36143
37093
  return JSON.parse(raw);
36144
37094
  } catch {
36145
37095
  return { files: {} };
@@ -36153,13 +37103,13 @@ var init_file_hash_state = __esm({
36153
37103
  import {
36154
37104
  existsSync as existsSync71,
36155
37105
  mkdirSync as mkdirSync41,
36156
- readFileSync as readFileSync61,
36157
- readdirSync as readdirSync16,
37106
+ readFileSync as readFileSync62,
37107
+ readdirSync as readdirSync17,
36158
37108
  rmSync as rmSync4,
36159
37109
  statSync as statSync12,
36160
37110
  writeFileSync as writeFileSync36
36161
37111
  } from "fs";
36162
- import { join as join72 } from "path";
37112
+ import { join as join74 } from "path";
36163
37113
  var MAX_STASH_SNAPSHOTS, OVERLAY_FILE2, HASHES_FILE2, _log6, StashManager;
36164
37114
  var init_stash_manager = __esm({
36165
37115
  "src/tracking/stash-manager.ts"() {
@@ -36177,8 +37127,8 @@ var init_stash_manager = __esm({
36177
37127
  constructor(unerrDir2, projectRoot) {
36178
37128
  this.unerrDir = unerrDir2;
36179
37129
  this.projectRoot = projectRoot;
36180
- this.stashDir = join72(unerrDir2, "drift", "stash");
36181
- this.gitDir = join72(projectRoot, ".git");
37130
+ this.stashDir = join74(unerrDir2, "drift", "stash");
37131
+ this.gitDir = join74(projectRoot, ".git");
36182
37132
  this.previousStashRef = this.readStashRef();
36183
37133
  this.previousStashCount = this.getStashCount();
36184
37134
  }
@@ -36223,7 +37173,7 @@ var init_stash_manager = __esm({
36223
37173
  return null;
36224
37174
  }
36225
37175
  const snapshotId = stashRef.slice(0, 12);
36226
- const snapshotDir = join72(this.stashDir, snapshotId);
37176
+ const snapshotDir = join74(this.stashDir, snapshotId);
36227
37177
  if (!existsSync71(snapshotDir)) {
36228
37178
  mkdirSync41(snapshotDir, { recursive: true });
36229
37179
  }
@@ -36235,12 +37185,12 @@ var init_stash_manager = __esm({
36235
37185
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
36236
37186
  };
36237
37187
  writeFileSync36(
36238
- join72(snapshotDir, OVERLAY_FILE2),
37188
+ join74(snapshotDir, OVERLAY_FILE2),
36239
37189
  JSON.stringify(snapshot, null, 2),
36240
37190
  "utf-8"
36241
37191
  );
36242
37192
  writeFileSync36(
36243
- join72(snapshotDir, HASHES_FILE2),
37193
+ join74(snapshotDir, HASHES_FILE2),
36244
37194
  JSON.stringify(fileHashState, null, 2),
36245
37195
  "utf-8"
36246
37196
  );
@@ -36261,14 +37211,14 @@ var init_stash_manager = __esm({
36261
37211
  return 0;
36262
37212
  }
36263
37213
  const latest = snapshots[0];
36264
- const snapshotDir = join72(this.stashDir, latest.id);
36265
- const overlayPath = join72(snapshotDir, OVERLAY_FILE2);
37214
+ const snapshotDir = join74(this.stashDir, latest.id);
37215
+ const overlayPath = join74(snapshotDir, OVERLAY_FILE2);
36266
37216
  if (!existsSync71(overlayPath)) {
36267
37217
  _log6.warn(`Snapshot ${latest.id} missing overlay file`);
36268
37218
  return 0;
36269
37219
  }
36270
37220
  try {
36271
- const raw = readFileSync61(overlayPath, "utf-8");
37221
+ const raw = readFileSync62(overlayPath, "utf-8");
36272
37222
  const snapshot = JSON.parse(raw);
36273
37223
  for (const entity of snapshot.entities) {
36274
37224
  await localGraph.upsertDriftEntity(entity);
@@ -36298,10 +37248,10 @@ var init_stash_manager = __esm({
36298
37248
  const snapshots = this.listSnapshots();
36299
37249
  if (snapshots.length === 0) return null;
36300
37250
  const latest = snapshots[0];
36301
- const hashesPath = join72(this.stashDir, latest.id, HASHES_FILE2);
37251
+ const hashesPath = join74(this.stashDir, latest.id, HASHES_FILE2);
36302
37252
  if (!existsSync71(hashesPath)) return null;
36303
37253
  try {
36304
- const raw = readFileSync61(hashesPath, "utf-8");
37254
+ const raw = readFileSync62(hashesPath, "utf-8");
36305
37255
  return JSON.parse(raw);
36306
37256
  } catch {
36307
37257
  return null;
@@ -36312,7 +37262,7 @@ var init_stash_manager = __esm({
36312
37262
  */
36313
37263
  dropSnapshot(stashRef) {
36314
37264
  const snapshotId = stashRef.slice(0, 12);
36315
- const snapshotDir = join72(this.stashDir, snapshotId);
37265
+ const snapshotDir = join74(this.stashDir, snapshotId);
36316
37266
  if (!existsSync71(snapshotDir)) return false;
36317
37267
  rmSync4(snapshotDir, { recursive: true, force: true });
36318
37268
  _log6.info(`Dropped stash snapshot: ${snapshotId}`);
@@ -36324,11 +37274,11 @@ var init_stash_manager = __esm({
36324
37274
  listSnapshots() {
36325
37275
  if (!existsSync71(this.stashDir)) return [];
36326
37276
  try {
36327
- const entries = readdirSync16(this.stashDir, { withFileTypes: true });
37277
+ const entries = readdirSync17(this.stashDir, { withFileTypes: true });
36328
37278
  const snapshots = [];
36329
37279
  for (const entry of entries) {
36330
37280
  if (!entry.isDirectory()) continue;
36331
- const overlayPath = join72(this.stashDir, entry.name, OVERLAY_FILE2);
37281
+ const overlayPath = join74(this.stashDir, entry.name, OVERLAY_FILE2);
36332
37282
  if (!existsSync71(overlayPath)) continue;
36333
37283
  try {
36334
37284
  const stat2 = statSync12(overlayPath);
@@ -36346,10 +37296,10 @@ var init_stash_manager = __esm({
36346
37296
  * Read the current stash ref SHA from `.git/refs/stash`.
36347
37297
  */
36348
37298
  readStashRef() {
36349
- const stashPath = join72(this.gitDir, "refs", "stash");
37299
+ const stashPath = join74(this.gitDir, "refs", "stash");
36350
37300
  if (!existsSync71(stashPath)) return null;
36351
37301
  try {
36352
- return readFileSync61(stashPath, "utf-8").trim() || null;
37302
+ return readFileSync62(stashPath, "utf-8").trim() || null;
36353
37303
  } catch {
36354
37304
  return null;
36355
37305
  }
@@ -36358,10 +37308,10 @@ var init_stash_manager = __esm({
36358
37308
  * Count current stash entries via `.git/logs/refs/stash`.
36359
37309
  */
36360
37310
  getStashCount() {
36361
- const logPath = join72(this.gitDir, "logs", "refs", "stash");
37311
+ const logPath = join74(this.gitDir, "logs", "refs", "stash");
36362
37312
  if (!existsSync71(logPath)) return 0;
36363
37313
  try {
36364
- const content = readFileSync61(logPath, "utf-8");
37314
+ const content = readFileSync62(logPath, "utf-8");
36365
37315
  return content.split("\n").filter((line) => line.trim().length > 0).length;
36366
37316
  } catch {
36367
37317
  return 0;
@@ -36375,7 +37325,7 @@ var init_stash_manager = __esm({
36375
37325
  if (snapshots.length <= MAX_STASH_SNAPSHOTS) return;
36376
37326
  const toRemove = snapshots.slice(MAX_STASH_SNAPSHOTS);
36377
37327
  for (const snapshot of toRemove) {
36378
- const dir = join72(this.stashDir, snapshot.id);
37328
+ const dir = join74(this.stashDir, snapshot.id);
36379
37329
  rmSync4(dir, { recursive: true, force: true });
36380
37330
  _log6.info(`LRU evicted stash snapshot: ${snapshot.id}`);
36381
37331
  }
@@ -36395,11 +37345,11 @@ __export(drift_tracker_exports, {
36395
37345
  import {
36396
37346
  existsSync as existsSync72,
36397
37347
  mkdirSync as mkdirSync42,
36398
- readFileSync as readFileSync62,
37348
+ readFileSync as readFileSync63,
36399
37349
  statSync as statSync13,
36400
37350
  writeFileSync as writeFileSync37
36401
37351
  } from "fs";
36402
- import { join as join73 } from "path";
37352
+ import { join as join75 } from "path";
36403
37353
  function determineOrigin(lastSyncTimestamp) {
36404
37354
  if (lastSyncTimestamp === 0) return "human";
36405
37355
  const elapsed = Date.now() - lastSyncTimestamp;
@@ -36610,7 +37560,7 @@ var init_drift_tracker = __esm({
36610
37560
  crossFileInvalidated: 0,
36611
37561
  edgesExtracted: 0
36612
37562
  };
36613
- const absPath = filePath.startsWith("/") ? filePath : join73(this.config.projectRoot, filePath);
37563
+ const absPath = filePath.startsWith("/") ? filePath : join75(this.config.projectRoot, filePath);
36614
37564
  const relPath = filePath.startsWith("/") ? filePath.slice(this.config.projectRoot.length + 1) : filePath;
36615
37565
  const language = detectLanguage2(relPath);
36616
37566
  if (!language) return result;
@@ -36656,7 +37606,7 @@ var init_drift_tracker = __esm({
36656
37606
  this.maybeEmitDrift(relPath, result);
36657
37607
  return result;
36658
37608
  }
36659
- const content = readFileSync62(absPath, "utf-8");
37609
+ const content = readFileSync63(absPath, "utf-8");
36660
37610
  const sha = contentSha256(content);
36661
37611
  const decision = this.fileHashManager.shouldProcess(relPath, sha, headSha);
36662
37612
  if (decision === "skip") {
@@ -36890,7 +37840,7 @@ var init_drift_tracker = __esm({
36890
37840
  return await this.stashManager.restoreSnapshot(this.localGraph);
36891
37841
  }
36892
37842
  async markFileDeleted(filePath, intentId) {
36893
- const absPath = filePath.startsWith("/") ? filePath : join73(this.config.projectRoot, filePath);
37843
+ const absPath = filePath.startsWith("/") ? filePath : join75(this.config.projectRoot, filePath);
36894
37844
  this.mtimeCache.evict(absPath);
36895
37845
  const baseEntities = await this.localGraph.getEntitiesByFile(filePath);
36896
37846
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -36975,13 +37925,13 @@ var init_drift_tracker = __esm({
36975
37925
  return invalidated;
36976
37926
  }
36977
37927
  async saveDriftSummary() {
36978
- const driftDir = join73(this.config.unerrDir, "drift");
37928
+ const driftDir = join75(this.config.unerrDir, "drift");
36979
37929
  if (!existsSync72(driftDir)) {
36980
37930
  mkdirSync42(driftDir, { recursive: true });
36981
37931
  }
36982
37932
  const summary = await this.getDriftSummary();
36983
37933
  writeFileSync37(
36984
- join73(driftDir, "drift_summary.json"),
37934
+ join75(driftDir, "drift_summary.json"),
36985
37935
  JSON.stringify(summary, null, 2),
36986
37936
  "utf-8"
36987
37937
  );
@@ -37392,8 +38342,8 @@ var incremental_indexer_exports = {};
37392
38342
  __export(incremental_indexer_exports, {
37393
38343
  indexFilesIncremental: () => indexFilesIncremental
37394
38344
  });
37395
- import { existsSync as existsSync73, readFileSync as readFileSync63 } from "fs";
37396
- import { join as join74, relative as relative5 } from "path";
38345
+ import { existsSync as existsSync73, readFileSync as readFileSync64 } from "fs";
38346
+ import { join as join76, relative as relative5 } from "path";
37397
38347
  async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repoId) {
37398
38348
  const startMs = Date.now();
37399
38349
  const db = {
@@ -37411,7 +38361,7 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
37411
38361
  const changedEntityKeys = /* @__PURE__ */ new Set();
37412
38362
  const deletedEntityKeys = /* @__PURE__ */ new Set();
37413
38363
  for (const filePath of changedFiles) {
37414
- const absPath = filePath.startsWith("/") ? filePath : join74(projectRoot, filePath);
38364
+ const absPath = filePath.startsWith("/") ? filePath : join76(projectRoot, filePath);
37415
38365
  const relPath = filePath.startsWith("/") ? relative5(projectRoot, filePath) : filePath;
37416
38366
  if (!existsSync73(absPath)) {
37417
38367
  const deleted2 = await deleteFileFromGraph(db, relPath);
@@ -37426,7 +38376,7 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
37426
38376
  }
37427
38377
  let content;
37428
38378
  try {
37429
- content = readFileSync63(absPath, "utf-8");
38379
+ content = readFileSync64(absPath, "utf-8");
37430
38380
  } catch {
37431
38381
  continue;
37432
38382
  }
@@ -38604,8 +39554,8 @@ var workspace_manifest_exports = {};
38604
39554
  __export(workspace_manifest_exports, {
38605
39555
  WorkspaceManifest: () => WorkspaceManifest
38606
39556
  });
38607
- import { existsSync as existsSync74, mkdirSync as mkdirSync43, readFileSync as readFileSync64, writeFileSync as writeFileSync38 } from "fs";
38608
- import { join as join75 } from "path";
39557
+ import { existsSync as existsSync74, mkdirSync as mkdirSync43, readFileSync as readFileSync65, writeFileSync as writeFileSync38 } from "fs";
39558
+ import { join as join77 } from "path";
38609
39559
  var WorkspaceManifest;
38610
39560
  var init_workspace_manifest = __esm({
38611
39561
  "src/tracking/workspace-manifest.ts"() {
@@ -38615,7 +39565,7 @@ var init_workspace_manifest = __esm({
38615
39565
  this.unerrDir = unerrDir2;
38616
39566
  this.repoId = repoId;
38617
39567
  this.sessionId = sessionId;
38618
- this.manifestPath = join75(unerrDir2, "manifest.json");
39568
+ this.manifestPath = join77(unerrDir2, "manifest.json");
38619
39569
  this.data = this.load();
38620
39570
  }
38621
39571
  unerrDir;
@@ -38720,7 +39670,7 @@ var init_workspace_manifest = __esm({
38720
39670
  };
38721
39671
  }
38722
39672
  try {
38723
- const raw = readFileSync64(this.manifestPath, "utf-8");
39673
+ const raw = readFileSync65(this.manifestPath, "utf-8");
38724
39674
  const parsed = JSON.parse(raw);
38725
39675
  if (parsed.repoId !== this.repoId) {
38726
39676
  return {
@@ -38945,7 +39895,7 @@ import {
38945
39895
  statSync as statSync14,
38946
39896
  watch
38947
39897
  } from "fs";
38948
- import { join as join76 } from "path";
39898
+ import { join as join78 } from "path";
38949
39899
  function formatSize(bytes) {
38950
39900
  if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
38951
39901
  if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`;
@@ -39092,9 +40042,9 @@ function tokenFlowRowToEntry(r) {
39092
40042
  };
39093
40043
  }
39094
40044
  function startLogTailer(cwd, options) {
39095
- const unerrDir2 = join76(cwd, ".unerr");
39096
- const logsDir = join76(unerrDir2, "logs");
39097
- const generalPath = join76(logsDir, "events.jsonl");
40045
+ const unerrDir2 = join78(cwd, ".unerr");
40046
+ const logsDir = join78(unerrDir2, "logs");
40047
+ const generalPath = join78(logsDir, "events.jsonl");
39098
40048
  const pollIntervalMs = options?.pollIntervalMs ?? 500;
39099
40049
  const generalState = {
39100
40050
  path: generalPath,
@@ -39240,13 +40190,13 @@ var init_middleware = __esm({
39240
40190
  });
39241
40191
 
39242
40192
  // src/server/routes/drift.ts
39243
- import { existsSync as existsSync76, readFileSync as readFileSync65, readdirSync as readdirSync17 } from "fs";
39244
- import { join as join77 } from "path";
40193
+ import { existsSync as existsSync76, readFileSync as readFileSync66, readdirSync as readdirSync18 } from "fs";
40194
+ import { join as join79 } from "path";
39245
40195
  import { Hono as Hono2 } from "hono";
39246
40196
  function readFlags(path7) {
39247
40197
  try {
39248
40198
  if (!existsSync76(path7)) return null;
39249
- return JSON.parse(readFileSync65(path7, "utf8"));
40199
+ return JSON.parse(readFileSync66(path7, "utf8"));
39250
40200
  } catch {
39251
40201
  return null;
39252
40202
  }
@@ -39254,7 +40204,7 @@ function readFlags(path7) {
39254
40204
  function listSessionFiles(stateDir) {
39255
40205
  try {
39256
40206
  if (!existsSync76(stateDir)) return [];
39257
- return readdirSync17(stateDir).filter((f) => f.startsWith("nudge-") && f.endsWith(".flags")).map((f) => join77(stateDir, f));
40207
+ return readdirSync18(stateDir).filter((f) => f.startsWith("nudge-") && f.endsWith(".flags")).map((f) => join79(stateDir, f));
39258
40208
  } catch {
39259
40209
  return [];
39260
40210
  }
@@ -39274,7 +40224,7 @@ function rowFor(file) {
39274
40224
  }
39275
40225
  function createDriftRoutes(deps) {
39276
40226
  const app = new Hono2();
39277
- const stateDir = join77(deps.cwd, ".unerr", "state");
40227
+ const stateDir = join79(deps.cwd, ".unerr", "state");
39278
40228
  app.get("/sessions", (c) => {
39279
40229
  const rows = [];
39280
40230
  for (const file of listSessionFiles(stateDir)) {
@@ -39295,7 +40245,7 @@ function createDriftRoutes(deps) {
39295
40245
  });
39296
40246
  app.get("/current", (c) => {
39297
40247
  const id = process.env.UNERR_SESSION_ID ?? `pid-${process.pid}`;
39298
- const file = join77(stateDir, `nudge-${id}.flags`);
40248
+ const file = join79(stateDir, `nudge-${id}.flags`);
39299
40249
  const row = rowFor(file);
39300
40250
  return c.json({
39301
40251
  data: row ?? {
@@ -41458,13 +42408,13 @@ function createSystemRoutes(deps) {
41458
42408
  });
41459
42409
  app.get("/config", async (c) => {
41460
42410
  const start = performance.now();
41461
- const { existsSync: existsSync82, readFileSync: readFileSync70 } = await import("fs");
41462
- const { join: join83 } = await import("path");
42411
+ const { existsSync: existsSync82, readFileSync: readFileSync71 } = await import("fs");
42412
+ const { join: join85 } = await import("path");
41463
42413
  let config = {};
41464
- const configPath2 = join83(deps.cwd, ".unerr", "config.json");
42414
+ const configPath2 = join85(deps.cwd, ".unerr", "config.json");
41465
42415
  if (existsSync82(configPath2)) {
41466
42416
  try {
41467
- config = JSON.parse(readFileSync70(configPath2, "utf-8"));
42417
+ config = JSON.parse(readFileSync71(configPath2, "utf-8"));
41468
42418
  } catch {
41469
42419
  config = { error: "unreadable" };
41470
42420
  }
@@ -42757,10 +43707,10 @@ var http_exports = {};
42757
43707
  __export(http_exports, {
42758
43708
  startDashboardServer: () => startDashboardServer
42759
43709
  });
42760
- import { existsSync as existsSync77, readFileSync as readFileSync66, unlinkSync as unlinkSync14, writeFileSync as writeFileSync39 } from "fs";
43710
+ import { existsSync as existsSync77, readFileSync as readFileSync67, unlinkSync as unlinkSync14, writeFileSync as writeFileSync39 } from "fs";
42761
43711
  import { createServer as createServer5 } from "net";
42762
- import { dirname as dirname17, join as join78 } from "path";
42763
- import { fileURLToPath as fileURLToPath3 } from "url";
43712
+ import { dirname as dirname17, join as join80 } from "path";
43713
+ import { fileURLToPath as fileURLToPath4 } from "url";
42764
43714
  import { serve as serve2 } from "@hono/node-server";
42765
43715
  import { serveStatic as serveStatic2 } from "@hono/node-server/serve-static";
42766
43716
  import { Hono as Hono13 } from "hono";
@@ -42819,10 +43769,10 @@ async function startDashboardServer(opts) {
42819
43769
  app.route("/api/router", createRouterApiV2(opts.routerV2));
42820
43770
  }
42821
43771
  if (!opts.apiOnly) {
42822
- const distDir = join78(dirname17(fileURLToPath3(import.meta.url)), "ui");
42823
- const spaIndex = join78(distDir, "index.html");
43772
+ const distDir = join80(dirname17(fileURLToPath4(import.meta.url)), "ui");
43773
+ const spaIndex = join80(distDir, "index.html");
42824
43774
  if (existsSync77(spaIndex)) {
42825
- const spaHtml = readFileSync66(spaIndex, "utf-8");
43775
+ const spaHtml = readFileSync67(spaIndex, "utf-8");
42826
43776
  app.use("*", serveStatic2({ root: distDir }));
42827
43777
  app.get("*", (c) => {
42828
43778
  const path7 = c.req.path;
@@ -42854,7 +43804,7 @@ async function startDashboardServer(opts) {
42854
43804
  port,
42855
43805
  hostname: "127.0.0.1"
42856
43806
  });
42857
- const serverJsonPath = join78(opts.stateDir, "server.json");
43807
+ const serverJsonPath = join80(opts.stateDir, "server.json");
42858
43808
  const serverInfo = {
42859
43809
  port,
42860
43810
  pid: process.pid,
@@ -43284,15 +44234,15 @@ import {
43284
44234
  existsSync as existsSync78,
43285
44235
  writeFileSync as fsWriteFileSync,
43286
44236
  mkdirSync as mkdirSync44,
43287
- readFileSync as readFileSync67,
43288
- readdirSync as readdirSync18
44237
+ readFileSync as readFileSync68,
44238
+ readdirSync as readdirSync19
43289
44239
  } from "fs";
43290
- import { join as join79 } from "path";
44240
+ import { join as join81 } from "path";
43291
44241
  async function getProxyFactStore(unerrDir2) {
43292
44242
  if (proxyFactStore !== void 0) return proxyFactStore;
43293
44243
  try {
43294
44244
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
43295
- const cwd = join79(unerrDir2, "..");
44245
+ const cwd = join81(unerrDir2, "..");
43296
44246
  proxyFactStore = await TemporalFactStore2.create(cwd);
43297
44247
  return proxyFactStore;
43298
44248
  } catch {
@@ -43436,9 +44386,9 @@ async function handleRecallFactsProxy(args, unerrDir2, effectiveness) {
43436
44386
  }
43437
44387
  function migrateAgentPermissions(cwd) {
43438
44388
  try {
43439
- const settingsPath = join79(cwd, ".claude", "settings.json");
44389
+ const settingsPath = join81(cwd, ".claude", "settings.json");
43440
44390
  if (!existsSync78(settingsPath)) return;
43441
- const raw = readFileSync67(settingsPath, "utf-8");
44391
+ const raw = readFileSync68(settingsPath, "utf-8");
43442
44392
  const settings = JSON.parse(raw);
43443
44393
  const deny = settings?.permissions?.deny;
43444
44394
  if (!Array.isArray(deny)) return;
@@ -43455,7 +44405,7 @@ function migrateAgentPermissions(cwd) {
43455
44405
  }
43456
44406
  async function startProxy(opts = {}) {
43457
44407
  installFileLogger({
43458
- filePath: join79(process.cwd(), ".unerr", "logs", "unerr.log"),
44408
+ filePath: join81(process.cwd(), ".unerr", "logs", "unerr.log"),
43459
44409
  maxBytes: 5e6,
43460
44410
  keep: 5
43461
44411
  });
@@ -43468,7 +44418,7 @@ async function startProxy(opts = {}) {
43468
44418
  const lifecycle = createLifecycleActor(process.cwd());
43469
44419
  lifecycle.send({ type: "START_DETECT" });
43470
44420
  startup.setLocalMode(true);
43471
- const stateDir = join79(process.cwd(), ".unerr", "state");
44421
+ const stateDir = join81(process.cwd(), ".unerr", "state");
43472
44422
  if (!existsSync78(stateDir)) {
43473
44423
  mkdirSync44(stateDir, { recursive: true });
43474
44424
  }
@@ -43487,7 +44437,7 @@ async function startProxy(opts = {}) {
43487
44437
  startupLog.step(
43488
44438
  `PID ${process.pid} ${startupLog.fmt.muted(`\xB7 health localhost:${lockResult.healthPort}`)}`
43489
44439
  );
43490
- const ledgerDir = join79(process.cwd(), ".unerr", "ledger");
44440
+ const ledgerDir = join81(process.cwd(), ".unerr", "ledger");
43491
44441
  const previousSession = detectSessionResume(stateDir, ledgerDir);
43492
44442
  if (previousSession) {
43493
44443
  stats.isResumedSession = true;
@@ -43502,21 +44452,21 @@ async function startProxy(opts = {}) {
43502
44452
  if (opts.repoId) {
43503
44453
  repoIds = [opts.repoId];
43504
44454
  } else {
43505
- const configPath2 = join79(process.cwd(), ".unerr", "config.json");
44455
+ const configPath2 = join81(process.cwd(), ".unerr", "config.json");
43506
44456
  if (existsSync78(configPath2)) {
43507
44457
  try {
43508
- const config = JSON.parse(readFileSync67(configPath2, "utf-8"));
44458
+ const config = JSON.parse(readFileSync68(configPath2, "utf-8"));
43509
44459
  if (config.repoId) repoIds = [config.repoId];
43510
44460
  } catch {
43511
44461
  }
43512
44462
  }
43513
- const manifestsDir = join79(process.cwd(), ".unerr", "manifests");
44463
+ const manifestsDir = join81(process.cwd(), ".unerr", "manifests");
43514
44464
  if (repoIds.length === 0 && existsSync78(manifestsDir)) {
43515
- repoIds = readdirSync18(manifestsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
44465
+ repoIds = readdirSync19(manifestsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
43516
44466
  }
43517
44467
  }
43518
44468
  if (repoIds.length === 0) {
43519
- const { createHash: createHash5 } = await import("crypto");
44469
+ const { createHash: createHash6 } = await import("crypto");
43520
44470
  let repoIdentifier = process.cwd();
43521
44471
  try {
43522
44472
  const { getRemoteUrl: getRemoteUrl2 } = await Promise.resolve().then(() => (init_git(), git_exports));
@@ -43524,7 +44474,7 @@ async function startProxy(opts = {}) {
43524
44474
  if (remote) repoIdentifier = remote;
43525
44475
  } catch {
43526
44476
  }
43527
- const localRepoId = createHash5("sha256").update(repoIdentifier).digest("hex").slice(0, 12);
44477
+ const localRepoId = createHash6("sha256").update(repoIdentifier).digest("hex").slice(0, 12);
43528
44478
  repoIds = [localRepoId];
43529
44479
  startupLog.done(
43530
44480
  `Repository ${startupLog.fmt.cyan(localRepoId)} ${startupLog.fmt.muted(`(from ${repoIdentifier === process.cwd() ? "directory" : "git remote"})`)}`
@@ -43636,7 +44586,7 @@ async function startProxy(opts = {}) {
43636
44586
  `Migrated snapshot to persistent graph ${startupLog.fmt.muted(`\u2192 ${dbPath}`)}`
43637
44587
  );
43638
44588
  try {
43639
- const migrationUnerrDir = join79(process.cwd(), ".unerr");
44589
+ const migrationUnerrDir = join81(process.cwd(), ".unerr");
43640
44590
  const factStoreForMigration = await getProxyFactStore(migrationUnerrDir);
43641
44591
  if (factStoreForMigration) {
43642
44592
  const { detectLocalConventions: detectLocalConventions2 } = await Promise.resolve().then(() => (init_local_convention_detector(), local_convention_detector_exports));
@@ -43735,7 +44685,7 @@ async function startProxy(opts = {}) {
43735
44685
  let efficiencyTracker = createEfficiencyTracker2();
43736
44686
  router.setTokenCounter(tokenCounter);
43737
44687
  router.setEfficiencyTracker(efficiencyTracker);
43738
- const proxyFactStore2 = await getProxyFactStore(join79(process.cwd(), ".unerr"));
44688
+ const proxyFactStore2 = await getProxyFactStore(join81(process.cwd(), ".unerr"));
43739
44689
  if (proxyFactStore2) {
43740
44690
  router.setFactStore(proxyFactStore2);
43741
44691
  }
@@ -43743,7 +44693,7 @@ async function startProxy(opts = {}) {
43743
44693
  try {
43744
44694
  const { generateSessionResume: generateSessionResume2 } = await Promise.resolve().then(() => (init_session_resume(), session_resume_exports));
43745
44695
  const { ShadowLedger: ResumeLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
43746
- const resumeLedger = new ResumeLedger(join79(process.cwd(), ".unerr"));
44696
+ const resumeLedger = new ResumeLedger(join81(process.cwd(), ".unerr"));
43747
44697
  const ledgerEntries = resumeLedger.getRecentEntries(50);
43748
44698
  const resumeCtx = generateSessionResume2(ledgerEntries);
43749
44699
  if (resumeCtx) {
@@ -43764,7 +44714,7 @@ async function startProxy(opts = {}) {
43764
44714
  const { createDurabilityScorer: createDurabilityScorer2 } = await Promise.resolve().then(() => (init_durability_scorer(), durability_scorer_exports));
43765
44715
  const durabilityScorer = createDurabilityScorer2();
43766
44716
  const { ShadowLedger: DurLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
43767
- const durLedger = new DurLedger(join79(process.cwd(), ".unerr"));
44717
+ const durLedger = new DurLedger(join81(process.cwd(), ".unerr"));
43768
44718
  const durEntries = durLedger.getRecentEntries(200);
43769
44719
  if (durEntries.length > 0) {
43770
44720
  durabilityScorer.computeScores(durEntries);
@@ -43784,7 +44734,7 @@ async function startProxy(opts = {}) {
43784
44734
  try {
43785
44735
  const { detectInstableEntities: detectInstableEntities2 } = await Promise.resolve().then(() => (init_negative_knowledge(), negative_knowledge_exports));
43786
44736
  const { ShadowLedger: NkLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
43787
- const nkLedger = new NkLedger(join79(process.cwd(), ".unerr"));
44737
+ const nkLedger = new NkLedger(join81(process.cwd(), ".unerr"));
43788
44738
  const nkEntries = nkLedger.getRecentEntries(200);
43789
44739
  if (nkEntries.length > 0) {
43790
44740
  const antiPatterns = detectInstableEntities2(nkEntries);
@@ -43806,7 +44756,7 @@ async function startProxy(opts = {}) {
43806
44756
  try {
43807
44757
  const { CausalBridge: CausalBridge2 } = await Promise.resolve().then(() => (init_causal_bridge(), causal_bridge_exports));
43808
44758
  const causalBridge = new CausalBridge2(
43809
- join79(process.cwd(), ".unerr"),
44759
+ join81(process.cwd(), ".unerr"),
43810
44760
  process.cwd()
43811
44761
  );
43812
44762
  router.setCausalBridge(causalBridge);
@@ -43816,7 +44766,7 @@ async function startProxy(opts = {}) {
43816
44766
  try {
43817
44767
  const { learnConventions: learnConventions2 } = await Promise.resolve().then(() => (init_convention_learner(), convention_learner_exports));
43818
44768
  const { ShadowLedger: ConvLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
43819
- const convLedger = new ConvLedger(join79(process.cwd(), ".unerr"));
44769
+ const convLedger = new ConvLedger(join81(process.cwd(), ".unerr"));
43820
44770
  const convEntries = convLedger.getRecentEntries(100);
43821
44771
  if (convEntries.length > 0) {
43822
44772
  const learned = learnConventions2(convEntries);
@@ -43839,7 +44789,7 @@ async function startProxy(opts = {}) {
43839
44789
  try {
43840
44790
  const { computePromptDurabilityProfiles: computePromptDurabilityProfiles2 } = await Promise.resolve().then(() => (init_prompt_durability(), prompt_durability_exports));
43841
44791
  const { ShadowLedger: DurProfLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
43842
- const durProfLedger = new DurProfLedger(join79(process.cwd(), ".unerr"));
44792
+ const durProfLedger = new DurProfLedger(join81(process.cwd(), ".unerr"));
43843
44793
  const durProfEntries = durProfLedger.getRecentEntries(200);
43844
44794
  if (durProfEntries.length > 0) {
43845
44795
  const profiles = computePromptDurabilityProfiles2(durProfEntries);
@@ -43859,7 +44809,7 @@ async function startProxy(opts = {}) {
43859
44809
  }
43860
44810
  try {
43861
44811
  const { createContextLedger: createContextLedger2 } = await Promise.resolve().then(() => (init_context_ledger(), context_ledger_exports));
43862
- const contextLedger = createContextLedger2(join79(process.cwd(), ".unerr"));
44812
+ const contextLedger = createContextLedger2(join81(process.cwd(), ".unerr"));
43863
44813
  contextLedger.load();
43864
44814
  contextLedger.prune();
43865
44815
  router.setContextLedger(contextLedger);
@@ -43984,7 +44934,7 @@ async function startProxy(opts = {}) {
43984
44934
  });
43985
44935
  const { ShadowLedger: ShadowLedger2 } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
43986
44936
  const { IntentCorrelator: IntentCorrelator2 } = await Promise.resolve().then(() => (init_intent_correlator(), intent_correlator_exports));
43987
- const unerrDirForLedger = join79(process.cwd(), ".unerr");
44937
+ const unerrDirForLedger = join81(process.cwd(), ".unerr");
43988
44938
  const shadowLedger = new ShadowLedger2(unerrDirForLedger);
43989
44939
  const intentCorrelator = new IntentCorrelator2(unerrDirForLedger);
43990
44940
  log22.info(
@@ -44094,7 +45044,7 @@ async function startProxy(opts = {}) {
44094
45044
  try {
44095
45045
  const { writeFileSync: writeFileSync42 } = await import("fs");
44096
45046
  writeFileSync42(
44097
- join79(unerrDirForLedger, "state", "session.id"),
45047
+ join81(unerrDirForLedger, "state", "session.id"),
44098
45048
  shadowLedger.getSessionId(),
44099
45049
  "utf-8"
44100
45050
  );
@@ -44522,7 +45472,7 @@ ${signalFooter.trimEnd()}` : "";
44522
45472
  lifecycle.send({ type: "INDEX_COMPLETE" });
44523
45473
  lifecycle.send({ type: "MCP_READY" });
44524
45474
  const { TransportMux: TransportMux2 } = await Promise.resolve().then(() => (init_transport_mux(), transport_mux_exports));
44525
- const sockPath2 = join79(stateDir, "proxy.sock");
45475
+ const sockPath2 = join81(stateDir, "proxy.sock");
44526
45476
  const transportMux = new TransportMux2(sockPath2);
44527
45477
  const agentNameByClient = /* @__PURE__ */ new Map();
44528
45478
  transportMux.setCustomHttpHandler("/commit-context", (_url) => {
@@ -44807,7 +45757,7 @@ ${signalFooter2.trimEnd()}` : "";
44807
45757
  try {
44808
45758
  const { DriftTracker: DriftTracker2 } = await Promise.resolve().then(() => (init_drift_tracker(), drift_tracker_exports));
44809
45759
  const { FileHashManager: FileHashManager2 } = await Promise.resolve().then(() => (init_file_hash_state(), file_hash_state_exports));
44810
- const unerrDir2 = join79(process.cwd(), ".unerr");
45760
+ const unerrDir2 = join81(process.cwd(), ".unerr");
44811
45761
  const fileHashManager = new FileHashManager2(unerrDir2);
44812
45762
  _driftTracker = new DriftTracker2(
44813
45763
  { projectRoot: process.cwd(), repoId: repoIds[0], unerrDir: unerrDir2 },
@@ -45031,7 +45981,7 @@ ${signalFooter2.trimEnd()}` : "";
45031
45981
  }
45032
45982
  const { WorkspaceManifest: WorkspaceManifest2 } = await Promise.resolve().then(() => (init_workspace_manifest(), workspace_manifest_exports));
45033
45983
  const workspaceManifest = repoIds[0] ? new WorkspaceManifest2(
45034
- join79(process.cwd(), ".unerr"),
45984
+ join81(process.cwd(), ".unerr"),
45035
45985
  repoIds[0],
45036
45986
  shadowLedger.getSessionId()
45037
45987
  ) : null;
@@ -45116,7 +46066,7 @@ ${signalFooter2.trimEnd()}` : "";
45116
46066
  if (proxyMode === "parse" && parseIndex) {
45117
46067
  try {
45118
46068
  const { extractEntitiesFromSource: extractEntitiesFromSource2 } = await Promise.resolve().then(() => (init_auto_bootstrap(), auto_bootstrap_exports));
45119
- const allFiles = readdirSync18(process.cwd(), {
46069
+ const allFiles = readdirSync19(process.cwd(), {
45120
46070
  recursive: true,
45121
46071
  encoding: "utf-8"
45122
46072
  });
@@ -45130,7 +46080,7 @@ ${signalFooter2.trimEnd()}` : "";
45130
46080
  });
45131
46081
  for (const file of sourceFiles.slice(0, 500)) {
45132
46082
  try {
45133
- const content = readFileSync67(join79(process.cwd(), file), "utf-8");
46083
+ const content = readFileSync68(join81(process.cwd(), file), "utf-8");
45134
46084
  const entities = extractEntitiesFromSource2(file, content);
45135
46085
  parseIndex.addEntities(entities);
45136
46086
  } catch {
@@ -45194,7 +46144,7 @@ ${signalFooter2.trimEnd()}` : "";
45194
46144
  }
45195
46145
  const { writeFileSync: writeStatsFile } = await import("fs");
45196
46146
  const { computePercentiles: computePercentiles2 } = await Promise.resolve().then(() => (init_session_stats(), session_stats_exports));
45197
- const statsSnapshotPath = join79(stateDir, "session_stats.json");
46147
+ const statsSnapshotPath = join81(stateDir, "session_stats.json");
45198
46148
  const statsSnapshotInterval = setInterval(() => {
45199
46149
  try {
45200
46150
  const total = stats.toolCallsLocal;
@@ -45232,7 +46182,7 @@ ${signalFooter2.trimEnd()}` : "";
45232
46182
  const { startDashboardServer: startDashboardServer2 } = await Promise.resolve().then(() => (init_http(), http_exports));
45233
46183
  const { detectIde: detectIdeDashboard } = await Promise.resolve().then(() => (init_detect(), detect_exports));
45234
46184
  const ideType = await detectIdeDashboard(process.cwd());
45235
- const unerrDirForApi = join79(process.cwd(), ".unerr");
46185
+ const unerrDirForApi = join81(process.cwd(), ".unerr");
45236
46186
  dashboardHandle = await startDashboardServer2({
45237
46187
  system: {
45238
46188
  stats,
@@ -45300,16 +46250,16 @@ ${signalFooter2.trimEnd()}` : "";
45300
46250
  temporal: await (async () => {
45301
46251
  try {
45302
46252
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
45303
- const { readdirSync: readdirSync19, readFileSync: readFileSync70 } = await import("fs");
46253
+ const { readdirSync: readdirSync20, readFileSync: readFileSync71 } = await import("fs");
45304
46254
  const factStore = await TemporalFactStore2.create(process.cwd());
45305
46255
  return {
45306
46256
  factStore,
45307
46257
  loadRecentSessions: (limit) => {
45308
46258
  try {
45309
- const sessDir = join79(unerrDirForApi, "sessions");
45310
- const files = readdirSync19(sessDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
46259
+ const sessDir = join81(unerrDirForApi, "sessions");
46260
+ const files = readdirSync20(sessDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
45311
46261
  return files.map((f) => {
45312
- const content = readFileSync70(join79(sessDir, f), "utf-8").trim().split("\n").pop();
46262
+ const content = readFileSync71(join81(sessDir, f), "utf-8").trim().split("\n").pop();
45313
46263
  return JSON.parse(content);
45314
46264
  });
45315
46265
  } catch {
@@ -45396,7 +46346,7 @@ ${signalFooter2.trimEnd()}` : "";
45396
46346
  const topMech = Object.entries(tokenFlowSummary.by_mechanism).sort(
45397
46347
  ([, a], [, b]) => b.tokens_saved - a.tokens_saved
45398
46348
  )[0];
45399
- appendSessionHistory2(join79(process.cwd(), ".unerr"), {
46349
+ appendSessionHistory2(join81(process.cwd(), ".unerr"), {
45400
46350
  sessionId: shadowLedger.getSessionId(),
45401
46351
  startedAt: new Date(stats.sessionStartedAt).toISOString(),
45402
46352
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -45556,7 +46506,7 @@ ${signalFooter2.trimEnd()}` : "";
45556
46506
  try {
45557
46507
  const correctionModule = (init_correction_detector(), __toCommonJS(correction_detector_exports));
45558
46508
  const detectCorrections2 = correctionModule.detectCorrections;
45559
- const ledgerPath = join79(
46509
+ const ledgerPath = join81(
45560
46510
  process.cwd(),
45561
46511
  ".unerr",
45562
46512
  "ledger",
@@ -45920,19 +46870,19 @@ __export(setup_wizard_exports, {
45920
46870
  promptLocalOrExit: () => promptLocalOrExit,
45921
46871
  runSetup: () => runSetup
45922
46872
  });
45923
- import { createHash as createHash4 } from "crypto";
46873
+ import { createHash as createHash5 } from "crypto";
45924
46874
  import { existsSync as existsSync80, mkdirSync as mkdirSync46, writeFileSync as writeFileSync40 } from "fs";
45925
- import { join as join80 } from "path";
46875
+ import { join as join82 } from "path";
45926
46876
  import * as clack from "@clack/prompts";
45927
46877
  async function runSetup(cwd) {
45928
46878
  const projectDir = cwd ?? process.cwd();
45929
46879
  clack.intro("unerr");
45930
46880
  clack.log.step("Project Setup");
45931
46881
  const repoId = await generateRepoId(projectDir);
45932
- const configDir = join80(projectDir, ".unerr");
46882
+ const configDir = join82(projectDir, ".unerr");
45933
46883
  mkdirSync46(configDir, { recursive: true });
45934
- const configPath2 = join80(configDir, "config.json");
45935
- const settingsPath = join80(configDir, "settings.json");
46884
+ const configPath2 = join82(configDir, "config.json");
46885
+ const settingsPath = join82(configDir, "settings.json");
45936
46886
  writeFileSync40(configPath2, `${JSON.stringify({ repoId }, null, 2)}
45937
46887
  `);
45938
46888
  let existingSettings = {};
@@ -46003,7 +46953,7 @@ async function generateRepoId(cwd) {
46003
46953
  let repoIdentifier = cwd;
46004
46954
  const remote = await getRemoteUrl(cwd);
46005
46955
  if (remote) repoIdentifier = remote;
46006
- return createHash4("sha256").update(repoIdentifier).digest("hex").slice(0, 12);
46956
+ return createHash5("sha256").update(repoIdentifier).digest("hex").slice(0, 12);
46007
46957
  }
46008
46958
  function formatSize2(bytes) {
46009
46959
  if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)}GB`;
@@ -46316,6 +47266,112 @@ var init_setup_wizard = __esm({
46316
47266
  }
46317
47267
  });
46318
47268
 
47269
+ // src/proxy/bridge-catalog.ts
47270
+ var bridge_catalog_exports = {};
47271
+ __export(bridge_catalog_exports, {
47272
+ PROTOCOL_VERSION: () => PROTOCOL_VERSION,
47273
+ SERVER_INFO: () => SERVER_INFO,
47274
+ StaticCatalogInterceptor: () => StaticCatalogInterceptor,
47275
+ buildInitializeResult: () => buildInitializeResult,
47276
+ buildToolsListResult: () => buildToolsListResult
47277
+ });
47278
+ function tryIntercept(line) {
47279
+ let msg;
47280
+ try {
47281
+ const parsed = JSON.parse(line);
47282
+ if (!parsed || typeof parsed !== "object") return null;
47283
+ msg = parsed;
47284
+ } catch {
47285
+ return null;
47286
+ }
47287
+ if (msg.method === "initialize") {
47288
+ return JSON.stringify({
47289
+ jsonrpc: "2.0",
47290
+ id: msg.id ?? null,
47291
+ result: {
47292
+ protocolVersion: PROTOCOL_VERSION,
47293
+ capabilities: { tools: {} },
47294
+ serverInfo: SERVER_INFO
47295
+ }
47296
+ });
47297
+ }
47298
+ if (msg.method === "tools/list") {
47299
+ return JSON.stringify({
47300
+ jsonrpc: "2.0",
47301
+ id: msg.id ?? null,
47302
+ result: { tools: TOOL_DEFINITIONS }
47303
+ });
47304
+ }
47305
+ return null;
47306
+ }
47307
+ function buildInitializeResult(id) {
47308
+ return {
47309
+ jsonrpc: "2.0",
47310
+ id,
47311
+ result: {
47312
+ protocolVersion: PROTOCOL_VERSION,
47313
+ capabilities: { tools: {} },
47314
+ serverInfo: SERVER_INFO
47315
+ }
47316
+ };
47317
+ }
47318
+ function buildToolsListResult(id) {
47319
+ return {
47320
+ jsonrpc: "2.0",
47321
+ id,
47322
+ result: { tools: TOOL_DEFINITIONS }
47323
+ };
47324
+ }
47325
+ var PROTOCOL_VERSION, SERVER_INFO, StaticCatalogInterceptor;
47326
+ var init_bridge_catalog = __esm({
47327
+ "src/proxy/bridge-catalog.ts"() {
47328
+ "use strict";
47329
+ init_tool_definitions();
47330
+ PROTOCOL_VERSION = "2024-11-05";
47331
+ SERVER_INFO = {
47332
+ name: "unerr-local",
47333
+ version: "0.1.7"
47334
+ };
47335
+ StaticCatalogInterceptor = class {
47336
+ partial = "";
47337
+ ingest(chunk) {
47338
+ const replies = [];
47339
+ const forward = [];
47340
+ this.partial += chunk.toString("utf8");
47341
+ const lastNewline = this.partial.lastIndexOf("\n");
47342
+ if (lastNewline < 0) {
47343
+ return { replies, forward };
47344
+ }
47345
+ const completeBlock = this.partial.slice(0, lastNewline + 1);
47346
+ this.partial = this.partial.slice(lastNewline + 1);
47347
+ for (const rawLine of completeBlock.split("\n")) {
47348
+ if (rawLine === "") continue;
47349
+ const reply = tryIntercept(rawLine);
47350
+ if (reply !== null) {
47351
+ replies.push(`${reply}
47352
+ `);
47353
+ } else {
47354
+ forward.push(Buffer.from(`${rawLine}
47355
+ `, "utf8"));
47356
+ }
47357
+ }
47358
+ return { replies, forward };
47359
+ }
47360
+ /**
47361
+ * Returns any unfinished line (no terminating newline yet). Call exactly
47362
+ * once when handing the pre-buffer off to the bridge so partial frames are
47363
+ * forwarded to the proxy without being dropped.
47364
+ */
47365
+ drainPartial() {
47366
+ if (this.partial.length === 0) return null;
47367
+ const buf = Buffer.from(this.partial, "utf8");
47368
+ this.partial = "";
47369
+ return buf;
47370
+ }
47371
+ };
47372
+ }
47373
+ });
47374
+
46319
47375
  // src/daemon/spawn-lock.ts
46320
47376
  var spawn_lock_exports = {};
46321
47377
  __export(spawn_lock_exports, {
@@ -46327,16 +47383,16 @@ import {
46327
47383
  closeSync as closeSync2,
46328
47384
  mkdirSync as mkdirSync47,
46329
47385
  openSync as openSync2,
46330
- readFileSync as readFileSync68,
47386
+ readFileSync as readFileSync69,
46331
47387
  unlinkSync as unlinkSync16,
46332
47388
  writeFileSync as writeFileSync41
46333
47389
  } from "fs";
46334
- import { join as join81 } from "path";
47390
+ import { join as join83 } from "path";
46335
47391
  function spawnLockPath() {
46336
- return join81(globalDir(), "state", "spawn.lock");
47392
+ return join83(globalDir(), "state", "spawn.lock");
46337
47393
  }
46338
47394
  function tryAcquireSpawnLock() {
46339
- mkdirSync47(join81(globalDir(), "state"), { recursive: true });
47395
+ mkdirSync47(join83(globalDir(), "state"), { recursive: true });
46340
47396
  const path7 = spawnLockPath();
46341
47397
  for (let attempt = 0; attempt < 2; attempt++) {
46342
47398
  try {
@@ -46364,7 +47420,7 @@ function releaseSpawnLock() {
46364
47420
  function reclaimIfStale(path7) {
46365
47421
  let body = null;
46366
47422
  try {
46367
- body = JSON.parse(readFileSync68(path7, "utf8"));
47423
+ body = JSON.parse(readFileSync69(path7, "utf8"));
46368
47424
  } catch {
46369
47425
  try {
46370
47426
  unlinkSync16(path7);
@@ -46536,8 +47592,8 @@ var init_bridge = __esm({
46536
47592
  });
46537
47593
 
46538
47594
  // src/entrypoints/cli.ts
46539
- import { existsSync as existsSync81, readFileSync as readFileSync69 } from "fs";
46540
- import { join as join82 } from "path";
47595
+ import { existsSync as existsSync81, readFileSync as readFileSync70 } from "fs";
47596
+ import { join as join84 } from "path";
46541
47597
  import { Command } from "commander";
46542
47598
 
46543
47599
  // src/commands/branches.ts
@@ -49573,12 +50629,12 @@ async function tryLoadGraphForShellBoost(cwd) {
49573
50629
  if (cached && Date.now() - cached.loadedAt < BOOST_TTL_MS) {
49574
50630
  return cached.kind === "ok" ? cached.graph : null;
49575
50631
  }
49576
- const { existsSync: existsSync82, readFileSync: readFileSync70 } = await import("fs");
49577
- const { join: join83 } = await import("path");
49578
- const snapshotsDir = join83(cwd, ".unerr", "snapshots");
49579
- let snapshotPath2 = join83(snapshotsDir, "graph.msgpack.gz");
50632
+ const { existsSync: existsSync82, readFileSync: readFileSync71 } = await import("fs");
50633
+ const { join: join85 } = await import("path");
50634
+ const snapshotsDir = join85(cwd, ".unerr", "snapshots");
50635
+ let snapshotPath2 = join85(snapshotsDir, "graph.msgpack.gz");
49580
50636
  if (!existsSync82(snapshotPath2)) {
49581
- snapshotPath2 = join83(snapshotsDir, "graph.msgpack");
50637
+ snapshotPath2 = join85(snapshotsDir, "graph.msgpack");
49582
50638
  }
49583
50639
  if (!existsSync82(snapshotPath2)) {
49584
50640
  const reason = `snapshot missing at ${snapshotsDir}`;
@@ -49605,7 +50661,7 @@ async function tryLoadGraphForShellBoost(cwd) {
49605
50661
  let buffer;
49606
50662
  try {
49607
50663
  const { gunzipSync: gunzipSync3 } = await import("zlib");
49608
- const raw = readFileSync70(snapshotPath2);
50664
+ const raw = readFileSync71(snapshotPath2);
49609
50665
  try {
49610
50666
  buffer = gunzipSync3(raw);
49611
50667
  } catch {
@@ -54765,9 +55821,9 @@ When your next action is Edit, use built-in Read with offset/limit on the target
54765
55821
 
54766
55822
  **Common failure mode:** Using \`file_read\` to understand a file, then attempting Edit without calling built-in Read first. The Edit tool WILL reject with "File has not been read yet". Always call built-in Read (with offset/limit) immediately before Edit.` : "";
54767
55823
  const summaryEditNote = isClaudeCode ? "\nNEVER use built-in Read/Grep/Glob for code navigation. EXCEPTION: built-in Read (with offset/limit) is REQUIRED immediately before Edit (file_read cannot substitute \u2014 Edit will fail without it)." : "\nNEVER use built-in Read/Grep/Glob for code navigation \u2014 use unerr MCP tools instead.";
54768
- return `## REQUIRED: Use unerr Graph Intelligence Tools (19 MCP tools)
55824
+ return `## REQUIRED: Use unerr Graph Intelligence Tools (20 MCP tools)
54769
55825
 
54770
- This project has unerr MCP tools installed. You MUST use these instead of built-in Read/Grep/Glob for code navigation. unerr tools are graph-backed, return results in <5ms, and include project context that built-in tools miss.
55826
+ This project has unerr MCP tools installed. You MUST use these instead of built-in Read/Grep/Glob for code navigation, and \`fetch_url\` instead of built-in WebFetch. unerr tools are graph-backed, return results in <5ms, and include project context that built-in tools miss.
54771
55827
 
54772
55828
  ### Tool Routing (MANDATORY \u2014 match your goal before calling any tool)
54773
55829
 
@@ -54781,6 +55837,7 @@ ${readForEditRow}
54781
55837
  | Get a specific function or class | \`get_entity\` or \`file_read\` with \`entity\` param | Reading entire file |
54782
55838
  | Trace imports/dependencies | \`get_imports\` or \`get_references\` (direction: callees) | Manual import scanning |
54783
55839
  | Find hotspots / high fan-in / blast-radius candidates | \`get_critical_nodes\` | \`get_entity\` (won't show ranked list), guessing |
55840
+ | Fetch a web page by URL | \`fetch_url\` (Defuddle/Readability \u2192 markdown passages \u2192 BM25 ranking when \`prompt\` supplied \u2192 diff-cache) | Built-in WebFetch |
54784
55841
  | Run a shell command | Automatic \u2014 routed through shell intelligence | N/A |
54785
55842
 
54786
55843
  ### FORBIDDEN Patterns (these waste tokens and miss context)
@@ -54791,7 +55848,8 @@ ${readForEditRow}
54791
55848
  - Reading multiple files to understand conventions -> use \`get_conventions\`
54792
55849
  - Guessing code style for new code -> use \`get_conventions\`
54793
55850
  - Guessing which entity has the highest fan-in / is the biggest hotspot -> use \`get_critical_nodes\`
54794
- - Reading a full file when you only need a section -> use \`file_read\` with \`entity\` param or offset/limit${twoStepSection}
55851
+ - Reading a full file when you only need a section -> use \`file_read\` with \`entity\` param or offset/limit
55852
+ - Using built-in WebFetch for a URL -> use \`fetch_url\` (DOM extraction + markdown + BM25 passage selection cuts 5\u201310\xD7 tokens; pass \`prompt\` to rank passages by relevance)${twoStepSection}
54795
55853
 
54796
55854
  ### Tool Reference
54797
55855
 
@@ -54829,6 +55887,14 @@ ${readForEditRow}
54829
55887
  - \`purpose:'explore'\` (default) \u2014 budget-capped, returns outline for large files. Use for browsing and pre-edit understanding.
54830
55888
  - \`purpose:'reference'\` \u2014 tight budget, entity/offset reads only. Use for quick lookups.
54831
55889
 
55890
+ #### Web Fetch (1 tool)
55891
+
55892
+ | Task | Tool | Replaces |
55893
+ |------|------|----------|
55894
+ | Fetch a web page by URL | \`fetch_url\` | Built-in WebFetch |
55895
+
55896
+ \`fetch_url\` strips chrome (nav, footer, ads), converts to markdown, splits into heading-bounded passages, optionally re-ranks passages with BM25 when you pass \`prompt\`, and caches by content hash so re-fetching an unchanged page costs near-zero tokens. Pass \`offset\`/\`limit\` to paginate large pages.
55897
+
54832
55898
  #### Shell Compression (automatic)
54833
55899
 
54834
55900
  All shell commands automatically route through unerr's compression layer. It strips ANSI codes, classifies output (diffs, test results, logs, errors), and returns compressed summaries \u2014 saving tokens without losing critical information.
@@ -55187,7 +56253,8 @@ function registerInstallCommand(program2) {
55187
56253
  }
55188
56254
  process.stderr.write("\n");
55189
56255
  process.stderr.write(
55190
- " \x1B[38;2;161;161;170mRun \x1B[0munerr\x1B[38;2;161;161;170m to start the intelligence engine.\x1B[0m\n"
56256
+ ` \x1B[38;2;161;161;170mRestart ${agentDef.name} to start using unerr.\x1B[0m
56257
+ `
55191
56258
  );
55192
56259
  process.stderr.write("\n");
55193
56260
  }
@@ -55243,29 +56310,13 @@ async function runInstall(cwd, ide, opts) {
55243
56310
  const { daemonSockPath: daemonSockPath2, probeDaemon: probeDaemon2, ensureRepo: ensureRepo2 } = await Promise.resolve().then(() => (init_client(), client_exports));
55244
56311
  const { addRepo: addRepo2, findRepo: findRepo2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
55245
56312
  const sock = daemonSockPath2();
55246
- const daemonRunning = await probeDaemon2(sock);
55247
- if (daemonRunning) {
56313
+ if (await probeDaemon2(sock)) {
55248
56314
  if (!findRepo2(cwd)) {
55249
56315
  addRepo2(cwd, {});
55250
56316
  repoRegistered = true;
55251
- process.stderr.write(
55252
- "\x1B[38;2;52;211;153m\u2713\x1B[0m Registered repo with unerrd.\n"
55253
- );
55254
- }
55255
- try {
55256
- await ensureRepo2(sock, cwd);
55257
- process.stderr.write(
55258
- "\x1B[38;2;52;211;153m\u2713\x1B[0m unerr process started via daemon.\n"
55259
- );
55260
- } catch {
55261
- process.stderr.write(
55262
- "\x1B[38;2;251;191;36m\u26A0\x1B[0m Repo registered but process did not start. It will start on next IDE connection.\n"
55263
- );
55264
56317
  }
55265
- } else {
55266
- process.stderr.write(
55267
- "\x1B[38;2;34;211;238m\u25B8\x1B[0m Daemon not running. To use daemon mode: \x1B[1munerr daemon initialize\x1B[0m\n For standalone mode: run \x1B[1munerr\x1B[0m in this directory.\n"
55268
- );
56318
+ await ensureRepo2(sock, cwd).catch(() => {
56319
+ });
55269
56320
  }
55270
56321
  } catch {
55271
56322
  }
@@ -55417,12 +56468,12 @@ function showSetupInstructions(agentName) {
55417
56468
  w(" After restarting your agent, verify unerr tools are available.\n");
55418
56469
  w(" You should see tools like: get_callers, search_code, file_read,\n");
55419
56470
  w(" file_outline, get_imports, get_callees.\n\n");
55420
- w(" \x1B[1mStep 4: Start unerr\x1B[0m\n");
55421
- w(" \x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m\n");
56471
+ w(" \x1B[1mStep 4: Restart your agent\x1B[0m\n");
56472
+ w(" \x1B[2m\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\x1B[0m\n");
55422
56473
  w(
55423
- " Run \x1B[1munerr\x1B[0m in your project root to start the intelligence engine.\n"
56474
+ " unerr starts automatically when your agent first connects to its MCP server.\n"
55424
56475
  );
55425
- w(" The MCP server will be available at \x1B[1munerr --mcp\x1B[0m.\n");
56476
+ w(" No background service to install \u2014 no boot-time setup needed.\n");
55426
56477
  }
55427
56478
  w("\n");
55428
56479
  }
@@ -55757,8 +56808,8 @@ async function persistPatterns(patterns, _unerrDir) {
55757
56808
  );
55758
56809
  return;
55759
56810
  }
55760
- const { readFileSync: readFileSync70 } = await import("fs");
55761
- const config = JSON.parse(readFileSync70(configPath2, "utf-8"));
56811
+ const { readFileSync: readFileSync71 } = await import("fs");
56812
+ const config = JSON.parse(readFileSync71(configPath2, "utf-8"));
55762
56813
  if (!config.repoId || !config.orgId) {
55763
56814
  warn("Repo not configured \u2014 cannot persist to CozoDB.");
55764
56815
  return;
@@ -55776,7 +56827,7 @@ async function persistPatterns(patterns, _unerrDir) {
55776
56827
  const { CozoGraphStore: CozoGraphStore2 } = await Promise.resolve().then(() => (init_local_graph(), local_graph_exports));
55777
56828
  const store = await CozoGraphStore2.create(db);
55778
56829
  const { unpack } = await import("msgpackr");
55779
- const raw = readFileSync70(snapshotPath2);
56830
+ const raw = readFileSync71(snapshotPath2);
55780
56831
  const buffer = gunzipSync2(raw);
55781
56832
  const envelope = unpack(buffer);
55782
56833
  await store.loadSnapshot(envelope);
@@ -56037,7 +57088,13 @@ function parseSettingsFlags(raw) {
56037
57088
  var write3 = (msg) => process.stderr.write(msg);
56038
57089
  function registerPmCommand(program2) {
56039
57090
  const pm = program2.command("pm").description("Manage the unerr process manager");
56040
- pm.command("start").description("Start the unerr process manager").option("--foreground", "Run in foreground (blocks terminal \u2014 useful for debugging)").option("--detached", "Run in-process as a detached supervisor (used by auto-spawn)").action(async (opts) => {
57091
+ pm.command("start").description("Start the unerr process manager").option(
57092
+ "--foreground",
57093
+ "Run in foreground (blocks terminal \u2014 useful for debugging)"
57094
+ ).option(
57095
+ "--detached",
57096
+ "Run in-process as a detached supervisor (used by auto-spawn)"
57097
+ ).action(async (opts) => {
56041
57098
  if (opts.foreground || opts.detached) {
56042
57099
  process.title = "unerrd";
56043
57100
  const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
@@ -56049,7 +57106,12 @@ function registerPmCommand(program2) {
56049
57106
  const child = spawn(
56050
57107
  process.execPath,
56051
57108
  [unerrBin, "pm", "start", "--detached"],
56052
- { detached: true, stdio: "ignore", windowsHide: true, env: { ...process.env } }
57109
+ {
57110
+ detached: true,
57111
+ stdio: "ignore",
57112
+ windowsHide: true,
57113
+ env: { ...process.env }
57114
+ }
56053
57115
  );
56054
57116
  child.unref();
56055
57117
  const { daemonSockPath: daemonSockPath2, probeDaemon: probeDaemon2 } = await Promise.resolve().then(() => (init_client(), client_exports));
@@ -56066,12 +57128,12 @@ function registerPmCommand(program2) {
56066
57128
  }
56067
57129
  if (running) {
56068
57130
  const { homedir: homedir12 } = await import("os");
56069
- const { join: join83 } = await import("path");
56070
- const pidPath2 = join83(homedir12(), ".unerr", "unerrd.pid");
57131
+ const { join: join85 } = await import("path");
57132
+ const pidPath2 = join85(homedir12(), ".unerr", "unerrd.pid");
56071
57133
  let daemonPid = String(child.pid);
56072
57134
  try {
56073
- const { readFileSync: readFileSync70 } = await import("fs");
56074
- daemonPid = readFileSync70(pidPath2, "utf-8").trim();
57135
+ const { readFileSync: readFileSync71 } = await import("fs");
57136
+ daemonPid = readFileSync71(pidPath2, "utf-8").trim();
56075
57137
  } catch {
56076
57138
  }
56077
57139
  write3(
@@ -56088,8 +57150,8 @@ function registerPmCommand(program2) {
56088
57150
  pm.command("stop").description("Stop the unerrd supervisor").action(async () => {
56089
57151
  const { createConnection: createConnection3 } = await import("net");
56090
57152
  const { globalDir: globalDir2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
56091
- const { join: join83 } = await import("path");
56092
- const sock = join83(globalDir2(), "unerrd.sock");
57153
+ const { join: join85 } = await import("path");
57154
+ const sock = join85(globalDir2(), "unerrd.sock");
56093
57155
  const { existsSync: existsSync82 } = await import("fs");
56094
57156
  if (!existsSync82(sock)) {
56095
57157
  write3("unerrd is not running (no socket found).\n");
@@ -56214,7 +57276,7 @@ function registerPmCommand(program2) {
56214
57276
  pm.command("status").description("List all registered repos and their state").action(async () => {
56215
57277
  const repos = listRepos();
56216
57278
  if (repos.length === 0) {
56217
- write3("No repos registered. Use `unerr daemon add .` to register.\n");
57279
+ write3("No repos registered. Use `unerr pm add .` to register.\n");
56218
57280
  return;
56219
57281
  }
56220
57282
  let liveStatus = null;
@@ -56263,7 +57325,7 @@ function registerPmCommand(program2) {
56263
57325
  }
56264
57326
  write3(
56265
57327
  `
56266
- \x1B[1munerr daemon\x1B[0m \u2014 ${repos.length} repo${repos.length === 1 ? "" : "s"} registered
57328
+ \x1B[1munerr pm\x1B[0m \u2014 ${repos.length} repo${repos.length === 1 ? "" : "s"} registered
56267
57329
 
56268
57330
  `
56269
57331
  );
@@ -56296,7 +57358,7 @@ function registerPmCommand(program2) {
56296
57358
  write3(
56297
57359
  ` ${ni.key}: auto-selected \x1B[1m${ni.auto}\x1B[0m (${ni.reason})
56298
57360
  Alternatives: ${ni.alternatives.join(", ")}
56299
- Override: unerr daemon config ${repo.path} --${toKebab(ni.key)}=${ni.alternatives[0]}
57361
+ Override: unerr pm config ${repo.path} --${toKebab(ni.key)}=${ni.alternatives[0]}
56300
57362
  `
56301
57363
  );
56302
57364
  }
@@ -56327,7 +57389,7 @@ function registerPmCommand(program2) {
56327
57389
  if (!entry) {
56328
57390
  write3(
56329
57391
  `\x1B[38;2;248;113;113m\u2717\x1B[0m Not registered: ${targetPath}
56330
- Register first: unerr daemon add ${targetPath}
57392
+ Register first: unerr pm add ${targetPath}
56331
57393
  `
56332
57394
  );
56333
57395
  process.exitCode = 1;
@@ -56382,7 +57444,7 @@ function registerPmCommand(program2) {
56382
57444
  if (!updated) {
56383
57445
  write3(
56384
57446
  `\x1B[38;2;248;113;113m\u2717\x1B[0m Not registered: ${targetPath}
56385
- Register first: unerr daemon add ${targetPath}
57447
+ Register first: unerr pm add ${targetPath}
56386
57448
  `
56387
57449
  );
56388
57450
  process.exitCode = 1;
@@ -58025,8 +59087,8 @@ function registerStatusCommand(program2) {
58025
59087
  try {
58026
59088
  const logsDir = join53(unerrDir2, "logs");
58027
59089
  if (existsSync50(logsDir)) {
58028
- const { readdirSync: readdirSync19, statSync: statSync16 } = await import("fs");
58029
- const logs = readdirSync19(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
59090
+ const { readdirSync: readdirSync20, statSync: statSync16 } = await import("fs");
59091
+ const logs = readdirSync20(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
58030
59092
  name: f,
58031
59093
  mtime: statSync16(join53(logsDir, f)).mtimeMs
58032
59094
  })).sort((a, b) => b.mtime - a.mtime);
@@ -59034,7 +60096,7 @@ var SOURCE_DIRS = /* @__PURE__ */ new Set([
59034
60096
  ]);
59035
60097
  var DETECTION_THRESHOLD = 5;
59036
60098
  async function detectProjectRoot(cwd) {
59037
- const { readdirSync: readdirSync19 } = await import("fs");
60099
+ const { readdirSync: readdirSync20 } = await import("fs");
59038
60100
  let score = 0;
59039
60101
  const signals = [];
59040
60102
  let hasGit = false;
@@ -59052,7 +60114,7 @@ async function detectProjectRoot(cwd) {
59052
60114
  }
59053
60115
  let rootEntries = [];
59054
60116
  try {
59055
- rootEntries = readdirSync19(cwd, { withFileTypes: true }).map((e) => ({
60117
+ rootEntries = readdirSync20(cwd, { withFileTypes: true }).map((e) => ({
59056
60118
  name: e.name,
59057
60119
  isFile: e.isFile(),
59058
60120
  isDir: e.isDirectory()
@@ -59143,7 +60205,7 @@ async function detectProjectRoot(cwd) {
59143
60205
  }
59144
60206
  function dirHasCodeFile(dir) {
59145
60207
  try {
59146
- const entries = readdirSync19(dir, { withFileTypes: true });
60208
+ const entries = readdirSync20(dir, { withFileTypes: true });
59147
60209
  for (const entry of entries) {
59148
60210
  if (!entry.isFile()) continue;
59149
60211
  const dot = entry.name.lastIndexOf(".");
@@ -59167,7 +60229,7 @@ async function detectProjectRoot(cwd) {
59167
60229
  for (const dirEntry of rootEntries) {
59168
60230
  if (!dirEntry.isDir || dirEntry.name.startsWith(".")) continue;
59169
60231
  if (!SOURCE_DIRS.has(dirEntry.name.toLowerCase())) continue;
59170
- const sourceDir = join82(cwd, dirEntry.name);
60232
+ const sourceDir = join84(cwd, dirEntry.name);
59171
60233
  const srcCodeFile = dirHasCodeFile(sourceDir);
59172
60234
  if (srcCodeFile) {
59173
60235
  score += 6;
@@ -59175,11 +60237,11 @@ async function detectProjectRoot(cwd) {
59175
60237
  break;
59176
60238
  }
59177
60239
  try {
59178
- const subEntries = readdirSync19(sourceDir, { withFileTypes: true });
60240
+ const subEntries = readdirSync20(sourceDir, { withFileTypes: true });
59179
60241
  let found = false;
59180
60242
  for (const sub of subEntries) {
59181
60243
  if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
59182
- const deepCode = dirHasCodeFile(join82(sourceDir, sub.name));
60244
+ const deepCode = dirHasCodeFile(join84(sourceDir, sub.name));
59183
60245
  if (deepCode) {
59184
60246
  score += 6;
59185
60247
  signals.push(`code-src: ${dirEntry.name}/${sub.name}/${deepCode}`);
@@ -59202,10 +60264,10 @@ async function detectProjectRoot(cwd) {
59202
60264
  };
59203
60265
  }
59204
60266
  function readLocalConfig(cwd) {
59205
- const configPath2 = join82(cwd, ".unerr", "config.json");
60267
+ const configPath2 = join84(cwd, ".unerr", "config.json");
59206
60268
  if (!existsSync81(configPath2)) return null;
59207
60269
  try {
59208
- return JSON.parse(readFileSync69(configPath2, "utf-8"));
60270
+ return JSON.parse(readFileSync70(configPath2, "utf-8"));
59209
60271
  } catch {
59210
60272
  return null;
59211
60273
  }
@@ -59328,8 +60390,8 @@ async function daemonChildBoot(cwd) {
59328
60390
  repoId: config.repoId,
59329
60391
  daemonChild: true
59330
60392
  });
59331
- const stateDir = join82(cwd, ".unerr", "state");
59332
- const sockPath2 = join82(stateDir, "proxy.sock");
60393
+ const stateDir = join84(cwd, ".unerr", "state");
60394
+ const sockPath2 = join84(stateDir, "proxy.sock");
59333
60395
  if (process.send) {
59334
60396
  process.send({ type: "ready", sock: sockPath2 });
59335
60397
  }
@@ -59383,8 +60445,18 @@ async function mcpBoot(cwd) {
59383
60445
  keep: 5
59384
60446
  });
59385
60447
  initFileLog(cwd);
60448
+ const { StaticCatalogInterceptor: StaticCatalogInterceptor2 } = await Promise.resolve().then(() => (init_bridge_catalog(), bridge_catalog_exports));
60449
+ const interceptor = new StaticCatalogInterceptor2();
59386
60450
  const stdinPreBuffer = [];
59387
- const preBufferHandler = (chunk) => stdinPreBuffer.push(chunk);
60451
+ const preBufferHandler = (chunk) => {
60452
+ const out = interceptor.ingest(chunk);
60453
+ for (const reply of out.replies) {
60454
+ process.stdout.write(reply);
60455
+ }
60456
+ for (const buf of out.forward) {
60457
+ stdinPreBuffer.push(buf);
60458
+ }
60459
+ };
59388
60460
  process.stdin.on("data", preBufferHandler);
59389
60461
  let stdinEndedEarly = false;
59390
60462
  const earlyEndHandler = () => {
@@ -59446,6 +60518,8 @@ ${antiSignals.length > 0 ? ` Negative signals: ${antiSignals.map((s) => s.repla
59446
60518
  if (process.stdin.listenerCount("data") > 0 && stdinPreBuffer.length >= 0) {
59447
60519
  process.stdin.removeListener("data", preBufferHandler);
59448
60520
  process.stdin.removeListener("end", earlyEndHandler);
60521
+ const partial = interceptor.drainPartial();
60522
+ if (partial) stdinPreBuffer.push(partial);
59449
60523
  bufferForBridge = stdinPreBuffer.slice();
59450
60524
  stdinPreBuffer.length = 0;
59451
60525
  }
@@ -59500,10 +60574,10 @@ async function discoverWithRetry(cwd, daemonSockPath2, probeDaemon2, ensureRepo2
59500
60574
  let attempt = 0;
59501
60575
  let spawnAttempted = false;
59502
60576
  for (; ; ) {
59503
- const repoSock = join82(cwd, ".unerr", "state", "proxy.sock");
60577
+ const repoSock = join84(cwd, ".unerr", "state", "proxy.sock");
59504
60578
  if (existsSync81(repoSock)) {
59505
60579
  const { PidLock: PidLock2 } = await Promise.resolve().then(() => (init_pid_lock(), pid_lock_exports));
59506
- const pidLock = new PidLock2(join82(cwd, ".unerr", "state"));
60580
+ const pidLock = new PidLock2(join84(cwd, ".unerr", "state"));
59507
60581
  const probeResult = await pidLock.probe();
59508
60582
  if (probeResult.alive) {
59509
60583
  return {