homegraph 1.5.10 → 1.6.0
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 +18 -0
- package/dist/extraction/grammars.d.ts +19 -0
- package/dist/extraction/grammars.js +44 -1
- package/dist/mcp/arkts-evidence-packs.js +1 -0
- 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/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/server-instructions.d.ts +2 -2
- package/dist/mcp/server-instructions.js +7 -4
- package/dist/mcp/session.js +15 -0
- package/dist/mcp/tools.d.ts +49 -2
- package/dist/mcp/tools.js +714 -64
- 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/query-plan-provider.js +3 -2
- package/dist/search/query-plan.js +21 -19
- package/dist/search/query-utils.d.ts +13 -0
- package/dist/search/query-utils.js +64 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,23 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
## [1.6.0] - 2026-09-23
|
|
14
|
+
|
|
15
|
+
### Improvements
|
|
16
|
+
|
|
17
|
+
- MCP auto-init (Spec 0049): empty workspace roots no longer create `.homegraph/` on `--auto-init` / `HOMEGRAPH_AUTO_INIT` (fixes DevEco in-place `devecocli create` hitting `PROJECT_EXISTS` because only an index dir existed). A 60s probe (`HOMEGRAPH_DEFER_PROBE_MS`; `0` = tool-kick only) and the first `homegraph_*` call re-check `isIndexableRoot` (root `build-profile.json5` or any indexable source); once non-empty, the existing fast-map + background full index path runs once. Status footers stay the product empty/fast/full/dirty/syncing lines. Lifecycle logging no longer mkdir's `.homegraph/` just to write `daemon.log`.
|
|
18
|
+
- Harmony locate seams (Spec 0048): index `form_config.json` / `shortcuts_config.json` (plus module.json5 form extensionAbilities and shortcuts/form metadata) as searchable capability nodes — explore may lead with **Capability profiles**, or an explicit no-in-repo form note instead of SDK `.d.ts`. Resource hits can append in-repo `$r('app.string.<key>')` **bound** `.ets` anchors. `homegraph_project` adds a **Module roster** (oh-package + local `file:` deps) and lists capability profile paths separately from rawfile. Explore may prepend **Seam notes** for nearby profiles and empty/log-only stubs.
|
|
19
|
+
- MCP product lifecycle (Spec 0047): main-thread liveness watchdog is **off by default** (enable with `HOMEGRAPH_WATCHDOG=1`; `HOMEGRAPH_NO_WATCHDOG=1` still forces off) so concurrent DevEco init no longer gets false "Main thread unresponsive" kills — file watch / incremental sync unchanged. Shared daemon idle linger defaults to **60s** after the last client disconnects; if auto-init/full index is still in `building_fast`/`indexing`, idle exit waits until that build settles, then starts the 60s linger. While any client is connected the daemon never idle-exits.
|
|
20
|
+
- MCP runtime log (Spec 0046): always-on coarse lifecycle lines (`mcp.start` / `mcp.initialize` / `auto-init.*` / `index.*` / `watcher.degraded`) go to stderr and `.homegraph/daemon.log` (same file daemon stdio already uses); soft-rotate at ~5 MiB to `daemon.log.1`. Set `HOMEGRAPH_DEBUG=1` for per-tool summaries (`tool.<name>` with duration / error / truncated query). No new `logs/` directory; default is not a full request ledger.
|
|
21
|
+
- ArkUI index-time edge synthesis (Spec 0045): `build` / `@Builder` bodies that call another in-repo `@Component` / `@Builder` gain `arkui-child` edges (ambiguous names dropped); literal `pushPathByName` / `replacePathByName` / `pushNamedRoute({ name })` link to indexed `route` nodes as `arkui-named-nav`. Complements existing `arkui-route` (`pushUrl`) and Spec 0039 route_map edges — no query-time synthesis.
|
|
22
|
+
- Explore locate contract (Spec 0044): Chinese/UI **literal witness** files stay in the first pack and rank ahead of Logger/hilog/Toast spines unless the query names those APIs; **Located** / **Partial** / **Miss** banners (Partial next-step → `node`/`usages`/`search`, no same-name Grep); Miss omits source bodies and lists fuzzy paths; filename≠primary-declaration risk note; `homegraph_node` for symbols already on the explore locate list bypasses the Partial depth-fuse cap. Dynamic-edge synthesis (10.3) remains deferred.
|
|
23
|
+
- When an MCP session already has a bound default project (`--path` / opened root), a tool `projectPath` that resolves to a **different** `.homegraph/` index root is soft-pinned: HomeGraph keeps answering from the bound root and prepends a short English notice (not `isError`). Same-root nested paths are unchanged; sessions with no default project still open cross-project indexes (Spec 0043).
|
|
24
|
+
- In-repo locate for explore: `taskContext` no longer enters the lexical/FTS query string (quoted literals from it still merge); PascalCase identifiers (≥8 chars) seed exact file/symbol roots like `Foo.ets`; business-oriented queries default `sourceScope=local` so SDK `.d.ts` stubs do not become roots; MCP trails / used-by / evidence packs hide `@dummyFile` and `%AM*` noise. `homegraph_project` adds a bounded HarmonyOS **resources path inventory** (string.json / rawfile / media dir counts / on-disk modules with zero indexed sources) — paths only, no JSON bodies or edges (Spec 0042).
|
|
25
|
+
- Harmony `resources/**/element/string.json` is indexed as searchable `constant` nodes (key + UI value in FTS). Explore may lead with a short **Resource hits** table and a `$r('app.string.<key>')` Grep hint — **no** graph edges to controls. Color/media/rawfile stay out of scope (Spec 0041).
|
|
26
|
+
- `homegraph_project` on Harmony repos now prints a **skeleton summary**: `bundle` from `app.json5`, a note that modules come from `build-profile.json5`, per-module `route profile:` paths (`route_map` / `router_map` / `main_pages`), and `oh-package` names when present. Still no call edges or profile JSON bodies — use explore for route→page (Spec 0039) and Read to edit. Tool descriptions and MCP initialize instructions spell out project vs explore (Spec 0040).
|
|
27
|
+
- Harmony `route_map.json` / `router_map.json` / `main_pages.json` are indexed (basename allowlist) and linked into the graph as `route` nodes → page / `buildFunction` symbols. Edges carry `synthesizedBy: arkts-route-map` and `registeredAt` pointing at the config file so explore/trails show the JSON wiring site. When an explore query mentions those filenames, replies lead with a short **Registration sources** table — evidence from those profiles, reducing blind re-Read of the JSON (Spec 0039).
|
|
28
|
+
- MCP tool replies that know the indexed project now begin with `HomeGraph project root: \`<absolute>\`` plus a one-line join rule: repo-relative paths below that root should be passed to Read/Grep as-is (or `<root>/<relative>` with `/`), without inventing experiment/result prefixes. MCP initialize instructions mention the same join base. Indexed path storage stays repo-relative (Spec 0038).
|
|
29
|
+
|
|
13
30
|
## [1.5.10] - 2026-09-20
|
|
14
31
|
|
|
15
32
|
### Fixes
|
|
@@ -256,3 +273,4 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
256
273
|
[1.5.7]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.5.7
|
|
257
274
|
[1.5.8]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.5.8
|
|
258
275
|
[1.5.10]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.5.10
|
|
276
|
+
[1.6.0]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.0
|
|
@@ -45,6 +45,25 @@ export declare function isErlangAppFile(filePath: string): boolean;
|
|
|
45
45
|
export declare function isPlayRoutesFile(filePath: string): boolean;
|
|
46
46
|
/** HarmonyOS module manifest — parsed by the arkts-entry framework extractor. */
|
|
47
47
|
export declare function isArkModuleJson5(filePath: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Harmony Navigation / pages profile JSON (Spec 0039) — basename allowlist only.
|
|
50
|
+
* Parsed by arkts-entry; not general `.json` indexing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isHarmonyRouteProfileJson(filePath: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Harmony element string resources (Spec 0041) — path allowlist only.
|
|
55
|
+
* `…/resources/…/element/string.json` (not bare string.json, not color/media).
|
|
56
|
+
* Indexed as yaml file-level + arkts-entry constants for FTS; no graph edges.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isHarmonyElementStringJson(filePath: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Harmony capability profiles (Spec 0048) — basename allowlist only.
|
|
61
|
+
* `form_config.json` (service widgets) and `shortcuts_config.json` (home-screen shortcuts).
|
|
62
|
+
* Indexed as yaml + arkts-entry constants; no UI call edges.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isHarmonyCapabilityProfileJson(filePath: string): boolean;
|
|
65
|
+
/** Config files that host ArkTS `route` nodes (module manifest + Spec 0039 profiles). */
|
|
66
|
+
export declare function isHarmonyRouteConfigFile(filePath: string): boolean;
|
|
48
67
|
/**
|
|
49
68
|
* Initialize the tree-sitter WASM runtime. Must be called before loading grammars.
|
|
50
69
|
* Does NOT load any grammar WASM files — use loadGrammarsForLanguages() for that.
|
|
@@ -46,6 +46,10 @@ exports.isShopifyLiquidJson = isShopifyLiquidJson;
|
|
|
46
46
|
exports.isErlangAppFile = isErlangAppFile;
|
|
47
47
|
exports.isPlayRoutesFile = isPlayRoutesFile;
|
|
48
48
|
exports.isArkModuleJson5 = isArkModuleJson5;
|
|
49
|
+
exports.isHarmonyRouteProfileJson = isHarmonyRouteProfileJson;
|
|
50
|
+
exports.isHarmonyElementStringJson = isHarmonyElementStringJson;
|
|
51
|
+
exports.isHarmonyCapabilityProfileJson = isHarmonyCapabilityProfileJson;
|
|
52
|
+
exports.isHarmonyRouteConfigFile = isHarmonyRouteConfigFile;
|
|
49
53
|
exports.initGrammars = initGrammars;
|
|
50
54
|
exports.vendoredWasmFilenames = vendoredWasmFilenames;
|
|
51
55
|
exports.readGrammarWasmBytes = readGrammarWasmBytes;
|
|
@@ -235,6 +239,12 @@ function isSourceFile(filePath, overrides) {
|
|
|
235
239
|
return true; // Play `conf/routes` is extensionless
|
|
236
240
|
if (isArkModuleJson5(filePath))
|
|
237
241
|
return true;
|
|
242
|
+
if (isHarmonyRouteProfileJson(filePath))
|
|
243
|
+
return true; // Spec 0039
|
|
244
|
+
if (isHarmonyElementStringJson(filePath))
|
|
245
|
+
return true; // Spec 0041
|
|
246
|
+
if (isHarmonyCapabilityProfileJson(filePath))
|
|
247
|
+
return true; // Spec 0048
|
|
238
248
|
if (isShopifyLiquidJson(filePath))
|
|
239
249
|
return true; // Shopify OS 2.0 JSON templates / section groups
|
|
240
250
|
if (isErlangAppFile(filePath))
|
|
@@ -280,6 +290,35 @@ function isPlayRoutesFile(filePath) {
|
|
|
280
290
|
function isArkModuleJson5(filePath) {
|
|
281
291
|
return filePath.endsWith('module.json5');
|
|
282
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Harmony Navigation / pages profile JSON (Spec 0039) — basename allowlist only.
|
|
295
|
+
* Parsed by arkts-entry; not general `.json` indexing.
|
|
296
|
+
*/
|
|
297
|
+
function isHarmonyRouteProfileJson(filePath) {
|
|
298
|
+
const base = filePath.replace(/\\/g, '/').split('/').pop()?.toLowerCase() ?? '';
|
|
299
|
+
return base === 'route_map.json' || base === 'router_map.json' || base === 'main_pages.json';
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Harmony element string resources (Spec 0041) — path allowlist only.
|
|
303
|
+
* `…/resources/…/element/string.json` (not bare string.json, not color/media).
|
|
304
|
+
* Indexed as yaml file-level + arkts-entry constants for FTS; no graph edges.
|
|
305
|
+
*/
|
|
306
|
+
function isHarmonyElementStringJson(filePath) {
|
|
307
|
+
return /(?:^|\/)resources\/(?:[^/]+\/)*element\/string\.json$/i.test(filePath.replace(/\\/g, '/'));
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Harmony capability profiles (Spec 0048) — basename allowlist only.
|
|
311
|
+
* `form_config.json` (service widgets) and `shortcuts_config.json` (home-screen shortcuts).
|
|
312
|
+
* Indexed as yaml + arkts-entry constants; no UI call edges.
|
|
313
|
+
*/
|
|
314
|
+
function isHarmonyCapabilityProfileJson(filePath) {
|
|
315
|
+
const base = filePath.replace(/\\/g, '/').split('/').pop()?.toLowerCase() ?? '';
|
|
316
|
+
return base === 'form_config.json' || base === 'shortcuts_config.json';
|
|
317
|
+
}
|
|
318
|
+
/** Config files that host ArkTS `route` nodes (module manifest + Spec 0039 profiles). */
|
|
319
|
+
function isHarmonyRouteConfigFile(filePath) {
|
|
320
|
+
return isArkModuleJson5(filePath) || isHarmonyRouteProfileJson(filePath);
|
|
321
|
+
}
|
|
283
322
|
/**
|
|
284
323
|
* Caches for loaded grammars and parsers
|
|
285
324
|
*/
|
|
@@ -450,8 +489,12 @@ function detectLanguage(filePath, source, overrides) {
|
|
|
450
489
|
return 'yaml';
|
|
451
490
|
if (isArkModuleJson5(filePath))
|
|
452
491
|
return 'yaml';
|
|
453
|
-
if (
|
|
492
|
+
if (isHarmonyRouteProfileJson(filePath))
|
|
454
493
|
return 'yaml';
|
|
494
|
+
if (isHarmonyElementStringJson(filePath))
|
|
495
|
+
return 'yaml'; // Spec 0041
|
|
496
|
+
if (isHarmonyCapabilityProfileJson(filePath))
|
|
497
|
+
return 'yaml'; // Spec 0048
|
|
455
498
|
const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
|
|
456
499
|
// Shopify OS 2.0 JSON templates / section groups → the Liquid extractor (it
|
|
457
500
|
// links each section `"type"` to its `sections/<type>.liquid`).
|
|
@@ -18,6 +18,7 @@ const local = (n) => !n.filePath.startsWith('ohos-sdk:') && !/\.d\.(?:ts|ets)$/i
|
|
|
18
18
|
// ArkAnalyzer's virtual entry/implicit constructor has no declaration on disk.
|
|
19
19
|
// Explicit constructors with a real range remain available through the normal renderer.
|
|
20
20
|
const artifact = (n) => n.filePath.includes('@dummy') || n.name.startsWith('@dummy')
|
|
21
|
+
|| /^%AM\d+/i.test(n.name)
|
|
21
22
|
|| (n.name === 'constructor' && n.startLine === 1 && n.endLine === 1);
|
|
22
23
|
/** Consume already-located nodes. No text search, new planner call or recursive retrieval. */
|
|
23
24
|
function buildArktsEvidencePacks(graph, options) {
|
package/dist/mcp/daemon.d.ts
CHANGED
|
@@ -20,9 +20,12 @@
|
|
|
20
20
|
* so a SIGKILL'd host still reaps its proxy promptly; the proxy's socket
|
|
21
21
|
* close then decrements the daemon's refcount.
|
|
22
22
|
* - When the last client disconnects the daemon lingers for
|
|
23
|
-
* `HOMEGRAPH_DAEMON_IDLE_TIMEOUT_MS` (default
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* `HOMEGRAPH_DAEMON_IDLE_TIMEOUT_MS` (default **60s**, Spec 0047) so a
|
|
24
|
+
* quick reconnect does not repay startup — but **only after** any in-flight
|
|
25
|
+
* auto-init/full index finishes (`building_fast` / `indexing`). While a
|
|
26
|
+
* client is connected (refcount > 0) the daemon never idle-exits. This is
|
|
27
|
+
* what keeps a single-agent session from leaking a daemon forever (#277)
|
|
28
|
+
* without killing a half-built graph when the IDE just closed.
|
|
26
29
|
*
|
|
27
30
|
* What this file owns:
|
|
28
31
|
* - Listening on the daemon socket and spawning per-connection sessions.
|
|
@@ -41,6 +44,21 @@
|
|
|
41
44
|
*/
|
|
42
45
|
import * as net from 'net';
|
|
43
46
|
import { DaemonLockInfo } from './daemon-paths';
|
|
47
|
+
/**
|
|
48
|
+
* Spec 0047: phases that block daemon idle-exit when clients=0.
|
|
49
|
+
* Routine watch/sync is NOT included.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isBuildPhaseBlockingIdle(phase: string | null | undefined): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Decide idle-timer action when the linger timer fires (refcount already 0).
|
|
54
|
+
* Pure helper for unit tests.
|
|
55
|
+
*/
|
|
56
|
+
export declare function decideIdleExitAction(opts: {
|
|
57
|
+
clientCount: number;
|
|
58
|
+
buildBlocking: boolean;
|
|
59
|
+
}): 'exit' | 'rearm-idle' | 'poll-build';
|
|
60
|
+
/** Default idle linger after last client (exported for tests). */
|
|
61
|
+
export declare function defaultDaemonIdleTimeoutMs(): number;
|
|
44
62
|
/**
|
|
45
63
|
* Finalize daemon shutdown. On POSIX, exit immediately — it's clean and fast.
|
|
46
64
|
* On Windows, do NOT force an exit while watchers may still be closing (that
|
package/dist/mcp/daemon.js
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
* so a SIGKILL'd host still reaps its proxy promptly; the proxy's socket
|
|
22
22
|
* close then decrements the daemon's refcount.
|
|
23
23
|
* - When the last client disconnects the daemon lingers for
|
|
24
|
-
* `HOMEGRAPH_DAEMON_IDLE_TIMEOUT_MS` (default
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* `HOMEGRAPH_DAEMON_IDLE_TIMEOUT_MS` (default **60s**, Spec 0047) so a
|
|
25
|
+
* quick reconnect does not repay startup — but **only after** any in-flight
|
|
26
|
+
* auto-init/full index finishes (`building_fast` / `indexing`). While a
|
|
27
|
+
* client is connected (refcount > 0) the daemon never idle-exits. This is
|
|
28
|
+
* what keeps a single-agent session from leaking a daemon forever (#277)
|
|
29
|
+
* without killing a half-built graph when the IDE just closed.
|
|
27
30
|
*
|
|
28
31
|
* What this file owns:
|
|
29
32
|
* - Listening on the daemon socket and spawning per-connection sessions.
|
|
@@ -75,6 +78,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
75
78
|
})();
|
|
76
79
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
77
80
|
exports.MAX_HELLO_LINE_BYTES = exports.Daemon = void 0;
|
|
81
|
+
exports.isBuildPhaseBlockingIdle = isBuildPhaseBlockingIdle;
|
|
82
|
+
exports.decideIdleExitAction = decideIdleExitAction;
|
|
83
|
+
exports.defaultDaemonIdleTimeoutMs = defaultDaemonIdleTimeoutMs;
|
|
78
84
|
exports.finalizeDaemonExit = finalizeDaemonExit;
|
|
79
85
|
exports.tryAcquireDaemonLock = tryAcquireDaemonLock;
|
|
80
86
|
exports.acquireLockViaExclusiveOpen = acquireLockViaExclusiveOpen;
|
|
@@ -92,8 +98,32 @@ const transport_1 = require("./transport");
|
|
|
92
98
|
const daemon_paths_1 = require("./daemon-paths");
|
|
93
99
|
const version_1 = require("./version");
|
|
94
100
|
const daemon_registry_1 = require("./daemon-registry");
|
|
95
|
-
/**
|
|
96
|
-
const
|
|
101
|
+
/** How often to re-check build phase while deferring idle exit (Spec 0047). */
|
|
102
|
+
const BUILD_IDLE_POLL_MS = 2_000;
|
|
103
|
+
/** Default idle linger after the last client disconnects (Spec 0047: 60s). */
|
|
104
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
|
|
105
|
+
/**
|
|
106
|
+
* Spec 0047: phases that block daemon idle-exit when clients=0.
|
|
107
|
+
* Routine watch/sync is NOT included.
|
|
108
|
+
*/
|
|
109
|
+
function isBuildPhaseBlockingIdle(phase) {
|
|
110
|
+
return phase === 'building_fast' || phase === 'indexing';
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Decide idle-timer action when the linger timer fires (refcount already 0).
|
|
114
|
+
* Pure helper for unit tests.
|
|
115
|
+
*/
|
|
116
|
+
function decideIdleExitAction(opts) {
|
|
117
|
+
if (opts.clientCount > 0)
|
|
118
|
+
return 'rearm-idle';
|
|
119
|
+
if (opts.buildBlocking)
|
|
120
|
+
return 'poll-build';
|
|
121
|
+
return 'exit';
|
|
122
|
+
}
|
|
123
|
+
/** Default idle linger after last client (exported for tests). */
|
|
124
|
+
function defaultDaemonIdleTimeoutMs() {
|
|
125
|
+
return DEFAULT_IDLE_TIMEOUT_MS;
|
|
126
|
+
}
|
|
97
127
|
/**
|
|
98
128
|
* Hard ceiling on how long the daemon stays up with clients connected but no
|
|
99
129
|
* inbound traffic. A backstop (#692): if a client's socket-close is never
|
|
@@ -372,15 +402,39 @@ class Daemon {
|
|
|
372
402
|
return;
|
|
373
403
|
if (this.idleTimeoutMs <= 0)
|
|
374
404
|
return; // 0 = never idle-exit
|
|
405
|
+
// Spec 0047: if the last window closed mid-index, wait for build to finish
|
|
406
|
+
// before starting the idle linger — never exit while clients>0 either.
|
|
407
|
+
if (this.clients.size === 0 && this.engine.isIndexBuildInProgress()) {
|
|
408
|
+
this.idleTimer = setTimeout(() => {
|
|
409
|
+
this.idleTimer = null;
|
|
410
|
+
if (this.clients.size > 0)
|
|
411
|
+
return; // reconnect disarmed the need
|
|
412
|
+
if (this.engine.isIndexBuildInProgress()) {
|
|
413
|
+
this.armIdleTimer(); // keep polling until build settles
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
this.armIdleTimer(); // build done → arm full idle linger
|
|
417
|
+
}, BUILD_IDLE_POLL_MS);
|
|
418
|
+
this.idleTimer.unref?.();
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
375
421
|
this.idleTimer = setTimeout(() => {
|
|
376
422
|
this.idleTimer = null;
|
|
377
423
|
// Last-second sanity check: if a connection landed between the timer
|
|
378
424
|
// firing and now, don't exit. (setImmediate-ordering is the only way
|
|
379
425
|
// this races; cheap to defend against.)
|
|
380
|
-
|
|
426
|
+
const action = decideIdleExitAction({
|
|
427
|
+
clientCount: this.clients.size,
|
|
428
|
+
buildBlocking: this.engine.isIndexBuildInProgress(),
|
|
429
|
+
});
|
|
430
|
+
if (action === 'rearm-idle') {
|
|
381
431
|
this.armIdleTimer();
|
|
382
432
|
return;
|
|
383
433
|
}
|
|
434
|
+
if (action === 'poll-build') {
|
|
435
|
+
this.armIdleTimer(); // switches to build-poll branch above
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
384
438
|
void this.stop('idle timeout');
|
|
385
439
|
}, this.idleTimeoutMs);
|
|
386
440
|
// Don't keep the event loop alive just for this — the net.Server keeps the
|
package/dist/mcp/engine.d.ts
CHANGED
|
@@ -48,6 +48,13 @@ export declare class MCPEngine {
|
|
|
48
48
|
private opts;
|
|
49
49
|
private closed;
|
|
50
50
|
private queryPool;
|
|
51
|
+
/**
|
|
52
|
+
* Spec 0049: auto-init deferred because the root was empty. Probe until
|
|
53
|
+
* indexable, then run the normal create-DB path once.
|
|
54
|
+
*/
|
|
55
|
+
private deferredAutoInitRoot;
|
|
56
|
+
private deferProbeTimer;
|
|
57
|
+
private deferAutoInitPromise;
|
|
51
58
|
constructor(opts?: MCPEngineOptions);
|
|
52
59
|
/**
|
|
53
60
|
* Start the worker-thread query pool once a default project is open (daemon
|
|
@@ -67,8 +74,21 @@ export declare class MCPEngine {
|
|
|
67
74
|
getProjectPath(): string | null;
|
|
68
75
|
/** Shared ToolHandler — sessions delegate tool dispatch through this. */
|
|
69
76
|
getToolHandler(): ToolHandler;
|
|
77
|
+
/**
|
|
78
|
+
* Spec 0047: true while auto-init / full symbol build is in flight.
|
|
79
|
+
* Used by the daemon idle timer — never idle-exit mid-build when clients=0.
|
|
80
|
+
* Does not include routine watch/sync increments.
|
|
81
|
+
*/
|
|
82
|
+
isIndexBuildInProgress(): boolean;
|
|
70
83
|
/** Whether the default project's HomeGraph is open. */
|
|
71
84
|
hasDefaultHomeGraph(): boolean;
|
|
85
|
+
/** Spec 0049: true while auto-init is waiting for an empty root to grow sources. */
|
|
86
|
+
isAutoInitDeferred(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Spec 0049: on tool call (or timer), re-check the deferred root and start
|
|
89
|
+
* the normal auto-init once it becomes indexable. Idempotent.
|
|
90
|
+
*/
|
|
91
|
+
kickDeferredAutoInit(): Promise<boolean>;
|
|
72
92
|
/**
|
|
73
93
|
* Walk up from `searchFrom` to find the nearest `.homegraph/` and open it.
|
|
74
94
|
* Idempotent: concurrent callers share one in-flight init; subsequent
|
|
@@ -100,6 +120,11 @@ export declare class MCPEngine {
|
|
|
100
120
|
* @returns true if the engine is fully wired to a new (or racing) index
|
|
101
121
|
*/
|
|
102
122
|
private tryAutoInit;
|
|
123
|
+
/** Spec 0049: create DB + fast map + background full (shared by first hit and kick). */
|
|
124
|
+
private runAutoInitCreate;
|
|
125
|
+
/** Spec 0049: remember empty root and (optionally) poll until indexable. */
|
|
126
|
+
private armDeferProbe;
|
|
127
|
+
private clearDeferProbe;
|
|
103
128
|
/**
|
|
104
129
|
* Open an index another process just created (Spec 0032 cold-start race).
|
|
105
130
|
* Brief retries cover the window between mkdir and db file create.
|
|
@@ -143,6 +168,7 @@ export declare class MCPEngine {
|
|
|
143
168
|
* Range: 1s … 30min. Used by product hosts (e.g. DevEco Code = 5 minutes).
|
|
144
169
|
*/
|
|
145
170
|
export declare function parseFixedWindowEnv(raw: string | undefined): number | undefined;
|
|
171
|
+
export { isIndexableRoot, parseDeferProbeMs } from './indexable-root';
|
|
146
172
|
/**
|
|
147
173
|
* Parse and clamp the HOMEGRAPH_WATCH_DEBOUNCE_MS env override.
|
|
148
174
|
*
|
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
|
*
|
|
@@ -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
|