@hiveai/cli 0.21.0 → 0.23.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 +259 -123
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -208,7 +208,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
208
208
|
if (!f) continue;
|
|
209
209
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
210
210
|
}
|
|
211
|
-
let entries = [...counts.entries()].map(([
|
|
211
|
+
let entries = [...counts.entries()].map(([path61, changes]) => ({ path: path61, changes }));
|
|
212
212
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
213
213
|
if (lowerPaths.length > 0) {
|
|
214
214
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -3386,7 +3386,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3386
3386
|
|
|
3387
3387
|
// src/commands/init.ts
|
|
3388
3388
|
var execFileAsync = promisify2(execFile2);
|
|
3389
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3389
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.23.0"}`;
|
|
3390
3390
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3391
3391
|
|
|
3392
3392
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -4742,7 +4742,6 @@ import { z as z25 } from "zod";
|
|
|
4742
4742
|
import { existsSync as existsSync25 } from "fs";
|
|
4743
4743
|
import {
|
|
4744
4744
|
addedLinesFromDiff,
|
|
4745
|
-
appendPreventionEvent,
|
|
4746
4745
|
BRIDGE_TARGET_PATH,
|
|
4747
4746
|
buildDocFrequency,
|
|
4748
4747
|
CODE_STOPWORDS,
|
|
@@ -4754,9 +4753,8 @@ import {
|
|
|
4754
4753
|
loadUsageIndex as loadUsageIndex10,
|
|
4755
4754
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
4756
4755
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
4757
|
-
|
|
4756
|
+
recordPreventionHits,
|
|
4758
4757
|
runSensors,
|
|
4759
|
-
saveUsageIndex as saveUsageIndex4,
|
|
4760
4758
|
sensorTargetsFromDiff,
|
|
4761
4759
|
tokenizeQuery as tokenizeQuery3
|
|
4762
4760
|
} from "@hiveai/core";
|
|
@@ -4803,7 +4801,8 @@ import { existsSync as existsSync30 } from "fs";
|
|
|
4803
4801
|
import {
|
|
4804
4802
|
findLexicalConflictPairs,
|
|
4805
4803
|
findTopicStatusConflictPairs,
|
|
4806
|
-
loadMemoriesFromDir as loadMemoriesFromDir23
|
|
4804
|
+
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
4805
|
+
planConflictResolution
|
|
4807
4806
|
} from "@hiveai/core";
|
|
4808
4807
|
import { z as z32 } from "zod";
|
|
4809
4808
|
import { resolveProjectInfo } from "@hiveai/core";
|
|
@@ -5807,7 +5806,15 @@ async function memTried(input, ctx) {
|
|
|
5807
5806
|
throw new Error(`Memory already exists at ${file}`);
|
|
5808
5807
|
}
|
|
5809
5808
|
await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
|
|
5810
|
-
|
|
5809
|
+
const sensorGenerated = Boolean(sensor);
|
|
5810
|
+
const hint = sensorGenerated ? void 0 : input.paths.length === 0 ? "No sensor was generated (no `paths` given), so this lesson is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives to close the loop." : "No sensor could be derived from the wording (no distinctive code token). The lesson is briefed but not enforced; add a concrete forbidden token/value, or attach a sensor manually, to make the gate block the repeat.";
|
|
5811
|
+
return {
|
|
5812
|
+
id: frontmatter.id,
|
|
5813
|
+
scope: frontmatter.scope,
|
|
5814
|
+
file_path: file,
|
|
5815
|
+
sensor_generated: sensorGenerated,
|
|
5816
|
+
...hint ? { hint } : {}
|
|
5817
|
+
};
|
|
5811
5818
|
}
|
|
5812
5819
|
var IngestFindingsInputSchema = {
|
|
5813
5820
|
format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
|
|
@@ -7358,19 +7365,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
7358
7365
|
const strongCatches = warnings.filter(
|
|
7359
7366
|
(w) => w.reasons.includes("sensor") || w.reasons.includes("anchor") && w.reasons.includes("literal")
|
|
7360
7367
|
);
|
|
7361
|
-
|
|
7362
|
-
const recordedIds = [];
|
|
7363
|
-
for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
|
|
7364
|
-
if (recordedIds.length > 0) {
|
|
7365
|
-
await saveUsageIndex4(ctx.paths, usage).catch(() => {
|
|
7366
|
-
});
|
|
7367
|
-
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
7368
|
-
for (const id of recordedIds) {
|
|
7369
|
-
await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
|
|
7370
|
-
});
|
|
7371
|
-
}
|
|
7372
|
-
}
|
|
7373
|
-
}
|
|
7368
|
+
await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
|
|
7374
7369
|
return {
|
|
7375
7370
|
scanned: negative.length,
|
|
7376
7371
|
warnings
|
|
@@ -8288,6 +8283,18 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
8288
8283
|
return null;
|
|
8289
8284
|
}
|
|
8290
8285
|
}
|
|
8286
|
+
function suggestResolution(byId, idA, idB) {
|
|
8287
|
+
const a = byId.get(idA);
|
|
8288
|
+
const b = byId.get(idB);
|
|
8289
|
+
if (!a || !b) return null;
|
|
8290
|
+
const plan = planConflictResolution(a, b);
|
|
8291
|
+
return {
|
|
8292
|
+
keep_id: plan.keep_id,
|
|
8293
|
+
supersede_id: plan.supersede_id,
|
|
8294
|
+
reason: plan.reason,
|
|
8295
|
+
command: `haive memory resolve-conflict ${plan.keep_id} ${plan.supersede_id} --yes`
|
|
8296
|
+
};
|
|
8297
|
+
}
|
|
8291
8298
|
var MemConflictCandidatesInputSchema = {
|
|
8292
8299
|
since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
8293
8300
|
types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
@@ -8309,6 +8316,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
8309
8316
|
};
|
|
8310
8317
|
}
|
|
8311
8318
|
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
8319
|
+
const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
|
|
8312
8320
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
8313
8321
|
sinceDays: input.since_days,
|
|
8314
8322
|
types: input.types,
|
|
@@ -8317,8 +8325,22 @@ async function memConflictCandidates(input, ctx) {
|
|
|
8317
8325
|
maxScan: input.max_scan
|
|
8318
8326
|
});
|
|
8319
8327
|
const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
|
|
8328
|
+
const enrichedPairs = pairs.map((p) => ({
|
|
8329
|
+
...p,
|
|
8330
|
+
suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
|
|
8331
|
+
}));
|
|
8332
|
+
const enrichedTopicStatusPairs = topicStatusPairs.map((p) => ({
|
|
8333
|
+
...p,
|
|
8334
|
+
suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
|
|
8335
|
+
}));
|
|
8320
8336
|
const notice = pairs.length === 0 && topicStatusPairs.length === 0 ? "No lexical or topic-status candidates \u2014 widen since_days/types or lower min_jaccard." : void 0;
|
|
8321
|
-
return {
|
|
8337
|
+
return {
|
|
8338
|
+
pairs: enrichedPairs,
|
|
8339
|
+
topic_status_pairs: enrichedTopicStatusPairs,
|
|
8340
|
+
scanned,
|
|
8341
|
+
truncated,
|
|
8342
|
+
notice
|
|
8343
|
+
};
|
|
8322
8344
|
}
|
|
8323
8345
|
var MemResolveProjectInputSchema = {
|
|
8324
8346
|
cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
@@ -8632,7 +8654,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
8632
8654
|
};
|
|
8633
8655
|
}
|
|
8634
8656
|
var SERVER_NAME = "haive";
|
|
8635
|
-
var SERVER_VERSION = "0.
|
|
8657
|
+
var SERVER_VERSION = "0.23.0";
|
|
8636
8658
|
function jsonResult(data) {
|
|
8637
8659
|
return {
|
|
8638
8660
|
content: [
|
|
@@ -11166,7 +11188,13 @@ function registerMemoryTried(memory2) {
|
|
|
11166
11188
|
await writeFile21(file, serializeMemory19({ frontmatter, body }), "utf8");
|
|
11167
11189
|
ui.success(`Recorded: ${path27.relative(root, file)}`);
|
|
11168
11190
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
11169
|
-
if (sensor)
|
|
11191
|
+
if (sensor) {
|
|
11192
|
+
ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
11193
|
+
} else {
|
|
11194
|
+
ui.warn(
|
|
11195
|
+
frontmatter.anchor.paths.length === 0 ? "No sensor generated (no --paths) \u2014 lesson is briefed but NOT enforced. Add --paths to close the loop." : "No sensor could be derived from the wording \u2014 lesson is briefed but NOT enforced. Use a concrete forbidden token to enable a gate block."
|
|
11196
|
+
);
|
|
11197
|
+
}
|
|
11170
11198
|
});
|
|
11171
11199
|
}
|
|
11172
11200
|
function parseCsv4(value) {
|
|
@@ -11426,7 +11454,7 @@ import {
|
|
|
11426
11454
|
loadUsageIndex as loadUsageIndex18,
|
|
11427
11455
|
recordRejection as recordRejection3,
|
|
11428
11456
|
resolveHaivePaths as resolveHaivePaths23,
|
|
11429
|
-
saveUsageIndex as
|
|
11457
|
+
saveUsageIndex as saveUsageIndex4,
|
|
11430
11458
|
serializeMemory as serializeMemory20
|
|
11431
11459
|
} from "@hiveai/core";
|
|
11432
11460
|
function registerMemoryReject(memory2) {
|
|
@@ -11459,7 +11487,7 @@ function registerMemoryReject(memory2) {
|
|
|
11459
11487
|
);
|
|
11460
11488
|
const idx = await loadUsageIndex18(paths);
|
|
11461
11489
|
recordRejection3(idx, id, opts.reason ?? null);
|
|
11462
|
-
await
|
|
11490
|
+
await saveUsageIndex4(paths, idx);
|
|
11463
11491
|
const u = idx.by_id[id];
|
|
11464
11492
|
ui.success(
|
|
11465
11493
|
`Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
|
|
@@ -11478,7 +11506,7 @@ import {
|
|
|
11478
11506
|
findProjectRoot as findProjectRoot27,
|
|
11479
11507
|
loadUsageIndex as loadUsageIndex19,
|
|
11480
11508
|
resolveHaivePaths as resolveHaivePaths24,
|
|
11481
|
-
saveUsageIndex as
|
|
11509
|
+
saveUsageIndex as saveUsageIndex5
|
|
11482
11510
|
} from "@hiveai/core";
|
|
11483
11511
|
function registerMemoryRm(memory2) {
|
|
11484
11512
|
memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
@@ -11512,7 +11540,7 @@ function registerMemoryRm(memory2) {
|
|
|
11512
11540
|
const idx = await loadUsageIndex19(paths);
|
|
11513
11541
|
if (idx.by_id[id]) {
|
|
11514
11542
|
delete idx.by_id[id];
|
|
11515
|
-
await
|
|
11543
|
+
await saveUsageIndex5(paths, idx);
|
|
11516
11544
|
ui.info("Removed usage entry");
|
|
11517
11545
|
}
|
|
11518
11546
|
}
|
|
@@ -11725,7 +11753,7 @@ import {
|
|
|
11725
11753
|
recordRejection as recordRejection4,
|
|
11726
11754
|
recommendFeedbackAdjustment as recommendFeedbackAdjustment2,
|
|
11727
11755
|
resolveHaivePaths as resolveHaivePaths28,
|
|
11728
|
-
saveUsageIndex as
|
|
11756
|
+
saveUsageIndex as saveUsageIndex6,
|
|
11729
11757
|
serializeMemory as serializeMemory21
|
|
11730
11758
|
} from "@hiveai/core";
|
|
11731
11759
|
function registerMemoryFeedback(memory2) {
|
|
@@ -11755,7 +11783,7 @@ function registerMemoryFeedback(memory2) {
|
|
|
11755
11783
|
const outcome = opts.applied ? "applied" : "rejected";
|
|
11756
11784
|
if (opts.applied) recordApplied2(index, id);
|
|
11757
11785
|
else recordRejection4(index, id, opts.reason ?? null);
|
|
11758
|
-
await
|
|
11786
|
+
await saveUsageIndex6(paths, index);
|
|
11759
11787
|
const usage = getUsage19(index, id);
|
|
11760
11788
|
const adjustment = opts.rejected ? recommendFeedbackAdjustment2(target.memory.frontmatter, usage) : { action: "none", reason: "No automatic adjustment needed." };
|
|
11761
11789
|
const adjustedFrontmatter = applyFeedbackAdjustment2(target.memory.frontmatter, adjustment);
|
|
@@ -13586,6 +13614,7 @@ function registerEval(program2) {
|
|
|
13586
13614
|
root,
|
|
13587
13615
|
k,
|
|
13588
13616
|
spec_source: resolvedSpec.source,
|
|
13617
|
+
provenance: { synthesized: resolvedSpec.synthesized, authored: resolvedSpec.authored },
|
|
13589
13618
|
report,
|
|
13590
13619
|
gate_precision: gatePrecision,
|
|
13591
13620
|
...delta ? { delta } : {},
|
|
@@ -13594,13 +13623,18 @@ function registerEval(program2) {
|
|
|
13594
13623
|
applyExitGates(opts, report, delta, gatePrecision, gateDelta);
|
|
13595
13624
|
return;
|
|
13596
13625
|
}
|
|
13626
|
+
if (resolvedSpec.authored === 0 && resolvedSpec.synthesized > 0) {
|
|
13627
|
+
ui.warn(
|
|
13628
|
+
`All ${resolvedSpec.synthesized} case(s) are self-synthesized from your own memories (self-referential). Add hand-labeled cases in .ai/eval/spec.json, or run \`haive eval --spec <file>\`, for an independent score.`
|
|
13629
|
+
);
|
|
13630
|
+
}
|
|
13597
13631
|
if (delta) {
|
|
13598
13632
|
console.log(renderDelta(delta));
|
|
13599
13633
|
}
|
|
13600
13634
|
if (gateDelta) {
|
|
13601
13635
|
console.log(renderGateDelta(gateDelta));
|
|
13602
13636
|
}
|
|
13603
|
-
const md = renderMarkdown2(root, k, resolvedSpec
|
|
13637
|
+
const md = renderMarkdown2(root, k, resolvedSpec, report, gatePrecision);
|
|
13604
13638
|
if (opts.out) {
|
|
13605
13639
|
const outFile = path44.isAbsolute(opts.out) ? opts.out : path44.join(root, opts.out);
|
|
13606
13640
|
await writeFile33(outFile, md, "utf8");
|
|
@@ -13683,30 +13717,39 @@ function renderGateDelta(delta) {
|
|
|
13683
13717
|
lines.push(` rejections ${delta.rejections.baseline ?? "n/a"} \u2192 ${delta.rejections.current ?? "n/a"} ${ui.dim(`(${rejectionDelta})`)}`);
|
|
13684
13718
|
return lines.join("\n");
|
|
13685
13719
|
}
|
|
13720
|
+
function countCases(spec) {
|
|
13721
|
+
return (spec.retrieval?.length ?? 0) + (spec.sensors?.length ?? 0);
|
|
13722
|
+
}
|
|
13686
13723
|
async function resolveSpec(opts, root, memoriesDir) {
|
|
13687
13724
|
if (opts.spec) {
|
|
13688
13725
|
const file = path44.resolve(opts.spec);
|
|
13689
13726
|
const raw = await readFile21(file, "utf8");
|
|
13690
|
-
|
|
13727
|
+
const spec = JSON.parse(raw);
|
|
13728
|
+
return { spec, source: file, synthesized: 0, authored: countCases(spec) };
|
|
13691
13729
|
}
|
|
13692
13730
|
const defaultSpec = path44.join(root, ".ai", "eval", "spec.json");
|
|
13693
13731
|
if (existsSync65(defaultSpec)) {
|
|
13694
13732
|
const raw = await readFile21(defaultSpec, "utf8");
|
|
13695
13733
|
const explicit = JSON.parse(raw);
|
|
13696
13734
|
const memories2 = await loadMemoriesFromDir27(memoriesDir);
|
|
13697
|
-
const
|
|
13735
|
+
const synthesized2 = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
|
|
13698
13736
|
return {
|
|
13699
13737
|
spec: {
|
|
13700
|
-
retrieval: [...
|
|
13738
|
+
retrieval: [...synthesized2, ...explicit.retrieval ?? []],
|
|
13701
13739
|
sensors: explicit.sensors ?? []
|
|
13702
13740
|
},
|
|
13703
|
-
source: ".ai/eval/spec.json + synthesized anchored retrieval"
|
|
13741
|
+
source: ".ai/eval/spec.json + synthesized anchored retrieval",
|
|
13742
|
+
synthesized: synthesized2.length,
|
|
13743
|
+
authored: countCases(explicit)
|
|
13704
13744
|
};
|
|
13705
13745
|
}
|
|
13706
13746
|
const memories = await loadMemoriesFromDir27(memoriesDir);
|
|
13747
|
+
const synthesized = synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly });
|
|
13707
13748
|
return {
|
|
13708
|
-
spec: { retrieval:
|
|
13709
|
-
source: "synthesized anchored retrieval"
|
|
13749
|
+
spec: { retrieval: synthesized },
|
|
13750
|
+
source: "synthesized anchored retrieval",
|
|
13751
|
+
synthesized: synthesized.length,
|
|
13752
|
+
authored: 0
|
|
13710
13753
|
};
|
|
13711
13754
|
}
|
|
13712
13755
|
async function runRetrieval(c, k, ctx) {
|
|
@@ -13739,12 +13782,14 @@ async function runSensorCase(c, ctx) {
|
|
|
13739
13782
|
function pct(n) {
|
|
13740
13783
|
return `${Math.round(n * 100)}%`;
|
|
13741
13784
|
}
|
|
13742
|
-
function renderMarkdown2(root, k,
|
|
13785
|
+
function renderMarkdown2(root, k, resolved, report, gatePrecision) {
|
|
13786
|
+
const provenance = resolved.authored === 0 ? `${resolved.synthesized} synthesized (self-referential \u2014 sanity floor, not ground truth)` : resolved.synthesized === 0 ? `${resolved.authored} authored (independent ground truth)` : `${resolved.authored} authored (independent) + ${resolved.synthesized} synthesized (self-referential)`;
|
|
13743
13787
|
const lines = [
|
|
13744
13788
|
"# hAIve eval report",
|
|
13745
13789
|
"",
|
|
13746
13790
|
`Project: \`${root}\` \xB7 top-k: ${k}`,
|
|
13747
|
-
`Spec: ${source}`,
|
|
13791
|
+
`Spec: ${resolved.source}`,
|
|
13792
|
+
`Cases: ${provenance}`,
|
|
13748
13793
|
"",
|
|
13749
13794
|
`## Overall score: ${report.score}/100`,
|
|
13750
13795
|
""
|
|
@@ -14185,8 +14230,8 @@ function registerDoctor(program2) {
|
|
|
14185
14230
|
fix: "haive init"
|
|
14186
14231
|
});
|
|
14187
14232
|
} else {
|
|
14188
|
-
const { readFile:
|
|
14189
|
-
const content = await
|
|
14233
|
+
const { readFile: readFile29 } = await import("fs/promises");
|
|
14234
|
+
const content = await readFile29(paths.projectContext, "utf8");
|
|
14190
14235
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
14191
14236
|
if (isTemplate) {
|
|
14192
14237
|
findings.push({
|
|
@@ -14360,8 +14405,8 @@ function registerDoctor(program2) {
|
|
|
14360
14405
|
let hasClaudeEnforcement = false;
|
|
14361
14406
|
if (existsSync68(claudeSettings)) {
|
|
14362
14407
|
try {
|
|
14363
|
-
const { readFile:
|
|
14364
|
-
const raw = await
|
|
14408
|
+
const { readFile: readFile29 } = await import("fs/promises");
|
|
14409
|
+
const raw = await readFile29(claudeSettings, "utf8");
|
|
14365
14410
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
14366
14411
|
} catch {
|
|
14367
14412
|
hasClaudeEnforcement = false;
|
|
@@ -14384,7 +14429,7 @@ function registerDoctor(program2) {
|
|
|
14384
14429
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
14385
14430
|
});
|
|
14386
14431
|
}
|
|
14387
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
14432
|
+
findings.push(...await collectInstallFindings(root, "0.23.0"));
|
|
14388
14433
|
findings.push(...await collectToolchainFindings(root));
|
|
14389
14434
|
try {
|
|
14390
14435
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -14392,7 +14437,7 @@ function registerDoctor(program2) {
|
|
|
14392
14437
|
timeout: 3e3,
|
|
14393
14438
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14394
14439
|
}).trim();
|
|
14395
|
-
const cliVersion = "0.
|
|
14440
|
+
const cliVersion = "0.23.0";
|
|
14396
14441
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
14397
14442
|
findings.push({
|
|
14398
14443
|
severity: "warn",
|
|
@@ -15465,10 +15510,11 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15465
15510
|
}
|
|
15466
15511
|
|
|
15467
15512
|
// src/commands/enforce.ts
|
|
15468
|
-
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
15513
|
+
import { execFile as execFile3, execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
15469
15514
|
import { existsSync as existsSync75, statSync as statSync3 } from "fs";
|
|
15470
15515
|
import { chmod as chmod2, mkdir as mkdir21, readFile as readFile24, readdir as readdir6, rm as rm3, writeFile as writeFile37 } from "fs/promises";
|
|
15471
15516
|
import path53 from "path";
|
|
15517
|
+
import { promisify as promisify3 } from "util";
|
|
15472
15518
|
import "commander";
|
|
15473
15519
|
import {
|
|
15474
15520
|
antiPatternGateParams as antiPatternGateParams2,
|
|
@@ -15482,15 +15528,18 @@ import {
|
|
|
15482
15528
|
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
15483
15529
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
15484
15530
|
readRecentBriefingMarker,
|
|
15531
|
+
recordPreventionHits as recordPreventionHits2,
|
|
15485
15532
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
15486
15533
|
resolveHaivePaths as resolveHaivePaths48,
|
|
15487
15534
|
runSensors as runSensors2,
|
|
15488
15535
|
saveConfig as saveConfig4,
|
|
15536
|
+
selectCommandSensors,
|
|
15489
15537
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15490
15538
|
SESSION_RECAP_TTL_MS,
|
|
15491
15539
|
verifyAnchor as verifyAnchor4,
|
|
15492
15540
|
writeBriefingMarker as writeBriefingMarker3
|
|
15493
15541
|
} from "@hiveai/core";
|
|
15542
|
+
var execFileAsync2 = promisify3(execFile3);
|
|
15494
15543
|
var MAX_STDIN_BYTES2 = 256 * 1024;
|
|
15495
15544
|
var ENFORCE_HOOK_MARKER = "# hAIve enforcement hook";
|
|
15496
15545
|
function registerEnforce(program2) {
|
|
@@ -16092,7 +16141,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
16092
16141
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
16093
16142
|
});
|
|
16094
16143
|
}
|
|
16095
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
16144
|
+
findings.push(...await inspectIntegrationVersions(root, "0.23.0"));
|
|
16096
16145
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
16097
16146
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
16098
16147
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -16344,19 +16393,19 @@ async function runSensorGate(paths, diff) {
|
|
|
16344
16393
|
if (!diff || !existsSync75(paths.memoriesDir)) return [];
|
|
16345
16394
|
try {
|
|
16346
16395
|
const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16347
|
-
const
|
|
16348
|
-
|
|
16349
|
-
return Boolean(s) && s.kind === "regex" && !isRetiredMemory3(m.frontmatter, m.body);
|
|
16350
|
-
});
|
|
16351
|
-
if (sensorMemories.length === 0) return [];
|
|
16396
|
+
const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory3(m.frontmatter, m.body));
|
|
16397
|
+
if (scannable.length === 0) return [];
|
|
16352
16398
|
const targets = sensorTargetsFromDiff2(diff).filter((t) => isSensorScannablePath(t.path));
|
|
16353
16399
|
if (targets.length === 0) return [];
|
|
16354
|
-
const hits = runSensors2(sensorMemories, targets);
|
|
16355
16400
|
const findings = [];
|
|
16356
16401
|
const seen = /* @__PURE__ */ new Set();
|
|
16402
|
+
const firedIds = /* @__PURE__ */ new Set();
|
|
16403
|
+
const regexSensorMemories = scannable.filter((m) => m.frontmatter.sensor.kind === "regex");
|
|
16404
|
+
const hits = regexSensorMemories.length > 0 ? runSensors2(regexSensorMemories, targets) : [];
|
|
16357
16405
|
for (const hit of hits) {
|
|
16358
16406
|
if (seen.has(hit.memory_id)) continue;
|
|
16359
16407
|
seen.add(hit.memory_id);
|
|
16408
|
+
firedIds.add(hit.memory_id);
|
|
16360
16409
|
const where = hit.file ? ` (${hit.file})` : "";
|
|
16361
16410
|
if (hit.severity === "block") {
|
|
16362
16411
|
findings.push({
|
|
@@ -16376,11 +16425,51 @@ async function runSensorGate(paths, diff) {
|
|
|
16376
16425
|
});
|
|
16377
16426
|
}
|
|
16378
16427
|
}
|
|
16428
|
+
const config = await loadConfig14(paths).catch(() => null);
|
|
16429
|
+
if (config?.enforcement?.runCommandSensors === true) {
|
|
16430
|
+
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
16431
|
+
for (const spec of selectCommandSensors(scannable, changedPaths)) {
|
|
16432
|
+
if (seen.has(spec.memory_id)) continue;
|
|
16433
|
+
const failed = await runGateCommandSensor(spec, paths.root);
|
|
16434
|
+
if (!failed) continue;
|
|
16435
|
+
seen.add(spec.memory_id);
|
|
16436
|
+
firedIds.add(spec.memory_id);
|
|
16437
|
+
if (spec.severity === "block") {
|
|
16438
|
+
findings.push({
|
|
16439
|
+
severity: "error",
|
|
16440
|
+
code: "sensor-block",
|
|
16441
|
+
message: `Block command sensor fired \u2014 ${spec.memory_id}: ${spec.message} (command: ${spec.command})`,
|
|
16442
|
+
fix: "Fix the condition the command checks, or run `haive sensors check --commands` to inspect it.",
|
|
16443
|
+
impact: 45
|
|
16444
|
+
});
|
|
16445
|
+
} else {
|
|
16446
|
+
findings.push({
|
|
16447
|
+
severity: "warn",
|
|
16448
|
+
code: "sensor-warn",
|
|
16449
|
+
message: `Command sensor flagged ${spec.memory_id}: ${spec.message}`,
|
|
16450
|
+
fix: "Review the failing command; `haive sensors check --commands` re-runs it.",
|
|
16451
|
+
impact: 5
|
|
16452
|
+
});
|
|
16453
|
+
}
|
|
16454
|
+
}
|
|
16455
|
+
}
|
|
16456
|
+
if (firedIds.size > 0) {
|
|
16457
|
+
await recordPreventionHits2(paths, [...firedIds], "sensor").catch(() => {
|
|
16458
|
+
});
|
|
16459
|
+
}
|
|
16379
16460
|
return findings;
|
|
16380
16461
|
} catch {
|
|
16381
16462
|
return [];
|
|
16382
16463
|
}
|
|
16383
16464
|
}
|
|
16465
|
+
async function runGateCommandSensor(spec, root) {
|
|
16466
|
+
try {
|
|
16467
|
+
await execFileAsync2("bash", ["-c", spec.command], { cwd: root, timeout: 12e4, maxBuffer: 8 * 1024 * 1024 });
|
|
16468
|
+
return false;
|
|
16469
|
+
} catch {
|
|
16470
|
+
return true;
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16384
16473
|
async function findGeneratedArtifacts(paths) {
|
|
16385
16474
|
const dirty = await runCommand4("git", ["status", "--short", "--untracked-files=all"], paths.root).catch(() => "");
|
|
16386
16475
|
const generated = dirty.split("\n").map((line) => line.trim()).filter(Boolean).filter(
|
|
@@ -17166,28 +17255,25 @@ function registerRun(program2) {
|
|
|
17166
17255
|
}
|
|
17167
17256
|
|
|
17168
17257
|
// src/commands/sensors.ts
|
|
17169
|
-
import { execFile as
|
|
17258
|
+
import { execFile as execFile4 } from "child_process";
|
|
17170
17259
|
import { existsSync as existsSync76 } from "fs";
|
|
17171
17260
|
import { chmod as chmod3, mkdir as mkdir23, readFile as readFile25, writeFile as writeFile38 } from "fs/promises";
|
|
17172
17261
|
import path54 from "path";
|
|
17173
|
-
import { promisify as
|
|
17262
|
+
import { promisify as promisify4 } from "util";
|
|
17174
17263
|
import "commander";
|
|
17175
17264
|
import {
|
|
17176
|
-
appendPreventionEvent as appendPreventionEvent2,
|
|
17177
17265
|
findProjectRoot as findProjectRoot53,
|
|
17178
17266
|
isRetiredMemory as isRetiredMemory4,
|
|
17179
17267
|
loadConfig as loadConfig15,
|
|
17180
17268
|
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
17181
|
-
|
|
17182
|
-
recordPrevention as recordPrevention2,
|
|
17269
|
+
recordPreventionHits as recordPreventionHits3,
|
|
17183
17270
|
resolveHaivePaths as resolveHaivePaths49,
|
|
17184
17271
|
runSensors as runSensors3,
|
|
17185
|
-
|
|
17186
|
-
selectCommandSensors,
|
|
17272
|
+
selectCommandSensors as selectCommandSensors2,
|
|
17187
17273
|
sensorTargetsFromDiff as sensorTargetsFromDiff3,
|
|
17188
17274
|
serializeMemory as serializeMemory29
|
|
17189
17275
|
} from "@hiveai/core";
|
|
17190
|
-
var exec2 =
|
|
17276
|
+
var exec2 = promisify4(execFile4);
|
|
17191
17277
|
function registerSensors(program2) {
|
|
17192
17278
|
const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
|
|
17193
17279
|
sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
@@ -17224,7 +17310,7 @@ function registerSensors(program2) {
|
|
|
17224
17310
|
const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
|
|
17225
17311
|
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
17226
17312
|
const allSensorMemories = await runnableSensorMemories(paths, false);
|
|
17227
|
-
const commandSpecs =
|
|
17313
|
+
const commandSpecs = selectCommandSensors2(allSensorMemories, changedPaths);
|
|
17228
17314
|
const commandHits = [];
|
|
17229
17315
|
const commandSkipped = [];
|
|
17230
17316
|
if (commandSpecs.length > 0 && runCommands) {
|
|
@@ -17243,20 +17329,7 @@ function registerSensors(program2) {
|
|
|
17243
17329
|
for (const spec of commandSpecs) commandSkipped.push(spec.memory_id);
|
|
17244
17330
|
}
|
|
17245
17331
|
const firedIds = [...new Set([...hits, ...commandHits].map((hit) => hit.memory_id))];
|
|
17246
|
-
|
|
17247
|
-
const usage = await loadUsageIndex31(paths);
|
|
17248
|
-
const recordedIds = [];
|
|
17249
|
-
for (const id of firedIds) if (recordPrevention2(usage, id)) recordedIds.push(id);
|
|
17250
|
-
if (recordedIds.length > 0) {
|
|
17251
|
-
await saveUsageIndex8(paths, usage).catch(() => {
|
|
17252
|
-
});
|
|
17253
|
-
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17254
|
-
for (const id of recordedIds) {
|
|
17255
|
-
await appendPreventionEvent2(paths, { at, id, source: "sensor" }).catch(() => {
|
|
17256
|
-
});
|
|
17257
|
-
}
|
|
17258
|
-
}
|
|
17259
|
-
}
|
|
17332
|
+
await recordPreventionHits3(paths, firedIds, "sensor");
|
|
17260
17333
|
const output = {
|
|
17261
17334
|
scanned: memories.length,
|
|
17262
17335
|
hits: hits.map((hit) => ({
|
|
@@ -17623,7 +17696,7 @@ import {
|
|
|
17623
17696
|
loadConfig as loadConfig16,
|
|
17624
17697
|
loadMemoriesFromDir as loadMemoriesFromDir41,
|
|
17625
17698
|
loadPreventionEvents as loadPreventionEvents4,
|
|
17626
|
-
loadUsageIndex as
|
|
17699
|
+
loadUsageIndex as loadUsageIndex31,
|
|
17627
17700
|
resolveHaivePaths as resolveHaivePaths51
|
|
17628
17701
|
} from "@hiveai/core";
|
|
17629
17702
|
function registerDashboard(program2) {
|
|
@@ -17638,7 +17711,7 @@ function registerDashboard(program2) {
|
|
|
17638
17711
|
return;
|
|
17639
17712
|
}
|
|
17640
17713
|
const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
|
|
17641
|
-
const usage = await
|
|
17714
|
+
const usage = await loadUsageIndex31(paths);
|
|
17642
17715
|
const preventionEvents = await loadPreventionEvents4(paths);
|
|
17643
17716
|
const config = await loadConfig16(paths);
|
|
17644
17717
|
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
@@ -17749,14 +17822,14 @@ function warnNum(n) {
|
|
|
17749
17822
|
}
|
|
17750
17823
|
|
|
17751
17824
|
// src/commands/dev-link.ts
|
|
17752
|
-
import { execFile as
|
|
17825
|
+
import { execFile as execFile5 } from "child_process";
|
|
17753
17826
|
import { cp, readFile as readFile27 } from "fs/promises";
|
|
17754
17827
|
import { existsSync as existsSync79 } from "fs";
|
|
17755
17828
|
import path56 from "path";
|
|
17756
|
-
import { promisify as
|
|
17829
|
+
import { promisify as promisify5 } from "util";
|
|
17757
17830
|
import "commander";
|
|
17758
17831
|
import { findProjectRoot as findProjectRoot56 } from "@hiveai/core";
|
|
17759
|
-
var exec3 =
|
|
17832
|
+
var exec3 = promisify5(execFile5);
|
|
17760
17833
|
function registerDevLink(program2) {
|
|
17761
17834
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
|
|
17762
17835
|
dev.command("link").description("Hot-swap this repo's built dist into the global @hiveai install so `haive` runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
|
|
@@ -17812,8 +17885,41 @@ function registerDevLink(program2) {
|
|
|
17812
17885
|
}
|
|
17813
17886
|
|
|
17814
17887
|
// src/commands/coverage.ts
|
|
17888
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
17889
|
+
import { existsSync as existsSync80 } from "fs";
|
|
17890
|
+
import path57 from "path";
|
|
17815
17891
|
import "commander";
|
|
17816
|
-
import {
|
|
17892
|
+
import {
|
|
17893
|
+
findCoverageGaps,
|
|
17894
|
+
findProjectRoot as findProjectRoot57,
|
|
17895
|
+
mergeHotFiles,
|
|
17896
|
+
resolveHaivePaths as resolveHaivePaths52,
|
|
17897
|
+
tallyHotFiles
|
|
17898
|
+
} from "@hiveai/core";
|
|
17899
|
+
async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
17900
|
+
if (!existsSync80(cacheFile)) return [];
|
|
17901
|
+
const raw = await readFile28(cacheFile, "utf8").catch(() => "");
|
|
17902
|
+
const files = [];
|
|
17903
|
+
for (const line of raw.split("\n")) {
|
|
17904
|
+
const trimmed = line.trim();
|
|
17905
|
+
if (!trimmed) continue;
|
|
17906
|
+
try {
|
|
17907
|
+
const obs = JSON.parse(trimmed);
|
|
17908
|
+
if (sinceMs > 0 && obs.ts) {
|
|
17909
|
+
const t = Date.parse(obs.ts);
|
|
17910
|
+
if (Number.isFinite(t) && t < sinceMs) continue;
|
|
17911
|
+
}
|
|
17912
|
+
for (const f of obs.files ?? []) {
|
|
17913
|
+
if (typeof f !== "string" || !f) continue;
|
|
17914
|
+
const rel = path57.isAbsolute(f) ? path57.relative(root, f) : f;
|
|
17915
|
+
if (rel.startsWith("..")) continue;
|
|
17916
|
+
files.push(rel);
|
|
17917
|
+
}
|
|
17918
|
+
} catch {
|
|
17919
|
+
}
|
|
17920
|
+
}
|
|
17921
|
+
return tallyHotFiles(files, "agent");
|
|
17922
|
+
}
|
|
17817
17923
|
function isNoisePath(p) {
|
|
17818
17924
|
if (/(^|\/)(node_modules|dist|build|coverage|\.next)\//.test(p)) return true;
|
|
17819
17925
|
if (p.startsWith(".ai/")) return true;
|
|
@@ -17825,29 +17931,47 @@ function isNoisePath(p) {
|
|
|
17825
17931
|
function registerCoverage(program2) {
|
|
17826
17932
|
program2.command("coverage").description(
|
|
17827
17933
|
"Coverage-gap report: frequently-edited files with no covering team memory (blind spots)."
|
|
17828
|
-
).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum
|
|
17934
|
+
).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum churn count to flag a file", "3").option("--limit <n>", "max gaps to report", "20").option("--days <n>", "lookback window in days (git history + agent edits)", "90").option("--source <which>", "heat source: git | agent | both", "both").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
17829
17935
|
const root = findProjectRoot57(opts.dir);
|
|
17830
17936
|
const paths = resolveHaivePaths52(root);
|
|
17831
17937
|
const minChanges = Math.max(1, parseInt(opts.minChanges ?? "3", 10) || 3);
|
|
17832
17938
|
const limit = Math.max(1, parseInt(opts.limit ?? "20", 10) || 20);
|
|
17833
17939
|
const days = Math.max(1, parseInt(opts.days ?? "90", 10) || 90);
|
|
17834
|
-
const
|
|
17940
|
+
const source = (opts.source ?? "both").toLowerCase();
|
|
17941
|
+
if (!["git", "agent", "both"].includes(source)) {
|
|
17942
|
+
ui.error("--source must be one of: git | agent | both");
|
|
17943
|
+
process.exitCode = 1;
|
|
17944
|
+
return;
|
|
17945
|
+
}
|
|
17946
|
+
const useGit = source === "git" || source === "both";
|
|
17947
|
+
const useAgent = source === "agent" || source === "both";
|
|
17948
|
+
const radar = useGit ? await buildRadar({
|
|
17835
17949
|
root,
|
|
17836
17950
|
taskTokens: null,
|
|
17837
17951
|
filePaths: [],
|
|
17838
17952
|
daysBack: Math.ceil(days / 6),
|
|
17839
17953
|
// getHotFiles multiplies daysBack by 6
|
|
17840
17954
|
maxHotFiles: 500
|
|
17841
|
-
});
|
|
17842
|
-
const
|
|
17955
|
+
}) : null;
|
|
17956
|
+
const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
|
|
17957
|
+
const sinceMs = Date.now() - days * 864e5;
|
|
17958
|
+
const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path57.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
|
|
17959
|
+
const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
|
|
17843
17960
|
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
17844
17961
|
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
17845
17962
|
if (opts.json) {
|
|
17846
|
-
console.log(JSON.stringify({
|
|
17963
|
+
console.log(JSON.stringify({
|
|
17964
|
+
root,
|
|
17965
|
+
source,
|
|
17966
|
+
scanned_hot_files: hotFiles.length,
|
|
17967
|
+
git_hot_files: gitHotFiles.length,
|
|
17968
|
+
agent_hot_files: agentHotFiles.length,
|
|
17969
|
+
gaps
|
|
17970
|
+
}, null, 2));
|
|
17847
17971
|
return;
|
|
17848
17972
|
}
|
|
17849
|
-
if (!radar.insideGitRepo) {
|
|
17850
|
-
ui.warn("Not a git repository
|
|
17973
|
+
if (useGit && radar && !radar.insideGitRepo && agentHotFiles.length === 0) {
|
|
17974
|
+
ui.warn("Not a git repository and no agent-edit history \u2014 nothing to cross-check.");
|
|
17851
17975
|
return;
|
|
17852
17976
|
}
|
|
17853
17977
|
if (gaps.length === 0) {
|
|
@@ -17856,7 +17980,8 @@ function registerCoverage(program2) {
|
|
|
17856
17980
|
}
|
|
17857
17981
|
console.log(ui.bold(`hAIve coverage \u2014 ${gaps.length} blind spot(s) (hot files with no covering memory)`));
|
|
17858
17982
|
for (const gap of gaps) {
|
|
17859
|
-
|
|
17983
|
+
const src = gap.source ? ui.dim(` [${gap.source}]`) : "";
|
|
17984
|
+
console.log(` ${ui.yellow("\u25CB")} ${gap.path} ${ui.dim(`(${gap.changes} change${gap.changes === 1 ? "" : "s"})`)}${src}`);
|
|
17860
17985
|
}
|
|
17861
17986
|
console.log(
|
|
17862
17987
|
ui.dim(
|
|
@@ -17868,8 +17993,8 @@ function registerCoverage(program2) {
|
|
|
17868
17993
|
|
|
17869
17994
|
// src/commands/merge-driver.ts
|
|
17870
17995
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
17871
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
17872
|
-
import
|
|
17996
|
+
import { readFileSync, writeFileSync, existsSync as existsSync81 } from "fs";
|
|
17997
|
+
import path58 from "path";
|
|
17873
17998
|
import "commander";
|
|
17874
17999
|
import { findProjectRoot as findProjectRoot58, mergeMemoryVersions } from "@hiveai/core";
|
|
17875
18000
|
var GITATTRIBUTES_MARK = "# hAIve merge driver";
|
|
@@ -17901,8 +18026,8 @@ function registerMergeDriver(program2) {
|
|
|
17901
18026
|
process.exitCode = 1;
|
|
17902
18027
|
return;
|
|
17903
18028
|
}
|
|
17904
|
-
const gaPath =
|
|
17905
|
-
let content =
|
|
18029
|
+
const gaPath = path58.join(root, ".gitattributes");
|
|
18030
|
+
let content = existsSync81(gaPath) ? readFileSync(gaPath, "utf8") : "";
|
|
17906
18031
|
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
17907
18032
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
17908
18033
|
content += GITATTRIBUTES_BLOCK + "\n";
|
|
@@ -17917,11 +18042,12 @@ function registerMergeDriver(program2) {
|
|
|
17917
18042
|
|
|
17918
18043
|
// src/commands/memory-resolve-conflict.ts
|
|
17919
18044
|
import { writeFile as writeFile40 } from "fs/promises";
|
|
17920
|
-
import { existsSync as
|
|
18045
|
+
import { existsSync as existsSync83 } from "fs";
|
|
17921
18046
|
import "commander";
|
|
17922
18047
|
import {
|
|
18048
|
+
applyConflictResolution,
|
|
17923
18049
|
findProjectRoot as findProjectRoot59,
|
|
17924
|
-
planConflictResolution,
|
|
18050
|
+
planConflictResolution as planConflictResolution2,
|
|
17925
18051
|
resolveHaivePaths as resolveHaivePaths53,
|
|
17926
18052
|
serializeMemory as serializeMemory31
|
|
17927
18053
|
} from "@hiveai/core";
|
|
@@ -17929,7 +18055,7 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
17929
18055
|
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) => {
|
|
17930
18056
|
const root = findProjectRoot59(opts.dir);
|
|
17931
18057
|
const paths = resolveHaivePaths53(root);
|
|
17932
|
-
if (!
|
|
18058
|
+
if (!existsSync83(paths.memoriesDir)) {
|
|
17933
18059
|
ui.error(`No .ai/memories at ${root}.`);
|
|
17934
18060
|
process.exitCode = 1;
|
|
17935
18061
|
return;
|
|
@@ -17942,43 +18068,53 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
17942
18068
|
process.exitCode = 1;
|
|
17943
18069
|
return;
|
|
17944
18070
|
}
|
|
17945
|
-
const plan =
|
|
18071
|
+
const plan = planConflictResolution2(a, b);
|
|
18072
|
+
const winner = plan.keep_id === idA ? a : b;
|
|
17946
18073
|
const loser = plan.supersede_id === idA ? a : b;
|
|
18074
|
+
const applied = applyConflictResolution(winner, loser, plan);
|
|
17947
18075
|
if (opts.json) {
|
|
17948
|
-
console.log(JSON.stringify({
|
|
18076
|
+
console.log(JSON.stringify({
|
|
18077
|
+
...plan,
|
|
18078
|
+
winner_revision_count: applied.winner.revision_count,
|
|
18079
|
+
topic: applied.topic,
|
|
18080
|
+
topic_adopted: applied.topic_adopted,
|
|
18081
|
+
applied: Boolean(opts.yes)
|
|
18082
|
+
}, null, 2));
|
|
17949
18083
|
} else {
|
|
17950
18084
|
console.log(ui.bold("Conflict resolution"));
|
|
17951
|
-
console.log(` keep: ${ui.green(plan.keep_id)}`);
|
|
18085
|
+
console.log(` keep: ${ui.green(plan.keep_id)} ${ui.dim(`(rev ${winner.memory.frontmatter.revision_count}\u2192${applied.winner.revision_count})`)}`);
|
|
17952
18086
|
console.log(` supersede: ${ui.red(plan.supersede_id)} ${ui.dim(`\u2192 deprecated`)}`);
|
|
17953
18087
|
console.log(` reason: ${plan.reason}`);
|
|
18088
|
+
if (applied.topic) {
|
|
18089
|
+
console.log(` topic: ${applied.topic}${applied.topic_adopted ? ui.dim(" (adopted from superseded \u2014 future captures upsert into the winner)") : ""}`);
|
|
18090
|
+
}
|
|
17954
18091
|
}
|
|
17955
18092
|
if (!opts.yes) {
|
|
17956
18093
|
if (!opts.json) ui.info("Preview only \u2014 re-run with --yes to apply.");
|
|
17957
18094
|
return;
|
|
17958
18095
|
}
|
|
18096
|
+
await writeFile40(
|
|
18097
|
+
winner.filePath,
|
|
18098
|
+
serializeMemory31({ frontmatter: applied.winner, body: winner.memory.body }),
|
|
18099
|
+
"utf8"
|
|
18100
|
+
);
|
|
17959
18101
|
await writeFile40(
|
|
17960
18102
|
loser.filePath,
|
|
17961
|
-
serializeMemory31({
|
|
17962
|
-
frontmatter: {
|
|
17963
|
-
...loser.memory.frontmatter,
|
|
17964
|
-
status: "deprecated",
|
|
17965
|
-
stale_reason: plan.stale_reason,
|
|
17966
|
-
related_ids: [.../* @__PURE__ */ new Set([...loser.memory.frontmatter.related_ids, plan.keep_id])]
|
|
17967
|
-
},
|
|
17968
|
-
body: loser.memory.body
|
|
17969
|
-
}),
|
|
18103
|
+
serializeMemory31({ frontmatter: applied.loser, body: loser.memory.body }),
|
|
17970
18104
|
"utf8"
|
|
17971
18105
|
);
|
|
17972
|
-
if (!opts.json)
|
|
18106
|
+
if (!opts.json) {
|
|
18107
|
+
ui.success(`Deprecated ${plan.supersede_id}; promoted ${plan.keep_id} (rev ${applied.winner.revision_count}${applied.topic ? `, topic=${applied.topic}` : ""}).`);
|
|
18108
|
+
}
|
|
17973
18109
|
});
|
|
17974
18110
|
}
|
|
17975
18111
|
|
|
17976
18112
|
// src/commands/memory-seed-git.ts
|
|
17977
|
-
import { execFile as
|
|
18113
|
+
import { execFile as execFile6 } from "child_process";
|
|
17978
18114
|
import { mkdir as mkdir25, writeFile as writeFile41 } from "fs/promises";
|
|
17979
|
-
import { existsSync as
|
|
17980
|
-
import
|
|
17981
|
-
import { promisify as
|
|
18115
|
+
import { existsSync as existsSync84 } from "fs";
|
|
18116
|
+
import path59 from "path";
|
|
18117
|
+
import { promisify as promisify6 } from "util";
|
|
17982
18118
|
import "commander";
|
|
17983
18119
|
import {
|
|
17984
18120
|
buildFrontmatter as buildFrontmatter12,
|
|
@@ -17988,12 +18124,12 @@ import {
|
|
|
17988
18124
|
resolveHaivePaths as resolveHaivePaths54,
|
|
17989
18125
|
serializeMemory as serializeMemory33
|
|
17990
18126
|
} from "@hiveai/core";
|
|
17991
|
-
var exec4 =
|
|
18127
|
+
var exec4 = promisify6(execFile6);
|
|
17992
18128
|
function registerMemorySeedGit(memory2) {
|
|
17993
18129
|
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) => {
|
|
17994
18130
|
const root = findProjectRoot60(opts.dir);
|
|
17995
18131
|
const paths = resolveHaivePaths54(root);
|
|
17996
|
-
if (!
|
|
18132
|
+
if (!existsSync84(paths.haiveDir)) {
|
|
17997
18133
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
17998
18134
|
process.exitCode = 1;
|
|
17999
18135
|
return;
|
|
@@ -18038,8 +18174,8 @@ function registerMemorySeedGit(memory2) {
|
|
|
18038
18174
|
_Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
|
|
18039
18175
|
`;
|
|
18040
18176
|
const file = memoryFilePath13(paths, fm.scope, fm.id, fm.module);
|
|
18041
|
-
if (
|
|
18042
|
-
await mkdir25(
|
|
18177
|
+
if (existsSync84(file)) continue;
|
|
18178
|
+
await mkdir25(path59.dirname(file), { recursive: true });
|
|
18043
18179
|
await writeFile41(file, serializeMemory33({ frontmatter: fm, body }), "utf8");
|
|
18044
18180
|
written += 1;
|
|
18045
18181
|
}
|
|
@@ -18071,8 +18207,8 @@ async function readCommits(root, days) {
|
|
|
18071
18207
|
}
|
|
18072
18208
|
|
|
18073
18209
|
// src/commands/bridges.ts
|
|
18074
|
-
import { existsSync as
|
|
18075
|
-
import
|
|
18210
|
+
import { existsSync as existsSync85 } from "fs";
|
|
18211
|
+
import path60 from "path";
|
|
18076
18212
|
import "commander";
|
|
18077
18213
|
import {
|
|
18078
18214
|
findProjectRoot as findProjectRoot61,
|
|
@@ -18093,7 +18229,7 @@ function registerBridges(program2) {
|
|
|
18093
18229
|
const root = findProjectRoot61(opts.dir);
|
|
18094
18230
|
const paths = resolveHaivePaths55(root);
|
|
18095
18231
|
const dryRun = opts.dryRun === true;
|
|
18096
|
-
if (!
|
|
18232
|
+
if (!existsSync85(paths.memoriesDir)) {
|
|
18097
18233
|
ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
18098
18234
|
process.exitCode = 1;
|
|
18099
18235
|
return;
|
|
@@ -18112,7 +18248,7 @@ function registerBridges(program2) {
|
|
|
18112
18248
|
targets = BRIDGE_TARGETS3;
|
|
18113
18249
|
} else {
|
|
18114
18250
|
targets = BRIDGE_TARGETS3.filter(
|
|
18115
|
-
(t) =>
|
|
18251
|
+
(t) => existsSync85(path60.join(root, BRIDGE_TARGET_PATH3[t]))
|
|
18116
18252
|
);
|
|
18117
18253
|
if (targets.length === 0) {
|
|
18118
18254
|
ui.info(
|
|
@@ -18141,7 +18277,7 @@ function registerBridges(program2) {
|
|
|
18141
18277
|
console.log(ui.bold("hAIve bridge targets:"));
|
|
18142
18278
|
for (const target of BRIDGE_TARGETS3) {
|
|
18143
18279
|
const relPath = BRIDGE_TARGET_PATH3[target];
|
|
18144
|
-
const exists =
|
|
18280
|
+
const exists = existsSync85(path60.join(root, relPath));
|
|
18145
18281
|
const marker = exists ? ui.dim("\u2713") : ui.dim("\xB7");
|
|
18146
18282
|
console.log(` ${marker} ${target.padEnd(10)} ${relPath}${exists ? "" : " (not present)"}`);
|
|
18147
18283
|
}
|
|
@@ -18152,7 +18288,7 @@ function registerBridges(program2) {
|
|
|
18152
18288
|
|
|
18153
18289
|
// src/index.ts
|
|
18154
18290
|
var program = new Command64();
|
|
18155
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
18291
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.23.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
18156
18292
|
registerInit(program);
|
|
18157
18293
|
registerWelcome(program);
|
|
18158
18294
|
registerResolveProject(program);
|