homegraph 1.6.1 → 1.6.3
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 +33 -0
- package/dist/bin/homegraph.js +2 -2
- package/dist/db/index.js +1 -0
- package/dist/db/migrations.d.ts +6 -1
- package/dist/db/migrations.js +26 -1
- package/dist/db/queries.d.ts +5 -0
- package/dist/db/queries.js +17 -7
- package/dist/db/schema.sql +4 -0
- package/dist/extraction/grammars.js +2 -1
- package/dist/extraction/languages/arkts.js +139 -8
- package/dist/extraction/languages/c-cpp.d.ts +8 -0
- package/dist/extraction/languages/c-cpp.js +44 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +4 -0
- package/dist/extraction/wasm-runtime-flags.js +9 -0
- package/dist/index.js +7 -1
- package/dist/installer/targets/icode.d.ts +43 -0
- package/dist/installer/targets/icode.js +631 -0
- package/dist/installer/targets/registry.js +2 -0
- package/dist/installer/targets/types.d.ts +1 -1
- package/dist/mcp/evidence-rendering.js +1 -1
- package/dist/mcp/explore-repeat-guard.d.ts +19 -5
- package/dist/mcp/explore-repeat-guard.js +35 -14
- package/dist/mcp/explore-session-state.d.ts +5 -0
- package/dist/mcp/explore-session-state.js +1 -0
- package/dist/mcp/locate-contract.js +1 -1
- package/dist/mcp/server-instructions.d.ts +2 -2
- package/dist/mcp/server-instructions.js +10 -10
- package/dist/mcp/session.js +3 -2
- package/dist/mcp/tools.d.ts +21 -2
- package/dist/mcp/tools.js +109 -50
- package/dist/resolution/c-feature-variants.d.ts +40 -0
- package/dist/resolution/c-feature-variants.js +272 -0
- package/dist/resolution/cpp-macro-visibility.d.ts +21 -0
- package/dist/resolution/cpp-macro-visibility.js +393 -0
- package/dist/resolution/index.js +11 -0
- package/dist/resolution/name-matcher.js +49 -2
- package/dist/resolution/types.d.ts +8 -0
- package/dist/resolution/types.js +8 -0
- package/dist/sync/watcher.d.ts +30 -0
- package/dist/sync/watcher.js +45 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,37 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
## [1.6.3] - 2026-10-22
|
|
14
|
+
|
|
15
|
+
### Improvements
|
|
16
|
+
|
|
17
|
+
- Release workflow jobs are split so a failed stage can be re-run alone: unit tests, CHANGELOG prepare, GitHub Release, npm publish, long registry verify, then OHOS API db packages.
|
|
18
|
+
|
|
19
|
+
### Fixes
|
|
20
|
+
|
|
21
|
+
- ArkTS: cross-file `imports` and `extends`/`implements` edges are no longer dropped during per-file persist. They ride the same `crossFileEdges` path as calls. Built-in ArkUI decorators (`@State`, `@Builder`, …) are not fuzzy-matched onto unrelated same-named symbols (Spec 0059).
|
|
22
|
+
|
|
23
|
+
## [1.6.2] - 2026-10-19
|
|
24
|
+
|
|
25
|
+
### New Features
|
|
26
|
+
|
|
27
|
+
- **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).
|
|
28
|
+
|
|
29
|
+
### Improvements
|
|
30
|
+
|
|
31
|
+
- `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).
|
|
32
|
+
- 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.
|
|
33
|
+
- 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).
|
|
34
|
+
- 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.
|
|
35
|
+
|
|
36
|
+
### Fixes
|
|
37
|
+
|
|
38
|
+
- 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).
|
|
39
|
+
- 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).
|
|
40
|
+
- 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).
|
|
41
|
+
- 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).
|
|
42
|
+
- 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.
|
|
43
|
+
|
|
13
44
|
## [1.6.1] - 2026-09-28
|
|
14
45
|
|
|
15
46
|
### Improvements
|
|
@@ -283,3 +314,5 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
283
314
|
[1.5.10]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.5.10
|
|
284
315
|
[1.6.0]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.0
|
|
285
316
|
[1.6.1]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.1
|
|
317
|
+
[1.6.2]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.2
|
|
318
|
+
[1.6.3]: https://github.com/fujiaxin-coder/homegraph/releases/tag/v1.6.3
|
package/dist/bin/homegraph.js
CHANGED
|
@@ -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
package/dist/db/migrations.d.ts
CHANGED
|
@@ -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 =
|
|
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
|
*/
|
package/dist/db/migrations.js
CHANGED
|
@@ -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 =
|
|
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
|
*/
|
package/dist/db/queries.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/db/queries.js
CHANGED
|
@@ -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 ', '(
|
|
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:
|
|
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
|
|
1717
|
-
AND metadata
|
|
1726
|
+
AND synthesized_by = 'arkanalyzer'
|
|
1727
|
+
AND json_extract(metadata, '$.sourceFile') IS NOT NULL`)
|
|
1718
1728
|
.run();
|
|
1719
1729
|
return result.changes;
|
|
1720
1730
|
}
|
package/dist/db/schema.sql
CHANGED
|
@@ -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
|
-
|
|
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?
|
|
@@ -2026,6 +2026,47 @@ function filesHaveArkUiEntries(files) {
|
|
|
2026
2026
|
// ArkUI ViewTree — component tree, @Prop/@Link transfer, event bindings
|
|
2027
2027
|
// =============================================================================
|
|
2028
2028
|
const VIEWTREE_CALLBACK_ATTRS = new Set(arkanalyzer_1.CALLBACK_METHOD_NAME);
|
|
2029
|
+
/**
|
|
2030
|
+
* Framework decorators with no project-local definition. Emitting them as
|
|
2031
|
+
* unresolved `decorates` refs lets name-matcher bind @State → enum State, etc.
|
|
2032
|
+
*/
|
|
2033
|
+
const ARKUI_BUILTIN_DECORATORS = new Set([
|
|
2034
|
+
'State',
|
|
2035
|
+
'Prop',
|
|
2036
|
+
'Link',
|
|
2037
|
+
'Provide',
|
|
2038
|
+
'Consume',
|
|
2039
|
+
'Watch',
|
|
2040
|
+
'Builder',
|
|
2041
|
+
'BuilderParam',
|
|
2042
|
+
'Styles',
|
|
2043
|
+
'Extend',
|
|
2044
|
+
'AnimatableExtend',
|
|
2045
|
+
'Component',
|
|
2046
|
+
'ComponentV2',
|
|
2047
|
+
'Entry',
|
|
2048
|
+
'CustomDialog',
|
|
2049
|
+
'Observed',
|
|
2050
|
+
'ObservedV2',
|
|
2051
|
+
'ObjectLink',
|
|
2052
|
+
'LocalStorageLink',
|
|
2053
|
+
'LocalStorageProp',
|
|
2054
|
+
'StorageLink',
|
|
2055
|
+
'StorageProp',
|
|
2056
|
+
'Track',
|
|
2057
|
+
'Require',
|
|
2058
|
+
'Once',
|
|
2059
|
+
'Reusable',
|
|
2060
|
+
'Concurrent',
|
|
2061
|
+
'Sendable',
|
|
2062
|
+
'Local',
|
|
2063
|
+
'Param',
|
|
2064
|
+
'Event',
|
|
2065
|
+
'Monitor',
|
|
2066
|
+
'Computed',
|
|
2067
|
+
'Trace',
|
|
2068
|
+
'Type',
|
|
2069
|
+
]);
|
|
2029
2070
|
/** State decorator kinds on a field (@State, @Prop, @Link, …) from arkanalyzer. */
|
|
2030
2071
|
function stateDecoratorKinds(field) {
|
|
2031
2072
|
const decs = field.getStateDecorators?.();
|
|
@@ -2727,6 +2768,11 @@ class ArkTSAdapter {
|
|
|
2727
2768
|
continue;
|
|
2728
2769
|
if (!this.scanned.has(rel) || !isArkAnalyzerSourcePath(rel))
|
|
2729
2770
|
continue;
|
|
2771
|
+
// Already indexed (usually streamed to DB). Re-running indexScannedFile
|
|
2772
|
+
// would hit addNode's nodeIds short-circuit and leave an empty shell;
|
|
2773
|
+
// live IR rebind is handled by bindLiveMapsFromResident.
|
|
2774
|
+
if (this.nodeIds.has(`file:${rel}`))
|
|
2775
|
+
continue;
|
|
2730
2776
|
this.indexScannedFile(arkFile);
|
|
2731
2777
|
if (this.fileResults.has(rel))
|
|
2732
2778
|
seeded.push(rel);
|
|
@@ -3279,6 +3325,20 @@ class ArkTSAdapter {
|
|
|
3279
3325
|
const result = this.fileResults.get(relativePath);
|
|
3280
3326
|
return result?.nodes.some((n) => n.id === nodeId) ?? false;
|
|
3281
3327
|
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Same-file edges stay on the file's ExtractionResult (persistFileResult FK
|
|
3330
|
+
* filter). Cross-file edges go to crossFileEdges and are inserted after all
|
|
3331
|
+
* modules persist — do not require nodeIds.has(target) at emit time (target
|
|
3332
|
+
* may be indexed later in the same batch).
|
|
3333
|
+
*/
|
|
3334
|
+
addMaybeCrossFileEdge(result, callerFile, edge) {
|
|
3335
|
+
if (this.nodeInFile(edge.target, callerFile)) {
|
|
3336
|
+
result.edges.push(edge);
|
|
3337
|
+
}
|
|
3338
|
+
else {
|
|
3339
|
+
this.crossFileEdges.push(edge);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3282
3342
|
ensureFileResult(relativePath, lineCount) {
|
|
3283
3343
|
let result = this.fileResults.get(relativePath);
|
|
3284
3344
|
if (!result) {
|
|
@@ -3431,6 +3491,10 @@ class ArkTSAdapter {
|
|
|
3431
3491
|
const kind = dec.getKind();
|
|
3432
3492
|
if (!kind)
|
|
3433
3493
|
continue;
|
|
3494
|
+
// Built-in ArkUI/ArkTS decorators have no project definition; name-match
|
|
3495
|
+
// would falsely link @State → enum State, @Builder → class Builder, etc.
|
|
3496
|
+
if (ARKUI_BUILTIN_DECORATORS.has(kind))
|
|
3497
|
+
continue;
|
|
3434
3498
|
result.unresolvedReferences.push({
|
|
3435
3499
|
fromNodeId,
|
|
3436
3500
|
referenceName: kind,
|
|
@@ -3529,17 +3593,62 @@ class ArkTSAdapter {
|
|
|
3529
3593
|
if (superName) {
|
|
3530
3594
|
const superCls = cls.getHeritageClass(superName);
|
|
3531
3595
|
if (superCls && !superCls.isDefaultArkClass()) {
|
|
3532
|
-
const superId = this.classToId.get(superCls);
|
|
3596
|
+
const superId = this.classToId.get(superCls) ?? this.resolveClassNodeId(superCls);
|
|
3597
|
+
const edgeKind = superCls.getCategory() === CLASS_CATEGORY.INTERFACE ? 'implements' : 'extends';
|
|
3533
3598
|
if (superId) {
|
|
3534
|
-
|
|
3535
|
-
|
|
3599
|
+
this.addMaybeCrossFileEdge(result, relativePath, arkEdge(classNode.id, superId, edgeKind));
|
|
3600
|
+
}
|
|
3601
|
+
else {
|
|
3602
|
+
const simple = superName.includes('.') ? superName.split('.').pop() : superName;
|
|
3603
|
+
if (simple && simple !== 'Object') {
|
|
3604
|
+
result.unresolvedReferences.push({
|
|
3605
|
+
fromNodeId: classNode.id,
|
|
3606
|
+
referenceName: simple,
|
|
3607
|
+
referenceKind: edgeKind,
|
|
3608
|
+
line,
|
|
3609
|
+
column: col,
|
|
3610
|
+
filePath: relativePath,
|
|
3611
|
+
language,
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
else if (superName && superName !== 'Object') {
|
|
3617
|
+
const simple = superName.includes('.') ? superName.split('.').pop() : superName;
|
|
3618
|
+
if (simple) {
|
|
3619
|
+
result.unresolvedReferences.push({
|
|
3620
|
+
fromNodeId: classNode.id,
|
|
3621
|
+
referenceName: simple,
|
|
3622
|
+
referenceKind: 'extends',
|
|
3623
|
+
line,
|
|
3624
|
+
column: col,
|
|
3625
|
+
filePath: relativePath,
|
|
3626
|
+
language,
|
|
3627
|
+
});
|
|
3536
3628
|
}
|
|
3537
3629
|
}
|
|
3538
3630
|
}
|
|
3539
3631
|
for (const ifaceName of cls.getImplementedInterfaceNames()) {
|
|
3540
3632
|
const iface = cls.getImplementedInterface(ifaceName);
|
|
3541
|
-
|
|
3542
|
-
|
|
3633
|
+
const ifaceId = iface
|
|
3634
|
+
? (this.classToId.get(iface) ?? this.resolveClassNodeId(iface))
|
|
3635
|
+
: null;
|
|
3636
|
+
if (ifaceId) {
|
|
3637
|
+
this.addMaybeCrossFileEdge(result, relativePath, arkEdge(classNode.id, ifaceId, 'implements'));
|
|
3638
|
+
}
|
|
3639
|
+
else if (ifaceName) {
|
|
3640
|
+
const simple = ifaceName.includes('.') ? ifaceName.split('.').pop() : ifaceName;
|
|
3641
|
+
if (simple) {
|
|
3642
|
+
result.unresolvedReferences.push({
|
|
3643
|
+
fromNodeId: classNode.id,
|
|
3644
|
+
referenceName: simple,
|
|
3645
|
+
referenceKind: 'implements',
|
|
3646
|
+
line,
|
|
3647
|
+
column: col,
|
|
3648
|
+
filePath: relativePath,
|
|
3649
|
+
language,
|
|
3650
|
+
});
|
|
3651
|
+
}
|
|
3543
3652
|
}
|
|
3544
3653
|
}
|
|
3545
3654
|
for (const method of cls.getMethods(true)) {
|
|
@@ -4017,12 +4126,34 @@ class ArkTSAdapter {
|
|
|
4017
4126
|
const targetFile = exportInfo.getDeclaringArkFile();
|
|
4018
4127
|
if (targetFile) {
|
|
4019
4128
|
const targetRel = this.normalizeRelPath(targetFile.getFilePath());
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4129
|
+
if (targetRel) {
|
|
4130
|
+
const targetFileId = `file:${targetRel}`;
|
|
4131
|
+
this.addMaybeCrossFileEdge(result, relativePath, arkEdge(importNode.id, targetFileId, 'imports', {
|
|
4023
4132
|
metadata: { synthesizedBy: 'arkanalyzer', module: modulePath },
|
|
4024
4133
|
}));
|
|
4025
4134
|
}
|
|
4135
|
+
else if (modulePath) {
|
|
4136
|
+
result.unresolvedReferences.push({
|
|
4137
|
+
fromNodeId: importNode.id,
|
|
4138
|
+
referenceName: modulePath,
|
|
4139
|
+
referenceKind: 'imports',
|
|
4140
|
+
line,
|
|
4141
|
+
column: 0,
|
|
4142
|
+
filePath: relativePath,
|
|
4143
|
+
language,
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
else if (modulePath) {
|
|
4148
|
+
result.unresolvedReferences.push({
|
|
4149
|
+
fromNodeId: importNode.id,
|
|
4150
|
+
referenceName: modulePath,
|
|
4151
|
+
referenceKind: 'imports',
|
|
4152
|
+
line,
|
|
4153
|
+
column: 0,
|
|
4154
|
+
filePath: relativePath,
|
|
4155
|
+
language,
|
|
4156
|
+
});
|
|
4026
4157
|
}
|
|
4027
4158
|
}
|
|
4028
4159
|
else if (modulePath) {
|
|
@@ -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
|
-
},
|
|
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
|