homegraph 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +64 -14
- package/dist/bin/homegraph.js +2 -2
- package/dist/db/index.js +1 -0
- package/dist/db/migrations.d.ts +6 -1
- package/dist/db/migrations.js +26 -1
- package/dist/db/queries.d.ts +9 -0
- package/dist/db/queries.js +32 -7
- package/dist/db/schema.sql +4 -0
- package/dist/extraction/grammars.js +2 -1
- package/dist/extraction/languages/c-cpp.d.ts +8 -0
- package/dist/extraction/languages/c-cpp.js +44 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +4 -0
- package/dist/extraction/wasm-runtime-flags.js +9 -0
- package/dist/graph/evidence-paths.d.ts +2 -0
- package/dist/graph/evidence-paths.js +20 -3
- package/dist/index.js +7 -1
- package/dist/installer/targets/icode.d.ts +43 -0
- package/dist/installer/targets/icode.js +631 -0
- package/dist/installer/targets/registry.js +2 -0
- package/dist/installer/targets/types.d.ts +1 -1
- package/dist/mcp/arkts-evidence-packs.d.ts +17 -0
- package/dist/mcp/arkts-evidence-packs.js +218 -10
- package/dist/mcp/evidence-audit.d.ts +3 -0
- package/dist/mcp/evidence-audit.js +40 -0
- package/dist/mcp/evidence-rendering.js +1 -1
- package/dist/mcp/explore-repeat-guard.d.ts +19 -5
- package/dist/mcp/explore-repeat-guard.js +35 -14
- package/dist/mcp/explore-session-state.d.ts +5 -0
- package/dist/mcp/explore-session-state.js +1 -0
- package/dist/mcp/implementation-context.d.ts +35 -0
- package/dist/mcp/implementation-context.js +232 -0
- package/dist/mcp/locate-contract.js +1 -1
- 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 +15 -15
- package/dist/mcp/session.js +3 -2
- package/dist/mcp/tools.d.ts +39 -12
- package/dist/mcp/tools.js +237 -121
- package/dist/resolution/c-feature-variants.d.ts +40 -0
- package/dist/resolution/c-feature-variants.js +272 -0
- package/dist/resolution/cpp-macro-visibility.d.ts +21 -0
- package/dist/resolution/cpp-macro-visibility.js +393 -0
- package/dist/resolution/index.js +11 -0
- package/dist/resolution/name-matcher.js +49 -2
- package/dist/resolution/types.d.ts +8 -0
- package/dist/resolution/types.js +8 -0
- package/dist/search/literal-evidence.js +11 -3
- package/dist/search/query-plan-provider.js +13 -5
- package/dist/search/query-plan.d.ts +3 -1
- package/dist/search/query-plan.js +11 -4
- package/dist/search/request-contract.d.ts +29 -0
- package/dist/search/request-contract.js +76 -0
- package/dist/sync/watcher.d.ts +30 -0
- package/dist/sync/watcher.js +45 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,34 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
## [1.6.2] - 2026-10-19
|
|
14
|
+
|
|
15
|
+
### New Features
|
|
16
|
+
|
|
17
|
+
- **iCode support** in the installer. `homegraph install --target icode` wires the MCP server into iCode agent profiles (`~/.chrys/agents/` on macOS/Linux, `%APPDATA%\chrys\agents\` on Windows). The target is global-only. It edits existing profiles in place and, when the iCode runtime is found, writes shadow profiles for the built-in agents. If the runtime is missing, install still updates existing profiles and prints manual guidance. `HOMEGRAPH_ICODE_NO_BUILTINS=1` skips shadows. No instructions file is written (Spec 0057).
|
|
18
|
+
|
|
19
|
+
### Improvements
|
|
20
|
+
|
|
21
|
+
- `homegraph_explore` no longer stops after 2 calls on a long-lived MCP connection. Another explore within 10 seconds of the previous result is refused; a later call, including a different question, is allowed. The tool description, initialize instructions, and explore footer state that wait (Spec 0058).
|
|
22
|
+
- MCP initialize / `homegraph_explore` description: drop bash-first and "HomeGraph is optional" wording that conflicted with DevEco Code's graph-first product prompt; existing-code tasks open with `homegraph_explore` alone. Tool list, budgets, and evidence caveats unchanged.
|
|
23
|
+
- MCP under Bun skips the Node/V8 `--liftoff-only` relaunch (those flags do not apply), so product hosts can spawn `homegraph.js` with the host Bun binary (Spec 0052).
|
|
24
|
+
- MCP product hosts that prefix tools with the server name (e.g. DevEco Code) can set `HOMEGRAPH_MCP_SHORT_TOOL_NAMES=1` so tools/list exposes short names (`explore`, `project`) and tools/call still accepts both short and `homegraph_*` forms — avoiding a doubled `homegraph_homegraph_*` name after the host prefix. Unset by default; Cursor/Claude keep canonical `homegraph_*` names.
|
|
25
|
+
|
|
26
|
+
### Fixes
|
|
27
|
+
|
|
28
|
+
- C/C++: a call no longer binds another file's `static` function when a definition with external linkage exists. A C definition compiled only under a feature flag nothing in the project sets yields to the definition the default build compiles (Spec 0056).
|
|
29
|
+
- Windows: a file the daemon only read no longer shows up as edited. Watcher events whose size and modification time still match the index are ignored, so an NTFS last-access update does not mark the file pending sync (Spec 0055).
|
|
30
|
+
- C/C++: a header whose only C++ signal is `struct Derived : Base` is indexed as C++. A call that names a function-like macro visible at that line is not linked to a same-named function, and the include walk that decides this is bounded so a deep guard graph cannot blow the heap (Spec 0054).
|
|
31
|
+
- MCP `homegraph_explore` call budget shown to agents is **2** on every repo size, matching the session hard cap. Output sizing tiers are unchanged. Heuristic edges store `synthesized_by`, and ArkTS cross-file call cleanup deletes that channel by column instead of a JSON text match. Explore and Partial replies no longer tell the agent to call `homegraph_node`. Name search still tries FTS first, including Chinese literals (Spec 0053).
|
|
32
|
+
- MCP tools/list: when a session has no default project, `projectPath` is already marked `required` in the schema — its property description no longer says "Omit to use this session's default project", so agents are not told the field is both required and optional.
|
|
33
|
+
|
|
34
|
+
## [1.6.1] - 2026-09-28
|
|
35
|
+
|
|
36
|
+
### Improvements
|
|
37
|
+
|
|
38
|
+
- MCP default tools/list is now the product slim pair — `homegraph_explore`, `homegraph_project` — so tool schemas stop dominating host context. Full catalog via `HOMEGRAPH_MCP_TOOLS=all` (or `*`) or an explicit comma list (e.g. add `arkui_migrate` when needed). MCP initialize instructions and the explore tool description match the slim surface (no pointers to tools hidden by default) (Spec 0051).
|
|
39
|
+
- ArkTS explore can bundle verified render/navigation scope, used imports and types, explicit module exports and local dependency configuration with the located source. Attached SDK modules provide bounded method/parameter signatures, while control summaries distinguish icon appearance, click guards and direct enabled bindings. Whole-group budgets and source fingerprints preserve evidence boundaries; optional local pack audit supports error analysis. No additional model call or mandatory tool (Spec 0050).
|
|
40
|
+
|
|
13
41
|
## [1.6.0] - 2026-09-23
|
|
14
42
|
|
|
15
43
|
### Improvements
|
|
@@ -38,6 +66,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
38
66
|
|
|
39
67
|
### Improvements
|
|
40
68
|
|
|
69
|
+
- ArkTS explore can retain grounded page/literal/object targets and report request evidence separately from source completeness. Verified joint targets improve candidate ordering; a scoped enabled-binding check distinguishes actual modifiers from appearance-only controls while keeping runtime behavior unverified. Results honor source fingerprints and pack budgets, with independent target/behavior switches. Existing precise relationship and resource routes remain available (Spec 0049).
|
|
41
70
|
- Bump `arkanalyzer` to **1.0.94**.
|
|
42
71
|
- Harmony projects with a root `build-profile.json5` now default to **serial indexing** after ArkAnalyzer: one parse worker and no resolve/synthesis worker pool. On SceneBoard-scale repos this cut peak process-tree PrivateMemory from ~8.5GB to ~3.8GB with only ~6% more wall clock. Opt out with `HOMEGRAPH_HARMONY_SERIAL=0`; force on with `=1`. Explicit `CODEGRAPH_PARSE_WORKERS` still overrides parse concurrency (Spec 0037).
|
|
43
72
|
- MCP product index readiness is now five short states (`empty` / `fast` / `full` / `dirty` / `syncing`): tool replies use a one-line `HomeGraph status=…` footer (or that line alone when the tool cannot answer yet). Pending edits surface as `dirty` with path list instead of the long stale banner; write locks stay `syncing`. MCP initialize includes a one-line status glossary (Spec 0035).
|
|
@@ -274,3 +303,5 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
274
303
|
[1.5.8]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.5.8
|
|
275
304
|
[1.5.10]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.5.10
|
|
276
305
|
[1.6.0]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.0
|
|
306
|
+
[1.6.1]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.1
|
|
307
|
+
[1.6.2]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.2
|
package/README.md
CHANGED
|
@@ -171,29 +171,76 @@ homegraph serve mcp # 启动 MCP 服务(一般由 Agent 自动拉
|
|
|
171
171
|
|
|
172
172
|
Agent 侧工具名前缀为 `homegraph_`。
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
### 默认暴露(下载即用)
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
未设置 `HOMEGRAPH_MCP_TOOLS` 时,`tools/list` **只注册下面 2 个工具**(为压缩宿主上下文;Spec 0051)。刚升级后若发现以前能用的 `homegraph_node` / `search` / `arkui_migrate` 等「不见了」,不是卸载坏了,而是默认不再暴露——实现仍在,按下方「如何打开更多工具」恢复即可。
|
|
177
|
+
|
|
178
|
+
| 工具 | 用途 |
|
|
179
|
+
|------|------|
|
|
180
|
+
| `homegraph_project` | 工程/模块地图与 Harmony skeleton、资源**路径**导航(无符号正文、无调用图) |
|
|
181
|
+
| `homegraph_explore` | 通用结构探索:相关符号源码、调用路径、usage/依赖/NAPI 关系、route/resource/capability 等图证据 |
|
|
177
182
|
|
|
178
|
-
|
|
183
|
+
### 默认不暴露(需配置才进 tools/list)
|
|
184
|
+
|
|
185
|
+
下列工具 **handler 仍在**,默认不出现在 `tools/list`;未配置时直接调用会返回 `disabled via HOMEGRAPH_MCP_TOOLS`。
|
|
179
186
|
|
|
180
187
|
| 工具 | 用途 |
|
|
181
188
|
|------|------|
|
|
182
|
-
| `homegraph_usages` |
|
|
183
|
-
| `homegraph_modules` |
|
|
184
|
-
| `homegraph_native` |
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `homegraph_node` | 读取单个符号或整个文件的源码(带行号)及调用关系;可替代 Read 读文件 |
|
|
189
|
+
| `homegraph_usages` | 精确用法清单:已命名 API / 成员 / 常量 / 字段的引用位置 |
|
|
190
|
+
| `homegraph_modules` | 模块拓扑:已命名模块之间的依赖或循环依赖 |
|
|
191
|
+
| `homegraph_native` | Native 边界:NAPI / native 导出与注册位置 |
|
|
192
|
+
| `homegraph_search` | 按名称快速搜索符号(仅位置,不含源码) |
|
|
193
|
+
| `homegraph_node` | 读取单个符号或整个文件的源码(带行号)及调用关系 |
|
|
188
194
|
| `homegraph_callers` / `homegraph_callees` | 查看调用方 / 被调用方 |
|
|
189
195
|
| `homegraph_impact` | 变更影响分析(重构前使用) |
|
|
190
196
|
| `homegraph_diff_impact` | 传入 unified diff(或 hunks),返回与变更行相交的符号及调用/影响证据包(代码审查用) |
|
|
191
197
|
| `homegraph_arkui_migrate` | 一次返回 ArkUI 组件迁移/状态语义快照(装饰器、状态字段、Provide/Consume 等) |
|
|
192
198
|
| `homegraph_files` | 已索引的文件树(支持 glob 过滤、按语言分组) |
|
|
193
199
|
| `homegraph_status` | 索引健康状态(调试用;含 WAL 大小等) |
|
|
194
|
-
| `homegraph_spec_match` | 将新需求描述与 Commit4Spec
|
|
195
|
-
| `homegraph_spec_find` | 根据文件路径反向查找关联的Spec |
|
|
196
|
-
| `homegraph_spec_trace` | 根据代码符号追溯回关联的Spec |
|
|
200
|
+
| `homegraph_spec_match` | 将新需求描述与 Commit4Spec 知识图谱做全文匹配 |
|
|
201
|
+
| `homegraph_spec_find` | 根据文件路径反向查找关联的 Spec |
|
|
202
|
+
| `homegraph_spec_trace` | 根据代码符号追溯回关联的 Spec |
|
|
203
|
+
|
|
204
|
+
默认两件套下:用法/依赖/NAPI 类问题优先用 `homegraph_explore`;读已知路径用宿主 Read/Grep;ArkUI 迁移请显式打开 `arkui_migrate`(或 `all`)。
|
|
205
|
+
|
|
206
|
+
### 如何打开更多 / 全部 MCP 工具
|
|
207
|
+
|
|
208
|
+
在 **MCP 服务配置的 `environment` 字段**里设置(与 `HOMEGRAPH_NO_DAEMON` 同级)。这是注入给 HomeGraph MCP **进程**的环境变量;**不要**指望只在 Windows「系统属性 → 环境变量」里添加——MCP 子进程通常读不到系统/用户变量。
|
|
209
|
+
|
|
210
|
+
**打开全部工具:**
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"mcpServers": {
|
|
215
|
+
"homegraph": {
|
|
216
|
+
"command": "homegraph",
|
|
217
|
+
"args": ["serve", "mcp"],
|
|
218
|
+
"environment": {
|
|
219
|
+
"HOMEGRAPH_NO_DAEMON": "1",
|
|
220
|
+
"HOMEGRAPH_MCP_TOOLS": "all"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`HOMEGRAPH_MCP_TOOLS=*` 与 `all` 等价。
|
|
228
|
+
|
|
229
|
+
**只打开需要的子集(更省上下文):**
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
"environment": {
|
|
233
|
+
"HOMEGRAPH_MCP_TOOLS": "explore,project,node,search,arkui_migrate"
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
短名即可(`node` ≡ `homegraph_node`),逗号分隔。改完后**重启对应 Agent / MCP 服务**使配置生效。
|
|
238
|
+
|
|
239
|
+
**小仓注意:** 仅当设为 `all` / `*` 且索引文件数 **少于 500** 时,仍可能再裁到历史 TINY 核心集(`explore` / `search` / `node` / `diff_impact` / `project`)。小仓也要 `usages` / `arkui_migrate` 等时,请用**显式逗号列表**,不要只靠 `all`。
|
|
240
|
+
|
|
241
|
+
**跨项目查询:** 所有工具均支持可选参数 `projectPath`(绝对路径),用于在 monorepo 中查询子项目,或当 MCP 服务器根目录没有索引时指定目标项目。
|
|
242
|
+
|
|
243
|
+
**图谱数据源(`--sources` / `HOMEGRAPH_SOURCES`):** 控制 MCP 查询是否使用工程索引、OHOS SDK API 库,或两者。取值 `both`(默认)| `project` | `sdk` | `none`。CLI 优先于环境变量。评测示例:`homegraph serve mcp --path <app> --sources sdk`。`homegraph_status`(需先暴露该工具)会打印当前模式。
|
|
197
244
|
|
|
198
245
|
### 查询规划(实验性)
|
|
199
246
|
|
|
@@ -333,13 +380,16 @@ npm run cli
|
|
|
333
380
|
"homegraph": {
|
|
334
381
|
"type": "stdio",
|
|
335
382
|
"command": "homegraph",
|
|
336
|
-
"args": ["serve", "mcp"]
|
|
383
|
+
"args": ["serve", "mcp"],
|
|
384
|
+
"environment": {
|
|
385
|
+
"HOMEGRAPH_MCP_TOOLS": "all"
|
|
386
|
+
}
|
|
337
387
|
}
|
|
338
388
|
}
|
|
339
389
|
}
|
|
340
390
|
```
|
|
341
391
|
|
|
342
|
-
Cursor 等项目级配置写入 `./.cursor/mcp.json`,格式相同。推荐使用 `homegraph install` 自动完成。
|
|
392
|
+
不设 `HOMEGRAPH_MCP_TOOLS` 时默认只暴露 `explore` + `project`;需要全量或子集时按上一节「如何打开更多 / 全部 MCP 工具」配置。Cursor 等项目级配置写入 `./.cursor/mcp.json`,格式相同。推荐使用 `homegraph install` 自动完成。
|
|
343
393
|
|
|
344
394
|
---
|
|
345
395
|
|
package/dist/bin/homegraph.js
CHANGED
|
@@ -1917,7 +1917,7 @@ function main() {
|
|
|
1917
1917
|
*/
|
|
1918
1918
|
program
|
|
1919
1919
|
.command('install')
|
|
1920
|
-
.description('Install homegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
|
|
1920
|
+
.description('Install homegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent, iCode)')
|
|
1921
1921
|
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
|
|
1922
1922
|
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
|
|
1923
1923
|
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
|
|
@@ -1976,7 +1976,7 @@ function main() {
|
|
|
1976
1976
|
*/
|
|
1977
1977
|
program
|
|
1978
1978
|
.command('uninstall')
|
|
1979
|
-
.description('Remove homegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
|
|
1979
|
+
.description('Remove homegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent, iCode)')
|
|
1980
1980
|
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "all". Default: all')
|
|
1981
1981
|
.option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
|
|
1982
1982
|
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
|
package/dist/db/index.js
CHANGED
package/dist/db/migrations.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { SqliteDatabase } from './sqlite-adapter';
|
|
|
7
7
|
/**
|
|
8
8
|
* Current schema version
|
|
9
9
|
*/
|
|
10
|
-
export declare const CURRENT_SCHEMA_VERSION =
|
|
10
|
+
export declare const CURRENT_SCHEMA_VERSION = 12;
|
|
11
11
|
/**
|
|
12
12
|
* Migration definition
|
|
13
13
|
*/
|
|
@@ -16,6 +16,11 @@ interface Migration {
|
|
|
16
16
|
description: string;
|
|
17
17
|
up: (db: SqliteDatabase) => void;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Copy `metadata.synthesizedBy` onto `edges.synthesized_by` when the column is empty.
|
|
21
|
+
* Channel deletes read the column, not JSON text (Spec 0053).
|
|
22
|
+
*/
|
|
23
|
+
export declare function backfillEdgeSynthesizedBy(db: SqliteDatabase): void;
|
|
19
24
|
/**
|
|
20
25
|
* Get the current schema version from the database
|
|
21
26
|
*/
|
package/dist/db/migrations.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.CURRENT_SCHEMA_VERSION = void 0;
|
|
9
|
+
exports.backfillEdgeSynthesizedBy = backfillEdgeSynthesizedBy;
|
|
9
10
|
exports.getCurrentVersion = getCurrentVersion;
|
|
10
11
|
exports.runMigrations = runMigrations;
|
|
11
12
|
exports.needsMigration = needsMigration;
|
|
@@ -14,7 +15,7 @@ exports.getMigrationHistory = getMigrationHistory;
|
|
|
14
15
|
/**
|
|
15
16
|
* Current schema version
|
|
16
17
|
*/
|
|
17
|
-
exports.CURRENT_SCHEMA_VERSION =
|
|
18
|
+
exports.CURRENT_SCHEMA_VERSION = 12;
|
|
18
19
|
/**
|
|
19
20
|
* All migrations in order
|
|
20
21
|
*
|
|
@@ -175,7 +176,31 @@ const migrations = [
|
|
|
175
176
|
`);
|
|
176
177
|
},
|
|
177
178
|
},
|
|
179
|
+
{
|
|
180
|
+
version: 12,
|
|
181
|
+
description: 'Add edges.synthesized_by and backfill from metadata.synthesizedBy (Spec 0053)',
|
|
182
|
+
up: (db) => {
|
|
183
|
+
const cols = db.prepare('PRAGMA table_info(edges)').all();
|
|
184
|
+
if (!cols.some((c) => c.name === 'synthesized_by')) {
|
|
185
|
+
db.exec('ALTER TABLE edges ADD COLUMN synthesized_by TEXT');
|
|
186
|
+
}
|
|
187
|
+
backfillEdgeSynthesizedBy(db);
|
|
188
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_edges_synthesized_by ON edges(synthesized_by) WHERE synthesized_by IS NOT NULL');
|
|
189
|
+
},
|
|
190
|
+
},
|
|
178
191
|
];
|
|
192
|
+
/**
|
|
193
|
+
* Copy `metadata.synthesizedBy` onto `edges.synthesized_by` when the column is empty.
|
|
194
|
+
* Channel deletes read the column, not JSON text (Spec 0053).
|
|
195
|
+
*/
|
|
196
|
+
function backfillEdgeSynthesizedBy(db) {
|
|
197
|
+
db.exec(`
|
|
198
|
+
UPDATE edges
|
|
199
|
+
SET synthesized_by = json_extract(metadata, '$.synthesizedBy')
|
|
200
|
+
WHERE (synthesized_by IS NULL OR synthesized_by = '')
|
|
201
|
+
AND json_extract(metadata, '$.synthesizedBy') IS NOT NULL
|
|
202
|
+
`);
|
|
203
|
+
}
|
|
179
204
|
/**
|
|
180
205
|
* Get the current schema version from the database
|
|
181
206
|
*/
|
package/dist/db/queries.d.ts
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { SqliteDatabase } from './sqlite-adapter';
|
|
7
7
|
import { Node, Edge, FileRecord, UnresolvedReference, NodeKind, EdgeKind, Language, GraphStats, SearchOptions, SearchResult } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Convert database row to Edge object
|
|
10
|
+
*/
|
|
11
|
+
/** Channel name stored on `edges.synthesized_by` (Spec 0053). */
|
|
12
|
+
export declare function edgeSynthesizedBy(edge: Edge): string | null;
|
|
8
13
|
/**
|
|
9
14
|
* Query builder for the knowledge graph database
|
|
10
15
|
*/
|
|
@@ -46,6 +51,10 @@ export declare class QueryBuilder {
|
|
|
46
51
|
/** Attach a versioned OHOS API db for federated symbol lookup. */
|
|
47
52
|
attachOhosApiDb(dbPath: string | null): void;
|
|
48
53
|
getOhosApiDbPath(): string | null;
|
|
54
|
+
/** Exact imported SDK module only. No cross-module bare-name fallback.
|
|
55
|
+
* A missing/oversized/ambiguous module returns no claim of signature coverage.
|
|
56
|
+
*/
|
|
57
|
+
getOhosApiModuleNodes(specifier: string): Node[];
|
|
49
58
|
private hasOhosApiAttached;
|
|
50
59
|
private mapOhosApiNodeRow;
|
|
51
60
|
/** Set the normalized project-name tokens used to down-weight non-discriminative
|
package/dist/db/queries.js
CHANGED
|
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.QueryBuilder = void 0;
|
|
42
|
+
exports.edgeSynthesizedBy = edgeSynthesizedBy;
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
44
45
|
const utils_1 = require("../utils");
|
|
@@ -110,6 +111,11 @@ function rowToNode(row) {
|
|
|
110
111
|
/**
|
|
111
112
|
* Convert database row to Edge object
|
|
112
113
|
*/
|
|
114
|
+
/** Channel name stored on `edges.synthesized_by` (Spec 0053). */
|
|
115
|
+
function edgeSynthesizedBy(edge) {
|
|
116
|
+
const value = edge.metadata?.synthesizedBy;
|
|
117
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
118
|
+
}
|
|
113
119
|
function rowToEdge(row) {
|
|
114
120
|
return {
|
|
115
121
|
source: row.source,
|
|
@@ -247,6 +253,21 @@ class QueryBuilder {
|
|
|
247
253
|
getOhosApiDbPath() {
|
|
248
254
|
return this.ohosApiDbPath;
|
|
249
255
|
}
|
|
256
|
+
/** Exact imported SDK module only. No cross-module bare-name fallback.
|
|
257
|
+
* A missing/oversized/ambiguous module returns no claim of signature coverage.
|
|
258
|
+
*/
|
|
259
|
+
getOhosApiModuleNodes(specifier) {
|
|
260
|
+
if (!this.ohosApiDbPath || !/^@(ohos|hms|kit)\.[A-Za-z0-9_.]+$/.test(specifier))
|
|
261
|
+
return [];
|
|
262
|
+
const stem = specifier.startsWith('@kit.') ? specifier.slice(5) : specifier;
|
|
263
|
+
const escaped = stem.replace(/[\\%_]/g, c => `\\${c}`);
|
|
264
|
+
const rows = this.db.prepare(`SELECT * FROM ohos_api.nodes WHERE
|
|
265
|
+
file_path = ? OR file_path = ? OR file_path LIKE ? ESCAPE '\\' OR file_path LIKE ? ESCAPE '\\'
|
|
266
|
+
ORDER BY file_path, start_line LIMIT 257`).all(`${stem}.d.ts`, `${stem}.d.ets`, `%/${escaped}.d.ts`, `%/${escaped}.d.ets`);
|
|
267
|
+
if (rows.length > 256 || new Set(rows.map(row => row.file_path)).size !== 1)
|
|
268
|
+
return [];
|
|
269
|
+
return rows.map(row => this.mapOhosApiNodeRow(row));
|
|
270
|
+
}
|
|
250
271
|
hasOhosApiAttached() {
|
|
251
272
|
return this.ohosApiDbPath != null;
|
|
252
273
|
}
|
|
@@ -368,9 +389,10 @@ class QueryBuilder {
|
|
|
368
389
|
edge.line ?? null,
|
|
369
390
|
edge.column ?? null,
|
|
370
391
|
edge.provenance ?? null,
|
|
392
|
+
edgeSynthesizedBy(edge),
|
|
371
393
|
]);
|
|
372
394
|
}
|
|
373
|
-
this.runBatched('insertEdges', 'INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance) VALUES ', '(
|
|
395
|
+
this.runBatched('insertEdges', 'INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance, synthesized_by) VALUES ', '(?,?,?,?,?,?,?,?)', rows);
|
|
374
396
|
}
|
|
375
397
|
if (bundle.refs.length > 0)
|
|
376
398
|
this.insertUnresolvedRefsBatch(bundle.refs);
|
|
@@ -979,11 +1001,13 @@ class QueryBuilder {
|
|
|
979
1001
|
return [];
|
|
980
1002
|
return this.mergeOhosApiTextSearch(text, { kinds, languages, limit }, []);
|
|
981
1003
|
}
|
|
982
|
-
// First try FTS5 with prefix matching
|
|
1004
|
+
// First try FTS5 with prefix matching. Chinese literals (for example
|
|
1005
|
+
// string.json values in docstring) are real FTS tokens — do not skip them.
|
|
983
1006
|
let results = text
|
|
984
1007
|
? this.searchNodesFTS(text, { kinds, languages, limit, offset })
|
|
985
1008
|
// Over-fetch by 5× when running filter-only (no text). The
|
|
986
|
-
// post-scoring path:
|
|
1009
|
+
// post-scoring path: name: filters can be very selective, so
|
|
1010
|
+
// a smaller multiplier risks returning fewer than `limit`
|
|
987
1011
|
// results despite the DB having plenty of matches.
|
|
988
1012
|
: this.searchAllByFilters({ kinds, languages, limit: limit * 5 });
|
|
989
1013
|
// If no FTS results, try LIKE-based substring search
|
|
@@ -1482,8 +1506,8 @@ class QueryBuilder {
|
|
|
1482
1506
|
insertEdge(edge) {
|
|
1483
1507
|
if (!this.stmts.insertEdge) {
|
|
1484
1508
|
this.stmts.insertEdge = this.db.prepare(`
|
|
1485
|
-
INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance)
|
|
1486
|
-
VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance)
|
|
1509
|
+
INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance, synthesized_by)
|
|
1510
|
+
VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance, @synthesizedBy)
|
|
1487
1511
|
`);
|
|
1488
1512
|
}
|
|
1489
1513
|
this.stmts.insertEdge.run({
|
|
@@ -1494,6 +1518,7 @@ class QueryBuilder {
|
|
|
1494
1518
|
line: edge.line ?? null,
|
|
1495
1519
|
col: edge.column ?? null,
|
|
1496
1520
|
provenance: edge.provenance ?? null,
|
|
1521
|
+
synthesizedBy: edgeSynthesizedBy(edge),
|
|
1497
1522
|
});
|
|
1498
1523
|
}
|
|
1499
1524
|
/**
|
|
@@ -1698,8 +1723,8 @@ class QueryBuilder {
|
|
|
1698
1723
|
.prepare(`DELETE FROM edges
|
|
1699
1724
|
WHERE kind = 'calls'
|
|
1700
1725
|
AND provenance = 'heuristic'
|
|
1701
|
-
AND
|
|
1702
|
-
AND metadata
|
|
1726
|
+
AND synthesized_by = 'arkanalyzer'
|
|
1727
|
+
AND json_extract(metadata, '$.sourceFile') IS NOT NULL`)
|
|
1703
1728
|
.run();
|
|
1704
1729
|
return result.changes;
|
|
1705
1730
|
}
|
package/dist/db/schema.sql
CHANGED
|
@@ -52,6 +52,9 @@ CREATE TABLE IF NOT EXISTS edges (
|
|
|
52
52
|
line INTEGER,
|
|
53
53
|
col INTEGER,
|
|
54
54
|
provenance TEXT DEFAULT NULL,
|
|
55
|
+
-- Channel of a heuristic edge (`metadata.synthesizedBy`). Written on insert
|
|
56
|
+
-- so re-index can delete by channel without matching JSON text (Spec 0053).
|
|
57
|
+
synthesized_by TEXT,
|
|
55
58
|
FOREIGN KEY (source) REFERENCES nodes(id) ON DELETE CASCADE,
|
|
56
59
|
FOREIGN KEY (target) REFERENCES nodes(id) ON DELETE CASCADE
|
|
57
60
|
);
|
|
@@ -192,6 +195,7 @@ CREATE INDEX IF NOT EXISTS idx_unresolved_from_name ON unresolved_refs(from_node
|
|
|
192
195
|
CREATE INDEX IF NOT EXISTS idx_unresolved_status ON unresolved_refs(status);
|
|
193
196
|
CREATE INDEX IF NOT EXISTS idx_unresolved_failed_tail ON unresolved_refs(name_tail) WHERE status = 'failed';
|
|
194
197
|
CREATE INDEX IF NOT EXISTS idx_edges_provenance ON edges(provenance);
|
|
198
|
+
CREATE INDEX IF NOT EXISTS idx_edges_synthesized_by ON edges(synthesized_by) WHERE synthesized_by IS NOT NULL;
|
|
195
199
|
|
|
196
200
|
-- Project metadata for version/provenance tracking
|
|
197
201
|
CREATE TABLE IF NOT EXISTS project_metadata (
|
|
@@ -529,7 +529,8 @@ function looksLikeCpp(source) {
|
|
|
529
529
|
// routed through the C extractor (which extracts no classes), and its class
|
|
530
530
|
// definition silently vanishes. The two-token shape (`<KW> <MACRO> <Name>`
|
|
531
531
|
// before a `[:{]`) never occurs in valid C, so this can't misclassify C headers.
|
|
532
|
-
|
|
532
|
+
// `struct Derived : Base` is C++. A plain `struct Point {` stays C.
|
|
533
|
+
return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\bstruct\s+\w+\s*:|\b(?:class|struct)\s+[A-Z][A-Z0-9_]+\s+\w+\s*(?:final\s*)?[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample);
|
|
533
534
|
}
|
|
534
535
|
/**
|
|
535
536
|
* Heuristic: does a .h file contain Objective-C constructs?
|
|
@@ -102,6 +102,14 @@ export declare function blankMetalAttributes(source: string): string;
|
|
|
102
102
|
* ALL-CAPS call that isn't markup only loses that one annotation, never a whole
|
|
103
103
|
* class.
|
|
104
104
|
*/
|
|
105
|
+
/**
|
|
106
|
+
* Mask C++ raw string literals (`R"delim(...)delim"`) so later blankers do not
|
|
107
|
+
* treat their contents as code. Offsets stay aligned with the original source.
|
|
108
|
+
*/
|
|
109
|
+
export declare function maskCppRawStrings(source: string): {
|
|
110
|
+
source: string;
|
|
111
|
+
restore: (blanked: string) => string;
|
|
112
|
+
};
|
|
105
113
|
export declare function blankCppAnnotationMacroCalls(source: string): string;
|
|
106
114
|
export declare function blankCppApiPrefixMacros(source: string): string;
|
|
107
115
|
export declare function blankCppInlineAnnotationMacros(source: string): string;
|
|
@@ -7,6 +7,7 @@ exports.blankCppExportMacros = blankCppExportMacros;
|
|
|
7
7
|
exports.blankCppInlineMacros = blankCppInlineMacros;
|
|
8
8
|
exports.recoverMangledCppName = recoverMangledCppName;
|
|
9
9
|
exports.blankMetalAttributes = blankMetalAttributes;
|
|
10
|
+
exports.maskCppRawStrings = maskCppRawStrings;
|
|
10
11
|
exports.blankCppAnnotationMacroCalls = blankCppAnnotationMacroCalls;
|
|
11
12
|
exports.blankCppApiPrefixMacros = blankCppApiPrefixMacros;
|
|
12
13
|
exports.blankCppInlineAnnotationMacros = blankCppInlineAnnotationMacros;
|
|
@@ -493,6 +494,49 @@ function blankMetalAttributes(source) {
|
|
|
493
494
|
* ALL-CAPS call that isn't markup only loses that one annotation, never a whole
|
|
494
495
|
* class.
|
|
495
496
|
*/
|
|
497
|
+
/**
|
|
498
|
+
* Mask C++ raw string literals (`R"delim(...)delim"`) so later blankers do not
|
|
499
|
+
* treat their contents as code. Offsets stay aligned with the original source.
|
|
500
|
+
*/
|
|
501
|
+
function maskCppRawStrings(source) {
|
|
502
|
+
const unchanged = { source, restore: (blanked) => blanked };
|
|
503
|
+
if (source.indexOf('R"') === -1)
|
|
504
|
+
return unchanged;
|
|
505
|
+
const re = /\/\/[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$)|\b(?:u8|[LuU])?R"([^ \t\v\f\r\n()\\]{0,16})\(|"(?:\\[\s\S]|[^"\\])*(?:"|$)|(?<!\w)(?:u8|[LuU])?'(?:\\[\s\S]|[^'\\])*(?:'|$)/g;
|
|
506
|
+
const spans = [];
|
|
507
|
+
const parts = [];
|
|
508
|
+
let last = 0;
|
|
509
|
+
let m;
|
|
510
|
+
while ((m = re.exec(source)) !== null) {
|
|
511
|
+
if (m[1] === undefined)
|
|
512
|
+
continue;
|
|
513
|
+
const closer = `)${m[1]}"`;
|
|
514
|
+
const close = source.indexOf(closer, re.lastIndex);
|
|
515
|
+
const end = close < 0 ? source.length : close + closer.length;
|
|
516
|
+
spans.push({ start: m.index, end });
|
|
517
|
+
parts.push(source.slice(last, m.index), source.slice(m.index, end).replace(/[^\r\n]/g, '\0'));
|
|
518
|
+
last = re.lastIndex = end;
|
|
519
|
+
}
|
|
520
|
+
if (spans.length === 0)
|
|
521
|
+
return unchanged;
|
|
522
|
+
parts.push(source.slice(last));
|
|
523
|
+
const masked = parts.join('');
|
|
524
|
+
return {
|
|
525
|
+
source: masked,
|
|
526
|
+
restore(blanked) {
|
|
527
|
+
if (blanked === masked || blanked.length !== source.length)
|
|
528
|
+
return source;
|
|
529
|
+
const chars = blanked.split('');
|
|
530
|
+
for (const { start, end } of spans) {
|
|
531
|
+
for (let i = start; i < end; i++) {
|
|
532
|
+
if (chars[i] === '\0')
|
|
533
|
+
chars[i] = source[i];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return chars.join('');
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
}
|
|
496
540
|
function blankCppAnnotationMacroCalls(source) {
|
|
497
541
|
if (!/^[ \t]*[A-Z][A-Z0-9_]{2,}\s*\(/m.test(source))
|
|
498
542
|
return source;
|
|
@@ -44,5 +44,9 @@ export declare function buildRelaunchArgv(scriptPath: string, scriptArgs: readon
|
|
|
44
44
|
* On spawn failure, returns so the caller runs in-process anyway — risking the
|
|
45
45
|
* OOM is still better than refusing to start.
|
|
46
46
|
*/
|
|
47
|
+
/** Bun is not V8 — `--liftoff-only` / `--max-old-space-size` relaunch does not apply. */
|
|
48
|
+
export declare function isBunRuntime(g?: {
|
|
49
|
+
Bun?: unknown;
|
|
50
|
+
}): boolean;
|
|
47
51
|
export declare function relaunchWithWasmRuntimeFlagsIfNeeded(scriptPath: string): void;
|
|
48
52
|
//# sourceMappingURL=wasm-runtime-flags.d.ts.map
|
|
@@ -6,6 +6,7 @@ exports.maxOldSpaceSizeFlag = maxOldSpaceSizeFlag;
|
|
|
6
6
|
exports.processHasWasmRuntimeFlags = processHasWasmRuntimeFlags;
|
|
7
7
|
exports.processHasRequiredRuntimeFlags = processHasRequiredRuntimeFlags;
|
|
8
8
|
exports.buildRelaunchArgv = buildRelaunchArgv;
|
|
9
|
+
exports.isBunRuntime = isBunRuntime;
|
|
9
10
|
exports.relaunchWithWasmRuntimeFlagsIfNeeded = relaunchWithWasmRuntimeFlagsIfNeeded;
|
|
10
11
|
/**
|
|
11
12
|
* WASM runtime flags — workaround for the V8 turboshaft WASM Zone OOM.
|
|
@@ -134,6 +135,10 @@ function buildRelaunchArgv(scriptPath, scriptArgs, execArgv = process.execArgv)
|
|
|
134
135
|
* On spawn failure, returns so the caller runs in-process anyway — risking the
|
|
135
136
|
* OOM is still better than refusing to start.
|
|
136
137
|
*/
|
|
138
|
+
/** Bun is not V8 — `--liftoff-only` / `--max-old-space-size` relaunch does not apply. */
|
|
139
|
+
function isBunRuntime(g = globalThis) {
|
|
140
|
+
return typeof g.Bun !== 'undefined';
|
|
141
|
+
}
|
|
137
142
|
function relaunchWithWasmRuntimeFlagsIfNeeded(scriptPath) {
|
|
138
143
|
if (processHasRequiredRuntimeFlags())
|
|
139
144
|
return;
|
|
@@ -141,6 +146,10 @@ function relaunchWithWasmRuntimeFlagsIfNeeded(scriptPath) {
|
|
|
141
146
|
return;
|
|
142
147
|
if (process.env.HOMEGRAPH_NO_RELAUNCH)
|
|
143
148
|
return;
|
|
149
|
+
// Product hosts (e.g. DevEco Code) spawn MCP under Bun; V8 flags would be
|
|
150
|
+
// ignored or break relaunch. Stay in-process.
|
|
151
|
+
if (isBunRuntime())
|
|
152
|
+
return;
|
|
144
153
|
const argv = buildRelaunchArgv(scriptPath, process.argv.slice(2));
|
|
145
154
|
const result = (0, child_process_1.spawnSync)(process.execPath, argv, {
|
|
146
155
|
stdio: 'inherit',
|
|
@@ -53,6 +53,8 @@ export declare function isEvidencePathNode(n: Node): boolean;
|
|
|
53
53
|
export declare function completeEvidencePathCandidates(query: string, nodes: Node[], lookup: (qualifiedName: string, limit: number) => Node[], plan?: QueryPlan): Node[];
|
|
54
54
|
/** Resolve only actual candidate identities; a bare homonym never selects the first hit. */
|
|
55
55
|
export declare function resolveEvidencePathGoal(query: string, nodes: Node[], focusIds: Set<string>, plan?: QueryPlan): EvidencePathGoal;
|
|
56
|
+
/** Framework-labelled static UI wiring; never treat an arbitrary reference as rendering. */
|
|
57
|
+
export declare function isArkuiScopeEdge(edge: Edge): boolean;
|
|
56
58
|
export declare function evidenceEdgeCost(edge: Edge, goal: EvidencePathGoal): number | null;
|
|
57
59
|
export declare function evidenceEdgeKey(edge: Edge): string;
|
|
58
60
|
/** One bounded search per request. Each orientation follows directed edges throughout. */
|
|
@@ -4,6 +4,7 @@ exports.EVIDENCE_PATH_LIMITS = void 0;
|
|
|
4
4
|
exports.isEvidencePathNode = isEvidencePathNode;
|
|
5
5
|
exports.completeEvidencePathCandidates = completeEvidencePathCandidates;
|
|
6
6
|
exports.resolveEvidencePathGoal = resolveEvidencePathGoal;
|
|
7
|
+
exports.isArkuiScopeEdge = isArkuiScopeEdge;
|
|
7
8
|
exports.evidenceEdgeCost = evidenceEdgeCost;
|
|
8
9
|
exports.evidenceEdgeKey = evidenceEdgeKey;
|
|
9
10
|
exports.searchEvidencePaths = searchEvidencePaths;
|
|
@@ -108,11 +109,18 @@ function edgeSemantics(edge) {
|
|
|
108
109
|
|| ['callback', 'event-emitter', 'closure-collection', 'fn-pointer-dispatch', 'arkui-emitter', 'arkui-common-event', 'arkui-taskpool'].includes(String(by));
|
|
109
110
|
return { state, event };
|
|
110
111
|
}
|
|
112
|
+
/** Framework-labelled static UI wiring; never treat an arbitrary reference as rendering. */
|
|
113
|
+
function isArkuiScopeEdge(edge) {
|
|
114
|
+
const by = edge.metadata?.synthesizedBy;
|
|
115
|
+
return (edge.kind === 'references' || edge.kind === 'calls' || edge.kind === 'contains') && ((by === 'viewtree' && ['child-component', 'builder', 'builder-param'].includes(String(edge.metadata?.via)))
|
|
116
|
+
|| (by === 'arkui-ownership' && edge.metadata?.via === 'build') || by === 'arkui-child' || by === 'arkui-named-nav' || by === 'arkts-route-map' || by === 'arkui-route');
|
|
117
|
+
}
|
|
111
118
|
function evidenceEdgeCost(edge, goal) {
|
|
112
119
|
const { state, event } = edgeSemantics(edge);
|
|
113
120
|
let relevanceCost;
|
|
114
121
|
if (goal.kind === 'structure') {
|
|
115
|
-
if (!['extends', 'implements', 'overrides', 'instantiates'].includes(edge.kind)
|
|
122
|
+
if (!['extends', 'implements', 'overrides', 'instantiates'].includes(edge.kind)
|
|
123
|
+
&& !(process.env.HOMEGRAPH_ARKTS_IMPLEMENTATION_CONTEXT !== '0' && isArkuiScopeEdge(edge)))
|
|
116
124
|
return null;
|
|
117
125
|
relevanceCost = 0;
|
|
118
126
|
}
|
|
@@ -143,7 +151,7 @@ function searchEvidencePaths(graph, goal, options = {}) {
|
|
|
143
151
|
const neighbors = new Map();
|
|
144
152
|
const paths = [];
|
|
145
153
|
const missing = [];
|
|
146
|
-
const kinds = goal.kind === 'structure' ? ['extends', 'implements', 'overrides', 'instantiates'] : ['calls', 'references'];
|
|
154
|
+
const kinds = goal.kind === 'structure' ? ['extends', 'implements', 'overrides', 'instantiates', ...(process.env.HOMEGRAPH_ARKTS_IMPLEMENTATION_CONTEXT !== '0' ? ['calls', 'references', 'contains'] : [])] : ['calls', 'references'];
|
|
147
155
|
const alive = () => {
|
|
148
156
|
if (now() - started >= limits.milliseconds) {
|
|
149
157
|
hits.add('time');
|
|
@@ -179,7 +187,16 @@ function searchEvidencePaths(graph, goal, options = {}) {
|
|
|
179
187
|
stats.rowsRead += rows.length;
|
|
180
188
|
if (rows.length > limits.perNode)
|
|
181
189
|
hits.add('neighbors');
|
|
182
|
-
const valid = rows.slice(0, limits.perNode).
|
|
190
|
+
const valid = rows.slice(0, limits.perNode).map(e => {
|
|
191
|
+
if (goal.kind === 'structure' && e.kind === 'contains' && process.env.HOMEGRAPH_ARKTS_IMPLEMENTATION_CONTEXT !== '0') {
|
|
192
|
+
const from = node(e.source);
|
|
193
|
+
const to = node(e.target);
|
|
194
|
+
if (from && ['component', 'struct'].includes(from.kind) && to?.kind === 'method' && to.name === 'build') {
|
|
195
|
+
return { ...e, metadata: { ...e.metadata, synthesizedBy: 'arkui-ownership', via: 'build' } };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return e;
|
|
199
|
+
}).filter(e => evidenceEdgeCost(e, goal) !== null
|
|
183
200
|
&& node(e.source) && node(e.target)).sort((a, b) => evidenceEdgeCost(a, goal) - evidenceEdgeCost(b, goal)
|
|
184
201
|
|| evidenceEdgeKey(a).localeCompare(evidenceEdgeKey(b)));
|
|
185
202
|
adjacency.set(key, valid);
|
package/dist/index.js
CHANGED
|
@@ -854,7 +854,13 @@ class HomeGraph {
|
|
|
854
854
|
}
|
|
855
855
|
const filesChanged = result.filesAdded + result.filesModified + result.filesRemoved;
|
|
856
856
|
return { filesChanged, durationMs: result.durationMs };
|
|
857
|
-
},
|
|
857
|
+
}, {
|
|
858
|
+
...options,
|
|
859
|
+
indexedFingerprint: options.indexedFingerprint ?? ((rel) => {
|
|
860
|
+
const rec = this.getFile(rel);
|
|
861
|
+
return rec ? { size: rec.size, modifiedAt: rec.modifiedAt } : null;
|
|
862
|
+
}),
|
|
863
|
+
});
|
|
858
864
|
return this.watcher.start();
|
|
859
865
|
}
|
|
860
866
|
/**
|