@unerr-ai/unerr 0.0.0-beta.6 → 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,
@@ -4578,7 +4613,9 @@ function normalizeAgentName(input) {
4578
4613
  code: "vscode",
4579
4614
  vs: "vscode",
4580
4615
  google: "antigravity",
4581
- "google-antigravity": "antigravity"
4616
+ "google-antigravity": "antigravity",
4617
+ windsurf: "windsurf",
4618
+ cascade: "windsurf"
4582
4619
  };
4583
4620
  return aliases[input.toLowerCase()] ?? input.toLowerCase();
4584
4621
  }
@@ -4626,14 +4663,15 @@ var init_agent_registry = __esm({
4626
4663
  {
4627
4664
  id: "windsurf",
4628
4665
  name: "Windsurf",
4629
- projectConfigPath: ".windsurf/mcp.json",
4666
+ projectConfigPath: ".codeium/windsurf/mcp_config.json",
4667
+ configScope: "global",
4630
4668
  configFormat: "mcp-json",
4631
4669
  dirMarkers: [".windsurf"],
4632
- envVars: [],
4633
- hookSupport: false,
4634
- description: "Codeium's AI IDE",
4635
- instructionFilePath: null,
4636
- 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"
4637
4675
  },
4638
4676
  {
4639
4677
  id: "zed",
@@ -4677,8 +4715,8 @@ var init_agent_registry = __esm({
4677
4715
  projectConfigPath: ".gemini/settings.json",
4678
4716
  configFormat: "settings-json",
4679
4717
  dirMarkers: [".gemini"],
4680
- envVars: ["GEMINI_API_KEY"],
4681
- hookSupport: false,
4718
+ envVars: ["GEMINI_API_KEY", "GEMINI_CLI"],
4719
+ hookSupport: true,
4682
4720
  description: "Google's CLI coding agent",
4683
4721
  instructionFilePath: "GEMINI.md",
4684
4722
  instructionFormat: "markdown"
@@ -4746,11 +4784,11 @@ var init_agent_registry = __esm({
4746
4784
  {
4747
4785
  id: "github-copilot-cli",
4748
4786
  name: "GitHub Copilot CLI",
4749
- projectConfigPath: ".github/mcp.json",
4750
- configFormat: "mcp-json",
4751
- dirMarkers: [".github"],
4787
+ projectConfigPath: ".copilot/mcp-config.json",
4788
+ configFormat: "copilot-json",
4789
+ dirMarkers: [".github", ".copilot"],
4752
4790
  envVars: ["GITHUB_COPILOT_TOKEN"],
4753
- hookSupport: false,
4791
+ hookSupport: true,
4754
4792
  description: "GitHub's CLI AI assistant",
4755
4793
  instructionFilePath: ".github/copilot-instructions.md",
4756
4794
  instructionFormat: "markdown"
@@ -4794,6 +4832,7 @@ __export(mcp_config_writer_exports, {
4794
4832
  writeMcpConfig: () => writeMcpConfig
4795
4833
  });
4796
4834
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
4835
+ import { homedir } from "os";
4797
4836
  import { dirname as dirname2, join as join7 } from "path";
4798
4837
  function createUnerrServerEntry() {
4799
4838
  return {
@@ -4802,12 +4841,19 @@ function createUnerrServerEntry() {
4802
4841
  args: ["--mcp"]
4803
4842
  };
4804
4843
  }
4844
+ function createCopilotServerEntry() {
4845
+ return {
4846
+ type: "local",
4847
+ command: "unerr",
4848
+ args: ["--mcp"]
4849
+ };
4850
+ }
4805
4851
  function writeMcpConfig(cwd, ide) {
4806
4852
  const agent = getAgent(ide);
4807
4853
  if (!agent) {
4808
4854
  return { path: "", action: "skipped" };
4809
4855
  }
4810
- const configPath = join7(cwd, agent.projectConfigPath);
4856
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4811
4857
  const dir = dirname2(configPath);
4812
4858
  if (!existsSync5(dir)) {
4813
4859
  mkdirSync3(dir, { recursive: true });
@@ -4817,6 +4863,8 @@ function writeMcpConfig(cwd, ide) {
4817
4863
  return writeMcpJsonFormat(configPath);
4818
4864
  case "settings-json":
4819
4865
  return writeSettingsJsonFormat(configPath);
4866
+ case "copilot-json":
4867
+ return writeCopilotJsonFormat(configPath);
4820
4868
  case "continue-config":
4821
4869
  return writeContinueFormat(configPath);
4822
4870
  default:
@@ -4832,7 +4880,7 @@ function writeAllMcpConfigs(cwd, agents) {
4832
4880
  function removeMcpConfig(cwd, ide) {
4833
4881
  const agent = getAgent(ide);
4834
4882
  if (!agent) return false;
4835
- const configPath = join7(cwd, agent.projectConfigPath);
4883
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4836
4884
  if (!existsSync5(configPath)) return false;
4837
4885
  try {
4838
4886
  const existing = JSON.parse(readFileSync6(configPath, "utf-8"));
@@ -4844,6 +4892,9 @@ function removeMcpConfig(cwd, ide) {
4844
4892
  } else if (agent.configFormat === "settings-json") {
4845
4893
  if (!existing.mcp?.servers?.[UNERR_SERVER_KEY]) return false;
4846
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];
4847
4898
  } else {
4848
4899
  if (!existing.mcpServers?.[UNERR_SERVER_KEY]) return false;
4849
4900
  delete existing.mcpServers[UNERR_SERVER_KEY];
@@ -4857,7 +4908,7 @@ function removeMcpConfig(cwd, ide) {
4857
4908
  function isConfigured(cwd, ide) {
4858
4909
  const agent = getAgent(ide);
4859
4910
  if (!agent) return false;
4860
- const configPath = join7(cwd, agent.projectConfigPath);
4911
+ const configPath = agent.configScope === "global" ? join7(homedir(), agent.projectConfigPath) : join7(cwd, agent.projectConfigPath);
4861
4912
  if (!existsSync5(configPath)) return false;
4862
4913
  try {
4863
4914
  const existing = JSON.parse(readFileSync6(configPath, "utf-8"));
@@ -4869,6 +4920,9 @@ function isConfigured(cwd, ide) {
4869
4920
  if (agent.configFormat === "settings-json") {
4870
4921
  return !!existing.mcp?.servers?.[UNERR_SERVER_KEY];
4871
4922
  }
4923
+ if (agent.configFormat === "copilot-json") {
4924
+ return !!existing.mcpServers?.[UNERR_SERVER_KEY];
4925
+ }
4872
4926
  return !!existing.mcpServers?.[UNERR_SERVER_KEY];
4873
4927
  } catch {
4874
4928
  return false;
@@ -4885,6 +4939,14 @@ function generateConfigSnippet(ide) {
4885
4939
  null,
4886
4940
  2
4887
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
+ }
4888
4950
  case "continue-config":
4889
4951
  return JSON.stringify(
4890
4952
  { mcpServers: [{ name: UNERR_SERVER_KEY, ...entry }] },
@@ -4975,6 +5037,29 @@ function writeContinueFormat(configPath) {
4975
5037
  writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
4976
5038
  return { path: configPath, action: "created" };
4977
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
+ }
4978
5063
  var UNERR_SERVER_KEY;
4979
5064
  var init_mcp_config_writer = __esm({
4980
5065
  "src/config/mcp-config-writer.ts"() {
@@ -5375,7 +5460,7 @@ __export(settings_exports, {
5375
5460
  resolveInferenceEndpoint: () => resolveInferenceEndpoint
5376
5461
  });
5377
5462
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
5378
- import { homedir as homedir3 } from "os";
5463
+ import { homedir as homedir4 } from "os";
5379
5464
  import { join as join12 } from "path";
5380
5465
  import { z } from "zod";
5381
5466
  function resolveEmbeddingEndpoint(config) {
@@ -5406,7 +5491,7 @@ function loadJsonFile(filePath) {
5406
5491
  }
5407
5492
  function loadSettings(cwd) {
5408
5493
  const projectDir = cwd ?? process.cwd();
5409
- const userSettings = loadJsonFile(join12(homedir3(), ".unerr", "settings.json"));
5494
+ const userSettings = loadJsonFile(join12(homedir4(), ".unerr", "settings.json"));
5410
5495
  const projectSettings = loadJsonFile(
5411
5496
  join12(projectDir, ".unerr", "settings.json")
5412
5497
  );
@@ -6562,6 +6647,7 @@ __export(ast_extractor_exports, {
6562
6647
  import { createHash } from "crypto";
6563
6648
  import { existsSync as existsSync14 } from "fs";
6564
6649
  import { join as join19 } from "path";
6650
+ import { fileURLToPath } from "url";
6565
6651
  function detectLanguage2(filePath) {
6566
6652
  const dot = filePath.lastIndexOf(".");
6567
6653
  if (dot < 0) return null;
@@ -6574,13 +6660,18 @@ function extractEntities(content, filePath) {
6574
6660
  const lines = content.split("\n");
6575
6661
  const entities = [];
6576
6662
  const patterns = getPatterns(language);
6663
+ let currentClassName = null;
6577
6664
  for (let i = 0; i < lines.length; i++) {
6578
6665
  const line = lines[i] ?? "";
6579
6666
  for (const pattern of patterns) {
6580
6667
  const match = pattern.regex.exec(line);
6581
6668
  if (match) {
6582
- const name = match[pattern.nameGroup] ?? "";
6583
- 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;
6584
6675
  const signature = match[pattern.sigGroup ?? 0] ?? "";
6585
6676
  const lineStart = i + 1;
6586
6677
  const lineEnd = findBlockEnd(lines, i, language);
@@ -6593,7 +6684,8 @@ function extractEntities(content, filePath) {
6593
6684
  signature: signature.trim(),
6594
6685
  line_start: lineStart,
6595
6686
  line_end: lineEnd,
6596
- content_hash: contentHash
6687
+ content_hash: contentHash,
6688
+ parent_class: pattern.kind === "method" ? currentClassName ?? void 0 : void 0
6597
6689
  });
6598
6690
  break;
6599
6691
  }
@@ -6625,9 +6717,9 @@ function getPatterns(language) {
6625
6717
  kind: "interface",
6626
6718
  nameGroup: 1
6627
6719
  },
6628
- // method(params) { — inside class
6720
+ // method(params) { — inside class (with optional access modifiers)
6629
6721
  {
6630
- 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*)?[{]/,
6631
6723
  kind: "method",
6632
6724
  nameGroup: 1,
6633
6725
  sigGroup: 2
@@ -6949,10 +7041,16 @@ async function getTSParser(grammar) {
6949
7041
  }
6950
7042
  const parser = new TreeSitter();
6951
7043
  const wasmFile = `tree-sitter-${grammar}.wasm`;
7044
+ const pkgDir = import.meta.dirname ?? join19(fileURLToPath(import.meta.url), "..");
7045
+ const pkgRoot = join19(pkgDir, "..");
6952
7046
  const possiblePaths = [
6953
7047
  join19(process.cwd(), "node_modules", "tree-sitter-wasms", "out", wasmFile),
6954
7048
  join19(process.cwd(), "node_modules", `tree-sitter-${grammar}`, wasmFile),
6955
- 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)
6956
7054
  ];
6957
7055
  let wasmPath = null;
6958
7056
  for (const p of possiblePaths) {
@@ -9009,12 +9107,12 @@ var init_decoder = __esm({
9009
9107
  // src/intelligence/indexer/scip/downloader.ts
9010
9108
  import { chmodSync as chmodSync2, createWriteStream, existsSync as existsSync15, mkdirSync as mkdirSync11 } from "fs";
9011
9109
  import { rename, rm, stat } from "fs/promises";
9012
- import { homedir as homedir4 } from "os";
9110
+ import { homedir as homedir5 } from "os";
9013
9111
  import { join as join20 } from "path";
9014
9112
  import { pipeline } from "stream/promises";
9015
9113
  import { createGunzip } from "zlib";
9016
9114
  function getScipBinDir() {
9017
- return join20(homedir4(), ".unerr", "bin");
9115
+ return join20(homedir5(), ".unerr", "bin");
9018
9116
  }
9019
9117
  function getCachedBinaryPath(language) {
9020
9118
  const spec = DOWNLOAD_SPECS[language];
@@ -9720,6 +9818,10 @@ var init_orchestrator = __esm({
9720
9818
  });
9721
9819
 
9722
9820
  // src/intelligence/local-convention-detector.ts
9821
+ var local_convention_detector_exports = {};
9822
+ __export(local_convention_detector_exports, {
9823
+ detectLocalConventions: () => detectLocalConventions
9824
+ });
9723
9825
  import { dirname as dirname4 } from "path";
9724
9826
  async function detectLocalConventions(db) {
9725
9827
  const start = Date.now();
@@ -10281,6 +10383,7 @@ async function indexLocalProject(projectRoot, graphStore, repoId, opts) {
10281
10383
  name: entity.name,
10282
10384
  file_path: relPath,
10283
10385
  start_line: entity.line_start,
10386
+ end_line: entity.line_end,
10284
10387
  signature: entity.signature,
10285
10388
  body: "",
10286
10389
  // Skip body storage for indexing performance
@@ -10444,6 +10547,7 @@ async function reindexFile(projectRoot, filePath, graphStore, repoId) {
10444
10547
  name: e.name,
10445
10548
  file_path: relPath,
10446
10549
  start_line: e.line_start,
10550
+ end_line: e.line_end,
10447
10551
  signature: e.signature,
10448
10552
  body: "",
10449
10553
  fan_in: 0,
@@ -10860,8 +10964,8 @@ async function populateCozoDB(graphStore, entities, edges) {
10860
10964
  for (const fp of filePaths) {
10861
10965
  try {
10862
10966
  await graphStore.db.run(
10863
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, "module", $name, $fp, 0, "", "", 0, 0, "normal", $is_test]]
10864
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
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 }`,
10865
10969
  { key: `file:${fp}`, name: basename2(fp), fp, is_test: isTestFile(fp) }
10866
10970
  );
10867
10971
  } catch (err) {
@@ -10909,14 +11013,15 @@ async function populateCozoDB(graphStore, entities, edges) {
10909
11013
  async function insertEntity(graphStore, entity) {
10910
11014
  try {
10911
11015
  await graphStore.db.run(
10912
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, $kind, $name, $fp, $sl, $sig, $body, $fi, $fo, $rl, $is_test]]
10913
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
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 }`,
10914
11018
  {
10915
11019
  key: entity.key,
10916
11020
  kind: entity.kind,
10917
11021
  name: entity.name,
10918
11022
  fp: entity.file_path,
10919
11023
  sl: entity.start_line ?? 0,
11024
+ el: entity.end_line ?? 0,
10920
11025
  sig: entity.signature ?? "",
10921
11026
  body: entity.body ?? "",
10922
11027
  fi: entity.fan_in ?? 0,
@@ -11737,7 +11842,7 @@ import {
11737
11842
  unlinkSync as unlinkSync4,
11738
11843
  writeFileSync as writeFileSync12
11739
11844
  } from "fs";
11740
- import { homedir as homedir5 } from "os";
11845
+ import { homedir as homedir6 } from "os";
11741
11846
  import { dirname as dirname6, join as join29 } from "path";
11742
11847
  function getSkillDir(ide, cwd) {
11743
11848
  switch (ide) {
@@ -11761,9 +11866,9 @@ function getSkillDir(ide, cwd) {
11761
11866
  };
11762
11867
  case "windsurf":
11763
11868
  return {
11764
- dir: join29(cwd, ".windsurf", "rules"),
11869
+ dir: join29(cwd, ".windsurf", "skills"),
11765
11870
  ext: ".md",
11766
- dirPerSkill: false
11871
+ dirPerSkill: true
11767
11872
  };
11768
11873
  case "zed":
11769
11874
  return {
@@ -11771,6 +11876,18 @@ function getSkillDir(ide, cwd) {
11771
11876
  ext: ".md",
11772
11877
  dirPerSkill: false
11773
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
+ };
11774
11891
  case "antigravity":
11775
11892
  return {
11776
11893
  dir: join29(cwd, ".agents", "skills"),
@@ -11791,7 +11908,22 @@ function writeSkillFile(skill, ide, skillDir, ext, dirPerSkill) {
11791
11908
  const skillName = `unerr-${baseName}`;
11792
11909
  let filePath;
11793
11910
  let content;
11794
- if (dirPerSkill && ide === "antigravity") {
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") {
11795
11927
  const skillSubDir = join29(skillDir, skillName);
11796
11928
  mkdirSync16(skillSubDir, { recursive: true });
11797
11929
  filePath = join29(skillSubDir, "SKILL.md");
@@ -11804,9 +11936,6 @@ function writeSkillFile(skill, ide, skillDir, ext, dirPerSkill) {
11804
11936
  } else if (ide === "cursor" && ext === ".mdc") {
11805
11937
  filePath = join29(skillDir, `${skillName}${ext}`);
11806
11938
  content = formatCursorSkill(skill);
11807
- } else if (ide === "windsurf") {
11808
- filePath = join29(skillDir, `${skillName}${ext}`);
11809
- content = formatWindsurfSkill(skill);
11810
11939
  } else {
11811
11940
  filePath = join29(skillDir, `${skillName}${ext}`);
11812
11941
  content = formatMarkdownSkill(skill);
@@ -11888,53 +12017,45 @@ alwaysApply: ${alwaysApply}
11888
12017
  ${skill.content}
11889
12018
  `;
11890
12019
  }
11891
- function formatWindsurfSkill(skill) {
12020
+ function formatMarkdownSkill(skill) {
11892
12021
  const trigger = skill.trigger;
11893
12022
  const triggerType = trigger?.type ?? "always";
11894
- let wsType;
11895
- switch (triggerType) {
11896
- case "always":
11897
- wsType = "always_on";
11898
- break;
11899
- case "auto":
11900
- wsType = "glob";
11901
- break;
11902
- case "agent-requested":
11903
- wsType = "model_decision";
11904
- break;
11905
- case "manual":
11906
- wsType = "manual";
11907
- break;
11908
- default:
11909
- wsType = "always_on";
11910
- }
11911
12023
  const globLine = trigger?.globs && trigger.globs.length > 0 ? `
11912
12024
  globs: ${JSON.stringify(trigger.globs)}` : "";
11913
- const truncatedContent = skill.content.length > 5800 ? `${skill.content.slice(0, 5800)}
11914
-
11915
- [Truncated \u2014 full content exceeds Windsurf 6K limit]` : skill.content;
11916
12025
  return `---
11917
- trigger: ${wsType}${globLine}
11918
12026
  description: "${skill.description}"
12027
+ trigger: ${triggerType}${globLine}
12028
+ version: "${skill.version ?? "1.0.0"}"
11919
12029
  ---
11920
12030
 
11921
12031
  # ${skill.name}
11922
12032
 
11923
- ${truncatedContent}
12033
+ ${skill.content}
11924
12034
  `;
11925
12035
  }
11926
- function formatMarkdownSkill(skill) {
11927
- const trigger = skill.trigger;
11928
- const triggerType = trigger?.type ?? "always";
11929
- const globLine = trigger?.globs && trigger.globs.length > 0 ? `
11930
- globs: ${JSON.stringify(trigger.globs)}` : "";
12036
+ function formatWindsurfSkillMd(skill) {
11931
12037
  return `---
11932
- description: "${skill.description}"
11933
- trigger: ${triggerType}${globLine}
11934
- version: "${skill.version ?? "1.0.0"}"
12038
+ name: ${skill.name}
12039
+ description: ${skill.description}
11935
12040
  ---
11936
12041
 
11937
- # ${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
+ ---
11938
12059
 
11939
12060
  ${skill.content}
11940
12061
  `;
@@ -12020,7 +12141,7 @@ function scanSkillDirectory(dir) {
12020
12141
  return skills;
12021
12142
  }
12022
12143
  function loadLocalDirectorySkills(cwd) {
12023
- const userDir = join29(homedir5(), ".unerr", "skills");
12144
+ const userDir = join29(homedir6(), ".unerr", "skills");
12024
12145
  const projectDir = join29(cwd, ".unerr", "skills");
12025
12146
  const userSkills = scanSkillDirectory(userDir);
12026
12147
  const projectSkills = scanSkillDirectory(projectDir);
@@ -13085,9 +13206,9 @@ function createEmptyAllTime() {
13085
13206
  function loadStats() {
13086
13207
  const currentWeek = getWeekStart();
13087
13208
  try {
13088
- const { readFileSync: readFileSync56 } = __require("fs");
13209
+ const { readFileSync: readFileSync57 } = __require("fs");
13089
13210
  const raw = JSON.parse(
13090
- readFileSync56(getStatsPath(), "utf-8")
13211
+ readFileSync57(getStatsPath(), "utf-8")
13091
13212
  );
13092
13213
  if (raw.version !== 1) {
13093
13214
  return {
@@ -13800,9 +13921,9 @@ function getCumulativePath() {
13800
13921
  function loadCumulativeStats() {
13801
13922
  const currentWeek = getWeekStart2();
13802
13923
  try {
13803
- const { readFileSync: readFileSync56 } = __require("fs");
13924
+ const { readFileSync: readFileSync57 } = __require("fs");
13804
13925
  const raw = JSON.parse(
13805
- readFileSync56(getCumulativePath(), "utf-8")
13926
+ readFileSync57(getCumulativePath(), "utf-8")
13806
13927
  );
13807
13928
  if (raw.weekStart !== currentWeek) {
13808
13929
  return {
@@ -13863,9 +13984,9 @@ function createEmptyCumulativeLocal(weekStart) {
13863
13984
  function loadCumulativeLocalStats() {
13864
13985
  const currentWeek = getWeekStart2();
13865
13986
  try {
13866
- const { readFileSync: readFileSync56 } = __require("fs");
13987
+ const { readFileSync: readFileSync57 } = __require("fs");
13867
13988
  const raw = JSON.parse(
13868
- readFileSync56(getCumulativeLocalPath(), "utf-8")
13989
+ readFileSync57(getCumulativeLocalPath(), "utf-8")
13869
13990
  );
13870
13991
  if (raw.weekStartDate !== currentWeek) {
13871
13992
  return createEmptyCumulativeLocal(currentWeek);
@@ -16521,6 +16642,250 @@ var init_record_fact = __esm({
16521
16642
  }
16522
16643
  });
16523
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
+
16524
16889
  // src/proxy/auto-bootstrap.ts
16525
16890
  var auto_bootstrap_exports = {};
16526
16891
  __export(auto_bootstrap_exports, {
@@ -16969,8 +17334,8 @@ var init_model_pricing = __esm({
16969
17334
  });
16970
17335
 
16971
17336
  // src/tracking/entity-rewind.ts
16972
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync20 } from "fs";
16973
- import { join as join44 } from "path";
17337
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync20 } from "fs";
17338
+ import { join as join45 } from "path";
16974
17339
  async function revertEntity(entityName, localGraph, projectRoot, filePath) {
16975
17340
  const driftEntity = await findDriftEntity(localGraph, entityName, filePath);
16976
17341
  if (!driftEntity) {
@@ -16982,7 +17347,7 @@ async function revertEntity(entityName, localGraph, projectRoot, filePath) {
16982
17347
  error: `No drifted entity found: "${entityName}"${filePath ? ` in ${filePath}` : ""}`
16983
17348
  };
16984
17349
  }
16985
- const absPath = join44(projectRoot, driftEntity.file_path);
17350
+ const absPath = join45(projectRoot, driftEntity.file_path);
16986
17351
  if (driftEntity.drift_status === "added") {
16987
17352
  return removeAddedEntity(absPath, driftEntity, localGraph);
16988
17353
  }
@@ -17047,7 +17412,7 @@ async function findDriftEntity(localGraph, entityName, filePath) {
17047
17412
  }
17048
17413
  async function removeAddedEntity(absPath, entity, localGraph) {
17049
17414
  try {
17050
- const content = readFileSync34(absPath, "utf-8");
17415
+ const content = readFileSync35(absPath, "utf-8");
17051
17416
  const lines = content.split("\n");
17052
17417
  const startIdx = entity.line_start - 1;
17053
17418
  const endIdx = entity.line_end;
@@ -17082,7 +17447,7 @@ async function restoreDeletedEntity(absPath, entity, localGraph) {
17082
17447
  };
17083
17448
  }
17084
17449
  try {
17085
- const content = readFileSync34(absPath, "utf-8");
17450
+ const content = readFileSync35(absPath, "utf-8");
17086
17451
  const lines = content.split("\n");
17087
17452
  const previousLines = entity.previous_body.split("\n");
17088
17453
  const insertIdx = Math.min(entity.line_start - 1, lines.length);
@@ -17118,7 +17483,7 @@ async function restoreModifiedEntity(absPath, entity, localGraph) {
17118
17483
  };
17119
17484
  }
17120
17485
  try {
17121
- const content = readFileSync34(absPath, "utf-8");
17486
+ const content = readFileSync35(absPath, "utf-8");
17122
17487
  const lines = content.split("\n");
17123
17488
  const previousLines = entity.previous_body.split("\n");
17124
17489
  const startIdx = entity.line_start - 1;
@@ -17530,7 +17895,7 @@ __export(file_outline_exports, {
17530
17895
  buildFileOutline: () => buildFileOutline,
17531
17896
  fileOutlineTool: () => fileOutlineTool
17532
17897
  });
17533
- import { existsSync as existsSync37, readFileSync as readFileSync35 } from "fs";
17898
+ import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
17534
17899
  import { relative as relative2, resolve } from "path";
17535
17900
  function normRisk(rl) {
17536
17901
  const x2 = (rl ?? "low").toLowerCase();
@@ -17589,10 +17954,10 @@ function detectExportedNames(lines, scanLimit) {
17589
17954
  async function buildFileOutline(params) {
17590
17955
  const abs = resolve(params.cwd, params.filePathArg);
17591
17956
  const rel = relative2(params.cwd, abs).replace(/\\/g, "/") || params.filePathArg;
17592
- if (!existsSync37(abs)) {
17957
+ if (!existsSync38(abs)) {
17593
17958
  throw new Error(`File not found: ${abs}`);
17594
17959
  }
17595
- const raw = readFileSync35(abs);
17960
+ const raw = readFileSync36(abs);
17596
17961
  const sampleEnd = Math.min(raw.length, 8192);
17597
17962
  for (let i = 0; i < sampleEnd; i++) {
17598
17963
  if (raw[i] === 0) {
@@ -17740,7 +18105,7 @@ __export(file_read_protocol_exports, {
17740
18105
  runFileReadForRouter: () => runFileReadForRouter,
17741
18106
  runFileReadTool: () => runFileReadTool
17742
18107
  });
17743
- import { existsSync as existsSync38, readFileSync as readFileSync36 } from "fs";
18108
+ import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
17744
18109
  import { relative as relative3, resolve as resolve2 } from "path";
17745
18110
  function isGeneratedPath(rel) {
17746
18111
  return /(?:^|\/)node_modules\/|(?:^|\/)dist\/|\/\.next\/|\.generated\./.test(
@@ -17815,10 +18180,10 @@ async function runFileReadForRouter(args, ctx) {
17815
18180
  const abs = resolve2(ctx.cwd, filePathArg);
17816
18181
  const rel = relative3(ctx.cwd, abs).replace(/\\/g, "/") || filePathArg;
17817
18182
  const isOutOfProject = rel.startsWith("..");
17818
- if (!existsSync38(abs)) {
18183
+ if (!existsSync39(abs)) {
17819
18184
  return { content: { error: `File not found: ${abs}` } };
17820
18185
  }
17821
- const raw = readFileSync36(abs);
18186
+ const raw = readFileSync37(abs);
17822
18187
  const sampleEnd = Math.min(raw.length, 8192);
17823
18188
  for (let i = 0; i < sampleEnd; i++) {
17824
18189
  if (raw[i] === 0) {
@@ -17845,9 +18210,16 @@ async function runFileReadForRouter(args, ctx) {
17845
18210
  content: {
17846
18211
  ...outline,
17847
18212
  gated: true,
17848
- _gate_reason: `File has ${totalLines} lines (> ${effectiveGate}). Structure shown \u2014 call file_read again with entity or offset/limit, or force_full:true.`
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.`
17849
18214
  },
17850
- _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
+ }
17851
18223
  };
17852
18224
  }
17853
18225
  if (entityName) {
@@ -17869,7 +18241,7 @@ async function runFileReadForRouter(args, ctx) {
17869
18241
  const match = ranked[0].entity;
17870
18242
  if (match.start_line >= 1 && match.start_line <= totalLines) {
17871
18243
  const start = Math.max(1, match.start_line - ENTITY_CONTEXT);
17872
- 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;
17873
18245
  const end = Math.min(totalLines, endLine + ENTITY_CONTEXT);
17874
18246
  offset = start;
17875
18247
  limit = Math.min(MAX_READ_LINES, end - start + 1);
@@ -17958,9 +18330,16 @@ async function runFileReadForRouter(args, ctx) {
17958
18330
  query: entityName,
17959
18331
  suggestions: []
17960
18332
  },
17961
- _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.`
17962
18334
  },
17963
- _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
+ }
17964
18343
  };
17965
18344
  }
17966
18345
  }
@@ -17996,10 +18375,12 @@ ${body}`;
17996
18375
  const meta = {
17997
18376
  format: "json",
17998
18377
  tokens_estimate: Math.ceil(sliced.join("\n").length / CHARS_PER_TOKEN4),
17999
- total_lines: totalLines
18378
+ total_lines: totalLines,
18379
+ total_chars: text2.length
18000
18380
  };
18001
18381
  if (isFullRead && sliced.length === totalLines) {
18002
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.";
18003
18384
  } else if (effOffset > 1 || sliced.length < totalLines || entityWindowApplied) {
18004
18385
  meta.optimization = `file_read window lines ${effOffset}-${effOffset + sliced.length - 1}`;
18005
18386
  }
@@ -18182,9 +18563,13 @@ var init_query_router = __esm({
18182
18563
  LOCAL_TOOLS = /* @__PURE__ */ new Set([
18183
18564
  "get_function",
18184
18565
  "get_class",
18566
+ "get_entity",
18567
+ // consolidated: replaces get_function + get_class
18185
18568
  "get_file",
18186
18569
  "get_callers",
18187
18570
  "get_callees",
18571
+ "get_references",
18572
+ // consolidated: replaces get_callers + get_callees
18188
18573
  "get_imports",
18189
18574
  "search_code",
18190
18575
  "get_rules",
@@ -18610,7 +18995,7 @@ var init_query_router = __esm({
18610
18995
  Object.assign(meta, fr._layer6_meta);
18611
18996
  if (this.tokenFlow && fr._layer6_meta.optimization && fr._layer6_meta.total_lines) {
18612
18997
  const deliveredTokens = fr._layer6_meta.tokens_estimate ?? 0;
18613
- 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);
18614
18999
  const fileReadSaved = fullFileTokens - deliveredTokens;
18615
19000
  if (fileReadSaved > 50) {
18616
19001
  this.tokenFlow.record({
@@ -19558,7 +19943,21 @@ var init_query_router = __esm({
19558
19943
  case "get_file": {
19559
19944
  const raw = args.key ?? args.name;
19560
19945
  const key = await this.resolveKeyArg(raw);
19561
- 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;
19562
19961
  }
19563
19962
  case "get_references": {
19564
19963
  const key = await this.resolveKeyArg(args.key);
@@ -19931,11 +20330,14 @@ var init_query_router = __esm({
19931
20330
  ENTITY_ARRAY_TOOLS = /* @__PURE__ */ new Set([
19932
20331
  "get_callers",
19933
20332
  "get_callees",
20333
+ "get_references",
19934
20334
  "search_code"
19935
20335
  ]);
19936
20336
  SINGLE_ENTITY_TOOLS = /* @__PURE__ */ new Set([
20337
+ "get_entity",
19937
20338
  "get_function",
19938
- "get_class"
20339
+ "get_class",
20340
+ "get_file"
19939
20341
  ]);
19940
20342
  }
19941
20343
  });
@@ -21074,8 +21476,8 @@ __export(causal_bridge_exports, {
21074
21476
  assembleCausalChain: () => assembleCausalChain,
21075
21477
  computeDurability: () => computeDurability
21076
21478
  });
21077
- import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
21078
- import { join as join45 } from "path";
21479
+ import { existsSync as existsSync40, readFileSync as readFileSync38 } from "fs";
21480
+ import { join as join46 } from "path";
21079
21481
  function computeAggregateDurability(interactions) {
21080
21482
  if (interactions.length === 0) return 1;
21081
21483
  const survivedCount = interactions.filter((i) => i.survived).length;
@@ -21232,9 +21634,9 @@ var init_causal_bridge = __esm({
21232
21634
  return chains;
21233
21635
  }
21234
21636
  loadEntityLedgerEntries(entityKey2) {
21235
- const ledgerPath = join45(this.unerrDir, "ledger", "shadow.jsonl");
21236
- if (!existsSync39(ledgerPath)) return [];
21237
- 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");
21238
21640
  const lines = content.split("\n").filter((l) => l.trim().length > 0);
21239
21641
  const entries = [];
21240
21642
  for (const line of lines) {
@@ -21720,15 +22122,15 @@ var context_ledger_exports = {};
21720
22122
  __export(context_ledger_exports, {
21721
22123
  createContextLedger: () => createContextLedger
21722
22124
  });
21723
- import { existsSync as existsSync40, mkdirSync as mkdirSync23, readFileSync as readFileSync38, writeFileSync as writeFileSync21 } from "fs";
21724
- 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";
21725
22127
  function createContextLedger(unerrDir) {
21726
- const stateDir = join46(unerrDir, "state");
21727
- const filePath = join46(stateDir, "context-ledger.json");
22128
+ const stateDir = join47(unerrDir, "state");
22129
+ const filePath = join47(stateDir, "context-ledger.json");
21728
22130
  let records = [];
21729
22131
  let index = /* @__PURE__ */ new Map();
21730
22132
  function ensureDir() {
21731
- if (!existsSync40(stateDir)) {
22133
+ if (!existsSync41(stateDir)) {
21732
22134
  mkdirSync23(stateDir, { recursive: true });
21733
22135
  }
21734
22136
  }
@@ -21737,13 +22139,13 @@ function createContextLedger(unerrDir) {
21737
22139
  }
21738
22140
  function load() {
21739
22141
  ensureDir();
21740
- if (!existsSync40(filePath)) {
22142
+ if (!existsSync41(filePath)) {
21741
22143
  records = [];
21742
22144
  index = /* @__PURE__ */ new Map();
21743
22145
  return /* @__PURE__ */ new Map();
21744
22146
  }
21745
22147
  try {
21746
- const raw = readFileSync38(filePath, "utf-8");
22148
+ const raw = readFileSync39(filePath, "utf-8");
21747
22149
  const parsed = JSON.parse(raw);
21748
22150
  records = Array.isArray(parsed) ? parsed : [];
21749
22151
  } catch {
@@ -22650,13 +23052,13 @@ __export(intent_correlator_exports, {
22650
23052
  IntentCorrelator: () => IntentCorrelator
22651
23053
  });
22652
23054
  import {
22653
- existsSync as existsSync41,
23055
+ existsSync as existsSync42,
22654
23056
  mkdirSync as mkdirSync24,
22655
- readFileSync as readFileSync39,
23057
+ readFileSync as readFileSync40,
22656
23058
  renameSync,
22657
23059
  writeFileSync as writeFileSync22
22658
23060
  } from "fs";
22659
- import { join as join47 } from "path";
23061
+ import { join as join48 } from "path";
22660
23062
  function extractFiles4(args) {
22661
23063
  const files = args.files;
22662
23064
  if (files && Array.isArray(files)) {
@@ -22704,9 +23106,9 @@ var init_intent_correlator = __esm({
22704
23106
  pendingPath;
22705
23107
  pending = [];
22706
23108
  constructor(unerrDir) {
22707
- this.ledgerDir = join47(unerrDir, "ledger");
22708
- this.pendingPath = join47(this.ledgerDir, "pending_correlations.json");
22709
- if (!existsSync41(this.ledgerDir)) {
23109
+ this.ledgerDir = join48(unerrDir, "ledger");
23110
+ this.pendingPath = join48(this.ledgerDir, "pending_correlations.json");
23111
+ if (!existsSync42(this.ledgerDir)) {
22710
23112
  mkdirSync24(this.ledgerDir, { recursive: true });
22711
23113
  }
22712
23114
  this.load();
@@ -22798,9 +23200,9 @@ var init_intent_correlator = __esm({
22798
23200
  }
22799
23201
  // ── Persistence ─────────────────────────────────────────────────
22800
23202
  load() {
22801
- if (!existsSync41(this.pendingPath)) return;
23203
+ if (!existsSync42(this.pendingPath)) return;
22802
23204
  try {
22803
- const raw = readFileSync39(this.pendingPath, "utf-8");
23205
+ const raw = readFileSync40(this.pendingPath, "utf-8");
22804
23206
  const parsed = JSON.parse(raw);
22805
23207
  if (Array.isArray(parsed)) {
22806
23208
  this.pending = parsed;
@@ -22832,14 +23234,14 @@ __export(working_snapshots_exports, {
22832
23234
  });
22833
23235
  import { randomBytes as randomBytes3 } from "crypto";
22834
23236
  import {
22835
- existsSync as existsSync42,
23237
+ existsSync as existsSync43,
22836
23238
  mkdirSync as mkdirSync25,
22837
- readFileSync as readFileSync40,
22838
- readdirSync as readdirSync9,
23239
+ readFileSync as readFileSync41,
23240
+ readdirSync as readdirSync10,
22839
23241
  rmSync as rmSync2,
22840
23242
  writeFileSync as writeFileSync23
22841
23243
  } from "fs";
22842
- import { join as join48 } from "path";
23244
+ import { join as join49 } from "path";
22843
23245
  var _log2, MAX_SNAPSHOTS, AUTO_SNAPSHOT_COOLDOWN_MS, WorkingSnapshotStore;
22844
23246
  var init_working_snapshots = __esm({
22845
23247
  "src/tracking/working-snapshots.ts"() {
@@ -22857,8 +23259,8 @@ var init_working_snapshots = __esm({
22857
23259
  unerrDir;
22858
23260
  constructor(unerrDir) {
22859
23261
  this.unerrDir = unerrDir;
22860
- this.snapshotDir = join48(unerrDir, "snapshots");
22861
- if (!existsSync42(this.snapshotDir)) {
23262
+ this.snapshotDir = join49(unerrDir, "snapshots");
23263
+ if (!existsSync43(this.snapshotDir)) {
22862
23264
  mkdirSync25(this.snapshotDir, { recursive: true });
22863
23265
  }
22864
23266
  }
@@ -22878,7 +23280,7 @@ var init_working_snapshots = __esm({
22878
23280
  processed: false
22879
23281
  };
22880
23282
  writeFileSync23(
22881
- join48(this.snapshotDir, `${id}.json`),
23283
+ join49(this.snapshotDir, `${id}.json`),
22882
23284
  JSON.stringify(snapshot, null, 2),
22883
23285
  "utf-8"
22884
23286
  );
@@ -22892,10 +23294,10 @@ var init_working_snapshots = __esm({
22892
23294
  * Get a snapshot by ID.
22893
23295
  */
22894
23296
  get(snapshotId) {
22895
- const filePath = join48(this.snapshotDir, `${snapshotId}.json`);
22896
- if (!existsSync42(filePath)) return null;
23297
+ const filePath = join49(this.snapshotDir, `${snapshotId}.json`);
23298
+ if (!existsSync43(filePath)) return null;
22897
23299
  try {
22898
- return JSON.parse(readFileSync40(filePath, "utf-8"));
23300
+ return JSON.parse(readFileSync41(filePath, "utf-8"));
22899
23301
  } catch {
22900
23302
  return null;
22901
23303
  }
@@ -22904,14 +23306,14 @@ var init_working_snapshots = __esm({
22904
23306
  * List all snapshots, most recent first.
22905
23307
  */
22906
23308
  list() {
22907
- if (!existsSync42(this.snapshotDir)) return [];
22908
- const files = readdirSync9(this.snapshotDir).filter(
23309
+ if (!existsSync43(this.snapshotDir)) return [];
23310
+ const files = readdirSync10(this.snapshotDir).filter(
22909
23311
  (f) => f.endsWith(".json")
22910
23312
  );
22911
23313
  const snapshots = [];
22912
23314
  for (const file of files) {
22913
23315
  try {
22914
- const raw = readFileSync40(join48(this.snapshotDir, file), "utf-8");
23316
+ const raw = readFileSync41(join49(this.snapshotDir, file), "utf-8");
22915
23317
  snapshots.push(JSON.parse(raw));
22916
23318
  } catch {
22917
23319
  }
@@ -22952,7 +23354,7 @@ var init_working_snapshots = __esm({
22952
23354
  if (!snapshot) return;
22953
23355
  snapshot.processed = true;
22954
23356
  writeFileSync23(
22955
- join48(this.snapshotDir, `${snapshotId}.json`),
23357
+ join49(this.snapshotDir, `${snapshotId}.json`),
22956
23358
  JSON.stringify(snapshot, null, 2),
22957
23359
  "utf-8"
22958
23360
  );
@@ -22967,8 +23369,8 @@ var init_working_snapshots = __esm({
22967
23369
  * Delete a snapshot.
22968
23370
  */
22969
23371
  delete(snapshotId) {
22970
- const filePath = join48(this.snapshotDir, `${snapshotId}.json`);
22971
- if (!existsSync42(filePath)) return false;
23372
+ const filePath = join49(this.snapshotDir, `${snapshotId}.json`);
23373
+ if (!existsSync43(filePath)) return false;
22972
23374
  rmSync2(filePath);
22973
23375
  return true;
22974
23376
  }
@@ -22976,10 +23378,10 @@ var init_working_snapshots = __esm({
22976
23378
  * Get the timeline branch counter from branch_context.json.
22977
23379
  */
22978
23380
  getTimelineBranch() {
22979
- const contextPath = join48(this.unerrDir, "branch_context.json");
22980
- if (!existsSync42(contextPath)) return 0;
23381
+ const contextPath = join49(this.unerrDir, "branch_context.json");
23382
+ if (!existsSync43(contextPath)) return 0;
22981
23383
  try {
22982
- const ctx = JSON.parse(readFileSync40(contextPath, "utf-8"));
23384
+ const ctx = JSON.parse(readFileSync41(contextPath, "utf-8"));
22983
23385
  return ctx.timelineBranch ?? 0;
22984
23386
  } catch {
22985
23387
  return 0;
@@ -22989,11 +23391,11 @@ var init_working_snapshots = __esm({
22989
23391
  * Increment the timeline branch counter. Returns the new value.
22990
23392
  */
22991
23393
  incrementTimelineBranch() {
22992
- const contextPath = join48(this.unerrDir, "branch_context.json");
23394
+ const contextPath = join49(this.unerrDir, "branch_context.json");
22993
23395
  let ctx = {};
22994
- if (existsSync42(contextPath)) {
23396
+ if (existsSync43(contextPath)) {
22995
23397
  try {
22996
- ctx = JSON.parse(readFileSync40(contextPath, "utf-8"));
23398
+ ctx = JSON.parse(readFileSync41(contextPath, "utf-8"));
22997
23399
  } catch {
22998
23400
  ctx = {};
22999
23401
  }
@@ -23166,8 +23568,8 @@ var quality_signals_exports = {};
23166
23568
  __export(quality_signals_exports, {
23167
23569
  QualitySignalTracker: () => QualitySignalTracker
23168
23570
  });
23169
- import { existsSync as existsSync43, readFileSync as readFileSync41, writeFileSync as writeFileSync24 } from "fs";
23170
- 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";
23171
23573
  function computeDurabilityFromAge(survivalMs) {
23172
23574
  if (survivalMs < FRAGILE_THRESHOLD_MS) {
23173
23575
  return 0.1 + survivalMs / FRAGILE_THRESHOLD_MS * 0.2;
@@ -23194,7 +23596,7 @@ var init_quality_signals = __esm({
23194
23596
  /** Maximum corrections to retain in memory/disk. */
23195
23597
  static MAX_CORRECTIONS = 200;
23196
23598
  constructor(unerrDir) {
23197
- this.signalsPath = join49(unerrDir, "state", "quality_signals.json");
23599
+ this.signalsPath = join50(unerrDir, "state", "quality_signals.json");
23198
23600
  this.signals = this.load();
23199
23601
  }
23200
23602
  /**
@@ -23286,8 +23688,8 @@ var init_quality_signals = __esm({
23286
23688
  */
23287
23689
  save() {
23288
23690
  try {
23289
- const dir = join49(this.signalsPath, "..");
23290
- if (!existsSync43(dir)) {
23691
+ const dir = join50(this.signalsPath, "..");
23692
+ if (!existsSync44(dir)) {
23291
23693
  const { mkdirSync: mkdirSync39 } = __require("fs");
23292
23694
  mkdirSync39(dir, { recursive: true });
23293
23695
  }
@@ -23333,7 +23735,7 @@ var init_quality_signals = __esm({
23333
23735
  * Load signals from disk.
23334
23736
  */
23335
23737
  load() {
23336
- if (!existsSync43(this.signalsPath)) {
23738
+ if (!existsSync44(this.signalsPath)) {
23337
23739
  return {
23338
23740
  durabilityScores: {},
23339
23741
  corrections: [],
@@ -23342,7 +23744,7 @@ var init_quality_signals = __esm({
23342
23744
  }
23343
23745
  try {
23344
23746
  return JSON.parse(
23345
- readFileSync41(this.signalsPath, "utf-8")
23747
+ readFileSync42(this.signalsPath, "utf-8")
23346
23748
  );
23347
23749
  } catch {
23348
23750
  return {
@@ -24359,8 +24761,8 @@ var incomplete_work_exports = {};
24359
24761
  __export(incomplete_work_exports, {
24360
24762
  IncompleteWorkDetector: () => IncompleteWorkDetector
24361
24763
  });
24362
- import { existsSync as existsSync44, mkdirSync as mkdirSync26, readFileSync as readFileSync42, writeFileSync as writeFileSync25 } from "fs";
24363
- 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";
24364
24766
  function severityRank(severity) {
24365
24767
  switch (severity) {
24366
24768
  case "high":
@@ -24553,9 +24955,9 @@ var init_incomplete_work = __esm({
24553
24955
  persistItems(items) {
24554
24956
  if (!this.unerrDir) return false;
24555
24957
  try {
24556
- const stateDir = join50(this.unerrDir, "state");
24557
- if (!existsSync44(stateDir)) mkdirSync26(stateDir, { recursive: true });
24558
- 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);
24559
24961
  writeFileSync25(
24560
24962
  filePath,
24561
24963
  JSON.stringify({
@@ -24575,9 +24977,9 @@ var init_incomplete_work = __esm({
24575
24977
  */
24576
24978
  static readPersistedItems(unerrDir) {
24577
24979
  try {
24578
- const filePath = join50(unerrDir, "state", PERSISTENCE_FILE);
24579
- if (!existsSync44(filePath)) return [];
24580
- 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"));
24581
24983
  return data.items ?? [];
24582
24984
  } catch {
24583
24985
  return [];
@@ -26048,7 +26450,7 @@ var transport_mux_exports = {};
26048
26450
  __export(transport_mux_exports, {
26049
26451
  TransportMux: () => TransportMux
26050
26452
  });
26051
- import { existsSync as existsSync45, unlinkSync as unlinkSync7 } from "fs";
26453
+ import { existsSync as existsSync46, unlinkSync as unlinkSync7 } from "fs";
26052
26454
  import { createServer as createServer2 } from "net";
26053
26455
  var _log4, TransportMux;
26054
26456
  var init_transport_mux = __esm({
@@ -26088,7 +26490,7 @@ var init_transport_mux = __esm({
26088
26490
  * Start listening for secondary clients on the Unix domain socket.
26089
26491
  */
26090
26492
  start() {
26091
- if (existsSync45(this.sockPath)) {
26493
+ if (existsSync46(this.sockPath)) {
26092
26494
  try {
26093
26495
  unlinkSync7(this.sockPath);
26094
26496
  } catch {
@@ -26121,7 +26523,7 @@ var init_transport_mux = __esm({
26121
26523
  this.server.close();
26122
26524
  this.server = null;
26123
26525
  }
26124
- if (existsSync45(this.sockPath)) {
26526
+ if (existsSync46(this.sockPath)) {
26125
26527
  try {
26126
26528
  unlinkSync7(this.sockPath);
26127
26529
  } catch {
@@ -26269,8 +26671,8 @@ __export(git_trailers_exports, {
26269
26671
  parseTrailersFromMessage: () => parseTrailersFromMessage,
26270
26672
  uninstallPrepareCommitMsgHook: () => uninstallPrepareCommitMsgHook
26271
26673
  });
26272
- import { existsSync as existsSync46, readFileSync as readFileSync43, writeFileSync as writeFileSync26 } from "fs";
26273
- 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";
26274
26676
  function getCommitTrailers(ledger, timelineBranch, branch) {
26275
26677
  const recent = ledger.getRecentEntries(1);
26276
26678
  if (recent.length === 0) return null;
@@ -26290,15 +26692,15 @@ function formatTrailers(trailers) {
26290
26692
  ].join("\n");
26291
26693
  }
26292
26694
  function installPrepareCommitMsgHook(projectRoot) {
26293
- const hooksDir = join51(projectRoot, ".git", "hooks");
26294
- if (!existsSync46(hooksDir)) {
26695
+ const hooksDir = join52(projectRoot, ".git", "hooks");
26696
+ if (!existsSync47(hooksDir)) {
26295
26697
  return false;
26296
26698
  }
26297
- const hookPath = join51(hooksDir, "prepare-commit-msg");
26699
+ const hookPath = join52(hooksDir, "prepare-commit-msg");
26298
26700
  const marker = "# unerr-trailer-injection";
26299
- if (existsSync46(hookPath)) {
26701
+ if (existsSync47(hookPath)) {
26300
26702
  try {
26301
- const existing = readFileSync43(hookPath, "utf-8");
26703
+ const existing = readFileSync44(hookPath, "utf-8");
26302
26704
  if (existing.includes(marker)) {
26303
26705
  return true;
26304
26706
  }
@@ -26327,10 +26729,10 @@ ${generateHookScript()}`;
26327
26729
  }
26328
26730
  }
26329
26731
  function uninstallPrepareCommitMsgHook(projectRoot) {
26330
- const hookPath = join51(projectRoot, ".git", "hooks", "prepare-commit-msg");
26331
- if (!existsSync46(hookPath)) return true;
26732
+ const hookPath = join52(projectRoot, ".git", "hooks", "prepare-commit-msg");
26733
+ if (!existsSync47(hookPath)) return true;
26332
26734
  try {
26333
- const content = readFileSync43(hookPath, "utf-8");
26735
+ const content = readFileSync44(hookPath, "utf-8");
26334
26736
  const marker = "# unerr-trailer-injection";
26335
26737
  if (!content.includes(marker)) return true;
26336
26738
  const lines = content.split("\n");
@@ -26495,15 +26897,15 @@ var init_http_transport = __esm({
26495
26897
 
26496
26898
  // src/tracking/branch-snapshot.ts
26497
26899
  import {
26498
- existsSync as existsSync47,
26900
+ existsSync as existsSync48,
26499
26901
  mkdirSync as mkdirSync28,
26500
- readFileSync as readFileSync44,
26501
- readdirSync as readdirSync10,
26902
+ readFileSync as readFileSync45,
26903
+ readdirSync as readdirSync11,
26502
26904
  rmSync as rmSync3,
26503
26905
  statSync as statSync7,
26504
26906
  writeFileSync as writeFileSync27
26505
26907
  } from "fs";
26506
- import { join as join52 } from "path";
26908
+ import { join as join53 } from "path";
26507
26909
  function sanitizeBranchName(branch) {
26508
26910
  return branch.replace(/\//g, "__").replace(/[^a-zA-Z0-9_.\-]/g, "_");
26509
26911
  }
@@ -26521,7 +26923,7 @@ var init_branch_snapshot = __esm({
26521
26923
  branchDir;
26522
26924
  projectRoot;
26523
26925
  constructor(unerrDir, projectRoot) {
26524
- this.branchDir = join52(unerrDir, "drift", "branches");
26926
+ this.branchDir = join53(unerrDir, "drift", "branches");
26525
26927
  this.projectRoot = projectRoot;
26526
26928
  }
26527
26929
  /**
@@ -26535,8 +26937,8 @@ var init_branch_snapshot = __esm({
26535
26937
  log16.info(`No drift entities/edges to snapshot for branch ${branch}`);
26536
26938
  }
26537
26939
  const dirName = sanitizeBranchName(branch);
26538
- const snapshotDir = join52(this.branchDir, dirName);
26539
- if (!existsSync47(snapshotDir)) {
26940
+ const snapshotDir = join53(this.branchDir, dirName);
26941
+ if (!existsSync48(snapshotDir)) {
26540
26942
  mkdirSync28(snapshotDir, { recursive: true });
26541
26943
  }
26542
26944
  const snapshot = {
@@ -26547,12 +26949,12 @@ var init_branch_snapshot = __esm({
26547
26949
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
26548
26950
  };
26549
26951
  writeFileSync27(
26550
- join52(snapshotDir, OVERLAY_FILE),
26952
+ join53(snapshotDir, OVERLAY_FILE),
26551
26953
  JSON.stringify(snapshot, null, 2),
26552
26954
  "utf-8"
26553
26955
  );
26554
26956
  writeFileSync27(
26555
- join52(snapshotDir, HASHES_FILE),
26957
+ join53(snapshotDir, HASHES_FILE),
26556
26958
  JSON.stringify(fileHashState, null, 2),
26557
26959
  "utf-8"
26558
26960
  );
@@ -26568,14 +26970,14 @@ var init_branch_snapshot = __esm({
26568
26970
  */
26569
26971
  async restoreSnapshot(branch, localGraph) {
26570
26972
  const dirName = sanitizeBranchName(branch);
26571
- const snapshotDir = join52(this.branchDir, dirName);
26572
- const overlayPath = join52(snapshotDir, OVERLAY_FILE);
26573
- if (!existsSync47(overlayPath)) {
26973
+ const snapshotDir = join53(this.branchDir, dirName);
26974
+ const overlayPath = join53(snapshotDir, OVERLAY_FILE);
26975
+ if (!existsSync48(overlayPath)) {
26574
26976
  log16.info(`No snapshot for branch ${branch} \u2014 first visit`);
26575
26977
  return null;
26576
26978
  }
26577
26979
  try {
26578
- const raw = readFileSync44(overlayPath, "utf-8");
26980
+ const raw = readFileSync45(overlayPath, "utf-8");
26579
26981
  const snapshot = JSON.parse(raw);
26580
26982
  for (const entity of snapshot.entities) {
26581
26983
  await localGraph.upsertDriftEntity(entity);
@@ -26587,7 +26989,7 @@ var init_branch_snapshot = __esm({
26587
26989
  }
26588
26990
  const now = /* @__PURE__ */ new Date();
26589
26991
  writeFileSync27(
26590
- join52(snapshotDir, ".last_access"),
26992
+ join53(snapshotDir, ".last_access"),
26591
26993
  now.toISOString(),
26592
26994
  "utf-8"
26593
26995
  );
@@ -26607,17 +27009,17 @@ var init_branch_snapshot = __esm({
26607
27009
  */
26608
27010
  hasSnapshot(branch) {
26609
27011
  const dirName = sanitizeBranchName(branch);
26610
- return existsSync47(join52(this.branchDir, dirName, OVERLAY_FILE));
27012
+ return existsSync48(join53(this.branchDir, dirName, OVERLAY_FILE));
26611
27013
  }
26612
27014
  /**
26613
27015
  * Get the file hash state from a branch snapshot.
26614
27016
  */
26615
27017
  getSnapshotFileHashes(branch) {
26616
27018
  const dirName = sanitizeBranchName(branch);
26617
- const hashesPath = join52(this.branchDir, dirName, HASHES_FILE);
26618
- if (!existsSync47(hashesPath)) return null;
27019
+ const hashesPath = join53(this.branchDir, dirName, HASHES_FILE);
27020
+ if (!existsSync48(hashesPath)) return null;
26619
27021
  try {
26620
- const raw = readFileSync44(hashesPath, "utf-8");
27022
+ const raw = readFileSync45(hashesPath, "utf-8");
26621
27023
  return JSON.parse(raw);
26622
27024
  } catch {
26623
27025
  return null;
@@ -26628,8 +27030,8 @@ var init_branch_snapshot = __esm({
26628
27030
  */
26629
27031
  deleteSnapshot(branch) {
26630
27032
  const dirName = sanitizeBranchName(branch);
26631
- const snapshotDir = join52(this.branchDir, dirName);
26632
- if (!existsSync47(snapshotDir)) return false;
27033
+ const snapshotDir = join53(this.branchDir, dirName);
27034
+ if (!existsSync48(snapshotDir)) return false;
26633
27035
  rmSync3(snapshotDir, { recursive: true, force: true });
26634
27036
  log16.info(`Deleted branch snapshot: ${branch}`);
26635
27037
  return true;
@@ -26638,14 +27040,14 @@ var init_branch_snapshot = __esm({
26638
27040
  * Garbage-collect snapshots for branches that no longer exist in git.
26639
27041
  */
26640
27042
  async garbageCollect() {
26641
- if (!existsSync47(this.branchDir)) return 0;
27043
+ if (!existsSync48(this.branchDir)) return 0;
26642
27044
  const gitBranches = new Set(await listBranches(this.projectRoot));
26643
27045
  if (gitBranches.size === 0) return 0;
26644
27046
  const snapshots = this.listSnapshots();
26645
27047
  let removed = 0;
26646
27048
  for (const snapshot of snapshots) {
26647
27049
  if (!gitBranches.has(snapshot.branch)) {
26648
- const snapshotDir = join52(this.branchDir, snapshot.id);
27050
+ const snapshotDir = join53(this.branchDir, snapshot.id);
26649
27051
  rmSync3(snapshotDir, { recursive: true, force: true });
26650
27052
  log16.info(`GC removed snapshot for deleted branch: ${snapshot.branch}`);
26651
27053
  removed++;
@@ -26657,20 +27059,20 @@ var init_branch_snapshot = __esm({
26657
27059
  * List all branch snapshots, sorted by most recently accessed first.
26658
27060
  */
26659
27061
  listSnapshots() {
26660
- if (!existsSync47(this.branchDir)) return [];
27062
+ if (!existsSync48(this.branchDir)) return [];
26661
27063
  try {
26662
- const entries = readdirSync10(this.branchDir, { withFileTypes: true });
27064
+ const entries = readdirSync11(this.branchDir, { withFileTypes: true });
26663
27065
  const snapshots = [];
26664
27066
  for (const entry of entries) {
26665
27067
  if (!entry.isDirectory()) continue;
26666
- const overlayPath = join52(this.branchDir, entry.name, OVERLAY_FILE);
26667
- if (!existsSync47(overlayPath)) continue;
27068
+ const overlayPath = join53(this.branchDir, entry.name, OVERLAY_FILE);
27069
+ if (!existsSync48(overlayPath)) continue;
26668
27070
  try {
26669
- const raw = readFileSync44(overlayPath, "utf-8");
27071
+ const raw = readFileSync45(overlayPath, "utf-8");
26670
27072
  const snapshot = JSON.parse(raw);
26671
- const accessPath = join52(this.branchDir, entry.name, ".last_access");
27073
+ const accessPath = join53(this.branchDir, entry.name, ".last_access");
26672
27074
  let accessedAt;
26673
- if (existsSync47(accessPath)) {
27075
+ if (existsSync48(accessPath)) {
26674
27076
  accessedAt = statSync7(accessPath).mtime;
26675
27077
  } else {
26676
27078
  accessedAt = statSync7(overlayPath).mtime;
@@ -26697,7 +27099,7 @@ var init_branch_snapshot = __esm({
26697
27099
  if (snapshots.length <= MAX_BRANCH_SNAPSHOTS) return;
26698
27100
  const toRemove = snapshots.slice(MAX_BRANCH_SNAPSHOTS);
26699
27101
  for (const snapshot of toRemove) {
26700
- const dir = join52(this.branchDir, snapshot.id);
27102
+ const dir = join53(this.branchDir, snapshot.id);
26701
27103
  rmSync3(dir, { recursive: true, force: true });
26702
27104
  log16.info(`LRU evicted branch snapshot: ${snapshot.branch}`);
26703
27105
  }
@@ -26714,13 +27116,13 @@ __export(file_hash_state_exports, {
26714
27116
  });
26715
27117
  import { createHash as createHash3 } from "crypto";
26716
27118
  import {
26717
- existsSync as existsSync48,
27119
+ existsSync as existsSync49,
26718
27120
  mkdirSync as mkdirSync29,
26719
- readFileSync as readFileSync45,
27121
+ readFileSync as readFileSync46,
26720
27122
  renameSync as renameSync2,
26721
27123
  writeFileSync as writeFileSync28
26722
27124
  } from "fs";
26723
- import { join as join53 } from "path";
27125
+ import { join as join54 } from "path";
26724
27126
  function contentSha256(content) {
26725
27127
  return createHash3("sha256").update(content).digest("hex");
26726
27128
  }
@@ -26734,8 +27136,8 @@ var init_file_hash_state = __esm({
26734
27136
  statePath;
26735
27137
  state;
26736
27138
  constructor(unerrDir) {
26737
- this.stateDir = join53(unerrDir, "state");
26738
- this.statePath = join53(this.stateDir, STATE_FILE);
27139
+ this.stateDir = join54(unerrDir, "state");
27140
+ this.statePath = join54(this.stateDir, STATE_FILE);
26739
27141
  this.state = this.load();
26740
27142
  }
26741
27143
  /**
@@ -26780,7 +27182,7 @@ var init_file_hash_state = __esm({
26780
27182
  * Persist state to disk atomically (write .tmp → rename).
26781
27183
  */
26782
27184
  save() {
26783
- if (!existsSync48(this.stateDir)) {
27185
+ if (!existsSync49(this.stateDir)) {
26784
27186
  mkdirSync29(this.stateDir, { recursive: true });
26785
27187
  }
26786
27188
  const tmpPath = `${this.statePath}.tmp`;
@@ -26807,11 +27209,11 @@ var init_file_hash_state = __esm({
26807
27209
  this.state = { files: { ...snapshot.files } };
26808
27210
  }
26809
27211
  load() {
26810
- if (!existsSync48(this.statePath)) {
27212
+ if (!existsSync49(this.statePath)) {
26811
27213
  return { files: {} };
26812
27214
  }
26813
27215
  try {
26814
- const raw = readFileSync45(this.statePath, "utf-8");
27216
+ const raw = readFileSync46(this.statePath, "utf-8");
26815
27217
  return JSON.parse(raw);
26816
27218
  } catch {
26817
27219
  return { files: {} };
@@ -26823,15 +27225,15 @@ var init_file_hash_state = __esm({
26823
27225
 
26824
27226
  // src/tracking/stash-manager.ts
26825
27227
  import {
26826
- existsSync as existsSync49,
27228
+ existsSync as existsSync50,
26827
27229
  mkdirSync as mkdirSync30,
26828
- readFileSync as readFileSync46,
26829
- readdirSync as readdirSync11,
27230
+ readFileSync as readFileSync47,
27231
+ readdirSync as readdirSync12,
26830
27232
  rmSync as rmSync4,
26831
27233
  statSync as statSync8,
26832
27234
  writeFileSync as writeFileSync29
26833
27235
  } from "fs";
26834
- import { join as join54 } from "path";
27236
+ import { join as join55 } from "path";
26835
27237
  var MAX_STASH_SNAPSHOTS, OVERLAY_FILE2, HASHES_FILE2, _log6, StashManager;
26836
27238
  var init_stash_manager = __esm({
26837
27239
  "src/tracking/stash-manager.ts"() {
@@ -26849,8 +27251,8 @@ var init_stash_manager = __esm({
26849
27251
  constructor(unerrDir, projectRoot) {
26850
27252
  this.unerrDir = unerrDir;
26851
27253
  this.projectRoot = projectRoot;
26852
- this.stashDir = join54(unerrDir, "drift", "stash");
26853
- this.gitDir = join54(projectRoot, ".git");
27254
+ this.stashDir = join55(unerrDir, "drift", "stash");
27255
+ this.gitDir = join55(projectRoot, ".git");
26854
27256
  this.previousStashRef = this.readStashRef();
26855
27257
  this.previousStashCount = this.getStashCount();
26856
27258
  }
@@ -26895,8 +27297,8 @@ var init_stash_manager = __esm({
26895
27297
  return null;
26896
27298
  }
26897
27299
  const snapshotId = stashRef.slice(0, 12);
26898
- const snapshotDir = join54(this.stashDir, snapshotId);
26899
- if (!existsSync49(snapshotDir)) {
27300
+ const snapshotDir = join55(this.stashDir, snapshotId);
27301
+ if (!existsSync50(snapshotDir)) {
26900
27302
  mkdirSync30(snapshotDir, { recursive: true });
26901
27303
  }
26902
27304
  const snapshot = {
@@ -26907,12 +27309,12 @@ var init_stash_manager = __esm({
26907
27309
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
26908
27310
  };
26909
27311
  writeFileSync29(
26910
- join54(snapshotDir, OVERLAY_FILE2),
27312
+ join55(snapshotDir, OVERLAY_FILE2),
26911
27313
  JSON.stringify(snapshot, null, 2),
26912
27314
  "utf-8"
26913
27315
  );
26914
27316
  writeFileSync29(
26915
- join54(snapshotDir, HASHES_FILE2),
27317
+ join55(snapshotDir, HASHES_FILE2),
26916
27318
  JSON.stringify(fileHashState, null, 2),
26917
27319
  "utf-8"
26918
27320
  );
@@ -26933,14 +27335,14 @@ var init_stash_manager = __esm({
26933
27335
  return 0;
26934
27336
  }
26935
27337
  const latest = snapshots[0];
26936
- const snapshotDir = join54(this.stashDir, latest.id);
26937
- const overlayPath = join54(snapshotDir, OVERLAY_FILE2);
26938
- if (!existsSync49(overlayPath)) {
27338
+ const snapshotDir = join55(this.stashDir, latest.id);
27339
+ const overlayPath = join55(snapshotDir, OVERLAY_FILE2);
27340
+ if (!existsSync50(overlayPath)) {
26939
27341
  _log6.warn(`Snapshot ${latest.id} missing overlay file`);
26940
27342
  return 0;
26941
27343
  }
26942
27344
  try {
26943
- const raw = readFileSync46(overlayPath, "utf-8");
27345
+ const raw = readFileSync47(overlayPath, "utf-8");
26944
27346
  const snapshot = JSON.parse(raw);
26945
27347
  for (const entity of snapshot.entities) {
26946
27348
  await localGraph.upsertDriftEntity(entity);
@@ -26970,10 +27372,10 @@ var init_stash_manager = __esm({
26970
27372
  const snapshots = this.listSnapshots();
26971
27373
  if (snapshots.length === 0) return null;
26972
27374
  const latest = snapshots[0];
26973
- const hashesPath = join54(this.stashDir, latest.id, HASHES_FILE2);
26974
- if (!existsSync49(hashesPath)) return null;
27375
+ const hashesPath = join55(this.stashDir, latest.id, HASHES_FILE2);
27376
+ if (!existsSync50(hashesPath)) return null;
26975
27377
  try {
26976
- const raw = readFileSync46(hashesPath, "utf-8");
27378
+ const raw = readFileSync47(hashesPath, "utf-8");
26977
27379
  return JSON.parse(raw);
26978
27380
  } catch {
26979
27381
  return null;
@@ -26984,8 +27386,8 @@ var init_stash_manager = __esm({
26984
27386
  */
26985
27387
  dropSnapshot(stashRef) {
26986
27388
  const snapshotId = stashRef.slice(0, 12);
26987
- const snapshotDir = join54(this.stashDir, snapshotId);
26988
- if (!existsSync49(snapshotDir)) return false;
27389
+ const snapshotDir = join55(this.stashDir, snapshotId);
27390
+ if (!existsSync50(snapshotDir)) return false;
26989
27391
  rmSync4(snapshotDir, { recursive: true, force: true });
26990
27392
  _log6.info(`Dropped stash snapshot: ${snapshotId}`);
26991
27393
  return true;
@@ -26994,14 +27396,14 @@ var init_stash_manager = __esm({
26994
27396
  * List all stash snapshots, sorted by most recent first.
26995
27397
  */
26996
27398
  listSnapshots() {
26997
- if (!existsSync49(this.stashDir)) return [];
27399
+ if (!existsSync50(this.stashDir)) return [];
26998
27400
  try {
26999
- const entries = readdirSync11(this.stashDir, { withFileTypes: true });
27401
+ const entries = readdirSync12(this.stashDir, { withFileTypes: true });
27000
27402
  const snapshots = [];
27001
27403
  for (const entry of entries) {
27002
27404
  if (!entry.isDirectory()) continue;
27003
- const overlayPath = join54(this.stashDir, entry.name, OVERLAY_FILE2);
27004
- if (!existsSync49(overlayPath)) continue;
27405
+ const overlayPath = join55(this.stashDir, entry.name, OVERLAY_FILE2);
27406
+ if (!existsSync50(overlayPath)) continue;
27005
27407
  try {
27006
27408
  const stat2 = statSync8(overlayPath);
27007
27409
  snapshots.push({ id: entry.name, savedAt: stat2.mtime });
@@ -27018,10 +27420,10 @@ var init_stash_manager = __esm({
27018
27420
  * Read the current stash ref SHA from `.git/refs/stash`.
27019
27421
  */
27020
27422
  readStashRef() {
27021
- const stashPath = join54(this.gitDir, "refs", "stash");
27022
- if (!existsSync49(stashPath)) return null;
27423
+ const stashPath = join55(this.gitDir, "refs", "stash");
27424
+ if (!existsSync50(stashPath)) return null;
27023
27425
  try {
27024
- return readFileSync46(stashPath, "utf-8").trim() || null;
27426
+ return readFileSync47(stashPath, "utf-8").trim() || null;
27025
27427
  } catch {
27026
27428
  return null;
27027
27429
  }
@@ -27030,10 +27432,10 @@ var init_stash_manager = __esm({
27030
27432
  * Count current stash entries via `.git/logs/refs/stash`.
27031
27433
  */
27032
27434
  getStashCount() {
27033
- const logPath = join54(this.gitDir, "logs", "refs", "stash");
27034
- if (!existsSync49(logPath)) return 0;
27435
+ const logPath = join55(this.gitDir, "logs", "refs", "stash");
27436
+ if (!existsSync50(logPath)) return 0;
27035
27437
  try {
27036
- const content = readFileSync46(logPath, "utf-8");
27438
+ const content = readFileSync47(logPath, "utf-8");
27037
27439
  return content.split("\n").filter((line) => line.trim().length > 0).length;
27038
27440
  } catch {
27039
27441
  return 0;
@@ -27047,7 +27449,7 @@ var init_stash_manager = __esm({
27047
27449
  if (snapshots.length <= MAX_STASH_SNAPSHOTS) return;
27048
27450
  const toRemove = snapshots.slice(MAX_STASH_SNAPSHOTS);
27049
27451
  for (const snapshot of toRemove) {
27050
- const dir = join54(this.stashDir, snapshot.id);
27452
+ const dir = join55(this.stashDir, snapshot.id);
27051
27453
  rmSync4(dir, { recursive: true, force: true });
27052
27454
  _log6.info(`LRU evicted stash snapshot: ${snapshot.id}`);
27053
27455
  }
@@ -27065,13 +27467,13 @@ __export(drift_tracker_exports, {
27065
27467
  determineOrigin: () => determineOrigin
27066
27468
  });
27067
27469
  import {
27068
- existsSync as existsSync50,
27470
+ existsSync as existsSync51,
27069
27471
  mkdirSync as mkdirSync31,
27070
- readFileSync as readFileSync47,
27472
+ readFileSync as readFileSync48,
27071
27473
  statSync as statSync9,
27072
27474
  writeFileSync as writeFileSync30
27073
27475
  } from "fs";
27074
- import { join as join55 } from "path";
27476
+ import { join as join56 } from "path";
27075
27477
  function determineOrigin(lastSyncTimestamp) {
27076
27478
  if (lastSyncTimestamp === 0) return "human";
27077
27479
  const elapsed = Date.now() - lastSyncTimestamp;
@@ -27282,15 +27684,15 @@ var init_drift_tracker = __esm({
27282
27684
  crossFileInvalidated: 0,
27283
27685
  edgesExtracted: 0
27284
27686
  };
27285
- const absPath = filePath.startsWith("/") ? filePath : join55(this.config.projectRoot, filePath);
27687
+ const absPath = filePath.startsWith("/") ? filePath : join56(this.config.projectRoot, filePath);
27286
27688
  const relPath = filePath.startsWith("/") ? filePath.slice(this.config.projectRoot.length + 1) : filePath;
27287
27689
  const language = detectLanguage2(relPath);
27288
27690
  if (!language) return result;
27289
- if (existsSync50(absPath) && !this.mtimeCache.check(absPath)) {
27691
+ if (existsSync51(absPath) && !this.mtimeCache.check(absPath)) {
27290
27692
  result.filesSkipped = 1;
27291
27693
  return result;
27292
27694
  }
27293
- if (!existsSync50(absPath)) {
27695
+ if (!existsSync51(absPath)) {
27294
27696
  const baseEntities2 = await this.localGraph.getEntitiesByFile(relPath);
27295
27697
  this.markFileDeleted(relPath, intentId);
27296
27698
  result.filesProcessed = 1;
@@ -27328,7 +27730,7 @@ var init_drift_tracker = __esm({
27328
27730
  this.maybeEmitDrift(relPath, result);
27329
27731
  return result;
27330
27732
  }
27331
- const content = readFileSync47(absPath, "utf-8");
27733
+ const content = readFileSync48(absPath, "utf-8");
27332
27734
  const sha = contentSha256(content);
27333
27735
  const decision = this.fileHashManager.shouldProcess(relPath, sha, headSha);
27334
27736
  if (decision === "skip") {
@@ -27562,7 +27964,7 @@ var init_drift_tracker = __esm({
27562
27964
  return await this.stashManager.restoreSnapshot(this.localGraph);
27563
27965
  }
27564
27966
  async markFileDeleted(filePath, intentId) {
27565
- const absPath = filePath.startsWith("/") ? filePath : join55(this.config.projectRoot, filePath);
27967
+ const absPath = filePath.startsWith("/") ? filePath : join56(this.config.projectRoot, filePath);
27566
27968
  this.mtimeCache.evict(absPath);
27567
27969
  const baseEntities = await this.localGraph.getEntitiesByFile(filePath);
27568
27970
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -27647,13 +28049,13 @@ var init_drift_tracker = __esm({
27647
28049
  return invalidated;
27648
28050
  }
27649
28051
  async saveDriftSummary() {
27650
- const driftDir = join55(this.config.unerrDir, "drift");
27651
- if (!existsSync50(driftDir)) {
28052
+ const driftDir = join56(this.config.unerrDir, "drift");
28053
+ if (!existsSync51(driftDir)) {
27652
28054
  mkdirSync31(driftDir, { recursive: true });
27653
28055
  }
27654
28056
  const summary = await this.getDriftSummary();
27655
28057
  writeFileSync30(
27656
- join55(driftDir, "drift_summary.json"),
28058
+ join56(driftDir, "drift_summary.json"),
27657
28059
  JSON.stringify(summary, null, 2),
27658
28060
  "utf-8"
27659
28061
  );
@@ -28062,8 +28464,8 @@ var incremental_indexer_exports = {};
28062
28464
  __export(incremental_indexer_exports, {
28063
28465
  indexFilesIncremental: () => indexFilesIncremental
28064
28466
  });
28065
- import { existsSync as existsSync51, readFileSync as readFileSync48 } from "fs";
28066
- 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";
28067
28469
  async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repoId) {
28068
28470
  const startMs = Date.now();
28069
28471
  const db = graphStore.db;
@@ -28078,9 +28480,9 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
28078
28480
  const changedEntityKeys = /* @__PURE__ */ new Set();
28079
28481
  const deletedEntityKeys = /* @__PURE__ */ new Set();
28080
28482
  for (const filePath of changedFiles) {
28081
- const absPath = filePath.startsWith("/") ? filePath : join56(projectRoot, filePath);
28483
+ const absPath = filePath.startsWith("/") ? filePath : join57(projectRoot, filePath);
28082
28484
  const relPath = filePath.startsWith("/") ? relative4(projectRoot, filePath) : filePath;
28083
- if (!existsSync51(absPath)) {
28485
+ if (!existsSync52(absPath)) {
28084
28486
  const deleted2 = await deleteFileFromGraph(db, relPath);
28085
28487
  filesDeleted++;
28086
28488
  totalEntitiesDeleted += deleted2.entitiesDeleted;
@@ -28093,7 +28495,7 @@ async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repo
28093
28495
  }
28094
28496
  let content;
28095
28497
  try {
28096
- content = readFileSync48(absPath, "utf-8");
28498
+ content = readFileSync49(absPath, "utf-8");
28097
28499
  } catch {
28098
28500
  continue;
28099
28501
  }
@@ -28381,6 +28783,7 @@ async function upsertEntitiesBatched(db, entities) {
28381
28783
  e.name,
28382
28784
  e.file_path,
28383
28785
  e.start_line ?? 0,
28786
+ e.end_line ?? 0,
28384
28787
  e.signature ?? "",
28385
28788
  e.body ?? "",
28386
28789
  e.fan_in ?? 0,
@@ -28398,21 +28801,22 @@ async function upsertEntitiesBatched(db, entities) {
28398
28801
  });
28399
28802
  try {
28400
28803
  await db.run(
28401
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [${rowStrs.join(", ")}]
28402
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`
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 }`
28403
28806
  );
28404
28807
  } catch {
28405
28808
  for (const entity of entities) {
28406
28809
  try {
28407
28810
  await db.run(
28408
- `?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, $kind, $name, $fp, $sl, $sig, $body, $fi, $fo, $rl, $is_test]]
28409
- :put entities { key => kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test }`,
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 }`,
28410
28813
  {
28411
28814
  key: entity.key,
28412
28815
  kind: entity.kind,
28413
28816
  name: entity.name,
28414
28817
  fp: entity.file_path,
28415
28818
  sl: entity.start_line ?? 0,
28819
+ el: entity.end_line ?? 0,
28416
28820
  sig: entity.signature ?? "",
28417
28821
  body: entity.body ?? "",
28418
28822
  fi: entity.fan_in ?? 0,
@@ -29258,8 +29662,8 @@ var workspace_manifest_exports = {};
29258
29662
  __export(workspace_manifest_exports, {
29259
29663
  WorkspaceManifest: () => WorkspaceManifest
29260
29664
  });
29261
- import { existsSync as existsSync52, mkdirSync as mkdirSync32, readFileSync as readFileSync49, writeFileSync as writeFileSync31 } from "fs";
29262
- 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";
29263
29667
  var WorkspaceManifest;
29264
29668
  var init_workspace_manifest = __esm({
29265
29669
  "src/tracking/workspace-manifest.ts"() {
@@ -29269,7 +29673,7 @@ var init_workspace_manifest = __esm({
29269
29673
  this.unerrDir = unerrDir;
29270
29674
  this.repoId = repoId;
29271
29675
  this.sessionId = sessionId;
29272
- this.manifestPath = join57(unerrDir, "manifest.json");
29676
+ this.manifestPath = join58(unerrDir, "manifest.json");
29273
29677
  this.data = this.load();
29274
29678
  }
29275
29679
  unerrDir;
@@ -29364,7 +29768,7 @@ var init_workspace_manifest = __esm({
29364
29768
  }
29365
29769
  // ── Internal ─────────────────────────────────────────────────────
29366
29770
  load() {
29367
- if (!existsSync52(this.manifestPath)) {
29771
+ if (!existsSync53(this.manifestPath)) {
29368
29772
  return {
29369
29773
  version: 1,
29370
29774
  repoId: this.repoId,
@@ -29374,7 +29778,7 @@ var init_workspace_manifest = __esm({
29374
29778
  };
29375
29779
  }
29376
29780
  try {
29377
- const raw = readFileSync49(this.manifestPath, "utf-8");
29781
+ const raw = readFileSync50(this.manifestPath, "utf-8");
29378
29782
  const parsed = JSON.parse(raw);
29379
29783
  if (parsed.repoId !== this.repoId) {
29380
29784
  return {
@@ -29397,7 +29801,7 @@ var init_workspace_manifest = __esm({
29397
29801
  }
29398
29802
  }
29399
29803
  save() {
29400
- if (!existsSync52(this.unerrDir)) {
29804
+ if (!existsSync53(this.unerrDir)) {
29401
29805
  mkdirSync32(this.unerrDir, { recursive: true });
29402
29806
  }
29403
29807
  writeFileSync31(
@@ -29591,8 +29995,8 @@ var log_tailer_exports = {};
29591
29995
  __export(log_tailer_exports, {
29592
29996
  startLogTailer: () => startLogTailer
29593
29997
  });
29594
- import { existsSync as existsSync53, statSync as statSync10, openSync, readSync, closeSync, watch } from "fs";
29595
- 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";
29596
30000
  function formatSize2(bytes) {
29597
30001
  if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
29598
30002
  if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`;
@@ -29699,29 +30103,29 @@ function tailFile(state, handler) {
29699
30103
  }
29700
30104
  }
29701
30105
  function startLogTailer(cwd, options) {
29702
- const logsDir = join58(cwd, ".unerr", "logs");
29703
- const compressionPath = join58(logsDir, "compression.jsonl");
29704
- const generalPath = join58(logsDir, "unerr.jsonl");
29705
- const tokenFlowPath = join58(logsDir, "token-flow.jsonl");
29706
- const fileReadsPath = join58(logsDir, "file-reads.jsonl");
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");
29707
30111
  const compressionState = {
29708
30112
  path: compressionPath,
29709
- offset: existsSync53(compressionPath) ? statSync10(compressionPath).size : 0,
30113
+ offset: existsSync54(compressionPath) ? statSync10(compressionPath).size : 0,
29710
30114
  watcher: null
29711
30115
  };
29712
30116
  const generalState = {
29713
30117
  path: generalPath,
29714
- offset: existsSync53(generalPath) ? statSync10(generalPath).size : 0,
30118
+ offset: existsSync54(generalPath) ? statSync10(generalPath).size : 0,
29715
30119
  watcher: null
29716
30120
  };
29717
30121
  const tokenFlowState = {
29718
30122
  path: tokenFlowPath,
29719
- offset: existsSync53(tokenFlowPath) ? statSync10(tokenFlowPath).size : 0,
30123
+ offset: existsSync54(tokenFlowPath) ? statSync10(tokenFlowPath).size : 0,
29720
30124
  watcher: null
29721
30125
  };
29722
30126
  const fileReadsState = {
29723
30127
  path: fileReadsPath,
29724
- offset: existsSync53(fileReadsPath) ? statSync10(fileReadsPath).size : 0,
30128
+ offset: existsSync54(fileReadsPath) ? statSync10(fileReadsPath).size : 0,
29725
30129
  watcher: null
29726
30130
  };
29727
30131
  function setupWatcher(state, handler) {
@@ -29749,7 +30153,7 @@ function startLogTailer(cwd, options) {
29749
30153
  ];
29750
30154
  const pollInterval = setInterval(() => {
29751
30155
  for (const { state, handler } of allStates) {
29752
- if (!state.watcher && existsSync53(state.path)) {
30156
+ if (!state.watcher && existsSync54(state.path)) {
29753
30157
  try {
29754
30158
  state.offset = 0;
29755
30159
  state.watcher = watch(state.path, () => {
@@ -30467,6 +30871,336 @@ function createIntelligenceRoutes(deps) {
30467
30871
  }
30468
30872
  });
30469
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
+ });
30470
31204
  app.get("/durability", async (c) => {
30471
31205
  const start = performance.now();
30472
31206
  const limit = parseLimit(c.req.query("ledger_limit"), 800, 5e3);
@@ -30753,13 +31487,13 @@ function createSystemRoutes(deps) {
30753
31487
  });
30754
31488
  app.get("/config", async (c) => {
30755
31489
  const start = performance.now();
30756
- const { existsSync: existsSync62, readFileSync: readFileSync56 } = await import("fs");
30757
- const { join: join67 } = await import("path");
31490
+ const { existsSync: existsSync63, readFileSync: readFileSync57 } = await import("fs");
31491
+ const { join: join68 } = await import("path");
30758
31492
  let config = {};
30759
- const configPath = join67(deps.cwd, ".unerr", "config.json");
30760
- if (existsSync62(configPath)) {
31493
+ const configPath = join68(deps.cwd, ".unerr", "config.json");
31494
+ if (existsSync63(configPath)) {
30761
31495
  try {
30762
- config = JSON.parse(readFileSync56(configPath, "utf-8"));
31496
+ config = JSON.parse(readFileSync57(configPath, "utf-8"));
30763
31497
  } catch {
30764
31498
  config = { error: "unreadable" };
30765
31499
  }
@@ -30960,20 +31694,20 @@ __export(session_history_exports, {
30960
31694
  getWeeklyStats: () => getWeeklyStats,
30961
31695
  readSessionHistory: () => readSessionHistory
30962
31696
  });
30963
- import { appendFileSync as appendFileSync8, existsSync as existsSync54, mkdirSync as mkdirSync33, readFileSync as readFileSync50 } from "fs";
30964
- 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";
30965
31699
  function appendSessionHistory(unerrDir, entry) {
30966
- const stateDir = join59(unerrDir, "state");
30967
- if (!existsSync54(stateDir)) mkdirSync33(stateDir, { recursive: true });
30968
- const historyPath = join59(stateDir, "session-history.jsonl");
31700
+ const stateDir = join60(unerrDir, "state");
31701
+ if (!existsSync55(stateDir)) mkdirSync33(stateDir, { recursive: true });
31702
+ const historyPath = join60(stateDir, "session-history.jsonl");
30969
31703
  appendFileSync8(historyPath, `${JSON.stringify(entry)}
30970
31704
  `, "utf-8");
30971
31705
  }
30972
31706
  function readSessionHistory(unerrDir) {
30973
- const historyPath = join59(unerrDir, "state", "session-history.jsonl");
30974
- if (!existsSync54(historyPath)) return [];
31707
+ const historyPath = join60(unerrDir, "state", "session-history.jsonl");
31708
+ if (!existsSync55(historyPath)) return [];
30975
31709
  try {
30976
- const content = readFileSync50(historyPath, "utf-8");
31710
+ const content = readFileSync51(historyPath, "utf-8");
30977
31711
  return content.split("\n").filter(Boolean).map((line) => {
30978
31712
  try {
30979
31713
  return JSON.parse(line);
@@ -31211,7 +31945,7 @@ function createTokenFlowRoutes(deps) {
31211
31945
  first_ts: data.first_ts,
31212
31946
  last_ts: data.last_ts,
31213
31947
  mechanisms: [...data.mechanisms],
31214
- agent_name: agentBySession.get(id) ?? null
31948
+ agent_name: agentBySession.get(id) ?? deps.getAgentName?.(id) ?? null
31215
31949
  };
31216
31950
  }).sort((a, b) => b.last_ts.localeCompare(a.last_ts));
31217
31951
  const paginated = allSessions.slice(offset, offset + limit);
@@ -31350,18 +32084,297 @@ var init_token_flow2 = __esm({
31350
32084
  }
31351
32085
  });
31352
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
+
31353
32366
  // src/server/http.ts
31354
32367
  var http_exports = {};
31355
32368
  __export(http_exports, {
31356
32369
  startDashboardServer: () => startDashboardServer
31357
32370
  });
31358
- 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";
31359
32372
  import { createServer as createServer3 } from "net";
31360
- import { dirname as dirname7, join as join60 } from "path";
31361
- import { fileURLToPath } from "url";
32373
+ import { dirname as dirname7, join as join61 } from "path";
32374
+ import { fileURLToPath as fileURLToPath2 } from "url";
31362
32375
  import { serve } from "@hono/node-server";
31363
32376
  import { serveStatic } from "@hono/node-server/serve-static";
31364
- import { Hono as Hono7 } from "hono";
32377
+ import { Hono as Hono8 } from "hono";
31365
32378
  async function findAvailablePort() {
31366
32379
  for (let port = PORT_START; port <= PORT_END; port++) {
31367
32380
  const available = await new Promise((resolve3) => {
@@ -31385,7 +32398,7 @@ async function startDashboardServer(opts) {
31385
32398
  return null;
31386
32399
  }
31387
32400
  opts.system.dashboardPort = port;
31388
- const app = new Hono7();
32401
+ const app = new Hono8();
31389
32402
  app.use("*", corsMiddleware);
31390
32403
  app.use("*", cacheMiddleware);
31391
32404
  app.use("*", timingMiddleware);
@@ -31400,10 +32413,13 @@ async function startDashboardServer(opts) {
31400
32413
  if (opts.tokenFlow) {
31401
32414
  app.route("/api/token-flow", createTokenFlowRoutes(opts.tokenFlow));
31402
32415
  }
31403
- const distDir = join60(dirname7(fileURLToPath(import.meta.url)), "ui");
31404
- const spaIndex = join60(distDir, "index.html");
31405
- if (existsSync55(spaIndex)) {
31406
- const spaHtml = readFileSync51(spaIndex, "utf-8");
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");
31407
32423
  app.use("*", serveStatic({ root: distDir }));
31408
32424
  app.get("*", (c) => {
31409
32425
  const path7 = c.req.path;
@@ -31432,7 +32448,7 @@ async function startDashboardServer(opts) {
31432
32448
  port,
31433
32449
  hostname: "127.0.0.1"
31434
32450
  });
31435
- const serverJsonPath = join60(opts.stateDir, "server.json");
32451
+ const serverJsonPath = join61(opts.stateDir, "server.json");
31436
32452
  const serverInfo = {
31437
32453
  port,
31438
32454
  pid: process.pid,
@@ -31466,6 +32482,7 @@ var init_http = __esm({
31466
32482
  init_system();
31467
32483
  init_temporal();
31468
32484
  init_token_flow2();
32485
+ init_reasoning_quality();
31469
32486
  PORT_START = 7600;
31470
32487
  PORT_END = 7700;
31471
32488
  }
@@ -31833,13 +32850,13 @@ var proxy_exports = {};
31833
32850
  __export(proxy_exports, {
31834
32851
  startProxy: () => startProxy
31835
32852
  });
31836
- import { existsSync as existsSync56, mkdirSync as mkdirSync34, readFileSync as readFileSync52, readdirSync as readdirSync12 } from "fs";
31837
- 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";
31838
32855
  async function getProxyFactStore(unerrDir) {
31839
32856
  if (proxyFactStore !== void 0) return proxyFactStore;
31840
32857
  try {
31841
32858
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
31842
- const cwd = join61(unerrDir, "..");
32859
+ const cwd = join62(unerrDir, "..");
31843
32860
  proxyFactStore = await TemporalFactStore2.create(cwd);
31844
32861
  return proxyFactStore;
31845
32862
  } catch {
@@ -31917,15 +32934,32 @@ async function handleRecallFactsProxy(args, unerrDir) {
31917
32934
  };
31918
32935
  }
31919
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
+ }
31920
32953
  async function startProxy(opts = {}) {
31921
32954
  const stats = createSessionStats(true);
31922
32955
  const startup = new StartupRenderer();
31923
32956
  startup.mount();
32957
+ migrateAgentPermissions(process.cwd());
31924
32958
  const lifecycle = createLifecycleActor(process.cwd());
31925
32959
  lifecycle.send({ type: "START_DETECT" });
31926
32960
  startup.setLocalMode(true);
31927
- const stateDir = join61(process.cwd(), ".unerr", "state");
31928
- if (!existsSync56(stateDir)) {
32961
+ const stateDir = join62(process.cwd(), ".unerr", "state");
32962
+ if (!existsSync57(stateDir)) {
31929
32963
  mkdirSync34(stateDir, { recursive: true });
31930
32964
  }
31931
32965
  const pidLock = new PidLock(stateDir);
@@ -31943,7 +32977,7 @@ async function startProxy(opts = {}) {
31943
32977
  startupLog.step(
31944
32978
  `PID ${process.pid} ${startupLog.fmt.muted(`\xB7 health localhost:${lockResult.healthPort}`)}`
31945
32979
  );
31946
- const ledgerDir = join61(process.cwd(), ".unerr", "ledger");
32980
+ const ledgerDir = join62(process.cwd(), ".unerr", "ledger");
31947
32981
  const previousSession = detectSessionResume(stateDir, ledgerDir);
31948
32982
  if (previousSession) {
31949
32983
  stats.isResumedSession = true;
@@ -31958,17 +32992,17 @@ async function startProxy(opts = {}) {
31958
32992
  if (opts.repoId) {
31959
32993
  repoIds = [opts.repoId];
31960
32994
  } else {
31961
- const configPath = join61(process.cwd(), ".unerr", "config.json");
31962
- if (existsSync56(configPath)) {
32995
+ const configPath = join62(process.cwd(), ".unerr", "config.json");
32996
+ if (existsSync57(configPath)) {
31963
32997
  try {
31964
- const config = JSON.parse(readFileSync52(configPath, "utf-8"));
32998
+ const config = JSON.parse(readFileSync53(configPath, "utf-8"));
31965
32999
  if (config.repoId) repoIds = [config.repoId];
31966
33000
  } catch {
31967
33001
  }
31968
33002
  }
31969
- const manifestsDir = join61(process.cwd(), ".unerr", "manifests");
31970
- if (repoIds.length === 0 && existsSync56(manifestsDir)) {
31971
- repoIds = readdirSync12(manifestsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
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", ""));
31972
33006
  }
31973
33007
  }
31974
33008
  if (repoIds.length === 0) {
@@ -32048,13 +33082,10 @@ async function startProxy(opts = {}) {
32048
33082
  startupLog.perf(
32049
33083
  `${startupLog.fmt.cyan("Persistent graph")} ${startupLog.fmt.muted("\u2014 zero recomputation, all intelligence preserved")}`
32050
33084
  );
32051
- const { shouldReindex: shouldReindex2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
32052
- if (shouldReindex2(projectRoot)) {
32053
- needsBackgroundIndex = true;
32054
- startupLog.step(
32055
- `${startupLog.fmt.muted("Source changes detected \u2014 incremental update after MCP ready")}`
32056
- );
32057
- }
33085
+ needsBackgroundIndex = true;
33086
+ startupLog.step(
33087
+ `${startupLog.fmt.muted("Background reindex will refresh graph data after MCP ready")}`
33088
+ );
32058
33089
  } else {
32059
33090
  const { loadLocalSnapshot: loadLocalSnapshot2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
32060
33091
  const snapshotStart = Date.now();
@@ -32094,6 +33125,32 @@ async function startProxy(opts = {}) {
32094
33125
  startupLog.done(
32095
33126
  `Migrated snapshot to persistent graph ${startupLog.fmt.muted(`\u2192 ${dbPath}`)}`
32096
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
+ }
32097
33154
  } else {
32098
33155
  needsBackgroundIndex = true;
32099
33156
  startupLog.step(
@@ -32161,7 +33218,7 @@ async function startProxy(opts = {}) {
32161
33218
  try {
32162
33219
  const { generateSessionResume: generateSessionResume2 } = await Promise.resolve().then(() => (init_session_resume(), session_resume_exports));
32163
33220
  const { ShadowLedger: ResumeLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32164
- const resumeLedger = new ResumeLedger(join61(process.cwd(), ".unerr"));
33221
+ const resumeLedger = new ResumeLedger(join62(process.cwd(), ".unerr"));
32165
33222
  const ledgerEntries = resumeLedger.getRecentEntries(50);
32166
33223
  const resumeCtx = generateSessionResume2(ledgerEntries);
32167
33224
  if (resumeCtx) {
@@ -32181,7 +33238,7 @@ async function startProxy(opts = {}) {
32181
33238
  const { createDurabilityScorer: createDurabilityScorer2 } = await Promise.resolve().then(() => (init_durability_scorer(), durability_scorer_exports));
32182
33239
  const durabilityScorer = createDurabilityScorer2();
32183
33240
  const { ShadowLedger: DurLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32184
- const durLedger = new DurLedger(join61(process.cwd(), ".unerr"));
33241
+ const durLedger = new DurLedger(join62(process.cwd(), ".unerr"));
32185
33242
  const durEntries = durLedger.getRecentEntries(200);
32186
33243
  if (durEntries.length > 0) {
32187
33244
  durabilityScorer.computeScores(durEntries);
@@ -32201,7 +33258,7 @@ async function startProxy(opts = {}) {
32201
33258
  try {
32202
33259
  const { detectInstableEntities: detectInstableEntities2 } = await Promise.resolve().then(() => (init_negative_knowledge(), negative_knowledge_exports));
32203
33260
  const { ShadowLedger: NkLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32204
- const nkLedger = new NkLedger(join61(process.cwd(), ".unerr"));
33261
+ const nkLedger = new NkLedger(join62(process.cwd(), ".unerr"));
32205
33262
  const nkEntries = nkLedger.getRecentEntries(200);
32206
33263
  if (nkEntries.length > 0) {
32207
33264
  const antiPatterns = detectInstableEntities2(nkEntries);
@@ -32223,7 +33280,7 @@ async function startProxy(opts = {}) {
32223
33280
  try {
32224
33281
  const { CausalBridge: CausalBridge2 } = await Promise.resolve().then(() => (init_causal_bridge(), causal_bridge_exports));
32225
33282
  const causalBridge = new CausalBridge2(
32226
- join61(process.cwd(), ".unerr"),
33283
+ join62(process.cwd(), ".unerr"),
32227
33284
  process.cwd()
32228
33285
  );
32229
33286
  router2.setCausalBridge(causalBridge);
@@ -32233,7 +33290,7 @@ async function startProxy(opts = {}) {
32233
33290
  try {
32234
33291
  const { learnConventions: learnConventions2 } = await Promise.resolve().then(() => (init_convention_learner(), convention_learner_exports));
32235
33292
  const { ShadowLedger: ConvLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32236
- const convLedger = new ConvLedger(join61(process.cwd(), ".unerr"));
33293
+ const convLedger = new ConvLedger(join62(process.cwd(), ".unerr"));
32237
33294
  const convEntries = convLedger.getRecentEntries(100);
32238
33295
  if (convEntries.length > 0) {
32239
33296
  const learned = learnConventions2(convEntries);
@@ -32256,7 +33313,7 @@ async function startProxy(opts = {}) {
32256
33313
  try {
32257
33314
  const { computePromptDurabilityProfiles: computePromptDurabilityProfiles2 } = await Promise.resolve().then(() => (init_prompt_durability(), prompt_durability_exports));
32258
33315
  const { ShadowLedger: DurProfLedger } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32259
- const durProfLedger = new DurProfLedger(join61(process.cwd(), ".unerr"));
33316
+ const durProfLedger = new DurProfLedger(join62(process.cwd(), ".unerr"));
32260
33317
  const durProfEntries = durProfLedger.getRecentEntries(200);
32261
33318
  if (durProfEntries.length > 0) {
32262
33319
  const profiles = computePromptDurabilityProfiles2(durProfEntries);
@@ -32276,7 +33333,7 @@ async function startProxy(opts = {}) {
32276
33333
  }
32277
33334
  try {
32278
33335
  const { createContextLedger: createContextLedger2 } = await Promise.resolve().then(() => (init_context_ledger(), context_ledger_exports));
32279
- const contextLedger = createContextLedger2(join61(process.cwd(), ".unerr"));
33336
+ const contextLedger = createContextLedger2(join62(process.cwd(), ".unerr"));
32280
33337
  contextLedger.load();
32281
33338
  contextLedger.prune();
32282
33339
  router2.setContextLedger(contextLedger);
@@ -32365,7 +33422,7 @@ async function startProxy(opts = {}) {
32365
33422
  }));
32366
33423
  const { ShadowLedger: ShadowLedger2 } = await Promise.resolve().then(() => (init_shadow_ledger(), shadow_ledger_exports));
32367
33424
  const { IntentCorrelator: IntentCorrelator2 } = await Promise.resolve().then(() => (init_intent_correlator(), intent_correlator_exports));
32368
- const unerrDirForLedger = join61(process.cwd(), ".unerr");
33425
+ const unerrDirForLedger = join62(process.cwd(), ".unerr");
32369
33426
  const shadowLedger2 = new ShadowLedger2(unerrDirForLedger);
32370
33427
  const intentCorrelator = new IntentCorrelator2(unerrDirForLedger);
32371
33428
  log21.info(
@@ -32379,7 +33436,7 @@ async function startProxy(opts = {}) {
32379
33436
  process.env.UNERR_SESSION_ID = shadowLedger2.getSessionId();
32380
33437
  try {
32381
33438
  const { writeFileSync: writeFileSync36 } = await import("fs");
32382
- writeFileSync36(join61(unerrDirForLedger, "state", "session.id"), shadowLedger2.getSessionId(), "utf-8");
33439
+ writeFileSync36(join62(unerrDirForLedger, "state", "session.id"), shadowLedger2.getSessionId(), "utf-8");
32383
33440
  } catch {
32384
33441
  }
32385
33442
  router2.setTokenFlow(tokenFlowWriter2);
@@ -32718,8 +33775,9 @@ async function startProxy(opts = {}) {
32718
33775
  lifecycle.send({ type: "INDEX_COMPLETE" });
32719
33776
  lifecycle.send({ type: "MCP_READY" });
32720
33777
  const { TransportMux: TransportMux2 } = await Promise.resolve().then(() => (init_transport_mux(), transport_mux_exports));
32721
- const sockPath = join61(stateDir, "proxy.sock");
33778
+ const sockPath = join62(stateDir, "proxy.sock");
32722
33779
  const transportMux = new TransportMux2(sockPath);
33780
+ const agentNameByClient = /* @__PURE__ */ new Map();
32723
33781
  transportMux.setCustomHttpHandler("/commit-context", (_url) => {
32724
33782
  const { getCommitTrailers: getCommitTrailers2 } = (init_git_trailers(), __toCommonJS(git_trailers_exports));
32725
33783
  const branch = branchContext?.currentBranch ?? "unknown";
@@ -32729,6 +33787,10 @@ async function startProxy(opts = {}) {
32729
33787
  });
32730
33788
  transportMux.setHandler(async (clientId, message) => {
32731
33789
  if (message.method === "initialize") {
33790
+ const clientName = message.params?.clientInfo?.name;
33791
+ if (clientName) {
33792
+ agentNameByClient.set(clientId, clientName);
33793
+ }
32732
33794
  return {
32733
33795
  jsonrpc: "2.0",
32734
33796
  id: message.id,
@@ -32757,6 +33819,88 @@ async function startProxy(opts = {}) {
32757
33819
  };
32758
33820
  }
32759
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
+ }
32760
33904
  const result = await router2.execute(name, toolArgs);
32761
33905
  recordToolCall(stats);
32762
33906
  recordLatency(stats.latency, result._meta.latency_ms);
@@ -32829,7 +33973,7 @@ async function startProxy(opts = {}) {
32829
33973
  try {
32830
33974
  const { DriftTracker: DriftTracker2 } = await Promise.resolve().then(() => (init_drift_tracker(), drift_tracker_exports));
32831
33975
  const { FileHashManager: FileHashManager2 } = await Promise.resolve().then(() => (init_file_hash_state(), file_hash_state_exports));
32832
- const unerrDir = join61(process.cwd(), ".unerr");
33976
+ const unerrDir = join62(process.cwd(), ".unerr");
32833
33977
  const fileHashManager = new FileHashManager2(unerrDir);
32834
33978
  _driftTracker = new DriftTracker2(
32835
33979
  { projectRoot: process.cwd(), repoId: repoIds[0], unerrDir },
@@ -32974,6 +34118,32 @@ async function startProxy(opts = {}) {
32974
34118
  `Post-index DriftTracker init failed: ${err instanceof Error ? err.message : String(err)}`
32975
34119
  );
32976
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
+ }
32977
34147
  },
32978
34148
  // onError
32979
34149
  (err) => {
@@ -32997,7 +34167,7 @@ async function startProxy(opts = {}) {
32997
34167
  }
32998
34168
  const { WorkspaceManifest: WorkspaceManifest2 } = await Promise.resolve().then(() => (init_workspace_manifest(), workspace_manifest_exports));
32999
34169
  const workspaceManifest = repoIds[0] ? new WorkspaceManifest2(
33000
- join61(process.cwd(), ".unerr"),
34170
+ join62(process.cwd(), ".unerr"),
33001
34171
  repoIds[0],
33002
34172
  shadowLedger2.getSessionId()
33003
34173
  ) : null;
@@ -33082,7 +34252,7 @@ async function startProxy(opts = {}) {
33082
34252
  if (proxyMode === "parse" && parseIndex) {
33083
34253
  try {
33084
34254
  const { extractEntitiesFromSource: extractEntitiesFromSource2 } = await Promise.resolve().then(() => (init_auto_bootstrap(), auto_bootstrap_exports));
33085
- const allFiles = readdirSync12(process.cwd(), {
34255
+ const allFiles = readdirSync13(process.cwd(), {
33086
34256
  recursive: true,
33087
34257
  encoding: "utf-8"
33088
34258
  });
@@ -33096,7 +34266,7 @@ async function startProxy(opts = {}) {
33096
34266
  });
33097
34267
  for (const file of sourceFiles.slice(0, 500)) {
33098
34268
  try {
33099
- const content = readFileSync52(join61(process.cwd(), file), "utf-8");
34269
+ const content = readFileSync53(join62(process.cwd(), file), "utf-8");
33100
34270
  const entities = extractEntitiesFromSource2(file, content);
33101
34271
  parseIndex.addEntities(entities);
33102
34272
  } catch {
@@ -33158,7 +34328,7 @@ async function startProxy(opts = {}) {
33158
34328
  }
33159
34329
  const { writeFileSync: writeStatsFile } = await import("fs");
33160
34330
  const { computePercentiles: computePercentiles2 } = await Promise.resolve().then(() => (init_session_stats(), session_stats_exports));
33161
- const statsSnapshotPath = join61(stateDir, "session_stats.json");
34331
+ const statsSnapshotPath = join62(stateDir, "session_stats.json");
33162
34332
  const statsSnapshotInterval = setInterval(() => {
33163
34333
  try {
33164
34334
  const total = stats.toolCallsLocal;
@@ -33196,7 +34366,7 @@ async function startProxy(opts = {}) {
33196
34366
  const { startDashboardServer: startDashboardServer2 } = await Promise.resolve().then(() => (init_http(), http_exports));
33197
34367
  const { detectIde: detectIdeDashboard } = await Promise.resolve().then(() => (init_detect(), detect_exports));
33198
34368
  const ideType = await detectIdeDashboard(process.cwd());
33199
- const unerrDirForApi = join61(process.cwd(), ".unerr");
34369
+ const unerrDirForApi = join62(process.cwd(), ".unerr");
33200
34370
  dashboardHandle = await startDashboardServer2({
33201
34371
  system: {
33202
34372
  stats,
@@ -33241,12 +34411,24 @@ async function startProxy(opts = {}) {
33241
34411
  stateDir,
33242
34412
  tokenFlow: {
33243
34413
  unerrDir: unerrDirForApi,
33244
- 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
+ }
33245
34427
  },
33246
34428
  temporal: await (async () => {
33247
34429
  try {
33248
34430
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
33249
- const { readdirSync: readdirSync14, readFileSync: readFileSync56 } = await import("fs");
34431
+ const { readdirSync: readdirSync15, readFileSync: readFileSync57 } = await import("fs");
33250
34432
  const factStore2 = await TemporalFactStore2.create(
33251
34433
  process.cwd()
33252
34434
  );
@@ -33254,10 +34436,10 @@ async function startProxy(opts = {}) {
33254
34436
  factStore: factStore2,
33255
34437
  loadRecentSessions: (limit) => {
33256
34438
  try {
33257
- const sessDir = join61(unerrDirForApi, "sessions");
33258
- 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);
33259
34441
  return files.map((f) => {
33260
- 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();
33261
34443
  return JSON.parse(content);
33262
34444
  });
33263
34445
  } catch {
@@ -33330,7 +34512,7 @@ async function startProxy(opts = {}) {
33330
34512
  try {
33331
34513
  const { appendSessionHistory: appendSessionHistory2 } = (init_session_history(), __toCommonJS(session_history_exports));
33332
34514
  const topMech = Object.entries(tokenFlowSummary.by_mechanism).sort(([, a], [, b]) => b.tokens_saved - a.tokens_saved)[0];
33333
- appendSessionHistory2(join61(process.cwd(), ".unerr"), {
34515
+ appendSessionHistory2(join62(process.cwd(), ".unerr"), {
33334
34516
  sessionId: shadowLedger2.getSessionId(),
33335
34517
  startedAt: new Date(stats.sessionStartedAt).toISOString(),
33336
34518
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -33342,6 +34524,7 @@ async function startProxy(opts = {}) {
33342
34524
  dollarsSaved: router2.getSessionDollarsSaved(),
33343
34525
  modelId: "unknown",
33344
34526
  entityCount: 0,
34527
+ agentName: agentNameByClient.values().next().value ?? server.getClientVersion?.()?.name ?? void 0,
33345
34528
  tokenFlowSummary: {
33346
34529
  by_mechanism: Object.fromEntries(
33347
34530
  Object.entries(tokenFlowSummary.by_mechanism).map(([k, v]) => [
@@ -33455,7 +34638,7 @@ async function startProxy(opts = {}) {
33455
34638
  try {
33456
34639
  const correctionModule = (init_correction_detector(), __toCommonJS(correction_detector_exports));
33457
34640
  const detectCorrections2 = correctionModule.detectCorrections;
33458
- const ledgerPath = join61(
34641
+ const ledgerPath = join62(
33459
34642
  process.cwd(),
33460
34643
  ".unerr",
33461
34644
  "ledger",
@@ -33473,10 +34656,49 @@ async function startProxy(opts = {}) {
33473
34656
  `[unerr] Learned ${patterns.length} correction pattern${patterns.length !== 1 ? "s" : ""} from this session
33474
34657
  `
33475
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
+ }
33476
34682
  }
33477
34683
  } catch {
33478
34684
  }
33479
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
+ }
33480
34702
  if (workspaceManifest) {
33481
34703
  const orphans = intentCorrelator.getPending();
33482
34704
  if (orphans.length > 0) {
@@ -33639,13 +34861,13 @@ __export(session_logger_exports, {
33639
34861
  import { randomUUID as randomUUID2 } from "crypto";
33640
34862
  import {
33641
34863
  appendFileSync as appendFileSync9,
33642
- existsSync as existsSync57,
34864
+ existsSync as existsSync58,
33643
34865
  mkdirSync as mkdirSync35,
33644
- readdirSync as readdirSync13,
34866
+ readdirSync as readdirSync14,
33645
34867
  statSync as statSync11,
33646
34868
  unlinkSync as unlinkSync9
33647
34869
  } from "fs";
33648
- import { join as join62 } from "path";
34870
+ import { join as join63 } from "path";
33649
34871
  import { createConsola as createConsola2 } from "consola";
33650
34872
  function formatTimestamp() {
33651
34873
  const d = /* @__PURE__ */ new Date();
@@ -33653,10 +34875,10 @@ function formatTimestamp() {
33653
34875
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
33654
34876
  }
33655
34877
  function cleanupOldLogs(logsDir) {
33656
- if (!existsSync57(logsDir)) return;
34878
+ if (!existsSync58(logsDir)) return;
33657
34879
  try {
33658
- const files = readdirSync13(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => {
33659
- 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);
33660
34882
  const stat2 = statSync11(fullPath);
33661
34883
  return {
33662
34884
  name: f,
@@ -33673,7 +34895,7 @@ function cleanupOldLogs(logsDir) {
33673
34895
  }
33674
34896
  }
33675
34897
  }
33676
- const remaining = files.filter((f) => existsSync57(f.path));
34898
+ const remaining = files.filter((f) => existsSync58(f.path));
33677
34899
  if (remaining.length > MAX_FILES) {
33678
34900
  for (const file of remaining.slice(MAX_FILES)) {
33679
34901
  try {
@@ -33688,11 +34910,11 @@ function cleanupOldLogs(logsDir) {
33688
34910
  function initSessionLogger(opts = {}) {
33689
34911
  if (_logger) return _logger;
33690
34912
  const cwd = opts.cwd ?? process.cwd();
33691
- const logsDir = join62(cwd, ".unerr", "logs");
34913
+ const logsDir = join63(cwd, ".unerr", "logs");
33692
34914
  mkdirSync35(logsDir, { recursive: true });
33693
34915
  cleanupOldLogs(logsDir);
33694
34916
  const timestamp2 = formatTimestamp();
33695
- _logFilePath = join62(logsDir, `session-${timestamp2}.log`);
34917
+ _logFilePath = join63(logsDir, `session-${timestamp2}.log`);
33696
34918
  const levelNum = opts.level !== void 0 ? Number(opts.level) : void 0;
33697
34919
  const envLevel = process.env.UNERR_LOG_LEVEL;
33698
34920
  const resolvedLevel = levelNum ?? (envLevel ? Number(envLevel) : 3);
@@ -33766,22 +34988,22 @@ __export(setup_wizard_exports, {
33766
34988
  runSetup: () => runSetup
33767
34989
  });
33768
34990
  import { createHash as createHash4 } from "crypto";
33769
- import { existsSync as existsSync58, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "fs";
33770
- import { join as join63 } from "path";
34991
+ import { existsSync as existsSync59, mkdirSync as mkdirSync36, writeFileSync as writeFileSync33 } from "fs";
34992
+ import { join as join64 } from "path";
33771
34993
  import * as clack from "@clack/prompts";
33772
34994
  async function runSetup(cwd) {
33773
34995
  const projectDir = cwd ?? process.cwd();
33774
34996
  clack.intro("unerr");
33775
34997
  clack.log.step("Project Setup");
33776
34998
  const repoId = await generateRepoId(projectDir);
33777
- const configDir = join63(projectDir, ".unerr");
34999
+ const configDir = join64(projectDir, ".unerr");
33778
35000
  mkdirSync36(configDir, { recursive: true });
33779
- const configPath = join63(configDir, "config.json");
33780
- const settingsPath = join63(configDir, "settings.json");
35001
+ const configPath = join64(configDir, "config.json");
35002
+ const settingsPath = join64(configDir, "settings.json");
33781
35003
  writeFileSync33(configPath, `${JSON.stringify({ repoId }, null, 2)}
33782
35004
  `);
33783
35005
  let existingSettings = {};
33784
- if (existsSync58(settingsPath)) {
35006
+ if (existsSync59(settingsPath)) {
33785
35007
  try {
33786
35008
  existingSettings = JSON.parse(
33787
35009
  __require("fs").readFileSync(settingsPath, "utf-8")
@@ -34390,13 +35612,13 @@ __export(session_summary_writer_exports, {
34390
35612
  readLastSession: () => readLastSession,
34391
35613
  writeSessionSummary: () => writeSessionSummary
34392
35614
  });
34393
- import { existsSync as existsSync59, mkdirSync as mkdirSync37, appendFileSync as appendFileSync10, writeFileSync as writeFileSync34, readFileSync as readFileSync53 } from "fs";
34394
- import { join as join64 } from "path";
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";
34395
35617
  function writeSessionSummary(unerrDir, ctx) {
34396
35618
  if (ctx.entries.length === 0) return null;
34397
35619
  try {
34398
- const sessionsDir = join64(unerrDir, "sessions");
34399
- if (!existsSync59(sessionsDir)) {
35620
+ const sessionsDir = join65(unerrDir, "sessions");
35621
+ if (!existsSync60(sessionsDir)) {
34400
35622
  mkdirSync37(sessionsDir, { recursive: true });
34401
35623
  }
34402
35624
  const sorted = [...ctx.entries].sort(
@@ -34440,7 +35662,7 @@ function writeSessionSummary(unerrDir, ctx) {
34440
35662
  token_estimate: ctx.tokenEstimate,
34441
35663
  branch: last.branch ?? "unknown"
34442
35664
  };
34443
- const filePath = join64(sessionsDir, `${ctx.sessionId}.jsonl`);
35665
+ const filePath = join65(sessionsDir, `${ctx.sessionId}.jsonl`);
34444
35666
  appendFileSync10(filePath, `${JSON.stringify(record)}
34445
35667
  `, "utf-8");
34446
35668
  writeLastSessionPointer(unerrDir, ctx.sessionId, record);
@@ -34455,9 +35677,9 @@ function writeSessionSummary(unerrDir, ctx) {
34455
35677
  }
34456
35678
  function readLastSession(unerrDir) {
34457
35679
  try {
34458
- const pointerPath = join64(unerrDir, "state", "last_session.json");
34459
- if (!existsSync59(pointerPath)) return null;
34460
- 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");
34461
35683
  return JSON.parse(content);
34462
35684
  } catch {
34463
35685
  return null;
@@ -34465,11 +35687,11 @@ function readLastSession(unerrDir) {
34465
35687
  }
34466
35688
  function writeLastSessionPointer(unerrDir, sessionId, record) {
34467
35689
  try {
34468
- const stateDir = join64(unerrDir, "state");
34469
- if (!existsSync59(stateDir)) {
35690
+ const stateDir = join65(unerrDir, "state");
35691
+ if (!existsSync60(stateDir)) {
34470
35692
  mkdirSync37(stateDir, { recursive: true });
34471
35693
  }
34472
- const pointerPath = join64(stateDir, "last_session.json");
35694
+ const pointerPath = join65(stateDir, "last_session.json");
34473
35695
  writeFileSync34(pointerPath, JSON.stringify(record, null, 2), "utf-8");
34474
35696
  } catch {
34475
35697
  }
@@ -34667,16 +35889,33 @@ var mcp_server_exports = {};
34667
35889
  __export(mcp_server_exports, {
34668
35890
  startMcpServer: () => startMcpServer
34669
35891
  });
34670
- import { existsSync as existsSync60, mkdirSync as mkdirSync38, readFileSync as readFileSync54, writeFileSync as writeFileSync35 } from "fs";
34671
- 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
+ }
34672
35910
  async function startMcpServer(cwd) {
34673
35911
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
34674
35912
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
34675
35913
  const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
34676
- const unerrDir = join65(cwd, ".unerr");
34677
- if (!existsSync60(unerrDir)) {
35914
+ const unerrDir = join66(cwd, ".unerr");
35915
+ if (!existsSync61(unerrDir)) {
34678
35916
  mkdirSync38(unerrDir, { recursive: true });
34679
35917
  }
35918
+ migrateAgentPermissions2(cwd);
34680
35919
  await initTier2Modules(unerrDir);
34681
35920
  const server = new Server(
34682
35921
  { name: "unerr-local", version: "0.1.0" },
@@ -34694,6 +35933,25 @@ async function startMcpServer(cwd) {
34694
35933
  if (name === "recall_facts") {
34695
35934
  return handleRecallFacts(args);
34696
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
+ }
34697
35955
  if (!graphReady || !router) {
34698
35956
  if (name === "file_read" || name === "file_outline") {
34699
35957
  try {
@@ -34773,25 +36031,6 @@ async function startMcpServer(cwd) {
34773
36031
  ]
34774
36032
  };
34775
36033
  }
34776
- const PROXY_ONLY_TOOLS = [
34777
- "unerr_get_timeline",
34778
- "unerr_revert_to_working_state",
34779
- "unerr_mark_working"
34780
- ];
34781
- if (PROXY_ONLY_TOOLS.includes(name)) {
34782
- return {
34783
- content: [
34784
- {
34785
- type: "text",
34786
- text: JSON.stringify({
34787
- tool: name,
34788
- available: false,
34789
- message: `${name} requires the full unerr proxy (run 'unerr' without --mcp). The timeline/revert subsystem is not available in lightweight MCP mode.`
34790
- })
34791
- }
34792
- ]
34793
- };
34794
- }
34795
36034
  try {
34796
36035
  const result = await router.execute(name, args);
34797
36036
  let text2 = stringifyMcpToolJson(result.content);
@@ -35079,9 +36318,9 @@ async function initTier2Modules(unerrDir) {
35079
36318
  tokenFlowWriter = new TokenFlowWriter2(unerrDir, sessionId);
35080
36319
  process.env.UNERR_SESSION_ID = sessionId;
35081
36320
  try {
35082
- const stateDir = join65(unerrDir, "state");
36321
+ const stateDir = join66(unerrDir, "state");
35083
36322
  mkdirSync38(stateDir, { recursive: true });
35084
- writeFileSync35(join65(stateDir, "session.id"), sessionId, "utf-8");
36323
+ writeFileSync35(join66(stateDir, "session.id"), sessionId, "utf-8");
35085
36324
  } catch {
35086
36325
  }
35087
36326
  log24.info(`Token flow active (session ${sessionId.slice(0, 8)})`);
@@ -35112,7 +36351,7 @@ async function initTier2Modules(unerrDir) {
35112
36351
  }
35113
36352
  try {
35114
36353
  const { TemporalFactStore: TemporalFactStore2 } = await Promise.resolve().then(() => (init_temporal_facts(), temporal_facts_exports));
35115
- const projectRoot = join65(unerrDir, "..");
36354
+ const projectRoot = join66(unerrDir, "..");
35116
36355
  factStore = await TemporalFactStore2.create(projectRoot);
35117
36356
  log24.info("Temporal fact store active (facts.db)");
35118
36357
  } catch (err) {
@@ -35222,8 +36461,8 @@ function wireTier2IntoRouter(r) {
35222
36461
  async function loadIntelligence(cwd) {
35223
36462
  const projectRoot = cwd;
35224
36463
  try {
35225
- const unerrDir = join65(projectRoot, ".unerr");
35226
- if (!existsSync60(unerrDir)) {
36464
+ const unerrDir = join66(projectRoot, ".unerr");
36465
+ if (!existsSync61(unerrDir)) {
35227
36466
  mkdirSync38(unerrDir, { recursive: true });
35228
36467
  }
35229
36468
  const { openPersistentDb: openPersistentDb2 } = await Promise.resolve().then(() => (init_persistent_db(), persistent_db_exports));
@@ -35232,11 +36471,8 @@ async function loadIntelligence(cwd) {
35232
36471
  const localGraph = await CozoGraphStore2.create(db, projectRoot);
35233
36472
  let needsIndex = false;
35234
36473
  if (!isNew && await localGraph.isPopulated()) {
35235
- log24.info("Persistent graph loaded");
35236
- const { shouldReindex: shouldReindex2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
35237
- if (shouldReindex2(projectRoot)) {
35238
- needsIndex = true;
35239
- }
36474
+ log24.info("Persistent graph loaded \u2014 background reindex will refresh entity data");
36475
+ needsIndex = true;
35240
36476
  } else {
35241
36477
  const { loadLocalSnapshot: loadLocalSnapshot2 } = await Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports));
35242
36478
  const migrated = await loadLocalSnapshot2(projectRoot, localGraph);
@@ -35245,11 +36481,11 @@ async function loadIntelligence(cwd) {
35245
36481
  await buildSearchIndex2(localGraph.db);
35246
36482
  const { runCommunityDetection: runCommunityDetection2, runConventionDetection: runConventionDetection2 } = await Promise.resolve().then(() => (init_local_indexer(), local_indexer_exports));
35247
36483
  await runCommunityDetection2(localGraph);
35248
- const configPath = join65(projectRoot, ".unerr", "config.json");
36484
+ const configPath = join66(projectRoot, ".unerr", "config.json");
35249
36485
  let repoId = "unknown";
35250
- if (existsSync60(configPath)) {
36486
+ if (existsSync61(configPath)) {
35251
36487
  try {
35252
- const config = JSON.parse(readFileSync54(configPath, "utf-8"));
36488
+ const config = JSON.parse(readFileSync55(configPath, "utf-8"));
35253
36489
  repoId = config.repoId ?? repoId;
35254
36490
  } catch {
35255
36491
  }
@@ -35276,11 +36512,11 @@ async function loadIntelligence(cwd) {
35276
36512
  log24.info("Intelligence graph ready \u2014 tools fully operational");
35277
36513
  if (needsIndex) {
35278
36514
  try {
35279
- const configPath = join65(projectRoot, ".unerr", "config.json");
36515
+ const configPath = join66(projectRoot, ".unerr", "config.json");
35280
36516
  let indexRepoId = "unknown";
35281
- if (existsSync60(configPath)) {
36517
+ if (existsSync61(configPath)) {
35282
36518
  try {
35283
- const cfg = JSON.parse(readFileSync54(configPath, "utf-8"));
36519
+ const cfg = JSON.parse(readFileSync55(configPath, "utf-8"));
35284
36520
  indexRepoId = cfg.repoId ?? indexRepoId;
35285
36521
  } catch {
35286
36522
  }
@@ -35390,8 +36626,8 @@ var init_mcp_server = __esm({
35390
36626
 
35391
36627
  // src/entrypoints/cli.ts
35392
36628
  init_startup_log();
35393
- import { existsSync as existsSync61, readFileSync as readFileSync55 } from "fs";
35394
- import { join as join66 } from "path";
36629
+ import { existsSync as existsSync62, readFileSync as readFileSync56 } from "fs";
36630
+ import { join as join67 } from "path";
35395
36631
  import { Command } from "commander";
35396
36632
 
35397
36633
  // src/commands/branches.ts
@@ -35832,9 +37068,9 @@ function registerDashboardCommand(program2) {
35832
37068
  // src/commands/debug.ts
35833
37069
  init_git();
35834
37070
  import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
35835
- import { arch, homedir as homedir2, platform, release } from "os";
37071
+ import { arch, homedir as homedir3, platform, release } from "os";
35836
37072
  import { join as join11 } from "path";
35837
- var UNERR_DIR = join11(homedir2(), ".unerr");
37073
+ var UNERR_DIR = join11(homedir3(), ".unerr");
35838
37074
  function registerDebugCommand(program2) {
35839
37075
  program2.command("debug").description("Diagnostics dump for support").action(async () => {
35840
37076
  const sections = [];
@@ -35939,9 +37175,9 @@ function registerDebugCommand(program2) {
35939
37175
  const mcpLocations = [
35940
37176
  join11(process.cwd(), ".cursor", "mcp.json"),
35941
37177
  join11(process.cwd(), ".vscode", "mcp.json"),
35942
- join11(homedir2(), ".claude", "claude_desktop_config.json"),
37178
+ join11(homedir3(), ".claude", "claude_desktop_config.json"),
35943
37179
  join11(
35944
- homedir2(),
37180
+ homedir3(),
35945
37181
  "Library",
35946
37182
  "Application Support",
35947
37183
  "Cursor",
@@ -35954,7 +37190,7 @@ function registerDebugCommand(program2) {
35954
37190
  for (const loc of mcpLocations) {
35955
37191
  const exists = existsSync9(loc);
35956
37192
  sections.push(
35957
- ` ${exists ? "[x]" : "[ ]"} ${loc.replace(homedir2(), "~")}`
37193
+ ` ${exists ? "[x]" : "[ ]"} ${loc.replace(homedir3(), "~")}`
35958
37194
  );
35959
37195
  }
35960
37196
  sections.push("");
@@ -37395,19 +38631,19 @@ async function buildShellDiffRiskMap(graph, stdout) {
37395
38631
  }
37396
38632
  async function tryLoadGraphForShellBoost(cwd) {
37397
38633
  try {
37398
- const { existsSync: existsSync62, readFileSync: readFileSync56 } = await import("fs");
37399
- const { join: join67 } = await import("path");
38634
+ const { existsSync: existsSync63, readFileSync: readFileSync57 } = await import("fs");
38635
+ const { join: join68 } = await import("path");
37400
38636
  const { gunzipSync: gunzipSync3 } = await import("zlib");
37401
38637
  const { unpack } = await import("msgpackr");
37402
- const snapshotsDir = join67(cwd, ".unerr", "snapshots");
37403
- let snapshotPath2 = join67(snapshotsDir, "graph.msgpack.gz");
37404
- if (!existsSync62(snapshotPath2)) {
37405
- snapshotPath2 = join67(snapshotsDir, "graph.msgpack");
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");
37406
38642
  }
37407
- if (!existsSync62(snapshotPath2)) return null;
38643
+ if (!existsSync63(snapshotPath2)) return null;
37408
38644
  const { default: CozoDbConstructor } = await import("cozo-node");
37409
38645
  const { CozoGraphStore: CozoGraphStore2 } = await Promise.resolve().then(() => (init_local_graph(), local_graph_exports));
37410
- const raw = readFileSync56(snapshotPath2);
38646
+ const raw = readFileSync57(snapshotPath2);
37411
38647
  let buffer;
37412
38648
  try {
37413
38649
  buffer = gunzipSync3(raw);
@@ -40643,12 +41879,17 @@ var preReadHandler = (normalized) => {
40643
41879
  const input = normalized.toolInput;
40644
41880
  const filePath = extractFilePath2(input);
40645
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.`;
40646
41887
  return nudge(
40647
41888
  `READ ROUTING: Built-in Read is ONLY for the Edit workflow (Read \u2192 Edit). For all other reading, use unerr tools instead:
40648
41889
  - Reading to understand: \`file_read({ file_path: "${filePath}", purpose: "explore" })\`
40649
41890
  - Reading before Edit: built-in Read is correct (proceed)
40650
41891
  - File structure: \`file_outline("${filePath}")\`
40651
- - Specific function: \`get_entity\` or \`file_read\` with \`entity\` param`
41892
+ - Specific function: \`get_entity\` or \`file_read\` with \`entity\` param` + targetedReadGuidance
40652
41893
  );
40653
41894
  };
40654
41895
  var preGrepHandler = (normalized) => {
@@ -40700,17 +41941,20 @@ var preEditHandler = (normalized) => {
40700
41941
  const input = normalized.toolInput;
40701
41942
  const filePath = extractFilePath2(input);
40702
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
+ `;
40703
41947
  const oldStr = input.old_string;
40704
41948
  const hasSignatureChange = oldStr && /^(export\s+)?(async\s+)?function\s+\w+|^(export\s+)?class\s+\w+/.test(oldStr.trim());
40705
41949
  if (hasSignatureChange) {
40706
41950
  return nudge(
40707
- `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.
40708
41952
  - \`get_references\` on the entity you're modifying \u2014 all callers must be updated to match
40709
41953
  - \`get_rules "${filePath}"\` with content after editing \u2014 validate the change follows project conventions`
40710
41954
  );
40711
41955
  }
40712
41956
  return nudge(
40713
- `Before editing "${filePath}":
41957
+ readPrereq + `Before editing "${filePath}":
40714
41958
  - \`get_references\` on any entity you're changing \u2014 ensure callers won't break
40715
41959
  - \`get_rules "${filePath}"\` with content after editing \u2014 validate against project conventions`
40716
41960
  );
@@ -41168,7 +42412,7 @@ function mergePreToolUseBashHook(cwd) {
41168
42412
  return { ok: false, path: settingsPath, action: "failed" };
41169
42413
  }
41170
42414
  }
41171
- var DISALLOWED_TOOLS = ["Read", "Grep", "Glob"];
42415
+ var DISALLOWED_TOOLS = ["Grep", "Glob"];
41172
42416
  function addDisallowedTools(cwd) {
41173
42417
  const dir = join27(cwd, ".claude");
41174
42418
  const settingsPath = join27(dir, "settings.json");
@@ -41186,6 +42430,10 @@ function addDisallowedTools(cwd) {
41186
42430
  }
41187
42431
  const permissions = settings.permissions ?? {};
41188
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
+ }
41189
42437
  let added = 0;
41190
42438
  for (const tool of DISALLOWED_TOOLS) {
41191
42439
  if (!deny.includes(tool)) {
@@ -41306,10 +42554,12 @@ This project has unerr MCP tools installed. You MUST use these instead of built-
41306
42554
  | Intent | Tool | Why |
41307
42555
  |--------|------|-----|
41308
42556
  | Reading to understand code | \`file_read\` (unerr MCP) | Auto-injects conventions, facts, drift status |
41309
- | Reading immediately before Edit | Built-in \`Read\` | Required by Edit tool \u2014 \`file_read\` does NOT satisfy this |
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. |
41310
42558
 
41311
42559
  When your next action is Edit, use built-in Read. For everything else, use \`file_read\`.
41312
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
+
41313
42563
  ### Tool Reference
41314
42564
 
41315
42565
  #### Graph Navigation (8 tools)
@@ -41376,7 +42626,7 @@ ONLY use built-in Read/Grep/Glob when:
41376
42626
  ### Summary (CRITICAL \u2014 read this even if you skimmed above)
41377
42627
 
41378
42628
  ALWAYS use unerr MCP tools: \`search_code\`, \`get_references\`, \`file_read\`, \`file_outline\`, \`get_entity\`.
41379
- NEVER use built-in Read/Grep/Glob for code navigation. EXCEPTION: built-in Read is REQUIRED before Edit (file_read cannot substitute).
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).
41380
42630
  unerr skills available: /audit, /blame, /test, /lint, /commit, /review-pr.
41381
42631
  Before writing code: \`get_conventions\`, \`get_rules\`. After writing: \`get_rules\` with content to validate.
41382
42632
  Before risky changes: \`unerr_mark_working\`. If things break: \`unerr_revert_to_working_state\`.`;
@@ -41399,6 +42649,29 @@ function writeInstructionFile(cwd, ide) {
41399
42649
  if (agentDef.instructionFormat === "mdc") {
41400
42650
  return writeMdcInstructionFile(filePath);
41401
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
+ }
41402
42675
  if (agentDef.instructionFormat === "antigravity-rule") {
41403
42676
  mkdirSync15(dirname5(filePath), { recursive: true });
41404
42677
  const content = getInstructionContent();
@@ -41477,6 +42750,13 @@ function removeInstructionSection(cwd, ide) {
41477
42750
  unlinkSync3(filePath);
41478
42751
  return true;
41479
42752
  }
42753
+ if (agentDef.instructionFormat === "windsurf-rule") {
42754
+ if (existsSync21(filePath)) {
42755
+ unlinkSync3(filePath);
42756
+ return true;
42757
+ }
42758
+ return false;
42759
+ }
41480
42760
  if (agentDef.instructionFormat === "antigravity-rule") {
41481
42761
  if (existsSync21(filePath)) {
41482
42762
  unlinkSync3(filePath);
@@ -42041,8 +43321,8 @@ async function persistPatterns(patterns, _unerrDir) {
42041
43321
  );
42042
43322
  return;
42043
43323
  }
42044
- const { readFileSync: readFileSync56 } = await import("fs");
42045
- const config = JSON.parse(readFileSync56(configPath, "utf-8"));
43324
+ const { readFileSync: readFileSync57 } = await import("fs");
43325
+ const config = JSON.parse(readFileSync57(configPath, "utf-8"));
42046
43326
  if (!config.repoId || !config.orgId) {
42047
43327
  warn("Repo not configured \u2014 cannot persist to CozoDB.");
42048
43328
  return;
@@ -42060,7 +43340,7 @@ async function persistPatterns(patterns, _unerrDir) {
42060
43340
  const { CozoGraphStore: CozoGraphStore2 } = await Promise.resolve().then(() => (init_local_graph(), local_graph_exports));
42061
43341
  const store = await CozoGraphStore2.create(db);
42062
43342
  const { unpack } = await import("msgpackr");
42063
- const raw = readFileSync56(snapshotPath2);
43343
+ const raw = readFileSync57(snapshotPath2);
42064
43344
  const buffer = gunzipSync2(raw);
42065
43345
  const envelope = unpack(buffer);
42066
43346
  await store.loadSnapshot(envelope);
@@ -42727,8 +44007,8 @@ function registerStatusCommand(program2) {
42727
44007
  try {
42728
44008
  const logsDir = join38(unerrDir, "logs");
42729
44009
  if (existsSync31(logsDir)) {
42730
- const { readdirSync: readdirSync14, statSync: statSync12 } = await import("fs");
42731
- const logs = readdirSync14(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
44010
+ const { readdirSync: readdirSync15, statSync: statSync12 } = await import("fs");
44011
+ const logs = readdirSync15(logsDir).filter((f) => f.startsWith("session-") && f.endsWith(".log")).map((f) => ({
42732
44012
  name: f,
42733
44013
  mtime: statSync12(join38(logsDir, f)).mtimeMs
42734
44014
  })).sort((a, b) => b.mtime - a.mtime);
@@ -43293,17 +44573,538 @@ async function isInsideGitRepo2() {
43293
44573
  const { isGitRepo: isGitRepo3 } = await Promise.resolve().then(() => (init_git(), git_exports));
43294
44574
  return isGitRepo3(process.cwd());
43295
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
+ }
43296
45085
  function readLocalConfig(cwd) {
43297
- const configPath = join66(cwd, ".unerr", "config.json");
43298
- if (!existsSync61(configPath)) return null;
45086
+ const configPath = join67(cwd, ".unerr", "config.json");
45087
+ if (!existsSync62(configPath)) return null;
43299
45088
  try {
43300
- return JSON.parse(readFileSync55(configPath, "utf-8"));
45089
+ return JSON.parse(readFileSync56(configPath, "utf-8"));
43301
45090
  } catch {
43302
45091
  return null;
43303
45092
  }
43304
45093
  }
43305
45094
  async function resumeBoot(config) {
43306
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
+ }
43307
45108
  const { initSessionLogger: initSessionLogger2, createSessionModuleLogger: createSessionModuleLogger2 } = await Promise.resolve().then(() => (init_session_logger(), session_logger_exports));
43308
45109
  initSessionLogger2();
43309
45110
  const log25 = createSessionModuleLogger2("boot");
@@ -43317,13 +45118,24 @@ async function firstRunBoot() {
43317
45118
  const { initSessionLogger: initSessionLogger2, createSessionModuleLogger: createSessionModuleLogger2 } = await Promise.resolve().then(() => (init_session_logger(), session_logger_exports));
43318
45119
  initSessionLogger2();
43319
45120
  const log25 = createSessionModuleLogger2("boot");
43320
- if (!await isInsideGitRepo2()) {
45121
+ const detection = await detectProjectRoot(process.cwd());
45122
+ if (!detection.isProject) {
45123
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
43321
45124
  process.stderr.write(
43322
- "[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"
43323
45130
  );
43324
45131
  process.exit(1);
43325
45132
  }
43326
- 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 });
43327
45139
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup_wizard(), setup_wizard_exports));
43328
45140
  const result = await runSetup2();
43329
45141
  if (result.action === "setup") {
@@ -43335,15 +45147,21 @@ async function firstRunBoot() {
43335
45147
  }
43336
45148
  async function mcpBoot(cwd) {
43337
45149
  initFileLog(cwd);
43338
- if (!await isInsideGitRepo2()) {
45150
+ const detection = await detectProjectRoot(cwd);
45151
+ if (!detection.isProject) {
45152
+ const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
43339
45153
  process.stderr.write(
43340
- "[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"
43341
45159
  );
43342
45160
  process.exit(1);
43343
45161
  }
43344
- const stateDir = join66(cwd, ".unerr", "state");
43345
- const sockPath = join66(stateDir, "proxy.sock");
43346
- if (existsSync61(sockPath)) {
45162
+ const stateDir = join67(cwd, ".unerr", "state");
45163
+ const sockPath = join67(stateDir, "proxy.sock");
45164
+ if (existsSync62(sockPath)) {
43347
45165
  const { PidLock: PidLock2 } = await Promise.resolve().then(() => (init_pid_lock(), pid_lock_exports));
43348
45166
  const pidLock = new PidLock2(stateDir);
43349
45167
  const probeResult = await pidLock.probe();
@@ -43367,10 +45185,10 @@ async function mcpBoot(cwd) {
43367
45185
  const { mkdirSync: mkdirSync39, writeFileSync: writeFileSync36 } = await import("fs");
43368
45186
  const { createHash: createHash5 } = await import("crypto");
43369
45187
  const repoId = createHash5("sha256").update(cwd).digest("hex").slice(0, 12);
43370
- const unerrDir = join66(cwd, ".unerr");
45188
+ const unerrDir = join67(cwd, ".unerr");
43371
45189
  mkdirSync39(unerrDir, { recursive: true });
43372
45190
  writeFileSync36(
43373
- join66(unerrDir, "config.json"),
45191
+ join67(unerrDir, "config.json"),
43374
45192
  JSON.stringify(
43375
45193
  { repoId, mode: "local", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
43376
45194
  null,