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