@orquentra/codebuddy 2.5.0 → 2.6.2
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/CLAUDE_CODEX.md +6 -4
- package/README.md +8 -0
- package/WORKFLOW.md +5 -5
- package/dist/{chunk-JDZ3VK26.js → chunk-2XDPKFUW.js} +114 -823
- package/dist/chunk-2XDPKFUW.js.map +1 -0
- package/dist/{chunk-QDBDYCM2.js → chunk-5FW3PMNP.js} +2 -2
- package/dist/{chunk-776EQXDV.js → chunk-5KZY6OTG.js} +82 -5
- package/dist/chunk-5KZY6OTG.js.map +1 -0
- package/dist/{chunk-UFZD3JAA.js → chunk-5MQOE4J6.js} +3 -3
- package/dist/chunk-5UH3BEQR.js +32 -0
- package/dist/chunk-5UH3BEQR.js.map +1 -0
- package/dist/{chunk-ROQTT5ZO.js → chunk-B6CO63SQ.js} +8 -7
- package/dist/chunk-B6CO63SQ.js.map +1 -0
- package/dist/chunk-CSRD5OQA.js +79 -0
- package/dist/chunk-CSRD5OQA.js.map +1 -0
- package/dist/{chunk-PYLH4MH5.js → chunk-FJOTP632.js} +1 -1
- package/dist/{chunk-3XYQGFPB.js → chunk-IBLJOHJL.js} +16 -3
- package/dist/chunk-IBLJOHJL.js.map +1 -0
- package/dist/chunk-IMFUYEZA.js +1607 -0
- package/dist/chunk-IMFUYEZA.js.map +1 -0
- package/dist/{chunk-CQPCHMBS.js → chunk-J2EAZ4MA.js} +21 -8
- package/dist/chunk-J2EAZ4MA.js.map +1 -0
- package/dist/{chunk-KYK6RPAJ.js → chunk-JFDRBPYF.js} +2 -2
- package/dist/{chunk-JMWJ7V6Y.js → chunk-JPHJBCZB.js} +2 -2
- package/dist/{chunk-JMWJ7V6Y.js.map → chunk-JPHJBCZB.js.map} +1 -1
- package/dist/{chunk-L5AYZC3F.js → chunk-NERKZSSE.js} +7 -7
- package/dist/{chunk-PCE4STAY.js → chunk-OWWCDRXW.js} +5 -5
- package/dist/{chunk-WHZSVY2L.js → chunk-QBDJXGG5.js} +2 -2
- package/dist/{chunk-Z5QB367W.js → chunk-R7CD6EXS.js} +367 -1798
- package/dist/chunk-R7CD6EXS.js.map +1 -0
- package/dist/{chunk-6MKEIDX5.js → chunk-RAJJSCIP.js} +2 -2
- package/dist/chunk-S6VEG4S2.js +991 -0
- package/dist/chunk-S6VEG4S2.js.map +1 -0
- package/dist/{chunk-4BYGTEJT.js → chunk-XBJQKCLU.js} +2 -2
- package/dist/cli/index.js +14 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/{cloud-GP6G3EAK.js → cloud-7WM3YLI4.js} +3 -3
- package/dist/{codebuddy-DM-i0Fu5.d.ts → codebuddy-B_pCaQrV.d.ts} +22 -2
- package/dist/{config-KFEHLV6W.js → config-DEVHUDIU.js} +3 -3
- package/dist/docker-autostart-KDME7TVC.js +12 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +22 -18
- package/dist/langgraph/index.d.ts +1 -1
- package/dist/lean-command-HTQCPH76.js +15 -0
- package/dist/local-5MZURLCZ.js +17 -0
- package/dist/{main-BDVQAGAS.js → main-MTBQZGJV.js} +333 -219
- package/dist/main-MTBQZGJV.js.map +1 -0
- package/dist/{memory-4RGVLPKG.js → memory-KIC5FJVW.js} +4 -4
- package/dist/{operations-EQGOFCBD.js → operations-POCHY3WR.js} +8 -7
- package/dist/postgres-docker-QQZQ2ABK.js +16 -0
- package/dist/{pretool-command-MQQ2UNQM.js → pretool-command-AX6JXP25.js} +3 -3
- package/dist/{rerank-3P3C2UHN.js → rerank-5QZ3PNUW.js} +4 -4
- package/dist/runtime-CUBOOPZO.js +43 -0
- package/dist/runtime-CUBOOPZO.js.map +1 -0
- package/dist/stdio-HOV4P3DQ.js +12 -0
- package/dist/stdio-HOV4P3DQ.js.map +1 -0
- package/dist/{summaries-ZBV5IN7Y.js → summaries-SB5L2TSW.js} +6 -6
- package/dist/summaries-SB5L2TSW.js.map +1 -0
- package/dist/{zvec-MAPSJQSR.js → zvec-MSRKT7IV.js} +3 -3
- package/dist/zvec-MSRKT7IV.js.map +1 -0
- package/package.json +7 -10
- package/dist/chunk-3XYQGFPB.js.map +0 -1
- package/dist/chunk-776EQXDV.js.map +0 -1
- package/dist/chunk-CQPCHMBS.js.map +0 -1
- package/dist/chunk-JDZ3VK26.js.map +0 -1
- package/dist/chunk-ROQTT5ZO.js.map +0 -1
- package/dist/chunk-Z5QB367W.js.map +0 -1
- package/dist/lean-command-DA4ZNKG3.js +0 -15
- package/dist/local-3NGNESNB.js +0 -13
- package/dist/main-BDVQAGAS.js.map +0 -1
- /package/dist/{chunk-QDBDYCM2.js.map → chunk-5FW3PMNP.js.map} +0 -0
- /package/dist/{chunk-UFZD3JAA.js.map → chunk-5MQOE4J6.js.map} +0 -0
- /package/dist/{chunk-PYLH4MH5.js.map → chunk-FJOTP632.js.map} +0 -0
- /package/dist/{chunk-KYK6RPAJ.js.map → chunk-JFDRBPYF.js.map} +0 -0
- /package/dist/{chunk-L5AYZC3F.js.map → chunk-NERKZSSE.js.map} +0 -0
- /package/dist/{chunk-PCE4STAY.js.map → chunk-OWWCDRXW.js.map} +0 -0
- /package/dist/{chunk-WHZSVY2L.js.map → chunk-QBDJXGG5.js.map} +0 -0
- /package/dist/{chunk-6MKEIDX5.js.map → chunk-RAJJSCIP.js.map} +0 -0
- /package/dist/{chunk-4BYGTEJT.js.map → chunk-XBJQKCLU.js.map} +0 -0
- /package/dist/{cloud-GP6G3EAK.js.map → cloud-7WM3YLI4.js.map} +0 -0
- /package/dist/{config-KFEHLV6W.js.map → config-DEVHUDIU.js.map} +0 -0
- /package/dist/{lean-command-DA4ZNKG3.js.map → docker-autostart-KDME7TVC.js.map} +0 -0
- /package/dist/{local-3NGNESNB.js.map → lean-command-HTQCPH76.js.map} +0 -0
- /package/dist/{operations-EQGOFCBD.js.map → local-5MZURLCZ.js.map} +0 -0
- /package/dist/{memory-4RGVLPKG.js.map → memory-KIC5FJVW.js.map} +0 -0
- /package/dist/{pretool-command-MQQ2UNQM.js.map → operations-POCHY3WR.js.map} +0 -0
- /package/dist/{summaries-ZBV5IN7Y.js.map → postgres-docker-QQZQ2ABK.js.map} +0 -0
- /package/dist/{zvec-MAPSJQSR.js.map → pretool-command-AX6JXP25.js.map} +0 -0
- /package/dist/{rerank-3P3C2UHN.js.map → rerank-5QZ3PNUW.js.map} +0 -0
|
@@ -6,25 +6,20 @@ import {
|
|
|
6
6
|
SessionCapsuleLedger,
|
|
7
7
|
detectTestCommand,
|
|
8
8
|
estimateTokens,
|
|
9
|
-
findCoveringTest
|
|
10
|
-
|
|
9
|
+
findCoveringTest,
|
|
10
|
+
hookRoot
|
|
11
|
+
} from "./chunk-J2EAZ4MA.js";
|
|
11
12
|
import {
|
|
12
13
|
TASK_PATH_LIMIT,
|
|
13
|
-
buildArchitectureMap,
|
|
14
14
|
countTerms,
|
|
15
15
|
discoverTaskPaths,
|
|
16
|
-
generateGraphifyGraph,
|
|
17
16
|
identifierTokens,
|
|
18
|
-
indexRepository,
|
|
19
|
-
loadArchitectureMap,
|
|
20
17
|
loadFreshIndex,
|
|
21
18
|
loadProjectArchitectureMap,
|
|
22
19
|
namedSegments,
|
|
23
|
-
neighbours,
|
|
24
|
-
queryMap,
|
|
25
20
|
readBoundedSource,
|
|
26
21
|
taskTerms
|
|
27
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-NERKZSSE.js";
|
|
28
23
|
import {
|
|
29
24
|
extractSymbolTable,
|
|
30
25
|
supportsSymbols
|
|
@@ -32,12 +27,10 @@ import {
|
|
|
32
27
|
import {
|
|
33
28
|
INCIDENT_SEVERITIES,
|
|
34
29
|
MemoryFileStore,
|
|
35
|
-
codeBuddyConfigSchema,
|
|
36
|
-
createRuntime,
|
|
37
30
|
isEligibleMemory,
|
|
38
31
|
lexicalTerms,
|
|
39
|
-
|
|
40
|
-
} from "./chunk-
|
|
32
|
+
termOverlap
|
|
33
|
+
} from "./chunk-S6VEG4S2.js";
|
|
41
34
|
import {
|
|
42
35
|
claimText,
|
|
43
36
|
redactPersisted,
|
|
@@ -47,13 +40,10 @@ import {
|
|
|
47
40
|
} from "./chunk-GDJH7XJC.js";
|
|
48
41
|
import {
|
|
49
42
|
generateEntityId
|
|
50
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-XBJQKCLU.js";
|
|
51
44
|
import {
|
|
52
45
|
DEFAULT_RERANK_MODELS
|
|
53
|
-
} from "./chunk-
|
|
54
|
-
import {
|
|
55
|
-
refreshZvecIndex
|
|
56
|
-
} from "./chunk-QDBDYCM2.js";
|
|
46
|
+
} from "./chunk-JPHJBCZB.js";
|
|
57
47
|
import {
|
|
58
48
|
assertNoSymlinkBetween,
|
|
59
49
|
composeMarkdown,
|
|
@@ -61,126 +51,19 @@ import {
|
|
|
61
51
|
loadRuntimeConfig,
|
|
62
52
|
parseFrontMatter,
|
|
63
53
|
readConfigFile,
|
|
64
|
-
redactSecrets,
|
|
65
54
|
writeAtomic
|
|
66
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-5KZY6OTG.js";
|
|
67
56
|
|
|
68
|
-
// src/
|
|
69
|
-
import {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
conflicts: [],
|
|
79
|
-
failures: []
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const [facts, summaries, existingFacts, existingSummaries] = await Promise.all([
|
|
83
|
-
input.repository.listFacts({ namespaceId: namespace.id, limit: 1e5 }),
|
|
84
|
-
input.repository.listSummaries({ namespaceId: namespace.id, limit: 1e5 }),
|
|
85
|
-
input.store.listFacts(),
|
|
86
|
-
input.store.listSummaries()
|
|
87
|
-
]);
|
|
88
|
-
const warnings = facts.length >= 1e5 || summaries.length >= 1e5 ? [
|
|
89
|
-
"namespace is at or above the 100,000-row read cap; the migration may be partial \u2014 prune or export in batches"
|
|
90
|
-
] : [];
|
|
91
|
-
const existingFactsById = new Map(existingFacts.map((fact) => [fact.id, fact]));
|
|
92
|
-
const existingSummariesById = new Map(existingSummaries.map((summary) => [summary.id, summary]));
|
|
93
|
-
const conflicts = [];
|
|
94
|
-
const failures = [];
|
|
95
|
-
const factsToWrite = facts.filter((fact) => {
|
|
96
|
-
const existing = existingFactsById.get(fact.id);
|
|
97
|
-
if (!existing) return true;
|
|
98
|
-
const databaseChecksum = checksum(redactPersisted(fact).content);
|
|
99
|
-
const fileChecksum = checksum(existing.content);
|
|
100
|
-
if (databaseChecksum !== fileChecksum) {
|
|
101
|
-
conflicts.push({ kind: "fact", id: fact.id, databaseChecksum, fileChecksum });
|
|
102
|
-
}
|
|
103
|
-
return false;
|
|
104
|
-
});
|
|
105
|
-
const summariesToWrite = summaries.filter((summary) => {
|
|
106
|
-
const existing = existingSummariesById.get(summary.id);
|
|
107
|
-
if (!existing) return true;
|
|
108
|
-
const databaseChecksum = checksum(summary.content);
|
|
109
|
-
const fileChecksum = checksum(existing.content);
|
|
110
|
-
if (databaseChecksum !== fileChecksum) {
|
|
111
|
-
conflicts.push({ kind: "summary", id: summary.id, databaseChecksum, fileChecksum });
|
|
112
|
-
}
|
|
113
|
-
return false;
|
|
114
|
-
});
|
|
115
|
-
let factsWritten = 0;
|
|
116
|
-
let summariesWritten = 0;
|
|
117
|
-
if (input.write) {
|
|
118
|
-
for (const fact of factsToWrite) {
|
|
119
|
-
try {
|
|
120
|
-
await input.store.writeFact({
|
|
121
|
-
id: fact.id,
|
|
122
|
-
namespace: input.namespace,
|
|
123
|
-
subject: fact.subject,
|
|
124
|
-
predicate: fact.predicate,
|
|
125
|
-
object: fact.object,
|
|
126
|
-
confidence: fact.confidence,
|
|
127
|
-
createdAt: fact.createdAt.toISOString(),
|
|
128
|
-
createdByAgent: fact.createdByAgent,
|
|
129
|
-
sourceInteractionId: fact.sourceInteractionId,
|
|
130
|
-
sourceDeleted: fact.sourceDeleted,
|
|
131
|
-
supersededBy: fact.supersededBy,
|
|
132
|
-
content: fact.content
|
|
133
|
-
});
|
|
134
|
-
factsWritten += 1;
|
|
135
|
-
} catch (error) {
|
|
136
|
-
failures.push({ kind: "fact", id: fact.id, error: error.message });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
for (const summary of summariesToWrite) {
|
|
140
|
-
try {
|
|
141
|
-
await input.store.writeSummary({
|
|
142
|
-
id: summary.id,
|
|
143
|
-
namespace: input.namespace,
|
|
144
|
-
sessionId: summary.sessionId,
|
|
145
|
-
version: summary.version,
|
|
146
|
-
tokenCount: summary.tokenCount,
|
|
147
|
-
createdAt: summary.createdAt.toISOString(),
|
|
148
|
-
createdByAgent: summary.createdByAgent,
|
|
149
|
-
content: summary.content
|
|
150
|
-
});
|
|
151
|
-
summariesWritten += 1;
|
|
152
|
-
} catch (error) {
|
|
153
|
-
failures.push({ kind: "summary", id: summary.id, error: error.message });
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const factConflicts = conflicts.filter((conflict) => conflict.kind === "fact").length;
|
|
158
|
-
const summaryConflicts = conflicts.length - factConflicts;
|
|
159
|
-
return {
|
|
160
|
-
namespace: input.namespace,
|
|
161
|
-
dryRun: !input.write,
|
|
162
|
-
...warnings.length > 0 ? { warnings } : {},
|
|
163
|
-
facts: {
|
|
164
|
-
found: facts.length,
|
|
165
|
-
existing: facts.length - factsToWrite.length - factConflicts,
|
|
166
|
-
toWrite: factsToWrite.length,
|
|
167
|
-
conflicts: factConflicts,
|
|
168
|
-
written: factsWritten
|
|
169
|
-
},
|
|
170
|
-
summaries: {
|
|
171
|
-
found: summaries.length,
|
|
172
|
-
existing: summaries.length - summariesToWrite.length - summaryConflicts,
|
|
173
|
-
toWrite: summariesToWrite.length,
|
|
174
|
-
conflicts: summaryConflicts,
|
|
175
|
-
written: summariesWritten
|
|
176
|
-
},
|
|
177
|
-
conflicts,
|
|
178
|
-
failures
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
function checksum(content) {
|
|
182
|
-
return createHash("sha256").update(content).digest("hex");
|
|
183
|
-
}
|
|
57
|
+
// src/hooks/runtime.ts
|
|
58
|
+
import { mkdir as mkdir6, readFile as readFile8, rm as rm4, stat as stat5, writeFile } from "fs/promises";
|
|
59
|
+
import { basename, dirname as dirname5, isAbsolute, join as join8, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
|
|
60
|
+
import pTimeout2 from "p-timeout";
|
|
61
|
+
|
|
62
|
+
// src/context/engine.ts
|
|
63
|
+
import { execFile } from "child_process";
|
|
64
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
65
|
+
import { join as join4, resolve as resolve5 } from "path";
|
|
66
|
+
import { promisify } from "util";
|
|
184
67
|
|
|
185
68
|
// src/core/plan-file-store.ts
|
|
186
69
|
import { mkdir, open, readdir, readFile, rm, stat } from "fs/promises";
|
|
@@ -411,7 +294,7 @@ function formatIssues(error) {
|
|
|
411
294
|
});
|
|
412
295
|
}
|
|
413
296
|
function delay(ms) {
|
|
414
|
-
return new Promise((
|
|
297
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
415
298
|
}
|
|
416
299
|
async function releaseOwnedLock(lockPath) {
|
|
417
300
|
try {
|
|
@@ -607,124 +490,10 @@ var PlanLifecycle = class {
|
|
|
607
490
|
}
|
|
608
491
|
};
|
|
609
492
|
|
|
610
|
-
//
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
description: "Local-first project context and memory for AI coding agents.",
|
|
615
|
-
type: "module",
|
|
616
|
-
license: "MIT",
|
|
617
|
-
publishConfig: {
|
|
618
|
-
access: "public"
|
|
619
|
-
},
|
|
620
|
-
author: "Orquentra Inc",
|
|
621
|
-
repository: {
|
|
622
|
-
type: "git",
|
|
623
|
-
url: "https://github.com/Orquentra-Inc/CodeBuddy"
|
|
624
|
-
},
|
|
625
|
-
engines: {
|
|
626
|
-
node: ">=20"
|
|
627
|
-
},
|
|
628
|
-
bin: {
|
|
629
|
-
codebuddy: "./dist/cli/index.js"
|
|
630
|
-
},
|
|
631
|
-
exports: {
|
|
632
|
-
".": {
|
|
633
|
-
types: "./dist/index.d.ts",
|
|
634
|
-
import: "./dist/index.js"
|
|
635
|
-
},
|
|
636
|
-
"./langgraph": {
|
|
637
|
-
types: "./dist/langgraph/index.d.ts",
|
|
638
|
-
import: "./dist/langgraph/index.js"
|
|
639
|
-
}
|
|
640
|
-
},
|
|
641
|
-
files: [
|
|
642
|
-
"dist",
|
|
643
|
-
"src/db/migrations",
|
|
644
|
-
"README.md",
|
|
645
|
-
"CLAUDE_CODEX.md",
|
|
646
|
-
"ARCHITECTURE.md",
|
|
647
|
-
"WORKFLOW.md",
|
|
648
|
-
"LICENSE",
|
|
649
|
-
"docker-compose.yml",
|
|
650
|
-
"examples"
|
|
651
|
-
],
|
|
652
|
-
scripts: {
|
|
653
|
-
build: "tsup",
|
|
654
|
-
dev: "tsx src/cli/index.ts",
|
|
655
|
-
"db:generate": "drizzle-kit generate",
|
|
656
|
-
"db:migrate": "drizzle-kit migrate",
|
|
657
|
-
typecheck: "tsc --noEmit",
|
|
658
|
-
test: "vitest run --passWithNoTests",
|
|
659
|
-
"test:pg": "bash scripts/test-pg-recovery.sh",
|
|
660
|
-
"eval:agent-ab": "tsx scripts/eval-agent-ab.mts",
|
|
661
|
-
"eval:memory-ab": "tsx scripts/eval-memory-ab.mts",
|
|
662
|
-
"eval:retrieval": "tsx scripts/eval-retrieval.mts",
|
|
663
|
-
"eval:savings": "tsx scripts/eval-task-savings.mts",
|
|
664
|
-
"branches:prune": "bash scripts/prune-merged-branches.sh",
|
|
665
|
-
lint: "biome check .",
|
|
666
|
-
format: "biome format --write .",
|
|
667
|
-
prepublishOnly: "npm run typecheck && npm test && npm run build",
|
|
668
|
-
"eval:semantic": "tsx scripts/eval-semantic.mts"
|
|
669
|
-
},
|
|
670
|
-
keywords: [
|
|
671
|
-
"mcp",
|
|
672
|
-
"memory",
|
|
673
|
-
"agent",
|
|
674
|
-
"multi-agent",
|
|
675
|
-
"huggingface",
|
|
676
|
-
"pgvector",
|
|
677
|
-
"postgres",
|
|
678
|
-
"langgraph",
|
|
679
|
-
"claude"
|
|
680
|
-
],
|
|
681
|
-
homepage: "https://github.com/Orquentra-Inc/CodeBuddy",
|
|
682
|
-
bugs: {
|
|
683
|
-
url: "https://github.com/Orquentra-Inc/CodeBuddy/issues"
|
|
684
|
-
},
|
|
685
|
-
dependencies: {
|
|
686
|
-
"@clack/prompts": "^0.11.0",
|
|
687
|
-
"@huggingface/inference": "^4.13.19",
|
|
688
|
-
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
689
|
-
commander: "^14.0.0",
|
|
690
|
-
"drizzle-orm": "^0.45.2",
|
|
691
|
-
"js-tiktoken": "^1.0.21",
|
|
692
|
-
langsmith: "^0.7.10",
|
|
693
|
-
ora: "^8.2.0",
|
|
694
|
-
"p-queue": "^8.1.0",
|
|
695
|
-
"p-retry": "^6.2.1",
|
|
696
|
-
"p-timeout": "^6.1.4",
|
|
697
|
-
picocolors: "^1.1.1",
|
|
698
|
-
pino: "^9.9.0",
|
|
699
|
-
"pino-pretty": "^13.1.1",
|
|
700
|
-
postgres: "^3.4.7",
|
|
701
|
-
yaml: "^2.9.0",
|
|
702
|
-
zod: "^4.6.7"
|
|
703
|
-
},
|
|
704
|
-
peerDependencies: {
|
|
705
|
-
"@langchain/langgraph": "^0.2.0 || ^0.3.0"
|
|
706
|
-
},
|
|
707
|
-
peerDependenciesMeta: {
|
|
708
|
-
"@langchain/langgraph": {
|
|
709
|
-
optional: true
|
|
710
|
-
}
|
|
711
|
-
},
|
|
712
|
-
devDependencies: {
|
|
713
|
-
"@biomejs/biome": "^2.5.0",
|
|
714
|
-
"@types/node": "^24.3.0",
|
|
715
|
-
"drizzle-kit": "^0.31.10",
|
|
716
|
-
tsup: "^8.5.0",
|
|
717
|
-
tsx: "^4.20.5",
|
|
718
|
-
typescript: "^5.9.2",
|
|
719
|
-
vitest: "^4.1.12"
|
|
720
|
-
},
|
|
721
|
-
optionalDependencies: {
|
|
722
|
-
"@huggingface/transformers": "^4.3.2"
|
|
723
|
-
}
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
// src/version.ts
|
|
727
|
-
var VERSION = package_default.version;
|
|
493
|
+
// src/risk/service.ts
|
|
494
|
+
import { spawn as spawn2 } from "child_process";
|
|
495
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
496
|
+
import { relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
728
497
|
|
|
729
498
|
// src/risk/assessor.ts
|
|
730
499
|
var RiskAssessor = class {
|
|
@@ -745,7 +514,7 @@ var RiskAssessor = class {
|
|
|
745
514
|
const byPath = /* @__PURE__ */ new Map();
|
|
746
515
|
const perSignal = /* @__PURE__ */ new Map();
|
|
747
516
|
for (const { signal, contributions } of settled) {
|
|
748
|
-
const
|
|
517
|
+
const stat6 = perSignal.get(signal.id) ?? {
|
|
749
518
|
id: signal.id,
|
|
750
519
|
category: signal.category,
|
|
751
520
|
contributions: 0,
|
|
@@ -756,10 +525,10 @@ var RiskAssessor = class {
|
|
|
756
525
|
const list = byPath.get(contribution.path) ?? [];
|
|
757
526
|
list.push({ signal, contribution });
|
|
758
527
|
byPath.set(contribution.path, list);
|
|
759
|
-
|
|
760
|
-
|
|
528
|
+
stat6.contributions += 1;
|
|
529
|
+
stat6.totalWeight += contribution.weight;
|
|
761
530
|
}
|
|
762
|
-
perSignal.set(signal.id,
|
|
531
|
+
perSignal.set(signal.id, stat6);
|
|
763
532
|
}
|
|
764
533
|
const items = [];
|
|
765
534
|
for (const [path, contributions] of byPath) {
|
|
@@ -891,7 +660,7 @@ async function gitLogForPath(repositoryRoot, path, since) {
|
|
|
891
660
|
}).filter((record) => record.sha.length > 0);
|
|
892
661
|
}
|
|
893
662
|
function runGit(cwd, args) {
|
|
894
|
-
return new Promise((
|
|
663
|
+
return new Promise((resolve10, reject) => {
|
|
895
664
|
const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
896
665
|
let stdout = "";
|
|
897
666
|
let stderr = "";
|
|
@@ -902,7 +671,7 @@ function runGit(cwd, args) {
|
|
|
902
671
|
stderr += String(chunk);
|
|
903
672
|
});
|
|
904
673
|
child.on("error", reject);
|
|
905
|
-
child.on("close", (code) =>
|
|
674
|
+
child.on("close", (code) => resolve10({ code: code ?? 0, stdout, stderr }));
|
|
906
675
|
});
|
|
907
676
|
}
|
|
908
677
|
|
|
@@ -1038,8 +807,6 @@ function escapeRegExp(value) {
|
|
|
1038
807
|
}
|
|
1039
808
|
|
|
1040
809
|
// src/risk/service.ts
|
|
1041
|
-
import { spawn as spawn2 } from "child_process";
|
|
1042
|
-
import { relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
1043
810
|
async function assessRisk(input) {
|
|
1044
811
|
const repositoryRoot = resolve3(input.repositoryRoot ?? process.cwd());
|
|
1045
812
|
const paths = await resolveRiskPaths({
|
|
@@ -1090,7 +857,14 @@ async function resolveRiskPaths(input) {
|
|
|
1090
857
|
if (contained) paths.add(contained);
|
|
1091
858
|
}
|
|
1092
859
|
}
|
|
1093
|
-
const
|
|
860
|
+
const realRoot = await realpath2(input.repositoryRoot).catch(() => resolve3(input.repositoryRoot));
|
|
861
|
+
const inside = await Promise.all(
|
|
862
|
+
[...paths].map(async (path) => {
|
|
863
|
+
const real = await realpath2(resolve3(input.repositoryRoot, path)).catch(() => null);
|
|
864
|
+
return real === null || real === realRoot || real.startsWith(`${realRoot}${sep2}`);
|
|
865
|
+
})
|
|
866
|
+
);
|
|
867
|
+
const resolved = [...paths].filter((_, index) => inside[index]).sort();
|
|
1094
868
|
return resolved.length > MAX_RESOLVED_PATHS ? resolved.slice(0, MAX_RESOLVED_PATHS) : resolved;
|
|
1095
869
|
}
|
|
1096
870
|
var MAX_RESOLVED_PATHS = 200;
|
|
@@ -1107,9 +881,7 @@ async function changedGitPaths(repositoryRoot) {
|
|
|
1107
881
|
runGit2(repositoryRoot, ["diff", "--name-only", "HEAD"]),
|
|
1108
882
|
runGit2(repositoryRoot, ["ls-files", "--others", "--exclude-standard"])
|
|
1109
883
|
]);
|
|
1110
|
-
return [...tracked.stdout.split("\n"), ...untracked.stdout.split("\n")].map((path) => path.trim()).filter(
|
|
1111
|
-
(path) => path && !path.startsWith(".codebuddy/cache/") && !path.startsWith(".codebuddy/worklog/")
|
|
1112
|
-
);
|
|
884
|
+
return [...tracked.stdout.split("\n"), ...untracked.stdout.split("\n")].map((path) => path.trim()).filter((path) => path && !path.startsWith(".codebuddy/"));
|
|
1113
885
|
}
|
|
1114
886
|
function runGit2(cwd, args) {
|
|
1115
887
|
return new Promise((resolveResult, reject) => {
|
|
@@ -1130,207 +902,8 @@ function normalizePath2(path) {
|
|
|
1130
902
|
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1131
903
|
}
|
|
1132
904
|
|
|
1133
|
-
// src/mcp/tools/index.ts
|
|
1134
|
-
import { z as z6 } from "zod";
|
|
1135
|
-
|
|
1136
|
-
// src/context/delivery.ts
|
|
1137
|
-
import { createHash as createHash2 } from "crypto";
|
|
1138
|
-
|
|
1139
|
-
// src/context/budget.ts
|
|
1140
|
-
var WARNING = "Context incomplete: inspect omitted policies, risks and incidents before editing.";
|
|
1141
|
-
function enforceContextBudget(input) {
|
|
1142
|
-
const output = structuredClone(input);
|
|
1143
|
-
if (!output.tokens) return input;
|
|
1144
|
-
output.tokens.essentialOmitted ??= 0;
|
|
1145
|
-
if (account(output) <= output.tokens.budget) return output;
|
|
1146
|
-
const candidates = [];
|
|
1147
|
-
const add = (section, value, priority, essential = false) => {
|
|
1148
|
-
candidates.push({ section, value, priority, essential });
|
|
1149
|
-
};
|
|
1150
|
-
for (const [section, priority] of [
|
|
1151
|
-
["diffs", 10],
|
|
1152
|
-
["neighbours", 20],
|
|
1153
|
-
["handoffs", 30],
|
|
1154
|
-
["facts", 40],
|
|
1155
|
-
["rules", 45],
|
|
1156
|
-
["symbols", 50],
|
|
1157
|
-
["policyRules", 90],
|
|
1158
|
-
["incidents", 80]
|
|
1159
|
-
]) {
|
|
1160
|
-
const values = output[section];
|
|
1161
|
-
if (!Array.isArray(values)) continue;
|
|
1162
|
-
for (const value of values) {
|
|
1163
|
-
const essential = section === "policyRules" || section === "incidents" && !value.resolved;
|
|
1164
|
-
const severity = section === "incidents" ? { critical: 4, high: 3, medium: 2, low: 1 }[value.severity] ?? 0 : 0;
|
|
1165
|
-
add(section, value, priority + severity, essential);
|
|
1166
|
-
}
|
|
1167
|
-
output[section] = [];
|
|
1168
|
-
}
|
|
1169
|
-
const risks = output.risks;
|
|
1170
|
-
if (risks) {
|
|
1171
|
-
for (const value of risks.items)
|
|
1172
|
-
add(
|
|
1173
|
-
"risks",
|
|
1174
|
-
value,
|
|
1175
|
-
value.score >= 50 ? 100 + value.score : 60 + value.score / 100,
|
|
1176
|
-
value.score > 0
|
|
1177
|
-
);
|
|
1178
|
-
risks.items = [];
|
|
1179
|
-
}
|
|
1180
|
-
if (output.plan) {
|
|
1181
|
-
add("plan", output.plan, 55);
|
|
1182
|
-
output.plan = null;
|
|
1183
|
-
}
|
|
1184
|
-
const architecture = output.architecture;
|
|
1185
|
-
if (architecture) {
|
|
1186
|
-
for (const value of architecture.hotspots) add("hotspots", value, 25);
|
|
1187
|
-
architecture.hotspots = [];
|
|
1188
|
-
}
|
|
1189
|
-
output.explain = [];
|
|
1190
|
-
if (typeof output.task === "string") output.task = compact(output.task, 160);
|
|
1191
|
-
const riskStats = output.risks?.stats;
|
|
1192
|
-
if (riskStats) riskStats.perSignal = [];
|
|
1193
|
-
const previousOmitted = output.tokens.essentialOmitted;
|
|
1194
|
-
output.tokens.essentialOmitted = previousOmitted + candidates.filter((c) => c.essential).length;
|
|
1195
|
-
output.tokens.warning = WARNING;
|
|
1196
|
-
if (account(output) > output.tokens.budget && output.task) output.task = null;
|
|
1197
|
-
if (estimateTokens({ project: output.project, targetPaths: output.targetPaths }) > output.tokens.budget) {
|
|
1198
|
-
account(output);
|
|
1199
|
-
return output;
|
|
1200
|
-
}
|
|
1201
|
-
candidates.sort((a, b) => b.priority - a.priority);
|
|
1202
|
-
for (const candidate of candidates) {
|
|
1203
|
-
const insert = (value) => {
|
|
1204
|
-
if (candidate.section === "plan") output.plan = value;
|
|
1205
|
-
else if (candidate.section === "risks") risks?.items.push(value);
|
|
1206
|
-
else if (candidate.section === "hotspots") architecture?.hotspots.push(value);
|
|
1207
|
-
else output[candidate.section].push(value);
|
|
1208
|
-
};
|
|
1209
|
-
const remove = () => {
|
|
1210
|
-
if (candidate.section === "plan") output.plan = null;
|
|
1211
|
-
else if (candidate.section === "risks") risks?.items.pop();
|
|
1212
|
-
else if (candidate.section === "hotspots") architecture?.hotspots.pop();
|
|
1213
|
-
else output[candidate.section].pop();
|
|
1214
|
-
};
|
|
1215
|
-
insert(candidate.value);
|
|
1216
|
-
if (candidate.essential) output.tokens.essentialOmitted--;
|
|
1217
|
-
if (account(output) <= output.tokens.budget) continue;
|
|
1218
|
-
remove();
|
|
1219
|
-
if (candidate.essential) output.tokens.essentialOmitted++;
|
|
1220
|
-
const smaller = candidate.essential ? compactSafety(candidate.value) : compact(candidate.value, 100);
|
|
1221
|
-
insert(smaller);
|
|
1222
|
-
if (account(output) <= output.tokens.budget) continue;
|
|
1223
|
-
remove();
|
|
1224
|
-
}
|
|
1225
|
-
if (output.tokens.essentialOmitted === 0) delete output.tokens.warning;
|
|
1226
|
-
account(output);
|
|
1227
|
-
return output;
|
|
1228
|
-
}
|
|
1229
|
-
function compactSafety(value) {
|
|
1230
|
-
if (!value || typeof value !== "object") return value;
|
|
1231
|
-
const item = value;
|
|
1232
|
-
return {
|
|
1233
|
-
...item,
|
|
1234
|
-
...typeof item.reason === "string" ? { reason: compact(item.reason, 100) } : {},
|
|
1235
|
-
...typeof item.summary === "string" ? { summary: compact(item.summary, 100) } : {},
|
|
1236
|
-
...Array.isArray(item.evidence) ? { evidence: item.evidence.slice(0, 1).map((entry) => compact(entry, 100)) } : {}
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
function compact(value, limit) {
|
|
1240
|
-
if (typeof value === "string")
|
|
1241
|
-
return value.length > limit ? `${value.slice(0, limit - 1)}\u2026` : value;
|
|
1242
|
-
if (Array.isArray(value)) return value.slice(0, 3).map((item) => compact(item, limit));
|
|
1243
|
-
if (value && typeof value === "object")
|
|
1244
|
-
return Object.fromEntries(
|
|
1245
|
-
Object.entries(value).map(([key, item]) => [key, compact(item, limit)])
|
|
1246
|
-
);
|
|
1247
|
-
return value;
|
|
1248
|
-
}
|
|
1249
|
-
function account(output) {
|
|
1250
|
-
const tokens = output.tokens;
|
|
1251
|
-
const set = (count) => {
|
|
1252
|
-
tokens.returnedEstimate = count;
|
|
1253
|
-
tokens.countedSurface = "compact_json";
|
|
1254
|
-
tokens.status = count > tokens.budget ? "overflow" : tokens.essentialOmitted > 0 ? "minimum_envelope" : "within_budget";
|
|
1255
|
-
if (tokens.status === "overflow") tokens.warning = WARNING;
|
|
1256
|
-
};
|
|
1257
|
-
for (let padding = 0; padding < 32; padding++) {
|
|
1258
|
-
if (padding > 0) output.accountingPadding = " ".repeat(padding);
|
|
1259
|
-
let count = estimateTokens(output);
|
|
1260
|
-
for (let attempt = 0; attempt < 12; attempt++) {
|
|
1261
|
-
set(count);
|
|
1262
|
-
const measured = estimateTokens(output);
|
|
1263
|
-
if (measured === count) return count;
|
|
1264
|
-
count = measured;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
throw new Error("Could not stabilize compact context token accounting");
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
// src/context/delivery.ts
|
|
1271
|
-
var DELIVERY_VERSION = 1;
|
|
1272
|
-
var OMITTABLE_SECTIONS = [
|
|
1273
|
-
"plan",
|
|
1274
|
-
"policyRules",
|
|
1275
|
-
"incidents",
|
|
1276
|
-
"facts",
|
|
1277
|
-
"rules",
|
|
1278
|
-
"handoffs",
|
|
1279
|
-
"architecture",
|
|
1280
|
-
"symbols",
|
|
1281
|
-
"neighbours"
|
|
1282
|
-
];
|
|
1283
|
-
var SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
1284
|
-
async function applySessionDelivery(repositoryRoot, payload, options) {
|
|
1285
|
-
let output = { ...payload };
|
|
1286
|
-
if (!options.sessionId) return output;
|
|
1287
|
-
const sections = OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
|
|
1288
|
-
key: `mcp:${key}`,
|
|
1289
|
-
hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
|
|
1290
|
-
}));
|
|
1291
|
-
try {
|
|
1292
|
-
await new SessionCapsuleLedger(repositoryRoot).exchange(options.sessionId, sections, {
|
|
1293
|
-
...options.ack !== void 0 ? { ack: options.ack } : {},
|
|
1294
|
-
ttlMs: SESSION_TTL_MS,
|
|
1295
|
-
...options.full ? { full: true } : {},
|
|
1296
|
-
prepare: (exchange) => {
|
|
1297
|
-
const unchanged = exchange.unchanged.map((key) => key.slice("mcp:".length));
|
|
1298
|
-
let avoidedTokens = 0;
|
|
1299
|
-
for (const key of unchanged) {
|
|
1300
|
-
avoidedTokens += estimateTokens(output[key]);
|
|
1301
|
-
delete output[key];
|
|
1302
|
-
}
|
|
1303
|
-
const delivery = {
|
|
1304
|
-
version: DELIVERY_VERSION,
|
|
1305
|
-
sessionId: options.sessionId,
|
|
1306
|
-
deliveryId: exchange.deliveryId,
|
|
1307
|
-
ack: exchange.ack,
|
|
1308
|
-
unchanged,
|
|
1309
|
-
avoidedTokens,
|
|
1310
|
-
note: "Pass deliveryId as ack. Use full=true after losing context."
|
|
1311
|
-
};
|
|
1312
|
-
output.delivery = delivery;
|
|
1313
|
-
output = enforceContextBudget(output);
|
|
1314
|
-
return OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
|
|
1315
|
-
key: `mcp:${key}`,
|
|
1316
|
-
hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
|
|
1317
|
-
}));
|
|
1318
|
-
}
|
|
1319
|
-
});
|
|
1320
|
-
return output;
|
|
1321
|
-
} catch {
|
|
1322
|
-
return enforceContextBudget({ ...payload });
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
// src/context/engine.ts
|
|
1327
|
-
import { execFile } from "child_process";
|
|
1328
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
1329
|
-
import { join as join4, resolve as resolve5 } from "path";
|
|
1330
|
-
import { promisify } from "util";
|
|
1331
|
-
|
|
1332
905
|
// src/worklog/store.ts
|
|
1333
|
-
import { createHash
|
|
906
|
+
import { createHash } from "crypto";
|
|
1334
907
|
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1335
908
|
import { dirname as dirname2, join as join3, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
|
|
1336
909
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
@@ -1373,7 +946,7 @@ var WorklogStore = class {
|
|
|
1373
946
|
const files = [...new Set((input.files ?? []).map(normalizePath3))].filter(Boolean).sort().slice(0, LIMITS.files);
|
|
1374
947
|
const summary = boundSummary(input.summary, files);
|
|
1375
948
|
const verification = (input.verification ?? []).map((line) => line.trim().slice(0, LIMITS.verificationChars)).filter((line) => line && !scanSensitive(line).sensitive).slice(0, LIMITS.verification);
|
|
1376
|
-
const fingerprint =
|
|
949
|
+
const fingerprint = createHash("sha256").update(
|
|
1377
950
|
JSON.stringify({
|
|
1378
951
|
namespace: input.namespace.trim().toLowerCase(),
|
|
1379
952
|
session: input.sessionId ?? null,
|
|
@@ -1520,45 +1093,176 @@ function words(text) {
|
|
|
1520
1093
|
return new Set(text.toLowerCase().match(/[a-z0-9_]{4,}/g) ?? []);
|
|
1521
1094
|
}
|
|
1522
1095
|
|
|
1523
|
-
// src/context/
|
|
1524
|
-
var
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1096
|
+
// src/context/budget.ts
|
|
1097
|
+
var WARNING = "Context incomplete: inspect omitted policies, risks and incidents before editing.";
|
|
1098
|
+
function enforceContextBudget(input) {
|
|
1099
|
+
const output = structuredClone(input);
|
|
1100
|
+
if (!output.tokens) return input;
|
|
1101
|
+
output.tokens.essentialOmitted ??= 0;
|
|
1102
|
+
if (account(output) <= output.tokens.budget) return output;
|
|
1103
|
+
const candidates = [];
|
|
1104
|
+
const add = (section, value, priority, essential = false) => {
|
|
1105
|
+
candidates.push({ section, value, priority, essential });
|
|
1106
|
+
};
|
|
1107
|
+
for (const [section, priority] of [
|
|
1108
|
+
["diffs", 10],
|
|
1109
|
+
["neighbours", 20],
|
|
1110
|
+
["handoffs", 30],
|
|
1111
|
+
["facts", 40],
|
|
1112
|
+
["rules", 45],
|
|
1113
|
+
["symbols", 50],
|
|
1114
|
+
["policyRules", 90],
|
|
1115
|
+
["incidents", 80]
|
|
1116
|
+
]) {
|
|
1117
|
+
const values = output[section];
|
|
1118
|
+
if (!Array.isArray(values)) continue;
|
|
1119
|
+
for (const value of values) {
|
|
1120
|
+
const essential = section === "policyRules" || section === "incidents" && !value.resolved;
|
|
1121
|
+
const severity = section === "incidents" ? { critical: 4, high: 3, medium: 2, low: 1 }[value.severity] ?? 0 : 0;
|
|
1122
|
+
add(section, value, priority + severity, essential);
|
|
1123
|
+
}
|
|
1124
|
+
output[section] = [];
|
|
1125
|
+
}
|
|
1126
|
+
const risks = output.risks;
|
|
1127
|
+
if (risks) {
|
|
1128
|
+
for (const value of risks.items)
|
|
1129
|
+
add(
|
|
1130
|
+
"risks",
|
|
1131
|
+
value,
|
|
1132
|
+
value.score >= 50 ? 100 + value.score : 60 + value.score / 100,
|
|
1133
|
+
value.score > 0
|
|
1134
|
+
);
|
|
1135
|
+
risks.items = [];
|
|
1136
|
+
}
|
|
1137
|
+
if (output.plan) {
|
|
1138
|
+
add("plan", output.plan, 55);
|
|
1139
|
+
output.plan = null;
|
|
1140
|
+
}
|
|
1141
|
+
const architecture = output.architecture;
|
|
1142
|
+
if (architecture) {
|
|
1143
|
+
for (const value of architecture.hotspots) add("hotspots", value, 25);
|
|
1144
|
+
architecture.hotspots = [];
|
|
1145
|
+
}
|
|
1146
|
+
output.explain = [];
|
|
1147
|
+
if (typeof output.task === "string") output.task = compact(output.task, 160);
|
|
1148
|
+
const riskStats = output.risks?.stats;
|
|
1149
|
+
if (riskStats) riskStats.perSignal = [];
|
|
1150
|
+
const previousOmitted = output.tokens.essentialOmitted;
|
|
1151
|
+
output.tokens.essentialOmitted = previousOmitted + candidates.filter((c) => c.essential).length;
|
|
1152
|
+
output.tokens.warning = WARNING;
|
|
1153
|
+
if (account(output) > output.tokens.budget && output.task) output.task = null;
|
|
1154
|
+
if (estimateTokens({ project: output.project, targetPaths: output.targetPaths }) > output.tokens.budget) {
|
|
1155
|
+
account(output);
|
|
1156
|
+
return output;
|
|
1157
|
+
}
|
|
1158
|
+
candidates.sort((a, b) => b.priority - a.priority);
|
|
1159
|
+
for (const candidate of candidates) {
|
|
1160
|
+
const insert = (value) => {
|
|
1161
|
+
if (candidate.section === "plan") output.plan = value;
|
|
1162
|
+
else if (candidate.section === "risks") risks?.items.push(value);
|
|
1163
|
+
else if (candidate.section === "hotspots") architecture?.hotspots.push(value);
|
|
1164
|
+
else output[candidate.section].push(value);
|
|
1165
|
+
};
|
|
1166
|
+
const remove = () => {
|
|
1167
|
+
if (candidate.section === "plan") output.plan = null;
|
|
1168
|
+
else if (candidate.section === "risks") risks?.items.pop();
|
|
1169
|
+
else if (candidate.section === "hotspots") architecture?.hotspots.pop();
|
|
1170
|
+
else output[candidate.section].pop();
|
|
1171
|
+
};
|
|
1172
|
+
insert(candidate.value);
|
|
1173
|
+
if (candidate.essential) output.tokens.essentialOmitted--;
|
|
1174
|
+
if (account(output) <= output.tokens.budget) continue;
|
|
1175
|
+
remove();
|
|
1176
|
+
if (candidate.essential) output.tokens.essentialOmitted++;
|
|
1177
|
+
const smaller = candidate.essential ? compactSafety(candidate.value) : compact(candidate.value, 100);
|
|
1178
|
+
insert(smaller);
|
|
1179
|
+
if (account(output) <= output.tokens.budget) continue;
|
|
1180
|
+
remove();
|
|
1181
|
+
}
|
|
1182
|
+
if (output.tokens.essentialOmitted === 0) delete output.tokens.warning;
|
|
1183
|
+
account(output);
|
|
1184
|
+
return output;
|
|
1185
|
+
}
|
|
1186
|
+
function compactSafety(value) {
|
|
1187
|
+
if (!value || typeof value !== "object") return value;
|
|
1188
|
+
const item = value;
|
|
1189
|
+
return {
|
|
1190
|
+
...item,
|
|
1191
|
+
...typeof item.reason === "string" ? { reason: compact(item.reason, 100) } : {},
|
|
1192
|
+
...typeof item.summary === "string" ? { summary: compact(item.summary, 100) } : {},
|
|
1193
|
+
...Array.isArray(item.evidence) ? { evidence: item.evidence.slice(0, 1).map((entry) => compact(entry, 100)) } : {}
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
function compact(value, limit) {
|
|
1197
|
+
if (typeof value === "string")
|
|
1198
|
+
return value.length > limit ? `${value.slice(0, limit - 1)}\u2026` : value;
|
|
1199
|
+
if (Array.isArray(value)) return value.slice(0, 3).map((item) => compact(item, limit));
|
|
1200
|
+
if (value && typeof value === "object")
|
|
1201
|
+
return Object.fromEntries(
|
|
1202
|
+
Object.entries(value).map(([key, item]) => [key, compact(item, limit)])
|
|
1203
|
+
);
|
|
1204
|
+
return value;
|
|
1205
|
+
}
|
|
1206
|
+
function account(output) {
|
|
1207
|
+
const tokens = output.tokens;
|
|
1208
|
+
const set = (count) => {
|
|
1209
|
+
tokens.returnedEstimate = count;
|
|
1210
|
+
tokens.countedSurface = "compact_json";
|
|
1211
|
+
tokens.status = count > tokens.budget ? "overflow" : tokens.essentialOmitted > 0 ? "minimum_envelope" : "within_budget";
|
|
1212
|
+
if (tokens.status === "overflow") tokens.warning = WARNING;
|
|
1213
|
+
};
|
|
1214
|
+
for (let padding = 0; padding < 32; padding++) {
|
|
1215
|
+
if (padding > 0) output.accountingPadding = " ".repeat(padding);
|
|
1216
|
+
let count = estimateTokens(output);
|
|
1217
|
+
for (let attempt = 0; attempt < 12; attempt++) {
|
|
1218
|
+
set(count);
|
|
1219
|
+
const measured = estimateTokens(output);
|
|
1220
|
+
if (measured === count) return count;
|
|
1221
|
+
count = measured;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
throw new Error("Could not stabilize compact context token accounting");
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/context/engine.ts
|
|
1228
|
+
var LIMITS2 = {
|
|
1229
|
+
bootstrapIncidents: 5,
|
|
1230
|
+
beforeEditIncidents: 8,
|
|
1231
|
+
policyRules: 5,
|
|
1232
|
+
hotspots: 5,
|
|
1233
|
+
neighbourPaths: 10,
|
|
1234
|
+
neighbourEdges: 10,
|
|
1235
|
+
incidentSummaryChars: 140,
|
|
1236
|
+
symbolFiles: TASK_PATH_LIMIT,
|
|
1237
|
+
symbolsPerFile: 12,
|
|
1238
|
+
spanFiles: 3,
|
|
1239
|
+
excerptLines: 6,
|
|
1240
|
+
excerptCharsPerSpan: 400,
|
|
1241
|
+
excerptChars: 1200,
|
|
1242
|
+
relevantFacts: 5
|
|
1243
|
+
};
|
|
1244
|
+
var DEFAULT_TOKEN_BUDGET = 4e3;
|
|
1245
|
+
var MAX_DIFF_FILES = 8;
|
|
1246
|
+
var MAX_DIFF_CHARS_PER_FILE = 2400;
|
|
1247
|
+
var execFileAsync = promisify(execFile);
|
|
1248
|
+
async function buildBootstrapContext(input) {
|
|
1249
|
+
const repositoryRoot = resolve5(input.repositoryRoot ?? process.cwd());
|
|
1250
|
+
const namespace = input.namespace;
|
|
1251
|
+
const planStore = new PlanFileStore(repositoryRoot);
|
|
1252
|
+
const memoryStore = new MemoryFileStore(repositoryRoot);
|
|
1253
|
+
const freshPromise = loadFreshIndex(repositoryRoot);
|
|
1254
|
+
const manifestPromise = freshPromise.then((fresh2) => fresh2.manifest);
|
|
1255
|
+
const mapPromise = loadProjectArchitectureMap(repositoryRoot, manifestPromise);
|
|
1256
|
+
const [activePlan, policy, policyFile, incidents, facts, fresh, map, handoffs] = await Promise.all([
|
|
1257
|
+
new PlanLifecycle(planStore).current(namespace),
|
|
1258
|
+
loadPlanPolicy(repositoryRoot),
|
|
1259
|
+
readPolicyFile(join4(repositoryRoot, ".codebuddy", "policies.yaml")),
|
|
1260
|
+
memoryStore.listIncidentFacts({ namespace }),
|
|
1261
|
+
memoryStore.listFacts(),
|
|
1262
|
+
freshPromise,
|
|
1263
|
+
mapPromise,
|
|
1264
|
+
recallHandoffs(repositoryRoot, { namespace })
|
|
1265
|
+
]);
|
|
1562
1266
|
const explain = [fresh.explain];
|
|
1563
1267
|
const plan = summarizePlan(activePlan);
|
|
1564
1268
|
explain.push(plan ? `plan: active plan ${plan.id} (${plan.status})` : "plan: no active plan");
|
|
@@ -1661,10 +1365,10 @@ async function buildBeforeEditContext(input) {
|
|
|
1661
1365
|
explain.push(`incidents: ${incidentSummaries.length} incident(s) touch target paths`);
|
|
1662
1366
|
}
|
|
1663
1367
|
if (risks.items.length > 0) explain.push(`risks: ${risks.items.length} scored path(s)`);
|
|
1664
|
-
const
|
|
1665
|
-
if (
|
|
1368
|
+
const neighbours = computeNeighbours(map, targetPaths);
|
|
1369
|
+
if (neighbours.length > 0)
|
|
1666
1370
|
explain.push(
|
|
1667
|
-
`neighbours: import graph for ${
|
|
1371
|
+
`neighbours: import graph for ${neighbours.length} file(s)` + (map.source ? ` (${map.source})` : "")
|
|
1668
1372
|
);
|
|
1669
1373
|
const symbols = await computeTargetSymbols(
|
|
1670
1374
|
repositoryRoot,
|
|
@@ -1718,7 +1422,7 @@ async function buildBeforeEditContext(input) {
|
|
|
1718
1422
|
facts: relevantFacts,
|
|
1719
1423
|
handoffs,
|
|
1720
1424
|
risks,
|
|
1721
|
-
neighbours
|
|
1425
|
+
neighbours,
|
|
1722
1426
|
symbols,
|
|
1723
1427
|
tests,
|
|
1724
1428
|
testCommand,
|
|
@@ -2023,8 +1727,10 @@ async function buildUntrackedPatch(repositoryRoot, path) {
|
|
|
2023
1727
|
}
|
|
2024
1728
|
var STANDING_PREDICATES = /* @__PURE__ */ new Set(["rule", "procedure"]);
|
|
2025
1729
|
var STANDING_LIMITS = { rule: 5, procedure: 3, chars: 200 };
|
|
1730
|
+
var SHELL_CONTROL = /[;&|`$<>\n\r]/;
|
|
2026
1731
|
function selectRules(facts, namespace) {
|
|
2027
|
-
const
|
|
1732
|
+
const latest = new Date(Date.now() + 864e5).toISOString();
|
|
1733
|
+
const newest = (predicate) => facts.filter((fact) => fact.predicate === predicate && isEligibleMemory(fact, { namespace })).filter((fact) => fact.createdAt <= latest).filter((fact) => predicate !== "procedure" || !SHELL_CONTROL.test(fact.object)).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, STANDING_LIMITS[predicate]);
|
|
2028
1734
|
return [...newest("rule"), ...newest("procedure")].map((fact) => ({
|
|
2029
1735
|
id: fact.id,
|
|
2030
1736
|
subject: fact.predicate,
|
|
@@ -2115,7 +1821,16 @@ async function rankRelevantFacts(facts, namespace, task, targetPaths, semanticRa
|
|
|
2115
1821
|
} catch {
|
|
2116
1822
|
}
|
|
2117
1823
|
}
|
|
2118
|
-
|
|
1824
|
+
const selected = [];
|
|
1825
|
+
for (const candidate of fused) {
|
|
1826
|
+
if (selected.length >= LIMITS2.relevantFacts) break;
|
|
1827
|
+
const text = candidate.fact.content || candidate.fact.object;
|
|
1828
|
+
if (selected.some((kept) => termOverlap(text, kept.fact.content || kept.fact.object) >= 0.8)) {
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
selected.push(candidate);
|
|
1832
|
+
}
|
|
1833
|
+
return selected.map(({ fact, taskScore, pathScore, score }) => {
|
|
2119
1834
|
const evidence = [
|
|
2120
1835
|
pathScore > 0 ? "matches a target path" : "",
|
|
2121
1836
|
taskScore > 0 ? `shares ${taskScore / 3} task term(s)` : "",
|
|
@@ -2159,13 +1874,8 @@ function truncate(value, max) {
|
|
|
2159
1874
|
return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max - 1)}\u2026`;
|
|
2160
1875
|
}
|
|
2161
1876
|
|
|
2162
|
-
// src/hooks/runtime.ts
|
|
2163
|
-
import { readFile as readFile8 } from "fs/promises";
|
|
2164
|
-
import { basename, isAbsolute, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
|
|
2165
|
-
import pTimeout2 from "p-timeout";
|
|
2166
|
-
|
|
2167
1877
|
// src/memory-extract/engine.ts
|
|
2168
|
-
import { createHash as
|
|
1878
|
+
import { createHash as createHash2 } from "crypto";
|
|
2169
1879
|
import { resolve as resolve7 } from "path";
|
|
2170
1880
|
|
|
2171
1881
|
// src/memory-extract/deterministic.ts
|
|
@@ -2835,8 +2545,36 @@ async function gate(args) {
|
|
|
2835
2545
|
ref: match.incident.id
|
|
2836
2546
|
};
|
|
2837
2547
|
}
|
|
2838
|
-
const ref = await saveDurable(args);
|
|
2839
2548
|
const superseded = candidate.content.match(/\bsupersedes\s+(fact_[a-z0-9]+)\b/i)?.[1];
|
|
2549
|
+
if (candidate.class !== "incident" && !superseded) {
|
|
2550
|
+
const similar = await nearestFact(
|
|
2551
|
+
args.memoryStore,
|
|
2552
|
+
args.input.namespace,
|
|
2553
|
+
candidate,
|
|
2554
|
+
fingerprint
|
|
2555
|
+
);
|
|
2556
|
+
if (similar && similar.overlap >= DUPLICATE_OVERLAP) {
|
|
2557
|
+
return {
|
|
2558
|
+
...base,
|
|
2559
|
+
outcome: "ignored",
|
|
2560
|
+
reason: `near-duplicate of ${similar.id} (${Math.round(similar.overlap * 100)}% shared words)`,
|
|
2561
|
+
ref: similar.id
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
if (similar && similar.overlap >= UPDATE_OVERLAP) {
|
|
2565
|
+
const existing = (await args.reviewStore.list()).find(
|
|
2566
|
+
(item) => item.fingerprint === fingerprint
|
|
2567
|
+
);
|
|
2568
|
+
const ref2 = existing?.id ?? await queue(args, `possible update of ${similar.id}`);
|
|
2569
|
+
return {
|
|
2570
|
+
...base,
|
|
2571
|
+
outcome: "queued",
|
|
2572
|
+
reason: `overlaps ${similar.id} (${Math.round(similar.overlap * 100)}% shared words): review as a possible update`,
|
|
2573
|
+
ref: ref2
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
const ref = await saveDurable(args);
|
|
2840
2578
|
if (superseded && superseded !== ref) {
|
|
2841
2579
|
try {
|
|
2842
2580
|
await args.memoryStore.markFactSuperseded(superseded, ref);
|
|
@@ -2845,6 +2583,19 @@ async function gate(args) {
|
|
|
2845
2583
|
}
|
|
2846
2584
|
return { ...base, outcome: "saved", reason: `confident ${candidate.class} auto-saved`, ref };
|
|
2847
2585
|
}
|
|
2586
|
+
var DUPLICATE_OVERLAP = 0.8;
|
|
2587
|
+
var UPDATE_OVERLAP = 0.5;
|
|
2588
|
+
async function nearestFact(memoryStore, namespace, candidate, fingerprint) {
|
|
2589
|
+
let best = null;
|
|
2590
|
+
for (const fact of await memoryStore.listFacts()) {
|
|
2591
|
+
if (fact.fingerprint === fingerprint || fact.category === "incident") continue;
|
|
2592
|
+
if (fact.predicate === "rule" || fact.predicate === "procedure") continue;
|
|
2593
|
+
if (!isEligibleMemory(fact, { namespace })) continue;
|
|
2594
|
+
const overlap = termOverlap(candidate.content, fact.content || fact.object);
|
|
2595
|
+
if (!best || overlap > best.overlap) best = { id: fact.id, overlap };
|
|
2596
|
+
}
|
|
2597
|
+
return best;
|
|
2598
|
+
}
|
|
2848
2599
|
async function saveDurable(args) {
|
|
2849
2600
|
const { candidate, input, memoryStore, now } = args;
|
|
2850
2601
|
const fingerprint = candidateFingerprint(input.namespace, candidate);
|
|
@@ -2920,7 +2671,7 @@ function candidateFingerprint(namespace, raw) {
|
|
|
2920
2671
|
content: candidate.content.trim().replace(/\s+/g, " ").toLowerCase(),
|
|
2921
2672
|
paths: [...new Set(candidate.paths.map((path) => path.replace(/\\/g, "/")))].sort()
|
|
2922
2673
|
});
|
|
2923
|
-
return
|
|
2674
|
+
return createHash2("sha256").update(canonical).digest("hex");
|
|
2924
2675
|
}
|
|
2925
2676
|
async function approveReviewItem(id, options = {}) {
|
|
2926
2677
|
const repositoryRoot = resolve7(options.repositoryRoot ?? process.cwd());
|
|
@@ -2970,7 +2721,7 @@ async function approveReviewItem(id, options = {}) {
|
|
|
2970
2721
|
}
|
|
2971
2722
|
|
|
2972
2723
|
// src/tracing/log.ts
|
|
2973
|
-
import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat3
|
|
2724
|
+
import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
2974
2725
|
import { dirname as dirname4, join as join6 } from "path";
|
|
2975
2726
|
var MAX_BYTES = 4 * 1024 * 1024;
|
|
2976
2727
|
function tracePath(repositoryRoot) {
|
|
@@ -2985,7 +2736,7 @@ async function recordTrace(repositoryRoot, event) {
|
|
|
2985
2736
|
`);
|
|
2986
2737
|
if ((await stat3(path)).size > MAX_BYTES) {
|
|
2987
2738
|
const lines = (await readFile6(path, "utf8")).split("\n").filter(Boolean);
|
|
2988
|
-
await
|
|
2739
|
+
await writeAtomic(path, `${lines.slice(Math.floor(lines.length / 2)).join("\n")}
|
|
2989
2740
|
`);
|
|
2990
2741
|
}
|
|
2991
2742
|
} catch {
|
|
@@ -3028,7 +2779,7 @@ async function configuredReranker(repositoryRoot) {
|
|
|
3028
2779
|
if (provider === "local") {
|
|
3029
2780
|
const revision = model === DEFAULT_RERANK_MODELS.local.id ? DEFAULT_RERANK_MODELS.local.revision : void 0;
|
|
3030
2781
|
return async (task, documents) => {
|
|
3031
|
-
const { rerankLocally } = await import("./local-
|
|
2782
|
+
const { rerankLocally } = await import("./local-5MZURLCZ.js");
|
|
3032
2783
|
return bounded(
|
|
3033
2784
|
rerankLocally({ id: model, ...revision ? { revision } : {} }, task, documents)
|
|
3034
2785
|
);
|
|
@@ -3036,7 +2787,7 @@ async function configuredReranker(repositoryRoot) {
|
|
|
3036
2787
|
}
|
|
3037
2788
|
const apiKey = provider === "openrouter" ? process.env.OPENROUTER_API_KEY : process.env.HF_TOKEN ?? file.provider?.apiKey;
|
|
3038
2789
|
if (!apiKey) return void 0;
|
|
3039
|
-
const { rerankHuggingFace, rerankOpenRouter } = await import("./cloud-
|
|
2790
|
+
const { rerankHuggingFace, rerankOpenRouter } = await import("./cloud-7WM3YLI4.js");
|
|
3040
2791
|
const call = provider === "openrouter" ? rerankOpenRouter : rerankHuggingFace;
|
|
3041
2792
|
return (task, documents) => call({ apiKey, model, timeoutMs: RERANK_BUDGET_MS }, task, documents);
|
|
3042
2793
|
}
|
|
@@ -3045,8 +2796,8 @@ async function openHookSemantics(repositoryRoot) {
|
|
|
3045
2796
|
const connect = async () => {
|
|
3046
2797
|
const config = await loadRuntimeConfig(repositoryRoot, { requireDatabase: false });
|
|
3047
2798
|
if (!config.postgresUrl) return null;
|
|
3048
|
-
const { createRuntime
|
|
3049
|
-
const opening =
|
|
2799
|
+
const { createRuntime } = await import("./operations-POCHY3WR.js");
|
|
2800
|
+
const opening = createRuntime(config, {
|
|
3050
2801
|
repositoryRoot,
|
|
3051
2802
|
startWorker: false,
|
|
3052
2803
|
connectTimeoutSeconds: 2
|
|
@@ -3082,6 +2833,7 @@ async function openHookSemantics(repositoryRoot) {
|
|
|
3082
2833
|
// src/hooks/staging.ts
|
|
3083
2834
|
import { mkdir as mkdir5, readFile as readFile7, rename as rename2, rm as rm3, stat as stat4 } from "fs/promises";
|
|
3084
2835
|
import { join as join7, resolve as resolve8 } from "path";
|
|
2836
|
+
var STAGING_LOCK_STALE_MS = 1e4;
|
|
3085
2837
|
var HookStagingStore = class {
|
|
3086
2838
|
directory;
|
|
3087
2839
|
operations = Promise.resolve();
|
|
@@ -3206,6 +2958,14 @@ var HookStagingStore = class {
|
|
|
3206
2958
|
break;
|
|
3207
2959
|
} catch (error) {
|
|
3208
2960
|
if (error.code !== "EEXIST" || attempt >= 200) throw error;
|
|
2961
|
+
const age = await stat4(lock).then(
|
|
2962
|
+
(info) => Date.now() - info.mtimeMs,
|
|
2963
|
+
() => 0
|
|
2964
|
+
);
|
|
2965
|
+
if (age > STAGING_LOCK_STALE_MS) {
|
|
2966
|
+
await rm3(lock, { recursive: true, force: true });
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
3209
2969
|
await new Promise((resolveWait) => setTimeout(resolveWait, 10));
|
|
3210
2970
|
}
|
|
3211
2971
|
}
|
|
@@ -3252,7 +3012,7 @@ async function resolveNamespace(repositoryRoot) {
|
|
|
3252
3012
|
}
|
|
3253
3013
|
async function runContextHook(input, emit, budgetMs = CONTEXT_HOOK_BUDGET_MS) {
|
|
3254
3014
|
const started = Date.now();
|
|
3255
|
-
const repositoryRoot =
|
|
3015
|
+
const repositoryRoot = hookRoot(input.cwd);
|
|
3256
3016
|
const namespace = await resolveNamespace(repositoryRoot);
|
|
3257
3017
|
const bootstrap = await buildBootstrapContext({ repositoryRoot, namespace });
|
|
3258
3018
|
let beforeEdit = null;
|
|
@@ -3317,14 +3077,14 @@ function recallTrace(input, beforeEdit, emitted, text, started, late) {
|
|
|
3317
3077
|
};
|
|
3318
3078
|
}
|
|
3319
3079
|
async function runStageHook(input) {
|
|
3320
|
-
const repositoryRoot =
|
|
3080
|
+
const repositoryRoot = hookRoot(input.cwd);
|
|
3321
3081
|
const paths = extractEditedPaths(input.tool_input).map((path) => toRepoRelative(repositoryRoot, path)).filter((path) => path !== null);
|
|
3322
3082
|
await new HookStagingStore(repositoryRoot).add(input.session_id ?? "default", paths);
|
|
3323
3083
|
return paths;
|
|
3324
3084
|
}
|
|
3325
3085
|
async function llmMemoryOptions(repositoryRoot) {
|
|
3326
3086
|
try {
|
|
3327
|
-
return await (await import("./memory-
|
|
3087
|
+
return await (await import("./memory-KIC5FJVW.js")).llmCaptureOptions(repositoryRoot);
|
|
3328
3088
|
} catch {
|
|
3329
3089
|
return {};
|
|
3330
3090
|
}
|
|
@@ -3333,7 +3093,7 @@ var INVESTIGATION_MIN_CHARS = 200;
|
|
|
3333
3093
|
var NAMES_CODE = /[\w.-]+\/[\w./-]*\.\w{1,5}\b|\b[\w-]+\.(?:ts|tsx|js|jsx|mjs|py|go|rs|java|rb|php|cs|c|cpp|h|swift|kt)\b|`[^`\s]+`/;
|
|
3334
3094
|
async function runCaptureHook(input) {
|
|
3335
3095
|
const captureStarted = Date.now();
|
|
3336
|
-
const repositoryRoot =
|
|
3096
|
+
const repositoryRoot = hookRoot(input.cwd);
|
|
3337
3097
|
const staging = new HookStagingStore(repositoryRoot);
|
|
3338
3098
|
const sessionId = input.session_id ?? "default";
|
|
3339
3099
|
const claimed = await staging.claim(sessionId);
|
|
@@ -3403,39 +3163,78 @@ async function runCaptureHook(input) {
|
|
|
3403
3163
|
}
|
|
3404
3164
|
}
|
|
3405
3165
|
var SYNC_BUDGET_MS = 5 * 6e4;
|
|
3406
|
-
async function runSyncHook(repositoryRoot) {
|
|
3166
|
+
async function runSyncHook(repositoryRoot, options = {}) {
|
|
3407
3167
|
const config = await loadRuntimeConfig(repositoryRoot, { requireDatabase: false });
|
|
3408
3168
|
if (!config.postgresUrl) return null;
|
|
3409
|
-
const
|
|
3410
|
-
const
|
|
3411
|
-
|
|
3169
|
+
const lock = join8(repositoryRoot, ".codebuddy", "cache", "sync.lock");
|
|
3170
|
+
const dirty = join8(repositoryRoot, ".codebuddy", "cache", "sync.dirty");
|
|
3171
|
+
await mkdir6(dirname5(lock), { recursive: true });
|
|
3172
|
+
if (!await takeLock(lock)) {
|
|
3173
|
+
await writeFile(dirty, "").catch(() => void 0);
|
|
3174
|
+
return null;
|
|
3175
|
+
}
|
|
3176
|
+
const { createRuntime } = await import("./operations-POCHY3WR.js");
|
|
3177
|
+
const runtime = await createRuntime(config, { repositoryRoot }).catch(async (error) => {
|
|
3178
|
+
await rm4(lock, { recursive: true, force: true });
|
|
3179
|
+
throw error;
|
|
3180
|
+
});
|
|
3412
3181
|
try {
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3182
|
+
let result;
|
|
3183
|
+
let round = 0;
|
|
3184
|
+
do {
|
|
3185
|
+
await rm4(dirty, { force: true });
|
|
3186
|
+
const started = Date.now();
|
|
3187
|
+
const reindex = await runtime.memory.reindexMemory(options.respace ? {} : { respace: false });
|
|
3188
|
+
const drained = await pTimeout2(runtime.memory.getWorker().drain(), {
|
|
3189
|
+
milliseconds: SYNC_BUDGET_MS
|
|
3190
|
+
}).then(
|
|
3191
|
+
() => true,
|
|
3192
|
+
() => false
|
|
3193
|
+
);
|
|
3194
|
+
await recordTrace(repositoryRoot, {
|
|
3195
|
+
kind: "sync",
|
|
3196
|
+
name: "sync",
|
|
3197
|
+
ms: Date.now() - started,
|
|
3198
|
+
ok: drained,
|
|
3199
|
+
detail: {
|
|
3200
|
+
imported: reindex.facts.imported,
|
|
3201
|
+
updated: reindex.facts.updated,
|
|
3202
|
+
reembedding: reindex.reembedding ?? 0,
|
|
3203
|
+
failures: reindex.failures.length
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
result = { reindex, drained };
|
|
3207
|
+
round += 1;
|
|
3208
|
+
} while (round < 3 && await stat5(dirty).then(
|
|
3417
3209
|
() => true,
|
|
3418
3210
|
() => false
|
|
3419
|
-
);
|
|
3420
|
-
|
|
3421
|
-
kind: "sync",
|
|
3422
|
-
name: "sync",
|
|
3423
|
-
ms: Date.now() - started,
|
|
3424
|
-
ok: drained,
|
|
3425
|
-
detail: {
|
|
3426
|
-
imported: reindex.facts.imported,
|
|
3427
|
-
updated: reindex.facts.updated,
|
|
3428
|
-
reembedding: reindex.reembedding ?? 0,
|
|
3429
|
-
failures: reindex.failures.length
|
|
3430
|
-
}
|
|
3431
|
-
});
|
|
3432
|
-
return { reindex, drained };
|
|
3211
|
+
));
|
|
3212
|
+
return result;
|
|
3433
3213
|
} finally {
|
|
3434
3214
|
await runtime.close();
|
|
3215
|
+
await rm4(lock, { recursive: true, force: true });
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
var SYNC_LOCK_STALE_MS = SYNC_BUDGET_MS + 6e4;
|
|
3219
|
+
async function takeLock(path) {
|
|
3220
|
+
try {
|
|
3221
|
+
await mkdir6(path);
|
|
3222
|
+
return true;
|
|
3223
|
+
} catch {
|
|
3224
|
+
const age = await stat5(path).then(
|
|
3225
|
+
(info) => Date.now() - info.mtimeMs,
|
|
3226
|
+
() => 0
|
|
3227
|
+
);
|
|
3228
|
+
if (age < SYNC_LOCK_STALE_MS) return false;
|
|
3229
|
+
await rm4(path, { recursive: true, force: true });
|
|
3230
|
+
return mkdir6(path).then(
|
|
3231
|
+
() => true,
|
|
3232
|
+
() => false
|
|
3233
|
+
);
|
|
3435
3234
|
}
|
|
3436
3235
|
}
|
|
3437
3236
|
async function runResetHook(input) {
|
|
3438
|
-
const repositoryRoot =
|
|
3237
|
+
const repositoryRoot = hookRoot(input.cwd);
|
|
3439
3238
|
await new SessionCapsuleLedger(repositoryRoot).reset(input.session_id ?? "default");
|
|
3440
3239
|
}
|
|
3441
3240
|
function extractEditedPaths(toolInput) {
|
|
@@ -3750,1243 +3549,7 @@ function describeKeys(keys) {
|
|
|
3750
3549
|
}
|
|
3751
3550
|
var kinds = /* @__PURE__ */ new Set(["fact", "incident", "handoff", "rule", "procedure"]);
|
|
3752
3551
|
|
|
3753
|
-
// src/suggest/engine.ts
|
|
3754
|
-
import { constants } from "fs";
|
|
3755
|
-
import { access } from "fs/promises";
|
|
3756
|
-
import { extname, join as join8, resolve as resolve10 } from "path";
|
|
3757
|
-
|
|
3758
|
-
// src/suggest/types.ts
|
|
3759
|
-
var SEVERITY_RANK = {
|
|
3760
|
-
high: 3,
|
|
3761
|
-
medium: 2,
|
|
3762
|
-
low: 1,
|
|
3763
|
-
info: 0
|
|
3764
|
-
};
|
|
3765
|
-
|
|
3766
|
-
// src/suggest/engine.ts
|
|
3767
|
-
var RISK_SUGGEST_THRESHOLD = 50;
|
|
3768
|
-
var BLAST_RADIUS_MEDIUM = 5;
|
|
3769
|
-
var BLAST_RADIUS_HIGH = 10;
|
|
3770
|
-
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
3771
|
-
async function generateSuggestions(input) {
|
|
3772
|
-
const repositoryRoot = resolve10(input.repositoryRoot ?? process.cwd());
|
|
3773
|
-
const namespace = input.namespace;
|
|
3774
|
-
const targetPaths = await resolveRiskPaths({
|
|
3775
|
-
repositoryRoot,
|
|
3776
|
-
...input.paths !== void 0 ? { paths: input.paths } : {},
|
|
3777
|
-
...input.planId !== void 0 ? { planId: input.planId } : {},
|
|
3778
|
-
...input.useGit !== void 0 ? { useGit: input.useGit } : {}
|
|
3779
|
-
});
|
|
3780
|
-
const planStore = new PlanFileStore(repositoryRoot);
|
|
3781
|
-
const memoryStore = new MemoryFileStore(repositoryRoot);
|
|
3782
|
-
const [risks, plan, incidents, map, policyFile] = await Promise.all([
|
|
3783
|
-
assessRisk({
|
|
3784
|
-
repositoryRoot,
|
|
3785
|
-
namespace,
|
|
3786
|
-
...input.paths !== void 0 ? { paths: input.paths } : {},
|
|
3787
|
-
...input.planId !== void 0 ? { planId: input.planId } : {},
|
|
3788
|
-
...input.useGit !== void 0 ? { useGit: input.useGit } : {}
|
|
3789
|
-
}),
|
|
3790
|
-
resolveRelevantPlan2(planStore, namespace, input.planId),
|
|
3791
|
-
memoryStore.findIncidentFactsForPaths({ namespace, paths: targetPaths }),
|
|
3792
|
-
buildArchitectureMap(repositoryRoot),
|
|
3793
|
-
readPolicyFile(join8(repositoryRoot, ".codebuddy", "policies.yaml"))
|
|
3794
|
-
]);
|
|
3795
|
-
const suggestions = [];
|
|
3796
|
-
for (const item of risks.items) {
|
|
3797
|
-
if (item.score < RISK_SUGGEST_THRESHOLD) continue;
|
|
3798
|
-
suggestions.push({
|
|
3799
|
-
id: `risk:${item.path}`,
|
|
3800
|
-
category: "risk",
|
|
3801
|
-
severity: item.score >= 75 ? "high" : "medium",
|
|
3802
|
-
title: `Review high-risk change to ${item.path}`,
|
|
3803
|
-
detail: item.reason,
|
|
3804
|
-
path: item.path,
|
|
3805
|
-
evidence: [
|
|
3806
|
-
{ kind: "risk_score", detail: `score ${item.score} (${item.category})` },
|
|
3807
|
-
...item.evidence.length > 0 ? [{ kind: "risk_evidence", detail: `${item.evidence.length} evidence item(s)` }] : []
|
|
3808
|
-
],
|
|
3809
|
-
commandHint: `codebuddy risk assess --paths ${item.path} --explain`
|
|
3810
|
-
});
|
|
3811
|
-
}
|
|
3812
|
-
for (const incident of incidents) {
|
|
3813
|
-
const path = incident.paths[0] ?? "(unknown)";
|
|
3814
|
-
suggestions.push({
|
|
3815
|
-
id: `incident:${path}`,
|
|
3816
|
-
category: "incident",
|
|
3817
|
-
severity: "high",
|
|
3818
|
-
title: `Recurring incident hotspot: ${path}`,
|
|
3819
|
-
detail: `${incident.subject} \u2014 proceed carefully; this area has an unresolved incident.`,
|
|
3820
|
-
path,
|
|
3821
|
-
evidence: [{ kind: "incident", detail: `${incident.id} severity=${incident.severity}` }]
|
|
3822
|
-
});
|
|
3823
|
-
}
|
|
3824
|
-
const inbound = inboundDegrees(map);
|
|
3825
|
-
for (const path of targetPaths) {
|
|
3826
|
-
const dependents = inbound.get(path) ?? 0;
|
|
3827
|
-
if (dependents < BLAST_RADIUS_MEDIUM) continue;
|
|
3828
|
-
suggestions.push({
|
|
3829
|
-
id: `architecture:${path}`,
|
|
3830
|
-
category: "architecture",
|
|
3831
|
-
severity: dependents >= BLAST_RADIUS_HIGH ? "high" : "medium",
|
|
3832
|
-
title: `High blast radius: ${path}`,
|
|
3833
|
-
detail: `${dependents} module(s) import ${path}; a change here ripples widely.`,
|
|
3834
|
-
path,
|
|
3835
|
-
evidence: [{ kind: "dependents", detail: `${dependents} inbound import(s)` }],
|
|
3836
|
-
commandHint: `codebuddy map dependents ${path}`
|
|
3837
|
-
});
|
|
3838
|
-
}
|
|
3839
|
-
if (plan) {
|
|
3840
|
-
const planned = new Set(plan.filesToTouch.map((file) => normalize3(file.path)));
|
|
3841
|
-
const strays = targetPaths.filter((path) => !planned.has(normalize3(path)));
|
|
3842
|
-
if (planned.size > 0 && strays.length > 0) {
|
|
3843
|
-
suggestions.push({
|
|
3844
|
-
id: `plan_divergence:${plan.id}`,
|
|
3845
|
-
category: "plan_divergence",
|
|
3846
|
-
severity: "medium",
|
|
3847
|
-
title: `Change set diverges from plan ${plan.id}`,
|
|
3848
|
-
detail: `${strays.length} target file(s) are outside the active plan's scope: ${strays.slice(0, 5).join(", ")}.`,
|
|
3849
|
-
evidence: [
|
|
3850
|
-
{ kind: "plan", detail: `plan ${plan.id} (${plan.status})` },
|
|
3851
|
-
{ kind: "out_of_scope", detail: strays.slice(0, 5).join(", ") }
|
|
3852
|
-
],
|
|
3853
|
-
commandHint: `codebuddy plan show ${plan.id}`
|
|
3854
|
-
});
|
|
3855
|
-
}
|
|
3856
|
-
}
|
|
3857
|
-
for (const path of targetPaths) {
|
|
3858
|
-
if (!isTestableCode(path)) continue;
|
|
3859
|
-
if (await hasSiblingTest(repositoryRoot, path)) continue;
|
|
3860
|
-
suggestions.push({
|
|
3861
|
-
id: `missing_test:${path}`,
|
|
3862
|
-
category: "missing_test",
|
|
3863
|
-
severity: "low",
|
|
3864
|
-
title: `No test found for ${path}`,
|
|
3865
|
-
detail: "Consider adding a test alongside this change.",
|
|
3866
|
-
path,
|
|
3867
|
-
evidence: [{ kind: "no_test", detail: "no *.test.* / *.spec.* sibling found" }]
|
|
3868
|
-
});
|
|
3869
|
-
}
|
|
3870
|
-
for (const rule of policyFile.rules) {
|
|
3871
|
-
const prefix = literalPrefix(rule.pattern);
|
|
3872
|
-
if (!prefix) continue;
|
|
3873
|
-
if (await pathExists(join8(repositoryRoot, prefix))) continue;
|
|
3874
|
-
suggestions.push({
|
|
3875
|
-
id: `stale_policy:${rule.id}`,
|
|
3876
|
-
category: "stale_policy",
|
|
3877
|
-
severity: "low",
|
|
3878
|
-
title: `Stale policy rule "${rule.id}"`,
|
|
3879
|
-
detail: `Pattern "${rule.pattern}" targets "${prefix}", which does not exist.`,
|
|
3880
|
-
evidence: [{ kind: "policy_rule", detail: `${rule.id} \u2192 ${rule.pattern}` }]
|
|
3881
|
-
});
|
|
3882
|
-
}
|
|
3883
|
-
const deduped = dedupe(suggestions).sort(compareSuggestions);
|
|
3884
|
-
const limited = input.limit ? deduped.slice(0, input.limit) : deduped;
|
|
3885
|
-
return {
|
|
3886
|
-
suggestions: limited,
|
|
3887
|
-
stats: buildStats(targetPaths.length, limited)
|
|
3888
|
-
};
|
|
3889
|
-
}
|
|
3890
|
-
async function resolveRelevantPlan2(store, namespace, planId) {
|
|
3891
|
-
if (planId) {
|
|
3892
|
-
try {
|
|
3893
|
-
return await store.readPlan(planId);
|
|
3894
|
-
} catch (error) {
|
|
3895
|
-
if (error.code === "ENOENT") return null;
|
|
3896
|
-
throw error;
|
|
3897
|
-
}
|
|
3898
|
-
}
|
|
3899
|
-
return new PlanLifecycle(store).current(namespace);
|
|
3900
|
-
}
|
|
3901
|
-
function inboundDegrees(map) {
|
|
3902
|
-
const inbound = /* @__PURE__ */ new Map();
|
|
3903
|
-
for (const edge of map.edges) inbound.set(edge.to, (inbound.get(edge.to) ?? 0) + 1);
|
|
3904
|
-
return inbound;
|
|
3905
|
-
}
|
|
3906
|
-
function isTestableCode(path) {
|
|
3907
|
-
if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(path)) return false;
|
|
3908
|
-
if (path.endsWith(".d.ts")) return false;
|
|
3909
|
-
if (path.includes("__tests__/")) return false;
|
|
3910
|
-
return CODE_EXTENSIONS.has(extname(path).toLowerCase());
|
|
3911
|
-
}
|
|
3912
|
-
async function hasSiblingTest(repositoryRoot, path) {
|
|
3913
|
-
const ext = extname(path);
|
|
3914
|
-
const base = path.slice(0, path.length - ext.length);
|
|
3915
|
-
const dir = base.includes("/") ? base.slice(0, base.lastIndexOf("/")) : "";
|
|
3916
|
-
const name = base.includes("/") ? base.slice(base.lastIndexOf("/") + 1) : base;
|
|
3917
|
-
const candidates = [
|
|
3918
|
-
`${base}.test${ext}`,
|
|
3919
|
-
`${base}.spec${ext}`,
|
|
3920
|
-
`${dir ? `${dir}/` : ""}__tests__/${name}${ext}`,
|
|
3921
|
-
`${dir ? `${dir}/` : ""}__tests__/${name}.test${ext}`
|
|
3922
|
-
];
|
|
3923
|
-
for (const candidate of candidates) {
|
|
3924
|
-
if (await pathExists(join8(repositoryRoot, candidate))) return true;
|
|
3925
|
-
}
|
|
3926
|
-
return false;
|
|
3927
|
-
}
|
|
3928
|
-
function literalPrefix(pattern) {
|
|
3929
|
-
const normalized = normalize3(pattern);
|
|
3930
|
-
const wildcard = normalized.search(/[*?[]/);
|
|
3931
|
-
if (wildcard === -1) return normalized.replace(/\/+$/, "");
|
|
3932
|
-
const beforeWildcard = normalized.slice(0, wildcard);
|
|
3933
|
-
const lastSlash = beforeWildcard.lastIndexOf("/");
|
|
3934
|
-
return lastSlash === -1 ? "" : beforeWildcard.slice(0, lastSlash);
|
|
3935
|
-
}
|
|
3936
|
-
function dedupe(suggestions) {
|
|
3937
|
-
const byId = /* @__PURE__ */ new Map();
|
|
3938
|
-
for (const suggestion of suggestions) {
|
|
3939
|
-
const existing = byId.get(suggestion.id);
|
|
3940
|
-
if (!existing || SEVERITY_RANK[suggestion.severity] > SEVERITY_RANK[existing.severity]) {
|
|
3941
|
-
byId.set(suggestion.id, suggestion);
|
|
3942
|
-
}
|
|
3943
|
-
}
|
|
3944
|
-
return [...byId.values()];
|
|
3945
|
-
}
|
|
3946
|
-
function compareSuggestions(left, right) {
|
|
3947
|
-
const severity = SEVERITY_RANK[right.severity] - SEVERITY_RANK[left.severity];
|
|
3948
|
-
if (severity !== 0) return severity;
|
|
3949
|
-
if (left.category !== right.category) return left.category.localeCompare(right.category);
|
|
3950
|
-
return (left.path ?? "").localeCompare(right.path ?? "");
|
|
3951
|
-
}
|
|
3952
|
-
function buildStats(pathsConsidered, suggestions) {
|
|
3953
|
-
const byCategory = {};
|
|
3954
|
-
const bySeverity = {};
|
|
3955
|
-
for (const suggestion of suggestions) {
|
|
3956
|
-
byCategory[suggestion.category] = (byCategory[suggestion.category] ?? 0) + 1;
|
|
3957
|
-
bySeverity[suggestion.severity] = (bySeverity[suggestion.severity] ?? 0) + 1;
|
|
3958
|
-
}
|
|
3959
|
-
return { pathsConsidered, produced: suggestions.length, byCategory, bySeverity };
|
|
3960
|
-
}
|
|
3961
|
-
async function pathExists(path) {
|
|
3962
|
-
try {
|
|
3963
|
-
await access(path, constants.F_OK);
|
|
3964
|
-
return true;
|
|
3965
|
-
} catch {
|
|
3966
|
-
return false;
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
function normalize3(path) {
|
|
3970
|
-
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3971
|
-
}
|
|
3972
|
-
|
|
3973
|
-
// src/mcp/tool-result.ts
|
|
3974
|
-
function serializeToolResult(value, format = process.env.CODEBUDDY_RESPONSE_FORMAT ?? "both") {
|
|
3975
|
-
if (format !== "text" && format !== "both") {
|
|
3976
|
-
throw new Error('CODEBUDDY_RESPONSE_FORMAT must be "text" or "both"');
|
|
3977
|
-
}
|
|
3978
|
-
return {
|
|
3979
|
-
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
3980
|
-
...format === "both" && value !== null && typeof value === "object" && !Array.isArray(value) ? { structuredContent: value } : {}
|
|
3981
|
-
};
|
|
3982
|
-
}
|
|
3983
|
-
|
|
3984
|
-
// src/mcp/tools/index.ts
|
|
3985
|
-
var INPUT_LIMITS = {
|
|
3986
|
-
contentChars: 32e3,
|
|
3987
|
-
batchItems: 100,
|
|
3988
|
-
summaryChars: 32e3,
|
|
3989
|
-
planBodyChars: 64e3,
|
|
3990
|
-
planBriefChars: 4e3,
|
|
3991
|
-
titleChars: 300,
|
|
3992
|
-
arrayItems: 100,
|
|
3993
|
-
pathChars: 1024,
|
|
3994
|
-
taskChars: 2e3,
|
|
3995
|
-
agentIdChars: 200
|
|
3996
|
-
};
|
|
3997
|
-
var deliverySchema = {
|
|
3998
|
-
sessionId: z6.string().min(1).max(200).optional(),
|
|
3999
|
-
ack: z6.string().regex(/^dlv_[a-z0-9]+$/).optional(),
|
|
4000
|
-
full: z6.boolean().optional()
|
|
4001
|
-
};
|
|
4002
|
-
var rememberSchema = {
|
|
4003
|
-
sessionId: z6.string().min(1).max(200).optional(),
|
|
4004
|
-
content: z6.string().min(1).max(INPUT_LIMITS.contentChars),
|
|
4005
|
-
type: z6.enum(["interaction", "fact", "summary"]).optional(),
|
|
4006
|
-
idempotencyKey: z6.string().min(1).max(200).optional(),
|
|
4007
|
-
agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
|
|
4008
|
-
};
|
|
4009
|
-
var recallSchema = {
|
|
4010
|
-
sessionId: z6.string().min(1).max(200),
|
|
4011
|
-
query: z6.string().min(1).max(INPUT_LIMITS.taskChars),
|
|
4012
|
-
budget: z6.number().int().positive().optional(),
|
|
4013
|
-
callerModel: z6.string().min(1).max(200).optional(),
|
|
4014
|
-
conflictMode: z6.enum(["all", "latest", "highest_confidence"]).optional()
|
|
4015
|
-
};
|
|
4016
|
-
var shareSchema = {
|
|
4017
|
-
from: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional(),
|
|
4018
|
-
to_namespace: z6.string().min(1).max(INPUT_LIMITS.agentIdChars),
|
|
4019
|
-
factIds: z6.array(z6.string().min(1).max(100)).min(1).max(INPUT_LIMITS.arrayItems),
|
|
4020
|
-
mode: z6.enum(["reference", "snapshot"]).optional(),
|
|
4021
|
-
/** Snapshot mode: the target project's root; its Markdown store receives the copy. */
|
|
4022
|
-
to_project_root: z6.string().min(1).max(INPUT_LIMITS.pathChars).optional(),
|
|
4023
|
-
agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
|
|
4024
|
-
};
|
|
4025
|
-
var unshareSchema = {
|
|
4026
|
-
to_namespace: z6.string().min(1).max(INPUT_LIMITS.agentIdChars),
|
|
4027
|
-
factIds: z6.array(z6.string().min(1).max(100)).min(1).max(INPUT_LIMITS.arrayItems),
|
|
4028
|
-
agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
|
|
4029
|
-
};
|
|
4030
|
-
var plannedFileSchema2 = z6.object({
|
|
4031
|
-
path: z6.string().min(1).max(INPUT_LIMITS.pathChars),
|
|
4032
|
-
action: z6.enum(["create", "edit", "delete"]),
|
|
4033
|
-
notes: z6.string().max(INPUT_LIMITS.taskChars).optional()
|
|
4034
|
-
});
|
|
4035
|
-
var plannedTestSchema2 = z6.object({
|
|
4036
|
-
path: z6.string().min(1).max(INPUT_LIMITS.pathChars),
|
|
4037
|
-
description: z6.string().max(INPUT_LIMITS.taskChars).optional()
|
|
4038
|
-
});
|
|
4039
|
-
var planPatchSchema = {
|
|
4040
|
-
title: z6.string().min(1).max(INPUT_LIMITS.titleChars).optional(),
|
|
4041
|
-
brief: z6.string().max(INPUT_LIMITS.planBriefChars).optional(),
|
|
4042
|
-
body: z6.string().max(INPUT_LIMITS.planBodyChars).optional(),
|
|
4043
|
-
filesToTouch: z6.array(plannedFileSchema2).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4044
|
-
testsToAdd: z6.array(plannedTestSchema2).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4045
|
-
outOfScope: z6.array(z6.string().max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4046
|
-
risks: z6.array(z6.string().max(INPUT_LIMITS.taskChars)).max(INPUT_LIMITS.arrayItems).optional()
|
|
4047
|
-
};
|
|
4048
|
-
var mcpToolInputSchemas = {
|
|
4049
|
-
remember: z6.object(rememberSchema),
|
|
4050
|
-
remember_batch: z6.object({ items: z6.array(z6.object(rememberSchema)).min(1) }),
|
|
4051
|
-
recall: z6.object(recallSchema),
|
|
4052
|
-
list_facts: z6.object({
|
|
4053
|
-
subject: z6.string().optional(),
|
|
4054
|
-
limit: z6.number().int().positive().max(100).default(50),
|
|
4055
|
-
cursor: z6.string().optional()
|
|
4056
|
-
}),
|
|
4057
|
-
list_namespaces: z6.object({}),
|
|
4058
|
-
forget: z6.object({ id: z6.string().min(1) }),
|
|
4059
|
-
share: z6.object(shareSchema),
|
|
4060
|
-
unshare: z6.object(unshareSchema),
|
|
4061
|
-
plan_current: z6.object({}),
|
|
4062
|
-
plan_create: z6.object({
|
|
4063
|
-
title: z6.string().min(1).max(INPUT_LIMITS.titleChars),
|
|
4064
|
-
brief: z6.string().min(1).max(INPUT_LIMITS.planBriefChars),
|
|
4065
|
-
body: z6.string().max(INPUT_LIMITS.planBodyChars).optional(),
|
|
4066
|
-
filesToTouch: z6.array(plannedFileSchema2).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4067
|
-
testsToAdd: z6.array(plannedTestSchema2).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4068
|
-
outOfScope: z6.array(z6.string().max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4069
|
-
risks: z6.array(z6.string().max(INPUT_LIMITS.taskChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4070
|
-
agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
|
|
4071
|
-
}),
|
|
4072
|
-
plan_amend: z6.object({ id: z6.string().min(1), patch: z6.object(planPatchSchema) }),
|
|
4073
|
-
plan_status: z6.object({
|
|
4074
|
-
id: z6.string().min(1),
|
|
4075
|
-
status: z6.enum(["approved", "executing", "complete", "abandoned"]),
|
|
4076
|
-
commitSha: z6.string().optional(),
|
|
4077
|
-
notes: z6.string().optional(),
|
|
4078
|
-
reason: z6.string().optional()
|
|
4079
|
-
}),
|
|
4080
|
-
risk_assess: z6.object({
|
|
4081
|
-
planId: z6.string().min(1).max(100).optional(),
|
|
4082
|
-
paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4083
|
-
evidence: z6.enum(["overview", "source"]).optional(),
|
|
4084
|
-
useGit: z6.boolean().optional(),
|
|
4085
|
-
limit: z6.number().int().positive().max(100).optional()
|
|
4086
|
-
}),
|
|
4087
|
-
map_query: z6.object({
|
|
4088
|
-
from: z6.string().min(1).max(INPUT_LIMITS.pathChars).optional(),
|
|
4089
|
-
to: z6.string().min(1).max(INPUT_LIMITS.pathChars).optional(),
|
|
4090
|
-
limit: z6.number().int().positive().max(100).optional()
|
|
4091
|
-
}),
|
|
4092
|
-
map_neighbours: z6.object({
|
|
4093
|
-
module: z6.string().min(1).max(INPUT_LIMITS.pathChars),
|
|
4094
|
-
direction: z6.enum(["in", "out", "both"]).default("both")
|
|
4095
|
-
}),
|
|
4096
|
-
context_bootstrap: z6.object({ ...deliverySchema }),
|
|
4097
|
-
context_before_edit: z6.object({
|
|
4098
|
-
task: z6.string().min(1).max(INPUT_LIMITS.taskChars).optional(),
|
|
4099
|
-
paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4100
|
-
planId: z6.string().min(1).max(100).optional(),
|
|
4101
|
-
evidence: z6.enum(["overview", "source"]).optional(),
|
|
4102
|
-
useGit: z6.boolean().optional(),
|
|
4103
|
-
tokenBudget: z6.number().int().positive().max(1e5).optional(),
|
|
4104
|
-
...deliverySchema
|
|
4105
|
-
}),
|
|
4106
|
-
context_pack: z6.object({
|
|
4107
|
-
task: z6.string().min(1).max(INPUT_LIMITS.taskChars),
|
|
4108
|
-
paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4109
|
-
planId: z6.string().min(1).max(100).optional(),
|
|
4110
|
-
evidence: z6.enum(["overview", "source"]).optional(),
|
|
4111
|
-
useGit: z6.boolean().optional(),
|
|
4112
|
-
tokenBudget: z6.number().int().positive().max(1e5).optional(),
|
|
4113
|
-
...deliverySchema
|
|
4114
|
-
}),
|
|
4115
|
-
context_after_turn: z6.object({
|
|
4116
|
-
summary: z6.string().min(1).max(INPUT_LIMITS.summaryChars),
|
|
4117
|
-
changedFiles: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4118
|
-
planId: z6.string().min(1).max(100).optional(),
|
|
4119
|
-
taskType: z6.string().min(1).max(100).optional(),
|
|
4120
|
-
agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional(),
|
|
4121
|
-
sessionId: z6.string().min(1).max(200).optional(),
|
|
4122
|
-
verification: z6.array(z6.string().min(1).max(500)).max(20).optional(),
|
|
4123
|
-
commitSha: z6.string().min(7).max(64).optional()
|
|
4124
|
-
}),
|
|
4125
|
-
code_suggestions: z6.object({
|
|
4126
|
-
paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
|
|
4127
|
-
planId: z6.string().min(1).max(100).optional(),
|
|
4128
|
-
evidence: z6.enum(["overview", "source"]).optional(),
|
|
4129
|
-
useGit: z6.boolean().optional(),
|
|
4130
|
-
limit: z6.number().int().positive().max(100).optional()
|
|
4131
|
-
}),
|
|
4132
|
-
health: z6.object({})
|
|
4133
|
-
};
|
|
4134
|
-
var DATABASE_TOOLS = [
|
|
4135
|
-
"remember",
|
|
4136
|
-
"remember_batch",
|
|
4137
|
-
"recall",
|
|
4138
|
-
"list_facts",
|
|
4139
|
-
"list_namespaces",
|
|
4140
|
-
"forget",
|
|
4141
|
-
"share",
|
|
4142
|
-
"unshare"
|
|
4143
|
-
];
|
|
4144
|
-
var FILE_TOOLS = Object.keys(mcpToolInputSchemas).filter(
|
|
4145
|
-
(name) => !DATABASE_TOOLS.includes(name)
|
|
4146
|
-
);
|
|
4147
|
-
var DEGRADED_FALLBACK = "PostgreSQL is unavailable, so this call did nothing. File-based tools still work; read the repository directly and say recall is unavailable instead of guessing. Run `codebuddy db doctor` to diagnose; the next call after retryInMs reconnects.";
|
|
4148
|
-
var CORE_TOOLS = [
|
|
4149
|
-
"context_pack",
|
|
4150
|
-
"context_bootstrap",
|
|
4151
|
-
"context_before_edit",
|
|
4152
|
-
"context_after_turn",
|
|
4153
|
-
"code_suggestions",
|
|
4154
|
-
"health"
|
|
4155
|
-
];
|
|
4156
|
-
function resolveToolProfile(value = process.env.CODEBUDDY_TOOL_PROFILE) {
|
|
4157
|
-
if (value === void 0 || value === "" || value === "full") return "full";
|
|
4158
|
-
if (value === "core") return "core";
|
|
4159
|
-
throw new Error(`CODEBUDDY_TOOL_PROFILE must be "core" or "full", got "${value}"`);
|
|
4160
|
-
}
|
|
4161
|
-
var PLAN_BODY_LIMIT = 8e3;
|
|
4162
|
-
function serializePlan(plan) {
|
|
4163
|
-
if (!plan) return { plan: null };
|
|
4164
|
-
const body = plan.body.length > PLAN_BODY_LIMIT ? `${plan.body.slice(0, PLAN_BODY_LIMIT)}
|
|
4165
|
-
|
|
4166
|
-
\u2026[truncated]` : plan.body;
|
|
4167
|
-
return { plan: { ...plan, body } };
|
|
4168
|
-
}
|
|
4169
|
-
function registerCodeBuddyTools(server, options, profile = "full") {
|
|
4170
|
-
const exposed = (name) => profile === "full" || CORE_TOOLS.includes(name);
|
|
4171
|
-
const config = "database" in options ? options.config : options.memory.config;
|
|
4172
|
-
const traceRoot = config.projectRoot ?? process.cwd();
|
|
4173
|
-
const register = (name, toolConfig, callback) => {
|
|
4174
|
-
const traced = (async (...args) => {
|
|
4175
|
-
const started = Date.now();
|
|
4176
|
-
const input = args.length > 1 ? args[0] : void 0;
|
|
4177
|
-
const trace = (ok, output) => void recordTrace(traceRoot, {
|
|
4178
|
-
kind: "tool",
|
|
4179
|
-
name,
|
|
4180
|
-
ms: Date.now() - started,
|
|
4181
|
-
ok,
|
|
4182
|
-
tokensIn: estimateTokens(input),
|
|
4183
|
-
tokensOut: estimateTokens(output),
|
|
4184
|
-
...typeof input?.sessionId === "string" ? { session: input.sessionId } : {}
|
|
4185
|
-
});
|
|
4186
|
-
try {
|
|
4187
|
-
const result = await callback(...args);
|
|
4188
|
-
trace(result?.isError !== true, result?.content?.map((part) => part.text ?? "").join(""));
|
|
4189
|
-
return result;
|
|
4190
|
-
} catch (error) {
|
|
4191
|
-
trace(false);
|
|
4192
|
-
throw error;
|
|
4193
|
-
}
|
|
4194
|
-
});
|
|
4195
|
-
const tool = server.registerTool(name, toolConfig, traced);
|
|
4196
|
-
if (!exposed(name)) tool.remove();
|
|
4197
|
-
return tool;
|
|
4198
|
-
};
|
|
4199
|
-
const json = serializeToolResult;
|
|
4200
|
-
const database = "database" in options ? options.database : { get: async () => options, status: () => ({ available: true, state: "ready" }) };
|
|
4201
|
-
const viaDatabase = async (tool, run) => {
|
|
4202
|
-
let db;
|
|
4203
|
-
try {
|
|
4204
|
-
db = await database.get();
|
|
4205
|
-
} catch {
|
|
4206
|
-
return {
|
|
4207
|
-
...json({ degraded: true, tool, database: database.status(), fallback: DEGRADED_FALLBACK }),
|
|
4208
|
-
isError: true
|
|
4209
|
-
};
|
|
4210
|
-
}
|
|
4211
|
-
return json(await run(db));
|
|
4212
|
-
};
|
|
4213
|
-
const namespace = config.namespace;
|
|
4214
|
-
const projectRoot = config.projectRoot ?? process.cwd();
|
|
4215
|
-
const rankContextFacts = async (task, candidates) => {
|
|
4216
|
-
if (database.status().state !== "ready") return {};
|
|
4217
|
-
try {
|
|
4218
|
-
const { memory } = await database.get();
|
|
4219
|
-
return await memory.rankContextFacts(
|
|
4220
|
-
task,
|
|
4221
|
-
candidates.map(({ id }) => id)
|
|
4222
|
-
);
|
|
4223
|
-
} catch {
|
|
4224
|
-
return {};
|
|
4225
|
-
}
|
|
4226
|
-
};
|
|
4227
|
-
const reranker = configuredReranker(projectRoot).catch(() => void 0);
|
|
4228
|
-
const rerankerOption = async () => {
|
|
4229
|
-
const configured = await reranker;
|
|
4230
|
-
return configured ? { reranker: configured } : {};
|
|
4231
|
-
};
|
|
4232
|
-
const planStore = new PlanFileStore(projectRoot);
|
|
4233
|
-
const planLifecycle = new PlanLifecycle(planStore);
|
|
4234
|
-
const toFiles = (items) => items?.map((item) => ({ path: item.path, action: item.action, notes: item.notes ?? "" }));
|
|
4235
|
-
const toTests = (items) => items?.map((item) => ({ path: item.path, description: item.description ?? "" }));
|
|
4236
|
-
register(
|
|
4237
|
-
"remember",
|
|
4238
|
-
{
|
|
4239
|
-
title: "Remember",
|
|
4240
|
-
description: "Store one CodeBuddy memory item.",
|
|
4241
|
-
inputSchema: mcpToolInputSchemas.remember.shape
|
|
4242
|
-
},
|
|
4243
|
-
async (input) => viaDatabase("remember", (db) => db.memory.remember(toRememberInput(input)))
|
|
4244
|
-
);
|
|
4245
|
-
register(
|
|
4246
|
-
"remember_batch",
|
|
4247
|
-
{
|
|
4248
|
-
title: "Remember batch",
|
|
4249
|
-
description: "Store multiple CodeBuddy memory items.",
|
|
4250
|
-
inputSchema: mcpToolInputSchemas.remember_batch.shape
|
|
4251
|
-
},
|
|
4252
|
-
async (input) => viaDatabase("remember_batch", async (db) => ({
|
|
4253
|
-
items: await db.memory.rememberBatch(input.items.map(toRememberInput))
|
|
4254
|
-
}))
|
|
4255
|
-
);
|
|
4256
|
-
register(
|
|
4257
|
-
"recall",
|
|
4258
|
-
{
|
|
4259
|
-
title: "Recall",
|
|
4260
|
-
description: "Recall prompt-ready CodeBuddy context.",
|
|
4261
|
-
inputSchema: mcpToolInputSchemas.recall.shape
|
|
4262
|
-
},
|
|
4263
|
-
async (input) => viaDatabase("recall", (db) => db.memory.recall(toRecallInput(input)))
|
|
4264
|
-
);
|
|
4265
|
-
register(
|
|
4266
|
-
"list_facts",
|
|
4267
|
-
{
|
|
4268
|
-
title: "List facts",
|
|
4269
|
-
description: "List facts in the configured namespace.",
|
|
4270
|
-
inputSchema: mcpToolInputSchemas.list_facts.shape
|
|
4271
|
-
},
|
|
4272
|
-
async (input) => viaDatabase(
|
|
4273
|
-
"list_facts",
|
|
4274
|
-
(db) => listFactsPage(
|
|
4275
|
-
db.memory,
|
|
4276
|
-
db.repository,
|
|
4277
|
-
cleanOptional(input)
|
|
4278
|
-
)
|
|
4279
|
-
)
|
|
4280
|
-
);
|
|
4281
|
-
register(
|
|
4282
|
-
"list_namespaces",
|
|
4283
|
-
{
|
|
4284
|
-
title: "List namespaces",
|
|
4285
|
-
description: "List CodeBuddy namespaces.",
|
|
4286
|
-
inputSchema: mcpToolInputSchemas.list_namespaces.shape
|
|
4287
|
-
},
|
|
4288
|
-
async () => viaDatabase("list_namespaces", async (db) => ({
|
|
4289
|
-
namespaces: await db.repository.listNamespaces()
|
|
4290
|
-
}))
|
|
4291
|
-
);
|
|
4292
|
-
register(
|
|
4293
|
-
"forget",
|
|
4294
|
-
{
|
|
4295
|
-
title: "Forget",
|
|
4296
|
-
description: "Delete an interaction or fact by id.",
|
|
4297
|
-
inputSchema: mcpToolInputSchemas.forget.shape
|
|
4298
|
-
},
|
|
4299
|
-
async (input) => viaDatabase("forget", (db) => db.memory.forget(input.id))
|
|
4300
|
-
);
|
|
4301
|
-
register(
|
|
4302
|
-
"share",
|
|
4303
|
-
{
|
|
4304
|
-
title: "Share",
|
|
4305
|
-
description: "Share facts from the configured namespace to another namespace. Reference (default) is a live, revocable grant; snapshot copies the fact into the target project's Markdown store (requires to_project_root).",
|
|
4306
|
-
inputSchema: mcpToolInputSchemas.share.shape
|
|
4307
|
-
},
|
|
4308
|
-
async (input) => viaDatabase(
|
|
4309
|
-
"share",
|
|
4310
|
-
(db) => db.memory.share({
|
|
4311
|
-
from: input.from ?? namespace,
|
|
4312
|
-
to: input.to_namespace,
|
|
4313
|
-
factIds: input.factIds,
|
|
4314
|
-
...input.mode ? { mode: input.mode } : {},
|
|
4315
|
-
...input.to_project_root ? { targetProjectRoot: input.to_project_root } : {},
|
|
4316
|
-
...input.agentId ? { agentId: input.agentId } : {}
|
|
4317
|
-
})
|
|
4318
|
-
)
|
|
4319
|
-
);
|
|
4320
|
-
register(
|
|
4321
|
-
"unshare",
|
|
4322
|
-
{
|
|
4323
|
-
title: "Unshare",
|
|
4324
|
-
description: "Revoke live reference shares of configured-namespace facts to another namespace.",
|
|
4325
|
-
inputSchema: mcpToolInputSchemas.unshare.shape
|
|
4326
|
-
},
|
|
4327
|
-
async (input) => viaDatabase(
|
|
4328
|
-
"unshare",
|
|
4329
|
-
(db) => db.memory.unshare({
|
|
4330
|
-
to: input.to_namespace,
|
|
4331
|
-
factIds: input.factIds,
|
|
4332
|
-
...input.agentId ? { agentId: input.agentId } : {}
|
|
4333
|
-
})
|
|
4334
|
-
)
|
|
4335
|
-
);
|
|
4336
|
-
register(
|
|
4337
|
-
"plan_current",
|
|
4338
|
-
{
|
|
4339
|
-
title: "Current plan",
|
|
4340
|
-
description: "Return the active (approved or executing) plan for this namespace, or null.",
|
|
4341
|
-
inputSchema: mcpToolInputSchemas.plan_current.shape
|
|
4342
|
-
},
|
|
4343
|
-
async () => {
|
|
4344
|
-
const [current, policy] = await Promise.all([
|
|
4345
|
-
planLifecycle.current(namespace),
|
|
4346
|
-
loadPlanPolicy(projectRoot)
|
|
4347
|
-
]);
|
|
4348
|
-
return json({ ...serializePlan(current), policy });
|
|
4349
|
-
}
|
|
4350
|
-
);
|
|
4351
|
-
register(
|
|
4352
|
-
"plan_create",
|
|
4353
|
-
{
|
|
4354
|
-
title: "Create plan",
|
|
4355
|
-
description: "Create a draft plan in this namespace.",
|
|
4356
|
-
inputSchema: mcpToolInputSchemas.plan_create.shape
|
|
4357
|
-
},
|
|
4358
|
-
async (input) => {
|
|
4359
|
-
const files = toFiles(input.filesToTouch);
|
|
4360
|
-
const tests = toTests(input.testsToAdd);
|
|
4361
|
-
const created = await planLifecycle.create({
|
|
4362
|
-
namespace,
|
|
4363
|
-
title: input.title,
|
|
4364
|
-
brief: input.brief,
|
|
4365
|
-
...input.body !== void 0 ? { body: input.body } : {},
|
|
4366
|
-
...files ? { filesToTouch: files } : {},
|
|
4367
|
-
...tests ? { testsToAdd: tests } : {},
|
|
4368
|
-
...input.outOfScope !== void 0 ? { outOfScope: input.outOfScope } : {},
|
|
4369
|
-
...input.risks !== void 0 ? { risks: input.risks } : {},
|
|
4370
|
-
...input.agentId !== void 0 ? { createdByAgent: input.agentId } : {}
|
|
4371
|
-
});
|
|
4372
|
-
return json(serializePlan(created));
|
|
4373
|
-
}
|
|
4374
|
-
);
|
|
4375
|
-
register(
|
|
4376
|
-
"plan_amend",
|
|
4377
|
-
{
|
|
4378
|
-
title: "Amend plan",
|
|
4379
|
-
description: "Amend a draft or executing plan's content fields.",
|
|
4380
|
-
inputSchema: mcpToolInputSchemas.plan_amend.shape
|
|
4381
|
-
},
|
|
4382
|
-
async (input) => {
|
|
4383
|
-
const patch = input.patch;
|
|
4384
|
-
const files = toFiles(patch.filesToTouch);
|
|
4385
|
-
const tests = toTests(patch.testsToAdd);
|
|
4386
|
-
const amended = await planLifecycle.amend(input.id, {
|
|
4387
|
-
...patch.title !== void 0 ? { title: patch.title } : {},
|
|
4388
|
-
...patch.brief !== void 0 ? { brief: patch.brief } : {},
|
|
4389
|
-
...patch.body !== void 0 ? { body: patch.body } : {},
|
|
4390
|
-
...files ? { filesToTouch: files } : {},
|
|
4391
|
-
...tests ? { testsToAdd: tests } : {},
|
|
4392
|
-
...patch.outOfScope !== void 0 ? { outOfScope: patch.outOfScope } : {},
|
|
4393
|
-
...patch.risks !== void 0 ? { risks: patch.risks } : {}
|
|
4394
|
-
});
|
|
4395
|
-
return json(serializePlan(amended));
|
|
4396
|
-
}
|
|
4397
|
-
);
|
|
4398
|
-
register(
|
|
4399
|
-
"plan_status",
|
|
4400
|
-
{
|
|
4401
|
-
title: "Transition plan",
|
|
4402
|
-
description: "Move a plan through its lifecycle: approved, executing, complete, abandoned.",
|
|
4403
|
-
inputSchema: mcpToolInputSchemas.plan_status.shape
|
|
4404
|
-
},
|
|
4405
|
-
async (input) => {
|
|
4406
|
-
let next;
|
|
4407
|
-
if (input.status === "approved") next = await planLifecycle.approve(input.id);
|
|
4408
|
-
else if (input.status === "executing") next = await planLifecycle.start(input.id);
|
|
4409
|
-
else if (input.status === "complete")
|
|
4410
|
-
next = await planLifecycle.complete(input.id, {
|
|
4411
|
-
...input.commitSha !== void 0 ? { commitSha: input.commitSha } : {},
|
|
4412
|
-
...input.notes !== void 0 ? { notes: input.notes } : {}
|
|
4413
|
-
});
|
|
4414
|
-
else
|
|
4415
|
-
next = await planLifecycle.abandon(
|
|
4416
|
-
input.id,
|
|
4417
|
-
input.reason !== void 0 ? { reason: input.reason } : {}
|
|
4418
|
-
);
|
|
4419
|
-
return json(serializePlan(next));
|
|
4420
|
-
}
|
|
4421
|
-
);
|
|
4422
|
-
register(
|
|
4423
|
-
"risk_assess",
|
|
4424
|
-
{
|
|
4425
|
-
title: "Assess risk",
|
|
4426
|
-
description: "Assess evidence-backed risk for a plan, explicit paths, or git changes.",
|
|
4427
|
-
inputSchema: mcpToolInputSchemas.risk_assess.shape
|
|
4428
|
-
},
|
|
4429
|
-
async (input) => json(
|
|
4430
|
-
await assessRisk({
|
|
4431
|
-
repositoryRoot: projectRoot,
|
|
4432
|
-
namespace,
|
|
4433
|
-
...input.planId !== void 0 ? { planId: input.planId } : {},
|
|
4434
|
-
...input.paths !== void 0 ? { paths: input.paths } : {},
|
|
4435
|
-
...input.useGit !== void 0 ? { useGit: input.useGit } : {},
|
|
4436
|
-
...input.limit !== void 0 ? { limit: input.limit } : {}
|
|
4437
|
-
})
|
|
4438
|
-
)
|
|
4439
|
-
);
|
|
4440
|
-
register(
|
|
4441
|
-
"map_query",
|
|
4442
|
-
{
|
|
4443
|
-
title: "Query architecture map",
|
|
4444
|
-
description: "Query lightweight module import edges in the current repository.",
|
|
4445
|
-
inputSchema: mcpToolInputSchemas.map_query.shape
|
|
4446
|
-
},
|
|
4447
|
-
async (input) => {
|
|
4448
|
-
const graph = await cachedMap();
|
|
4449
|
-
return json(
|
|
4450
|
-
queryMap(graph, {
|
|
4451
|
-
...input.from !== void 0 ? { from: input.from } : {},
|
|
4452
|
-
...input.to !== void 0 ? { to: input.to } : {},
|
|
4453
|
-
...input.limit !== void 0 ? { limit: input.limit } : {}
|
|
4454
|
-
})
|
|
4455
|
-
);
|
|
4456
|
-
}
|
|
4457
|
-
);
|
|
4458
|
-
register(
|
|
4459
|
-
"map_neighbours",
|
|
4460
|
-
{
|
|
4461
|
-
title: "Module neighbours",
|
|
4462
|
-
description: "Return inbound/outbound neighbours for a repository module.",
|
|
4463
|
-
inputSchema: mcpToolInputSchemas.map_neighbours.shape
|
|
4464
|
-
},
|
|
4465
|
-
async (input) => json(neighbours(await cachedMap(), input.module, input.direction))
|
|
4466
|
-
);
|
|
4467
|
-
const tokenBudget = config.tokenBudget;
|
|
4468
|
-
const cachedMap = async () => {
|
|
4469
|
-
const { manifest } = await loadFreshIndex(projectRoot);
|
|
4470
|
-
const cached = await loadArchitectureMap(projectRoot, manifest);
|
|
4471
|
-
if (cached.map.modules.length > 0) return cached.map;
|
|
4472
|
-
return buildArchitectureMap(projectRoot);
|
|
4473
|
-
};
|
|
4474
|
-
register(
|
|
4475
|
-
"context_bootstrap",
|
|
4476
|
-
{
|
|
4477
|
-
title: "Bootstrap project context",
|
|
4478
|
-
description: "Return compact project awareness for the start of a turn: identity, active plan and policy, top policy rules, incident hotspots, and an architecture summary. Prefer this over a manual recall at session start. Optional sessionId enables session-aware delivery: pass the previous response's delivery.deliveryId as ack and unchanged sections are omitted (listed in delivery.unchanged); pass full=true to get everything again.",
|
|
4479
|
-
inputSchema: mcpToolInputSchemas.context_bootstrap.shape
|
|
4480
|
-
},
|
|
4481
|
-
async (input) => json(
|
|
4482
|
-
await applySessionDelivery(
|
|
4483
|
-
projectRoot,
|
|
4484
|
-
await buildBootstrapContext({
|
|
4485
|
-
repositoryRoot: projectRoot,
|
|
4486
|
-
namespace,
|
|
4487
|
-
...tokenBudget !== void 0 ? { tokenBudget } : {}
|
|
4488
|
-
}),
|
|
4489
|
-
deliveryOptions(input)
|
|
4490
|
-
)
|
|
4491
|
-
)
|
|
4492
|
-
);
|
|
4493
|
-
register(
|
|
4494
|
-
"context_before_edit",
|
|
4495
|
-
{
|
|
4496
|
-
title: "Context before edit",
|
|
4497
|
-
description: "Assemble relevant context before changing code: resolves target files from paths, a plan, or git, then returns the relevant plan, matching policy rules, incident memory, an evidence-backed risk assessment, and import neighbours. Optional sessionId enables session-aware delivery: pass the previous response's delivery.deliveryId as ack and unchanged sections are omitted (listed in delivery.unchanged); pass full=true to get everything again.",
|
|
4498
|
-
inputSchema: mcpToolInputSchemas.context_before_edit.shape
|
|
4499
|
-
},
|
|
4500
|
-
async (input) => json(
|
|
4501
|
-
await applySessionDelivery(
|
|
4502
|
-
projectRoot,
|
|
4503
|
-
await buildBeforeEditContext({
|
|
4504
|
-
repositoryRoot: projectRoot,
|
|
4505
|
-
namespace,
|
|
4506
|
-
semanticRanker: rankContextFacts,
|
|
4507
|
-
...await rerankerOption(),
|
|
4508
|
-
...input.evidence ? { evidence: input.evidence } : {},
|
|
4509
|
-
...input.task !== void 0 ? { task: input.task } : {},
|
|
4510
|
-
...input.paths !== void 0 ? { paths: input.paths } : {},
|
|
4511
|
-
...input.planId !== void 0 ? { planId: input.planId } : {},
|
|
4512
|
-
...input.useGit !== void 0 ? { useGit: input.useGit } : {},
|
|
4513
|
-
...input.tokenBudget !== void 0 ? { tokenBudget: input.tokenBudget } : {},
|
|
4514
|
-
...tokenBudget !== void 0 && input.tokenBudget === void 0 ? { tokenBudget } : {}
|
|
4515
|
-
}),
|
|
4516
|
-
deliveryOptions(input)
|
|
4517
|
-
)
|
|
4518
|
-
)
|
|
4519
|
-
);
|
|
4520
|
-
register(
|
|
4521
|
-
"context_pack",
|
|
4522
|
-
{
|
|
4523
|
-
title: "Build task context pack",
|
|
4524
|
-
description: "Build the smallest useful context pack for a coding task. Automatically finds relevant files from the local index when paths are omitted, then includes only bounded symbols, policies, risks, incidents, architecture neighbours, and relevant team facts within the requested token budget. Use this once at the start of a task instead of manually chaining context, risk, map, and memory tools. Request evidence=source with exact paths when short excerpts omit needed behavior; source excerpts remain bounded by the token budget. Optional sessionId enables session-aware delivery: pass the previous response's delivery.deliveryId as ack and unchanged sections are omitted (listed in delivery.unchanged); pass full=true to get everything again.",
|
|
4525
|
-
inputSchema: mcpToolInputSchemas.context_pack.shape
|
|
4526
|
-
},
|
|
4527
|
-
async (input) => json(
|
|
4528
|
-
await applySessionDelivery(
|
|
4529
|
-
projectRoot,
|
|
4530
|
-
await buildBeforeEditContext({
|
|
4531
|
-
repositoryRoot: projectRoot,
|
|
4532
|
-
namespace,
|
|
4533
|
-
task: input.task,
|
|
4534
|
-
semanticRanker: rankContextFacts,
|
|
4535
|
-
...await rerankerOption(),
|
|
4536
|
-
...input.evidence ? { evidence: input.evidence } : {},
|
|
4537
|
-
...input.paths !== void 0 ? { paths: input.paths } : {},
|
|
4538
|
-
...input.planId !== void 0 ? { planId: input.planId } : {},
|
|
4539
|
-
...input.useGit !== void 0 ? { useGit: input.useGit } : {},
|
|
4540
|
-
tokenBudget: input.tokenBudget ?? tokenBudget ?? 4e3
|
|
4541
|
-
}),
|
|
4542
|
-
deliveryOptions(input)
|
|
4543
|
-
)
|
|
4544
|
-
)
|
|
4545
|
-
);
|
|
4546
|
-
register(
|
|
4547
|
-
"context_after_turn",
|
|
4548
|
-
{
|
|
4549
|
-
title: "Capture context after a turn",
|
|
4550
|
-
description: "Extract durable knowledge from a turn summary. Confident facts/decisions/incidents are auto-saved as Markdown; temporary notes, low-confidence, and sensitive items are queued for review under .codebuddy/memory/review/; chatter is ignored. Never saves sensitive content automatically. Turns that changed files or report verification also record a bounded worklog handoff under .codebuddy/worklog/ so later chats can recover the work.",
|
|
4551
|
-
inputSchema: mcpToolInputSchemas.context_after_turn.shape
|
|
4552
|
-
},
|
|
4553
|
-
async (input) => {
|
|
4554
|
-
const result = await captureAfterTurn(
|
|
4555
|
-
{
|
|
4556
|
-
summary: input.summary,
|
|
4557
|
-
namespace,
|
|
4558
|
-
...input.changedFiles !== void 0 ? { changedFiles: input.changedFiles } : {},
|
|
4559
|
-
...input.planId !== void 0 ? { planId: input.planId } : {},
|
|
4560
|
-
...input.taskType !== void 0 ? { taskType: input.taskType } : {},
|
|
4561
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
4562
|
-
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
4563
|
-
...input.verification !== void 0 ? { verification: input.verification } : {},
|
|
4564
|
-
...input.commitSha !== void 0 ? { commitSha: input.commitSha } : {}
|
|
4565
|
-
},
|
|
4566
|
-
{
|
|
4567
|
-
repositoryRoot: projectRoot,
|
|
4568
|
-
...(input.changedFiles?.length ?? 0) > 0 ? await llmMemoryOptions(projectRoot) : {}
|
|
4569
|
-
}
|
|
4570
|
-
);
|
|
4571
|
-
const db = await database.get().catch(() => void 0);
|
|
4572
|
-
await db?.memory.reindexMemory();
|
|
4573
|
-
return json({
|
|
4574
|
-
saved: result.saved.map((d) => ({
|
|
4575
|
-
id: d.ref,
|
|
4576
|
-
class: d.candidate.class,
|
|
4577
|
-
subject: d.candidate.subject
|
|
4578
|
-
})),
|
|
4579
|
-
queuedForReview: result.queuedForReview.map((d) => ({
|
|
4580
|
-
id: d.ref,
|
|
4581
|
-
class: d.candidate.class,
|
|
4582
|
-
reason: d.reason,
|
|
4583
|
-
sensitive: d.sensitive
|
|
4584
|
-
})),
|
|
4585
|
-
ignored: result.ignored.length,
|
|
4586
|
-
reasons: result.reasons,
|
|
4587
|
-
extractor: result.extractor,
|
|
4588
|
-
worklogId: result.worklogId,
|
|
4589
|
-
...db ? {} : { degraded: true, database: database.status() }
|
|
4590
|
-
});
|
|
4591
|
-
}
|
|
4592
|
-
);
|
|
4593
|
-
register(
|
|
4594
|
-
"code_suggestions",
|
|
4595
|
-
{
|
|
4596
|
-
title: "Code suggestions",
|
|
4597
|
-
description: "Return read-only, evidence-backed code-quality suggestions for a change set (paths, a plan, or git). Composes risk, plan divergence, architecture blast radius, incident history, missing tests, and stale policies. Never edits code.",
|
|
4598
|
-
inputSchema: mcpToolInputSchemas.code_suggestions.shape
|
|
4599
|
-
},
|
|
4600
|
-
async (input) => json(
|
|
4601
|
-
await generateSuggestions({
|
|
4602
|
-
repositoryRoot: projectRoot,
|
|
4603
|
-
namespace,
|
|
4604
|
-
...input.paths !== void 0 ? { paths: input.paths } : {},
|
|
4605
|
-
...input.planId !== void 0 ? { planId: input.planId } : {},
|
|
4606
|
-
...input.useGit !== void 0 ? { useGit: input.useGit } : {},
|
|
4607
|
-
...input.limit !== void 0 ? { limit: input.limit } : {}
|
|
4608
|
-
})
|
|
4609
|
-
)
|
|
4610
|
-
);
|
|
4611
|
-
register(
|
|
4612
|
-
"health",
|
|
4613
|
-
{
|
|
4614
|
-
title: "Health",
|
|
4615
|
-
description: "Report which CodeBuddy tools are available right now: file-based tools always work; database tools (recall, remember, facts) need PostgreSQL. Does not wait on a connection attempt.",
|
|
4616
|
-
inputSchema: mcpToolInputSchemas.health.shape
|
|
4617
|
-
},
|
|
4618
|
-
async () => json({
|
|
4619
|
-
database: database.status(),
|
|
4620
|
-
tools: {
|
|
4621
|
-
profile,
|
|
4622
|
-
file: FILE_TOOLS.filter(exposed),
|
|
4623
|
-
database: DATABASE_TOOLS.filter(exposed)
|
|
4624
|
-
}
|
|
4625
|
-
})
|
|
4626
|
-
);
|
|
4627
|
-
}
|
|
4628
|
-
function deliveryOptions(input) {
|
|
4629
|
-
return cleanOptional(input);
|
|
4630
|
-
}
|
|
4631
|
-
function toRememberInput(input) {
|
|
4632
|
-
return cleanOptional(input);
|
|
4633
|
-
}
|
|
4634
|
-
function toRecallInput(input) {
|
|
4635
|
-
return cleanOptional(input);
|
|
4636
|
-
}
|
|
4637
|
-
function cleanOptional(input) {
|
|
4638
|
-
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
|
|
4639
|
-
}
|
|
4640
|
-
|
|
4641
|
-
// src/mcp/server.ts
|
|
4642
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4643
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4644
|
-
import pino from "pino";
|
|
4645
|
-
|
|
4646
|
-
// src/automation/project-sync.ts
|
|
4647
|
-
import { watch as fsWatch2 } from "fs";
|
|
4648
|
-
import { join as join9, relative as relative6, resolve as resolve12, sep as sep6 } from "path";
|
|
4649
|
-
|
|
4650
|
-
// src/indexer/watch.ts
|
|
4651
|
-
import { watch as fsWatch } from "fs";
|
|
4652
|
-
import { resolve as resolve11 } from "path";
|
|
4653
|
-
var DEFAULT_DEBOUNCE_MS = 300;
|
|
4654
|
-
function createCoalescer(fn, delayMs) {
|
|
4655
|
-
let timer = null;
|
|
4656
|
-
let running = null;
|
|
4657
|
-
let dirty = false;
|
|
4658
|
-
let stopped = false;
|
|
4659
|
-
let firstTriggerAt = null;
|
|
4660
|
-
let maxWaitTimer = null;
|
|
4661
|
-
const clearTimers = () => {
|
|
4662
|
-
if (timer) {
|
|
4663
|
-
clearTimeout(timer);
|
|
4664
|
-
timer = null;
|
|
4665
|
-
}
|
|
4666
|
-
if (maxWaitTimer) {
|
|
4667
|
-
clearTimeout(maxWaitTimer);
|
|
4668
|
-
maxWaitTimer = null;
|
|
4669
|
-
}
|
|
4670
|
-
firstTriggerAt = null;
|
|
4671
|
-
};
|
|
4672
|
-
const run = () => {
|
|
4673
|
-
if (stopped || running) return;
|
|
4674
|
-
clearTimers();
|
|
4675
|
-
dirty = false;
|
|
4676
|
-
running = (async () => {
|
|
4677
|
-
try {
|
|
4678
|
-
await fn();
|
|
4679
|
-
} finally {
|
|
4680
|
-
running = null;
|
|
4681
|
-
if (dirty) schedule();
|
|
4682
|
-
}
|
|
4683
|
-
})();
|
|
4684
|
-
};
|
|
4685
|
-
const schedule = () => {
|
|
4686
|
-
if (stopped) return;
|
|
4687
|
-
dirty = true;
|
|
4688
|
-
if (firstTriggerAt === null) {
|
|
4689
|
-
firstTriggerAt = Date.now();
|
|
4690
|
-
maxWaitTimer = setTimeout(run, MAX_WAIT_MS);
|
|
4691
|
-
}
|
|
4692
|
-
if (timer) clearTimeout(timer);
|
|
4693
|
-
timer = setTimeout(run, delayMs);
|
|
4694
|
-
};
|
|
4695
|
-
return {
|
|
4696
|
-
trigger: schedule,
|
|
4697
|
-
stop: async () => {
|
|
4698
|
-
stopped = true;
|
|
4699
|
-
clearTimers();
|
|
4700
|
-
await running?.catch(() => void 0);
|
|
4701
|
-
}
|
|
4702
|
-
};
|
|
4703
|
-
}
|
|
4704
|
-
var MAX_WAIT_MS = 5e3;
|
|
4705
|
-
function isIgnoredEvent(filename, ignoredPaths = []) {
|
|
4706
|
-
if (!filename) return false;
|
|
4707
|
-
const normalized = filename.replace(/\\/g, "/");
|
|
4708
|
-
return ignoredPaths.some((path) => normalized === path || normalized.startsWith(`${path}/`)) || normalized.startsWith(".codebuddy/") || normalized.includes("/.git/") || normalized.startsWith(".git/") || normalized.includes("/node_modules/") || normalized.startsWith("node_modules/") || normalized.endsWith(".tmp");
|
|
4709
|
-
}
|
|
4710
|
-
async function watchRepository(options = {}) {
|
|
4711
|
-
const repositoryRoot = resolve11(options.repositoryRoot ?? process.cwd());
|
|
4712
|
-
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
4713
|
-
const runIndex = async () => {
|
|
4714
|
-
try {
|
|
4715
|
-
const result = await indexRepository({
|
|
4716
|
-
repositoryRoot,
|
|
4717
|
-
...options.now ? { now: options.now } : {}
|
|
4718
|
-
});
|
|
4719
|
-
options.onIndex?.(result);
|
|
4720
|
-
} catch (error) {
|
|
4721
|
-
options.onError?.(error);
|
|
4722
|
-
}
|
|
4723
|
-
};
|
|
4724
|
-
await runIndex();
|
|
4725
|
-
const coalescer = createCoalescer(runIndex, debounceMs);
|
|
4726
|
-
const watcher = fsWatch(repositoryRoot, { recursive: true }, (_event, filename) => {
|
|
4727
|
-
if (isIgnoredEvent(filename, options.ignoredPaths)) return;
|
|
4728
|
-
coalescer.trigger();
|
|
4729
|
-
});
|
|
4730
|
-
return new Promise((resolvePromise) => {
|
|
4731
|
-
const stop = () => {
|
|
4732
|
-
watcher.close();
|
|
4733
|
-
void coalescer.stop().then(() => resolvePromise());
|
|
4734
|
-
};
|
|
4735
|
-
if (options.signal) {
|
|
4736
|
-
if (options.signal.aborted) return stop();
|
|
4737
|
-
options.signal.addEventListener("abort", stop, { once: true });
|
|
4738
|
-
}
|
|
4739
|
-
watcher.on("error", () => stop());
|
|
4740
|
-
});
|
|
4741
|
-
}
|
|
4742
|
-
|
|
4743
|
-
// src/automation/project-sync.ts
|
|
4744
|
-
function startProjectSync(options) {
|
|
4745
|
-
const repositoryRoot = resolve12(options.repositoryRoot);
|
|
4746
|
-
const logger = options.logger ?? console;
|
|
4747
|
-
const controller = new AbortController();
|
|
4748
|
-
let watcher = null;
|
|
4749
|
-
let closing = false;
|
|
4750
|
-
let memoryWatcher = null;
|
|
4751
|
-
let memoryRefresh = null;
|
|
4752
|
-
const backgroundRefresh = (task) => {
|
|
4753
|
-
let current = null;
|
|
4754
|
-
let pending = false;
|
|
4755
|
-
const schedule = () => {
|
|
4756
|
-
if (closing) return;
|
|
4757
|
-
if (current) {
|
|
4758
|
-
pending = true;
|
|
4759
|
-
return;
|
|
4760
|
-
}
|
|
4761
|
-
current = (async () => {
|
|
4762
|
-
do {
|
|
4763
|
-
pending = false;
|
|
4764
|
-
await task();
|
|
4765
|
-
} while (pending && !closing);
|
|
4766
|
-
})().finally(() => {
|
|
4767
|
-
current = null;
|
|
4768
|
-
if (pending && !closing) schedule();
|
|
4769
|
-
});
|
|
4770
|
-
};
|
|
4771
|
-
return { schedule, settled: async () => current?.catch(() => void 0) };
|
|
4772
|
-
};
|
|
4773
|
-
const graphRefresh = backgroundRefresh(
|
|
4774
|
-
() => refreshGraph().catch(
|
|
4775
|
-
(error) => logger.error(`[sync] Graphify refresh failed: ${error.message}`)
|
|
4776
|
-
)
|
|
4777
|
-
);
|
|
4778
|
-
const zvecRefresh = backgroundRefresh(async () => {
|
|
4779
|
-
const result = await refreshZvecIndex(repositoryRoot, { signal: controller.signal }).catch(
|
|
4780
|
-
(error) => ({
|
|
4781
|
-
status: "failed",
|
|
4782
|
-
reason: error.message
|
|
4783
|
-
})
|
|
4784
|
-
);
|
|
4785
|
-
if (result.status !== "disabled") logger.info(`[sync] ${result.reason}`);
|
|
4786
|
-
});
|
|
4787
|
-
const ready = (async () => {
|
|
4788
|
-
await indexRepository({ repositoryRoot });
|
|
4789
|
-
zvecRefresh.schedule();
|
|
4790
|
-
watchMemory();
|
|
4791
|
-
if (options.graphify?.enabled) {
|
|
4792
|
-
await refreshGraph().catch(
|
|
4793
|
-
(error) => logger.error(`[sync] Graphify startup refresh failed: ${error.message}`)
|
|
4794
|
-
);
|
|
4795
|
-
}
|
|
4796
|
-
watcher = watchRepository({
|
|
4797
|
-
repositoryRoot,
|
|
4798
|
-
signal: controller.signal,
|
|
4799
|
-
...options.graphify?.enabled ? {
|
|
4800
|
-
ignoredPaths: [
|
|
4801
|
-
options.graphify.graphPath ? relative6(repositoryRoot, resolve12(repositoryRoot, options.graphify.graphPath)).split(sep6).join("/") : "graphify-out"
|
|
4802
|
-
]
|
|
4803
|
-
} : {},
|
|
4804
|
-
onIndex: (result) => {
|
|
4805
|
-
if (result.added + result.changed + result.removed === 0) return;
|
|
4806
|
-
zvecRefresh.schedule();
|
|
4807
|
-
if (options.graphify?.enabled) graphRefresh.schedule();
|
|
4808
|
-
},
|
|
4809
|
-
onError: (error) => logger.error(`[sync] repository watch failed: ${error.message}`)
|
|
4810
|
-
});
|
|
4811
|
-
void watcher.catch(
|
|
4812
|
-
(error) => logger.error(`[sync] live repository watch unavailable: ${error.message}`)
|
|
4813
|
-
);
|
|
4814
|
-
})().catch((error) => {
|
|
4815
|
-
logger.error(`[sync] startup synchronization failed: ${error.message}`);
|
|
4816
|
-
throw error;
|
|
4817
|
-
});
|
|
4818
|
-
function watchMemory() {
|
|
4819
|
-
const onMemoryChange = options.onMemoryChange;
|
|
4820
|
-
if (closing || !onMemoryChange) return;
|
|
4821
|
-
try {
|
|
4822
|
-
memoryRefresh = createCoalescer(
|
|
4823
|
-
() => onMemoryChange().catch(
|
|
4824
|
-
(error) => logger.error(`[sync] memory reindex failed: ${error.message}`)
|
|
4825
|
-
),
|
|
4826
|
-
300
|
|
4827
|
-
);
|
|
4828
|
-
memoryWatcher = fsWatch2(
|
|
4829
|
-
join9(repositoryRoot, ".codebuddy", "memory"),
|
|
4830
|
-
{ recursive: true },
|
|
4831
|
-
(_event, filename) => {
|
|
4832
|
-
const name = filename?.replace(/\\/g, "/") ?? "";
|
|
4833
|
-
if (/^(facts\/fact|summaries\/sum)_[a-z0-9]+\.md$/.test(name)) memoryRefresh?.trigger();
|
|
4834
|
-
}
|
|
4835
|
-
);
|
|
4836
|
-
memoryWatcher.on("error", () => memoryWatcher?.close());
|
|
4837
|
-
} catch (error) {
|
|
4838
|
-
logger.error(`[sync] live memory watch unavailable: ${error.message}`);
|
|
4839
|
-
}
|
|
4840
|
-
}
|
|
4841
|
-
async function refreshGraph() {
|
|
4842
|
-
const result = await generateGraphifyGraph({
|
|
4843
|
-
repositoryRoot,
|
|
4844
|
-
...options.graphify?.graphPath ? { graphPath: options.graphify.graphPath } : {}
|
|
4845
|
-
});
|
|
4846
|
-
logger.info(`[sync] Graphify updated (${result.nodes} nodes, ${result.edges} edges)`);
|
|
4847
|
-
}
|
|
4848
|
-
return {
|
|
4849
|
-
ready,
|
|
4850
|
-
async close() {
|
|
4851
|
-
closing = true;
|
|
4852
|
-
controller.abort();
|
|
4853
|
-
memoryRefresh?.stop();
|
|
4854
|
-
memoryWatcher?.close();
|
|
4855
|
-
await watcher?.catch(() => void 0);
|
|
4856
|
-
await Promise.all([graphRefresh.settled(), zvecRefresh.settled()]);
|
|
4857
|
-
}
|
|
4858
|
-
};
|
|
4859
|
-
}
|
|
4860
|
-
|
|
4861
|
-
// src/mcp/database.ts
|
|
4862
|
-
function createLazyDatabase(connect, options = {}) {
|
|
4863
|
-
const retryMs = options.retryMs ?? 5e3;
|
|
4864
|
-
let runtime;
|
|
4865
|
-
let pending;
|
|
4866
|
-
let failure;
|
|
4867
|
-
let closed = false;
|
|
4868
|
-
return {
|
|
4869
|
-
get() {
|
|
4870
|
-
if (runtime) return Promise.resolve(runtime);
|
|
4871
|
-
if (closed) return Promise.reject(new Error("CodeBuddy MCP server is shutting down."));
|
|
4872
|
-
if (pending) return pending;
|
|
4873
|
-
if (failure && Date.now() - failure.at < retryMs) {
|
|
4874
|
-
return Promise.reject(new Error(failure.error));
|
|
4875
|
-
}
|
|
4876
|
-
pending = connect().then(
|
|
4877
|
-
async (handle) => {
|
|
4878
|
-
if (closed) {
|
|
4879
|
-
await handle.close();
|
|
4880
|
-
throw new Error("CodeBuddy MCP server is shutting down.");
|
|
4881
|
-
}
|
|
4882
|
-
runtime = handle;
|
|
4883
|
-
failure = void 0;
|
|
4884
|
-
return handle;
|
|
4885
|
-
},
|
|
4886
|
-
(error) => {
|
|
4887
|
-
failure = { error: describeError(error), at: Date.now() };
|
|
4888
|
-
throw error;
|
|
4889
|
-
}
|
|
4890
|
-
).finally(() => {
|
|
4891
|
-
pending = void 0;
|
|
4892
|
-
});
|
|
4893
|
-
return pending;
|
|
4894
|
-
},
|
|
4895
|
-
status() {
|
|
4896
|
-
if (runtime) return { available: true, state: "ready" };
|
|
4897
|
-
if (pending) return { available: false, state: "connecting" };
|
|
4898
|
-
if (!failure) return { available: false, state: "idle" };
|
|
4899
|
-
return {
|
|
4900
|
-
available: false,
|
|
4901
|
-
state: "unavailable",
|
|
4902
|
-
error: failure.error,
|
|
4903
|
-
retryInMs: Math.max(0, failure.at + retryMs - Date.now())
|
|
4904
|
-
};
|
|
4905
|
-
},
|
|
4906
|
-
async close() {
|
|
4907
|
-
closed = true;
|
|
4908
|
-
await pending?.catch(() => void 0);
|
|
4909
|
-
const current = runtime;
|
|
4910
|
-
runtime = void 0;
|
|
4911
|
-
await current?.close();
|
|
4912
|
-
}
|
|
4913
|
-
};
|
|
4914
|
-
}
|
|
4915
|
-
function describeError(error) {
|
|
4916
|
-
const err = error;
|
|
4917
|
-
const text = err?.message || err?.code || String(error);
|
|
4918
|
-
return String(redactSecrets(text)).slice(0, 300);
|
|
4919
|
-
}
|
|
4920
|
-
|
|
4921
|
-
// src/mcp/server.ts
|
|
4922
|
-
async function startMcpServer(options = {}) {
|
|
4923
|
-
const logger = pino({ level: process.env.LOG_LEVEL ?? "info" }, process.stderr);
|
|
4924
|
-
const config = options.config ?? await loadRuntimeConfig(process.cwd(), { requireDatabase: false });
|
|
4925
|
-
(config.postgresUrl ? codeBuddyConfigSchema : codeBuddyConfigSchema.omit({ postgresUrl: true })).parse(config);
|
|
4926
|
-
const toolProfile = resolveToolProfile();
|
|
4927
|
-
const projectRoot = config.projectRoot ?? process.env.CODEBUDDY_PROJECT_ROOT ?? process.cwd();
|
|
4928
|
-
const importMemory = async (memory) => {
|
|
4929
|
-
try {
|
|
4930
|
-
const result = await memory.reindexMemory();
|
|
4931
|
-
for (const failure of result.failures) {
|
|
4932
|
-
logger.error(`[sync] memory file skipped (${failure.id}): ${failure.error}`);
|
|
4933
|
-
}
|
|
4934
|
-
} catch (error) {
|
|
4935
|
-
logger.error(`[sync] Markdown memory import failed: ${error.message}`);
|
|
4936
|
-
}
|
|
4937
|
-
};
|
|
4938
|
-
const database = createLazyDatabase(async () => {
|
|
4939
|
-
try {
|
|
4940
|
-
const runtime = await createRuntime(config, { autoBootstrap: true });
|
|
4941
|
-
await importMemory(runtime.memory);
|
|
4942
|
-
logger.info("PostgreSQL connected; memory tools available");
|
|
4943
|
-
return runtime;
|
|
4944
|
-
} catch (error) {
|
|
4945
|
-
logger.warn(
|
|
4946
|
-
`PostgreSQL unavailable; serving file-based tools only: ${error.message}`
|
|
4947
|
-
);
|
|
4948
|
-
throw error;
|
|
4949
|
-
}
|
|
4950
|
-
});
|
|
4951
|
-
void database.get().catch(() => void 0);
|
|
4952
|
-
const sync = startProjectSync({
|
|
4953
|
-
repositoryRoot: projectRoot,
|
|
4954
|
-
...config.graphify ? { graphify: config.graphify } : {},
|
|
4955
|
-
logger,
|
|
4956
|
-
// Hand edits re-import live once PostgreSQL is connected; until then the
|
|
4957
|
-
// next successful connect imports everything.
|
|
4958
|
-
onMemoryChange: async () => {
|
|
4959
|
-
if (database.status().state === "ready") await importMemory((await database.get()).memory);
|
|
4960
|
-
}
|
|
4961
|
-
});
|
|
4962
|
-
const server = new McpServer({ name: "codebuddy", version: VERSION });
|
|
4963
|
-
registerCodeBuddyTools(server, { config, database }, toolProfile);
|
|
4964
|
-
const shutdown = async () => {
|
|
4965
|
-
logger.info("codebuddy MCP server shutting down");
|
|
4966
|
-
await server.close();
|
|
4967
|
-
await sync.close();
|
|
4968
|
-
await database.close();
|
|
4969
|
-
};
|
|
4970
|
-
let stopping;
|
|
4971
|
-
const stop = () => {
|
|
4972
|
-
stopping ??= shutdown().finally(() => process.exit(0));
|
|
4973
|
-
};
|
|
4974
|
-
process.once("SIGINT", stop);
|
|
4975
|
-
process.once("SIGTERM", stop);
|
|
4976
|
-
process.stdin.once("end", stop);
|
|
4977
|
-
try {
|
|
4978
|
-
await sync.ready;
|
|
4979
|
-
await server.connect(new StdioServerTransport());
|
|
4980
|
-
} catch (error) {
|
|
4981
|
-
await sync.close();
|
|
4982
|
-
await database.close();
|
|
4983
|
-
throw error;
|
|
4984
|
-
}
|
|
4985
|
-
logger.info(`codebuddy MCP server started over stdio (tool profile: ${toolProfile})`);
|
|
4986
|
-
}
|
|
4987
|
-
|
|
4988
3552
|
export {
|
|
4989
|
-
migrateMemoryToFiles,
|
|
4990
3553
|
PLAN_STATUSES,
|
|
4991
3554
|
ACTIVE_PLAN_STATUSES,
|
|
4992
3555
|
PlanLockError,
|
|
@@ -4996,8 +3559,7 @@ export {
|
|
|
4996
3559
|
PlanNotFoundError,
|
|
4997
3560
|
ActivePlanExistsError,
|
|
4998
3561
|
PlanLifecycle,
|
|
4999
|
-
|
|
5000
|
-
VERSION,
|
|
3562
|
+
enforceContextBudget,
|
|
5001
3563
|
RiskAssessor,
|
|
5002
3564
|
createChurnSignal,
|
|
5003
3565
|
createIncidentSignal,
|
|
@@ -5009,20 +3571,27 @@ export {
|
|
|
5009
3571
|
buildBootstrapContext,
|
|
5010
3572
|
buildBeforeEditContext,
|
|
5011
3573
|
ReviewStore,
|
|
3574
|
+
captureAfterTurn,
|
|
5012
3575
|
approveReviewItem,
|
|
5013
3576
|
tracePath,
|
|
3577
|
+
recordTrace,
|
|
5014
3578
|
readTraces,
|
|
5015
3579
|
parseTraceLines,
|
|
3580
|
+
configuredReranker,
|
|
5016
3581
|
openHookSemantics,
|
|
3582
|
+
CONTEXT_HOOK_BUDGET_MS,
|
|
5017
3583
|
runContextHook,
|
|
5018
3584
|
runStageHook,
|
|
3585
|
+
llmMemoryOptions,
|
|
5019
3586
|
runCaptureHook,
|
|
5020
3587
|
runSyncHook,
|
|
5021
3588
|
runResetHook,
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
3589
|
+
extractEditedPaths,
|
|
3590
|
+
deriveSummary,
|
|
3591
|
+
turnVerification,
|
|
3592
|
+
verificationLines,
|
|
3593
|
+
turnUserPrompts,
|
|
3594
|
+
lastAssistantMessage,
|
|
3595
|
+
renderContextBlock
|
|
5027
3596
|
};
|
|
5028
|
-
//# sourceMappingURL=chunk-
|
|
3597
|
+
//# sourceMappingURL=chunk-R7CD6EXS.js.map
|