homegraph 1.6.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/bin/homegraph.js +2 -2
  3. package/dist/db/index.js +1 -0
  4. package/dist/db/migrations.d.ts +6 -1
  5. package/dist/db/migrations.js +26 -1
  6. package/dist/db/queries.d.ts +5 -0
  7. package/dist/db/queries.js +17 -7
  8. package/dist/db/schema.sql +4 -0
  9. package/dist/extraction/grammars.js +2 -1
  10. package/dist/extraction/languages/c-cpp.d.ts +8 -0
  11. package/dist/extraction/languages/c-cpp.js +44 -0
  12. package/dist/extraction/wasm-runtime-flags.d.ts +4 -0
  13. package/dist/extraction/wasm-runtime-flags.js +9 -0
  14. package/dist/index.js +7 -1
  15. package/dist/installer/targets/icode.d.ts +43 -0
  16. package/dist/installer/targets/icode.js +631 -0
  17. package/dist/installer/targets/registry.js +2 -0
  18. package/dist/installer/targets/types.d.ts +1 -1
  19. package/dist/mcp/evidence-rendering.js +1 -1
  20. package/dist/mcp/explore-repeat-guard.d.ts +19 -5
  21. package/dist/mcp/explore-repeat-guard.js +35 -14
  22. package/dist/mcp/explore-session-state.d.ts +5 -0
  23. package/dist/mcp/explore-session-state.js +1 -0
  24. package/dist/mcp/locate-contract.js +1 -1
  25. package/dist/mcp/server-instructions.d.ts +2 -2
  26. package/dist/mcp/server-instructions.js +10 -10
  27. package/dist/mcp/session.js +3 -2
  28. package/dist/mcp/tools.d.ts +21 -2
  29. package/dist/mcp/tools.js +109 -50
  30. package/dist/resolution/c-feature-variants.d.ts +40 -0
  31. package/dist/resolution/c-feature-variants.js +272 -0
  32. package/dist/resolution/cpp-macro-visibility.d.ts +21 -0
  33. package/dist/resolution/cpp-macro-visibility.js +393 -0
  34. package/dist/resolution/index.js +11 -0
  35. package/dist/resolution/name-matcher.js +49 -2
  36. package/dist/resolution/types.d.ts +8 -0
  37. package/dist/resolution/types.js +8 -0
  38. package/dist/sync/watcher.d.ts +30 -0
  39. package/dist/sync/watcher.js +45 -0
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,27 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
10
10
  ## [Unreleased]
11
11
 
12
12
 
13
+ ## [1.6.2] - 2026-10-19
14
+
15
+ ### New Features
16
+
17
+ - **iCode support** in the installer. `homegraph install --target icode` wires the MCP server into iCode agent profiles (`~/.chrys/agents/` on macOS/Linux, `%APPDATA%\chrys\agents\` on Windows). The target is global-only. It edits existing profiles in place and, when the iCode runtime is found, writes shadow profiles for the built-in agents. If the runtime is missing, install still updates existing profiles and prints manual guidance. `HOMEGRAPH_ICODE_NO_BUILTINS=1` skips shadows. No instructions file is written (Spec 0057).
18
+
19
+ ### Improvements
20
+
21
+ - `homegraph_explore` no longer stops after 2 calls on a long-lived MCP connection. Another explore within 10 seconds of the previous result is refused; a later call, including a different question, is allowed. The tool description, initialize instructions, and explore footer state that wait (Spec 0058).
22
+ - MCP initialize / `homegraph_explore` description: drop bash-first and "HomeGraph is optional" wording that conflicted with DevEco Code's graph-first product prompt; existing-code tasks open with `homegraph_explore` alone. Tool list, budgets, and evidence caveats unchanged.
23
+ - MCP under Bun skips the Node/V8 `--liftoff-only` relaunch (those flags do not apply), so product hosts can spawn `homegraph.js` with the host Bun binary (Spec 0052).
24
+ - MCP product hosts that prefix tools with the server name (e.g. DevEco Code) can set `HOMEGRAPH_MCP_SHORT_TOOL_NAMES=1` so tools/list exposes short names (`explore`, `project`) and tools/call still accepts both short and `homegraph_*` forms — avoiding a doubled `homegraph_homegraph_*` name after the host prefix. Unset by default; Cursor/Claude keep canonical `homegraph_*` names.
25
+
26
+ ### Fixes
27
+
28
+ - C/C++: a call no longer binds another file's `static` function when a definition with external linkage exists. A C definition compiled only under a feature flag nothing in the project sets yields to the definition the default build compiles (Spec 0056).
29
+ - Windows: a file the daemon only read no longer shows up as edited. Watcher events whose size and modification time still match the index are ignored, so an NTFS last-access update does not mark the file pending sync (Spec 0055).
30
+ - C/C++: a header whose only C++ signal is `struct Derived : Base` is indexed as C++. A call that names a function-like macro visible at that line is not linked to a same-named function, and the include walk that decides this is bounded so a deep guard graph cannot blow the heap (Spec 0054).
31
+ - MCP `homegraph_explore` call budget shown to agents is **2** on every repo size, matching the session hard cap. Output sizing tiers are unchanged. Heuristic edges store `synthesized_by`, and ArkTS cross-file call cleanup deletes that channel by column instead of a JSON text match. Explore and Partial replies no longer tell the agent to call `homegraph_node`. Name search still tries FTS first, including Chinese literals (Spec 0053).
32
+ - MCP tools/list: when a session has no default project, `projectPath` is already marked `required` in the schema — its property description no longer says "Omit to use this session's default project", so agents are not told the field is both required and optional.
33
+
13
34
  ## [1.6.1] - 2026-09-28
14
35
 
15
36
  ### Improvements
@@ -283,3 +304,4 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
283
304
  [1.5.10]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.5.10
284
305
  [1.6.0]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.0
285
306
  [1.6.1]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.1
307
+ [1.6.2]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.2
@@ -1917,7 +1917,7 @@ function main() {
1917
1917
  */
1918
1918
  program
1919
1919
  .command('install')
1920
- .description('Install homegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
1920
+ .description('Install homegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent, iCode)')
1921
1921
  .option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
1922
1922
  .option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
1923
1923
  .option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
@@ -1976,7 +1976,7 @@ function main() {
1976
1976
  */
1977
1977
  program
1978
1978
  .command('uninstall')
1979
- .description('Remove homegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
1979
+ .description('Remove homegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent, iCode)')
1980
1980
  .option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "all". Default: all')
1981
1981
  .option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
1982
1982
  .option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
package/dist/db/index.js CHANGED
@@ -329,6 +329,7 @@ class DatabaseConnection {
329
329
  'idx_edges_source_kind',
330
330
  'idx_edges_target_kind',
331
331
  'idx_edges_provenance',
332
+ 'idx_edges_synthesized_by',
332
333
  ];
333
334
  /**
334
335
  * Enter bulk-edge-load mode: drop the non-unique edge indexes so the mass
@@ -7,7 +7,7 @@ import { SqliteDatabase } from './sqlite-adapter';
7
7
  /**
8
8
  * Current schema version
9
9
  */
10
- export declare const CURRENT_SCHEMA_VERSION = 11;
10
+ export declare const CURRENT_SCHEMA_VERSION = 12;
11
11
  /**
12
12
  * Migration definition
13
13
  */
@@ -16,6 +16,11 @@ interface Migration {
16
16
  description: string;
17
17
  up: (db: SqliteDatabase) => void;
18
18
  }
19
+ /**
20
+ * Copy `metadata.synthesizedBy` onto `edges.synthesized_by` when the column is empty.
21
+ * Channel deletes read the column, not JSON text (Spec 0053).
22
+ */
23
+ export declare function backfillEdgeSynthesizedBy(db: SqliteDatabase): void;
19
24
  /**
20
25
  * Get the current schema version from the database
21
26
  */
@@ -6,6 +6,7 @@
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.CURRENT_SCHEMA_VERSION = void 0;
9
+ exports.backfillEdgeSynthesizedBy = backfillEdgeSynthesizedBy;
9
10
  exports.getCurrentVersion = getCurrentVersion;
10
11
  exports.runMigrations = runMigrations;
11
12
  exports.needsMigration = needsMigration;
@@ -14,7 +15,7 @@ exports.getMigrationHistory = getMigrationHistory;
14
15
  /**
15
16
  * Current schema version
16
17
  */
17
- exports.CURRENT_SCHEMA_VERSION = 11;
18
+ exports.CURRENT_SCHEMA_VERSION = 12;
18
19
  /**
19
20
  * All migrations in order
20
21
  *
@@ -175,7 +176,31 @@ const migrations = [
175
176
  `);
176
177
  },
177
178
  },
179
+ {
180
+ version: 12,
181
+ description: 'Add edges.synthesized_by and backfill from metadata.synthesizedBy (Spec 0053)',
182
+ up: (db) => {
183
+ const cols = db.prepare('PRAGMA table_info(edges)').all();
184
+ if (!cols.some((c) => c.name === 'synthesized_by')) {
185
+ db.exec('ALTER TABLE edges ADD COLUMN synthesized_by TEXT');
186
+ }
187
+ backfillEdgeSynthesizedBy(db);
188
+ db.exec('CREATE INDEX IF NOT EXISTS idx_edges_synthesized_by ON edges(synthesized_by) WHERE synthesized_by IS NOT NULL');
189
+ },
190
+ },
178
191
  ];
192
+ /**
193
+ * Copy `metadata.synthesizedBy` onto `edges.synthesized_by` when the column is empty.
194
+ * Channel deletes read the column, not JSON text (Spec 0053).
195
+ */
196
+ function backfillEdgeSynthesizedBy(db) {
197
+ db.exec(`
198
+ UPDATE edges
199
+ SET synthesized_by = json_extract(metadata, '$.synthesizedBy')
200
+ WHERE (synthesized_by IS NULL OR synthesized_by = '')
201
+ AND json_extract(metadata, '$.synthesizedBy') IS NOT NULL
202
+ `);
203
+ }
179
204
  /**
180
205
  * Get the current schema version from the database
181
206
  */
@@ -5,6 +5,11 @@
5
5
  */
6
6
  import { SqliteDatabase } from './sqlite-adapter';
7
7
  import { Node, Edge, FileRecord, UnresolvedReference, NodeKind, EdgeKind, Language, GraphStats, SearchOptions, SearchResult } from '../types';
8
+ /**
9
+ * Convert database row to Edge object
10
+ */
11
+ /** Channel name stored on `edges.synthesized_by` (Spec 0053). */
12
+ export declare function edgeSynthesizedBy(edge: Edge): string | null;
8
13
  /**
9
14
  * Query builder for the knowledge graph database
10
15
  */
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.QueryBuilder = void 0;
42
+ exports.edgeSynthesizedBy = edgeSynthesizedBy;
42
43
  const fs = __importStar(require("fs"));
43
44
  const path = __importStar(require("path"));
44
45
  const utils_1 = require("../utils");
@@ -110,6 +111,11 @@ function rowToNode(row) {
110
111
  /**
111
112
  * Convert database row to Edge object
112
113
  */
114
+ /** Channel name stored on `edges.synthesized_by` (Spec 0053). */
115
+ function edgeSynthesizedBy(edge) {
116
+ const value = edge.metadata?.synthesizedBy;
117
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
118
+ }
113
119
  function rowToEdge(row) {
114
120
  return {
115
121
  source: row.source,
@@ -383,9 +389,10 @@ class QueryBuilder {
383
389
  edge.line ?? null,
384
390
  edge.column ?? null,
385
391
  edge.provenance ?? null,
392
+ edgeSynthesizedBy(edge),
386
393
  ]);
387
394
  }
388
- this.runBatched('insertEdges', 'INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance) VALUES ', '(?,?,?,?,?,?,?)', rows);
395
+ this.runBatched('insertEdges', 'INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance, synthesized_by) VALUES ', '(?,?,?,?,?,?,?,?)', rows);
389
396
  }
390
397
  if (bundle.refs.length > 0)
391
398
  this.insertUnresolvedRefsBatch(bundle.refs);
@@ -994,11 +1001,13 @@ class QueryBuilder {
994
1001
  return [];
995
1002
  return this.mergeOhosApiTextSearch(text, { kinds, languages, limit }, []);
996
1003
  }
997
- // First try FTS5 with prefix matching
1004
+ // First try FTS5 with prefix matching. Chinese literals (for example
1005
+ // string.json values in docstring) are real FTS tokens — do not skip them.
998
1006
  let results = text
999
1007
  ? this.searchNodesFTS(text, { kinds, languages, limit, offset })
1000
1008
  // Over-fetch by 5× when running filter-only (no text). The
1001
- // post-scoring path: + name: filters can be very selective, so // a smaller multiplier risks returning fewer than `limit`
1009
+ // post-scoring path: name: filters can be very selective, so
1010
+ // a smaller multiplier risks returning fewer than `limit`
1002
1011
  // results despite the DB having plenty of matches.
1003
1012
  : this.searchAllByFilters({ kinds, languages, limit: limit * 5 });
1004
1013
  // If no FTS results, try LIKE-based substring search
@@ -1497,8 +1506,8 @@ class QueryBuilder {
1497
1506
  insertEdge(edge) {
1498
1507
  if (!this.stmts.insertEdge) {
1499
1508
  this.stmts.insertEdge = this.db.prepare(`
1500
- INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance)
1501
- VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance)
1509
+ INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance, synthesized_by)
1510
+ VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance, @synthesizedBy)
1502
1511
  `);
1503
1512
  }
1504
1513
  this.stmts.insertEdge.run({
@@ -1509,6 +1518,7 @@ class QueryBuilder {
1509
1518
  line: edge.line ?? null,
1510
1519
  col: edge.column ?? null,
1511
1520
  provenance: edge.provenance ?? null,
1521
+ synthesizedBy: edgeSynthesizedBy(edge),
1512
1522
  });
1513
1523
  }
1514
1524
  /**
@@ -1713,8 +1723,8 @@ class QueryBuilder {
1713
1723
  .prepare(`DELETE FROM edges
1714
1724
  WHERE kind = 'calls'
1715
1725
  AND provenance = 'heuristic'
1716
- AND metadata LIKE '%"synthesizedBy":"arkanalyzer"%'
1717
- AND metadata LIKE '%"sourceFile"%'`)
1726
+ AND synthesized_by = 'arkanalyzer'
1727
+ AND json_extract(metadata, '$.sourceFile') IS NOT NULL`)
1718
1728
  .run();
1719
1729
  return result.changes;
1720
1730
  }
@@ -52,6 +52,9 @@ CREATE TABLE IF NOT EXISTS edges (
52
52
  line INTEGER,
53
53
  col INTEGER,
54
54
  provenance TEXT DEFAULT NULL,
55
+ -- Channel of a heuristic edge (`metadata.synthesizedBy`). Written on insert
56
+ -- so re-index can delete by channel without matching JSON text (Spec 0053).
57
+ synthesized_by TEXT,
55
58
  FOREIGN KEY (source) REFERENCES nodes(id) ON DELETE CASCADE,
56
59
  FOREIGN KEY (target) REFERENCES nodes(id) ON DELETE CASCADE
57
60
  );
@@ -192,6 +195,7 @@ CREATE INDEX IF NOT EXISTS idx_unresolved_from_name ON unresolved_refs(from_node
192
195
  CREATE INDEX IF NOT EXISTS idx_unresolved_status ON unresolved_refs(status);
193
196
  CREATE INDEX IF NOT EXISTS idx_unresolved_failed_tail ON unresolved_refs(name_tail) WHERE status = 'failed';
194
197
  CREATE INDEX IF NOT EXISTS idx_edges_provenance ON edges(provenance);
198
+ CREATE INDEX IF NOT EXISTS idx_edges_synthesized_by ON edges(synthesized_by) WHERE synthesized_by IS NOT NULL;
195
199
 
196
200
  -- Project metadata for version/provenance tracking
197
201
  CREATE TABLE IF NOT EXISTS project_metadata (
@@ -529,7 +529,8 @@ function looksLikeCpp(source) {
529
529
  // routed through the C extractor (which extracts no classes), and its class
530
530
  // definition silently vanishes. The two-token shape (`<KW> <MACRO> <Name>`
531
531
  // before a `[:{]`) never occurs in valid C, so this can't misclassify C headers.
532
- return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\b(?:class|struct)\s+[A-Z][A-Z0-9_]+\s+\w+\s*(?:final\s*)?[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample);
532
+ // `struct Derived : Base` is C++. A plain `struct Point {` stays C.
533
+ return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\bstruct\s+\w+\s*:|\b(?:class|struct)\s+[A-Z][A-Z0-9_]+\s+\w+\s*(?:final\s*)?[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample);
533
534
  }
534
535
  /**
535
536
  * Heuristic: does a .h file contain Objective-C constructs?
@@ -102,6 +102,14 @@ export declare function blankMetalAttributes(source: string): string;
102
102
  * ALL-CAPS call that isn't markup only loses that one annotation, never a whole
103
103
  * class.
104
104
  */
105
+ /**
106
+ * Mask C++ raw string literals (`R"delim(...)delim"`) so later blankers do not
107
+ * treat their contents as code. Offsets stay aligned with the original source.
108
+ */
109
+ export declare function maskCppRawStrings(source: string): {
110
+ source: string;
111
+ restore: (blanked: string) => string;
112
+ };
105
113
  export declare function blankCppAnnotationMacroCalls(source: string): string;
106
114
  export declare function blankCppApiPrefixMacros(source: string): string;
107
115
  export declare function blankCppInlineAnnotationMacros(source: string): string;
@@ -7,6 +7,7 @@ exports.blankCppExportMacros = blankCppExportMacros;
7
7
  exports.blankCppInlineMacros = blankCppInlineMacros;
8
8
  exports.recoverMangledCppName = recoverMangledCppName;
9
9
  exports.blankMetalAttributes = blankMetalAttributes;
10
+ exports.maskCppRawStrings = maskCppRawStrings;
10
11
  exports.blankCppAnnotationMacroCalls = blankCppAnnotationMacroCalls;
11
12
  exports.blankCppApiPrefixMacros = blankCppApiPrefixMacros;
12
13
  exports.blankCppInlineAnnotationMacros = blankCppInlineAnnotationMacros;
@@ -493,6 +494,49 @@ function blankMetalAttributes(source) {
493
494
  * ALL-CAPS call that isn't markup only loses that one annotation, never a whole
494
495
  * class.
495
496
  */
497
+ /**
498
+ * Mask C++ raw string literals (`R"delim(...)delim"`) so later blankers do not
499
+ * treat their contents as code. Offsets stay aligned with the original source.
500
+ */
501
+ function maskCppRawStrings(source) {
502
+ const unchanged = { source, restore: (blanked) => blanked };
503
+ if (source.indexOf('R"') === -1)
504
+ return unchanged;
505
+ const re = /\/\/[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$)|\b(?:u8|[LuU])?R"([^ \t\v\f\r\n()\\]{0,16})\(|"(?:\\[\s\S]|[^"\\])*(?:"|$)|(?<!\w)(?:u8|[LuU])?'(?:\\[\s\S]|[^'\\])*(?:'|$)/g;
506
+ const spans = [];
507
+ const parts = [];
508
+ let last = 0;
509
+ let m;
510
+ while ((m = re.exec(source)) !== null) {
511
+ if (m[1] === undefined)
512
+ continue;
513
+ const closer = `)${m[1]}"`;
514
+ const close = source.indexOf(closer, re.lastIndex);
515
+ const end = close < 0 ? source.length : close + closer.length;
516
+ spans.push({ start: m.index, end });
517
+ parts.push(source.slice(last, m.index), source.slice(m.index, end).replace(/[^\r\n]/g, '\0'));
518
+ last = re.lastIndex = end;
519
+ }
520
+ if (spans.length === 0)
521
+ return unchanged;
522
+ parts.push(source.slice(last));
523
+ const masked = parts.join('');
524
+ return {
525
+ source: masked,
526
+ restore(blanked) {
527
+ if (blanked === masked || blanked.length !== source.length)
528
+ return source;
529
+ const chars = blanked.split('');
530
+ for (const { start, end } of spans) {
531
+ for (let i = start; i < end; i++) {
532
+ if (chars[i] === '\0')
533
+ chars[i] = source[i];
534
+ }
535
+ }
536
+ return chars.join('');
537
+ },
538
+ };
539
+ }
496
540
  function blankCppAnnotationMacroCalls(source) {
497
541
  if (!/^[ \t]*[A-Z][A-Z0-9_]{2,}\s*\(/m.test(source))
498
542
  return source;
@@ -44,5 +44,9 @@ export declare function buildRelaunchArgv(scriptPath: string, scriptArgs: readon
44
44
  * On spawn failure, returns so the caller runs in-process anyway — risking the
45
45
  * OOM is still better than refusing to start.
46
46
  */
47
+ /** Bun is not V8 — `--liftoff-only` / `--max-old-space-size` relaunch does not apply. */
48
+ export declare function isBunRuntime(g?: {
49
+ Bun?: unknown;
50
+ }): boolean;
47
51
  export declare function relaunchWithWasmRuntimeFlagsIfNeeded(scriptPath: string): void;
48
52
  //# sourceMappingURL=wasm-runtime-flags.d.ts.map
@@ -6,6 +6,7 @@ exports.maxOldSpaceSizeFlag = maxOldSpaceSizeFlag;
6
6
  exports.processHasWasmRuntimeFlags = processHasWasmRuntimeFlags;
7
7
  exports.processHasRequiredRuntimeFlags = processHasRequiredRuntimeFlags;
8
8
  exports.buildRelaunchArgv = buildRelaunchArgv;
9
+ exports.isBunRuntime = isBunRuntime;
9
10
  exports.relaunchWithWasmRuntimeFlagsIfNeeded = relaunchWithWasmRuntimeFlagsIfNeeded;
10
11
  /**
11
12
  * WASM runtime flags — workaround for the V8 turboshaft WASM Zone OOM.
@@ -134,6 +135,10 @@ function buildRelaunchArgv(scriptPath, scriptArgs, execArgv = process.execArgv)
134
135
  * On spawn failure, returns so the caller runs in-process anyway — risking the
135
136
  * OOM is still better than refusing to start.
136
137
  */
138
+ /** Bun is not V8 — `--liftoff-only` / `--max-old-space-size` relaunch does not apply. */
139
+ function isBunRuntime(g = globalThis) {
140
+ return typeof g.Bun !== 'undefined';
141
+ }
137
142
  function relaunchWithWasmRuntimeFlagsIfNeeded(scriptPath) {
138
143
  if (processHasRequiredRuntimeFlags())
139
144
  return;
@@ -141,6 +146,10 @@ function relaunchWithWasmRuntimeFlagsIfNeeded(scriptPath) {
141
146
  return;
142
147
  if (process.env.HOMEGRAPH_NO_RELAUNCH)
143
148
  return;
149
+ // Product hosts (e.g. DevEco Code) spawn MCP under Bun; V8 flags would be
150
+ // ignored or break relaunch. Stay in-process.
151
+ if (isBunRuntime())
152
+ return;
144
153
  const argv = buildRelaunchArgv(scriptPath, process.argv.slice(2));
145
154
  const result = (0, child_process_1.spawnSync)(process.execPath, argv, {
146
155
  stdio: 'inherit',
package/dist/index.js CHANGED
@@ -854,7 +854,13 @@ class HomeGraph {
854
854
  }
855
855
  const filesChanged = result.filesAdded + result.filesModified + result.filesRemoved;
856
856
  return { filesChanged, durationMs: result.durationMs };
857
- }, options);
857
+ }, {
858
+ ...options,
859
+ indexedFingerprint: options.indexedFingerprint ?? ((rel) => {
860
+ const rec = this.getFile(rel);
861
+ return rec ? { size: rec.size, modifiedAt: rec.modifiedAt } : null;
862
+ }),
863
+ });
858
864
  return this.watcher.start();
859
865
  }
860
866
  /**
@@ -0,0 +1,43 @@
1
+ /**
2
+ * iCode target.
3
+ *
4
+ * iCode (product name) ships as the Python package `chrys`; agent profiles
5
+ * are individual YAML files, one per profile, under `~/.chrys/agents/`
6
+ * (macOS/Linux) or `%APPDATA%\chrys\agents\` (Windows). Each profile is a
7
+ * complete agent definition carrying a `tools.mcp` list of MCP server
8
+ * configs.
9
+ *
10
+ * iCode has NO project-local config concept — this target is global-only.
11
+ *
12
+ * Install (synchronous, non-interactive — no per-profile prompt):
13
+ * 1. Every EXISTING user profile under `~/.chrys/agents/*.yaml|yml` gets
14
+ * the homegraph MCP entry injected in place — a surgical line edit
15
+ * that preserves sibling MCP servers and every other section.
16
+ * 2. Built-in agents (Code / QA / Explore / General) are discovered from
17
+ * the installed iCode runtime
18
+ * (`.../site-packages/chrys/service/profiles/agents/builtins/`). A
19
+ * built-in with no user profile of the same name gets a "shadow"
20
+ * profile written to `~/.chrys/agents/<name>.yaml`: the built-in's
21
+ * FULL content plus the homegraph entry. (iCode's own override rule:
22
+ * a user profile with a built-in's name REPLACES the built-in
23
+ * entirely, so the copy must be complete.)
24
+ * If the runtime can't be located, install degrades to editing
25
+ * existing profiles only and returns manual guidance in `notes` — it
26
+ * never throws.
27
+ *
28
+ * Uninstall strips the homegraph entry from every user profile that
29
+ * carries it (shadow files are left as plain built-in copies), matching
30
+ * the other targets' "remove only what install wrote" contract.
31
+ *
32
+ * No instructions file is written (#529/#704) — guidance ships in the MCP
33
+ * `initialize` response, which iCode injects into system reminders by
34
+ * default (`expose_instructions: true`).
35
+ *
36
+ * NOTE: unlike the codegraph fork, this target must NOT suppress the
37
+ * "install the CLI on PATH" step in `src/installer/index.ts` — the MCP
38
+ * command is the `homegraph` Node CLI, which iCode (a Python app) does not
39
+ * bundle.
40
+ */
41
+ import { AgentTarget } from './types';
42
+ export declare const icodeTarget: AgentTarget;
43
+ //# sourceMappingURL=icode.d.ts.map