@unerr-ai/unerr 0.0.0-beta.5 → 0.0.0-beta.7

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
  }
@@ -1670,6 +1670,7 @@ async function initSchema(db) {
1670
1670
  name: String,
1671
1671
  file_path: String,
1672
1672
  start_line: Int default 0,
1673
+ end_line: Int default 0,
1673
1674
  signature: String default "",
1674
1675
  body: String default "",
1675
1676
  fan_in: Int default 0,
@@ -1680,6 +1681,32 @@ async function initSchema(db) {
1680
1681
  }
1681
1682
  `
1682
1683
  );
1684
+ if (existing.has("entities")) {
1685
+ try {
1686
+ await db.run(
1687
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, community, is_test] :=
1688
+ *entities{key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, community, is_test},
1689
+ end_line = 0
1690
+ :replace entities {
1691
+ key: String
1692
+ =>
1693
+ kind: String,
1694
+ name: String,
1695
+ file_path: String,
1696
+ start_line: Int default 0,
1697
+ end_line: Int default 0,
1698
+ signature: String default "",
1699
+ body: String default "",
1700
+ fan_in: Int default 0,
1701
+ fan_out: Int default 0,
1702
+ risk_level: String default "normal",
1703
+ community: Int default -1,
1704
+ is_test: Bool default false
1705
+ }`
1706
+ );
1707
+ } catch {
1708
+ }
1709
+ }
1683
1710
  await createIfMissing(
1684
1711
  db,
1685
1712
  existing,
@@ -2339,14 +2366,15 @@ var init_local_graph = __esm({
2339
2366
  async loadSnapshot(envelope) {
2340
2367
  for (const entity of envelope.entities) {
2341
2368
  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 }`,
2369
+ `?[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]]
2370
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level }`,
2344
2371
  {
2345
2372
  key: entity.key,
2346
2373
  kind: entity.kind,
2347
2374
  name: entity.name,
2348
2375
  fp: entity.file_path,
2349
2376
  sl: entity.start_line ?? 0,
2377
+ el: entity.end_line ?? 0,
2350
2378
  sig: entity.signature ?? "",
2351
2379
  body: entity.body ?? "",
2352
2380
  fi: entity.fan_in ?? 0,
@@ -2454,17 +2482,18 @@ var init_local_graph = __esm({
2454
2482
  */
2455
2483
  async getEntity(key) {
2456
2484
  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",
2485
+ "?[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
2486
  { key }
2459
2487
  );
2460
2488
  if (result.rows.length === 0) return null;
2461
- const [k, kind, name, fp, sl, sig, body, fi, fo, rl, community] = result.rows[0];
2489
+ const [k, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] = result.rows[0];
2462
2490
  return {
2463
2491
  key: k,
2464
2492
  kind,
2465
2493
  name,
2466
2494
  file_path: fp,
2467
2495
  start_line: sl,
2496
+ end_line: el,
2468
2497
  signature: sig,
2469
2498
  body,
2470
2499
  fan_in: fi,
@@ -2507,6 +2536,7 @@ var init_local_graph = __esm({
2507
2536
  name,
2508
2537
  file_path: fp,
2509
2538
  start_line: sl,
2539
+ end_line: 0,
2510
2540
  signature: sig,
2511
2541
  body,
2512
2542
  fan_in: fi,
@@ -2550,6 +2580,7 @@ var init_local_graph = __esm({
2550
2580
  name,
2551
2581
  file_path: fp,
2552
2582
  start_line: sl,
2583
+ end_line: 0,
2553
2584
  signature: sig,
2554
2585
  body,
2555
2586
  fan_in: fi,
@@ -2904,14 +2935,14 @@ var init_local_graph = __esm({
2904
2935
  * Degree ranking excluding kind=="file" and kind=="module".
2905
2936
  */
2906
2937
  async getCriticalNodes(topN = 10, communityId) {
2907
- const query = communityId !== void 0 ? `?[key, name, fp, fi, fo, degree, community, label, rl] :=
2938
+ const query = communityId !== void 0 ? `?[key, name, fp, fi, fo, degree, community, label, rl, kind] :=
2908
2939
  *entities{key, name, file_path: fp, fan_in: fi, fan_out: fo, community, risk_level: rl, kind},
2909
2940
  kind != "file", kind != "module",
2910
2941
  community = $cid, community >= 0,
2911
2942
  *communities{id: community, label},
2912
2943
  degree = fi + fo
2913
2944
  :order -degree
2914
- :limit $top_n` : `?[key, name, fp, fi, fo, degree, community, label, rl] :=
2945
+ :limit $top_n` : `?[key, name, fp, fi, fo, degree, community, label, rl, kind] :=
2915
2946
  *entities{key, name, file_path: fp, fan_in: fi, fan_out: fo, community, risk_level: rl, kind},
2916
2947
  kind != "file", kind != "module",
2917
2948
  community >= 0,
@@ -2923,11 +2954,12 @@ var init_local_graph = __esm({
2923
2954
  if (communityId !== void 0) params.cid = communityId;
2924
2955
  const result = await this.query(query, params);
2925
2956
  return result.rows.map((row) => {
2926
- const [key, name, fp, fi, fo, degree, community, label, rl] = row;
2957
+ const [key, name, fp, fi, fo, degree, community, label, rl, kind] = row;
2927
2958
  return {
2928
2959
  key,
2929
2960
  name,
2930
2961
  file_path: fp,
2962
+ kind,
2931
2963
  fan_in: fi,
2932
2964
  fan_out: fo,
2933
2965
  degree,
@@ -2954,19 +2986,20 @@ var init_local_graph = __esm({
2954
2986
  */
2955
2987
  async getEntitiesByFile(filePath) {
2956
2988
  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},
2989
+ `?[k, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] := *file_index[$fp, ek],
2990
+ *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
2991
  k = ek`,
2960
2992
  { fp: filePath }
2961
2993
  );
2962
2994
  return result.rows.map((row) => {
2963
- const [k, kind, name, fp, sl, sig, body, fi, fo, rl, community] = row;
2995
+ const [k, kind, name, fp, sl, el, sig, body, fi, fo, rl, community] = row;
2964
2996
  return {
2965
2997
  key: k,
2966
2998
  kind,
2967
2999
  name,
2968
3000
  file_path: fp,
2969
3001
  start_line: sl,
3002
+ end_line: el,
2970
3003
  signature: sig,
2971
3004
  body,
2972
3005
  fan_in: fi,
@@ -2982,17 +3015,18 @@ var init_local_graph = __esm({
2982
3015
  */
2983
3016
  async findEntityByName(name) {
2984
3017
  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",
3018
+ "?[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
3019
  { name }
2987
3020
  );
2988
3021
  if (result.rows.length === 0) return null;
2989
- const [k, kind, n, fp, sl, sig, body, fi, fo, rl, community] = result.rows[0];
3022
+ const [k, kind, n, fp, sl, el, sig, body, fi, fo, rl, community] = result.rows[0];
2990
3023
  return {
2991
3024
  key: k,
2992
3025
  kind,
2993
3026
  name: n,
2994
3027
  file_path: fp,
2995
3028
  start_line: sl,
3029
+ end_line: el,
2996
3030
  signature: sig,
2997
3031
  body,
2998
3032
  fan_in: fi,
@@ -3704,14 +3738,15 @@ var init_local_graph = __esm({
3704
3738
  const allEntities = [...delta.entities.added, ...delta.entities.updated];
3705
3739
  for (const entity of allEntities) {
3706
3740
  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 }`,
3741
+ `?[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]]
3742
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level }`,
3709
3743
  {
3710
3744
  key: entity.key,
3711
3745
  kind: entity.kind,
3712
3746
  name: entity.name,
3713
3747
  fp: entity.file_path,
3714
3748
  sl: entity.start_line ?? 0,
3749
+ el: entity.end_line ?? 0,
3715
3750
  sig: entity.signature ?? "",
3716
3751
  body: entity.body ?? "",
3717
3752
  fi: entity.fan_in ?? 0,
@@ -4576,7 +4611,11 @@ function normalizeAgentName(input) {
4576
4611
  "copilot-cli": "github-copilot-cli",
4577
4612
  gemini: "gemini-cli",
4578
4613
  code: "vscode",
4579
- vs: "vscode"
4614
+ vs: "vscode",
4615
+ google: "antigravity",
4616
+ "google-antigravity": "antigravity",
4617
+ windsurf: "windsurf",
4618
+ cascade: "windsurf"
4580
4619
  };
4581
4620
  return aliases[input.toLowerCase()] ?? input.toLowerCase();
4582
4621
  }
@@ -4624,14 +4663,15 @@ var init_agent_registry = __esm({
4624
4663
  {
4625
4664
  id: "windsurf",
4626
4665
  name: "Windsurf",
4627
- projectConfigPath: ".windsurf/mcp.json",
4666
+ projectConfigPath: ".codeium/windsurf/mcp_config.json",
4667
+ configScope: "global",
4628
4668
  configFormat: "mcp-json",
4629
4669
  dirMarkers: [".windsurf"],
4630
- envVars: [],
4631
- hookSupport: false,
4632
- description: "Codeium's AI IDE",
4633
- instructionFilePath: null,
4634
- instructionFormat: null
4670
+ envVars: ["WINDSURF_EDITOR"],
4671
+ hookSupport: true,
4672
+ description: "Codeium's AI IDE (Cascade)",
4673
+ instructionFilePath: ".windsurf/rules/unerr-instructions.md",
4674
+ instructionFormat: "windsurf-rule"
4635
4675
  },
4636
4676
  {
4637
4677
  id: "zed",
@@ -4675,8 +4715,8 @@ var init_agent_registry = __esm({
4675
4715
  projectConfigPath: ".gemini/settings.json",
4676
4716
  configFormat: "settings-json",
4677
4717
  dirMarkers: [".gemini"],
4678
- envVars: ["GEMINI_API_KEY"],
4679
- hookSupport: false,
4718
+ envVars: ["GEMINI_API_KEY", "GEMINI_CLI"],
4719
+ hookSupport: true,
4680
4720
  description: "Google's CLI coding agent",
4681
4721
  instructionFilePath: "GEMINI.md",
4682
4722
  instructionFormat: "markdown"
@@ -4744,11 +4784,11 @@ var init_agent_registry = __esm({
4744
4784
  {
4745
4785
  id: "github-copilot-cli",
4746
4786
  name: "GitHub Copilot CLI",
4747
- projectConfigPath: ".github/mcp.json",
4748
- configFormat: "mcp-json",
4749
- dirMarkers: [".github"],
4787
+ projectConfigPath: ".copilot/mcp-config.json",
4788
+ configFormat: "copilot-json",
4789
+ dirMarkers: [".github", ".copilot"],
4750
4790
  envVars: ["GITHUB_COPILOT_TOKEN"],
4751
- hookSupport: false,
4791
+ hookSupport: true,
4752
4792
  description: "GitHub's CLI AI assistant",
4753
4793
  instructionFilePath: ".github/copilot-instructions.md",
4754
4794
  instructionFormat: "markdown"
@@ -4764,6 +4804,18 @@ var init_agent_registry = __esm({
4764
4804
  description: "Open-source AI code assistant (VS Code/JetBrains)",
4765
4805
  instructionFilePath: null,
4766
4806
  instructionFormat: null
4807
+ },
4808
+ {
4809
+ id: "antigravity",
4810
+ name: "Google Antigravity",
4811
+ projectConfigPath: ".antigravity/mcp_config.json",
4812
+ configFormat: "mcp-json",
4813
+ dirMarkers: [".antigravity", ".agents"],
4814
+ envVars: ["ANTIGRAVITY_PROJECT_DIR", "ANTIGRAVITY_VERSION"],
4815
+ hookSupport: false,
4816
+ description: "Google's AI coding IDE (Gemini 3)",
4817
+ instructionFilePath: ".agents/rules/unerr-instructions.md",
4818
+ instructionFormat: "antigravity-rule"
4767
4819
  }
4768
4820
  ];
4769
4821
  }
@@ -4780,6 +4832,7 @@ __export(mcp_config_writer_exports, {
4780
4832
  writeMcpConfig: () => writeMcpConfig
4781
4833
  });
4782
4834
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
4835
+ import { homedir } from "os";
4783
4836
  import { dirname as dirname2, join as join7 } from "path";
4784
4837
  function createUnerrServerEntry() {
4785
4838
  return {
@@ -4788,12 +4841,19 @@ function createUnerrServerEntry() {
4788
4841
  args: ["--mcp"]
4789
4842
  };
4790
4843
  }
4844
+ function createCopilotServerEntry() {
4845
+ return {
4846
+ type: "local",
4847
+ command: "unerr",
4848
+ args: ["--mcp"]
4849
+ };
4850
+ }
4791
4851
  function writeMcpConfig(cwd, ide) {
4792
4852
  const agent = getAgent(ide);
4793
4853
  if (!agent) {
4794
4854
  return { path: "", action: "skipped" };
4795
4855
  }
4796
- const configPath = join7(cwd, agent.projectConfigPath);
4856
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4797
4857
  const dir = dirname2(configPath);
4798
4858
  if (!existsSync5(dir)) {
4799
4859
  mkdirSync3(dir, { recursive: true });
@@ -4803,6 +4863,8 @@ function writeMcpConfig(cwd, ide) {
4803
4863
  return writeMcpJsonFormat(configPath);
4804
4864
  case "settings-json":
4805
4865
  return writeSettingsJsonFormat(configPath);
4866
+ case "copilot-json":
4867
+ return writeCopilotJsonFormat(configPath);
4806
4868
  case "continue-config":
4807
4869
  return writeContinueFormat(configPath);
4808
4870
  default:
@@ -4818,7 +4880,7 @@ function writeAllMcpConfigs(cwd, agents) {
4818
4880
  function removeMcpConfig(cwd, ide) {
4819
4881
  const agent = getAgent(ide);
4820
4882
  if (!agent) return false;
4821
- const configPath = join7(cwd, agent.projectConfigPath);
4883
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4822
4884
  if (!existsSync5(configPath)) return false;
4823
4885
  try {
4824
4886
  const existing = JSON.parse(readFileSync6(configPath, "utf-8"));
@@ -4830,6 +4892,9 @@ function removeMcpConfig(cwd, ide) {
4830
4892
  } else if (agent.configFormat === "settings-json") {
4831
4893
  if (!existing.mcp?.servers?.[UNERR_SERVER_KEY]) return false;
4832
4894
  delete existing.mcp.servers[UNERR_SERVER_KEY];
4895
+ } else if (agent.configFormat === "copilot-json") {
4896
+ if (!existing.mcpServers?.[UNERR_SERVER_KEY]) return false;
4897
+ delete existing.mcpServers[UNERR_SERVER_KEY];
4833
4898
  } else {
4834
4899
  if (!existing.mcpServers?.[UNERR_SERVER_KEY]) return false;
4835
4900
  delete existing.mcpServers[UNERR_SERVER_KEY];
@@ -4843,7 +4908,7 @@ function removeMcpConfig(cwd, ide) {
4843
4908
  function isConfigured(cwd, ide) {
4844
4909
  const agent = getAgent(ide);
4845
4910
  if (!agent) return false;
4846
- const configPath = join7(cwd, agent.projectConfigPath);
4911
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4847
4912
  if (!existsSync5(configPath)) return false;
4848
4913
  try {
4849
4914
  const existing = JSON.parse(readFileSync6(configPath, "utf-8"));
@@ -4855,6 +4920,9 @@ function isConfigured(cwd, ide) {
4855
4920
  if (agent.configFormat === "settings-json") {
4856
4921
  return !!existing.mcp?.servers?.[UNERR_SERVER_KEY];
4857
4922
  }
4923
+ if (agent.configFormat === "copilot-json") {
4924
+ return !!existing.mcpServers?.[UNERR_SERVER_KEY];
4925
+ }
4858
4926
  return !!existing.mcpServers?.[UNERR_SERVER_KEY];
4859
4927
  } catch {
4860
4928
  return false;
@@ -4871,6 +4939,14 @@ function generateConfigSnippet(ide) {
4871
4939
  null,
4872
4940
  2
4873
4941
  );
4942
+ case "copilot-json": {
4943
+ const copilotEntry = createCopilotServerEntry();
4944
+ return JSON.stringify(
4945
+ { mcpServers: { [UNERR_SERVER_KEY]: copilotEntry } },
4946
+ null,
4947
+ 2
4948
+ );
4949
+ }
4874
4950
  case "continue-config":
4875
4951
  return JSON.stringify(
4876
4952
  { mcpServers: [{ name: UNERR_SERVER_KEY, ...entry }] },
@@ -4961,6 +5037,29 @@ function writeContinueFormat(configPath) {
4961
5037
  writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
4962
5038
  return { path: configPath, action: "created" };
4963
5039
  }
5040
+ function writeCopilotJsonFormat(configPath) {
5041
+ if (existsSync5(configPath)) {
5042
+ try {
5043
+ const existing = JSON.parse(
5044
+ readFileSync6(configPath, "utf-8")
5045
+ );
5046
+ if (existing.mcpServers?.[UNERR_SERVER_KEY]) {
5047
+ return { path: configPath, action: "skipped" };
5048
+ }
5049
+ existing.mcpServers = existing.mcpServers ?? {};
5050
+ existing.mcpServers[UNERR_SERVER_KEY] = createCopilotServerEntry();
5051
+ writeFileSync2(configPath, JSON.stringify(existing, null, 2), "utf-8");
5052
+ return { path: configPath, action: "updated" };
5053
+ } catch {
5054
+ return { path: configPath, action: "skipped" };
5055
+ }
5056
+ }
5057
+ const config = {
5058
+ mcpServers: { [UNERR_SERVER_KEY]: createCopilotServerEntry() }
5059
+ };
5060
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
5061
+ return { path: configPath, action: "created" };
5062
+ }
4964
5063
  var UNERR_SERVER_KEY;
4965
5064
  var init_mcp_config_writer = __esm({
4966
5065
  "src/config/mcp-config-writer.ts"() {
@@ -5049,6 +5148,9 @@ async function detectIde(cwd) {
5049
5148
  if (existsSync6(join8(cwd, ".vscode"))) return "vscode";
5050
5149
  if (process.env.ZED_TERM === "true" || existsSync6(join8(cwd, ".zed")))
5051
5150
  return "zed";
5151
+ if (process.env.ANTIGRAVITY_PROJECT_DIR || process.env.ANTIGRAVITY_VERSION)
5152
+ return "antigravity";
5153
+ if (existsSync6(join8(cwd, ".antigravity"))) return "antigravity";
5052
5154
  return "unknown";
5053
5155
  }
5054
5156
  function ideDisplayName(ide) {
@@ -5083,6 +5185,8 @@ function ideDisplayName(ide) {
5083
5185
  return "GitHub Copilot CLI";
5084
5186
  case "continue":
5085
5187
  return "Continue";
5188
+ case "antigravity":
5189
+ return "Google Antigravity";
5086
5190
  case "other":
5087
5191
  return "Other";
5088
5192
  case "unknown":
@@ -5111,6 +5215,7 @@ var init_detect = __esm({
5111
5215
  { title: "Augment", value: "augment" },
5112
5216
  { title: "GitHub Copilot CLI", value: "github-copilot-cli" },
5113
5217
  { title: "Continue", value: "continue" },
5218
+ { title: "Google Antigravity", value: "antigravity" },
5114
5219
  { title: "Other (manual MCP setup)", value: "other" }
5115
5220
  ];
5116
5221
  }
@@ -5355,7 +5460,7 @@ __export(settings_exports, {
5355
5460
  resolveInferenceEndpoint: () => resolveInferenceEndpoint
5356
5461
  });
5357
5462
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
5358
- import { homedir as homedir3 } from "os";
5463
+ import { homedir as homedir4 } from "os";
5359
5464
  import { join as join12 } from "path";
5360
5465
  import { z } from "zod";
5361
5466
  function resolveEmbeddingEndpoint(config) {
@@ -5386,7 +5491,7 @@ function loadJsonFile(filePath) {
5386
5491
  }
5387
5492
  function loadSettings(cwd) {
5388
5493
  const projectDir = cwd ?? process.cwd();
5389
- const userSettings = loadJsonFile(join12(homedir3(), ".unerr", "settings.json"));
5494
+ const userSettings = loadJsonFile(join12(homedir4(), ".unerr", "settings.json"));
5390
5495
  const projectSettings = loadJsonFile(
5391
5496
  join12(projectDir, ".unerr", "settings.json")
5392
5497
  );
@@ -6542,6 +6647,7 @@ __export(ast_extractor_exports, {
6542
6647
  import { createHash } from "crypto";
6543
6648
  import { existsSync as existsSync14 } from "fs";
6544
6649
  import { join as join19 } from "path";
6650
+ import { fileURLToPath } from "url";
6545
6651
  function detectLanguage2(filePath) {
6546
6652
  const dot = filePath.lastIndexOf(".");
6547
6653
  if (dot < 0) return null;
@@ -6554,13 +6660,18 @@ function extractEntities(content, filePath) {
6554
6660
  const lines = content.split("\n");
6555
6661
  const entities = [];
6556
6662
  const patterns = getPatterns(language);
6663
+ let currentClassName = null;
6557
6664
  for (let i = 0; i < lines.length; i++) {
6558
6665
  const line = lines[i] ?? "";
6559
6666
  for (const pattern of patterns) {
6560
6667
  const match = pattern.regex.exec(line);
6561
6668
  if (match) {
6562
- const name = match[pattern.nameGroup] ?? "";
6563
- if (!name || name.length === 0) continue;
6669
+ const rawName = match[pattern.nameGroup] ?? "";
6670
+ if (!rawName || rawName.length === 0) continue;
6671
+ if (pattern.kind === "class") {
6672
+ currentClassName = rawName;
6673
+ }
6674
+ const name = pattern.kind === "method" && currentClassName ? `${currentClassName}.${rawName}` : rawName;
6564
6675
  const signature = match[pattern.sigGroup ?? 0] ?? "";
6565
6676
  const lineStart = i + 1;
6566
6677
  const lineEnd = findBlockEnd(lines, i, language);
@@ -6573,7 +6684,8 @@ function extractEntities(content, filePath) {
6573
6684
  signature: signature.trim(),
6574
6685
  line_start: lineStart,
6575
6686
  line_end: lineEnd,
6576
- content_hash: contentHash
6687
+ content_hash: contentHash,
6688
+ parent_class: pattern.kind === "method" ? currentClassName ?? void 0 : void 0
6577
6689
  });
6578
6690
  break;
6579
6691
  }
@@ -6605,9 +6717,9 @@ function getPatterns(language) {
6605
6717
  kind: "interface",
6606
6718
  nameGroup: 1
6607
6719
  },
6608
- // method(params) { — inside class
6720
+ // method(params) { — inside class (with optional access modifiers)
6609
6721
  {
6610
- regex: /^\s+(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(\w+)\s*(\([^)]*\))\s*(?::\s*\S+\s*)?[{]/,
6722
+ regex: /^\s+(?:(?:private|protected|public|override|abstract|readonly)\s+)*(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?(\w+)\s*(\([^)]*\))\s*(?::\s*\S+\s*)?[{]/,
6611
6723
  kind: "method",
6612
6724
  nameGroup: 1,
6613
6725
  sigGroup: 2
@@ -6929,10 +7041,16 @@ async function getTSParser(grammar) {
6929
7041
  }
6930
7042
  const parser = new TreeSitter();
6931
7043
  const wasmFile = `tree-sitter-${grammar}.wasm`;
7044
+ const pkgDir = import.meta.dirname ?? join19(fileURLToPath(import.meta.url), "..");
7045
+ const pkgRoot = join19(pkgDir, "..");
6932
7046
  const possiblePaths = [
6933
7047
  join19(process.cwd(), "node_modules", "tree-sitter-wasms", "out", wasmFile),
6934
7048
  join19(process.cwd(), "node_modules", `tree-sitter-${grammar}`, wasmFile),
6935
- join19(process.cwd(), "node_modules", "web-tree-sitter", wasmFile)
7049
+ join19(process.cwd(), "node_modules", "web-tree-sitter", wasmFile),
7050
+ // Package-relative paths (when unerr is installed globally or in another project)
7051
+ join19(pkgRoot, "node_modules", "tree-sitter-wasms", "out", wasmFile),
7052
+ join19(pkgRoot, "node_modules", `tree-sitter-${grammar}`, wasmFile),
7053
+ join19(pkgRoot, "node_modules", "web-tree-sitter", wasmFile)
6936
7054
  ];
6937
7055
  let wasmPath = null;
6938
7056
  for (const p of possiblePaths) {
@@ -8989,12 +9107,12 @@ var init_decoder = __esm({
8989
9107
  // src/intelligence/indexer/scip/downloader.ts
8990
9108
  import { chmodSync as chmodSync2, createWriteStream, existsSync as existsSync15, mkdirSync as mkdirSync11 } from "fs";
8991
9109
  import { rename, rm, stat } from "fs/promises";
8992
- import { homedir as homedir4 } from "os";
9110
+ import { homedir as homedir5 } from "os";
8993
9111
  import { join as join20 } from "path";
8994
9112
  import { pipeline } from "stream/promises";
8995
9113
  import { createGunzip } from "zlib";
8996
9114
  function getScipBinDir() {
8997
- return join20(homedir4(), ".unerr", "bin");
9115
+ return join20(homedir5(), ".unerr", "bin");
8998
9116
  }
8999
9117
  function getCachedBinaryPath(language) {
9000
9118
  const spec = DOWNLOAD_SPECS[language];
@@ -9569,9 +9687,9 @@ function buildScipArgs(language, binaryPath, projectRoot, outputPath) {
9569
9687
  case "go":
9570
9688
  return [binaryPath, "-o", outputPath, "./..."];
9571
9689
  case "java":
9572
- return [binaryPath, "--output", outputPath];
9690
+ return [binaryPath, "index", "--output", outputPath];
9573
9691
  case "rust":
9574
- return [binaryPath, "scip", outputPath];
9692
+ return [binaryPath, "scip", projectRoot, "--output", outputPath];
9575
9693
  default:
9576
9694
  return [binaryPath, "--output", outputPath];
9577
9695
  }
@@ -9700,6 +9818,10 @@ var init_orchestrator = __esm({
9700
9818
  });
9701
9819
 
9702
9820
  // src/intelligence/local-convention-detector.ts
9821
+ var local_convention_detector_exports = {};
9822
+ __export(local_convention_detector_exports, {
9823
+ detectLocalConventions: () => detectLocalConventions
9824
+ });
9703
9825
  import { dirname as dirname4 } from "path";
9704
9826
  async function detectLocalConventions(db) {
9705
9827
  const start = Date.now();
@@ -10261,6 +10383,7 @@ async function indexLocalProject(projectRoot, graphStore, repoId, opts) {
10261
10383
  name: entity.name,
10262
10384
  file_path: relPath,
10263
10385
  start_line: entity.line_start,
10386
+ end_line: entity.line_end,
10264
10387
  signature: entity.signature,
10265
10388
  body: "",
10266
10389
  // Skip body storage for indexing performance
@@ -10424,6 +10547,7 @@ async function reindexFile(projectRoot, filePath, graphStore, repoId) {
10424
10547
  name: e.name,
10425
10548
  file_path: relPath,
10426
10549
  start_line: e.line_start,
10550
+ end_line: e.line_end,
10427
10551
  signature: e.signature,
10428
10552
  body: "",
10429
10553
  fan_in: 0,
@@ -10840,8 +10964,8 @@ async function populateCozoDB(graphStore, entities, edges) {
10840
10964
  for (const fp of filePaths) {
10841
10965
  try {
10842
10966
  await graphStore.db.run(
10843
- `?[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]]
10844
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
10967
+ `?[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]]
10968
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
10845
10969
  { key: `file:${fp}`, name: basename2(fp), fp, is_test: isTestFile(fp) }
10846
10970
  );
10847
10971
  } catch (err) {
@@ -10889,14 +11013,15 @@ async function populateCozoDB(graphStore, entities, edges) {
10889
11013
  async function insertEntity(graphStore, entity) {
10890
11014
  try {
10891
11015
  await graphStore.db.run(
10892
- `?[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]]
10893
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
11016
+ `?[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]]
11017
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
10894
11018
  {
10895
11019
  key: entity.key,
10896
11020
  kind: entity.kind,
10897
11021
  name: entity.name,
10898
11022
  fp: entity.file_path,
10899
11023
  sl: entity.start_line ?? 0,
11024
+ el: entity.end_line ?? 0,
10900
11025
  sig: entity.signature ?? "",
10901
11026
  body: entity.body ?? "",
10902
11027
  fi: entity.fan_in ?? 0,
@@ -11717,7 +11842,7 @@ import {
11717
11842
  unlinkSync as unlinkSync4,
11718
11843
  writeFileSync as writeFileSync12
11719
11844
  } from "fs";
11720
- import { homedir as homedir5 } from "os";
11845
+ import { homedir as homedir6 } from "os";
11721
11846
  import { dirname as dirname6, join as join29 } from "path";
11722
11847
  function getSkillDir(ide, cwd) {
11723
11848
  switch (ide) {
@@ -11741,9 +11866,9 @@ function getSkillDir(ide, cwd) {
11741
11866
  };
11742
11867
  case "windsurf":
11743
11868
  return {
11744
- dir: join29(cwd, ".windsurf", "rules"),
11869
+ dir: join29(cwd, ".windsurf", "skills"),
11745
11870
  ext: ".md",
11746
- dirPerSkill: false
11871
+ dirPerSkill: true
11747
11872
  };
11748
11873
  case "zed":
11749
11874
  return {
@@ -11751,6 +11876,24 @@ function getSkillDir(ide, cwd) {
11751
11876
  ext: ".md",
11752
11877
  dirPerSkill: false
11753
11878
  };
11879
+ case "gemini-cli":
11880
+ return {
11881
+ dir: join29(cwd, ".gemini", "skills"),
11882
+ ext: ".md",
11883
+ dirPerSkill: true
11884
+ };
11885
+ case "github-copilot-cli":
11886
+ return {
11887
+ dir: join29(cwd, ".github", "skills"),
11888
+ ext: ".md",
11889
+ dirPerSkill: true
11890
+ };
11891
+ case "antigravity":
11892
+ return {
11893
+ dir: join29(cwd, ".agents", "skills"),
11894
+ ext: ".md",
11895
+ dirPerSkill: true
11896
+ };
11754
11897
  default:
11755
11898
  return {
11756
11899
  dir: join29(cwd, ".unerr", "skills"),
@@ -11765,7 +11908,27 @@ function writeSkillFile(skill, ide, skillDir, ext, dirPerSkill) {
11765
11908
  const skillName = `unerr-${baseName}`;
11766
11909
  let filePath;
11767
11910
  let content;
11768
- if (dirPerSkill) {
11911
+ if (dirPerSkill && ide === "windsurf") {
11912
+ const skillSubDir = join29(skillDir, skillName);
11913
+ mkdirSync16(skillSubDir, { recursive: true });
11914
+ filePath = join29(skillSubDir, "SKILL.md");
11915
+ content = formatWindsurfSkillMd(skill);
11916
+ } else if (dirPerSkill && ide === "gemini-cli") {
11917
+ const skillSubDir = join29(skillDir, skillName);
11918
+ mkdirSync16(skillSubDir, { recursive: true });
11919
+ filePath = join29(skillSubDir, "SKILL.md");
11920
+ content = formatGeminiSkill(skill);
11921
+ } else if (dirPerSkill && ide === "github-copilot-cli") {
11922
+ const skillSubDir = join29(skillDir, skillName);
11923
+ mkdirSync16(skillSubDir, { recursive: true });
11924
+ filePath = join29(skillSubDir, "SKILL.md");
11925
+ content = formatCopilotSkill(skill);
11926
+ } else if (dirPerSkill && ide === "antigravity") {
11927
+ const skillSubDir = join29(skillDir, skillName);
11928
+ mkdirSync16(skillSubDir, { recursive: true });
11929
+ filePath = join29(skillSubDir, "SKILL.md");
11930
+ content = formatAntigravitySkill(skill);
11931
+ } else if (dirPerSkill) {
11769
11932
  const skillSubDir = join29(skillDir, skillName);
11770
11933
  mkdirSync16(skillSubDir, { recursive: true });
11771
11934
  filePath = join29(skillSubDir, "SKILL.md");
@@ -11773,9 +11936,6 @@ function writeSkillFile(skill, ide, skillDir, ext, dirPerSkill) {
11773
11936
  } else if (ide === "cursor" && ext === ".mdc") {
11774
11937
  filePath = join29(skillDir, `${skillName}${ext}`);
11775
11938
  content = formatCursorSkill(skill);
11776
- } else if (ide === "windsurf") {
11777
- filePath = join29(skillDir, `${skillName}${ext}`);
11778
- content = formatWindsurfSkill(skill);
11779
11939
  } else {
11780
11940
  filePath = join29(skillDir, `${skillName}${ext}`);
11781
11941
  content = formatMarkdownSkill(skill);
@@ -11857,53 +12017,71 @@ alwaysApply: ${alwaysApply}
11857
12017
  ${skill.content}
11858
12018
  `;
11859
12019
  }
11860
- function formatWindsurfSkill(skill) {
12020
+ function formatMarkdownSkill(skill) {
11861
12021
  const trigger = skill.trigger;
11862
12022
  const triggerType = trigger?.type ?? "always";
11863
- let wsType;
11864
- switch (triggerType) {
11865
- case "always":
11866
- wsType = "always_on";
11867
- break;
11868
- case "auto":
11869
- wsType = "glob";
11870
- break;
11871
- case "agent-requested":
11872
- wsType = "model_decision";
11873
- break;
11874
- case "manual":
11875
- wsType = "manual";
11876
- break;
11877
- default:
11878
- wsType = "always_on";
11879
- }
11880
12023
  const globLine = trigger?.globs && trigger.globs.length > 0 ? `
11881
12024
  globs: ${JSON.stringify(trigger.globs)}` : "";
11882
- const truncatedContent = skill.content.length > 5800 ? `${skill.content.slice(0, 5800)}
11883
-
11884
- [Truncated \u2014 full content exceeds Windsurf 6K limit]` : skill.content;
11885
12025
  return `---
11886
- trigger: ${wsType}${globLine}
11887
12026
  description: "${skill.description}"
12027
+ trigger: ${triggerType}${globLine}
12028
+ version: "${skill.version ?? "1.0.0"}"
11888
12029
  ---
11889
12030
 
11890
12031
  # ${skill.name}
11891
12032
 
11892
- ${truncatedContent}
12033
+ ${skill.content}
11893
12034
  `;
11894
12035
  }
11895
- function formatMarkdownSkill(skill) {
11896
- const trigger = skill.trigger;
11897
- const triggerType = trigger?.type ?? "always";
11898
- const globLine = trigger?.globs && trigger.globs.length > 0 ? `
11899
- globs: ${JSON.stringify(trigger.globs)}` : "";
12036
+ function formatWindsurfSkillMd(skill) {
11900
12037
  return `---
11901
- description: "${skill.description}"
11902
- trigger: ${triggerType}${globLine}
11903
- version: "${skill.version ?? "1.0.0"}"
12038
+ name: ${skill.name}
12039
+ description: ${skill.description}
11904
12040
  ---
11905
12041
 
11906
- # ${skill.name}
12042
+ ${skill.content}
12043
+ `;
12044
+ }
12045
+ function formatGeminiSkill(skill) {
12046
+ return `---
12047
+ name: ${skill.name}
12048
+ description: ${skill.description}
12049
+ ---
12050
+
12051
+ ${skill.content}
12052
+ `;
12053
+ }
12054
+ function formatCopilotSkill(skill) {
12055
+ return `---
12056
+ name: ${skill.name}
12057
+ description: ${skill.description}
12058
+ ---
12059
+
12060
+ ${skill.content}
12061
+ `;
12062
+ }
12063
+ function formatAntigravitySkill(skill) {
12064
+ const trigger = skill.trigger;
12065
+ const triggerType = trigger?.type ?? "always";
12066
+ let globs = [];
12067
+ if (trigger?.globs) {
12068
+ globs = trigger.globs;
12069
+ }
12070
+ let frontmatter = `---
12071
+ name: ${skill.name}
12072
+ description: ${skill.description}`;
12073
+ if (globs.length > 0) {
12074
+ frontmatter += `
12075
+ globs:
12076
+ ${globs.map((g) => ` - ${g}`).join("\n")}`;
12077
+ }
12078
+ if (triggerType === "manual") {
12079
+ frontmatter += `
12080
+ user_invocable: true`;
12081
+ }
12082
+ frontmatter += `
12083
+ ---`;
12084
+ return `${frontmatter}
11907
12085
 
11908
12086
  ${skill.content}
11909
12087
  `;
@@ -11963,7 +12141,7 @@ function scanSkillDirectory(dir) {
11963
12141
  return skills;
11964
12142
  }
11965
12143
  function loadLocalDirectorySkills(cwd) {
11966
- const userDir = join29(homedir5(), ".unerr", "skills");
12144
+ const userDir = join29(homedir6(), ".unerr", "skills");
11967
12145
  const projectDir = join29(cwd, ".unerr", "skills");
11968
12146
  const userSkills = scanSkillDirectory(userDir);
11969
12147
  const projectSkills = scanSkillDirectory(projectDir);
@@ -13028,9 +13206,9 @@ function createEmptyAllTime() {
13028
13206
  function loadStats() {
13029
13207
  const currentWeek = getWeekStart();
13030
13208
  try {
13031
- const { readFileSync: readFileSync56 } = __require("fs");
13209
+ const { readFileSync: readFileSync57 } = __require("fs");
13032
13210
  const raw = JSON.parse(
13033
- readFileSync56(getStatsPath(), "utf-8")
13211
+ readFileSync57(getStatsPath(), "utf-8")
13034
13212
  );
13035
13213
  if (raw.version !== 1) {
13036
13214
  return {
@@ -13743,9 +13921,9 @@ function getCumulativePath() {
13743
13921
  function loadCumulativeStats() {
13744
13922
  const currentWeek = getWeekStart2();
13745
13923
  try {
13746
- const { readFileSync: readFileSync56 } = __require("fs");
13924
+ const { readFileSync: readFileSync57 } = __require("fs");
13747
13925
  const raw = JSON.parse(
13748
- readFileSync56(getCumulativePath(), "utf-8")
13926
+ readFileSync57(getCumulativePath(), "utf-8")
13749
13927
  );
13750
13928
  if (raw.weekStart !== currentWeek) {
13751
13929
  return {
@@ -13806,9 +13984,9 @@ function createEmptyCumulativeLocal(weekStart) {
13806
13984
  function loadCumulativeLocalStats() {
13807
13985
  const currentWeek = getWeekStart2();
13808
13986
  try {
13809
- const { readFileSync: readFileSync56 } = __require("fs");
13987
+ const { readFileSync: readFileSync57 } = __require("fs");
13810
13988
  const raw = JSON.parse(
13811
- readFileSync56(getCumulativeLocalPath(), "utf-8")
13989
+ readFileSync57(getCumulativeLocalPath(), "utf-8")
13812
13990
  );
13813
13991
  if (raw.weekStartDate !== currentWeek) {
13814
13992
  return createEmptyCumulativeLocal(currentWeek);
@@ -16464,6 +16642,250 @@ var init_record_fact = __esm({
16464
16642
  }
16465
16643
  });
16466
16644
 
16645
+ // src/intelligence/fact-generator.ts
16646
+ var fact_generator_exports = {};
16647
+ __export(fact_generator_exports, {
16648
+ generateFromCausalBridge: () => generateFromCausalBridge,
16649
+ generateFromConventions: () => generateFromConventions,
16650
+ generateFromNegativeKnowledge: () => generateFromNegativeKnowledge,
16651
+ generateFromSessionAnalysis: () => generateFromSessionAnalysis,
16652
+ runFactGenerationPipeline: () => runFactGenerationPipeline
16653
+ });
16654
+ import { existsSync as existsSync37, readdirSync as readdirSync9, readFileSync as readFileSync34 } from "fs";
16655
+ import { join as join44 } from "path";
16656
+ async function runFactGenerationPipeline(factStore2, unerrDir) {
16657
+ const results = [];
16658
+ try {
16659
+ const sessionResult = await generateFromSessionAnalysis(factStore2, unerrDir);
16660
+ if (sessionResult) results.push(sessionResult);
16661
+ } catch {
16662
+ }
16663
+ return results;
16664
+ }
16665
+ async function generateFromConventions(factStore2, conventions) {
16666
+ let created = 0;
16667
+ let reinforced = 0;
16668
+ const details = [];
16669
+ const MIN_CONFIDENCE = 0.7;
16670
+ const MIN_FREQUENCY = 5;
16671
+ for (const conv of conventions) {
16672
+ if (conv.confidence < MIN_CONFIDENCE) continue;
16673
+ if (conv.frequency < MIN_FREQUENCY) continue;
16674
+ const content = formatConventionFact(conv);
16675
+ if (content.length > 280) continue;
16676
+ const input = {
16677
+ fact_type: "semantic",
16678
+ scope: inferScopeFromConvention(conv),
16679
+ subject: conv.name,
16680
+ content,
16681
+ source: "convention_detector",
16682
+ base_confidence: Math.min(0.9, conv.confidence)
16683
+ };
16684
+ const factId = await factStore2.createFact(input);
16685
+ if (factId) {
16686
+ const isNew = !details.some((d) => d.includes(conv.name));
16687
+ if (isNew) {
16688
+ created++;
16689
+ details.push(`[convention] ${conv.name}: ${content.slice(0, 80)}`);
16690
+ } else {
16691
+ reinforced++;
16692
+ }
16693
+ }
16694
+ }
16695
+ return { created, reinforced, source: "convention_detector", details };
16696
+ }
16697
+ async function generateFromNegativeKnowledge(factStore2, corrections) {
16698
+ let created = 0;
16699
+ let reinforced = 0;
16700
+ const details = [];
16701
+ for (const correction of corrections) {
16702
+ const content = correction.reason.slice(0, 280);
16703
+ const input = {
16704
+ fact_type: "negative",
16705
+ scope: correction.entityKey,
16706
+ subject: correction.entityKey,
16707
+ content,
16708
+ source: "negative_knowledge",
16709
+ base_confidence: 0.9
16710
+ };
16711
+ const factId = await factStore2.createFact(input);
16712
+ if (factId) {
16713
+ created++;
16714
+ details.push(`[negative] ${correction.entityKey}: ${content.slice(0, 60)}`);
16715
+ }
16716
+ }
16717
+ return { created, reinforced, source: "negative_knowledge", details };
16718
+ }
16719
+ async function generateFromCausalBridge(factStore2, events) {
16720
+ let created = 0;
16721
+ let reinforced = 0;
16722
+ const details = [];
16723
+ for (const event of events) {
16724
+ if (event.action === "survived") {
16725
+ const content = `Change to ${event.entity_key} on ${new Date(event.timestamp).toISOString().split("T")[0]} survived \u2014 shipped to ${event.branch}`;
16726
+ const input = {
16727
+ fact_type: "episodic",
16728
+ scope: event.entity_key,
16729
+ subject: event.entity_key,
16730
+ content: content.slice(0, 280),
16731
+ source: "causal_bridge",
16732
+ base_confidence: 1
16733
+ };
16734
+ const factId = await factStore2.createFact(input);
16735
+ if (factId) {
16736
+ created++;
16737
+ details.push(`[episodic:survived] ${event.entity_key}`);
16738
+ }
16739
+ } else if (event.action === "reverted") {
16740
+ const content = `Change to ${event.entity_key} was reverted within 24h \u2014 approach was incorrect`;
16741
+ const input = {
16742
+ fact_type: "negative",
16743
+ scope: event.entity_key,
16744
+ subject: event.entity_key,
16745
+ content: content.slice(0, 280),
16746
+ source: "causal_bridge",
16747
+ base_confidence: 0.85
16748
+ };
16749
+ const factId = await factStore2.createFact(input);
16750
+ if (factId) {
16751
+ created++;
16752
+ details.push(`[negative:reverted] ${event.entity_key}`);
16753
+ }
16754
+ }
16755
+ }
16756
+ return { created, reinforced, source: "causal_bridge", details };
16757
+ }
16758
+ async function generateFromSessionAnalysis(factStore2, unerrDir) {
16759
+ let created = 0;
16760
+ let reinforced = 0;
16761
+ const details = [];
16762
+ const summaries = loadRecentSessions(unerrDir, 20);
16763
+ if (summaries.length < 3) {
16764
+ return { created: 0, reinforced: 0, source: "session_analysis", details: [] };
16765
+ }
16766
+ const hotFiles = detectHotFiles(summaries);
16767
+ for (const [file, frequency] of hotFiles) {
16768
+ const pct = Math.round(frequency * 100);
16769
+ const content = `${file} is accessed in ${pct}% of sessions (hot file)`;
16770
+ const confidence = Math.min(0.85, 0.4 + frequency * 0.5);
16771
+ const input = {
16772
+ fact_type: "procedural",
16773
+ scope: file,
16774
+ subject: file,
16775
+ content,
16776
+ source: "session_analysis",
16777
+ base_confidence: confidence
16778
+ };
16779
+ const factId = await factStore2.createFact(input);
16780
+ if (factId) {
16781
+ created++;
16782
+ details.push(`[procedural:hot] ${file} (${pct}%)`);
16783
+ }
16784
+ }
16785
+ const highRevertFiles = detectHighRevertFiles(summaries);
16786
+ for (const [file, revertRate] of highRevertFiles) {
16787
+ const pct = Math.round(revertRate * 100);
16788
+ const content = `${file} has a ${pct}% revert rate across sessions \u2014 changes here are fragile`;
16789
+ const input = {
16790
+ fact_type: "negative",
16791
+ scope: file,
16792
+ subject: file,
16793
+ content: content.slice(0, 280),
16794
+ source: "session_analysis",
16795
+ base_confidence: Math.min(0.85, 0.5 + revertRate * 0.4)
16796
+ };
16797
+ const factId = await factStore2.createFact(input);
16798
+ if (factId) {
16799
+ created++;
16800
+ details.push(`[negative:fragile] ${file} (${pct}% revert rate)`);
16801
+ }
16802
+ }
16803
+ return { created, reinforced, source: "session_analysis", details };
16804
+ }
16805
+ function loadRecentSessions(unerrDir, limit) {
16806
+ const sessionsDir = join44(unerrDir, "sessions");
16807
+ if (!existsSync37(sessionsDir)) return [];
16808
+ try {
16809
+ const files = readdirSync9(sessionsDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
16810
+ const summaries = [];
16811
+ for (const file of files) {
16812
+ try {
16813
+ const content = readFileSync34(join44(sessionsDir, file), "utf-8");
16814
+ const lines = content.trim().split("\n").filter(Boolean);
16815
+ const lastLine = lines[lines.length - 1];
16816
+ if (lastLine) {
16817
+ summaries.push(JSON.parse(lastLine));
16818
+ }
16819
+ } catch {
16820
+ }
16821
+ }
16822
+ return summaries;
16823
+ } catch {
16824
+ return [];
16825
+ }
16826
+ }
16827
+ function detectHotFiles(summaries) {
16828
+ const fileCounts = /* @__PURE__ */ new Map();
16829
+ const total = summaries.length;
16830
+ for (const session of summaries) {
16831
+ const seen = /* @__PURE__ */ new Set();
16832
+ for (const file of session.files_modified) {
16833
+ if (!seen.has(file)) {
16834
+ seen.add(file);
16835
+ fileCounts.set(file, (fileCounts.get(file) ?? 0) + 1);
16836
+ }
16837
+ }
16838
+ }
16839
+ const HOT_THRESHOLD = 0.5;
16840
+ return [...fileCounts.entries()].map(([file, count]) => [file, count / total]).filter(([_, freq]) => freq >= HOT_THRESHOLD).sort((a, b) => b[1] - a[1]).slice(0, 10);
16841
+ }
16842
+ function detectHighRevertFiles(summaries) {
16843
+ const fileModifiedCount = /* @__PURE__ */ new Map();
16844
+ const fileRevertCount = /* @__PURE__ */ new Map();
16845
+ for (const session of summaries) {
16846
+ for (const file of session.files_modified) {
16847
+ fileModifiedCount.set(file, (fileModifiedCount.get(file) ?? 0) + 1);
16848
+ if (session.revert_count > 0) {
16849
+ fileRevertCount.set(file, (fileRevertCount.get(file) ?? 0) + 1);
16850
+ }
16851
+ }
16852
+ }
16853
+ const HIGH_REVERT_THRESHOLD = 0.4;
16854
+ const MIN_MODIFICATIONS = 3;
16855
+ return [...fileModifiedCount.entries()].filter(([_, count]) => count >= MIN_MODIFICATIONS).map(([file, count]) => {
16856
+ const reverts = fileRevertCount.get(file) ?? 0;
16857
+ return [file, reverts / count];
16858
+ }).filter(([_, rate]) => rate >= HIGH_REVERT_THRESHOLD).sort((a, b) => b[1] - a[1]).slice(0, 5);
16859
+ }
16860
+ function formatConventionFact(conv) {
16861
+ const pct = Math.round(conv.confidence * 100);
16862
+ switch (conv.kind) {
16863
+ case "naming":
16864
+ return `Naming convention: ${conv.name} (${pct}% confidence, ${conv.frequency} entities)`;
16865
+ case "structure":
16866
+ return `Structure pattern: ${conv.name} (${pct}% confidence)`;
16867
+ case "import_direction":
16868
+ return `Import convention: ${conv.name} (${pct}% confidence)`;
16869
+ default:
16870
+ return `Convention: ${conv.name} (${pct}% confidence)`;
16871
+ }
16872
+ }
16873
+ function inferScopeFromConvention(conv) {
16874
+ if (conv.exemplarKeys.length > 0) {
16875
+ const first = conv.exemplarKeys[0];
16876
+ const parts = first.split("/");
16877
+ if (parts.length >= 2) {
16878
+ return parts.slice(0, 2).join("/");
16879
+ }
16880
+ }
16881
+ return "project";
16882
+ }
16883
+ var init_fact_generator = __esm({
16884
+ "src/intelligence/fact-generator.ts"() {
16885
+ "use strict";
16886
+ }
16887
+ });
16888
+
16467
16889
  // src/proxy/auto-bootstrap.ts
16468
16890
  var auto_bootstrap_exports = {};
16469
16891
  __export(auto_bootstrap_exports, {
@@ -16912,8 +17334,8 @@ var init_model_pricing = __esm({
16912
17334
  });
16913
17335
 
16914
17336
  // src/tracking/entity-rewind.ts
16915
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync20 } from "fs";
16916
- import { join as join44 } from "path";
17337
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync20 } from "fs";
17338
+ import { join as join45 } from "path";
16917
17339
  async function revertEntity(entityName, localGraph, projectRoot, filePath) {
16918
17340
  const driftEntity = await findDriftEntity(localGraph, entityName, filePath);
16919
17341
  if (!driftEntity) {
@@ -16925,7 +17347,7 @@ async function revertEntity(entityName, localGraph, projectRoot, filePath) {
16925
17347
  error: `No drifted entity found: "${entityName}"${filePath ? ` in ${filePath}` : ""}`
16926
17348
  };
16927
17349
  }
16928
- const absPath = join44(projectRoot, driftEntity.file_path);
17350
+ const absPath = join45(projectRoot, driftEntity.file_path);
16929
17351
  if (driftEntity.drift_status === "added") {
16930
17352
  return removeAddedEntity(absPath, driftEntity, localGraph);
16931
17353
  }
@@ -16990,7 +17412,7 @@ async function findDriftEntity(localGraph, entityName, filePath) {
16990
17412
  }
16991
17413
  async function removeAddedEntity(absPath, entity, localGraph) {
16992
17414
  try {
16993
- const content = readFileSync34(absPath, "utf-8");
17415
+ const content = readFileSync35(absPath, "utf-8");
16994
17416
  const lines = content.split("\n");
16995
17417
  const startIdx = entity.line_start - 1;
16996
17418
  const endIdx = entity.line_end;
@@ -17025,7 +17447,7 @@ async function restoreDeletedEntity(absPath, entity, localGraph) {
17025
17447
  };
17026
17448
  }
17027
17449
  try {
17028
- const content = readFileSync34(absPath, "utf-8");
17450
+ const content = readFileSync35(absPath, "utf-8");
17029
17451
  const lines = content.split("\n");
17030
17452
  const previousLines = entity.previous_body.split("\n");
17031
17453
  const insertIdx = Math.min(entity.line_start - 1, lines.length);
@@ -17061,7 +17483,7 @@ async function restoreModifiedEntity(absPath, entity, localGraph) {
17061
17483
  };
17062
17484
  }
17063
17485
  try {
17064
- const content = readFileSync34(absPath, "utf-8");
17486
+ const content = readFileSync35(absPath, "utf-8");
17065
17487
  const lines = content.split("\n");
17066
17488
  const previousLines = entity.previous_body.split("\n");
17067
17489
  const startIdx = entity.line_start - 1;
@@ -17473,7 +17895,7 @@ __export(file_outline_exports, {
17473
17895
  buildFileOutline: () => buildFileOutline,
17474
17896
  fileOutlineTool: () => fileOutlineTool
17475
17897
  });
17476
- import { existsSync as existsSync37, readFileSync as readFileSync35 } from "fs";
17898
+ import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
17477
17899
  import { relative as relative2, resolve } from "path";
17478
17900
  function normRisk(rl) {
17479
17901
  const x2 = (rl ?? "low").toLowerCase();
@@ -17532,10 +17954,10 @@ function detectExportedNames(lines, scanLimit) {
17532
17954
  async function buildFileOutline(params) {
17533
17955
  const abs = resolve(params.cwd, params.filePathArg);
17534
17956
  const rel = relative2(params.cwd, abs).replace(/\\/g, "/") || params.filePathArg;
17535
- if (!existsSync37(abs)) {
17957
+ if (!existsSync38(abs)) {
17536
17958
  throw new Error(`File not found: ${abs}`);
17537
17959
  }
17538
- const raw = readFileSync35(abs);
17960
+ const raw = readFileSync36(abs);
17539
17961
  const sampleEnd = Math.min(raw.length, 8192);
17540
17962
  for (let i = 0; i < sampleEnd; i++) {
17541
17963
  if (raw[i] === 0) {
@@ -17683,7 +18105,7 @@ __export(file_read_protocol_exports, {
17683
18105
  runFileReadForRouter: () => runFileReadForRouter,
17684
18106
  runFileReadTool: () => runFileReadTool
17685
18107
  });
17686
- import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
18108
+ import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
17687
18109
  import { relative as relative3, resolve as resolve2 } from "path";
17688
18110
  function isGeneratedPath(rel) {
17689
18111
  return /(?:^|\/)node_modules\/|(?:^|\/)dist\/|\/\.next\/|\.generated\./.test(
@@ -17758,10 +18180,10 @@ async function runFileReadForRouter(args, ctx) {
17758
18180
  const abs = resolve2(ctx.cwd, filePathArg);
17759
18181
  const rel = relative3(ctx.cwd, abs).replace(/\\/g, "/") || filePathArg;
17760
18182
  const isOutOfProject = rel.startsWith("..");
17761
- if (!existsSync38(abs)) {
18183
+ if (!existsSync39(abs)) {
17762
18184
  return { content: { error: `File not found: ${abs}` } };
17763
18185
  }
17764
- const raw = readFileSync36(abs);
18186
+ const raw = readFileSync37(abs);
17765
18187
  const sampleEnd = Math.min(raw.length, 8192);
17766
18188
  for (let i = 0; i < sampleEnd; i++) {
17767
18189
  if (raw[i] === 0) {
@@ -17788,9 +18210,16 @@ async function runFileReadForRouter(args, ctx) {
17788
18210
  content: {
17789
18211
  ...outline,
17790
18212
  gated: true,
17791
- _gate_reason: `File has ${totalLines} lines (> ${effectiveGate}). Structure shown \u2014 call file_read again with entity or offset/limit, or force_full:true.`
18213
+ _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.`
17792
18214
  },
17793
- _layer6_meta: { format: "outline", gated: true }
18215
+ _layer6_meta: {
18216
+ format: "outline",
18217
+ gated: true,
18218
+ total_lines: totalLines,
18219
+ total_chars: text2.length,
18220
+ tokens_estimate: Math.ceil(outline.token_estimate ?? 0),
18221
+ optimization: `file_read gated \u2192 outline (${totalLines} lines)`
18222
+ }
17794
18223
  };
17795
18224
  }
17796
18225
  if (entityName) {
@@ -17812,7 +18241,7 @@ async function runFileReadForRouter(args, ctx) {
17812
18241
  const match = ranked[0].entity;
17813
18242
  if (match.start_line >= 1 && match.start_line <= totalLines) {
17814
18243
  const start = Math.max(1, match.start_line - ENTITY_CONTEXT);
17815
- const endLine = match.start_line + match.body.split("\n").length - 1;
18244
+ const endLine = (match.end_line ?? 0) > match.start_line ? match.end_line : match.start_line + match.body.split("\n").length - 1;
17816
18245
  const end = Math.min(totalLines, endLine + ENTITY_CONTEXT);
17817
18246
  offset = start;
17818
18247
  limit = Math.min(MAX_READ_LINES, end - start + 1);
@@ -17901,9 +18330,16 @@ async function runFileReadForRouter(args, ctx) {
17901
18330
  query: entityName,
17902
18331
  suggestions: []
17903
18332
  },
17904
- _gate_reason: `Entity "${entityName}" not found with high confidence. Use one of the suggestions or specify offset/limit.`
18333
+ _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.`
17905
18334
  },
17906
- _layer6_meta: { format: "outline", gated: true }
18335
+ _layer6_meta: {
18336
+ format: "outline",
18337
+ gated: true,
18338
+ total_lines: totalLines,
18339
+ total_chars: text2.length,
18340
+ tokens_estimate: Math.ceil(outline.token_estimate ?? 0),
18341
+ optimization: `file_read gated \u2192 outline (${totalLines} lines)`
18342
+ }
17907
18343
  };
17908
18344
  }
17909
18345
  }
@@ -17939,10 +18375,12 @@ ${body}`;
17939
18375
  const meta = {
17940
18376
  format: "json",
17941
18377
  tokens_estimate: Math.ceil(sliced.join("\n").length / CHARS_PER_TOKEN4),
17942
- total_lines: totalLines
18378
+ total_lines: totalLines,
18379
+ total_chars: text2.length
17943
18380
  };
17944
18381
  if (isFullRead && sliced.length === totalLines) {
17945
18382
  meta.optimization = `file_read full (purpose:${purpose})`;
18383
+ 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.";
17946
18384
  } else if (effOffset > 1 || sliced.length < totalLines || entityWindowApplied) {
17947
18385
  meta.optimization = `file_read window lines ${effOffset}-${effOffset + sliced.length - 1}`;
17948
18386
  }
@@ -18125,9 +18563,13 @@ var init_query_router = __esm({
18125
18563
  LOCAL_TOOLS = /* @__PURE__ */ new Set([
18126
18564
  "get_function",
18127
18565
  "get_class",
18566
+ "get_entity",
18567
+ // consolidated: replaces get_function + get_class
18128
18568
  "get_file",
18129
18569
  "get_callers",
18130
18570
  "get_callees",
18571
+ "get_references",
18572
+ // consolidated: replaces get_callers + get_callees
18131
18573
  "get_imports",
18132
18574
  "search_code",
18133
18575
  "get_rules",
@@ -18553,7 +18995,7 @@ var init_query_router = __esm({
18553
18995
  Object.assign(meta, fr._layer6_meta);
18554
18996
  if (this.tokenFlow && fr._layer6_meta.optimization && fr._layer6_meta.total_lines) {
18555
18997
  const deliveredTokens = fr._layer6_meta.tokens_estimate ?? 0;
18556
- const fullFileTokens = Math.ceil(fr._layer6_meta.total_lines * 80 / 4);
18998
+ const fullFileTokens = fr._layer6_meta.total_chars ? Math.ceil(fr._layer6_meta.total_chars / 4) : Math.ceil(fr._layer6_meta.total_lines * 80 / 4);
18557
18999
  const fileReadSaved = fullFileTokens - deliveredTokens;
18558
19000
  if (fileReadSaved > 50) {
18559
19001
  this.tokenFlow.record({
@@ -19501,7 +19943,21 @@ var init_query_router = __esm({
19501
19943
  case "get_file": {
19502
19944
  const raw = args.key ?? args.name;
19503
19945
  const key = await this.resolveKeyArg(raw);
19504
- return this.resolveEntityWithOverlay(key);
19946
+ const entity = await this.resolveEntityWithOverlay(key);
19947
+ if (entity && entity.file_path && entity.start_line > 0) {
19948
+ try {
19949
+ const { readFileSync: readFileSync57 } = await import("fs");
19950
+ const { resolve: resolve3 } = await import("path");
19951
+ const cwd = this.projectRoot ?? process.cwd();
19952
+ const abs = resolve3(cwd, entity.file_path);
19953
+ const lines = readFileSync57(abs, "utf-8").split("\n");
19954
+ const start = entity.start_line - 1;
19955
+ const end = entity.end_line ?? lines.length;
19956
+ entity.body = lines.slice(start, end).join("\n");
19957
+ } catch {
19958
+ }
19959
+ }
19960
+ return entity;
19505
19961
  }
19506
19962
  case "get_references": {
19507
19963
  const key = await this.resolveKeyArg(args.key);
@@ -19874,11 +20330,14 @@ var init_query_router = __esm({
19874
20330
  ENTITY_ARRAY_TOOLS = /* @__PURE__ */ new Set([
19875
20331
  "get_callers",
19876
20332
  "get_callees",
20333
+ "get_references",
19877
20334
  "search_code"
19878
20335
  ]);
19879
20336
  SINGLE_ENTITY_TOOLS = /* @__PURE__ */ new Set([
20337
+ "get_entity",
19880
20338
  "get_function",
19881
- "get_class"
20339
+ "get_class",
20340
+ "get_file"
19882
20341
  ]);
19883
20342
  }
19884
20343
  });
@@ -21017,8 +21476,8 @@ __export(causal_bridge_exports, {
21017
21476
  assembleCausalChain: () => assembleCausalChain,
21018
21477
  computeDurability: () => computeDurability
21019
21478
  });
21020
- import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
21021
- import { join as join45 } from "path";
21479
+ import { existsSync as existsSync40, readFileSync as readFileSync38 } from "fs";
21480
+ import { join as join46 } from "path";
21022
21481
  function computeAggregateDurability(interactions) {
21023
21482
  if (interactions.length === 0) return 1;
21024
21483
  const survivedCount = interactions.filter((i) => i.survived).length;
@@ -21175,9 +21634,9 @@ var init_causal_bridge = __esm({
21175
21634
  return chains;
21176
21635
  }
21177
21636
  loadEntityLedgerEntries(entityKey2) {
21178
- const ledgerPath = join45(this.unerrDir, "ledger", "shadow.jsonl");
21179
- if (!existsSync39(ledgerPath)) return [];
21180
- const content = readFileSync37(ledgerPath, "utf-8");
21637
+ const ledgerPath = join46(this.unerrDir, "ledger", "shadow.jsonl");
21638
+ if (!existsSync40(ledgerPath)) return [];
21639
+ const content = readFileSync38(ledgerPath, "utf-8");
21181
21640
  const lines = content.split("\n").filter((l) => l.trim().length > 0);
21182
21641
  const entries = [];
21183
21642
  for (const line of lines) {
@@ -21663,15 +22122,15 @@ var context_ledger_exports = {};
21663
22122
  __export(context_ledger_exports, {
21664
22123
  createContextLedger: () => createContextLedger
21665
22124
  });
21666
- import { existsSync as existsSync40, mkdirSync as mkdirSync23, readFileSync as readFileSync38, writeFileSync as writeFileSync21 } from "fs";
21667
- import { join as join46 } from "path";
22125
+ import { existsSync as existsSync41, mkdirSync as mkdirSync23, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "fs";
22126
+ import { join as join47 } from "path";
21668
22127
  function createContextLedger(unerrDir) {
21669
- const stateDir = join46(unerrDir, "state");
21670
- const filePath = join46(stateDir, "context-ledger.json");
22128
+ const stateDir = join47(unerrDir, "state");
22129
+ const filePath = join47(stateDir, "context-ledger.json");
21671
22130
  let records = [];
21672
22131
  let index = /* @__PURE__ */ new Map();
21673
22132
  function ensureDir() {
21674
- if (!existsSync40(stateDir)) {
22133
+ if (!existsSync41(stateDir)) {
21675
22134
  mkdirSync23(stateDir, { recursive: true });
21676
22135
  }
21677
22136
  }
@@ -21680,13 +22139,13 @@ function createContextLedger(unerrDir) {
21680
22139
  }
21681
22140
  function load() {
21682
22141
  ensureDir();
21683
- if (!existsSync40(filePath)) {
22142
+ if (!existsSync41(filePath)) {
21684
22143
  records = [];
21685
22144
  index = /* @__PURE__ */ new Map();
21686
22145
  return /* @__PURE__ */ new Map();
21687
22146
  }
21688
22147
  try {
21689
- const raw = readFileSync38(filePath, "utf-8");
22148
+ const raw = readFileSync39(filePath, "utf-8");
21690
22149
  const parsed = JSON.parse(raw);
21691
22150
  records = Array.isArray(parsed) ? parsed : [];
21692
22151
  } catch {
@@ -22593,13 +23052,13 @@ __export(intent_correlator_exports, {
22593
23052
  IntentCorrelator: () => IntentCorrelator
22594
23053
  });
22595
23054
  import {
22596
- existsSync as existsSync41,
23055
+ existsSync as existsSync42,
22597
23056
  mkdirSync as mkdirSync24,
22598
- readFileSync as readFileSync39,
23057
+ readFileSync as readFileSync40,
22599
23058
  renameSync,
22600
23059
  writeFileSync as writeFileSync22
22601
23060
  } from "fs";
22602
- import { join as join47 } from "path";
23061
+ import { join as join48 } from "path";
22603
23062
  function extractFiles4(args) {
22604
23063
  const files = args.files;
22605
23064
  if (files && Array.isArray(files)) {
@@ -22647,9 +23106,9 @@ var init_intent_correlator = __esm({
22647
23106
  pendingPath;
22648
23107
  pending = [];
22649
23108
  constructor(unerrDir) {
22650
- this.ledgerDir = join47(unerrDir, "ledger");
22651
- this.pendingPath = join47(this.ledgerDir, "pending_correlations.json");
22652
- if (!existsSync41(this.ledgerDir)) {
23109
+ this.ledgerDir = join48(unerrDir, "ledger");
23110
+ this.pendingPath = join48(this.ledgerDir, "pending_correlations.json");
23111
+ if (!existsSync42(this.ledgerDir)) {
22653
23112
  mkdirSync24(this.ledgerDir, { recursive: true });
22654
23113
  }
22655
23114
  this.load();
@@ -22741,9 +23200,9 @@ var init_intent_correlator = __esm({
22741
23200
  }
22742
23201
  // ── Persistence ─────────────────────────────────────────────────
22743
23202
  load() {
22744
- if (!existsSync41(this.pendingPath)) return;
23203
+ if (!existsSync42(this.pendingPath)) return;
22745
23204
  try {
22746
- const raw = readFileSync39(this.pendingPath, "utf-8");
23205
+ const raw = readFileSync40(this.pendingPath, "utf-8");
22747
23206
  const parsed = JSON.parse(raw);
22748
23207
  if (Array.isArray(parsed)) {
22749
23208
  this.pending = parsed;
@@ -22775,14 +23234,14 @@ __export(working_snapshots_exports, {
22775
23234
  });
22776
23235
  import { randomBytes as randomBytes3 } from "crypto";
22777
23236
  import {
22778
- existsSync as existsSync42,
23237
+ existsSync as existsSync43,
22779
23238
  mkdirSync as mkdirSync25,
22780
- readFileSync as readFileSync40,
22781
- readdirSync as readdirSync9,
23239
+ readFileSync as readFileSync41,
23240
+ readdirSync as readdirSync10,
22782
23241
  rmSync as rmSync2,
22783
23242
  writeFileSync as writeFileSync23
22784
23243
  } from "fs";
22785
- import { join as join48 } from "path";
23244
+ import { join as join49 } from "path";
22786
23245
  var _log2, MAX_SNAPSHOTS, AUTO_SNAPSHOT_COOLDOWN_MS, WorkingSnapshotStore;
22787
23246
  var init_working_snapshots = __esm({
22788
23247
  "src/tracking/working-snapshots.ts"() {
@@ -22800,8 +23259,8 @@ var init_working_snapshots = __esm({
22800
23259
  unerrDir;
22801
23260
  constructor(unerrDir) {
22802
23261
  this.unerrDir = unerrDir;
22803
- this.snapshotDir = join48(unerrDir, "snapshots");
22804
- if (!existsSync42(this.snapshotDir)) {
23262
+ this.snapshotDir = join49(unerrDir, "snapshots");
23263
+ if (!existsSync43(this.snapshotDir)) {
22805
23264
  mkdirSync25(this.snapshotDir, { recursive: true });
22806
23265
  }
22807
23266
  }
@@ -22821,7 +23280,7 @@ var init_working_snapshots = __esm({
22821
23280
  processed: false
22822
23281
  };
22823
23282
  writeFileSync23(
22824
- join48(this.snapshotDir, `${id}.json`),
23283
+ join49(this.snapshotDir, `${id}.json`),
22825
23284
  JSON.stringify(snapshot, null, 2),
22826
23285
  "utf-8"
22827
23286
  );
@@ -22835,10 +23294,10 @@ var init_working_snapshots = __esm({
22835
23294
  * Get a snapshot by ID.
22836
23295
  */
22837
23296
  get(snapshotId) {
22838
- const filePath = join48(this.snapshotDir, `${snapshotId}.json`);
22839
- if (!existsSync42(filePath)) return null;
23297
+ const filePath = join49(this.snapshotDir, `${snapshotId}.json`);
23298
+ if (!existsSync43(filePath)) return null;
22840
23299
  try {
22841
- return JSON.parse(readFileSync40(filePath, "utf-8"));
23300
+ return JSON.parse(readFileSync41(filePath, "utf-8"));
22842
23301
  } catch {
22843
23302
  return null;
22844
23303
  }
@@ -22847,14 +23306,14 @@ var init_working_snapshots = __esm({
22847
23306
  * List all snapshots, most recent first.
22848
23307
  */
22849
23308
  list() {
22850
- if (!existsSync42(this.snapshotDir)) return [];
22851
- const files = readdirSync9(this.snapshotDir).filter(
23309
+ if (!existsSync43(this.snapshotDir)) return [];
23310
+ const files = readdirSync10(this.snapshotDir).filter(
22852
23311
  (f) => f.endsWith(".json")
22853
23312
  );
22854
23313
  const snapshots = [];
22855
23314
  for (const file of files) {
22856
23315
  try {
22857
- const raw = readFileSync40(join48(this.snapshotDir, file), "utf-8");
23316
+ const raw = readFileSync41(join49(this.snapshotDir, file), "utf-8");
22858
23317
  snapshots.push(JSON.parse(raw));
22859
23318
  } catch {
22860
23319
  }
@@ -22895,7 +23354,7 @@ var init_working_snapshots = __esm({
22895
23354
  if (!snapshot) return;
22896
23355
  snapshot.processed = true;
22897
23356
  writeFileSync23(
22898
- join48(this.snapshotDir, `${snapshotId}.json`),
23357
+ join49(this.snapshotDir, `${snapshotId}.json`),
22899
23358
  JSON.stringify(snapshot, null, 2),
22900
23359
  "utf-8"
22901
23360
  );
@@ -22910,8 +23369,8 @@ var init_working_snapshots = __esm({
22910
23369
  * Delete a snapshot.
22911
23370
  */
22912
23371
  delete(snapshotId) {
22913
- const filePath = join48(this.snapshotDir, `${snapshotId}.json`);
22914
- if (!existsSync42(filePath)) return false;
23372
+ const filePath = join49(this.snapshotDir, `${snapshotId}.json`);
23373
+ if (!existsSync43(filePath)) return false;
22915
23374
  rmSync2(filePath);
22916
23375
  return true;
22917
23376
  }
@@ -22919,10 +23378,10 @@ var init_working_snapshots = __esm({
22919
23378
  * Get the timeline branch counter from branch_context.json.
22920
23379
  */
22921
23380
  getTimelineBranch() {
22922
- const contextPath = join48(this.unerrDir, "branch_context.json");
22923
- if (!existsSync42(contextPath)) return 0;
23381
+ const contextPath = join49(this.unerrDir, "branch_context.json");
23382
+ if (!existsSync43(contextPath)) return 0;
22924
23383
  try {
22925
- const ctx = JSON.parse(readFileSync40(contextPath, "utf-8"));
23384
+ const ctx = JSON.parse(readFileSync41(contextPath, "utf-8"));
22926
23385
  return ctx.timelineBranch ?? 0;
22927
23386
  } catch {
22928
23387
  return 0;
@@ -22932,11 +23391,11 @@ var init_working_snapshots = __esm({
22932
23391
  * Increment the timeline branch counter. Returns the new value.
22933
23392
  */
22934
23393
  incrementTimelineBranch() {
22935
- const contextPath = join48(this.unerrDir, "branch_context.json");
23394
+ const contextPath = join49(this.unerrDir, "branch_context.json");
22936
23395
  let ctx = {};
22937
- if (existsSync42(contextPath)) {
23396
+ if (existsSync43(contextPath)) {
22938
23397
  try {
22939
- ctx = JSON.parse(readFileSync40(contextPath, "utf-8"));
23398
+ ctx = JSON.parse(readFileSync41(contextPath, "utf-8"));
22940
23399
  } catch {
22941
23400
  ctx = {};
22942
23401
  }
@@ -23109,8 +23568,8 @@ var quality_signals_exports = {};
23109
23568
  __export(quality_signals_exports, {
23110
23569
  QualitySignalTracker: () => QualitySignalTracker
23111
23570
  });
23112
- import { existsSync as existsSync43, readFileSync as readFileSync41, writeFileSync as writeFileSync24 } from "fs";
23113
- import { join as join49 } from "path";
23571
+ import { existsSync as existsSync44, readFileSync as readFileSync42, writeFileSync as writeFileSync24 } from "fs";
23572
+ import { join as join50 } from "path";
23114
23573
  function computeDurabilityFromAge(survivalMs) {
23115
23574
  if (survivalMs < FRAGILE_THRESHOLD_MS) {
23116
23575
  return 0.1 + survivalMs / FRAGILE_THRESHOLD_MS * 0.2;
@@ -23137,7 +23596,7 @@ var init_quality_signals = __esm({
23137
23596
  /** Maximum corrections to retain in memory/disk. */
23138
23597
  static MAX_CORRECTIONS = 200;
23139
23598
  constructor(unerrDir) {
23140
- this.signalsPath = join49(unerrDir, "state", "quality_signals.json");
23599
+ this.signalsPath = join50(unerrDir, "state", "quality_signals.json");
23141
23600
  this.signals = this.load();
23142
23601
  }
23143
23602
  /**
@@ -23229,8 +23688,8 @@ var init_quality_signals = __esm({
23229
23688
  */
23230
23689
  save() {
23231
23690
  try {
23232
- const dir = join49(this.signalsPath, "..");
23233
- if (!existsSync43(dir)) {
23691
+ const dir = join50(this.signalsPath, "..");
23692
+ if (!existsSync44(dir)) {
23234
23693
  const { mkdirSync: mkdirSync39 } = __require("fs");
23235
23694
  mkdirSync39(dir, { recursive: true });
23236
23695
  }
@@ -23276,7 +23735,7 @@ var init_quality_signals = __esm({
23276
23735
  * Load signals from disk.
23277
23736
  */
23278
23737
  load() {
23279
- if (!existsSync43(this.signalsPath)) {
23738
+ if (!existsSync44(this.signalsPath)) {
23280
23739
  return {
23281
23740
  durabilityScores: {},
23282
23741
  corrections: [],
@@ -23285,7 +23744,7 @@ var init_quality_signals = __esm({
23285
23744
  }
23286
23745
  try {
23287
23746
  return JSON.parse(
23288
- readFileSync41(this.signalsPath, "utf-8")
23747
+ readFileSync42(this.signalsPath, "utf-8")
23289
23748
  );
23290
23749
  } catch {
23291
23750
  return {
@@ -24302,8 +24761,8 @@ var incomplete_work_exports = {};
24302
24761
  __export(incomplete_work_exports, {
24303
24762
  IncompleteWorkDetector: () => IncompleteWorkDetector
24304
24763
  });
24305
- import { existsSync as existsSync44, mkdirSync as mkdirSync26, readFileSync as readFileSync42, writeFileSync as writeFileSync25 } from "fs";
24306
- import { join as join50 } from "path";
24764
+ import { existsSync as existsSync45, mkdirSync as mkdirSync26, readFileSync as readFileSync43, writeFileSync as writeFileSync25 } from "fs";
24765
+ import { join as join51 } from "path";
24307
24766
  function severityRank(severity) {
24308
24767
  switch (severity) {
24309
24768
  case "high":
@@ -24496,9 +24955,9 @@ var init_incomplete_work = __esm({
24496
24955
  persistItems(items) {
24497
24956
  if (!this.unerrDir) return false;
24498
24957
  try {
24499
- const stateDir = join50(this.unerrDir, "state");
24500
- if (!existsSync44(stateDir)) mkdirSync26(stateDir, { recursive: true });
24501
- const filePath = join50(stateDir, PERSISTENCE_FILE);
24958
+ const stateDir = join51(this.unerrDir, "state");
24959
+ if (!existsSync45(stateDir)) mkdirSync26(stateDir, { recursive: true });
24960
+ const filePath = join51(stateDir, PERSISTENCE_FILE);
24502
24961
  writeFileSync25(
24503
24962
  filePath,
24504
24963
  JSON.stringify({
@@ -24518,9 +24977,9 @@ var init_incomplete_work = __esm({
24518
24977
  */
24519
24978
  static readPersistedItems(unerrDir) {
24520
24979
  try {
24521
- const filePath = join50(unerrDir, "state", PERSISTENCE_FILE);
24522
- if (!existsSync44(filePath)) return [];
24523
- const data = JSON.parse(readFileSync42(filePath, "utf-8"));
24980
+ const filePath = join51(unerrDir, "state", PERSISTENCE_FILE);
24981
+ if (!existsSync45(filePath)) return [];
24982
+ const data = JSON.parse(readFileSync43(filePath, "utf-8"));
24524
24983
  return data.items ?? [];
24525
24984
  } catch {
24526
24985
  return [];
@@ -25991,7 +26450,7 @@ var transport_mux_exports = {};
25991
26450
  __export(transport_mux_exports, {
25992
26451
  TransportMux: () => TransportMux
25993
26452
  });
25994
- import { existsSync as existsSync45, unlinkSync as unlinkSync7 } from "fs";
26453
+ import { existsSync as existsSync46, unlinkSync as unlinkSync7 } from "fs";
25995
26454
  import { createServer as createServer2 } from "net";
25996
26455
  var _log4, TransportMux;
25997
26456
  var init_transport_mux = __esm({
@@ -26031,7 +26490,7 @@ var init_transport_mux = __esm({
26031
26490
  * Start listening for secondary clients on the Unix domain socket.
26032
26491
  */
26033
26492
  start() {
26034
- if (existsSync45(this.sockPath)) {
26493
+ if (existsSync46(this.sockPath)) {
26035
26494
  try {
26036
26495
  unlinkSync7(this.sockPath);
26037
26496
  } catch {
@@ -26064,7 +26523,7 @@ var init_transport_mux = __esm({
26064
26523
  this.server.close();
26065
26524
  this.server = null;
26066
26525
  }
26067
- if (existsSync45(this.sockPath)) {
26526
+ if (existsSync46(this.sockPath)) {
26068
26527
  try {
26069
26528
  unlinkSync7(this.sockPath);
26070
26529
  } catch {
@@ -26212,8 +26671,8 @@ __export(git_trailers_exports, {
26212
26671
  parseTrailersFromMessage: () => parseTrailersFromMessage,
26213
26672
  uninstallPrepareCommitMsgHook: () => uninstallPrepareCommitMsgHook
26214
26673
  });
26215
- import { existsSync as existsSync46, readFileSync as readFileSync43, writeFileSync as writeFileSync26 } from "fs";
26216
- import { join as join51 } from "path";
26674
+ import { existsSync as existsSync47, readFileSync as readFileSync44, writeFileSync as writeFileSync26 } from "fs";
26675
+ import { join as join52 } from "path";
26217
26676
  function getCommitTrailers(ledger, timelineBranch, branch) {
26218
26677
  const recent = ledger.getRecentEntries(1);
26219
26678
  if (recent.length === 0) return null;
@@ -26233,15 +26692,15 @@ function formatTrailers(trailers) {
26233
26692
  ].join("\n");
26234
26693
  }
26235
26694
  function installPrepareCommitMsgHook(projectRoot) {
26236
- const hooksDir = join51(projectRoot, ".git", "hooks");
26237
- if (!existsSync46(hooksDir)) {
26695
+ const hooksDir = join52(projectRoot, ".git", "hooks");
26696
+ if (!existsSync47(hooksDir)) {
26238
26697
  return false;
26239
26698
  }
26240
- const hookPath = join51(hooksDir, "prepare-commit-msg");
26699
+ const hookPath = join52(hooksDir, "prepare-commit-msg");
26241
26700
  const marker = "# unerr-trailer-injection";
26242
- if (existsSync46(hookPath)) {
26701
+ if (existsSync47(hookPath)) {
26243
26702
  try {
26244
- const existing = readFileSync43(hookPath, "utf-8");
26703
+ const existing = readFileSync44(hookPath, "utf-8");
26245
26704
  if (existing.includes(marker)) {
26246
26705
  return true;
26247
26706
  }
@@ -26270,10 +26729,10 @@ ${generateHookScript()}`;
26270
26729
  }
26271
26730
  }
26272
26731
  function uninstallPrepareCommitMsgHook(projectRoot) {
26273
- const hookPath = join51(projectRoot, ".git", "hooks", "prepare-commit-msg");
26274
- if (!existsSync46(hookPath)) return true;
26732
+ const hookPath = join52(projectRoot, ".git", "hooks", "prepare-commit-msg");
26733
+ if (!existsSync47(hookPath)) return true;
26275
26734
  try {
26276
- const content = readFileSync43(hookPath, "utf-8");
26735
+ const content = readFileSync44(hookPath, "utf-8");
26277
26736
  const marker = "# unerr-trailer-injection";
26278
26737
  if (!content.includes(marker)) return true;
26279
26738
  const lines = content.split("\n");
@@ -26438,15 +26897,15 @@ var init_http_transport = __esm({
26438
26897
 
26439
26898
  // src/tracking/branch-snapshot.ts
26440
26899
  import {
26441
- existsSync as existsSync47,
26900
+ existsSync as existsSync48,
26442
26901
  mkdirSync as mkdirSync28,
26443
- readFileSync as readFileSync44,
26444
- readdirSync as readdirSync10,
26902
+ readFileSync as readFileSync45,
26903
+ readdirSync as readdirSync11,
26445
26904
  rmSync as rmSync3,
26446
26905
  statSync as statSync7,
26447
26906
  writeFileSync as writeFileSync27
26448
26907
  } from "fs";
26449
- import { join as join52 } from "path";
26908
+ import { join as join53 } from "path";
26450
26909
  function sanitizeBranchName(branch) {
26451
26910
  return branch.replace(/\//g, "__").replace(/[^a-zA-Z0-9_.\-]/g, "_");
26452
26911
  }
@@ -26464,7 +26923,7 @@ var init_branch_snapshot = __esm({
26464
26923
  branchDir;
26465
26924
  projectRoot;
26466
26925
  constructor(unerrDir, projectRoot) {
26467
- this.branchDir = join52(unerrDir, "drift", "branches");
26926
+ this.branchDir = join53(unerrDir, "drift", "branches");
26468
26927
  this.projectRoot = projectRoot;
26469
26928
  }
26470
26929
  /**
@@ -26478,8 +26937,8 @@ var init_branch_snapshot = __esm({
26478
26937
  log16.info(`No drift entities/edges to snapshot for branch ${branch}`);
26479
26938
  }
26480
26939
  const dirName = sanitizeBranchName(branch);
26481
- const snapshotDir = join52(this.branchDir, dirName);
26482
- if (!existsSync47(snapshotDir)) {
26940
+ const snapshotDir = join53(this.branchDir, dirName);
26941
+ if (!existsSync48(snapshotDir)) {
26483
26942
  mkdirSync28(snapshotDir, { recursive: true });
26484
26943
  }
26485
26944
  const snapshot = {
@@ -26490,12 +26949,12 @@ var init_branch_snapshot = __esm({
26490
26949
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
26491
26950
  };
26492
26951
  writeFileSync27(
26493
- join52(snapshotDir, OVERLAY_FILE),
26952
+ join53(snapshotDir, OVERLAY_FILE),
26494
26953
  JSON.stringify(snapshot, null, 2),
26495
26954
  "utf-8"
26496
26955
  );
26497
26956
  writeFileSync27(
26498
- join52(snapshotDir, HASHES_FILE),
26957
+ join53(snapshotDir, HASHES_FILE),
26499
26958
  JSON.stringify(fileHashState, null, 2),
26500
26959
  "utf-8"
26501
26960
  );
@@ -26511,14 +26970,14 @@ var init_branch_snapshot = __esm({
26511
26970
  */
26512
26971
  async restoreSnapshot(branch, localGraph) {
26513
26972
  const dirName = sanitizeBranchName(branch);
26514
- const snapshotDir = join52(this.branchDir, dirName);
26515
- const overlayPath = join52(snapshotDir, OVERLAY_FILE);
26516
- if (!existsSync47(overlayPath)) {
26973
+ const snapshotDir = join53(this.branchDir, dirName);
26974
+ const overlayPath = join53(snapshotDir, OVERLAY_FILE);
26975
+ if (!existsSync48(overlayPath)) {
26517
26976
  log16.info(`No snapshot for branch ${branch} \u2014 first visit`);
26518
26977
  return null;
26519
26978
  }
26520
26979
  try {
26521
- const raw = readFileSync44(overlayPath, "utf-8");
26980
+ const raw = readFileSync45(overlayPath, "utf-8");
26522
26981
  const snapshot = JSON.parse(raw);
26523
26982
  for (const entity of snapshot.entities) {
26524
26983
  await localGraph.upsertDriftEntity(entity);
@@ -26530,7 +26989,7 @@ var init_branch_snapshot = __esm({
26530
26989
  }
26531
26990
  const now = /* @__PURE__ */ new Date();
26532
26991
  writeFileSync27(
26533
- join52(snapshotDir, ".last_access"),
26992
+ join53(snapshotDir, ".last_access"),
26534
26993
  now.toISOString(),
26535
26994
  "utf-8"
26536
26995
  );
@@ -26550,17 +27009,17 @@ var init_branch_snapshot = __esm({
26550
27009
  */
26551
27010
  hasSnapshot(branch) {
26552
27011
  const dirName = sanitizeBranchName(branch);
26553
- return existsSync47(join52(this.branchDir, dirName, OVERLAY_FILE));
27012
+ return existsSync48(join53(this.branchDir, dirName, OVERLAY_FILE));
26554
27013
  }
26555
27014
  /**
26556
27015
  * Get the file hash state from a branch snapshot.
26557
27016
  */
26558
27017
  getSnapshotFileHashes(branch) {
26559
27018
  const dirName = sanitizeBranchName(branch);
26560
- const hashesPath = join52(this.branchDir, dirName, HASHES_FILE);
26561
- if (!existsSync47(hashesPath)) return null;
27019
+ const hashesPath = join53(this.branchDir, dirName, HASHES_FILE);
27020
+ if (!existsSync48(hashesPath)) return null;
26562
27021
  try {
26563
- const raw = readFileSync44(hashesPath, "utf-8");
27022
+ const raw = readFileSync45(hashesPath, "utf-8");
26564
27023
  return JSON.parse(raw);
26565
27024
  } catch {
26566
27025
  return null;
@@ -26571,8 +27030,8 @@ var init_branch_snapshot = __esm({
26571
27030
  */
26572
27031
  deleteSnapshot(branch) {
26573
27032
  const dirName = sanitizeBranchName(branch);
26574
- const snapshotDir = join52(this.branchDir, dirName);
26575
- if (!existsSync47(snapshotDir)) return false;
27033
+ const snapshotDir = join53(this.branchDir, dirName);
27034
+ if (!existsSync48(snapshotDir)) return false;
26576
27035
  rmSync3(snapshotDir, { recursive: true, force: true });
26577
27036
  log16.info(`Deleted branch snapshot: ${branch}`);
26578
27037
  return true;
@@ -26581,14 +27040,14 @@ var init_branch_snapshot = __esm({
26581
27040
  * Garbage-collect snapshots for branches that no longer exist in git.
26582
27041
  */
26583
27042
  async garbageCollect() {
26584
- if (!existsSync47(this.branchDir)) return 0;
27043
+ if (!existsSync48(this.branchDir)) return 0;
26585
27044
  const gitBranches = new Set(await listBranches(this.projectRoot));
26586
27045
  if (gitBranches.size === 0) return 0;
26587
27046
  const snapshots = this.listSnapshots();
26588
27047
  let removed = 0;
26589
27048
  for (const snapshot of snapshots) {
26590
27049
  if (!gitBranches.has(snapshot.branch)) {
26591
- const snapshotDir = join52(this.branchDir, snapshot.id);
27050
+ const snapshotDir = join53(this.branchDir, snapshot.id);
26592
27051
  rmSync3(snapshotDir, { recursive: true, force: true });
26593
27052
  log16.info(`GC removed snapshot for deleted branch: ${snapshot.branch}`);
26594
27053
  removed++;
@@ -26600,20 +27059,20 @@ var init_branch_snapshot = __esm({
26600
27059
  * List all branch snapshots, sorted by most recently accessed first.
26601
27060
  */
26602
27061
  listSnapshots() {
26603
- if (!existsSync47(this.branchDir)) return [];
27062
+ if (!existsSync48(this.branchDir)) return [];
26604
27063
  try {
26605
- const entries = readdirSync10(this.branchDir, { withFileTypes: true });
27064
+ const entries = readdirSync11(this.branchDir, { withFileTypes: true });
26606
27065
  const snapshots = [];
26607
27066
  for (const entry of entries) {
26608
27067
  if (!entry.isDirectory()) continue;
26609
- const overlayPath = join52(this.branchDir, entry.name, OVERLAY_FILE);
26610
- if (!existsSync47(overlayPath)) continue;
27068
+ const overlayPath = join53(this.branchDir, entry.name, OVERLAY_FILE);
27069
+ if (!existsSync48(overlayPath)) continue;
26611
27070
  try {
26612
- const raw = readFileSync44(overlayPath, "utf-8");
27071
+ const raw = readFileSync45(overlayPath, "utf-8");
26613
27072
  const snapshot = JSON.parse(raw);
26614
- const accessPath = join52(this.branchDir, entry.name, ".last_access");
27073
+ const accessPath = join53(this.branchDir, entry.name, ".last_access");
26615
27074
  let accessedAt;
26616
- if (existsSync47(accessPath)) {
27075
+ if (existsSync48(accessPath)) {
26617
27076
  accessedAt = statSync7(accessPath).mtime;
26618
27077
  } else {
26619
27078
  accessedAt = statSync7(overlayPath).mtime;
@@ -26640,7 +27099,7 @@ var init_branch_snapshot = __esm({
26640
27099
  if (snapshots.length <= MAX_BRANCH_SNAPSHOTS) return;
26641
27100
  const toRemove = snapshots.slice(MAX_BRANCH_SNAPSHOTS);
26642
27101
  for (const snapshot of toRemove) {
26643
- const dir = join52(this.branchDir, snapshot.id);
27102
+ const dir = join53(this.branchDir, snapshot.id);
26644
27103
  rmSync3(dir, { recursive: true, force: true });
26645
27104
  log16.info(`LRU evicted branch snapshot: ${snapshot.branch}`);
26646
27105
  }
@@ -26657,13 +27116,13 @@ __export(file_hash_state_exports, {
26657
27116
  });
26658
27117
  import { createHash as createHash3 } from "crypto";
26659
27118
  import {
26660
- existsSync as existsSync48,
27119
+ existsSync as existsSync49,
26661
27120
  mkdirSync as mkdirSync29,
26662
- readFileSync as readFileSync45,
27121
+ readFileSync as readFileSync46,
26663
27122
  renameSync as renameSync2,
26664
27123
  writeFileSync as writeFileSync28
26665
27124
  } from "fs";
26666
- import { join as join53 } from "path";
27125
+ import { join as join54 } from "path";
26667
27126
  function contentSha256(content) {
26668
27127
  return createHash3("sha256").update(content).digest("hex");
26669
27128
  }
@@ -26677,8 +27136,8 @@ var init_file_hash_state = __esm({
26677
27136
  statePath;
26678
27137
  state;
26679
27138
  constructor(unerrDir) {
26680
- this.stateDir = join53(unerrDir, "state");
26681
- this.statePath = join53(this.stateDir, STATE_FILE);
27139
+ this.stateDir = join54(unerrDir, "state");
27140
+ this.statePath = join54(this.stateDir, STATE_FILE);
26682
27141
  this.state = this.load();
26683
27142
  }
26684
27143
  /**
@@ -26723,7 +27182,7 @@ var init_file_hash_state = __esm({
26723
27182
  * Persist state to disk atomically (write .tmp → rename).
26724
27183
  */
26725
27184
  save() {
26726
- if (!existsSync48(this.stateDir)) {
27185
+ if (!existsSync49(this.stateDir)) {
26727
27186
  mkdirSync29(this.stateDir, { recursive: true });
26728
27187
  }
26729
27188
  const tmpPath = `${this.statePath}.tmp`;
@@ -26750,11 +27209,11 @@ var init_file_hash_state = __esm({
26750
27209
  this.state = { files: { ...snapshot.files } };
26751
27210
  }
26752
27211
  load() {
26753
- if (!existsSync48(this.statePath)) {
27212
+ if (!existsSync49(this.statePath)) {
26754
27213
  return { files: {} };
26755
27214
  }
26756
27215
  try {
26757
- const raw = readFileSync45(this.statePath, "utf-8");
27216
+ const raw = readFileSync46(this.statePath, "utf-8");
26758
27217
  return JSON.parse(raw);
26759
27218
  } catch {
26760
27219
  return { files: {} };
@@ -26766,15 +27225,15 @@ var init_file_hash_state = __esm({
26766
27225
 
26767
27226
  // src/tracking/stash-manager.ts
26768
27227
  import {
26769
- existsSync as existsSync49,
27228
+ existsSync as existsSync50,
26770
27229
  mkdirSync as mkdirSync30,
26771
- readFileSync as readFileSync46,
26772
- readdirSync as readdirSync11,
27230
+ readFileSync as readFileSync47,
27231
+ readdirSync as readdirSync12,
26773
27232
  rmSync as rmSync4,
26774
27233
  statSync as statSync8,
26775
27234
  writeFileSync as writeFileSync29
26776
27235
  } from "fs";
26777
- import { join as join54 } from "path";
27236
+ import { join as join55 } from "path";
26778
27237
  var MAX_STASH_SNAPSHOTS, OVERLAY_FILE2, HASHES_FILE2, _log6, StashManager;
26779
27238
  var init_stash_manager = __esm({
26780
27239
  "src/tracking/stash-manager.ts"() {
@@ -26792,8 +27251,8 @@ var init_stash_manager = __esm({
26792
27251
  constructor(unerrDir, projectRoot) {
26793
27252
  this.unerrDir = unerrDir;
26794
27253
  this.projectRoot = projectRoot;
26795
- this.stashDir = join54(unerrDir, "drift", "stash");
26796
- this.gitDir = join54(projectRoot, ".git");
27254
+ this.stashDir = join55(unerrDir, "drift", "stash");
27255
+ this.gitDir = join55(projectRoot, ".git");
26797
27256
  this.previousStashRef = this.readStashRef();
26798
27257
  this.previousStashCount = this.getStashCount();
26799
27258
  }
@@ -26838,8 +27297,8 @@ var init_stash_manager = __esm({
26838
27297
  return null;
26839
27298
  }
26840
27299
  const snapshotId = stashRef.slice(0, 12);
26841
- const snapshotDir = join54(this.stashDir, snapshotId);
26842
- if (!existsSync49(snapshotDir)) {
27300
+ const snapshotDir = join55(this.stashDir, snapshotId);
27301
+ if (!existsSync50(snapshotDir)) {
26843
27302
  mkdirSync30(snapshotDir, { recursive: true });
26844
27303
  }
26845
27304
  const snapshot = {
@@ -26850,12 +27309,12 @@ var init_stash_manager = __esm({
26850
27309
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
26851
27310
  };
26852
27311
  writeFileSync29(
26853
- join54(snapshotDir, OVERLAY_FILE2),
27312
+ join55(snapshotDir, OVERLAY_FILE2),
26854
27313
  JSON.stringify(snapshot, null, 2),
26855
27314
  "utf-8"
26856
27315
  );
26857
27316
  writeFileSync29(
26858
- join54(snapshotDir, HASHES_FILE2),
27317
+ join55(snapshotDir, HASHES_FILE2),
26859
27318
  JSON.stringify(fileHashState, null, 2),
26860
27319
  "utf-8"
26861
27320
  );
@@ -26876,14 +27335,14 @@ var init_stash_manager = __esm({
26876
27335
  return 0;
26877
27336
  }
26878
27337
  const latest = snapshots[0];
26879
- const snapshotDir = join54(this.stashDir, latest.id);
26880
- const overlayPath = join54(snapshotDir, OVERLAY_FILE2);
26881
- if (!existsSync49(overlayPath)) {
27338
+ const snapshotDir = join55(this.stashDir, latest.id);
27339
+ const overlayPath = join55(snapshotDir, OVERLAY_FILE2);
27340
+ if (!existsSync50(overlayPath)) {
26882
27341
  _log6.warn(`Snapshot ${latest.id} missing overlay file`);
26883
27342
  return 0;
26884
27343
  }
26885
27344
  try {
26886
- const raw = readFileSync46(overlayPath, "utf-8");
27345
+ const raw = readFileSync47(overlayPath, "utf-8");
26887
27346
  const snapshot = JSON.parse(raw);
26888
27347
  for (const entity of snapshot.entities) {
26889
27348
  await localGraph.upsertDriftEntity(entity);
@@ -26913,10 +27372,10 @@ var init_stash_manager = __esm({
26913
27372
  const snapshots = this.listSnapshots();
26914
27373
  if (snapshots.length === 0) return null;
26915
27374
  const latest = snapshots[0];
26916
- const hashesPath = join54(this.stashDir, latest.id, HASHES_FILE2);
26917
- if (!existsSync49(hashesPath)) return null;
27375
+ const hashesPath = join55(this.stashDir, latest.id, HASHES_FILE2);
27376
+ if (!existsSync50(hashesPath)) return null;
26918
27377
  try {
26919
- const raw = readFileSync46(hashesPath, "utf-8");
27378
+ const raw = readFileSync47(hashesPath, "utf-8");
26920
27379
  return JSON.parse(raw);
26921
27380
  } catch {
26922
27381
  return null;
@@ -26927,8 +27386,8 @@ var init_stash_manager = __esm({
26927
27386
  */
26928
27387
  dropSnapshot(stashRef) {
26929
27388
  const snapshotId = stashRef.slice(0, 12);
26930
- const snapshotDir = join54(this.stashDir, snapshotId);
26931
- if (!existsSync49(snapshotDir)) return false;
27389
+ const snapshotDir = join55(this.stashDir, snapshotId);
27390
+ if (!existsSync50(snapshotDir)) return false;
26932
27391
  rmSync4(snapshotDir, { recursive: true, force: true });
26933
27392
  _log6.info(`Dropped stash snapshot: ${snapshotId}`);
26934
27393
  return true;
@@ -26937,14 +27396,14 @@ var init_stash_manager = __esm({
26937
27396
  * List all stash snapshots, sorted by most recent first.
26938
27397
  */
26939
27398
  listSnapshots() {
26940
- if (!existsSync49(this.stashDir)) return [];
27399
+ if (!existsSync50(this.stashDir)) return [];
26941
27400
  try {
26942
- const entries = readdirSync11(this.stashDir, { withFileTypes: true });
27401
+ const entries = readdirSync12(this.stashDir, { withFileTypes: true });
26943
27402
  const snapshots = [];
26944
27403
  for (const entry of entries) {
26945
27404
  if (!entry.isDirectory()) continue;
26946
- const overlayPath = join54(this.stashDir, entry.name, OVERLAY_FILE2);
26947
- if (!existsSync49(overlayPath)) continue;
27405
+ const overlayPath = join55(this.stashDir, entry.name, OVERLAY_FILE2);
27406
+ if (!existsSync50(overlayPath)) continue;
26948
27407
  try {
26949
27408
  const stat2 = statSync8(overlayPath);
26950
27409
  snapshots.push({ id: entry.name, savedAt: stat2.mtime });
@@ -26961,10 +27420,10 @@ var init_stash_manager = __esm({
26961
27420
  * Read the current stash ref SHA from `.git/refs/stash`.
26962
27421
  */
26963
27422
  readStashRef() {
26964
- const stashPath = join54(this.gitDir, "refs", "stash");
26965
- if (!existsSync49(stashPath)) return null;
27423
+ const stashPath = join55(this.gitDir, "refs", "stash");
27424
+ if (!existsSync50(stashPath)) return null;
26966
27425
  try {
26967
- return readFileSync46(stashPath, "utf-8").trim() || null;
27426
+ return readFileSync47(stashPath, "utf-8").trim() || null;
26968
27427
  } catch {
26969
27428
  return null;
26970
27429
  }
@@ -26973,10 +27432,10 @@ var init_stash_manager = __esm({
26973
27432
  * Count current stash entries via `.git/logs/refs/stash`.
26974
27433
  */
26975
27434
  getStashCount() {
26976
- const logPath = join54(this.gitDir, "logs", "refs", "stash");
26977
- if (!existsSync49(logPath)) return 0;
27435
+ const logPath = join55(this.gitDir, "logs", "refs", "stash");
27436
+ if (!existsSync50(logPath)) return 0;
26978
27437
  try {
26979
- const content = readFileSync46(logPath, "utf-8");
27438
+ const content = readFileSync47(logPath, "utf-8");
26980
27439
  return content.split("\n").filter((line) => line.trim().length > 0).length;
26981
27440
  } catch {
26982
27441
  return 0;
@@ -26990,7 +27449,7 @@ var init_stash_manager = __esm({
26990
27449
  if (snapshots.length <= MAX_STASH_SNAPSHOTS) return;
26991
27450
  const toRemove = snapshots.slice(MAX_STASH_SNAPSHOTS);
26992
27451
  for (const snapshot of toRemove) {
26993
- const dir = join54(this.stashDir, snapshot.id);
27452
+ const dir = join55(this.stashDir, snapshot.id);
26994
27453
  rmSync4(dir, { recursive: true, force: true });
26995
27454
  _log6.info(`LRU evicted stash snapshot: ${snapshot.id}`);
26996
27455
  }
@@ -27008,13 +27467,13 @@ __export(drift_tracker_exports, {
27008
27467
  determineOrigin: () => determineOrigin
27009
27468
  });
27010
27469
  import {
27011
- existsSync as existsSync50,
27470
+ existsSync as existsSync51,
27012
27471
  mkdirSync as mkdirSync31,
27013
- readFileSync as readFileSync47,
27472
+ readFileSync as readFileSync48,
27014
27473
  statSync as statSync9,
27015
27474
  writeFileSync as writeFileSync30
27016
27475
  } from "fs";
27017
- import { join as join55 } from "path";
27476
+ import { join as join56 } from "path";
27018
27477
  function determineOrigin(lastSyncTimestamp) {
27019
27478
  if (lastSyncTimestamp === 0) return "human";
27020
27479
  const elapsed = Date.now() - lastSyncTimestamp;
@@ -27225,15 +27684,15 @@ var init_drift_tracker = __esm({
27225
27684
  crossFileInvalidated: 0,
27226
27685
  edgesExtracted: 0
27227
27686
  };
27228
- const absPath = filePath.startsWith("/") ? filePath : join55(this.config.projectRoot, filePath);
27687
+ const absPath = filePath.startsWith("/") ? filePath : join56(this.config.projectRoot, filePath);
27229
27688
  const relPath = filePath.startsWith("/") ? filePath.slice(this.config.projectRoot.length + 1) : filePath;
27230
27689
  const language = detectLanguage2(relPath);
27231
27690
  if (!language) return result;
27232
- if (existsSync50(absPath) && !this.mtimeCache.check(absPath)) {
27691
+ if (existsSync51(absPath) && !this.mtimeCache.check(absPath)) {
27233
27692
  result.filesSkipped = 1;
27234
27693
  return result;
27235
27694
  }
27236
- if (!existsSync50(absPath)) {
27695
+ if (!existsSync51(absPath)) {
27237
27696
  const baseEntities2 = await this.localGraph.getEntitiesByFile(relPath);
27238
27697
  this.markFileDeleted(relPath, intentId);
27239
27698
  result.filesProcessed = 1;
@@ -27271,7 +27730,7 @@ var init_drift_tracker = __esm({
27271
27730
  this.maybeEmitDrift(relPath, result);
27272
27731
  return result;
27273
27732
  }
27274
- const content = readFileSync47(absPath, "utf-8");
27733
+ const content = readFileSync48(absPath, "utf-8");
27275
27734
  const sha = contentSha256(content);
27276
27735
  const decision = this.fileHashManager.shouldProcess(relPath, sha, headSha);
27277
27736
  if (decision === "skip") {
@@ -27505,7 +27964,7 @@ var init_drift_tracker = __esm({
27505
27964
  return await this.stashManager.restoreSnapshot(this.localGraph);
27506
27965
  }
27507
27966
  async markFileDeleted(filePath, intentId) {
27508
- const absPath = filePath.startsWith("/") ? filePath : join55(this.config.projectRoot, filePath);
27967
+ const absPath = filePath.startsWith("/") ? filePath : join56(this.config.projectRoot, filePath);
27509
27968
  this.mtimeCache.evict(absPath);
27510
27969
  const baseEntities = await this.localGraph.getEntitiesByFile(filePath);
27511
27970
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -27590,13 +28049,13 @@ var init_drift_tracker = __esm({
27590
28049
  return invalidated;
27591
28050
  }
27592
28051
  async saveDriftSummary() {
27593
- const driftDir = join55(this.config.unerrDir, "drift");
27594
- if (!existsSync50(driftDir)) {
28052
+ const driftDir = join56(this.config.unerrDir, "drift");
28053
+ if (!existsSync51(driftDir)) {
27595
28054
  mkdirSync31(driftDir, { recursive: true });
27596
28055
  }
27597
28056
  const summary = await this.getDriftSummary();
27598
28057
  writeFileSync30(
27599
- join55(driftDir, "drift_summary.json"),
28058
+ join56(driftDir, "drift_summary.json"),
27600
28059
  JSON.stringify(summary, null, 2),
27601
28060
  "utf-8"
27602
28061
  );
@@ -28005,8 +28464,8 @@ var incremental_indexer_exports = {};
28005
28464
  __export(incremental_indexer_exports, {
28006
28465
  indexFilesIncremental: () => indexFilesIncremental
28007
28466
  });
28008
- import { existsSync as existsSync51, readFileSync as readFileSync48 } from "fs";
28009
- import { join as join56, relative as relative4 } from "path";
28467
+ import { existsSync as existsSync52, readFileSync as readFileSync49 } from "fs";
28468
+ import { join as join57, relative as relative4 } from "path";
28010
28469
  async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repoId) {
28011
28470
  const startMs = Date.now();
28012
28471
  const db = graphStore.db;
@@ -28021,9 +28480,9 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
28021
28480
  const changedEntityKeys = /* @__PURE__ */ new Set();
28022
28481
  const deletedEntityKeys = /* @__PURE__ */ new Set();
28023
28482
  for (const filePath of changedFiles) {
28024
- const absPath = filePath.startsWith("/") ? filePath : join56(projectRoot, filePath);
28483
+ const absPath = filePath.startsWith("/") ? filePath : join57(projectRoot, filePath);
28025
28484
  const relPath = filePath.startsWith("/") ? relative4(projectRoot, filePath) : filePath;
28026
- if (!existsSync51(absPath)) {
28485
+ if (!existsSync52(absPath)) {
28027
28486
  const deleted2 = await deleteFileFromGraph(db, relPath);
28028
28487
  filesDeleted++;
28029
28488
  totalEntitiesDeleted += deleted2.entitiesDeleted;
@@ -28036,7 +28495,7 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
28036
28495
  }
28037
28496
  let content;
28038
28497
  try {
28039
- content = readFileSync48(absPath, "utf-8");
28498
+ content = readFileSync49(absPath, "utf-8");
28040
28499
  } catch {
28041
28500
  continue;
28042
28501
  }
@@ -28324,6 +28783,7 @@ async function upsertEntitiesBatched(db, entities) {
28324
28783
  e.name,
28325
28784
  e.file_path,
28326
28785
  e.start_line ?? 0,
28786
+ e.end_line ?? 0,
28327
28787
  e.signature ?? "",
28328
28788
  e.body ?? "",
28329
28789
  e.fan_in ?? 0,
@@ -28341,21 +28801,22 @@ async function upsertEntitiesBatched(db, entities) {
28341
28801
  });
28342
28802
  try {
28343
28803
  await db.run(
28344
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [${rowStrs.join(", ")}]
28345
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`
28804
+ `?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [${rowStrs.join(", ")}]
28805
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`
28346
28806
  );
28347
28807
  } catch {
28348
28808
  for (const entity of entities) {
28349
28809
  try {
28350
28810
  await db.run(
28351
- `?[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]]
28352
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
28811
+ `?[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]]
28812
+ :put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
28353
28813
  {
28354
28814
  key: entity.key,
28355
28815
  kind: entity.kind,
28356
28816
  name: entity.name,
28357
28817
  fp: entity.file_path,
28358
28818
  sl: entity.start_line ?? 0,
28819
+ el: entity.end_line ?? 0,
28359
28820
  sig: entity.signature ?? "",
28360
28821
  body: entity.body ?? "",
28361
28822
  fi: entity.fan_in ?? 0,
@@ -29201,8 +29662,8 @@ var workspace_manifest_exports = {};
29201
29662
  __export(workspace_manifest_exports, {
29202
29663
  WorkspaceManifest: () => WorkspaceManifest
29203
29664
  });
29204
- import { existsSync as existsSync52, mkdirSync as mkdirSync32, readFileSync as readFileSync49, writeFileSync as writeFileSync31 } from "fs";
29205
- import { join as join57 } from "path";
29665
+ import { existsSync as existsSync53, mkdirSync as mkdirSync32, readFileSync as readFileSync50, writeFileSync as writeFileSync31 } from "fs";
29666
+ import { join as join58 } from "path";
29206
29667
  var WorkspaceManifest;
29207
29668
  var init_workspace_manifest = __esm({
29208
29669
  "src/tracking/workspace-manifest.ts"() {
@@ -29212,7 +29673,7 @@ var init_workspace_manifest = __esm({
29212
29673
  this.unerrDir = unerrDir;
29213
29674
  this.repoId = repoId;
29214
29675
  this.sessionId = sessionId;
29215
- this.manifestPath = join57(unerrDir, "manifest.json");
29676
+ this.manifestPath = join58(unerrDir, "manifest.json");
29216
29677
  this.data = this.load();
29217
29678
  }
29218
29679
  unerrDir;
@@ -29307,7 +29768,7 @@ var init_workspace_manifest = __esm({
29307
29768
  }
29308
29769
  // ── Internal ─────────────────────────────────────────────────────
29309
29770
  load() {
29310
- if (!existsSync52(this.manifestPath)) {
29771
+ if (!existsSync53(this.manifestPath)) {
29311
29772
  return {
29312
29773
  version: 1,
29313
29774
  repoId: this.repoId,
@@ -29317,7 +29778,7 @@ var init_workspace_manifest = __esm({
29317
29778
  };
29318
29779
  }
29319
29780
  try {
29320
- const raw = readFileSync49(this.manifestPath, "utf-8");
29781
+ const raw = readFileSync50(this.manifestPath, "utf-8");
29321
29782
  const parsed = JSON.parse(raw);
29322
29783
  if (parsed.repoId !== this.repoId) {
29323
29784
  return {
@@ -29340,7 +29801,7 @@ var init_workspace_manifest = __esm({
29340
29801
  }
29341
29802
  }
29342
29803
  save() {
29343
- if (!existsSync52(this.unerrDir)) {
29804
+ if (!existsSync53(this.unerrDir)) {
29344
29805
  mkdirSync32(this.unerrDir, { recursive: true });
29345
29806
  }
29346
29807
  writeFileSync31(
@@ -29534,8 +29995,8 @@ var log_tailer_exports = {};
29534
29995
  __export(log_tailer_exports, {
29535
29996
  startLogTailer: () => startLogTailer
29536
29997
  });
29537
- import { existsSync as existsSync53, statSync as statSync10, openSync, readSync, closeSync, watch } from "fs";
29538
- import { join as join58 } from "path";
29998
+ import { existsSync as existsSync54, statSync as statSync10, openSync, readSync, closeSync, watch } from "fs";
29999
+ import { join as join59 } from "path";
29539
30000
  function formatSize2(bytes) {
29540
30001
  if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
29541
30002
  if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`;
@@ -29642,29 +30103,29 @@ function tailFile(state, handler) {
29642
30103
  }
29643
30104
  }
29644
30105
  function startLogTailer(cwd, options) {
29645
- const logsDir = join58(cwd, ".unerr", "logs");
29646
- const compressionPath = join58(logsDir, "compression.jsonl");
29647
- const generalPath = join58(logsDir, "unerr.jsonl");
29648
- const tokenFlowPath = join58(logsDir, "token-flow.jsonl");
29649
- const fileReadsPath = join58(logsDir, "file-reads.jsonl");
30106
+ const logsDir = join59(cwd, ".unerr", "logs");
30107
+ const compressionPath = join59(logsDir, "compression.jsonl");
30108
+ const generalPath = join59(logsDir, "unerr.jsonl");
30109
+ const tokenFlowPath = join59(logsDir, "token-flow.jsonl");
30110
+ const fileReadsPath = join59(logsDir, "file-reads.jsonl");
29650
30111
  const compressionState = {
29651
30112
  path: compressionPath,
29652
- offset: existsSync53(compressionPath) ? statSync10(compressionPath).size : 0,
30113
+ offset: existsSync54(compressionPath) ? statSync10(compressionPath).size : 0,
29653
30114
  watcher: null
29654
30115
  };
29655
30116
  const generalState = {
29656
30117
  path: generalPath,
29657
- offset: existsSync53(generalPath) ? statSync10(generalPath).size : 0,
30118
+ offset: existsSync54(generalPath) ? statSync10(generalPath).size : 0,
29658
30119
  watcher: null
29659
30120
  };
29660
30121
  const tokenFlowState = {
29661
30122
  path: tokenFlowPath,
29662
- offset: existsSync53(tokenFlowPath) ? statSync10(tokenFlowPath).size : 0,
30123
+ offset: existsSync54(tokenFlowPath) ? statSync10(tokenFlowPath).size : 0,
29663
30124
  watcher: null
29664
30125
  };
29665
30126
  const fileReadsState = {
29666
30127
  path: fileReadsPath,
29667
- offset: existsSync53(fileReadsPath) ? statSync10(fileReadsPath).size : 0,
30128
+ offset: existsSync54(fileReadsPath) ? statSync10(fileReadsPath).size : 0,
29668
30129
  watcher: null
29669
30130
  };
29670
30131
  function setupWatcher(state, handler) {
@@ -29692,7 +30153,7 @@ function startLogTailer(cwd, options) {
29692
30153
  ];
29693
30154
  const pollInterval = setInterval(() => {
29694
30155
  for (const { state, handler } of allStates) {
29695
- if (!state.watcher && existsSync53(state.path)) {
30156
+ if (!state.watcher && existsSync54(state.path)) {
29696
30157
  try {
29697
30158
  state.offset = 0;
29698
30159
  state.watcher = watch(state.path, () => {
@@ -30410,6 +30871,336 @@ function createIntelligenceRoutes(deps) {
30410
30871
  }
30411
30872
  });
30412
30873
  });
30874
+ app.get("/risk-hotspots", async (c) => {
30875
+ const start = performance.now();
30876
+ const limit = parseLimit(c.req.query("limit"), 20, 50);
30877
+ if (!deps.localGraph) {
30878
+ return c.json(
30879
+ {
30880
+ data: [],
30881
+ _meta: {
30882
+ source: "local",
30883
+ graph: "unavailable",
30884
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
30885
+ }
30886
+ },
30887
+ 503
30888
+ );
30889
+ }
30890
+ const topNodes = await deps.localGraph.getCriticalNodes(limit);
30891
+ const keys = topNodes.map((n) => n.key);
30892
+ const testCountMap = /* @__PURE__ */ new Map();
30893
+ if (keys.length > 0) {
30894
+ try {
30895
+ const directResult = await deps.localGraph.db.run(
30896
+ `?[target, count(tk)] := *edges{from_key: tk, to_key: target, type: "tests"},
30897
+ target in $keys`,
30898
+ { keys }
30899
+ );
30900
+ for (const row of directResult.rows) {
30901
+ testCountMap.set(row[0], row[1]);
30902
+ }
30903
+ const transitiveResult = await deps.localGraph.db.run(
30904
+ `?[target, count(tk)] := *edges{from_key: mid, to_key: target, type: "calls"},
30905
+ *edges{from_key: tk, to_key: mid, type: "tests"},
30906
+ target in $keys`,
30907
+ { keys }
30908
+ );
30909
+ for (const row of transitiveResult.rows) {
30910
+ const key = row[0];
30911
+ const existing = testCountMap.get(key) ?? 0;
30912
+ testCountMap.set(key, existing + row[1]);
30913
+ }
30914
+ } catch {
30915
+ }
30916
+ }
30917
+ const callerCountMap = /* @__PURE__ */ new Map();
30918
+ if (keys.length > 0) {
30919
+ try {
30920
+ const callerResult = await deps.localGraph.db.run(
30921
+ `?[target, count(caller)] := *edges{from_key: caller, to_key: target, type: "calls"},
30922
+ target in $keys`,
30923
+ { keys }
30924
+ );
30925
+ for (const row of callerResult.rows) {
30926
+ callerCountMap.set(row[0], row[1]);
30927
+ }
30928
+ } catch {
30929
+ }
30930
+ }
30931
+ const hotspots = topNodes.map((n) => ({
30932
+ key: n.key,
30933
+ name: n.name,
30934
+ file_path: n.file_path,
30935
+ kind: n.kind,
30936
+ fan_in: n.fan_in,
30937
+ fan_out: n.fan_out,
30938
+ degree: n.degree,
30939
+ risk_level: n.risk_level,
30940
+ community_label: n.community_label,
30941
+ test_count: testCountMap.get(n.key) ?? 0,
30942
+ caller_count: callerCountMap.get(n.key) ?? 0
30943
+ }));
30944
+ return c.json({
30945
+ data: hotspots,
30946
+ _meta: {
30947
+ source: "local",
30948
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
30949
+ }
30950
+ });
30951
+ });
30952
+ app.get("/insights", async (c) => {
30953
+ const start = performance.now();
30954
+ if (!deps.localGraph) {
30955
+ return c.json(
30956
+ {
30957
+ data: null,
30958
+ _meta: {
30959
+ source: "local",
30960
+ graph: "unavailable",
30961
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
30962
+ }
30963
+ },
30964
+ 503
30965
+ );
30966
+ }
30967
+ const topN = 50;
30968
+ const topNodes = await deps.localGraph.getCriticalNodes(topN);
30969
+ const keys = topNodes.map((n) => n.key);
30970
+ const testCountMap = /* @__PURE__ */ new Map();
30971
+ if (keys.length > 0) {
30972
+ try {
30973
+ const directResult = await deps.localGraph.db.run(
30974
+ `?[target, count(tk)] := *edges{from_key: tk, to_key: target, type: "tests"},
30975
+ target in $keys`,
30976
+ { keys }
30977
+ );
30978
+ for (const row of directResult.rows) {
30979
+ testCountMap.set(row[0], row[1]);
30980
+ }
30981
+ const transitiveResult = await deps.localGraph.db.run(
30982
+ `?[target, count(tk)] := *edges{from_key: mid, to_key: target, type: "calls"},
30983
+ *edges{from_key: tk, to_key: mid, type: "tests"},
30984
+ target in $keys`,
30985
+ { keys }
30986
+ );
30987
+ for (const row of transitiveResult.rows) {
30988
+ const k = row[0];
30989
+ testCountMap.set(k, (testCountMap.get(k) ?? 0) + row[1]);
30990
+ }
30991
+ } catch {
30992
+ }
30993
+ }
30994
+ let totalBlastRadius = 0;
30995
+ let testedBlastRadius = 0;
30996
+ let untestedBlastRadius = 0;
30997
+ for (const n of topNodes) {
30998
+ const weight = Math.max(n.fan_in, 1);
30999
+ totalBlastRadius += weight;
31000
+ if ((testCountMap.get(n.key) ?? 0) > 0) {
31001
+ testedBlastRadius += weight;
31002
+ } else {
31003
+ untestedBlastRadius += weight;
31004
+ }
31005
+ }
31006
+ const blastRadiusCoverage = totalBlastRadius > 0 ? Math.round(testedBlastRadius / totalBlastRadius * 100) : 0;
31007
+ const bottlenecks = topNodes.filter(
31008
+ (n) => n.fan_in >= 3 && n.fan_out >= 2 && (testCountMap.get(n.key) ?? 0) === 0
31009
+ ).map((n) => ({
31010
+ key: n.key,
31011
+ name: n.name,
31012
+ file_path: n.file_path,
31013
+ kind: n.kind,
31014
+ fan_in: n.fan_in,
31015
+ fan_out: n.fan_out,
31016
+ degree: n.degree,
31017
+ risk_level: n.risk_level
31018
+ }));
31019
+ const riskDistribution = { high: 0, medium: 0, low: 0 };
31020
+ for (const n of topNodes) {
31021
+ const level = n.risk_level;
31022
+ if (level in riskDistribution) riskDistribution[level]++;
31023
+ }
31024
+ const sortedByDegree = [...topNodes].sort((a, b) => b.degree - a.degree);
31025
+ const totalDegree = topNodes.reduce((s, n) => s + n.degree, 0);
31026
+ const top5Degree = sortedByDegree.slice(0, 5).reduce((s, n) => s + n.degree, 0);
31027
+ const riskConcentration = totalDegree > 0 ? Math.round(top5Degree / totalDegree * 100) : 0;
31028
+ const communityMap = /* @__PURE__ */ new Map();
31029
+ for (const n of topNodes) {
31030
+ const label = n.community_label || `cluster-${n.community}`;
31031
+ let comm = communityMap.get(label);
31032
+ if (!comm) {
31033
+ comm = {
31034
+ label,
31035
+ entities: 0,
31036
+ totalDegree: 0,
31037
+ riskHigh: 0,
31038
+ riskMedium: 0,
31039
+ riskLow: 0,
31040
+ untested: 0,
31041
+ tested: 0,
31042
+ totalFanIn: 0,
31043
+ untestedFanIn: 0
31044
+ };
31045
+ communityMap.set(label, comm);
31046
+ }
31047
+ comm.entities++;
31048
+ comm.totalDegree += n.degree;
31049
+ comm.totalFanIn += n.fan_in;
31050
+ const hasCoverage = (testCountMap.get(n.key) ?? 0) > 0;
31051
+ if (hasCoverage) comm.tested++;
31052
+ else {
31053
+ comm.untested++;
31054
+ comm.untestedFanIn += n.fan_in;
31055
+ }
31056
+ if (n.risk_level === "high") comm.riskHigh++;
31057
+ else if (n.risk_level === "medium") comm.riskMedium++;
31058
+ else comm.riskLow++;
31059
+ }
31060
+ const communityHealth = [];
31061
+ for (const [, comm] of communityMap) {
31062
+ const total = comm.tested + comm.untested;
31063
+ communityHealth.push({
31064
+ label: comm.label,
31065
+ entities: comm.entities,
31066
+ tested: comm.tested,
31067
+ untested: comm.untested,
31068
+ coveragePct: total > 0 ? Math.round(comm.tested / total * 100) : 0,
31069
+ blastRadiusCoveragePct: comm.totalFanIn > 0 ? Math.round(
31070
+ (comm.totalFanIn - comm.untestedFanIn) / comm.totalFanIn * 100
31071
+ ) : 0,
31072
+ riskHigh: comm.riskHigh,
31073
+ riskMedium: comm.riskMedium,
31074
+ riskLow: comm.riskLow,
31075
+ totalDegree: comm.totalDegree
31076
+ });
31077
+ }
31078
+ communityHealth.sort(
31079
+ (a, b) => a.blastRadiusCoveragePct - b.blastRadiusCoveragePct
31080
+ );
31081
+ let mostCoupledPair = null;
31082
+ try {
31083
+ const fcResult = await deps.localGraph.db.run(
31084
+ `?[from_label, to_label, w] :=
31085
+ *file_edges{from_file, to_file, weight: w},
31086
+ *file_communities{file_path: from_file, label: from_label},
31087
+ *file_communities{file_path: to_file, label: to_label},
31088
+ from_label != to_label
31089
+ :order -w
31090
+ :limit 1`
31091
+ );
31092
+ if (fcResult.rows.length > 0) {
31093
+ const [from, to, weight] = fcResult.rows[0];
31094
+ mostCoupledPair = { from, to, weight };
31095
+ }
31096
+ } catch {
31097
+ }
31098
+ const insights = [];
31099
+ if (blastRadiusCoverage < 50) {
31100
+ insights.push({
31101
+ id: "blast-radius-coverage",
31102
+ severity: "critical",
31103
+ title: `Blast-radius coverage is only ${blastRadiusCoverage}%`,
31104
+ 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.`,
31105
+ metric: blastRadiusCoverage,
31106
+ metricLabel: "blast-radius coverage"
31107
+ });
31108
+ } else if (blastRadiusCoverage < 75) {
31109
+ insights.push({
31110
+ id: "blast-radius-coverage",
31111
+ severity: "warning",
31112
+ title: `Blast-radius coverage at ${blastRadiusCoverage}%`,
31113
+ description: `Your most critical code paths are partially covered, but ${untestedBlastRadius} dependency-weight remains untested.`,
31114
+ metric: blastRadiusCoverage,
31115
+ metricLabel: "blast-radius coverage"
31116
+ });
31117
+ } else {
31118
+ insights.push({
31119
+ id: "blast-radius-coverage",
31120
+ severity: "positive",
31121
+ title: `Strong blast-radius coverage: ${blastRadiusCoverage}%`,
31122
+ description: "Your highest-impact code paths are well tested. Regressions are unlikely to cascade silently.",
31123
+ metric: blastRadiusCoverage,
31124
+ metricLabel: "blast-radius coverage"
31125
+ });
31126
+ }
31127
+ if (bottlenecks.length > 0) {
31128
+ const totalBottleneckFanIn = bottlenecks.reduce(
31129
+ (s, b) => s + b.fan_in,
31130
+ 0
31131
+ );
31132
+ insights.push({
31133
+ id: "bottlenecks",
31134
+ severity: bottlenecks.length >= 5 ? "critical" : "warning",
31135
+ title: `${bottlenecks.length} structural bottleneck${bottlenecks.length !== 1 ? "s" : ""} with zero tests`,
31136
+ 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.`,
31137
+ metric: bottlenecks.length,
31138
+ metricLabel: "bottlenecks"
31139
+ });
31140
+ }
31141
+ if (riskConcentration > 40) {
31142
+ insights.push({
31143
+ id: "risk-concentration",
31144
+ severity: riskConcentration > 60 ? "warning" : "info",
31145
+ title: `${riskConcentration}% of structural risk concentrated in top 5 entities`,
31146
+ 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.`,
31147
+ metric: riskConcentration,
31148
+ metricLabel: "risk in top 5"
31149
+ });
31150
+ }
31151
+ if (mostCoupledPair && mostCoupledPair.weight >= 3) {
31152
+ insights.push({
31153
+ id: "coupling-hotspot",
31154
+ severity: mostCoupledPair.weight >= 10 ? "warning" : "info",
31155
+ title: `"${mostCoupledPair.from}" and "${mostCoupledPair.to}" are tightly coupled`,
31156
+ description: `${mostCoupledPair.weight} cross-boundary calls between these modules. Changes in one are likely to require changes in the other.`,
31157
+ metric: mostCoupledPair.weight,
31158
+ metricLabel: "cross-boundary calls"
31159
+ });
31160
+ }
31161
+ if (riskDistribution.high === 0 && topNodes.length > 0) {
31162
+ insights.push({
31163
+ id: "no-high-risk",
31164
+ severity: "positive",
31165
+ title: "No high-risk entities detected",
31166
+ description: "Your top entities are well-balanced with moderate dependency counts. The codebase structure is healthy."
31167
+ });
31168
+ }
31169
+ const bottleneckPenalty = Math.min(bottlenecks.length * 5, 25);
31170
+ const highRiskPenalty = Math.min(riskDistribution.high * 3, 15);
31171
+ const concentrationPenalty = riskConcentration > 50 ? (riskConcentration - 50) * 0.4 : 0;
31172
+ const healthScore = Math.max(
31173
+ 0,
31174
+ Math.min(
31175
+ 100,
31176
+ Math.round(
31177
+ blastRadiusCoverage * 0.4 + (100 - bottleneckPenalty) * 0.25 + (100 - concentrationPenalty) * 0.2 + (100 - highRiskPenalty) * 0.15
31178
+ )
31179
+ )
31180
+ );
31181
+ const healthGrade = healthScore >= 90 ? "A" : healthScore >= 80 ? "B" : healthScore >= 65 ? "C" : healthScore >= 50 ? "D" : "F";
31182
+ return c.json({
31183
+ data: {
31184
+ healthScore,
31185
+ healthGrade,
31186
+ blastRadiusCoverage,
31187
+ untestedBlastRadius,
31188
+ testedBlastRadius,
31189
+ totalBlastRadius,
31190
+ bottlenecks,
31191
+ riskDistribution,
31192
+ riskConcentration,
31193
+ communityHealth,
31194
+ mostCoupledPair,
31195
+ insights
31196
+ },
31197
+ _meta: {
31198
+ source: "local",
31199
+ entities_analyzed: topNodes.length,
31200
+ latency_ms: Math.round((performance.now() - start) * 100) / 100
31201
+ }
31202
+ });
31203
+ });
30413
31204
  app.get("/durability", async (c) => {
30414
31205
  const start = performance.now();
30415
31206
  const limit = parseLimit(c.req.query("ledger_limit"), 800, 5e3);
@@ -30696,13 +31487,13 @@ function createSystemRoutes(deps) {
30696
31487
  });
30697
31488
  app.get("/config", async (c) => {
30698
31489
  const start = performance.now();
30699
- const { existsSync: existsSync62, readFileSync: readFileSync56 } = await import("fs");
30700
- const { join: join67 } = await import("path");
31490
+ const { existsSync: existsSync63, readFileSync: readFileSync57 } = await import("fs");
31491
+ const { join: join68 } = await import("path");
30701
31492
  let config = {};
30702
- const configPath = join67(deps.cwd, ".unerr", "config.json");
30703
- if (existsSync62(configPath)) {
31493
+ const configPath = join68(deps.cwd, ".unerr", "config.json");
31494
+ if (existsSync63(configPath)) {
30704
31495
  try {
30705
- config = JSON.parse(readFileSync56(configPath, "utf-8"));
31496
+ config = JSON.parse(readFileSync57(configPath, "utf-8"));
30706
31497
  } catch {
30707
31498
  config = { error: "unreadable" };
30708
31499
  }
@@ -30903,20 +31694,20 @@ __export(session_history_exports, {
30903
31694
  getWeeklyStats: () => getWeeklyStats,
30904
31695
  readSessionHistory: () => readSessionHistory
30905
31696
  });
30906
- import { appendFileSync as appendFileSync8, existsSync as existsSync54, mkdirSync as mkdirSync33, readFileSync as readFileSync50 } from "fs";
30907
- import { join as join59 } from "path";
31697
+ import { appendFileSync as appendFileSync8, existsSync as existsSync55, mkdirSync as mkdirSync33, readFileSync as readFileSync51 } from "fs";
31698
+ import { join as join60 } from "path";
30908
31699
  function appendSessionHistory(unerrDir, entry) {
30909
- const stateDir = join59(unerrDir, "state");
30910
- if (!existsSync54(stateDir)) mkdirSync33(stateDir, { recursive: true });
30911
- const historyPath = join59(stateDir, "session-history.jsonl");
31700
+ const stateDir = join60(unerrDir, "state");
31701
+ if (!existsSync55(stateDir)) mkdirSync33(stateDir, { recursive: true });
31702
+ const historyPath = join60(stateDir, "session-history.jsonl");
30912
31703
  appendFileSync8(historyPath, `${JSON.stringify(entry)}
30913
31704
  `, "utf-8");
30914
31705
  }
30915
31706
  function readSessionHistory(unerrDir) {
30916
- const historyPath = join59(unerrDir, "state", "session-history.jsonl");
30917
- if (!existsSync54(historyPath)) return [];
31707
+ const historyPath = join60(unerrDir, "state", "session-history.jsonl");
31708
+ if (!existsSync55(historyPath)) return [];
30918
31709
  try {
30919
- const content = readFileSync50(historyPath, "utf-8");
31710
+ const content = readFileSync51(historyPath, "utf-8");
30920
31711
  return content.split("\n").filter(Boolean).map((line) => {
30921
31712
  try {
30922
31713
  return JSON.parse(line);
@@ -31154,7 +31945,7 @@ function createTokenFlowRoutes(deps) {
31154
31945
  first_ts: data.first_ts,
31155
31946
  last_ts: data.last_ts,
31156
31947
  mechanisms: [...data.mechanisms],
31157
- agent_name: agentBySession.get(id) ?? null
31948
+ agent_name: agentBySession.get(id) ?? deps.getAgentName?.(id) ?? null
31158
31949
  };
31159
31950
  }).sort((a, b) => b.last_ts.localeCompare(a.last_ts));
31160
31951
  const paginated = allSessions.slice(offset, offset + limit);
@@ -31293,18 +32084,297 @@ var init_token_flow2 = __esm({
31293
32084
  }
31294
32085
  });
31295
32086
 
32087
+ // src/server/routes/reasoning-quality.ts
32088
+ import { Hono as Hono7 } from "hono";
32089
+ function computeQualityMetrics(events) {
32090
+ if (events.length === 0) {
32091
+ return {
32092
+ signal_to_noise_ratio: 0,
32093
+ noise_removed_pct: 0,
32094
+ context_density: 0,
32095
+ entities_resolved: 0,
32096
+ graph_tokens_delivered: 0,
32097
+ attention_multiplier: 1,
32098
+ first_call_resolution_rate: 0,
32099
+ graph_calls: 0,
32100
+ total_tool_calls: 0,
32101
+ turns_saved: 0,
32102
+ exploration_loops_prevented: 0,
32103
+ blast_radius_warnings: 0,
32104
+ circuit_breaker_activations: 0,
32105
+ convention_injections: 0,
32106
+ prevention_score: 0,
32107
+ reasoning_quality_multiplier: 0,
32108
+ total_sessions: 0,
32109
+ total_turns: 0,
32110
+ total_events: 0
32111
+ };
32112
+ }
32113
+ let totalWithout = 0;
32114
+ let totalWith = 0;
32115
+ let graphCalls = 0;
32116
+ let graphTokensDelivered = 0;
32117
+ let shellCompressionEvents = 0;
32118
+ let behaviorEvents = 0;
32119
+ let blastRadiusWarnings = 0;
32120
+ let circuitBreakerActivations = 0;
32121
+ let conventionInjections = 0;
32122
+ let explorationLoopsPrevented = 0;
32123
+ let dedupEvents = 0;
32124
+ const sessions = /* @__PURE__ */ new Set();
32125
+ const turns = /* @__PURE__ */ new Set();
32126
+ for (const e of events) {
32127
+ totalWithout += e.tokens_without;
32128
+ totalWith += e.tokens_with;
32129
+ sessions.add(e.session_id);
32130
+ turns.add(`${e.session_id}:${e.turn}`);
32131
+ if (GRAPH_MECHANISMS.has(e.mechanism)) {
32132
+ graphCalls++;
32133
+ graphTokensDelivered += e.tokens_with;
32134
+ }
32135
+ if (SHELL_MECHANISMS.has(e.mechanism)) {
32136
+ shellCompressionEvents++;
32137
+ }
32138
+ if (SAFETY_MECHANISMS.has(e.mechanism)) {
32139
+ behaviorEvents++;
32140
+ }
32141
+ const d = e.detail;
32142
+ if (d) {
32143
+ if (d.counterfactual === "blast_radius" || d.blast_radius) {
32144
+ blastRadiusWarnings++;
32145
+ }
32146
+ if (d.circuit_breaker || d.counterfactual === "circuit_breaker") {
32147
+ circuitBreakerActivations++;
32148
+ }
32149
+ if (d.conventions_injected || d.counterfactual === "convention_injection") {
32150
+ conventionInjections++;
32151
+ }
32152
+ if (d.hook_redirect || d.counterfactual === "hook_redirect") {
32153
+ explorationLoopsPrevented++;
32154
+ }
32155
+ }
32156
+ if (e.mechanism === "session_dedup") {
32157
+ dedupEvents++;
32158
+ }
32159
+ }
32160
+ const totalSaved = totalWithout - totalWith;
32161
+ const totalToolCalls = events.length;
32162
+ const snr = totalWithout > 0 ? totalWith / totalWithout : 0;
32163
+ const noiseRemovedPct = totalWithout > 0 ? Math.round((totalWithout - totalWith) / totalWithout * 100) : 0;
32164
+ const contextDensity = graphTokensDelivered > 0 ? Math.round(graphCalls / (graphTokensDelivered / 1e3) * 10) / 10 : 0;
32165
+ const compressionRatio = totalWith > 0 ? totalWithout / totalWith : 1;
32166
+ const attentionMultiplier = Math.round(Math.sqrt(compressionRatio) * 100) / 100;
32167
+ const firstCallRate = totalToolCalls > 0 ? Math.round(graphCalls / totalToolCalls * 100) : 0;
32168
+ const turnsSaved = Math.round(graphCalls * 2.5);
32169
+ const totalConventionInjections = conventionInjections + behaviorEvents;
32170
+ const preventionScore = blastRadiusWarnings + circuitBreakerActivations * 10 + totalConventionInjections;
32171
+ const firstCallRateDecimal = firstCallRate / 100;
32172
+ const reasoningMultiplier = compressionRatio > 0 && firstCallRateDecimal > 0 ? Math.round(compressionRatio * (1 + firstCallRateDecimal) * 100) / 100 : 0;
32173
+ return {
32174
+ signal_to_noise_ratio: Math.round(snr * 1e3) / 1e3,
32175
+ noise_removed_pct: noiseRemovedPct,
32176
+ context_density: contextDensity,
32177
+ entities_resolved: graphCalls,
32178
+ graph_tokens_delivered: graphTokensDelivered,
32179
+ attention_multiplier: attentionMultiplier,
32180
+ first_call_resolution_rate: firstCallRate,
32181
+ graph_calls: graphCalls,
32182
+ total_tool_calls: totalToolCalls,
32183
+ turns_saved: turnsSaved,
32184
+ exploration_loops_prevented: explorationLoopsPrevented,
32185
+ blast_radius_warnings: blastRadiusWarnings,
32186
+ circuit_breaker_activations: circuitBreakerActivations,
32187
+ convention_injections: totalConventionInjections,
32188
+ prevention_score: preventionScore,
32189
+ reasoning_quality_multiplier: reasoningMultiplier,
32190
+ total_sessions: sessions.size,
32191
+ total_turns: turns.size,
32192
+ total_events: events.length
32193
+ };
32194
+ }
32195
+ function createReasoningQualityRoutes(deps) {
32196
+ const app = new Hono7();
32197
+ app.get("/global", (c) => {
32198
+ const start = performance.now();
32199
+ const fromTs = c.req.query("from_ts");
32200
+ const toTs = c.req.query("to_ts");
32201
+ const events = readTokenFlowEvents(deps.unerrDir, {
32202
+ from_ts: fromTs || void 0,
32203
+ to_ts: toTs || void 0
32204
+ });
32205
+ const metrics = computeQualityMetrics(events);
32206
+ return c.json({
32207
+ data: metrics,
32208
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32209
+ });
32210
+ });
32211
+ app.get("/session", (c) => {
32212
+ const start = performance.now();
32213
+ const querySessionId = c.req.query("session_id");
32214
+ const writer = deps.getTokenFlowWriter();
32215
+ const allEvents = readTokenFlowEvents(deps.unerrDir);
32216
+ if (allEvents.length === 0) {
32217
+ return c.json({ data: null, _meta: { latency_ms: 0 } });
32218
+ }
32219
+ let sessionId = querySessionId || writer?.sessionId;
32220
+ if (!sessionId) {
32221
+ sessionId = allEvents[allEvents.length - 1]?.session_id;
32222
+ }
32223
+ if (!sessionId) {
32224
+ return c.json({ data: null, _meta: { latency_ms: 0 } });
32225
+ }
32226
+ const sessionEvents = querySessionId ? allEvents.filter((e) => e.session_id === sessionId) : allEvents.filter((e) => e.session_id === sessionId || e.session_id === "unknown");
32227
+ const metrics = computeQualityMetrics(sessionEvents);
32228
+ const turnGroups = /* @__PURE__ */ new Map();
32229
+ for (const e of sessionEvents) {
32230
+ const group = turnGroups.get(e.turn) ?? [];
32231
+ group.push(e);
32232
+ turnGroups.set(e.turn, group);
32233
+ }
32234
+ const trajectory = [];
32235
+ let cumWithout = 0;
32236
+ let cumWith = 0;
32237
+ for (const [turn, turnEvents] of [...turnGroups.entries()].sort(([a], [b]) => a - b)) {
32238
+ let turnGraphCalls = 0;
32239
+ let turnGraphDelivered = 0;
32240
+ for (const e of turnEvents) {
32241
+ cumWithout += e.tokens_without;
32242
+ cumWith += e.tokens_with;
32243
+ if (GRAPH_MECHANISMS.has(e.mechanism)) {
32244
+ turnGraphCalls++;
32245
+ turnGraphDelivered += e.tokens_with;
32246
+ }
32247
+ }
32248
+ trajectory.push({
32249
+ turn,
32250
+ snr: cumWithout > 0 ? Math.round(cumWith / cumWithout * 1e3) / 1e3 : 0,
32251
+ cumulative_noise_removed_pct: cumWithout > 0 ? Math.round((cumWithout - cumWith) / cumWithout * 100) : 0,
32252
+ graph_calls_this_turn: turnGraphCalls,
32253
+ context_density: turnGraphDelivered > 0 ? Math.round(turnGraphCalls / (turnGraphDelivered / 1e3) * 10) / 10 : 0
32254
+ });
32255
+ }
32256
+ return c.json({
32257
+ data: {
32258
+ ...metrics,
32259
+ session_id: sessionId,
32260
+ trajectory
32261
+ },
32262
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32263
+ });
32264
+ });
32265
+ app.get("/sessions", (c) => {
32266
+ const start = performance.now();
32267
+ const fromTs = c.req.query("from_ts");
32268
+ const toTs = c.req.query("to_ts");
32269
+ const limit = Math.min(Number(c.req.query("limit") ?? 50), 200);
32270
+ const offset = Math.max(Number(c.req.query("offset") ?? 0), 0);
32271
+ const allEvents = readTokenFlowEvents(deps.unerrDir, {
32272
+ from_ts: fromTs || void 0,
32273
+ to_ts: toTs || void 0
32274
+ });
32275
+ const historyEntries = readSessionHistory(deps.unerrDir);
32276
+ const agentBySession = /* @__PURE__ */ new Map();
32277
+ for (const h of historyEntries) {
32278
+ if (h.agentName) agentBySession.set(h.sessionId, h.agentName);
32279
+ }
32280
+ const sessionMap = /* @__PURE__ */ new Map();
32281
+ for (const e of allEvents) {
32282
+ const group = sessionMap.get(e.session_id) ?? [];
32283
+ group.push(e);
32284
+ sessionMap.set(e.session_id, group);
32285
+ }
32286
+ const allSessions = [...sessionMap.entries()].map(([sessionId, events]) => {
32287
+ const m = computeQualityMetrics(events);
32288
+ const lastTs = events.reduce((max, e) => e.ts > max ? e.ts : max, events[0].ts);
32289
+ const firstTs = events.reduce((min, e) => e.ts < min ? e.ts : min, events[0].ts);
32290
+ return {
32291
+ session_id: sessionId,
32292
+ first_ts: firstTs,
32293
+ last_ts: lastTs,
32294
+ agent_name: agentBySession.get(sessionId) ?? deps.getAgentName?.(sessionId) ?? null,
32295
+ noise_removed_pct: m.noise_removed_pct,
32296
+ first_call_resolution_rate: m.first_call_resolution_rate,
32297
+ prevention_score: m.prevention_score,
32298
+ reasoning_quality_multiplier: m.reasoning_quality_multiplier,
32299
+ context_density: m.context_density,
32300
+ turns_saved: m.turns_saved,
32301
+ total_events: m.total_events,
32302
+ total_turns: m.total_turns
32303
+ };
32304
+ }).sort((a, b) => b.last_ts.localeCompare(a.last_ts));
32305
+ const paginated = allSessions.slice(offset, offset + limit);
32306
+ return c.json({
32307
+ data: paginated,
32308
+ total: allSessions.length,
32309
+ limit,
32310
+ offset,
32311
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32312
+ });
32313
+ });
32314
+ app.get("/trend", (c) => {
32315
+ const start = performance.now();
32316
+ const fromTs = c.req.query("from_ts");
32317
+ const toTs = c.req.query("to_ts");
32318
+ const allEvents = readTokenFlowEvents(deps.unerrDir, {
32319
+ from_ts: fromTs || void 0,
32320
+ to_ts: toTs || void 0
32321
+ });
32322
+ const sessionMap = /* @__PURE__ */ new Map();
32323
+ for (const e of allEvents) {
32324
+ const group = sessionMap.get(e.session_id) ?? [];
32325
+ group.push(e);
32326
+ sessionMap.set(e.session_id, group);
32327
+ }
32328
+ const trend = [...sessionMap.entries()].map(([sessionId, events]) => {
32329
+ const m = computeQualityMetrics(events);
32330
+ const firstTs = events.reduce((min, e) => e.ts < min ? e.ts : min, events[0].ts);
32331
+ const lastTs = events.reduce((max, e) => e.ts > max ? e.ts : max, events[0].ts);
32332
+ return {
32333
+ session_id: sessionId,
32334
+ first_ts: firstTs,
32335
+ last_ts: lastTs,
32336
+ noise_removed_pct: m.noise_removed_pct,
32337
+ first_call_resolution_rate: m.first_call_resolution_rate,
32338
+ prevention_score: m.prevention_score,
32339
+ reasoning_quality_multiplier: m.reasoning_quality_multiplier,
32340
+ context_density: m.context_density,
32341
+ attention_multiplier: m.attention_multiplier,
32342
+ turns_saved: m.turns_saved,
32343
+ total_events: m.total_events
32344
+ };
32345
+ }).sort((a, b) => a.first_ts.localeCompare(b.first_ts));
32346
+ return c.json({
32347
+ data: trend,
32348
+ total: trend.length,
32349
+ _meta: { latency_ms: Math.round((performance.now() - start) * 100) / 100 }
32350
+ });
32351
+ });
32352
+ return app;
32353
+ }
32354
+ var GRAPH_MECHANISMS, SHELL_MECHANISMS, SAFETY_MECHANISMS;
32355
+ var init_reasoning_quality = __esm({
32356
+ "src/server/routes/reasoning-quality.ts"() {
32357
+ "use strict";
32358
+ init_token_flow();
32359
+ init_session_history();
32360
+ GRAPH_MECHANISMS = /* @__PURE__ */ new Set(["graph_query", "file_read"]);
32361
+ SHELL_MECHANISMS = /* @__PURE__ */ new Set(["shell_compression"]);
32362
+ SAFETY_MECHANISMS = /* @__PURE__ */ new Set(["behavior_automation"]);
32363
+ }
32364
+ });
32365
+
31296
32366
  // src/server/http.ts
31297
32367
  var http_exports = {};
31298
32368
  __export(http_exports, {
31299
32369
  startDashboardServer: () => startDashboardServer
31300
32370
  });
31301
- import { existsSync as existsSync55, readFileSync as readFileSync51, unlinkSync as unlinkSync8, writeFileSync as writeFileSync32 } from "fs";
32371
+ import { existsSync as existsSync56, readFileSync as readFileSync52, unlinkSync as unlinkSync8, writeFileSync as writeFileSync32 } from "fs";
31302
32372
  import { createServer as createServer3 } from "net";
31303
- import { dirname as dirname7, join as join60 } from "path";
31304
- import { fileURLToPath } from "url";
32373
+ import { dirname as dirname7, join as join61 } from "path";
32374
+ import { fileURLToPath as fileURLToPath2 } from "url";
31305
32375
  import { serve } from "@hono/node-server";
31306
32376
  import { serveStatic } from "@hono/node-server/serve-static";
31307
- import { Hono as Hono7 } from "hono";
32377
+ import { Hono as Hono8 } from "hono";
31308
32378
  async function findAvailablePort() {
31309
32379
  for (let port = PORT_START; port <= PORT_END; port++) {
31310
32380
  const available = await new Promise((resolve3) => {
@@ -31328,7 +32398,7 @@ async function startDashboardServer(opts) {
31328
32398
  return null;
31329
32399
  }
31330
32400
  opts.system.dashboardPort = port;
31331
- const app = new Hono7();
32401
+ const app = new Hono8();
31332
32402
  app.use("*", corsMiddleware);
31333
32403
  app.use("*", cacheMiddleware);
31334
32404
  app.use("*", timingMiddleware);
@@ -31343,10 +32413,13 @@ async function startDashboardServer(opts) {
31343
32413
  if (opts.tokenFlow) {
31344
32414
  app.route("/api/token-flow", createTokenFlowRoutes(opts.tokenFlow));
31345
32415
  }
31346
- const distDir = join60(dirname7(fileURLToPath(import.meta.url)), "ui");
31347
- const spaIndex = join60(distDir, "index.html");
31348
- if (existsSync55(spaIndex)) {
31349
- const spaHtml = readFileSync51(spaIndex, "utf-8");
32416
+ if (opts.reasoningQuality) {
32417
+ app.route("/api/reasoning-quality", createReasoningQualityRoutes(opts.reasoningQuality));
32418
+ }
32419
+ const distDir = join61(dirname7(fileURLToPath2(import.meta.url)), "ui");
32420
+ const spaIndex = join61(distDir, "index.html");
32421
+ if (existsSync56(spaIndex)) {
32422
+ const spaHtml = readFileSync52(spaIndex, "utf-8");
31350
32423
  app.use("*", serveStatic({ root: distDir }));
31351
32424
  app.get("*", (c) => {
31352
32425
  const path7 = c.req.path;
@@ -31375,7 +32448,7 @@ async function startDashboardServer(opts) {
31375
32448
  port,
31376
32449
  hostname: "127.0.0.1"
31377
32450
  });
31378
- const serverJsonPath = join60(opts.stateDir, "server.json");
32451
+ const serverJsonPath = join61(opts.stateDir, "server.json");
31379
32452
  const serverInfo = {
31380
32453
  port,
31381
32454
  pid: process.pid,
@@ -31409,6 +32482,7 @@ var init_http = __esm({
31409
32482
  init_system();
31410
32483
  init_temporal();
31411
32484
  init_token_flow2();
32485
+ init_reasoning_quality();
31412
32486
  PORT_START = 7600;
31413
32487
  PORT_END = 7700;
31414
32488
  }
@@ -31776,13 +32850,13 @@ var proxy_exports = {};
31776
32850
  __export(proxy_exports, {
31777
32851
  startProxy: () => startProxy
31778
32852
  });
31779
- import { existsSync as existsSync56, mkdirSync as mkdirSync34, readFileSync as readFileSync52, readdirSync as readdirSync12 } from "fs";
31780
- import { join as join61 } from "path";
32853
+ import { existsSync as existsSync57, mkdirSync as mkdirSync34, readFileSync as readFileSync53, readdirSync as readdirSync13, writeFileSync as fsWriteFileSync } from "fs";
32854
+ import { join as join62 } from "path";
31781
32855
  async function getProxyFactStore(unerrDir) {
31782
32856
  if (proxyFactStore !== void 0) return proxyFactStore;
31783
32857
  try {
31784
32858
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
31785
- const cwd = join61(unerrDir, "..");
32859
+ const cwd = join62(unerrDir, "..");
31786
32860
  proxyFactStore = await TemporalFactStore2.create(cwd);
31787
32861
  return proxyFactStore;
31788
32862
  } catch {
@@ -31860,15 +32934,32 @@ async function handleRecallFactsProxy(args, unerrDir) {
31860
32934
  };
31861
32935
  }
31862
32936
  }
32937
+ function migrateAgentPermissions(cwd) {
32938
+ try {
32939
+ const settingsPath = join62(cwd, ".claude", "settings.json");
32940
+ if (!existsSync57(settingsPath)) return;
32941
+ const raw = readFileSync53(settingsPath, "utf-8");
32942
+ const settings = JSON.parse(raw);
32943
+ const deny = settings?.permissions?.deny;
32944
+ if (!Array.isArray(deny)) return;
32945
+ const readIdx = deny.indexOf("Read");
32946
+ if (readIdx < 0) return;
32947
+ deny.splice(readIdx, 1);
32948
+ fsWriteFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32949
+ process.stderr.write("[unerr] Migrated permissions: removed Read from deny list (required for Edit workflow)\n");
32950
+ } catch {
32951
+ }
32952
+ }
31863
32953
  async function startProxy(opts = {}) {
31864
32954
  const stats = createSessionStats(true);
31865
32955
  const startup = new StartupRenderer();
31866
32956
  startup.mount();
32957
+ migrateAgentPermissions(process.cwd());
31867
32958
  const lifecycle = createLifecycleActor(process.cwd());
31868
32959
  lifecycle.send({ type: "START_DETECT" });
31869
32960
  startup.setLocalMode(true);
31870
- const stateDir = join61(process.cwd(), ".unerr", "state");
31871
- if (!existsSync56(stateDir)) {
32961
+ const stateDir = join62(process.cwd(), ".unerr", "state");
32962
+ if (!existsSync57(stateDir)) {
31872
32963
  mkdirSync34(stateDir, { recursive: true });
31873
32964
  }
31874
32965
  const pidLock = new PidLock(stateDir);
@@ -31886,7 +32977,7 @@ async function startProxy(opts = {}) {
31886
32977
  startupLog.step(
31887
32978
  `PID ${process.pid} ${startupLog.fmt.muted(`\xB7 health localhost:${lockResult.healthPort}`)}`
31888
32979
  );
31889
- const ledgerDir = join61(process.cwd(), ".unerr", "ledger");
32980
+ const ledgerDir = join62(process.cwd(), ".unerr", "ledger");
31890
32981
  const previousSession = detectSessionResume(stateDir, ledgerDir);
31891
32982
  if (previousSession) {
31892
32983
  stats.isResumedSession = true;
@@ -31901,17 +32992,17 @@ async function startProxy(opts = {}) {
31901
32992
  if (opts.repoId) {
31902
32993
  repoIds = [opts.repoId];
31903
32994
  } else {
31904
- const configPath = join61(process.cwd(), ".unerr", "config.json");
31905
- if (existsSync56(configPath)) {
32995
+ const configPath = join62(process.cwd(), ".unerr", "config.json");
32996
+ if (existsSync57(configPath)) {
31906
32997
  try {
31907
- const config = JSON.parse(readFileSync52(configPath, "utf-8"));
32998
+ const config = JSON.parse(readFileSync53(configPath, "utf-8"));
31908
32999
  if (config.repoId) repoIds = [config.repoId];
31909
33000
  } catch {
31910
33001
  }
31911
33002
  }
31912
- const manifestsDir = join61(process.cwd(), ".unerr", "manifests");
31913
- if (repoIds.length === 0 && existsSync56(manifestsDir)) {
31914
- repoIds = readdirSync12(manifestsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
33003
+ const manifestsDir = join62(process.cwd(), ".unerr", "manifests");
33004
+ if (repoIds.length === 0 && existsSync57(manifestsDir)) {
33005
+ repoIds = readdirSync13(manifestsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
31915
33006
  }
31916
33007
  }
31917
33008
  if (repoIds.length === 0) {
@@ -31991,13 +33082,10 @@ async function startProxy(opts = {}) {
31991
33082
  startupLog.perf(
31992
33083
  `${startupLog.fmt.cyan("Persistent graph")} ${startupLog.fmt.muted("\u2014 zero recomputation, all intelligence preserved")}`
31993
33084
  );
31994
- const { shouldReindex: shouldReindex2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
31995
- if (shouldReindex2(projectRoot)) {
31996
- needsBackgroundIndex = true;
31997
- startupLog.step(
31998
- `${startupLog.fmt.muted("Source changes detected \u2014 incremental update after MCP ready")}`
31999
- );
32000
- }
33085
+ needsBackgroundIndex = true;
33086
+ startupLog.step(
33087
+ `${startupLog.fmt.muted("Background reindex will refresh graph data after MCP ready")}`
33088
+ );
32001
33089
  } else {
32002
33090
  const { loadLocalSnapshot: loadLocalSnapshot2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
32003
33091
  const snapshotStart = Date.now();
@@ -32037,6 +33125,32 @@ async function startProxy(opts = {}) {
32037
33125
  startupLog.done(
32038
33126
  `Migrated snapshot to persistent graph ${startupLog.fmt.muted(`\u2192 ${dbPath}`)}`
32039
33127
  );
33128
+ try {
33129
+ const migrationUnerrDir = join62(process.cwd(), ".unerr");
33130
+ const factStoreForMigration = await getProxyFactStore(migrationUnerrDir);
33131
+ if (factStoreForMigration) {
33132
+ const { detectLocalConventions: detectLocalConventions2 } = await Promise.resolve().then(() => (init_local_convention_detector(), local_convention_detector_exports));
33133
+ const { generateFromConventions: generateFromConventions2, runFactGenerationPipeline: runFactGenerationPipeline2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
33134
+ const detection = await detectLocalConventions2(localGraph.db);
33135
+ if (detection.conventions.length > 0) {
33136
+ const convResult = await generateFromConventions2(factStoreForMigration, detection.conventions);
33137
+ if (convResult.created > 0 || convResult.reinforced > 0) {
33138
+ log21.info(
33139
+ `Fact generator: ${convResult.created} convention facts created, ${convResult.reinforced} reinforced`
33140
+ );
33141
+ }
33142
+ }
33143
+ const pipelineResults = await runFactGenerationPipeline2(factStoreForMigration, migrationUnerrDir);
33144
+ for (const r of pipelineResults) {
33145
+ if (r.created > 0 || r.reinforced > 0) {
33146
+ log21.info(
33147
+ `Fact generator [${r.source}]: ${r.created} created, ${r.reinforced} reinforced`
33148
+ );
33149
+ }
33150
+ }
33151
+ }
33152
+ } catch {
33153
+ }
32040
33154
  } else {
32041
33155
  needsBackgroundIndex = true;
32042
33156
  startupLog.step(
@@ -32104,7 +33218,7 @@ async function startProxy(opts = {}) {
32104
33218
  try {
32105
33219
  const { generateSessionResume: generateSessionResume2 } = await Promise.resolve().then(() => (init_session_resume(), session_resume_exports));
32106
33220
  const { ShadowLedger: ResumeLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32107
- const resumeLedger = new ResumeLedger(join61(process.cwd(), ".unerr"));
33221
+ const resumeLedger = new ResumeLedger(join62(process.cwd(), ".unerr"));
32108
33222
  const ledgerEntries = resumeLedger.getRecentEntries(50);
32109
33223
  const resumeCtx = generateSessionResume2(ledgerEntries);
32110
33224
  if (resumeCtx) {
@@ -32124,7 +33238,7 @@ async function startProxy(opts = {}) {
32124
33238
  const { createDurabilityScorer: createDurabilityScorer2 } = await Promise.resolve().then(() => (init_durability_scorer(), durability_scorer_exports));
32125
33239
  const durabilityScorer = createDurabilityScorer2();
32126
33240
  const { ShadowLedger: DurLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32127
- const durLedger = new DurLedger(join61(process.cwd(), ".unerr"));
33241
+ const durLedger = new DurLedger(join62(process.cwd(), ".unerr"));
32128
33242
  const durEntries = durLedger.getRecentEntries(200);
32129
33243
  if (durEntries.length > 0) {
32130
33244
  durabilityScorer.computeScores(durEntries);
@@ -32144,7 +33258,7 @@ async function startProxy(opts = {}) {
32144
33258
  try {
32145
33259
  const { detectInstableEntities: detectInstableEntities2 } = await Promise.resolve().then(() => (init_negative_knowledge(), negative_knowledge_exports));
32146
33260
  const { ShadowLedger: NkLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32147
- const nkLedger = new NkLedger(join61(process.cwd(), ".unerr"));
33261
+ const nkLedger = new NkLedger(join62(process.cwd(), ".unerr"));
32148
33262
  const nkEntries = nkLedger.getRecentEntries(200);
32149
33263
  if (nkEntries.length > 0) {
32150
33264
  const antiPatterns = detectInstableEntities2(nkEntries);
@@ -32166,7 +33280,7 @@ async function startProxy(opts = {}) {
32166
33280
  try {
32167
33281
  const { CausalBridge: CausalBridge2 } = await Promise.resolve().then(() => (init_causal_bridge(), causal_bridge_exports));
32168
33282
  const causalBridge = new CausalBridge2(
32169
- join61(process.cwd(), ".unerr"),
33283
+ join62(process.cwd(), ".unerr"),
32170
33284
  process.cwd()
32171
33285
  );
32172
33286
  router2.setCausalBridge(causalBridge);
@@ -32176,7 +33290,7 @@ async function startProxy(opts = {}) {
32176
33290
  try {
32177
33291
  const { learnConventions: learnConventions2 } = await Promise.resolve().then(() => (init_convention_learner(), convention_learner_exports));
32178
33292
  const { ShadowLedger: ConvLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32179
- const convLedger = new ConvLedger(join61(process.cwd(), ".unerr"));
33293
+ const convLedger = new ConvLedger(join62(process.cwd(), ".unerr"));
32180
33294
  const convEntries = convLedger.getRecentEntries(100);
32181
33295
  if (convEntries.length > 0) {
32182
33296
  const learned = learnConventions2(convEntries);
@@ -32199,7 +33313,7 @@ async function startProxy(opts = {}) {
32199
33313
  try {
32200
33314
  const { computePromptDurabilityProfiles: computePromptDurabilityProfiles2 } = await Promise.resolve().then(() => (init_prompt_durability(), prompt_durability_exports));
32201
33315
  const { ShadowLedger: DurProfLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32202
- const durProfLedger = new DurProfLedger(join61(process.cwd(), ".unerr"));
33316
+ const durProfLedger = new DurProfLedger(join62(process.cwd(), ".unerr"));
32203
33317
  const durProfEntries = durProfLedger.getRecentEntries(200);
32204
33318
  if (durProfEntries.length > 0) {
32205
33319
  const profiles = computePromptDurabilityProfiles2(durProfEntries);
@@ -32219,7 +33333,7 @@ async function startProxy(opts = {}) {
32219
33333
  }
32220
33334
  try {
32221
33335
  const { createContextLedger: createContextLedger2 } = await Promise.resolve().then(() => (init_context_ledger(), context_ledger_exports));
32222
- const contextLedger = createContextLedger2(join61(process.cwd(), ".unerr"));
33336
+ const contextLedger = createContextLedger2(join62(process.cwd(), ".unerr"));
32223
33337
  contextLedger.load();
32224
33338
  contextLedger.prune();
32225
33339
  router2.setContextLedger(contextLedger);
@@ -32308,7 +33422,7 @@ async function startProxy(opts = {}) {
32308
33422
  }));
32309
33423
  const { ShadowLedger: ShadowLedger2 } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32310
33424
  const { IntentCorrelator: IntentCorrelator2 } = await Promise.resolve().then(() => (init_intent_correlator(), intent_correlator_exports));
32311
- const unerrDirForLedger = join61(process.cwd(), ".unerr");
33425
+ const unerrDirForLedger = join62(process.cwd(), ".unerr");
32312
33426
  const shadowLedger2 = new ShadowLedger2(unerrDirForLedger);
32313
33427
  const intentCorrelator = new IntentCorrelator2(unerrDirForLedger);
32314
33428
  log21.info(
@@ -32322,7 +33436,7 @@ async function startProxy(opts = {}) {
32322
33436
  process.env.UNERR_SESSION_ID = shadowLedger2.getSessionId();
32323
33437
  try {
32324
33438
  const { writeFileSync: writeFileSync36 } = await import("fs");
32325
- writeFileSync36(join61(unerrDirForLedger, "state", "session.id"), shadowLedger2.getSessionId(), "utf-8");
33439
+ writeFileSync36(join62(unerrDirForLedger, "state", "session.id"), shadowLedger2.getSessionId(), "utf-8");
32326
33440
  } catch {
32327
33441
  }
32328
33442
  router2.setTokenFlow(tokenFlowWriter2);
@@ -32661,8 +33775,9 @@ async function startProxy(opts = {}) {
32661
33775
  lifecycle.send({ type: "INDEX_COMPLETE" });
32662
33776
  lifecycle.send({ type: "MCP_READY" });
32663
33777
  const { TransportMux: TransportMux2 } = await Promise.resolve().then(() => (init_transport_mux(), transport_mux_exports));
32664
- const sockPath = join61(stateDir, "proxy.sock");
33778
+ const sockPath = join62(stateDir, "proxy.sock");
32665
33779
  const transportMux = new TransportMux2(sockPath);
33780
+ const agentNameByClient = /* @__PURE__ */ new Map();
32666
33781
  transportMux.setCustomHttpHandler("/commit-context", (_url) => {
32667
33782
  const { getCommitTrailers: getCommitTrailers2 } = (init_git_trailers(), __toCommonJS(git_trailers_exports));
32668
33783
  const branch = branchContext?.currentBranch ?? "unknown";
@@ -32672,6 +33787,10 @@ async function startProxy(opts = {}) {
32672
33787
  });
32673
33788
  transportMux.setHandler(async (clientId, message) => {
32674
33789
  if (message.method === "initialize") {
33790
+ const clientName = message.params?.clientInfo?.name;
33791
+ if (clientName) {
33792
+ agentNameByClient.set(clientId, clientName);
33793
+ }
32675
33794
  return {
32676
33795
  jsonrpc: "2.0",
32677
33796
  id: message.id,
@@ -32700,6 +33819,88 @@ async function startProxy(opts = {}) {
32700
33819
  };
32701
33820
  }
32702
33821
  const { name, arguments: toolArgs = {} } = params;
33822
+ toolUsageTracker2.record(name);
33823
+ if (name === "record_fact") {
33824
+ const factResult = await handleRecordFactProxy(toolArgs, unerrDirForLedger, shadowLedger2);
33825
+ return { jsonrpc: "2.0", result: factResult };
33826
+ }
33827
+ if (name === "recall_facts") {
33828
+ const factResult = await handleRecallFactsProxy(toolArgs, unerrDirForLedger);
33829
+ return { jsonrpc: "2.0", result: factResult };
33830
+ }
33831
+ if (name === "unerr_mark_working") {
33832
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33833
+ const headSha2 = branchContext?.headSha ?? "";
33834
+ const snapshot = workingSnapshotStore.create({
33835
+ commitSha: headSha2,
33836
+ reason: toolArgs.reason ?? "manual mark",
33837
+ branch: branch2,
33838
+ timelineBranch: workingSnapshotStore.getTimelineBranch(),
33839
+ sessionId: shadowLedger2.getSessionId()
33840
+ });
33841
+ shadowLedger2.record(name, toolArgs, { snapshot_id: snapshot.id, commit: headSha2 }, branch2, headSha2);
33842
+ return {
33843
+ jsonrpc: "2.0",
33844
+ result: {
33845
+ content: [{ type: "text", text: stringifyMcpToolJson({ snapshot_id: snapshot.id, commit_sha: snapshot.commitSha, reason: snapshot.reason, timestamp: snapshot.timestamp, branch: snapshot.branch }) }],
33846
+ _meta: { source: "local", latency_ms: 0, format: "json" }
33847
+ }
33848
+ };
33849
+ }
33850
+ if (name === "unerr_revert_to_working_state") {
33851
+ const { revertToWorkingState: revertToWorkingState2 } = await Promise.resolve().then(() => (init_rewind_engine(), rewind_engine_exports));
33852
+ const revertResult = await revertToWorkingState2({
33853
+ snapshotId: toolArgs.snapshot_id ?? "latest",
33854
+ cwd: process.cwd(),
33855
+ unerrDir: unerrDirForLedger,
33856
+ graph: graphForRouter,
33857
+ ledger: shadowLedger2,
33858
+ snapshotStore: workingSnapshotStore,
33859
+ dryRun: toolArgs.dry_run ?? false
33860
+ });
33861
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33862
+ const headSha2 = branchContext?.headSha ?? "";
33863
+ shadowLedger2.record(name, toolArgs, { success: revertResult.success, snapshot_id: revertResult.snapshot?.id, files_restored: revertResult.rewindResult?.filesRestored.length ?? 0 }, branch2, headSha2);
33864
+ return {
33865
+ jsonrpc: "2.0",
33866
+ result: {
33867
+ content: [{ type: "text", text: stringifyMcpToolJson(revertResult) }],
33868
+ _meta: { source: "local", latency_ms: 0, format: "json" }
33869
+ }
33870
+ };
33871
+ }
33872
+ if (name === "unerr_get_timeline") {
33873
+ const { getTimeline: getTimeline2 } = await Promise.resolve().then(() => (init_timeline(), timeline_exports));
33874
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33875
+ const timelineBranch = workingSnapshotStore.getTimelineBranch();
33876
+ const timeline = getTimeline2(shadowLedger2, branch2, timelineBranch, {
33877
+ branch: toolArgs.branch,
33878
+ limit: toolArgs.limit,
33879
+ tool: toolArgs.tool,
33880
+ rootsOnly: toolArgs.roots_only
33881
+ });
33882
+ return {
33883
+ jsonrpc: "2.0",
33884
+ result: {
33885
+ content: [{ type: "text", text: stringifyMcpToolJson(timeline) }],
33886
+ _meta: { source: "local", latency_ms: 0, format: "json" }
33887
+ }
33888
+ };
33889
+ }
33890
+ if (localGraph) {
33891
+ const { handleDeepDiveTool: handleDeepDiveTool2 } = await Promise.resolve().then(() => (init_deep_dive_tools(), deep_dive_tools_exports));
33892
+ const deepDiveResult = await handleDeepDiveTool2(name, toolArgs, localGraph);
33893
+ if (deepDiveResult) {
33894
+ recordToolCall(stats);
33895
+ recordLatency(stats.latency, 0);
33896
+ pidLock.recordToolCall();
33897
+ if (stats.localMode) recordGraphQuery(stats.localMode, name);
33898
+ const branch2 = branchContext?.currentBranch ?? "unknown";
33899
+ const headSha2 = branchContext?.headSha ?? "";
33900
+ shadowLedger2.record(name, toolArgs, { tool: name, source: "local", client: clientId }, branch2, headSha2);
33901
+ return { jsonrpc: "2.0", result: deepDiveResult };
33902
+ }
33903
+ }
32703
33904
  const result = await router2.execute(name, toolArgs);
32704
33905
  recordToolCall(stats);
32705
33906
  recordLatency(stats.latency, result._meta.latency_ms);
@@ -32772,7 +33973,7 @@ async function startProxy(opts = {}) {
32772
33973
  try {
32773
33974
  const { DriftTracker: DriftTracker2 } = await Promise.resolve().then(() => (init_drift_tracker(), drift_tracker_exports));
32774
33975
  const { FileHashManager: FileHashManager2 } = await Promise.resolve().then(() => (init_file_hash_state(), file_hash_state_exports));
32775
- const unerrDir = join61(process.cwd(), ".unerr");
33976
+ const unerrDir = join62(process.cwd(), ".unerr");
32776
33977
  const fileHashManager = new FileHashManager2(unerrDir);
32777
33978
  _driftTracker = new DriftTracker2(
32778
33979
  { projectRoot: process.cwd(), repoId: repoIds[0], unerrDir },
@@ -32917,6 +34118,32 @@ async function startProxy(opts = {}) {
32917
34118
  `Post-index DriftTracker init failed: ${err instanceof Error ? err.message : String(err)}`
32918
34119
  );
32919
34120
  });
34121
+ try {
34122
+ const factStoreForGen = await getProxyFactStore(unerrDirForLedger);
34123
+ if (factStoreForGen) {
34124
+ const { detectLocalConventions: detectLocalConventions2 } = await Promise.resolve().then(() => (init_local_convention_detector(), local_convention_detector_exports));
34125
+ const { generateFromConventions: generateFromConventions2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34126
+ const detection = await detectLocalConventions2(localGraph.db);
34127
+ if (detection.conventions.length > 0) {
34128
+ const convResult = await generateFromConventions2(factStoreForGen, detection.conventions);
34129
+ if (convResult.created > 0 || convResult.reinforced > 0) {
34130
+ log21.info(
34131
+ `Fact generator: ${convResult.created} convention facts created, ${convResult.reinforced} reinforced`
34132
+ );
34133
+ }
34134
+ }
34135
+ const { runFactGenerationPipeline: runFactGenerationPipeline2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34136
+ const pipelineResults = await runFactGenerationPipeline2(factStoreForGen, unerrDirForLedger);
34137
+ for (const r of pipelineResults) {
34138
+ if (r.created > 0 || r.reinforced > 0) {
34139
+ log21.info(
34140
+ `Fact generator [${r.source}]: ${r.created} created, ${r.reinforced} reinforced`
34141
+ );
34142
+ }
34143
+ }
34144
+ }
34145
+ } catch {
34146
+ }
32920
34147
  },
32921
34148
  // onError
32922
34149
  (err) => {
@@ -32940,7 +34167,7 @@ async function startProxy(opts = {}) {
32940
34167
  }
32941
34168
  const { WorkspaceManifest: WorkspaceManifest2 } = await Promise.resolve().then(() => (init_workspace_manifest(), workspace_manifest_exports));
32942
34169
  const workspaceManifest = repoIds[0] ? new WorkspaceManifest2(
32943
- join61(process.cwd(), ".unerr"),
34170
+ join62(process.cwd(), ".unerr"),
32944
34171
  repoIds[0],
32945
34172
  shadowLedger2.getSessionId()
32946
34173
  ) : null;
@@ -33025,7 +34252,7 @@ async function startProxy(opts = {}) {
33025
34252
  if (proxyMode === "parse" && parseIndex) {
33026
34253
  try {
33027
34254
  const { extractEntitiesFromSource: extractEntitiesFromSource2 } = await Promise.resolve().then(() => (init_auto_bootstrap(), auto_bootstrap_exports));
33028
- const allFiles = readdirSync12(process.cwd(), {
34255
+ const allFiles = readdirSync13(process.cwd(), {
33029
34256
  recursive: true,
33030
34257
  encoding: "utf-8"
33031
34258
  });
@@ -33039,7 +34266,7 @@ async function startProxy(opts = {}) {
33039
34266
  });
33040
34267
  for (const file of sourceFiles.slice(0, 500)) {
33041
34268
  try {
33042
- const content = readFileSync52(join61(process.cwd(), file), "utf-8");
34269
+ const content = readFileSync53(join62(process.cwd(), file), "utf-8");
33043
34270
  const entities = extractEntitiesFromSource2(file, content);
33044
34271
  parseIndex.addEntities(entities);
33045
34272
  } catch {
@@ -33101,7 +34328,7 @@ async function startProxy(opts = {}) {
33101
34328
  }
33102
34329
  const { writeFileSync: writeStatsFile } = await import("fs");
33103
34330
  const { computePercentiles: computePercentiles2 } = await Promise.resolve().then(() => (init_session_stats(), session_stats_exports));
33104
- const statsSnapshotPath = join61(stateDir, "session_stats.json");
34331
+ const statsSnapshotPath = join62(stateDir, "session_stats.json");
33105
34332
  const statsSnapshotInterval = setInterval(() => {
33106
34333
  try {
33107
34334
  const total = stats.toolCallsLocal;
@@ -33139,7 +34366,7 @@ async function startProxy(opts = {}) {
33139
34366
  const { startDashboardServer: startDashboardServer2 } = await Promise.resolve().then(() => (init_http(), http_exports));
33140
34367
  const { detectIde: detectIdeDashboard } = await Promise.resolve().then(() => (init_detect(), detect_exports));
33141
34368
  const ideType = await detectIdeDashboard(process.cwd());
33142
- const unerrDirForApi = join61(process.cwd(), ".unerr");
34369
+ const unerrDirForApi = join62(process.cwd(), ".unerr");
33143
34370
  dashboardHandle = await startDashboardServer2({
33144
34371
  system: {
33145
34372
  stats,
@@ -33184,12 +34411,24 @@ async function startProxy(opts = {}) {
33184
34411
  stateDir,
33185
34412
  tokenFlow: {
33186
34413
  unerrDir: unerrDirForApi,
33187
- getTokenFlowWriter: () => tokenFlowWriter2
34414
+ getTokenFlowWriter: () => tokenFlowWriter2,
34415
+ getAgentName: (_sessionId) => {
34416
+ const last = [...agentNameByClient.values()].pop();
34417
+ return last ?? server.getClientVersion?.()?.name ?? void 0;
34418
+ }
34419
+ },
34420
+ reasoningQuality: {
34421
+ unerrDir: unerrDirForApi,
34422
+ getTokenFlowWriter: () => tokenFlowWriter2,
34423
+ getAgentName: (_sessionId) => {
34424
+ const last = [...agentNameByClient.values()].pop();
34425
+ return last ?? server.getClientVersion?.()?.name ?? void 0;
34426
+ }
33188
34427
  },
33189
34428
  temporal: await (async () => {
33190
34429
  try {
33191
34430
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
33192
- const { readdirSync: readdirSync14, readFileSync: readFileSync56 } = await import("fs");
34431
+ const { readdirSync: readdirSync15, readFileSync: readFileSync57 } = await import("fs");
33193
34432
  const factStore2 = await TemporalFactStore2.create(
33194
34433
  process.cwd()
33195
34434
  );
@@ -33197,10 +34436,10 @@ async function startProxy(opts = {}) {
33197
34436
  factStore: factStore2,
33198
34437
  loadRecentSessions: (limit) => {
33199
34438
  try {
33200
- const sessDir = join61(unerrDirForApi, "sessions");
33201
- const files = readdirSync14(sessDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
34439
+ const sessDir = join62(unerrDirForApi, "sessions");
34440
+ const files = readdirSync15(sessDir).filter((f) => f.endsWith(".jsonl")).sort().slice(-limit);
33202
34441
  return files.map((f) => {
33203
- const content = readFileSync56(join61(sessDir, f), "utf-8").trim().split("\n").pop();
34442
+ const content = readFileSync57(join62(sessDir, f), "utf-8").trim().split("\n").pop();
33204
34443
  return JSON.parse(content);
33205
34444
  });
33206
34445
  } catch {
@@ -33273,7 +34512,7 @@ async function startProxy(opts = {}) {
33273
34512
  try {
33274
34513
  const { appendSessionHistory: appendSessionHistory2 } = (init_session_history(), __toCommonJS(session_history_exports));
33275
34514
  const topMech = Object.entries(tokenFlowSummary.by_mechanism).sort(([, a], [, b]) => b.tokens_saved - a.tokens_saved)[0];
33276
- appendSessionHistory2(join61(process.cwd(), ".unerr"), {
34515
+ appendSessionHistory2(join62(process.cwd(), ".unerr"), {
33277
34516
  sessionId: shadowLedger2.getSessionId(),
33278
34517
  startedAt: new Date(stats.sessionStartedAt).toISOString(),
33279
34518
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -33285,6 +34524,7 @@ async function startProxy(opts = {}) {
33285
34524
  dollarsSaved: router2.getSessionDollarsSaved(),
33286
34525
  modelId: "unknown",
33287
34526
  entityCount: 0,
34527
+ agentName: agentNameByClient.values().next().value ?? server.getClientVersion?.()?.name ?? void 0,
33288
34528
  tokenFlowSummary: {
33289
34529
  by_mechanism: Object.fromEntries(
33290
34530
  Object.entries(tokenFlowSummary.by_mechanism).map(([k, v]) => [
@@ -33398,7 +34638,7 @@ async function startProxy(opts = {}) {
33398
34638
  try {
33399
34639
  const correctionModule = (init_correction_detector(), __toCommonJS(correction_detector_exports));
33400
34640
  const detectCorrections2 = correctionModule.detectCorrections;
33401
- const ledgerPath = join61(
34641
+ const ledgerPath = join62(
33402
34642
  process.cwd(),
33403
34643
  ".unerr",
33404
34644
  "ledger",
@@ -33416,10 +34656,49 @@ async function startProxy(opts = {}) {
33416
34656
  `[unerr] Learned ${patterns.length} correction pattern${patterns.length !== 1 ? "s" : ""} from this session
33417
34657
  `
33418
34658
  );
34659
+ try {
34660
+ const factStoreForShutdown = await getProxyFactStore(unerrDirForLedger);
34661
+ if (factStoreForShutdown) {
34662
+ const { generateFromNegativeKnowledge: generateFromNegativeKnowledge2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34663
+ const corrections = patterns.map((p, i) => ({
34664
+ id: `correction-${shadowLedger2.getSessionId()}-${i}`,
34665
+ entityKey: p.entity_key,
34666
+ pattern: p.error_type,
34667
+ reason: p.correction_summary,
34668
+ detectedAt: p.last_seen,
34669
+ rewindEntryId: shadowLedger2.getSessionId(),
34670
+ confidence: p.confidence
34671
+ }));
34672
+ const negResult = await generateFromNegativeKnowledge2(factStoreForShutdown, corrections);
34673
+ if (negResult.created > 0) {
34674
+ process.stderr.write(
34675
+ `[unerr] Fact generator: ${negResult.created} negative knowledge facts created
34676
+ `
34677
+ );
34678
+ }
34679
+ }
34680
+ } catch {
34681
+ }
33419
34682
  }
33420
34683
  } catch {
33421
34684
  }
33422
34685
  }
34686
+ try {
34687
+ const factStoreForSession = await getProxyFactStore(unerrDirForLedger);
34688
+ if (factStoreForSession) {
34689
+ const { runFactGenerationPipeline: runFactGenerationPipeline2 } = await Promise.resolve().then(() => (init_fact_generator(), fact_generator_exports));
34690
+ const pipelineResults = await runFactGenerationPipeline2(factStoreForSession, unerrDirForLedger);
34691
+ for (const r of pipelineResults) {
34692
+ if (r.created > 0 || r.reinforced > 0) {
34693
+ process.stderr.write(
34694
+ `[unerr] Fact generator [${r.source}]: ${r.created} created, ${r.reinforced} reinforced
34695
+ `
34696
+ );
34697
+ }
34698
+ }
34699
+ }
34700
+ } catch {
34701
+ }
33423
34702
  if (workspaceManifest) {
33424
34703
  const orphans = intentCorrelator.getPending();
33425
34704
  if (orphans.length > 0) {
@@ -33582,13 +34861,13 @@ __export(session_logger_exports, {
33582
34861
  import { randomUUID as randomUUID2 } from "crypto";
33583
34862
  import {
33584
34863
  appendFileSync as appendFileSync9,
33585
- existsSync as existsSync57,
34864
+ existsSync as existsSync58,
33586
34865
  mkdirSync as mkdirSync35,
33587
- readdirSync as readdirSync13,
34866
+ readdirSync as readdirSync14,
33588
34867
  statSync as statSync11,
33589
34868
  unlinkSync as unlinkSync9
33590
34869
  } from "fs";
33591
- import { join as join62 } from "path";
34870
+ import { join as join63 } from "path";
33592
34871
  import { createConsola as createConsola2 } from "consola";
33593
34872
  function formatTimestamp() {
33594
34873
  const d = /* @__PURE__ */ new Date();
@@ -33596,10 +34875,10 @@ function formatTimestamp() {
33596
34875
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
33597
34876
  }
33598
34877
  function cleanupOldLogs(logsDir) {
33599
- if (!existsSync57(logsDir)) return;
34878
+ if (!existsSync58(logsDir)) return;
33600
34879
  try {
33601
- const files = readdirSync13(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => {
33602
- const fullPath = join62(logsDir, f);
34880
+ const files = readdirSync14(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => {
34881
+ const fullPath = join63(logsDir, f);
33603
34882
  const stat2 = statSync11(fullPath);
33604
34883
  return {
33605
34884
  name: f,
@@ -33616,7 +34895,7 @@ function cleanupOldLogs(logsDir) {
33616
34895
  }
33617
34896
  }
33618
34897
  }
33619
- const remaining = files.filter((f) => existsSync57(f.path));
34898
+ const remaining = files.filter((f) => existsSync58(f.path));
33620
34899
  if (remaining.length > MAX_FILES) {
33621
34900
  for (const file of remaining.slice(MAX_FILES)) {
33622
34901
  try {
@@ -33631,11 +34910,11 @@ function cleanupOldLogs(logsDir) {
33631
34910
  function initSessionLogger(opts = {}) {
33632
34911
  if (_logger) return _logger;
33633
34912
  const cwd = opts.cwd ?? process.cwd();
33634
- const logsDir = join62(cwd, ".unerr", "logs");
34913
+ const logsDir = join63(cwd, ".unerr", "logs");
33635
34914
  mkdirSync35(logsDir, { recursive: true });
33636
34915
  cleanupOldLogs(logsDir);
33637
34916
  const timestamp2 = formatTimestamp();
33638
- _logFilePath = join62(logsDir, `session-${timestamp2}.log`);
34917
+ _logFilePath = join63(logsDir, `session-${timestamp2}.log`);
33639
34918
  const levelNum = opts.level !== void 0 ? Number(opts.level) : void 0;
33640
34919
  const envLevel = process.env.UNERR_LOG_LEVEL;
33641
34920
  const resolvedLevel = levelNum ?? (envLevel ? Number(envLevel) : 3);
@@ -33709,22 +34988,22 @@ __export(setup_wizard_exports, {
33709
34988
  runSetup: () => runSetup
33710
34989
  });
33711
34990
  import { createHash as createHash4 } from "crypto";
33712
- import { existsSync as existsSync58, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "fs";
33713
- import { join as join63 } from "path";
34991
+ import { existsSync as existsSync59, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "fs";
34992
+ import { join as join64 } from "path";
33714
34993
  import * as clack from "@clack/prompts";
33715
34994
  async function runSetup(cwd) {
33716
34995
  const projectDir = cwd ?? process.cwd();
33717
34996
  clack.intro("unerr");
33718
34997
  clack.log.step("Project Setup");
33719
34998
  const repoId = await generateRepoId(projectDir);
33720
- const configDir = join63(projectDir, ".unerr");
34999
+ const configDir = join64(projectDir, ".unerr");
33721
35000
  mkdirSync36(configDir, { recursive: true });
33722
- const configPath = join63(configDir, "config.json");
33723
- const settingsPath = join63(configDir, "settings.json");
35001
+ const configPath = join64(configDir, "config.json");
35002
+ const settingsPath = join64(configDir, "settings.json");
33724
35003
  writeFileSync33(configPath, `${JSON.stringify({ repoId }, null, 2)}
33725
35004
  `);
33726
35005
  let existingSettings = {};
33727
- if (existsSync58(settingsPath)) {
35006
+ if (existsSync59(settingsPath)) {
33728
35007
  try {
33729
35008
  existingSettings = JSON.parse(
33730
35009
  __require("fs").readFileSync(settingsPath, "utf-8")
@@ -34333,13 +35612,13 @@ __export(session_summary_writer_exports, {
34333
35612
  readLastSession: () => readLastSession,
34334
35613
  writeSessionSummary: () => writeSessionSummary
34335
35614
  });
34336
- import { existsSync as existsSync59, mkdirSync as mkdirSync37, appendFileSync as appendFileSync10, writeFileSync as writeFileSync34, readFileSync as readFileSync53 } from "fs";
34337
- import { join as join64 } from "path";
35615
+ import { existsSync as existsSync60, mkdirSync as mkdirSync37, appendFileSync as appendFileSync10, writeFileSync as writeFileSync34, readFileSync as readFileSync54 } from "fs";
35616
+ import { join as join65 } from "path";
34338
35617
  function writeSessionSummary(unerrDir, ctx) {
34339
35618
  if (ctx.entries.length === 0) return null;
34340
35619
  try {
34341
- const sessionsDir = join64(unerrDir, "sessions");
34342
- if (!existsSync59(sessionsDir)) {
35620
+ const sessionsDir = join65(unerrDir, "sessions");
35621
+ if (!existsSync60(sessionsDir)) {
34343
35622
  mkdirSync37(sessionsDir, { recursive: true });
34344
35623
  }
34345
35624
  const sorted = [...ctx.entries].sort(
@@ -34383,7 +35662,7 @@ function writeSessionSummary(unerrDir, ctx) {
34383
35662
  token_estimate: ctx.tokenEstimate,
34384
35663
  branch: last.branch ?? "unknown"
34385
35664
  };
34386
- const filePath = join64(sessionsDir, `${ctx.sessionId}.jsonl`);
35665
+ const filePath = join65(sessionsDir, `${ctx.sessionId}.jsonl`);
34387
35666
  appendFileSync10(filePath, `${JSON.stringify(record)}
34388
35667
  `, "utf-8");
34389
35668
  writeLastSessionPointer(unerrDir, ctx.sessionId, record);
@@ -34398,9 +35677,9 @@ function writeSessionSummary(unerrDir, ctx) {
34398
35677
  }
34399
35678
  function readLastSession(unerrDir) {
34400
35679
  try {
34401
- const pointerPath = join64(unerrDir, "state", "last_session.json");
34402
- if (!existsSync59(pointerPath)) return null;
34403
- const content = readFileSync53(pointerPath, "utf-8");
35680
+ const pointerPath = join65(unerrDir, "state", "last_session.json");
35681
+ if (!existsSync60(pointerPath)) return null;
35682
+ const content = readFileSync54(pointerPath, "utf-8");
34404
35683
  return JSON.parse(content);
34405
35684
  } catch {
34406
35685
  return null;
@@ -34408,11 +35687,11 @@ function readLastSession(unerrDir) {
34408
35687
  }
34409
35688
  function writeLastSessionPointer(unerrDir, sessionId, record) {
34410
35689
  try {
34411
- const stateDir = join64(unerrDir, "state");
34412
- if (!existsSync59(stateDir)) {
35690
+ const stateDir = join65(unerrDir, "state");
35691
+ if (!existsSync60(stateDir)) {
34413
35692
  mkdirSync37(stateDir, { recursive: true });
34414
35693
  }
34415
- const pointerPath = join64(stateDir, "last_session.json");
35694
+ const pointerPath = join65(stateDir, "last_session.json");
34416
35695
  writeFileSync34(pointerPath, JSON.stringify(record, null, 2), "utf-8");
34417
35696
  } catch {
34418
35697
  }
@@ -34610,16 +35889,33 @@ var mcp_server_exports = {};
34610
35889
  __export(mcp_server_exports, {
34611
35890
  startMcpServer: () => startMcpServer
34612
35891
  });
34613
- import { existsSync as existsSync60, mkdirSync as mkdirSync38, readFileSync as readFileSync54, writeFileSync as writeFileSync35 } from "fs";
34614
- import { join as join65 } from "path";
35892
+ import { existsSync as existsSync61, mkdirSync as mkdirSync38, readFileSync as readFileSync55, writeFileSync as writeFileSync35 } from "fs";
35893
+ import { join as join66 } from "path";
35894
+ function migrateAgentPermissions2(cwd) {
35895
+ try {
35896
+ const settingsPath = join66(cwd, ".claude", "settings.json");
35897
+ if (!existsSync61(settingsPath)) return;
35898
+ const raw = readFileSync55(settingsPath, "utf-8");
35899
+ const settings = JSON.parse(raw);
35900
+ const deny = settings?.permissions?.deny;
35901
+ if (!Array.isArray(deny)) return;
35902
+ const readIdx = deny.indexOf("Read");
35903
+ if (readIdx < 0) return;
35904
+ deny.splice(readIdx, 1);
35905
+ writeFileSync35(settingsPath, JSON.stringify(settings, null, 2) + "\n");
35906
+ process.stderr.write("[unerr] Migrated permissions: removed Read from deny list (required for Edit workflow)\n");
35907
+ } catch {
35908
+ }
35909
+ }
34615
35910
  async function startMcpServer(cwd) {
34616
35911
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
34617
35912
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
34618
35913
  const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
34619
- const unerrDir = join65(cwd, ".unerr");
34620
- if (!existsSync60(unerrDir)) {
35914
+ const unerrDir = join66(cwd, ".unerr");
35915
+ if (!existsSync61(unerrDir)) {
34621
35916
  mkdirSync38(unerrDir, { recursive: true });
34622
35917
  }
35918
+ migrateAgentPermissions2(cwd);
34623
35919
  await initTier2Modules(unerrDir);
34624
35920
  const server = new Server(
34625
35921
  { name: "unerr-local", version: "0.1.0" },
@@ -34637,6 +35933,25 @@ async function startMcpServer(cwd) {
34637
35933
  if (name === "recall_facts") {
34638
35934
  return handleRecallFacts(args);
34639
35935
  }
35936
+ const PROXY_ONLY_TOOLS = [
35937
+ "unerr_get_timeline",
35938
+ "unerr_revert_to_working_state",
35939
+ "unerr_mark_working"
35940
+ ];
35941
+ if (PROXY_ONLY_TOOLS.includes(name)) {
35942
+ return {
35943
+ content: [
35944
+ {
35945
+ type: "text",
35946
+ text: JSON.stringify({
35947
+ tool: name,
35948
+ available: false,
35949
+ message: `${name} requires the full unerr proxy (run 'unerr' without --mcp). The timeline/revert subsystem is not available in lightweight MCP mode.`
35950
+ })
35951
+ }
35952
+ ]
35953
+ };
35954
+ }
34640
35955
  if (!graphReady || !router) {
34641
35956
  if (name === "file_read" || name === "file_outline") {
34642
35957
  try {
@@ -34716,25 +36031,6 @@ async function startMcpServer(cwd) {
34716
36031
  ]
34717
36032
  };
34718
36033
  }
34719
- const PROXY_ONLY_TOOLS = [
34720
- "unerr_get_timeline",
34721
- "unerr_revert_to_working_state",
34722
- "unerr_mark_working"
34723
- ];
34724
- if (PROXY_ONLY_TOOLS.includes(name)) {
34725
- return {
34726
- content: [
34727
- {
34728
- type: "text",
34729
- text: JSON.stringify({
34730
- tool: name,
34731
- available: false,
34732
- message: `${name} requires the full unerr proxy (run 'unerr' without --mcp). The timeline/revert subsystem is not available in lightweight MCP mode.`
34733
- })
34734
- }
34735
- ]
34736
- };
34737
- }
34738
36034
  try {
34739
36035
  const result = await router.execute(name, args);
34740
36036
  let text2 = stringifyMcpToolJson(result.content);
@@ -35022,9 +36318,9 @@ async function initTier2Modules(unerrDir) {
35022
36318
  tokenFlowWriter = new TokenFlowWriter2(unerrDir, sessionId);
35023
36319
  process.env.UNERR_SESSION_ID = sessionId;
35024
36320
  try {
35025
- const stateDir = join65(unerrDir, "state");
36321
+ const stateDir = join66(unerrDir, "state");
35026
36322
  mkdirSync38(stateDir, { recursive: true });
35027
- writeFileSync35(join65(stateDir, "session.id"), sessionId, "utf-8");
36323
+ writeFileSync35(join66(stateDir, "session.id"), sessionId, "utf-8");
35028
36324
  } catch {
35029
36325
  }
35030
36326
  log24.info(`Token flow active (session ${sessionId.slice(0, 8)})`);
@@ -35055,7 +36351,7 @@ async function initTier2Modules(unerrDir) {
35055
36351
  }
35056
36352
  try {
35057
36353
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
35058
- const projectRoot = join65(unerrDir, "..");
36354
+ const projectRoot = join66(unerrDir, "..");
35059
36355
  factStore = await TemporalFactStore2.create(projectRoot);
35060
36356
  log24.info("Temporal fact store active (facts.db)");
35061
36357
  } catch (err) {
@@ -35165,8 +36461,8 @@ function wireTier2IntoRouter(r) {
35165
36461
  async function loadIntelligence(cwd) {
35166
36462
  const projectRoot = cwd;
35167
36463
  try {
35168
- const unerrDir = join65(projectRoot, ".unerr");
35169
- if (!existsSync60(unerrDir)) {
36464
+ const unerrDir = join66(projectRoot, ".unerr");
36465
+ if (!existsSync61(unerrDir)) {
35170
36466
  mkdirSync38(unerrDir, { recursive: true });
35171
36467
  }
35172
36468
  const { openPersistentDb: openPersistentDb2 } = await Promise.resolve().then(() => (init_persistent_db(), persistent_db_exports));
@@ -35175,11 +36471,8 @@ async function loadIntelligence(cwd) {
35175
36471
  const localGraph = await CozoGraphStore2.create(db, projectRoot);
35176
36472
  let needsIndex = false;
35177
36473
  if (!isNew && await localGraph.isPopulated()) {
35178
- log24.info("Persistent graph loaded");
35179
- const { shouldReindex: shouldReindex2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
35180
- if (shouldReindex2(projectRoot)) {
35181
- needsIndex = true;
35182
- }
36474
+ log24.info("Persistent graph loaded \u2014 background reindex will refresh entity data");
36475
+ needsIndex = true;
35183
36476
  } else {
35184
36477
  const { loadLocalSnapshot: loadLocalSnapshot2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
35185
36478
  const migrated = await loadLocalSnapshot2(projectRoot, localGraph);
@@ -35188,11 +36481,11 @@ async function loadIntelligence(cwd) {
35188
36481
  await buildSearchIndex2(localGraph.db);
35189
36482
  const { runCommunityDetection: runCommunityDetection2, runConventionDetection: runConventionDetection2 } = await Promise.resolve().then(() => (init_local_indexer(), local_indexer_exports));
35190
36483
  await runCommunityDetection2(localGraph);
35191
- const configPath = join65(projectRoot, ".unerr", "config.json");
36484
+ const configPath = join66(projectRoot, ".unerr", "config.json");
35192
36485
  let repoId = "unknown";
35193
- if (existsSync60(configPath)) {
36486
+ if (existsSync61(configPath)) {
35194
36487
  try {
35195
- const config = JSON.parse(readFileSync54(configPath, "utf-8"));
36488
+ const config = JSON.parse(readFileSync55(configPath, "utf-8"));
35196
36489
  repoId = config.repoId ?? repoId;
35197
36490
  } catch {
35198
36491
  }
@@ -35219,11 +36512,11 @@ async function loadIntelligence(cwd) {
35219
36512
  log24.info("Intelligence graph ready \u2014 tools fully operational");
35220
36513
  if (needsIndex) {
35221
36514
  try {
35222
- const configPath = join65(projectRoot, ".unerr", "config.json");
36515
+ const configPath = join66(projectRoot, ".unerr", "config.json");
35223
36516
  let indexRepoId = "unknown";
35224
- if (existsSync60(configPath)) {
36517
+ if (existsSync61(configPath)) {
35225
36518
  try {
35226
- const cfg = JSON.parse(readFileSync54(configPath, "utf-8"));
36519
+ const cfg = JSON.parse(readFileSync55(configPath, "utf-8"));
35227
36520
  indexRepoId = cfg.repoId ?? indexRepoId;
35228
36521
  } catch {
35229
36522
  }
@@ -35333,8 +36626,8 @@ var init_mcp_server = __esm({
35333
36626
 
35334
36627
  // src/entrypoints/cli.ts
35335
36628
  init_startup_log();
35336
- import { existsSync as existsSync61, readFileSync as readFileSync55 } from "fs";
35337
- import { join as join66 } from "path";
36629
+ import { existsSync as existsSync62, readFileSync as readFileSync56 } from "fs";
36630
+ import { join as join67 } from "path";
35338
36631
  import { Command } from "commander";
35339
36632
 
35340
36633
  // src/commands/branches.ts
@@ -35775,9 +37068,9 @@ function registerDashboardCommand(program2) {
35775
37068
  // src/commands/debug.ts
35776
37069
  init_git();
35777
37070
  import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
35778
- import { arch, homedir as homedir2, platform, release } from "os";
37071
+ import { arch, homedir as homedir3, platform, release } from "os";
35779
37072
  import { join as join11 } from "path";
35780
- var UNERR_DIR = join11(homedir2(), ".unerr");
37073
+ var UNERR_DIR = join11(homedir3(), ".unerr");
35781
37074
  function registerDebugCommand(program2) {
35782
37075
  program2.command("debug").description("Diagnostics dump for support").action(async () => {
35783
37076
  const sections = [];
@@ -35882,9 +37175,9 @@ function registerDebugCommand(program2) {
35882
37175
  const mcpLocations = [
35883
37176
  join11(process.cwd(), ".cursor", "mcp.json"),
35884
37177
  join11(process.cwd(), ".vscode", "mcp.json"),
35885
- join11(homedir2(), ".claude", "claude_desktop_config.json"),
37178
+ join11(homedir3(), ".claude", "claude_desktop_config.json"),
35886
37179
  join11(
35887
- homedir2(),
37180
+ homedir3(),
35888
37181
  "Library",
35889
37182
  "Application Support",
35890
37183
  "Cursor",
@@ -35897,7 +37190,7 @@ function registerDebugCommand(program2) {
35897
37190
  for (const loc of mcpLocations) {
35898
37191
  const exists = existsSync9(loc);
35899
37192
  sections.push(
35900
- ` ${exists ? "[x]" : "[ ]"} ${loc.replace(homedir2(), "~")}`
37193
+ ` ${exists ? "[x]" : "[ ]"} ${loc.replace(homedir3(), "~")}`
35901
37194
  );
35902
37195
  }
35903
37196
  sections.push("");
@@ -36986,6 +38279,10 @@ var COMMAND_HINTS = {
36986
38279
  // JS/TS
36987
38280
  tsc: "error_diagnostic",
36988
38281
  "npx tsc": "error_diagnostic",
38282
+ "pnpm run typecheck": "error_diagnostic",
38283
+ "npm run typecheck": "error_diagnostic",
38284
+ "yarn typecheck": "error_diagnostic",
38285
+ "pnpm typecheck": "error_diagnostic",
36989
38286
  eslint: "error_diagnostic",
36990
38287
  "npx eslint": "error_diagnostic",
36991
38288
  "biome check": "error_diagnostic",
@@ -36996,6 +38293,16 @@ var COMMAND_HINTS = {
36996
38293
  jshint: "error_diagnostic",
36997
38294
  "deno lint": "error_diagnostic",
36998
38295
  "bun lint": "error_diagnostic",
38296
+ "pnpm run lint": "error_diagnostic",
38297
+ "npm run lint": "error_diagnostic",
38298
+ "yarn lint": "error_diagnostic",
38299
+ "pnpm lint": "error_diagnostic",
38300
+ "pnpm run lint:fix": "error_diagnostic",
38301
+ "npm run lint:fix": "error_diagnostic",
38302
+ "yarn lint:fix": "error_diagnostic",
38303
+ "pnpm run check": "error_diagnostic",
38304
+ "npm run check": "error_diagnostic",
38305
+ "yarn check": "error_diagnostic",
36999
38306
  // Python
37000
38307
  mypy: "error_diagnostic",
37001
38308
  pyright: "error_diagnostic",
@@ -37324,19 +38631,19 @@ async function buildShellDiffRiskMap(graph, stdout) {
37324
38631
  }
37325
38632
  async function tryLoadGraphForShellBoost(cwd) {
37326
38633
  try {
37327
- const { existsSync: existsSync62, readFileSync: readFileSync56 } = await import("fs");
37328
- const { join: join67 } = await import("path");
38634
+ const { existsSync: existsSync63, readFileSync: readFileSync57 } = await import("fs");
38635
+ const { join: join68 } = await import("path");
37329
38636
  const { gunzipSync: gunzipSync3 } = await import("zlib");
37330
38637
  const { unpack } = await import("msgpackr");
37331
- const snapshotsDir = join67(cwd, ".unerr", "snapshots");
37332
- let snapshotPath2 = join67(snapshotsDir, "graph.msgpack.gz");
37333
- if (!existsSync62(snapshotPath2)) {
37334
- snapshotPath2 = join67(snapshotsDir, "graph.msgpack");
38638
+ const snapshotsDir = join68(cwd, ".unerr", "snapshots");
38639
+ let snapshotPath2 = join68(snapshotsDir, "graph.msgpack.gz");
38640
+ if (!existsSync63(snapshotPath2)) {
38641
+ snapshotPath2 = join68(snapshotsDir, "graph.msgpack");
37335
38642
  }
37336
- if (!existsSync62(snapshotPath2)) return null;
38643
+ if (!existsSync63(snapshotPath2)) return null;
37337
38644
  const { default: CozoDbConstructor } = await import("cozo-node");
37338
38645
  const { CozoGraphStore: CozoGraphStore2 } = await Promise.resolve().then(() => (init_local_graph(), local_graph_exports));
37339
- const raw = readFileSync56(snapshotPath2);
38646
+ const raw = readFileSync57(snapshotPath2);
37340
38647
  let buffer;
37341
38648
  try {
37342
38649
  buffer = gunzipSync3(raw);
@@ -40257,6 +41564,25 @@ async function runExecMain(argv) {
40257
41564
  if (combined.length > 0 && !combined.endsWith("\n")) process.stdout.write("\n");
40258
41565
  return exitCode;
40259
41566
  }
41567
+ const TEE_RAW_LINE_LIMIT = 150;
41568
+ if (/\.unerr\/tee\/[^\s]*\.txt/.test(cmd)) {
41569
+ const lines = combined.split("\n");
41570
+ if (lines.length > TEE_RAW_LINE_LIMIT) {
41571
+ const truncated = lines.slice(0, TEE_RAW_LINE_LIMIT).join("\n");
41572
+ process.stdout.write(truncated);
41573
+ process.stdout.write(
41574
+ `
41575
+
41576
+ [unerr] Raw output truncated to ${TEE_RAW_LINE_LIMIT}/${lines.length} lines to prevent context overload. Use filtering (grep/head/tail) for specific sections.
41577
+ `
41578
+ );
41579
+ } else {
41580
+ process.stdout.write(combined);
41581
+ if (combined.length > 0 && !combined.endsWith("\n")) process.stdout.write("\n");
41582
+ }
41583
+ appendExecNudge();
41584
+ return exitCode;
41585
+ }
40260
41586
  try {
40261
41587
  const out = await compressShellOutput(cmd, combined, {
40262
41588
  cwd: process.cwd(),
@@ -40274,8 +41600,7 @@ async function runExecMain(argv) {
40274
41600
  }
40275
41601
  appendExecNudge();
40276
41602
  if (exitCode !== 0) {
40277
- process.stderr.write(`[unerr:exec] command exited with code ${exitCode}: ${cmd.slice(0, 120)}
40278
- `);
41603
+ startupLog.warn(`command exited with code ${exitCode}: ${cmd.slice(0, 120)}`);
40279
41604
  }
40280
41605
  return exitCode;
40281
41606
  }
@@ -40554,12 +41879,17 @@ var preReadHandler = (normalized) => {
40554
41879
  const input = normalized.toolInput;
40555
41880
  const filePath = extractFilePath2(input);
40556
41881
  if (!filePath) return passthrough();
41882
+ const hasOffset = input.offset !== void 0;
41883
+ const hasLimit = input.limit !== void 0;
41884
+ const isTargeted = hasOffset || hasLimit;
41885
+ const targetedReadGuidance = isTargeted ? "" : `
41886
+ 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.`;
40557
41887
  return nudge(
40558
41888
  `READ ROUTING: Built-in Read is ONLY for the Edit workflow (Read \u2192 Edit). For all other reading, use unerr tools instead:
40559
41889
  - Reading to understand: \`file_read({ file_path: "${filePath}", purpose: "explore" })\`
40560
41890
  - Reading before Edit: built-in Read is correct (proceed)
40561
41891
  - File structure: \`file_outline("${filePath}")\`
40562
- - Specific function: \`get_entity\` or \`file_read\` with \`entity\` param`
41892
+ - Specific function: \`get_entity\` or \`file_read\` with \`entity\` param` + targetedReadGuidance
40563
41893
  );
40564
41894
  };
40565
41895
  var preGrepHandler = (normalized) => {
@@ -40611,17 +41941,20 @@ var preEditHandler = (normalized) => {
40611
41941
  const input = normalized.toolInput;
40612
41942
  const filePath = extractFilePath2(input);
40613
41943
  if (!filePath || !isCodeFile(filePath)) return passthrough();
41944
+ 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.
41945
+
41946
+ `;
40614
41947
  const oldStr = input.old_string;
40615
41948
  const hasSignatureChange = oldStr && /^(export\s+)?(async\s+)?function\s+\w+|^(export\s+)?class\s+\w+/.test(oldStr.trim());
40616
41949
  if (hasSignatureChange) {
40617
41950
  return nudge(
40618
- `You're editing a function/class signature in "${filePath}". This may break callers.
41951
+ readPrereq + `You're editing a function/class signature in "${filePath}". This may break callers.
40619
41952
  - \`get_references\` on the entity you're modifying \u2014 all callers must be updated to match
40620
41953
  - \`get_rules "${filePath}"\` with content after editing \u2014 validate the change follows project conventions`
40621
41954
  );
40622
41955
  }
40623
41956
  return nudge(
40624
- `Before editing "${filePath}":
41957
+ readPrereq + `Before editing "${filePath}":
40625
41958
  - \`get_references\` on any entity you're changing \u2014 ensure callers won't break
40626
41959
  - \`get_rules "${filePath}"\` with content after editing \u2014 validate against project conventions`
40627
41960
  );
@@ -41079,7 +42412,7 @@ function mergePreToolUseBashHook(cwd) {
41079
42412
  return { ok: false, path: settingsPath, action: "failed" };
41080
42413
  }
41081
42414
  }
41082
- var DISALLOWED_TOOLS = ["Read", "Grep", "Glob"];
42415
+ var DISALLOWED_TOOLS = ["Grep", "Glob"];
41083
42416
  function addDisallowedTools(cwd) {
41084
42417
  const dir = join27(cwd, ".claude");
41085
42418
  const settingsPath = join27(dir, "settings.json");
@@ -41097,6 +42430,10 @@ function addDisallowedTools(cwd) {
41097
42430
  }
41098
42431
  const permissions = settings.permissions ?? {};
41099
42432
  const deny = Array.isArray(permissions.deny) ? [...permissions.deny] : [];
42433
+ const readIdx = deny.indexOf("Read");
42434
+ if (readIdx >= 0) {
42435
+ deny.splice(readIdx, 1);
42436
+ }
41100
42437
  let added = 0;
41101
42438
  for (const tool of DISALLOWED_TOOLS) {
41102
42439
  if (!deny.includes(tool)) {
@@ -41217,10 +42554,12 @@ This project has unerr MCP tools installed. You MUST use these instead of built-
41217
42554
  | Intent | Tool | Why |
41218
42555
  |--------|------|-----|
41219
42556
  | Reading to understand code | \`file_read\` (unerr MCP) | Auto-injects conventions, facts, drift status |
41220
- | Reading immediately before Edit | Built-in \`Read\` | Required by Edit tool \u2014 \`file_read\` does NOT satisfy this |
42557
+ | 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. |
41221
42558
 
41222
42559
  When your next action is Edit, use built-in Read. For everything else, use \`file_read\`.
41223
42560
 
42561
+ **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.
42562
+
41224
42563
  ### Tool Reference
41225
42564
 
41226
42565
  #### Graph Navigation (8 tools)
@@ -41287,7 +42626,7 @@ ONLY use built-in Read/Grep/Glob when:
41287
42626
  ### Summary (CRITICAL \u2014 read this even if you skimmed above)
41288
42627
 
41289
42628
  ALWAYS use unerr MCP tools: \`search_code\`, \`get_references\`, \`file_read\`, \`file_outline\`, \`get_entity\`.
41290
- NEVER use built-in Read/Grep/Glob for code navigation. EXCEPTION: built-in Read is REQUIRED before Edit (file_read cannot substitute).
42629
+ 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).
41291
42630
  unerr skills available: /audit, /blame, /test, /lint, /commit, /review-pr.
41292
42631
  Before writing code: \`get_conventions\`, \`get_rules\`. After writing: \`get_rules\` with content to validate.
41293
42632
  Before risky changes: \`unerr_mark_working\`. If things break: \`unerr_revert_to_working_state\`.`;
@@ -41310,6 +42649,50 @@ function writeInstructionFile(cwd, ide) {
41310
42649
  if (agentDef.instructionFormat === "mdc") {
41311
42650
  return writeMdcInstructionFile(filePath);
41312
42651
  }
42652
+ if (agentDef.instructionFormat === "windsurf-rule") {
42653
+ mkdirSync15(dirname5(filePath), { recursive: true });
42654
+ const content = getInstructionContent();
42655
+ const truncatedContent = content.length > 5800 ? `${content.slice(0, 5800)}
42656
+
42657
+ [Truncated \u2014 full content exceeds Windsurf 6K limit]` : content;
42658
+ const windsurfContent = `---
42659
+ trigger: always_on
42660
+ description: "Tool routing instructions for unerr MCP integration"
42661
+ ---
42662
+
42663
+ ${truncatedContent}
42664
+ `;
42665
+ const existed = existsSync21(filePath);
42666
+ if (existed) {
42667
+ const existing = readFileSync20(filePath, "utf-8");
42668
+ if (existing === windsurfContent) {
42669
+ return { path: filePath, action: "skipped" };
42670
+ }
42671
+ }
42672
+ writeFileSync11(filePath, windsurfContent, "utf-8");
42673
+ return { path: filePath, action: existed ? "updated" : "created" };
42674
+ }
42675
+ if (agentDef.instructionFormat === "antigravity-rule") {
42676
+ mkdirSync15(dirname5(filePath), { recursive: true });
42677
+ const content = getInstructionContent();
42678
+ const antigravityContent = `---
42679
+ name: unerr-instructions
42680
+ description: Tool routing instructions for unerr MCP integration
42681
+ type: manual
42682
+ ---
42683
+
42684
+ ${content}
42685
+ `;
42686
+ const existed = existsSync21(filePath);
42687
+ if (existed) {
42688
+ const existing = readFileSync20(filePath, "utf-8");
42689
+ if (existing === antigravityContent) {
42690
+ return { path: filePath, action: "skipped" };
42691
+ }
42692
+ }
42693
+ writeFileSync11(filePath, antigravityContent, "utf-8");
42694
+ return { path: filePath, action: existed ? "updated" : "created" };
42695
+ }
41313
42696
  return mergeMarkdownSection(filePath, getInstructionContent());
41314
42697
  }
41315
42698
  function mergeMarkdownSection(filePath, content) {
@@ -41367,6 +42750,20 @@ function removeInstructionSection(cwd, ide) {
41367
42750
  unlinkSync3(filePath);
41368
42751
  return true;
41369
42752
  }
42753
+ if (agentDef.instructionFormat === "windsurf-rule") {
42754
+ if (existsSync21(filePath)) {
42755
+ unlinkSync3(filePath);
42756
+ return true;
42757
+ }
42758
+ return false;
42759
+ }
42760
+ if (agentDef.instructionFormat === "antigravity-rule") {
42761
+ if (existsSync21(filePath)) {
42762
+ unlinkSync3(filePath);
42763
+ return true;
42764
+ }
42765
+ return false;
42766
+ }
41370
42767
  const content = readFileSync20(filePath, "utf-8");
41371
42768
  const startIdx = content.indexOf(SENTINEL_START);
41372
42769
  const endIdx = content.indexOf(SENTINEL_END);
@@ -41924,8 +43321,8 @@ async function persistPatterns(patterns, _unerrDir) {
41924
43321
  );
41925
43322
  return;
41926
43323
  }
41927
- const { readFileSync: readFileSync56 } = await import("fs");
41928
- const config = JSON.parse(readFileSync56(configPath, "utf-8"));
43324
+ const { readFileSync: readFileSync57 } = await import("fs");
43325
+ const config = JSON.parse(readFileSync57(configPath, "utf-8"));
41929
43326
  if (!config.repoId || !config.orgId) {
41930
43327
  warn("Repo not configured \u2014 cannot persist to CozoDB.");
41931
43328
  return;
@@ -41943,7 +43340,7 @@ async function persistPatterns(patterns, _unerrDir) {
41943
43340
  const { CozoGraphStore: CozoGraphStore2 } = await Promise.resolve().then(() => (init_local_graph(), local_graph_exports));
41944
43341
  const store = await CozoGraphStore2.create(db);
41945
43342
  const { unpack } = await import("msgpackr");
41946
- const raw = readFileSync56(snapshotPath2);
43343
+ const raw = readFileSync57(snapshotPath2);
41947
43344
  const buffer = gunzipSync2(raw);
41948
43345
  const envelope = unpack(buffer);
41949
43346
  await store.loadSnapshot(envelope);
@@ -42610,8 +44007,8 @@ function registerStatusCommand(program2) {
42610
44007
  try {
42611
44008
  const logsDir = join38(unerrDir, "logs");
42612
44009
  if (existsSync31(logsDir)) {
42613
- const { readdirSync: readdirSync14, statSync: statSync12 } = await import("fs");
42614
- const logs = readdirSync14(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
44010
+ const { readdirSync: readdirSync15, statSync: statSync12 } = await import("fs");
44011
+ const logs = readdirSync15(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
42615
44012
  name: f,
42616
44013
  mtime: statSync12(join38(logsDir, f)).mtimeMs
42617
44014
  })).sort((a, b) => b.mtime - a.mtime);
@@ -43176,17 +44573,538 @@ async function isInsideGitRepo2() {
43176
44573
  const { isGitRepo: isGitRepo3 } = await Promise.resolve().then(() => (init_git(), git_exports));
43177
44574
  return isGitRepo3(process.cwd());
43178
44575
  }
44576
+ var DEFINITIVE_MARKERS = [
44577
+ // JavaScript / TypeScript
44578
+ "package.json",
44579
+ "tsconfig.json",
44580
+ "jsconfig.json",
44581
+ "deno.json",
44582
+ "deno.jsonc",
44583
+ "bun.lockb",
44584
+ "bunfig.toml",
44585
+ // Python
44586
+ "pyproject.toml",
44587
+ "setup.py",
44588
+ "setup.cfg",
44589
+ "Pipfile",
44590
+ "hatch.toml",
44591
+ // Rust
44592
+ "Cargo.toml",
44593
+ // Go
44594
+ "go.mod",
44595
+ // Java / Kotlin / Gradle
44596
+ "pom.xml",
44597
+ "build.gradle",
44598
+ "build.gradle.kts",
44599
+ "settings.gradle",
44600
+ "settings.gradle.kts",
44601
+ // C# / .NET
44602
+ "Directory.Build.props",
44603
+ "global.json",
44604
+ "nuget.config",
44605
+ // Ruby
44606
+ "Gemfile",
44607
+ "Rakefile",
44608
+ // PHP
44609
+ "composer.json",
44610
+ // Swift / Obj-C / Apple
44611
+ "Package.swift",
44612
+ "Podfile",
44613
+ // C / C++
44614
+ "CMakeLists.txt",
44615
+ "Makefile",
44616
+ "configure.ac",
44617
+ "meson.build",
44618
+ "conanfile.txt",
44619
+ "conanfile.py",
44620
+ "vcpkg.json",
44621
+ // Dart / Flutter
44622
+ "pubspec.yaml",
44623
+ // Elixir
44624
+ "mix.exs",
44625
+ // Scala
44626
+ "build.sbt",
44627
+ "build.sc",
44628
+ // Haskell
44629
+ "stack.yaml",
44630
+ "cabal.project",
44631
+ // Clojure
44632
+ "project.clj",
44633
+ "deps.edn",
44634
+ "shadow-cljs.edn",
44635
+ // Zig
44636
+ "build.zig",
44637
+ "build.zig.zon",
44638
+ // Julia
44639
+ "Project.toml",
44640
+ // Terraform / IaC
44641
+ "main.tf",
44642
+ "terraform.tf",
44643
+ "pulumi.yaml",
44644
+ "serverless.yml",
44645
+ "cdk.json",
44646
+ "sam.yaml",
44647
+ // Containers
44648
+ "Dockerfile",
44649
+ "docker-compose.yml",
44650
+ "docker-compose.yaml",
44651
+ // Monorepo
44652
+ "lerna.json",
44653
+ "nx.json",
44654
+ "turbo.json",
44655
+ "pnpm-workspace.yaml",
44656
+ "rush.json",
44657
+ "pants.toml",
44658
+ // Bazel
44659
+ "BUILD.bazel",
44660
+ "WORKSPACE",
44661
+ "WORKSPACE.bazel",
44662
+ // General build / task
44663
+ "Justfile",
44664
+ "Taskfile.yml",
44665
+ // Nix
44666
+ "flake.nix",
44667
+ "shell.nix",
44668
+ "default.nix",
44669
+ // Android
44670
+ "AndroidManifest.xml"
44671
+ ];
44672
+ var STRONG_SIGNAL_FILES = [
44673
+ // Lock files (imply a package manager was run here)
44674
+ "package-lock.json",
44675
+ "yarn.lock",
44676
+ "pnpm-lock.yaml",
44677
+ "Pipfile.lock",
44678
+ "poetry.lock",
44679
+ "Gemfile.lock",
44680
+ "composer.lock",
44681
+ "Cargo.lock",
44682
+ "flake.lock",
44683
+ "pubspec.lock",
44684
+ "mix.lock",
44685
+ "go.sum",
44686
+ "uv.lock",
44687
+ "pdm.lock",
44688
+ // CI/CD (a CI config strongly implies project root)
44689
+ ".gitlab-ci.yml",
44690
+ "Jenkinsfile",
44691
+ ".travis.yml",
44692
+ "azure-pipelines.yml",
44693
+ "bitbucket-pipelines.yml",
44694
+ // Editor / LSP configs (placed at project root)
44695
+ ".editorconfig",
44696
+ ".prettierrc",
44697
+ ".prettierrc.json",
44698
+ ".eslintrc",
44699
+ ".eslintrc.json",
44700
+ ".eslintrc.js",
44701
+ "biome.json",
44702
+ "biome.jsonc",
44703
+ "pyrightconfig.json",
44704
+ "rust-toolchain.toml",
44705
+ ".clang-format",
44706
+ ".clangd",
44707
+ "compile_commands.json",
44708
+ ".luarc.json",
44709
+ "stylua.toml",
44710
+ ".rubocop.yml",
44711
+ ".php-cs-fixer.php",
44712
+ // Environment
44713
+ ".env",
44714
+ ".envrc",
44715
+ // Git-specific (at root level these are strong signals)
44716
+ ".gitignore",
44717
+ ".gitattributes"
44718
+ ];
44719
+ var STRONG_SIGNAL_DIRS = [
44720
+ ".git",
44721
+ ".svn",
44722
+ ".hg",
44723
+ ".fossil",
44724
+ ".bzr",
44725
+ "_darcs",
44726
+ // VCS
44727
+ ".github",
44728
+ ".circleci",
44729
+ ".buildkite",
44730
+ // CI/CD
44731
+ ".idea",
44732
+ ".vscode",
44733
+ ".vs",
44734
+ ".fleet",
44735
+ ".zed"
44736
+ // IDE
44737
+ ];
44738
+ var EXTENSION_MARKERS = [
44739
+ ".sln",
44740
+ ".csproj",
44741
+ ".fsproj",
44742
+ ".vbproj",
44743
+ // .NET
44744
+ ".xcodeproj",
44745
+ ".xcworkspace",
44746
+ // Xcode (dirs)
44747
+ ".cabal",
44748
+ // Haskell
44749
+ ".nimble",
44750
+ // Nim
44751
+ ".gemspec",
44752
+ // Ruby
44753
+ ".rockspec"
44754
+ // Lua
44755
+ ];
44756
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
44757
+ // Web / JS ecosystem
44758
+ ".ts",
44759
+ ".tsx",
44760
+ ".js",
44761
+ ".jsx",
44762
+ ".mjs",
44763
+ ".cjs",
44764
+ ".vue",
44765
+ ".svelte",
44766
+ ".astro",
44767
+ // Systems
44768
+ ".rs",
44769
+ ".go",
44770
+ ".c",
44771
+ ".h",
44772
+ ".cpp",
44773
+ ".cc",
44774
+ ".cxx",
44775
+ ".hpp",
44776
+ ".hxx",
44777
+ ".zig",
44778
+ // JVM
44779
+ ".java",
44780
+ ".kt",
44781
+ ".kts",
44782
+ ".scala",
44783
+ ".sc",
44784
+ ".clj",
44785
+ ".cljs",
44786
+ ".cljc",
44787
+ // .NET
44788
+ ".cs",
44789
+ ".fs",
44790
+ ".fsx",
44791
+ ".vb",
44792
+ // Scripting
44793
+ ".py",
44794
+ ".pyi",
44795
+ ".rb",
44796
+ ".php",
44797
+ ".lua",
44798
+ ".pl",
44799
+ ".pm",
44800
+ ".raku",
44801
+ // Apple / Mobile
44802
+ ".swift",
44803
+ ".m",
44804
+ ".mm",
44805
+ ".dart",
44806
+ // Functional
44807
+ ".hs",
44808
+ ".lhs",
44809
+ ".ml",
44810
+ ".mli",
44811
+ ".re",
44812
+ ".rei",
44813
+ ".elm",
44814
+ ".ex",
44815
+ ".exs",
44816
+ ".purs",
44817
+ ".idr",
44818
+ ".agda",
44819
+ ".lean",
44820
+ // Data / Science
44821
+ ".r",
44822
+ ".R",
44823
+ ".jl",
44824
+ // Infrastructure
44825
+ ".tf",
44826
+ ".hcl",
44827
+ // Emerging / Niche
44828
+ ".nim",
44829
+ ".cr",
44830
+ ".d",
44831
+ ".v",
44832
+ ".sol",
44833
+ ".move",
44834
+ ".cairo",
44835
+ ".nr",
44836
+ // Shell / Config as code
44837
+ ".sh",
44838
+ ".bash",
44839
+ ".zsh",
44840
+ ".fish",
44841
+ ".ps1"
44842
+ ]);
44843
+ var ANTI_SIGNAL_FILES = [
44844
+ ".bashrc",
44845
+ ".zshrc",
44846
+ ".bash_profile",
44847
+ ".profile",
44848
+ ".zprofile",
44849
+ ".zshenv",
44850
+ ".bash_history",
44851
+ ".zsh_history"
44852
+ ];
44853
+ var ANTI_SIGNAL_DIRS = [
44854
+ ".config",
44855
+ ".local",
44856
+ ".cache",
44857
+ ".ssh",
44858
+ ".gnupg",
44859
+ ".npm",
44860
+ ".cargo",
44861
+ "Desktop",
44862
+ "Downloads",
44863
+ "Documents",
44864
+ "Pictures",
44865
+ "Music",
44866
+ "Movies",
44867
+ "Library",
44868
+ "Applications"
44869
+ ];
44870
+ var ANTI_SIGNAL_PATHS = [
44871
+ "/",
44872
+ "/usr",
44873
+ "/tmp",
44874
+ "/var",
44875
+ "/etc",
44876
+ "/opt",
44877
+ "/home",
44878
+ "/Users",
44879
+ "/root"
44880
+ ];
44881
+ var SOURCE_DIRS = /* @__PURE__ */ new Set([
44882
+ "src",
44883
+ "lib",
44884
+ "app",
44885
+ "apps",
44886
+ "cmd",
44887
+ "pkg",
44888
+ "packages",
44889
+ "internal",
44890
+ "modules",
44891
+ "components",
44892
+ "pages",
44893
+ "routes",
44894
+ "api",
44895
+ "server",
44896
+ "client",
44897
+ "web",
44898
+ "core",
44899
+ "common",
44900
+ "shared",
44901
+ "utils",
44902
+ "helpers",
44903
+ "services",
44904
+ "models",
44905
+ "views",
44906
+ "controllers",
44907
+ "handlers",
44908
+ "middleware",
44909
+ "plugins",
44910
+ "test",
44911
+ "tests",
44912
+ "spec",
44913
+ "__tests__",
44914
+ "e2e",
44915
+ "integration",
44916
+ "scripts",
44917
+ "tools",
44918
+ "bin",
44919
+ "examples",
44920
+ "samples",
44921
+ "proto",
44922
+ "schemas",
44923
+ "migrations",
44924
+ "seeds"
44925
+ ]);
44926
+ var DETECTION_THRESHOLD = 5;
44927
+ async function detectProjectRoot(cwd) {
44928
+ const { readdirSync: readdirSync15 } = await import("fs");
44929
+ let score = 0;
44930
+ const signals = [];
44931
+ let hasGit = false;
44932
+ const normalizedCwd = cwd.replace(/\\/g, "/");
44933
+ for (const ap of ANTI_SIGNAL_PATHS) {
44934
+ if (normalizedCwd === ap || normalizedCwd === ap + "/") {
44935
+ return {
44936
+ isProject: false,
44937
+ hasGit: false,
44938
+ reason: `System/root path: ${cwd}`,
44939
+ score: -15,
44940
+ signals: [`anti-path: ${ap}`]
44941
+ };
44942
+ }
44943
+ }
44944
+ let rootEntries = [];
44945
+ try {
44946
+ rootEntries = readdirSync15(cwd, { withFileTypes: true }).map((e) => ({
44947
+ name: e.name,
44948
+ isFile: e.isFile(),
44949
+ isDir: e.isDirectory()
44950
+ }));
44951
+ } catch {
44952
+ return {
44953
+ isProject: false,
44954
+ hasGit: false,
44955
+ reason: "Cannot read directory",
44956
+ score: -1,
44957
+ signals: ["unreadable"]
44958
+ };
44959
+ }
44960
+ const rootFileNames = new Set(rootEntries.filter((e) => e.isFile).map((e) => e.name));
44961
+ const rootDirNames = new Set(rootEntries.filter((e) => e.isDir).map((e) => e.name));
44962
+ for (const af of ANTI_SIGNAL_FILES) {
44963
+ if (rootFileNames.has(af)) {
44964
+ score -= 3;
44965
+ signals.push(`anti-file: ${af}`);
44966
+ }
44967
+ }
44968
+ for (const ad of ANTI_SIGNAL_DIRS) {
44969
+ if (rootDirNames.has(ad)) {
44970
+ score -= 2;
44971
+ signals.push(`anti-dir: ${ad}/`);
44972
+ }
44973
+ }
44974
+ for (const marker of DEFINITIVE_MARKERS) {
44975
+ if (rootFileNames.has(marker)) {
44976
+ score += 10;
44977
+ signals.push(`marker: ${marker}`);
44978
+ break;
44979
+ }
44980
+ }
44981
+ if (score < DETECTION_THRESHOLD) {
44982
+ for (const entry of rootEntries) {
44983
+ if (!entry.isFile && !entry.isDir) continue;
44984
+ const name = entry.name.toLowerCase();
44985
+ for (const ext of EXTENSION_MARKERS) {
44986
+ if (name.endsWith(ext)) {
44987
+ score += 10;
44988
+ signals.push(`ext-marker: ${entry.name}`);
44989
+ break;
44990
+ }
44991
+ }
44992
+ if (score >= DETECTION_THRESHOLD) break;
44993
+ }
44994
+ }
44995
+ for (const vcs of STRONG_SIGNAL_DIRS.slice(0, 6)) {
44996
+ if (rootDirNames.has(vcs)) {
44997
+ if (vcs === ".git") hasGit = true;
44998
+ score += 8;
44999
+ signals.push(`vcs: ${vcs}/`);
45000
+ break;
45001
+ }
45002
+ }
45003
+ for (const sf of STRONG_SIGNAL_FILES) {
45004
+ if (rootFileNames.has(sf)) {
45005
+ score += 4;
45006
+ signals.push(`strong: ${sf}`);
45007
+ if (score >= DETECTION_THRESHOLD) break;
45008
+ }
45009
+ }
45010
+ for (const sd of STRONG_SIGNAL_DIRS.slice(6)) {
45011
+ if (rootDirNames.has(sd)) {
45012
+ score += 4;
45013
+ signals.push(`strong: ${sd}/`);
45014
+ if (score >= DETECTION_THRESHOLD) break;
45015
+ }
45016
+ }
45017
+ if (!hasGit) {
45018
+ try {
45019
+ hasGit = await isInsideGitRepo2();
45020
+ if (hasGit) {
45021
+ score += 6;
45022
+ signals.push("git: inside work tree");
45023
+ }
45024
+ } catch {
45025
+ }
45026
+ }
45027
+ if (score >= DETECTION_THRESHOLD) {
45028
+ const topSignal2 = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "unknown";
45029
+ return { isProject: true, hasGit, reason: topSignal2, score, signals };
45030
+ }
45031
+ function dirHasCodeFile(dir) {
45032
+ try {
45033
+ const entries = readdirSync15(dir, { withFileTypes: true });
45034
+ for (const entry of entries) {
45035
+ if (!entry.isFile()) continue;
45036
+ const dot = entry.name.lastIndexOf(".");
45037
+ if (dot >= 0 && CODE_EXTENSIONS.has(entry.name.slice(dot).toLowerCase())) {
45038
+ return entry.name;
45039
+ }
45040
+ }
45041
+ } catch {
45042
+ }
45043
+ return null;
45044
+ }
45045
+ const rootCodeFile = dirHasCodeFile(cwd);
45046
+ if (rootCodeFile) {
45047
+ score += 5;
45048
+ signals.push(`code-root: ${rootCodeFile}`);
45049
+ }
45050
+ if (score >= DETECTION_THRESHOLD) {
45051
+ const topSignal2 = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "unknown";
45052
+ return { isProject: true, hasGit, reason: topSignal2, score, signals };
45053
+ }
45054
+ for (const dirEntry of rootEntries) {
45055
+ if (!dirEntry.isDir || dirEntry.name.startsWith(".")) continue;
45056
+ if (!SOURCE_DIRS.has(dirEntry.name.toLowerCase())) continue;
45057
+ const sourceDir = join67(cwd, dirEntry.name);
45058
+ const srcCodeFile = dirHasCodeFile(sourceDir);
45059
+ if (srcCodeFile) {
45060
+ score += 6;
45061
+ signals.push(`code-src: ${dirEntry.name}/${srcCodeFile}`);
45062
+ break;
45063
+ }
45064
+ try {
45065
+ const subEntries = readdirSync15(sourceDir, { withFileTypes: true });
45066
+ let found = false;
45067
+ for (const sub of subEntries) {
45068
+ if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
45069
+ const deepCode = dirHasCodeFile(join67(sourceDir, sub.name));
45070
+ if (deepCode) {
45071
+ score += 6;
45072
+ signals.push(`code-src: ${dirEntry.name}/${sub.name}/${deepCode}`);
45073
+ found = true;
45074
+ break;
45075
+ }
45076
+ }
45077
+ if (found) break;
45078
+ } catch {
45079
+ }
45080
+ }
45081
+ const isProject = score >= DETECTION_THRESHOLD;
45082
+ const topSignal = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "No signals";
45083
+ return { isProject, hasGit, reason: isProject ? topSignal : "No code files or project markers found", score, signals };
45084
+ }
43179
45085
  function readLocalConfig(cwd) {
43180
- const configPath = join66(cwd, ".unerr", "config.json");
43181
- if (!existsSync61(configPath)) return null;
45086
+ const configPath = join67(cwd, ".unerr", "config.json");
45087
+ if (!existsSync62(configPath)) return null;
43182
45088
  try {
43183
- return JSON.parse(readFileSync55(configPath, "utf-8"));
45089
+ return JSON.parse(readFileSync56(configPath, "utf-8"));
43184
45090
  } catch {
43185
45091
  return null;
43186
45092
  }
43187
45093
  }
43188
45094
  async function resumeBoot(config) {
43189
45095
  initFileLog(process.cwd());
45096
+ const detection = await detectProjectRoot(process.cwd());
45097
+ if (!detection.isProject) {
45098
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
45099
+ process.stderr.write(
45100
+ `\x1B[38;2;248;113;113m\u2717\x1B[0m No code project detected in this directory.
45101
+ Found .unerr/config.json but the directory doesn't look like a project root.
45102
+ Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})
45103
+ ` + (antiSignals.length > 0 ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}
45104
+ ` : "") + "\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"
45105
+ );
45106
+ process.exit(1);
45107
+ }
43190
45108
  const { initSessionLogger: initSessionLogger2, createSessionModuleLogger: createSessionModuleLogger2 } = await Promise.resolve().then(() => (init_session_logger(), session_logger_exports));
43191
45109
  initSessionLogger2();
43192
45110
  const log25 = createSessionModuleLogger2("boot");
@@ -43200,13 +45118,24 @@ async function firstRunBoot() {
43200
45118
  const { initSessionLogger: initSessionLogger2, createSessionModuleLogger: createSessionModuleLogger2 } = await Promise.resolve().then(() => (init_session_logger(), session_logger_exports));
43201
45119
  initSessionLogger2();
43202
45120
  const log25 = createSessionModuleLogger2("boot");
43203
- if (!await isInsideGitRepo2()) {
45121
+ const detection = await detectProjectRoot(process.cwd());
45122
+ if (!detection.isProject) {
45123
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
43204
45124
  process.stderr.write(
43205
- "[unerr] Not inside a git repository. Run unerr from a project folder.\n"
45125
+ `\x1B[38;2;248;113;113m\u2717\x1B[0m No code project detected in this directory.
45126
+ unerr needs a folder containing source code to index.
45127
+ Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})
45128
+ ` + (antiSignals.length > 0 ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}
45129
+ ` : "") + "\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"
43206
45130
  );
43207
45131
  process.exit(1);
43208
45132
  }
43209
- log25.info({ msg: "First-run boot, entering local setup" });
45133
+ if (!detection.hasGit) {
45134
+ process.stderr.write(
45135
+ "\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"
45136
+ );
45137
+ }
45138
+ log25.info({ msg: "First-run boot, entering local setup", detection: detection.reason, score: detection.score, hasGit: detection.hasGit });
43210
45139
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup_wizard(), setup_wizard_exports));
43211
45140
  const result = await runSetup2();
43212
45141
  if (result.action === "setup") {
@@ -43218,15 +45147,21 @@ async function firstRunBoot() {
43218
45147
  }
43219
45148
  async function mcpBoot(cwd) {
43220
45149
  initFileLog(cwd);
43221
- if (!await isInsideGitRepo2()) {
45150
+ const detection = await detectProjectRoot(cwd);
45151
+ if (!detection.isProject) {
45152
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
43222
45153
  process.stderr.write(
43223
- "[unerr] Not inside a git repository. Run unerr from a project folder.\n"
45154
+ `\x1B[38;2;248;113;113m\u2717\x1B[0m No code project detected in this directory.
45155
+ unerr needs a folder containing source code to index.
45156
+ Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})
45157
+ ` + (antiSignals.length > 0 ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}
45158
+ ` : "") + "\n \u25B8 Run \x1B[1munerr --mcp\x1B[0m from a project root directory.\n"
43224
45159
  );
43225
45160
  process.exit(1);
43226
45161
  }
43227
- const stateDir = join66(cwd, ".unerr", "state");
43228
- const sockPath = join66(stateDir, "proxy.sock");
43229
- if (existsSync61(sockPath)) {
45162
+ const stateDir = join67(cwd, ".unerr", "state");
45163
+ const sockPath = join67(stateDir, "proxy.sock");
45164
+ if (existsSync62(sockPath)) {
43230
45165
  const { PidLock: PidLock2 } = await Promise.resolve().then(() => (init_pid_lock(), pid_lock_exports));
43231
45166
  const pidLock = new PidLock2(stateDir);
43232
45167
  const probeResult = await pidLock.probe();
@@ -43250,10 +45185,10 @@ async function mcpBoot(cwd) {
43250
45185
  const { mkdirSync: mkdirSync39, writeFileSync: writeFileSync36 } = await import("fs");
43251
45186
  const { createHash: createHash5 } = await import("crypto");
43252
45187
  const repoId = createHash5("sha256").update(cwd).digest("hex").slice(0, 12);
43253
- const unerrDir = join66(cwd, ".unerr");
45188
+ const unerrDir = join67(cwd, ".unerr");
43254
45189
  mkdirSync39(unerrDir, { recursive: true });
43255
45190
  writeFileSync36(
43256
- join66(unerrDir, "config.json"),
45191
+ join67(unerrDir, "config.json"),
43257
45192
  JSON.stringify(
43258
45193
  { repoId, mode: "local", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
43259
45194
  null,