homegraph 1.5.8 → 1.5.10

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.
@@ -1,44 +1,58 @@
1
1
  "use strict";
2
2
  /**
3
- * Product-facing index availability for MCP tool results (Spec 0032).
3
+ * Product-facing index availability for MCP tool results (Spec 0032 + 0035).
4
4
  *
5
- * Internal `build_phase` stays as-is; this layer maps it (+ write-lock / busy)
6
- * to the four states hosts and agents should see in tool text.
5
+ * Internal `build_phase` stays as-is; this layer maps it (+ write-lock / busy /
6
+ * pending dirty files) to the five states hosts and agents should see.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.PRODUCT_STATUS_GLOSSARY = void 0;
9
10
  exports.productIndexGuidance = productIndexGuidance;
11
+ exports.formatProductStatusLine = formatProductStatusLine;
10
12
  exports.resolveProductIndexState = resolveProductIndexState;
11
13
  exports.isSqliteBusyMessage = isSqliteBusyMessage;
12
- const EMPTY_LINES = [
13
- 'HomeGraph status=empty — project map is not ready yet.',
14
- 'Retry in a few seconds (or call `homegraph_project` once the fast build completes).',
15
- ];
16
- const FAST_LINES = [
17
- 'HomeGraph status=fast — module/file map is ready; full symbol index is still building.',
18
- 'Use `homegraph_project` for the module/file map now, then retry this tool once indexing finishes.',
19
- ];
20
- const SYNCING_LINES = [
21
- 'HomeGraph status=syncing — the index is being written (lock held or local index/sync in progress).',
22
- 'Retry this tool shortly; do not busy-loop.',
23
- ];
14
+ exports.textAlreadyHasProductStatus = textAlreadyHasProductStatus;
15
+ /** One-line glossary for MCP initialize / tool surface (Spec 0035). */
16
+ exports.PRODUCT_STATUS_GLOSSARY = 'status: empty=not ready · fast=map only (homegraph_project) · full=fresh · dirty=usable but listed paths outdated · syncing=write lock, retry';
17
+ const MAX_DIRTY_PATHS = 5;
18
+ /** Whole-response guidance when the tool cannot usefully answer yet. */
24
19
  function productIndexGuidance(state) {
25
20
  switch (state) {
26
21
  case 'empty':
27
- return EMPTY_LINES.join('\n');
22
+ return 'HomeGraph status=empty — not ready; retry shortly.';
28
23
  case 'fast':
29
- return FAST_LINES.join('\n');
24
+ return 'HomeGraph status=fast — map only; use homegraph_project; retry other tools later.';
30
25
  case 'syncing':
31
- return SYNCING_LINES.join('\n');
26
+ return 'HomeGraph status=syncing — locked; retry shortly, do not loop.';
27
+ }
28
+ }
29
+ /**
30
+ * Single status line for tool footers (and for guidance-only replies).
31
+ * `pendingPaths` only used when `state === 'dirty'`.
32
+ */
33
+ function formatProductStatusLine(state, opts) {
34
+ switch (state) {
35
+ case 'empty':
36
+ case 'fast':
37
+ case 'syncing':
38
+ return productIndexGuidance(state);
39
+ case 'full':
40
+ return 'HomeGraph status=full — complete and up to date.';
41
+ case 'dirty': {
42
+ const paths = opts?.pendingPaths ?? [];
43
+ if (paths.length === 0) {
44
+ return 'HomeGraph status=dirty — outdated: pending files';
45
+ }
46
+ const shown = paths.slice(0, MAX_DIRTY_PATHS);
47
+ const more = paths.length > MAX_DIRTY_PATHS ? `, …+${paths.length - MAX_DIRTY_PATHS}` : '';
48
+ return `HomeGraph status=dirty — outdated: ${shown.join(', ')}${more}`;
49
+ }
32
50
  }
33
51
  }
34
52
  /**
35
53
  * Resolve the product state from a live HomeGraph handle.
36
54
  *
37
- * - empty: fast map not ready (`none` / `building_fast`)
38
- * - fast: map ready, full index not done (`fast` / `indexing`)
39
- * - full: symbol index ready
40
- * - syncing: full (or empty-while-contended-init) and a writer holds the lock /
41
- * this process is indexing — reads may fail or see a moving target
55
+ * Priority: syncing > empty > fast > dirty > full
42
56
  */
43
57
  function resolveProductIndexState(cg) {
44
58
  let phase;
@@ -69,10 +83,23 @@ function resolveProductIndexState(cg) {
69
83
  catch {
70
84
  return 'syncing';
71
85
  }
86
+ let pendingCount = 0;
87
+ try {
88
+ pendingCount = cg.getPendingFiles?.()?.length ?? 0;
89
+ }
90
+ catch {
91
+ pendingCount = 0;
92
+ }
93
+ if (pendingCount > 0)
94
+ return 'dirty';
72
95
  return 'full';
73
96
  }
74
97
  /** True when an error message indicates SQLite writer contention. */
75
98
  function isSqliteBusyMessage(message) {
76
99
  return /SQLITE_BUSY|database is locked/i.test(message);
77
100
  }
101
+ /** True when text is already a pure (or leading) HomeGraph status line. */
102
+ function textAlreadyHasProductStatus(text) {
103
+ return /^\s*HomeGraph status=/m.test(text);
104
+ }
78
105
  //# sourceMappingURL=index-availability.js.map
@@ -1,7 +1,3 @@
1
- /**
2
- * MCP initialize 中的统一工具指引;工具描述必须遵守相同的按需策略。
3
- * 仅调整检索选择,不改变索引、查询语义或编码任务的验收要求。
4
- */
5
- export declare const SERVER_INSTRUCTIONS = "# HomeGraph \u2014 optional structural evidence for this repo\n\n## When to call (path-first, bash-first)\n\nUse ordinary bash/search/read tools first for repository paths, symbols, literal strings and local changes. Skip HomeGraph when those tools provide sufficient evidence, including for difficult implementation tasks. A known path can be read directly; an adequate source result does not need a second graph lookup.\n\nHomeGraph is optional. Use it only for a concrete unresolved relationship that benefits from graph evidence: cross-file state/event propagation, callers/callees, module dependencies or ArkTS-to-native registration. Name the missing relation and use anchors from the current task or source. There is no mandatory number of bash searches before a useful graph query.\n\nChoose the smallest available tool for that gap:\n- Exact usage/reference locations \u2192 `homegraph_usages`; callers/callees \u2192 the corresponding tool.\n- Named module dependencies/cycles \u2192 `homegraph_modules`; native exports/registration \u2192 `homegraph_native`.\n- One missing symbol body \u2192 `homegraph_node`; prefer direct read if its path is already known.\n- An unresolved cross-symbol mechanism \u2192 `homegraph_explore`. Do not use it for routine pre-edit orientation, a literal search, or to re-confirm source already found with bash.\n- ArkUI migration analysis \u2192 `homegraph_arkui_migrate` when that analysis is needed; SDK contracts \u2192 project declarations or SDK documentation.\n\nDo not call explore after a focused tool already answered the relation. Keep working directly once the edit location and affected behavior are sufficiently supported. Task difficulty and file count alone do not require graph use.\n\n## Query and recovery\n\nWrite one focused sentence: requested action + target + known anchors + unresolved relation + preservation constraints. Use the full public task as `taskContext` when needed. Keep UI labels verbatim; use exact symbols from the task or source instead of inventing names or piling up generic keywords. Use returned repository-relative paths without reconstructing an experiment directory; a path refusal requires valid in-repo relocation, not broader permissions.\n\nA project map is navigation, not proof of a located feature. Preserve requested product/module scope and verify each candidate before editing. Start with one focused graph request; recover only a named missing body/relation. Budget: \u22642 `homegraph_explore` attempts per project, \u22641 focused depth recovery; existing runtime budgets may be tighter. These are ceilings, never a required sequence. If evidence is still missing, use targeted bash/search/read and continue implementation. Do not expand into unrelated files merely to exhaust a budget.\n\n\n- ArkTS evidence packs keep complete declarations and the source dependencies of displayed static relations together. Gaps identify omitted, stale or unindexed evidence; inspect only a gap relevant to the task. A static link does not prove runtime ordering or value propagation, and a complete declaration does not prove its enclosing call conditions.\n- ArkTS path evidence follows typed, directed relations between named anchors within a bounded search. A provided path includes intermediate declarations and registration sites. Check its goal and stop reason; no path in scope does not prove no relationship. Qualify ambiguous symbols with their owning type or file.\n- Reuse complete, unchanged, line-numbered source ranges already visible. An outline, path list, truncated body or SDK declaration cannot replace missing implementation evidence. A slice hash identifies that excerpt, not the whole file. Refresh affected ranges after edits.\n- An empty edge set means the relation may be unindexed, not absent. For partial, stale or irrelevant results, inspect the exact missing source with scoped search/read. After a query adds no evidence, change the method or scope rather than paraphrasing the same explore.\n- Retrieval completion is not task completion. Continue the requested edits and validation; check the original task's behavior and preservation constraints. Build success alone does not establish functional correctness.\n\nNo index \u2192 use ordinary tools; indexing is managed by the host. Do not run HomeGraph initialization as part of solving the task.\n";
6
- export declare const SERVER_INSTRUCTIONS_NO_ROOT_INDEX = "# HomeGraph \u2014 optional per-project evidence\n\nPass `projectPath` to an already indexed folder with `.homegraph/`. No index \u2192 use ordinary tools; indexing is managed by the host.\n\n## When to call (path-first, bash-first)\n\nUse ordinary bash/search/read tools first for repository paths, symbols, literal strings and local changes. Skip HomeGraph when those tools provide sufficient evidence, including for difficult implementation tasks. A known path can be read directly; an adequate source result does not need a second graph lookup.\n\nHomeGraph is optional. Use it only for a concrete unresolved relationship that benefits from graph evidence: cross-file state/event propagation, callers/callees, module dependencies or ArkTS-to-native registration. Name the missing relation and use anchors from the current task or source. There is no mandatory number of bash searches before a useful graph query.\n\nChoose the smallest available tool for that gap:\n- Exact usage/reference locations \u2192 `homegraph_usages`; callers/callees \u2192 the corresponding tool.\n- Named module dependencies/cycles \u2192 `homegraph_modules`; native exports/registration \u2192 `homegraph_native`.\n- One missing symbol body \u2192 `homegraph_node`; prefer direct read if its path is already known.\n- An unresolved cross-symbol mechanism \u2192 `homegraph_explore`. Do not use it for routine pre-edit orientation, a literal search, or to re-confirm source already found with bash.\n- ArkUI migration analysis \u2192 `homegraph_arkui_migrate` when that analysis is needed; SDK contracts \u2192 project declarations or SDK documentation.\n\nDo not call explore after a focused tool already answered the relation. Keep working directly once the edit location and affected behavior are sufficiently supported. Task difficulty and file count alone do not require graph use.\n\n## Query and recovery\n\nWrite one focused sentence: requested action + target + known anchors + unresolved relation + preservation constraints. Use the full public task as `taskContext` when needed. Keep UI labels verbatim; use exact symbols from the task or source instead of inventing names or piling up generic keywords. Use returned repository-relative paths without reconstructing an experiment directory; a path refusal requires valid in-repo relocation, not broader permissions.\n\nA project map is navigation, not proof of a located feature. Preserve requested product/module scope and verify each candidate before editing. Start with one focused graph request; recover only a named missing body/relation. Budget: \u22642 `homegraph_explore` attempts per project, \u22641 focused depth recovery; existing runtime budgets may be tighter. These are ceilings, never a required sequence. If evidence is still missing, use targeted bash/search/read and continue implementation. Do not expand into unrelated files merely to exhaust a budget.\n\n\n- ArkTS evidence packs keep complete declarations and the source dependencies of displayed static relations together. Gaps identify omitted, stale or unindexed evidence; inspect only a gap relevant to the task. A static link does not prove runtime ordering or value propagation, and a complete declaration does not prove its enclosing call conditions.\n- ArkTS path evidence follows typed, directed relations between named anchors within a bounded search. A provided path includes intermediate declarations and registration sites. Check its goal and stop reason; no path in scope does not prove no relationship. Qualify ambiguous symbols with their owning type or file.\n- Reuse complete, unchanged, line-numbered source ranges already visible. An outline, path list, truncated body or SDK declaration cannot replace missing implementation evidence. A slice hash identifies that excerpt, not the whole file. Refresh affected ranges after edits.\n- An empty edge set means the relation may be unindexed, not absent. For partial, stale or irrelevant results, inspect the exact missing source with scoped search/read. After a query adds no evidence, change the method or scope rather than paraphrasing the same explore.\n- Retrieval completion is not task completion. Continue the requested edits and validation; check the original task's behavior and preservation constraints. Build success alone does not establish functional correctness.\n\n";
1
+ export declare const SERVER_INSTRUCTIONS = "# HomeGraph \u2014 optional structural evidence for this repo\n\n## When to call (path-first, bash-first)\n\nUse ordinary bash/search/read tools first for repository paths, symbols, literal strings and local changes. Skip HomeGraph when those tools provide sufficient evidence, including for difficult implementation tasks. A known path can be read directly; an adequate source result does not need a second graph lookup.\n\nHomeGraph is optional. Use it only for a concrete unresolved relationship that benefits from graph evidence: cross-file state/event propagation, callers/callees, module dependencies or ArkTS-to-native registration. Name the missing relation and use anchors from the current task or source. There is no mandatory number of bash searches before a useful graph query.\n\nChoose the smallest available tool for that gap:\n- Exact usage/reference locations \u2192 `homegraph_usages`; callers/callees \u2192 the corresponding tool.\n- Named module dependencies/cycles \u2192 `homegraph_modules`; native exports/registration \u2192 `homegraph_native`.\n- One missing symbol body \u2192 `homegraph_node`; prefer direct read if its path is already known.\n- An unresolved cross-symbol mechanism \u2192 `homegraph_explore`. Do not use it for routine pre-edit orientation, a literal search, or to re-confirm source already found with bash.\n- ArkUI migration analysis \u2192 `homegraph_arkui_migrate` when that analysis is needed; SDK contracts \u2192 project declarations or SDK documentation.\n\nDo not call explore after a focused tool already answered the relation. Keep working directly once the edit location and affected behavior are sufficiently supported. Task difficulty and file count alone do not require graph use.\n\n## Query and recovery\n\nWrite one focused sentence: requested action + target + known anchors + unresolved relation + preservation constraints. Use the full public task as `taskContext` when needed. Keep UI labels verbatim; use exact symbols from the task or source instead of inventing names or piling up generic keywords. Use returned repository-relative paths without reconstructing an experiment directory; a path refusal requires valid in-repo relocation, not broader permissions.\n\nA project map is navigation, not proof of a located feature. Preserve requested product/module scope and verify each candidate before editing. Start with one focused graph request; recover only a named missing body/relation. Budget: \u22642 `homegraph_explore` attempts per project, \u22641 focused depth recovery; existing runtime budgets may be tighter. These are ceilings, never a required sequence. If evidence is still missing, use targeted bash/search/read and continue implementation. Do not expand into unrelated files merely to exhaust a budget.\n\n## Index status\n\nTool replies end with `HomeGraph status=\u2026`. status: empty=not ready \u00B7 fast=map only (homegraph_project) \u00B7 full=fresh \u00B7 dirty=usable but listed paths outdated \u00B7 syncing=write lock, retry.\n\n\n- ArkTS evidence packs keep complete declarations and the source dependencies of displayed static relations together. Gaps identify omitted, stale or unindexed evidence; inspect only a gap relevant to the task. A static link does not prove runtime ordering or value propagation, and a complete declaration does not prove its enclosing call conditions.\n- ArkTS path evidence follows typed, directed relations between named anchors within a bounded search. A provided path includes intermediate declarations and registration sites. Check its goal and stop reason; no path in scope does not prove no relationship. Qualify ambiguous symbols with their owning type or file.\n- Reuse complete, unchanged, line-numbered source ranges already visible. An outline, path list, truncated body or SDK declaration cannot replace missing implementation evidence. A slice hash identifies that excerpt, not the whole file. Refresh affected ranges after edits.\n- An empty edge set means the relation may be unindexed, not absent. For partial, stale or irrelevant results, inspect the exact missing source with scoped search/read. After a query adds no evidence, change the method or scope rather than paraphrasing the same explore.\n- Retrieval completion is not task completion. Continue the requested edits and validation; check the original task's behavior and preservation constraints. Build success alone does not establish functional correctness.\n\nNo index \u2192 use ordinary tools; indexing is managed by the host. Do not run HomeGraph initialization as part of solving the task.\n";
2
+ export declare const SERVER_INSTRUCTIONS_NO_ROOT_INDEX = "# HomeGraph \u2014 optional per-project evidence\n\nPass `projectPath` to an already indexed folder with `.homegraph/`. No index \u2192 use ordinary tools; indexing is managed by the host.\n\n## When to call (path-first, bash-first)\n\nUse ordinary bash/search/read tools first for repository paths, symbols, literal strings and local changes. Skip HomeGraph when those tools provide sufficient evidence, including for difficult implementation tasks. A known path can be read directly; an adequate source result does not need a second graph lookup.\n\nHomeGraph is optional. Use it only for a concrete unresolved relationship that benefits from graph evidence: cross-file state/event propagation, callers/callees, module dependencies or ArkTS-to-native registration. Name the missing relation and use anchors from the current task or source. There is no mandatory number of bash searches before a useful graph query.\n\nChoose the smallest available tool for that gap:\n- Exact usage/reference locations \u2192 `homegraph_usages`; callers/callees \u2192 the corresponding tool.\n- Named module dependencies/cycles \u2192 `homegraph_modules`; native exports/registration \u2192 `homegraph_native`.\n- One missing symbol body \u2192 `homegraph_node`; prefer direct read if its path is already known.\n- An unresolved cross-symbol mechanism \u2192 `homegraph_explore`. Do not use it for routine pre-edit orientation, a literal search, or to re-confirm source already found with bash.\n- ArkUI migration analysis \u2192 `homegraph_arkui_migrate` when that analysis is needed; SDK contracts \u2192 project declarations or SDK documentation.\n\nDo not call explore after a focused tool already answered the relation. Keep working directly once the edit location and affected behavior are sufficiently supported. Task difficulty and file count alone do not require graph use.\n\n## Query and recovery\n\nWrite one focused sentence: requested action + target + known anchors + unresolved relation + preservation constraints. Use the full public task as `taskContext` when needed. Keep UI labels verbatim; use exact symbols from the task or source instead of inventing names or piling up generic keywords. Use returned repository-relative paths without reconstructing an experiment directory; a path refusal requires valid in-repo relocation, not broader permissions.\n\nA project map is navigation, not proof of a located feature. Preserve requested product/module scope and verify each candidate before editing. Start with one focused graph request; recover only a named missing body/relation. Budget: \u22642 `homegraph_explore` attempts per project, \u22641 focused depth recovery; existing runtime budgets may be tighter. These are ceilings, never a required sequence. If evidence is still missing, use targeted bash/search/read and continue implementation. Do not expand into unrelated files merely to exhaust a budget.\n\n## Index status\n\nTool replies end with `HomeGraph status=\u2026`. status: empty=not ready \u00B7 fast=map only (homegraph_project) \u00B7 full=fresh \u00B7 dirty=usable but listed paths outdated \u00B7 syncing=write lock, retry.\n\n\n- ArkTS evidence packs keep complete declarations and the source dependencies of displayed static relations together. Gaps identify omitted, stale or unindexed evidence; inspect only a gap relevant to the task. A static link does not prove runtime ordering or value propagation, and a complete declaration does not prove its enclosing call conditions.\n- ArkTS path evidence follows typed, directed relations between named anchors within a bounded search. A provided path includes intermediate declarations and registration sites. Check its goal and stop reason; no path in scope does not prove no relationship. Qualify ambiguous symbols with their owning type or file.\n- Reuse complete, unchanged, line-numbered source ranges already visible. An outline, path list, truncated body or SDK declaration cannot replace missing implementation evidence. A slice hash identifies that excerpt, not the whole file. Refresh affected ranges after edits.\n- An empty edge set means the relation may be unindexed, not absent. For partial, stale or irrelevant results, inspect the exact missing source with scoped search/read. After a query adds no evidence, change the method or scope rather than paraphrasing the same explore.\n- Retrieval completion is not task completion. Continue the requested edits and validation; check the original task's behavior and preservation constraints. Build success alone does not establish functional correctness.\n\n";
7
3
  //# sourceMappingURL=server-instructions.d.ts.map
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SERVER_INSTRUCTIONS_NO_ROOT_INDEX = exports.SERVER_INSTRUCTIONS = void 0;
4
+ const index_availability_1 = require("./index-availability");
2
5
  /**
3
6
  * MCP initialize 中的统一工具指引;工具描述必须遵守相同的按需策略。
4
7
  * 仅调整检索选择,不改变索引、查询语义或编码任务的验收要求。
5
8
  */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.SERVER_INSTRUCTIONS_NO_ROOT_INDEX = exports.SERVER_INSTRUCTIONS = void 0;
8
9
  const SOURCE_AND_VALIDATION = `
9
10
  - ArkTS evidence packs keep complete declarations and the source dependencies of displayed static relations together. Gaps identify omitted, stale or unindexed evidence; inspect only a gap relevant to the task. A static link does not prove runtime ordering or value propagation, and a complete declaration does not prove its enclosing call conditions.
10
11
  - ArkTS path evidence follows typed, directed relations between named anchors within a bounded search. A provided path includes intermediate declarations and registration sites. Check its goal and stop reason; no path in scope does not prove no relationship. Qualify ambiguous symbols with their owning type or file.
@@ -33,10 +34,15 @@ Write one focused sentence: requested action + target + known anchors + unresolv
33
34
 
34
35
  A project map is navigation, not proof of a located feature. Preserve requested product/module scope and verify each candidate before editing. Start with one focused graph request; recover only a named missing body/relation. Budget: ≤2 \`homegraph_explore\` attempts per project, ≤1 focused depth recovery; existing runtime budgets may be tighter. These are ceilings, never a required sequence. If evidence is still missing, use targeted bash/search/read and continue implementation. Do not expand into unrelated files merely to exhaust a budget.
35
36
  `;
37
+ const INDEX_STATUS = `## Index status
38
+
39
+ Tool replies end with \`HomeGraph status=…\`. ${index_availability_1.PRODUCT_STATUS_GLOSSARY}.
40
+ `;
36
41
  exports.SERVER_INSTRUCTIONS = `# HomeGraph — optional structural evidence for this repo
37
42
 
38
43
  ${ON_DEMAND}
39
44
  ${QUERY}
45
+ ${INDEX_STATUS}
40
46
  ${SOURCE_AND_VALIDATION}
41
47
  No index → use ordinary tools; indexing is managed by the host. Do not run HomeGraph initialization as part of solving the task.
42
48
  `;
@@ -46,6 +52,7 @@ Pass \`projectPath\` to an already indexed folder with \`.homegraph/\`. No index
46
52
 
47
53
  ${ON_DEMAND}
48
54
  ${QUERY}
55
+ ${INDEX_STATUS}
49
56
  ${SOURCE_AND_VALIDATION}
50
57
  `;
51
58
  //# sourceMappingURL=server-instructions.js.map
@@ -418,6 +418,11 @@ export declare class ToolHandler {
418
418
  */
419
419
  private isFileStaleOnDisk;
420
420
  private withStalenessNotice;
421
+ /**
422
+ * Append a one-line product index status footer (Spec 0035).
423
+ * Guidance-only replies that already start with `HomeGraph status=` are left alone.
424
+ */
425
+ private withProductStatusFooter;
421
426
  /**
422
427
  * Execute a tool by name.
423
428
  *
package/dist/mcp/tools.js CHANGED
@@ -26,7 +26,6 @@ const evidence_rendering_1 = require("./evidence-rendering");
26
26
  const arkts_evidence_packs_1 = require("./arkts-evidence-packs");
27
27
  const evidence_paths_1 = require("../graph/evidence-paths");
28
28
  const query_plan_1 = require("../search/query-plan");
29
- const memory_budget_1 = require("./memory-budget");
30
29
  const index_availability_1 = require("./index-availability");
31
30
  const directory_1 = require("../directory");
32
31
  // Lazy-load the heavy HomeGraph chain off the MCP startup path — see the same
@@ -1560,12 +1559,11 @@ class ToolHandler {
1560
1559
  if (state === 'empty') {
1561
1560
  return (0, index_availability_1.productIndexGuidance)('empty');
1562
1561
  }
1563
- if (state === 'fast' || state === 'syncing') {
1564
- // Keep the Spec 0027 "still building → homegraph_project" story for
1565
- // frozen tools/list descriptions (hosts snapshot once at connect).
1566
- return state === 'syncing'
1567
- ? (0, index_availability_1.productIndexGuidance)('syncing')
1568
- : "HomeGraph is still building this project's index. A call right now returns build-progress guidance; use `homegraph_project` for the module/file map, then retry once indexing finishes.";
1562
+ if (state === 'fast') {
1563
+ return (0, index_availability_1.productIndexGuidance)('fast');
1564
+ }
1565
+ if (state === 'syncing') {
1566
+ return (0, index_availability_1.productIndexGuidance)('syncing');
1569
1567
  }
1570
1568
  }
1571
1569
  catch {
@@ -1894,55 +1892,61 @@ class ToolHandler {
1894
1892
  const composed = `${formatDegradedBanner(reason)}\n\n${head.text}`;
1895
1893
  return { ...result, content: [{ type: 'text', text: composed }, ...tail] };
1896
1894
  }
1897
- // Defensive: some test fakes inject a partial HomeGraph stub without the
1898
- // newer pending-files API. Treat missing/throwing as "no pending files."
1899
- let pending = [];
1900
- try {
1901
- pending = cg.getPendingFiles?.() ?? [];
1902
- }
1903
- catch {
1904
- return result;
1905
- }
1906
- if (pending.length === 0)
1895
+ // Spec 0035: pending/dirty is a one-line status footer (withProductStatusFooter),
1896
+ // not the long ⚠️ stale banner. Keep formatStaleBanner helpers for tests /
1897
+ // status tool; do not prepend them on every read tool.
1898
+ return result;
1899
+ }
1900
+ /**
1901
+ * Append a one-line product index status footer (Spec 0035).
1902
+ * Guidance-only replies that already start with `HomeGraph status=` are left alone.
1903
+ */
1904
+ withProductStatusFooter(result, projectPath) {
1905
+ if (result.isError)
1907
1906
  return result;
1908
1907
  const [first, ...rest] = result.content;
1909
1908
  if (!first || first.type !== 'text')
1910
1909
  return result;
1911
- const text = first.text;
1912
- const inResponse = [];
1913
- const elsewhere = [];
1914
- for (const p of pending) {
1915
- // Substring match against the project-relative POSIX path — that's
1916
- // exactly the format both the watcher and every homegraph response
1917
- // emit, so a plain includes() is sufficient and avoids regex pitfalls.
1918
- if (text.includes(p.path))
1919
- inResponse.push(p);
1920
- else
1921
- elsewhere.push(p);
1910
+ if ((0, index_availability_1.textAlreadyHasProductStatus)(first.text))
1911
+ return result;
1912
+ let cg;
1913
+ try {
1914
+ cg = this.getHomeGraph(projectPath);
1915
+ }
1916
+ catch {
1917
+ return result;
1922
1918
  }
1923
- let banner = '';
1924
- if (inResponse.length > 0) {
1925
- let dbPath = null;
1919
+ if (this.cg && cg !== this.cg) {
1926
1920
  try {
1927
- // Large indexes skip catch-up — soft banner so agents don't abandon HG for Read.
1928
- const root = cg.getProjectRoot();
1929
- dbPath = (0, path_1.resolve)(root, '.homegraph', 'homegraph.db');
1921
+ const sameProject = (0, path_1.resolve)(this.cg.getProjectRoot()) === (0, path_1.resolve)(cg.getProjectRoot());
1922
+ if (sameProject)
1923
+ cg = this.cg;
1930
1924
  }
1931
1925
  catch {
1932
- dbPath = null;
1926
+ /* leave cg */
1933
1927
  }
1934
- banner = formatStaleBanner(inResponse, {
1935
- catchUpDeferred: (0, memory_budget_1.shouldSkipCatchUpSync)(dbPath),
1936
- });
1937
1928
  }
1938
- let footer = '';
1939
- if (elsewhere.length > 0) {
1940
- footer = formatStaleFooter(elsewhere);
1929
+ let state;
1930
+ try {
1931
+ state = (0, index_availability_1.resolveProductIndexState)(cg);
1941
1932
  }
1942
- if (!banner && !footer)
1933
+ catch {
1943
1934
  return result;
1944
- const composed = [banner, text, footer].filter(Boolean).join('\n\n');
1945
- return { ...result, content: [{ type: 'text', text: composed }, ...rest] };
1935
+ }
1936
+ let pendingPaths;
1937
+ if (state === 'dirty') {
1938
+ try {
1939
+ pendingPaths = (cg.getPendingFiles?.() ?? []).map((p) => p.path);
1940
+ }
1941
+ catch {
1942
+ pendingPaths = [];
1943
+ }
1944
+ }
1945
+ const line = (0, index_availability_1.formatProductStatusLine)(state, { pendingPaths });
1946
+ return {
1947
+ ...result,
1948
+ content: [{ type: 'text', text: `${first.text}\n\n${line}` }, ...rest],
1949
+ };
1946
1950
  }
1947
1951
  /**
1948
1952
  * Execute a tool by name.
@@ -2069,7 +2073,7 @@ class ToolHandler {
2069
2073
  && this.areEvidenceFilesCurrent(cachedFiles ?? [], cacheCg.getProjectRoot())))) {
2070
2074
  const diagnosed = this.withQueryPlanDiagnostics(cached, args, true);
2071
2075
  const withWorktree = this.withWorktreeNotice(diagnosed, projectPath);
2072
- return this.withStalenessNotice(withWorktree, projectPath);
2076
+ return this.withProductStatusFooter(this.withStalenessNotice(withWorktree, projectPath), projectPath);
2073
2077
  }
2074
2078
  }
2075
2079
  catch {
@@ -2133,7 +2137,7 @@ class ToolHandler {
2133
2137
  cacheIndex.setEntry(cacheQueries, cacheKey, toolName, served);
2134
2138
  }
2135
2139
  const withWorktree = this.withWorktreeNotice(served, projectPath);
2136
- return this.withStalenessNotice(withWorktree, projectPath);
2140
+ return this.withProductStatusFooter(this.withStalenessNotice(withWorktree, projectPath), projectPath);
2137
2141
  }
2138
2142
  if (requestPlan)
2139
2143
  args[QUERY_FAST_ATTEMPTED_ARG] = true;
@@ -2164,7 +2168,7 @@ class ToolHandler {
2164
2168
  cacheIndex.setEntry(cacheQueries, cacheKey, toolName, result);
2165
2169
  }
2166
2170
  const withWorktree = this.withWorktreeNotice(result, projectPath);
2167
- return this.withStalenessNotice(withWorktree, projectPath);
2171
+ return this.withProductStatusFooter(this.withStalenessNotice(withWorktree, projectPath), projectPath);
2168
2172
  }
2169
2173
  catch (err) {
2170
2174
  // Expected condition, not a malfunction: answer as a SUCCESS so the
@@ -2509,7 +2513,7 @@ class ToolHandler {
2509
2513
  */
2510
2514
  maybeDeepToolPhaseGate(cg, _toolName) {
2511
2515
  const state = (0, index_availability_1.resolveProductIndexState)(cg);
2512
- if (state === 'full')
2516
+ if (state === 'full' || state === 'dirty')
2513
2517
  return null;
2514
2518
  if (state === 'syncing') {
2515
2519
  return this.textResult((0, index_availability_1.productIndexGuidance)('syncing'));
@@ -12190,18 +12194,11 @@ class ToolHandler {
12190
12194
  return this.textResult('No modules found in the project map.');
12191
12195
  }
12192
12196
  const FILE_CAP = 80;
12193
- const productState = (0, index_availability_1.resolveProductIndexState)(cg);
12194
12197
  const lines = [
12195
- `**Project map** (status=${productState}, phase=${map.phase})`,
12198
+ `**Project map** (phase=${map.phase})`,
12196
12199
  `modules: ${map.modules.length} · files: ${map.fileCount}`,
12197
12200
  '',
12198
12201
  ];
12199
- if (productState === 'fast' || map.phase === 'fast' || map.phase === 'indexing') {
12200
- lines.push('_Full symbol index still building — this map has modules/files only (no call graph)._', '');
12201
- }
12202
- if (productState === 'syncing') {
12203
- lines.push('_Index write in progress — map may be briefly stale._', '');
12204
- }
12205
12202
  for (const m of map.modules) {
12206
12203
  const rootLabel = m.rootPath || '.';
12207
12204
  lines.push(`### ${m.name} (\`${rootLabel}\`) · ${m.kind} · ${m.fileCount} files`);
@@ -103,7 +103,8 @@ function discoverModules(projectRoot) {
103
103
  rootPath: '',
104
104
  kind: 'root',
105
105
  });
106
- const harmony = listHarmonyProjectModulesLoose(projectRoot);
106
+ // Same parser as ArkTS dirty-module mapping (Spec 0034): bare keys + single quotes.
107
+ const harmony = (0, arkts_1.listHarmonyProjectModules)(projectRoot);
107
108
  if (harmony.length > 0) {
108
109
  for (const m of harmony) {
109
110
  ensure({
@@ -132,47 +133,6 @@ function discoverModules(projectRoot) {
132
133
  }
133
134
  return [...byId.values()];
134
135
  }
135
- function listHarmonyProjectModulesLoose(projectRoot) {
136
- const strict = (0, arkts_1.listHarmonyProjectModules)(projectRoot);
137
- if (strict.length > 0)
138
- return strict;
139
- // Real DevEco `build-profile.json5` often uses unquoted keys; the strict
140
- // parser only strips comments/trailing commas. Quote bare keys and retry.
141
- const profilePath = path.join(projectRoot, 'build-profile.json5');
142
- if (!fs.existsSync(profilePath))
143
- return [];
144
- let raw;
145
- try {
146
- const text = fs.readFileSync(profilePath, 'utf-8');
147
- const stripped = text
148
- .replace(/\/\/.*$/gm, '')
149
- .replace(/\/\*[\s\S]*?\*\//g, '')
150
- .replace(/,(\s*[}\]])/g, '$1')
151
- .replace(/([{,]\s*)([A-Za-z_][\w]*)\s*:/g, '$1"$2":');
152
- raw = JSON.parse(stripped);
153
- }
154
- catch {
155
- return [];
156
- }
157
- if (!raw || typeof raw !== 'object')
158
- return [];
159
- const modules = raw.modules;
160
- if (!Array.isArray(modules))
161
- return [];
162
- const out = [];
163
- for (const entry of modules) {
164
- if (!entry || typeof entry !== 'object')
165
- continue;
166
- const rec = entry;
167
- if (typeof rec.name !== 'string' || typeof rec.srcPath !== 'string')
168
- continue;
169
- const srcPath = (0, arkts_1.normalizeHarmonyModuleSrcPath)(rec.srcPath);
170
- if (!srcPath)
171
- continue;
172
- out.push({ name: rec.name, srcPath });
173
- }
174
- return out;
175
- }
176
136
  function discoverOhpmPackages(projectRoot) {
177
137
  const out = [];
178
138
  const queue = [{ rel: '', depth: 0 }];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homegraph",
3
- "version": "1.5.8",
3
+ "version": "1.5.10",
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",