homegraph 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -18
- package/dist/arkts/ohos-api-index.d.ts +15 -0
- package/dist/arkts/ohos-api-index.d.ts.map +1 -0
- package/dist/arkts/ohos-api-index.js +190 -0
- package/dist/arkts/ohos-api-index.js.map +1 -0
- package/dist/arkts/ohos-sdk-input.d.ts +36 -0
- package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
- package/dist/arkts/ohos-sdk-input.js +214 -0
- package/dist/arkts/ohos-sdk-input.js.map +1 -0
- package/dist/bin/command-supervision.d.ts +12 -0
- package/dist/bin/command-supervision.d.ts.map +1 -0
- package/dist/bin/command-supervision.js +86 -0
- package/dist/bin/command-supervision.js.map +1 -0
- package/dist/bin/homegraph.d.ts +1 -1
- package/dist/bin/homegraph.js +706 -168
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/db/index.d.ts +20 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +39 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +40 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +14 -0
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +174 -3
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +20 -0
- package/dist/directory.d.ts +32 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +83 -0
- package/dist/directory.js.map +1 -1
- package/dist/extraction/arkts-batch-worker.d.ts +2 -0
- package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
- package/dist/extraction/arkts-batch-worker.js +28 -0
- package/dist/extraction/arkts-batch-worker.js.map +1 -0
- package/dist/extraction/context.d.ts +11 -0
- package/dist/extraction/context.d.ts.map +1 -1
- package/dist/extraction/context.js +20 -0
- package/dist/extraction/context.js.map +1 -1
- package/dist/extraction/index.d.ts +16 -2
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +619 -380
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
- package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
- package/dist/extraction/languages/arkts-state-decorators.js +26 -0
- package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
- package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.js +21 -6
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +98 -2
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +1290 -61
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +56 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +198 -1
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.js +283 -0
- package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
- package/dist/extraction/parse-pool.d.ts +126 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -0
- package/dist/extraction/parse-pool.js +319 -0
- package/dist/extraction/parse-pool.js.map +1 -0
- package/dist/extraction/tree-sitter-types.d.ts +17 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +21 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +198 -27
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +76 -17
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp/daemon-paths.d.ts +30 -3
- package/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/dist/mcp/daemon-paths.js +50 -10
- package/dist/mcp/daemon-paths.js.map +1 -1
- package/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/dist/mcp/daemon-registry.js +7 -3
- package/dist/mcp/daemon-registry.js.map +1 -1
- package/dist/mcp/daemon.d.ts +48 -0
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +196 -32
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/engine.d.ts +17 -0
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +73 -1
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +25 -43
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/ppid-watchdog.d.ts +18 -0
- package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
- package/dist/mcp/ppid-watchdog.js +37 -0
- package/dist/mcp/ppid-watchdog.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +25 -0
- package/dist/mcp/query-cache.d.ts.map +1 -0
- package/dist/mcp/query-cache.js +191 -0
- package/dist/mcp/query-cache.js.map +1 -0
- package/dist/mcp/query-pool.d.ts +94 -0
- package/dist/mcp/query-pool.d.ts.map +1 -0
- package/dist/mcp/query-pool.js +297 -0
- package/dist/mcp/query-pool.js.map +1 -0
- package/dist/mcp/query-worker.d.ts +24 -0
- package/dist/mcp/query-worker.d.ts.map +1 -0
- package/dist/mcp/query-worker.js +87 -0
- package/dist/mcp/query-worker.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +5 -7
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +12 -14
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/tools.d.ts +77 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +696 -60
- package/dist/mcp/tools.js.map +1 -1
- package/dist/project-config.d.ts +20 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +42 -2
- package/dist/project-config.js.map +1 -1
- package/dist/reasoning/login.js +1 -1
- package/dist/reasoning/login.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts +0 -28
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +765 -79
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +1 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +72 -11
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/cooperative-yield.d.ts +32 -0
- package/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/dist/resolution/cooperative-yield.js +42 -0
- package/dist/resolution/cooperative-yield.js.map +1 -0
- package/dist/resolution/index.d.ts +11 -2
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +72 -4
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +22 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +317 -20
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/spec/config.d.ts +7 -11
- package/dist/spec/config.d.ts.map +1 -1
- package/dist/spec/config.js +77 -48
- package/dist/spec/config.js.map +1 -1
- package/dist/spec/db/fragment-node.d.ts +1 -0
- package/dist/spec/db/fragment-node.d.ts.map +1 -1
- package/dist/spec/db/fragment-node.js +8 -0
- package/dist/spec/db/fragment-node.js.map +1 -1
- package/dist/spec/db/fts.d.ts +14 -0
- package/dist/spec/db/fts.d.ts.map +1 -1
- package/dist/spec/db/fts.js +43 -4
- package/dist/spec/db/fts.js.map +1 -1
- package/dist/spec/db/schema.d.ts +6 -3
- package/dist/spec/db/schema.d.ts.map +1 -1
- package/dist/spec/db/schema.js +36 -4
- package/dist/spec/db/schema.js.map +1 -1
- package/dist/spec/db/schema.sql +11 -1
- package/dist/spec/evolve/logic-checker.d.ts +2 -2
- package/dist/spec/evolve/logic-checker.d.ts.map +1 -1
- package/dist/spec/evolve/logic-checker.js +3 -27
- package/dist/spec/evolve/logic-checker.js.map +1 -1
- package/dist/spec/evolve/pipeline.d.ts +21 -19
- package/dist/spec/evolve/pipeline.d.ts.map +1 -1
- package/dist/spec/evolve/pipeline.js +284 -31
- package/dist/spec/evolve/pipeline.js.map +1 -1
- package/dist/spec/evolve/spec-rewriter.d.ts +2 -2
- package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
- package/dist/spec/evolve/spec-rewriter.js +7 -31
- package/dist/spec/evolve/spec-rewriter.js.map +1 -1
- package/dist/spec/graph/queries.d.ts +97 -0
- package/dist/spec/graph/queries.d.ts.map +1 -1
- package/dist/spec/graph/queries.js +225 -0
- package/dist/spec/graph/queries.js.map +1 -1
- package/dist/spec/llm/client.d.ts +29 -0
- package/dist/spec/llm/client.d.ts.map +1 -0
- package/dist/spec/llm/client.js +123 -0
- package/dist/spec/llm/client.js.map +1 -0
- package/dist/spec/llm/index.d.ts +3 -0
- package/dist/spec/llm/index.d.ts.map +1 -0
- package/dist/spec/llm/index.js +11 -0
- package/dist/spec/llm/index.js.map +1 -0
- package/dist/spec/llm/prompts.d.ts +13 -0
- package/dist/spec/llm/prompts.d.ts.map +1 -0
- package/dist/spec/llm/prompts.js +75 -0
- package/dist/spec/llm/prompts.js.map +1 -0
- package/dist/spec/mining/git-scanner.d.ts +12 -0
- package/dist/spec/mining/git-scanner.d.ts.map +1 -1
- package/dist/spec/mining/git-scanner.js +41 -0
- package/dist/spec/mining/git-scanner.js.map +1 -1
- package/dist/spec/mining/pipeline.d.ts.map +1 -1
- package/dist/spec/mining/pipeline.js +14 -1
- package/dist/spec/mining/pipeline.js.map +1 -1
- package/dist/spec/utils.d.ts +2 -1
- package/dist/spec/utils.d.ts.map +1 -1
- package/dist/spec/utils.js +3 -1
- package/dist/spec/utils.js.map +1 -1
- package/dist/sync/worktree.d.ts +9 -0
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +40 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/shimmer-progress.d.ts +2 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -1
- package/dist/ui/shimmer-progress.js +19 -2
- package/dist/ui/shimmer-progress.js.map +1 -1
- package/dist/upgrade/index.js +1 -1
- package/dist/upgrade/index.js.map +1 -1
- package/package.json +3 -3
- package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
- package/scripts/exp_boundary_eval/README.md +247 -0
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
- package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
- package/scripts/exp_boundary_eval/_utils.py +1116 -0
- package/scripts/exp_boundary_eval/analyze.py +1313 -0
- package/scripts/exp_boundary_eval/data/agents.json +109 -0
- package/scripts/exp_boundary_eval/data/experiments.json +140 -0
- package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
- package/scripts/exp_boundary_eval/run_all.py +378 -0
- package/scripts/exp_boundary_eval/run_one.py +165 -0
- package/scripts/exp_boundary_eval/run_session.py +158 -0
- package/scripts/exp_boundary_eval/setup.py +120 -0
- package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
- package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
- package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
- package/scripts/npm-shim.js +25 -3
- package/scripts/ohos-sdk-publish.mjs +133 -0
- package/scripts/qa_eval/README.md +1 -1
- package/scripts/qa_eval/agent_runner.py +1 -1
package/dist/bin/homegraph.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* homegraph callers <symbol> Find what calls a function/method
|
|
21
21
|
* homegraph callees <symbol> Find what a function/method calls
|
|
22
22
|
* homegraph impact <symbol> Analyze what code is affected by changing a symbol
|
|
23
|
-
* homegraph
|
|
23
|
+
* homegraph index-api <tools> Build OHOS API db from command-line-tools
|
|
24
24
|
* homegraph upgrade [version] Update HomeGraph to the latest release
|
|
25
25
|
*/
|
|
26
26
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -60,14 +60,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
60
60
|
const commander_1 = require("commander");
|
|
61
61
|
const path = __importStar(require("path"));
|
|
62
62
|
const fs = __importStar(require("fs"));
|
|
63
|
-
const utils_1 = require("../spec/utils");
|
|
64
63
|
const directory_1 = require("../directory");
|
|
64
|
+
const utils_1 = require("../spec/utils");
|
|
65
65
|
const worktree_1 = require("../sync/worktree");
|
|
66
66
|
const shimmer_progress_1 = require("../ui/shimmer-progress");
|
|
67
67
|
const glyphs_1 = require("../ui/glyphs");
|
|
68
68
|
const node_version_check_1 = require("./node-version-check");
|
|
69
69
|
const fatal_handler_1 = require("./fatal-handler");
|
|
70
70
|
const wasm_runtime_flags_1 = require("../extraction/wasm-runtime-flags");
|
|
71
|
+
const command_supervision_1 = require("./command-supervision");
|
|
71
72
|
const extraction_version_1 = require("../extraction/extraction-version");
|
|
72
73
|
const telemetry_1 = require("../telemetry");
|
|
73
74
|
// Lazy-load heavy modules (HomeGraph, runInstaller) to keep CLI startup fast.
|
|
@@ -416,6 +417,19 @@ function main() {
|
|
|
416
417
|
fs.unlinkSync(logPath);
|
|
417
418
|
}
|
|
418
419
|
}
|
|
420
|
+
const ohosApiWarnings = result.errors.filter((e) => e.severity === 'warning' && e.code?.startsWith('ohos_api_'));
|
|
421
|
+
if (ohosApiWarnings.length > 0) {
|
|
422
|
+
clack.log.warn('OHOS SDK API db not attached — project index is complete; explore uses project code only.');
|
|
423
|
+
clack.note(ohosApiWarnings.map((e) => e.message).join('\n'), 'OHOS API notice');
|
|
424
|
+
}
|
|
425
|
+
const arktsDegraded = result.errors.filter((e) => e.code === 'arkts_degraded');
|
|
426
|
+
if (arktsDegraded.length > 0) {
|
|
427
|
+
clack.log.warn('ArkTS index quality notice — see details below.');
|
|
428
|
+
clack.note(`${arktsDegraded[0].message}\n\n` +
|
|
429
|
+
'Large ArkTS repos on Windows use an isolated indexer with enableMethodBodyBuild=true.\n' +
|
|
430
|
+
'If indexing failed, set HOMEGRAPH_ARKTS_STACK_SIZES_KB=65536,131072,262144 and retry:\n' +
|
|
431
|
+
' node --stack-size=65536 dist/bin/homegraph.js index <path>', 'ArkTS quality notice');
|
|
432
|
+
}
|
|
419
433
|
}
|
|
420
434
|
/**
|
|
421
435
|
* Write detailed error log to .homegraph/errors.log
|
|
@@ -512,20 +526,27 @@ function main() {
|
|
|
512
526
|
// Indexing runs by default now. The legacy -i/--index flag is still
|
|
513
527
|
// accepted (so existing muscle memory and scripts don't break) but is a
|
|
514
528
|
// no-op — initializing always builds the initial index.
|
|
529
|
+
// Supervise the index: self-terminate if orphaned or wedged (#999).
|
|
530
|
+
const supervision = (0, command_supervision_1.installCommandSupervision)('init');
|
|
515
531
|
let result;
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
532
|
+
try {
|
|
533
|
+
if (options.verbose) {
|
|
534
|
+
result = await cg.indexAll({
|
|
535
|
+
onProgress: createVerboseProgress(),
|
|
536
|
+
verbose: true,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
|
|
541
|
+
const progress = (0, shimmer_progress_1.createShimmerProgress)();
|
|
542
|
+
result = await cg.indexAll({
|
|
543
|
+
onProgress: progress.onProgress,
|
|
544
|
+
});
|
|
545
|
+
await progress.stop();
|
|
546
|
+
}
|
|
521
547
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const progress = (0, shimmer_progress_1.createShimmerProgress)();
|
|
525
|
-
result = await cg.indexAll({
|
|
526
|
-
onProgress: progress.onProgress,
|
|
527
|
-
});
|
|
528
|
-
await progress.stop();
|
|
548
|
+
finally {
|
|
549
|
+
supervision.stop();
|
|
529
550
|
}
|
|
530
551
|
printIndexResult(clack, result, projectPath);
|
|
531
552
|
await recordIndexTelemetry(cg, result);
|
|
@@ -620,47 +641,56 @@ function main() {
|
|
|
620
641
|
process.exit(1);
|
|
621
642
|
}
|
|
622
643
|
const { default: HomeGraph } = await loadHomeGraph();
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
644
|
+
// `index` is a FULL re-index — identical to a fresh `init`. RECREATE the
|
|
645
|
+
// database from scratch (discard .homegraph/homegraph.db + its WAL) rather
|
|
646
|
+
// than opening the old graph and DELETE-ing every row. The clear-then-index
|
|
647
|
+
// approach reported "0 nodes" without the clear (#874); the recreate keeps
|
|
648
|
+
// that fixed AND avoids the failure mode where, on a large or pre-fix
|
|
649
|
+
// poisoned index, the per-row FTS delete churn wedged the main thread long
|
|
650
|
+
// enough to trip the liveness watchdog before scanning even began (#1067).
|
|
651
|
+
// recreate() hands back a fresh, empty instance — no clear() needed. For
|
|
652
|
+
// fast incremental updates use `sync`.
|
|
653
|
+
const cg = await HomeGraph.recreate(projectPath);
|
|
654
|
+
// Supervise the indexer: self-terminate if orphaned (parent shim killed)
|
|
655
|
+
// or if the main thread wedges — neither was guarded on this path (#999).
|
|
656
|
+
const supervision = (0, command_supervision_1.installCommandSupervision)('index');
|
|
657
|
+
try {
|
|
658
|
+
if (options.quiet) {
|
|
659
|
+
// Quiet mode: no UI, just run against the freshly-recreated graph.
|
|
660
|
+
const result = await cg.indexAll();
|
|
661
|
+
if (!result.success)
|
|
662
|
+
process.exit(1);
|
|
663
|
+
cg.destroy();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const clack = await importESM('@clack/prompts');
|
|
667
|
+
clack.intro('Indexing project');
|
|
668
|
+
let result;
|
|
669
|
+
if (options.verbose) {
|
|
670
|
+
result = await cg.indexAll({
|
|
671
|
+
onProgress: createVerboseProgress(),
|
|
672
|
+
verbose: true,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
|
|
677
|
+
const progress = (0, shimmer_progress_1.createShimmerProgress)();
|
|
678
|
+
result = await cg.indexAll({
|
|
679
|
+
onProgress: progress.onProgress,
|
|
680
|
+
});
|
|
681
|
+
await progress.stop();
|
|
682
|
+
}
|
|
683
|
+
printIndexResult(clack, result, projectPath);
|
|
684
|
+
await recordIndexTelemetry(cg, result);
|
|
685
|
+
if (!result.success) {
|
|
630
686
|
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
clack.outro('Done');
|
|
631
689
|
cg.destroy();
|
|
632
|
-
return;
|
|
633
690
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
// `index` is a FULL re-index: clear the existing graph and rebuild it from
|
|
637
|
-
// scratch so the result is identical to a fresh `init`. Without the clear,
|
|
638
|
-
// indexAll() skips every unchanged file by its content hash and reports
|
|
639
|
-
// "0 nodes, 0 edges" against the already-populated graph — which reads as
|
|
640
|
-
// "index wiped my index" (#874). For fast incremental updates use `sync`.
|
|
641
|
-
cg.clear();
|
|
642
|
-
let result;
|
|
643
|
-
if (options.verbose) {
|
|
644
|
-
result = await cg.indexAll({
|
|
645
|
-
onProgress: createVerboseProgress(),
|
|
646
|
-
verbose: true,
|
|
647
|
-
});
|
|
691
|
+
finally {
|
|
692
|
+
supervision.stop();
|
|
648
693
|
}
|
|
649
|
-
else {
|
|
650
|
-
process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
|
|
651
|
-
const progress = (0, shimmer_progress_1.createShimmerProgress)();
|
|
652
|
-
result = await cg.indexAll({
|
|
653
|
-
onProgress: progress.onProgress,
|
|
654
|
-
});
|
|
655
|
-
await progress.stop();
|
|
656
|
-
}
|
|
657
|
-
printIndexResult(clack, result, projectPath);
|
|
658
|
-
await recordIndexTelemetry(cg, result);
|
|
659
|
-
if (!result.success) {
|
|
660
|
-
process.exit(1);
|
|
661
|
-
}
|
|
662
|
-
clack.outro('Done');
|
|
663
|
-
cg.destroy();
|
|
664
694
|
}
|
|
665
695
|
catch (err) {
|
|
666
696
|
error(`Failed to index: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -763,6 +793,7 @@ function main() {
|
|
|
763
793
|
const journalMode = cg.getJournalMode();
|
|
764
794
|
const buildInfo = cg.getIndexBuildInfo();
|
|
765
795
|
const reindexRecommended = cg.isIndexStale();
|
|
796
|
+
const ohosApi = cg.getOhosApiBinding();
|
|
766
797
|
// JSON output mode
|
|
767
798
|
if (options.json) {
|
|
768
799
|
const lastIndexedMs = cg.getLastIndexedAt();
|
|
@@ -788,6 +819,9 @@ function main() {
|
|
|
788
819
|
worktreeMismatch: worktreeMismatch
|
|
789
820
|
? { worktreeRoot: worktreeMismatch.worktreeRoot, indexRoot: worktreeMismatch.indexRoot }
|
|
790
821
|
: null,
|
|
822
|
+
ohosApi: ohosApi
|
|
823
|
+
? { version: ohosApi.version, packageName: ohosApi.packageName, dbPath: ohosApi.dbPath }
|
|
824
|
+
: null,
|
|
791
825
|
index: {
|
|
792
826
|
builtWithVersion: buildInfo.version,
|
|
793
827
|
builtWithExtractionVersion: buildInfo.extractionVersion,
|
|
@@ -842,6 +876,13 @@ function main() {
|
|
|
842
876
|
console.log(` ${lang.padEnd(15)} ${formatNumber(count)}`);
|
|
843
877
|
}
|
|
844
878
|
console.log();
|
|
879
|
+
if (ohosApi) {
|
|
880
|
+
console.log(chalk.bold('OHOS API Database:'));
|
|
881
|
+
console.log(` Version: ${ohosApi.version}`);
|
|
882
|
+
console.log(` Package: ${ohosApi.packageName}`);
|
|
883
|
+
console.log(` DB: ${ohosApi.dbPath}`);
|
|
884
|
+
console.log();
|
|
885
|
+
}
|
|
845
886
|
// Pending changes
|
|
846
887
|
const totalChanges = changes.added.length + changes.modified.length + changes.removed.length;
|
|
847
888
|
if (totalChanges > 0) {
|
|
@@ -918,13 +959,16 @@ function main() {
|
|
|
918
959
|
}
|
|
919
960
|
else {
|
|
920
961
|
console.log(chalk.bold(`\nSearch Results for "${search}":\n`));
|
|
962
|
+
// Results arrive already ranked by relevance, so the order conveys
|
|
963
|
+
// it. We don't print the raw score: it's an unbounded BM25/FTS value
|
|
964
|
+
// (relative-ranking only), and the old `(score * 100)%` rendered it
|
|
965
|
+
// as nonsensical percentages like "12042%" (#1045). The MCP search
|
|
966
|
+
// tool likewise shows no score. Raw `score` stays in --json output.
|
|
921
967
|
for (const result of results) {
|
|
922
968
|
const node = result.node;
|
|
923
969
|
const location = `${node.filePath}:${node.startLine}`;
|
|
924
|
-
const score = chalk.dim(`(${(result.score * 100).toFixed(0)}%)`);
|
|
925
970
|
console.log(chalk.cyan(node.kind.padEnd(12)) +
|
|
926
|
-
chalk.white(node.name)
|
|
927
|
-
' ' + score);
|
|
971
|
+
chalk.white(node.name));
|
|
928
972
|
console.log(chalk.dim(` ${location}`));
|
|
929
973
|
if (node.signature) {
|
|
930
974
|
console.log(chalk.dim(` ${node.signature}`));
|
|
@@ -1019,11 +1063,15 @@ function main() {
|
|
|
1019
1063
|
return;
|
|
1020
1064
|
}
|
|
1021
1065
|
const prompt = String(input.prompt || '');
|
|
1022
|
-
// Gate: only structural / flow / impact / where-how prompts get context
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
|
|
1026
|
-
|
|
1066
|
+
// Gate: only structural / flow / impact / where-how prompts get context, so
|
|
1067
|
+
// every other prompt ("fix this typo") stays a zero-cost no-op. Language-aware
|
|
1068
|
+
// (English + CJK keywords, plus code-shaped tokens) so it fires for non-English
|
|
1069
|
+
// prompts too (issue #994). A keyword fires on its own; a code-token is only a
|
|
1070
|
+
// CANDIDATE — verified against the graph below, so a tech brand ("JavaScript")
|
|
1071
|
+
// that looks like a symbol but isn't one here doesn't inject spurious context.
|
|
1072
|
+
const keyworded = (0, directory_1.hasStructuralKeyword)(prompt);
|
|
1073
|
+
const codeTokens = keyworded ? [] : (0, directory_1.extractCodeTokens)(prompt);
|
|
1074
|
+
if (!keyworded && codeTokens.length === 0)
|
|
1027
1075
|
return;
|
|
1028
1076
|
// Decide what to inject, shaped by WHERE the index(es) are: the nearest
|
|
1029
1077
|
// indexed ancestor of cwd, or — when cwd is an un-indexed workspace root
|
|
@@ -1043,6 +1091,13 @@ function main() {
|
|
|
1043
1091
|
const { default: HomeGraph } = await loadHomeGraph();
|
|
1044
1092
|
const cg = await HomeGraph.open(plan.exploreRoot);
|
|
1045
1093
|
try {
|
|
1094
|
+
// Code-token-only prompt: require that at least one token is a REAL symbol
|
|
1095
|
+
// in THIS index before front-loading. Without it, a brand name or common
|
|
1096
|
+
// word that merely looks like code ("JavaScript", "GitHub") would run
|
|
1097
|
+
// explore and inject ~16KB of low-relevance context (issue #994 follow-up).
|
|
1098
|
+
// A keyword-bearing prompt skips this — the keyword is signal enough.
|
|
1099
|
+
if (!keyworded && !codeTokens.some((t) => cg.getNodesByName(t).length > 0))
|
|
1100
|
+
return;
|
|
1046
1101
|
const { ToolHandler } = await Promise.resolve().then(() => __importStar(require('../mcp/tools')));
|
|
1047
1102
|
const handler = new ToolHandler(cg);
|
|
1048
1103
|
const result = await handler.execute('homegraph_explore', { query: prompt });
|
|
@@ -1078,14 +1133,17 @@ function main() {
|
|
|
1078
1133
|
}
|
|
1079
1134
|
});
|
|
1080
1135
|
/**
|
|
1081
|
-
* homegraph node
|
|
1136
|
+
* homegraph node [name]
|
|
1082
1137
|
*
|
|
1083
1138
|
* The CLI face of the MCP homegraph_node tool: one symbol's source +
|
|
1084
1139
|
* caller/callee trail, or a whole file with line numbers + dependents
|
|
1085
1140
|
* (Read-parity). Same subagent/non-MCP rationale as `explore`.
|
|
1141
|
+
*
|
|
1142
|
+
* `name` is OPTIONAL because `--file` (file-read mode) carries no symbol —
|
|
1143
|
+
* a required `<name>` made `codegraph node -f <file>` unreachable (#1044).
|
|
1086
1144
|
*/
|
|
1087
1145
|
program
|
|
1088
|
-
.command('node
|
|
1146
|
+
.command('node [name]')
|
|
1089
1147
|
.description('One symbol\'s source + caller/callee trail, or read a file with line numbers + dependents (same output as the homegraph_node MCP tool)')
|
|
1090
1148
|
.option('-p, --path <path>', 'Project path')
|
|
1091
1149
|
.option('-f, --file <file>', 'Treat as file mode (or disambiguate a symbol to this file)')
|
|
@@ -1093,6 +1151,13 @@ function main() {
|
|
|
1093
1151
|
.option('--limit <number>', 'File mode: maximum lines')
|
|
1094
1152
|
.option('--symbols-only', 'File mode: just the symbol map + dependents')
|
|
1095
1153
|
.action(async (name, options) => {
|
|
1154
|
+
// Need a symbol (positional) OR a file (--file / a path-like positional).
|
|
1155
|
+
// With [name] optional, a bare `codegraph node` reaches here with neither
|
|
1156
|
+
// and must be told what to pass, rather than crashing downstream.
|
|
1157
|
+
if (!name && !options.file) {
|
|
1158
|
+
error("Pass a symbol name (e.g. 'codegraph node parseToken') or a file (e.g. 'codegraph node -f src/auth.ts', or 'codegraph node src/auth.ts').");
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1096
1161
|
const projectPath = resolveProjectPath(options.path);
|
|
1097
1162
|
try {
|
|
1098
1163
|
if (!(0, directory_1.isInitialized)(projectPath)) {
|
|
@@ -1114,10 +1179,10 @@ function main() {
|
|
|
1114
1179
|
if (name && name !== options.file)
|
|
1115
1180
|
args.symbol = name;
|
|
1116
1181
|
}
|
|
1117
|
-
else if (name.includes('/') || name.includes('\\')) {
|
|
1182
|
+
else if (name && (name.includes('/') || name.includes('\\'))) {
|
|
1118
1183
|
args.file = name.replace(/\\/g, '/');
|
|
1119
1184
|
}
|
|
1120
|
-
else {
|
|
1185
|
+
else if (name) {
|
|
1121
1186
|
args.symbol = name;
|
|
1122
1187
|
args.includeCode = true;
|
|
1123
1188
|
}
|
|
@@ -1850,6 +1915,92 @@ function main() {
|
|
|
1850
1915
|
process.exit(1);
|
|
1851
1916
|
}
|
|
1852
1917
|
});
|
|
1918
|
+
/**
|
|
1919
|
+
* homegraph install
|
|
1920
|
+
*/
|
|
1921
|
+
program
|
|
1922
|
+
.command('install')
|
|
1923
|
+
.description('Install homegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
|
|
1924
|
+
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
|
|
1925
|
+
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
|
|
1926
|
+
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
|
|
1927
|
+
.option('--no-permissions', 'Skip writing the auto-allow permissions list (Claude Code only)')
|
|
1928
|
+
.option('--print-config <id>', 'Print MCP config snippet for the named agent and exit (no file writes)')
|
|
1929
|
+
.action(async (opts) => {
|
|
1930
|
+
if (opts.printConfig) {
|
|
1931
|
+
const { getTarget, listTargetIds } = await Promise.resolve().then(() => __importStar(require('../installer/targets/registry')));
|
|
1932
|
+
const target = getTarget(opts.printConfig);
|
|
1933
|
+
if (!target) {
|
|
1934
|
+
const known = listTargetIds().join(', ');
|
|
1935
|
+
error(`Unknown target "${opts.printConfig}". Known: ${known}.`);
|
|
1936
|
+
process.exit(1);
|
|
1937
|
+
}
|
|
1938
|
+
const loc = (opts.location === 'local' ? 'local' : 'global');
|
|
1939
|
+
process.stdout.write(target.printConfig(loc));
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
const { runInstallerWithOptions } = await Promise.resolve().then(() => __importStar(require('../installer')));
|
|
1943
|
+
if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
|
|
1944
|
+
error(`--location must be "global" or "local" (got "${opts.location}").`);
|
|
1945
|
+
process.exit(1);
|
|
1946
|
+
}
|
|
1947
|
+
try {
|
|
1948
|
+
// Commander's `--no-permissions` makes `opts.permissions === false`;
|
|
1949
|
+
// omitting the flag leaves it `true` (the positive-form default).
|
|
1950
|
+
// We MUST treat the default-true as "user did not override — let
|
|
1951
|
+
// the orchestrator prompt" and only forward an explicit `false`
|
|
1952
|
+
// (or `true` when --yes implies it). Otherwise the auto-allow
|
|
1953
|
+
// prompt is silently skipped on every interactive run.
|
|
1954
|
+
const explicitNoPermissions = opts.permissions === false;
|
|
1955
|
+
const autoAllow = explicitNoPermissions
|
|
1956
|
+
? false
|
|
1957
|
+
: opts.yes
|
|
1958
|
+
? true
|
|
1959
|
+
: undefined;
|
|
1960
|
+
await runInstallerWithOptions({
|
|
1961
|
+
target: opts.target,
|
|
1962
|
+
location: opts.location,
|
|
1963
|
+
autoAllow,
|
|
1964
|
+
yes: opts.yes,
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
catch (err) {
|
|
1968
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1969
|
+
process.exit(1);
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
/**
|
|
1973
|
+
* homegraph uninstall
|
|
1974
|
+
*
|
|
1975
|
+
* Inverse of `install`. Removes the homegraph MCP server entry,
|
|
1976
|
+
* instructions block, and permissions from every agent (or a
|
|
1977
|
+
* `--target` subset). Prompts global-vs-local when not given. Does NOT
|
|
1978
|
+
* delete the `.homegraph/` index — that's `homegraph uninit`.
|
|
1979
|
+
*/
|
|
1980
|
+
program
|
|
1981
|
+
.command('uninstall')
|
|
1982
|
+
.description('Remove homegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
|
|
1983
|
+
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "all". Default: all')
|
|
1984
|
+
.option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
|
|
1985
|
+
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
|
|
1986
|
+
.action(async (opts) => {
|
|
1987
|
+
const { runUninstaller } = await Promise.resolve().then(() => __importStar(require('../installer')));
|
|
1988
|
+
if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
|
|
1989
|
+
error(`--location must be "global" or "local" (got "${opts.location}").`);
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
}
|
|
1992
|
+
try {
|
|
1993
|
+
await runUninstaller({
|
|
1994
|
+
target: opts.target,
|
|
1995
|
+
location: opts.location,
|
|
1996
|
+
yes: opts.yes,
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
catch (err) {
|
|
2000
|
+
error(err instanceof Error ? err.message : String(err));
|
|
2001
|
+
process.exit(1);
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
1853
2004
|
// =============================================================================
|
|
1854
2005
|
// Spec commands (Commit4Spec knowledge graph)
|
|
1855
2006
|
// =============================================================================
|
|
@@ -1926,7 +2077,6 @@ function main() {
|
|
|
1926
2077
|
const repoPath = resolveSpecProjectPath(options.path);
|
|
1927
2078
|
const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
|
|
1928
2079
|
const { resolveDbPath, computeBudgetProfile } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
1929
|
-
const { initSpecSchema } = await Promise.resolve().then(() => __importStar(require('../spec/db/schema')));
|
|
1930
2080
|
const { searchAndGetContext } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
|
|
1931
2081
|
const dbPath = resolveDbPath(repoPath, options.dbPath);
|
|
1932
2082
|
if (!fs.existsSync(dbPath)) {
|
|
@@ -1935,7 +2085,6 @@ function main() {
|
|
|
1935
2085
|
}
|
|
1936
2086
|
const created = createDatabase(dbPath);
|
|
1937
2087
|
db = created.db;
|
|
1938
|
-
initSpecSchema(db);
|
|
1939
2088
|
const topK = Math.max(1, Math.min(parseInt(options.topK || '5', 10) || 5, 50));
|
|
1940
2089
|
const includeFragments = options.fragments !== false;
|
|
1941
2090
|
const contexts = searchAndGetContext(db, text, topK, includeFragments);
|
|
@@ -1996,6 +2145,298 @@ function main() {
|
|
|
1996
2145
|
catch { /* best effort */ }
|
|
1997
2146
|
}
|
|
1998
2147
|
});
|
|
2148
|
+
/**
|
|
2149
|
+
* homegraph spec find <filePath>
|
|
2150
|
+
*/
|
|
2151
|
+
specCommand
|
|
2152
|
+
.command('find <filePath>')
|
|
2153
|
+
.description('Find specs related to the given file path via code-fragment matching')
|
|
2154
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
2155
|
+
.option('--db-path <path>', 'Path to the SQLite database file')
|
|
2156
|
+
.option('-j, --json', 'Output as JSON')
|
|
2157
|
+
.action(async (filePath, options) => {
|
|
2158
|
+
let db;
|
|
2159
|
+
try {
|
|
2160
|
+
const repoPath = resolveSpecProjectPath(options.path);
|
|
2161
|
+
const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
|
|
2162
|
+
const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
2163
|
+
const { findSpecsByFilePath } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
|
|
2164
|
+
const dbPath = resolveDbPath(repoPath, options.dbPath);
|
|
2165
|
+
if (!fs.existsSync(dbPath)) {
|
|
2166
|
+
error(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`);
|
|
2167
|
+
process.exit(1);
|
|
2168
|
+
}
|
|
2169
|
+
const created = createDatabase(dbPath);
|
|
2170
|
+
db = created.db;
|
|
2171
|
+
const result = findSpecsByFilePath(db, filePath);
|
|
2172
|
+
if (options.json) {
|
|
2173
|
+
console.log(JSON.stringify({
|
|
2174
|
+
filePath,
|
|
2175
|
+
matched_count: result.matched_count,
|
|
2176
|
+
truncated: result.truncated,
|
|
2177
|
+
results: result.results,
|
|
2178
|
+
}, null, 2));
|
|
2179
|
+
}
|
|
2180
|
+
else {
|
|
2181
|
+
console.log(chalk.bold(`\n${result.matched_count} spec${result.matched_count !== 1 ? 's' : ''} matched for ${filePath}:\n`));
|
|
2182
|
+
for (const r of result.results) {
|
|
2183
|
+
console.log(` ${chalk.cyan(r.id.padEnd(16))} ${r.title.padEnd(32)} ${chalk.green(r.status.padEnd(12))} v${r.version} ${chalk.dim(r.filePath)}`);
|
|
2184
|
+
}
|
|
2185
|
+
console.log();
|
|
2186
|
+
if (result.truncated) {
|
|
2187
|
+
console.log(chalk.yellow(` ... and more (showing first ${result.matched_count} of >${result.matched_count} results)`));
|
|
2188
|
+
console.log();
|
|
2189
|
+
}
|
|
2190
|
+
if (result.matched_count === 0) {
|
|
2191
|
+
info(`No specs found for file path "${filePath}". Try a partial path (e.g. "src/auth" instead of "src/auth/login.ts").`);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
catch (err) {
|
|
2196
|
+
error(`Find failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2197
|
+
process.exit(1);
|
|
2198
|
+
}
|
|
2199
|
+
finally {
|
|
2200
|
+
try {
|
|
2201
|
+
db?.close();
|
|
2202
|
+
}
|
|
2203
|
+
catch { /* best effort */ }
|
|
2204
|
+
}
|
|
2205
|
+
});
|
|
2206
|
+
/**
|
|
2207
|
+
* homegraph spec trace <symbol>
|
|
2208
|
+
*/
|
|
2209
|
+
specCommand
|
|
2210
|
+
.command('trace <symbol>')
|
|
2211
|
+
.description('Trace a code symbol back to its associated design Specs')
|
|
2212
|
+
.option('-f, --file <path>', 'File path for symbol disambiguation')
|
|
2213
|
+
.option('-l, --line <number>', 'Line number for symbol disambiguation')
|
|
2214
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
2215
|
+
.option('--db-path <path>', 'Path to the SQLite database file')
|
|
2216
|
+
.option('--top-k <number>', 'Number of results to return', '10')
|
|
2217
|
+
.option('-j, --json', 'Output as JSON')
|
|
2218
|
+
.action(async (symbol, options) => {
|
|
2219
|
+
let db;
|
|
2220
|
+
let cg;
|
|
2221
|
+
try {
|
|
2222
|
+
const repoPath = resolveSpecProjectPath(options.path);
|
|
2223
|
+
// isNaN check so --line 0 is handled correctly (parseInt('0') → 0 is falsy)
|
|
2224
|
+
const lineRaw = options.line !== undefined ? parseInt(options.line, 10) : NaN;
|
|
2225
|
+
const line = isNaN(lineRaw) ? undefined : lineRaw;
|
|
2226
|
+
// Open HomeGraph code index
|
|
2227
|
+
const { default: HomeGraph } = await Promise.resolve().then(() => __importStar(require('../index')));
|
|
2228
|
+
cg = await HomeGraph.open(repoPath);
|
|
2229
|
+
// Inline symbol resolution (mirrors findSymbolMatches in tools.ts)
|
|
2230
|
+
const isQualified = /[.\/]|::/.test(symbol);
|
|
2231
|
+
const { isGeneratedFile } = await Promise.resolve().then(() => __importStar(require('../extraction/generated-detection')));
|
|
2232
|
+
let nodes = [];
|
|
2233
|
+
if (!isQualified) {
|
|
2234
|
+
const exact = cg.getNodesByName(symbol);
|
|
2235
|
+
if (exact.length > 0) {
|
|
2236
|
+
nodes = [...exact].sort((a, b) => (isGeneratedFile(a.filePath) ? 1 : 0) - (isGeneratedFile(b.filePath) ? 1 : 0));
|
|
2237
|
+
}
|
|
2238
|
+
else {
|
|
2239
|
+
const fuzzy = cg.searchNodes(symbol, { limit: 10 });
|
|
2240
|
+
if (fuzzy[0])
|
|
2241
|
+
nodes = [fuzzy[0].node];
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
else {
|
|
2245
|
+
let results = cg.searchNodes(symbol, { limit: 50 });
|
|
2246
|
+
if (results.length === 0) {
|
|
2247
|
+
const parts = symbol.split(/::|[./]/).filter((p) => p.length > 0);
|
|
2248
|
+
const tail = parts[parts.length - 1];
|
|
2249
|
+
if (tail && tail !== symbol)
|
|
2250
|
+
results = cg.searchNodes(tail, { limit: 50 });
|
|
2251
|
+
}
|
|
2252
|
+
// Rough matchesSymbol equivalent for CLI
|
|
2253
|
+
const exactMatches = results.filter((r) => {
|
|
2254
|
+
const n = r.node;
|
|
2255
|
+
if (n.name === symbol)
|
|
2256
|
+
return true;
|
|
2257
|
+
const fileBase = n.filePath.split('/').pop()?.replace(/\.[^.]+$/, '');
|
|
2258
|
+
if (fileBase === symbol)
|
|
2259
|
+
return true;
|
|
2260
|
+
const parts = symbol.split(/::|[./]/);
|
|
2261
|
+
if (parts[parts.length - 1] === n.name) {
|
|
2262
|
+
const firstPart = parts[0];
|
|
2263
|
+
if (n.qualifiedName.includes(firstPart))
|
|
2264
|
+
return true;
|
|
2265
|
+
if (n.filePath.includes(firstPart))
|
|
2266
|
+
return true;
|
|
2267
|
+
return parts.length === 1;
|
|
2268
|
+
}
|
|
2269
|
+
return false;
|
|
2270
|
+
});
|
|
2271
|
+
if (exactMatches.length > 0) {
|
|
2272
|
+
nodes = exactMatches
|
|
2273
|
+
.sort((a, b) => (isGeneratedFile(a.node.filePath) ? 1 : 0) - (isGeneratedFile(b.node.filePath) ? 1 : 0))
|
|
2274
|
+
.map((r) => r.node);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
if (nodes.length === 0) {
|
|
2278
|
+
console.log(chalk.yellow(`No code entities found for symbol "${symbol}".`));
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
// Disambiguate
|
|
2282
|
+
if (options.file) {
|
|
2283
|
+
// Use endsWith for precise file name matching (not substring matching)
|
|
2284
|
+
const filePattern = options.file;
|
|
2285
|
+
nodes = nodes.filter((n) => n.filePath.endsWith(filePattern));
|
|
2286
|
+
}
|
|
2287
|
+
if (line !== undefined && nodes.length > 1) {
|
|
2288
|
+
const closest = nodes.reduce((best, n) => {
|
|
2289
|
+
const bestDist = Math.abs(best.startLine - line) + Math.abs(best.endLine - line);
|
|
2290
|
+
const curDist = Math.abs(n.startLine - line) + Math.abs(n.endLine - line);
|
|
2291
|
+
return curDist < bestDist ? n : best;
|
|
2292
|
+
});
|
|
2293
|
+
nodes = [closest];
|
|
2294
|
+
}
|
|
2295
|
+
const node = nodes[0];
|
|
2296
|
+
if (!node) {
|
|
2297
|
+
console.log(chalk.yellow(`Could not resolve symbol "${symbol}" to a specific code entity.`));
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
// Open Spec database
|
|
2301
|
+
const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
|
|
2302
|
+
const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
2303
|
+
const { findSpecsByCodeSymbol } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
|
|
2304
|
+
const { initSpecSchema, runSpecMigrations, getCurrentSpecVersion, CURRENT_SPEC_SCHEMA_VERSION } = await Promise.resolve().then(() => __importStar(require('../spec/db/schema')));
|
|
2305
|
+
const dbPath = resolveDbPath(repoPath, options.dbPath);
|
|
2306
|
+
if (!fs.existsSync(dbPath)) {
|
|
2307
|
+
console.log(chalk.yellow(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`));
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
const created = createDatabase(dbPath);
|
|
2311
|
+
db = created.db;
|
|
2312
|
+
initSpecSchema(db);
|
|
2313
|
+
const currentVersion = getCurrentSpecVersion(db);
|
|
2314
|
+
if (currentVersion < CURRENT_SPEC_SCHEMA_VERSION) {
|
|
2315
|
+
runSpecMigrations(db, currentVersion);
|
|
2316
|
+
}
|
|
2317
|
+
const topKRaw = parseInt(options.topK || '10', 10);
|
|
2318
|
+
const topK = Math.max(1, Math.min(isNaN(topKRaw) ? 10 : topKRaw, 50));
|
|
2319
|
+
console.log(chalk.dim(`Resolved: ${node.name} (${node.kind}) in ${node.filePath}:${node.startLine}-${node.endLine}`));
|
|
2320
|
+
const result = findSpecsByCodeSymbol(db, {
|
|
2321
|
+
name: node.name,
|
|
2322
|
+
qualifiedName: node.qualifiedName,
|
|
2323
|
+
kind: node.kind,
|
|
2324
|
+
filePath: node.filePath,
|
|
2325
|
+
startLine: node.startLine,
|
|
2326
|
+
endLine: node.endLine,
|
|
2327
|
+
}, topK);
|
|
2328
|
+
if (options.json) {
|
|
2329
|
+
console.log(JSON.stringify({
|
|
2330
|
+
symbol,
|
|
2331
|
+
entity: {
|
|
2332
|
+
name: node.name,
|
|
2333
|
+
qualifiedName: node.qualifiedName,
|
|
2334
|
+
kind: node.kind,
|
|
2335
|
+
filePath: node.filePath,
|
|
2336
|
+
startLine: node.startLine,
|
|
2337
|
+
endLine: node.endLine,
|
|
2338
|
+
},
|
|
2339
|
+
matched_count: result.matches.length,
|
|
2340
|
+
total_candidates: result.totalCandidates,
|
|
2341
|
+
matches: result.matches.map((m) => ({
|
|
2342
|
+
spec_id: m.spec.id,
|
|
2343
|
+
title: m.spec.title,
|
|
2344
|
+
status: m.spec.status,
|
|
2345
|
+
version: m.spec.version,
|
|
2346
|
+
file_path: m.spec.filePath,
|
|
2347
|
+
score: Math.round(m.score * 1000) / 1000,
|
|
2348
|
+
score_detail: {
|
|
2349
|
+
file_path: Math.round(m.scoreDetail.filePathScore * 1000) / 1000,
|
|
2350
|
+
content: Math.round(m.scoreDetail.contentScore * 1000) / 1000,
|
|
2351
|
+
name: Math.round(m.scoreDetail.nameScore * 1000) / 1000,
|
|
2352
|
+
recency: Math.round(m.scoreDetail.recencyScore * 1000) / 1000,
|
|
2353
|
+
line_overlap: Math.round(m.scoreDetail.overlapScore * 1000) / 1000,
|
|
2354
|
+
},
|
|
2355
|
+
fragment_count: m.fragmentCount,
|
|
2356
|
+
commit_count: m.commitCount,
|
|
2357
|
+
})),
|
|
2358
|
+
}, null, 2));
|
|
2359
|
+
}
|
|
2360
|
+
else {
|
|
2361
|
+
console.log(chalk.bold(`\n${result.matches.length} spec${result.matches.length !== 1 ? 's' : ''} matched (${result.totalCandidates} candidates):\n`));
|
|
2362
|
+
for (const m of result.matches) {
|
|
2363
|
+
const scoreColor = m.score >= 0.7 ? chalk.green : m.score >= 0.4 ? chalk.yellow : chalk.red;
|
|
2364
|
+
console.log(` ${chalk.cyan(m.spec.id.padEnd(14))} ${m.spec.title.padEnd(36)} ${scoreColor((m.score * 100).toFixed(1) + '%')}`);
|
|
2365
|
+
console.log(chalk.dim(` fp=${m.scoreDetail.filePathScore.toFixed(2)} content=${m.scoreDetail.contentScore.toFixed(2)} name=${m.scoreDetail.nameScore.toFixed(2)} recency=${m.scoreDetail.recencyScore.toFixed(2)} overlap=${m.scoreDetail.overlapScore.toFixed(2)}`));
|
|
2366
|
+
console.log();
|
|
2367
|
+
}
|
|
2368
|
+
if (result.matches.length === 0) {
|
|
2369
|
+
info(`No Specs found for symbol "${symbol}". Try a more specific file path or check that specs have been mined for this project.`);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
catch (err) {
|
|
2374
|
+
error(`Trace failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2375
|
+
process.exit(1);
|
|
2376
|
+
}
|
|
2377
|
+
finally {
|
|
2378
|
+
try {
|
|
2379
|
+
await cg?.close();
|
|
2380
|
+
}
|
|
2381
|
+
catch { /* best effort */ }
|
|
2382
|
+
try {
|
|
2383
|
+
db?.close();
|
|
2384
|
+
}
|
|
2385
|
+
catch { /* best effort */ }
|
|
2386
|
+
}
|
|
2387
|
+
});
|
|
2388
|
+
/**
|
|
2389
|
+
* homegraph spec stats
|
|
2390
|
+
*/
|
|
2391
|
+
specCommand
|
|
2392
|
+
.command('stats')
|
|
2393
|
+
.description('Show statistics about the spec knowledge graph')
|
|
2394
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
2395
|
+
.option('--db-path <path>', 'Path to the SQLite database file')
|
|
2396
|
+
.option('-j, --json', 'Output as JSON')
|
|
2397
|
+
.action(async (options) => {
|
|
2398
|
+
let db;
|
|
2399
|
+
try {
|
|
2400
|
+
const repoPath = resolveSpecProjectPath(options.path);
|
|
2401
|
+
const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
|
|
2402
|
+
const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
2403
|
+
const { getSpecStats } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
|
|
2404
|
+
const dbPath = resolveDbPath(repoPath, options.dbPath);
|
|
2405
|
+
if (!fs.existsSync(dbPath)) {
|
|
2406
|
+
error(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`);
|
|
2407
|
+
process.exit(1);
|
|
2408
|
+
}
|
|
2409
|
+
const created = createDatabase(dbPath);
|
|
2410
|
+
db = created.db;
|
|
2411
|
+
const stats = getSpecStats(db);
|
|
2412
|
+
if (options.json) {
|
|
2413
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
2414
|
+
}
|
|
2415
|
+
else {
|
|
2416
|
+
console.log(chalk.bold('\nSpec Knowledge Graph Stats\n'));
|
|
2417
|
+
console.log(` Specs: ${chalk.cyan(String(stats.specCount))}`);
|
|
2418
|
+
console.log(` Active: ${chalk.green(String(stats.activeSpecCount))}`);
|
|
2419
|
+
console.log(` Deprecated: ${chalk.yellow(String(stats.deprecatedSpecCount))}`);
|
|
2420
|
+
console.log(` Commits: ${stats.commitCount}`);
|
|
2421
|
+
console.log(` Fragments: ${stats.fragmentCount}`);
|
|
2422
|
+
console.log(` Relations: ${stats.relationCount}`);
|
|
2423
|
+
console.log();
|
|
2424
|
+
if (stats.specCount === 0) {
|
|
2425
|
+
info("No specs yet. Run 'homegraph spec mine' to build the knowledge graph.");
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
catch (err) {
|
|
2430
|
+
error(`Stats failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2431
|
+
process.exit(1);
|
|
2432
|
+
}
|
|
2433
|
+
finally {
|
|
2434
|
+
try {
|
|
2435
|
+
db?.close();
|
|
2436
|
+
}
|
|
2437
|
+
catch { /* best effort */ }
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
1999
2440
|
/**
|
|
2000
2441
|
* homegraph spec evolve
|
|
2001
2442
|
*/
|
|
@@ -2069,8 +2510,12 @@ function main() {
|
|
|
2069
2510
|
'# Triggers spec self-evolution after each commit. Runs in background.',
|
|
2070
2511
|
'# Installed by: homegraph spec evolve install',
|
|
2071
2512
|
`# Logs: ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log`,
|
|
2072
|
-
|
|
2073
|
-
`
|
|
2513
|
+
'# Runtime guard: skip if homegraph is not available',
|
|
2514
|
+
`HOMEGRAPH_BIN="${homegraphBin}"`,
|
|
2515
|
+
'if [ -x "$HOMEGRAPH_BIN" ] || command -v homegraph >/dev/null 2>&1; then',
|
|
2516
|
+
' "${HOMEGRAPH_BIN:-homegraph}" spec evolve process --path "$(pwd)" --json \\',
|
|
2517
|
+
` >> ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log 2>&1 &`,
|
|
2518
|
+
'fi',
|
|
2074
2519
|
MARKER_END,
|
|
2075
2520
|
].join('\n');
|
|
2076
2521
|
let content;
|
|
@@ -2111,15 +2556,85 @@ function main() {
|
|
|
2111
2556
|
process.exit(1);
|
|
2112
2557
|
}
|
|
2113
2558
|
});
|
|
2559
|
+
/**
|
|
2560
|
+
* homegraph spec evolve uninstall
|
|
2561
|
+
*/
|
|
2562
|
+
evolveCommand
|
|
2563
|
+
.command('uninstall')
|
|
2564
|
+
.description('Remove the git post-commit hook installed by spec evolve')
|
|
2565
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
2566
|
+
.action(async (options) => {
|
|
2567
|
+
try {
|
|
2568
|
+
const repoPath = resolveSpecProjectPath(options.path);
|
|
2569
|
+
const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
|
|
2570
|
+
if (!isGitRepo(repoPath)) {
|
|
2571
|
+
error(`Not a git repository: ${repoPath}`);
|
|
2572
|
+
process.exit(1);
|
|
2573
|
+
}
|
|
2574
|
+
const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
2575
|
+
const hooksDir = execFileSync('git', ['rev-parse', '--git-path', 'hooks'], {
|
|
2576
|
+
cwd: repoPath,
|
|
2577
|
+
encoding: 'utf8',
|
|
2578
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
2579
|
+
windowsHide: true,
|
|
2580
|
+
}).trim();
|
|
2581
|
+
const resolvedHooksDir = path.isAbsolute(hooksDir)
|
|
2582
|
+
? hooksDir
|
|
2583
|
+
: path.resolve(repoPath, hooksDir);
|
|
2584
|
+
const hookPath = path.join(resolvedHooksDir, 'post-commit');
|
|
2585
|
+
if (!fs.existsSync(hookPath)) {
|
|
2586
|
+
info('No post-commit hook found — nothing to uninstall.');
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
const MARKER_BEGIN = '# >>> homegraph spec evolve hook >>>';
|
|
2590
|
+
const MARKER_END = '# <<< homegraph spec evolve hook <<<';
|
|
2591
|
+
const existing = fs.readFileSync(hookPath, 'utf8');
|
|
2592
|
+
const lines = existing.split('\n');
|
|
2593
|
+
const kept = [];
|
|
2594
|
+
let inBlock = false;
|
|
2595
|
+
let removedBlock = false;
|
|
2596
|
+
for (const line of lines) {
|
|
2597
|
+
const trimmed = line.trim();
|
|
2598
|
+
if (trimmed === MARKER_BEGIN) {
|
|
2599
|
+
inBlock = true;
|
|
2600
|
+
removedBlock = true;
|
|
2601
|
+
continue;
|
|
2602
|
+
}
|
|
2603
|
+
if (trimmed === MARKER_END) {
|
|
2604
|
+
inBlock = false;
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
if (!inBlock)
|
|
2608
|
+
kept.push(line);
|
|
2609
|
+
}
|
|
2610
|
+
if (!removedBlock) {
|
|
2611
|
+
info('No homegraph spec evolve hook block found — nothing to uninstall.');
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
const remaining = kept.join('\n').trim();
|
|
2615
|
+
if (remaining.length === 0 || /^#!\/bin\/sh\s*$/.test(remaining)) {
|
|
2616
|
+
// Hook only contained our block (or just a shebang) — delete entirely
|
|
2617
|
+
fs.unlinkSync(hookPath);
|
|
2618
|
+
success(`Removed post-commit hook at ${hookPath}`);
|
|
2619
|
+
}
|
|
2620
|
+
else {
|
|
2621
|
+
fs.writeFileSync(hookPath, remaining + '\n');
|
|
2622
|
+
success(`Removed spec evolve hook block from ${hookPath} (user content preserved)`);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
catch (err) {
|
|
2626
|
+
error(`Hook uninstall failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2627
|
+
process.exit(1);
|
|
2628
|
+
}
|
|
2629
|
+
});
|
|
2114
2630
|
/**
|
|
2115
2631
|
* homegraph spec evolve process
|
|
2116
2632
|
*/
|
|
2117
2633
|
evolveCommand
|
|
2118
2634
|
.command('process')
|
|
2119
|
-
.description('Process
|
|
2635
|
+
.description('Process commits through the spec self-evolve pipeline since last evolve')
|
|
2120
2636
|
.option('-p, --path <path>', 'Path to the repository')
|
|
2121
2637
|
.option('--db-path <path>', 'Path to the SQLite database file')
|
|
2122
|
-
.option('--commit-hash <hash>', 'Commit hash to process (default: HEAD)', 'HEAD')
|
|
2123
2638
|
.option('-j, --json', 'Output as JSON')
|
|
2124
2639
|
.action(async (options) => {
|
|
2125
2640
|
let db;
|
|
@@ -2129,7 +2644,7 @@ function main() {
|
|
|
2129
2644
|
const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
2130
2645
|
const { initSpecSchema } = await Promise.resolve().then(() => __importStar(require('../spec/db/schema')));
|
|
2131
2646
|
const { loadSpecConfig } = await Promise.resolve().then(() => __importStar(require('../spec/config')));
|
|
2132
|
-
const {
|
|
2647
|
+
const { runBatchEvolvePipeline } = await Promise.resolve().then(() => __importStar(require('../spec/evolve/pipeline')));
|
|
2133
2648
|
const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
|
|
2134
2649
|
if (!isGitRepo(repoPath)) {
|
|
2135
2650
|
error(`Not a git repository: ${repoPath}`);
|
|
@@ -2141,25 +2656,77 @@ function main() {
|
|
|
2141
2656
|
initSpecSchema(db);
|
|
2142
2657
|
const config = loadSpecConfig(repoPath);
|
|
2143
2658
|
const llmConfig = config.llm;
|
|
2144
|
-
|
|
2145
|
-
|
|
2659
|
+
if (!llmConfig) {
|
|
2660
|
+
warn('LLM not configured — Path B (LLM-based spec evolution) will be skipped.');
|
|
2661
|
+
warn('Only Path A (commit message scope → GENERATE) will be processed.');
|
|
2662
|
+
warn('Configure LLM in .homegraph/commit4spec/configs.json for full functionality:\n' +
|
|
2663
|
+
'{\n' +
|
|
2664
|
+
' "llm": {\n' +
|
|
2665
|
+
' "provider": "openai",\n' +
|
|
2666
|
+
' "apiKeyEnv": "OPENAI_API_KEY",\n' +
|
|
2667
|
+
' "model": "gpt-4o"\n' +
|
|
2668
|
+
' }\n' +
|
|
2669
|
+
'}');
|
|
2670
|
+
}
|
|
2671
|
+
const result = await runBatchEvolvePipeline(repoPath, db, llmConfig ?? undefined);
|
|
2146
2672
|
if (options.json) {
|
|
2147
2673
|
console.log(JSON.stringify(result, null, 2));
|
|
2148
2674
|
}
|
|
2149
2675
|
else {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
console.log(chalk.cyan(`Link Path A - GENERATE ${result.generateSpecId}`));
|
|
2676
|
+
if (result.commitsProcessed === 0) {
|
|
2677
|
+
console.log(chalk.green('No new commits to evolve.'));
|
|
2678
|
+
console.log(`Last evolved commit: ${result.fromCommit ? result.fromCommit.slice(0, 7) : 'none'}`);
|
|
2679
|
+
console.log(`Current HEAD: ${result.toCommit.slice(0, 7)}`);
|
|
2155
2680
|
}
|
|
2156
|
-
|
|
2157
|
-
console.log(chalk.
|
|
2158
|
-
|
|
2159
|
-
|
|
2681
|
+
else {
|
|
2682
|
+
console.log(chalk.bold(`Evolve complete: ${result.commitsProcessed} commit(s) processed`));
|
|
2683
|
+
console.log(`Range: ${result.fromCommit ? result.fromCommit.slice(0, 7) : 'none'} → ${result.toCommit.slice(0, 7)}`);
|
|
2684
|
+
if (result.metaUpdated) {
|
|
2685
|
+
console.log(chalk.green(`meta.json updated (currentCommitID = ${result.toCommit.slice(0, 7)})`));
|
|
2686
|
+
}
|
|
2687
|
+
else if (result.failures > 0) {
|
|
2688
|
+
console.log(chalk.yellow(`⚠ meta.json NOT updated — ${result.failures} commit(s) failed`));
|
|
2160
2689
|
}
|
|
2690
|
+
else if (result.skippedCommits > 0) {
|
|
2691
|
+
console.log(chalk.yellow('⚠ meta.json NOT updated — all commits were skipped (no Path A match, no LLM)'));
|
|
2692
|
+
}
|
|
2693
|
+
else {
|
|
2694
|
+
console.log(chalk.yellow('⚠ meta.json NOT updated'));
|
|
2695
|
+
}
|
|
2696
|
+
// Show per-commit summary
|
|
2697
|
+
for (const r of result.perCommitResults) {
|
|
2698
|
+
let prefix;
|
|
2699
|
+
if (r.skipped) {
|
|
2700
|
+
prefix = chalk.yellow(' ⚠');
|
|
2701
|
+
}
|
|
2702
|
+
else {
|
|
2703
|
+
prefix = r.persisted ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
2704
|
+
}
|
|
2705
|
+
console.log(`${prefix} ${r.commitHash.slice(0, 7)}`);
|
|
2706
|
+
if (r.skipped) {
|
|
2707
|
+
console.log(` skipped: ${r.skipReason}`);
|
|
2708
|
+
}
|
|
2709
|
+
if (r.isLogicChange) {
|
|
2710
|
+
console.log(` logic change: ${r.logicCheckReason}`);
|
|
2711
|
+
}
|
|
2712
|
+
if (r.generateSpecId) {
|
|
2713
|
+
console.log(` Path A - GENERATE ${r.generateSpecId}`);
|
|
2714
|
+
}
|
|
2715
|
+
for (const ev of r.evolvedSpecs) {
|
|
2716
|
+
console.log(` ${ev.specId}: ${ev.action}`);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
// Aggregate summary
|
|
2720
|
+
const totalFragments = result.perCommitResults.reduce((s, r) => s + r.fragmentsCount, 0);
|
|
2721
|
+
const totalRelations = result.perCommitResults.reduce((s, r) => s + r.relationsCreated, 0);
|
|
2722
|
+
if (result.skippedCommits > 0) {
|
|
2723
|
+
console.log(chalk.yellow(`${result.skippedCommits} commit(s) skipped — no LLM configured, did not match Path A`));
|
|
2724
|
+
}
|
|
2725
|
+
if (result.failures > 0) {
|
|
2726
|
+
console.log(chalk.red(`${result.failures} commit(s) failed — see details above`));
|
|
2727
|
+
}
|
|
2728
|
+
console.log(`Summary: fragments=${totalFragments}, relations=${totalRelations}`);
|
|
2161
2729
|
}
|
|
2162
|
-
console.log(`Summary: fragments=${result.fragmentsCount}, relations=${result.relationsCreated}`);
|
|
2163
2730
|
}
|
|
2164
2731
|
}
|
|
2165
2732
|
catch (err) {
|
|
@@ -2179,92 +2746,6 @@ function main() {
|
|
|
2179
2746
|
catch { /* best effort */ }
|
|
2180
2747
|
}
|
|
2181
2748
|
});
|
|
2182
|
-
/**
|
|
2183
|
-
* homegraph install
|
|
2184
|
-
*/
|
|
2185
|
-
program
|
|
2186
|
-
.command('install')
|
|
2187
|
-
.description('Install homegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
|
|
2188
|
-
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
|
|
2189
|
-
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
|
|
2190
|
-
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
|
|
2191
|
-
.option('--no-permissions', 'Skip writing the auto-allow permissions list (Claude Code only)')
|
|
2192
|
-
.option('--print-config <id>', 'Print MCP config snippet for the named agent and exit (no file writes)')
|
|
2193
|
-
.action(async (opts) => {
|
|
2194
|
-
if (opts.printConfig) {
|
|
2195
|
-
const { getTarget, listTargetIds } = await Promise.resolve().then(() => __importStar(require('../installer/targets/registry')));
|
|
2196
|
-
const target = getTarget(opts.printConfig);
|
|
2197
|
-
if (!target) {
|
|
2198
|
-
const known = listTargetIds().join(', ');
|
|
2199
|
-
error(`Unknown target "${opts.printConfig}". Known: ${known}.`);
|
|
2200
|
-
process.exit(1);
|
|
2201
|
-
}
|
|
2202
|
-
const loc = (opts.location === 'local' ? 'local' : 'global');
|
|
2203
|
-
process.stdout.write(target.printConfig(loc));
|
|
2204
|
-
return;
|
|
2205
|
-
}
|
|
2206
|
-
const { runInstallerWithOptions } = await Promise.resolve().then(() => __importStar(require('../installer')));
|
|
2207
|
-
if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
|
|
2208
|
-
error(`--location must be "global" or "local" (got "${opts.location}").`);
|
|
2209
|
-
process.exit(1);
|
|
2210
|
-
}
|
|
2211
|
-
try {
|
|
2212
|
-
// Commander's `--no-permissions` makes `opts.permissions === false`;
|
|
2213
|
-
// omitting the flag leaves it `true` (the positive-form default).
|
|
2214
|
-
// We MUST treat the default-true as "user did not override — let
|
|
2215
|
-
// the orchestrator prompt" and only forward an explicit `false`
|
|
2216
|
-
// (or `true` when --yes implies it). Otherwise the auto-allow
|
|
2217
|
-
// prompt is silently skipped on every interactive run.
|
|
2218
|
-
const explicitNoPermissions = opts.permissions === false;
|
|
2219
|
-
const autoAllow = explicitNoPermissions
|
|
2220
|
-
? false
|
|
2221
|
-
: opts.yes
|
|
2222
|
-
? true
|
|
2223
|
-
: undefined;
|
|
2224
|
-
await runInstallerWithOptions({
|
|
2225
|
-
target: opts.target,
|
|
2226
|
-
location: opts.location,
|
|
2227
|
-
autoAllow,
|
|
2228
|
-
yes: opts.yes,
|
|
2229
|
-
});
|
|
2230
|
-
}
|
|
2231
|
-
catch (err) {
|
|
2232
|
-
error(err instanceof Error ? err.message : String(err));
|
|
2233
|
-
process.exit(1);
|
|
2234
|
-
}
|
|
2235
|
-
});
|
|
2236
|
-
/**
|
|
2237
|
-
* homegraph uninstall
|
|
2238
|
-
*
|
|
2239
|
-
* Inverse of `install`. Removes the homegraph MCP server entry,
|
|
2240
|
-
* instructions block, and permissions from every agent (or a
|
|
2241
|
-
* `--target` subset). Prompts global-vs-local when not given. Does NOT
|
|
2242
|
-
* delete the `.homegraph/` index — that's `homegraph uninit`.
|
|
2243
|
-
*/
|
|
2244
|
-
program
|
|
2245
|
-
.command('uninstall')
|
|
2246
|
-
.description('Remove homegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, DevEco Code, CodeBuddy, Hermes Agent)')
|
|
2247
|
-
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "all". Default: all')
|
|
2248
|
-
.option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
|
|
2249
|
-
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
|
|
2250
|
-
.action(async (opts) => {
|
|
2251
|
-
const { runUninstaller } = await Promise.resolve().then(() => __importStar(require('../installer')));
|
|
2252
|
-
if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
|
|
2253
|
-
error(`--location must be "global" or "local" (got "${opts.location}").`);
|
|
2254
|
-
process.exit(1);
|
|
2255
|
-
}
|
|
2256
|
-
try {
|
|
2257
|
-
await runUninstaller({
|
|
2258
|
-
target: opts.target,
|
|
2259
|
-
location: opts.location,
|
|
2260
|
-
yes: opts.yes,
|
|
2261
|
-
});
|
|
2262
|
-
}
|
|
2263
|
-
catch (err) {
|
|
2264
|
-
error(err instanceof Error ? err.message : String(err));
|
|
2265
|
-
process.exit(1);
|
|
2266
|
-
}
|
|
2267
|
-
});
|
|
2268
2749
|
/**
|
|
2269
2750
|
* homegraph telemetry [on|off|status]
|
|
2270
2751
|
*/
|
|
@@ -2304,6 +2785,63 @@ function main() {
|
|
|
2304
2785
|
console.log(`Config: ${s.configPath}`);
|
|
2305
2786
|
console.log(chalk.dim(`\nExactly what is collected (and never collected): ${telemetry_1.TELEMETRY_DOCS}\n`));
|
|
2306
2787
|
});
|
|
2788
|
+
/**
|
|
2789
|
+
* homegraph index-api <input> [version]
|
|
2790
|
+
* Build a standalone OHOS API database from command-line-tools SDK.
|
|
2791
|
+
*/
|
|
2792
|
+
program
|
|
2793
|
+
.command('index-api <input> [version]')
|
|
2794
|
+
.description('Index OpenHarmony SDK API declarations into a standalone SQLite db')
|
|
2795
|
+
.option('-o, --output <path>', 'Output database path')
|
|
2796
|
+
.option('-q, --quiet', 'Suppress progress output')
|
|
2797
|
+
.action(async (input, versionArg, options) => {
|
|
2798
|
+
try {
|
|
2799
|
+
const { resolveOhosSdkInput, ohosApiDbFilename, indexOhosApiDb } = await Promise.resolve().then(() => __importStar(require('../extraction/languages/arkts')));
|
|
2800
|
+
const resolved = resolveOhosSdkInput({
|
|
2801
|
+
inputPath: input,
|
|
2802
|
+
versionOverride: versionArg,
|
|
2803
|
+
});
|
|
2804
|
+
const outputPath = options.output ?? path.join(process.cwd(), ohosApiDbFilename(resolved.version));
|
|
2805
|
+
if (!options.quiet) {
|
|
2806
|
+
info(`SDK home: ${resolved.sdkHome}`);
|
|
2807
|
+
info(`API version: ${resolved.version}`);
|
|
2808
|
+
info(`Output: ${outputPath}`);
|
|
2809
|
+
}
|
|
2810
|
+
const result = await indexOhosApiDb({
|
|
2811
|
+
sdkHome: resolved.sdkHome,
|
|
2812
|
+
version: resolved.version,
|
|
2813
|
+
outputPath,
|
|
2814
|
+
onProgress: options.quiet
|
|
2815
|
+
? undefined
|
|
2816
|
+
: (progress) => {
|
|
2817
|
+
if (progress.phase === 'arkts-batch') {
|
|
2818
|
+
const label = progress.subphase === 'scene' ? 'Scene' : 'Persist';
|
|
2819
|
+
process.stdout.write(`\r${label}: ${progress.current}/${progress.total} ${progress.currentFile ?? ''}`.padEnd(80));
|
|
2820
|
+
}
|
|
2821
|
+
},
|
|
2822
|
+
});
|
|
2823
|
+
resolved.cleanup?.();
|
|
2824
|
+
if (!options.quiet) {
|
|
2825
|
+
process.stdout.write('\n');
|
|
2826
|
+
if (result.success) {
|
|
2827
|
+
info(`Indexed ${result.filesIndexed} SDK files → ${result.nodesCreated} nodes, ${result.edgesCreated} edges (${result.durationMs}ms)`);
|
|
2828
|
+
}
|
|
2829
|
+
else {
|
|
2830
|
+
error('OHOS API indexing failed');
|
|
2831
|
+
for (const err of result.errors.filter((e) => e.severity === 'error')) {
|
|
2832
|
+
error(err.message);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
if (!result.success) {
|
|
2837
|
+
process.exit(1);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
catch (err) {
|
|
2841
|
+
error(err instanceof Error ? err.message : String(err));
|
|
2842
|
+
process.exit(1);
|
|
2843
|
+
}
|
|
2844
|
+
});
|
|
2307
2845
|
/**
|
|
2308
2846
|
* homegraph upgrade [version]
|
|
2309
2847
|
*
|