homegraph 1.5.8 → 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 +34 -0
- package/dist/extraction/grammars.d.ts +19 -0
- package/dist/extraction/grammars.js +44 -1
- package/dist/extraction/index.js +17 -12
- package/dist/extraction/languages/arkts.d.ts +40 -7
- package/dist/extraction/languages/arkts.js +290 -84
- package/dist/extraction/tree-sitter.js +12 -3
- package/dist/index.js +23 -12
- 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 +40 -10
- package/dist/mcp/index-availability.js +89 -23
- 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 -6
- package/dist/mcp/server-instructions.js +15 -5
- package/dist/mcp/session.js +15 -0
- package/dist/mcp/tools.d.ts +52 -0
- package/dist/mcp/tools.js +750 -103
- package/dist/project-map/index.d.ts +45 -0
- package/dist/project-map/index.js +373 -45
- 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 +2 -2
|
@@ -983,6 +983,255 @@ async function arkuiRouterEdges(ctx, onYield) {
|
|
|
983
983
|
}
|
|
984
984
|
return edges;
|
|
985
985
|
}
|
|
986
|
+
/** ArkUI custom-component / builder decorators that qualify as child targets. */
|
|
987
|
+
const ARKUI_CHILD_TARGET_DECORATORS = new Set([
|
|
988
|
+
'Component', 'ComponentV2', 'Entry', 'Builder', 'LocalBuilder',
|
|
989
|
+
]);
|
|
990
|
+
const ARKUI_BUILDER_DECORATORS = new Set(['Builder', 'LocalBuilder']);
|
|
991
|
+
/** PascalCase call sites inside build / @Builder bodies — `ShoppingCart({…})` / `Foo()`. */
|
|
992
|
+
const ARKUI_CHILD_CALL_RE = /\b([A-Z][A-Za-z0-9]*)\s*\(/g;
|
|
993
|
+
function isArkuiChildTarget(n) {
|
|
994
|
+
const dec = n.decorators ?? [];
|
|
995
|
+
if (n.kind === 'struct' || n.kind === 'component') {
|
|
996
|
+
return dec.some((d) => ARKUI_CHILD_TARGET_DECORATORS.has(d));
|
|
997
|
+
}
|
|
998
|
+
if (n.kind === 'function' || n.kind === 'method') {
|
|
999
|
+
return dec.some((d) => ARKUI_BUILDER_DECORATORS.has(d));
|
|
1000
|
+
}
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
function isArkuiBuildParent(n) {
|
|
1004
|
+
if (n.kind === 'method' && n.name === 'build')
|
|
1005
|
+
return true;
|
|
1006
|
+
if (n.kind === 'function' || n.kind === 'method') {
|
|
1007
|
+
return (n.decorators ?? []).some((d) => ARKUI_BUILDER_DECORATORS.has(d));
|
|
1008
|
+
}
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Spec 0045 A — ArkUI parent build/@Builder → child @Component/@Builder.
|
|
1013
|
+
* Analog of react jsx-child: only static PascalCase call names that resolve to
|
|
1014
|
+
* a unique in-repo component/builder; ambiguous names are dropped.
|
|
1015
|
+
*
|
|
1016
|
+
* Scan the enclosing @Component/@Builder struct (or Builder function) body —
|
|
1017
|
+
* not only the `build()` method span — because ArkUI trailing lambdas
|
|
1018
|
+
* (`Column() { Child() }`) are often attributed with tight method ranges that
|
|
1019
|
+
* omit nested call sites.
|
|
1020
|
+
*/
|
|
1021
|
+
async function arkuiChildComponentEdges(ctx, onYield) {
|
|
1022
|
+
const edges = [];
|
|
1023
|
+
const seen = new Set();
|
|
1024
|
+
const moduleDirs = (() => {
|
|
1025
|
+
const ws = ctx.getWorkspacePackages?.();
|
|
1026
|
+
return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
|
|
1027
|
+
})();
|
|
1028
|
+
const moduleScopeOf = (file) => {
|
|
1029
|
+
for (const dir of moduleDirs) {
|
|
1030
|
+
if (file === dir || file.startsWith(dir + '/'))
|
|
1031
|
+
return dir;
|
|
1032
|
+
}
|
|
1033
|
+
return '';
|
|
1034
|
+
};
|
|
1035
|
+
const resolveChild = (name, fromFile, parentId) => {
|
|
1036
|
+
let candidates = ctx.getNodesByName(name).filter(isArkuiChildTarget);
|
|
1037
|
+
if (candidates.length === 0) {
|
|
1038
|
+
candidates = [
|
|
1039
|
+
...ctx.getNodesByKind('struct'),
|
|
1040
|
+
...ctx.getNodesByKind('component'),
|
|
1041
|
+
...ctx.getNodesByKind('function'),
|
|
1042
|
+
...ctx.getNodesByKind('method'),
|
|
1043
|
+
].filter((n) => n.name === name && isArkuiChildTarget(n));
|
|
1044
|
+
}
|
|
1045
|
+
candidates = candidates.filter((n) => n.id !== parentId);
|
|
1046
|
+
// Dedupe by id — name index can surface the same row twice across caches.
|
|
1047
|
+
candidates = [...new Map(candidates.map((n) => [n.id, n])).values()];
|
|
1048
|
+
if (candidates.length === 0)
|
|
1049
|
+
return null;
|
|
1050
|
+
if (candidates.length > 1) {
|
|
1051
|
+
const sameFile = candidates.filter((n) => n.filePath === fromFile);
|
|
1052
|
+
if (sameFile.length === 1) {
|
|
1053
|
+
candidates = sameFile;
|
|
1054
|
+
}
|
|
1055
|
+
else if (sameFile.length > 1) {
|
|
1056
|
+
// Same file + same name: extraction duplicate — keep earliest.
|
|
1057
|
+
candidates = [sameFile.sort((a, b) => a.startLine - b.startLine)[0]];
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
const scope = moduleScopeOf(fromFile);
|
|
1061
|
+
const sameMod = candidates.filter((n) => moduleScopeOf(n.filePath) === scope);
|
|
1062
|
+
if (sameMod.length === 1)
|
|
1063
|
+
candidates = sameMod;
|
|
1064
|
+
else
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return candidates[0] ?? null;
|
|
1069
|
+
};
|
|
1070
|
+
const emitFromSource = (parent, src, file) => {
|
|
1071
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(src, 'typescript');
|
|
1072
|
+
const names = new Set();
|
|
1073
|
+
ARKUI_CHILD_CALL_RE.lastIndex = 0;
|
|
1074
|
+
let m;
|
|
1075
|
+
while ((m = ARKUI_CHILD_CALL_RE.exec(safe)))
|
|
1076
|
+
names.add(m[1]);
|
|
1077
|
+
let added = 0;
|
|
1078
|
+
for (const name of names) {
|
|
1079
|
+
if (added >= MAX_JSX_CHILDREN)
|
|
1080
|
+
break;
|
|
1081
|
+
if (name === parent.name)
|
|
1082
|
+
continue;
|
|
1083
|
+
const child = resolveChild(name, file, parent.id);
|
|
1084
|
+
if (!child)
|
|
1085
|
+
continue;
|
|
1086
|
+
const key = `${parent.id}>${child.id}`;
|
|
1087
|
+
if (seen.has(key))
|
|
1088
|
+
continue;
|
|
1089
|
+
seen.add(key);
|
|
1090
|
+
edges.push({
|
|
1091
|
+
source: parent.id,
|
|
1092
|
+
target: child.id,
|
|
1093
|
+
kind: 'calls',
|
|
1094
|
+
line: parent.startLine,
|
|
1095
|
+
provenance: 'heuristic',
|
|
1096
|
+
metadata: {
|
|
1097
|
+
synthesizedBy: 'arkui-child',
|
|
1098
|
+
via: name,
|
|
1099
|
+
registeredAt: `${file}:${parent.startLine}`,
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
1102
|
+
added++;
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
let scannedFiles = 0;
|
|
1106
|
+
for (const file of ctx.getAllFiles()) {
|
|
1107
|
+
if ((++scannedFiles & 15) === 0)
|
|
1108
|
+
await onYield();
|
|
1109
|
+
if (!file.endsWith('.ets'))
|
|
1110
|
+
continue;
|
|
1111
|
+
const content = ctx.readFile(file);
|
|
1112
|
+
if (!content)
|
|
1113
|
+
continue;
|
|
1114
|
+
const fileNodes = ctx.getNodesInFile(file);
|
|
1115
|
+
// @Component / @Entry structs: attribute child calls to their `build()` method.
|
|
1116
|
+
for (const struct of fileNodes) {
|
|
1117
|
+
if (struct.kind !== 'struct' && struct.kind !== 'component')
|
|
1118
|
+
continue;
|
|
1119
|
+
if (!isArkuiChildTarget(struct))
|
|
1120
|
+
continue;
|
|
1121
|
+
const build = fileNodes.find((n) => n.kind === 'method' && n.name === 'build'
|
|
1122
|
+
&& n.startLine >= struct.startLine && n.endLine <= struct.endLine) ?? fileNodes.find((n) => n.kind === 'method' && n.name === 'build'
|
|
1123
|
+
&& n.filePath === struct.filePath
|
|
1124
|
+
&& (n.qualifiedName?.includes(struct.name) ?? false));
|
|
1125
|
+
if (!build)
|
|
1126
|
+
continue;
|
|
1127
|
+
const src = sliceLines(content, struct.startLine, struct.endLine);
|
|
1128
|
+
if (!src)
|
|
1129
|
+
continue;
|
|
1130
|
+
emitFromSource(build, src, file);
|
|
1131
|
+
}
|
|
1132
|
+
// Standalone @Builder functions/methods (not the struct build path).
|
|
1133
|
+
for (const parent of fileNodes.filter(isArkuiBuildParent)) {
|
|
1134
|
+
if (parent.kind === 'method' && parent.name === 'build')
|
|
1135
|
+
continue; // handled above
|
|
1136
|
+
const src = sliceLines(content, parent.startLine, parent.endLine);
|
|
1137
|
+
if (!src)
|
|
1138
|
+
continue;
|
|
1139
|
+
emitFromSource(parent, src, file);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return edges;
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Named Navigation / router APIs with a string literal route name.
|
|
1146
|
+
* `pushPathByName('DemoPage')`, `replacePathByName('x')`, `pushNamedRoute({ name: 'x' })`.
|
|
1147
|
+
*/
|
|
1148
|
+
const ARKUI_NAMED_NAV_RE = /\b(?:pushPathByName|replacePathByName)\s*\(\s*['"]([^'"]+)['"]|\bpushNamedRoute\s*\(\s*\{[^)]{0,200}?\bname\s*:\s*['"]([^'"]+)['"]/g;
|
|
1149
|
+
/**
|
|
1150
|
+
* Spec 0045 B — literal named-nav → indexed `route` node (from route_map).
|
|
1151
|
+
* Variable / non-literal names are ignored; ambiguous route names dropped.
|
|
1152
|
+
*/
|
|
1153
|
+
async function arkuiNamedNavEdges(queries, ctx, onYield) {
|
|
1154
|
+
const edges = [];
|
|
1155
|
+
const seen = new Set();
|
|
1156
|
+
const routesByName = new Map();
|
|
1157
|
+
for (const r of queries.iterateNodesByKind('route')) {
|
|
1158
|
+
const list = routesByName.get(r.name) ?? [];
|
|
1159
|
+
list.push(r);
|
|
1160
|
+
routesByName.set(r.name, list);
|
|
1161
|
+
}
|
|
1162
|
+
if (routesByName.size === 0)
|
|
1163
|
+
return edges;
|
|
1164
|
+
const moduleDirs = (() => {
|
|
1165
|
+
const ws = ctx.getWorkspacePackages?.();
|
|
1166
|
+
return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
|
|
1167
|
+
})();
|
|
1168
|
+
const moduleScopeOf = (file) => {
|
|
1169
|
+
for (const dir of moduleDirs) {
|
|
1170
|
+
if (file === dir || file.startsWith(dir + '/'))
|
|
1171
|
+
return dir;
|
|
1172
|
+
}
|
|
1173
|
+
return '';
|
|
1174
|
+
};
|
|
1175
|
+
let scannedFiles = 0;
|
|
1176
|
+
for (const file of ctx.getAllFiles()) {
|
|
1177
|
+
if ((++scannedFiles & 15) === 0)
|
|
1178
|
+
await onYield();
|
|
1179
|
+
if (!file.endsWith('.ets'))
|
|
1180
|
+
continue;
|
|
1181
|
+
const content = ctx.readFile(file);
|
|
1182
|
+
if (!content
|
|
1183
|
+
|| (!content.includes('pushPathByName')
|
|
1184
|
+
&& !content.includes('replacePathByName')
|
|
1185
|
+
&& !content.includes('pushNamedRoute'))) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
|
|
1189
|
+
const nodes = ctx.getNodesInFile(file)
|
|
1190
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
1191
|
+
ARKUI_NAMED_NAV_RE.lastIndex = 0;
|
|
1192
|
+
let m;
|
|
1193
|
+
while ((m = ARKUI_NAMED_NAV_RE.exec(safe))) {
|
|
1194
|
+
const routeName = m[1] || m[2];
|
|
1195
|
+
if (!routeName)
|
|
1196
|
+
continue;
|
|
1197
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
1198
|
+
const encl = nodes
|
|
1199
|
+
.filter((n) => n.startLine <= line && n.endLine >= line)
|
|
1200
|
+
.sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
|
|
1201
|
+
if (!encl)
|
|
1202
|
+
continue;
|
|
1203
|
+
let candidates = routesByName.get(routeName) ?? [];
|
|
1204
|
+
if (candidates.length === 0)
|
|
1205
|
+
continue;
|
|
1206
|
+
if (candidates.length > 1) {
|
|
1207
|
+
const scope = moduleScopeOf(file);
|
|
1208
|
+
const sameMod = candidates.filter((r) => moduleScopeOf(r.filePath) === scope);
|
|
1209
|
+
if (sameMod.length === 1)
|
|
1210
|
+
candidates = sameMod;
|
|
1211
|
+
else
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
const route = candidates[0];
|
|
1215
|
+
const key = `${encl.id}>${route.id}`;
|
|
1216
|
+
if (seen.has(key))
|
|
1217
|
+
continue;
|
|
1218
|
+
seen.add(key);
|
|
1219
|
+
edges.push({
|
|
1220
|
+
source: encl.id,
|
|
1221
|
+
target: route.id,
|
|
1222
|
+
kind: 'calls',
|
|
1223
|
+
line,
|
|
1224
|
+
provenance: 'heuristic',
|
|
1225
|
+
metadata: {
|
|
1226
|
+
synthesizedBy: 'arkui-named-nav',
|
|
1227
|
+
event: routeName,
|
|
1228
|
+
registeredAt: `${file}:${line}`,
|
|
1229
|
+
},
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return edges;
|
|
1234
|
+
}
|
|
986
1235
|
/**
|
|
987
1236
|
* Phase 4c: C++ virtual override. A call through a base/interface pointer
|
|
988
1237
|
* (`db->Get(...)`, `iter->Next()`) dispatches at runtime to a subclass override,
|
|
@@ -4088,6 +4337,8 @@ exports.SYNTH_PASSES = [
|
|
|
4088
4337
|
{ name: 'arkuiCommonEvent', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiCommonEventEdges(c, y) },
|
|
4089
4338
|
{ name: 'arkuiTaskpool', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiTaskpoolEdges(c, y) },
|
|
4090
4339
|
{ name: 'arkuiRoutes', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiRouterEdges(c, y) },
|
|
4340
|
+
{ name: 'arkuiChildEdges', gate: (has) => has('arkts'), run: (_q, c, y) => arkuiChildComponentEdges(c, y) },
|
|
4341
|
+
{ name: 'arkuiNamedNav', gate: (has) => has('arkts'), run: (q, c, y) => arkuiNamedNavEdges(q, c, y) },
|
|
4091
4342
|
{ name: 'arktsStartupEdges', gate: (has) => has('arkts'), run: async (q, c) => arktsEntryEdges(q, c) },
|
|
4092
4343
|
{ name: 'cppEdges', gate: (has) => has('cpp'), run: (q, _c, y) => cppOverrideEdges(q, y) },
|
|
4093
4344
|
{
|
|
@@ -1,12 +1,41 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ArkTS entry / module manifest resolver.
|
|
3
|
-
*
|
|
4
|
-
* Parses HarmonyOS `module.json5` page routes and links startup paths:
|
|
5
|
-
* module pages → @Entry components, loadContent(url) → first-screen lifecycle.
|
|
6
|
-
*/
|
|
7
1
|
import type { Node } from '../../types';
|
|
8
2
|
import { FrameworkResolver, ResolutionContext } from '../types';
|
|
3
|
+
/** Module root = path prefix before `/src/main/` (Harmony HAP/HAR layout). */
|
|
4
|
+
export declare function harmonyModuleRootFromProfile(profileRel: string): string;
|
|
5
|
+
/** Resolve `pageSourceFile` (module-relative) against the profile's module root. */
|
|
6
|
+
export declare function resolveHarmonyPageSourcePath(profileRel: string, pageSourceFile: string): string;
|
|
7
|
+
interface RouterMapEntry {
|
|
8
|
+
name: string;
|
|
9
|
+
pageSourceFile: string;
|
|
10
|
+
buildFunction?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Parse standard Harmony `routerMap` JSON (route_map / router_map). */
|
|
13
|
+
export declare function parseHarmonyRouterMap(content: string): RouterMapEntry[];
|
|
14
|
+
/** Parse `main_pages.json` `{ "src": ["pages/Index", ...] }`. */
|
|
15
|
+
export declare function parseHarmonyMainPages(content: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Spec 0041 — collect `{ name, value }` string resource entries (no edges).
|
|
18
|
+
* Same shape as literal-evidence: walk objects; skip illegal keys.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseHarmonyStringResources(content: string): Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
value: string;
|
|
23
|
+
line: number;
|
|
24
|
+
}>;
|
|
25
|
+
/** Spec 0048 — form_config.json `forms[].name`. */
|
|
26
|
+
export declare function parseHarmonyFormConfig(content: string): Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
line: number;
|
|
29
|
+
}>;
|
|
30
|
+
/** Spec 0048 — shortcuts_config.json `shortcuts[]`. */
|
|
31
|
+
export declare function parseHarmonyShortcutsConfig(content: string): Array<{
|
|
32
|
+
shortcutId: string;
|
|
33
|
+
label: string;
|
|
34
|
+
abilityName?: string;
|
|
35
|
+
line: number;
|
|
36
|
+
}>;
|
|
9
37
|
export declare const arktsEntryResolver: FrameworkResolver;
|
|
10
38
|
export declare function findArktsEntryPageComponent(context: ResolutionContext, pagePath: string): Node | null;
|
|
11
39
|
export declare function findArktsLifecycleMethod(context: ResolutionContext, component: Node, methodName: string): Node | null;
|
|
40
|
+
export {};
|
|
12
41
|
//# sourceMappingURL=arkts-entry.d.ts.map
|