@hiveai/cli 0.14.0 → 0.15.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/dist/index.js +413 -17
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command63 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -199,7 +199,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
199
199
|
if (!f) continue;
|
|
200
200
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
201
201
|
}
|
|
202
|
-
let entries = [...counts.entries()].map(([
|
|
202
|
+
let entries = [...counts.entries()].map(([path58, changes]) => ({ path: path58, changes }));
|
|
203
203
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
204
204
|
if (lowerPaths.length > 0) {
|
|
205
205
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -3019,7 +3019,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3019
3019
|
}
|
|
3020
3020
|
|
|
3021
3021
|
// src/commands/init.ts
|
|
3022
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3022
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.15.0"}`;
|
|
3023
3023
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3024
3024
|
|
|
3025
3025
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3202,7 +3202,11 @@ jobs:
|
|
|
3202
3202
|
run: npm install -g @hiveai/cli
|
|
3203
3203
|
|
|
3204
3204
|
- name: harness quality regression gate
|
|
3205
|
-
run: haive eval --regression-gate
|
|
3205
|
+
run: haive eval --regression-gate --record --ref "\${{ github.sha }}"
|
|
3206
|
+
|
|
3207
|
+
- name: harness quality trend
|
|
3208
|
+
if: always()
|
|
3209
|
+
run: haive eval --trend || true
|
|
3206
3210
|
|
|
3207
3211
|
# On push to main: push shared memories to the hub (if hubPath is configured)
|
|
3208
3212
|
# Uncomment and configure hubPath in .ai/haive.config.json to enable.
|
|
@@ -7958,7 +7962,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7958
7962
|
};
|
|
7959
7963
|
}
|
|
7960
7964
|
var SERVER_NAME = "haive";
|
|
7961
|
-
var SERVER_VERSION = "0.
|
|
7965
|
+
var SERVER_VERSION = "0.15.0";
|
|
7962
7966
|
function jsonResult(data) {
|
|
7963
7967
|
return {
|
|
7964
7968
|
content: [
|
|
@@ -12709,9 +12713,12 @@ import "commander";
|
|
|
12709
12713
|
import {
|
|
12710
12714
|
aggregateRetrieval,
|
|
12711
12715
|
aggregateSensors,
|
|
12716
|
+
appendEvalHistory,
|
|
12712
12717
|
buildReport,
|
|
12713
12718
|
compareEvalReports,
|
|
12719
|
+
computeEvalTrend,
|
|
12714
12720
|
findProjectRoot as findProjectRoot42,
|
|
12721
|
+
loadEvalHistory,
|
|
12715
12722
|
resolveHaivePaths as resolveHaivePaths38,
|
|
12716
12723
|
scoreRetrievalCase,
|
|
12717
12724
|
scoreSensorCase,
|
|
@@ -12720,7 +12727,7 @@ import {
|
|
|
12720
12727
|
function registerEval(program2) {
|
|
12721
12728
|
program2.command("eval").description(
|
|
12722
12729
|
"Rigorous, repeatable quality eval: do the right memories surface (retrieval) and do the right sensors fire (catch-rate)? Emits a numeric 0\u2013100 score. Uses .ai/eval cases via --spec, or auto-synthesizes cases from anchored memories."
|
|
12723
|
-
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12730
|
+
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("--record", "append this run's score to .ai/.cache/eval-history.jsonl (trend the harness over time)", false).option("--trend", "print the recorded score trend (sparkline + latest/best/delta) and exit", false).option("--ref <ref>", "version/commit label stored with a --record run").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12724
12731
|
const root = findProjectRoot42(opts.dir);
|
|
12725
12732
|
const paths = resolveHaivePaths38(root);
|
|
12726
12733
|
if (!existsSync65(paths.memoriesDir)) {
|
|
@@ -12728,6 +12735,22 @@ function registerEval(program2) {
|
|
|
12728
12735
|
process.exitCode = 1;
|
|
12729
12736
|
return;
|
|
12730
12737
|
}
|
|
12738
|
+
if (opts.trend) {
|
|
12739
|
+
const trend = computeEvalTrend(await loadEvalHistory(paths));
|
|
12740
|
+
if (opts.json) {
|
|
12741
|
+
console.log(JSON.stringify(trend, null, 2));
|
|
12742
|
+
return;
|
|
12743
|
+
}
|
|
12744
|
+
if (trend.runs === 0) {
|
|
12745
|
+
ui.info("No eval history yet. Run `haive eval --record` to start trending the harness.");
|
|
12746
|
+
return;
|
|
12747
|
+
}
|
|
12748
|
+
const spark = trend.recent.map((s) => "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588"[Math.min(7, Math.round(s / 100 * 7))]).join("");
|
|
12749
|
+
const arrow = trend.regressed ? ui.red("\u25BC") : (trend.delta ?? 0) > 0 ? ui.green("\u25B2") : ui.dim("=");
|
|
12750
|
+
console.log(ui.bold("hAIve eval trend"));
|
|
12751
|
+
console.log(` ${spark} latest ${arrow} ${trend.latest}/100 ${ui.dim(`(best ${trend.best}, ${trend.runs} run${trend.runs === 1 ? "" : "s"})`)}`);
|
|
12752
|
+
return;
|
|
12753
|
+
}
|
|
12731
12754
|
const k = Math.max(1, parseInt(opts.top ?? "8", 10) || 8);
|
|
12732
12755
|
const ctx = { paths };
|
|
12733
12756
|
const resolvedSpec = await resolveSpec(opts, root, paths.memoriesDir);
|
|
@@ -12755,6 +12778,17 @@ function registerEval(program2) {
|
|
|
12755
12778
|
sensorAgg = aggregateSensors(results);
|
|
12756
12779
|
}
|
|
12757
12780
|
const report = buildReport(retrievalAgg, sensorAgg);
|
|
12781
|
+
if (opts.record) {
|
|
12782
|
+
await appendEvalHistory(paths, {
|
|
12783
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12784
|
+
score: report.score,
|
|
12785
|
+
...report.retrieval ? { mean_recall: report.retrieval.mean_recall, mrr: report.retrieval.mrr } : {},
|
|
12786
|
+
...report.sensors ? { catch_rate: report.sensors.catch_rate } : {},
|
|
12787
|
+
...opts.ref ? { ref: opts.ref } : {}
|
|
12788
|
+
}).catch(() => {
|
|
12789
|
+
});
|
|
12790
|
+
if (!opts.json) ui.success(`Recorded eval score ${report.score}/100 to history.`);
|
|
12791
|
+
}
|
|
12758
12792
|
const baselineFile = opts.baselineFile ? path43.isAbsolute(opts.baselineFile) ? opts.baselineFile : path43.join(root, opts.baselineFile) : path43.join(root, ".ai", "eval", "baseline.json");
|
|
12759
12793
|
if (opts.baseline) {
|
|
12760
12794
|
const snapshot = {
|
|
@@ -13529,7 +13563,7 @@ function registerDoctor(program2) {
|
|
|
13529
13563
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
13530
13564
|
});
|
|
13531
13565
|
}
|
|
13532
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
13566
|
+
findings.push(...await collectInstallFindings(root, "0.15.0"));
|
|
13533
13567
|
findings.push(...await collectToolchainFindings(root));
|
|
13534
13568
|
try {
|
|
13535
13569
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -13537,7 +13571,7 @@ function registerDoctor(program2) {
|
|
|
13537
13571
|
timeout: 3e3,
|
|
13538
13572
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13539
13573
|
}).trim();
|
|
13540
|
-
const cliVersion = "0.
|
|
13574
|
+
const cliVersion = "0.15.0";
|
|
13541
13575
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
13542
13576
|
findings.push({
|
|
13543
13577
|
severity: "warn",
|
|
@@ -14599,6 +14633,7 @@ import "commander";
|
|
|
14599
14633
|
import {
|
|
14600
14634
|
antiPatternGateParams as antiPatternGateParams2,
|
|
14601
14635
|
findProjectRoot as findProjectRoot52,
|
|
14636
|
+
findUncapturedFailures,
|
|
14602
14637
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
14603
14638
|
isFreshIsoDate,
|
|
14604
14639
|
loadConfig as loadConfig13,
|
|
@@ -14860,6 +14895,7 @@ async function buildFinishReport(dir) {
|
|
|
14860
14895
|
}]
|
|
14861
14896
|
});
|
|
14862
14897
|
}
|
|
14898
|
+
findings.push(...await checkFailureCapture(paths, config));
|
|
14863
14899
|
const status = await getGitSyncStatus(root);
|
|
14864
14900
|
if (!status.available) {
|
|
14865
14901
|
findings.push({
|
|
@@ -15023,6 +15059,47 @@ async function buildFinishReport(dir) {
|
|
|
15023
15059
|
findings.push(...await verifyGithubActionsForHead(root, status));
|
|
15024
15060
|
return finishReport(root, initialized, mode, findings, config);
|
|
15025
15061
|
}
|
|
15062
|
+
async function checkFailureCapture(paths, config) {
|
|
15063
|
+
const gate = config.enforcement?.failureCaptureGate ?? "warn";
|
|
15064
|
+
if (gate === "off") return [];
|
|
15065
|
+
const obsFile = path51.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
15066
|
+
if (!existsSync75(obsFile)) return [];
|
|
15067
|
+
const failures = [];
|
|
15068
|
+
try {
|
|
15069
|
+
const raw = await readFile23(obsFile, "utf8");
|
|
15070
|
+
for (const line of raw.split("\n")) {
|
|
15071
|
+
const trimmed = line.trim();
|
|
15072
|
+
if (!trimmed) continue;
|
|
15073
|
+
try {
|
|
15074
|
+
const o = JSON.parse(trimmed);
|
|
15075
|
+
if (o.failure_hint && o.ts) failures.push({ ts: o.ts, tool: o.tool ?? "?", summary: o.summary ?? "" });
|
|
15076
|
+
} catch {
|
|
15077
|
+
}
|
|
15078
|
+
}
|
|
15079
|
+
} catch {
|
|
15080
|
+
return [];
|
|
15081
|
+
}
|
|
15082
|
+
if (failures.length === 0) return [];
|
|
15083
|
+
const memories = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
|
|
15084
|
+
const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
|
|
15085
|
+
const uncaptured = findUncapturedFailures(failures, captureTimes);
|
|
15086
|
+
if (uncaptured.length === 0) {
|
|
15087
|
+
return [{
|
|
15088
|
+
severity: "ok",
|
|
15089
|
+
code: "failure-capture-clean",
|
|
15090
|
+
message: "No uncaptured hard failures from this session."
|
|
15091
|
+
}];
|
|
15092
|
+
}
|
|
15093
|
+
return [{
|
|
15094
|
+
severity: gate === "block" ? "error" : "info",
|
|
15095
|
+
code: "uncaptured-failures",
|
|
15096
|
+
message: `${uncaptured.length} hard failure(s) this session were never captured as a lesson (mem_tried).`,
|
|
15097
|
+
fix: "Call `mem_tried` (or `haive memory tried`) for each real failure so the next session doesn't repeat it. False positives (e.g. a grep that found nothing) can be ignored.",
|
|
15098
|
+
reason: "Harness ratchet: a mistake that isn't written down gets re-introduced. Set enforcement.failureCaptureGate to 'off' to disable, or 'block' to hard-fail.",
|
|
15099
|
+
affected_files: uncaptured.slice(0, 8).map((f) => `${f.tool}: ${f.summary}`.slice(0, 100)),
|
|
15100
|
+
...gate === "block" ? { impact: 30 } : {}
|
|
15101
|
+
}];
|
|
15102
|
+
}
|
|
15026
15103
|
function finishReport(root, initialized, mode, findings, config) {
|
|
15027
15104
|
const score = buildScore(findings, config.enforcement?.scoreThreshold);
|
|
15028
15105
|
const hasErrors = findings.some((f) => f.severity === "error");
|
|
@@ -15167,7 +15244,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
15167
15244
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
15168
15245
|
});
|
|
15169
15246
|
}
|
|
15170
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
15247
|
+
findings.push(...await inspectIntegrationVersions(root, "0.15.0"));
|
|
15171
15248
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
15172
15249
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
15173
15250
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -16163,12 +16240,14 @@ import {
|
|
|
16163
16240
|
appendPreventionEvent as appendPreventionEvent2,
|
|
16164
16241
|
findProjectRoot as findProjectRoot53,
|
|
16165
16242
|
isRetiredMemory as isRetiredMemory3,
|
|
16243
|
+
loadConfig as loadConfig14,
|
|
16166
16244
|
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
16167
16245
|
loadUsageIndex as loadUsageIndex29,
|
|
16168
16246
|
recordPrevention as recordPrevention2,
|
|
16169
16247
|
resolveHaivePaths as resolveHaivePaths49,
|
|
16170
16248
|
runSensors as runSensors2,
|
|
16171
16249
|
saveUsageIndex as saveUsageIndex8,
|
|
16250
|
+
selectCommandSensors,
|
|
16172
16251
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
16173
16252
|
serializeMemory as serializeMemory27
|
|
16174
16253
|
} from "@hiveai/core";
|
|
@@ -16198,14 +16277,36 @@ function registerSensors(program2) {
|
|
|
16198
16277
|
});
|
|
16199
16278
|
sensors.command("check").description(
|
|
16200
16279
|
"Run regex sensors against a diff (the deterministic/computational layer); defaults to `git diff --cached`.\n Diff-scan layers: `sensors check` (regex) and `anti_patterns_check` (memory match) are components;\n `pre_commit_check` combines them; `haive enforce check` is THE gate that runs at commit."
|
|
16201
|
-
).option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
16280
|
+
).option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("--commands", "ALSO execute shell/test sensors (runs repo-authored commands)", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
16202
16281
|
const root = findProjectRoot53(opts.dir);
|
|
16203
16282
|
const paths = resolveHaivePaths49(root);
|
|
16204
16283
|
const memories = await runnableSensorMemories(paths);
|
|
16205
16284
|
const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
16206
16285
|
const targets = sensorTargetsFromDiff2(diff);
|
|
16207
16286
|
const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
|
|
16208
|
-
const
|
|
16287
|
+
const config = await loadConfig14(paths);
|
|
16288
|
+
const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
|
|
16289
|
+
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
16290
|
+
const allSensorMemories = await runnableSensorMemories(paths, false);
|
|
16291
|
+
const commandSpecs = selectCommandSensors(allSensorMemories, changedPaths);
|
|
16292
|
+
const commandHits = [];
|
|
16293
|
+
const commandSkipped = [];
|
|
16294
|
+
if (commandSpecs.length > 0 && runCommands) {
|
|
16295
|
+
for (const spec of commandSpecs) {
|
|
16296
|
+
const failed = await runCommandSensor(spec, root);
|
|
16297
|
+
if (failed) {
|
|
16298
|
+
commandHits.push({
|
|
16299
|
+
memory_id: spec.memory_id,
|
|
16300
|
+
severity: spec.severity,
|
|
16301
|
+
message: spec.message,
|
|
16302
|
+
matched_line: `command failed: ${spec.command}`
|
|
16303
|
+
});
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
} else if (commandSpecs.length > 0) {
|
|
16307
|
+
for (const spec of commandSpecs) commandSkipped.push(spec.memory_id);
|
|
16308
|
+
}
|
|
16309
|
+
const firedIds = [...new Set([...hits, ...commandHits].map((hit) => hit.memory_id))];
|
|
16209
16310
|
if (firedIds.length > 0) {
|
|
16210
16311
|
const usage = await loadUsageIndex29(paths);
|
|
16211
16312
|
const recordedIds = [];
|
|
@@ -16228,12 +16329,15 @@ function registerSensors(program2) {
|
|
|
16228
16329
|
severity: hit.severity,
|
|
16229
16330
|
message: hit.message,
|
|
16230
16331
|
matched_line: hit.matched_line
|
|
16231
|
-
}))
|
|
16332
|
+
})),
|
|
16333
|
+
command_hits: commandHits,
|
|
16334
|
+
command_skipped: commandSkipped
|
|
16232
16335
|
};
|
|
16233
16336
|
if (opts.json) {
|
|
16234
16337
|
console.log(JSON.stringify(output, null, 2));
|
|
16235
16338
|
} else {
|
|
16236
|
-
|
|
16339
|
+
const total = hits.length + commandHits.length;
|
|
16340
|
+
console.log(ui.bold(`hAIve sensors check \u2014 ${total} hit(s), ${memories.length} regex + ${commandSpecs.length} command sensor(s)`));
|
|
16237
16341
|
for (const hit of hits) {
|
|
16238
16342
|
const marker = hit.severity === "block" ? ui.red("\u2717") : ui.yellow("\u26A0");
|
|
16239
16343
|
console.log(` ${marker} ${hit.memory_id} ${ui.dim(`(${hit.severity})`)}`);
|
|
@@ -16241,8 +16345,17 @@ function registerSensors(program2) {
|
|
|
16241
16345
|
console.log(` ${hit.message}`);
|
|
16242
16346
|
if (hit.matched_line) console.log(` ${ui.dim(hit.matched_line)}`);
|
|
16243
16347
|
}
|
|
16348
|
+
for (const hit of commandHits) {
|
|
16349
|
+
const marker = hit.severity === "block" ? ui.red("\u2717") : ui.yellow("\u26A0");
|
|
16350
|
+
console.log(` ${marker} ${hit.memory_id} ${ui.dim(`(${hit.severity}, command)`)}`);
|
|
16351
|
+
console.log(` ${hit.message}`);
|
|
16352
|
+
console.log(` ${ui.dim(hit.matched_line)}`);
|
|
16353
|
+
}
|
|
16354
|
+
if (commandSkipped.length > 0) {
|
|
16355
|
+
console.log(ui.dim(` ${commandSkipped.length} command sensor(s) not run \u2014 pass --commands or set enforcement.runCommandSensors.`));
|
|
16356
|
+
}
|
|
16244
16357
|
}
|
|
16245
|
-
if (hits.some((hit) => hit.severity === "block")) process.exitCode = 1;
|
|
16358
|
+
if ([...hits, ...commandHits].some((hit) => hit.severity === "block")) process.exitCode = 1;
|
|
16246
16359
|
});
|
|
16247
16360
|
sensors.command("promote").description("Promote or demote an existing memory sensor severity").argument("<memory-id>", "memory id carrying the sensor").option("--severity <severity>", "block | warn", "block").option("--yes", "confirm promotion to block severity", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
16248
16361
|
const severity = opts.severity ?? "block";
|
|
@@ -16329,6 +16442,14 @@ async function runnableSensorMemories(paths, regexOnly = true) {
|
|
|
16329
16442
|
return !isRetiredMemory3(memory2.frontmatter, memory2.body);
|
|
16330
16443
|
});
|
|
16331
16444
|
}
|
|
16445
|
+
async function runCommandSensor(spec, root) {
|
|
16446
|
+
try {
|
|
16447
|
+
await exec2("bash", ["-c", spec.command], { cwd: root, timeout: 12e4, maxBuffer: 8 * 1024 * 1024 });
|
|
16448
|
+
return false;
|
|
16449
|
+
} catch {
|
|
16450
|
+
return true;
|
|
16451
|
+
}
|
|
16452
|
+
}
|
|
16332
16453
|
async function stagedDiff(root) {
|
|
16333
16454
|
try {
|
|
16334
16455
|
const { stdout } = await exec2("git", ["diff", "--cached"], { cwd: root });
|
|
@@ -16562,6 +16683,7 @@ import "commander";
|
|
|
16562
16683
|
import {
|
|
16563
16684
|
buildDashboard,
|
|
16564
16685
|
findProjectRoot as findProjectRoot55,
|
|
16686
|
+
loadConfig as loadConfig15,
|
|
16565
16687
|
loadMemoriesFromDir as loadMemoriesFromDir41,
|
|
16566
16688
|
loadPreventionEvents,
|
|
16567
16689
|
loadUsageIndex as loadUsageIndex30,
|
|
@@ -16581,11 +16703,13 @@ function registerDashboard(program2) {
|
|
|
16581
16703
|
const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
|
|
16582
16704
|
const usage = await loadUsageIndex30(paths);
|
|
16583
16705
|
const preventionEvents = await loadPreventionEvents(paths);
|
|
16706
|
+
const config = await loadConfig15(paths);
|
|
16584
16707
|
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
16585
16708
|
const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
|
|
16586
16709
|
const report = buildDashboard(memories, usage, {
|
|
16587
16710
|
top,
|
|
16588
16711
|
preventionEvents,
|
|
16712
|
+
antiPatternGate: config.enforcement?.antiPatternGate ?? "anchored",
|
|
16589
16713
|
...dormantDays !== void 0 && Number.isFinite(dormantDays) ? { dormantDays } : {}
|
|
16590
16714
|
});
|
|
16591
16715
|
if (opts.json) {
|
|
@@ -16596,7 +16720,7 @@ function registerDashboard(program2) {
|
|
|
16596
16720
|
});
|
|
16597
16721
|
}
|
|
16598
16722
|
function renderDashboard(r) {
|
|
16599
|
-
const { inventory: inv, impact, sensors, health, decay, corpus, prevention } = r;
|
|
16723
|
+
const { inventory: inv, impact, sensors, health, decay, corpus, prevention, gate_precision: gate } = r;
|
|
16600
16724
|
console.log(ui.bold("hAIve dashboard"));
|
|
16601
16725
|
console.log(
|
|
16602
16726
|
` ${ui.dim("corpus:")} ${inv.total} policy memor${inv.total === 1 ? "y" : "ies"} (${inv.active} active, ${inv.retired} retired) \xB7 ${inv.session_recaps} recap(s) \xB7 ~${corpus.est_tokens.toLocaleString()} tokens`
|
|
@@ -16647,6 +16771,15 @@ function renderDashboard(r) {
|
|
|
16647
16771
|
}
|
|
16648
16772
|
}
|
|
16649
16773
|
console.log();
|
|
16774
|
+
console.log(ui.bold("Gate precision") + ui.dim(" (is the anti-pattern gate real or noisy?)"));
|
|
16775
|
+
const precisionLabel = gate.precision === null ? ui.dim("no signal yet") : gate.precision >= 0.7 ? ui.green(`${Math.round(gate.precision * 100)}%`) : ui.yellow(`${Math.round(gate.precision * 100)}%`);
|
|
16776
|
+
console.log(
|
|
16777
|
+
` ${precisionLabel} precision \xB7 ${gate.useful} useful (sensor ${gate.sensor_catches} \xB7 anti-pattern ${gate.anti_pattern_catches}) \xB7 ${gate.rejections > 0 ? ui.yellow(`${gate.rejections} rejected`) : "0 rejected"}`
|
|
16778
|
+
);
|
|
16779
|
+
if (gate.suggestion) {
|
|
16780
|
+
ui.info(`Tuning: set enforcement.antiPatternGate="${gate.suggestion.recommended}" \u2014 ${gate.suggestion.reason}`);
|
|
16781
|
+
}
|
|
16782
|
+
console.log();
|
|
16650
16783
|
console.log(ui.bold("Health"));
|
|
16651
16784
|
console.log(
|
|
16652
16785
|
` stale ${warnNum(health.stale)} \xB7 anchorless ${warnNum(health.anchorless)} \xB7 pending ${health.pending} \xB7 prune candidates ${warnNum(health.prune_candidates)}`
|
|
@@ -16744,9 +16877,268 @@ function registerDevLink(program2) {
|
|
|
16744
16877
|
});
|
|
16745
16878
|
}
|
|
16746
16879
|
|
|
16880
|
+
// src/commands/coverage.ts
|
|
16881
|
+
import "commander";
|
|
16882
|
+
import { findCoverageGaps, findProjectRoot as findProjectRoot57, resolveHaivePaths as resolveHaivePaths52 } from "@hiveai/core";
|
|
16883
|
+
function isNoisePath(p) {
|
|
16884
|
+
if (/(^|\/)(node_modules|dist|build|coverage|\.next)\//.test(p)) return true;
|
|
16885
|
+
if (p.startsWith(".ai/")) return true;
|
|
16886
|
+
if (/\.(jsonl|lock|map|snap|min\.js)$/.test(p)) return true;
|
|
16887
|
+
if (/(^|\/)(pnpm-lock\.yaml|package-lock\.json|yarn\.lock)$/.test(p)) return true;
|
|
16888
|
+
if (/(^|\/)(CHANGELOG|LICENSE)(\.md)?$/.test(p)) return true;
|
|
16889
|
+
return false;
|
|
16890
|
+
}
|
|
16891
|
+
function registerCoverage(program2) {
|
|
16892
|
+
program2.command("coverage").description(
|
|
16893
|
+
"Coverage-gap report: frequently-edited files with no covering team memory (blind spots)."
|
|
16894
|
+
).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum git-churn count to flag a file", "3").option("--limit <n>", "max gaps to report", "20").option("--days <n>", "git-history lookback window in days", "90").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
16895
|
+
const root = findProjectRoot57(opts.dir);
|
|
16896
|
+
const paths = resolveHaivePaths52(root);
|
|
16897
|
+
const minChanges = Math.max(1, parseInt(opts.minChanges ?? "3", 10) || 3);
|
|
16898
|
+
const limit = Math.max(1, parseInt(opts.limit ?? "20", 10) || 20);
|
|
16899
|
+
const days = Math.max(1, parseInt(opts.days ?? "90", 10) || 90);
|
|
16900
|
+
const radar = await buildRadar({
|
|
16901
|
+
root,
|
|
16902
|
+
taskTokens: null,
|
|
16903
|
+
filePaths: [],
|
|
16904
|
+
daysBack: Math.ceil(days / 6),
|
|
16905
|
+
// getHotFiles multiplies daysBack by 6
|
|
16906
|
+
maxHotFiles: 500
|
|
16907
|
+
});
|
|
16908
|
+
const hotFiles = radar.hotFiles.filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes }));
|
|
16909
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
16910
|
+
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
16911
|
+
if (opts.json) {
|
|
16912
|
+
console.log(JSON.stringify({ root, scanned_hot_files: hotFiles.length, gaps }, null, 2));
|
|
16913
|
+
return;
|
|
16914
|
+
}
|
|
16915
|
+
if (!radar.insideGitRepo) {
|
|
16916
|
+
ui.warn("Not a git repository \u2014 coverage uses git churn to find hot files.");
|
|
16917
|
+
return;
|
|
16918
|
+
}
|
|
16919
|
+
if (gaps.length === 0) {
|
|
16920
|
+
ui.success(`No coverage gaps: every file changed \u2265${minChanges}\xD7 is covered by a team memory.`);
|
|
16921
|
+
return;
|
|
16922
|
+
}
|
|
16923
|
+
console.log(ui.bold(`hAIve coverage \u2014 ${gaps.length} blind spot(s) (hot files with no covering memory)`));
|
|
16924
|
+
for (const gap of gaps) {
|
|
16925
|
+
console.log(` ${ui.yellow("\u25CB")} ${gap.path} ${ui.dim(`(${gap.changes} change${gap.changes === 1 ? "" : "s"})`)}`);
|
|
16926
|
+
}
|
|
16927
|
+
console.log(
|
|
16928
|
+
ui.dim(
|
|
16929
|
+
"\nAdd a decision/convention/gotcha anchored to the top files, or a sensor, to close the gap."
|
|
16930
|
+
)
|
|
16931
|
+
);
|
|
16932
|
+
});
|
|
16933
|
+
}
|
|
16934
|
+
|
|
16935
|
+
// src/commands/merge-driver.ts
|
|
16936
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
16937
|
+
import { readFileSync, writeFileSync, existsSync as existsSync80 } from "fs";
|
|
16938
|
+
import path56 from "path";
|
|
16939
|
+
import "commander";
|
|
16940
|
+
import { findProjectRoot as findProjectRoot58, mergeMemoryVersions } from "@hiveai/core";
|
|
16941
|
+
var GITATTRIBUTES_MARK = "# hAIve merge driver";
|
|
16942
|
+
var GITATTRIBUTES_BLOCK = [
|
|
16943
|
+
GITATTRIBUTES_MARK,
|
|
16944
|
+
".ai/memories/**/*.md merge=haive",
|
|
16945
|
+
"# hAIve merge driver end"
|
|
16946
|
+
].join("\n");
|
|
16947
|
+
function registerMergeDriver(program2) {
|
|
16948
|
+
const cmd = program2.command("merge-driver").description("Deterministic git merge driver for hAIve memory files (kills .ai/ conflict markers)");
|
|
16949
|
+
cmd.command("run <base> <ours> <theirs>").description("Git merge-driver entrypoint: resolve ours/theirs by frontmatter order, write into <ours>").action((base, ours, theirs) => {
|
|
16950
|
+
try {
|
|
16951
|
+
const oursContent = readFileSync(ours, "utf8");
|
|
16952
|
+
const theirsContent = readFileSync(theirs, "utf8");
|
|
16953
|
+
const result = mergeMemoryVersions(oursContent, theirsContent);
|
|
16954
|
+
if (result.content !== oursContent) writeFileSync(ours, result.content, "utf8");
|
|
16955
|
+
process.exit(0);
|
|
16956
|
+
} catch {
|
|
16957
|
+
process.exit(1);
|
|
16958
|
+
}
|
|
16959
|
+
});
|
|
16960
|
+
cmd.command("install").description("Configure git + .gitattributes so memory-file conflicts auto-resolve").option("-d, --dir <dir>", "project root").action((opts) => {
|
|
16961
|
+
const root = findProjectRoot58(opts.dir);
|
|
16962
|
+
try {
|
|
16963
|
+
execFileSync3("git", ["config", "merge.haive.name", "hAIve memory merge driver"], { cwd: root });
|
|
16964
|
+
execFileSync3("git", ["config", "merge.haive.driver", "haive merge-driver run %O %A %B"], { cwd: root });
|
|
16965
|
+
} catch {
|
|
16966
|
+
ui.error("Could not set git config \u2014 is this a git repository?");
|
|
16967
|
+
process.exitCode = 1;
|
|
16968
|
+
return;
|
|
16969
|
+
}
|
|
16970
|
+
const gaPath = path56.join(root, ".gitattributes");
|
|
16971
|
+
let content = existsSync80(gaPath) ? readFileSync(gaPath, "utf8") : "";
|
|
16972
|
+
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
16973
|
+
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
16974
|
+
content += GITATTRIBUTES_BLOCK + "\n";
|
|
16975
|
+
writeFileSync(gaPath, content, "utf8");
|
|
16976
|
+
ui.success("Installed hAIve merge driver (git config + .gitattributes).");
|
|
16977
|
+
} else {
|
|
16978
|
+
ui.info("hAIve merge driver already present in .gitattributes \u2014 refreshed git config.");
|
|
16979
|
+
}
|
|
16980
|
+
ui.info("Memory-file conflicts under .ai/memories/ now resolve by revision_count \u2192 created_at.");
|
|
16981
|
+
});
|
|
16982
|
+
}
|
|
16983
|
+
|
|
16984
|
+
// src/commands/memory-resolve-conflict.ts
|
|
16985
|
+
import { writeFile as writeFile38 } from "fs/promises";
|
|
16986
|
+
import { existsSync as existsSync81 } from "fs";
|
|
16987
|
+
import "commander";
|
|
16988
|
+
import {
|
|
16989
|
+
findProjectRoot as findProjectRoot59,
|
|
16990
|
+
planConflictResolution,
|
|
16991
|
+
resolveHaivePaths as resolveHaivePaths53,
|
|
16992
|
+
serializeMemory as serializeMemory29
|
|
16993
|
+
} from "@hiveai/core";
|
|
16994
|
+
function registerMemoryResolveConflict(memory2) {
|
|
16995
|
+
memory2.command("resolve-conflict <id_a> <id_b>").description("Resolve a contradiction: keep the stronger memory, deprecate (supersede) the other").option("--yes", "apply the resolution (without this, only previews it)", false).option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (idA, idB, opts) => {
|
|
16996
|
+
const root = findProjectRoot59(opts.dir);
|
|
16997
|
+
const paths = resolveHaivePaths53(root);
|
|
16998
|
+
if (!existsSync81(paths.memoriesDir)) {
|
|
16999
|
+
ui.error(`No .ai/memories at ${root}.`);
|
|
17000
|
+
process.exitCode = 1;
|
|
17001
|
+
return;
|
|
17002
|
+
}
|
|
17003
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
17004
|
+
const a = memories.find((m) => m.memory.frontmatter.id === idA);
|
|
17005
|
+
const b = memories.find((m) => m.memory.frontmatter.id === idB);
|
|
17006
|
+
if (!a || !b) {
|
|
17007
|
+
ui.error(`Memory not found: ${!a ? idA : ""} ${!b ? idB : ""}`.trim());
|
|
17008
|
+
process.exitCode = 1;
|
|
17009
|
+
return;
|
|
17010
|
+
}
|
|
17011
|
+
const plan = planConflictResolution(a, b);
|
|
17012
|
+
const loser = plan.supersede_id === idA ? a : b;
|
|
17013
|
+
if (opts.json) {
|
|
17014
|
+
console.log(JSON.stringify({ ...plan, applied: Boolean(opts.yes) }, null, 2));
|
|
17015
|
+
} else {
|
|
17016
|
+
console.log(ui.bold("Conflict resolution"));
|
|
17017
|
+
console.log(` keep: ${ui.green(plan.keep_id)}`);
|
|
17018
|
+
console.log(` supersede: ${ui.red(plan.supersede_id)} ${ui.dim(`\u2192 deprecated`)}`);
|
|
17019
|
+
console.log(` reason: ${plan.reason}`);
|
|
17020
|
+
}
|
|
17021
|
+
if (!opts.yes) {
|
|
17022
|
+
if (!opts.json) ui.info("Preview only \u2014 re-run with --yes to apply.");
|
|
17023
|
+
return;
|
|
17024
|
+
}
|
|
17025
|
+
await writeFile38(
|
|
17026
|
+
loser.filePath,
|
|
17027
|
+
serializeMemory29({
|
|
17028
|
+
frontmatter: {
|
|
17029
|
+
...loser.memory.frontmatter,
|
|
17030
|
+
status: "deprecated",
|
|
17031
|
+
stale_reason: plan.stale_reason,
|
|
17032
|
+
related_ids: [.../* @__PURE__ */ new Set([...loser.memory.frontmatter.related_ids, plan.keep_id])]
|
|
17033
|
+
},
|
|
17034
|
+
body: loser.memory.body
|
|
17035
|
+
}),
|
|
17036
|
+
"utf8"
|
|
17037
|
+
);
|
|
17038
|
+
if (!opts.json) ui.success(`Deprecated ${plan.supersede_id}; ${plan.keep_id} remains authoritative.`);
|
|
17039
|
+
});
|
|
17040
|
+
}
|
|
17041
|
+
|
|
17042
|
+
// src/commands/memory-seed-git.ts
|
|
17043
|
+
import { execFile as execFile4 } from "child_process";
|
|
17044
|
+
import { mkdir as mkdir24, writeFile as writeFile39 } from "fs/promises";
|
|
17045
|
+
import { existsSync as existsSync83 } from "fs";
|
|
17046
|
+
import path57 from "path";
|
|
17047
|
+
import { promisify as promisify4 } from "util";
|
|
17048
|
+
import "commander";
|
|
17049
|
+
import {
|
|
17050
|
+
buildFrontmatter as buildFrontmatter12,
|
|
17051
|
+
findProjectRoot as findProjectRoot60,
|
|
17052
|
+
memoryFilePath as memoryFilePath13,
|
|
17053
|
+
proposeSeedsFromCommits,
|
|
17054
|
+
resolveHaivePaths as resolveHaivePaths54,
|
|
17055
|
+
serializeMemory as serializeMemory30
|
|
17056
|
+
} from "@hiveai/core";
|
|
17057
|
+
var exec4 = promisify4(execFile4);
|
|
17058
|
+
function registerMemorySeedGit(memory2) {
|
|
17059
|
+
memory2.command("seed-git").description("Propose draft `attempt` seeds from revert/hotfix commits in git history (cold-start)").option("--apply", "write the proposed seeds as draft memories (default: preview only)", false).option("--limit <n>", "max seeds to propose", "20").option("--days <n>", "git-history lookback window in days", "365").option("--scope <scope>", "personal | team", "team").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
17060
|
+
const root = findProjectRoot60(opts.dir);
|
|
17061
|
+
const paths = resolveHaivePaths54(root);
|
|
17062
|
+
if (!existsSync83(paths.haiveDir)) {
|
|
17063
|
+
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
17064
|
+
process.exitCode = 1;
|
|
17065
|
+
return;
|
|
17066
|
+
}
|
|
17067
|
+
const limit = Math.max(1, parseInt(opts.limit ?? "20", 10) || 20);
|
|
17068
|
+
const days = Math.max(1, parseInt(opts.days ?? "365", 10) || 365);
|
|
17069
|
+
const commits = await readCommits(root, days);
|
|
17070
|
+
const proposals = proposeSeedsFromCommits(commits, limit);
|
|
17071
|
+
if (opts.json) {
|
|
17072
|
+
console.log(JSON.stringify({ scanned_commits: commits.length, proposals, applied: Boolean(opts.apply) }, null, 2));
|
|
17073
|
+
} else if (proposals.length === 0) {
|
|
17074
|
+
ui.info("No revert/hotfix signals found in git history \u2014 nothing to seed.");
|
|
17075
|
+
return;
|
|
17076
|
+
} else {
|
|
17077
|
+
console.log(ui.bold(`hAIve seed-git \u2014 ${proposals.length} proposal(s) from ${commits.length} commit(s)`));
|
|
17078
|
+
for (const p of proposals) {
|
|
17079
|
+
console.log(` ${ui.yellow("\u25C6")} ${ui.dim(`[${p.kind}]`)} ${p.what}`);
|
|
17080
|
+
if (p.paths.length > 0) console.log(` ${ui.dim("paths:")} ${p.paths.join(", ")}`);
|
|
17081
|
+
}
|
|
17082
|
+
}
|
|
17083
|
+
if (!opts.apply) {
|
|
17084
|
+
if (!opts.json) ui.info("Preview only \u2014 re-run with --apply to write these as draft memories.");
|
|
17085
|
+
return;
|
|
17086
|
+
}
|
|
17087
|
+
let written = 0;
|
|
17088
|
+
for (const p of proposals) {
|
|
17089
|
+
const fm = {
|
|
17090
|
+
...buildFrontmatter12({
|
|
17091
|
+
type: "attempt",
|
|
17092
|
+
slug: p.slug,
|
|
17093
|
+
scope: opts.scope ?? "team",
|
|
17094
|
+
tags: ["seed", "git-history", p.kind],
|
|
17095
|
+
paths: p.paths
|
|
17096
|
+
}),
|
|
17097
|
+
status: "draft"
|
|
17098
|
+
// human reviews before it becomes validated
|
|
17099
|
+
};
|
|
17100
|
+
const body = `# ${p.what}
|
|
17101
|
+
|
|
17102
|
+
**Why it failed / do NOT use:** ${p.why_failed}
|
|
17103
|
+
|
|
17104
|
+
_Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
|
|
17105
|
+
`;
|
|
17106
|
+
const file = memoryFilePath13(paths, fm.scope, fm.id, fm.module);
|
|
17107
|
+
if (existsSync83(file)) continue;
|
|
17108
|
+
await mkdir24(path57.dirname(file), { recursive: true });
|
|
17109
|
+
await writeFile39(file, serializeMemory30({ frontmatter: fm, body }), "utf8");
|
|
17110
|
+
written += 1;
|
|
17111
|
+
}
|
|
17112
|
+
if (!opts.json) {
|
|
17113
|
+
ui.success(`Wrote ${written} draft seed(s). Review them: \`haive memory pending\` \u2192 validate or delete.`);
|
|
17114
|
+
}
|
|
17115
|
+
});
|
|
17116
|
+
}
|
|
17117
|
+
async function readCommits(root, days) {
|
|
17118
|
+
try {
|
|
17119
|
+
const { stdout } = await exec4(
|
|
17120
|
+
"git",
|
|
17121
|
+
["log", `--since=${days}.days.ago`, "--name-only", "--pretty=format:%x1f%h%x1f%s", "-n", "500"],
|
|
17122
|
+
{ cwd: root, maxBuffer: 8 * 1024 * 1024 }
|
|
17123
|
+
);
|
|
17124
|
+
const blocks = stdout.split("").filter((b) => b.length > 0);
|
|
17125
|
+
const commits = [];
|
|
17126
|
+
for (let i = 0; i + 1 < blocks.length; i += 2) {
|
|
17127
|
+
const sha = blocks[i].trim();
|
|
17128
|
+
const tail = blocks[i + 1];
|
|
17129
|
+
const lines = tail.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
17130
|
+
const subject = lines.shift() ?? "";
|
|
17131
|
+
commits.push({ sha, subject, files: lines });
|
|
17132
|
+
}
|
|
17133
|
+
return commits;
|
|
17134
|
+
} catch {
|
|
17135
|
+
return [];
|
|
17136
|
+
}
|
|
17137
|
+
}
|
|
17138
|
+
|
|
16747
17139
|
// src/index.ts
|
|
16748
|
-
var program = new
|
|
16749
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
17140
|
+
var program = new Command63();
|
|
17141
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.15.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
16750
17142
|
registerInit(program);
|
|
16751
17143
|
registerWelcome(program);
|
|
16752
17144
|
registerResolveProject(program);
|
|
@@ -16757,6 +17149,8 @@ registerAgent(program);
|
|
|
16757
17149
|
registerSensors(program);
|
|
16758
17150
|
registerIngest(program);
|
|
16759
17151
|
registerDashboard(program);
|
|
17152
|
+
registerCoverage(program);
|
|
17153
|
+
registerMergeDriver(program);
|
|
16760
17154
|
registerDevLink(program);
|
|
16761
17155
|
registerMcp(program);
|
|
16762
17156
|
registerBriefing(program);
|
|
@@ -16787,6 +17181,8 @@ registerMemoryUpdate(memory);
|
|
|
16787
17181
|
registerMemoryHot(memory);
|
|
16788
17182
|
registerMemoryTried(memory);
|
|
16789
17183
|
registerMemorySeed(memory);
|
|
17184
|
+
registerMemorySeedGit(memory);
|
|
17185
|
+
registerMemoryResolveConflict(memory);
|
|
16790
17186
|
registerMemoryImport(memory);
|
|
16791
17187
|
registerMemoryImportChangelog(memory);
|
|
16792
17188
|
registerMemoryDigest(memory);
|