@unerr-ai/unerr 0.0.0-beta.6 → 0.0.0-beta.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.
package/dist/cli.js CHANGED
@@ -749,20 +749,20 @@ async function getParser(language) {
749
749
  const parser = new TreeSitter();
750
750
  const langFile = `tree-sitter-${language}.wasm`;
751
751
  try {
752
- const { join: join67 } = await import("path");
753
- const { existsSync: existsSync62 } = await import("fs");
752
+ const { join: join68 } = await import("path");
753
+ const { existsSync: existsSync63 } = await import("fs");
754
754
  const possiblePaths = [
755
- join67(
755
+ join68(
756
756
  process.cwd(),
757
757
  "node_modules",
758
758
  `tree-sitter-${language}`,
759
759
  langFile
760
760
  ),
761
- join67(process.cwd(), "node_modules", "web-tree-sitter", langFile)
761
+ join68(process.cwd(), "node_modules", "web-tree-sitter", langFile)
762
762
  ];
763
763
  let wasmPath = null;
764
764
  for (const p of possiblePaths) {
765
- if (existsSync62(p)) {
765
+ if (existsSync63(p)) {
766
766
  wasmPath = p;
767
767
  break;
768
768
  }
@@ -1656,8 +1656,21 @@ async function createIfMissing(db, existing, name, schema) {
1656
1656
  if (existing.has(name)) return;
1657
1657
  await db.run(schema);
1658
1658
  }
1659
+ async function dropIfStale(db, existing, name, requiredColumns) {
1660
+ if (!existing.has(name)) return;
1661
+ try {
1662
+ const colList = ["key", ...requiredColumns].join(", ");
1663
+ await db.run(
1664
+ `?[] := *${name}{${colList}}, key = '__schema_probe__'`
1665
+ );
1666
+ } catch {
1667
+ await db.run(`::remove ${name}`);
1668
+ existing.delete(name);
1669
+ }
1670
+ }
1659
1671
  async function initSchema(db) {
1660
1672
  const existing = await getExistingRelations(db);
1673
+ await dropIfStale(db, existing, "entities", ["end_line", "is_test"]);
1661
1674
  await createIfMissing(
1662
1675
  db,
1663
1676
  existing,
@@ -1670,6 +1683,7 @@ async function initSchema(db) {
1670
1683
  name: String,
1671
1684
  file_path: String,
1672
1685
  start_line: Int default 0,
1686
+ end_line: Int default 0,
1673
1687
  signature: String default "",
1674
1688
  body: String default "",
1675
1689
  fan_in: Int default 0,
@@ -2339,14 +2353,15 @@ var init_local_graph = __esm({
2339
2353
  async loadSnapshot(envelope) {
2340
2354
  for (const entity of envelope.entities) {
2341
2355
  await this.write(
2342
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level] <- [[$key, $kind, $name, $fp, $sl, $sig, $body, $fi, $fo, $rl]]
2343
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level }`,
2356
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level] <- [[$key, $kind, $name, $fp, $sl, $el, $sig, $body, $fi, $fo, $rl]]
2357
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level }`,
2344
2358
  {
2345
2359
  key: entity.key,
2346
2360
  kind: entity.kind,
2347
2361
  name: entity.name,
2348
2362
  fp: entity.file_path,
2349
2363
  sl: entity.start_line ?? 0,
2364
+ el: entity.end_line ?? 0,
2350
2365
  sig: entity.signature ?? "",
2351
2366
  body: entity.body ?? "",
2352
2367
  fi: entity.fan_in ?? 0,
@@ -2454,17 +2469,18 @@ var init_local_graph = __esm({
2454
2469
  */
2455
2470
  async getEntity(key) {
2456
2471
  const result = await this.query(
2457
- "?[key, kind, name, fp, sl, sig, body, fi, fo, rl, community] := *entities{key, kind, name, file_path: fp, start_line: sl, signature: sig, body, fan_in: fi, fan_out: fo, risk_level: rl, community}, key = $key",
2472
+ "?[key, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] := *entities{key, kind, name, file_path: fp, start_line: sl, end_line: el, signature: sig, body, fan_in: fi, fan_out: fo, risk_level: rl, community}, key = $key",
2458
2473
  { key }
2459
2474
  );
2460
2475
  if (result.rows.length === 0) return null;
2461
- const [k, kind, name, fp, sl, sig, body, fi, fo, rl, community] = result.rows[0];
2476
+ const [k, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] = result.rows[0];
2462
2477
  return {
2463
2478
  key: k,
2464
2479
  kind,
2465
2480
  name,
2466
2481
  file_path: fp,
2467
2482
  start_line: sl,
2483
+ end_line: el,
2468
2484
  signature: sig,
2469
2485
  body,
2470
2486
  fan_in: fi,
@@ -2507,6 +2523,7 @@ var init_local_graph = __esm({
2507
2523
  name,
2508
2524
  file_path: fp,
2509
2525
  start_line: sl,
2526
+ end_line: 0,
2510
2527
  signature: sig,
2511
2528
  body,
2512
2529
  fan_in: fi,
@@ -2550,6 +2567,7 @@ var init_local_graph = __esm({
2550
2567
  name,
2551
2568
  file_path: fp,
2552
2569
  start_line: sl,
2570
+ end_line: 0,
2553
2571
  signature: sig,
2554
2572
  body,
2555
2573
  fan_in: fi,
@@ -2904,14 +2922,14 @@ var init_local_graph = __esm({
2904
2922
  * Degree ranking excluding kind=="file" and kind=="module".
2905
2923
  */
2906
2924
  async getCriticalNodes(topN = 10, communityId) {
2907
- const query = communityId !== void 0 ? `?[key, name, fp, fi, fo, degree, community, label, rl] :=
2925
+ const query = communityId !== void 0 ? `?[key, name, fp, fi, fo, degree, community, label, rl, kind] :=
2908
2926
  *entities{key, name, file_path: fp, fan_in: fi, fan_out: fo, community, risk_level: rl, kind},
2909
2927
  kind != "file", kind != "module",
2910
2928
  community = $cid, community >= 0,
2911
2929
  *communities{id: community, label},
2912
2930
  degree = fi + fo
2913
2931
  :order -degree
2914
- :limit $top_n` : `?[key, name, fp, fi, fo, degree, community, label, rl] :=
2932
+ :limit $top_n` : `?[key, name, fp, fi, fo, degree, community, label, rl, kind] :=
2915
2933
  *entities{key, name, file_path: fp, fan_in: fi, fan_out: fo, community, risk_level: rl, kind},
2916
2934
  kind != "file", kind != "module",
2917
2935
  community >= 0,
@@ -2923,11 +2941,12 @@ var init_local_graph = __esm({
2923
2941
  if (communityId !== void 0) params.cid = communityId;
2924
2942
  const result = await this.query(query, params);
2925
2943
  return result.rows.map((row) => {
2926
- const [key, name, fp, fi, fo, degree, community, label, rl] = row;
2944
+ const [key, name, fp, fi, fo, degree, community, label, rl, kind] = row;
2927
2945
  return {
2928
2946
  key,
2929
2947
  name,
2930
2948
  file_path: fp,
2949
+ kind,
2931
2950
  fan_in: fi,
2932
2951
  fan_out: fo,
2933
2952
  degree,
@@ -2954,19 +2973,20 @@ var init_local_graph = __esm({
2954
2973
  */
2955
2974
  async getEntitiesByFile(filePath) {
2956
2975
  const result = await this.query(
2957
- `?[k, kind, name, fp, sl, sig, body, fi, fo, rl, community] := *file_index[$fp, ek],
2958
- *entities{key: ek, kind, name, file_path: fp, start_line: sl, signature: sig, body, fan_in: fi, fan_out: fo, risk_level: rl, community},
2976
+ `?[k, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] := *file_index[$fp, ek],
2977
+ *entities{key: ek, kind, name, file_path: fp, start_line: sl, end_line: el, signature: sig, body, fan_in: fi, fan_out: fo, risk_level: rl, community},
2959
2978
  k = ek`,
2960
2979
  { fp: filePath }
2961
2980
  );
2962
2981
  return result.rows.map((row) => {
2963
- const [k, kind, name, fp, sl, sig, body, fi, fo, rl, community] = row;
2982
+ const [k, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] = row;
2964
2983
  return {
2965
2984
  key: k,
2966
2985
  kind,
2967
2986
  name,
2968
2987
  file_path: fp,
2969
2988
  start_line: sl,
2989
+ end_line: el,
2970
2990
  signature: sig,
2971
2991
  body,
2972
2992
  fan_in: fi,
@@ -2982,17 +3002,18 @@ var init_local_graph = __esm({
2982
3002
  */
2983
3003
  async findEntityByName(name) {
2984
3004
  const result = await this.query(
2985
- "?[k, kind, name, fp, sl, sig, body, fi, fo, rl, community] := *entities{key: k, kind, name, file_path: fp, start_line: sl, signature: sig, body, fan_in: fi, fan_out: fo, risk_level: rl, community}, name = $name :limit 1",
3005
+ "?[k, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] := *entities{key: k, kind, name, file_path: fp, start_line: sl, end_line: el, signature: sig, body, fan_in: fi, fan_out: fo, risk_level: rl, community}, name = $name :limit 1",
2986
3006
  { name }
2987
3007
  );
2988
3008
  if (result.rows.length === 0) return null;
2989
- const [k, kind, n, fp, sl, sig, body, fi, fo, rl, community] = result.rows[0];
3009
+ const [k, kind, n, fp, sl, el, sig, body, fi, fo, rl, community] = result.rows[0];
2990
3010
  return {
2991
3011
  key: k,
2992
3012
  kind,
2993
3013
  name: n,
2994
3014
  file_path: fp,
2995
3015
  start_line: sl,
3016
+ end_line: el,
2996
3017
  signature: sig,
2997
3018
  body,
2998
3019
  fan_in: fi,
@@ -3704,14 +3725,15 @@ var init_local_graph = __esm({
3704
3725
  const allEntities = [...delta.entities.added, ...delta.entities.updated];
3705
3726
  for (const entity of allEntities) {
3706
3727
  await this.write(
3707
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level] <- [[$key, $kind, $name, $fp, $sl, $sig, $body, $fi, $fo, $rl]]
3708
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level }`,
3728
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level] <- [[$key, $kind, $name, $fp, $sl, $el, $sig, $body, $fi, $fo, $rl]]
3729
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level }`,
3709
3730
  {
3710
3731
  key: entity.key,
3711
3732
  kind: entity.kind,
3712
3733
  name: entity.name,
3713
3734
  fp: entity.file_path,
3714
3735
  sl: entity.start_line ?? 0,
3736
+ el: entity.end_line ?? 0,
3715
3737
  sig: entity.signature ?? "",
3716
3738
  body: entity.body ?? "",
3717
3739
  fi: entity.fan_in ?? 0,
@@ -4578,7 +4600,9 @@ function normalizeAgentName(input) {
4578
4600
  code: "vscode",
4579
4601
  vs: "vscode",
4580
4602
  google: "antigravity",
4581
- "google-antigravity": "antigravity"
4603
+ "google-antigravity": "antigravity",
4604
+ windsurf: "windsurf",
4605
+ cascade: "windsurf"
4582
4606
  };
4583
4607
  return aliases[input.toLowerCase()] ?? input.toLowerCase();
4584
4608
  }
@@ -4626,14 +4650,15 @@ var init_agent_registry = __esm({
4626
4650
  {
4627
4651
  id: "windsurf",
4628
4652
  name: "Windsurf",
4629
- projectConfigPath: ".windsurf/mcp.json",
4653
+ projectConfigPath: ".codeium/windsurf/mcp_config.json",
4654
+ configScope: "global",
4630
4655
  configFormat: "mcp-json",
4631
4656
  dirMarkers: [".windsurf"],
4632
- envVars: [],
4633
- hookSupport: false,
4634
- description: "Codeium's AI IDE",
4635
- instructionFilePath: null,
4636
- instructionFormat: null
4657
+ envVars: ["WINDSURF_EDITOR"],
4658
+ hookSupport: true,
4659
+ description: "Codeium's AI IDE (Cascade)",
4660
+ instructionFilePath: ".windsurf/rules/unerr-instructions.md",
4661
+ instructionFormat: "windsurf-rule"
4637
4662
  },
4638
4663
  {
4639
4664
  id: "zed",
@@ -4677,8 +4702,8 @@ var init_agent_registry = __esm({
4677
4702
  projectConfigPath: ".gemini/settings.json",
4678
4703
  configFormat: "settings-json",
4679
4704
  dirMarkers: [".gemini"],
4680
- envVars: ["GEMINI_API_KEY"],
4681
- hookSupport: false,
4705
+ envVars: ["GEMINI_API_KEY", "GEMINI_CLI"],
4706
+ hookSupport: true,
4682
4707
  description: "Google's CLI coding agent",
4683
4708
  instructionFilePath: "GEMINI.md",
4684
4709
  instructionFormat: "markdown"
@@ -4746,11 +4771,11 @@ var init_agent_registry = __esm({
4746
4771
  {
4747
4772
  id: "github-copilot-cli",
4748
4773
  name: "GitHub Copilot CLI",
4749
- projectConfigPath: ".github/mcp.json",
4750
- configFormat: "mcp-json",
4751
- dirMarkers: [".github"],
4774
+ projectConfigPath: ".copilot/mcp-config.json",
4775
+ configFormat: "copilot-json",
4776
+ dirMarkers: [".github", ".copilot"],
4752
4777
  envVars: ["GITHUB_COPILOT_TOKEN"],
4753
- hookSupport: false,
4778
+ hookSupport: true,
4754
4779
  description: "GitHub's CLI AI assistant",
4755
4780
  instructionFilePath: ".github/copilot-instructions.md",
4756
4781
  instructionFormat: "markdown"
@@ -4794,6 +4819,7 @@ __export(mcp_config_writer_exports, {
4794
4819
  writeMcpConfig: () => writeMcpConfig
4795
4820
  });
4796
4821
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
4822
+ import { homedir } from "os";
4797
4823
  import { dirname as dirname2, join as join7 } from "path";
4798
4824
  function createUnerrServerEntry() {
4799
4825
  return {
@@ -4802,12 +4828,19 @@ function createUnerrServerEntry() {
4802
4828
  args: ["--mcp"]
4803
4829
  };
4804
4830
  }
4831
+ function createCopilotServerEntry() {
4832
+ return {
4833
+ type: "local",
4834
+ command: "unerr",
4835
+ args: ["--mcp"]
4836
+ };
4837
+ }
4805
4838
  function writeMcpConfig(cwd, ide) {
4806
4839
  const agent = getAgent(ide);
4807
4840
  if (!agent) {
4808
4841
  return { path: "", action: "skipped" };
4809
4842
  }
4810
- const configPath = join7(cwd, agent.projectConfigPath);
4843
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4811
4844
  const dir = dirname2(configPath);
4812
4845
  if (!existsSync5(dir)) {
4813
4846
  mkdirSync3(dir, { recursive: true });
@@ -4817,6 +4850,8 @@ function writeMcpConfig(cwd, ide) {
4817
4850
  return writeMcpJsonFormat(configPath);
4818
4851
  case "settings-json":
4819
4852
  return writeSettingsJsonFormat(configPath);
4853
+ case "copilot-json":
4854
+ return writeCopilotJsonFormat(configPath);
4820
4855
  case "continue-config":
4821
4856
  return writeContinueFormat(configPath);
4822
4857
  default:
@@ -4832,7 +4867,7 @@ function writeAllMcpConfigs(cwd, agents) {
4832
4867
  function removeMcpConfig(cwd, ide) {
4833
4868
  const agent = getAgent(ide);
4834
4869
  if (!agent) return false;
4835
- const configPath = join7(cwd, agent.projectConfigPath);
4870
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4836
4871
  if (!existsSync5(configPath)) return false;
4837
4872
  try {
4838
4873
  const existing = JSON.parse(readFileSync6(configPath, "utf-8"));
@@ -4844,6 +4879,9 @@ function removeMcpConfig(cwd, ide) {
4844
4879
  } else if (agent.configFormat === "settings-json") {
4845
4880
  if (!existing.mcp?.servers?.[UNERR_SERVER_KEY]) return false;
4846
4881
  delete existing.mcp.servers[UNERR_SERVER_KEY];
4882
+ } else if (agent.configFormat === "copilot-json") {
4883
+ if (!existing.mcpServers?.[UNERR_SERVER_KEY]) return false;
4884
+ delete existing.mcpServers[UNERR_SERVER_KEY];
4847
4885
  } else {
4848
4886
  if (!existing.mcpServers?.[UNERR_SERVER_KEY]) return false;
4849
4887
  delete existing.mcpServers[UNERR_SERVER_KEY];
@@ -4857,7 +4895,7 @@ function removeMcpConfig(cwd, ide) {
4857
4895
  function isConfigured(cwd, ide) {
4858
4896
  const agent = getAgent(ide);
4859
4897
  if (!agent) return false;
4860
- const configPath = join7(cwd, agent.projectConfigPath);
4898
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4861
4899
  if (!existsSync5(configPath)) return false;
4862
4900
  try {
4863
4901
  const existing = JSON.parse(readFileSync6(configPath, "utf-8"));
@@ -4869,6 +4907,9 @@ function isConfigured(cwd, ide) {
4869
4907
  if (agent.configFormat === "settings-json") {
4870
4908
  return !!existing.mcp?.servers?.[UNERR_SERVER_KEY];
4871
4909
  }
4910
+ if (agent.configFormat === "copilot-json") {
4911
+ return !!existing.mcpServers?.[UNERR_SERVER_KEY];
4912
+ }
4872
4913
  return !!existing.mcpServers?.[UNERR_SERVER_KEY];
4873
4914
  } catch {
4874
4915
  return false;
@@ -4885,6 +4926,14 @@ function generateConfigSnippet(ide) {
4885
4926
  null,
4886
4927
  2
4887
4928
  );
4929
+ case "copilot-json": {
4930
+ const copilotEntry = createCopilotServerEntry();
4931
+ return JSON.stringify(
4932
+ { mcpServers: { [UNERR_SERVER_KEY]: copilotEntry } },
4933
+ null,
4934
+ 2
4935
+ );
4936
+ }
4888
4937
  case "continue-config":
4889
4938
  return JSON.stringify(
4890
4939
  { mcpServers: [{ name: UNERR_SERVER_KEY, ...entry }] },
@@ -4975,6 +5024,29 @@ function writeContinueFormat(configPath) {
4975
5024
  writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
4976
5025
  return { path: configPath, action: "created" };
4977
5026
  }
5027
+ function writeCopilotJsonFormat(configPath) {
5028
+ if (existsSync5(configPath)) {
5029
+ try {
5030
+ const existing = JSON.parse(
5031
+ readFileSync6(configPath, "utf-8")
5032
+ );
5033
+ if (existing.mcpServers?.[UNERR_SERVER_KEY]) {
5034
+ return { path: configPath, action: "skipped" };
5035
+ }
5036
+ existing.mcpServers = existing.mcpServers ?? {};
5037
+ existing.mcpServers[UNERR_SERVER_KEY] = createCopilotServerEntry();
5038
+ writeFileSync2(configPath, JSON.stringify(existing, null, 2), "utf-8");
5039
+ return { path: configPath, action: "updated" };
5040
+ } catch {
5041
+ return { path: configPath, action: "skipped" };
5042
+ }
5043
+ }
5044
+ const config = {
5045
+ mcpServers: { [UNERR_SERVER_KEY]: createCopilotServerEntry() }
5046
+ };
5047
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
5048
+ return { path: configPath, action: "created" };
5049
+ }
4978
5050
  var UNERR_SERVER_KEY;
4979
5051
  var init_mcp_config_writer = __esm({
4980
5052
  "src/config/mcp-config-writer.ts"() {
@@ -5375,7 +5447,7 @@ __export(settings_exports, {
5375
5447
  resolveInferenceEndpoint: () => resolveInferenceEndpoint
5376
5448
  });
5377
5449
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
5378
- import { homedir as homedir3 } from "os";
5450
+ import { homedir as homedir4 } from "os";
5379
5451
  import { join as join12 } from "path";
5380
5452
  import { z } from "zod";
5381
5453
  function resolveEmbeddingEndpoint(config) {
@@ -5406,7 +5478,7 @@ function loadJsonFile(filePath) {
5406
5478
  }
5407
5479
  function loadSettings(cwd) {
5408
5480
  const projectDir = cwd ?? process.cwd();
5409
- const userSettings = loadJsonFile(join12(homedir3(), ".unerr", "settings.json"));
5481
+ const userSettings = loadJsonFile(join12(homedir4(), ".unerr", "settings.json"));
5410
5482
  const projectSettings = loadJsonFile(
5411
5483
  join12(projectDir, ".unerr", "settings.json")
5412
5484
  );
@@ -6562,6 +6634,7 @@ __export(ast_extractor_exports, {
6562
6634
  import { createHash } from "crypto";
6563
6635
  import { existsSync as existsSync14 } from "fs";
6564
6636
  import { join as join19 } from "path";
6637
+ import { fileURLToPath } from "url";
6565
6638
  function detectLanguage2(filePath) {
6566
6639
  const dot = filePath.lastIndexOf(".");
6567
6640
  if (dot < 0) return null;
@@ -6574,13 +6647,18 @@ function extractEntities(content, filePath) {
6574
6647
  const lines = content.split("\n");
6575
6648
  const entities = [];
6576
6649
  const patterns = getPatterns(language);
6650
+ let currentClassName = null;
6577
6651
  for (let i = 0; i < lines.length; i++) {
6578
6652
  const line = lines[i] ?? "";
6579
6653
  for (const pattern of patterns) {
6580
6654
  const match = pattern.regex.exec(line);
6581
6655
  if (match) {
6582
- const name = match[pattern.nameGroup] ?? "";
6583
- if (!name || name.length === 0) continue;
6656
+ const rawName = match[pattern.nameGroup] ?? "";
6657
+ if (!rawName || rawName.length === 0) continue;
6658
+ if (pattern.kind === "class") {
6659
+ currentClassName = rawName;
6660
+ }
6661
+ const name = pattern.kind === "method" && currentClassName ? `${currentClassName}.${rawName}` : rawName;
6584
6662
  const signature = match[pattern.sigGroup ?? 0] ?? "";
6585
6663
  const lineStart = i + 1;
6586
6664
  const lineEnd = findBlockEnd(lines, i, language);
@@ -6593,7 +6671,8 @@ function extractEntities(content, filePath) {
6593
6671
  signature: signature.trim(),
6594
6672
  line_start: lineStart,
6595
6673
  line_end: lineEnd,
6596
- content_hash: contentHash
6674
+ content_hash: contentHash,
6675
+ parent_class: pattern.kind === "method" ? currentClassName ?? void 0 : void 0
6597
6676
  });
6598
6677
  break;
6599
6678
  }
@@ -6625,9 +6704,9 @@ function getPatterns(language) {
6625
6704
  kind: "interface",
6626
6705
  nameGroup: 1
6627
6706
  },
6628
- // method(params) { — inside class
6707
+ // method(params) { — inside class (with optional access modifiers)
6629
6708
  {
6630
- regex: /^\s+(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(\w+)\s*(\([^)]*\))\s*(?::\s*\S+\s*)?[{]/,
6709
+ regex: /^\s+(?:(?:private|protected|public|override|abstract|readonly)\s+)*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(\w+)\s*(\([^)]*\))\s*(?::\s*\S+\s*)?[{]/,
6631
6710
  kind: "method",
6632
6711
  nameGroup: 1,
6633
6712
  sigGroup: 2
@@ -6949,10 +7028,16 @@ async function getTSParser(grammar) {
6949
7028
  }
6950
7029
  const parser = new TreeSitter();
6951
7030
  const wasmFile = `tree-sitter-${grammar}.wasm`;
7031
+ const pkgDir = import.meta.dirname ?? join19(fileURLToPath(import.meta.url), "..");
7032
+ const pkgRoot = join19(pkgDir, "..");
6952
7033
  const possiblePaths = [
6953
7034
  join19(process.cwd(), "node_modules", "tree-sitter-wasms", "out", wasmFile),
6954
7035
  join19(process.cwd(), "node_modules", `tree-sitter-${grammar}`, wasmFile),
6955
- join19(process.cwd(), "node_modules", "web-tree-sitter", wasmFile)
7036
+ join19(process.cwd(), "node_modules", "web-tree-sitter", wasmFile),
7037
+ // Package-relative paths (when unerr is installed globally or in another project)
7038
+ join19(pkgRoot, "node_modules", "tree-sitter-wasms", "out", wasmFile),
7039
+ join19(pkgRoot, "node_modules", `tree-sitter-${grammar}`, wasmFile),
7040
+ join19(pkgRoot, "node_modules", "web-tree-sitter", wasmFile)
6956
7041
  ];
6957
7042
  let wasmPath = null;
6958
7043
  for (const p of possiblePaths) {
@@ -9009,12 +9094,12 @@ var init_decoder = __esm({
9009
9094
  // src/intelligence/indexer/scip/downloader.ts
9010
9095
  import { chmodSync as chmodSync2, createWriteStream, existsSync as existsSync15, mkdirSync as mkdirSync11 } from "fs";
9011
9096
  import { rename, rm, stat } from "fs/promises";
9012
- import { homedir as homedir4 } from "os";
9097
+ import { homedir as homedir5 } from "os";
9013
9098
  import { join as join20 } from "path";
9014
9099
  import { pipeline } from "stream/promises";
9015
9100
  import { createGunzip } from "zlib";
9016
9101
  function getScipBinDir() {
9017
- return join20(homedir4(), ".unerr", "bin");
9102
+ return join20(homedir5(), ".unerr", "bin");
9018
9103
  }
9019
9104
  function getCachedBinaryPath(language) {
9020
9105
  const spec = DOWNLOAD_SPECS[language];
@@ -9366,7 +9451,7 @@ async function detectScipBinary(language) {
9366
9451
  path: null
9367
9452
  };
9368
9453
  }
9369
- function detectPrimaryLanguage(files) {
9454
+ function detectProjectLanguages(files) {
9370
9455
  const counts = {};
9371
9456
  for (const file of files) {
9372
9457
  const dotIdx = file.lastIndexOf(".");
@@ -9377,15 +9462,7 @@ function detectPrimaryLanguage(files) {
9377
9462
  counts[lang] = (counts[lang] ?? 0) + 1;
9378
9463
  }
9379
9464
  }
9380
- let maxLang = null;
9381
- let maxCount = 0;
9382
- for (const [lang, count] of Object.entries(counts)) {
9383
- if (count > maxCount) {
9384
- maxCount = count;
9385
- maxLang = lang;
9386
- }
9387
- }
9388
- return maxLang;
9465
+ return Object.entries(counts).map(([language, fileCount]) => ({ language, fileCount })).sort((a, b) => b.fileCount - a.fileCount);
9389
9466
  }
9390
9467
  var BUNDLED_SCIP, EXTERNAL_SCIP, EXT_TO_SCIP_LANG;
9391
9468
  var init_detector = __esm({
@@ -9609,77 +9686,94 @@ var init_runner = __esm({
9609
9686
  // src/intelligence/indexer/scip/orchestrator.ts
9610
9687
  import { join as join23 } from "path";
9611
9688
  async function enrichWithScip(files, projectRoot, existingEdges, entities) {
9612
- const language = detectPrimaryLanguage(files);
9613
- if (!language) {
9689
+ const languages = detectProjectLanguages(files);
9690
+ if (languages.length === 0) {
9614
9691
  return skipResult(existingEdges, "No supported language detected");
9615
9692
  }
9616
- let binaryInfo = await detectScipBinary(language);
9617
- if (!binaryInfo.available && isAutoDownloadSupported(language)) {
9618
- log5.info(`SCIP binary not found for ${language}, downloading...`);
9619
- const downloadResult = await downloadScipBinary(language, (msg) => {
9620
- log5.info(msg);
9621
- });
9622
- if (downloadResult.success && downloadResult.binaryPath) {
9623
- binaryInfo = await detectScipBinary(language);
9624
- } else {
9625
- const manualInstr = getManualInstallInstructions(language);
9626
- return skipResult(
9627
- existingEdges,
9628
- `SCIP auto-download failed for ${language}: ${downloadResult.error}${manualInstr ? `
9693
+ let currentEdges = markAsStructural(existingEdges);
9694
+ let lastRunResult = null;
9695
+ let lastDecodeResult = null;
9696
+ let lastMergeResult = null;
9697
+ let anySucceeded = false;
9698
+ const processedLanguages = [];
9699
+ for (const { language, fileCount } of languages) {
9700
+ log5.info(`SCIP: processing ${language} (${fileCount} files)`);
9701
+ let binaryInfo = await detectScipBinary(language);
9702
+ if (!binaryInfo.available && isAutoDownloadSupported(language)) {
9703
+ log5.info(`SCIP binary not found for ${language}, downloading...`);
9704
+ const downloadResult = await downloadScipBinary(language, (msg) => {
9705
+ log5.info(msg);
9706
+ });
9707
+ if (downloadResult.success && downloadResult.binaryPath) {
9708
+ binaryInfo = await detectScipBinary(language);
9709
+ } else {
9710
+ const manualInstr = getManualInstallInstructions(language);
9711
+ log5.warn(
9712
+ `SCIP auto-download failed for ${language}: ${downloadResult.error}${manualInstr ? `
9629
9713
  Manual install: ${manualInstr}` : ""}`
9714
+ );
9715
+ continue;
9716
+ }
9717
+ }
9718
+ if (!binaryInfo.available) {
9719
+ log5.info(
9720
+ `SCIP binary not available for ${language}, skipping`
9630
9721
  );
9722
+ continue;
9723
+ }
9724
+ log5.info(
9725
+ `SCIP enrichment: ${language} (${binaryInfo.bundled ? "bundled" : "external"}: ${binaryInfo.binaryName})`
9726
+ );
9727
+ const outputDir = join23(projectRoot, ".unerr", "scip");
9728
+ const binaryPath = binaryInfo.path ?? binaryInfo.binaryName;
9729
+ const runResult = await runScipIndexer({
9730
+ language,
9731
+ binaryPath,
9732
+ projectRoot,
9733
+ outputDir,
9734
+ timeoutMs: 3e4
9735
+ });
9736
+ lastRunResult = runResult;
9737
+ if (!runResult.success || !runResult.outputPath) {
9738
+ log5.warn(`SCIP indexer failed for ${language}: ${runResult.error}`);
9739
+ continue;
9631
9740
  }
9741
+ const decodeResult = await decodeScipOutput(runResult.outputPath);
9742
+ lastDecodeResult = decodeResult;
9743
+ const indexedEdges = currentEdges.map((e) => ({
9744
+ from_key: e.from_key,
9745
+ to_key: e.to_key,
9746
+ type: e.type,
9747
+ file_path: e.file_path,
9748
+ line: e.line
9749
+ }));
9750
+ const { edges: enrichedEdges, result: mergeResult } = mergeScipResults(
9751
+ indexedEdges,
9752
+ decodeResult,
9753
+ entities
9754
+ );
9755
+ currentEdges = enrichedEdges;
9756
+ lastMergeResult = mergeResult;
9757
+ anySucceeded = true;
9758
+ processedLanguages.push(language);
9759
+ log5.info(
9760
+ `SCIP ${language}: ${mergeResult.edgesUpgraded} edges upgraded to compiler-verified (${Math.round(runResult.durationMs)}ms)`
9761
+ );
9632
9762
  }
9633
- if (!binaryInfo.available) {
9763
+ if (!anySucceeded) {
9634
9764
  return skipResult(
9635
9765
  existingEdges,
9636
- `SCIP binary not available for ${language} (not bundled, not on PATH, no auto-download)`
9766
+ `No SCIP binary available for any detected language (${languages.map((l) => l.language).join(", ")})`
9637
9767
  );
9638
9768
  }
9639
- log5.info(
9640
- `SCIP enrichment: ${language} (${binaryInfo.bundled ? "bundled" : "external"}: ${binaryInfo.binaryName})`
9641
- );
9642
- const outputDir = join23(projectRoot, ".unerr", "scip");
9643
- const binaryPath = binaryInfo.path ?? binaryInfo.binaryName;
9644
- const runResult = await runScipIndexer({
9645
- language,
9646
- binaryPath,
9647
- projectRoot,
9648
- outputDir,
9649
- timeoutMs: 3e4
9650
- // 30s max for inline indexing
9651
- });
9652
- if (!runResult.success || !runResult.outputPath) {
9653
- log5.warn(`SCIP indexer failed for ${language}: ${runResult.error}`);
9654
- return {
9655
- language,
9656
- binaryAvailable: true,
9657
- bundled: binaryInfo.bundled,
9658
- runResult,
9659
- decodeResult: null,
9660
- mergeResult: null,
9661
- enrichedEdges: markAsStructural(existingEdges),
9662
- skipped: false,
9663
- skipReason: null
9664
- };
9665
- }
9666
- const decodeResult = await decodeScipOutput(runResult.outputPath);
9667
- const { edges: enrichedEdges, result: mergeResult } = mergeScipResults(
9668
- existingEdges,
9669
- decodeResult,
9670
- entities
9671
- );
9672
- log5.info(
9673
- `SCIP enrichment complete: ${mergeResult.edgesUpgraded} edges upgraded to compiler-verified (${Math.round(runResult.durationMs)}ms)`
9674
- );
9675
9769
  return {
9676
- language,
9770
+ language: processedLanguages.join("+"),
9677
9771
  binaryAvailable: true,
9678
- bundled: binaryInfo.bundled,
9679
- runResult,
9680
- decodeResult,
9681
- mergeResult,
9682
- enrichedEdges,
9772
+ bundled: false,
9773
+ runResult: lastRunResult,
9774
+ decodeResult: lastDecodeResult,
9775
+ mergeResult: lastMergeResult,
9776
+ enrichedEdges: currentEdges,
9683
9777
  skipped: false,
9684
9778
  skipReason: null
9685
9779
  };
@@ -9720,6 +9814,10 @@ var init_orchestrator = __esm({
9720
9814
  });
9721
9815
 
9722
9816
  // src/intelligence/local-convention-detector.ts
9817
+ var local_convention_detector_exports = {};
9818
+ __export(local_convention_detector_exports, {
9819
+ detectLocalConventions: () => detectLocalConventions
9820
+ });
9723
9821
  import { dirname as dirname4 } from "path";
9724
9822
  async function detectLocalConventions(db) {
9725
9823
  const start = Date.now();
@@ -10281,6 +10379,7 @@ async function indexLocalProject(projectRoot, graphStore, repoId, opts) {
10281
10379
  name: entity.name,
10282
10380
  file_path: relPath,
10283
10381
  start_line: entity.line_start,
10382
+ end_line: entity.line_end,
10284
10383
  signature: entity.signature,
10285
10384
  body: "",
10286
10385
  // Skip body storage for indexing performance
@@ -10444,6 +10543,7 @@ async function reindexFile(projectRoot, filePath, graphStore, repoId) {
10444
10543
  name: e.name,
10445
10544
  file_path: relPath,
10446
10545
  start_line: e.line_start,
10546
+ end_line: e.line_end,
10447
10547
  signature: e.signature,
10448
10548
  body: "",
10449
10549
  fan_in: 0,
@@ -10860,12 +10960,13 @@ async function populateCozoDB(graphStore, entities, edges) {
10860
10960
  for (const fp of filePaths) {
10861
10961
  try {
10862
10962
  await graphStore.db.run(
10863
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, "module", $name, $fp, 0, "", "", 0, 0, "normal", $is_test]]
10864
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
10963
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, "module", $name, $fp, 0, 0, "", "", 0, 0, "normal", $is_test]]
10964
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
10865
10965
  { key: `file:${fp}`, name: basename2(fp), fp, is_test: isTestFile(fp) }
10866
10966
  );
10867
10967
  } catch (err) {
10868
- process.stderr.write(`[unerr] \u26A0 File entity insert failed for ${fp}: ${err instanceof Error ? err.message : String(err)}
10968
+ const msg = err instanceof Error ? err.message : JSON.stringify(err);
10969
+ process.stderr.write(`[unerr] \u26A0 File entity insert failed for ${fp}: ${msg}
10869
10970
  `);
10870
10971
  }
10871
10972
  }
@@ -10909,14 +11010,15 @@ async function populateCozoDB(graphStore, entities, edges) {
10909
11010
  async function insertEntity(graphStore, entity) {
10910
11011
  try {
10911
11012
  await graphStore.db.run(
10912
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, $kind, $name, $fp, $sl, $sig, $body, $fi, $fo, $rl, $is_test]]
10913
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
11013
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, $kind, $name, $fp, $sl, $el, $sig, $body, $fi, $fo, $rl, $is_test]]
11014
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
10914
11015
  {
10915
11016
  key: entity.key,
10916
11017
  kind: entity.kind,
10917
11018
  name: entity.name,
10918
11019
  fp: entity.file_path,
10919
11020
  sl: entity.start_line ?? 0,
11021
+ el: entity.end_line ?? 0,
10920
11022
  sig: entity.signature ?? "",
10921
11023
  body: entity.body ?? "",
10922
11024
  fi: entity.fan_in ?? 0,
@@ -11737,7 +11839,7 @@ import {
11737
11839
  unlinkSync as unlinkSync4,
11738
11840
  writeFileSync as writeFileSync12
11739
11841
  } from "fs";
11740
- import { homedir as homedir5 } from "os";
11842
+ import { homedir as homedir6 } from "os";
11741
11843
  import { dirname as dirname6, join as join29 } from "path";
11742
11844
  function getSkillDir(ide, cwd) {
11743
11845
  switch (ide) {
@@ -11761,9 +11863,9 @@ function getSkillDir(ide, cwd) {
11761
11863
  };
11762
11864
  case "windsurf":
11763
11865
  return {
11764
- dir: join29(cwd, ".windsurf", "rules"),
11866
+ dir: join29(cwd, ".windsurf", "skills"),
11765
11867
  ext: ".md",
11766
- dirPerSkill: false
11868
+ dirPerSkill: true
11767
11869
  };
11768
11870
  case "zed":
11769
11871
  return {
@@ -11771,6 +11873,18 @@ function getSkillDir(ide, cwd) {
11771
11873
  ext: ".md",
11772
11874
  dirPerSkill: false
11773
11875
  };
11876
+ case "gemini-cli":
11877
+ return {
11878
+ dir: join29(cwd, ".gemini", "skills"),
11879
+ ext: ".md",
11880
+ dirPerSkill: true
11881
+ };
11882
+ case "github-copilot-cli":
11883
+ return {
11884
+ dir: join29(cwd, ".github", "skills"),
11885
+ ext: ".md",
11886
+ dirPerSkill: true
11887
+ };
11774
11888
  case "antigravity":
11775
11889
  return {
11776
11890
  dir: join29(cwd, ".agents", "skills"),
@@ -11791,7 +11905,22 @@ function writeSkillFile(skill, ide, skillDir, ext, dirPerSkill) {
11791
11905
  const skillName = `unerr-${baseName}`;
11792
11906
  let filePath;
11793
11907
  let content;
11794
- if (dirPerSkill && ide === "antigravity") {
11908
+ if (dirPerSkill && ide === "windsurf") {
11909
+ const skillSubDir = join29(skillDir, skillName);
11910
+ mkdirSync16(skillSubDir, { recursive: true });
11911
+ filePath = join29(skillSubDir, "SKILL.md");
11912
+ content = formatWindsurfSkillMd(skill);
11913
+ } else if (dirPerSkill && ide === "gemini-cli") {
11914
+ const skillSubDir = join29(skillDir, skillName);
11915
+ mkdirSync16(skillSubDir, { recursive: true });
11916
+ filePath = join29(skillSubDir, "SKILL.md");
11917
+ content = formatGeminiSkill(skill);
11918
+ } else if (dirPerSkill && ide === "github-copilot-cli") {
11919
+ const skillSubDir = join29(skillDir, skillName);
11920
+ mkdirSync16(skillSubDir, { recursive: true });
11921
+ filePath = join29(skillSubDir, "SKILL.md");
11922
+ content = formatCopilotSkill(skill);
11923
+ } else if (dirPerSkill && ide === "antigravity") {
11795
11924
  const skillSubDir = join29(skillDir, skillName);
11796
11925
  mkdirSync16(skillSubDir, { recursive: true });
11797
11926
  filePath = join29(skillSubDir, "SKILL.md");
@@ -11804,9 +11933,6 @@ function writeSkillFile(skill, ide, skillDir, ext, dirPerSkill) {
11804
11933
  } else if (ide === "cursor" && ext === ".mdc") {
11805
11934
  filePath = join29(skillDir, `${skillName}${ext}`);
11806
11935
  content = formatCursorSkill(skill);
11807
- } else if (ide === "windsurf") {
11808
- filePath = join29(skillDir, `${skillName}${ext}`);
11809
- content = formatWindsurfSkill(skill);
11810
11936
  } else {
11811
11937
  filePath = join29(skillDir, `${skillName}${ext}`);
11812
11938
  content = formatMarkdownSkill(skill);
@@ -11888,53 +12014,45 @@ alwaysApply: ${alwaysApply}
11888
12014
  ${skill.content}
11889
12015
  `;
11890
12016
  }
11891
- function formatWindsurfSkill(skill) {
12017
+ function formatMarkdownSkill(skill) {
11892
12018
  const trigger = skill.trigger;
11893
12019
  const triggerType = trigger?.type ?? "always";
11894
- let wsType;
11895
- switch (triggerType) {
11896
- case "always":
11897
- wsType = "always_on";
11898
- break;
11899
- case "auto":
11900
- wsType = "glob";
11901
- break;
11902
- case "agent-requested":
11903
- wsType = "model_decision";
11904
- break;
11905
- case "manual":
11906
- wsType = "manual";
11907
- break;
11908
- default:
11909
- wsType = "always_on";
11910
- }
11911
12020
  const globLine = trigger?.globs && trigger.globs.length > 0 ? `
11912
12021
  globs: ${JSON.stringify(trigger.globs)}` : "";
11913
- const truncatedContent = skill.content.length > 5800 ? `${skill.content.slice(0, 5800)}
11914
-
11915
- [Truncated \u2014 full content exceeds Windsurf 6K limit]` : skill.content;
11916
12022
  return `---
11917
- trigger: ${wsType}${globLine}
11918
12023
  description: "${skill.description}"
12024
+ trigger: ${triggerType}${globLine}
12025
+ version: "${skill.version ?? "1.0.0"}"
11919
12026
  ---
11920
12027
 
11921
12028
  # ${skill.name}
11922
12029
 
11923
- ${truncatedContent}
12030
+ ${skill.content}
11924
12031
  `;
11925
12032
  }
11926
- function formatMarkdownSkill(skill) {
11927
- const trigger = skill.trigger;
11928
- const triggerType = trigger?.type ?? "always";
11929
- const globLine = trigger?.globs && trigger.globs.length > 0 ? `
11930
- globs: ${JSON.stringify(trigger.globs)}` : "";
12033
+ function formatWindsurfSkillMd(skill) {
11931
12034
  return `---
11932
- description: "${skill.description}"
11933
- trigger: ${triggerType}${globLine}
11934
- version: "${skill.version ?? "1.0.0"}"
12035
+ name: ${skill.name}
12036
+ description: ${skill.description}
11935
12037
  ---
11936
12038
 
11937
- # ${skill.name}
12039
+ ${skill.content}
12040
+ `;
12041
+ }
12042
+ function formatGeminiSkill(skill) {
12043
+ return `---
12044
+ name: ${skill.name}
12045
+ description: ${skill.description}
12046
+ ---
12047
+
12048
+ ${skill.content}
12049
+ `;
12050
+ }
12051
+ function formatCopilotSkill(skill) {
12052
+ return `---
12053
+ name: ${skill.name}
12054
+ description: ${skill.description}
12055
+ ---
11938
12056
 
11939
12057
  ${skill.content}
11940
12058
  `;
@@ -12020,7 +12138,7 @@ function scanSkillDirectory(dir) {
12020
12138
  return skills;
12021
12139
  }
12022
12140
  function loadLocalDirectorySkills(cwd) {
12023
- const userDir = join29(homedir5(), ".unerr", "skills");
12141
+ const userDir = join29(homedir6(), ".unerr", "skills");
12024
12142
  const projectDir = join29(cwd, ".unerr", "skills");
12025
12143
  const userSkills = scanSkillDirectory(userDir);
12026
12144
  const projectSkills = scanSkillDirectory(projectDir);
@@ -13085,9 +13203,9 @@ function createEmptyAllTime() {
13085
13203
  function loadStats() {
13086
13204
  const currentWeek = getWeekStart();
13087
13205
  try {
13088
- const { readFileSync: readFileSync56 } = __require("fs");
13206
+ const { readFileSync: readFileSync57 } = __require("fs");
13089
13207
  const raw = JSON.parse(
13090
- readFileSync56(getStatsPath(), "utf-8")
13208
+ readFileSync57(getStatsPath(), "utf-8")
13091
13209
  );
13092
13210
  if (raw.version !== 1) {
13093
13211
  return {
@@ -13800,9 +13918,9 @@ function getCumulativePath() {
13800
13918
  function loadCumulativeStats() {
13801
13919
  const currentWeek = getWeekStart2();
13802
13920
  try {
13803
- const { readFileSync: readFileSync56 } = __require("fs");
13921
+ const { readFileSync: readFileSync57 } = __require("fs");
13804
13922
  const raw = JSON.parse(
13805
- readFileSync56(getCumulativePath(), "utf-8")
13923
+ readFileSync57(getCumulativePath(), "utf-8")
13806
13924
  );
13807
13925
  if (raw.weekStart !== currentWeek) {
13808
13926
  return {
@@ -13863,9 +13981,9 @@ function createEmptyCumulativeLocal(weekStart) {
13863
13981
  function loadCumulativeLocalStats() {
13864
13982
  const currentWeek = getWeekStart2();
13865
13983
  try {
13866
- const { readFileSync: readFileSync56 } = __require("fs");
13984
+ const { readFileSync: readFileSync57 } = __require("fs");
13867
13985
  const raw = JSON.parse(
13868
- readFileSync56(getCumulativeLocalPath(), "utf-8")
13986
+ readFileSync57(getCumulativeLocalPath(), "utf-8")
13869
13987
  );
13870
13988
  if (raw.weekStartDate !== currentWeek) {
13871
13989
  return createEmptyCumulativeLocal(currentWeek);
@@ -16521,6 +16639,250 @@ var init_record_fact = __esm({
16521
16639
  }
16522
16640
  });
16523
16641
 
16642
+ // src/intelligence/fact-generator.ts
16643
+ var fact_generator_exports = {};
16644
+ __export(fact_generator_exports, {
16645
+ generateFromCausalBridge: () => generateFromCausalBridge,
16646
+ generateFromConventions: () => generateFromConventions,
16647
+ generateFromNegativeKnowledge: () => generateFromNegativeKnowledge,
16648
+ generateFromSessionAnalysis: () => generateFromSessionAnalysis,
16649
+ runFactGenerationPipeline: () => runFactGenerationPipeline
16650
+ });
16651
+ import { existsSync as existsSync37, readdirSync as readdirSync9, readFileSync as readFileSync34 } from "fs";
16652
+ import { join as join44 } from "path";
16653
+ async function runFactGenerationPipeline(factStore2, unerrDir) {
16654
+ const results = [];
16655
+ try {
16656
+ const sessionResult = await generateFromSessionAnalysis(factStore2, unerrDir);
16657
+ if (sessionResult) results.push(sessionResult);
16658
+ } catch {
16659
+ }
16660
+ return results;
16661
+ }
16662
+ async function generateFromConventions(factStore2, conventions) {
16663
+ let created = 0;
16664
+ let reinforced = 0;
16665
+ const details = [];
16666
+ const MIN_CONFIDENCE = 0.7;
16667
+ const MIN_FREQUENCY = 5;
16668
+ for (const conv of conventions) {
16669
+ if (conv.confidence < MIN_CONFIDENCE) continue;
16670
+ if (conv.frequency < MIN_FREQUENCY) continue;
16671
+ const content = formatConventionFact(conv);
16672
+ if (content.length > 280) continue;
16673
+ const input = {
16674
+ fact_type: "semantic",
16675
+ scope: inferScopeFromConvention(conv),
16676
+ subject: conv.name,
16677
+ content,
16678
+ source: "convention_detector",
16679
+ base_confidence: Math.min(0.9, conv.confidence)
16680
+ };
16681
+ const factId = await factStore2.createFact(input);
16682
+ if (factId) {
16683
+ const isNew = !details.some((d) => d.includes(conv.name));
16684
+ if (isNew) {
16685
+ created++;
16686
+ details.push(`[convention] ${conv.name}: ${content.slice(0, 80)}`);
16687
+ } else {
16688
+ reinforced++;
16689
+ }
16690
+ }
16691
+ }
16692
+ return { created, reinforced, source: "convention_detector", details };
16693
+ }
16694
+ async function generateFromNegativeKnowledge(factStore2, corrections) {
16695
+ let created = 0;
16696
+ let reinforced = 0;
16697
+ const details = [];
16698
+ for (const correction of corrections) {
16699
+ const content = correction.reason.slice(0, 280);
16700
+ const input = {
16701
+ fact_type: "negative",
16702
+ scope: correction.entityKey,
16703
+ subject: correction.entityKey,
16704
+ content,
16705
+ source: "negative_knowledge",
16706
+ base_confidence: 0.9
16707
+ };
16708
+ const factId = await factStore2.createFact(input);
16709
+ if (factId) {
16710
+ created++;
16711
+ details.push(`[negative] ${correction.entityKey}: ${content.slice(0, 60)}`);
16712
+ }
16713
+ }
16714
+ return { created, reinforced, source: "negative_knowledge", details };
16715
+ }
16716
+ async function generateFromCausalBridge(factStore2, events) {
16717
+ let created = 0;
16718
+ let reinforced = 0;
16719
+ const details = [];
16720
+ for (const event of events) {
16721
+ if (event.action === "survived") {
16722
+ const content = `Change to ${event.entity_key} on ${new Date(event.timestamp).toISOString().split("T")[0]} survived \u2014 shipped to ${event.branch}`;
16723
+ const input = {
16724
+ fact_type: "episodic",
16725
+ scope: event.entity_key,
16726
+ subject: event.entity_key,
16727
+ content: content.slice(0, 280),
16728
+ source: "causal_bridge",
16729
+ base_confidence: 1
16730
+ };
16731
+ const factId = await factStore2.createFact(input);
16732
+ if (factId) {
16733
+ created++;
16734
+ details.push(`[episodic:survived] ${event.entity_key}`);
16735
+ }
16736
+ } else if (event.action === "reverted") {
16737
+ const content = `Change to ${event.entity_key} was reverted within 24h \u2014 approach was incorrect`;
16738
+ const input = {
16739
+ fact_type: "negative",
16740
+ scope: event.entity_key,
16741
+ subject: event.entity_key,
16742
+ content: content.slice(0, 280),
16743
+ source: "causal_bridge",
16744
+ base_confidence: 0.85
16745
+ };
16746
+ const factId = await factStore2.createFact(input);
16747
+ if (factId) {
16748
+ created++;
16749
+ details.push(`[negative:reverted] ${event.entity_key}`);
16750
+ }
16751
+ }
16752
+ }
16753
+ return { created, reinforced, source: "causal_bridge", details };
16754
+ }
16755
+ async function generateFromSessionAnalysis(factStore2, unerrDir) {
16756
+ let created = 0;
16757
+ let reinforced = 0;
16758
+ const details = [];
16759
+ const summaries = loadRecentSessions(unerrDir, 20);
16760
+ if (summaries.length < 3) {
16761
+ return { created: 0, reinforced: 0, source: "session_analysis", details: [] };
16762
+ }
16763
+ const hotFiles = detectHotFiles(summaries);
16764
+ for (const [file, frequency] of hotFiles) {
16765
+ const pct = Math.round(frequency * 100);
16766
+ const content = `${file} is accessed in ${pct}% of sessions (hot file)`;
16767
+ const confidence = Math.min(0.85, 0.4 + frequency * 0.5);
16768
+ const input = {
16769
+ fact_type: "procedural",
16770
+ scope: file,
16771
+ subject: file,
16772
+ content,
16773
+ source: "session_analysis",
16774
+ base_confidence: confidence
16775
+ };
16776
+ const factId = await factStore2.createFact(input);
16777
+ if (factId) {
16778
+ created++;
16779
+ details.push(`[procedural:hot] ${file} (${pct}%)`);
16780
+ }
16781
+ }
16782
+ const highRevertFiles = detectHighRevertFiles(summaries);
16783
+ for (const [file, revertRate] of highRevertFiles) {
16784
+ const pct = Math.round(revertRate * 100);
16785
+ const content = `${file} has a ${pct}% revert rate across sessions \u2014 changes here are fragile`;
16786
+ const input = {
16787
+ fact_type: "negative",
16788
+ scope: file,
16789
+ subject: file,
16790
+ content: content.slice(0, 280),
16791
+ source: "session_analysis",
16792
+ base_confidence: Math.min(0.85, 0.5 + revertRate * 0.4)
16793
+ };
16794
+ const factId = await factStore2.createFact(input);
16795
+ if (factId) {
16796
+ created++;
16797
+ details.push(`[negative:fragile] ${file} (${pct}% revert rate)`);
16798
+ }
16799
+ }
16800
+ return { created, reinforced, source: "session_analysis", details };
16801
+ }
16802
+ function loadRecentSessions(unerrDir, limit) {
16803
+ const sessionsDir = join44(unerrDir, "sessions");
16804
+ if (!existsSync37(sessionsDir)) return [];
16805
+ try {
16806
+ const files = readdirSync9(sessionsDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
16807
+ const summaries = [];
16808
+ for (const file of files) {
16809
+ try {
16810
+ const content = readFileSync34(join44(sessionsDir, file), "utf-8");
16811
+ const lines = content.trim().split("\n").filter(Boolean);
16812
+ const lastLine = lines[lines.length - 1];
16813
+ if (lastLine) {
16814
+ summaries.push(JSON.parse(lastLine));
16815
+ }
16816
+ } catch {
16817
+ }
16818
+ }
16819
+ return summaries;
16820
+ } catch {
16821
+ return [];
16822
+ }
16823
+ }
16824
+ function detectHotFiles(summaries) {
16825
+ const fileCounts = /* @__PURE__ */ new Map();
16826
+ const total = summaries.length;
16827
+ for (const session of summaries) {
16828
+ const seen = /* @__PURE__ */ new Set();
16829
+ for (const file of session.files_modified) {
16830
+ if (!seen.has(file)) {
16831
+ seen.add(file);
16832
+ fileCounts.set(file, (fileCounts.get(file) ?? 0) + 1);
16833
+ }
16834
+ }
16835
+ }
16836
+ const HOT_THRESHOLD = 0.5;
16837
+ return [...fileCounts.entries()].map(([file, count]) => [file, count / total]).filter(([_, freq]) => freq >= HOT_THRESHOLD).sort((a, b) => b[1] - a[1]).slice(0, 10);
16838
+ }
16839
+ function detectHighRevertFiles(summaries) {
16840
+ const fileModifiedCount = /* @__PURE__ */ new Map();
16841
+ const fileRevertCount = /* @__PURE__ */ new Map();
16842
+ for (const session of summaries) {
16843
+ for (const file of session.files_modified) {
16844
+ fileModifiedCount.set(file, (fileModifiedCount.get(file) ?? 0) + 1);
16845
+ if (session.revert_count > 0) {
16846
+ fileRevertCount.set(file, (fileRevertCount.get(file) ?? 0) + 1);
16847
+ }
16848
+ }
16849
+ }
16850
+ const HIGH_REVERT_THRESHOLD = 0.4;
16851
+ const MIN_MODIFICATIONS = 3;
16852
+ return [...fileModifiedCount.entries()].filter(([_, count]) => count >= MIN_MODIFICATIONS).map(([file, count]) => {
16853
+ const reverts = fileRevertCount.get(file) ?? 0;
16854
+ return [file, reverts / count];
16855
+ }).filter(([_, rate]) => rate >= HIGH_REVERT_THRESHOLD).sort((a, b) => b[1] - a[1]).slice(0, 5);
16856
+ }
16857
+ function formatConventionFact(conv) {
16858
+ const pct = Math.round(conv.confidence * 100);
16859
+ switch (conv.kind) {
16860
+ case "naming":
16861
+ return `Naming convention: ${conv.name} (${pct}% confidence, ${conv.frequency} entities)`;
16862
+ case "structure":
16863
+ return `Structure pattern: ${conv.name} (${pct}% confidence)`;
16864
+ case "import_direction":
16865
+ return `Import convention: ${conv.name} (${pct}% confidence)`;
16866
+ default:
16867
+ return `Convention: ${conv.name} (${pct}% confidence)`;
16868
+ }
16869
+ }
16870
+ function inferScopeFromConvention(conv) {
16871
+ if (conv.exemplarKeys.length > 0) {
16872
+ const first = conv.exemplarKeys[0];
16873
+ const parts = first.split("/");
16874
+ if (parts.length >= 2) {
16875
+ return parts.slice(0, 2).join("/");
16876
+ }
16877
+ }
16878
+ return "project";
16879
+ }
16880
+ var init_fact_generator = __esm({
16881
+ "src/intelligence/fact-generator.ts"() {
16882
+ "use strict";
16883
+ }
16884
+ });
16885
+
16524
16886
  // src/proxy/auto-bootstrap.ts
16525
16887
  var auto_bootstrap_exports = {};
16526
16888
  __export(auto_bootstrap_exports, {
@@ -16969,8 +17331,8 @@ var init_model_pricing = __esm({
16969
17331
  });
16970
17332
 
16971
17333
  // src/tracking/entity-rewind.ts
16972
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync20 } from "fs";
16973
- import { join as join44 } from "path";
17334
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync20 } from "fs";
17335
+ import { join as join45 } from "path";
16974
17336
  async function revertEntity(entityName, localGraph, projectRoot, filePath) {
16975
17337
  const driftEntity = await findDriftEntity(localGraph, entityName, filePath);
16976
17338
  if (!driftEntity) {
@@ -16982,7 +17344,7 @@ async function revertEntity(entityName, localGraph, projectRoot, filePath) {
16982
17344
  error: `No drifted entity found: "${entityName}"${filePath ? ` in ${filePath}` : ""}`
16983
17345
  };
16984
17346
  }
16985
- const absPath = join44(projectRoot, driftEntity.file_path);
17347
+ const absPath = join45(projectRoot, driftEntity.file_path);
16986
17348
  if (driftEntity.drift_status === "added") {
16987
17349
  return removeAddedEntity(absPath, driftEntity, localGraph);
16988
17350
  }
@@ -17047,7 +17409,7 @@ async function findDriftEntity(localGraph, entityName, filePath) {
17047
17409
  }
17048
17410
  async function removeAddedEntity(absPath, entity, localGraph) {
17049
17411
  try {
17050
- const content = readFileSync34(absPath, "utf-8");
17412
+ const content = readFileSync35(absPath, "utf-8");
17051
17413
  const lines = content.split("\n");
17052
17414
  const startIdx = entity.line_start - 1;
17053
17415
  const endIdx = entity.line_end;
@@ -17082,7 +17444,7 @@ async function restoreDeletedEntity(absPath, entity, localGraph) {
17082
17444
  };
17083
17445
  }
17084
17446
  try {
17085
- const content = readFileSync34(absPath, "utf-8");
17447
+ const content = readFileSync35(absPath, "utf-8");
17086
17448
  const lines = content.split("\n");
17087
17449
  const previousLines = entity.previous_body.split("\n");
17088
17450
  const insertIdx = Math.min(entity.line_start - 1, lines.length);
@@ -17118,7 +17480,7 @@ async function restoreModifiedEntity(absPath, entity, localGraph) {
17118
17480
  };
17119
17481
  }
17120
17482
  try {
17121
- const content = readFileSync34(absPath, "utf-8");
17483
+ const content = readFileSync35(absPath, "utf-8");
17122
17484
  const lines = content.split("\n");
17123
17485
  const previousLines = entity.previous_body.split("\n");
17124
17486
  const startIdx = entity.line_start - 1;
@@ -17530,7 +17892,7 @@ __export(file_outline_exports, {
17530
17892
  buildFileOutline: () => buildFileOutline,
17531
17893
  fileOutlineTool: () => fileOutlineTool
17532
17894
  });
17533
- import { existsSync as existsSync37, readFileSync as readFileSync35 } from "fs";
17895
+ import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
17534
17896
  import { relative as relative2, resolve } from "path";
17535
17897
  function normRisk(rl) {
17536
17898
  const x2 = (rl ?? "low").toLowerCase();
@@ -17589,10 +17951,10 @@ function detectExportedNames(lines, scanLimit) {
17589
17951
  async function buildFileOutline(params) {
17590
17952
  const abs = resolve(params.cwd, params.filePathArg);
17591
17953
  const rel = relative2(params.cwd, abs).replace(/\\/g, "/") || params.filePathArg;
17592
- if (!existsSync37(abs)) {
17954
+ if (!existsSync38(abs)) {
17593
17955
  throw new Error(`File not found: ${abs}`);
17594
17956
  }
17595
- const raw = readFileSync35(abs);
17957
+ const raw = readFileSync36(abs);
17596
17958
  const sampleEnd = Math.min(raw.length, 8192);
17597
17959
  for (let i = 0; i < sampleEnd; i++) {
17598
17960
  if (raw[i] === 0) {
@@ -17740,7 +18102,7 @@ __export(file_read_protocol_exports, {
17740
18102
  runFileReadForRouter: () => runFileReadForRouter,
17741
18103
  runFileReadTool: () => runFileReadTool
17742
18104
  });
17743
- import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
18105
+ import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
17744
18106
  import { relative as relative3, resolve as resolve2 } from "path";
17745
18107
  function isGeneratedPath(rel) {
17746
18108
  return /(?:^|\/)node_modules\/|(?:^|\/)dist\/|\/\.next\/|\.generated\./.test(
@@ -17815,10 +18177,10 @@ async function runFileReadForRouter(args, ctx) {
17815
18177
  const abs = resolve2(ctx.cwd, filePathArg);
17816
18178
  const rel = relative3(ctx.cwd, abs).replace(/\\/g, "/") || filePathArg;
17817
18179
  const isOutOfProject = rel.startsWith("..");
17818
- if (!existsSync38(abs)) {
18180
+ if (!existsSync39(abs)) {
17819
18181
  return { content: { error: `File not found: ${abs}` } };
17820
18182
  }
17821
- const raw = readFileSync36(abs);
18183
+ const raw = readFileSync37(abs);
17822
18184
  const sampleEnd = Math.min(raw.length, 8192);
17823
18185
  for (let i = 0; i < sampleEnd; i++) {
17824
18186
  if (raw[i] === 0) {
@@ -17845,9 +18207,16 @@ async function runFileReadForRouter(args, ctx) {
17845
18207
  content: {
17846
18208
  ...outline,
17847
18209
  gated: true,
17848
- _gate_reason: `File has ${totalLines} lines (> ${effectiveGate}). Structure shown \u2014 call file_read again with entity or offset/limit, or force_full:true.`
18210
+ _gate_reason: `File has ${totalLines} lines (> ${effectiveGate}). Structure shown \u2014 call file_read again with entity or offset/limit, or force_full:true. NOTE: If you plan to Edit this file, you MUST call built-in Read (not file_read) on the target lines first.`
17849
18211
  },
17850
- _layer6_meta: { format: "outline", gated: true }
18212
+ _layer6_meta: {
18213
+ format: "outline",
18214
+ gated: true,
18215
+ total_lines: totalLines,
18216
+ total_chars: text2.length,
18217
+ tokens_estimate: Math.ceil(outline.token_estimate ?? 0),
18218
+ optimization: `file_read gated \u2192 outline (${totalLines} lines)`
18219
+ }
17851
18220
  };
17852
18221
  }
17853
18222
  if (entityName) {
@@ -17869,7 +18238,7 @@ async function runFileReadForRouter(args, ctx) {
17869
18238
  const match = ranked[0].entity;
17870
18239
  if (match.start_line >= 1 && match.start_line <= totalLines) {
17871
18240
  const start = Math.max(1, match.start_line - ENTITY_CONTEXT);
17872
- const endLine = match.start_line + match.body.split("\n").length - 1;
18241
+ const endLine = (match.end_line ?? 0) > match.start_line ? match.end_line : match.start_line + match.body.split("\n").length - 1;
17873
18242
  const end = Math.min(totalLines, endLine + ENTITY_CONTEXT);
17874
18243
  offset = start;
17875
18244
  limit = Math.min(MAX_READ_LINES, end - start + 1);
@@ -17958,9 +18327,16 @@ async function runFileReadForRouter(args, ctx) {
17958
18327
  query: entityName,
17959
18328
  suggestions: []
17960
18329
  },
17961
- _gate_reason: `Entity "${entityName}" not found with high confidence. Use one of the suggestions or specify offset/limit.`
18330
+ _gate_reason: `Entity "${entityName}" not found with high confidence. Use one of the suggestions or specify offset/limit. NOTE: If you plan to Edit this file, you MUST call built-in Read (not file_read) first.`
17962
18331
  },
17963
- _layer6_meta: { format: "outline", gated: true }
18332
+ _layer6_meta: {
18333
+ format: "outline",
18334
+ gated: true,
18335
+ total_lines: totalLines,
18336
+ total_chars: text2.length,
18337
+ tokens_estimate: Math.ceil(outline.token_estimate ?? 0),
18338
+ optimization: `file_read gated \u2192 outline (${totalLines} lines)`
18339
+ }
17964
18340
  };
17965
18341
  }
17966
18342
  }
@@ -17996,10 +18372,12 @@ ${body}`;
17996
18372
  const meta = {
17997
18373
  format: "json",
17998
18374
  tokens_estimate: Math.ceil(sliced.join("\n").length / CHARS_PER_TOKEN4),
17999
- total_lines: totalLines
18375
+ total_lines: totalLines,
18376
+ total_chars: text2.length
18000
18377
  };
18001
18378
  if (isFullRead && sliced.length === totalLines) {
18002
18379
  meta.optimization = `file_read full (purpose:${purpose})`;
18380
+ meta._edit_note = "file_read does NOT satisfy Edit's Read prerequisite. You must still call built-in Read before using Edit on this file.";
18003
18381
  } else if (effOffset > 1 || sliced.length < totalLines || entityWindowApplied) {
18004
18382
  meta.optimization = `file_read window lines ${effOffset}-${effOffset + sliced.length - 1}`;
18005
18383
  }
@@ -18182,9 +18560,13 @@ var init_query_router = __esm({
18182
18560
  LOCAL_TOOLS = /* @__PURE__ */ new Set([
18183
18561
  "get_function",
18184
18562
  "get_class",
18563
+ "get_entity",
18564
+ // consolidated: replaces get_function + get_class
18185
18565
  "get_file",
18186
18566
  "get_callers",
18187
18567
  "get_callees",
18568
+ "get_references",
18569
+ // consolidated: replaces get_callers + get_callees
18188
18570
  "get_imports",
18189
18571
  "search_code",
18190
18572
  "get_rules",
@@ -18610,7 +18992,7 @@ var init_query_router = __esm({
18610
18992
  Object.assign(meta, fr._layer6_meta);
18611
18993
  if (this.tokenFlow && fr._layer6_meta.optimization && fr._layer6_meta.total_lines) {
18612
18994
  const deliveredTokens = fr._layer6_meta.tokens_estimate ?? 0;
18613
- const fullFileTokens = Math.ceil(fr._layer6_meta.total_lines * 80 / 4);
18995
+ const fullFileTokens = fr._layer6_meta.total_chars ? Math.ceil(fr._layer6_meta.total_chars / 4) : Math.ceil(fr._layer6_meta.total_lines * 80 / 4);
18614
18996
  const fileReadSaved = fullFileTokens - deliveredTokens;
18615
18997
  if (fileReadSaved > 50) {
18616
18998
  this.tokenFlow.record({
@@ -19558,7 +19940,21 @@ var init_query_router = __esm({
19558
19940
  case "get_file": {
19559
19941
  const raw = args.key ?? args.name;
19560
19942
  const key = await this.resolveKeyArg(raw);
19561
- return this.resolveEntityWithOverlay(key);
19943
+ const entity = await this.resolveEntityWithOverlay(key);
19944
+ if (entity && entity.file_path && entity.start_line > 0) {
19945
+ try {
19946
+ const { readFileSync: readFileSync57 } = await import("fs");
19947
+ const { resolve: resolve3 } = await import("path");
19948
+ const cwd = this.projectRoot ?? process.cwd();
19949
+ const abs = resolve3(cwd, entity.file_path);
19950
+ const lines = readFileSync57(abs, "utf-8").split("\n");
19951
+ const start = entity.start_line - 1;
19952
+ const end = entity.end_line ?? lines.length;
19953
+ entity.body = lines.slice(start, end).join("\n");
19954
+ } catch {
19955
+ }
19956
+ }
19957
+ return entity;
19562
19958
  }
19563
19959
  case "get_references": {
19564
19960
  const key = await this.resolveKeyArg(args.key);
@@ -19931,11 +20327,14 @@ var init_query_router = __esm({
19931
20327
  ENTITY_ARRAY_TOOLS = /* @__PURE__ */ new Set([
19932
20328
  "get_callers",
19933
20329
  "get_callees",
20330
+ "get_references",
19934
20331
  "search_code"
19935
20332
  ]);
19936
20333
  SINGLE_ENTITY_TOOLS = /* @__PURE__ */ new Set([
20334
+ "get_entity",
19937
20335
  "get_function",
19938
- "get_class"
20336
+ "get_class",
20337
+ "get_file"
19939
20338
  ]);
19940
20339
  }
19941
20340
  });
@@ -21074,8 +21473,8 @@ __export(causal_bridge_exports, {
21074
21473
  assembleCausalChain: () => assembleCausalChain,
21075
21474
  computeDurability: () => computeDurability
21076
21475
  });
21077
- import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
21078
- import { join as join45 } from "path";
21476
+ import { existsSync as existsSync40, readFileSync as readFileSync38 } from "fs";
21477
+ import { join as join46 } from "path";
21079
21478
  function computeAggregateDurability(interactions) {
21080
21479
  if (interactions.length === 0) return 1;
21081
21480
  const survivedCount = interactions.filter((i) => i.survived).length;
@@ -21232,9 +21631,9 @@ var init_causal_bridge = __esm({
21232
21631
  return chains;
21233
21632
  }
21234
21633
  loadEntityLedgerEntries(entityKey2) {
21235
- const ledgerPath = join45(this.unerrDir, "ledger", "shadow.jsonl");
21236
- if (!existsSync39(ledgerPath)) return [];
21237
- const content = readFileSync37(ledgerPath, "utf-8");
21634
+ const ledgerPath = join46(this.unerrDir, "ledger", "shadow.jsonl");
21635
+ if (!existsSync40(ledgerPath)) return [];
21636
+ const content = readFileSync38(ledgerPath, "utf-8");
21238
21637
  const lines = content.split("\n").filter((l) => l.trim().length > 0);
21239
21638
  const entries = [];
21240
21639
  for (const line of lines) {
@@ -21720,15 +22119,15 @@ var context_ledger_exports = {};
21720
22119
  __export(context_ledger_exports, {
21721
22120
  createContextLedger: () => createContextLedger
21722
22121
  });
21723
- import { existsSync as existsSync40, mkdirSync as mkdirSync23, readFileSync as readFileSync38, writeFileSync as writeFileSync21 } from "fs";
21724
- import { join as join46 } from "path";
22122
+ import { existsSync as existsSync41, mkdirSync as mkdirSync23, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "fs";
22123
+ import { join as join47 } from "path";
21725
22124
  function createContextLedger(unerrDir) {
21726
- const stateDir = join46(unerrDir, "state");
21727
- const filePath = join46(stateDir, "context-ledger.json");
22125
+ const stateDir = join47(unerrDir, "state");
22126
+ const filePath = join47(stateDir, "context-ledger.json");
21728
22127
  let records = [];
21729
22128
  let index = /* @__PURE__ */ new Map();
21730
22129
  function ensureDir() {
21731
- if (!existsSync40(stateDir)) {
22130
+ if (!existsSync41(stateDir)) {
21732
22131
  mkdirSync23(stateDir, { recursive: true });
21733
22132
  }
21734
22133
  }
@@ -21737,13 +22136,13 @@ function createContextLedger(unerrDir) {
21737
22136
  }
21738
22137
  function load() {
21739
22138
  ensureDir();
21740
- if (!existsSync40(filePath)) {
22139
+ if (!existsSync41(filePath)) {
21741
22140
  records = [];
21742
22141
  index = /* @__PURE__ */ new Map();
21743
22142
  return /* @__PURE__ */ new Map();
21744
22143
  }
21745
22144
  try {
21746
- const raw = readFileSync38(filePath, "utf-8");
22145
+ const raw = readFileSync39(filePath, "utf-8");
21747
22146
  const parsed = JSON.parse(raw);
21748
22147
  records = Array.isArray(parsed) ? parsed : [];
21749
22148
  } catch {
@@ -22650,13 +23049,13 @@ __export(intent_correlator_exports, {
22650
23049
  IntentCorrelator: () => IntentCorrelator
22651
23050
  });
22652
23051
  import {
22653
- existsSync as existsSync41,
23052
+ existsSync as existsSync42,
22654
23053
  mkdirSync as mkdirSync24,
22655
- readFileSync as readFileSync39,
23054
+ readFileSync as readFileSync40,
22656
23055
  renameSync,
22657
23056
  writeFileSync as writeFileSync22
22658
23057
  } from "fs";
22659
- import { join as join47 } from "path";
23058
+ import { join as join48 } from "path";
22660
23059
  function extractFiles4(args) {
22661
23060
  const files = args.files;
22662
23061
  if (files && Array.isArray(files)) {
@@ -22704,9 +23103,9 @@ var init_intent_correlator = __esm({
22704
23103
  pendingPath;
22705
23104
  pending = [];
22706
23105
  constructor(unerrDir) {
22707
- this.ledgerDir = join47(unerrDir, "ledger");
22708
- this.pendingPath = join47(this.ledgerDir, "pending_correlations.json");
22709
- if (!existsSync41(this.ledgerDir)) {
23106
+ this.ledgerDir = join48(unerrDir, "ledger");
23107
+ this.pendingPath = join48(this.ledgerDir, "pending_correlations.json");
23108
+ if (!existsSync42(this.ledgerDir)) {
22710
23109
  mkdirSync24(this.ledgerDir, { recursive: true });
22711
23110
  }
22712
23111
  this.load();
@@ -22798,9 +23197,9 @@ var init_intent_correlator = __esm({
22798
23197
  }
22799
23198
  // ── Persistence ─────────────────────────────────────────────────
22800
23199
  load() {
22801
- if (!existsSync41(this.pendingPath)) return;
23200
+ if (!existsSync42(this.pendingPath)) return;
22802
23201
  try {
22803
- const raw = readFileSync39(this.pendingPath, "utf-8");
23202
+ const raw = readFileSync40(this.pendingPath, "utf-8");
22804
23203
  const parsed = JSON.parse(raw);
22805
23204
  if (Array.isArray(parsed)) {
22806
23205
  this.pending = parsed;
@@ -22832,14 +23231,14 @@ __export(working_snapshots_exports, {
22832
23231
  });
22833
23232
  import { randomBytes as randomBytes3 } from "crypto";
22834
23233
  import {
22835
- existsSync as existsSync42,
23234
+ existsSync as existsSync43,
22836
23235
  mkdirSync as mkdirSync25,
22837
- readFileSync as readFileSync40,
22838
- readdirSync as readdirSync9,
23236
+ readFileSync as readFileSync41,
23237
+ readdirSync as readdirSync10,
22839
23238
  rmSync as rmSync2,
22840
23239
  writeFileSync as writeFileSync23
22841
23240
  } from "fs";
22842
- import { join as join48 } from "path";
23241
+ import { join as join49 } from "path";
22843
23242
  var _log2, MAX_SNAPSHOTS, AUTO_SNAPSHOT_COOLDOWN_MS, WorkingSnapshotStore;
22844
23243
  var init_working_snapshots = __esm({
22845
23244
  "src/tracking/working-snapshots.ts"() {
@@ -22857,8 +23256,8 @@ var init_working_snapshots = __esm({
22857
23256
  unerrDir;
22858
23257
  constructor(unerrDir) {
22859
23258
  this.unerrDir = unerrDir;
22860
- this.snapshotDir = join48(unerrDir, "snapshots");
22861
- if (!existsSync42(this.snapshotDir)) {
23259
+ this.snapshotDir = join49(unerrDir, "snapshots");
23260
+ if (!existsSync43(this.snapshotDir)) {
22862
23261
  mkdirSync25(this.snapshotDir, { recursive: true });
22863
23262
  }
22864
23263
  }
@@ -22878,7 +23277,7 @@ var init_working_snapshots = __esm({
22878
23277
  processed: false
22879
23278
  };
22880
23279
  writeFileSync23(
22881
- join48(this.snapshotDir, `${id}.json`),
23280
+ join49(this.snapshotDir, `${id}.json`),
22882
23281
  JSON.stringify(snapshot, null, 2),
22883
23282
  "utf-8"
22884
23283
  );
@@ -22892,10 +23291,10 @@ var init_working_snapshots = __esm({
22892
23291
  * Get a snapshot by ID.
22893
23292
  */
22894
23293
  get(snapshotId) {
22895
- const filePath = join48(this.snapshotDir, `${snapshotId}.json`);
22896
- if (!existsSync42(filePath)) return null;
23294
+ const filePath = join49(this.snapshotDir, `${snapshotId}.json`);
23295
+ if (!existsSync43(filePath)) return null;
22897
23296
  try {
22898
- return JSON.parse(readFileSync40(filePath, "utf-8"));
23297
+ return JSON.parse(readFileSync41(filePath, "utf-8"));
22899
23298
  } catch {
22900
23299
  return null;
22901
23300
  }
@@ -22904,14 +23303,14 @@ var init_working_snapshots = __esm({
22904
23303
  * List all snapshots, most recent first.
22905
23304
  */
22906
23305
  list() {
22907
- if (!existsSync42(this.snapshotDir)) return [];
22908
- const files = readdirSync9(this.snapshotDir).filter(
23306
+ if (!existsSync43(this.snapshotDir)) return [];
23307
+ const files = readdirSync10(this.snapshotDir).filter(
22909
23308
  (f) => f.endsWith(".json")
22910
23309
  );
22911
23310
  const snapshots = [];
22912
23311
  for (const file of files) {
22913
23312
  try {
22914
- const raw = readFileSync40(join48(this.snapshotDir, file), "utf-8");
23313
+ const raw = readFileSync41(join49(this.snapshotDir, file), "utf-8");
22915
23314
  snapshots.push(JSON.parse(raw));
22916
23315
  } catch {
22917
23316
  }
@@ -22952,7 +23351,7 @@ var init_working_snapshots = __esm({
22952
23351
  if (!snapshot) return;
22953
23352
  snapshot.processed = true;
22954
23353
  writeFileSync23(
22955
- join48(this.snapshotDir, `${snapshotId}.json`),
23354
+ join49(this.snapshotDir, `${snapshotId}.json`),
22956
23355
  JSON.stringify(snapshot, null, 2),
22957
23356
  "utf-8"
22958
23357
  );
@@ -22967,8 +23366,8 @@ var init_working_snapshots = __esm({
22967
23366
  * Delete a snapshot.
22968
23367
  */
22969
23368
  delete(snapshotId) {
22970
- const filePath = join48(this.snapshotDir, `${snapshotId}.json`);
22971
- if (!existsSync42(filePath)) return false;
23369
+ const filePath = join49(this.snapshotDir, `${snapshotId}.json`);
23370
+ if (!existsSync43(filePath)) return false;
22972
23371
  rmSync2(filePath);
22973
23372
  return true;
22974
23373
  }
@@ -22976,10 +23375,10 @@ var init_working_snapshots = __esm({
22976
23375
  * Get the timeline branch counter from branch_context.json.
22977
23376
  */
22978
23377
  getTimelineBranch() {
22979
- const contextPath = join48(this.unerrDir, "branch_context.json");
22980
- if (!existsSync42(contextPath)) return 0;
23378
+ const contextPath = join49(this.unerrDir, "branch_context.json");
23379
+ if (!existsSync43(contextPath)) return 0;
22981
23380
  try {
22982
- const ctx = JSON.parse(readFileSync40(contextPath, "utf-8"));
23381
+ const ctx = JSON.parse(readFileSync41(contextPath, "utf-8"));
22983
23382
  return ctx.timelineBranch ?? 0;
22984
23383
  } catch {
22985
23384
  return 0;
@@ -22989,11 +23388,11 @@ var init_working_snapshots = __esm({
22989
23388
  * Increment the timeline branch counter. Returns the new value.
22990
23389
  */
22991
23390
  incrementTimelineBranch() {
22992
- const contextPath = join48(this.unerrDir, "branch_context.json");
23391
+ const contextPath = join49(this.unerrDir, "branch_context.json");
22993
23392
  let ctx = {};
22994
- if (existsSync42(contextPath)) {
23393
+ if (existsSync43(contextPath)) {
22995
23394
  try {
22996
- ctx = JSON.parse(readFileSync40(contextPath, "utf-8"));
23395
+ ctx = JSON.parse(readFileSync41(contextPath, "utf-8"));
22997
23396
  } catch {
22998
23397
  ctx = {};
22999
23398
  }
@@ -23166,8 +23565,8 @@ var quality_signals_exports = {};
23166
23565
  __export(quality_signals_exports, {
23167
23566
  QualitySignalTracker: () => QualitySignalTracker
23168
23567
  });
23169
- import { existsSync as existsSync43, readFileSync as readFileSync41, writeFileSync as writeFileSync24 } from "fs";
23170
- import { join as join49 } from "path";
23568
+ import { existsSync as existsSync44, readFileSync as readFileSync42, writeFileSync as writeFileSync24 } from "fs";
23569
+ import { join as join50 } from "path";
23171
23570
  function computeDurabilityFromAge(survivalMs) {
23172
23571
  if (survivalMs < FRAGILE_THRESHOLD_MS) {
23173
23572
  return 0.1 + survivalMs / FRAGILE_THRESHOLD_MS * 0.2;
@@ -23194,7 +23593,7 @@ var init_quality_signals = __esm({
23194
23593
  /** Maximum corrections to retain in memory/disk. */
23195
23594
  static MAX_CORRECTIONS = 200;
23196
23595
  constructor(unerrDir) {
23197
- this.signalsPath = join49(unerrDir, "state", "quality_signals.json");
23596
+ this.signalsPath = join50(unerrDir, "state", "quality_signals.json");
23198
23597
  this.signals = this.load();
23199
23598
  }
23200
23599
  /**
@@ -23286,8 +23685,8 @@ var init_quality_signals = __esm({
23286
23685
  */
23287
23686
  save() {
23288
23687
  try {
23289
- const dir = join49(this.signalsPath, "..");
23290
- if (!existsSync43(dir)) {
23688
+ const dir = join50(this.signalsPath, "..");
23689
+ if (!existsSync44(dir)) {
23291
23690
  const { mkdirSync: mkdirSync39 } = __require("fs");
23292
23691
  mkdirSync39(dir, { recursive: true });
23293
23692
  }
@@ -23333,7 +23732,7 @@ var init_quality_signals = __esm({
23333
23732
  * Load signals from disk.
23334
23733
  */
23335
23734
  load() {
23336
- if (!existsSync43(this.signalsPath)) {
23735
+ if (!existsSync44(this.signalsPath)) {
23337
23736
  return {
23338
23737
  durabilityScores: {},
23339
23738
  corrections: [],
@@ -23342,7 +23741,7 @@ var init_quality_signals = __esm({
23342
23741
  }
23343
23742
  try {
23344
23743
  return JSON.parse(
23345
- readFileSync41(this.signalsPath, "utf-8")
23744
+ readFileSync42(this.signalsPath, "utf-8")
23346
23745
  );
23347
23746
  } catch {
23348
23747
  return {
@@ -24359,8 +24758,8 @@ var incomplete_work_exports = {};
24359
24758
  __export(incomplete_work_exports, {
24360
24759
  IncompleteWorkDetector: () => IncompleteWorkDetector
24361
24760
  });
24362
- import { existsSync as existsSync44, mkdirSync as mkdirSync26, readFileSync as readFileSync42, writeFileSync as writeFileSync25 } from "fs";
24363
- import { join as join50 } from "path";
24761
+ import { existsSync as existsSync45, mkdirSync as mkdirSync26, readFileSync as readFileSync43, writeFileSync as writeFileSync25 } from "fs";
24762
+ import { join as join51 } from "path";
24364
24763
  function severityRank(severity) {
24365
24764
  switch (severity) {
24366
24765
  case "high":
@@ -24553,9 +24952,9 @@ var init_incomplete_work = __esm({
24553
24952
  persistItems(items) {
24554
24953
  if (!this.unerrDir) return false;
24555
24954
  try {
24556
- const stateDir = join50(this.unerrDir, "state");
24557
- if (!existsSync44(stateDir)) mkdirSync26(stateDir, { recursive: true });
24558
- const filePath = join50(stateDir, PERSISTENCE_FILE);
24955
+ const stateDir = join51(this.unerrDir, "state");
24956
+ if (!existsSync45(stateDir)) mkdirSync26(stateDir, { recursive: true });
24957
+ const filePath = join51(stateDir, PERSISTENCE_FILE);
24559
24958
  writeFileSync25(
24560
24959
  filePath,
24561
24960
  JSON.stringify({
@@ -24575,9 +24974,9 @@ var init_incomplete_work = __esm({
24575
24974
  */
24576
24975
  static readPersistedItems(unerrDir) {
24577
24976
  try {
24578
- const filePath = join50(unerrDir, "state", PERSISTENCE_FILE);
24579
- if (!existsSync44(filePath)) return [];
24580
- const data = JSON.parse(readFileSync42(filePath, "utf-8"));
24977
+ const filePath = join51(unerrDir, "state", PERSISTENCE_FILE);
24978
+ if (!existsSync45(filePath)) return [];
24979
+ const data = JSON.parse(readFileSync43(filePath, "utf-8"));
24581
24980
  return data.items ?? [];
24582
24981
  } catch {
24583
24982
  return [];
@@ -26048,7 +26447,7 @@ var transport_mux_exports = {};
26048
26447
  __export(transport_mux_exports, {
26049
26448
  TransportMux: () => TransportMux
26050
26449
  });
26051
- import { existsSync as existsSync45, unlinkSync as unlinkSync7 } from "fs";
26450
+ import { existsSync as existsSync46, unlinkSync as unlinkSync7 } from "fs";
26052
26451
  import { createServer as createServer2 } from "net";
26053
26452
  var _log4, TransportMux;
26054
26453
  var init_transport_mux = __esm({
@@ -26088,7 +26487,7 @@ var init_transport_mux = __esm({
26088
26487
  * Start listening for secondary clients on the Unix domain socket.
26089
26488
  */
26090
26489
  start() {
26091
- if (existsSync45(this.sockPath)) {
26490
+ if (existsSync46(this.sockPath)) {
26092
26491
  try {
26093
26492
  unlinkSync7(this.sockPath);
26094
26493
  } catch {
@@ -26121,7 +26520,7 @@ var init_transport_mux = __esm({
26121
26520
  this.server.close();
26122
26521
  this.server = null;
26123
26522
  }
26124
- if (existsSync45(this.sockPath)) {
26523
+ if (existsSync46(this.sockPath)) {
26125
26524
  try {
26126
26525
  unlinkSync7(this.sockPath);
26127
26526
  } catch {
@@ -26269,8 +26668,8 @@ __export(git_trailers_exports, {
26269
26668
  parseTrailersFromMessage: () => parseTrailersFromMessage,
26270
26669
  uninstallPrepareCommitMsgHook: () => uninstallPrepareCommitMsgHook
26271
26670
  });
26272
- import { existsSync as existsSync46, readFileSync as readFileSync43, writeFileSync as writeFileSync26 } from "fs";
26273
- import { join as join51 } from "path";
26671
+ import { existsSync as existsSync47, readFileSync as readFileSync44, writeFileSync as writeFileSync26 } from "fs";
26672
+ import { join as join52 } from "path";
26274
26673
  function getCommitTrailers(ledger, timelineBranch, branch) {
26275
26674
  const recent = ledger.getRecentEntries(1);
26276
26675
  if (recent.length === 0) return null;
@@ -26290,15 +26689,15 @@ function formatTrailers(trailers) {
26290
26689
  ].join("\n");
26291
26690
  }
26292
26691
  function installPrepareCommitMsgHook(projectRoot) {
26293
- const hooksDir = join51(projectRoot, ".git", "hooks");
26294
- if (!existsSync46(hooksDir)) {
26692
+ const hooksDir = join52(projectRoot, ".git", "hooks");
26693
+ if (!existsSync47(hooksDir)) {
26295
26694
  return false;
26296
26695
  }
26297
- const hookPath = join51(hooksDir, "prepare-commit-msg");
26696
+ const hookPath = join52(hooksDir, "prepare-commit-msg");
26298
26697
  const marker = "# unerr-trailer-injection";
26299
- if (existsSync46(hookPath)) {
26698
+ if (existsSync47(hookPath)) {
26300
26699
  try {
26301
- const existing = readFileSync43(hookPath, "utf-8");
26700
+ const existing = readFileSync44(hookPath, "utf-8");
26302
26701
  if (existing.includes(marker)) {
26303
26702
  return true;
26304
26703
  }
@@ -26327,10 +26726,10 @@ ${generateHookScript()}`;
26327
26726
  }
26328
26727
  }
26329
26728
  function uninstallPrepareCommitMsgHook(projectRoot) {
26330
- const hookPath = join51(projectRoot, ".git", "hooks", "prepare-commit-msg");
26331
- if (!existsSync46(hookPath)) return true;
26729
+ const hookPath = join52(projectRoot, ".git", "hooks", "prepare-commit-msg");
26730
+ if (!existsSync47(hookPath)) return true;
26332
26731
  try {
26333
- const content = readFileSync43(hookPath, "utf-8");
26732
+ const content = readFileSync44(hookPath, "utf-8");
26334
26733
  const marker = "# unerr-trailer-injection";
26335
26734
  if (!content.includes(marker)) return true;
26336
26735
  const lines = content.split("\n");
@@ -26495,15 +26894,15 @@ var init_http_transport = __esm({
26495
26894
 
26496
26895
  // src/tracking/branch-snapshot.ts
26497
26896
  import {
26498
- existsSync as existsSync47,
26897
+ existsSync as existsSync48,
26499
26898
  mkdirSync as mkdirSync28,
26500
- readFileSync as readFileSync44,
26501
- readdirSync as readdirSync10,
26899
+ readFileSync as readFileSync45,
26900
+ readdirSync as readdirSync11,
26502
26901
  rmSync as rmSync3,
26503
26902
  statSync as statSync7,
26504
26903
  writeFileSync as writeFileSync27
26505
26904
  } from "fs";
26506
- import { join as join52 } from "path";
26905
+ import { join as join53 } from "path";
26507
26906
  function sanitizeBranchName(branch) {
26508
26907
  return branch.replace(/\//g, "__").replace(/[^a-zA-Z0-9_.\-]/g, "_");
26509
26908
  }
@@ -26521,7 +26920,7 @@ var init_branch_snapshot = __esm({
26521
26920
  branchDir;
26522
26921
  projectRoot;
26523
26922
  constructor(unerrDir, projectRoot) {
26524
- this.branchDir = join52(unerrDir, "drift", "branches");
26923
+ this.branchDir = join53(unerrDir, "drift", "branches");
26525
26924
  this.projectRoot = projectRoot;
26526
26925
  }
26527
26926
  /**
@@ -26535,8 +26934,8 @@ var init_branch_snapshot = __esm({
26535
26934
  log16.info(`No drift entities/edges to snapshot for branch ${branch}`);
26536
26935
  }
26537
26936
  const dirName = sanitizeBranchName(branch);
26538
- const snapshotDir = join52(this.branchDir, dirName);
26539
- if (!existsSync47(snapshotDir)) {
26937
+ const snapshotDir = join53(this.branchDir, dirName);
26938
+ if (!existsSync48(snapshotDir)) {
26540
26939
  mkdirSync28(snapshotDir, { recursive: true });
26541
26940
  }
26542
26941
  const snapshot = {
@@ -26547,12 +26946,12 @@ var init_branch_snapshot = __esm({
26547
26946
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
26548
26947
  };
26549
26948
  writeFileSync27(
26550
- join52(snapshotDir, OVERLAY_FILE),
26949
+ join53(snapshotDir, OVERLAY_FILE),
26551
26950
  JSON.stringify(snapshot, null, 2),
26552
26951
  "utf-8"
26553
26952
  );
26554
26953
  writeFileSync27(
26555
- join52(snapshotDir, HASHES_FILE),
26954
+ join53(snapshotDir, HASHES_FILE),
26556
26955
  JSON.stringify(fileHashState, null, 2),
26557
26956
  "utf-8"
26558
26957
  );
@@ -26568,14 +26967,14 @@ var init_branch_snapshot = __esm({
26568
26967
  */
26569
26968
  async restoreSnapshot(branch, localGraph) {
26570
26969
  const dirName = sanitizeBranchName(branch);
26571
- const snapshotDir = join52(this.branchDir, dirName);
26572
- const overlayPath = join52(snapshotDir, OVERLAY_FILE);
26573
- if (!existsSync47(overlayPath)) {
26970
+ const snapshotDir = join53(this.branchDir, dirName);
26971
+ const overlayPath = join53(snapshotDir, OVERLAY_FILE);
26972
+ if (!existsSync48(overlayPath)) {
26574
26973
  log16.info(`No snapshot for branch ${branch} \u2014 first visit`);
26575
26974
  return null;
26576
26975
  }
26577
26976
  try {
26578
- const raw = readFileSync44(overlayPath, "utf-8");
26977
+ const raw = readFileSync45(overlayPath, "utf-8");
26579
26978
  const snapshot = JSON.parse(raw);
26580
26979
  for (const entity of snapshot.entities) {
26581
26980
  await localGraph.upsertDriftEntity(entity);
@@ -26587,7 +26986,7 @@ var init_branch_snapshot = __esm({
26587
26986
  }
26588
26987
  const now = /* @__PURE__ */ new Date();
26589
26988
  writeFileSync27(
26590
- join52(snapshotDir, ".last_access"),
26989
+ join53(snapshotDir, ".last_access"),
26591
26990
  now.toISOString(),
26592
26991
  "utf-8"
26593
26992
  );
@@ -26607,17 +27006,17 @@ var init_branch_snapshot = __esm({
26607
27006
  */
26608
27007
  hasSnapshot(branch) {
26609
27008
  const dirName = sanitizeBranchName(branch);
26610
- return existsSync47(join52(this.branchDir, dirName, OVERLAY_FILE));
27009
+ return existsSync48(join53(this.branchDir, dirName, OVERLAY_FILE));
26611
27010
  }
26612
27011
  /**
26613
27012
  * Get the file hash state from a branch snapshot.
26614
27013
  */
26615
27014
  getSnapshotFileHashes(branch) {
26616
27015
  const dirName = sanitizeBranchName(branch);
26617
- const hashesPath = join52(this.branchDir, dirName, HASHES_FILE);
26618
- if (!existsSync47(hashesPath)) return null;
27016
+ const hashesPath = join53(this.branchDir, dirName, HASHES_FILE);
27017
+ if (!existsSync48(hashesPath)) return null;
26619
27018
  try {
26620
- const raw = readFileSync44(hashesPath, "utf-8");
27019
+ const raw = readFileSync45(hashesPath, "utf-8");
26621
27020
  return JSON.parse(raw);
26622
27021
  } catch {
26623
27022
  return null;
@@ -26628,8 +27027,8 @@ var init_branch_snapshot = __esm({
26628
27027
  */
26629
27028
  deleteSnapshot(branch) {
26630
27029
  const dirName = sanitizeBranchName(branch);
26631
- const snapshotDir = join52(this.branchDir, dirName);
26632
- if (!existsSync47(snapshotDir)) return false;
27030
+ const snapshotDir = join53(this.branchDir, dirName);
27031
+ if (!existsSync48(snapshotDir)) return false;
26633
27032
  rmSync3(snapshotDir, { recursive: true, force: true });
26634
27033
  log16.info(`Deleted branch snapshot: ${branch}`);
26635
27034
  return true;
@@ -26638,14 +27037,14 @@ var init_branch_snapshot = __esm({
26638
27037
  * Garbage-collect snapshots for branches that no longer exist in git.
26639
27038
  */
26640
27039
  async garbageCollect() {
26641
- if (!existsSync47(this.branchDir)) return 0;
27040
+ if (!existsSync48(this.branchDir)) return 0;
26642
27041
  const gitBranches = new Set(await listBranches(this.projectRoot));
26643
27042
  if (gitBranches.size === 0) return 0;
26644
27043
  const snapshots = this.listSnapshots();
26645
27044
  let removed = 0;
26646
27045
  for (const snapshot of snapshots) {
26647
27046
  if (!gitBranches.has(snapshot.branch)) {
26648
- const snapshotDir = join52(this.branchDir, snapshot.id);
27047
+ const snapshotDir = join53(this.branchDir, snapshot.id);
26649
27048
  rmSync3(snapshotDir, { recursive: true, force: true });
26650
27049
  log16.info(`GC removed snapshot for deleted branch: ${snapshot.branch}`);
26651
27050
  removed++;
@@ -26657,20 +27056,20 @@ var init_branch_snapshot = __esm({
26657
27056
  * List all branch snapshots, sorted by most recently accessed first.
26658
27057
  */
26659
27058
  listSnapshots() {
26660
- if (!existsSync47(this.branchDir)) return [];
27059
+ if (!existsSync48(this.branchDir)) return [];
26661
27060
  try {
26662
- const entries = readdirSync10(this.branchDir, { withFileTypes: true });
27061
+ const entries = readdirSync11(this.branchDir, { withFileTypes: true });
26663
27062
  const snapshots = [];
26664
27063
  for (const entry of entries) {
26665
27064
  if (!entry.isDirectory()) continue;
26666
- const overlayPath = join52(this.branchDir, entry.name, OVERLAY_FILE);
26667
- if (!existsSync47(overlayPath)) continue;
27065
+ const overlayPath = join53(this.branchDir, entry.name, OVERLAY_FILE);
27066
+ if (!existsSync48(overlayPath)) continue;
26668
27067
  try {
26669
- const raw = readFileSync44(overlayPath, "utf-8");
27068
+ const raw = readFileSync45(overlayPath, "utf-8");
26670
27069
  const snapshot = JSON.parse(raw);
26671
- const accessPath = join52(this.branchDir, entry.name, ".last_access");
27070
+ const accessPath = join53(this.branchDir, entry.name, ".last_access");
26672
27071
  let accessedAt;
26673
- if (existsSync47(accessPath)) {
27072
+ if (existsSync48(accessPath)) {
26674
27073
  accessedAt = statSync7(accessPath).mtime;
26675
27074
  } else {
26676
27075
  accessedAt = statSync7(overlayPath).mtime;
@@ -26697,7 +27096,7 @@ var init_branch_snapshot = __esm({
26697
27096
  if (snapshots.length <= MAX_BRANCH_SNAPSHOTS) return;
26698
27097
  const toRemove = snapshots.slice(MAX_BRANCH_SNAPSHOTS);
26699
27098
  for (const snapshot of toRemove) {
26700
- const dir = join52(this.branchDir, snapshot.id);
27099
+ const dir = join53(this.branchDir, snapshot.id);
26701
27100
  rmSync3(dir, { recursive: true, force: true });
26702
27101
  log16.info(`LRU evicted branch snapshot: ${snapshot.branch}`);
26703
27102
  }
@@ -26714,13 +27113,13 @@ __export(file_hash_state_exports, {
26714
27113
  });
26715
27114
  import { createHash as createHash3 } from "crypto";
26716
27115
  import {
26717
- existsSync as existsSync48,
27116
+ existsSync as existsSync49,
26718
27117
  mkdirSync as mkdirSync29,
26719
- readFileSync as readFileSync45,
27118
+ readFileSync as readFileSync46,
26720
27119
  renameSync as renameSync2,
26721
27120
  writeFileSync as writeFileSync28
26722
27121
  } from "fs";
26723
- import { join as join53 } from "path";
27122
+ import { join as join54 } from "path";
26724
27123
  function contentSha256(content) {
26725
27124
  return createHash3("sha256").update(content).digest("hex");
26726
27125
  }
@@ -26734,8 +27133,8 @@ var init_file_hash_state = __esm({
26734
27133
  statePath;
26735
27134
  state;
26736
27135
  constructor(unerrDir) {
26737
- this.stateDir = join53(unerrDir, "state");
26738
- this.statePath = join53(this.stateDir, STATE_FILE);
27136
+ this.stateDir = join54(unerrDir, "state");
27137
+ this.statePath = join54(this.stateDir, STATE_FILE);
26739
27138
  this.state = this.load();
26740
27139
  }
26741
27140
  /**
@@ -26780,7 +27179,7 @@ var init_file_hash_state = __esm({
26780
27179
  * Persist state to disk atomically (write .tmp → rename).
26781
27180
  */
26782
27181
  save() {
26783
- if (!existsSync48(this.stateDir)) {
27182
+ if (!existsSync49(this.stateDir)) {
26784
27183
  mkdirSync29(this.stateDir, { recursive: true });
26785
27184
  }
26786
27185
  const tmpPath = `${this.statePath}.tmp`;
@@ -26807,11 +27206,11 @@ var init_file_hash_state = __esm({
26807
27206
  this.state = { files: { ...snapshot.files } };
26808
27207
  }
26809
27208
  load() {
26810
- if (!existsSync48(this.statePath)) {
27209
+ if (!existsSync49(this.statePath)) {
26811
27210
  return { files: {} };
26812
27211
  }
26813
27212
  try {
26814
- const raw = readFileSync45(this.statePath, "utf-8");
27213
+ const raw = readFileSync46(this.statePath, "utf-8");
26815
27214
  return JSON.parse(raw);
26816
27215
  } catch {
26817
27216
  return { files: {} };
@@ -26823,15 +27222,15 @@ var init_file_hash_state = __esm({
26823
27222
 
26824
27223
  // src/tracking/stash-manager.ts
26825
27224
  import {
26826
- existsSync as existsSync49,
27225
+ existsSync as existsSync50,
26827
27226
  mkdirSync as mkdirSync30,
26828
- readFileSync as readFileSync46,
26829
- readdirSync as readdirSync11,
27227
+ readFileSync as readFileSync47,
27228
+ readdirSync as readdirSync12,
26830
27229
  rmSync as rmSync4,
26831
27230
  statSync as statSync8,
26832
27231
  writeFileSync as writeFileSync29
26833
27232
  } from "fs";
26834
- import { join as join54 } from "path";
27233
+ import { join as join55 } from "path";
26835
27234
  var MAX_STASH_SNAPSHOTS, OVERLAY_FILE2, HASHES_FILE2, _log6, StashManager;
26836
27235
  var init_stash_manager = __esm({
26837
27236
  "src/tracking/stash-manager.ts"() {
@@ -26849,8 +27248,8 @@ var init_stash_manager = __esm({
26849
27248
  constructor(unerrDir, projectRoot) {
26850
27249
  this.unerrDir = unerrDir;
26851
27250
  this.projectRoot = projectRoot;
26852
- this.stashDir = join54(unerrDir, "drift", "stash");
26853
- this.gitDir = join54(projectRoot, ".git");
27251
+ this.stashDir = join55(unerrDir, "drift", "stash");
27252
+ this.gitDir = join55(projectRoot, ".git");
26854
27253
  this.previousStashRef = this.readStashRef();
26855
27254
  this.previousStashCount = this.getStashCount();
26856
27255
  }
@@ -26895,8 +27294,8 @@ var init_stash_manager = __esm({
26895
27294
  return null;
26896
27295
  }
26897
27296
  const snapshotId = stashRef.slice(0, 12);
26898
- const snapshotDir = join54(this.stashDir, snapshotId);
26899
- if (!existsSync49(snapshotDir)) {
27297
+ const snapshotDir = join55(this.stashDir, snapshotId);
27298
+ if (!existsSync50(snapshotDir)) {
26900
27299
  mkdirSync30(snapshotDir, { recursive: true });
26901
27300
  }
26902
27301
  const snapshot = {
@@ -26907,12 +27306,12 @@ var init_stash_manager = __esm({
26907
27306
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
26908
27307
  };
26909
27308
  writeFileSync29(
26910
- join54(snapshotDir, OVERLAY_FILE2),
27309
+ join55(snapshotDir, OVERLAY_FILE2),
26911
27310
  JSON.stringify(snapshot, null, 2),
26912
27311
  "utf-8"
26913
27312
  );
26914
27313
  writeFileSync29(
26915
- join54(snapshotDir, HASHES_FILE2),
27314
+ join55(snapshotDir, HASHES_FILE2),
26916
27315
  JSON.stringify(fileHashState, null, 2),
26917
27316
  "utf-8"
26918
27317
  );
@@ -26933,14 +27332,14 @@ var init_stash_manager = __esm({
26933
27332
  return 0;
26934
27333
  }
26935
27334
  const latest = snapshots[0];
26936
- const snapshotDir = join54(this.stashDir, latest.id);
26937
- const overlayPath = join54(snapshotDir, OVERLAY_FILE2);
26938
- if (!existsSync49(overlayPath)) {
27335
+ const snapshotDir = join55(this.stashDir, latest.id);
27336
+ const overlayPath = join55(snapshotDir, OVERLAY_FILE2);
27337
+ if (!existsSync50(overlayPath)) {
26939
27338
  _log6.warn(`Snapshot ${latest.id} missing overlay file`);
26940
27339
  return 0;
26941
27340
  }
26942
27341
  try {
26943
- const raw = readFileSync46(overlayPath, "utf-8");
27342
+ const raw = readFileSync47(overlayPath, "utf-8");
26944
27343
  const snapshot = JSON.parse(raw);
26945
27344
  for (const entity of snapshot.entities) {
26946
27345
  await localGraph.upsertDriftEntity(entity);
@@ -26970,10 +27369,10 @@ var init_stash_manager = __esm({
26970
27369
  const snapshots = this.listSnapshots();
26971
27370
  if (snapshots.length === 0) return null;
26972
27371
  const latest = snapshots[0];
26973
- const hashesPath = join54(this.stashDir, latest.id, HASHES_FILE2);
26974
- if (!existsSync49(hashesPath)) return null;
27372
+ const hashesPath = join55(this.stashDir, latest.id, HASHES_FILE2);
27373
+ if (!existsSync50(hashesPath)) return null;
26975
27374
  try {
26976
- const raw = readFileSync46(hashesPath, "utf-8");
27375
+ const raw = readFileSync47(hashesPath, "utf-8");
26977
27376
  return JSON.parse(raw);
26978
27377
  } catch {
26979
27378
  return null;
@@ -26984,8 +27383,8 @@ var init_stash_manager = __esm({
26984
27383
  */
26985
27384
  dropSnapshot(stashRef) {
26986
27385
  const snapshotId = stashRef.slice(0, 12);
26987
- const snapshotDir = join54(this.stashDir, snapshotId);
26988
- if (!existsSync49(snapshotDir)) return false;
27386
+ const snapshotDir = join55(this.stashDir, snapshotId);
27387
+ if (!existsSync50(snapshotDir)) return false;
26989
27388
  rmSync4(snapshotDir, { recursive: true, force: true });
26990
27389
  _log6.info(`Dropped stash snapshot: ${snapshotId}`);
26991
27390
  return true;
@@ -26994,14 +27393,14 @@ var init_stash_manager = __esm({
26994
27393
  * List all stash snapshots, sorted by most recent first.
26995
27394
  */
26996
27395
  listSnapshots() {
26997
- if (!existsSync49(this.stashDir)) return [];
27396
+ if (!existsSync50(this.stashDir)) return [];
26998
27397
  try {
26999
- const entries = readdirSync11(this.stashDir, { withFileTypes: true });
27398
+ const entries = readdirSync12(this.stashDir, { withFileTypes: true });
27000
27399
  const snapshots = [];
27001
27400
  for (const entry of entries) {
27002
27401
  if (!entry.isDirectory()) continue;
27003
- const overlayPath = join54(this.stashDir, entry.name, OVERLAY_FILE2);
27004
- if (!existsSync49(overlayPath)) continue;
27402
+ const overlayPath = join55(this.stashDir, entry.name, OVERLAY_FILE2);
27403
+ if (!existsSync50(overlayPath)) continue;
27005
27404
  try {
27006
27405
  const stat2 = statSync8(overlayPath);
27007
27406
  snapshots.push({ id: entry.name, savedAt: stat2.mtime });
@@ -27018,10 +27417,10 @@ var init_stash_manager = __esm({
27018
27417
  * Read the current stash ref SHA from `.git/refs/stash`.
27019
27418
  */
27020
27419
  readStashRef() {
27021
- const stashPath = join54(this.gitDir, "refs", "stash");
27022
- if (!existsSync49(stashPath)) return null;
27420
+ const stashPath = join55(this.gitDir, "refs", "stash");
27421
+ if (!existsSync50(stashPath)) return null;
27023
27422
  try {
27024
- return readFileSync46(stashPath, "utf-8").trim() || null;
27423
+ return readFileSync47(stashPath, "utf-8").trim() || null;
27025
27424
  } catch {
27026
27425
  return null;
27027
27426
  }
@@ -27030,10 +27429,10 @@ var init_stash_manager = __esm({
27030
27429
  * Count current stash entries via `.git/logs/refs/stash`.
27031
27430
  */
27032
27431
  getStashCount() {
27033
- const logPath = join54(this.gitDir, "logs", "refs", "stash");
27034
- if (!existsSync49(logPath)) return 0;
27432
+ const logPath = join55(this.gitDir, "logs", "refs", "stash");
27433
+ if (!existsSync50(logPath)) return 0;
27035
27434
  try {
27036
- const content = readFileSync46(logPath, "utf-8");
27435
+ const content = readFileSync47(logPath, "utf-8");
27037
27436
  return content.split("\n").filter((line) => line.trim().length > 0).length;
27038
27437
  } catch {
27039
27438
  return 0;
@@ -27047,7 +27446,7 @@ var init_stash_manager = __esm({
27047
27446
  if (snapshots.length <= MAX_STASH_SNAPSHOTS) return;
27048
27447
  const toRemove = snapshots.slice(MAX_STASH_SNAPSHOTS);
27049
27448
  for (const snapshot of toRemove) {
27050
- const dir = join54(this.stashDir, snapshot.id);
27449
+ const dir = join55(this.stashDir, snapshot.id);
27051
27450
  rmSync4(dir, { recursive: true, force: true });
27052
27451
  _log6.info(`LRU evicted stash snapshot: ${snapshot.id}`);
27053
27452
  }
@@ -27065,13 +27464,13 @@ __export(drift_tracker_exports, {
27065
27464
  determineOrigin: () => determineOrigin
27066
27465
  });
27067
27466
  import {
27068
- existsSync as existsSync50,
27467
+ existsSync as existsSync51,
27069
27468
  mkdirSync as mkdirSync31,
27070
- readFileSync as readFileSync47,
27469
+ readFileSync as readFileSync48,
27071
27470
  statSync as statSync9,
27072
27471
  writeFileSync as writeFileSync30
27073
27472
  } from "fs";
27074
- import { join as join55 } from "path";
27473
+ import { join as join56 } from "path";
27075
27474
  function determineOrigin(lastSyncTimestamp) {
27076
27475
  if (lastSyncTimestamp === 0) return "human";
27077
27476
  const elapsed = Date.now() - lastSyncTimestamp;
@@ -27282,15 +27681,15 @@ var init_drift_tracker = __esm({
27282
27681
  crossFileInvalidated: 0,
27283
27682
  edgesExtracted: 0
27284
27683
  };
27285
- const absPath = filePath.startsWith("/") ? filePath : join55(this.config.projectRoot, filePath);
27684
+ const absPath = filePath.startsWith("/") ? filePath : join56(this.config.projectRoot, filePath);
27286
27685
  const relPath = filePath.startsWith("/") ? filePath.slice(this.config.projectRoot.length + 1) : filePath;
27287
27686
  const language = detectLanguage2(relPath);
27288
27687
  if (!language) return result;
27289
- if (existsSync50(absPath) && !this.mtimeCache.check(absPath)) {
27688
+ if (existsSync51(absPath) && !this.mtimeCache.check(absPath)) {
27290
27689
  result.filesSkipped = 1;
27291
27690
  return result;
27292
27691
  }
27293
- if (!existsSync50(absPath)) {
27692
+ if (!existsSync51(absPath)) {
27294
27693
  const baseEntities2 = await this.localGraph.getEntitiesByFile(relPath);
27295
27694
  this.markFileDeleted(relPath, intentId);
27296
27695
  result.filesProcessed = 1;
@@ -27328,7 +27727,7 @@ var init_drift_tracker = __esm({
27328
27727
  this.maybeEmitDrift(relPath, result);
27329
27728
  return result;
27330
27729
  }
27331
- const content = readFileSync47(absPath, "utf-8");
27730
+ const content = readFileSync48(absPath, "utf-8");
27332
27731
  const sha = contentSha256(content);
27333
27732
  const decision = this.fileHashManager.shouldProcess(relPath, sha, headSha);
27334
27733
  if (decision === "skip") {
@@ -27562,7 +27961,7 @@ var init_drift_tracker = __esm({
27562
27961
  return await this.stashManager.restoreSnapshot(this.localGraph);
27563
27962
  }
27564
27963
  async markFileDeleted(filePath, intentId) {
27565
- const absPath = filePath.startsWith("/") ? filePath : join55(this.config.projectRoot, filePath);
27964
+ const absPath = filePath.startsWith("/") ? filePath : join56(this.config.projectRoot, filePath);
27566
27965
  this.mtimeCache.evict(absPath);
27567
27966
  const baseEntities = await this.localGraph.getEntitiesByFile(filePath);
27568
27967
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -27647,13 +28046,13 @@ var init_drift_tracker = __esm({
27647
28046
  return invalidated;
27648
28047
  }
27649
28048
  async saveDriftSummary() {
27650
- const driftDir = join55(this.config.unerrDir, "drift");
27651
- if (!existsSync50(driftDir)) {
28049
+ const driftDir = join56(this.config.unerrDir, "drift");
28050
+ if (!existsSync51(driftDir)) {
27652
28051
  mkdirSync31(driftDir, { recursive: true });
27653
28052
  }
27654
28053
  const summary = await this.getDriftSummary();
27655
28054
  writeFileSync30(
27656
- join55(driftDir, "drift_summary.json"),
28055
+ join56(driftDir, "drift_summary.json"),
27657
28056
  JSON.stringify(summary, null, 2),
27658
28057
  "utf-8"
27659
28058
  );
@@ -28062,8 +28461,8 @@ var incremental_indexer_exports = {};
28062
28461
  __export(incremental_indexer_exports, {
28063
28462
  indexFilesIncremental: () => indexFilesIncremental
28064
28463
  });
28065
- import { existsSync as existsSync51, readFileSync as readFileSync48 } from "fs";
28066
- import { join as join56, relative as relative4 } from "path";
28464
+ import { existsSync as existsSync52, readFileSync as readFileSync49 } from "fs";
28465
+ import { join as join57, relative as relative4 } from "path";
28067
28466
  async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repoId) {
28068
28467
  const startMs = Date.now();
28069
28468
  const db = graphStore.db;
@@ -28078,9 +28477,9 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
28078
28477
  const changedEntityKeys = /* @__PURE__ */ new Set();
28079
28478
  const deletedEntityKeys = /* @__PURE__ */ new Set();
28080
28479
  for (const filePath of changedFiles) {
28081
- const absPath = filePath.startsWith("/") ? filePath : join56(projectRoot, filePath);
28480
+ const absPath = filePath.startsWith("/") ? filePath : join57(projectRoot, filePath);
28082
28481
  const relPath = filePath.startsWith("/") ? relative4(projectRoot, filePath) : filePath;
28083
- if (!existsSync51(absPath)) {
28482
+ if (!existsSync52(absPath)) {
28084
28483
  const deleted2 = await deleteFileFromGraph(db, relPath);
28085
28484
  filesDeleted++;
28086
28485
  totalEntitiesDeleted += deleted2.entitiesDeleted;
@@ -28093,7 +28492,7 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
28093
28492
  }
28094
28493
  let content;
28095
28494
  try {
28096
- content = readFileSync48(absPath, "utf-8");
28495
+ content = readFileSync49(absPath, "utf-8");
28097
28496
  } catch {
28098
28497
  continue;
28099
28498
  }
@@ -28381,6 +28780,7 @@ async function upsertEntitiesBatched(db, entities) {
28381
28780
  e.name,
28382
28781
  e.file_path,
28383
28782
  e.start_line ?? 0,
28783
+ e.end_line ?? 0,
28384
28784
  e.signature ?? "",
28385
28785
  e.body ?? "",
28386
28786
  e.fan_in ?? 0,
@@ -28398,21 +28798,22 @@ async function upsertEntitiesBatched(db, entities) {
28398
28798
  });
28399
28799
  try {
28400
28800
  await db.run(
28401
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [${rowStrs.join(", ")}]
28402
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`
28801
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [${rowStrs.join(", ")}]
28802
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`
28403
28803
  );
28404
28804
  } catch {
28405
28805
  for (const entity of entities) {
28406
28806
  try {
28407
28807
  await db.run(
28408
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, $kind, $name, $fp, $sl, $sig, $body, $fi, $fo, $rl, $is_test]]
28409
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
28808
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, $kind, $name, $fp, $sl, $el, $sig, $body, $fi, $fo, $rl, $is_test]]
28809
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
28410
28810
  {
28411
28811
  key: entity.key,
28412
28812
  kind: entity.kind,
28413
28813
  name: entity.name,
28414
28814
  fp: entity.file_path,
28415
28815
  sl: entity.start_line ?? 0,
28816
+ el: entity.end_line ?? 0,
28416
28817
  sig: entity.signature ?? "",
28417
28818
  body: entity.body ?? "",
28418
28819
  fi: entity.fan_in ?? 0,
@@ -29258,8 +29659,8 @@ var workspace_manifest_exports = {};
29258
29659
  __export(workspace_manifest_exports, {
29259
29660
  WorkspaceManifest: () => WorkspaceManifest
29260
29661
  });
29261
- import { existsSync as existsSync52, mkdirSync as mkdirSync32, readFileSync as readFileSync49, writeFileSync as writeFileSync31 } from "fs";
29262
- import { join as join57 } from "path";
29662
+ import { existsSync as existsSync53, mkdirSync as mkdirSync32, readFileSync as readFileSync50, writeFileSync as writeFileSync31 } from "fs";
29663
+ import { join as join58 } from "path";
29263
29664
  var WorkspaceManifest;
29264
29665
  var init_workspace_manifest = __esm({
29265
29666
  "src/tracking/workspace-manifest.ts"() {
@@ -29269,7 +29670,7 @@ var init_workspace_manifest = __esm({
29269
29670
  this.unerrDir = unerrDir;
29270
29671
  this.repoId = repoId;
29271
29672
  this.sessionId = sessionId;
29272
- this.manifestPath = join57(unerrDir, "manifest.json");
29673
+ this.manifestPath = join58(unerrDir, "manifest.json");
29273
29674
  this.data = this.load();
29274
29675
  }
29275
29676
  unerrDir;
@@ -29364,7 +29765,7 @@ var init_workspace_manifest = __esm({
29364
29765
  }
29365
29766
  // ── Internal ─────────────────────────────────────────────────────
29366
29767
  load() {
29367
- if (!existsSync52(this.manifestPath)) {
29768
+ if (!existsSync53(this.manifestPath)) {
29368
29769
  return {
29369
29770
  version: 1,
29370
29771
  repoId: this.repoId,
@@ -29374,7 +29775,7 @@ var init_workspace_manifest = __esm({
29374
29775
  };
29375
29776
  }
29376
29777
  try {
29377
- const raw = readFileSync49(this.manifestPath, "utf-8");
29778
+ const raw = readFileSync50(this.manifestPath, "utf-8");
29378
29779
  const parsed = JSON.parse(raw);
29379
29780
  if (parsed.repoId !== this.repoId) {
29380
29781
  return {
@@ -29397,7 +29798,7 @@ var init_workspace_manifest = __esm({
29397
29798
  }
29398
29799
  }
29399
29800
  save() {
29400
- if (!existsSync52(this.unerrDir)) {
29801
+ if (!existsSync53(this.unerrDir)) {
29401
29802
  mkdirSync32(this.unerrDir, { recursive: true });
29402
29803
  }
29403
29804
  writeFileSync31(
@@ -29591,8 +29992,8 @@ var log_tailer_exports = {};
29591
29992
  __export(log_tailer_exports, {
29592
29993
  startLogTailer: () => startLogTailer
29593
29994
  });
29594
- import { existsSync as existsSync53, statSync as statSync10, openSync, readSync, closeSync, watch } from "fs";
29595
- import { join as join58 } from "path";
29995
+ import { existsSync as existsSync54, statSync as statSync10, openSync, readSync, closeSync, watch } from "fs";
29996
+ import { join as join59 } from "path";
29596
29997
  function formatSize2(bytes) {
29597
29998
  if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
29598
29999
  if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`;
@@ -29699,29 +30100,29 @@ function tailFile(state, handler) {
29699
30100
  }
29700
30101
  }
29701
30102
  function startLogTailer(cwd, options) {
29702
- const logsDir = join58(cwd, ".unerr", "logs");
29703
- const compressionPath = join58(logsDir, "compression.jsonl");
29704
- const generalPath = join58(logsDir, "unerr.jsonl");
29705
- const tokenFlowPath = join58(logsDir, "token-flow.jsonl");
29706
- const fileReadsPath = join58(logsDir, "file-reads.jsonl");
30103
+ const logsDir = join59(cwd, ".unerr", "logs");
30104
+ const compressionPath = join59(logsDir, "compression.jsonl");
30105
+ const generalPath = join59(logsDir, "unerr.jsonl");
30106
+ const tokenFlowPath = join59(logsDir, "token-flow.jsonl");
30107
+ const fileReadsPath = join59(logsDir, "file-reads.jsonl");
29707
30108
  const compressionState = {
29708
30109
  path: compressionPath,
29709
- offset: existsSync53(compressionPath) ? statSync10(compressionPath).size : 0,
30110
+ offset: existsSync54(compressionPath) ? statSync10(compressionPath).size : 0,
29710
30111
  watcher: null
29711
30112
  };
29712
30113
  const generalState = {
29713
30114
  path: generalPath,
29714
- offset: existsSync53(generalPath) ? statSync10(generalPath).size : 0,
30115
+ offset: existsSync54(generalPath) ? statSync10(generalPath).size : 0,
29715
30116
  watcher: null
29716
30117
  };
29717
30118
  const tokenFlowState = {
29718
30119
  path: tokenFlowPath,
29719
- offset: existsSync53(tokenFlowPath) ? statSync10(tokenFlowPath).size : 0,
30120
+ offset: existsSync54(tokenFlowPath) ? statSync10(tokenFlowPath).size : 0,
29720
30121
  watcher: null
29721
30122
  };
29722
30123
  const fileReadsState = {
29723
30124
  path: fileReadsPath,
29724
- offset: existsSync53(fileReadsPath) ? statSync10(fileReadsPath).size : 0,
30125
+ offset: existsSync54(fileReadsPath) ? statSync10(fileReadsPath).size : 0,
29725
30126
  watcher: null
29726
30127
  };
29727
30128
  function setupWatcher(state, handler) {
@@ -29749,7 +30150,7 @@ function startLogTailer(cwd, options) {
29749
30150
  ];
29750
30151
  const pollInterval = setInterval(() => {
29751
30152
  for (const { state, handler } of allStates) {
29752
- if (!state.watcher && existsSync53(state.path)) {
30153
+ if (!state.watcher && existsSync54(state.path)) {
29753
30154
  try {
29754
30155
  state.offset = 0;
29755
30156
  state.watcher = watch(state.path, () => {
@@ -30467,6 +30868,336 @@ function createIntelligenceRoutes(deps) {
30467
30868
  }
30468
30869
  });
30469
30870
  });
30871
+ app.get("/risk-hotspots", async (c) => {
30872
+ const start = performance.now();
30873
+ const limit = parseLimit(c.req.query("limit"), 20, 50);
30874
+ if (!deps.localGraph) {
30875
+ return c.json(
30876
+ {
30877
+ data: [],
30878
+ _meta: {
30879
+ source: "local",
30880
+ graph: "unavailable",
30881
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
30882
+ }
30883
+ },
30884
+ 503
30885
+ );
30886
+ }
30887
+ const topNodes = await deps.localGraph.getCriticalNodes(limit);
30888
+ const keys = topNodes.map((n) => n.key);
30889
+ const testCountMap = /* @__PURE__ */ new Map();
30890
+ if (keys.length > 0) {
30891
+ try {
30892
+ const directResult = await deps.localGraph.db.run(
30893
+ `?[target, count(tk)] := *edges{from_key: tk, to_key: target, type: "tests"},
30894
+ target in $keys`,
30895
+ { keys }
30896
+ );
30897
+ for (const row of directResult.rows) {
30898
+ testCountMap.set(row[0], row[1]);
30899
+ }
30900
+ const transitiveResult = await deps.localGraph.db.run(
30901
+ `?[target, count(tk)] := *edges{from_key: mid, to_key: target, type: "calls"},
30902
+ *edges{from_key: tk, to_key: mid, type: "tests"},
30903
+ target in $keys`,
30904
+ { keys }
30905
+ );
30906
+ for (const row of transitiveResult.rows) {
30907
+ const key = row[0];
30908
+ const existing = testCountMap.get(key) ?? 0;
30909
+ testCountMap.set(key, existing + row[1]);
30910
+ }
30911
+ } catch {
30912
+ }
30913
+ }
30914
+ const callerCountMap = /* @__PURE__ */ new Map();
30915
+ if (keys.length > 0) {
30916
+ try {
30917
+ const callerResult = await deps.localGraph.db.run(
30918
+ `?[target, count(caller)] := *edges{from_key: caller, to_key: target, type: "calls"},
30919
+ target in $keys`,
30920
+ { keys }
30921
+ );
30922
+ for (const row of callerResult.rows) {
30923
+ callerCountMap.set(row[0], row[1]);
30924
+ }
30925
+ } catch {
30926
+ }
30927
+ }
30928
+ const hotspots = topNodes.map((n) => ({
30929
+ key: n.key,
30930
+ name: n.name,
30931
+ file_path: n.file_path,
30932
+ kind: n.kind,
30933
+ fan_in: n.fan_in,
30934
+ fan_out: n.fan_out,
30935
+ degree: n.degree,
30936
+ risk_level: n.risk_level,
30937
+ community_label: n.community_label,
30938
+ test_count: testCountMap.get(n.key) ?? 0,
30939
+ caller_count: callerCountMap.get(n.key) ?? 0
30940
+ }));
30941
+ return c.json({
30942
+ data: hotspots,
30943
+ _meta: {
30944
+ source: "local",
30945
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
30946
+ }
30947
+ });
30948
+ });
30949
+ app.get("/insights", async (c) => {
30950
+ const start = performance.now();
30951
+ if (!deps.localGraph) {
30952
+ return c.json(
30953
+ {
30954
+ data: null,
30955
+ _meta: {
30956
+ source: "local",
30957
+ graph: "unavailable",
30958
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
30959
+ }
30960
+ },
30961
+ 503
30962
+ );
30963
+ }
30964
+ const topN = 50;
30965
+ const topNodes = await deps.localGraph.getCriticalNodes(topN);
30966
+ const keys = topNodes.map((n) => n.key);
30967
+ const testCountMap = /* @__PURE__ */ new Map();
30968
+ if (keys.length > 0) {
30969
+ try {
30970
+ const directResult = await deps.localGraph.db.run(
30971
+ `?[target, count(tk)] := *edges{from_key: tk, to_key: target, type: "tests"},
30972
+ target in $keys`,
30973
+ { keys }
30974
+ );
30975
+ for (const row of directResult.rows) {
30976
+ testCountMap.set(row[0], row[1]);
30977
+ }
30978
+ const transitiveResult = await deps.localGraph.db.run(
30979
+ `?[target, count(tk)] := *edges{from_key: mid, to_key: target, type: "calls"},
30980
+ *edges{from_key: tk, to_key: mid, type: "tests"},
30981
+ target in $keys`,
30982
+ { keys }
30983
+ );
30984
+ for (const row of transitiveResult.rows) {
30985
+ const k = row[0];
30986
+ testCountMap.set(k, (testCountMap.get(k) ?? 0) + row[1]);
30987
+ }
30988
+ } catch {
30989
+ }
30990
+ }
30991
+ let totalBlastRadius = 0;
30992
+ let testedBlastRadius = 0;
30993
+ let untestedBlastRadius = 0;
30994
+ for (const n of topNodes) {
30995
+ const weight = Math.max(n.fan_in, 1);
30996
+ totalBlastRadius += weight;
30997
+ if ((testCountMap.get(n.key) ?? 0) > 0) {
30998
+ testedBlastRadius += weight;
30999
+ } else {
31000
+ untestedBlastRadius += weight;
31001
+ }
31002
+ }
31003
+ const blastRadiusCoverage = totalBlastRadius > 0 ? Math.round(testedBlastRadius / totalBlastRadius * 100) : 0;
31004
+ const bottlenecks = topNodes.filter(
31005
+ (n) => n.fan_in >= 3 && n.fan_out >= 2 && (testCountMap.get(n.key) ?? 0) === 0
31006
+ ).map((n) => ({
31007
+ key: n.key,
31008
+ name: n.name,
31009
+ file_path: n.file_path,
31010
+ kind: n.kind,
31011
+ fan_in: n.fan_in,
31012
+ fan_out: n.fan_out,
31013
+ degree: n.degree,
31014
+ risk_level: n.risk_level
31015
+ }));
31016
+ const riskDistribution = { high: 0, medium: 0, low: 0 };
31017
+ for (const n of topNodes) {
31018
+ const level = n.risk_level;
31019
+ if (level in riskDistribution) riskDistribution[level]++;
31020
+ }
31021
+ const sortedByDegree = [...topNodes].sort((a, b) => b.degree - a.degree);
31022
+ const totalDegree = topNodes.reduce((s, n) => s + n.degree, 0);
31023
+ const top5Degree = sortedByDegree.slice(0, 5).reduce((s, n) => s + n.degree, 0);
31024
+ const riskConcentration = totalDegree > 0 ? Math.round(top5Degree / totalDegree * 100) : 0;
31025
+ const communityMap = /* @__PURE__ */ new Map();
31026
+ for (const n of topNodes) {
31027
+ const label = n.community_label || `cluster-${n.community}`;
31028
+ let comm = communityMap.get(label);
31029
+ if (!comm) {
31030
+ comm = {
31031
+ label,
31032
+ entities: 0,
31033
+ totalDegree: 0,
31034
+ riskHigh: 0,
31035
+ riskMedium: 0,
31036
+ riskLow: 0,
31037
+ untested: 0,
31038
+ tested: 0,
31039
+ totalFanIn: 0,
31040
+ untestedFanIn: 0
31041
+ };
31042
+ communityMap.set(label, comm);
31043
+ }
31044
+ comm.entities++;
31045
+ comm.totalDegree += n.degree;
31046
+ comm.totalFanIn += n.fan_in;
31047
+ const hasCoverage = (testCountMap.get(n.key) ?? 0) > 0;
31048
+ if (hasCoverage) comm.tested++;
31049
+ else {
31050
+ comm.untested++;
31051
+ comm.untestedFanIn += n.fan_in;
31052
+ }
31053
+ if (n.risk_level === "high") comm.riskHigh++;
31054
+ else if (n.risk_level === "medium") comm.riskMedium++;
31055
+ else comm.riskLow++;
31056
+ }
31057
+ const communityHealth = [];
31058
+ for (const [, comm] of communityMap) {
31059
+ const total = comm.tested + comm.untested;
31060
+ communityHealth.push({
31061
+ label: comm.label,
31062
+ entities: comm.entities,
31063
+ tested: comm.tested,
31064
+ untested: comm.untested,
31065
+ coveragePct: total > 0 ? Math.round(comm.tested / total * 100) : 0,
31066
+ blastRadiusCoveragePct: comm.totalFanIn > 0 ? Math.round(
31067
+ (comm.totalFanIn - comm.untestedFanIn) / comm.totalFanIn * 100
31068
+ ) : 0,
31069
+ riskHigh: comm.riskHigh,
31070
+ riskMedium: comm.riskMedium,
31071
+ riskLow: comm.riskLow,
31072
+ totalDegree: comm.totalDegree
31073
+ });
31074
+ }
31075
+ communityHealth.sort(
31076
+ (a, b) => a.blastRadiusCoveragePct - b.blastRadiusCoveragePct
31077
+ );
31078
+ let mostCoupledPair = null;
31079
+ try {
31080
+ const fcResult = await deps.localGraph.db.run(
31081
+ `?[from_label, to_label, w] :=
31082
+ *file_edges{from_file, to_file, weight: w},
31083
+ *file_communities{file_path: from_file, label: from_label},
31084
+ *file_communities{file_path: to_file, label: to_label},
31085
+ from_label != to_label
31086
+ :order -w
31087
+ :limit 1`
31088
+ );
31089
+ if (fcResult.rows.length > 0) {
31090
+ const [from, to, weight] = fcResult.rows[0];
31091
+ mostCoupledPair = { from, to, weight };
31092
+ }
31093
+ } catch {
31094
+ }
31095
+ const insights = [];
31096
+ if (blastRadiusCoverage < 50) {
31097
+ insights.push({
31098
+ id: "blast-radius-coverage",
31099
+ severity: "critical",
31100
+ title: `Blast-radius coverage is only ${blastRadiusCoverage}%`,
31101
+ description: `${untestedBlastRadius} dependency-weighted risk sits in untested code. Your entity-count coverage looks better but hides that your most-depended-on code is unprotected.`,
31102
+ metric: blastRadiusCoverage,
31103
+ metricLabel: "blast-radius coverage"
31104
+ });
31105
+ } else if (blastRadiusCoverage < 75) {
31106
+ insights.push({
31107
+ id: "blast-radius-coverage",
31108
+ severity: "warning",
31109
+ title: `Blast-radius coverage at ${blastRadiusCoverage}%`,
31110
+ description: `Your most critical code paths are partially covered, but ${untestedBlastRadius} dependency-weight remains untested.`,
31111
+ metric: blastRadiusCoverage,
31112
+ metricLabel: "blast-radius coverage"
31113
+ });
31114
+ } else {
31115
+ insights.push({
31116
+ id: "blast-radius-coverage",
31117
+ severity: "positive",
31118
+ title: `Strong blast-radius coverage: ${blastRadiusCoverage}%`,
31119
+ description: "Your highest-impact code paths are well tested. Regressions are unlikely to cascade silently.",
31120
+ metric: blastRadiusCoverage,
31121
+ metricLabel: "blast-radius coverage"
31122
+ });
31123
+ }
31124
+ if (bottlenecks.length > 0) {
31125
+ const totalBottleneckFanIn = bottlenecks.reduce(
31126
+ (s, b) => s + b.fan_in,
31127
+ 0
31128
+ );
31129
+ insights.push({
31130
+ id: "bottlenecks",
31131
+ severity: bottlenecks.length >= 5 ? "critical" : "warning",
31132
+ title: `${bottlenecks.length} structural bottleneck${bottlenecks.length !== 1 ? "s" : ""} with zero tests`,
31133
+ description: `These functions have high fan-in AND fan-out \u2014 all dependency traffic flows through them. Combined, ${totalBottleneckFanIn} dependents are exposed. A failure in any one cascades in both directions.`,
31134
+ metric: bottlenecks.length,
31135
+ metricLabel: "bottlenecks"
31136
+ });
31137
+ }
31138
+ if (riskConcentration > 40) {
31139
+ insights.push({
31140
+ id: "risk-concentration",
31141
+ severity: riskConcentration > 60 ? "warning" : "info",
31142
+ title: `${riskConcentration}% of structural risk concentrated in top 5 entities`,
31143
+ description: `Your risk isn't spread evenly \u2014 a small number of entities carry most of the dependency weight. Focus testing and review here for maximum impact.`,
31144
+ metric: riskConcentration,
31145
+ metricLabel: "risk in top 5"
31146
+ });
31147
+ }
31148
+ if (mostCoupledPair && mostCoupledPair.weight >= 3) {
31149
+ insights.push({
31150
+ id: "coupling-hotspot",
31151
+ severity: mostCoupledPair.weight >= 10 ? "warning" : "info",
31152
+ title: `"${mostCoupledPair.from}" and "${mostCoupledPair.to}" are tightly coupled`,
31153
+ description: `${mostCoupledPair.weight} cross-boundary calls between these modules. Changes in one are likely to require changes in the other.`,
31154
+ metric: mostCoupledPair.weight,
31155
+ metricLabel: "cross-boundary calls"
31156
+ });
31157
+ }
31158
+ if (riskDistribution.high === 0 && topNodes.length > 0) {
31159
+ insights.push({
31160
+ id: "no-high-risk",
31161
+ severity: "positive",
31162
+ title: "No high-risk entities detected",
31163
+ description: "Your top entities are well-balanced with moderate dependency counts. The codebase structure is healthy."
31164
+ });
31165
+ }
31166
+ const bottleneckPenalty = Math.min(bottlenecks.length * 5, 25);
31167
+ const highRiskPenalty = Math.min(riskDistribution.high * 3, 15);
31168
+ const concentrationPenalty = riskConcentration > 50 ? (riskConcentration - 50) * 0.4 : 0;
31169
+ const healthScore = Math.max(
31170
+ 0,
31171
+ Math.min(
31172
+ 100,
31173
+ Math.round(
31174
+ blastRadiusCoverage * 0.4 + (100 - bottleneckPenalty) * 0.25 + (100 - concentrationPenalty) * 0.2 + (100 - highRiskPenalty) * 0.15
31175
+ )
31176
+ )
31177
+ );
31178
+ const healthGrade = healthScore >= 90 ? "A" : healthScore >= 80 ? "B" : healthScore >= 65 ? "C" : healthScore >= 50 ? "D" : "F";
31179
+ return c.json({
31180
+ data: {
31181
+ healthScore,
31182
+ healthGrade,
31183
+ blastRadiusCoverage,
31184
+ untestedBlastRadius,
31185
+ testedBlastRadius,
31186
+ totalBlastRadius,
31187
+ bottlenecks,
31188
+ riskDistribution,
31189
+ riskConcentration,
31190
+ communityHealth,
31191
+ mostCoupledPair,
31192
+ insights
31193
+ },
31194
+ _meta: {
31195
+ source: "local",
31196
+ entities_analyzed: topNodes.length,
31197
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
31198
+ }
31199
+ });
31200
+ });
30470
31201
  app.get("/durability", async (c) => {
30471
31202
  const start = performance.now();
30472
31203
  const limit = parseLimit(c.req.query("ledger_limit"), 800, 5e3);
@@ -30753,13 +31484,13 @@ function createSystemRoutes(deps) {
30753
31484
  });
30754
31485
  app.get("/config", async (c) => {
30755
31486
  const start = performance.now();
30756
- const { existsSync: existsSync62, readFileSync: readFileSync56 } = await import("fs");
30757
- const { join: join67 } = await import("path");
31487
+ const { existsSync: existsSync63, readFileSync: readFileSync57 } = await import("fs");
31488
+ const { join: join68 } = await import("path");
30758
31489
  let config = {};
30759
- const configPath = join67(deps.cwd, ".unerr", "config.json");
30760
- if (existsSync62(configPath)) {
31490
+ const configPath = join68(deps.cwd, ".unerr", "config.json");
31491
+ if (existsSync63(configPath)) {
30761
31492
  try {
30762
- config = JSON.parse(readFileSync56(configPath, "utf-8"));
31493
+ config = JSON.parse(readFileSync57(configPath, "utf-8"));
30763
31494
  } catch {
30764
31495
  config = { error: "unreadable" };
30765
31496
  }
@@ -30960,20 +31691,20 @@ __export(session_history_exports, {
30960
31691
  getWeeklyStats: () => getWeeklyStats,
30961
31692
  readSessionHistory: () => readSessionHistory
30962
31693
  });
30963
- import { appendFileSync as appendFileSync8, existsSync as existsSync54, mkdirSync as mkdirSync33, readFileSync as readFileSync50 } from "fs";
30964
- import { join as join59 } from "path";
31694
+ import { appendFileSync as appendFileSync8, existsSync as existsSync55, mkdirSync as mkdirSync33, readFileSync as readFileSync51 } from "fs";
31695
+ import { join as join60 } from "path";
30965
31696
  function appendSessionHistory(unerrDir, entry) {
30966
- const stateDir = join59(unerrDir, "state");
30967
- if (!existsSync54(stateDir)) mkdirSync33(stateDir, { recursive: true });
30968
- const historyPath = join59(stateDir, "session-history.jsonl");
31697
+ const stateDir = join60(unerrDir, "state");
31698
+ if (!existsSync55(stateDir)) mkdirSync33(stateDir, { recursive: true });
31699
+ const historyPath = join60(stateDir, "session-history.jsonl");
30969
31700
  appendFileSync8(historyPath, `${JSON.stringify(entry)}
30970
31701
  `, "utf-8");
30971
31702
  }
30972
31703
  function readSessionHistory(unerrDir) {
30973
- const historyPath = join59(unerrDir, "state", "session-history.jsonl");
30974
- if (!existsSync54(historyPath)) return [];
31704
+ const historyPath = join60(unerrDir, "state", "session-history.jsonl");
31705
+ if (!existsSync55(historyPath)) return [];
30975
31706
  try {
30976
- const content = readFileSync50(historyPath, "utf-8");
31707
+ const content = readFileSync51(historyPath, "utf-8");
30977
31708
  return content.split("\n").filter(Boolean).map((line) => {
30978
31709
  try {
30979
31710
  return JSON.parse(line);
@@ -31211,7 +31942,7 @@ function createTokenFlowRoutes(deps) {
31211
31942
  first_ts: data.first_ts,
31212
31943
  last_ts: data.last_ts,
31213
31944
  mechanisms: [...data.mechanisms],
31214
- agent_name: agentBySession.get(id) ?? null
31945
+ agent_name: agentBySession.get(id) ?? deps.getAgentName?.(id) ?? null
31215
31946
  };
31216
31947
  }).sort((a, b) => b.last_ts.localeCompare(a.last_ts));
31217
31948
  const paginated = allSessions.slice(offset, offset + limit);
@@ -31350,18 +32081,297 @@ var init_token_flow2 = __esm({
31350
32081
  }
31351
32082
  });
31352
32083
 
32084
+ // src/server/routes/reasoning-quality.ts
32085
+ import { Hono as Hono7 } from "hono";
32086
+ function computeQualityMetrics(events) {
32087
+ if (events.length === 0) {
32088
+ return {
32089
+ signal_to_noise_ratio: 0,
32090
+ noise_removed_pct: 0,
32091
+ context_density: 0,
32092
+ entities_resolved: 0,
32093
+ graph_tokens_delivered: 0,
32094
+ attention_multiplier: 1,
32095
+ first_call_resolution_rate: 0,
32096
+ graph_calls: 0,
32097
+ total_tool_calls: 0,
32098
+ turns_saved: 0,
32099
+ exploration_loops_prevented: 0,
32100
+ blast_radius_warnings: 0,
32101
+ circuit_breaker_activations: 0,
32102
+ convention_injections: 0,
32103
+ prevention_score: 0,
32104
+ reasoning_quality_multiplier: 0,
32105
+ total_sessions: 0,
32106
+ total_turns: 0,
32107
+ total_events: 0
32108
+ };
32109
+ }
32110
+ let totalWithout = 0;
32111
+ let totalWith = 0;
32112
+ let graphCalls = 0;
32113
+ let graphTokensDelivered = 0;
32114
+ let shellCompressionEvents = 0;
32115
+ let behaviorEvents = 0;
32116
+ let blastRadiusWarnings = 0;
32117
+ let circuitBreakerActivations = 0;
32118
+ let conventionInjections = 0;
32119
+ let explorationLoopsPrevented = 0;
32120
+ let dedupEvents = 0;
32121
+ const sessions = /* @__PURE__ */ new Set();
32122
+ const turns = /* @__PURE__ */ new Set();
32123
+ for (const e of events) {
32124
+ totalWithout += e.tokens_without;
32125
+ totalWith += e.tokens_with;
32126
+ sessions.add(e.session_id);
32127
+ turns.add(`${e.session_id}:${e.turn}`);
32128
+ if (GRAPH_MECHANISMS.has(e.mechanism)) {
32129
+ graphCalls++;
32130
+ graphTokensDelivered += e.tokens_with;
32131
+ }
32132
+ if (SHELL_MECHANISMS.has(e.mechanism)) {
32133
+ shellCompressionEvents++;
32134
+ }
32135
+ if (SAFETY_MECHANISMS.has(e.mechanism)) {
32136
+ behaviorEvents++;
32137
+ }
32138
+ const d = e.detail;
32139
+ if (d) {
32140
+ if (d.counterfactual === "blast_radius" || d.blast_radius) {
32141
+ blastRadiusWarnings++;
32142
+ }
32143
+ if (d.circuit_breaker || d.counterfactual === "circuit_breaker") {
32144
+ circuitBreakerActivations++;
32145
+ }
32146
+ if (d.conventions_injected || d.counterfactual === "convention_injection") {
32147
+ conventionInjections++;
32148
+ }
32149
+ if (d.hook_redirect || d.counterfactual === "hook_redirect") {
32150
+ explorationLoopsPrevented++;
32151
+ }
32152
+ }
32153
+ if (e.mechanism === "session_dedup") {
32154
+ dedupEvents++;
32155
+ }
32156
+ }
32157
+ const totalSaved = totalWithout - totalWith;
32158
+ const totalToolCalls = events.length;
32159
+ const snr = totalWithout > 0 ? totalWith / totalWithout : 0;
32160
+ const noiseRemovedPct = totalWithout > 0 ? Math.round((totalWithout - totalWith) / totalWithout * 100) : 0;
32161
+ const contextDensity = graphTokensDelivered > 0 ? Math.round(graphCalls / (graphTokensDelivered / 1e3) * 10) / 10 : 0;
32162
+ const compressionRatio = totalWith > 0 ? totalWithout / totalWith : 1;
32163
+ const attentionMultiplier = Math.round(Math.sqrt(compressionRatio) * 100) / 100;
32164
+ const firstCallRate = totalToolCalls > 0 ? Math.round(graphCalls / totalToolCalls * 100) : 0;
32165
+ const turnsSaved = Math.round(graphCalls * 2.5);
32166
+ const totalConventionInjections = conventionInjections + behaviorEvents;
32167
+ const preventionScore = blastRadiusWarnings + circuitBreakerActivations * 10 + totalConventionInjections;
32168
+ const firstCallRateDecimal = firstCallRate / 100;
32169
+ const reasoningMultiplier = compressionRatio > 0 && firstCallRateDecimal > 0 ? Math.round(compressionRatio * (1 + firstCallRateDecimal) * 100) / 100 : 0;
32170
+ return {
32171
+ signal_to_noise_ratio: Math.round(snr * 1e3) / 1e3,
32172
+ noise_removed_pct: noiseRemovedPct,
32173
+ context_density: contextDensity,
32174
+ entities_resolved: graphCalls,
32175
+ graph_tokens_delivered: graphTokensDelivered,
32176
+ attention_multiplier: attentionMultiplier,
32177
+ first_call_resolution_rate: firstCallRate,
32178
+ graph_calls: graphCalls,
32179
+ total_tool_calls: totalToolCalls,
32180
+ turns_saved: turnsSaved,
32181
+ exploration_loops_prevented: explorationLoopsPrevented,
32182
+ blast_radius_warnings: blastRadiusWarnings,
32183
+ circuit_breaker_activations: circuitBreakerActivations,
32184
+ convention_injections: totalConventionInjections,
32185
+ prevention_score: preventionScore,
32186
+ reasoning_quality_multiplier: reasoningMultiplier,
32187
+ total_sessions: sessions.size,
32188
+ total_turns: turns.size,
32189
+ total_events: events.length
32190
+ };
32191
+ }
32192
+ function createReasoningQualityRoutes(deps) {
32193
+ const app = new Hono7();
32194
+ app.get("/global", (c) => {
32195
+ const start = performance.now();
32196
+ const fromTs = c.req.query("from_ts");
32197
+ const toTs = c.req.query("to_ts");
32198
+ const events = readTokenFlowEvents(deps.unerrDir, {
32199
+ from_ts: fromTs || void 0,
32200
+ to_ts: toTs || void 0
32201
+ });
32202
+ const metrics = computeQualityMetrics(events);
32203
+ return c.json({
32204
+ data: metrics,
32205
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32206
+ });
32207
+ });
32208
+ app.get("/session", (c) => {
32209
+ const start = performance.now();
32210
+ const querySessionId = c.req.query("session_id");
32211
+ const writer = deps.getTokenFlowWriter();
32212
+ const allEvents = readTokenFlowEvents(deps.unerrDir);
32213
+ if (allEvents.length === 0) {
32214
+ return c.json({ data: null, _meta: { latency_ms: 0 } });
32215
+ }
32216
+ let sessionId = querySessionId || writer?.sessionId;
32217
+ if (!sessionId) {
32218
+ sessionId = allEvents[allEvents.length - 1]?.session_id;
32219
+ }
32220
+ if (!sessionId) {
32221
+ return c.json({ data: null, _meta: { latency_ms: 0 } });
32222
+ }
32223
+ const sessionEvents = querySessionId ? allEvents.filter((e) => e.session_id === sessionId) : allEvents.filter((e) => e.session_id === sessionId || e.session_id === "unknown");
32224
+ const metrics = computeQualityMetrics(sessionEvents);
32225
+ const turnGroups = /* @__PURE__ */ new Map();
32226
+ for (const e of sessionEvents) {
32227
+ const group = turnGroups.get(e.turn) ?? [];
32228
+ group.push(e);
32229
+ turnGroups.set(e.turn, group);
32230
+ }
32231
+ const trajectory = [];
32232
+ let cumWithout = 0;
32233
+ let cumWith = 0;
32234
+ for (const [turn, turnEvents] of [...turnGroups.entries()].sort(([a], [b]) => a - b)) {
32235
+ let turnGraphCalls = 0;
32236
+ let turnGraphDelivered = 0;
32237
+ for (const e of turnEvents) {
32238
+ cumWithout += e.tokens_without;
32239
+ cumWith += e.tokens_with;
32240
+ if (GRAPH_MECHANISMS.has(e.mechanism)) {
32241
+ turnGraphCalls++;
32242
+ turnGraphDelivered += e.tokens_with;
32243
+ }
32244
+ }
32245
+ trajectory.push({
32246
+ turn,
32247
+ snr: cumWithout > 0 ? Math.round(cumWith / cumWithout * 1e3) / 1e3 : 0,
32248
+ cumulative_noise_removed_pct: cumWithout > 0 ? Math.round((cumWithout - cumWith) / cumWithout * 100) : 0,
32249
+ graph_calls_this_turn: turnGraphCalls,
32250
+ context_density: turnGraphDelivered > 0 ? Math.round(turnGraphCalls / (turnGraphDelivered / 1e3) * 10) / 10 : 0
32251
+ });
32252
+ }
32253
+ return c.json({
32254
+ data: {
32255
+ ...metrics,
32256
+ session_id: sessionId,
32257
+ trajectory
32258
+ },
32259
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32260
+ });
32261
+ });
32262
+ app.get("/sessions", (c) => {
32263
+ const start = performance.now();
32264
+ const fromTs = c.req.query("from_ts");
32265
+ const toTs = c.req.query("to_ts");
32266
+ const limit = Math.min(Number(c.req.query("limit") ?? 50), 200);
32267
+ const offset = Math.max(Number(c.req.query("offset") ?? 0), 0);
32268
+ const allEvents = readTokenFlowEvents(deps.unerrDir, {
32269
+ from_ts: fromTs || void 0,
32270
+ to_ts: toTs || void 0
32271
+ });
32272
+ const historyEntries = readSessionHistory(deps.unerrDir);
32273
+ const agentBySession = /* @__PURE__ */ new Map();
32274
+ for (const h of historyEntries) {
32275
+ if (h.agentName) agentBySession.set(h.sessionId, h.agentName);
32276
+ }
32277
+ const sessionMap = /* @__PURE__ */ new Map();
32278
+ for (const e of allEvents) {
32279
+ const group = sessionMap.get(e.session_id) ?? [];
32280
+ group.push(e);
32281
+ sessionMap.set(e.session_id, group);
32282
+ }
32283
+ const allSessions = [...sessionMap.entries()].map(([sessionId, events]) => {
32284
+ const m = computeQualityMetrics(events);
32285
+ const lastTs = events.reduce((max, e) => e.ts > max ? e.ts : max, events[0].ts);
32286
+ const firstTs = events.reduce((min, e) => e.ts < min ? e.ts : min, events[0].ts);
32287
+ return {
32288
+ session_id: sessionId,
32289
+ first_ts: firstTs,
32290
+ last_ts: lastTs,
32291
+ agent_name: agentBySession.get(sessionId) ?? deps.getAgentName?.(sessionId) ?? null,
32292
+ noise_removed_pct: m.noise_removed_pct,
32293
+ first_call_resolution_rate: m.first_call_resolution_rate,
32294
+ prevention_score: m.prevention_score,
32295
+ reasoning_quality_multiplier: m.reasoning_quality_multiplier,
32296
+ context_density: m.context_density,
32297
+ turns_saved: m.turns_saved,
32298
+ total_events: m.total_events,
32299
+ total_turns: m.total_turns
32300
+ };
32301
+ }).sort((a, b) => b.last_ts.localeCompare(a.last_ts));
32302
+ const paginated = allSessions.slice(offset, offset + limit);
32303
+ return c.json({
32304
+ data: paginated,
32305
+ total: allSessions.length,
32306
+ limit,
32307
+ offset,
32308
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32309
+ });
32310
+ });
32311
+ app.get("/trend", (c) => {
32312
+ const start = performance.now();
32313
+ const fromTs = c.req.query("from_ts");
32314
+ const toTs = c.req.query("to_ts");
32315
+ const allEvents = readTokenFlowEvents(deps.unerrDir, {
32316
+ from_ts: fromTs || void 0,
32317
+ to_ts: toTs || void 0
32318
+ });
32319
+ const sessionMap = /* @__PURE__ */ new Map();
32320
+ for (const e of allEvents) {
32321
+ const group = sessionMap.get(e.session_id) ?? [];
32322
+ group.push(e);
32323
+ sessionMap.set(e.session_id, group);
32324
+ }
32325
+ const trend = [...sessionMap.entries()].map(([sessionId, events]) => {
32326
+ const m = computeQualityMetrics(events);
32327
+ const firstTs = events.reduce((min, e) => e.ts < min ? e.ts : min, events[0].ts);
32328
+ const lastTs = events.reduce((max, e) => e.ts > max ? e.ts : max, events[0].ts);
32329
+ return {
32330
+ session_id: sessionId,
32331
+ first_ts: firstTs,
32332
+ last_ts: lastTs,
32333
+ noise_removed_pct: m.noise_removed_pct,
32334
+ first_call_resolution_rate: m.first_call_resolution_rate,
32335
+ prevention_score: m.prevention_score,
32336
+ reasoning_quality_multiplier: m.reasoning_quality_multiplier,
32337
+ context_density: m.context_density,
32338
+ attention_multiplier: m.attention_multiplier,
32339
+ turns_saved: m.turns_saved,
32340
+ total_events: m.total_events
32341
+ };
32342
+ }).sort((a, b) => a.first_ts.localeCompare(b.first_ts));
32343
+ return c.json({
32344
+ data: trend,
32345
+ total: trend.length,
32346
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32347
+ });
32348
+ });
32349
+ return app;
32350
+ }
32351
+ var GRAPH_MECHANISMS, SHELL_MECHANISMS, SAFETY_MECHANISMS;
32352
+ var init_reasoning_quality = __esm({
32353
+ "src/server/routes/reasoning-quality.ts"() {
32354
+ "use strict";
32355
+ init_token_flow();
32356
+ init_session_history();
32357
+ GRAPH_MECHANISMS = /* @__PURE__ */ new Set(["graph_query", "file_read"]);
32358
+ SHELL_MECHANISMS = /* @__PURE__ */ new Set(["shell_compression"]);
32359
+ SAFETY_MECHANISMS = /* @__PURE__ */ new Set(["behavior_automation"]);
32360
+ }
32361
+ });
32362
+
31353
32363
  // src/server/http.ts
31354
32364
  var http_exports = {};
31355
32365
  __export(http_exports, {
31356
32366
  startDashboardServer: () => startDashboardServer
31357
32367
  });
31358
- import { existsSync as existsSync55, readFileSync as readFileSync51, unlinkSync as unlinkSync8, writeFileSync as writeFileSync32 } from "fs";
32368
+ import { existsSync as existsSync56, readFileSync as readFileSync52, unlinkSync as unlinkSync8, writeFileSync as writeFileSync32 } from "fs";
31359
32369
  import { createServer as createServer3 } from "net";
31360
- import { dirname as dirname7, join as join60 } from "path";
31361
- import { fileURLToPath } from "url";
32370
+ import { dirname as dirname7, join as join61 } from "path";
32371
+ import { fileURLToPath as fileURLToPath2 } from "url";
31362
32372
  import { serve } from "@hono/node-server";
31363
32373
  import { serveStatic } from "@hono/node-server/serve-static";
31364
- import { Hono as Hono7 } from "hono";
32374
+ import { Hono as Hono8 } from "hono";
31365
32375
  async function findAvailablePort() {
31366
32376
  for (let port = PORT_START; port <= PORT_END; port++) {
31367
32377
  const available = await new Promise((resolve3) => {
@@ -31385,7 +32395,7 @@ async function startDashboardServer(opts) {
31385
32395
  return null;
31386
32396
  }
31387
32397
  opts.system.dashboardPort = port;
31388
- const app = new Hono7();
32398
+ const app = new Hono8();
31389
32399
  app.use("*", corsMiddleware);
31390
32400
  app.use("*", cacheMiddleware);
31391
32401
  app.use("*", timingMiddleware);
@@ -31400,10 +32410,13 @@ async function startDashboardServer(opts) {
31400
32410
  if (opts.tokenFlow) {
31401
32411
  app.route("/api/token-flow", createTokenFlowRoutes(opts.tokenFlow));
31402
32412
  }
31403
- const distDir = join60(dirname7(fileURLToPath(import.meta.url)), "ui");
31404
- const spaIndex = join60(distDir, "index.html");
31405
- if (existsSync55(spaIndex)) {
31406
- const spaHtml = readFileSync51(spaIndex, "utf-8");
32413
+ if (opts.reasoningQuality) {
32414
+ app.route("/api/reasoning-quality", createReasoningQualityRoutes(opts.reasoningQuality));
32415
+ }
32416
+ const distDir = join61(dirname7(fileURLToPath2(import.meta.url)), "ui");
32417
+ const spaIndex = join61(distDir, "index.html");
32418
+ if (existsSync56(spaIndex)) {
32419
+ const spaHtml = readFileSync52(spaIndex, "utf-8");
31407
32420
  app.use("*", serveStatic({ root: distDir }));
31408
32421
  app.get("*", (c) => {
31409
32422
  const path7 = c.req.path;
@@ -31432,7 +32445,7 @@ async function startDashboardServer(opts) {
31432
32445
  port,
31433
32446
  hostname: "127.0.0.1"
31434
32447
  });
31435
- const serverJsonPath = join60(opts.stateDir, "server.json");
32448
+ const serverJsonPath = join61(opts.stateDir, "server.json");
31436
32449
  const serverInfo = {
31437
32450
  port,
31438
32451
  pid: process.pid,
@@ -31466,6 +32479,7 @@ var init_http = __esm({
31466
32479
  init_system();
31467
32480
  init_temporal();
31468
32481
  init_token_flow2();
32482
+ init_reasoning_quality();
31469
32483
  PORT_START = 7600;
31470
32484
  PORT_END = 7700;
31471
32485
  }
@@ -31833,13 +32847,13 @@ var proxy_exports = {};
31833
32847
  __export(proxy_exports, {
31834
32848
  startProxy: () => startProxy
31835
32849
  });
31836
- import { existsSync as existsSync56, mkdirSync as mkdirSync34, readFileSync as readFileSync52, readdirSync as readdirSync12 } from "fs";
31837
- import { join as join61 } from "path";
32850
+ import { existsSync as existsSync57, mkdirSync as mkdirSync34, readFileSync as readFileSync53, readdirSync as readdirSync13, writeFileSync as fsWriteFileSync } from "fs";
32851
+ import { join as join62 } from "path";
31838
32852
  async function getProxyFactStore(unerrDir) {
31839
32853
  if (proxyFactStore !== void 0) return proxyFactStore;
31840
32854
  try {
31841
32855
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
31842
- const cwd = join61(unerrDir, "..");
32856
+ const cwd = join62(unerrDir, "..");
31843
32857
  proxyFactStore = await TemporalFactStore2.create(cwd);
31844
32858
  return proxyFactStore;
31845
32859
  } catch {
@@ -31917,15 +32931,32 @@ async function handleRecallFactsProxy(args, unerrDir) {
31917
32931
  };
31918
32932
  }
31919
32933
  }
32934
+ function migrateAgentPermissions(cwd) {
32935
+ try {
32936
+ const settingsPath = join62(cwd, ".claude", "settings.json");
32937
+ if (!existsSync57(settingsPath)) return;
32938
+ const raw = readFileSync53(settingsPath, "utf-8");
32939
+ const settings = JSON.parse(raw);
32940
+ const deny = settings?.permissions?.deny;
32941
+ if (!Array.isArray(deny)) return;
32942
+ const readIdx = deny.indexOf("Read");
32943
+ if (readIdx < 0) return;
32944
+ deny.splice(readIdx, 1);
32945
+ fsWriteFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32946
+ process.stderr.write("[unerr] Migrated permissions: removed Read from deny list (required for Edit workflow)\n");
32947
+ } catch {
32948
+ }
32949
+ }
31920
32950
  async function startProxy(opts = {}) {
31921
32951
  const stats = createSessionStats(true);
31922
32952
  const startup = new StartupRenderer();
31923
32953
  startup.mount();
32954
+ migrateAgentPermissions(process.cwd());
31924
32955
  const lifecycle = createLifecycleActor(process.cwd());
31925
32956
  lifecycle.send({ type: "START_DETECT" });
31926
32957
  startup.setLocalMode(true);
31927
- const stateDir = join61(process.cwd(), ".unerr", "state");
31928
- if (!existsSync56(stateDir)) {
32958
+ const stateDir = join62(process.cwd(), ".unerr", "state");
32959
+ if (!existsSync57(stateDir)) {
31929
32960
  mkdirSync34(stateDir, { recursive: true });
31930
32961
  }
31931
32962
  const pidLock = new PidLock(stateDir);
@@ -31943,7 +32974,7 @@ async function startProxy(opts = {}) {
31943
32974
  startupLog.step(
31944
32975
  `PID ${process.pid} ${startupLog.fmt.muted(`\xB7 health localhost:${lockResult.healthPort}`)}`
31945
32976
  );
31946
- const ledgerDir = join61(process.cwd(), ".unerr", "ledger");
32977
+ const ledgerDir = join62(process.cwd(), ".unerr", "ledger");
31947
32978
  const previousSession = detectSessionResume(stateDir, ledgerDir);
31948
32979
  if (previousSession) {
31949
32980
  stats.isResumedSession = true;
@@ -31958,17 +32989,17 @@ async function startProxy(opts = {}) {
31958
32989
  if (opts.repoId) {
31959
32990
  repoIds = [opts.repoId];
31960
32991
  } else {
31961
- const configPath = join61(process.cwd(), ".unerr", "config.json");
31962
- if (existsSync56(configPath)) {
32992
+ const configPath = join62(process.cwd(), ".unerr", "config.json");
32993
+ if (existsSync57(configPath)) {
31963
32994
  try {
31964
- const config = JSON.parse(readFileSync52(configPath, "utf-8"));
32995
+ const config = JSON.parse(readFileSync53(configPath, "utf-8"));
31965
32996
  if (config.repoId) repoIds = [config.repoId];
31966
32997
  } catch {
31967
32998
  }
31968
32999
  }
31969
- const manifestsDir = join61(process.cwd(), ".unerr", "manifests");
31970
- if (repoIds.length === 0 && existsSync56(manifestsDir)) {
31971
- repoIds = readdirSync12(manifestsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
33000
+ const manifestsDir = join62(process.cwd(), ".unerr", "manifests");
33001
+ if (repoIds.length === 0 && existsSync57(manifestsDir)) {
33002
+ repoIds = readdirSync13(manifestsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
31972
33003
  }
31973
33004
  }
31974
33005
  if (repoIds.length === 0) {
@@ -32048,13 +33079,10 @@ async function startProxy(opts = {}) {
32048
33079
  startupLog.perf(
32049
33080
  `${startupLog.fmt.cyan("Persistent graph")} ${startupLog.fmt.muted("\u2014 zero recomputation, all intelligence preserved")}`
32050
33081
  );
32051
- const { shouldReindex: shouldReindex2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
32052
- if (shouldReindex2(projectRoot)) {
32053
- needsBackgroundIndex = true;
32054
- startupLog.step(
32055
- `${startupLog.fmt.muted("Source changes detected \u2014 incremental update after MCP ready")}`
32056
- );
32057
- }
33082
+ needsBackgroundIndex = true;
33083
+ startupLog.step(
33084
+ `${startupLog.fmt.muted("Background reindex will refresh graph data after MCP ready")}`
33085
+ );
32058
33086
  } else {
32059
33087
  const { loadLocalSnapshot: loadLocalSnapshot2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
32060
33088
  const snapshotStart = Date.now();
@@ -32094,6 +33122,32 @@ async function startProxy(opts = {}) {
32094
33122
  startupLog.done(
32095
33123
  `Migrated snapshot to persistent graph ${startupLog.fmt.muted(`\u2192 ${dbPath}`)}`
32096
33124
  );
33125
+ try {
33126
+ const migrationUnerrDir = join62(process.cwd(), ".unerr");
33127
+ const factStoreForMigration = await getProxyFactStore(migrationUnerrDir);
33128
+ if (factStoreForMigration) {
33129
+ const { detectLocalConventions: detectLocalConventions2 } = await Promise.resolve().then(() => (init_local_convention_detector(), local_convention_detector_exports));
33130
+ const { generateFromConventions: generateFromConventions2, runFactGenerationPipeline: runFactGenerationPipeline2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
33131
+ const detection = await detectLocalConventions2(localGraph.db);
33132
+ if (detection.conventions.length > 0) {
33133
+ const convResult = await generateFromConventions2(factStoreForMigration, detection.conventions);
33134
+ if (convResult.created > 0 || convResult.reinforced > 0) {
33135
+ log21.info(
33136
+ `Fact generator: ${convResult.created} convention facts created, ${convResult.reinforced} reinforced`
33137
+ );
33138
+ }
33139
+ }
33140
+ const pipelineResults = await runFactGenerationPipeline2(factStoreForMigration, migrationUnerrDir);
33141
+ for (const r of pipelineResults) {
33142
+ if (r.created > 0 || r.reinforced > 0) {
33143
+ log21.info(
33144
+ `Fact generator [${r.source}]: ${r.created} created, ${r.reinforced} reinforced`
33145
+ );
33146
+ }
33147
+ }
33148
+ }
33149
+ } catch {
33150
+ }
32097
33151
  } else {
32098
33152
  needsBackgroundIndex = true;
32099
33153
  startupLog.step(
@@ -32161,7 +33215,7 @@ async function startProxy(opts = {}) {
32161
33215
  try {
32162
33216
  const { generateSessionResume: generateSessionResume2 } = await Promise.resolve().then(() => (init_session_resume(), session_resume_exports));
32163
33217
  const { ShadowLedger: ResumeLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32164
- const resumeLedger = new ResumeLedger(join61(process.cwd(), ".unerr"));
33218
+ const resumeLedger = new ResumeLedger(join62(process.cwd(), ".unerr"));
32165
33219
  const ledgerEntries = resumeLedger.getRecentEntries(50);
32166
33220
  const resumeCtx = generateSessionResume2(ledgerEntries);
32167
33221
  if (resumeCtx) {
@@ -32181,7 +33235,7 @@ async function startProxy(opts = {}) {
32181
33235
  const { createDurabilityScorer: createDurabilityScorer2 } = await Promise.resolve().then(() => (init_durability_scorer(), durability_scorer_exports));
32182
33236
  const durabilityScorer = createDurabilityScorer2();
32183
33237
  const { ShadowLedger: DurLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32184
- const durLedger = new DurLedger(join61(process.cwd(), ".unerr"));
33238
+ const durLedger = new DurLedger(join62(process.cwd(), ".unerr"));
32185
33239
  const durEntries = durLedger.getRecentEntries(200);
32186
33240
  if (durEntries.length > 0) {
32187
33241
  durabilityScorer.computeScores(durEntries);
@@ -32201,7 +33255,7 @@ async function startProxy(opts = {}) {
32201
33255
  try {
32202
33256
  const { detectInstableEntities: detectInstableEntities2 } = await Promise.resolve().then(() => (init_negative_knowledge(), negative_knowledge_exports));
32203
33257
  const { ShadowLedger: NkLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32204
- const nkLedger = new NkLedger(join61(process.cwd(), ".unerr"));
33258
+ const nkLedger = new NkLedger(join62(process.cwd(), ".unerr"));
32205
33259
  const nkEntries = nkLedger.getRecentEntries(200);
32206
33260
  if (nkEntries.length > 0) {
32207
33261
  const antiPatterns = detectInstableEntities2(nkEntries);
@@ -32223,7 +33277,7 @@ async function startProxy(opts = {}) {
32223
33277
  try {
32224
33278
  const { CausalBridge: CausalBridge2 } = await Promise.resolve().then(() => (init_causal_bridge(), causal_bridge_exports));
32225
33279
  const causalBridge = new CausalBridge2(
32226
- join61(process.cwd(), ".unerr"),
33280
+ join62(process.cwd(), ".unerr"),
32227
33281
  process.cwd()
32228
33282
  );
32229
33283
  router2.setCausalBridge(causalBridge);
@@ -32233,7 +33287,7 @@ async function startProxy(opts = {}) {
32233
33287
  try {
32234
33288
  const { learnConventions: learnConventions2 } = await Promise.resolve().then(() => (init_convention_learner(), convention_learner_exports));
32235
33289
  const { ShadowLedger: ConvLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32236
- const convLedger = new ConvLedger(join61(process.cwd(), ".unerr"));
33290
+ const convLedger = new ConvLedger(join62(process.cwd(), ".unerr"));
32237
33291
  const convEntries = convLedger.getRecentEntries(100);
32238
33292
  if (convEntries.length > 0) {
32239
33293
  const learned = learnConventions2(convEntries);
@@ -32256,7 +33310,7 @@ async function startProxy(opts = {}) {
32256
33310
  try {
32257
33311
  const { computePromptDurabilityProfiles: computePromptDurabilityProfiles2 } = await Promise.resolve().then(() => (init_prompt_durability(), prompt_durability_exports));
32258
33312
  const { ShadowLedger: DurProfLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32259
- const durProfLedger = new DurProfLedger(join61(process.cwd(), ".unerr"));
33313
+ const durProfLedger = new DurProfLedger(join62(process.cwd(), ".unerr"));
32260
33314
  const durProfEntries = durProfLedger.getRecentEntries(200);
32261
33315
  if (durProfEntries.length > 0) {
32262
33316
  const profiles = computePromptDurabilityProfiles2(durProfEntries);
@@ -32276,7 +33330,7 @@ async function startProxy(opts = {}) {
32276
33330
  }
32277
33331
  try {
32278
33332
  const { createContextLedger: createContextLedger2 } = await Promise.resolve().then(() => (init_context_ledger(), context_ledger_exports));
32279
- const contextLedger = createContextLedger2(join61(process.cwd(), ".unerr"));
33333
+ const contextLedger = createContextLedger2(join62(process.cwd(), ".unerr"));
32280
33334
  contextLedger.load();
32281
33335
  contextLedger.prune();
32282
33336
  router2.setContextLedger(contextLedger);
@@ -32365,7 +33419,7 @@ async function startProxy(opts = {}) {
32365
33419
  }));
32366
33420
  const { ShadowLedger: ShadowLedger2 } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32367
33421
  const { IntentCorrelator: IntentCorrelator2 } = await Promise.resolve().then(() => (init_intent_correlator(), intent_correlator_exports));
32368
- const unerrDirForLedger = join61(process.cwd(), ".unerr");
33422
+ const unerrDirForLedger = join62(process.cwd(), ".unerr");
32369
33423
  const shadowLedger2 = new ShadowLedger2(unerrDirForLedger);
32370
33424
  const intentCorrelator = new IntentCorrelator2(unerrDirForLedger);
32371
33425
  log21.info(
@@ -32379,7 +33433,7 @@ async function startProxy(opts = {}) {
32379
33433
  process.env.UNERR_SESSION_ID = shadowLedger2.getSessionId();
32380
33434
  try {
32381
33435
  const { writeFileSync: writeFileSync36 } = await import("fs");
32382
- writeFileSync36(join61(unerrDirForLedger, "state", "session.id"), shadowLedger2.getSessionId(), "utf-8");
33436
+ writeFileSync36(join62(unerrDirForLedger, "state", "session.id"), shadowLedger2.getSessionId(), "utf-8");
32383
33437
  } catch {
32384
33438
  }
32385
33439
  router2.setTokenFlow(tokenFlowWriter2);
@@ -32718,8 +33772,9 @@ async function startProxy(opts = {}) {
32718
33772
  lifecycle.send({ type: "INDEX_COMPLETE" });
32719
33773
  lifecycle.send({ type: "MCP_READY" });
32720
33774
  const { TransportMux: TransportMux2 } = await Promise.resolve().then(() => (init_transport_mux(), transport_mux_exports));
32721
- const sockPath = join61(stateDir, "proxy.sock");
33775
+ const sockPath = join62(stateDir, "proxy.sock");
32722
33776
  const transportMux = new TransportMux2(sockPath);
33777
+ const agentNameByClient = /* @__PURE__ */ new Map();
32723
33778
  transportMux.setCustomHttpHandler("/commit-context", (_url) => {
32724
33779
  const { getCommitTrailers: getCommitTrailers2 } = (init_git_trailers(), __toCommonJS(git_trailers_exports));
32725
33780
  const branch = branchContext?.currentBranch ?? "unknown";
@@ -32729,6 +33784,10 @@ async function startProxy(opts = {}) {
32729
33784
  });
32730
33785
  transportMux.setHandler(async (clientId, message) => {
32731
33786
  if (message.method === "initialize") {
33787
+ const clientName = message.params?.clientInfo?.name;
33788
+ if (clientName) {
33789
+ agentNameByClient.set(clientId, clientName);
33790
+ }
32732
33791
  return {
32733
33792
  jsonrpc: "2.0",
32734
33793
  id: message.id,
@@ -32757,6 +33816,88 @@ async function startProxy(opts = {}) {
32757
33816
  };
32758
33817
  }
32759
33818
  const { name, arguments: toolArgs = {} } = params;
33819
+ toolUsageTracker2.record(name);
33820
+ if (name === "record_fact") {
33821
+ const factResult = await handleRecordFactProxy(toolArgs, unerrDirForLedger, shadowLedger2);
33822
+ return { jsonrpc: "2.0", result: factResult };
33823
+ }
33824
+ if (name === "recall_facts") {
33825
+ const factResult = await handleRecallFactsProxy(toolArgs, unerrDirForLedger);
33826
+ return { jsonrpc: "2.0", result: factResult };
33827
+ }
33828
+ if (name === "unerr_mark_working") {
33829
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33830
+ const headSha2 = branchContext?.headSha ?? "";
33831
+ const snapshot = workingSnapshotStore.create({
33832
+ commitSha: headSha2,
33833
+ reason: toolArgs.reason ?? "manual mark",
33834
+ branch: branch2,
33835
+ timelineBranch: workingSnapshotStore.getTimelineBranch(),
33836
+ sessionId: shadowLedger2.getSessionId()
33837
+ });
33838
+ shadowLedger2.record(name, toolArgs, { snapshot_id: snapshot.id, commit: headSha2 }, branch2, headSha2);
33839
+ return {
33840
+ jsonrpc: "2.0",
33841
+ result: {
33842
+ content: [{ type: "text", text: stringifyMcpToolJson({ snapshot_id: snapshot.id, commit_sha: snapshot.commitSha, reason: snapshot.reason, timestamp: snapshot.timestamp, branch: snapshot.branch }) }],
33843
+ _meta: { source: "local", latency_ms: 0, format: "json" }
33844
+ }
33845
+ };
33846
+ }
33847
+ if (name === "unerr_revert_to_working_state") {
33848
+ const { revertToWorkingState: revertToWorkingState2 } = await Promise.resolve().then(() => (init_rewind_engine(), rewind_engine_exports));
33849
+ const revertResult = await revertToWorkingState2({
33850
+ snapshotId: toolArgs.snapshot_id ?? "latest",
33851
+ cwd: process.cwd(),
33852
+ unerrDir: unerrDirForLedger,
33853
+ graph: graphForRouter,
33854
+ ledger: shadowLedger2,
33855
+ snapshotStore: workingSnapshotStore,
33856
+ dryRun: toolArgs.dry_run ?? false
33857
+ });
33858
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33859
+ const headSha2 = branchContext?.headSha ?? "";
33860
+ shadowLedger2.record(name, toolArgs, { success: revertResult.success, snapshot_id: revertResult.snapshot?.id, files_restored: revertResult.rewindResult?.filesRestored.length ?? 0 }, branch2, headSha2);
33861
+ return {
33862
+ jsonrpc: "2.0",
33863
+ result: {
33864
+ content: [{ type: "text", text: stringifyMcpToolJson(revertResult) }],
33865
+ _meta: { source: "local", latency_ms: 0, format: "json" }
33866
+ }
33867
+ };
33868
+ }
33869
+ if (name === "unerr_get_timeline") {
33870
+ const { getTimeline: getTimeline2 } = await Promise.resolve().then(() => (init_timeline(), timeline_exports));
33871
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33872
+ const timelineBranch = workingSnapshotStore.getTimelineBranch();
33873
+ const timeline = getTimeline2(shadowLedger2, branch2, timelineBranch, {
33874
+ branch: toolArgs.branch,
33875
+ limit: toolArgs.limit,
33876
+ tool: toolArgs.tool,
33877
+ rootsOnly: toolArgs.roots_only
33878
+ });
33879
+ return {
33880
+ jsonrpc: "2.0",
33881
+ result: {
33882
+ content: [{ type: "text", text: stringifyMcpToolJson(timeline) }],
33883
+ _meta: { source: "local", latency_ms: 0, format: "json" }
33884
+ }
33885
+ };
33886
+ }
33887
+ if (localGraph) {
33888
+ const { handleDeepDiveTool: handleDeepDiveTool2 } = await Promise.resolve().then(() => (init_deep_dive_tools(), deep_dive_tools_exports));
33889
+ const deepDiveResult = await handleDeepDiveTool2(name, toolArgs, localGraph);
33890
+ if (deepDiveResult) {
33891
+ recordToolCall(stats);
33892
+ recordLatency(stats.latency, 0);
33893
+ pidLock.recordToolCall();
33894
+ if (stats.localMode) recordGraphQuery(stats.localMode, name);
33895
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33896
+ const headSha2 = branchContext?.headSha ?? "";
33897
+ shadowLedger2.record(name, toolArgs, { tool: name, source: "local", client: clientId }, branch2, headSha2);
33898
+ return { jsonrpc: "2.0", result: deepDiveResult };
33899
+ }
33900
+ }
32760
33901
  const result = await router2.execute(name, toolArgs);
32761
33902
  recordToolCall(stats);
32762
33903
  recordLatency(stats.latency, result._meta.latency_ms);
@@ -32829,7 +33970,7 @@ async function startProxy(opts = {}) {
32829
33970
  try {
32830
33971
  const { DriftTracker: DriftTracker2 } = await Promise.resolve().then(() => (init_drift_tracker(), drift_tracker_exports));
32831
33972
  const { FileHashManager: FileHashManager2 } = await Promise.resolve().then(() => (init_file_hash_state(), file_hash_state_exports));
32832
- const unerrDir = join61(process.cwd(), ".unerr");
33973
+ const unerrDir = join62(process.cwd(), ".unerr");
32833
33974
  const fileHashManager = new FileHashManager2(unerrDir);
32834
33975
  _driftTracker = new DriftTracker2(
32835
33976
  { projectRoot: process.cwd(), repoId: repoIds[0], unerrDir },
@@ -32974,6 +34115,32 @@ async function startProxy(opts = {}) {
32974
34115
  `Post-index DriftTracker init failed: ${err instanceof Error ? err.message : String(err)}`
32975
34116
  );
32976
34117
  });
34118
+ try {
34119
+ const factStoreForGen = await getProxyFactStore(unerrDirForLedger);
34120
+ if (factStoreForGen) {
34121
+ const { detectLocalConventions: detectLocalConventions2 } = await Promise.resolve().then(() => (init_local_convention_detector(), local_convention_detector_exports));
34122
+ const { generateFromConventions: generateFromConventions2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34123
+ const detection = await detectLocalConventions2(localGraph.db);
34124
+ if (detection.conventions.length > 0) {
34125
+ const convResult = await generateFromConventions2(factStoreForGen, detection.conventions);
34126
+ if (convResult.created > 0 || convResult.reinforced > 0) {
34127
+ log21.info(
34128
+ `Fact generator: ${convResult.created} convention facts created, ${convResult.reinforced} reinforced`
34129
+ );
34130
+ }
34131
+ }
34132
+ const { runFactGenerationPipeline: runFactGenerationPipeline2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34133
+ const pipelineResults = await runFactGenerationPipeline2(factStoreForGen, unerrDirForLedger);
34134
+ for (const r of pipelineResults) {
34135
+ if (r.created > 0 || r.reinforced > 0) {
34136
+ log21.info(
34137
+ `Fact generator [${r.source}]: ${r.created} created, ${r.reinforced} reinforced`
34138
+ );
34139
+ }
34140
+ }
34141
+ }
34142
+ } catch {
34143
+ }
32977
34144
  },
32978
34145
  // onError
32979
34146
  (err) => {
@@ -32997,7 +34164,7 @@ async function startProxy(opts = {}) {
32997
34164
  }
32998
34165
  const { WorkspaceManifest: WorkspaceManifest2 } = await Promise.resolve().then(() => (init_workspace_manifest(), workspace_manifest_exports));
32999
34166
  const workspaceManifest = repoIds[0] ? new WorkspaceManifest2(
33000
- join61(process.cwd(), ".unerr"),
34167
+ join62(process.cwd(), ".unerr"),
33001
34168
  repoIds[0],
33002
34169
  shadowLedger2.getSessionId()
33003
34170
  ) : null;
@@ -33082,7 +34249,7 @@ async function startProxy(opts = {}) {
33082
34249
  if (proxyMode === "parse" && parseIndex) {
33083
34250
  try {
33084
34251
  const { extractEntitiesFromSource: extractEntitiesFromSource2 } = await Promise.resolve().then(() => (init_auto_bootstrap(), auto_bootstrap_exports));
33085
- const allFiles = readdirSync12(process.cwd(), {
34252
+ const allFiles = readdirSync13(process.cwd(), {
33086
34253
  recursive: true,
33087
34254
  encoding: "utf-8"
33088
34255
  });
@@ -33096,7 +34263,7 @@ async function startProxy(opts = {}) {
33096
34263
  });
33097
34264
  for (const file of sourceFiles.slice(0, 500)) {
33098
34265
  try {
33099
- const content = readFileSync52(join61(process.cwd(), file), "utf-8");
34266
+ const content = readFileSync53(join62(process.cwd(), file), "utf-8");
33100
34267
  const entities = extractEntitiesFromSource2(file, content);
33101
34268
  parseIndex.addEntities(entities);
33102
34269
  } catch {
@@ -33158,7 +34325,7 @@ async function startProxy(opts = {}) {
33158
34325
  }
33159
34326
  const { writeFileSync: writeStatsFile } = await import("fs");
33160
34327
  const { computePercentiles: computePercentiles2 } = await Promise.resolve().then(() => (init_session_stats(), session_stats_exports));
33161
- const statsSnapshotPath = join61(stateDir, "session_stats.json");
34328
+ const statsSnapshotPath = join62(stateDir, "session_stats.json");
33162
34329
  const statsSnapshotInterval = setInterval(() => {
33163
34330
  try {
33164
34331
  const total = stats.toolCallsLocal;
@@ -33196,7 +34363,7 @@ async function startProxy(opts = {}) {
33196
34363
  const { startDashboardServer: startDashboardServer2 } = await Promise.resolve().then(() => (init_http(), http_exports));
33197
34364
  const { detectIde: detectIdeDashboard } = await Promise.resolve().then(() => (init_detect(), detect_exports));
33198
34365
  const ideType = await detectIdeDashboard(process.cwd());
33199
- const unerrDirForApi = join61(process.cwd(), ".unerr");
34366
+ const unerrDirForApi = join62(process.cwd(), ".unerr");
33200
34367
  dashboardHandle = await startDashboardServer2({
33201
34368
  system: {
33202
34369
  stats,
@@ -33241,12 +34408,24 @@ async function startProxy(opts = {}) {
33241
34408
  stateDir,
33242
34409
  tokenFlow: {
33243
34410
  unerrDir: unerrDirForApi,
33244
- getTokenFlowWriter: () => tokenFlowWriter2
34411
+ getTokenFlowWriter: () => tokenFlowWriter2,
34412
+ getAgentName: (_sessionId) => {
34413
+ const last = [...agentNameByClient.values()].pop();
34414
+ return last ?? server.getClientVersion?.()?.name ?? void 0;
34415
+ }
34416
+ },
34417
+ reasoningQuality: {
34418
+ unerrDir: unerrDirForApi,
34419
+ getTokenFlowWriter: () => tokenFlowWriter2,
34420
+ getAgentName: (_sessionId) => {
34421
+ const last = [...agentNameByClient.values()].pop();
34422
+ return last ?? server.getClientVersion?.()?.name ?? void 0;
34423
+ }
33245
34424
  },
33246
34425
  temporal: await (async () => {
33247
34426
  try {
33248
34427
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
33249
- const { readdirSync: readdirSync14, readFileSync: readFileSync56 } = await import("fs");
34428
+ const { readdirSync: readdirSync15, readFileSync: readFileSync57 } = await import("fs");
33250
34429
  const factStore2 = await TemporalFactStore2.create(
33251
34430
  process.cwd()
33252
34431
  );
@@ -33254,10 +34433,10 @@ async function startProxy(opts = {}) {
33254
34433
  factStore: factStore2,
33255
34434
  loadRecentSessions: (limit) => {
33256
34435
  try {
33257
- const sessDir = join61(unerrDirForApi, "sessions");
33258
- const files = readdirSync14(sessDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
34436
+ const sessDir = join62(unerrDirForApi, "sessions");
34437
+ const files = readdirSync15(sessDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
33259
34438
  return files.map((f) => {
33260
- const content = readFileSync56(join61(sessDir, f), "utf-8").trim().split("\n").pop();
34439
+ const content = readFileSync57(join62(sessDir, f), "utf-8").trim().split("\n").pop();
33261
34440
  return JSON.parse(content);
33262
34441
  });
33263
34442
  } catch {
@@ -33330,7 +34509,7 @@ async function startProxy(opts = {}) {
33330
34509
  try {
33331
34510
  const { appendSessionHistory: appendSessionHistory2 } = (init_session_history(), __toCommonJS(session_history_exports));
33332
34511
  const topMech = Object.entries(tokenFlowSummary.by_mechanism).sort(([, a], [, b]) => b.tokens_saved - a.tokens_saved)[0];
33333
- appendSessionHistory2(join61(process.cwd(), ".unerr"), {
34512
+ appendSessionHistory2(join62(process.cwd(), ".unerr"), {
33334
34513
  sessionId: shadowLedger2.getSessionId(),
33335
34514
  startedAt: new Date(stats.sessionStartedAt).toISOString(),
33336
34515
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -33342,6 +34521,7 @@ async function startProxy(opts = {}) {
33342
34521
  dollarsSaved: router2.getSessionDollarsSaved(),
33343
34522
  modelId: "unknown",
33344
34523
  entityCount: 0,
34524
+ agentName: agentNameByClient.values().next().value ?? server.getClientVersion?.()?.name ?? void 0,
33345
34525
  tokenFlowSummary: {
33346
34526
  by_mechanism: Object.fromEntries(
33347
34527
  Object.entries(tokenFlowSummary.by_mechanism).map(([k, v]) => [
@@ -33455,7 +34635,7 @@ async function startProxy(opts = {}) {
33455
34635
  try {
33456
34636
  const correctionModule = (init_correction_detector(), __toCommonJS(correction_detector_exports));
33457
34637
  const detectCorrections2 = correctionModule.detectCorrections;
33458
- const ledgerPath = join61(
34638
+ const ledgerPath = join62(
33459
34639
  process.cwd(),
33460
34640
  ".unerr",
33461
34641
  "ledger",
@@ -33473,10 +34653,49 @@ async function startProxy(opts = {}) {
33473
34653
  `[unerr] Learned ${patterns.length} correction pattern${patterns.length !== 1 ? "s" : ""} from this session
33474
34654
  `
33475
34655
  );
34656
+ try {
34657
+ const factStoreForShutdown = await getProxyFactStore(unerrDirForLedger);
34658
+ if (factStoreForShutdown) {
34659
+ const { generateFromNegativeKnowledge: generateFromNegativeKnowledge2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34660
+ const corrections = patterns.map((p, i) => ({
34661
+ id: `correction-${shadowLedger2.getSessionId()}-${i}`,
34662
+ entityKey: p.entity_key,
34663
+ pattern: p.error_type,
34664
+ reason: p.correction_summary,
34665
+ detectedAt: p.last_seen,
34666
+ rewindEntryId: shadowLedger2.getSessionId(),
34667
+ confidence: p.confidence
34668
+ }));
34669
+ const negResult = await generateFromNegativeKnowledge2(factStoreForShutdown, corrections);
34670
+ if (negResult.created > 0) {
34671
+ process.stderr.write(
34672
+ `[unerr] Fact generator: ${negResult.created} negative knowledge facts created
34673
+ `
34674
+ );
34675
+ }
34676
+ }
34677
+ } catch {
34678
+ }
33476
34679
  }
33477
34680
  } catch {
33478
34681
  }
33479
34682
  }
34683
+ try {
34684
+ const factStoreForSession = await getProxyFactStore(unerrDirForLedger);
34685
+ if (factStoreForSession) {
34686
+ const { runFactGenerationPipeline: runFactGenerationPipeline2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34687
+ const pipelineResults = await runFactGenerationPipeline2(factStoreForSession, unerrDirForLedger);
34688
+ for (const r of pipelineResults) {
34689
+ if (r.created > 0 || r.reinforced > 0) {
34690
+ process.stderr.write(
34691
+ `[unerr] Fact generator [${r.source}]: ${r.created} created, ${r.reinforced} reinforced
34692
+ `
34693
+ );
34694
+ }
34695
+ }
34696
+ }
34697
+ } catch {
34698
+ }
33480
34699
  if (workspaceManifest) {
33481
34700
  const orphans = intentCorrelator.getPending();
33482
34701
  if (orphans.length > 0) {
@@ -33639,13 +34858,13 @@ __export(session_logger_exports, {
33639
34858
  import { randomUUID as randomUUID2 } from "crypto";
33640
34859
  import {
33641
34860
  appendFileSync as appendFileSync9,
33642
- existsSync as existsSync57,
34861
+ existsSync as existsSync58,
33643
34862
  mkdirSync as mkdirSync35,
33644
- readdirSync as readdirSync13,
34863
+ readdirSync as readdirSync14,
33645
34864
  statSync as statSync11,
33646
34865
  unlinkSync as unlinkSync9
33647
34866
  } from "fs";
33648
- import { join as join62 } from "path";
34867
+ import { join as join63 } from "path";
33649
34868
  import { createConsola as createConsola2 } from "consola";
33650
34869
  function formatTimestamp() {
33651
34870
  const d = /* @__PURE__ */ new Date();
@@ -33653,10 +34872,10 @@ function formatTimestamp() {
33653
34872
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
33654
34873
  }
33655
34874
  function cleanupOldLogs(logsDir) {
33656
- if (!existsSync57(logsDir)) return;
34875
+ if (!existsSync58(logsDir)) return;
33657
34876
  try {
33658
- const files = readdirSync13(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => {
33659
- const fullPath = join62(logsDir, f);
34877
+ const files = readdirSync14(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => {
34878
+ const fullPath = join63(logsDir, f);
33660
34879
  const stat2 = statSync11(fullPath);
33661
34880
  return {
33662
34881
  name: f,
@@ -33673,7 +34892,7 @@ function cleanupOldLogs(logsDir) {
33673
34892
  }
33674
34893
  }
33675
34894
  }
33676
- const remaining = files.filter((f) => existsSync57(f.path));
34895
+ const remaining = files.filter((f) => existsSync58(f.path));
33677
34896
  if (remaining.length > MAX_FILES) {
33678
34897
  for (const file of remaining.slice(MAX_FILES)) {
33679
34898
  try {
@@ -33688,11 +34907,11 @@ function cleanupOldLogs(logsDir) {
33688
34907
  function initSessionLogger(opts = {}) {
33689
34908
  if (_logger) return _logger;
33690
34909
  const cwd = opts.cwd ?? process.cwd();
33691
- const logsDir = join62(cwd, ".unerr", "logs");
34910
+ const logsDir = join63(cwd, ".unerr", "logs");
33692
34911
  mkdirSync35(logsDir, { recursive: true });
33693
34912
  cleanupOldLogs(logsDir);
33694
34913
  const timestamp2 = formatTimestamp();
33695
- _logFilePath = join62(logsDir, `session-${timestamp2}.log`);
34914
+ _logFilePath = join63(logsDir, `session-${timestamp2}.log`);
33696
34915
  const levelNum = opts.level !== void 0 ? Number(opts.level) : void 0;
33697
34916
  const envLevel = process.env.UNERR_LOG_LEVEL;
33698
34917
  const resolvedLevel = levelNum ?? (envLevel ? Number(envLevel) : 3);
@@ -33766,22 +34985,22 @@ __export(setup_wizard_exports, {
33766
34985
  runSetup: () => runSetup
33767
34986
  });
33768
34987
  import { createHash as createHash4 } from "crypto";
33769
- import { existsSync as existsSync58, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "fs";
33770
- import { join as join63 } from "path";
34988
+ import { existsSync as existsSync59, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "fs";
34989
+ import { join as join64 } from "path";
33771
34990
  import * as clack from "@clack/prompts";
33772
34991
  async function runSetup(cwd) {
33773
34992
  const projectDir = cwd ?? process.cwd();
33774
34993
  clack.intro("unerr");
33775
34994
  clack.log.step("Project Setup");
33776
34995
  const repoId = await generateRepoId(projectDir);
33777
- const configDir = join63(projectDir, ".unerr");
34996
+ const configDir = join64(projectDir, ".unerr");
33778
34997
  mkdirSync36(configDir, { recursive: true });
33779
- const configPath = join63(configDir, "config.json");
33780
- const settingsPath = join63(configDir, "settings.json");
34998
+ const configPath = join64(configDir, "config.json");
34999
+ const settingsPath = join64(configDir, "settings.json");
33781
35000
  writeFileSync33(configPath, `${JSON.stringify({ repoId }, null, 2)}
33782
35001
  `);
33783
35002
  let existingSettings = {};
33784
- if (existsSync58(settingsPath)) {
35003
+ if (existsSync59(settingsPath)) {
33785
35004
  try {
33786
35005
  existingSettings = JSON.parse(
33787
35006
  __require("fs").readFileSync(settingsPath, "utf-8")
@@ -34390,13 +35609,13 @@ __export(session_summary_writer_exports, {
34390
35609
  readLastSession: () => readLastSession,
34391
35610
  writeSessionSummary: () => writeSessionSummary
34392
35611
  });
34393
- import { existsSync as existsSync59, mkdirSync as mkdirSync37, appendFileSync as appendFileSync10, writeFileSync as writeFileSync34, readFileSync as readFileSync53 } from "fs";
34394
- import { join as join64 } from "path";
35612
+ import { existsSync as existsSync60, mkdirSync as mkdirSync37, appendFileSync as appendFileSync10, writeFileSync as writeFileSync34, readFileSync as readFileSync54 } from "fs";
35613
+ import { join as join65 } from "path";
34395
35614
  function writeSessionSummary(unerrDir, ctx) {
34396
35615
  if (ctx.entries.length === 0) return null;
34397
35616
  try {
34398
- const sessionsDir = join64(unerrDir, "sessions");
34399
- if (!existsSync59(sessionsDir)) {
35617
+ const sessionsDir = join65(unerrDir, "sessions");
35618
+ if (!existsSync60(sessionsDir)) {
34400
35619
  mkdirSync37(sessionsDir, { recursive: true });
34401
35620
  }
34402
35621
  const sorted = [...ctx.entries].sort(
@@ -34440,7 +35659,7 @@ function writeSessionSummary(unerrDir, ctx) {
34440
35659
  token_estimate: ctx.tokenEstimate,
34441
35660
  branch: last.branch ?? "unknown"
34442
35661
  };
34443
- const filePath = join64(sessionsDir, `${ctx.sessionId}.jsonl`);
35662
+ const filePath = join65(sessionsDir, `${ctx.sessionId}.jsonl`);
34444
35663
  appendFileSync10(filePath, `${JSON.stringify(record)}
34445
35664
  `, "utf-8");
34446
35665
  writeLastSessionPointer(unerrDir, ctx.sessionId, record);
@@ -34455,9 +35674,9 @@ function writeSessionSummary(unerrDir, ctx) {
34455
35674
  }
34456
35675
  function readLastSession(unerrDir) {
34457
35676
  try {
34458
- const pointerPath = join64(unerrDir, "state", "last_session.json");
34459
- if (!existsSync59(pointerPath)) return null;
34460
- const content = readFileSync53(pointerPath, "utf-8");
35677
+ const pointerPath = join65(unerrDir, "state", "last_session.json");
35678
+ if (!existsSync60(pointerPath)) return null;
35679
+ const content = readFileSync54(pointerPath, "utf-8");
34461
35680
  return JSON.parse(content);
34462
35681
  } catch {
34463
35682
  return null;
@@ -34465,11 +35684,11 @@ function readLastSession(unerrDir) {
34465
35684
  }
34466
35685
  function writeLastSessionPointer(unerrDir, sessionId, record) {
34467
35686
  try {
34468
- const stateDir = join64(unerrDir, "state");
34469
- if (!existsSync59(stateDir)) {
35687
+ const stateDir = join65(unerrDir, "state");
35688
+ if (!existsSync60(stateDir)) {
34470
35689
  mkdirSync37(stateDir, { recursive: true });
34471
35690
  }
34472
- const pointerPath = join64(stateDir, "last_session.json");
35691
+ const pointerPath = join65(stateDir, "last_session.json");
34473
35692
  writeFileSync34(pointerPath, JSON.stringify(record, null, 2), "utf-8");
34474
35693
  } catch {
34475
35694
  }
@@ -34667,16 +35886,33 @@ var mcp_server_exports = {};
34667
35886
  __export(mcp_server_exports, {
34668
35887
  startMcpServer: () => startMcpServer
34669
35888
  });
34670
- import { existsSync as existsSync60, mkdirSync as mkdirSync38, readFileSync as readFileSync54, writeFileSync as writeFileSync35 } from "fs";
34671
- import { join as join65 } from "path";
35889
+ import { existsSync as existsSync61, mkdirSync as mkdirSync38, readFileSync as readFileSync55, writeFileSync as writeFileSync35 } from "fs";
35890
+ import { join as join66 } from "path";
35891
+ function migrateAgentPermissions2(cwd) {
35892
+ try {
35893
+ const settingsPath = join66(cwd, ".claude", "settings.json");
35894
+ if (!existsSync61(settingsPath)) return;
35895
+ const raw = readFileSync55(settingsPath, "utf-8");
35896
+ const settings = JSON.parse(raw);
35897
+ const deny = settings?.permissions?.deny;
35898
+ if (!Array.isArray(deny)) return;
35899
+ const readIdx = deny.indexOf("Read");
35900
+ if (readIdx < 0) return;
35901
+ deny.splice(readIdx, 1);
35902
+ writeFileSync35(settingsPath, JSON.stringify(settings, null, 2) + "\n");
35903
+ process.stderr.write("[unerr] Migrated permissions: removed Read from deny list (required for Edit workflow)\n");
35904
+ } catch {
35905
+ }
35906
+ }
34672
35907
  async function startMcpServer(cwd) {
34673
35908
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
34674
35909
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
34675
35910
  const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
34676
- const unerrDir = join65(cwd, ".unerr");
34677
- if (!existsSync60(unerrDir)) {
35911
+ const unerrDir = join66(cwd, ".unerr");
35912
+ if (!existsSync61(unerrDir)) {
34678
35913
  mkdirSync38(unerrDir, { recursive: true });
34679
35914
  }
35915
+ migrateAgentPermissions2(cwd);
34680
35916
  await initTier2Modules(unerrDir);
34681
35917
  const server = new Server(
34682
35918
  { name: "unerr-local", version: "0.1.0" },
@@ -34694,6 +35930,25 @@ async function startMcpServer(cwd) {
34694
35930
  if (name === "recall_facts") {
34695
35931
  return handleRecallFacts(args);
34696
35932
  }
35933
+ const PROXY_ONLY_TOOLS = [
35934
+ "unerr_get_timeline",
35935
+ "unerr_revert_to_working_state",
35936
+ "unerr_mark_working"
35937
+ ];
35938
+ if (PROXY_ONLY_TOOLS.includes(name)) {
35939
+ return {
35940
+ content: [
35941
+ {
35942
+ type: "text",
35943
+ text: JSON.stringify({
35944
+ tool: name,
35945
+ available: false,
35946
+ message: `${name} requires the full unerr proxy (run 'unerr' without --mcp). The timeline/revert subsystem is not available in lightweight MCP mode.`
35947
+ })
35948
+ }
35949
+ ]
35950
+ };
35951
+ }
34697
35952
  if (!graphReady || !router) {
34698
35953
  if (name === "file_read" || name === "file_outline") {
34699
35954
  try {
@@ -34773,25 +36028,6 @@ async function startMcpServer(cwd) {
34773
36028
  ]
34774
36029
  };
34775
36030
  }
34776
- const PROXY_ONLY_TOOLS = [
34777
- "unerr_get_timeline",
34778
- "unerr_revert_to_working_state",
34779
- "unerr_mark_working"
34780
- ];
34781
- if (PROXY_ONLY_TOOLS.includes(name)) {
34782
- return {
34783
- content: [
34784
- {
34785
- type: "text",
34786
- text: JSON.stringify({
34787
- tool: name,
34788
- available: false,
34789
- message: `${name} requires the full unerr proxy (run 'unerr' without --mcp). The timeline/revert subsystem is not available in lightweight MCP mode.`
34790
- })
34791
- }
34792
- ]
34793
- };
34794
- }
34795
36031
  try {
34796
36032
  const result = await router.execute(name, args);
34797
36033
  let text2 = stringifyMcpToolJson(result.content);
@@ -35079,9 +36315,9 @@ async function initTier2Modules(unerrDir) {
35079
36315
  tokenFlowWriter = new TokenFlowWriter2(unerrDir, sessionId);
35080
36316
  process.env.UNERR_SESSION_ID = sessionId;
35081
36317
  try {
35082
- const stateDir = join65(unerrDir, "state");
36318
+ const stateDir = join66(unerrDir, "state");
35083
36319
  mkdirSync38(stateDir, { recursive: true });
35084
- writeFileSync35(join65(stateDir, "session.id"), sessionId, "utf-8");
36320
+ writeFileSync35(join66(stateDir, "session.id"), sessionId, "utf-8");
35085
36321
  } catch {
35086
36322
  }
35087
36323
  log24.info(`Token flow active (session ${sessionId.slice(0, 8)})`);
@@ -35112,7 +36348,7 @@ async function initTier2Modules(unerrDir) {
35112
36348
  }
35113
36349
  try {
35114
36350
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
35115
- const projectRoot = join65(unerrDir, "..");
36351
+ const projectRoot = join66(unerrDir, "..");
35116
36352
  factStore = await TemporalFactStore2.create(projectRoot);
35117
36353
  log24.info("Temporal fact store active (facts.db)");
35118
36354
  } catch (err) {
@@ -35222,8 +36458,8 @@ function wireTier2IntoRouter(r) {
35222
36458
  async function loadIntelligence(cwd) {
35223
36459
  const projectRoot = cwd;
35224
36460
  try {
35225
- const unerrDir = join65(projectRoot, ".unerr");
35226
- if (!existsSync60(unerrDir)) {
36461
+ const unerrDir = join66(projectRoot, ".unerr");
36462
+ if (!existsSync61(unerrDir)) {
35227
36463
  mkdirSync38(unerrDir, { recursive: true });
35228
36464
  }
35229
36465
  const { openPersistentDb: openPersistentDb2 } = await Promise.resolve().then(() => (init_persistent_db(), persistent_db_exports));
@@ -35232,11 +36468,8 @@ async function loadIntelligence(cwd) {
35232
36468
  const localGraph = await CozoGraphStore2.create(db, projectRoot);
35233
36469
  let needsIndex = false;
35234
36470
  if (!isNew && await localGraph.isPopulated()) {
35235
- log24.info("Persistent graph loaded");
35236
- const { shouldReindex: shouldReindex2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
35237
- if (shouldReindex2(projectRoot)) {
35238
- needsIndex = true;
35239
- }
36471
+ log24.info("Persistent graph loaded \u2014 background reindex will refresh entity data");
36472
+ needsIndex = true;
35240
36473
  } else {
35241
36474
  const { loadLocalSnapshot: loadLocalSnapshot2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
35242
36475
  const migrated = await loadLocalSnapshot2(projectRoot, localGraph);
@@ -35245,11 +36478,11 @@ async function loadIntelligence(cwd) {
35245
36478
  await buildSearchIndex2(localGraph.db);
35246
36479
  const { runCommunityDetection: runCommunityDetection2, runConventionDetection: runConventionDetection2 } = await Promise.resolve().then(() => (init_local_indexer(), local_indexer_exports));
35247
36480
  await runCommunityDetection2(localGraph);
35248
- const configPath = join65(projectRoot, ".unerr", "config.json");
36481
+ const configPath = join66(projectRoot, ".unerr", "config.json");
35249
36482
  let repoId = "unknown";
35250
- if (existsSync60(configPath)) {
36483
+ if (existsSync61(configPath)) {
35251
36484
  try {
35252
- const config = JSON.parse(readFileSync54(configPath, "utf-8"));
36485
+ const config = JSON.parse(readFileSync55(configPath, "utf-8"));
35253
36486
  repoId = config.repoId ?? repoId;
35254
36487
  } catch {
35255
36488
  }
@@ -35276,11 +36509,11 @@ async function loadIntelligence(cwd) {
35276
36509
  log24.info("Intelligence graph ready \u2014 tools fully operational");
35277
36510
  if (needsIndex) {
35278
36511
  try {
35279
- const configPath = join65(projectRoot, ".unerr", "config.json");
36512
+ const configPath = join66(projectRoot, ".unerr", "config.json");
35280
36513
  let indexRepoId = "unknown";
35281
- if (existsSync60(configPath)) {
36514
+ if (existsSync61(configPath)) {
35282
36515
  try {
35283
- const cfg = JSON.parse(readFileSync54(configPath, "utf-8"));
36516
+ const cfg = JSON.parse(readFileSync55(configPath, "utf-8"));
35284
36517
  indexRepoId = cfg.repoId ?? indexRepoId;
35285
36518
  } catch {
35286
36519
  }
@@ -35390,8 +36623,8 @@ var init_mcp_server = __esm({
35390
36623
 
35391
36624
  // src/entrypoints/cli.ts
35392
36625
  init_startup_log();
35393
- import { existsSync as existsSync61, readFileSync as readFileSync55 } from "fs";
35394
- import { join as join66 } from "path";
36626
+ import { existsSync as existsSync62, readFileSync as readFileSync56 } from "fs";
36627
+ import { join as join67 } from "path";
35395
36628
  import { Command } from "commander";
35396
36629
 
35397
36630
  // src/commands/branches.ts
@@ -35832,9 +37065,9 @@ function registerDashboardCommand(program2) {
35832
37065
  // src/commands/debug.ts
35833
37066
  init_git();
35834
37067
  import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
35835
- import { arch, homedir as homedir2, platform, release } from "os";
37068
+ import { arch, homedir as homedir3, platform, release } from "os";
35836
37069
  import { join as join11 } from "path";
35837
- var UNERR_DIR = join11(homedir2(), ".unerr");
37070
+ var UNERR_DIR = join11(homedir3(), ".unerr");
35838
37071
  function registerDebugCommand(program2) {
35839
37072
  program2.command("debug").description("Diagnostics dump for support").action(async () => {
35840
37073
  const sections = [];
@@ -35939,9 +37172,9 @@ function registerDebugCommand(program2) {
35939
37172
  const mcpLocations = [
35940
37173
  join11(process.cwd(), ".cursor", "mcp.json"),
35941
37174
  join11(process.cwd(), ".vscode", "mcp.json"),
35942
- join11(homedir2(), ".claude", "claude_desktop_config.json"),
37175
+ join11(homedir3(), ".claude", "claude_desktop_config.json"),
35943
37176
  join11(
35944
- homedir2(),
37177
+ homedir3(),
35945
37178
  "Library",
35946
37179
  "Application Support",
35947
37180
  "Cursor",
@@ -35954,7 +37187,7 @@ function registerDebugCommand(program2) {
35954
37187
  for (const loc of mcpLocations) {
35955
37188
  const exists = existsSync9(loc);
35956
37189
  sections.push(
35957
- ` ${exists ? "[x]" : "[ ]"} ${loc.replace(homedir2(), "~")}`
37190
+ ` ${exists ? "[x]" : "[ ]"} ${loc.replace(homedir3(), "~")}`
35958
37191
  );
35959
37192
  }
35960
37193
  sections.push("");
@@ -37395,19 +38628,19 @@ async function buildShellDiffRiskMap(graph, stdout) {
37395
38628
  }
37396
38629
  async function tryLoadGraphForShellBoost(cwd) {
37397
38630
  try {
37398
- const { existsSync: existsSync62, readFileSync: readFileSync56 } = await import("fs");
37399
- const { join: join67 } = await import("path");
38631
+ const { existsSync: existsSync63, readFileSync: readFileSync57 } = await import("fs");
38632
+ const { join: join68 } = await import("path");
37400
38633
  const { gunzipSync: gunzipSync3 } = await import("zlib");
37401
38634
  const { unpack } = await import("msgpackr");
37402
- const snapshotsDir = join67(cwd, ".unerr", "snapshots");
37403
- let snapshotPath2 = join67(snapshotsDir, "graph.msgpack.gz");
37404
- if (!existsSync62(snapshotPath2)) {
37405
- snapshotPath2 = join67(snapshotsDir, "graph.msgpack");
38635
+ const snapshotsDir = join68(cwd, ".unerr", "snapshots");
38636
+ let snapshotPath2 = join68(snapshotsDir, "graph.msgpack.gz");
38637
+ if (!existsSync63(snapshotPath2)) {
38638
+ snapshotPath2 = join68(snapshotsDir, "graph.msgpack");
37406
38639
  }
37407
- if (!existsSync62(snapshotPath2)) return null;
38640
+ if (!existsSync63(snapshotPath2)) return null;
37408
38641
  const { default: CozoDbConstructor } = await import("cozo-node");
37409
38642
  const { CozoGraphStore: CozoGraphStore2 } = await Promise.resolve().then(() => (init_local_graph(), local_graph_exports));
37410
- const raw = readFileSync56(snapshotPath2);
38643
+ const raw = readFileSync57(snapshotPath2);
37411
38644
  let buffer;
37412
38645
  try {
37413
38646
  buffer = gunzipSync3(raw);
@@ -40643,12 +41876,17 @@ var preReadHandler = (normalized) => {
40643
41876
  const input = normalized.toolInput;
40644
41877
  const filePath = extractFilePath2(input);
40645
41878
  if (!filePath) return passthrough();
41879
+ const hasOffset = input.offset !== void 0;
41880
+ const hasLimit = input.limit !== void 0;
41881
+ const isTargeted = hasOffset || hasLimit;
41882
+ const targetedReadGuidance = isTargeted ? "" : `
41883
+ IMPORTANT: Use offset/limit to read only the lines you plan to edit \u2014 do NOT read the entire file. You already know the target lines from file_read. Example: Read with offset=50, limit=30 to read lines 50-79.`;
40646
41884
  return nudge(
40647
41885
  `READ ROUTING: Built-in Read is ONLY for the Edit workflow (Read \u2192 Edit). For all other reading, use unerr tools instead:
40648
41886
  - Reading to understand: \`file_read({ file_path: "${filePath}", purpose: "explore" })\`
40649
41887
  - Reading before Edit: built-in Read is correct (proceed)
40650
41888
  - File structure: \`file_outline("${filePath}")\`
40651
- - Specific function: \`get_entity\` or \`file_read\` with \`entity\` param`
41889
+ - Specific function: \`get_entity\` or \`file_read\` with \`entity\` param` + targetedReadGuidance
40652
41890
  );
40653
41891
  };
40654
41892
  var preGrepHandler = (normalized) => {
@@ -40700,17 +41938,20 @@ var preEditHandler = (normalized) => {
40700
41938
  const input = normalized.toolInput;
40701
41939
  const filePath = extractFilePath2(input);
40702
41940
  if (!filePath || !isCodeFile(filePath)) return passthrough();
41941
+ const readPrereq = `CRITICAL: Edit REQUIRES built-in Read to have been called on "${filePath}" first. file_read (MCP) does NOT satisfy this \u2014 the Edit tool will fail with "File has not been read yet". If you haven't called built-in Read on this file, do so now before attempting Edit. Use offset/limit to read only the lines you plan to edit \u2014 do NOT read the entire file.
41942
+
41943
+ `;
40703
41944
  const oldStr = input.old_string;
40704
41945
  const hasSignatureChange = oldStr && /^(export\s+)?(async\s+)?function\s+\w+|^(export\s+)?class\s+\w+/.test(oldStr.trim());
40705
41946
  if (hasSignatureChange) {
40706
41947
  return nudge(
40707
- `You're editing a function/class signature in "${filePath}". This may break callers.
41948
+ readPrereq + `You're editing a function/class signature in "${filePath}". This may break callers.
40708
41949
  - \`get_references\` on the entity you're modifying \u2014 all callers must be updated to match
40709
41950
  - \`get_rules "${filePath}"\` with content after editing \u2014 validate the change follows project conventions`
40710
41951
  );
40711
41952
  }
40712
41953
  return nudge(
40713
- `Before editing "${filePath}":
41954
+ readPrereq + `Before editing "${filePath}":
40714
41955
  - \`get_references\` on any entity you're changing \u2014 ensure callers won't break
40715
41956
  - \`get_rules "${filePath}"\` with content after editing \u2014 validate against project conventions`
40716
41957
  );
@@ -41168,7 +42409,7 @@ function mergePreToolUseBashHook(cwd) {
41168
42409
  return { ok: false, path: settingsPath, action: "failed" };
41169
42410
  }
41170
42411
  }
41171
- var DISALLOWED_TOOLS = ["Read", "Grep", "Glob"];
42412
+ var DISALLOWED_TOOLS = ["Grep", "Glob"];
41172
42413
  function addDisallowedTools(cwd) {
41173
42414
  const dir = join27(cwd, ".claude");
41174
42415
  const settingsPath = join27(dir, "settings.json");
@@ -41186,6 +42427,10 @@ function addDisallowedTools(cwd) {
41186
42427
  }
41187
42428
  const permissions = settings.permissions ?? {};
41188
42429
  const deny = Array.isArray(permissions.deny) ? [...permissions.deny] : [];
42430
+ const readIdx = deny.indexOf("Read");
42431
+ if (readIdx >= 0) {
42432
+ deny.splice(readIdx, 1);
42433
+ }
41189
42434
  let added = 0;
41190
42435
  for (const tool of DISALLOWED_TOOLS) {
41191
42436
  if (!deny.includes(tool)) {
@@ -41306,10 +42551,12 @@ This project has unerr MCP tools installed. You MUST use these instead of built-
41306
42551
  | Intent | Tool | Why |
41307
42552
  |--------|------|-----|
41308
42553
  | Reading to understand code | \`file_read\` (unerr MCP) | Auto-injects conventions, facts, drift status |
41309
- | Reading immediately before Edit | Built-in \`Read\` | Required by Edit tool \u2014 \`file_read\` does NOT satisfy this |
42554
+ | Reading immediately before Edit | Built-in \`Read\` with offset/limit | Required by Edit tool \u2014 \`file_read\` does NOT satisfy this. Use targeted reads (offset/limit) for only the lines you plan to edit. |
41310
42555
 
41311
42556
  When your next action is Edit, use built-in Read. For everything else, use \`file_read\`.
41312
42557
 
42558
+ **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" \u2014 \`file_read\` (MCP) cannot satisfy this requirement. Always call built-in Read immediately before Edit.
42559
+
41313
42560
  ### Tool Reference
41314
42561
 
41315
42562
  #### Graph Navigation (8 tools)
@@ -41376,7 +42623,7 @@ ONLY use built-in Read/Grep/Glob when:
41376
42623
  ### Summary (CRITICAL \u2014 read this even if you skimmed above)
41377
42624
 
41378
42625
  ALWAYS use unerr MCP tools: \`search_code\`, \`get_references\`, \`file_read\`, \`file_outline\`, \`get_entity\`.
41379
- NEVER use built-in Read/Grep/Glob for code navigation. EXCEPTION: built-in Read is REQUIRED before Edit (file_read cannot substitute).
42626
+ NEVER use built-in Read/Grep/Glob for code navigation. EXCEPTION: built-in Read is REQUIRED before Edit (file_read cannot substitute \u2014 Edit will fail without it).
41380
42627
  unerr skills available: /audit, /blame, /test, /lint, /commit, /review-pr.
41381
42628
  Before writing code: \`get_conventions\`, \`get_rules\`. After writing: \`get_rules\` with content to validate.
41382
42629
  Before risky changes: \`unerr_mark_working\`. If things break: \`unerr_revert_to_working_state\`.`;
@@ -41399,6 +42646,29 @@ function writeInstructionFile(cwd, ide) {
41399
42646
  if (agentDef.instructionFormat === "mdc") {
41400
42647
  return writeMdcInstructionFile(filePath);
41401
42648
  }
42649
+ if (agentDef.instructionFormat === "windsurf-rule") {
42650
+ mkdirSync15(dirname5(filePath), { recursive: true });
42651
+ const content = getInstructionContent();
42652
+ const truncatedContent = content.length > 5800 ? `${content.slice(0, 5800)}
42653
+
42654
+ [Truncated \u2014 full content exceeds Windsurf 6K limit]` : content;
42655
+ const windsurfContent = `---
42656
+ trigger: always_on
42657
+ description: "Tool routing instructions for unerr MCP integration"
42658
+ ---
42659
+
42660
+ ${truncatedContent}
42661
+ `;
42662
+ const existed = existsSync21(filePath);
42663
+ if (existed) {
42664
+ const existing = readFileSync20(filePath, "utf-8");
42665
+ if (existing === windsurfContent) {
42666
+ return { path: filePath, action: "skipped" };
42667
+ }
42668
+ }
42669
+ writeFileSync11(filePath, windsurfContent, "utf-8");
42670
+ return { path: filePath, action: existed ? "updated" : "created" };
42671
+ }
41402
42672
  if (agentDef.instructionFormat === "antigravity-rule") {
41403
42673
  mkdirSync15(dirname5(filePath), { recursive: true });
41404
42674
  const content = getInstructionContent();
@@ -41477,6 +42747,13 @@ function removeInstructionSection(cwd, ide) {
41477
42747
  unlinkSync3(filePath);
41478
42748
  return true;
41479
42749
  }
42750
+ if (agentDef.instructionFormat === "windsurf-rule") {
42751
+ if (existsSync21(filePath)) {
42752
+ unlinkSync3(filePath);
42753
+ return true;
42754
+ }
42755
+ return false;
42756
+ }
41480
42757
  if (agentDef.instructionFormat === "antigravity-rule") {
41481
42758
  if (existsSync21(filePath)) {
41482
42759
  unlinkSync3(filePath);
@@ -42041,8 +43318,8 @@ async function persistPatterns(patterns, _unerrDir) {
42041
43318
  );
42042
43319
  return;
42043
43320
  }
42044
- const { readFileSync: readFileSync56 } = await import("fs");
42045
- const config = JSON.parse(readFileSync56(configPath, "utf-8"));
43321
+ const { readFileSync: readFileSync57 } = await import("fs");
43322
+ const config = JSON.parse(readFileSync57(configPath, "utf-8"));
42046
43323
  if (!config.repoId || !config.orgId) {
42047
43324
  warn("Repo not configured \u2014 cannot persist to CozoDB.");
42048
43325
  return;
@@ -42060,7 +43337,7 @@ async function persistPatterns(patterns, _unerrDir) {
42060
43337
  const { CozoGraphStore: CozoGraphStore2 } = await Promise.resolve().then(() => (init_local_graph(), local_graph_exports));
42061
43338
  const store = await CozoGraphStore2.create(db);
42062
43339
  const { unpack } = await import("msgpackr");
42063
- const raw = readFileSync56(snapshotPath2);
43340
+ const raw = readFileSync57(snapshotPath2);
42064
43341
  const buffer = gunzipSync2(raw);
42065
43342
  const envelope = unpack(buffer);
42066
43343
  await store.loadSnapshot(envelope);
@@ -42727,8 +44004,8 @@ function registerStatusCommand(program2) {
42727
44004
  try {
42728
44005
  const logsDir = join38(unerrDir, "logs");
42729
44006
  if (existsSync31(logsDir)) {
42730
- const { readdirSync: readdirSync14, statSync: statSync12 } = await import("fs");
42731
- const logs = readdirSync14(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
44007
+ const { readdirSync: readdirSync15, statSync: statSync12 } = await import("fs");
44008
+ const logs = readdirSync15(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
42732
44009
  name: f,
42733
44010
  mtime: statSync12(join38(logsDir, f)).mtimeMs
42734
44011
  })).sort((a, b) => b.mtime - a.mtime);
@@ -43293,17 +44570,538 @@ async function isInsideGitRepo2() {
43293
44570
  const { isGitRepo: isGitRepo3 } = await Promise.resolve().then(() => (init_git(), git_exports));
43294
44571
  return isGitRepo3(process.cwd());
43295
44572
  }
44573
+ var DEFINITIVE_MARKERS = [
44574
+ // JavaScript / TypeScript
44575
+ "package.json",
44576
+ "tsconfig.json",
44577
+ "jsconfig.json",
44578
+ "deno.json",
44579
+ "deno.jsonc",
44580
+ "bun.lockb",
44581
+ "bunfig.toml",
44582
+ // Python
44583
+ "pyproject.toml",
44584
+ "setup.py",
44585
+ "setup.cfg",
44586
+ "Pipfile",
44587
+ "hatch.toml",
44588
+ // Rust
44589
+ "Cargo.toml",
44590
+ // Go
44591
+ "go.mod",
44592
+ // Java / Kotlin / Gradle
44593
+ "pom.xml",
44594
+ "build.gradle",
44595
+ "build.gradle.kts",
44596
+ "settings.gradle",
44597
+ "settings.gradle.kts",
44598
+ // C# / .NET
44599
+ "Directory.Build.props",
44600
+ "global.json",
44601
+ "nuget.config",
44602
+ // Ruby
44603
+ "Gemfile",
44604
+ "Rakefile",
44605
+ // PHP
44606
+ "composer.json",
44607
+ // Swift / Obj-C / Apple
44608
+ "Package.swift",
44609
+ "Podfile",
44610
+ // C / C++
44611
+ "CMakeLists.txt",
44612
+ "Makefile",
44613
+ "configure.ac",
44614
+ "meson.build",
44615
+ "conanfile.txt",
44616
+ "conanfile.py",
44617
+ "vcpkg.json",
44618
+ // Dart / Flutter
44619
+ "pubspec.yaml",
44620
+ // Elixir
44621
+ "mix.exs",
44622
+ // Scala
44623
+ "build.sbt",
44624
+ "build.sc",
44625
+ // Haskell
44626
+ "stack.yaml",
44627
+ "cabal.project",
44628
+ // Clojure
44629
+ "project.clj",
44630
+ "deps.edn",
44631
+ "shadow-cljs.edn",
44632
+ // Zig
44633
+ "build.zig",
44634
+ "build.zig.zon",
44635
+ // Julia
44636
+ "Project.toml",
44637
+ // Terraform / IaC
44638
+ "main.tf",
44639
+ "terraform.tf",
44640
+ "pulumi.yaml",
44641
+ "serverless.yml",
44642
+ "cdk.json",
44643
+ "sam.yaml",
44644
+ // Containers
44645
+ "Dockerfile",
44646
+ "docker-compose.yml",
44647
+ "docker-compose.yaml",
44648
+ // Monorepo
44649
+ "lerna.json",
44650
+ "nx.json",
44651
+ "turbo.json",
44652
+ "pnpm-workspace.yaml",
44653
+ "rush.json",
44654
+ "pants.toml",
44655
+ // Bazel
44656
+ "BUILD.bazel",
44657
+ "WORKSPACE",
44658
+ "WORKSPACE.bazel",
44659
+ // General build / task
44660
+ "Justfile",
44661
+ "Taskfile.yml",
44662
+ // Nix
44663
+ "flake.nix",
44664
+ "shell.nix",
44665
+ "default.nix",
44666
+ // Android
44667
+ "AndroidManifest.xml"
44668
+ ];
44669
+ var STRONG_SIGNAL_FILES = [
44670
+ // Lock files (imply a package manager was run here)
44671
+ "package-lock.json",
44672
+ "yarn.lock",
44673
+ "pnpm-lock.yaml",
44674
+ "Pipfile.lock",
44675
+ "poetry.lock",
44676
+ "Gemfile.lock",
44677
+ "composer.lock",
44678
+ "Cargo.lock",
44679
+ "flake.lock",
44680
+ "pubspec.lock",
44681
+ "mix.lock",
44682
+ "go.sum",
44683
+ "uv.lock",
44684
+ "pdm.lock",
44685
+ // CI/CD (a CI config strongly implies project root)
44686
+ ".gitlab-ci.yml",
44687
+ "Jenkinsfile",
44688
+ ".travis.yml",
44689
+ "azure-pipelines.yml",
44690
+ "bitbucket-pipelines.yml",
44691
+ // Editor / LSP configs (placed at project root)
44692
+ ".editorconfig",
44693
+ ".prettierrc",
44694
+ ".prettierrc.json",
44695
+ ".eslintrc",
44696
+ ".eslintrc.json",
44697
+ ".eslintrc.js",
44698
+ "biome.json",
44699
+ "biome.jsonc",
44700
+ "pyrightconfig.json",
44701
+ "rust-toolchain.toml",
44702
+ ".clang-format",
44703
+ ".clangd",
44704
+ "compile_commands.json",
44705
+ ".luarc.json",
44706
+ "stylua.toml",
44707
+ ".rubocop.yml",
44708
+ ".php-cs-fixer.php",
44709
+ // Environment
44710
+ ".env",
44711
+ ".envrc",
44712
+ // Git-specific (at root level these are strong signals)
44713
+ ".gitignore",
44714
+ ".gitattributes"
44715
+ ];
44716
+ var STRONG_SIGNAL_DIRS = [
44717
+ ".git",
44718
+ ".svn",
44719
+ ".hg",
44720
+ ".fossil",
44721
+ ".bzr",
44722
+ "_darcs",
44723
+ // VCS
44724
+ ".github",
44725
+ ".circleci",
44726
+ ".buildkite",
44727
+ // CI/CD
44728
+ ".idea",
44729
+ ".vscode",
44730
+ ".vs",
44731
+ ".fleet",
44732
+ ".zed"
44733
+ // IDE
44734
+ ];
44735
+ var EXTENSION_MARKERS = [
44736
+ ".sln",
44737
+ ".csproj",
44738
+ ".fsproj",
44739
+ ".vbproj",
44740
+ // .NET
44741
+ ".xcodeproj",
44742
+ ".xcworkspace",
44743
+ // Xcode (dirs)
44744
+ ".cabal",
44745
+ // Haskell
44746
+ ".nimble",
44747
+ // Nim
44748
+ ".gemspec",
44749
+ // Ruby
44750
+ ".rockspec"
44751
+ // Lua
44752
+ ];
44753
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
44754
+ // Web / JS ecosystem
44755
+ ".ts",
44756
+ ".tsx",
44757
+ ".js",
44758
+ ".jsx",
44759
+ ".mjs",
44760
+ ".cjs",
44761
+ ".vue",
44762
+ ".svelte",
44763
+ ".astro",
44764
+ // Systems
44765
+ ".rs",
44766
+ ".go",
44767
+ ".c",
44768
+ ".h",
44769
+ ".cpp",
44770
+ ".cc",
44771
+ ".cxx",
44772
+ ".hpp",
44773
+ ".hxx",
44774
+ ".zig",
44775
+ // JVM
44776
+ ".java",
44777
+ ".kt",
44778
+ ".kts",
44779
+ ".scala",
44780
+ ".sc",
44781
+ ".clj",
44782
+ ".cljs",
44783
+ ".cljc",
44784
+ // .NET
44785
+ ".cs",
44786
+ ".fs",
44787
+ ".fsx",
44788
+ ".vb",
44789
+ // Scripting
44790
+ ".py",
44791
+ ".pyi",
44792
+ ".rb",
44793
+ ".php",
44794
+ ".lua",
44795
+ ".pl",
44796
+ ".pm",
44797
+ ".raku",
44798
+ // Apple / Mobile
44799
+ ".swift",
44800
+ ".m",
44801
+ ".mm",
44802
+ ".dart",
44803
+ // Functional
44804
+ ".hs",
44805
+ ".lhs",
44806
+ ".ml",
44807
+ ".mli",
44808
+ ".re",
44809
+ ".rei",
44810
+ ".elm",
44811
+ ".ex",
44812
+ ".exs",
44813
+ ".purs",
44814
+ ".idr",
44815
+ ".agda",
44816
+ ".lean",
44817
+ // Data / Science
44818
+ ".r",
44819
+ ".R",
44820
+ ".jl",
44821
+ // Infrastructure
44822
+ ".tf",
44823
+ ".hcl",
44824
+ // Emerging / Niche
44825
+ ".nim",
44826
+ ".cr",
44827
+ ".d",
44828
+ ".v",
44829
+ ".sol",
44830
+ ".move",
44831
+ ".cairo",
44832
+ ".nr",
44833
+ // Shell / Config as code
44834
+ ".sh",
44835
+ ".bash",
44836
+ ".zsh",
44837
+ ".fish",
44838
+ ".ps1"
44839
+ ]);
44840
+ var ANTI_SIGNAL_FILES = [
44841
+ ".bashrc",
44842
+ ".zshrc",
44843
+ ".bash_profile",
44844
+ ".profile",
44845
+ ".zprofile",
44846
+ ".zshenv",
44847
+ ".bash_history",
44848
+ ".zsh_history"
44849
+ ];
44850
+ var ANTI_SIGNAL_DIRS = [
44851
+ ".config",
44852
+ ".local",
44853
+ ".cache",
44854
+ ".ssh",
44855
+ ".gnupg",
44856
+ ".npm",
44857
+ ".cargo",
44858
+ "Desktop",
44859
+ "Downloads",
44860
+ "Documents",
44861
+ "Pictures",
44862
+ "Music",
44863
+ "Movies",
44864
+ "Library",
44865
+ "Applications"
44866
+ ];
44867
+ var ANTI_SIGNAL_PATHS = [
44868
+ "/",
44869
+ "/usr",
44870
+ "/tmp",
44871
+ "/var",
44872
+ "/etc",
44873
+ "/opt",
44874
+ "/home",
44875
+ "/Users",
44876
+ "/root"
44877
+ ];
44878
+ var SOURCE_DIRS = /* @__PURE__ */ new Set([
44879
+ "src",
44880
+ "lib",
44881
+ "app",
44882
+ "apps",
44883
+ "cmd",
44884
+ "pkg",
44885
+ "packages",
44886
+ "internal",
44887
+ "modules",
44888
+ "components",
44889
+ "pages",
44890
+ "routes",
44891
+ "api",
44892
+ "server",
44893
+ "client",
44894
+ "web",
44895
+ "core",
44896
+ "common",
44897
+ "shared",
44898
+ "utils",
44899
+ "helpers",
44900
+ "services",
44901
+ "models",
44902
+ "views",
44903
+ "controllers",
44904
+ "handlers",
44905
+ "middleware",
44906
+ "plugins",
44907
+ "test",
44908
+ "tests",
44909
+ "spec",
44910
+ "__tests__",
44911
+ "e2e",
44912
+ "integration",
44913
+ "scripts",
44914
+ "tools",
44915
+ "bin",
44916
+ "examples",
44917
+ "samples",
44918
+ "proto",
44919
+ "schemas",
44920
+ "migrations",
44921
+ "seeds"
44922
+ ]);
44923
+ var DETECTION_THRESHOLD = 5;
44924
+ async function detectProjectRoot(cwd) {
44925
+ const { readdirSync: readdirSync15 } = await import("fs");
44926
+ let score = 0;
44927
+ const signals = [];
44928
+ let hasGit = false;
44929
+ const normalizedCwd = cwd.replace(/\\/g, "/");
44930
+ for (const ap of ANTI_SIGNAL_PATHS) {
44931
+ if (normalizedCwd === ap || normalizedCwd === ap + "/") {
44932
+ return {
44933
+ isProject: false,
44934
+ hasGit: false,
44935
+ reason: `System/root path: ${cwd}`,
44936
+ score: -15,
44937
+ signals: [`anti-path: ${ap}`]
44938
+ };
44939
+ }
44940
+ }
44941
+ let rootEntries = [];
44942
+ try {
44943
+ rootEntries = readdirSync15(cwd, { withFileTypes: true }).map((e) => ({
44944
+ name: e.name,
44945
+ isFile: e.isFile(),
44946
+ isDir: e.isDirectory()
44947
+ }));
44948
+ } catch {
44949
+ return {
44950
+ isProject: false,
44951
+ hasGit: false,
44952
+ reason: "Cannot read directory",
44953
+ score: -1,
44954
+ signals: ["unreadable"]
44955
+ };
44956
+ }
44957
+ const rootFileNames = new Set(rootEntries.filter((e) => e.isFile).map((e) => e.name));
44958
+ const rootDirNames = new Set(rootEntries.filter((e) => e.isDir).map((e) => e.name));
44959
+ for (const af of ANTI_SIGNAL_FILES) {
44960
+ if (rootFileNames.has(af)) {
44961
+ score -= 3;
44962
+ signals.push(`anti-file: ${af}`);
44963
+ }
44964
+ }
44965
+ for (const ad of ANTI_SIGNAL_DIRS) {
44966
+ if (rootDirNames.has(ad)) {
44967
+ score -= 2;
44968
+ signals.push(`anti-dir: ${ad}/`);
44969
+ }
44970
+ }
44971
+ for (const marker of DEFINITIVE_MARKERS) {
44972
+ if (rootFileNames.has(marker)) {
44973
+ score += 10;
44974
+ signals.push(`marker: ${marker}`);
44975
+ break;
44976
+ }
44977
+ }
44978
+ if (score < DETECTION_THRESHOLD) {
44979
+ for (const entry of rootEntries) {
44980
+ if (!entry.isFile && !entry.isDir) continue;
44981
+ const name = entry.name.toLowerCase();
44982
+ for (const ext of EXTENSION_MARKERS) {
44983
+ if (name.endsWith(ext)) {
44984
+ score += 10;
44985
+ signals.push(`ext-marker: ${entry.name}`);
44986
+ break;
44987
+ }
44988
+ }
44989
+ if (score >= DETECTION_THRESHOLD) break;
44990
+ }
44991
+ }
44992
+ for (const vcs of STRONG_SIGNAL_DIRS.slice(0, 6)) {
44993
+ if (rootDirNames.has(vcs)) {
44994
+ if (vcs === ".git") hasGit = true;
44995
+ score += 8;
44996
+ signals.push(`vcs: ${vcs}/`);
44997
+ break;
44998
+ }
44999
+ }
45000
+ for (const sf of STRONG_SIGNAL_FILES) {
45001
+ if (rootFileNames.has(sf)) {
45002
+ score += 4;
45003
+ signals.push(`strong: ${sf}`);
45004
+ if (score >= DETECTION_THRESHOLD) break;
45005
+ }
45006
+ }
45007
+ for (const sd of STRONG_SIGNAL_DIRS.slice(6)) {
45008
+ if (rootDirNames.has(sd)) {
45009
+ score += 4;
45010
+ signals.push(`strong: ${sd}/`);
45011
+ if (score >= DETECTION_THRESHOLD) break;
45012
+ }
45013
+ }
45014
+ if (!hasGit) {
45015
+ try {
45016
+ hasGit = await isInsideGitRepo2();
45017
+ if (hasGit) {
45018
+ score += 6;
45019
+ signals.push("git: inside work tree");
45020
+ }
45021
+ } catch {
45022
+ }
45023
+ }
45024
+ if (score >= DETECTION_THRESHOLD) {
45025
+ const topSignal2 = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "unknown";
45026
+ return { isProject: true, hasGit, reason: topSignal2, score, signals };
45027
+ }
45028
+ function dirHasCodeFile(dir) {
45029
+ try {
45030
+ const entries = readdirSync15(dir, { withFileTypes: true });
45031
+ for (const entry of entries) {
45032
+ if (!entry.isFile()) continue;
45033
+ const dot = entry.name.lastIndexOf(".");
45034
+ if (dot >= 0 && CODE_EXTENSIONS.has(entry.name.slice(dot).toLowerCase())) {
45035
+ return entry.name;
45036
+ }
45037
+ }
45038
+ } catch {
45039
+ }
45040
+ return null;
45041
+ }
45042
+ const rootCodeFile = dirHasCodeFile(cwd);
45043
+ if (rootCodeFile) {
45044
+ score += 5;
45045
+ signals.push(`code-root: ${rootCodeFile}`);
45046
+ }
45047
+ if (score >= DETECTION_THRESHOLD) {
45048
+ const topSignal2 = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "unknown";
45049
+ return { isProject: true, hasGit, reason: topSignal2, score, signals };
45050
+ }
45051
+ for (const dirEntry of rootEntries) {
45052
+ if (!dirEntry.isDir || dirEntry.name.startsWith(".")) continue;
45053
+ if (!SOURCE_DIRS.has(dirEntry.name.toLowerCase())) continue;
45054
+ const sourceDir = join67(cwd, dirEntry.name);
45055
+ const srcCodeFile = dirHasCodeFile(sourceDir);
45056
+ if (srcCodeFile) {
45057
+ score += 6;
45058
+ signals.push(`code-src: ${dirEntry.name}/${srcCodeFile}`);
45059
+ break;
45060
+ }
45061
+ try {
45062
+ const subEntries = readdirSync15(sourceDir, { withFileTypes: true });
45063
+ let found = false;
45064
+ for (const sub of subEntries) {
45065
+ if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
45066
+ const deepCode = dirHasCodeFile(join67(sourceDir, sub.name));
45067
+ if (deepCode) {
45068
+ score += 6;
45069
+ signals.push(`code-src: ${dirEntry.name}/${sub.name}/${deepCode}`);
45070
+ found = true;
45071
+ break;
45072
+ }
45073
+ }
45074
+ if (found) break;
45075
+ } catch {
45076
+ }
45077
+ }
45078
+ const isProject = score >= DETECTION_THRESHOLD;
45079
+ const topSignal = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "No signals";
45080
+ return { isProject, hasGit, reason: isProject ? topSignal : "No code files or project markers found", score, signals };
45081
+ }
43296
45082
  function readLocalConfig(cwd) {
43297
- const configPath = join66(cwd, ".unerr", "config.json");
43298
- if (!existsSync61(configPath)) return null;
45083
+ const configPath = join67(cwd, ".unerr", "config.json");
45084
+ if (!existsSync62(configPath)) return null;
43299
45085
  try {
43300
- return JSON.parse(readFileSync55(configPath, "utf-8"));
45086
+ return JSON.parse(readFileSync56(configPath, "utf-8"));
43301
45087
  } catch {
43302
45088
  return null;
43303
45089
  }
43304
45090
  }
43305
45091
  async function resumeBoot(config) {
43306
45092
  initFileLog(process.cwd());
45093
+ const detection = await detectProjectRoot(process.cwd());
45094
+ if (!detection.isProject) {
45095
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
45096
+ process.stderr.write(
45097
+ `\x1B[38;2;248;113;113m\u2717\x1B[0m No code project detected in this directory.
45098
+ Found .unerr/config.json but the directory doesn't look like a project root.
45099
+ Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})
45100
+ ` + (antiSignals.length > 0 ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}
45101
+ ` : "") + "\n unerr checks for: project files (package.json, Cargo.toml, go.mod, ...),\n VCS directories (.git, .hg), IDE configs, CI/CD files, lock files,\n and source code across 50+ languages.\n\n \u25B8 Run \x1B[1munerr\x1B[0m from a project root directory.\n"
45102
+ );
45103
+ process.exit(1);
45104
+ }
43307
45105
  const { initSessionLogger: initSessionLogger2, createSessionModuleLogger: createSessionModuleLogger2 } = await Promise.resolve().then(() => (init_session_logger(), session_logger_exports));
43308
45106
  initSessionLogger2();
43309
45107
  const log25 = createSessionModuleLogger2("boot");
@@ -43317,13 +45115,24 @@ async function firstRunBoot() {
43317
45115
  const { initSessionLogger: initSessionLogger2, createSessionModuleLogger: createSessionModuleLogger2 } = await Promise.resolve().then(() => (init_session_logger(), session_logger_exports));
43318
45116
  initSessionLogger2();
43319
45117
  const log25 = createSessionModuleLogger2("boot");
43320
- if (!await isInsideGitRepo2()) {
45118
+ const detection = await detectProjectRoot(process.cwd());
45119
+ if (!detection.isProject) {
45120
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
43321
45121
  process.stderr.write(
43322
- "[unerr] Not inside a git repository. Run unerr from a project folder.\n"
45122
+ `\x1B[38;2;248;113;113m\u2717\x1B[0m No code project detected in this directory.
45123
+ unerr needs a folder containing source code to index.
45124
+ Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})
45125
+ ` + (antiSignals.length > 0 ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}
45126
+ ` : "") + "\n Checked for: project files (package.json, Cargo.toml, go.mod, pyproject.toml, ...),\n VCS directories (.git, .hg, .svn), IDE configs (.vscode, .idea),\n CI/CD files (.github, Jenkinsfile), lock files, and source code\n across 50+ languages in root + standard source directories.\n\n \u25B8 Run \x1B[1munerr\x1B[0m from a project root directory.\n"
43323
45127
  );
43324
45128
  process.exit(1);
43325
45129
  }
43326
- log25.info({ msg: "First-run boot, entering local setup" });
45130
+ if (!detection.hasGit) {
45131
+ process.stderr.write(
45132
+ "\x1B[38;2;251;191;36m\u26A0\x1B[0m Project detected (" + detection.reason + ") but no git repository found.\n unerr works best with git. Consider running \x1B[1mgit init\x1B[0m first.\n Continuing anyway...\n\n"
45133
+ );
45134
+ }
45135
+ log25.info({ msg: "First-run boot, entering local setup", detection: detection.reason, score: detection.score, hasGit: detection.hasGit });
43327
45136
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup_wizard(), setup_wizard_exports));
43328
45137
  const result = await runSetup2();
43329
45138
  if (result.action === "setup") {
@@ -43335,15 +45144,21 @@ async function firstRunBoot() {
43335
45144
  }
43336
45145
  async function mcpBoot(cwd) {
43337
45146
  initFileLog(cwd);
43338
- if (!await isInsideGitRepo2()) {
45147
+ const detection = await detectProjectRoot(cwd);
45148
+ if (!detection.isProject) {
45149
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
43339
45150
  process.stderr.write(
43340
- "[unerr] Not inside a git repository. Run unerr from a project folder.\n"
45151
+ `\x1B[38;2;248;113;113m\u2717\x1B[0m No code project detected in this directory.
45152
+ unerr needs a folder containing source code to index.
45153
+ Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})
45154
+ ` + (antiSignals.length > 0 ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}
45155
+ ` : "") + "\n \u25B8 Run \x1B[1munerr --mcp\x1B[0m from a project root directory.\n"
43341
45156
  );
43342
45157
  process.exit(1);
43343
45158
  }
43344
- const stateDir = join66(cwd, ".unerr", "state");
43345
- const sockPath = join66(stateDir, "proxy.sock");
43346
- if (existsSync61(sockPath)) {
45159
+ const stateDir = join67(cwd, ".unerr", "state");
45160
+ const sockPath = join67(stateDir, "proxy.sock");
45161
+ if (existsSync62(sockPath)) {
43347
45162
  const { PidLock: PidLock2 } = await Promise.resolve().then(() => (init_pid_lock(), pid_lock_exports));
43348
45163
  const pidLock = new PidLock2(stateDir);
43349
45164
  const probeResult = await pidLock.probe();
@@ -43367,10 +45182,10 @@ async function mcpBoot(cwd) {
43367
45182
  const { mkdirSync: mkdirSync39, writeFileSync: writeFileSync36 } = await import("fs");
43368
45183
  const { createHash: createHash5 } = await import("crypto");
43369
45184
  const repoId = createHash5("sha256").update(cwd).digest("hex").slice(0, 12);
43370
- const unerrDir = join66(cwd, ".unerr");
45185
+ const unerrDir = join67(cwd, ".unerr");
43371
45186
  mkdirSync39(unerrDir, { recursive: true });
43372
45187
  writeFileSync36(
43373
- join66(unerrDir, "config.json"),
45188
+ join67(unerrDir, "config.json"),
43374
45189
  JSON.stringify(
43375
45190
  { repoId, mode: "local", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
43376
45191
  null,