homegraph 1.1.2 → 1.1.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/LICENSE +21 -21
- package/README.md +243 -243
- package/dist/bin/homegraph.js +371 -9
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/db/migrations.js +18 -18
- package/dist/db/queries.js +140 -140
- package/dist/db/schema.sql +152 -152
- package/dist/directory.js +5 -5
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/installer/instructions-template.js +9 -9
- package/dist/installer/targets/shared.d.ts +5 -6
- package/dist/installer/targets/shared.d.ts.map +1 -1
- package/dist/installer/targets/shared.js +5 -6
- package/dist/installer/targets/shared.js.map +1 -1
- package/dist/mcp/liveness-watchdog.js +16 -16
- package/dist/mcp/server-instructions.js +69 -69
- package/dist/mcp/tools.d.ts +17 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +192 -22
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reasoning/reasoner.js +32 -32
- package/dist/spec/config.d.ts +43 -0
- package/dist/spec/config.d.ts.map +1 -0
- package/dist/spec/config.js +275 -0
- package/dist/spec/config.js.map +1 -0
- package/dist/spec/db/commit-node.d.ts +23 -0
- package/dist/spec/db/commit-node.d.ts.map +1 -0
- package/dist/spec/db/commit-node.js +62 -0
- package/dist/spec/db/commit-node.js.map +1 -0
- package/dist/spec/db/fragment-node.d.ts +23 -0
- package/dist/spec/db/fragment-node.d.ts.map +1 -0
- package/dist/spec/db/fragment-node.js +120 -0
- package/dist/spec/db/fragment-node.js.map +1 -0
- package/dist/spec/db/fts.d.ts +60 -0
- package/dist/spec/db/fts.d.ts.map +1 -0
- package/dist/spec/db/fts.js +285 -0
- package/dist/spec/db/fts.js.map +1 -0
- package/dist/spec/db/index.d.ts +13 -0
- package/dist/spec/db/index.d.ts.map +1 -0
- package/dist/spec/db/index.js +50 -0
- package/dist/spec/db/index.js.map +1 -0
- package/dist/spec/db/relations.d.ts +55 -0
- package/dist/spec/db/relations.d.ts.map +1 -0
- package/dist/spec/db/relations.js +158 -0
- package/dist/spec/db/relations.js.map +1 -0
- package/dist/spec/db/schema.d.ts +30 -0
- package/dist/spec/db/schema.d.ts.map +1 -0
- package/dist/spec/db/schema.js +87 -0
- package/dist/spec/db/schema.js.map +1 -0
- package/dist/spec/db/schema.sql +107 -0
- package/dist/spec/db/spec-node.d.ts +41 -0
- package/dist/spec/db/spec-node.d.ts.map +1 -0
- package/dist/spec/db/spec-node.js +114 -0
- package/dist/spec/db/spec-node.js.map +1 -0
- package/dist/spec/evolve/impact-locator.d.ts +13 -0
- package/dist/spec/evolve/impact-locator.d.ts.map +1 -0
- package/dist/spec/evolve/impact-locator.js +25 -0
- package/dist/spec/evolve/impact-locator.js.map +1 -0
- package/dist/spec/evolve/llm-client.d.ts +50 -0
- package/dist/spec/evolve/llm-client.d.ts.map +1 -0
- package/dist/spec/evolve/llm-client.js +176 -0
- package/dist/spec/evolve/llm-client.js.map +1 -0
- package/dist/spec/evolve/logic-checker.d.ts +12 -0
- package/dist/spec/evolve/logic-checker.d.ts.map +1 -0
- package/dist/spec/evolve/logic-checker.js +48 -0
- package/dist/spec/evolve/logic-checker.js.map +1 -0
- package/dist/spec/evolve/pipeline.d.ts +40 -0
- package/dist/spec/evolve/pipeline.d.ts.map +1 -0
- package/dist/spec/evolve/pipeline.js +314 -0
- package/dist/spec/evolve/pipeline.js.map +1 -0
- package/dist/spec/evolve/spec-rewriter.d.ts +42 -0
- package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -0
- package/dist/spec/evolve/spec-rewriter.js +254 -0
- package/dist/spec/evolve/spec-rewriter.js.map +1 -0
- package/dist/spec/graph/queries.d.ts +58 -0
- package/dist/spec/graph/queries.d.ts.map +1 -0
- package/dist/spec/graph/queries.js +215 -0
- package/dist/spec/graph/queries.js.map +1 -0
- package/dist/spec/mining/diff-parser.d.ts +33 -0
- package/dist/spec/mining/diff-parser.d.ts.map +1 -0
- package/dist/spec/mining/diff-parser.js +166 -0
- package/dist/spec/mining/diff-parser.js.map +1 -0
- package/dist/spec/mining/git-scanner.d.ts +91 -0
- package/dist/spec/mining/git-scanner.d.ts.map +1 -0
- package/dist/spec/mining/git-scanner.js +266 -0
- package/dist/spec/mining/git-scanner.js.map +1 -0
- package/dist/spec/mining/pipeline.d.ts +53 -0
- package/dist/spec/mining/pipeline.d.ts.map +1 -0
- package/dist/spec/mining/pipeline.js +165 -0
- package/dist/spec/mining/pipeline.js.map +1 -0
- package/dist/spec/mining/scope-resolver.d.ts +45 -0
- package/dist/spec/mining/scope-resolver.d.ts.map +1 -0
- package/dist/spec/mining/scope-resolver.js +103 -0
- package/dist/spec/mining/scope-resolver.js.map +1 -0
- package/dist/spec/mining/spec-extractor.d.ts +69 -0
- package/dist/spec/mining/spec-extractor.d.ts.map +1 -0
- package/dist/spec/mining/spec-extractor.js +369 -0
- package/dist/spec/mining/spec-extractor.js.map +1 -0
- package/dist/spec/types.d.ts +149 -0
- package/dist/spec/types.d.ts.map +1 -0
- package/dist/spec/types.js +15 -0
- package/dist/spec/types.js.map +1 -0
- package/dist/spec/utils.d.ts +166 -0
- package/dist/spec/utils.d.ts.map +1 -0
- package/dist/spec/utils.js +461 -0
- package/dist/spec/utils.js.map +1 -0
- package/package.json +58 -57
- package/scripts/add-lang/bench.sh +60 -60
- package/scripts/add-lang/check-grammar.mjs +75 -75
- package/scripts/add-lang/dump-ast.mjs +103 -103
- package/scripts/add-lang/verify-extraction.mjs +70 -70
- package/scripts/agent-eval/ab-adoption.sh +91 -91
- package/scripts/agent-eval/ab-hook.sh +86 -86
- package/scripts/agent-eval/ab-impl.sh +78 -78
- package/scripts/agent-eval/ab-new-vs-baseline.sh +102 -102
- package/scripts/agent-eval/ab-sufficiency.sh +78 -78
- package/scripts/agent-eval/arms-F.sh +21 -21
- package/scripts/agent-eval/arms-matrix.sh +37 -37
- package/scripts/agent-eval/audit.sh +68 -68
- package/scripts/agent-eval/bench-readme.sh +28 -28
- package/scripts/agent-eval/bench-why-repo.sh +22 -22
- package/scripts/agent-eval/block-read-hook.sh +19 -19
- package/scripts/agent-eval/hook-settings.json +15 -15
- package/scripts/agent-eval/itrun.sh +120 -120
- package/scripts/agent-eval/offload-eval-3arm.sh +72 -72
- package/scripts/agent-eval/offload-eval-cost.mjs +133 -133
- package/scripts/agent-eval/offload-eval-effort.mjs +108 -108
- package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -25
- package/scripts/agent-eval/offload-eval-frontload.sh +47 -47
- package/scripts/agent-eval/offload-eval-ground-truth.json +18 -18
- package/scripts/agent-eval/offload-eval-hook.mjs +84 -84
- package/scripts/agent-eval/offload-eval-judge.mjs +103 -103
- package/scripts/agent-eval/offload-eval-matrix.sh +20 -20
- package/scripts/agent-eval/offload-eval-metrics.mjs +94 -94
- package/scripts/agent-eval/offload-eval-refs1.sh +50 -50
- package/scripts/agent-eval/offload-eval-setup.sh +24 -24
- package/scripts/agent-eval/offload-eval-styles.sh +71 -71
- package/scripts/agent-eval/offload-eval-summarize.mjs +68 -68
- package/scripts/agent-eval/offload-eval.md +76 -76
- package/scripts/agent-eval/parse-arms.mjs +116 -116
- package/scripts/agent-eval/parse-bench-readme.mjs +84 -84
- package/scripts/agent-eval/parse-run.mjs +45 -45
- package/scripts/agent-eval/parse-session.mjs +93 -93
- package/scripts/agent-eval/probe-context.mjs +21 -21
- package/scripts/agent-eval/probe-explore.mjs +40 -40
- package/scripts/agent-eval/probe-node.mjs +20 -20
- package/scripts/agent-eval/probe-sweep.mjs +119 -119
- package/scripts/agent-eval/probe-trace.mjs +20 -20
- package/scripts/agent-eval/redirect-read-hook.sh +38 -38
- package/scripts/agent-eval/run-agent.sh +34 -34
- package/scripts/agent-eval/run-all.sh +69 -69
- package/scripts/agent-eval/run-arms.sh +56 -56
- package/scripts/agent-eval/seq-matrix.mjs +137 -137
- package/scripts/build-bundle.sh +118 -118
- package/scripts/extract-release-notes.mjs +130 -130
- package/scripts/local-install.sh +41 -41
- package/scripts/npm-sdk.js +75 -75
- package/scripts/npm-shim.js +246 -246
- package/scripts/pack-npm.sh +119 -119
- package/scripts/prepare-release.mjs +270 -270
- package/scripts/qa_eval/README.md +407 -404
- package/scripts/qa_eval/_test_deveco_probe.py +41 -41
- package/scripts/qa_eval/agent_runner.py +526 -526
- package/scripts/qa_eval/data/.gitignore +4 -4
- package/scripts/qa_eval/data/test-set.jsonl +2 -22
- package/scripts/qa_eval/eval_metrics.py +274 -233
- package/scripts/qa_eval/external_agent.py +976 -671
- package/scripts/qa_eval/llm_config.py +92 -92
- package/scripts/qa_eval/memory_monitor.py +132 -132
- package/scripts/qa_eval/my_answer_accuracy.py +187 -187
- package/scripts/qa_eval/requirements.txt +2 -2
- package/scripts/qa_eval/run_pipeline.py +804 -711
- package/scripts/qa_eval/stats_efficiency.py +279 -279
- package/scripts/qa_eval/stats_scores.py +207 -207
package/dist/bin/homegraph.js
CHANGED
|
@@ -60,6 +60,7 @@ 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");
|
|
63
64
|
const directory_1 = require("../directory");
|
|
64
65
|
const worktree_1 = require("../sync/worktree");
|
|
65
66
|
const shimmer_progress_1 = require("../ui/shimmer-progress");
|
|
@@ -235,6 +236,38 @@ function main() {
|
|
|
235
236
|
// Not found - return original path (will fail later with helpful error)
|
|
236
237
|
return absolutePath;
|
|
237
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Resolve the project root for spec operations by walking up from a path
|
|
241
|
+
* looking for `${SPEC_DATA_DIR}/meta.json` or `${SPEC_DATA_DIR}/commit4spec.db`.
|
|
242
|
+
* Falls back to the starting path when neither marker is found (callers
|
|
243
|
+
* handle the not-found case).
|
|
244
|
+
*/
|
|
245
|
+
function resolveSpecProjectPath(pathArg) {
|
|
246
|
+
const startPath = path.resolve(pathArg || process.cwd());
|
|
247
|
+
// Direct hit
|
|
248
|
+
const checkDirs = [
|
|
249
|
+
`${utils_1.SPEC_DATA_DIR}/meta.json`,
|
|
250
|
+
`${utils_1.SPEC_DATA_DIR}/commit4spec.db`,
|
|
251
|
+
];
|
|
252
|
+
for (const check of checkDirs) {
|
|
253
|
+
if (fs.existsSync(path.join(startPath, check)))
|
|
254
|
+
return startPath;
|
|
255
|
+
}
|
|
256
|
+
// Walk up
|
|
257
|
+
let current = startPath;
|
|
258
|
+
const root = path.parse(current).root;
|
|
259
|
+
while (current !== root) {
|
|
260
|
+
const parent = path.dirname(current);
|
|
261
|
+
if (parent === current)
|
|
262
|
+
break;
|
|
263
|
+
current = parent;
|
|
264
|
+
for (const check of checkDirs) {
|
|
265
|
+
if (fs.existsSync(path.join(current, check)))
|
|
266
|
+
return current;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return startPath; // fallback — callers handle not-found
|
|
270
|
+
}
|
|
238
271
|
/**
|
|
239
272
|
* Format a number with commas
|
|
240
273
|
*/
|
|
@@ -1393,15 +1426,15 @@ function main() {
|
|
|
1393
1426
|
console.error(chalk.bold('\nHomeGraph MCP Server\n'));
|
|
1394
1427
|
console.error(chalk.blue((0, glyphs_1.getGlyphs)().info) + ' Use --mcp flag to start the MCP server');
|
|
1395
1428
|
console.error('\nTo use with Claude Code, add to your MCP configuration:');
|
|
1396
|
-
console.error(chalk.dim(`
|
|
1397
|
-
{
|
|
1398
|
-
"mcpServers": {
|
|
1399
|
-
"homegraph": {
|
|
1400
|
-
"command": "homegraph",
|
|
1401
|
-
"args": ["serve", "--mcp"]
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1429
|
+
console.error(chalk.dim(`
|
|
1430
|
+
{
|
|
1431
|
+
"mcpServers": {
|
|
1432
|
+
"homegraph": {
|
|
1433
|
+
"command": "homegraph",
|
|
1434
|
+
"args": ["serve", "--mcp"]
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1405
1438
|
`));
|
|
1406
1439
|
console.error('Available tools:');
|
|
1407
1440
|
console.error(chalk.cyan(' homegraph_explore') + ' - Primary: source of the relevant symbols for any question');
|
|
@@ -1817,6 +1850,335 @@ function main() {
|
|
|
1817
1850
|
process.exit(1);
|
|
1818
1851
|
}
|
|
1819
1852
|
});
|
|
1853
|
+
// =============================================================================
|
|
1854
|
+
// Spec commands (Commit4Spec knowledge graph)
|
|
1855
|
+
// =============================================================================
|
|
1856
|
+
const specCommand = program
|
|
1857
|
+
.command('spec')
|
|
1858
|
+
.description('Manage spec knowledge graph (reverse-mining, search, self-evolve)');
|
|
1859
|
+
/**
|
|
1860
|
+
* homegraph spec mine
|
|
1861
|
+
*/
|
|
1862
|
+
specCommand
|
|
1863
|
+
.command('mine')
|
|
1864
|
+
.description('Reverse-mine spec knowledge from Git history')
|
|
1865
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
1866
|
+
.option('--spec-storage-path <path>', 'Path to the .spec directory')
|
|
1867
|
+
.option('--db-path <path>', 'Path to the SQLite database file')
|
|
1868
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
1869
|
+
.option('-j, --json', 'Output as JSON')
|
|
1870
|
+
.action(async (options) => {
|
|
1871
|
+
try {
|
|
1872
|
+
const repoPath = path.resolve(options.path || process.cwd());
|
|
1873
|
+
const specStoragePath = options.specStoragePath ?? path.join(repoPath, '.spec');
|
|
1874
|
+
const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
|
|
1875
|
+
const { runMiningPipeline } = await Promise.resolve().then(() => __importStar(require('../spec/mining/pipeline')));
|
|
1876
|
+
const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
|
|
1877
|
+
const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
1878
|
+
const dbPath = resolveDbPath(repoPath, options.dbPath);
|
|
1879
|
+
if (!isGitRepo(repoPath)) {
|
|
1880
|
+
error(`Not a git repository: ${repoPath}`);
|
|
1881
|
+
process.exit(1);
|
|
1882
|
+
}
|
|
1883
|
+
if (options.verbose) {
|
|
1884
|
+
info(`Repository: ${repoPath}`);
|
|
1885
|
+
info(`Spec storage: ${specStoragePath}`);
|
|
1886
|
+
info(`Database: ${dbPath}`);
|
|
1887
|
+
}
|
|
1888
|
+
const { db } = createDatabase(dbPath);
|
|
1889
|
+
const result = runMiningPipeline(repoPath, specStoragePath, db);
|
|
1890
|
+
if (options.json) {
|
|
1891
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1892
|
+
}
|
|
1893
|
+
else {
|
|
1894
|
+
info(`Specs found: ${result.specsFound}`);
|
|
1895
|
+
info(`Commits found: ${result.commitsFound}`);
|
|
1896
|
+
info(`Fragments: ${result.fragmentsFound}`);
|
|
1897
|
+
info(`Relations: ${result.relationsCreated}`);
|
|
1898
|
+
info(`Total entries: ${result.totalEntries}`);
|
|
1899
|
+
info(`Skipped: ${result.skippedEntries.length}`);
|
|
1900
|
+
if (result.skippedEntries.length > 0) {
|
|
1901
|
+
for (const entry of result.skippedEntries) {
|
|
1902
|
+
warn(`${entry.specId}: ${entry.reason}`);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
catch (err) {
|
|
1908
|
+
error(`Mining failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1909
|
+
process.exit(1);
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
/**
|
|
1913
|
+
* homegraph spec match <text>
|
|
1914
|
+
*/
|
|
1915
|
+
specCommand
|
|
1916
|
+
.command('match <text>')
|
|
1917
|
+
.description('Search the spec knowledge graph for a term or phrase')
|
|
1918
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
1919
|
+
.option('--db-path <path>', 'Path to the SQLite database file')
|
|
1920
|
+
.option('--top-k <number>', 'Number of results to return', '5')
|
|
1921
|
+
.option('--no-fragments', 'Exclude code fragments from results')
|
|
1922
|
+
.option('-j, --json', 'Output as JSON')
|
|
1923
|
+
.action(async (text, options) => {
|
|
1924
|
+
let db;
|
|
1925
|
+
try {
|
|
1926
|
+
const repoPath = resolveSpecProjectPath(options.path);
|
|
1927
|
+
const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
|
|
1928
|
+
const { resolveDbPath, computeBudgetProfile } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
1929
|
+
const { initSpecSchema } = await Promise.resolve().then(() => __importStar(require('../spec/db/schema')));
|
|
1930
|
+
const { searchAndGetContext } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
|
|
1931
|
+
const dbPath = resolveDbPath(repoPath, options.dbPath);
|
|
1932
|
+
if (!fs.existsSync(dbPath)) {
|
|
1933
|
+
error(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`);
|
|
1934
|
+
process.exit(1);
|
|
1935
|
+
}
|
|
1936
|
+
const created = createDatabase(dbPath);
|
|
1937
|
+
db = created.db;
|
|
1938
|
+
initSpecSchema(db);
|
|
1939
|
+
const topK = Math.max(1, Math.min(parseInt(options.topK || '5', 10) || 5, 50));
|
|
1940
|
+
const includeFragments = options.fragments !== false;
|
|
1941
|
+
const contexts = searchAndGetContext(db, text, topK, includeFragments);
|
|
1942
|
+
if (contexts.length === 0) {
|
|
1943
|
+
info('No specs matched your query.');
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
const profile = computeBudgetProfile(contexts.length);
|
|
1947
|
+
const results = contexts.map((ctx) => ({
|
|
1948
|
+
spec_id: ctx.spec.id,
|
|
1949
|
+
title: ctx.spec.title,
|
|
1950
|
+
subtitles: ctx.spec.subtitles,
|
|
1951
|
+
file_path: ctx.spec.filePath,
|
|
1952
|
+
commits: ctx.commits.map((c) => ({
|
|
1953
|
+
hash: c.commit.hash,
|
|
1954
|
+
message: c.commit.message,
|
|
1955
|
+
relation_type: c.relationType,
|
|
1956
|
+
fragments: c.fragments.map((f) => ({
|
|
1957
|
+
file_path: f.filePath,
|
|
1958
|
+
change_type: f.changeType,
|
|
1959
|
+
start_line: f.startLine,
|
|
1960
|
+
end_line: f.endLine,
|
|
1961
|
+
code_diff: f.codeDiff,
|
|
1962
|
+
})),
|
|
1963
|
+
})),
|
|
1964
|
+
}));
|
|
1965
|
+
if (options.json) {
|
|
1966
|
+
console.log(JSON.stringify({
|
|
1967
|
+
query: text.slice(0, 200),
|
|
1968
|
+
matched_count: contexts.length,
|
|
1969
|
+
results,
|
|
1970
|
+
}, null, 2));
|
|
1971
|
+
}
|
|
1972
|
+
else {
|
|
1973
|
+
console.log(chalk.bold(`\n${contexts.length} spec${contexts.length !== 1 ? 's' : ''} matched:\n`));
|
|
1974
|
+
for (const r of results) {
|
|
1975
|
+
console.log(chalk.bold(r.title));
|
|
1976
|
+
for (const commit of r.commits) {
|
|
1977
|
+
console.log(` ${chalk.yellow(commit.hash.slice(0, 7))} ${commit.message}`);
|
|
1978
|
+
if (includeFragments && commit.fragments && commit.fragments.length > 0) {
|
|
1979
|
+
for (const fragment of commit.fragments.slice(0, profile.maxFragments || 3)) {
|
|
1980
|
+
console.log(chalk.dim(` ${fragment.file_path}:${fragment.start_line}-${fragment.end_line} [${fragment.change_type}]`));
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
console.log();
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
catch (err) {
|
|
1989
|
+
error(`Match failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
}
|
|
1992
|
+
finally {
|
|
1993
|
+
try {
|
|
1994
|
+
db?.close();
|
|
1995
|
+
}
|
|
1996
|
+
catch { /* best effort */ }
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
/**
|
|
2000
|
+
* homegraph spec evolve
|
|
2001
|
+
*/
|
|
2002
|
+
const evolveCommand = specCommand
|
|
2003
|
+
.command('evolve')
|
|
2004
|
+
.description('Self-evolve specs after code changes');
|
|
2005
|
+
/**
|
|
2006
|
+
* homegraph spec evolve install
|
|
2007
|
+
*/
|
|
2008
|
+
evolveCommand
|
|
2009
|
+
.command('install')
|
|
2010
|
+
.description('Install a git post-commit hook that triggers spec evolution')
|
|
2011
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
2012
|
+
.option('-f, --force', 'Overwrite existing hook without prompting')
|
|
2013
|
+
.action(async (options) => {
|
|
2014
|
+
try {
|
|
2015
|
+
const repoPath = resolveSpecProjectPath(options.path);
|
|
2016
|
+
const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
|
|
2017
|
+
if (!isGitRepo(repoPath)) {
|
|
2018
|
+
error(`Not a git repository: ${repoPath}`);
|
|
2019
|
+
process.exit(1);
|
|
2020
|
+
}
|
|
2021
|
+
const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
2022
|
+
// Resolve the absolute path to homegraph so the hook works
|
|
2023
|
+
// regardless of PATH (matching Python's shutil.which("c4s")).
|
|
2024
|
+
let homegraphBin;
|
|
2025
|
+
try {
|
|
2026
|
+
homegraphBin = execFileSync('command', ['-v', 'homegraph'], {
|
|
2027
|
+
encoding: 'utf8',
|
|
2028
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
2029
|
+
}).trim();
|
|
2030
|
+
}
|
|
2031
|
+
catch {
|
|
2032
|
+
homegraphBin = '';
|
|
2033
|
+
}
|
|
2034
|
+
if (!homegraphBin) {
|
|
2035
|
+
error('homegraph command not found in PATH.\n' +
|
|
2036
|
+
' Make sure homegraph is installed globally: npm install -g homegraph');
|
|
2037
|
+
process.exit(1);
|
|
2038
|
+
}
|
|
2039
|
+
const hooksDir = execFileSync('git', ['rev-parse', '--git-path', 'hooks'], {
|
|
2040
|
+
cwd: repoPath,
|
|
2041
|
+
encoding: 'utf8',
|
|
2042
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
2043
|
+
windowsHide: true,
|
|
2044
|
+
}).trim();
|
|
2045
|
+
const resolvedHooksDir = path.isAbsolute(hooksDir)
|
|
2046
|
+
? hooksDir
|
|
2047
|
+
: path.resolve(repoPath, hooksDir);
|
|
2048
|
+
fs.mkdirSync(resolvedHooksDir, { recursive: true });
|
|
2049
|
+
const hookPath = path.join(resolvedHooksDir, 'post-commit');
|
|
2050
|
+
if (fs.existsSync(hookPath) && !options.force) {
|
|
2051
|
+
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
2052
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2053
|
+
const answer = await new Promise((resolve) => {
|
|
2054
|
+
rl.question(chalk.yellow('A post-commit hook already exists. Overwrite? (y/N) '), resolve);
|
|
2055
|
+
});
|
|
2056
|
+
rl.close();
|
|
2057
|
+
if (answer.toLowerCase() !== 'y') {
|
|
2058
|
+
info('Cancelled');
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
// Ensure logs directory
|
|
2063
|
+
fs.mkdirSync(path.join(repoPath, utils_1.SPEC_DATA_DIR, 'logs'), { recursive: true });
|
|
2064
|
+
// Marker constants matching the git-hooks pattern
|
|
2065
|
+
const MARKER_BEGIN = '# >>> homegraph spec evolve hook >>>';
|
|
2066
|
+
const MARKER_END = '# <<< homegraph spec evolve hook <<<';
|
|
2067
|
+
const hookBlock = [
|
|
2068
|
+
MARKER_BEGIN,
|
|
2069
|
+
'# Triggers spec self-evolution after each commit. Runs in background.',
|
|
2070
|
+
'# Installed by: homegraph spec evolve install',
|
|
2071
|
+
`# Logs: ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log`,
|
|
2072
|
+
`"${homegraphBin}" spec evolve process --path "$(pwd)" --json \\`,
|
|
2073
|
+
` >> ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log 2>&1 &`,
|
|
2074
|
+
MARKER_END,
|
|
2075
|
+
].join('\n');
|
|
2076
|
+
let content;
|
|
2077
|
+
if (fs.existsSync(hookPath)) {
|
|
2078
|
+
// Strip any prior marker block, then re-append the current one
|
|
2079
|
+
const existing = fs.readFileSync(hookPath, 'utf8');
|
|
2080
|
+
const lines = existing.split('\n');
|
|
2081
|
+
const kept = [];
|
|
2082
|
+
let inBlock = false;
|
|
2083
|
+
for (const line of lines) {
|
|
2084
|
+
const trimmed = line.trim();
|
|
2085
|
+
if (trimmed === MARKER_BEGIN) {
|
|
2086
|
+
inBlock = true;
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
if (trimmed === MARKER_END) {
|
|
2090
|
+
inBlock = false;
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
if (!inBlock)
|
|
2094
|
+
kept.push(line);
|
|
2095
|
+
}
|
|
2096
|
+
const base = kept.join('\n').replace(/\s*$/, '');
|
|
2097
|
+
content = base.length > 0
|
|
2098
|
+
? `${base}\n\n${hookBlock}\n`
|
|
2099
|
+
: `#!/bin/sh\n${hookBlock}\n`;
|
|
2100
|
+
}
|
|
2101
|
+
else {
|
|
2102
|
+
content = `#!/bin/sh\n${hookBlock}\n`;
|
|
2103
|
+
}
|
|
2104
|
+
fs.writeFileSync(hookPath, content);
|
|
2105
|
+
fs.chmodSync(hookPath, 0o755);
|
|
2106
|
+
success(`Post-commit hook installed at ${hookPath}`);
|
|
2107
|
+
info(`Logs written to ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log`);
|
|
2108
|
+
}
|
|
2109
|
+
catch (err) {
|
|
2110
|
+
error(`Hook install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2111
|
+
process.exit(1);
|
|
2112
|
+
}
|
|
2113
|
+
});
|
|
2114
|
+
/**
|
|
2115
|
+
* homegraph spec evolve process
|
|
2116
|
+
*/
|
|
2117
|
+
evolveCommand
|
|
2118
|
+
.command('process')
|
|
2119
|
+
.description('Process a commit through the spec self-evolve pipeline')
|
|
2120
|
+
.option('-p, --path <path>', 'Path to the repository')
|
|
2121
|
+
.option('--db-path <path>', 'Path to the SQLite database file')
|
|
2122
|
+
.option('--commit-hash <hash>', 'Commit hash to process (default: HEAD)', 'HEAD')
|
|
2123
|
+
.option('-j, --json', 'Output as JSON')
|
|
2124
|
+
.action(async (options) => {
|
|
2125
|
+
let db;
|
|
2126
|
+
try {
|
|
2127
|
+
const repoPath = resolveSpecProjectPath(options.path);
|
|
2128
|
+
const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
|
|
2129
|
+
const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
|
|
2130
|
+
const { initSpecSchema } = await Promise.resolve().then(() => __importStar(require('../spec/db/schema')));
|
|
2131
|
+
const { loadSpecConfig } = await Promise.resolve().then(() => __importStar(require('../spec/config')));
|
|
2132
|
+
const { runEvolvePipeline } = await Promise.resolve().then(() => __importStar(require('../spec/evolve/pipeline')));
|
|
2133
|
+
const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
|
|
2134
|
+
if (!isGitRepo(repoPath)) {
|
|
2135
|
+
error(`Not a git repository: ${repoPath}`);
|
|
2136
|
+
process.exit(1);
|
|
2137
|
+
}
|
|
2138
|
+
const dbPath = resolveDbPath(repoPath, options.dbPath);
|
|
2139
|
+
const created = createDatabase(dbPath);
|
|
2140
|
+
db = created.db;
|
|
2141
|
+
initSpecSchema(db);
|
|
2142
|
+
const config = loadSpecConfig(repoPath);
|
|
2143
|
+
const llmConfig = config.llm;
|
|
2144
|
+
const commitHash = options.commitHash || 'HEAD';
|
|
2145
|
+
const result = await runEvolvePipeline(repoPath, db, commitHash, llmConfig);
|
|
2146
|
+
if (options.json) {
|
|
2147
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2148
|
+
}
|
|
2149
|
+
else {
|
|
2150
|
+
console.log(chalk.bold(`Processing commit ${result.commitHash.slice(0, 7)}`));
|
|
2151
|
+
console.log(`Is logic change: ${result.isLogicChange ? chalk.green('✅ yes') : chalk.red('❌ no')}`);
|
|
2152
|
+
console.log(`Reason: ${result.logicCheckReason}`);
|
|
2153
|
+
if (result.generateSpecId) {
|
|
2154
|
+
console.log(chalk.cyan(`Link Path A - GENERATE ${result.generateSpecId}`));
|
|
2155
|
+
}
|
|
2156
|
+
if (result.evolvedSpecs.length > 0) {
|
|
2157
|
+
console.log(chalk.cyan(`Cycle Path B - Self-evolve (${result.affectedSpecCount} affected)`));
|
|
2158
|
+
for (const evolved of result.evolvedSpecs) {
|
|
2159
|
+
console.log(` ${evolved.specId}: ${evolved.action}`);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
console.log(`Summary: fragments=${result.fragmentsCount}, relations=${result.relationsCreated}`);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
catch (err) {
|
|
2166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2167
|
+
if (message.includes('No meta.json found')) {
|
|
2168
|
+
error("No meta.json found. Run 'homegraph spec mine' first.");
|
|
2169
|
+
}
|
|
2170
|
+
else {
|
|
2171
|
+
error(`Evolve failed: ${message}`);
|
|
2172
|
+
}
|
|
2173
|
+
process.exit(1);
|
|
2174
|
+
}
|
|
2175
|
+
finally {
|
|
2176
|
+
try {
|
|
2177
|
+
db?.close();
|
|
2178
|
+
}
|
|
2179
|
+
catch { /* best effort */ }
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
1820
2182
|
/**
|
|
1821
2183
|
* homegraph install
|
|
1822
2184
|
*/
|