@colbymchenry/codegraph-darwin-x64 1.1.1 → 1.1.3
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/lib/dist/bin/codegraph.js +99 -59
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/command-supervision.d.ts +12 -0
- package/lib/dist/bin/command-supervision.d.ts.map +1 -0
- package/lib/dist/bin/command-supervision.js +76 -0
- package/lib/dist/bin/command-supervision.js.map +1 -0
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +25 -1
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +10 -2
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/db/schema.sql +11 -0
- package/lib/dist/directory.d.ts +32 -0
- package/lib/dist/directory.d.ts.map +1 -1
- package/lib/dist/directory.js +83 -0
- package/lib/dist/directory.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +13 -1
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +310 -218
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.d.ts +16 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +33 -0
- package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
- package/lib/dist/extraction/parse-pool.d.ts +126 -0
- package/lib/dist/extraction/parse-pool.d.ts.map +1 -0
- package/lib/dist/extraction/parse-pool.js +319 -0
- package/lib/dist/extraction/parse-pool.js.map +1 -0
- package/lib/dist/extraction/tree-sitter.d.ts +21 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +106 -21
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/mcp/daemon-paths.d.ts +30 -3
- package/lib/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-paths.js +50 -10
- package/lib/dist/mcp/daemon-paths.js.map +1 -1
- package/lib/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-registry.js +7 -3
- package/lib/dist/mcp/daemon-registry.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +48 -0
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +203 -32
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/engine.d.ts +17 -0
- package/lib/dist/mcp/engine.d.ts.map +1 -1
- package/lib/dist/mcp/engine.js +73 -1
- package/lib/dist/mcp/engine.js.map +1 -1
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +25 -43
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/ppid-watchdog.d.ts +18 -0
- package/lib/dist/mcp/ppid-watchdog.d.ts.map +1 -1
- package/lib/dist/mcp/ppid-watchdog.js +37 -0
- package/lib/dist/mcp/ppid-watchdog.js.map +1 -1
- package/lib/dist/mcp/query-pool.d.ts +94 -0
- package/lib/dist/mcp/query-pool.d.ts.map +1 -0
- package/lib/dist/mcp/query-pool.js +297 -0
- package/lib/dist/mcp/query-pool.js.map +1 -0
- package/lib/dist/mcp/query-worker.d.ts +24 -0
- package/lib/dist/mcp/query-worker.d.ts.map +1 -0
- package/lib/dist/mcp/query-worker.js +87 -0
- package/lib/dist/mcp/query-worker.js.map +1 -0
- package/lib/dist/mcp/tools.d.ts +57 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +196 -40
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/project-config.d.ts +20 -0
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +42 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +0 -28
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.js +765 -79
- package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +44 -0
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts +9 -0
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +40 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/types.d.ts +6 -1
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
package/lib/dist/mcp/tools.js
CHANGED
|
@@ -278,6 +278,12 @@ function numberSourceLines(slice, firstLineNumber) {
|
|
|
278
278
|
* (`reasoning/reasoner.ts`) both key off to cut on whole file sections.
|
|
279
279
|
*/
|
|
280
280
|
const FILE_SECTION_PREFIX = '**`';
|
|
281
|
+
// Placeholder for codegraph_explore's "Found N symbols across M files." line.
|
|
282
|
+
// The honest N/M can only be known after the final truncation drops trailing
|
|
283
|
+
// sections (#1046), so the header is emitted as this sentinel and substituted
|
|
284
|
+
// at the very end. This bracketed token never occurs in rendered source or a
|
|
285
|
+
// file path, so the final string-replace can't collide.
|
|
286
|
+
const SUMMARY_SENTINEL = '[[codegraph-explore-summary]]';
|
|
281
287
|
function fileSectionHeader(filePath, suffix) {
|
|
282
288
|
return suffix
|
|
283
289
|
? `${FILE_SECTION_PREFIX}${filePath}\`** — ${suffix}`
|
|
@@ -340,6 +346,23 @@ const projectPathProperty = {
|
|
|
340
346
|
type: 'string',
|
|
341
347
|
description: 'Absolute path to the project to query (or any directory inside it) — codegraph uses the nearest .codegraph/ index at or above that path. Omit to use this session\'s default project. Pass it to query a second codebase, or when the server root has no index of its own (e.g. a monorepo where only sub-projects are indexed, so there is no default project).',
|
|
342
348
|
};
|
|
349
|
+
/**
|
|
350
|
+
* EVERY codegraph tool is query-only: it reads the pre-built index and never
|
|
351
|
+
* mutates the workspace (indexing is the user's explicit CLI call, never the
|
|
352
|
+
* agent's). Advertising this read-only contract lets clients that gate on it run
|
|
353
|
+
* the tools where a possibly-mutating tool would be blocked — most concretely,
|
|
354
|
+
* Cursor's Ask mode, which rejects any MCP tool lacking `readOnlyHint: true`
|
|
355
|
+
* (issue #1018). `idempotentHint`: a repeated query has no additional effect.
|
|
356
|
+
* `openWorldHint: false`: the domain is the closed local index, not an open
|
|
357
|
+
* external world. Shared so the contract is declared once; a hypothetical
|
|
358
|
+
* mutating tool would simply not reference it.
|
|
359
|
+
*/
|
|
360
|
+
const READ_ONLY_ANNOTATIONS = {
|
|
361
|
+
readOnlyHint: true,
|
|
362
|
+
destructiveHint: false,
|
|
363
|
+
idempotentHint: true,
|
|
364
|
+
openWorldHint: false,
|
|
365
|
+
};
|
|
343
366
|
/**
|
|
344
367
|
* All CodeGraph MCP tools
|
|
345
368
|
*
|
|
@@ -374,6 +397,7 @@ exports.tools = [
|
|
|
374
397
|
},
|
|
375
398
|
required: ['query'],
|
|
376
399
|
},
|
|
400
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
377
401
|
},
|
|
378
402
|
{
|
|
379
403
|
name: 'codegraph_callers',
|
|
@@ -398,6 +422,7 @@ exports.tools = [
|
|
|
398
422
|
},
|
|
399
423
|
required: ['symbol'],
|
|
400
424
|
},
|
|
425
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
401
426
|
},
|
|
402
427
|
{
|
|
403
428
|
name: 'codegraph_callees',
|
|
@@ -422,6 +447,7 @@ exports.tools = [
|
|
|
422
447
|
},
|
|
423
448
|
required: ['symbol'],
|
|
424
449
|
},
|
|
450
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
425
451
|
},
|
|
426
452
|
{
|
|
427
453
|
name: 'codegraph_impact',
|
|
@@ -446,6 +472,7 @@ exports.tools = [
|
|
|
446
472
|
},
|
|
447
473
|
required: ['symbol'],
|
|
448
474
|
},
|
|
475
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
449
476
|
},
|
|
450
477
|
{
|
|
451
478
|
name: 'codegraph_node',
|
|
@@ -487,6 +514,7 @@ exports.tools = [
|
|
|
487
514
|
},
|
|
488
515
|
required: [],
|
|
489
516
|
},
|
|
517
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
490
518
|
},
|
|
491
519
|
{
|
|
492
520
|
name: 'codegraph_explore',
|
|
@@ -507,6 +535,7 @@ exports.tools = [
|
|
|
507
535
|
},
|
|
508
536
|
required: ['query'],
|
|
509
537
|
},
|
|
538
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
510
539
|
},
|
|
511
540
|
{
|
|
512
541
|
name: 'codegraph_status',
|
|
@@ -517,6 +546,7 @@ exports.tools = [
|
|
|
517
546
|
projectPath: projectPathProperty,
|
|
518
547
|
},
|
|
519
548
|
},
|
|
549
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
520
550
|
},
|
|
521
551
|
{
|
|
522
552
|
name: 'codegraph_files',
|
|
@@ -550,8 +580,40 @@ exports.tools = [
|
|
|
550
580
|
projectPath: projectPathProperty,
|
|
551
581
|
},
|
|
552
582
|
},
|
|
583
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
553
584
|
},
|
|
554
585
|
];
|
|
586
|
+
/**
|
|
587
|
+
* Return `defs` with `projectPath` marked `required` in each tool's inputSchema.
|
|
588
|
+
*
|
|
589
|
+
* Used for the NO-DEFAULT-PROJECT tool surface (issue #993): when the MCP server
|
|
590
|
+
* has no default project to fall back to — a gateway server started outside any
|
|
591
|
+
* repo, or a monorepo root whose `.codegraph/` indexes live only in sub-projects
|
|
592
|
+
* — every call MUST carry an explicit `projectPath`, so the schema should say so.
|
|
593
|
+
* A `required` field is a HIGH-salience channel (MCP clients surface and often
|
|
594
|
+
* validate it), unlike the instructions text the reporter found too weak to stop
|
|
595
|
+
* the agent omitting the param. When a default project IS open, callers leave
|
|
596
|
+
* projectPath optional and never call this.
|
|
597
|
+
*
|
|
598
|
+
* Pure: clones each tool's schema rather than mutating the shared module-level
|
|
599
|
+
* `tools` array (reused by every session and the static surface). A tool that
|
|
600
|
+
* doesn't expose projectPath, or already requires it, is returned untouched;
|
|
601
|
+
* explore's `['query']` becomes `['query', 'projectPath']`, and a tool with no
|
|
602
|
+
* `required` list (status/files) gains `['projectPath']`.
|
|
603
|
+
*/
|
|
604
|
+
function withRequiredProjectPath(defs) {
|
|
605
|
+
return defs.map((tool) => {
|
|
606
|
+
if (!tool.inputSchema.properties.projectPath)
|
|
607
|
+
return tool;
|
|
608
|
+
const required = tool.inputSchema.required ?? [];
|
|
609
|
+
if (required.includes('projectPath'))
|
|
610
|
+
return tool;
|
|
611
|
+
return {
|
|
612
|
+
...tool,
|
|
613
|
+
inputSchema: { ...tool.inputSchema, required: [...required, 'projectPath'] },
|
|
614
|
+
};
|
|
615
|
+
});
|
|
616
|
+
}
|
|
555
617
|
/**
|
|
556
618
|
* Allowlist-filtered tool definitions WITHOUT an engine — the static surface the
|
|
557
619
|
* proxy answers `tools/list` with before any project is open. Mirrors
|
|
@@ -607,9 +669,23 @@ class ToolHandler {
|
|
|
607
669
|
// huge repo can't hang the first call (#905); cleared on first await so
|
|
608
670
|
// subsequent calls don't pay any cost.
|
|
609
671
|
catchUpGate = null;
|
|
672
|
+
// Optional worker-thread pool for off-loop read-tool dispatch (daemon mode).
|
|
673
|
+
// When set + healthy, the heavy read tools run on a worker so the daemon's
|
|
674
|
+
// main loop stays free for the MCP transport under concurrent load. Null in
|
|
675
|
+
// direct/in-process mode (one client, no concurrency to parallelize).
|
|
676
|
+
queryPool = null;
|
|
610
677
|
constructor(cg) {
|
|
611
678
|
this.cg = cg;
|
|
612
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Engine-only: attach (or detach with null) the worker-thread query pool. The
|
|
682
|
+
* shared daemon sets this once its default project is open; the workers each
|
|
683
|
+
* hold their own WAL read connection and run {@link executeReadTool}. A
|
|
684
|
+
* worker's own ToolHandler never has a pool, so there is no nested off-loading.
|
|
685
|
+
*/
|
|
686
|
+
setQueryPool(pool) {
|
|
687
|
+
this.queryPool = pool;
|
|
688
|
+
}
|
|
613
689
|
/**
|
|
614
690
|
* Update the default CodeGraph instance (e.g. after lazy initialization)
|
|
615
691
|
*/
|
|
@@ -713,8 +789,18 @@ class ToolHandler {
|
|
|
713
789
|
let visible = allow
|
|
714
790
|
? exports.tools.filter(t => allow.has(t.name.replace(/^codegraph_/, '')))
|
|
715
791
|
: exports.tools.filter(t => DEFAULT_MCP_TOOLS.has(t.name.replace(/^codegraph_/, '')));
|
|
792
|
+
// No default project loaded → no-root-index case (#993): a gateway server
|
|
793
|
+
// started outside any repo, or a monorepo root whose indexes live in
|
|
794
|
+
// sub-projects. With nothing to fall back to, EVERY call needs an explicit
|
|
795
|
+
// projectPath, so mark it required in the schema — a high-salience nudge the
|
|
796
|
+
// agent acts on, where SERVER_INSTRUCTIONS_NO_ROOT_INDEX's prose alone
|
|
797
|
+
// wasn't enough (the reporter had to add an AGENTS.md note). `this.cg` is
|
|
798
|
+
// settled by `retryInitIfNeeded()` before `handleToolsList` calls us, so a
|
|
799
|
+
// null here means "genuinely no default", not a startup race. When a default
|
|
800
|
+
// IS open we leave projectPath optional (below): a bare call falls back to
|
|
801
|
+
// it, exactly as in the common single-project launch.
|
|
716
802
|
if (!this.cg)
|
|
717
|
-
return visible;
|
|
803
|
+
return withRequiredProjectPath(visible);
|
|
718
804
|
try {
|
|
719
805
|
const stats = this.cg.getStats();
|
|
720
806
|
const budget = getExploreBudget(stats.fileCount);
|
|
@@ -1113,43 +1199,26 @@ class ToolHandler {
|
|
|
1113
1199
|
if (typeof check === 'object' && check !== undefined)
|
|
1114
1200
|
return check;
|
|
1115
1201
|
}
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
//
|
|
1119
|
-
//
|
|
1120
|
-
//
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
switch (toolName) {
|
|
1124
|
-
case 'codegraph_search':
|
|
1125
|
-
result = await this.handleSearch(args);
|
|
1126
|
-
break;
|
|
1127
|
-
case 'codegraph_callers':
|
|
1128
|
-
result = await this.handleCallers(args);
|
|
1129
|
-
break;
|
|
1130
|
-
case 'codegraph_callees':
|
|
1131
|
-
result = await this.handleCallees(args);
|
|
1132
|
-
break;
|
|
1133
|
-
case 'codegraph_impact':
|
|
1134
|
-
result = await this.handleImpact(args);
|
|
1135
|
-
break;
|
|
1136
|
-
case 'codegraph_explore':
|
|
1137
|
-
result = await this.handleExplore(args);
|
|
1138
|
-
break;
|
|
1139
|
-
case 'codegraph_node':
|
|
1140
|
-
result = await this.handleNode(args);
|
|
1141
|
-
break;
|
|
1142
|
-
case 'codegraph_status':
|
|
1143
|
-
// status embeds the pending-files list as a first-class section
|
|
1144
|
-
// (see handleStatus), so we skip the auto-banner wrapper here to
|
|
1145
|
-
// avoid duplicating the same info at the top of the response.
|
|
1146
|
-
return await this.handleStatus(args);
|
|
1147
|
-
case 'codegraph_files':
|
|
1148
|
-
result = await this.handleFiles(args);
|
|
1149
|
-
break;
|
|
1150
|
-
default:
|
|
1151
|
-
return this.errorResult(`Unknown tool: ${toolName}`);
|
|
1202
|
+
// codegraph_status reports watcher state (pending files, degraded mode,
|
|
1203
|
+
// worktree warning) and embeds its own sections — it must run on the MAIN
|
|
1204
|
+
// thread against the watched default instance, so it is NEVER off-loaded to
|
|
1205
|
+
// a worker (whose read connection has no watcher). It also skips the
|
|
1206
|
+
// auto-banner wrapper to avoid duplicating its own pending-files section.
|
|
1207
|
+
if (toolName === 'codegraph_status') {
|
|
1208
|
+
return await this.handleStatus(args);
|
|
1152
1209
|
}
|
|
1210
|
+
// Read tools: off-load the CPU-heavy dispatch to the worker pool when one
|
|
1211
|
+
// is attached and healthy (daemon mode), so the daemon's single event loop
|
|
1212
|
+
// stays free for the MCP transport under concurrent load — otherwise N
|
|
1213
|
+
// concurrent explores serialize AND starve the transport until the whole
|
|
1214
|
+
// batch drains (clients then time out). With no pool (direct mode) or a
|
|
1215
|
+
// degraded one, dispatch runs in-process exactly as before. Either way the
|
|
1216
|
+
// result flows through the cross-cutting notices — worktree-index mismatch
|
|
1217
|
+
// (#155) and per-file staleness (#403) — which need the watched MAIN
|
|
1218
|
+
// instance and so are always applied here, never in the worker.
|
|
1219
|
+
const result = (this.queryPool && this.queryPool.healthy)
|
|
1220
|
+
? await this.queryPool.run(toolName, args)
|
|
1221
|
+
: await this.executeReadTool(toolName, args);
|
|
1153
1222
|
const withWorktree = this.withWorktreeNotice(result, args.projectPath);
|
|
1154
1223
|
return this.withStalenessNotice(withWorktree, args.projectPath);
|
|
1155
1224
|
}
|
|
@@ -1169,6 +1238,53 @@ class ToolHandler {
|
|
|
1169
1238
|
'continue without codegraph for this task.');
|
|
1170
1239
|
}
|
|
1171
1240
|
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Run a single read tool to completion and return its raw {@link ToolResult},
|
|
1243
|
+
* classifying expected failures the same way {@link execute}'s catch does so
|
|
1244
|
+
* the SHAPE is identical whether dispatch runs in-process or on a worker:
|
|
1245
|
+
* NotIndexed → success-shaped guidance, PathRefusal → clean error, anything
|
|
1246
|
+
* else → internal-error-with-retry. Never throws.
|
|
1247
|
+
*
|
|
1248
|
+
* This is the worker thread's entry point (see {@link ./query-worker}) and the
|
|
1249
|
+
* in-process fallback for {@link execute}. It deliberately does NOT run the
|
|
1250
|
+
* catch-up gate or the staleness/worktree notices — those need the daemon's
|
|
1251
|
+
* watched main instance and stay on the main thread. Cross-cutting allowlist +
|
|
1252
|
+
* path validation already ran in {@link execute} before routing here.
|
|
1253
|
+
*/
|
|
1254
|
+
async executeReadTool(toolName, args) {
|
|
1255
|
+
try {
|
|
1256
|
+
return await this.dispatchTool(toolName, args);
|
|
1257
|
+
}
|
|
1258
|
+
catch (err) {
|
|
1259
|
+
if (err instanceof NotIndexedError) {
|
|
1260
|
+
return this.textResult(err.message);
|
|
1261
|
+
}
|
|
1262
|
+
if (err instanceof PathRefusalError) {
|
|
1263
|
+
return this.errorResult(err.message);
|
|
1264
|
+
}
|
|
1265
|
+
return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
1266
|
+
'This is an internal codegraph error — retry the call once; if it persists, ' +
|
|
1267
|
+
'continue without codegraph for this task.');
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Pure dispatch over the read tools — the switch, with no gate, no notices, no
|
|
1272
|
+
* allowlist/validation (the caller owns those). `codegraph_status` is handled
|
|
1273
|
+
* on the main thread in {@link execute} and never reaches here. May throw
|
|
1274
|
+
* NotIndexed/PathRefusal, which {@link executeReadTool} classifies.
|
|
1275
|
+
*/
|
|
1276
|
+
async dispatchTool(toolName, args) {
|
|
1277
|
+
switch (toolName) {
|
|
1278
|
+
case 'codegraph_search': return await this.handleSearch(args);
|
|
1279
|
+
case 'codegraph_callers': return await this.handleCallers(args);
|
|
1280
|
+
case 'codegraph_callees': return await this.handleCallees(args);
|
|
1281
|
+
case 'codegraph_impact': return await this.handleImpact(args);
|
|
1282
|
+
case 'codegraph_explore': return await this.handleExplore(args);
|
|
1283
|
+
case 'codegraph_node': return await this.handleNode(args);
|
|
1284
|
+
case 'codegraph_files': return await this.handleFiles(args);
|
|
1285
|
+
default: return this.errorResult(`Unknown tool: ${toolName}`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1172
1288
|
/**
|
|
1173
1289
|
* Handle codegraph_search
|
|
1174
1290
|
*/
|
|
@@ -2607,9 +2723,16 @@ class ToolHandler {
|
|
|
2607
2723
|
const lines = [
|
|
2608
2724
|
`**Exploration: ${query}**`,
|
|
2609
2725
|
'',
|
|
2610
|
-
|
|
2726
|
+
// Curated summary — filled in after the source loop (see below). We do NOT
|
|
2727
|
+
// report `subgraph.nodes.size` / `fileGroups.size` here: that's the raw
|
|
2728
|
+
// candidate gather, which a broad natural-language query inflates wildly
|
|
2729
|
+
// (260 symbols / 124 files on a 636-file repo) even though only a handful
|
|
2730
|
+
// render. Reporting the pool read as "260 results to wade through" when the
|
|
2731
|
+
// real, correctly-ranked answer is the few files below (#1046).
|
|
2732
|
+
'',
|
|
2611
2733
|
'',
|
|
2612
2734
|
];
|
|
2735
|
+
const summaryLineIdx = 2;
|
|
2613
2736
|
// Blast radius (always-on, compact): for the entry symbols, who depends on
|
|
2614
2737
|
// them + which tests cover them — locations only, no source — so the agent
|
|
2615
2738
|
// knows what to update/verify before editing without a separate call.
|
|
@@ -2711,6 +2834,9 @@ class ToolHandler {
|
|
|
2711
2834
|
lines.push('');
|
|
2712
2835
|
let totalChars = lines.join('\n').length;
|
|
2713
2836
|
let filesIncluded = 0;
|
|
2837
|
+
// Paths we actually render source for below. Drives the curated header count
|
|
2838
|
+
// (#1046) — it must reflect what we show, not the raw candidate gather.
|
|
2839
|
+
const renderedFilePaths = [];
|
|
2714
2840
|
let anyFileTrimmed = false;
|
|
2715
2841
|
for (const [filePath, group] of sortedFiles) {
|
|
2716
2842
|
if (filesIncluded >= maxFiles)
|
|
@@ -2862,6 +2988,7 @@ class ToolHandler {
|
|
|
2862
2988
|
: 'skeleton (signatures only — codegraph_explore a name for its full body; do NOT Read)';
|
|
2863
2989
|
lines.push(fileSectionHeader(filePath, `${names} · ${tag}`), '', '```' + lang, skel.join('\n'), '```', '');
|
|
2864
2990
|
totalChars += skel.join('\n').length + 120;
|
|
2991
|
+
renderedFilePaths.push(filePath);
|
|
2865
2992
|
filesIncluded++;
|
|
2866
2993
|
continue;
|
|
2867
2994
|
}
|
|
@@ -2908,6 +3035,7 @@ class ToolHandler {
|
|
|
2908
3035
|
}
|
|
2909
3036
|
lines.push(wholeHeader, '', '```' + lang, wholeSection, '```', '');
|
|
2910
3037
|
totalChars += wholeSection.length + 200;
|
|
3038
|
+
renderedFilePaths.push(filePath);
|
|
2911
3039
|
filesIncluded++;
|
|
2912
3040
|
continue;
|
|
2913
3041
|
}
|
|
@@ -3196,8 +3324,14 @@ class ToolHandler {
|
|
|
3196
3324
|
lines.push('```');
|
|
3197
3325
|
lines.push('');
|
|
3198
3326
|
totalChars += fileSection.length + 200;
|
|
3327
|
+
renderedFilePaths.push(filePath);
|
|
3199
3328
|
filesIncluded++;
|
|
3200
3329
|
}
|
|
3330
|
+
// The curated header count is computed from the files that SURVIVE the final
|
|
3331
|
+
// truncation (see end of method) — `filesIncluded` can over-count when the
|
|
3332
|
+
// hard ceiling drops trailing sections — so leave a sentinel here and fill it
|
|
3333
|
+
// in once the output is final.
|
|
3334
|
+
lines[summaryLineIdx] = SUMMARY_SENTINEL;
|
|
3201
3335
|
// Add remaining files as references (from both relevant and peripheral files).
|
|
3202
3336
|
// Small projects (per budget) skip this — the relevant story already fits
|
|
3203
3337
|
// in the source section, and a trailing pointer list is pure overhead.
|
|
@@ -3254,6 +3388,7 @@ class ToolHandler {
|
|
|
3254
3388
|
// externalize territory.
|
|
3255
3389
|
const output = flow.text + lines.join('\n');
|
|
3256
3390
|
const hardCeiling = Math.min(Math.round(budget.maxOutputChars * 1.5), 25000);
|
|
3391
|
+
let finalText;
|
|
3257
3392
|
if (output.length > hardCeiling) {
|
|
3258
3393
|
// Cut at a FILE-SECTION boundary (the last ``**` `` file header before the
|
|
3259
3394
|
// ceiling) so we drop whole trailing file-sections rather than slicing
|
|
@@ -3264,9 +3399,30 @@ class ToolHandler {
|
|
|
3264
3399
|
const lastSection = cut.lastIndexOf('\n' + FILE_SECTION_PREFIX);
|
|
3265
3400
|
const boundary = lastSection > hardCeiling * 0.5 ? lastSection : cut.lastIndexOf('\n');
|
|
3266
3401
|
const safe = boundary > 0 ? cut.slice(0, boundary) : cut;
|
|
3267
|
-
|
|
3402
|
+
finalText = safe + '\n\n... (output truncated to budget; the source above is complete and verbatim — treat it as already Read. For any area not covered, run another codegraph_explore with the specific names — do NOT Read these files.)';
|
|
3268
3403
|
}
|
|
3269
|
-
|
|
3404
|
+
else {
|
|
3405
|
+
finalText = output;
|
|
3406
|
+
}
|
|
3407
|
+
// Curated header (#1046): substitute the sentinel with the count of files
|
|
3408
|
+
// whose source SURVIVES in the final text — not `subgraph`/`fileGroups` (the
|
|
3409
|
+
// raw gather a broad query inflates) and not `filesIncluded` (which can
|
|
3410
|
+
// over-count when the ceiling above drops trailing sections). A file counts
|
|
3411
|
+
// only if its section header is still present; its relevant (non-import)
|
|
3412
|
+
// symbols are summed for N. Files we couldn't fit are still named under "Not
|
|
3413
|
+
// shown above" + the budget note, so nothing is silently dropped.
|
|
3414
|
+
const survivors = renderedFilePaths.filter((fp) => finalText.includes(`${FILE_SECTION_PREFIX}${fp}\``));
|
|
3415
|
+
const shownSymbols = survivors.reduce((sum, fp) => {
|
|
3416
|
+
const g = fileGroups.get(fp);
|
|
3417
|
+
if (!g)
|
|
3418
|
+
return sum;
|
|
3419
|
+
return sum + new Set(g.nodes.filter((n) => n.kind !== 'import' && n.kind !== 'export').map((n) => n.id)).size;
|
|
3420
|
+
}, 0);
|
|
3421
|
+
const summaryLine = survivors.length > 0
|
|
3422
|
+
? `Found ${shownSymbols} symbol${shownSymbols === 1 ? '' : 's'} across ${survivors.length} file${survivors.length === 1 ? '' : 's'}.`
|
|
3423
|
+
: `Found ${subgraph.nodes.size} symbol${subgraph.nodes.size === 1 ? '' : 's'} across ${fileGroups.size} file${fileGroups.size === 1 ? '' : 's'}.`;
|
|
3424
|
+
finalText = finalText.replace(SUMMARY_SENTINEL, summaryLine);
|
|
3425
|
+
return this.textResult(finalText);
|
|
3270
3426
|
}
|
|
3271
3427
|
/**
|
|
3272
3428
|
* Handle codegraph_node
|