ctxloom-pro 1.2.5 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/dashboard/dist/server/index.js +189 -85
- package/dist/{chunk-6S3ZF2YS.js → chunk-RY3JAC2Q.js} +974 -366
- package/dist/{chunk-NMXQC5CG.js → chunk-UVR65QBJ.js} +1 -1
- package/dist/{embedder-5LMEYY4M.js → embedder-R4KCXSGO.js} +2 -2
- package/dist/index.js +16 -8
- package/dist/{src-JZWAESJU.js → src-3ZB6BHFW.js} +7 -3
- package/dist/workers/indexerWorker.js +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
collectFiles,
|
|
6
6
|
generateEmbedding
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UVR65QBJ.js";
|
|
8
8
|
import {
|
|
9
9
|
logger
|
|
10
10
|
} from "./chunk-TYDMSHV7.js";
|
|
@@ -165,7 +165,7 @@ var GrammarLoader = class {
|
|
|
165
165
|
const url = entry.downloadUrl?.trim() ? entry.downloadUrl : `${this.cdn}/${entry.npmPackage}@${entry.version}/${entry.wasmFile}`;
|
|
166
166
|
const dest = path.join(this.cacheDir, entry.wasmFile);
|
|
167
167
|
logger.info("Downloading grammar", { language, url, source: entry.downloadUrl?.trim() ? "custom" : "cdn" });
|
|
168
|
-
fs.mkdirSync(
|
|
168
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
169
169
|
await this.download(url, dest);
|
|
170
170
|
if (entry.sha256 && !this.skipVerify) {
|
|
171
171
|
await this.verifyHash(dest, entry.sha256, language);
|
|
@@ -179,6 +179,12 @@ var GrammarLoader = class {
|
|
|
179
179
|
return new Promise((resolve, reject) => {
|
|
180
180
|
const tmp = dest + ".tmp";
|
|
181
181
|
const file = fs.createWriteStream(tmp);
|
|
182
|
+
const onFileError = (err) => {
|
|
183
|
+
file.destroy();
|
|
184
|
+
fs.rmSync(tmp, { force: true });
|
|
185
|
+
reject(err);
|
|
186
|
+
};
|
|
187
|
+
file.on("error", onFileError);
|
|
182
188
|
const request = https.get(url, (response) => {
|
|
183
189
|
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
184
190
|
const location = response.headers.location;
|
|
@@ -208,11 +214,6 @@ var GrammarLoader = class {
|
|
|
208
214
|
fs.rmSync(tmp, { force: true });
|
|
209
215
|
reject(err);
|
|
210
216
|
});
|
|
211
|
-
file.on("error", (err) => {
|
|
212
|
-
file.destroy();
|
|
213
|
-
fs.rmSync(tmp, { force: true });
|
|
214
|
-
reject(err);
|
|
215
|
-
});
|
|
216
217
|
file.on("finish", () => {
|
|
217
218
|
fs.renameSync(tmp, dest);
|
|
218
219
|
resolve();
|
|
@@ -843,15 +844,14 @@ var ASTParser = class {
|
|
|
843
844
|
return;
|
|
844
845
|
}
|
|
845
846
|
case "import_declaration": {
|
|
847
|
+
const specs = [];
|
|
846
848
|
const walkImport = (n) => {
|
|
847
849
|
if (n.type === "import_spec") {
|
|
848
850
|
const pathNode = n.childForFieldName?.("path");
|
|
849
851
|
if (pathNode) {
|
|
850
852
|
const spec = pathNode.text.replace(/^"|"$/g, "");
|
|
851
|
-
|
|
852
|
-
type: "import",
|
|
853
|
+
specs.push({
|
|
853
854
|
name: spec,
|
|
854
|
-
source: spec,
|
|
855
855
|
startLine: n.startPosition.row + 1,
|
|
856
856
|
endLine: n.endPosition.row + 1
|
|
857
857
|
});
|
|
@@ -862,6 +862,25 @@ var ASTParser = class {
|
|
|
862
862
|
}
|
|
863
863
|
};
|
|
864
864
|
walkImport(node);
|
|
865
|
+
if (specs.length > 0) {
|
|
866
|
+
const firstSpec = specs[0];
|
|
867
|
+
nodes.push({
|
|
868
|
+
type: "import",
|
|
869
|
+
name: firstSpec.name,
|
|
870
|
+
source: firstSpec.name,
|
|
871
|
+
startLine: node.startPosition.row + 1,
|
|
872
|
+
endLine: node.endPosition.row + 1
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
for (const spec of specs) {
|
|
876
|
+
nodes.push({
|
|
877
|
+
type: "import",
|
|
878
|
+
name: spec.name,
|
|
879
|
+
source: spec.name,
|
|
880
|
+
startLine: spec.startLine,
|
|
881
|
+
endLine: spec.endLine
|
|
882
|
+
});
|
|
883
|
+
}
|
|
865
884
|
return;
|
|
866
885
|
}
|
|
867
886
|
}
|
|
@@ -1183,6 +1202,24 @@ var ASTParser = class {
|
|
|
1183
1202
|
const lines = source.split("\n");
|
|
1184
1203
|
const walk = (node) => {
|
|
1185
1204
|
switch (node.type) {
|
|
1205
|
+
case "call": {
|
|
1206
|
+
const methodNode = node.childForFieldName?.("method") ?? node.children.find((c) => c?.type === "identifier");
|
|
1207
|
+
const name = methodNode?.text ?? "";
|
|
1208
|
+
if (name === "require" || name === "require_relative" || name === "load" || name === "autoload") {
|
|
1209
|
+
const argsNode = node.childForFieldName?.("arguments") ?? node.children.find((c) => c?.type === "argument_list");
|
|
1210
|
+
const firstStringArg = argsNode?.children.find((c) => c?.type === "string" || c?.type === "simple_symbol");
|
|
1211
|
+
const spec = firstStringArg?.text.replace(/^['":]+|['"]+$/g, "") ?? "";
|
|
1212
|
+
nodes.push({
|
|
1213
|
+
type: "import",
|
|
1214
|
+
name: spec,
|
|
1215
|
+
source: spec,
|
|
1216
|
+
startLine: node.startPosition.row + 1,
|
|
1217
|
+
endLine: node.endPosition.row + 1
|
|
1218
|
+
});
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1186
1223
|
case "method":
|
|
1187
1224
|
case "singleton_method": {
|
|
1188
1225
|
const nameNode = node.childForFieldName?.("name") ?? node.children.find((c) => c?.type === "identifier");
|
|
@@ -1470,9 +1507,18 @@ var ASTParser = class {
|
|
|
1470
1507
|
const walk = (node) => {
|
|
1471
1508
|
switch (node.type) {
|
|
1472
1509
|
case "import_or_export": {
|
|
1473
|
-
const
|
|
1510
|
+
const findUri = (n) => {
|
|
1511
|
+
if (n.type === "uri") return n;
|
|
1512
|
+
for (const c of n.children) {
|
|
1513
|
+
if (!c) continue;
|
|
1514
|
+
const hit = findUri(c);
|
|
1515
|
+
if (hit) return hit;
|
|
1516
|
+
}
|
|
1517
|
+
return void 0;
|
|
1518
|
+
};
|
|
1519
|
+
const uriNode = findUri(node);
|
|
1474
1520
|
const uri = uriNode?.text?.replace(/['"]/g, "") ?? "";
|
|
1475
|
-
if (uri
|
|
1521
|
+
if (uri) {
|
|
1476
1522
|
nodes.push({
|
|
1477
1523
|
type: "import",
|
|
1478
1524
|
name: uri,
|
|
@@ -3411,8 +3457,8 @@ var CoChangeIndex = class _CoChangeIndex {
|
|
|
3411
3457
|
if (event.isBulk || event.isMerge) return;
|
|
3412
3458
|
const paths = event.files.map((f) => f.path);
|
|
3413
3459
|
if (paths.length === 0) return;
|
|
3414
|
-
for (const
|
|
3415
|
-
this.nodeCounts.set(
|
|
3460
|
+
for (const path36 of paths) {
|
|
3461
|
+
this.nodeCounts.set(path36, (this.nodeCounts.get(path36) ?? 0) + 1);
|
|
3416
3462
|
}
|
|
3417
3463
|
for (let i = 0; i < paths.length; i++) {
|
|
3418
3464
|
for (let j = i + 1; j < paths.length; j++) {
|
|
@@ -3559,8 +3605,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3559
3605
|
*/
|
|
3560
3606
|
snapshot() {
|
|
3561
3607
|
const nodes = {};
|
|
3562
|
-
for (const [
|
|
3563
|
-
nodes[
|
|
3608
|
+
for (const [path36, raw] of this.nodes) {
|
|
3609
|
+
nodes[path36] = {
|
|
3564
3610
|
commits: raw.commits,
|
|
3565
3611
|
churnLines: raw.churnLines,
|
|
3566
3612
|
bugCommits: raw.bugCommits,
|
|
@@ -3575,8 +3621,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3575
3621
|
*/
|
|
3576
3622
|
static load(s) {
|
|
3577
3623
|
const idx = new _ChurnIndex();
|
|
3578
|
-
for (const [
|
|
3579
|
-
idx.nodes.set(
|
|
3624
|
+
for (const [path36, raw] of Object.entries(s.nodes)) {
|
|
3625
|
+
idx.nodes.set(path36, {
|
|
3580
3626
|
commits: raw.commits,
|
|
3581
3627
|
churnLines: raw.churnLines,
|
|
3582
3628
|
bugCommits: raw.bugCommits,
|
|
@@ -3589,8 +3635,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3589
3635
|
// -------------------------------------------------------------------------
|
|
3590
3636
|
// Private helpers
|
|
3591
3637
|
// -------------------------------------------------------------------------
|
|
3592
|
-
getOrCreate(
|
|
3593
|
-
const existing = this.nodes.get(
|
|
3638
|
+
getOrCreate(path36) {
|
|
3639
|
+
const existing = this.nodes.get(path36);
|
|
3594
3640
|
if (existing !== void 0) return existing;
|
|
3595
3641
|
const fresh = {
|
|
3596
3642
|
commits: 0,
|
|
@@ -3599,7 +3645,7 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3599
3645
|
authorCounts: {},
|
|
3600
3646
|
lastTouch: 0
|
|
3601
3647
|
};
|
|
3602
|
-
this.nodes.set(
|
|
3648
|
+
this.nodes.set(path36, fresh);
|
|
3603
3649
|
return fresh;
|
|
3604
3650
|
}
|
|
3605
3651
|
};
|
|
@@ -3680,12 +3726,12 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3680
3726
|
*/
|
|
3681
3727
|
snapshot() {
|
|
3682
3728
|
const nodes = {};
|
|
3683
|
-
for (const [
|
|
3729
|
+
for (const [path36, raw] of this.nodes) {
|
|
3684
3730
|
const authorWeights = {};
|
|
3685
3731
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3686
3732
|
authorWeights[email] = { ...entry };
|
|
3687
3733
|
}
|
|
3688
|
-
nodes[
|
|
3734
|
+
nodes[path36] = { authorWeights, lastTouch: raw.lastTouch };
|
|
3689
3735
|
}
|
|
3690
3736
|
return { version: 1, nodes };
|
|
3691
3737
|
}
|
|
@@ -3694,23 +3740,23 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3694
3740
|
*/
|
|
3695
3741
|
static load(s) {
|
|
3696
3742
|
const idx = new _OwnershipIndex();
|
|
3697
|
-
for (const [
|
|
3743
|
+
for (const [path36, raw] of Object.entries(s.nodes)) {
|
|
3698
3744
|
const authorWeights = {};
|
|
3699
3745
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3700
3746
|
authorWeights[email] = { ...entry };
|
|
3701
3747
|
}
|
|
3702
|
-
idx.nodes.set(
|
|
3748
|
+
idx.nodes.set(path36, { authorWeights, lastTouch: raw.lastTouch });
|
|
3703
3749
|
}
|
|
3704
3750
|
return idx;
|
|
3705
3751
|
}
|
|
3706
3752
|
// -------------------------------------------------------------------------
|
|
3707
3753
|
// Private helpers
|
|
3708
3754
|
// -------------------------------------------------------------------------
|
|
3709
|
-
getOrCreate(
|
|
3710
|
-
const existing = this.nodes.get(
|
|
3755
|
+
getOrCreate(path36) {
|
|
3756
|
+
const existing = this.nodes.get(path36);
|
|
3711
3757
|
if (existing !== void 0) return existing;
|
|
3712
3758
|
const fresh = { authorWeights: {}, lastTouch: 0 };
|
|
3713
|
-
this.nodes.set(
|
|
3759
|
+
this.nodes.set(path36, fresh);
|
|
3714
3760
|
return fresh;
|
|
3715
3761
|
}
|
|
3716
3762
|
};
|
|
@@ -4564,86 +4610,349 @@ var ToolRegistry = class {
|
|
|
4564
4610
|
|
|
4565
4611
|
// packages/core/src/tools/search.ts
|
|
4566
4612
|
import { z as z3 } from "zod";
|
|
4613
|
+
|
|
4614
|
+
// packages/core/src/budget/budget.ts
|
|
4615
|
+
var defaultTokenEstimator = (text) => Math.ceil(text.length / 4);
|
|
4616
|
+
function hasBudgetArgs(args) {
|
|
4617
|
+
if (!args || typeof args !== "object") return false;
|
|
4618
|
+
const a = args;
|
|
4619
|
+
return a.max_response_tokens !== void 0 || a.on_budget_exceeded !== void 0 || a.response_format !== void 0;
|
|
4620
|
+
}
|
|
4621
|
+
function readBudgetArgs(args) {
|
|
4622
|
+
if (!args || typeof args !== "object") return {};
|
|
4623
|
+
const a = args;
|
|
4624
|
+
const out = {};
|
|
4625
|
+
if (typeof a.max_response_tokens === "number") out.max_response_tokens = a.max_response_tokens;
|
|
4626
|
+
if (a.on_budget_exceeded === "skeleton" || a.on_budget_exceeded === "truncate" || a.on_budget_exceeded === "error") {
|
|
4627
|
+
out.on_budget_exceeded = a.on_budget_exceeded;
|
|
4628
|
+
}
|
|
4629
|
+
if (a.response_format === "full" || a.response_format === "skeleton" || a.response_format === "auto") {
|
|
4630
|
+
out.response_format = a.response_format;
|
|
4631
|
+
}
|
|
4632
|
+
return out;
|
|
4633
|
+
}
|
|
4634
|
+
function isBudgetDisabled() {
|
|
4635
|
+
return process.env.CTXLOOM_DISABLE_BUDGET === "1";
|
|
4636
|
+
}
|
|
4637
|
+
function emitTelemetry(event) {
|
|
4638
|
+
if (process.env.CTXLOOM_TELEMETRY_LEVEL !== "full") return;
|
|
4639
|
+
logger.info(event.event, event);
|
|
4640
|
+
}
|
|
4641
|
+
async function enforceBudget(opts) {
|
|
4642
|
+
const { full, args, toolName, defaultMaxTokens, skeletonProducer } = opts;
|
|
4643
|
+
const estimate = opts.estimator ?? defaultTokenEstimator;
|
|
4644
|
+
const originalTokens = estimate(full);
|
|
4645
|
+
if (isBudgetDisabled()) {
|
|
4646
|
+
return {
|
|
4647
|
+
text: full,
|
|
4648
|
+
meta: {
|
|
4649
|
+
format: "full",
|
|
4650
|
+
original_tokens_est: originalTokens,
|
|
4651
|
+
returned_tokens_est: originalTokens,
|
|
4652
|
+
fallback_reason: null
|
|
4653
|
+
}
|
|
4654
|
+
};
|
|
4655
|
+
}
|
|
4656
|
+
if (args.response_format === "skeleton" && skeletonProducer) {
|
|
4657
|
+
const skeleton2 = await safeSkeleton(skeletonProducer, toolName);
|
|
4658
|
+
if (skeleton2 !== null) {
|
|
4659
|
+
const skTokens = estimate(skeleton2);
|
|
4660
|
+
return {
|
|
4661
|
+
text: skeleton2,
|
|
4662
|
+
meta: {
|
|
4663
|
+
format: "skeleton",
|
|
4664
|
+
original_tokens_est: originalTokens,
|
|
4665
|
+
returned_tokens_est: skTokens,
|
|
4666
|
+
fallback_reason: null
|
|
4667
|
+
}
|
|
4668
|
+
};
|
|
4669
|
+
}
|
|
4670
|
+
return {
|
|
4671
|
+
text: full,
|
|
4672
|
+
meta: {
|
|
4673
|
+
format: "full",
|
|
4674
|
+
original_tokens_est: originalTokens,
|
|
4675
|
+
returned_tokens_est: originalTokens,
|
|
4676
|
+
fallback_reason: "skeleton_failed"
|
|
4677
|
+
}
|
|
4678
|
+
};
|
|
4679
|
+
}
|
|
4680
|
+
const budget = args.max_response_tokens ?? defaultMaxTokens;
|
|
4681
|
+
if (budget === void 0) {
|
|
4682
|
+
return {
|
|
4683
|
+
text: full,
|
|
4684
|
+
meta: {
|
|
4685
|
+
format: "full",
|
|
4686
|
+
original_tokens_est: originalTokens,
|
|
4687
|
+
returned_tokens_est: originalTokens,
|
|
4688
|
+
fallback_reason: null
|
|
4689
|
+
}
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
4692
|
+
if (originalTokens <= budget) {
|
|
4693
|
+
return {
|
|
4694
|
+
text: full,
|
|
4695
|
+
meta: {
|
|
4696
|
+
format: "full",
|
|
4697
|
+
original_tokens_est: originalTokens,
|
|
4698
|
+
returned_tokens_est: originalTokens,
|
|
4699
|
+
fallback_reason: null
|
|
4700
|
+
}
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4703
|
+
emitTelemetry({
|
|
4704
|
+
event: "mcp.budget.exceeded",
|
|
4705
|
+
tool: toolName,
|
|
4706
|
+
original_tokens: originalTokens,
|
|
4707
|
+
budget,
|
|
4708
|
+
ratio: originalTokens / budget
|
|
4709
|
+
});
|
|
4710
|
+
const mode = args.on_budget_exceeded ?? "skeleton";
|
|
4711
|
+
if (mode === "error") {
|
|
4712
|
+
const err = new Error(
|
|
4713
|
+
`Response of ~${originalTokens} tokens exceeds max_response_tokens=${budget} for tool '${toolName}'. Re-ask with response_format: 'skeleton' or a larger budget.`
|
|
4714
|
+
);
|
|
4715
|
+
err.tokensOriginal = originalTokens;
|
|
4716
|
+
err.budget = budget;
|
|
4717
|
+
err.tool = toolName;
|
|
4718
|
+
throw err;
|
|
4719
|
+
}
|
|
4720
|
+
if (mode === "truncate") {
|
|
4721
|
+
const sliced2 = full.slice(0, budget * 4);
|
|
4722
|
+
const slicedTokens = estimate(sliced2);
|
|
4723
|
+
emitTelemetry({
|
|
4724
|
+
event: "mcp.fallback.used",
|
|
4725
|
+
tool: toolName,
|
|
4726
|
+
fallback_reason: "budget_exceeded",
|
|
4727
|
+
mode: "truncate"
|
|
4728
|
+
});
|
|
4729
|
+
return {
|
|
4730
|
+
text: sliced2,
|
|
4731
|
+
meta: {
|
|
4732
|
+
format: "truncated",
|
|
4733
|
+
original_tokens_est: originalTokens,
|
|
4734
|
+
returned_tokens_est: slicedTokens,
|
|
4735
|
+
fallback_reason: "budget_exceeded"
|
|
4736
|
+
}
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
const skeleton = skeletonProducer ? await safeSkeleton(skeletonProducer, toolName) : null;
|
|
4740
|
+
if (skeleton !== null) {
|
|
4741
|
+
const skTokens = estimate(skeleton);
|
|
4742
|
+
if (skTokens <= budget) {
|
|
4743
|
+
emitTelemetry({
|
|
4744
|
+
event: "mcp.fallback.used",
|
|
4745
|
+
tool: toolName,
|
|
4746
|
+
fallback_reason: "budget_exceeded",
|
|
4747
|
+
mode: "skeleton"
|
|
4748
|
+
});
|
|
4749
|
+
return {
|
|
4750
|
+
text: skeleton,
|
|
4751
|
+
meta: {
|
|
4752
|
+
format: "skeleton",
|
|
4753
|
+
original_tokens_est: originalTokens,
|
|
4754
|
+
returned_tokens_est: skTokens,
|
|
4755
|
+
fallback_reason: "budget_exceeded"
|
|
4756
|
+
}
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
const slicedSk = skeleton.slice(0, budget * 4);
|
|
4760
|
+
emitTelemetry({
|
|
4761
|
+
event: "mcp.fallback.used",
|
|
4762
|
+
tool: toolName,
|
|
4763
|
+
fallback_reason: "budget_exceeded",
|
|
4764
|
+
mode: "skeleton+truncate"
|
|
4765
|
+
});
|
|
4766
|
+
return {
|
|
4767
|
+
text: slicedSk,
|
|
4768
|
+
meta: {
|
|
4769
|
+
format: "truncated",
|
|
4770
|
+
original_tokens_est: originalTokens,
|
|
4771
|
+
returned_tokens_est: estimate(slicedSk),
|
|
4772
|
+
fallback_reason: "budget_exceeded"
|
|
4773
|
+
}
|
|
4774
|
+
};
|
|
4775
|
+
}
|
|
4776
|
+
const sliced = full.slice(0, budget * 4);
|
|
4777
|
+
emitTelemetry({
|
|
4778
|
+
event: "mcp.fallback.used",
|
|
4779
|
+
tool: toolName,
|
|
4780
|
+
fallback_reason: "skeleton_failed",
|
|
4781
|
+
mode: "truncate-fallback"
|
|
4782
|
+
});
|
|
4783
|
+
return {
|
|
4784
|
+
text: sliced,
|
|
4785
|
+
meta: {
|
|
4786
|
+
format: "truncated",
|
|
4787
|
+
original_tokens_est: originalTokens,
|
|
4788
|
+
returned_tokens_est: estimate(sliced),
|
|
4789
|
+
fallback_reason: "skeleton_failed"
|
|
4790
|
+
}
|
|
4791
|
+
};
|
|
4792
|
+
}
|
|
4793
|
+
async function safeSkeleton(producer, toolName) {
|
|
4794
|
+
try {
|
|
4795
|
+
return await producer();
|
|
4796
|
+
} catch (err) {
|
|
4797
|
+
logger.warn("Skeleton fallback failed", {
|
|
4798
|
+
tool: toolName,
|
|
4799
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
4800
|
+
});
|
|
4801
|
+
return null;
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
function wrapResponse(result) {
|
|
4805
|
+
const envelope = {
|
|
4806
|
+
data: result.text,
|
|
4807
|
+
meta: result.meta
|
|
4808
|
+
};
|
|
4809
|
+
return JSON.stringify(envelope);
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4812
|
+
// packages/core/src/tools/search.ts
|
|
4813
|
+
var DEFAULT_MAX_RESPONSE_TOKENS = 4e3;
|
|
4567
4814
|
var Schema = z3.object({
|
|
4568
4815
|
query: z3.string().describe("Search query \u2014 natural language or code fragment"),
|
|
4569
4816
|
limit: z3.number().max(100).optional().default(10).describe("Maximum results to return"),
|
|
4570
|
-
project_root: ProjectRootField
|
|
4817
|
+
project_root: ProjectRootField,
|
|
4818
|
+
// ─── Phase B2 budget surface ──
|
|
4819
|
+
max_response_tokens: z3.number().int().positive().optional().describe("Soft response budget. Default: 4000 (when budget surface is opted into). Over-budget rebuilds the result list without the content snippets (paths + scores only)."),
|
|
4820
|
+
on_budget_exceeded: z3.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton' (default) drops snippets; 'truncate' slices the raw XML; 'error' throws."),
|
|
4821
|
+
response_format: z3.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces the path-and-score-only view; 'full'/'auto' lets the budget decide.")
|
|
4571
4822
|
});
|
|
4572
4823
|
function escapeXML3(text) {
|
|
4573
4824
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4574
4825
|
}
|
|
4826
|
+
function renderResults(query, ranked, includeContent) {
|
|
4827
|
+
const lines = [`<search_results query="${escapeXML3(query)}" count="${ranked.length}">`];
|
|
4828
|
+
for (const result of ranked) {
|
|
4829
|
+
lines.push(` <result file="${escapeXML3(result.filePath)}" score="${result.score.toFixed(4)}">`);
|
|
4830
|
+
if (includeContent && result.content) {
|
|
4831
|
+
lines.push(` ${result.content.slice(0, 200).replace(/&/g, "&").replace(/</g, "<")}`);
|
|
4832
|
+
}
|
|
4833
|
+
lines.push(" </result>");
|
|
4834
|
+
}
|
|
4835
|
+
lines.push("</search_results>");
|
|
4836
|
+
return lines.join("\n");
|
|
4837
|
+
}
|
|
4575
4838
|
function registerSearchTool(registry, ctx) {
|
|
4576
4839
|
registry.register(
|
|
4577
4840
|
"ctx_search",
|
|
4578
4841
|
{
|
|
4579
4842
|
name: "ctx_search",
|
|
4580
|
-
description: "Hybrid semantic + graph search over the codebase. Uses vector embeddings for semantic similarity and the dependency graph for structural expansion. Returns ranked file results.",
|
|
4843
|
+
description: "Hybrid semantic + graph search over the codebase. Uses vector embeddings for semantic similarity and the dependency graph for structural expansion. Returns ranked file results. When callers opt into the budget surface, over-budget responses drop the content snippets and return paths + scores only.",
|
|
4581
4844
|
inputSchema: {
|
|
4582
4845
|
type: "object",
|
|
4583
4846
|
properties: {
|
|
4584
4847
|
query: { type: "string", description: "Search query \u2014 natural language or code fragment" },
|
|
4585
4848
|
limit: { type: "number", description: "Maximum results to return (default: 10)" },
|
|
4586
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
4849
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
4850
|
+
max_response_tokens: {
|
|
4851
|
+
type: "number",
|
|
4852
|
+
description: "Soft response budget in tokens. Default: 4000 (when opted into)."
|
|
4853
|
+
},
|
|
4854
|
+
on_budget_exceeded: {
|
|
4855
|
+
type: "string",
|
|
4856
|
+
enum: ["skeleton", "truncate", "error"],
|
|
4857
|
+
description: "Behavior when over budget. 'skeleton' (default) drops snippets; 'truncate' slices; 'error' throws."
|
|
4858
|
+
},
|
|
4859
|
+
response_format: {
|
|
4860
|
+
type: "string",
|
|
4861
|
+
enum: ["full", "skeleton", "auto"],
|
|
4862
|
+
description: "'skeleton' forces path+score-only view; 'full'/'auto' lets the budget decide."
|
|
4863
|
+
}
|
|
4587
4864
|
},
|
|
4588
4865
|
required: ["query"]
|
|
4589
4866
|
}
|
|
4590
4867
|
},
|
|
4591
4868
|
async (args) => {
|
|
4592
|
-
const
|
|
4593
|
-
const [store, graph] = await Promise.all([ctx.getStore(project_root), ctx.getGraph(project_root)]);
|
|
4594
|
-
const queryEmbedding = await generateEmbedding(query);
|
|
4595
|
-
const vectorResults = await store.search(queryEmbedding, limit);
|
|
4869
|
+
const parsed = Schema.parse(args);
|
|
4870
|
+
const [store, graph] = await Promise.all([ctx.getStore(parsed.project_root), ctx.getGraph(parsed.project_root)]);
|
|
4871
|
+
const queryEmbedding = await generateEmbedding(parsed.query);
|
|
4872
|
+
const vectorResults = await store.search(queryEmbedding, parsed.limit);
|
|
4596
4873
|
const expandedResults = /* @__PURE__ */ new Map();
|
|
4597
|
-
for (const
|
|
4598
|
-
const existingScore = expandedResults.get(
|
|
4599
|
-
if (
|
|
4600
|
-
expandedResults.set(
|
|
4874
|
+
for (const result2 of vectorResults) {
|
|
4875
|
+
const existingScore = expandedResults.get(result2.filePath)?.score ?? Infinity;
|
|
4876
|
+
if (result2.score < existingScore) {
|
|
4877
|
+
expandedResults.set(result2.filePath, { score: result2.score, content: result2.content });
|
|
4601
4878
|
}
|
|
4602
|
-
for (const related of [...graph.getImports(
|
|
4879
|
+
for (const related of [...graph.getImports(result2.filePath), ...graph.getImporters(result2.filePath)]) {
|
|
4603
4880
|
if (!expandedResults.has(related)) {
|
|
4604
|
-
expandedResults.set(related, { score:
|
|
4881
|
+
expandedResults.set(related, { score: result2.score + 0.1, content: "" });
|
|
4605
4882
|
}
|
|
4606
4883
|
}
|
|
4607
4884
|
}
|
|
4608
|
-
const ranked = Array.from(expandedResults.entries()).map(([filePath, data]) => ({ filePath, score: data.score, content: data.content })).sort((a, b) => a.score - b.score).slice(0, limit);
|
|
4609
|
-
const
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4885
|
+
const ranked = Array.from(expandedResults.entries()).map(([filePath, data]) => ({ filePath, score: data.score, content: data.content })).sort((a, b) => a.score - b.score).slice(0, parsed.limit);
|
|
4886
|
+
const full = renderResults(parsed.query, ranked, true);
|
|
4887
|
+
if (!hasBudgetArgs(args)) return full;
|
|
4888
|
+
const skeletonProducer = async () => renderResults(parsed.query, ranked, false);
|
|
4889
|
+
const result = await enforceBudget({
|
|
4890
|
+
full,
|
|
4891
|
+
args: readBudgetArgs(args),
|
|
4892
|
+
toolName: "ctx_search",
|
|
4893
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS,
|
|
4894
|
+
skeletonProducer
|
|
4895
|
+
});
|
|
4896
|
+
return wrapResponse(result);
|
|
4619
4897
|
}
|
|
4620
4898
|
);
|
|
4621
4899
|
}
|
|
4622
4900
|
|
|
4623
4901
|
// packages/core/src/tools/file.ts
|
|
4624
4902
|
import { z as z4 } from "zod";
|
|
4903
|
+
var DEFAULT_MAX_RESPONSE_TOKENS2 = 8e3;
|
|
4625
4904
|
var Schema2 = z4.object({
|
|
4626
4905
|
path: z4.string().describe("Relative path to the file"),
|
|
4627
|
-
project_root: ProjectRootField
|
|
4906
|
+
project_root: ProjectRootField,
|
|
4907
|
+
// ─── Phase B2 budget surface (all optional; back-compat preserved) ──
|
|
4908
|
+
max_response_tokens: z4.number().int().positive().optional().describe("Soft response budget in tokens. Falls back to a skeleton when exceeded. Default: 8000 (when budget surface is opted into)."),
|
|
4909
|
+
on_budget_exceeded: z4.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when the response would exceed max_response_tokens. 'skeleton' (default) substitutes a Skeletonizer signature view; 'truncate' slices the raw text; 'error' throws a structured error with token counts so the caller can re-ask."),
|
|
4910
|
+
response_format: z4.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces a Skeletonizer view regardless of budget; 'full'/'auto' lets the budget decide.")
|
|
4628
4911
|
});
|
|
4629
4912
|
function registerFileTool(registry, ctx) {
|
|
4630
4913
|
registry.register(
|
|
4631
4914
|
"ctx_get_file",
|
|
4632
4915
|
{
|
|
4633
4916
|
name: "ctx_get_file",
|
|
4634
|
-
description: "Read a file from the project. Path is validated to prevent traversal outside the project root. Returns the full file content.",
|
|
4917
|
+
description: "Read a file from the project. Path is validated to prevent traversal outside the project root. Returns the full file content; when callers opt into the budget surface (max_response_tokens / on_budget_exceeded / response_format), the response is wrapped in a {data, meta} envelope and oversize content is auto-substituted with a Skeletonizer signature view.",
|
|
4635
4918
|
inputSchema: {
|
|
4636
4919
|
type: "object",
|
|
4637
4920
|
properties: {
|
|
4638
4921
|
path: { type: "string", description: "Relative path to the file" },
|
|
4639
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
4922
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
4923
|
+
max_response_tokens: {
|
|
4924
|
+
type: "number",
|
|
4925
|
+
description: "Soft response budget in tokens. Falls back to a skeleton when exceeded. Default: 8000 (when budget surface is opted into)."
|
|
4926
|
+
},
|
|
4927
|
+
on_budget_exceeded: {
|
|
4928
|
+
type: "string",
|
|
4929
|
+
enum: ["skeleton", "truncate", "error"],
|
|
4930
|
+
description: "Behavior when over budget. 'skeleton' (default) substitutes a signature view; 'truncate' slices the raw text; 'error' throws."
|
|
4931
|
+
},
|
|
4932
|
+
response_format: {
|
|
4933
|
+
type: "string",
|
|
4934
|
+
enum: ["full", "skeleton", "auto"],
|
|
4935
|
+
description: "'skeleton' forces a Skeletonizer view regardless of budget; 'full'/'auto' lets the budget decide."
|
|
4936
|
+
}
|
|
4640
4937
|
},
|
|
4641
4938
|
required: ["path"]
|
|
4642
4939
|
}
|
|
4643
4940
|
},
|
|
4644
4941
|
async (args) => {
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4942
|
+
const parsed = Schema2.parse(args);
|
|
4943
|
+
const validator = ctx.getPathValidator(parsed.project_root);
|
|
4944
|
+
const full = validator.readFile(parsed.path);
|
|
4945
|
+
if (!hasBudgetArgs(args)) return full;
|
|
4946
|
+
const absPath = validator.validate(parsed.path);
|
|
4947
|
+
const skeletonizer = await ctx.getSkeletonizer(parsed.project_root);
|
|
4948
|
+
const result = await enforceBudget({
|
|
4949
|
+
full,
|
|
4950
|
+
args: readBudgetArgs(args),
|
|
4951
|
+
toolName: "ctx_get_file",
|
|
4952
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS2,
|
|
4953
|
+
skeletonProducer: () => skeletonizer.skeletonize(absPath)
|
|
4954
|
+
});
|
|
4955
|
+
return wrapResponse(result);
|
|
4647
4956
|
}
|
|
4648
4957
|
);
|
|
4649
4958
|
}
|
|
@@ -4651,34 +4960,71 @@ function registerFileTool(registry, ctx) {
|
|
|
4651
4960
|
// packages/core/src/tools/context-packet.ts
|
|
4652
4961
|
import { z as z5 } from "zod";
|
|
4653
4962
|
import path14 from "path";
|
|
4963
|
+
var DEFAULT_MAX_RESPONSE_TOKENS3 = 6e3;
|
|
4654
4964
|
var Schema3 = z5.object({
|
|
4655
4965
|
target_file: z5.string().describe("Relative path to the primary file"),
|
|
4656
4966
|
mode: z5.enum(["edit", "read"]).optional().default("edit").describe("Context mode"),
|
|
4657
|
-
project_root: ProjectRootField
|
|
4967
|
+
project_root: ProjectRootField,
|
|
4968
|
+
// ─── Phase B2 budget surface ──
|
|
4969
|
+
max_response_tokens: z5.number().int().positive().optional().describe("Soft response budget. Default: 6000 (when budget surface is opted into). Over-budget rebuilds the packet with the primary file replaced by its skeleton."),
|
|
4970
|
+
on_budget_exceeded: z5.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton' (default) re-renders the packet with the primary file skeletonized; 'truncate' slices the raw envelope; 'error' throws."),
|
|
4971
|
+
response_format: z5.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces the skeletonized-primary packet; 'full'/'auto' lets the budget decide.")
|
|
4658
4972
|
});
|
|
4973
|
+
function renderPacket(parts) {
|
|
4974
|
+
return [
|
|
4975
|
+
`<context_packet target="${parts.target_file}" mode="${parts.mode}">`,
|
|
4976
|
+
` <primary_context file="${parts.target_file}">`,
|
|
4977
|
+
` ${parts.primaryContent.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")}`,
|
|
4978
|
+
" </primary_context>",
|
|
4979
|
+
` <dependency_skeletons count="${parts.imports.length}">`,
|
|
4980
|
+
...parts.skeletons.map((s) => ` ${s}`),
|
|
4981
|
+
" </dependency_skeletons>",
|
|
4982
|
+
` <imported_by count="${parts.importers.length}">`,
|
|
4983
|
+
...parts.importers.map((imp) => ` <importer file="${imp}" />`),
|
|
4984
|
+
" </imported_by>",
|
|
4985
|
+
"</context_packet>"
|
|
4986
|
+
].join("\n");
|
|
4987
|
+
}
|
|
4659
4988
|
function registerContextPacketTool(registry, ctx) {
|
|
4660
4989
|
registry.register(
|
|
4661
4990
|
"ctx_get_context_packet",
|
|
4662
4991
|
{
|
|
4663
4992
|
name: "ctx_get_context_packet",
|
|
4664
|
-
description: "Returns a smart multi-file context packet: the full target file, skeletons of its imports, and the list of files that import it. Reduces token usage by ~80% vs. sending full dependencies.",
|
|
4993
|
+
description: "Returns a smart multi-file context packet: the full target file, skeletons of its imports, and the list of files that import it. Reduces token usage by ~80% vs. sending full dependencies. When callers opt into the budget surface, over-budget responses re-render the packet with the primary file ALSO replaced by its Skeletonizer view.",
|
|
4665
4994
|
inputSchema: {
|
|
4666
4995
|
type: "object",
|
|
4667
4996
|
properties: {
|
|
4668
4997
|
target_file: { type: "string", description: "Relative path to the primary file" },
|
|
4669
4998
|
mode: { type: "string", enum: ["edit", "read"], description: "Context mode (default: edit)" },
|
|
4670
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
4999
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
5000
|
+
max_response_tokens: {
|
|
5001
|
+
type: "number",
|
|
5002
|
+
description: "Soft response budget in tokens. Default: 6000 (when opted into)."
|
|
5003
|
+
},
|
|
5004
|
+
on_budget_exceeded: {
|
|
5005
|
+
type: "string",
|
|
5006
|
+
enum: ["skeleton", "truncate", "error"],
|
|
5007
|
+
description: "Behavior when over budget. 'skeleton' (default) skeletonizes the primary; 'truncate' slices; 'error' throws."
|
|
5008
|
+
},
|
|
5009
|
+
response_format: {
|
|
5010
|
+
type: "string",
|
|
5011
|
+
enum: ["full", "skeleton", "auto"],
|
|
5012
|
+
description: "'skeleton' forces the skeletonized-primary packet; 'full'/'auto' lets the budget decide."
|
|
5013
|
+
}
|
|
4671
5014
|
},
|
|
4672
5015
|
required: ["target_file"]
|
|
4673
5016
|
}
|
|
4674
5017
|
},
|
|
4675
5018
|
async (args) => {
|
|
4676
|
-
const
|
|
4677
|
-
const [skeletonizer, graph] = await Promise.all([
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
const
|
|
5019
|
+
const parsed = Schema3.parse(args);
|
|
5020
|
+
const [skeletonizer, graph] = await Promise.all([
|
|
5021
|
+
ctx.getSkeletonizer(parsed.project_root),
|
|
5022
|
+
ctx.getGraph(parsed.project_root)
|
|
5023
|
+
]);
|
|
5024
|
+
const pathValidator = ctx.getPathValidator(parsed.project_root);
|
|
5025
|
+
const primaryContent = pathValidator.readFile(parsed.target_file);
|
|
5026
|
+
const imports = graph.getImports(parsed.target_file);
|
|
5027
|
+
const importers = graph.getImporters(parsed.target_file);
|
|
4682
5028
|
const skeletons = await Promise.all(
|
|
4683
5029
|
imports.map(async (dep) => {
|
|
4684
5030
|
try {
|
|
@@ -4692,19 +5038,29 @@ ${sk}`;
|
|
|
4692
5038
|
}
|
|
4693
5039
|
})
|
|
4694
5040
|
);
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
5041
|
+
const parts = {
|
|
5042
|
+
target_file: parsed.target_file,
|
|
5043
|
+
mode: parsed.mode,
|
|
5044
|
+
primaryContent,
|
|
5045
|
+
skeletons,
|
|
5046
|
+
imports,
|
|
5047
|
+
importers
|
|
5048
|
+
};
|
|
5049
|
+
const full = renderPacket(parts);
|
|
5050
|
+
if (!hasBudgetArgs(args)) return full;
|
|
5051
|
+
const absPrimary = pathValidator.validate(parsed.target_file);
|
|
5052
|
+
const skeletonProducer = async () => {
|
|
5053
|
+
const primarySkeleton = await skeletonizer.skeletonize(absPrimary);
|
|
5054
|
+
return renderPacket({ ...parts, primaryContent: primarySkeleton });
|
|
5055
|
+
};
|
|
5056
|
+
const result = await enforceBudget({
|
|
5057
|
+
full,
|
|
5058
|
+
args: readBudgetArgs(args),
|
|
5059
|
+
toolName: "ctx_get_context_packet",
|
|
5060
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS3,
|
|
5061
|
+
skeletonProducer
|
|
5062
|
+
});
|
|
5063
|
+
return wrapResponse(result);
|
|
4708
5064
|
}
|
|
4709
5065
|
);
|
|
4710
5066
|
}
|
|
@@ -4799,9 +5155,14 @@ function registerCallGraphTool(registry, ctx) {
|
|
|
4799
5155
|
|
|
4800
5156
|
// packages/core/src/tools/definition.ts
|
|
4801
5157
|
import { z as z7 } from "zod";
|
|
5158
|
+
var DEFAULT_MAX_RESPONSE_TOKENS4 = 2e3;
|
|
4802
5159
|
var Schema5 = z7.object({
|
|
4803
5160
|
symbol: z7.string().describe("Symbol name to look up"),
|
|
4804
|
-
project_root: ProjectRootField
|
|
5161
|
+
project_root: ProjectRootField,
|
|
5162
|
+
// ─── Phase B2 budget surface (all optional; back-compat preserved) ──
|
|
5163
|
+
max_response_tokens: z7.number().int().positive().optional().describe("Soft response budget in tokens. Default: 2000 (when budget surface is opted into). No skeleton fallback for this tool \u2014 the response is structural metadata; over-budget falls back to truncation."),
|
|
5164
|
+
on_budget_exceeded: z7.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton'/'truncate' both slice the XML (no file context to skeletonize from); 'error' throws."),
|
|
5165
|
+
response_format: z7.enum(["full", "skeleton", "auto"]).optional().describe("'full'/'auto' default. 'skeleton' is accepted for consistency but produces the same output as 'full' here \u2014 the response is already a compact symbol list.")
|
|
4805
5166
|
});
|
|
4806
5167
|
function escapeXML5(text) {
|
|
4807
5168
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -4811,33 +5172,57 @@ function registerDefinitionTool(registry, ctx) {
|
|
|
4811
5172
|
"ctx_get_definition",
|
|
4812
5173
|
{
|
|
4813
5174
|
name: "ctx_get_definition",
|
|
4814
|
-
description: "Look up the definition of a symbol by name. Returns file path, type, and signature for all definitions matching the symbol name.",
|
|
5175
|
+
description: "Look up the definition of a symbol by name. Returns file path, type, and signature for all definitions matching the symbol name. When callers opt into the budget surface (max_response_tokens / on_budget_exceeded / response_format), the response is wrapped in a {data, meta} envelope and over-budget responses are truncated (no skeleton fallback \u2014 the response is already structural).",
|
|
4815
5176
|
inputSchema: {
|
|
4816
5177
|
type: "object",
|
|
4817
5178
|
properties: {
|
|
4818
5179
|
symbol: { type: "string", description: "Symbol name to look up" },
|
|
4819
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
5180
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
5181
|
+
max_response_tokens: {
|
|
5182
|
+
type: "number",
|
|
5183
|
+
description: "Soft response budget in tokens. Default: 2000 (when budget surface is opted into)."
|
|
5184
|
+
},
|
|
5185
|
+
on_budget_exceeded: {
|
|
5186
|
+
type: "string",
|
|
5187
|
+
enum: ["skeleton", "truncate", "error"],
|
|
5188
|
+
description: "Behavior when over budget. 'skeleton'/'truncate' both slice the XML; 'error' throws."
|
|
5189
|
+
},
|
|
5190
|
+
response_format: {
|
|
5191
|
+
type: "string",
|
|
5192
|
+
enum: ["full", "skeleton", "auto"],
|
|
5193
|
+
description: "'full'/'auto' default; 'skeleton' produces the same output (response is already compact)."
|
|
5194
|
+
}
|
|
4820
5195
|
},
|
|
4821
5196
|
required: ["symbol"]
|
|
4822
5197
|
}
|
|
4823
5198
|
},
|
|
4824
5199
|
async (args) => {
|
|
4825
|
-
const
|
|
4826
|
-
const graph = await ctx.getGraph(project_root);
|
|
4827
|
-
const definitions = graph.lookupSymbol(symbol);
|
|
5200
|
+
const parsed = Schema5.parse(args);
|
|
5201
|
+
const graph = await ctx.getGraph(parsed.project_root);
|
|
5202
|
+
const definitions = graph.lookupSymbol(parsed.symbol);
|
|
5203
|
+
let full;
|
|
4828
5204
|
if (definitions.length === 0) {
|
|
4829
|
-
|
|
5205
|
+
full = `<definitions symbol="${escapeXML5(parsed.symbol)}" count="0">
|
|
4830
5206
|
<!-- Symbol not found -->
|
|
4831
5207
|
</definitions>`;
|
|
4832
|
-
}
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
5208
|
+
} else {
|
|
5209
|
+
const lines = [`<definitions symbol="${escapeXML5(parsed.symbol)}" count="${definitions.length}">`];
|
|
5210
|
+
for (const def of definitions) {
|
|
5211
|
+
lines.push(` <definition file="${def.filePath}" type="${def.type}">`);
|
|
5212
|
+
lines.push(` ${def.signature.replace(/&/g, "&").replace(/</g, "<")}`);
|
|
5213
|
+
lines.push(" </definition>");
|
|
5214
|
+
}
|
|
5215
|
+
lines.push("</definitions>");
|
|
5216
|
+
full = lines.join("\n");
|
|
5217
|
+
}
|
|
5218
|
+
if (!hasBudgetArgs(args)) return full;
|
|
5219
|
+
const result = await enforceBudget({
|
|
5220
|
+
full,
|
|
5221
|
+
args: readBudgetArgs(args),
|
|
5222
|
+
toolName: "ctx_get_definition",
|
|
5223
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS4
|
|
5224
|
+
});
|
|
5225
|
+
return wrapResponse(result);
|
|
4841
5226
|
}
|
|
4842
5227
|
);
|
|
4843
5228
|
}
|
|
@@ -5802,6 +6187,7 @@ function registerSurprisingConnectionsTool(registry, ctx) {
|
|
|
5802
6187
|
// packages/core/src/tools/wiki-generate.ts
|
|
5803
6188
|
import { z as z17 } from "zod";
|
|
5804
6189
|
import fs14 from "fs";
|
|
6190
|
+
var DEFAULT_MAX_RESPONSE_TOKENS5 = 12e3;
|
|
5805
6191
|
var Schema15 = z17.object({
|
|
5806
6192
|
force: z17.boolean().optional().default(false).describe(
|
|
5807
6193
|
"Regenerate all pages even if content unchanged (default: false)"
|
|
@@ -5809,7 +6195,11 @@ var Schema15 = z17.object({
|
|
|
5809
6195
|
detail_level: z17.enum(["standard", "minimal"]).default("standard").describe(
|
|
5810
6196
|
'"standard" (default) lists each written page with size. "minimal" returns counts only.'
|
|
5811
6197
|
),
|
|
5812
|
-
project_root: ProjectRootField
|
|
6198
|
+
project_root: ProjectRootField,
|
|
6199
|
+
// ─── Phase B2 budget surface ──
|
|
6200
|
+
max_response_tokens: z17.number().int().positive().optional().describe("Soft response budget. Default: 12000 (when opted in). Over-budget re-renders at detail_level=minimal (counts only) \u2014 the wiki files themselves are unaffected."),
|
|
6201
|
+
on_budget_exceeded: z17.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton' (default) downgrades to minimal output; 'truncate' slices; 'error' throws."),
|
|
6202
|
+
response_format: z17.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces minimal output regardless of budget; 'full'/'auto' lets the budget decide.")
|
|
5813
6203
|
});
|
|
5814
6204
|
function escapeXML14(text) {
|
|
5815
6205
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -5839,7 +6229,10 @@ function registerWikiGenerateTool(registry, ctx) {
|
|
|
5839
6229
|
enum: ["standard", "minimal"],
|
|
5840
6230
|
description: '"standard" lists written pages with size. "minimal" returns counts only.'
|
|
5841
6231
|
},
|
|
5842
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
6232
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
6233
|
+
max_response_tokens: { type: "number", description: "Soft response budget. Default: 12000 (when opted in)." },
|
|
6234
|
+
on_budget_exceeded: { type: "string", enum: ["skeleton", "truncate", "error"], description: "Behavior over budget. 'skeleton' (default) downgrades to minimal; 'truncate' slices; 'error' throws." },
|
|
6235
|
+
response_format: { type: "string", enum: ["full", "skeleton", "auto"], description: "'skeleton' forces minimal output; 'full'/'auto' lets the budget decide." }
|
|
5843
6236
|
}
|
|
5844
6237
|
}
|
|
5845
6238
|
},
|
|
@@ -5848,20 +6241,30 @@ function registerWikiGenerateTool(registry, ctx) {
|
|
|
5848
6241
|
const [graph, skeletonizer] = await Promise.all([ctx.getGraph(project_root), ctx.getSkeletonizer(project_root)]);
|
|
5849
6242
|
const generator = new WikiGenerator(graph, ctx.projectRoot, skeletonizer);
|
|
5850
6243
|
const result = await generator.generate(force);
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
6244
|
+
const renderMinimal = () => `<wiki_generate detail_level="minimal" wiki_dir="${escapeXML14(result.wikiDir)}" written="${result.written.length}" skipped="${result.skipped.length}" />`;
|
|
6245
|
+
const renderStandard = () => {
|
|
6246
|
+
const lines = [
|
|
6247
|
+
`<wiki_generate wiki_dir="${escapeXML14(result.wikiDir)}" written="${result.written.length}" skipped="${result.skipped.length}">`
|
|
6248
|
+
];
|
|
6249
|
+
for (const p of result.written) {
|
|
6250
|
+
const size = safeFileSize(p.filePath);
|
|
6251
|
+
lines.push(
|
|
6252
|
+
` <page community="${escapeXML14(p.communityName)}" file="${escapeXML14(p.filePath)}" size="${size}" status="written" />`
|
|
6253
|
+
);
|
|
6254
|
+
}
|
|
6255
|
+
lines.push("</wiki_generate>");
|
|
6256
|
+
return lines.join("\n");
|
|
6257
|
+
};
|
|
6258
|
+
const full = detail_level === "minimal" ? renderMinimal() : renderStandard();
|
|
6259
|
+
if (!hasBudgetArgs(args)) return full;
|
|
6260
|
+
const budgetResult = await enforceBudget({
|
|
6261
|
+
full,
|
|
6262
|
+
args: readBudgetArgs(args),
|
|
6263
|
+
toolName: "ctx_wiki_generate",
|
|
6264
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS5,
|
|
6265
|
+
skeletonProducer: detail_level === "standard" ? async () => renderMinimal() : void 0
|
|
6266
|
+
});
|
|
6267
|
+
return wrapResponse(budgetResult);
|
|
5865
6268
|
}
|
|
5866
6269
|
);
|
|
5867
6270
|
}
|
|
@@ -5910,6 +6313,7 @@ function registerGraphExportTool(registry, ctx) {
|
|
|
5910
6313
|
import { z as z19 } from "zod";
|
|
5911
6314
|
import { execFile } from "child_process";
|
|
5912
6315
|
import { promisify as promisify2 } from "util";
|
|
6316
|
+
var DEFAULT_MAX_RESPONSE_TOKENS6 = 8e3;
|
|
5913
6317
|
var execFileAsync = promisify2(execFile);
|
|
5914
6318
|
var Schema17 = z19.object({
|
|
5915
6319
|
changed_files: z19.array(z19.string()).optional().describe(
|
|
@@ -5923,7 +6327,11 @@ var Schema17 = z19.object({
|
|
|
5923
6327
|
max_diff_lines: z19.number().min(10).max(2e3).optional().default(300).describe(
|
|
5924
6328
|
"Max diff lines to include per file (default: 300)"
|
|
5925
6329
|
),
|
|
5926
|
-
project_root: ProjectRootField
|
|
6330
|
+
project_root: ProjectRootField,
|
|
6331
|
+
// ─── Phase B2 budget surface ──
|
|
6332
|
+
max_response_tokens: z19.number().int().positive().optional().describe("Soft response budget. Default: 8000 (when opted in). Over-budget re-renders without <skeleton> blocks and without the transitive_importers section \u2014 keeps diffs, direct importers, and call sites."),
|
|
6333
|
+
on_budget_exceeded: z19.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton' (default) drops skeletons + transitive importers; 'truncate' slices; 'error' throws."),
|
|
6334
|
+
response_format: z19.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces the lighter view; 'full'/'auto' lets the budget decide.")
|
|
5927
6335
|
});
|
|
5928
6336
|
function escapeXML16(text) {
|
|
5929
6337
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -5971,7 +6379,10 @@ function registerGitDiffReviewTool(registry, ctx) {
|
|
|
5971
6379
|
description: "Include API skeletons for changed and importer files (default: true)"
|
|
5972
6380
|
},
|
|
5973
6381
|
max_diff_lines: { type: "number", description: "Max diff lines per file (default: 300)" },
|
|
5974
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
6382
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
6383
|
+
max_response_tokens: { type: "number", description: "Soft response budget. Default: 8000 (when opted in)." },
|
|
6384
|
+
on_budget_exceeded: { type: "string", enum: ["skeleton", "truncate", "error"], description: "Behavior over budget. 'skeleton' (default) drops <skeleton> blocks + transitive importers; 'truncate' slices; 'error' throws." },
|
|
6385
|
+
response_format: { type: "string", enum: ["full", "skeleton", "auto"], description: "'skeleton' forces lighter view; 'full'/'auto' lets the budget decide." }
|
|
5975
6386
|
}
|
|
5976
6387
|
}
|
|
5977
6388
|
},
|
|
@@ -5991,10 +6402,21 @@ function registerGitDiffReviewTool(registry, ctx) {
|
|
|
5991
6402
|
logger.warn("git diff failed \u2014 no changed files detected");
|
|
5992
6403
|
}
|
|
5993
6404
|
}
|
|
6405
|
+
const maybeBudget = async (full2, skeletonProducer) => {
|
|
6406
|
+
if (!hasBudgetArgs(args)) return full2;
|
|
6407
|
+
const result = await enforceBudget({
|
|
6408
|
+
full: full2,
|
|
6409
|
+
args: readBudgetArgs(args),
|
|
6410
|
+
toolName: "ctx_git_diff_review",
|
|
6411
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS6,
|
|
6412
|
+
skeletonProducer
|
|
6413
|
+
});
|
|
6414
|
+
return wrapResponse(result);
|
|
6415
|
+
};
|
|
5994
6416
|
if (files.length === 0) {
|
|
5995
|
-
return `<git_diff_review changed_files="0">
|
|
6417
|
+
return maybeBudget(`<git_diff_review changed_files="0">
|
|
5996
6418
|
<!-- No changed files detected -->
|
|
5997
|
-
</git_diff_review
|
|
6419
|
+
</git_diff_review>`);
|
|
5998
6420
|
}
|
|
5999
6421
|
const graph = await ctx.getGraph(project_root);
|
|
6000
6422
|
const blast = await computeBlastRadius({
|
|
@@ -6003,62 +6425,69 @@ function registerGitDiffReviewTool(registry, ctx) {
|
|
|
6003
6425
|
projectRoot: ctx.projectRoot,
|
|
6004
6426
|
graph
|
|
6005
6427
|
});
|
|
6006
|
-
const
|
|
6007
|
-
`<git_diff_review changed_files="${files.length}" depth="${depth}">`
|
|
6008
|
-
];
|
|
6009
|
-
lines.push(` <changed_files count="${files.length}">`);
|
|
6010
|
-
for (const file of files) {
|
|
6011
|
-
lines.push(` <file path="${escapeXML16(file)}">`);
|
|
6428
|
+
const changedFileData = await Promise.all(files.map(async (file) => {
|
|
6012
6429
|
const rawDiff = use_git ? await getFileDiff(ctx.projectRoot, file) : "";
|
|
6013
6430
|
const diffLines = rawDiff ? rawDiff.split("\n") : [];
|
|
6014
6431
|
const truncated = diffLines.length > max_diff_lines;
|
|
6015
6432
|
const diffContent = truncated ? [...diffLines.slice(0, max_diff_lines), `... (${diffLines.length - max_diff_lines} more lines)`].join("\n") : rawDiff;
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6433
|
+
const skeleton = include_skeletons ? await trySkeletonize(ctx, file, project_root) : "";
|
|
6434
|
+
return { file, diffLines, truncated, diffContent, skeleton };
|
|
6435
|
+
}));
|
|
6436
|
+
const skeletonLimit = 5;
|
|
6437
|
+
const directImporterSkeletons = await Promise.all(
|
|
6438
|
+
blast.directImporters.map(async (file, i) => ({
|
|
6439
|
+
file,
|
|
6440
|
+
skeleton: include_skeletons && i < skeletonLimit ? await trySkeletonize(ctx, file, project_root) : ""
|
|
6441
|
+
}))
|
|
6442
|
+
);
|
|
6443
|
+
const render = (withSkeletons, withTransitive) => {
|
|
6444
|
+
const out = [`<git_diff_review changed_files="${files.length}" depth="${depth}">`];
|
|
6445
|
+
out.push(` <changed_files count="${files.length}">`);
|
|
6446
|
+
for (const cd of changedFileData) {
|
|
6447
|
+
out.push(` <file path="${escapeXML16(cd.file)}">`);
|
|
6448
|
+
out.push(` <diff lines="${cd.diffLines.length}" truncated="${cd.truncated}">`);
|
|
6449
|
+
if (cd.diffContent) out.push(escapeXML16(cd.diffContent));
|
|
6450
|
+
out.push(" </diff>");
|
|
6451
|
+
if (withSkeletons && cd.skeleton) {
|
|
6452
|
+
out.push(" <skeleton>");
|
|
6453
|
+
out.push(escapeXML16(cd.skeleton));
|
|
6454
|
+
out.push(" </skeleton>");
|
|
6455
|
+
}
|
|
6456
|
+
out.push(" </file>");
|
|
6457
|
+
}
|
|
6458
|
+
out.push(" </changed_files>");
|
|
6459
|
+
out.push(` <direct_importers count="${blast.directImporters.length}">`);
|
|
6460
|
+
for (const di of directImporterSkeletons) {
|
|
6461
|
+
out.push(` <file path="${escapeXML16(di.file)}">`);
|
|
6462
|
+
if (withSkeletons && di.skeleton) {
|
|
6463
|
+
out.push(" <skeleton>");
|
|
6464
|
+
out.push(escapeXML16(di.skeleton));
|
|
6465
|
+
out.push(" </skeleton>");
|
|
6027
6466
|
}
|
|
6467
|
+
out.push(" </file>");
|
|
6028
6468
|
}
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
for (let i = 0; i < blast.directImporters.length; i++) {
|
|
6035
|
-
const file = blast.directImporters[i];
|
|
6036
|
-
lines.push(` <file path="${escapeXML16(file)}">`);
|
|
6037
|
-
if (include_skeletons && i < skeletonLimit) {
|
|
6038
|
-
const skeleton = await trySkeletonize(ctx, file, project_root);
|
|
6039
|
-
if (skeleton) {
|
|
6040
|
-
lines.push(" <skeleton>");
|
|
6041
|
-
lines.push(escapeXML16(skeleton));
|
|
6042
|
-
lines.push(" </skeleton>");
|
|
6469
|
+
out.push(" </direct_importers>");
|
|
6470
|
+
if (withTransitive) {
|
|
6471
|
+
out.push(` <transitive_importers count="${blast.transitiveImporters.length}">`);
|
|
6472
|
+
for (const file of blast.transitiveImporters) {
|
|
6473
|
+
out.push(` <file path="${escapeXML16(file)}" />`);
|
|
6043
6474
|
}
|
|
6475
|
+
out.push(" </transitive_importers>");
|
|
6476
|
+
} else {
|
|
6477
|
+
out.push(` <transitive_importers count="${blast.transitiveImporters.length}" omitted="budget"/>`);
|
|
6044
6478
|
}
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
);
|
|
6058
|
-
}
|
|
6059
|
-
lines.push(" </call_sites>");
|
|
6060
|
-
lines.push("</git_diff_review>");
|
|
6061
|
-
return lines.join("\n");
|
|
6479
|
+
out.push(` <call_sites count="${blast.callSites.length}">`);
|
|
6480
|
+
for (const cs of blast.callSites) {
|
|
6481
|
+
out.push(
|
|
6482
|
+
` <call_site file="${escapeXML16(cs.file)}" caller="${escapeXML16(cs.callerSymbol)}" callee="${escapeXML16(cs.calleeSymbol)}" />`
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6485
|
+
out.push(" </call_sites>");
|
|
6486
|
+
out.push("</git_diff_review>");
|
|
6487
|
+
return out.join("\n");
|
|
6488
|
+
};
|
|
6489
|
+
const full = render(include_skeletons, true);
|
|
6490
|
+
return maybeBudget(full, async () => render(false, false));
|
|
6062
6491
|
}
|
|
6063
6492
|
);
|
|
6064
6493
|
}
|
|
@@ -6067,13 +6496,18 @@ function registerGitDiffReviewTool(registry, ctx) {
|
|
|
6067
6496
|
import { z as z20 } from "zod";
|
|
6068
6497
|
import fs15 from "fs";
|
|
6069
6498
|
import path17 from "path";
|
|
6499
|
+
var DEFAULT_MAX_RESPONSE_TOKENS7 = 4e3;
|
|
6070
6500
|
var Schema18 = z20.object({
|
|
6071
6501
|
symbol: z20.string().min(1).describe("Symbol name to rename (exact match, case-sensitive)"),
|
|
6072
6502
|
new_name: z20.string().min(1).describe("New name for the symbol"),
|
|
6073
6503
|
max_files: z20.number().min(1).max(200).optional().default(50).describe(
|
|
6074
6504
|
"Maximum number of files to scan for occurrences (default: 50)"
|
|
6075
6505
|
),
|
|
6076
|
-
project_root: ProjectRootField
|
|
6506
|
+
project_root: ProjectRootField,
|
|
6507
|
+
// ─── Phase B2 budget surface ──
|
|
6508
|
+
max_response_tokens: z20.number().int().positive().optional().describe("Soft response budget. Default: 4000 (when opted in). Over-budget drops the per-change before/after lines; keeps the file+occurrence summary so callers can decide which files to drill into."),
|
|
6509
|
+
on_budget_exceeded: z20.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton' (default) drops change details; 'truncate' slices; 'error' throws."),
|
|
6510
|
+
response_format: z20.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces the summary-only view; 'full'/'auto' lets the budget decide.")
|
|
6077
6511
|
});
|
|
6078
6512
|
function escapeXML17(text) {
|
|
6079
6513
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -6121,7 +6555,10 @@ function registerRefactorPreviewTool(registry, ctx) {
|
|
|
6121
6555
|
type: "number",
|
|
6122
6556
|
description: "Maximum number of candidate files to scan (default: 50)"
|
|
6123
6557
|
},
|
|
6124
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
6558
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
6559
|
+
max_response_tokens: { type: "number", description: "Soft response budget. Default: 4000 (when opted in)." },
|
|
6560
|
+
on_budget_exceeded: { type: "string", enum: ["skeleton", "truncate", "error"], description: "Behavior over budget. 'skeleton' (default) drops change details; 'truncate' slices; 'error' throws." },
|
|
6561
|
+
response_format: { type: "string", enum: ["full", "skeleton", "auto"], description: "'skeleton' forces summary-only view; 'full'/'auto' lets the budget decide." }
|
|
6125
6562
|
},
|
|
6126
6563
|
required: ["symbol", "new_name"]
|
|
6127
6564
|
}
|
|
@@ -6152,36 +6589,53 @@ function registerRefactorPreviewTool(registry, ctx) {
|
|
|
6152
6589
|
totalOccurrences += occurrences.length;
|
|
6153
6590
|
}
|
|
6154
6591
|
}
|
|
6155
|
-
const
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
xmlLines.push(" </definitions>");
|
|
6165
|
-
xmlLines.push(` <changes count="${fileChanges.length}">`);
|
|
6166
|
-
for (const fc of fileChanges) {
|
|
6167
|
-
xmlLines.push(` <file path="${escapeXML17(fc.filePath)}" occurrences="${fc.occurrences.length}">`);
|
|
6168
|
-
for (const occ of fc.occurrences) {
|
|
6169
|
-
xmlLines.push(` <change line="${occ.line}">`);
|
|
6170
|
-
xmlLines.push(` <before>${escapeXML17(occ.before)}</before>`);
|
|
6171
|
-
xmlLines.push(` <after>${escapeXML17(occ.after)}</after>`);
|
|
6172
|
-
xmlLines.push(" </change>");
|
|
6592
|
+
const render = (includeChanges) => {
|
|
6593
|
+
const xmlLines = [
|
|
6594
|
+
`<refactor_preview symbol="${escapeXML17(symbol)}" new_name="${escapeXML17(new_name)}" total_files="${fileChanges.length}" total_occurrences="${totalOccurrences}">`
|
|
6595
|
+
];
|
|
6596
|
+
xmlLines.push(` <definitions count="${definitions.length}">`);
|
|
6597
|
+
for (const def of definitions) {
|
|
6598
|
+
xmlLines.push(
|
|
6599
|
+
` <definition file="${escapeXML17(def.filePath)}" type="${escapeXML17(def.type)}" signature="${escapeXML17(def.signature)}" />`
|
|
6600
|
+
);
|
|
6173
6601
|
}
|
|
6174
|
-
xmlLines.push("
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6602
|
+
xmlLines.push(" </definitions>");
|
|
6603
|
+
xmlLines.push(` <changes count="${fileChanges.length}">`);
|
|
6604
|
+
for (const fc of fileChanges) {
|
|
6605
|
+
if (includeChanges) {
|
|
6606
|
+
xmlLines.push(` <file path="${escapeXML17(fc.filePath)}" occurrences="${fc.occurrences.length}">`);
|
|
6607
|
+
for (const occ of fc.occurrences) {
|
|
6608
|
+
xmlLines.push(` <change line="${occ.line}">`);
|
|
6609
|
+
xmlLines.push(` <before>${escapeXML17(occ.before)}</before>`);
|
|
6610
|
+
xmlLines.push(` <after>${escapeXML17(occ.after)}</after>`);
|
|
6611
|
+
xmlLines.push(" </change>");
|
|
6612
|
+
}
|
|
6613
|
+
xmlLines.push(" </file>");
|
|
6614
|
+
} else {
|
|
6615
|
+
xmlLines.push(` <file path="${escapeXML17(fc.filePath)}" occurrences="${fc.occurrences.length}"/>`);
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
xmlLines.push(" </changes>");
|
|
6619
|
+
xmlLines.push("</refactor_preview>");
|
|
6620
|
+
return xmlLines.join("\n");
|
|
6621
|
+
};
|
|
6622
|
+
const full = render(true);
|
|
6623
|
+
if (!hasBudgetArgs(args)) return full;
|
|
6624
|
+
const result = await enforceBudget({
|
|
6625
|
+
full,
|
|
6626
|
+
args: readBudgetArgs(args),
|
|
6627
|
+
toolName: "ctx_refactor_preview",
|
|
6628
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS7,
|
|
6629
|
+
skeletonProducer: async () => render(false)
|
|
6630
|
+
});
|
|
6631
|
+
return wrapResponse(result);
|
|
6179
6632
|
}
|
|
6180
6633
|
);
|
|
6181
6634
|
}
|
|
6182
6635
|
|
|
6183
6636
|
// packages/core/src/tools/execution-flow.ts
|
|
6184
6637
|
import { z as z21 } from "zod";
|
|
6638
|
+
var DEFAULT_MAX_RESPONSE_TOKENS8 = 4e3;
|
|
6185
6639
|
var Schema19 = z21.object({
|
|
6186
6640
|
entry_point: z21.string().min(1).describe("Symbol name to start the execution flow from"),
|
|
6187
6641
|
entry_file: z21.string().optional().describe(
|
|
@@ -6191,7 +6645,11 @@ var Schema19 = z21.object({
|
|
|
6191
6645
|
max_nodes: z21.number().min(1).max(200).optional().default(50).describe(
|
|
6192
6646
|
"Max total steps to include in output (default: 50)"
|
|
6193
6647
|
),
|
|
6194
|
-
project_root: ProjectRootField
|
|
6648
|
+
project_root: ProjectRootField,
|
|
6649
|
+
// ─── Phase B2 budget surface ──
|
|
6650
|
+
max_response_tokens: z21.number().int().positive().optional().describe("Soft response budget. Default: 4000 (when opted in). No skeleton fallback \u2014 response is already a bounded step list; over-budget falls through to truncation."),
|
|
6651
|
+
on_budget_exceeded: z21.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton'/'truncate' both slice; 'error' throws."),
|
|
6652
|
+
response_format: z21.enum(["full", "skeleton", "auto"]).optional().describe("'full'/'auto' default; 'skeleton' same output.")
|
|
6195
6653
|
});
|
|
6196
6654
|
function escapeXML18(text) {
|
|
6197
6655
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -6253,7 +6711,10 @@ function registerExecutionFlowTool(registry, ctx) {
|
|
|
6253
6711
|
type: "number",
|
|
6254
6712
|
description: "Max total steps to return (default: 50)"
|
|
6255
6713
|
},
|
|
6256
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
6714
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
6715
|
+
max_response_tokens: { type: "number", description: "Soft response budget. Default: 4000 (when opted in)." },
|
|
6716
|
+
on_budget_exceeded: { type: "string", enum: ["skeleton", "truncate", "error"], description: "Behavior over budget." },
|
|
6717
|
+
response_format: { type: "string", enum: ["full", "skeleton", "auto"], description: "'full'/'auto' default; 'skeleton' same output." }
|
|
6257
6718
|
},
|
|
6258
6719
|
required: ["entry_point"]
|
|
6259
6720
|
}
|
|
@@ -6279,10 +6740,20 @@ function registerExecutionFlowTool(registry, ctx) {
|
|
|
6279
6740
|
}
|
|
6280
6741
|
}
|
|
6281
6742
|
}
|
|
6743
|
+
const maybeBudget = async (full) => {
|
|
6744
|
+
if (!hasBudgetArgs(args)) return full;
|
|
6745
|
+
const result = await enforceBudget({
|
|
6746
|
+
full,
|
|
6747
|
+
args: readBudgetArgs(args),
|
|
6748
|
+
toolName: "ctx_execution_flow",
|
|
6749
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS8
|
|
6750
|
+
});
|
|
6751
|
+
return wrapResponse(result);
|
|
6752
|
+
};
|
|
6282
6753
|
if (!resolvedFile) {
|
|
6283
|
-
return `<execution_flow entry="${escapeXML18(entry_point)}" total_steps="0" has_cycles="false">
|
|
6754
|
+
return maybeBudget(`<execution_flow entry="${escapeXML18(entry_point)}" total_steps="0" has_cycles="false">
|
|
6284
6755
|
<!-- No call graph entries found for symbol -->
|
|
6285
|
-
</execution_flow
|
|
6756
|
+
</execution_flow>`);
|
|
6286
6757
|
}
|
|
6287
6758
|
const { steps, hasCycles } = buildFlowSteps(
|
|
6288
6759
|
entry_point,
|
|
@@ -6307,7 +6778,7 @@ function registerExecutionFlowTool(registry, ctx) {
|
|
|
6307
6778
|
}
|
|
6308
6779
|
}
|
|
6309
6780
|
xmlLines.push("</execution_flow>");
|
|
6310
|
-
return xmlLines.join("\n");
|
|
6781
|
+
return maybeBudget(xmlLines.join("\n"));
|
|
6311
6782
|
}
|
|
6312
6783
|
);
|
|
6313
6784
|
}
|
|
@@ -6316,6 +6787,7 @@ function registerExecutionFlowTool(registry, ctx) {
|
|
|
6316
6787
|
import { z as z22 } from "zod";
|
|
6317
6788
|
import fs16 from "fs";
|
|
6318
6789
|
import path18 from "path";
|
|
6790
|
+
var DEFAULT_MAX_RESPONSE_TOKENS9 = 4e3;
|
|
6319
6791
|
var ALIAS_REGEX = /^[a-z0-9-]{1,40}$/;
|
|
6320
6792
|
var RESERVED_ALIASES = /* @__PURE__ */ new Set([
|
|
6321
6793
|
"register",
|
|
@@ -6418,7 +6890,11 @@ var Schema20 = z22.object({
|
|
|
6418
6890
|
repos: z22.array(z22.string()).optional().describe(
|
|
6419
6891
|
"Specific repo root paths to search. Omit to search all registered repos."
|
|
6420
6892
|
),
|
|
6421
|
-
project_root: ProjectRootField
|
|
6893
|
+
project_root: ProjectRootField,
|
|
6894
|
+
// ─── Phase B2 budget surface ──
|
|
6895
|
+
max_response_tokens: z22.number().int().positive().optional().describe("Soft response budget. Default: 4000 (when opted in). Over-budget drops content snippets (repo + path + score only)."),
|
|
6896
|
+
on_budget_exceeded: z22.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton' (default) drops content snippets; 'truncate' slices; 'error' throws."),
|
|
6897
|
+
response_format: z22.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces the path-and-score-only view; 'full'/'auto' lets the budget decide.")
|
|
6422
6898
|
});
|
|
6423
6899
|
function escapeXML19(text) {
|
|
6424
6900
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -6440,7 +6916,10 @@ function registerCrossRepoSearchTool(registry, _ctx, registryFilePath) {
|
|
|
6440
6916
|
items: { type: "string" },
|
|
6441
6917
|
description: "Specific repo root paths to search. Omit to search all registered repos."
|
|
6442
6918
|
},
|
|
6443
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
6919
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
6920
|
+
max_response_tokens: { type: "number", description: "Soft response budget. Default: 4000 (when opted in)." },
|
|
6921
|
+
on_budget_exceeded: { type: "string", enum: ["skeleton", "truncate", "error"], description: "Behavior over budget. 'skeleton' (default) drops content snippets; 'truncate' slices; 'error' throws." },
|
|
6922
|
+
response_format: { type: "string", enum: ["full", "skeleton", "auto"], description: "'skeleton' forces path+score-only view; 'full'/'auto' lets the budget decide." }
|
|
6444
6923
|
},
|
|
6445
6924
|
required: ["query"]
|
|
6446
6925
|
}
|
|
@@ -6509,19 +6988,32 @@ function registerCrossRepoSearchTool(registry, _ctx, registryFilePath) {
|
|
|
6509
6988
|
);
|
|
6510
6989
|
}
|
|
6511
6990
|
xmlLines.push(" </repos>");
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6991
|
+
const render = (includeContent) => {
|
|
6992
|
+
const out = [...xmlLines];
|
|
6993
|
+
out.push(` <results count="${topResults.length}">`);
|
|
6994
|
+
for (const r of topResults) {
|
|
6995
|
+
if (includeContent && r.content) {
|
|
6996
|
+
out.push(` <result repo="${escapeXML19(r.repoName)}" file="${escapeXML19(r.filePath)}" score="${r.score.toFixed(4)}">`);
|
|
6997
|
+
out.push(` ${escapeXML19(r.content.slice(0, 200))}`);
|
|
6998
|
+
out.push(" </result>");
|
|
6999
|
+
} else {
|
|
7000
|
+
out.push(` <result repo="${escapeXML19(r.repoName)}" file="${escapeXML19(r.filePath)}" score="${r.score.toFixed(4)}"/>`);
|
|
7001
|
+
}
|
|
6519
7002
|
}
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
7003
|
+
out.push(" </results>");
|
|
7004
|
+
out.push("</cross_repo_search>");
|
|
7005
|
+
return out.join("\n");
|
|
7006
|
+
};
|
|
7007
|
+
const full = render(true);
|
|
7008
|
+
if (!hasBudgetArgs(args)) return full;
|
|
7009
|
+
const result = await enforceBudget({
|
|
7010
|
+
full,
|
|
7011
|
+
args: readBudgetArgs(args),
|
|
7012
|
+
toolName: "ctx_cross_repo_search",
|
|
7013
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS9,
|
|
7014
|
+
skeletonProducer: async () => render(false)
|
|
7015
|
+
});
|
|
7016
|
+
return wrapResponse(result);
|
|
6525
7017
|
}
|
|
6526
7018
|
);
|
|
6527
7019
|
}
|
|
@@ -6530,6 +7022,7 @@ function registerCrossRepoSearchTool(registry, _ctx, registryFilePath) {
|
|
|
6530
7022
|
import { z as z23 } from "zod";
|
|
6531
7023
|
import fs17 from "fs";
|
|
6532
7024
|
import path19 from "path";
|
|
7025
|
+
var DEFAULT_MAX_RESPONSE_TOKENS10 = 2e3;
|
|
6533
7026
|
var Schema21 = z23.object({
|
|
6534
7027
|
symbol: z23.string().min(1).describe("Symbol name to rename (exact, case-sensitive)"),
|
|
6535
7028
|
new_name: z23.string().min(1).describe("New name for the symbol"),
|
|
@@ -6539,7 +7032,11 @@ var Schema21 = z23.object({
|
|
|
6539
7032
|
max_files: z23.number().min(1).max(200).optional().default(50).describe(
|
|
6540
7033
|
"Maximum candidate files to process (default: 50)"
|
|
6541
7034
|
),
|
|
6542
|
-
project_root: ProjectRootField
|
|
7035
|
+
project_root: ProjectRootField,
|
|
7036
|
+
// ─── Phase B2 budget surface ──
|
|
7037
|
+
max_response_tokens: z23.number().int().positive().optional().describe("Soft response budget. Default: 2000 (when opted in). No skeleton fallback \u2014 response is already compact; over-budget falls through to truncation."),
|
|
7038
|
+
on_budget_exceeded: z23.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton'/'truncate' both slice the XML; 'error' throws."),
|
|
7039
|
+
response_format: z23.enum(["full", "skeleton", "auto"]).optional().describe("'full'/'auto' default; 'skeleton' produces the same output.")
|
|
6543
7040
|
});
|
|
6544
7041
|
function escapeXML20(text) {
|
|
6545
7042
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -6573,7 +7070,10 @@ function registerApplyRefactorTool(registry, ctx) {
|
|
|
6573
7070
|
new_name: { type: "string", description: "New name" },
|
|
6574
7071
|
dry_run: { type: "boolean", description: "Preview only, no writes (default: false)" },
|
|
6575
7072
|
max_files: { type: "number", description: "Max candidate files (default: 50)" },
|
|
6576
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
7073
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
7074
|
+
max_response_tokens: { type: "number", description: "Soft response budget. Default: 2000 (when opted in)." },
|
|
7075
|
+
on_budget_exceeded: { type: "string", enum: ["skeleton", "truncate", "error"], description: "Behavior over budget." },
|
|
7076
|
+
response_format: { type: "string", enum: ["full", "skeleton", "auto"], description: "'full'/'auto' default; 'skeleton' same output." }
|
|
6577
7077
|
},
|
|
6578
7078
|
required: ["symbol", "new_name"]
|
|
6579
7079
|
}
|
|
@@ -6613,7 +7113,15 @@ function registerApplyRefactorTool(registry, ctx) {
|
|
|
6613
7113
|
);
|
|
6614
7114
|
}
|
|
6615
7115
|
xml.push("</apply_refactor>");
|
|
6616
|
-
|
|
7116
|
+
const full = xml.join("\n");
|
|
7117
|
+
if (!hasBudgetArgs(args)) return full;
|
|
7118
|
+
const result = await enforceBudget({
|
|
7119
|
+
full,
|
|
7120
|
+
args: readBudgetArgs(args),
|
|
7121
|
+
toolName: "ctx_apply_refactor",
|
|
7122
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS10
|
|
7123
|
+
});
|
|
7124
|
+
return wrapResponse(result);
|
|
6617
7125
|
}
|
|
6618
7126
|
);
|
|
6619
7127
|
}
|
|
@@ -6734,13 +7242,18 @@ function registerDetectChangesTool(registry, ctx) {
|
|
|
6734
7242
|
import { z as z25 } from "zod";
|
|
6735
7243
|
import fs18 from "fs";
|
|
6736
7244
|
import path20 from "path";
|
|
7245
|
+
var DEFAULT_MAX_RESPONSE_TOKENS11 = 4e3;
|
|
6737
7246
|
var Schema23 = z25.object({
|
|
6738
7247
|
query: z25.string().min(1).describe("Search term \u2014 literal or /regex/"),
|
|
6739
7248
|
mode: z25.enum(["hybrid", "keyword", "semantic"]).optional().default("hybrid"),
|
|
6740
7249
|
case_sensitive: z25.boolean().optional().default(false),
|
|
6741
7250
|
limit: z25.number().min(1).max(100).optional().default(20),
|
|
6742
7251
|
context_lines: z25.number().min(0).max(5).optional().default(1),
|
|
6743
|
-
project_root: ProjectRootField
|
|
7252
|
+
project_root: ProjectRootField,
|
|
7253
|
+
// ─── Phase B2 budget surface ──
|
|
7254
|
+
max_response_tokens: z25.number().int().positive().optional().describe("Soft response budget. Default: 4000 (when budget surface is opted into). Over-budget rebuilds the result list without match snippets (paths + match counts only)."),
|
|
7255
|
+
on_budget_exceeded: z25.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton' (default) drops snippets; 'truncate' slices the raw XML; 'error' throws."),
|
|
7256
|
+
response_format: z25.enum(["full", "skeleton", "auto"]).optional().describe("'skeleton' forces the path-and-count-only view; 'full'/'auto' lets the budget decide.")
|
|
6744
7257
|
});
|
|
6745
7258
|
function escapeXML22(text) {
|
|
6746
7259
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -6795,27 +7308,53 @@ function registerFullTextSearchTool(registry, ctx) {
|
|
|
6795
7308
|
case_sensitive: { type: "boolean", description: "Case-sensitive match (default: false)" },
|
|
6796
7309
|
limit: { type: "number", description: "Max results (default: 20)" },
|
|
6797
7310
|
context_lines: { type: "number", description: "Context lines around each match (default: 1)" },
|
|
6798
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
7311
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
7312
|
+
max_response_tokens: {
|
|
7313
|
+
type: "number",
|
|
7314
|
+
description: "Soft response budget in tokens. Default: 4000 (when opted into)."
|
|
7315
|
+
},
|
|
7316
|
+
on_budget_exceeded: {
|
|
7317
|
+
type: "string",
|
|
7318
|
+
enum: ["skeleton", "truncate", "error"],
|
|
7319
|
+
description: "Behavior when over budget. 'skeleton' (default) drops match snippets; 'truncate' slices; 'error' throws."
|
|
7320
|
+
},
|
|
7321
|
+
response_format: {
|
|
7322
|
+
type: "string",
|
|
7323
|
+
enum: ["full", "skeleton", "auto"],
|
|
7324
|
+
description: "'skeleton' forces path+count-only view; 'full'/'auto' lets the budget decide."
|
|
7325
|
+
}
|
|
6799
7326
|
},
|
|
6800
7327
|
required: ["query"]
|
|
6801
7328
|
}
|
|
6802
7329
|
},
|
|
6803
7330
|
async (args) => {
|
|
6804
|
-
const
|
|
7331
|
+
const parsed = Schema23.parse(args);
|
|
7332
|
+
const { query, mode, case_sensitive, limit, context_lines, project_root } = parsed;
|
|
7333
|
+
const maybeBudget = async (full, skeletonProducer) => {
|
|
7334
|
+
if (!hasBudgetArgs(args)) return full;
|
|
7335
|
+
const result = await enforceBudget({
|
|
7336
|
+
full,
|
|
7337
|
+
args: readBudgetArgs(args),
|
|
7338
|
+
toolName: "ctx_full_text_search",
|
|
7339
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS11,
|
|
7340
|
+
skeletonProducer
|
|
7341
|
+
});
|
|
7342
|
+
return wrapResponse(result);
|
|
7343
|
+
};
|
|
6805
7344
|
if (mode === "semantic") {
|
|
6806
7345
|
try {
|
|
6807
|
-
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-
|
|
7346
|
+
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-R4KCXSGO.js");
|
|
6808
7347
|
const store = await ctx.getStore(project_root);
|
|
6809
7348
|
const embedding = await generateEmbedding2(query);
|
|
6810
7349
|
const results = await store.search(embedding, limit);
|
|
6811
|
-
const
|
|
7350
|
+
const xml = [`<full_text_search query="${escapeXML22(query)}" mode="semantic" count="${results.length}">`];
|
|
6812
7351
|
for (const r of results) {
|
|
6813
|
-
|
|
7352
|
+
xml.push(` <result file="${escapeXML22(r.filePath)}" matches="0"/>`);
|
|
6814
7353
|
}
|
|
6815
|
-
|
|
6816
|
-
return
|
|
7354
|
+
xml.push("</full_text_search>");
|
|
7355
|
+
return maybeBudget(xml.join("\n"));
|
|
6817
7356
|
} catch {
|
|
6818
|
-
return `<full_text_search query="${escapeXML22(query)}" mode="semantic" count="0"
|
|
7357
|
+
return maybeBudget(`<full_text_search query="${escapeXML22(query)}" mode="semantic" count="0"/>`);
|
|
6819
7358
|
}
|
|
6820
7359
|
}
|
|
6821
7360
|
const pattern = buildPattern(query, case_sensitive);
|
|
@@ -6841,7 +7380,7 @@ function registerFullTextSearchTool(registry, ctx) {
|
|
|
6841
7380
|
let merged = keywordResults.slice(0, limit);
|
|
6842
7381
|
if (mode === "hybrid") {
|
|
6843
7382
|
try {
|
|
6844
|
-
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-
|
|
7383
|
+
const { generateEmbedding: generateEmbedding2 } = await import("./embedder-R4KCXSGO.js");
|
|
6845
7384
|
const store = await ctx.getStore(project_root);
|
|
6846
7385
|
const embedding = await generateEmbedding2(query);
|
|
6847
7386
|
const vectorResults = await store.search(embedding, Math.ceil(limit / 2));
|
|
@@ -6857,18 +7396,28 @@ function registerFullTextSearchTool(registry, ctx) {
|
|
|
6857
7396
|
} catch {
|
|
6858
7397
|
}
|
|
6859
7398
|
}
|
|
6860
|
-
const
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
7399
|
+
const render = (includeSnippets) => {
|
|
7400
|
+
const xml = [
|
|
7401
|
+
`<full_text_search query="${escapeXML22(query)}" mode="${mode}" case_sensitive="${case_sensitive}" count="${merged.length}">`
|
|
7402
|
+
];
|
|
7403
|
+
for (const r of merged) {
|
|
7404
|
+
if (includeSnippets && r.snippets.length > 0) {
|
|
7405
|
+
xml.push(` <result file="${escapeXML22(r.filePath)}" matches="${r.matchCount}">`);
|
|
7406
|
+
for (const snippet of r.snippets) {
|
|
7407
|
+
xml.push(` <match><![CDATA[${snippet}]]></match>`);
|
|
7408
|
+
}
|
|
7409
|
+
xml.push(" </result>");
|
|
7410
|
+
} else {
|
|
7411
|
+
xml.push(` <result file="${escapeXML22(r.filePath)}" matches="${r.matchCount}"/>`);
|
|
7412
|
+
}
|
|
6867
7413
|
}
|
|
6868
|
-
xml.push("
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
return
|
|
7414
|
+
xml.push("</full_text_search>");
|
|
7415
|
+
return xml.join("\n");
|
|
7416
|
+
};
|
|
7417
|
+
return maybeBudget(
|
|
7418
|
+
render(true),
|
|
7419
|
+
async () => render(false)
|
|
7420
|
+
);
|
|
6872
7421
|
}
|
|
6873
7422
|
);
|
|
6874
7423
|
}
|
|
@@ -7255,6 +7804,7 @@ function registerGraphDiffTool(registry, ctx) {
|
|
|
7255
7804
|
|
|
7256
7805
|
// packages/core/src/tools/find-large-functions.ts
|
|
7257
7806
|
import { z as z30 } from "zod";
|
|
7807
|
+
var DEFAULT_MAX_RESPONSE_TOKENS12 = 2e3;
|
|
7258
7808
|
var schema3 = z30.object({
|
|
7259
7809
|
threshold: z30.number().int().min(1).default(50).describe(
|
|
7260
7810
|
"Minimum line count to include (default: 50). Functions/classes shorter than this are excluded."
|
|
@@ -7265,7 +7815,11 @@ var schema3 = z30.object({
|
|
|
7265
7815
|
limit: z30.number().int().min(1).max(200).default(30).describe(
|
|
7266
7816
|
"Maximum results to return (default: 30)."
|
|
7267
7817
|
),
|
|
7268
|
-
project_root: ProjectRootField
|
|
7818
|
+
project_root: ProjectRootField,
|
|
7819
|
+
// ─── Phase B2 budget surface ──
|
|
7820
|
+
max_response_tokens: z30.number().int().positive().optional().describe("Soft response budget. Default: 2000 (when opted in). No skeleton fallback \u2014 response is already structural; over-budget falls through to truncation."),
|
|
7821
|
+
on_budget_exceeded: z30.enum(["skeleton", "truncate", "error"]).optional().describe("Behavior when over budget. 'skeleton'/'truncate' both slice the XML; 'error' throws."),
|
|
7822
|
+
response_format: z30.enum(["full", "skeleton", "auto"]).optional().describe("'full'/'auto' default; 'skeleton' produces the same output (response is already compact).")
|
|
7269
7823
|
});
|
|
7270
7824
|
function findLargeFunctions(graph, threshold, fileFilter) {
|
|
7271
7825
|
const results = [];
|
|
@@ -7315,7 +7869,10 @@ function registerFindLargeFunctionsTool(registry, ctx) {
|
|
|
7315
7869
|
type: "number",
|
|
7316
7870
|
description: "Maximum results to return (default: 30, max: 200)."
|
|
7317
7871
|
},
|
|
7318
|
-
project_root: PROJECT_ROOT_JSON_SCHEMA
|
|
7872
|
+
project_root: PROJECT_ROOT_JSON_SCHEMA,
|
|
7873
|
+
max_response_tokens: { type: "number", description: "Soft response budget. Default: 2000 (when opted in)." },
|
|
7874
|
+
on_budget_exceeded: { type: "string", enum: ["skeleton", "truncate", "error"], description: "Behavior over budget. 'skeleton'/'truncate' slice; 'error' throws." },
|
|
7875
|
+
response_format: { type: "string", enum: ["full", "skeleton", "auto"], description: "'full'/'auto' default; 'skeleton' same output (already compact)." }
|
|
7319
7876
|
}
|
|
7320
7877
|
}
|
|
7321
7878
|
},
|
|
@@ -7323,19 +7880,29 @@ function registerFindLargeFunctionsTool(registry, ctx) {
|
|
|
7323
7880
|
const { threshold, file_filter, limit, project_root } = schema3.parse(args);
|
|
7324
7881
|
const graph = await ctx.getGraph(project_root);
|
|
7325
7882
|
const results = findLargeFunctions(graph, threshold, file_filter).slice(0, limit);
|
|
7883
|
+
let full;
|
|
7326
7884
|
if (results.length === 0) {
|
|
7327
|
-
|
|
7885
|
+
full = `<ctx_find_large_functions threshold="${threshold}" count="0">
|
|
7328
7886
|
<message>No functions or classes exceed ${threshold} lines.</message>
|
|
7329
7887
|
</ctx_find_large_functions>`;
|
|
7330
|
-
}
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7888
|
+
} else {
|
|
7889
|
+
const lines = [
|
|
7890
|
+
`<ctx_find_large_functions threshold="${threshold}" count="${results.length}">`,
|
|
7891
|
+
...results.map(
|
|
7892
|
+
(r) => ` <symbol name="${escapeXML24(r.name)}" type="${r.type}" file="${escapeXML24(r.filePath)}" start="${r.startLine}" end="${r.endLine}" lines="${r.lineCount}" />`
|
|
7893
|
+
),
|
|
7894
|
+
`</ctx_find_large_functions>`
|
|
7895
|
+
];
|
|
7896
|
+
full = lines.join("\n");
|
|
7897
|
+
}
|
|
7898
|
+
if (!hasBudgetArgs(args)) return full;
|
|
7899
|
+
const result = await enforceBudget({
|
|
7900
|
+
full,
|
|
7901
|
+
args: readBudgetArgs(args),
|
|
7902
|
+
toolName: "ctx_find_large_functions",
|
|
7903
|
+
defaultMaxTokens: DEFAULT_MAX_RESPONSE_TOKENS12
|
|
7904
|
+
});
|
|
7905
|
+
return wrapResponse(result);
|
|
7339
7906
|
}
|
|
7340
7907
|
);
|
|
7341
7908
|
}
|
|
@@ -8717,113 +9284,15 @@ function maybePrintExpiryWarning(expiresAt) {
|
|
|
8717
9284
|
}
|
|
8718
9285
|
|
|
8719
9286
|
// packages/core/src/license/index.ts
|
|
8720
|
-
import
|
|
8721
|
-
var REVALIDATION_DAYS = 7;
|
|
8722
|
-
var GRACE_HOURS = 72;
|
|
8723
|
-
function defaultHome() {
|
|
8724
|
-
return os3.homedir();
|
|
8725
|
-
}
|
|
8726
|
-
async function isActive(opts = {}) {
|
|
8727
|
-
const home = opts.home ?? defaultHome();
|
|
8728
|
-
const store = new LicenseStore(home);
|
|
8729
|
-
const license = await store.read();
|
|
8730
|
-
if (!license) return false;
|
|
8731
|
-
if (new Date(license.expiresAt).getTime() <= Date.now()) return false;
|
|
8732
|
-
const lastValidated = new Date(license.lastValidatedAt).getTime();
|
|
8733
|
-
const msSinceValidation = Date.now() - lastValidated;
|
|
8734
|
-
const revalidationMs = REVALIDATION_DAYS * 24 * 60 * 60 * 1e3;
|
|
8735
|
-
if (msSinceValidation <= revalidationMs) {
|
|
8736
|
-
maybePrintExpiryWarning(license.expiresAt);
|
|
8737
|
-
return true;
|
|
8738
|
-
}
|
|
8739
|
-
const client = new ApiClient(opts.apiBase);
|
|
8740
|
-
try {
|
|
8741
|
-
const result = await client.validate(license.key, license.instanceId);
|
|
8742
|
-
if (result.status === "revoked" || result.status === "expired") return false;
|
|
8743
|
-
const refreshed = {
|
|
8744
|
-
...license,
|
|
8745
|
-
lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8746
|
-
expiresAt: result.expiresAt || license.expiresAt
|
|
8747
|
-
};
|
|
8748
|
-
await store.write(refreshed);
|
|
8749
|
-
maybePrintExpiryWarning(refreshed.expiresAt);
|
|
8750
|
-
return true;
|
|
8751
|
-
} catch (err) {
|
|
8752
|
-
if (err instanceof LicenseRevokedError) return false;
|
|
8753
|
-
if (err instanceof NetworkError || err instanceof TypeError) {
|
|
8754
|
-
const graceMs = GRACE_HOURS * 60 * 60 * 1e3;
|
|
8755
|
-
if (msSinceValidation <= revalidationMs + graceMs) {
|
|
8756
|
-
process.stderr.write(
|
|
8757
|
-
`\u26A0 ctxloom is running offline. License will be reverified when network is available.
|
|
8758
|
-
|
|
8759
|
-
`
|
|
8760
|
-
);
|
|
8761
|
-
maybePrintExpiryWarning(license.expiresAt);
|
|
8762
|
-
return true;
|
|
8763
|
-
}
|
|
8764
|
-
return false;
|
|
8765
|
-
}
|
|
8766
|
-
return false;
|
|
8767
|
-
}
|
|
8768
|
-
}
|
|
8769
|
-
async function requireActive(opts = {}) {
|
|
8770
|
-
const active = await isActive(opts);
|
|
8771
|
-
if (!active) throw new LicenseRequiredError();
|
|
8772
|
-
}
|
|
8773
|
-
async function getLicenseInfo(opts = {}) {
|
|
8774
|
-
const home = opts.home ?? defaultHome();
|
|
8775
|
-
const store = new LicenseStore(home);
|
|
8776
|
-
return store.read();
|
|
8777
|
-
}
|
|
8778
|
-
async function activateLicense(key, opts = {}) {
|
|
8779
|
-
const home = opts.home ?? defaultHome();
|
|
8780
|
-
const fingerprint = await Fingerprint.compute();
|
|
8781
|
-
const hostname = os3.hostname();
|
|
8782
|
-
const platform = `${os3.platform()}-${os3.arch()}`;
|
|
8783
|
-
const client = new ApiClient(opts.apiBase);
|
|
8784
|
-
const result = await client.activate(key, fingerprint, hostname, platform);
|
|
8785
|
-
const license = {
|
|
8786
|
-
schemaVersion: 1,
|
|
8787
|
-
key,
|
|
8788
|
-
tier: result.tier,
|
|
8789
|
-
status: "active",
|
|
8790
|
-
fingerprint,
|
|
8791
|
-
seats: result.seatsTotal,
|
|
8792
|
-
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8793
|
-
expiresAt: result.expiresAt,
|
|
8794
|
-
lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8795
|
-
licenseId: result.licenseId,
|
|
8796
|
-
instanceId: result.instanceId
|
|
8797
|
-
};
|
|
8798
|
-
const store = new LicenseStore(home);
|
|
8799
|
-
await store.write(license);
|
|
8800
|
-
return license;
|
|
8801
|
-
}
|
|
8802
|
-
async function deactivateLicense(opts = {}) {
|
|
8803
|
-
const home = opts.home ?? defaultHome();
|
|
8804
|
-
const store = new LicenseStore(home);
|
|
8805
|
-
const license = await store.read();
|
|
8806
|
-
if (!license) return;
|
|
8807
|
-
const client = new ApiClient(opts.apiBase);
|
|
8808
|
-
await client.deactivate(license.key, license.instanceId);
|
|
8809
|
-
await store.clear();
|
|
8810
|
-
}
|
|
8811
|
-
async function startTrial(email, opts = {}) {
|
|
8812
|
-
const home = opts.home ?? defaultHome();
|
|
8813
|
-
const fingerprint = await Fingerprint.compute();
|
|
8814
|
-
const client = new ApiClient(opts.apiBase);
|
|
8815
|
-
const result = await client.startTrial(email, fingerprint);
|
|
8816
|
-
void home;
|
|
8817
|
-
return result;
|
|
8818
|
-
}
|
|
9287
|
+
import os5 from "os";
|
|
8819
9288
|
|
|
8820
9289
|
// packages/core/src/license/DistinctIdStore.ts
|
|
8821
9290
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
8822
9291
|
import path30 from "path";
|
|
8823
|
-
import
|
|
9292
|
+
import os3 from "os";
|
|
8824
9293
|
var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8825
9294
|
function distinctIdPath(home) {
|
|
8826
|
-
return path30.join(home ??
|
|
9295
|
+
return path30.join(home ?? os3.homedir(), ".ctxloom", "distinct_id");
|
|
8827
9296
|
}
|
|
8828
9297
|
function isValidV4(id) {
|
|
8829
9298
|
return typeof id === "string" && UUID_V4_REGEX.test(id);
|
|
@@ -8842,7 +9311,7 @@ function getOrCreateDistinctId(home) {
|
|
|
8842
9311
|
}
|
|
8843
9312
|
const record = {
|
|
8844
9313
|
id: crypto.randomUUID(),
|
|
8845
|
-
alias_pending:
|
|
9314
|
+
alias_pending: os3.hostname()
|
|
8846
9315
|
};
|
|
8847
9316
|
mkdirSync2(path30.dirname(filePath), { recursive: true });
|
|
8848
9317
|
writeFileSync2(filePath, JSON.stringify(record), { mode: 384 });
|
|
@@ -8873,7 +9342,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
|
|
|
8873
9342
|
function getTelemetryLevel() {
|
|
8874
9343
|
return TELEMETRY_LEVEL;
|
|
8875
9344
|
}
|
|
8876
|
-
var CTXLOOM_VERSION = "1.2.
|
|
9345
|
+
var CTXLOOM_VERSION = "1.2.7".length > 0 ? "1.2.7" : "dev";
|
|
8877
9346
|
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
8878
9347
|
var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
|
|
8879
9348
|
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
|
|
@@ -9004,32 +9473,169 @@ function parseStack(stack) {
|
|
|
9004
9473
|
}).filter((f) => f !== null).slice(0, 20);
|
|
9005
9474
|
}
|
|
9006
9475
|
|
|
9007
|
-
// packages/core/src/license/
|
|
9476
|
+
// packages/core/src/license/FunnelMilestones.ts
|
|
9008
9477
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
9009
9478
|
import path31 from "path";
|
|
9010
|
-
import
|
|
9479
|
+
import os4 from "os";
|
|
9480
|
+
var INSTALL_MARKER = "installed_at";
|
|
9481
|
+
var FIRST_REVIEW_MARKER = "first_review_at";
|
|
9482
|
+
function writeMarker(filePath) {
|
|
9483
|
+
mkdirSync3(path31.dirname(filePath), { recursive: true });
|
|
9484
|
+
writeFileSync3(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
|
|
9485
|
+
}
|
|
9486
|
+
function shouldEmitInstallCompleted(home) {
|
|
9487
|
+
const root = home ?? os4.homedir();
|
|
9488
|
+
const marker = path31.join(root, ".ctxloom", INSTALL_MARKER);
|
|
9489
|
+
if (existsSync3(marker)) return false;
|
|
9490
|
+
try {
|
|
9491
|
+
writeMarker(marker);
|
|
9492
|
+
} catch {
|
|
9493
|
+
}
|
|
9494
|
+
return true;
|
|
9495
|
+
}
|
|
9496
|
+
function shouldEmitFirstReviewRun(projectRoot) {
|
|
9497
|
+
const marker = path31.join(projectRoot, ".ctxloom", FIRST_REVIEW_MARKER);
|
|
9498
|
+
if (existsSync3(marker)) return false;
|
|
9499
|
+
try {
|
|
9500
|
+
writeMarker(marker);
|
|
9501
|
+
} catch {
|
|
9502
|
+
}
|
|
9503
|
+
return true;
|
|
9504
|
+
}
|
|
9505
|
+
|
|
9506
|
+
// packages/core/src/license/index.ts
|
|
9507
|
+
var REVALIDATION_DAYS = 7;
|
|
9508
|
+
var GRACE_HOURS = 72;
|
|
9509
|
+
function defaultHome() {
|
|
9510
|
+
return os5.homedir();
|
|
9511
|
+
}
|
|
9512
|
+
async function isActive(opts = {}) {
|
|
9513
|
+
const home = opts.home ?? defaultHome();
|
|
9514
|
+
const store = new LicenseStore(home);
|
|
9515
|
+
const license = await store.read();
|
|
9516
|
+
if (!license) return false;
|
|
9517
|
+
if (new Date(license.expiresAt).getTime() <= Date.now()) return false;
|
|
9518
|
+
const lastValidated = new Date(license.lastValidatedAt).getTime();
|
|
9519
|
+
const msSinceValidation = Date.now() - lastValidated;
|
|
9520
|
+
const revalidationMs = REVALIDATION_DAYS * 24 * 60 * 60 * 1e3;
|
|
9521
|
+
if (msSinceValidation <= revalidationMs) {
|
|
9522
|
+
maybePrintExpiryWarning(license.expiresAt);
|
|
9523
|
+
return true;
|
|
9524
|
+
}
|
|
9525
|
+
const client = new ApiClient(opts.apiBase);
|
|
9526
|
+
try {
|
|
9527
|
+
const result = await client.validate(license.key, license.instanceId);
|
|
9528
|
+
if (result.status === "revoked" || result.status === "expired") return false;
|
|
9529
|
+
const refreshed = {
|
|
9530
|
+
...license,
|
|
9531
|
+
lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9532
|
+
expiresAt: result.expiresAt || license.expiresAt
|
|
9533
|
+
};
|
|
9534
|
+
await store.write(refreshed);
|
|
9535
|
+
if (result.expiresAt && result.expiresAt !== license.expiresAt) {
|
|
9536
|
+
track("renewal", {
|
|
9537
|
+
tier: license.tier,
|
|
9538
|
+
previousExpiresAt: license.expiresAt,
|
|
9539
|
+
newExpiresAt: result.expiresAt
|
|
9540
|
+
});
|
|
9541
|
+
}
|
|
9542
|
+
maybePrintExpiryWarning(refreshed.expiresAt);
|
|
9543
|
+
return true;
|
|
9544
|
+
} catch (err) {
|
|
9545
|
+
if (err instanceof LicenseRevokedError) return false;
|
|
9546
|
+
if (err instanceof NetworkError || err instanceof TypeError) {
|
|
9547
|
+
const graceMs = GRACE_HOURS * 60 * 60 * 1e3;
|
|
9548
|
+
if (msSinceValidation <= revalidationMs + graceMs) {
|
|
9549
|
+
process.stderr.write(
|
|
9550
|
+
`\u26A0 ctxloom is running offline. License will be reverified when network is available.
|
|
9551
|
+
|
|
9552
|
+
`
|
|
9553
|
+
);
|
|
9554
|
+
maybePrintExpiryWarning(license.expiresAt);
|
|
9555
|
+
return true;
|
|
9556
|
+
}
|
|
9557
|
+
return false;
|
|
9558
|
+
}
|
|
9559
|
+
return false;
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
async function requireActive(opts = {}) {
|
|
9563
|
+
const active = await isActive(opts);
|
|
9564
|
+
if (!active) throw new LicenseRequiredError();
|
|
9565
|
+
}
|
|
9566
|
+
async function getLicenseInfo(opts = {}) {
|
|
9567
|
+
const home = opts.home ?? defaultHome();
|
|
9568
|
+
const store = new LicenseStore(home);
|
|
9569
|
+
return store.read();
|
|
9570
|
+
}
|
|
9571
|
+
async function activateLicense(key, opts = {}) {
|
|
9572
|
+
const home = opts.home ?? defaultHome();
|
|
9573
|
+
const fingerprint = await Fingerprint.compute();
|
|
9574
|
+
const hostname = os5.hostname();
|
|
9575
|
+
const platform = `${os5.platform()}-${os5.arch()}`;
|
|
9576
|
+
const client = new ApiClient(opts.apiBase);
|
|
9577
|
+
const result = await client.activate(key, fingerprint, hostname, platform);
|
|
9578
|
+
const license = {
|
|
9579
|
+
schemaVersion: 1,
|
|
9580
|
+
key,
|
|
9581
|
+
tier: result.tier,
|
|
9582
|
+
status: "active",
|
|
9583
|
+
fingerprint,
|
|
9584
|
+
seats: result.seatsTotal,
|
|
9585
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9586
|
+
expiresAt: result.expiresAt,
|
|
9587
|
+
lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9588
|
+
licenseId: result.licenseId,
|
|
9589
|
+
instanceId: result.instanceId
|
|
9590
|
+
};
|
|
9591
|
+
const store = new LicenseStore(home);
|
|
9592
|
+
await store.write(license);
|
|
9593
|
+
return license;
|
|
9594
|
+
}
|
|
9595
|
+
async function deactivateLicense(opts = {}) {
|
|
9596
|
+
const home = opts.home ?? defaultHome();
|
|
9597
|
+
const store = new LicenseStore(home);
|
|
9598
|
+
const license = await store.read();
|
|
9599
|
+
if (!license) return;
|
|
9600
|
+
const client = new ApiClient(opts.apiBase);
|
|
9601
|
+
await client.deactivate(license.key, license.instanceId);
|
|
9602
|
+
await store.clear();
|
|
9603
|
+
}
|
|
9604
|
+
async function startTrial(email, opts = {}) {
|
|
9605
|
+
const home = opts.home ?? defaultHome();
|
|
9606
|
+
const fingerprint = await Fingerprint.compute();
|
|
9607
|
+
const client = new ApiClient(opts.apiBase);
|
|
9608
|
+
const result = await client.startTrial(email, fingerprint);
|
|
9609
|
+
void home;
|
|
9610
|
+
return result;
|
|
9611
|
+
}
|
|
9612
|
+
|
|
9613
|
+
// packages/core/src/license/TelemetryNotice.ts
|
|
9614
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
9615
|
+
import path32 from "path";
|
|
9616
|
+
import os6 from "os";
|
|
9011
9617
|
function noticePath(home) {
|
|
9012
|
-
return
|
|
9618
|
+
return path32.join(home ?? os6.homedir(), ".ctxloom", "telemetry_notice_shown");
|
|
9013
9619
|
}
|
|
9014
9620
|
function shouldShowTelemetryNotice(home) {
|
|
9015
9621
|
const filePath = noticePath(home);
|
|
9016
|
-
if (
|
|
9622
|
+
if (existsSync4(filePath)) return false;
|
|
9017
9623
|
try {
|
|
9018
|
-
|
|
9019
|
-
|
|
9624
|
+
mkdirSync4(path32.dirname(filePath), { recursive: true });
|
|
9625
|
+
writeFileSync4(filePath, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
|
|
9020
9626
|
} catch {
|
|
9021
9627
|
}
|
|
9022
9628
|
return true;
|
|
9023
9629
|
}
|
|
9024
9630
|
|
|
9025
9631
|
// packages/core/src/server/ProjectState.ts
|
|
9026
|
-
import
|
|
9632
|
+
import path34 from "path";
|
|
9027
9633
|
|
|
9028
9634
|
// packages/core/src/server/projectId.ts
|
|
9029
9635
|
import crypto5 from "crypto";
|
|
9030
|
-
import
|
|
9636
|
+
import path33 from "path";
|
|
9031
9637
|
function hashProjectRoot(absPath) {
|
|
9032
|
-
const canonical =
|
|
9638
|
+
const canonical = path33.resolve(absPath);
|
|
9033
9639
|
return crypto5.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
9034
9640
|
}
|
|
9035
9641
|
|
|
@@ -9037,7 +9643,7 @@ function hashProjectRoot(absPath) {
|
|
|
9037
9643
|
function createProjectState(projectRoot, opts = {}) {
|
|
9038
9644
|
return {
|
|
9039
9645
|
projectRoot,
|
|
9040
|
-
dbPath:
|
|
9646
|
+
dbPath: path34.join(projectRoot, ".ctxloom", "vectors.lancedb"),
|
|
9041
9647
|
pinned: opts.pinned ?? false,
|
|
9042
9648
|
lastTouchedAt: Date.now(),
|
|
9043
9649
|
vectorsInitialized: false,
|
|
@@ -9190,7 +9796,7 @@ var ProjectStateManager = class {
|
|
|
9190
9796
|
|
|
9191
9797
|
// packages/core/src/server/resolveProjectRoot.ts
|
|
9192
9798
|
import fs27 from "fs";
|
|
9193
|
-
import
|
|
9799
|
+
import path35 from "path";
|
|
9194
9800
|
var PATH_SEPARATOR_PATTERN = /[/\\~]|^[A-Za-z]:/;
|
|
9195
9801
|
function looksLikePath(value) {
|
|
9196
9802
|
return PATH_SEPARATOR_PATTERN.test(value);
|
|
@@ -9215,9 +9821,9 @@ function resolvePathSafely(p, cwd) {
|
|
|
9215
9821
|
let expanded = p;
|
|
9216
9822
|
if (p === "~" || p.startsWith("~/")) {
|
|
9217
9823
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9218
|
-
expanded = p === "~" ? home :
|
|
9824
|
+
expanded = p === "~" ? home : path35.join(home, p.slice(2));
|
|
9219
9825
|
}
|
|
9220
|
-
return
|
|
9826
|
+
return path35.isAbsolute(expanded) ? path35.resolve(expanded) : path35.resolve(cwd, expanded);
|
|
9221
9827
|
}
|
|
9222
9828
|
function realpathOrSame(p) {
|
|
9223
9829
|
try {
|
|
@@ -9289,7 +9895,7 @@ function validateDefaultRoot(candidate) {
|
|
|
9289
9895
|
} catch {
|
|
9290
9896
|
return false;
|
|
9291
9897
|
}
|
|
9292
|
-
return PROJECT_MARKERS.some((m) => fs27.existsSync(
|
|
9898
|
+
return PROJECT_MARKERS.some((m) => fs27.existsSync(path35.join(candidate, m)));
|
|
9293
9899
|
}
|
|
9294
9900
|
|
|
9295
9901
|
// packages/core/src/server/structuredErrors.ts
|
|
@@ -9436,17 +10042,19 @@ export {
|
|
|
9436
10042
|
ApiClient,
|
|
9437
10043
|
Fingerprint,
|
|
9438
10044
|
maybePrintExpiryWarning,
|
|
10045
|
+
getOrCreateDistinctId,
|
|
10046
|
+
markAliasSent,
|
|
10047
|
+
getTelemetryLevel,
|
|
10048
|
+
track,
|
|
10049
|
+
captureError,
|
|
10050
|
+
shouldEmitInstallCompleted,
|
|
10051
|
+
shouldEmitFirstReviewRun,
|
|
9439
10052
|
isActive,
|
|
9440
10053
|
requireActive,
|
|
9441
10054
|
getLicenseInfo,
|
|
9442
10055
|
activateLicense,
|
|
9443
10056
|
deactivateLicense,
|
|
9444
10057
|
startTrial,
|
|
9445
|
-
getOrCreateDistinctId,
|
|
9446
|
-
markAliasSent,
|
|
9447
|
-
getTelemetryLevel,
|
|
9448
|
-
track,
|
|
9449
|
-
captureError,
|
|
9450
10058
|
shouldShowTelemetryNotice,
|
|
9451
10059
|
hashProjectRoot,
|
|
9452
10060
|
createProjectState,
|
|
@@ -9464,4 +10072,4 @@ export {
|
|
|
9464
10072
|
FirstTouchTracker,
|
|
9465
10073
|
EmittedOnceTracker
|
|
9466
10074
|
};
|
|
9467
|
-
//# sourceMappingURL=chunk-
|
|
10075
|
+
//# sourceMappingURL=chunk-RY3JAC2Q.js.map
|