homegraph 1.5.10 → 1.6.1
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/CHANGELOG.md +27 -0
- package/README.md +64 -14
- package/dist/db/queries.d.ts +4 -0
- package/dist/db/queries.js +15 -0
- package/dist/extraction/grammars.d.ts +19 -0
- package/dist/extraction/grammars.js +44 -1
- package/dist/graph/evidence-paths.d.ts +2 -0
- package/dist/graph/evidence-paths.js +20 -3
- package/dist/mcp/arkts-evidence-packs.d.ts +17 -0
- package/dist/mcp/arkts-evidence-packs.js +219 -10
- package/dist/mcp/daemon.d.ts +21 -3
- package/dist/mcp/daemon.js +60 -6
- package/dist/mcp/engine.d.ts +26 -0
- package/dist/mcp/engine.js +143 -7
- package/dist/mcp/evidence-audit.d.ts +3 -0
- package/dist/mcp/evidence-audit.js +40 -0
- package/dist/mcp/implementation-context.d.ts +35 -0
- package/dist/mcp/implementation-context.js +232 -0
- package/dist/mcp/index-availability.d.ts +22 -0
- package/dist/mcp/index-availability.js +40 -1
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/indexable-root.d.ts +22 -0
- package/dist/mcp/indexable-root.js +140 -0
- package/dist/mcp/liveness-watchdog.d.ts +6 -1
- package/dist/mcp/liveness-watchdog.js +17 -5
- package/dist/mcp/locate-contract.d.ts +50 -0
- package/dist/mcp/locate-contract.js +146 -0
- package/dist/mcp/query-cache.d.ts +1 -15
- package/dist/mcp/query-cache.js +20 -11
- package/dist/mcp/request-evidence.d.ts +69 -0
- package/dist/mcp/request-evidence.js +297 -0
- package/dist/mcp/server-instructions.d.ts +2 -2
- package/dist/mcp/server-instructions.js +12 -9
- package/dist/mcp/session.js +15 -0
- package/dist/mcp/tools.d.ts +67 -12
- package/dist/mcp/tools.js +841 -134
- package/dist/project-map/index.d.ts +45 -0
- package/dist/project-map/index.js +371 -3
- package/dist/resolution/callback-synthesizer.js +251 -0
- package/dist/resolution/frameworks/arkts-entry.d.ts +35 -6
- package/dist/resolution/frameworks/arkts-entry.js +513 -30
- package/dist/resolution/index.js +25 -21
- package/dist/runtime-log.d.ts +52 -0
- package/dist/runtime-log.js +199 -0
- package/dist/search/literal-evidence.js +11 -3
- package/dist/search/query-plan-provider.js +15 -6
- package/dist/search/query-plan.d.ts +3 -1
- package/dist/search/query-plan.js +25 -16
- package/dist/search/query-utils.d.ts +13 -0
- package/dist/search/query-utils.js +64 -0
- package/dist/search/request-contract.d.ts +29 -0
- package/dist/search/request-contract.js +76 -0
- package/package.json +1 -1
package/dist/mcp/engine.js
CHANGED
|
@@ -44,7 +44,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
};
|
|
45
45
|
})();
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
exports.MCPEngine = void 0;
|
|
47
|
+
exports.parseDeferProbeMs = exports.isIndexableRoot = exports.MCPEngine = void 0;
|
|
48
48
|
exports.parseFixedWindowEnv = parseFixedWindowEnv;
|
|
49
49
|
exports.parseDebounceEnv = parseDebounceEnv;
|
|
50
50
|
const os = __importStar(require("os"));
|
|
@@ -57,13 +57,28 @@ const memory_budget_1 = require("./memory-budget");
|
|
|
57
57
|
const db_1 = require("../db");
|
|
58
58
|
const graph_sources_1 = require("../graph-sources");
|
|
59
59
|
const utils_1 = require("../utils");
|
|
60
|
+
const runtime_log_1 = require("../runtime-log");
|
|
61
|
+
const indexable_root_1 = require("./indexable-root");
|
|
60
62
|
// Lazy-load the heavy HomeGraph chain (sqlite + query/graph/context layers) OFF
|
|
61
63
|
// the MCP startup path. It's only needed once a tool actually opens a project —
|
|
62
64
|
// not to answer initialize/tools-list — so deferring it lets `serve mcp` (and
|
|
63
65
|
// the daemon it spawns) bind + register tools in ~Node-startup time instead of
|
|
64
66
|
// ~800ms, closing the "No such tool available" cold-start race that made headless
|
|
65
|
-
// agents flounder.
|
|
66
|
-
|
|
67
|
+
// agents flounder. Prefer dynamic import (vitest + CJS); sync require remains for
|
|
68
|
+
// the sync retry path after an async load has warmed the cache (or under dist/).
|
|
69
|
+
let homeGraphCtor = null;
|
|
70
|
+
const loadHomeGraph = async () => {
|
|
71
|
+
if (!homeGraphCtor) {
|
|
72
|
+
homeGraphCtor = (await Promise.resolve().then(() => __importStar(require('../index')))).default;
|
|
73
|
+
}
|
|
74
|
+
return homeGraphCtor;
|
|
75
|
+
};
|
|
76
|
+
const loadHomeGraphSync = () => {
|
|
77
|
+
if (homeGraphCtor)
|
|
78
|
+
return homeGraphCtor;
|
|
79
|
+
homeGraphCtor = require('../index').default;
|
|
80
|
+
return homeGraphCtor;
|
|
81
|
+
};
|
|
67
82
|
/**
|
|
68
83
|
* Shared MCP engine. Thread-safe in the sense that multiple sessions can
|
|
69
84
|
* call its methods concurrently — internally it serializes initialization
|
|
@@ -85,6 +100,13 @@ class MCPEngine {
|
|
|
85
100
|
// Off-loop read-tool pool (daemon mode only). Created lazily once the default
|
|
86
101
|
// project is open — workers each hold their own WAL read connection.
|
|
87
102
|
queryPool = null;
|
|
103
|
+
/**
|
|
104
|
+
* Spec 0049: auto-init deferred because the root was empty. Probe until
|
|
105
|
+
* indexable, then run the normal create-DB path once.
|
|
106
|
+
*/
|
|
107
|
+
deferredAutoInitRoot = null;
|
|
108
|
+
deferProbeTimer = null;
|
|
109
|
+
deferAutoInitPromise = null;
|
|
88
110
|
constructor(opts = {}) {
|
|
89
111
|
this.opts = { watch: opts.watch ?? true, queryPool: opts.queryPool ?? false, rootFloor: opts.rootFloor ?? null };
|
|
90
112
|
this.toolHandler = new tools_1.ToolHandler(null);
|
|
@@ -132,10 +154,42 @@ class MCPEngine {
|
|
|
132
154
|
getToolHandler() {
|
|
133
155
|
return this.toolHandler;
|
|
134
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Spec 0047: true while auto-init / full symbol build is in flight.
|
|
159
|
+
* Used by the daemon idle timer — never idle-exit mid-build when clients=0.
|
|
160
|
+
* Does not include routine watch/sync increments.
|
|
161
|
+
*/
|
|
162
|
+
isIndexBuildInProgress() {
|
|
163
|
+
if (!this.cg)
|
|
164
|
+
return false;
|
|
165
|
+
try {
|
|
166
|
+
const phase = this.cg.getBuildPhase();
|
|
167
|
+
return phase === 'building_fast' || phase === 'indexing';
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
135
173
|
/** Whether the default project's HomeGraph is open. */
|
|
136
174
|
hasDefaultHomeGraph() {
|
|
137
175
|
return this.toolHandler.hasDefaultHomeGraph();
|
|
138
176
|
}
|
|
177
|
+
/** Spec 0049: true while auto-init is waiting for an empty root to grow sources. */
|
|
178
|
+
isAutoInitDeferred() {
|
|
179
|
+
return this.deferredAutoInitRoot !== null && !this.toolHandler.hasDefaultHomeGraph();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Spec 0049: on tool call (or timer), re-check the deferred root and start
|
|
183
|
+
* the normal auto-init once it becomes indexable. Idempotent.
|
|
184
|
+
*/
|
|
185
|
+
async kickDeferredAutoInit() {
|
|
186
|
+
if (this.closed || this.toolHandler.hasDefaultHomeGraph())
|
|
187
|
+
return false;
|
|
188
|
+
const root = this.deferredAutoInitRoot;
|
|
189
|
+
if (!root)
|
|
190
|
+
return false;
|
|
191
|
+
return this.tryAutoInit(root);
|
|
192
|
+
}
|
|
139
193
|
/**
|
|
140
194
|
* Walk up from `searchFrom` to find the nearest `.homegraph/` and open it.
|
|
141
195
|
* Idempotent: concurrent callers share one in-flight init; subsequent
|
|
@@ -194,7 +248,7 @@ class MCPEngine {
|
|
|
194
248
|
catch { /* ignore */ }
|
|
195
249
|
this.cg = null;
|
|
196
250
|
}
|
|
197
|
-
this.cg =
|
|
251
|
+
this.cg = loadHomeGraphSync().openSync(resolvedRoot, { sources: mode });
|
|
198
252
|
this.projectPath = resolvedRoot;
|
|
199
253
|
this.toolHandler.setDefaultHomeGraph(this.cg);
|
|
200
254
|
this.startWatching();
|
|
@@ -213,6 +267,7 @@ class MCPEngine {
|
|
|
213
267
|
if (this.closed)
|
|
214
268
|
return;
|
|
215
269
|
this.closed = true;
|
|
270
|
+
this.clearDeferProbe();
|
|
216
271
|
// Detach + terminate the worker pool first so no tool call routes to a
|
|
217
272
|
// worker mid-teardown; outstanding pool calls resolve with graceful guidance.
|
|
218
273
|
this.toolHandler.setQueryPool(null);
|
|
@@ -252,7 +307,7 @@ class MCPEngine {
|
|
|
252
307
|
process.stderr.write(`[HomeGraph MCP] Graph sources=${mode} — skipping project/SDK open (tools return guidance).\n`);
|
|
253
308
|
return;
|
|
254
309
|
}
|
|
255
|
-
this.cg = await loadHomeGraph().open(resolvedRoot, { sources: mode });
|
|
310
|
+
this.cg = await (await loadHomeGraph()).open(resolvedRoot, { sources: mode });
|
|
256
311
|
this.toolHandler.setDefaultHomeGraph(this.cg);
|
|
257
312
|
this.healBuildPhase(this.cg);
|
|
258
313
|
this.startWatching();
|
|
@@ -282,11 +337,34 @@ class MCPEngine {
|
|
|
282
337
|
// Race: another process finished init before we entered — open, don't bail
|
|
283
338
|
// (Spec 0032). Returning false here left the session with no cg forever.
|
|
284
339
|
if ((0, directory_1.isInitialized)(root)) {
|
|
340
|
+
this.clearDeferProbe();
|
|
285
341
|
return this.openAfterAutoInitRace(root);
|
|
286
342
|
}
|
|
343
|
+
// Spec 0049: empty workspace — do not create `.homegraph/` (blocks
|
|
344
|
+
// in-place `devecocli create`). Probe later / on tool kick.
|
|
345
|
+
if (!(0, indexable_root_1.isIndexableRoot)(root)) {
|
|
346
|
+
this.armDeferProbe(root);
|
|
347
|
+
process.stderr.write(`[HomeGraph MCP] Auto-init deferred — empty root at ${root} ` +
|
|
348
|
+
`(no build-profile.json5 / indexable sources yet)\n`);
|
|
349
|
+
(0, runtime_log_1.logLifecycle)('auto-init.deferred', { projectRoot: root });
|
|
350
|
+
this.projectPath = root;
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
if (this.deferAutoInitPromise) {
|
|
354
|
+
return this.deferAutoInitPromise;
|
|
355
|
+
}
|
|
356
|
+
this.deferAutoInitPromise = this.runAutoInitCreate(root).finally(() => {
|
|
357
|
+
this.deferAutoInitPromise = null;
|
|
358
|
+
});
|
|
359
|
+
return this.deferAutoInitPromise;
|
|
360
|
+
}
|
|
361
|
+
/** Spec 0049: create DB + fast map + background full (shared by first hit and kick). */
|
|
362
|
+
async runAutoInitCreate(root) {
|
|
363
|
+
this.clearDeferProbe();
|
|
287
364
|
try {
|
|
288
365
|
process.stderr.write(`[HomeGraph MCP] Auto-init at ${root}\n`);
|
|
289
|
-
|
|
366
|
+
(0, runtime_log_1.logLifecycle)('auto-init.start', { projectRoot: root });
|
|
367
|
+
const HomeGraph = await loadHomeGraph();
|
|
290
368
|
// Create DB immediately so tools/open succeed; index in background.
|
|
291
369
|
const cg = await HomeGraph.init(root, { index: false });
|
|
292
370
|
// Pin empty state before returning so the first tool call cannot see `none`
|
|
@@ -312,6 +390,7 @@ class MCPEngine {
|
|
|
312
390
|
catch (err) {
|
|
313
391
|
const msg = err instanceof Error ? err.message : String(err);
|
|
314
392
|
process.stderr.write(`[HomeGraph MCP] Fast build failed: ${msg}\n`);
|
|
393
|
+
(0, runtime_log_1.logLifecycleError)('auto-init.fail', { projectRoot: root, phase: 'fast', msg });
|
|
315
394
|
}
|
|
316
395
|
this.startBackgroundFullBuild(cg);
|
|
317
396
|
})();
|
|
@@ -324,9 +403,51 @@ class MCPEngine {
|
|
|
324
403
|
}
|
|
325
404
|
const msg = err instanceof Error ? err.message : String(err);
|
|
326
405
|
process.stderr.write(`[HomeGraph MCP] Auto-init failed: ${msg}\n`);
|
|
406
|
+
(0, runtime_log_1.logLifecycleError)('auto-init.fail', { projectRoot: root, msg });
|
|
407
|
+
// Keep probing so a later scaffold (or retry) can still land.
|
|
408
|
+
if (!(0, directory_1.isInitialized)(root)) {
|
|
409
|
+
this.armDeferProbe(root);
|
|
410
|
+
}
|
|
327
411
|
return false;
|
|
328
412
|
}
|
|
329
413
|
}
|
|
414
|
+
/** Spec 0049: remember empty root and (optionally) poll until indexable. */
|
|
415
|
+
armDeferProbe(root) {
|
|
416
|
+
this.deferredAutoInitRoot = root;
|
|
417
|
+
if (this.deferProbeTimer)
|
|
418
|
+
return;
|
|
419
|
+
const ms = (0, indexable_root_1.parseDeferProbeMs)(process.env.HOMEGRAPH_DEFER_PROBE_MS);
|
|
420
|
+
if (ms <= 0)
|
|
421
|
+
return;
|
|
422
|
+
this.deferProbeTimer = setInterval(() => {
|
|
423
|
+
if (this.closed || this.toolHandler.hasDefaultHomeGraph()) {
|
|
424
|
+
this.clearDeferProbe();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const target = this.deferredAutoInitRoot;
|
|
428
|
+
if (!target) {
|
|
429
|
+
this.clearDeferProbe();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (!(0, indexable_root_1.isIndexableRoot)(target))
|
|
433
|
+
return;
|
|
434
|
+
void this.tryAutoInit(target).then((ok) => {
|
|
435
|
+
if (ok)
|
|
436
|
+
this.clearDeferProbe();
|
|
437
|
+
});
|
|
438
|
+
}, ms);
|
|
439
|
+
// Don't keep the process alive solely for the probe in direct mode.
|
|
440
|
+
if (typeof this.deferProbeTimer.unref === 'function') {
|
|
441
|
+
this.deferProbeTimer.unref();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
clearDeferProbe() {
|
|
445
|
+
this.deferredAutoInitRoot = null;
|
|
446
|
+
if (this.deferProbeTimer) {
|
|
447
|
+
clearInterval(this.deferProbeTimer);
|
|
448
|
+
this.deferProbeTimer = null;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
330
451
|
/**
|
|
331
452
|
* Open an index another process just created (Spec 0032 cold-start race).
|
|
332
453
|
* Brief retries cover the window between mkdir and db file create.
|
|
@@ -342,7 +463,7 @@ class MCPEngine {
|
|
|
342
463
|
}
|
|
343
464
|
try {
|
|
344
465
|
process.stderr.write(`[HomeGraph MCP] Auto-init raced; opening existing index at ${root}\n`);
|
|
345
|
-
this.cg = await loadHomeGraph().open(root, { sources: mode });
|
|
466
|
+
this.cg = await (await loadHomeGraph()).open(root, { sources: mode });
|
|
346
467
|
this.projectPath = root;
|
|
347
468
|
this.toolHandler.setDefaultHomeGraph(this.cg);
|
|
348
469
|
this.healBuildPhase(this.cg);
|
|
@@ -400,6 +521,14 @@ class MCPEngine {
|
|
|
400
521
|
startBackgroundFullBuild(cg) {
|
|
401
522
|
cg.setBuildPhase('indexing');
|
|
402
523
|
process.stderr.write('[HomeGraph MCP] Full build starting in-process (background)\n');
|
|
524
|
+
let projectRoot;
|
|
525
|
+
try {
|
|
526
|
+
projectRoot = cg.getProjectRoot();
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
projectRoot = this.projectPath ?? undefined;
|
|
530
|
+
}
|
|
531
|
+
(0, runtime_log_1.logLifecycle)('index.start', { projectRoot, via: 'auto-init' });
|
|
403
532
|
void cg
|
|
404
533
|
.indexAll()
|
|
405
534
|
.then((result) => {
|
|
@@ -420,16 +549,20 @@ class MCPEngine {
|
|
|
420
549
|
cg.setBuildPhase('full');
|
|
421
550
|
process.stderr.write(`[HomeGraph MCP] Full build complete — files=${files || '?'} nodes=${nodes}` +
|
|
422
551
|
(ok ? '\n' : ' (soft-fail but symbols present)\n'));
|
|
552
|
+
(0, runtime_log_1.logLifecycle)('index.done', { projectRoot, files, nodes });
|
|
553
|
+
(0, runtime_log_1.logLifecycle)('auto-init.done', { projectRoot, files, nodes });
|
|
423
554
|
}
|
|
424
555
|
else {
|
|
425
556
|
cg.setBuildPhase('fast');
|
|
426
557
|
process.stderr.write('[HomeGraph MCP] Full build finished with no symbols — staying on fast map\n');
|
|
558
|
+
(0, runtime_log_1.logLifecycle)('index.done', { projectRoot, files: 0, nodes: 0, note: 'no-symbols' });
|
|
427
559
|
}
|
|
428
560
|
this.startWatchingAfterAutoInit();
|
|
429
561
|
})
|
|
430
562
|
.catch((err) => {
|
|
431
563
|
const msg = err instanceof Error ? err.message : String(err);
|
|
432
564
|
process.stderr.write(`[HomeGraph MCP] Full build failed: ${msg}\n`);
|
|
565
|
+
(0, runtime_log_1.logLifecycleError)('index.fail', { projectRoot, msg });
|
|
433
566
|
try {
|
|
434
567
|
let nodes = 0;
|
|
435
568
|
try {
|
|
@@ -575,6 +708,9 @@ function parseFixedWindowEnv(raw) {
|
|
|
575
708
|
return undefined;
|
|
576
709
|
return n;
|
|
577
710
|
}
|
|
711
|
+
var indexable_root_2 = require("./indexable-root");
|
|
712
|
+
Object.defineProperty(exports, "isIndexableRoot", { enumerable: true, get: function () { return indexable_root_2.isIndexableRoot; } });
|
|
713
|
+
Object.defineProperty(exports, "parseDeferProbeMs", { enumerable: true, get: function () { return indexable_root_2.parseDeferProbeMs; } });
|
|
578
714
|
/**
|
|
579
715
|
* Parse and clamp the HOMEGRAPH_WATCH_DEBOUNCE_MS env override.
|
|
580
716
|
*
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Opt-in local evidence only, never an answer cache. Logging cannot break retrieval. */
|
|
2
|
+
export declare function recordEvidencePack(projectRoot: string, input: unknown, output: unknown): 'written' | 'disabled' | 'limit' | 'unavailable';
|
|
3
|
+
//# sourceMappingURL=evidence-audit.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recordEvidencePack = recordEvidencePack;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
/** Opt-in local evidence only, never an answer cache. Logging cannot break retrieval. */
|
|
8
|
+
function recordEvidencePack(projectRoot, input, output) {
|
|
9
|
+
if (process.env.HOMEGRAPH_ARKTS_EVIDENCE_LOG !== '1')
|
|
10
|
+
return 'disabled';
|
|
11
|
+
let fd;
|
|
12
|
+
try {
|
|
13
|
+
const target = (0, utils_1.validatePathWithinRoot)(projectRoot, '.homegraph/evidence-packs.jsonl');
|
|
14
|
+
if (!target)
|
|
15
|
+
return 'unavailable';
|
|
16
|
+
const record = Buffer.from(JSON.stringify({ id: (0, node_crypto_1.randomUUID)(), time: new Date().toISOString(),
|
|
17
|
+
stage: 'arkts_pack_before_host_wrapping', version: 1, input, output }) + '\n');
|
|
18
|
+
fd = (0, node_fs_1.openSync)(target, node_fs_1.constants.O_WRONLY | node_fs_1.constants.O_CREAT | node_fs_1.constants.O_APPEND | node_fs_1.constants.O_NONBLOCK | (node_fs_1.constants.O_NOFOLLOW ?? 0), 0o600);
|
|
19
|
+
if (!(0, node_fs_1.fstatSync)(fd).isFile())
|
|
20
|
+
return 'unavailable';
|
|
21
|
+
if ((0, node_fs_1.fstatSync)(fd).size + record.length > 16 * 1024 * 1024)
|
|
22
|
+
return 'limit';
|
|
23
|
+
let at = 0;
|
|
24
|
+
while (at < record.length) {
|
|
25
|
+
const written = (0, node_fs_1.writeSync)(fd, record, at);
|
|
26
|
+
if (!written)
|
|
27
|
+
return 'unavailable';
|
|
28
|
+
at += written;
|
|
29
|
+
}
|
|
30
|
+
return 'written';
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return 'unavailable';
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
if (fd !== undefined)
|
|
37
|
+
(0, node_fs_1.closeSync)(fd);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=evidence-audit.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Edge, Node } from '../types';
|
|
2
|
+
export declare const implementationContextEnabled: () => boolean;
|
|
3
|
+
export declare const controlEvidenceEnabled: () => boolean;
|
|
4
|
+
export interface ImplementationGraph {
|
|
5
|
+
getNode(id: string): Node | null | undefined;
|
|
6
|
+
getNodesInFile(file: string): Node[];
|
|
7
|
+
getOutgoingEdges(id: string): Edge[];
|
|
8
|
+
getIncomingEdges(id: string): Edge[];
|
|
9
|
+
}
|
|
10
|
+
export interface ImplementationGroup {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
nodes: Node[];
|
|
14
|
+
edges: Edge[];
|
|
15
|
+
notes: string[];
|
|
16
|
+
missing: string[];
|
|
17
|
+
sdk?: {
|
|
18
|
+
module: string;
|
|
19
|
+
version: string;
|
|
20
|
+
nodes: Node[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface SdkModule {
|
|
24
|
+
version: string;
|
|
25
|
+
nodes: Node[];
|
|
26
|
+
}
|
|
27
|
+
/** No fuzzy symbol lookup: only stored edges and the target file of an actual import.
|
|
28
|
+
* Source must be hash-verified by the caller before its relationships are expanded.
|
|
29
|
+
* Limits bound work independently of repository size; gaps survive budget selection.
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectImplementationContext(graph: ImplementationGraph, seeds: Node[], source: (node: Node) => string | undefined, sdkModule?: (module: string) => SdkModule | undefined, moduleBinding?: (importer: string, module: string) => {
|
|
32
|
+
relativePath: string;
|
|
33
|
+
witnesses: Node[];
|
|
34
|
+
} | undefined): ImplementationGroup[];
|
|
35
|
+
//# sourceMappingURL=implementation-context.d.ts.map
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.controlEvidenceEnabled = exports.implementationContextEnabled = void 0;
|
|
37
|
+
exports.collectImplementationContext = collectImplementationContext;
|
|
38
|
+
const evidence_paths_1 = require("../graph/evidence-paths");
|
|
39
|
+
const request_evidence_1 = require("./request-evidence");
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const implementationContextEnabled = () => process.env.HOMEGRAPH_ARKTS_IMPLEMENTATION_CONTEXT !== '0';
|
|
42
|
+
exports.implementationContextEnabled = implementationContextEnabled;
|
|
43
|
+
const controlEvidenceEnabled = () => process.env.HOMEGRAPH_ARKTS_CONTROL_EVIDENCE !== '0';
|
|
44
|
+
exports.controlEvidenceEnabled = controlEvidenceEnabled;
|
|
45
|
+
const DECLARATIONS = new Set(['class', 'struct', 'component', 'interface', 'function', 'method', 'field', 'property', 'type_alias', 'enum', 'variable', 'constant']);
|
|
46
|
+
const TYPES = new Set(['type_of', 'returns', 'instantiates']);
|
|
47
|
+
const at = (n) => `${n.filePath}:${n.startLine}`;
|
|
48
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
49
|
+
const uses = (code, name) => /^[\w$]+$/.test(name) && new RegExp(`(?<![\\w$])${esc(name)}(?![\\w$])`).test(code);
|
|
50
|
+
/** No fuzzy symbol lookup: only stored edges and the target file of an actual import.
|
|
51
|
+
* Source must be hash-verified by the caller before its relationships are expanded.
|
|
52
|
+
* Limits bound work independently of repository size; gaps survive budget selection.
|
|
53
|
+
*/
|
|
54
|
+
function collectImplementationContext(graph, seeds, source, sdkModule, moduleBinding) {
|
|
55
|
+
const groups = [];
|
|
56
|
+
const files = new Map();
|
|
57
|
+
const sdkCache = new Map();
|
|
58
|
+
const inFile = (file) => {
|
|
59
|
+
if (!files.has(file))
|
|
60
|
+
files.set(file, graph.getNodesInFile(file));
|
|
61
|
+
return files.get(file);
|
|
62
|
+
};
|
|
63
|
+
// Import edges may be absent for ArkAnalyzer type-only imports. Resolve the exact
|
|
64
|
+
// relative module path, including barrel exports, without searching other modules.
|
|
65
|
+
let moduleReads = 0;
|
|
66
|
+
const relativeDefinition = (importer, module, name, depth = 0) => {
|
|
67
|
+
if (!module.startsWith('.') || depth > 2 || moduleReads >= 12)
|
|
68
|
+
return [];
|
|
69
|
+
const base = path.posix.normalize(path.posix.join(path.posix.dirname(importer), module));
|
|
70
|
+
if (base === '..' || base.startsWith('../') || path.posix.isAbsolute(base))
|
|
71
|
+
return [];
|
|
72
|
+
const paths = /\.(?:ets|ts)$/.test(base) ? [base] : [base + '.ets', base + '.ts', `${base}/Index.ets`, `${base}/index.ets`, `${base}/index.ts`];
|
|
73
|
+
const available = paths.map(file => ({ file, nodes: inFile(file) })).filter(x => x.nodes.length);
|
|
74
|
+
if (available.length !== 1)
|
|
75
|
+
return [];
|
|
76
|
+
moduleReads++;
|
|
77
|
+
const { file, nodes } = available[0];
|
|
78
|
+
const matches = [...new Map(nodes.filter(n => DECLARATIONS.has(n.kind) && n.name === name)
|
|
79
|
+
.map(n => [`${n.filePath}:${n.startLine}:${n.name}`, n])).values()];
|
|
80
|
+
if (matches.length === 1) {
|
|
81
|
+
const body = source(matches[0]);
|
|
82
|
+
if (body && new RegExp(`\\bexport\\s+(?:declare\\s+)?(?:class|struct|interface|enum|type|function|const|let|var)\\s+${esc(name)}\\b`).test((0, request_evidence_1.lexEvidenceSource)(body).code))
|
|
83
|
+
return [[matches[0]]];
|
|
84
|
+
}
|
|
85
|
+
const fileNode = { ...nodes[0], id: `context-file:${file}`, name: file, qualifiedName: file, kind: 'file', startLine: 1, endLine: 1 };
|
|
86
|
+
const body = source(fileNode);
|
|
87
|
+
if (!body)
|
|
88
|
+
return [];
|
|
89
|
+
const out = [];
|
|
90
|
+
const exportCode = (0, request_evidence_1.lexEvidenceSource)(body);
|
|
91
|
+
if (!exportCode.valid)
|
|
92
|
+
return [];
|
|
93
|
+
for (const m of body.matchAll(/\bexport\s+(\*|\{[^}]+\})\s+from\s*['"]([^'"\r\n]+)['"]/g)) {
|
|
94
|
+
if (!exportCode.code.slice(m.index).startsWith('export'))
|
|
95
|
+
continue;
|
|
96
|
+
let original = name;
|
|
97
|
+
if (m[1] !== '*') {
|
|
98
|
+
const binding = m[1].slice(1, -1).split(',').map(x => x.trim().split(/\s+as\s+/))
|
|
99
|
+
.find(parts => (parts[1] ?? parts[0]) === name);
|
|
100
|
+
if (!binding)
|
|
101
|
+
continue;
|
|
102
|
+
original = binding[0];
|
|
103
|
+
}
|
|
104
|
+
for (const chain of relativeDefinition(file, m[2], original, depth + 1))
|
|
105
|
+
out.push([fileNode, ...chain]);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
};
|
|
109
|
+
const queue = seeds.slice(0, 6).map(n => ({ node: n, depth: 0 }));
|
|
110
|
+
const visited = new Set();
|
|
111
|
+
const seenEdges = new Set();
|
|
112
|
+
const seenImports = new Set();
|
|
113
|
+
let reads = 0;
|
|
114
|
+
while (queue.length && visited.size < 24 && reads < 48 && groups.length < 24) {
|
|
115
|
+
const { node, depth } = queue.shift();
|
|
116
|
+
if (visited.has(node.id) || node.filePath.startsWith('ohos-sdk:'))
|
|
117
|
+
continue;
|
|
118
|
+
visited.add(node.id);
|
|
119
|
+
const body = source(node);
|
|
120
|
+
if (!body)
|
|
121
|
+
continue;
|
|
122
|
+
const parsed = (0, request_evidence_1.lexEvidenceSource)(body);
|
|
123
|
+
if (!parsed.valid)
|
|
124
|
+
continue;
|
|
125
|
+
const own = inFile(node.filePath);
|
|
126
|
+
// Component → build is containment, not an invented runtime call.
|
|
127
|
+
const nested = own.filter(n => n.id !== node.id && n.startLine >= node.startLine && n.endLine <= node.endLine
|
|
128
|
+
&& n.kind === 'method' && n.name === 'build').slice(0, 2);
|
|
129
|
+
if (depth < 2)
|
|
130
|
+
queue.push(...nested.map(n => ({ node: n, depth: depth + 1 })));
|
|
131
|
+
const adjacent = [...graph.getOutgoingEdges(node.id), ...graph.getIncomingEdges(node.id)];
|
|
132
|
+
reads += 2;
|
|
133
|
+
for (const edge of adjacent.slice(0, 80)) {
|
|
134
|
+
if (groups.length >= 24)
|
|
135
|
+
break;
|
|
136
|
+
const scope = (0, evidence_paths_1.isArkuiScopeEdge)(edge);
|
|
137
|
+
const outgoing = edge.source === node.id;
|
|
138
|
+
if (!scope && !(outgoing && TYPES.has(edge.kind)))
|
|
139
|
+
continue;
|
|
140
|
+
const key = JSON.stringify([edge.source, edge.target, edge.kind, edge.metadata]);
|
|
141
|
+
if (seenEdges.has(key))
|
|
142
|
+
continue;
|
|
143
|
+
seenEdges.add(key);
|
|
144
|
+
const other = graph.getNode(outgoing ? edge.target : edge.source);
|
|
145
|
+
if (!other || other.filePath.startsWith('ohos-sdk:'))
|
|
146
|
+
continue;
|
|
147
|
+
groups.push({ id: `impl-edge:${groups.length}`, label: `${scope ? 'render/navigation scope' : 'type dependency'}: ${node.name} ↔ ${other.name}`,
|
|
148
|
+
nodes: [node, other], edges: [edge], missing: [], notes: scope
|
|
149
|
+
? ['Static wiring only; enclosing conditions and other shared users remain relevant.'] : [] });
|
|
150
|
+
// Do not fan out from a shared caller into its unrelated siblings.
|
|
151
|
+
if (outgoing && depth < 2)
|
|
152
|
+
queue.push({ node: other, depth: depth + 1 });
|
|
153
|
+
}
|
|
154
|
+
if (adjacent.length > 80)
|
|
155
|
+
groups.push({ id: `impl-limit:${node.id}`, label: 'context expansion limit', nodes: [node], edges: [],
|
|
156
|
+
notes: [], missing: [`additional relationships at ${at(node)}`] });
|
|
157
|
+
for (const imp of own.filter(n => n.kind === 'import').slice(0, 40)) {
|
|
158
|
+
if (groups.length >= 24 || seenImports.has(imp.id) || !uses(parsed.code, imp.name))
|
|
159
|
+
continue;
|
|
160
|
+
seenImports.add(imp.id);
|
|
161
|
+
const importSource = source(imp);
|
|
162
|
+
if (!importSource)
|
|
163
|
+
continue;
|
|
164
|
+
const importCode = (0, request_evidence_1.lexEvidenceSource)(importSource);
|
|
165
|
+
const match = [...importSource.matchAll(/\bimport\s+([\s\S]*?)\s+from\s*['"]([^'"\r\n]+)['"]/g)]
|
|
166
|
+
.find(m => importCode.valid && importCode.code.slice(m.index).startsWith('import'));
|
|
167
|
+
if (!match)
|
|
168
|
+
continue;
|
|
169
|
+
const clause = match[1];
|
|
170
|
+
const module = match[2];
|
|
171
|
+
const aliases = [...clause.matchAll(/([\w$]+)\s+as\s+([\w$]+)/g)];
|
|
172
|
+
const name = aliases.find(a => a[2] === imp.name)?.[1] ?? imp.name;
|
|
173
|
+
const group = { id: `impl-import:${imp.id}`, label: `import ${imp.name} from ${module}`,
|
|
174
|
+
nodes: [node, imp], edges: [], notes: [], missing: [] };
|
|
175
|
+
const links = graph.getOutgoingEdges(imp.id).filter(e => e.kind === 'imports');
|
|
176
|
+
reads++;
|
|
177
|
+
for (const link of links.slice(0, 4)) {
|
|
178
|
+
const target = graph.getNode(link.target);
|
|
179
|
+
if (!target || target.filePath.startsWith('ohos-sdk:'))
|
|
180
|
+
continue;
|
|
181
|
+
const pool = target.kind === 'file' ? inFile(target.filePath) : [target];
|
|
182
|
+
const matches = pool.filter(n => DECLARATIONS.has(n.kind) && n.name === name);
|
|
183
|
+
const unique = [...new Map(matches.map(n => [`${n.filePath}:${n.startLine}:${n.name}`, n])).values()];
|
|
184
|
+
if (unique.length === 1) {
|
|
185
|
+
group.nodes.push(unique[0]);
|
|
186
|
+
group.edges.push(link);
|
|
187
|
+
}
|
|
188
|
+
// Export declarations are evidence of the public boundary, never proof that every file-local name is exported.
|
|
189
|
+
const exports = pool.filter(n => n.kind === 'export' && (n.name === name || uses(n.signature ?? '', name))).slice(0, 3);
|
|
190
|
+
group.nodes.push(...exports);
|
|
191
|
+
}
|
|
192
|
+
if (group.nodes.length === 2) {
|
|
193
|
+
const binding = module.startsWith('.') ? undefined : moduleBinding?.(imp.filePath, module);
|
|
194
|
+
const chains = relativeDefinition(imp.filePath, binding?.relativePath ?? module, name);
|
|
195
|
+
if (chains.length === 1)
|
|
196
|
+
group.nodes.push(...(binding?.witnesses ?? []), ...chains[0]);
|
|
197
|
+
else if (chains.length > 1)
|
|
198
|
+
group.missing.push(`ambiguous export ${name} from ${module}`);
|
|
199
|
+
}
|
|
200
|
+
if (group.nodes.length === 2 && /^@(?:kit|ohos|hms)\./.test(module) && sdkModule) {
|
|
201
|
+
if (!sdkCache.has(module))
|
|
202
|
+
sdkCache.set(module, sdkModule(module));
|
|
203
|
+
const sdk = sdkCache.get(module);
|
|
204
|
+
if (sdk) {
|
|
205
|
+
const memberNames = new Set([name]);
|
|
206
|
+
for (const m of parsed.code.matchAll(new RegExp(`\\b${esc(imp.name)}\\s*\\.\\s*([\\w$]+)`, 'g')))
|
|
207
|
+
memberNames.add(m[1]);
|
|
208
|
+
const sdkNodes = sdk.nodes.filter(n => n.signature && memberNames.has(n.name));
|
|
209
|
+
// Complete parameter objects are a group: include declared fields of referenced types.
|
|
210
|
+
const signature = sdkNodes.map(n => n.signature).join('\n');
|
|
211
|
+
const types = sdk.nodes.filter(n => ['interface', 'class', 'type_alias', 'enum'].includes(n.kind) && uses(signature, n.name));
|
|
212
|
+
const selected = [...new Map([...sdkNodes, ...types, ...sdk.nodes.filter(n => types.some(t => n.filePath === t.filePath && n.startLine >= t.startLine && n.endLine <= t.endLine))].map(n => [n.id, n])).values()];
|
|
213
|
+
if (selected.length && selected.length <= 32) {
|
|
214
|
+
group.sdk = { module, version: sdk.version, nodes: selected };
|
|
215
|
+
group.notes.push('Bounded SDK signature bundle; inherited/indirect types, API availability and runtime requirements remain unverified.');
|
|
216
|
+
}
|
|
217
|
+
else
|
|
218
|
+
group.missing.push(`SDK signature/type closure for ${module}.${name}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (group.nodes.length === 2 && !group.sdk)
|
|
222
|
+
group.missing.push(`resolved definition/export of ${imp.name} from ${module} (${at(imp)})`);
|
|
223
|
+
group.notes.push('Import/definition evidence does not prove module installation or public export availability.');
|
|
224
|
+
groups.push(group);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (queue.length)
|
|
228
|
+
groups.push({ id: 'impl-limit', label: 'context expansion limit', nodes: [], edges: [], notes: [],
|
|
229
|
+
missing: [at(queue[0].node)] });
|
|
230
|
+
return groups;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=implementation-context.js.map
|
|
@@ -28,4 +28,26 @@ export declare function resolveProductIndexState(cg: HomeGraph): ProductIndexSta
|
|
|
28
28
|
export declare function isSqliteBusyMessage(message: string): boolean;
|
|
29
29
|
/** True when text is already a pure (or leading) HomeGraph status line. */
|
|
30
30
|
export declare function textAlreadyHasProductStatus(text: string): boolean;
|
|
31
|
+
/** Marker line for Spec 0038 project-root path hint (idempotent prepend). */
|
|
32
|
+
export declare const PROJECT_ROOT_HINT_MARKER = "HomeGraph project root:";
|
|
33
|
+
/**
|
|
34
|
+
* Short preamble: absolute project root + how to join repo-relative paths (Spec 0038).
|
|
35
|
+
* `absRoot` should already be resolved (platform-native absolute path).
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatProjectRootPathHint(absRoot: string): string;
|
|
38
|
+
/** True when text already carries a Spec 0038 project-root hint. */
|
|
39
|
+
export declare function textAlreadyHasProjectRootHint(text: string): boolean;
|
|
40
|
+
/** Marker for Spec 0043 bound-root projectPath soft-pin notice (idempotent prepend). */
|
|
41
|
+
export declare const BOUND_PROJECT_PATH_PIN_MARKER = "This MCP session is bound to";
|
|
42
|
+
/**
|
|
43
|
+
* English preamble when a tool `projectPath` resolves to a different index root
|
|
44
|
+
* than the MCP session's default bound root (Spec 0043). Success-shaped — not isError.
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatBoundProjectPathPinNotice(opts: {
|
|
47
|
+
boundRoot: string;
|
|
48
|
+
requestedPath: string;
|
|
49
|
+
resolvedRoot: string | null;
|
|
50
|
+
}): string;
|
|
51
|
+
/** True when text already carries a Spec 0043 pin notice. */
|
|
52
|
+
export declare function textAlreadyHasBoundProjectPathPinNotice(text: string): boolean;
|
|
31
53
|
//# sourceMappingURL=index-availability.d.ts.map
|
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
* pending dirty files) to the five states hosts and agents should see.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.PRODUCT_STATUS_GLOSSARY = void 0;
|
|
9
|
+
exports.BOUND_PROJECT_PATH_PIN_MARKER = exports.PROJECT_ROOT_HINT_MARKER = exports.PRODUCT_STATUS_GLOSSARY = void 0;
|
|
10
10
|
exports.productIndexGuidance = productIndexGuidance;
|
|
11
11
|
exports.formatProductStatusLine = formatProductStatusLine;
|
|
12
12
|
exports.resolveProductIndexState = resolveProductIndexState;
|
|
13
13
|
exports.isSqliteBusyMessage = isSqliteBusyMessage;
|
|
14
14
|
exports.textAlreadyHasProductStatus = textAlreadyHasProductStatus;
|
|
15
|
+
exports.formatProjectRootPathHint = formatProjectRootPathHint;
|
|
16
|
+
exports.textAlreadyHasProjectRootHint = textAlreadyHasProjectRootHint;
|
|
17
|
+
exports.formatBoundProjectPathPinNotice = formatBoundProjectPathPinNotice;
|
|
18
|
+
exports.textAlreadyHasBoundProjectPathPinNotice = textAlreadyHasBoundProjectPathPinNotice;
|
|
15
19
|
/** One-line glossary for MCP initialize / tool surface (Spec 0035). */
|
|
16
20
|
exports.PRODUCT_STATUS_GLOSSARY = 'status: empty=not ready · fast=map only (homegraph_project) · full=fresh · dirty=usable but listed paths outdated · syncing=write lock, retry';
|
|
17
21
|
const MAX_DIRTY_PATHS = 5;
|
|
@@ -102,4 +106,39 @@ function isSqliteBusyMessage(message) {
|
|
|
102
106
|
function textAlreadyHasProductStatus(text) {
|
|
103
107
|
return /^\s*HomeGraph status=/m.test(text);
|
|
104
108
|
}
|
|
109
|
+
/** Marker line for Spec 0038 project-root path hint (idempotent prepend). */
|
|
110
|
+
exports.PROJECT_ROOT_HINT_MARKER = 'HomeGraph project root:';
|
|
111
|
+
/**
|
|
112
|
+
* Short preamble: absolute project root + how to join repo-relative paths (Spec 0038).
|
|
113
|
+
* `absRoot` should already be resolved (platform-native absolute path).
|
|
114
|
+
*/
|
|
115
|
+
function formatProjectRootPathHint(absRoot) {
|
|
116
|
+
const root = absRoot.trim();
|
|
117
|
+
return (`${exports.PROJECT_ROOT_HINT_MARKER} \`${root}\`\n` +
|
|
118
|
+
'Paths below are repo-relative to that root. Pass them to Read/Grep as-is, or join as `<root>/<relative>` (use `/`). Do not invent experiment/result directory prefixes.');
|
|
119
|
+
}
|
|
120
|
+
/** True when text already carries a Spec 0038 project-root hint. */
|
|
121
|
+
function textAlreadyHasProjectRootHint(text) {
|
|
122
|
+
return text.includes(exports.PROJECT_ROOT_HINT_MARKER);
|
|
123
|
+
}
|
|
124
|
+
/** Marker for Spec 0043 bound-root projectPath soft-pin notice (idempotent prepend). */
|
|
125
|
+
exports.BOUND_PROJECT_PATH_PIN_MARKER = 'This MCP session is bound to';
|
|
126
|
+
/**
|
|
127
|
+
* English preamble when a tool `projectPath` resolves to a different index root
|
|
128
|
+
* than the MCP session's default bound root (Spec 0043). Success-shaped — not isError.
|
|
129
|
+
*/
|
|
130
|
+
function formatBoundProjectPathPinNotice(opts) {
|
|
131
|
+
const bound = opts.boundRoot.trim();
|
|
132
|
+
const requested = opts.requestedPath.trim();
|
|
133
|
+
const resolved = opts.resolvedRoot === null
|
|
134
|
+
? 'no .homegraph/ found walking up from it'
|
|
135
|
+
: `resolves to \`${opts.resolvedRoot.trim()}\``;
|
|
136
|
+
return (`${exports.BOUND_PROJECT_PATH_PIN_MARKER} \`${bound}\`.\n` +
|
|
137
|
+
`Ignoring projectPath=\`${requested}\` (${resolved}).\n` +
|
|
138
|
+
'Results below are from the bound project root only. Omit projectPath, or pass a path under that root.');
|
|
139
|
+
}
|
|
140
|
+
/** True when text already carries a Spec 0043 pin notice. */
|
|
141
|
+
function textAlreadyHasBoundProjectPathPinNotice(text) {
|
|
142
|
+
return text.includes(exports.BOUND_PROJECT_PATH_PIN_MARKER);
|
|
143
|
+
}
|
|
105
144
|
//# sourceMappingURL=index-availability.js.map
|