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