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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec 0046 — coarse lifecycle lines + optional HOMEGRAPH_DEBUG tool traces.
|
|
3
|
+
*
|
|
4
|
+
* Sink: `.homegraph/daemon.log` (same file daemon stdio already uses) when a
|
|
5
|
+
* project root is known; always mirror to stderr. Soft-rotate at ~5 MiB.
|
|
6
|
+
*/
|
|
7
|
+
/** Soft rotate threshold for `.homegraph/daemon.log` (Spec 0046). */
|
|
8
|
+
export declare const DAEMON_LOG_MAX_BYTES: number;
|
|
9
|
+
/** Truncate free-text fields in log context (queries, messages). */
|
|
10
|
+
export declare const RUNTIME_LOG_TEXT_MAX = 80;
|
|
11
|
+
export type RuntimeLogLevel = 'info' | 'warn' | 'error' | 'debug';
|
|
12
|
+
export declare function getDaemonLogPath(projectRoot: string): string;
|
|
13
|
+
/** Compact `k=v` pairs; strings truncated; omit undefined/null. */
|
|
14
|
+
export declare function formatRuntimeLogContext(context?: Record<string, unknown>): string;
|
|
15
|
+
/**
|
|
16
|
+
* Stable one-line format (Spec 0046):
|
|
17
|
+
* `ISO8601Z [HomeGraph] <level> <event> key=value …`
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatRuntimeLogLine(level: RuntimeLogLevel, event: string, context?: Record<string, unknown>, now?: Date): string;
|
|
20
|
+
/** Rename oversized log to `daemon.log.1` (overwrite prior `.1`). */
|
|
21
|
+
export declare function maybeRotateDaemonLog(logPath: string, maxBytes?: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* Append one line to `.homegraph/daemon.log` under `projectRoot`.
|
|
24
|
+
* Never throws to callers — logging must not break MCP.
|
|
25
|
+
*/
|
|
26
|
+
export declare function appendProjectDaemonLog(projectRoot: string | null | undefined, line: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Always-on coarse event: stderr + daemon.log when `projectRoot` is set.
|
|
29
|
+
*/
|
|
30
|
+
export declare function logLifecycle(event: string, context?: Record<string, unknown> & {
|
|
31
|
+
projectRoot?: string;
|
|
32
|
+
}): void;
|
|
33
|
+
/**
|
|
34
|
+
* Always-on error-shaped lifecycle (still info-level event name namespace).
|
|
35
|
+
*/
|
|
36
|
+
export declare function logLifecycleError(event: string, context?: Record<string, unknown> & {
|
|
37
|
+
projectRoot?: string;
|
|
38
|
+
}): void;
|
|
39
|
+
/** True when fine-grained tool traces should emit (Spec 0046). */
|
|
40
|
+
export declare function isHomegraphDebugEnabled(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Tool-call summary — only when `HOMEGRAPH_DEBUG` is set.
|
|
43
|
+
* Does not dump full query/body; truncates query text.
|
|
44
|
+
*/
|
|
45
|
+
export declare function logToolDebug(toolName: string, context: {
|
|
46
|
+
projectRoot?: string;
|
|
47
|
+
durationMs: number;
|
|
48
|
+
isError?: boolean;
|
|
49
|
+
evidenceStatus?: string;
|
|
50
|
+
query?: string;
|
|
51
|
+
}): void;
|
|
52
|
+
//# sourceMappingURL=runtime-log.d.ts.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Spec 0046 — coarse lifecycle lines + optional HOMEGRAPH_DEBUG tool traces.
|
|
4
|
+
*
|
|
5
|
+
* Sink: `.homegraph/daemon.log` (same file daemon stdio already uses) when a
|
|
6
|
+
* project root is known; always mirror to stderr. Soft-rotate at ~5 MiB.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.RUNTIME_LOG_TEXT_MAX = exports.DAEMON_LOG_MAX_BYTES = void 0;
|
|
43
|
+
exports.getDaemonLogPath = getDaemonLogPath;
|
|
44
|
+
exports.formatRuntimeLogContext = formatRuntimeLogContext;
|
|
45
|
+
exports.formatRuntimeLogLine = formatRuntimeLogLine;
|
|
46
|
+
exports.maybeRotateDaemonLog = maybeRotateDaemonLog;
|
|
47
|
+
exports.appendProjectDaemonLog = appendProjectDaemonLog;
|
|
48
|
+
exports.logLifecycle = logLifecycle;
|
|
49
|
+
exports.logLifecycleError = logLifecycleError;
|
|
50
|
+
exports.isHomegraphDebugEnabled = isHomegraphDebugEnabled;
|
|
51
|
+
exports.logToolDebug = logToolDebug;
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const directory_1 = require("./directory");
|
|
55
|
+
/** Soft rotate threshold for `.homegraph/daemon.log` (Spec 0046). */
|
|
56
|
+
exports.DAEMON_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
57
|
+
/** Truncate free-text fields in log context (queries, messages). */
|
|
58
|
+
exports.RUNTIME_LOG_TEXT_MAX = 80;
|
|
59
|
+
function getDaemonLogPath(projectRoot) {
|
|
60
|
+
return path.join((0, directory_1.getHomeGraphDir)(projectRoot), 'daemon.log');
|
|
61
|
+
}
|
|
62
|
+
/** Compact `k=v` pairs; strings truncated; omit undefined/null. */
|
|
63
|
+
function formatRuntimeLogContext(context) {
|
|
64
|
+
if (!context)
|
|
65
|
+
return '';
|
|
66
|
+
const parts = [];
|
|
67
|
+
for (const [key, raw] of Object.entries(context)) {
|
|
68
|
+
if (raw === undefined || raw === null)
|
|
69
|
+
continue;
|
|
70
|
+
let value;
|
|
71
|
+
if (typeof raw === 'string') {
|
|
72
|
+
value = raw.length > exports.RUNTIME_LOG_TEXT_MAX
|
|
73
|
+
? `${raw.slice(0, exports.RUNTIME_LOG_TEXT_MAX)}…`
|
|
74
|
+
: raw;
|
|
75
|
+
}
|
|
76
|
+
else if (typeof raw === 'number' || typeof raw === 'boolean') {
|
|
77
|
+
value = String(raw);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
try {
|
|
81
|
+
const json = JSON.stringify(raw);
|
|
82
|
+
value = json.length > exports.RUNTIME_LOG_TEXT_MAX
|
|
83
|
+
? `${json.slice(0, exports.RUNTIME_LOG_TEXT_MAX)}…`
|
|
84
|
+
: json;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
value = String(raw);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Keep one token: spaces → _
|
|
91
|
+
value = value.replace(/\s+/g, '_');
|
|
92
|
+
parts.push(`${key}=${value}`);
|
|
93
|
+
}
|
|
94
|
+
return parts.length ? ` ${parts.join(' ')}` : '';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Stable one-line format (Spec 0046):
|
|
98
|
+
* `ISO8601Z [HomeGraph] <level> <event> key=value …`
|
|
99
|
+
*/
|
|
100
|
+
function formatRuntimeLogLine(level, event, context, now = new Date()) {
|
|
101
|
+
const iso = now.toISOString();
|
|
102
|
+
return `${iso} [HomeGraph] ${level} ${event}${formatRuntimeLogContext(context)}`;
|
|
103
|
+
}
|
|
104
|
+
/** Rename oversized log to `daemon.log.1` (overwrite prior `.1`). */
|
|
105
|
+
function maybeRotateDaemonLog(logPath, maxBytes = exports.DAEMON_LOG_MAX_BYTES) {
|
|
106
|
+
try {
|
|
107
|
+
const st = fs.statSync(logPath);
|
|
108
|
+
if (st.size < maxBytes)
|
|
109
|
+
return;
|
|
110
|
+
const rotated = `${logPath}.1`;
|
|
111
|
+
try {
|
|
112
|
+
fs.unlinkSync(rotated);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* no prior .1 */
|
|
116
|
+
}
|
|
117
|
+
fs.renameSync(logPath, rotated);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* missing or unreadable — append will create */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Append one line to `.homegraph/daemon.log` under `projectRoot`.
|
|
125
|
+
* Never throws to callers — logging must not break MCP.
|
|
126
|
+
*/
|
|
127
|
+
function appendProjectDaemonLog(projectRoot, line) {
|
|
128
|
+
if (!projectRoot)
|
|
129
|
+
return;
|
|
130
|
+
try {
|
|
131
|
+
const dir = (0, directory_1.getHomeGraphDir)(projectRoot);
|
|
132
|
+
// Spec 0049: never create `.homegraph/` solely to write a log line — that
|
|
133
|
+
// recreated the empty-workspace PROJECT_EXISTS trap for DevEco create.
|
|
134
|
+
if (!fs.existsSync(dir))
|
|
135
|
+
return;
|
|
136
|
+
const logPath = path.join(dir, 'daemon.log');
|
|
137
|
+
maybeRotateDaemonLog(logPath);
|
|
138
|
+
fs.appendFileSync(logPath, line.endsWith('\n') ? line : `${line}\n`, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* ignore I/O errors */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function writeStderr(line) {
|
|
145
|
+
try {
|
|
146
|
+
fs.writeSync(2, `${line}\n`);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
/* ignore */
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Always-on coarse event: stderr + daemon.log when `projectRoot` is set.
|
|
154
|
+
*/
|
|
155
|
+
function logLifecycle(event, context) {
|
|
156
|
+
const { projectRoot, ...rest } = context ?? {};
|
|
157
|
+
const line = formatRuntimeLogLine('info', event, {
|
|
158
|
+
...(projectRoot ? { root: projectRoot } : {}),
|
|
159
|
+
...rest,
|
|
160
|
+
});
|
|
161
|
+
writeStderr(line);
|
|
162
|
+
appendProjectDaemonLog(projectRoot, line);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Always-on error-shaped lifecycle (still info-level event name namespace).
|
|
166
|
+
*/
|
|
167
|
+
function logLifecycleError(event, context) {
|
|
168
|
+
const { projectRoot, ...rest } = context ?? {};
|
|
169
|
+
const line = formatRuntimeLogLine('error', event, {
|
|
170
|
+
...(projectRoot ? { root: projectRoot } : {}),
|
|
171
|
+
...rest,
|
|
172
|
+
});
|
|
173
|
+
writeStderr(line);
|
|
174
|
+
appendProjectDaemonLog(projectRoot, line);
|
|
175
|
+
}
|
|
176
|
+
/** True when fine-grained tool traces should emit (Spec 0046). */
|
|
177
|
+
function isHomegraphDebugEnabled() {
|
|
178
|
+
const v = process.env.HOMEGRAPH_DEBUG?.trim();
|
|
179
|
+
if (!v)
|
|
180
|
+
return false;
|
|
181
|
+
return v !== '0' && v.toLowerCase() !== 'false' && v.toLowerCase() !== 'off';
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Tool-call summary — only when `HOMEGRAPH_DEBUG` is set.
|
|
185
|
+
* Does not dump full query/body; truncates query text.
|
|
186
|
+
*/
|
|
187
|
+
function logToolDebug(toolName, context) {
|
|
188
|
+
if (!isHomegraphDebugEnabled())
|
|
189
|
+
return;
|
|
190
|
+
const { projectRoot, query, ...rest } = context;
|
|
191
|
+
const line = formatRuntimeLogLine('debug', `tool.${toolName}`, {
|
|
192
|
+
...(projectRoot ? { root: projectRoot } : {}),
|
|
193
|
+
...rest,
|
|
194
|
+
...(query !== undefined ? { query } : {}),
|
|
195
|
+
});
|
|
196
|
+
writeStderr(line);
|
|
197
|
+
appendProjectDaemonLog(projectRoot, line);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=runtime-log.js.map
|
|
@@ -75,7 +75,8 @@ function validateModelQueryPlan(value, local, options) {
|
|
|
75
75
|
const v = value;
|
|
76
76
|
const taskContext = (0, query_plan_1.normalizeQueryPlanTaskContext)(options.taskContext ?? local.taskContext);
|
|
77
77
|
const context = taskContext ? `\n${taskContext}` : '';
|
|
78
|
-
|
|
78
|
+
// Spec 0042: taskContext is retained on the plan for planner/literals, but must
|
|
79
|
+
// not be concatenated into the lexical canonicalQuery / FTS string.
|
|
79
80
|
const allowsOverview = (0, query_plan_1.queryExplicitlyRequestsProjectMap)(local.originalQuery);
|
|
80
81
|
const proposedRelation = relation(v.relation);
|
|
81
82
|
const proposedSourceScope = sourceScope(v.sourceScope) ?? 'local';
|
|
@@ -152,7 +153,7 @@ function validateModelQueryPlan(value, local, options) {
|
|
|
152
153
|
const retainedAnchors = normalized.anchors;
|
|
153
154
|
searchTerms = [...new Set([...searchTerms, ...normalized.terms])].slice(0, 24);
|
|
154
155
|
const canonicalQuery = (0, query_plan_1.adaptQueryPlanQuery)(downgradedOverview
|
|
155
|
-
?
|
|
156
|
+
? local.originalQuery : `${local.originalQuery}\n${rewrite}`, chosenIntent, retainedAnchors, proposedRelation);
|
|
156
157
|
if (canonicalQuery.length > 9000)
|
|
157
158
|
throw new Error('input_too_long');
|
|
158
159
|
// Every compiled step retains originalQuery/taskContext as constraints, not
|
|
@@ -58,11 +58,6 @@ exports.QUERY_SOURCE_SCOPES = ['local', 'sdk', 'all'];
|
|
|
58
58
|
function normalizeQueryPlanTaskContext(value) {
|
|
59
59
|
return typeof value === 'string' ? value.trim().slice(0, 4000) || undefined : undefined;
|
|
60
60
|
}
|
|
61
|
-
function withTaskContext(query, taskContext) {
|
|
62
|
-
// This string reaches lexical retrieval. Framework prose such as "code evidence"
|
|
63
|
-
// would become unrelated symbol seeds, so concatenate only the supplied data.
|
|
64
|
-
return taskContext && taskContext !== query ? `${query}\n${taskContext}` : query;
|
|
65
|
-
}
|
|
66
61
|
/** A host's original task cannot be replaced by an agent's abbreviated focus. */
|
|
67
62
|
function mergeQueryPlanTaskContext(original, focus) {
|
|
68
63
|
const parts = [normalizeQueryPlanTaskContext(original), normalizeQueryPlanTaskContext(focus)]
|
|
@@ -182,14 +177,19 @@ function extractQueryPlanLiteralTexts(query) {
|
|
|
182
177
|
function buildRuleQueryPlan(query, originalTaskContext) {
|
|
183
178
|
const started = Date.now();
|
|
184
179
|
const taskContext = normalizeQueryPlanTaskContext(originalTaskContext);
|
|
185
|
-
|
|
180
|
+
// Spec 0042: retrieval string is the user query only — never append taskContext.
|
|
181
|
+
const canonicalQuery = query;
|
|
186
182
|
const features = queryPlanFeatures(canonicalQuery);
|
|
187
183
|
const route = localRoute(canonicalQuery, features);
|
|
188
184
|
const intent = route === 'usages' || route === 'modules' || route === 'native'
|
|
189
185
|
? route : features.queryAsCrossModuleFlowSurvey ? 'flow' : 'general';
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const literalTexts =
|
|
186
|
+
// Anchors for retrieval come from the query only (taskContext prose must not seed FTS).
|
|
187
|
+
const anchors = [...new Set(extractQueryPlanAnchors(query))].slice(0, 16);
|
|
188
|
+
const literalTexts = [...new Set([
|
|
189
|
+
...extractQueryPlanLiteralTexts(query),
|
|
190
|
+
...extractQueryPlanLiteralTexts(taskContext ?? ''),
|
|
191
|
+
])].slice(0, 8);
|
|
192
|
+
const sourceScope = shape.resolveExploreSourceScope(query);
|
|
193
193
|
return {
|
|
194
194
|
version: exports.QUERY_PLAN_VERSION,
|
|
195
195
|
originalQuery: query,
|
|
@@ -200,8 +200,11 @@ function buildRuleQueryPlan(query, originalTaskContext) {
|
|
|
200
200
|
anchors,
|
|
201
201
|
...(literalTexts.length ? { literalTexts } : {}),
|
|
202
202
|
searchTerms: shape.extractSearchTerms(canonicalQuery, { stems: false }).slice(0, 24),
|
|
203
|
+
...(sourceScope !== 'all' ? { sourceScope } : {}),
|
|
203
204
|
steps: [{ id: 's1', query, intent, anchors: [...anchors],
|
|
204
|
-
...(literalTexts.length ? { literalTexts: [...literalTexts] } : {}),
|
|
205
|
+
...(literalTexts.length ? { literalTexts: [...literalTexts] } : {}),
|
|
206
|
+
...(sourceScope !== 'all' ? { sourceScope } : {}),
|
|
207
|
+
dependsOn: [] }],
|
|
205
208
|
features,
|
|
206
209
|
source: 'rules',
|
|
207
210
|
confidence: route !== 'general' || anchors.length ? 1 : 0.5,
|
|
@@ -293,20 +296,18 @@ function compileQueryPlanStep(plan, step, resolvedAnchors = []) {
|
|
|
293
296
|
const stepIntent = intentForQueryRelation(downgradedOverview ? 'general' : step.intent, relation);
|
|
294
297
|
const query = downgradedOverview ? plan.originalQuery : step.query;
|
|
295
298
|
const literalTexts = step.literalTexts ?? (plan.steps.length === 1 ? plan.literalTexts : undefined) ?? [];
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
// executor and cache. Repeating that prose here erases the step's focus.
|
|
299
|
-
// A planner's English explanation is not source text. Only its typed slots
|
|
300
|
-
// become seeds; this prevents a verb such as "locate" matching an SDK method.
|
|
301
|
-
// Legacy/malformed seedless steps retain the USER's query, never planner prose.
|
|
299
|
+
// Spec 0042: non-LLM retrieval must not concatenate taskContext into the FTS string.
|
|
300
|
+
// Planner prose stays out of seeds; only typed slots (anchors/searchTerms/literalTexts) retrieve.
|
|
302
301
|
const seeds = [...new Set([...anchors, ...(step.searchTerms ?? []), ...literalTexts])];
|
|
303
302
|
const retrievalQuery = plan.source === 'llm'
|
|
304
|
-
? seeds.join(' ') || plan.originalQuery :
|
|
303
|
+
? seeds.join(' ') || plan.originalQuery : query;
|
|
305
304
|
const canonicalQuery = plan.source === 'rules' && stepIntent === 'general'
|
|
306
305
|
? retrievalQuery : adaptQueryPlanQuery(retrievalQuery, stepIntent, anchors, relation);
|
|
307
306
|
const searchTerms = plan.source === 'llm' && seeds.length
|
|
308
307
|
? seeds.slice(0, 24) : [...new Set([...anchors, ...(step.searchTerms ?? []),
|
|
309
308
|
...shape.extractSearchTerms(retrievalQuery, { stems: false })])].slice(0, 24);
|
|
309
|
+
const resolvedScope = step.sourceScope ?? plan.sourceScope
|
|
310
|
+
?? shape.resolveExploreSourceScope(plan.originalQuery);
|
|
310
311
|
return {
|
|
311
312
|
...plan,
|
|
312
313
|
canonicalQuery,
|
|
@@ -316,8 +317,9 @@ function compileQueryPlanStep(plan, step, resolvedAnchors = []) {
|
|
|
316
317
|
searchTerms,
|
|
317
318
|
literalTexts,
|
|
318
319
|
relation,
|
|
319
|
-
sourceScope,
|
|
320
|
-
steps: [{ ...step, intent: stepIntent, anchors, literalTexts,
|
|
320
|
+
sourceScope: resolvedScope === 'all' ? undefined : resolvedScope,
|
|
321
|
+
steps: [{ ...step, intent: stepIntent, anchors, literalTexts,
|
|
322
|
+
sourceScope: resolvedScope === 'all' ? undefined : resolvedScope, dependsOn: [] }],
|
|
321
323
|
features: queryPlanFeatures(canonicalQuery),
|
|
322
324
|
};
|
|
323
325
|
}
|
|
@@ -19,6 +19,19 @@ export interface QueryMemberAccess {
|
|
|
19
19
|
* File basenames from queries — supports `Foo.ets` and path-style `common\\constants.ets`.
|
|
20
20
|
*/
|
|
21
21
|
export declare function extractFileBasenamesFromQuery(query: string): string[];
|
|
22
|
+
export declare function extractInRepoLocateAnchors(query: string): string[];
|
|
23
|
+
/** Query explicitly asks for SDK / Kit / d.ts surface (must not force sourceScope=local). */
|
|
24
|
+
export declare function queryExplicitlyRequestsSdkScope(query: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Spec 0042 — default explore retrieval scope.
|
|
27
|
+
* `local` when the query has in-repo locate signals; never override an explicit SDK ask.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveExploreSourceScope(query: string, planScope?: 'local' | 'sdk' | 'all'): 'local' | 'sdk' | 'all';
|
|
30
|
+
/** MCP-visible graph noise (ArkAnalyzer virtual entry / anonymous methods). */
|
|
31
|
+
export declare function isMcpNoiseNode(node: {
|
|
32
|
+
name: string;
|
|
33
|
+
filePath: string;
|
|
34
|
+
}): boolean;
|
|
22
35
|
/**
|
|
23
36
|
* Path-shaped anchors from a query — `feature/foo`, `staticcommon/launchercommon`.
|
|
24
37
|
*/
|
|
@@ -41,6 +41,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.IMPLEMENTATION_ENTRY_NAME_RE = exports.GENERIC_VERB_ANCHOR_NOISE = exports.STOP_WORDS = exports.FRAMEWORK_UI_DECORATOR_NAMES = exports.QUERY_SOURCE_FILE_EXT = void 0;
|
|
42
42
|
exports.normalizeNameToken = normalizeNameToken;
|
|
43
43
|
exports.extractFileBasenamesFromQuery = extractFileBasenamesFromQuery;
|
|
44
|
+
exports.extractInRepoLocateAnchors = extractInRepoLocateAnchors;
|
|
45
|
+
exports.queryExplicitlyRequestsSdkScope = queryExplicitlyRequestsSdkScope;
|
|
46
|
+
exports.resolveExploreSourceScope = resolveExploreSourceScope;
|
|
47
|
+
exports.isMcpNoiseNode = isMcpNoiseNode;
|
|
44
48
|
exports.extractPathSegmentsFromQuery = extractPathSegmentsFromQuery;
|
|
45
49
|
exports.queryMentionsCodeContext = queryMentionsCodeContext;
|
|
46
50
|
exports.extractKitModuleNamesFromQuery = extractKitModuleNamesFromQuery;
|
|
@@ -181,6 +185,66 @@ function extractFileBasenamesFromQuery(query) {
|
|
|
181
185
|
}
|
|
182
186
|
return [...basenames];
|
|
183
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Spec 0042 — PascalCase / CamelCase identifiers (≥8 chars) for in-repo locate seeding.
|
|
190
|
+
* Excludes short generic English tokens that pollute FTS when taken from taskContext-like prose.
|
|
191
|
+
*/
|
|
192
|
+
const IN_REPO_LOCATE_BLACKLIST = new Set([
|
|
193
|
+
'page', 'view', 'component', 'input', 'target', 'size', 'button', 'text', 'index',
|
|
194
|
+
'main', 'app', 'data', 'type', 'item', 'list', 'info', 'name', 'value', 'state',
|
|
195
|
+
'props', 'build', 'export', 'import', 'class', 'struct', 'method', 'function',
|
|
196
|
+
'object', 'string', 'number', 'boolean', 'result', 'error', 'event', 'option',
|
|
197
|
+
'config', 'manager', 'service', 'helper', 'utils', 'common', 'layout', 'style',
|
|
198
|
+
]);
|
|
199
|
+
function extractInRepoLocateAnchors(query) {
|
|
200
|
+
const out = new Set();
|
|
201
|
+
for (const m of query.matchAll(/\b([A-Z][A-Za-z0-9]*[a-z][A-Za-z0-9]*|[A-Z][a-z]+[A-Z][A-Za-z0-9]*)\b/g)) {
|
|
202
|
+
const tok = m[1];
|
|
203
|
+
if (tok.length < 8)
|
|
204
|
+
continue;
|
|
205
|
+
if (IN_REPO_LOCATE_BLACKLIST.has(tok.toLowerCase()))
|
|
206
|
+
continue;
|
|
207
|
+
out.add(tok);
|
|
208
|
+
}
|
|
209
|
+
for (const base of extractFileBasenamesFromQuery(query)) {
|
|
210
|
+
if (base.length >= 2 && !IN_REPO_LOCATE_BLACKLIST.has(base.toLowerCase()))
|
|
211
|
+
out.add(base);
|
|
212
|
+
}
|
|
213
|
+
return [...out].slice(0, 6);
|
|
214
|
+
}
|
|
215
|
+
/** Query explicitly asks for SDK / Kit / d.ts surface (must not force sourceScope=local). */
|
|
216
|
+
function queryExplicitlyRequestsSdkScope(query) {
|
|
217
|
+
return (/@kit\.|@ohos\./i.test(query)
|
|
218
|
+
|| /\.d\.ts\b/i.test(query)
|
|
219
|
+
|| /\b(?:HarmonyOS|OpenHarmony)\s+API\b/i.test(query)
|
|
220
|
+
|| /\bSDK\s+(?:API|docs?|declaration|stub)s?\b/i.test(query)
|
|
221
|
+
|| /\b(?:official\s+)?(?:API\s+)?reference\s+for\s+@/i.test(query));
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Spec 0042 — default explore retrieval scope.
|
|
225
|
+
* `local` when the query has in-repo locate signals; never override an explicit SDK ask.
|
|
226
|
+
*/
|
|
227
|
+
function resolveExploreSourceScope(query, planScope) {
|
|
228
|
+
if (planScope === 'sdk')
|
|
229
|
+
return 'sdk';
|
|
230
|
+
if (queryExplicitlyRequestsSdkScope(query)) {
|
|
231
|
+
return planScope ?? 'all';
|
|
232
|
+
}
|
|
233
|
+
if (extractFileBasenamesFromQuery(query).length > 0
|
|
234
|
+
|| extractInRepoLocateAnchors(query).length > 0
|
|
235
|
+
|| /[\u4e00-\u9fff]{2,}/.test(query)
|
|
236
|
+
|| queryAsCodeChangeOrientation(query)) {
|
|
237
|
+
return 'local';
|
|
238
|
+
}
|
|
239
|
+
return planScope ?? 'all';
|
|
240
|
+
}
|
|
241
|
+
/** MCP-visible graph noise (ArkAnalyzer virtual entry / anonymous methods). */
|
|
242
|
+
function isMcpNoiseNode(node) {
|
|
243
|
+
const fp = node.filePath.replace(/\\/g, '/');
|
|
244
|
+
return (fp.includes('@dummy')
|
|
245
|
+
|| node.name.startsWith('@dummy')
|
|
246
|
+
|| /^%AM\d+/i.test(node.name));
|
|
247
|
+
}
|
|
184
248
|
/**
|
|
185
249
|
* Path-shaped anchors from a query — `feature/foo`, `staticcommon/launchercommon`.
|
|
186
250
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homegraph",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Supercharge AI coding agents with semantic code intelligence — surgical context, fewer tool calls, faster answers. 100% local.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@clack/prompts": "^1.7.0",
|
|
39
|
-
"arkanalyzer": "^1.0.
|
|
39
|
+
"arkanalyzer": "^1.0.94",
|
|
40
40
|
"commander": "^14.0.3",
|
|
41
41
|
"fast-string-width": "^3.0.2",
|
|
42
42
|
"fast-wrap-ansi": "^0.2.0",
|