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.
Files changed (40) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/extraction/grammars.d.ts +19 -0
  3. package/dist/extraction/grammars.js +44 -1
  4. package/dist/extraction/index.js +17 -12
  5. package/dist/extraction/languages/arkts.d.ts +40 -7
  6. package/dist/extraction/languages/arkts.js +290 -84
  7. package/dist/extraction/tree-sitter.js +12 -3
  8. package/dist/index.js +23 -12
  9. package/dist/mcp/arkts-evidence-packs.js +1 -0
  10. package/dist/mcp/daemon.d.ts +21 -3
  11. package/dist/mcp/daemon.js +60 -6
  12. package/dist/mcp/engine.d.ts +26 -0
  13. package/dist/mcp/engine.js +143 -7
  14. package/dist/mcp/index-availability.d.ts +40 -10
  15. package/dist/mcp/index-availability.js +89 -23
  16. package/dist/mcp/index.js +9 -0
  17. package/dist/mcp/indexable-root.d.ts +22 -0
  18. package/dist/mcp/indexable-root.js +140 -0
  19. package/dist/mcp/liveness-watchdog.d.ts +6 -1
  20. package/dist/mcp/liveness-watchdog.js +17 -5
  21. package/dist/mcp/locate-contract.d.ts +50 -0
  22. package/dist/mcp/locate-contract.js +146 -0
  23. package/dist/mcp/server-instructions.d.ts +2 -6
  24. package/dist/mcp/server-instructions.js +15 -5
  25. package/dist/mcp/session.js +15 -0
  26. package/dist/mcp/tools.d.ts +52 -0
  27. package/dist/mcp/tools.js +750 -103
  28. package/dist/project-map/index.d.ts +45 -0
  29. package/dist/project-map/index.js +373 -45
  30. package/dist/resolution/callback-synthesizer.js +251 -0
  31. package/dist/resolution/frameworks/arkts-entry.d.ts +35 -6
  32. package/dist/resolution/frameworks/arkts-entry.js +513 -30
  33. package/dist/resolution/index.js +25 -21
  34. package/dist/runtime-log.d.ts +52 -0
  35. package/dist/runtime-log.js +199 -0
  36. package/dist/search/query-plan-provider.js +3 -2
  37. package/dist/search/query-plan.js +21 -19
  38. package/dist/search/query-utils.d.ts +13 -0
  39. package/dist/search/query-utils.js +64 -0
  40. 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
- const originalConstraints = local.originalQuery + context;
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
- ? originalConstraints : `${originalConstraints}\n${rewrite}`, chosenIntent, retainedAnchors, proposedRelation);
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
- const canonicalQuery = withTaskContext(query, taskContext);
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
- const anchors = [...new Set([...extractQueryPlanAnchors(query),
191
- ...extractQueryPlanAnchors(taskContext ?? '')])].slice(0, 16);
192
- const literalTexts = extractQueryPlanLiteralTexts(canonicalQuery);
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] } : {}), dependsOn: [] }],
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
- const sourceScope = step.sourceScope ?? plan.sourceScope;
297
- // Scope and negations remain losslessly in originalQuery/taskContext for the
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 : withTaskContext(query, plan.taskContext);
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, sourceScope, dependsOn: [] }],
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.5.8",
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.92",
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",