@hiveai/cli 0.9.17 → 0.9.18
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 +1278 -987
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -198,7 +198,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
198
198
|
if (!f) continue;
|
|
199
199
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
200
200
|
}
|
|
201
|
-
let entries = [...counts.entries()].map(([
|
|
201
|
+
let entries = [...counts.entries()].map(([path50, changes]) => ({ path: path50, changes }));
|
|
202
202
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
203
203
|
if (lowerPaths.length > 0) {
|
|
204
204
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -2462,7 +2462,7 @@ function registerInit(program2) {
|
|
|
2462
2462
|
console.log(ui.dim(` \u2713 Stack memory packs pre-seeded (${stacksToSeed.join(", ")})`));
|
|
2463
2463
|
}
|
|
2464
2464
|
console.log();
|
|
2465
|
-
if (!
|
|
2465
|
+
if (!wantBootstrap) {
|
|
2466
2466
|
console.log(ui.bold("One remaining step (optional but recommended):"));
|
|
2467
2467
|
console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 fill project-context.md without AI"));
|
|
2468
2468
|
console.log(" " + ui.dim("Or in your AI client: invoke the MCP prompt ") + ui.bold("bootstrap_project"));
|
|
@@ -2477,7 +2477,7 @@ function registerInit(program2) {
|
|
|
2477
2477
|
console.log(ui.dim(" haive memory import README.md \u2014 from README / docs"));
|
|
2478
2478
|
} else {
|
|
2479
2479
|
console.log(ui.bold("Next steps:"));
|
|
2480
|
-
if (!
|
|
2480
|
+
if (!wantBootstrap) {
|
|
2481
2481
|
console.log(ui.dim(" 1. Fill project context (pick one):"));
|
|
2482
2482
|
console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 instant, no AI needed"));
|
|
2483
2483
|
console.log(" or invoke the MCP prompt " + ui.bold("bootstrap_project") + ui.dim(" in your AI client"));
|
|
@@ -6515,7 +6515,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
6515
6515
|
};
|
|
6516
6516
|
}
|
|
6517
6517
|
var SERVER_NAME = "haive";
|
|
6518
|
-
var SERVER_VERSION = "0.9.
|
|
6518
|
+
var SERVER_VERSION = "0.9.18";
|
|
6519
6519
|
function jsonResult(data) {
|
|
6520
6520
|
return {
|
|
6521
6521
|
content: [
|
|
@@ -7483,30 +7483,511 @@ function registerMcp(program2) {
|
|
|
7483
7483
|
}
|
|
7484
7484
|
|
|
7485
7485
|
// src/commands/sync.ts
|
|
7486
|
-
import { spawnSync as
|
|
7487
|
-
import { readFile as
|
|
7488
|
-
import { existsSync as
|
|
7489
|
-
import
|
|
7486
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
7487
|
+
import { readFile as readFile9, writeFile as writeFile15, mkdir as mkdir10 } from "fs/promises";
|
|
7488
|
+
import { existsSync as existsSync31 } from "fs";
|
|
7489
|
+
import path15 from "path";
|
|
7490
7490
|
import "commander";
|
|
7491
7491
|
import {
|
|
7492
7492
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
7493
7493
|
buildFrontmatter as buildFrontmatter6,
|
|
7494
|
-
findProjectRoot as
|
|
7495
|
-
getUsage as
|
|
7494
|
+
findProjectRoot as findProjectRoot12,
|
|
7495
|
+
getUsage as getUsage11,
|
|
7496
7496
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
7497
7497
|
isDecaying as isDecaying2,
|
|
7498
|
-
loadCodeMap as
|
|
7499
|
-
loadConfig as
|
|
7500
|
-
loadMemoriesFromDir as
|
|
7501
|
-
loadUsageIndex as
|
|
7498
|
+
loadCodeMap as loadCodeMap6,
|
|
7499
|
+
loadConfig as loadConfig5,
|
|
7500
|
+
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
7501
|
+
loadUsageIndex as loadUsageIndex13,
|
|
7502
7502
|
pullCrossRepoSources,
|
|
7503
|
-
resolveHaivePaths as
|
|
7503
|
+
resolveHaivePaths as resolveHaivePaths9,
|
|
7504
7504
|
resolveManifestFiles,
|
|
7505
|
-
serializeMemory as
|
|
7505
|
+
serializeMemory as serializeMemory12,
|
|
7506
7506
|
trackDependencies,
|
|
7507
7507
|
verifyAnchor as verifyAnchor2,
|
|
7508
7508
|
watchContracts
|
|
7509
7509
|
} from "@hiveai/core";
|
|
7510
|
+
|
|
7511
|
+
// src/utils/autopilot.ts
|
|
7512
|
+
import { existsSync as existsSync30 } from "fs";
|
|
7513
|
+
import { readFile as readFile8, writeFile as writeFile14 } from "fs/promises";
|
|
7514
|
+
import path14 from "path";
|
|
7515
|
+
import {
|
|
7516
|
+
AUTOPILOT_DEFAULTS as AUTOPILOT_DEFAULTS2,
|
|
7517
|
+
buildCodeMap as buildCodeMap3,
|
|
7518
|
+
loadCodeMap as loadCodeMap5,
|
|
7519
|
+
loadConfig as loadConfig4,
|
|
7520
|
+
saveCodeMap as saveCodeMap3,
|
|
7521
|
+
saveConfig as saveConfig2
|
|
7522
|
+
} from "@hiveai/core";
|
|
7523
|
+
|
|
7524
|
+
// src/commands/memory-lint.ts
|
|
7525
|
+
import { existsSync as existsSync29 } from "fs";
|
|
7526
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
7527
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7528
|
+
import path13 from "path";
|
|
7529
|
+
import "commander";
|
|
7530
|
+
import {
|
|
7531
|
+
findProjectRoot as findProjectRoot11,
|
|
7532
|
+
getUsage as getUsage10,
|
|
7533
|
+
loadCodeMap as loadCodeMap4,
|
|
7534
|
+
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
7535
|
+
loadUsageIndex as loadUsageIndex12,
|
|
7536
|
+
resolveHaivePaths as resolveHaivePaths8,
|
|
7537
|
+
serializeMemory as serializeMemory11
|
|
7538
|
+
} from "@hiveai/core";
|
|
7539
|
+
async function lintMemoriesAsync(root, options = {}) {
|
|
7540
|
+
const paths = resolveHaivePaths8(root);
|
|
7541
|
+
const out = [];
|
|
7542
|
+
const fixes = [];
|
|
7543
|
+
if (!existsSync29(paths.memoriesDir)) return { findings: out, fixes };
|
|
7544
|
+
const loaded = await loadMemoriesFromDir23(paths.memoriesDir);
|
|
7545
|
+
const usage = await loadUsageIndex12(paths);
|
|
7546
|
+
const codeMap = await loadCodeMap4(paths);
|
|
7547
|
+
const trackedFiles = gitTrackedFiles(root);
|
|
7548
|
+
const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
|
|
7549
|
+
const actionableWords = /\b(always|never|prefer|use|run|avoid|because|instead|why|rationale|do not|must|should|require|required|requires|fix|fail|failed|fails|prevent|prevents|allow|allows|lets|ensure|ensures|catch|catches)\b/i;
|
|
7550
|
+
for (const { filePath, memory: memory2 } of loaded) {
|
|
7551
|
+
const fm = memory2.frontmatter;
|
|
7552
|
+
if (fm.type === "session_recap") continue;
|
|
7553
|
+
const body = memory2.body.trim();
|
|
7554
|
+
const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
|
|
7555
|
+
if (naked.length < 40 && fm.status !== "rejected") {
|
|
7556
|
+
out.push({
|
|
7557
|
+
file: filePath,
|
|
7558
|
+
id: fm.id,
|
|
7559
|
+
severity: "warn",
|
|
7560
|
+
code: "SHORT_BODY",
|
|
7561
|
+
message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
|
|
7562
|
+
});
|
|
7563
|
+
}
|
|
7564
|
+
if (["decision", "gotcha", "convention", "architecture", "attempt"].includes(fm.type) && fm.status !== "rejected" && !actionableWords.test(naked)) {
|
|
7565
|
+
out.push({
|
|
7566
|
+
file: filePath,
|
|
7567
|
+
id: fm.id,
|
|
7568
|
+
severity: "info",
|
|
7569
|
+
code: "LOW_ACTIONABILITY",
|
|
7570
|
+
message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
|
|
7571
|
+
});
|
|
7572
|
+
}
|
|
7573
|
+
const suggestedAnchors = suggestAnchors(root, { filePath, memory: memory2 }, codeMap, trackedFiles);
|
|
7574
|
+
if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
|
|
7575
|
+
out.push({
|
|
7576
|
+
file: filePath,
|
|
7577
|
+
id: fm.id,
|
|
7578
|
+
severity: "warn",
|
|
7579
|
+
code: "MISSING_ANCHOR",
|
|
7580
|
+
message: `${fm.type} is validated without anchor paths \u2014 add anchor.paths so haive sync can flag staleness.`,
|
|
7581
|
+
...suggestedAnchors.paths.length > 0 || suggestedAnchors.symbols.length > 0 ? { suggested_anchors: suggestedAnchors } : {}
|
|
7582
|
+
});
|
|
7583
|
+
}
|
|
7584
|
+
if (fm.status === "stale" && !fm.stale_reason) {
|
|
7585
|
+
out.push({
|
|
7586
|
+
file: filePath,
|
|
7587
|
+
id: fm.id,
|
|
7588
|
+
severity: "info",
|
|
7589
|
+
code: "STALE_NO_REASON",
|
|
7590
|
+
message: "Status is stale but stale_reason is empty \u2014 document why when possible."
|
|
7591
|
+
});
|
|
7592
|
+
}
|
|
7593
|
+
if (fm.type === "glossary" && naked.length > 6e3) {
|
|
7594
|
+
out.push({
|
|
7595
|
+
file: filePath,
|
|
7596
|
+
id: fm.id,
|
|
7597
|
+
severity: "info",
|
|
7598
|
+
code: "LONG_GLOSSARY",
|
|
7599
|
+
message: "Very long glossary \u2014 consider splitting concepts for tighter briefings."
|
|
7600
|
+
});
|
|
7601
|
+
}
|
|
7602
|
+
const hasMarkdownHeading = /^#{1,3}\s+\S/m.test(memory2.body.trim());
|
|
7603
|
+
if (!hasMarkdownHeading) {
|
|
7604
|
+
out.push({
|
|
7605
|
+
file: filePath,
|
|
7606
|
+
id: fm.id,
|
|
7607
|
+
severity: "warn",
|
|
7608
|
+
code: "NO_MD_HEADING",
|
|
7609
|
+
message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
|
|
7610
|
+
});
|
|
7611
|
+
}
|
|
7612
|
+
const u = getUsage10(usage, fm.id);
|
|
7613
|
+
if (fm.status === "validated" && u.read_count === 0) {
|
|
7614
|
+
out.push({
|
|
7615
|
+
file: filePath,
|
|
7616
|
+
id: fm.id,
|
|
7617
|
+
severity: "info",
|
|
7618
|
+
code: "NEVER_READ",
|
|
7619
|
+
message: "Validated record has never been surfaced/read. Consider improving tags/anchors or archiving it if it is not useful."
|
|
7620
|
+
});
|
|
7621
|
+
}
|
|
7622
|
+
if (options.fix) {
|
|
7623
|
+
const actions = [];
|
|
7624
|
+
let nextBody = memory2.body;
|
|
7625
|
+
let nextFrontmatter = memory2.frontmatter;
|
|
7626
|
+
if (!hasMarkdownHeading) {
|
|
7627
|
+
nextBody = `# ${titleFromId(fm.id)}
|
|
7628
|
+
|
|
7629
|
+
${nextBody.trim()}`;
|
|
7630
|
+
actions.push("add missing Markdown heading");
|
|
7631
|
+
}
|
|
7632
|
+
if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated" && suggestedAnchors.paths.length > 0) {
|
|
7633
|
+
nextFrontmatter = {
|
|
7634
|
+
...nextFrontmatter,
|
|
7635
|
+
anchor: {
|
|
7636
|
+
...nextFrontmatter.anchor,
|
|
7637
|
+
paths: [.../* @__PURE__ */ new Set([...nextFrontmatter.anchor.paths, ...suggestedAnchors.paths])],
|
|
7638
|
+
symbols: [
|
|
7639
|
+
.../* @__PURE__ */ new Set([...nextFrontmatter.anchor.symbols, ...suggestedAnchors.symbols])
|
|
7640
|
+
]
|
|
7641
|
+
},
|
|
7642
|
+
tags: nextFrontmatter.tags.filter((tag) => tag !== "needs_anchor")
|
|
7643
|
+
};
|
|
7644
|
+
actions.push("add suggested tracked anchor paths");
|
|
7645
|
+
if (suggestedAnchors.symbols.length > 0) {
|
|
7646
|
+
actions.push("add suggested anchor symbols");
|
|
7647
|
+
}
|
|
7648
|
+
}
|
|
7649
|
+
if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.anchor.symbols.length === 0 && suggestedAnchors.paths.length === 0 && fm.status === "validated" && !fm.tags.includes("needs_anchor")) {
|
|
7650
|
+
nextFrontmatter = {
|
|
7651
|
+
...nextFrontmatter,
|
|
7652
|
+
tags: [...nextFrontmatter.tags, "needs_anchor"]
|
|
7653
|
+
};
|
|
7654
|
+
actions.push("tag validated anchorless record with needs_anchor");
|
|
7655
|
+
}
|
|
7656
|
+
if (actions.length > 0) {
|
|
7657
|
+
fixes.push({ file: filePath, id: fm.id, actions, applied: Boolean(options.apply) });
|
|
7658
|
+
if (options.apply) {
|
|
7659
|
+
await writeFile13(
|
|
7660
|
+
filePath,
|
|
7661
|
+
serializeMemory11({ frontmatter: nextFrontmatter, body: nextBody }),
|
|
7662
|
+
"utf8"
|
|
7663
|
+
);
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
}
|
|
7667
|
+
}
|
|
7668
|
+
for (const dup of nearDuplicatePairs(loaded)) {
|
|
7669
|
+
out.push({
|
|
7670
|
+
file: dup.file,
|
|
7671
|
+
id: dup.id,
|
|
7672
|
+
severity: "warn",
|
|
7673
|
+
code: "NEAR_DUPLICATE",
|
|
7674
|
+
message: `Body overlaps ~${Math.round(dup.score * 100)}% with ${dup.otherId}. Merge or deprecate one record to reduce briefing noise.`
|
|
7675
|
+
});
|
|
7676
|
+
}
|
|
7677
|
+
return { findings: out, fixes };
|
|
7678
|
+
}
|
|
7679
|
+
function titleFromId(id) {
|
|
7680
|
+
const withoutDate = id.replace(/^\d{4}-\d{2}-\d{2}-/, "");
|
|
7681
|
+
return withoutDate.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
7682
|
+
}
|
|
7683
|
+
function suggestAnchors(root, loaded, codeMap, trackedFiles) {
|
|
7684
|
+
const body = loaded.memory.body;
|
|
7685
|
+
const paths = /* @__PURE__ */ new Set();
|
|
7686
|
+
const symbols = /* @__PURE__ */ new Set();
|
|
7687
|
+
for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
|
|
7688
|
+
const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
|
|
7689
|
+
if (!candidate || candidate.startsWith("http")) continue;
|
|
7690
|
+
if (existsSync29(path13.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles)) {
|
|
7691
|
+
paths.add(candidate);
|
|
7692
|
+
}
|
|
7693
|
+
}
|
|
7694
|
+
if (codeMap) {
|
|
7695
|
+
const lowered = body.toLowerCase();
|
|
7696
|
+
for (const [file, entry] of Object.entries(codeMap.files)) {
|
|
7697
|
+
for (const exp of entry.exports) {
|
|
7698
|
+
if (!exp.name || exp.name.length < 4) continue;
|
|
7699
|
+
if (lowered.includes(exp.name.toLowerCase())) {
|
|
7700
|
+
if (isSafeAnchorPath(file, trackedFiles)) {
|
|
7701
|
+
paths.add(file);
|
|
7702
|
+
symbols.add(exp.name);
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
if (paths.size >= 5 && symbols.size >= 5) break;
|
|
7706
|
+
}
|
|
7707
|
+
if (paths.size >= 5 && symbols.size >= 5) break;
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7710
|
+
return {
|
|
7711
|
+
paths: [...paths].slice(0, 5),
|
|
7712
|
+
symbols: [...symbols].slice(0, 5)
|
|
7713
|
+
};
|
|
7714
|
+
}
|
|
7715
|
+
function gitTrackedFiles(root) {
|
|
7716
|
+
const result = spawnSync3("git", ["ls-files"], {
|
|
7717
|
+
cwd: root,
|
|
7718
|
+
encoding: "utf8",
|
|
7719
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
7720
|
+
});
|
|
7721
|
+
if (result.status !== 0) return null;
|
|
7722
|
+
const files = result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
7723
|
+
return new Set(files);
|
|
7724
|
+
}
|
|
7725
|
+
function isSafeAnchorPath(file, trackedFiles) {
|
|
7726
|
+
const normalized = file.replace(/\\/g, "/").replace(/^\.?\//, "");
|
|
7727
|
+
if (normalized.startsWith(".ai/.cache/") || normalized.startsWith(".ai/.runtime/")) return false;
|
|
7728
|
+
if (normalized.includes("/node_modules/") || normalized.startsWith("node_modules/")) return false;
|
|
7729
|
+
if (normalized.includes("/dist/") || normalized.startsWith("dist/")) return false;
|
|
7730
|
+
if (trackedFiles && !trackedFiles.has(normalized)) return false;
|
|
7731
|
+
return true;
|
|
7732
|
+
}
|
|
7733
|
+
function nearDuplicatePairs(loaded) {
|
|
7734
|
+
const out = [];
|
|
7735
|
+
const candidates = loaded.filter(({ memory: memory2 }) => {
|
|
7736
|
+
const fm = memory2.frontmatter;
|
|
7737
|
+
return fm.type !== "session_recap" && fm.status !== "rejected" && fm.status !== "deprecated";
|
|
7738
|
+
});
|
|
7739
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
7740
|
+
for (let j = i + 1; j < candidates.length; j++) {
|
|
7741
|
+
const a = candidates[i];
|
|
7742
|
+
const b = candidates[j];
|
|
7743
|
+
if (a.memory.frontmatter.scope !== b.memory.frontmatter.scope) continue;
|
|
7744
|
+
if (a.memory.frontmatter.type !== b.memory.frontmatter.type) continue;
|
|
7745
|
+
const score = jaccard2(tokenSet(a.memory.body), tokenSet(b.memory.body));
|
|
7746
|
+
if (score >= 0.72) {
|
|
7747
|
+
out.push({
|
|
7748
|
+
id: a.memory.frontmatter.id,
|
|
7749
|
+
otherId: b.memory.frontmatter.id,
|
|
7750
|
+
file: a.filePath,
|
|
7751
|
+
score
|
|
7752
|
+
});
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
}
|
|
7756
|
+
return out;
|
|
7757
|
+
}
|
|
7758
|
+
function tokenSet(body) {
|
|
7759
|
+
return new Set(
|
|
7760
|
+
(body.toLowerCase().match(/\b[a-z0-9]{4,}\b/g) ?? []).filter((word) => !["this", "that", "with", "from", "have"].includes(word))
|
|
7761
|
+
);
|
|
7762
|
+
}
|
|
7763
|
+
function jaccard2(a, b) {
|
|
7764
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
7765
|
+
let inter = 0;
|
|
7766
|
+
for (const item of a) if (b.has(item)) inter++;
|
|
7767
|
+
return inter / (a.size + b.size - inter);
|
|
7768
|
+
}
|
|
7769
|
+
function registerMemoryLint(parent) {
|
|
7770
|
+
parent.command("lint").description(
|
|
7771
|
+
"Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
|
|
7772
|
+
).option("--json", "emit findings as JSON", false).option("--fix", "prepare simple automatic fixes (use with --dry-run or --apply)", false).option("--dry-run", "with --fix, show files that would change without writing", false).option("--apply", "with --fix, write simple fixes to disk", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7773
|
+
const root = findProjectRoot11(opts.dir);
|
|
7774
|
+
const apply = Boolean(opts.fix && opts.apply);
|
|
7775
|
+
const dryRun = Boolean(opts.fix && (opts.dryRun || !opts.apply));
|
|
7776
|
+
const report = await lintMemoriesAsync(root, { fix: Boolean(opts.fix), apply });
|
|
7777
|
+
const findings = report.findings;
|
|
7778
|
+
if (opts.json) {
|
|
7779
|
+
console.log(JSON.stringify({
|
|
7780
|
+
findings_count: findings.length,
|
|
7781
|
+
findings,
|
|
7782
|
+
fixes_count: report.fixes.length,
|
|
7783
|
+
fixes: report.fixes,
|
|
7784
|
+
fix_mode: opts.fix ? apply ? "apply" : "dry-run" : "off"
|
|
7785
|
+
}, null, 2));
|
|
7786
|
+
process.exitCode = findings.some((f) => f.severity === "error") ? 1 : 0;
|
|
7787
|
+
return;
|
|
7788
|
+
}
|
|
7789
|
+
if (findings.length === 0) {
|
|
7790
|
+
ui.success(`memory lint OK \u2014 ${root}`);
|
|
7791
|
+
return;
|
|
7792
|
+
}
|
|
7793
|
+
console.log(ui.bold(`memory lint (${findings.length} finding${findings.length === 1 ? "" : "s"})`) + `
|
|
7794
|
+
`);
|
|
7795
|
+
if (opts.fix) {
|
|
7796
|
+
const mode = apply ? "apply" : dryRun ? "dry-run" : "dry-run";
|
|
7797
|
+
const verb = apply ? "changed" : "would change";
|
|
7798
|
+
console.log(ui.bold(`fix ${mode}: ${report.fixes.length} file${report.fixes.length === 1 ? "" : "s"} ${verb}`));
|
|
7799
|
+
for (const fix of report.fixes) {
|
|
7800
|
+
console.log(` ${ui.dim(fix.id)} ${fix.actions.join("; ")}`);
|
|
7801
|
+
console.log(ui.dim(` \u2192 ${fix.file}`));
|
|
7802
|
+
}
|
|
7803
|
+
console.log();
|
|
7804
|
+
}
|
|
7805
|
+
const order = { error: 0, warn: 1, info: 2 };
|
|
7806
|
+
findings.sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
|
|
7807
|
+
for (const f of findings) {
|
|
7808
|
+
const color = f.severity === "error" ? ui.red : f.severity === "warn" ? ui.yellow : ui.dim;
|
|
7809
|
+
console.log(
|
|
7810
|
+
`${color(f.severity.padEnd(5))} ${ui.dim(f.code)} ${f.id}`
|
|
7811
|
+
);
|
|
7812
|
+
console.log(` ${f.message}`);
|
|
7813
|
+
if (f.suggested_anchors) {
|
|
7814
|
+
const pathHints = f.suggested_anchors.paths.length > 0 ? `paths: ${f.suggested_anchors.paths.join(", ")}` : "";
|
|
7815
|
+
const symbolHints = f.suggested_anchors.symbols.length > 0 ? `symbols: ${f.suggested_anchors.symbols.join(", ")}` : "";
|
|
7816
|
+
console.log(ui.dim(` suggested anchors: ${[pathHints, symbolHints].filter(Boolean).join(" \xB7 ")}`));
|
|
7817
|
+
}
|
|
7818
|
+
console.log(ui.dim(` \u2192 ${f.file}`));
|
|
7819
|
+
}
|
|
7820
|
+
process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
|
|
7821
|
+
});
|
|
7822
|
+
}
|
|
7823
|
+
|
|
7824
|
+
// src/utils/autopilot.ts
|
|
7825
|
+
async function applyAutopilotRepairs(root, paths, options = {}) {
|
|
7826
|
+
const repairs = [];
|
|
7827
|
+
const config = await loadConfig4(paths);
|
|
7828
|
+
if (options.applyConfig) {
|
|
7829
|
+
const changed = await ensureAutopilotConfig(paths, config);
|
|
7830
|
+
if (changed) {
|
|
7831
|
+
repairs.push({
|
|
7832
|
+
code: "autopilot-config",
|
|
7833
|
+
message: "Enabled autopilot defaults in .ai/haive.config.json."
|
|
7834
|
+
});
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
const current = await loadConfig4(paths);
|
|
7838
|
+
const autoRepair = current.autoRepair ?? {};
|
|
7839
|
+
if (options.applyContext ?? autoRepair.context ?? current.autopilot) {
|
|
7840
|
+
const changed = await syncProjectContextVersion(root, paths);
|
|
7841
|
+
if (changed) {
|
|
7842
|
+
repairs.push({
|
|
7843
|
+
code: "project-context-version",
|
|
7844
|
+
message: "Updated .ai/project-context.md version metadata from package.json."
|
|
7845
|
+
});
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
if (options.applyCorpus ?? autoRepair.corpus ?? current.autopilot) {
|
|
7849
|
+
const report = await lintMemoriesAsync(root, { fix: true, apply: true });
|
|
7850
|
+
const applied = report.fixes.filter((fix) => fix.applied);
|
|
7851
|
+
if (applied.length > 0) {
|
|
7852
|
+
repairs.push({
|
|
7853
|
+
code: "memory-lint-fix",
|
|
7854
|
+
message: `Applied ${applied.length} safe memory lint fix${applied.length === 1 ? "" : "es"}.`
|
|
7855
|
+
});
|
|
7856
|
+
}
|
|
7857
|
+
}
|
|
7858
|
+
if (options.applyCodeMap ?? autoRepair.codeMap ?? current.autopilot) {
|
|
7859
|
+
const refreshed = await refreshCodeMap(root, paths, Boolean(options.forceCodeMap));
|
|
7860
|
+
if (refreshed) {
|
|
7861
|
+
repairs.push({
|
|
7862
|
+
code: "code-map-refresh",
|
|
7863
|
+
message: "Refreshed .ai/code-map.json."
|
|
7864
|
+
});
|
|
7865
|
+
}
|
|
7866
|
+
}
|
|
7867
|
+
if (options.applyCodeSearch ?? autoRepair.codeSearch ?? current.autopilot) {
|
|
7868
|
+
const indexed = await refreshCodeSearchIndex(paths);
|
|
7869
|
+
if (indexed) {
|
|
7870
|
+
repairs.push({
|
|
7871
|
+
code: "code-search-index",
|
|
7872
|
+
message: "Refreshed code-search embeddings index."
|
|
7873
|
+
});
|
|
7874
|
+
}
|
|
7875
|
+
}
|
|
7876
|
+
return repairs;
|
|
7877
|
+
}
|
|
7878
|
+
async function ensureAutopilotConfig(paths, currentConfig) {
|
|
7879
|
+
const current = currentConfig ?? await loadConfig4(paths);
|
|
7880
|
+
const next = {
|
|
7881
|
+
...current,
|
|
7882
|
+
autopilot: true,
|
|
7883
|
+
defaultScope: "team",
|
|
7884
|
+
defaultStatus: "validated",
|
|
7885
|
+
autoApproveDelayHours: current.autoApproveDelayHours ?? AUTOPILOT_DEFAULTS2.autoApproveDelayHours,
|
|
7886
|
+
autoPromoteMinReads: current.autoPromoteMinReads ?? AUTOPILOT_DEFAULTS2.autoPromoteMinReads,
|
|
7887
|
+
autoSessionEnd: true,
|
|
7888
|
+
autoContext: true,
|
|
7889
|
+
autoRepair: {
|
|
7890
|
+
context: true,
|
|
7891
|
+
corpus: true,
|
|
7892
|
+
codeMap: true,
|
|
7893
|
+
codeSearch: current.autoRepair?.codeSearch ?? true
|
|
7894
|
+
},
|
|
7895
|
+
enforcement: {
|
|
7896
|
+
...AUTOPILOT_DEFAULTS2.enforcement,
|
|
7897
|
+
...current.enforcement,
|
|
7898
|
+
mode: "strict",
|
|
7899
|
+
requireBriefingFirst: true,
|
|
7900
|
+
requireSessionRecap: true,
|
|
7901
|
+
requireMemoryVerify: true,
|
|
7902
|
+
blockStaleDecisionChanges: true,
|
|
7903
|
+
requireDecisionCoverage: true,
|
|
7904
|
+
cleanupGeneratedArtifacts: true,
|
|
7905
|
+
toolProfile: current.enforcement?.toolProfile ?? "enforcement"
|
|
7906
|
+
}
|
|
7907
|
+
};
|
|
7908
|
+
if (JSON.stringify(current) === JSON.stringify(next)) return false;
|
|
7909
|
+
await saveConfig2(paths, next);
|
|
7910
|
+
return true;
|
|
7911
|
+
}
|
|
7912
|
+
async function syncProjectContextVersion(root, paths) {
|
|
7913
|
+
const status = await projectContextVersionStatus(root, paths);
|
|
7914
|
+
if (!status.canSync || !status.expectedVersion) return false;
|
|
7915
|
+
const original = await readFile8(paths.projectContext, "utf8");
|
|
7916
|
+
let updated = original.replace(
|
|
7917
|
+
/^# Project context — hAIve \(v[^)]+\)$/m,
|
|
7918
|
+
`# Project context \u2014 hAIve (v${status.expectedVersion})`
|
|
7919
|
+
).replace(
|
|
7920
|
+
/> \*\*Current version\*\*: [^—\n]+—/m,
|
|
7921
|
+
`> **Current version**: ${status.expectedVersion} \u2014`
|
|
7922
|
+
);
|
|
7923
|
+
if (updated === original && !original.includes("Current version")) {
|
|
7924
|
+
updated = original.replace(
|
|
7925
|
+
/^(> Repo-native context enforcement[^\n]*\n)/m,
|
|
7926
|
+
`$1> **Current version**: ${status.expectedVersion} \u2014 @hiveai/core, cli, mcp, embeddings are versioned together.
|
|
7927
|
+
`
|
|
7928
|
+
);
|
|
7929
|
+
}
|
|
7930
|
+
if (updated === original) return false;
|
|
7931
|
+
await writeFile14(paths.projectContext, updated, "utf8");
|
|
7932
|
+
return true;
|
|
7933
|
+
}
|
|
7934
|
+
async function projectContextVersionStatus(root, paths) {
|
|
7935
|
+
if (!existsSync30(paths.projectContext)) {
|
|
7936
|
+
return { mismatch: false, canSync: false };
|
|
7937
|
+
}
|
|
7938
|
+
const packagePath = path14.join(root, "package.json");
|
|
7939
|
+
if (!existsSync30(packagePath)) {
|
|
7940
|
+
return { mismatch: false, canSync: false };
|
|
7941
|
+
}
|
|
7942
|
+
const packageJson = JSON.parse(await readFile8(packagePath, "utf8"));
|
|
7943
|
+
const expectedVersion = packageJson.version;
|
|
7944
|
+
if (!expectedVersion) {
|
|
7945
|
+
return { mismatch: false, canSync: false };
|
|
7946
|
+
}
|
|
7947
|
+
const content = await readFile8(paths.projectContext, "utf8");
|
|
7948
|
+
const headingVersion = content.match(/^# Project context — hAIve \(v([^)]+)\)$/m)?.[1];
|
|
7949
|
+
const currentLineVersion = content.match(/^> \*\*Current version\*\*: ([^—\n]+)—/m)?.[1]?.trim();
|
|
7950
|
+
const currentVersion = currentLineVersion ?? headingVersion;
|
|
7951
|
+
return {
|
|
7952
|
+
expectedVersion,
|
|
7953
|
+
currentVersion,
|
|
7954
|
+
mismatch: currentVersion !== expectedVersion,
|
|
7955
|
+
canSync: true
|
|
7956
|
+
};
|
|
7957
|
+
}
|
|
7958
|
+
async function refreshCodeMap(root, paths, force) {
|
|
7959
|
+
if (!force) {
|
|
7960
|
+
const existing = await loadCodeMap5(paths);
|
|
7961
|
+
if (existing) return false;
|
|
7962
|
+
}
|
|
7963
|
+
const map = await buildCodeMap3(root, {
|
|
7964
|
+
excludeDirs: [
|
|
7965
|
+
"node_modules",
|
|
7966
|
+
"dist",
|
|
7967
|
+
"build",
|
|
7968
|
+
"out",
|
|
7969
|
+
".git",
|
|
7970
|
+
".next",
|
|
7971
|
+
".turbo",
|
|
7972
|
+
".vitest-cache",
|
|
7973
|
+
"coverage"
|
|
7974
|
+
]
|
|
7975
|
+
});
|
|
7976
|
+
await saveCodeMap3(paths, map);
|
|
7977
|
+
return true;
|
|
7978
|
+
}
|
|
7979
|
+
async function refreshCodeSearchIndex(paths) {
|
|
7980
|
+
try {
|
|
7981
|
+
const mod = await import("@hiveai/embeddings");
|
|
7982
|
+
const embedder = await mod.Embedder.create();
|
|
7983
|
+
const { report } = await mod.rebuildCodeIndex(paths, embedder);
|
|
7984
|
+
return report.added > 0 || report.updated > 0 || report.removed > 0;
|
|
7985
|
+
} catch {
|
|
7986
|
+
return false;
|
|
7987
|
+
}
|
|
7988
|
+
}
|
|
7989
|
+
|
|
7990
|
+
// src/commands/sync.ts
|
|
7510
7991
|
var BRIDGE_START = "<!-- haive:memories-start -->";
|
|
7511
7992
|
var BRIDGE_END = "<!-- haive:memories-end -->";
|
|
7512
7993
|
function registerSync(program2) {
|
|
@@ -7519,9 +8000,9 @@ function registerSync(program2) {
|
|
|
7519
8000
|
"--inject-bridge",
|
|
7520
8001
|
"inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
7521
8002
|
).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").action(async (opts) => {
|
|
7522
|
-
const root =
|
|
7523
|
-
const paths =
|
|
7524
|
-
if (!
|
|
8003
|
+
const root = findProjectRoot12(opts.dir);
|
|
8004
|
+
const paths = resolveHaivePaths9(root);
|
|
8005
|
+
if (!existsSync31(paths.memoriesDir)) {
|
|
7525
8006
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
7526
8007
|
process.exitCode = 1;
|
|
7527
8008
|
return;
|
|
@@ -7529,21 +8010,22 @@ function registerSync(program2) {
|
|
|
7529
8010
|
const log = (msg) => {
|
|
7530
8011
|
if (!opts.quiet) console.log(msg);
|
|
7531
8012
|
};
|
|
7532
|
-
const config = await
|
|
8013
|
+
const config = await loadConfig5(paths);
|
|
7533
8014
|
const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
|
|
7534
8015
|
const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads;
|
|
8016
|
+
const autoRepair = config.autoRepair ?? {};
|
|
7535
8017
|
let staleMarked = 0;
|
|
7536
8018
|
let revalidated = 0;
|
|
7537
8019
|
let promoted = 0;
|
|
7538
8020
|
let autoApproved = 0;
|
|
7539
8021
|
if (opts.verify !== false) {
|
|
7540
|
-
const memories = await
|
|
8022
|
+
const memories = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
7541
8023
|
for (const { memory: memory2, filePath } of memories) {
|
|
7542
8024
|
if (memory2.frontmatter.type === "session_recap") {
|
|
7543
8025
|
if (memory2.frontmatter.status === "stale") {
|
|
7544
|
-
await
|
|
8026
|
+
await writeFile15(
|
|
7545
8027
|
filePath,
|
|
7546
|
-
|
|
8028
|
+
serializeMemory12({
|
|
7547
8029
|
frontmatter: {
|
|
7548
8030
|
...memory2.frontmatter,
|
|
7549
8031
|
status: "validated",
|
|
@@ -7564,9 +8046,9 @@ function registerSync(program2) {
|
|
|
7564
8046
|
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7565
8047
|
if (result.stale) {
|
|
7566
8048
|
if (memory2.frontmatter.status !== "stale") {
|
|
7567
|
-
await
|
|
8049
|
+
await writeFile15(
|
|
7568
8050
|
filePath,
|
|
7569
|
-
|
|
8051
|
+
serializeMemory12({
|
|
7570
8052
|
frontmatter: {
|
|
7571
8053
|
...memory2.frontmatter,
|
|
7572
8054
|
status: "stale",
|
|
@@ -7580,9 +8062,9 @@ function registerSync(program2) {
|
|
|
7580
8062
|
staleMarked++;
|
|
7581
8063
|
}
|
|
7582
8064
|
} else if (memory2.frontmatter.status === "stale") {
|
|
7583
|
-
await
|
|
8065
|
+
await writeFile15(
|
|
7584
8066
|
filePath,
|
|
7585
|
-
|
|
8067
|
+
serializeMemory12({
|
|
7586
8068
|
frontmatter: {
|
|
7587
8069
|
...memory2.frontmatter,
|
|
7588
8070
|
status: "validated",
|
|
@@ -7598,19 +8080,19 @@ function registerSync(program2) {
|
|
|
7598
8080
|
}
|
|
7599
8081
|
}
|
|
7600
8082
|
if (opts.promote !== false) {
|
|
7601
|
-
const memories = await
|
|
7602
|
-
const usage = await
|
|
8083
|
+
const memories = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
8084
|
+
const usage = await loadUsageIndex13(paths);
|
|
7603
8085
|
const nowMs = Date.now();
|
|
7604
8086
|
for (const { memory: memory2, filePath } of memories) {
|
|
7605
8087
|
const fm = memory2.frontmatter;
|
|
7606
8088
|
if (fm.type === "session_recap") continue;
|
|
7607
|
-
if (isAutoPromoteEligible2(fm,
|
|
8089
|
+
if (isAutoPromoteEligible2(fm, getUsage11(usage, fm.id), {
|
|
7608
8090
|
minReads: autoPromoteMinReads,
|
|
7609
8091
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
|
|
7610
8092
|
})) {
|
|
7611
|
-
await
|
|
8093
|
+
await writeFile15(
|
|
7612
8094
|
filePath,
|
|
7613
|
-
|
|
8095
|
+
serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
7614
8096
|
"utf8"
|
|
7615
8097
|
);
|
|
7616
8098
|
promoted++;
|
|
@@ -7619,9 +8101,9 @@ function registerSync(program2) {
|
|
|
7619
8101
|
if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
|
|
7620
8102
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
7621
8103
|
if (ageHours >= autoApproveDelayHours) {
|
|
7622
|
-
await
|
|
8104
|
+
await writeFile15(
|
|
7623
8105
|
filePath,
|
|
7624
|
-
|
|
8106
|
+
serializeMemory12({
|
|
7625
8107
|
frontmatter: {
|
|
7626
8108
|
...fm,
|
|
7627
8109
|
status: "validated",
|
|
@@ -7636,8 +8118,17 @@ function registerSync(program2) {
|
|
|
7636
8118
|
}
|
|
7637
8119
|
}
|
|
7638
8120
|
}
|
|
8121
|
+
if (config.autopilot || autoRepair.context || autoRepair.corpus) {
|
|
8122
|
+
const repairs = await applyAutopilotRepairs(root, paths, {
|
|
8123
|
+
applyContext: autoRepair.context ?? config.autopilot,
|
|
8124
|
+
applyCorpus: autoRepair.corpus ?? config.autopilot,
|
|
8125
|
+
applyCodeMap: false,
|
|
8126
|
+
applyCodeSearch: false
|
|
8127
|
+
});
|
|
8128
|
+
for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
|
|
8129
|
+
}
|
|
7639
8130
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
7640
|
-
const draftMemories = (await
|
|
8131
|
+
const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
|
|
7641
8132
|
(m) => m.memory.frontmatter.status === "draft"
|
|
7642
8133
|
);
|
|
7643
8134
|
const draftCount = draftMemories.length;
|
|
@@ -7653,7 +8144,7 @@ function registerSync(program2) {
|
|
|
7653
8144
|
);
|
|
7654
8145
|
}
|
|
7655
8146
|
if (opts.injectBridge) {
|
|
7656
|
-
const bridgeFile = opts.bridgeFile ?
|
|
8147
|
+
const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
|
|
7657
8148
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
7658
8149
|
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
7659
8150
|
}
|
|
@@ -7672,12 +8163,12 @@ function registerSync(program2) {
|
|
|
7672
8163
|
}
|
|
7673
8164
|
}
|
|
7674
8165
|
if (!opts.quiet) {
|
|
7675
|
-
const allForDecay = await
|
|
7676
|
-
const usageForDecay = await
|
|
8166
|
+
const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
8167
|
+
const usageForDecay = await loadUsageIndex13(paths);
|
|
7677
8168
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
7678
8169
|
const fm = memory2.frontmatter;
|
|
7679
8170
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
7680
|
-
const u =
|
|
8171
|
+
const u = getUsage11(usageForDecay, fm.id);
|
|
7681
8172
|
return isDecaying2(u, fm.created_at);
|
|
7682
8173
|
});
|
|
7683
8174
|
if (decaying.length > 0) {
|
|
@@ -7759,11 +8250,11 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7759
8250
|
paths: [result.file],
|
|
7760
8251
|
topic: `dep-bump-${slugParts}`
|
|
7761
8252
|
});
|
|
7762
|
-
const teamDir =
|
|
8253
|
+
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
7763
8254
|
await mkdir10(teamDir, { recursive: true });
|
|
7764
|
-
await
|
|
7765
|
-
|
|
7766
|
-
|
|
8255
|
+
await writeFile15(
|
|
8256
|
+
path15.join(teamDir, `${fm.id}.md`),
|
|
8257
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
7767
8258
|
"utf8"
|
|
7768
8259
|
);
|
|
7769
8260
|
log(ui.yellow(` \u2192 memory created: ${fm.id}`));
|
|
@@ -7826,11 +8317,11 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7826
8317
|
paths: [diff.file],
|
|
7827
8318
|
topic: `contract-breaking-${diff.contract}`
|
|
7828
8319
|
});
|
|
7829
|
-
const teamDir =
|
|
8320
|
+
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
7830
8321
|
await mkdir10(teamDir, { recursive: true });
|
|
7831
|
-
await
|
|
7832
|
-
|
|
7833
|
-
|
|
8322
|
+
await writeFile15(
|
|
8323
|
+
path15.join(teamDir, `${fm.id}.md`),
|
|
8324
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
7834
8325
|
"utf8"
|
|
7835
8326
|
);
|
|
7836
8327
|
log(ui.yellow(` \u2192 memory created: ${fm.id}`));
|
|
@@ -7840,10 +8331,19 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7840
8331
|
ui.warn(`contract watcher failed: ${String(err)}`);
|
|
7841
8332
|
}
|
|
7842
8333
|
}
|
|
7843
|
-
const existingMap = await
|
|
7844
|
-
if (existingMap) {
|
|
8334
|
+
const existingMap = await loadCodeMap6(paths);
|
|
8335
|
+
if (!existingMap && (config.autopilot || autoRepair.codeMap)) {
|
|
8336
|
+
try {
|
|
8337
|
+
const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
|
|
8338
|
+
log(ui.dim("code-map: missing \u2014 building index\u2026"));
|
|
8339
|
+
const newMap = await buildCodeMap4(root);
|
|
8340
|
+
await saveCodeMap4(paths, newMap);
|
|
8341
|
+
log(ui.dim(`code-map: built (${Object.keys(newMap.files).length} files)`));
|
|
8342
|
+
} catch {
|
|
8343
|
+
}
|
|
8344
|
+
} else if (existingMap) {
|
|
7845
8345
|
const mapAge = new Date(existingMap.generated_at).getTime();
|
|
7846
|
-
const gitResult =
|
|
8346
|
+
const gitResult = spawnSync4(
|
|
7847
8347
|
"git",
|
|
7848
8348
|
[
|
|
7849
8349
|
"diff",
|
|
@@ -7868,22 +8368,32 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7868
8368
|
const changedSourceFiles = (gitResult.stdout ?? "").trim();
|
|
7869
8369
|
if (changedSourceFiles.length > 0) {
|
|
7870
8370
|
try {
|
|
7871
|
-
const { buildCodeMap:
|
|
8371
|
+
const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
|
|
7872
8372
|
log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
|
|
7873
|
-
const newMap = await
|
|
7874
|
-
await
|
|
8373
|
+
const newMap = await buildCodeMap4(root);
|
|
8374
|
+
await saveCodeMap4(paths, newMap);
|
|
7875
8375
|
log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
|
|
7876
8376
|
} catch {
|
|
7877
8377
|
}
|
|
7878
8378
|
}
|
|
7879
8379
|
}
|
|
7880
|
-
if (opts.embed) {
|
|
8380
|
+
if (opts.embed || autoRepair.codeSearch) {
|
|
7881
8381
|
try {
|
|
7882
|
-
const { Embedder, rebuildIndex } = await import("@hiveai/embeddings");
|
|
8382
|
+
const { Embedder, rebuildCodeIndex, rebuildIndex } = await import("@hiveai/embeddings");
|
|
7883
8383
|
log(ui.dim("embed: rebuilding index\u2026"));
|
|
7884
8384
|
const embedder = await Embedder.create();
|
|
7885
8385
|
const { report } = await rebuildIndex(paths, embedder);
|
|
7886
|
-
|
|
8386
|
+
const { report: codeReport } = await rebuildCodeIndex(paths, embedder);
|
|
8387
|
+
log(
|
|
8388
|
+
ui.dim(
|
|
8389
|
+
`embed: memory index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`
|
|
8390
|
+
)
|
|
8391
|
+
);
|
|
8392
|
+
log(
|
|
8393
|
+
ui.dim(
|
|
8394
|
+
`embed: code index rebuilt (${codeReport.total} symbols, ${codeReport.added} added, ${codeReport.updated} updated, ${codeReport.removed} removed)`
|
|
8395
|
+
)
|
|
8396
|
+
);
|
|
7887
8397
|
} catch {
|
|
7888
8398
|
ui.warn("--embed: @hiveai/embeddings not available or index build failed. Run `haive embeddings index` manually.");
|
|
7889
8399
|
}
|
|
@@ -7891,8 +8401,8 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7891
8401
|
});
|
|
7892
8402
|
}
|
|
7893
8403
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
7894
|
-
if (!
|
|
7895
|
-
const all = await
|
|
8404
|
+
if (!existsSync31(memoriesDir)) return;
|
|
8405
|
+
const all = await loadMemoriesFromDir24(memoriesDir);
|
|
7896
8406
|
const top = all.filter(({ memory: memory2 }) => {
|
|
7897
8407
|
const s = memory2.frontmatter.status;
|
|
7898
8408
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
@@ -7916,17 +8426,17 @@ ${m.memory.body.trim()}`;
|
|
|
7916
8426
|
` + block + `
|
|
7917
8427
|
|
|
7918
8428
|
${BRIDGE_END}`;
|
|
7919
|
-
const fileExists =
|
|
7920
|
-
let existing = fileExists ? await
|
|
8429
|
+
const fileExists = existsSync31(bridgeFile);
|
|
8430
|
+
let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
|
|
7921
8431
|
existing = existing.replace(/\r\n/g, "\n");
|
|
7922
8432
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
7923
8433
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
7924
8434
|
if (startIdx !== -1 && endIdx === -1) {
|
|
7925
|
-
ui.warn(`${
|
|
8435
|
+
ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
7926
8436
|
return;
|
|
7927
8437
|
}
|
|
7928
8438
|
if (startIdx === -1 && endIdx !== -1) {
|
|
7929
|
-
ui.warn(`${
|
|
8439
|
+
ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
7930
8440
|
return;
|
|
7931
8441
|
}
|
|
7932
8442
|
let updated;
|
|
@@ -7934,19 +8444,19 @@ ${BRIDGE_END}`;
|
|
|
7934
8444
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
7935
8445
|
} else {
|
|
7936
8446
|
if (!fileExists && !quiet) {
|
|
7937
|
-
ui.info(`Creating ${
|
|
8447
|
+
ui.info(`Creating ${path15.relative(root, bridgeFile)} with haive memory block.`);
|
|
7938
8448
|
}
|
|
7939
8449
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
7940
8450
|
}
|
|
7941
|
-
await
|
|
8451
|
+
await writeFile15(bridgeFile, updated, "utf8");
|
|
7942
8452
|
if (!quiet) {
|
|
7943
8453
|
console.log(
|
|
7944
|
-
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${
|
|
8454
|
+
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
|
|
7945
8455
|
);
|
|
7946
8456
|
}
|
|
7947
8457
|
}
|
|
7948
8458
|
function collectSinceChanges(root, ref) {
|
|
7949
|
-
const result =
|
|
8459
|
+
const result = spawnSync4(
|
|
7950
8460
|
"git",
|
|
7951
8461
|
["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
|
|
7952
8462
|
{ encoding: "utf8" }
|
|
@@ -7966,18 +8476,19 @@ function collectSinceChanges(root, ref) {
|
|
|
7966
8476
|
|
|
7967
8477
|
// src/commands/memory-add.ts
|
|
7968
8478
|
import { createHash as createHash2 } from "crypto";
|
|
7969
|
-
import { mkdir as mkdir11, readFile as
|
|
7970
|
-
import { existsSync as
|
|
7971
|
-
import
|
|
8479
|
+
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile16 } from "fs/promises";
|
|
8480
|
+
import { existsSync as existsSync33 } from "fs";
|
|
8481
|
+
import path16 from "path";
|
|
7972
8482
|
import "commander";
|
|
7973
8483
|
import {
|
|
7974
8484
|
buildFrontmatter as buildFrontmatter7,
|
|
7975
|
-
findProjectRoot as
|
|
8485
|
+
findProjectRoot as findProjectRoot13,
|
|
7976
8486
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
7977
|
-
|
|
8487
|
+
loadConfig as loadConfig6,
|
|
8488
|
+
loadMemoriesFromDir as loadMemoriesFromDir25,
|
|
7978
8489
|
memoryFilePath as memoryFilePath6,
|
|
7979
|
-
resolveHaivePaths as
|
|
7980
|
-
serializeMemory as
|
|
8490
|
+
resolveHaivePaths as resolveHaivePaths10,
|
|
8491
|
+
serializeMemory as serializeMemory13
|
|
7981
8492
|
} from "@hiveai/core";
|
|
7982
8493
|
function registerMemoryAdd(memory2) {
|
|
7983
8494
|
memory2.command("add").description(
|
|
@@ -8003,21 +8514,22 @@ function registerMemoryAdd(memory2) {
|
|
|
8003
8514
|
haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
|
|
8004
8515
|
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
8005
8516
|
`
|
|
8006
|
-
).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default:
|
|
8007
|
-
const root =
|
|
8008
|
-
const paths =
|
|
8009
|
-
if (!
|
|
8517
|
+
).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8518
|
+
const root = findProjectRoot13(opts.dir);
|
|
8519
|
+
const paths = resolveHaivePaths10(root);
|
|
8520
|
+
if (!existsSync33(paths.haiveDir)) {
|
|
8010
8521
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
8011
8522
|
process.exitCode = 1;
|
|
8012
8523
|
return;
|
|
8013
8524
|
}
|
|
8525
|
+
const config = await loadConfig6(paths);
|
|
8014
8526
|
const userTags = parseCsv2(opts.tags);
|
|
8015
8527
|
const anchorPaths = parseCsv2(opts.paths);
|
|
8016
8528
|
const autoTagsEnabled = opts.autoTag !== false;
|
|
8017
8529
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
8018
8530
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
8019
8531
|
if (anchorPaths.length > 0) {
|
|
8020
|
-
const missing = anchorPaths.filter((p) => !
|
|
8532
|
+
const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
|
|
8021
8533
|
if (missing.length > 0) {
|
|
8022
8534
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
8023
8535
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -8029,12 +8541,12 @@ function registerMemoryAdd(memory2) {
|
|
|
8029
8541
|
const title = opts.title ?? opts.slug;
|
|
8030
8542
|
let body;
|
|
8031
8543
|
if (opts.bodyFile !== void 0) {
|
|
8032
|
-
if (!
|
|
8544
|
+
if (!existsSync33(opts.bodyFile)) {
|
|
8033
8545
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
8034
8546
|
process.exitCode = 1;
|
|
8035
8547
|
return;
|
|
8036
8548
|
}
|
|
8037
|
-
const fileContent = await
|
|
8549
|
+
const fileContent = await readFile10(opts.bodyFile, "utf8");
|
|
8038
8550
|
body = opts.title ? `# ${opts.title}
|
|
8039
8551
|
|
|
8040
8552
|
${fileContent.trim()}
|
|
@@ -8049,10 +8561,10 @@ ${opts.body}` : opts.body;
|
|
|
8049
8561
|
TODO \u2014 write the memory body.
|
|
8050
8562
|
`;
|
|
8051
8563
|
}
|
|
8052
|
-
const scope = opts.scope ?? "personal";
|
|
8053
|
-
if (
|
|
8564
|
+
const scope = opts.scope ?? config.defaultScope ?? "personal";
|
|
8565
|
+
if (existsSync33(paths.memoriesDir)) {
|
|
8054
8566
|
const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
8055
|
-
const allForHash = await
|
|
8567
|
+
const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8056
8568
|
const hashDup = allForHash.find(
|
|
8057
8569
|
({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
|
|
8058
8570
|
);
|
|
@@ -8063,8 +8575,8 @@ TODO \u2014 write the memory body.
|
|
|
8063
8575
|
return;
|
|
8064
8576
|
}
|
|
8065
8577
|
}
|
|
8066
|
-
if (opts.topic &&
|
|
8067
|
-
const existing = await
|
|
8578
|
+
if (opts.topic && existsSync33(paths.memoriesDir)) {
|
|
8579
|
+
const existing = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8068
8580
|
const topicMatch = existing.find(
|
|
8069
8581
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
8070
8582
|
);
|
|
@@ -8081,8 +8593,8 @@ TODO \u2014 write the memory body.
|
|
|
8081
8593
|
symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
|
|
8082
8594
|
}
|
|
8083
8595
|
};
|
|
8084
|
-
await
|
|
8085
|
-
ui.success(`Updated (topic upsert) ${
|
|
8596
|
+
await writeFile16(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
|
|
8597
|
+
ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
|
|
8086
8598
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
8087
8599
|
return;
|
|
8088
8600
|
}
|
|
@@ -8098,17 +8610,18 @@ TODO \u2014 write the memory body.
|
|
|
8098
8610
|
paths: anchorPaths,
|
|
8099
8611
|
symbols: parseCsv2(opts.symbols),
|
|
8100
8612
|
commit: opts.commit,
|
|
8101
|
-
topic: opts.topic
|
|
8613
|
+
topic: opts.topic,
|
|
8614
|
+
status: config.defaultStatus === "validated" ? "validated" : void 0
|
|
8102
8615
|
});
|
|
8103
8616
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
8104
|
-
await mkdir11(
|
|
8105
|
-
if (
|
|
8617
|
+
await mkdir11(path16.dirname(file), { recursive: true });
|
|
8618
|
+
if (existsSync33(file)) {
|
|
8106
8619
|
ui.error(`Memory already exists at ${file}`);
|
|
8107
8620
|
process.exitCode = 1;
|
|
8108
8621
|
return;
|
|
8109
8622
|
}
|
|
8110
|
-
if (
|
|
8111
|
-
const existing = await
|
|
8623
|
+
if (existsSync33(paths.memoriesDir)) {
|
|
8624
|
+
const existing = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8112
8625
|
const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
8113
8626
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
8114
8627
|
const id = memory3.frontmatter.id.toLowerCase();
|
|
@@ -8119,8 +8632,8 @@ TODO \u2014 write the memory body.
|
|
|
8119
8632
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
8120
8633
|
}
|
|
8121
8634
|
}
|
|
8122
|
-
await
|
|
8123
|
-
ui.success(`Created ${
|
|
8635
|
+
await writeFile16(file, serializeMemory13({ frontmatter, body }), "utf8");
|
|
8636
|
+
ui.success(`Created ${path16.relative(root, file)}`);
|
|
8124
8637
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
8125
8638
|
if (inferredTags.length > 0) {
|
|
8126
8639
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
@@ -8131,7 +8644,9 @@ TODO \u2014 write the memory body.
|
|
|
8131
8644
|
Add file anchors: haive memory update ${frontmatter.id} --paths <file1,file2>`
|
|
8132
8645
|
);
|
|
8133
8646
|
}
|
|
8134
|
-
if (
|
|
8647
|
+
if (frontmatter.status === "validated") {
|
|
8648
|
+
console.log(ui.dim("\u2192 autopilot: memory is already validated and active"));
|
|
8649
|
+
} else if (scope === "personal") {
|
|
8135
8650
|
console.log(
|
|
8136
8651
|
ui.dim(
|
|
8137
8652
|
`\u2192 next: haive memory approve ${frontmatter.id} (activate) | haive memory promote ${frontmatter.id} (share with team)`
|
|
@@ -8150,14 +8665,14 @@ function parseCsv2(value) {
|
|
|
8150
8665
|
}
|
|
8151
8666
|
|
|
8152
8667
|
// src/commands/memory-list.ts
|
|
8153
|
-
import { existsSync as
|
|
8154
|
-
import
|
|
8668
|
+
import { existsSync as existsSync34 } from "fs";
|
|
8669
|
+
import path17 from "path";
|
|
8155
8670
|
import "commander";
|
|
8156
|
-
import { findProjectRoot as
|
|
8671
|
+
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
8157
8672
|
|
|
8158
8673
|
// src/utils/fs.ts
|
|
8159
8674
|
import {
|
|
8160
|
-
loadMemoriesFromDir as
|
|
8675
|
+
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
8161
8676
|
loadMemory,
|
|
8162
8677
|
listMarkdownFilesRecursive
|
|
8163
8678
|
} from "@hiveai/core";
|
|
@@ -8165,14 +8680,14 @@ import {
|
|
|
8165
8680
|
// src/commands/memory-list.ts
|
|
8166
8681
|
function registerMemoryList(memory2) {
|
|
8167
8682
|
memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8168
|
-
const root =
|
|
8169
|
-
const paths =
|
|
8170
|
-
if (!
|
|
8683
|
+
const root = findProjectRoot14(opts.dir);
|
|
8684
|
+
const paths = resolveHaivePaths11(root);
|
|
8685
|
+
if (!existsSync34(paths.memoriesDir)) {
|
|
8171
8686
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
8172
8687
|
process.exitCode = 1;
|
|
8173
8688
|
return;
|
|
8174
8689
|
}
|
|
8175
|
-
const all = await
|
|
8690
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8176
8691
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
8177
8692
|
const filtered = all.filter((m) => {
|
|
8178
8693
|
if (!matchesFilters(m, opts)) return false;
|
|
@@ -8199,7 +8714,7 @@ function registerMemoryList(memory2) {
|
|
|
8199
8714
|
console.log(
|
|
8200
8715
|
`${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
|
|
8201
8716
|
);
|
|
8202
|
-
console.log(` ${ui.dim(
|
|
8717
|
+
console.log(` ${ui.dim(path17.relative(root, filePath))}`);
|
|
8203
8718
|
}
|
|
8204
8719
|
console.log(ui.dim(`
|
|
8205
8720
|
${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
|
|
@@ -8234,26 +8749,26 @@ function matchesFilters(loaded, opts) {
|
|
|
8234
8749
|
}
|
|
8235
8750
|
|
|
8236
8751
|
// src/commands/memory-promote.ts
|
|
8237
|
-
import { mkdir as mkdir12, unlink as unlink2, writeFile as
|
|
8238
|
-
import { existsSync as
|
|
8239
|
-
import
|
|
8752
|
+
import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
|
|
8753
|
+
import { existsSync as existsSync35 } from "fs";
|
|
8754
|
+
import path18 from "path";
|
|
8240
8755
|
import "commander";
|
|
8241
8756
|
import {
|
|
8242
|
-
findProjectRoot as
|
|
8757
|
+
findProjectRoot as findProjectRoot15,
|
|
8243
8758
|
memoryFilePath as memoryFilePath7,
|
|
8244
|
-
resolveHaivePaths as
|
|
8245
|
-
serializeMemory as
|
|
8759
|
+
resolveHaivePaths as resolveHaivePaths12,
|
|
8760
|
+
serializeMemory as serializeMemory14
|
|
8246
8761
|
} from "@hiveai/core";
|
|
8247
8762
|
function registerMemoryPromote(memory2) {
|
|
8248
8763
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
8249
|
-
const root =
|
|
8250
|
-
const paths =
|
|
8251
|
-
if (!
|
|
8764
|
+
const root = findProjectRoot15(opts.dir);
|
|
8765
|
+
const paths = resolveHaivePaths12(root);
|
|
8766
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
8252
8767
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
8253
8768
|
process.exitCode = 1;
|
|
8254
8769
|
return;
|
|
8255
8770
|
}
|
|
8256
|
-
const teamAndModule = await
|
|
8771
|
+
const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8257
8772
|
const alreadyShared = teamAndModule.find(
|
|
8258
8773
|
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
8259
8774
|
);
|
|
@@ -8267,7 +8782,7 @@ function registerMemoryPromote(memory2) {
|
|
|
8267
8782
|
}
|
|
8268
8783
|
return;
|
|
8269
8784
|
}
|
|
8270
|
-
const all = await
|
|
8785
|
+
const all = await loadMemoriesFromDir26(paths.personalDir);
|
|
8271
8786
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
8272
8787
|
if (!found) {
|
|
8273
8788
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -8283,35 +8798,35 @@ function registerMemoryPromote(memory2) {
|
|
|
8283
8798
|
body: found.memory.body
|
|
8284
8799
|
};
|
|
8285
8800
|
const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
|
|
8286
|
-
await mkdir12(
|
|
8287
|
-
await
|
|
8801
|
+
await mkdir12(path18.dirname(newPath), { recursive: true });
|
|
8802
|
+
await writeFile17(newPath, serializeMemory14(updated), "utf8");
|
|
8288
8803
|
await unlink2(found.filePath);
|
|
8289
8804
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
8290
|
-
ui.info(`Now at ${
|
|
8805
|
+
ui.info(`Now at ${path18.relative(root, newPath)}`);
|
|
8291
8806
|
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
8292
8807
|
});
|
|
8293
8808
|
}
|
|
8294
8809
|
|
|
8295
8810
|
// src/commands/memory-approve.ts
|
|
8296
|
-
import { existsSync as
|
|
8297
|
-
import { writeFile as
|
|
8298
|
-
import
|
|
8811
|
+
import { existsSync as existsSync36 } from "fs";
|
|
8812
|
+
import { writeFile as writeFile18 } from "fs/promises";
|
|
8813
|
+
import path19 from "path";
|
|
8299
8814
|
import "commander";
|
|
8300
8815
|
import {
|
|
8301
|
-
findProjectRoot as
|
|
8302
|
-
resolveHaivePaths as
|
|
8303
|
-
serializeMemory as
|
|
8816
|
+
findProjectRoot as findProjectRoot16,
|
|
8817
|
+
resolveHaivePaths as resolveHaivePaths13,
|
|
8818
|
+
serializeMemory as serializeMemory15
|
|
8304
8819
|
} from "@hiveai/core";
|
|
8305
8820
|
function registerMemoryApprove(memory2) {
|
|
8306
8821
|
memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
8307
|
-
const root =
|
|
8308
|
-
const paths =
|
|
8309
|
-
if (!
|
|
8822
|
+
const root = findProjectRoot16(opts.dir);
|
|
8823
|
+
const paths = resolveHaivePaths13(root);
|
|
8824
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
8310
8825
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8311
8826
|
process.exitCode = 1;
|
|
8312
8827
|
return;
|
|
8313
8828
|
}
|
|
8314
|
-
const all = await
|
|
8829
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8315
8830
|
if (opts.all || opts.pending) {
|
|
8316
8831
|
const candidates = all.filter((m) => {
|
|
8317
8832
|
const s = m.memory.frontmatter.status;
|
|
@@ -8328,7 +8843,7 @@ function registerMemoryApprove(memory2) {
|
|
|
8328
8843
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
8329
8844
|
body: found2.memory.body
|
|
8330
8845
|
};
|
|
8331
|
-
await
|
|
8846
|
+
await writeFile18(found2.filePath, serializeMemory15(next2), "utf8");
|
|
8332
8847
|
count++;
|
|
8333
8848
|
}
|
|
8334
8849
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -8357,32 +8872,32 @@ function registerMemoryApprove(memory2) {
|
|
|
8357
8872
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
8358
8873
|
body: found.memory.body
|
|
8359
8874
|
};
|
|
8360
|
-
await
|
|
8875
|
+
await writeFile18(found.filePath, serializeMemory15(next), "utf8");
|
|
8361
8876
|
ui.success(`Approved ${id} (status=validated)`);
|
|
8362
|
-
ui.info(
|
|
8877
|
+
ui.info(path19.relative(root, found.filePath));
|
|
8363
8878
|
});
|
|
8364
8879
|
}
|
|
8365
8880
|
|
|
8366
8881
|
// src/commands/memory-update.ts
|
|
8367
|
-
import { writeFile as
|
|
8368
|
-
import { existsSync as
|
|
8369
|
-
import
|
|
8882
|
+
import { writeFile as writeFile19 } from "fs/promises";
|
|
8883
|
+
import { existsSync as existsSync37 } from "fs";
|
|
8884
|
+
import path20 from "path";
|
|
8370
8885
|
import "commander";
|
|
8371
8886
|
import {
|
|
8372
|
-
findProjectRoot as
|
|
8373
|
-
resolveHaivePaths as
|
|
8374
|
-
serializeMemory as
|
|
8887
|
+
findProjectRoot as findProjectRoot17,
|
|
8888
|
+
resolveHaivePaths as resolveHaivePaths14,
|
|
8889
|
+
serializeMemory as serializeMemory16
|
|
8375
8890
|
} from "@hiveai/core";
|
|
8376
8891
|
function registerMemoryUpdate(memory2) {
|
|
8377
8892
|
memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
8378
|
-
const root =
|
|
8379
|
-
const paths =
|
|
8380
|
-
if (!
|
|
8893
|
+
const root = findProjectRoot17(opts.dir);
|
|
8894
|
+
const paths = resolveHaivePaths14(root);
|
|
8895
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
8381
8896
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8382
8897
|
process.exitCode = 1;
|
|
8383
8898
|
return;
|
|
8384
8899
|
}
|
|
8385
|
-
const memories = await
|
|
8900
|
+
const memories = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8386
8901
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
8387
8902
|
if (!loaded) {
|
|
8388
8903
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -8424,12 +8939,12 @@ function registerMemoryUpdate(memory2) {
|
|
|
8424
8939
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
8425
8940
|
return;
|
|
8426
8941
|
}
|
|
8427
|
-
await
|
|
8942
|
+
await writeFile19(
|
|
8428
8943
|
loaded.filePath,
|
|
8429
|
-
|
|
8944
|
+
serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
|
|
8430
8945
|
"utf8"
|
|
8431
8946
|
);
|
|
8432
|
-
ui.success(`Updated ${
|
|
8947
|
+
ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
|
|
8433
8948
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
8434
8949
|
});
|
|
8435
8950
|
}
|
|
@@ -8448,18 +8963,18 @@ function parseCsv3(value) {
|
|
|
8448
8963
|
}
|
|
8449
8964
|
|
|
8450
8965
|
// src/commands/memory-auto-promote.ts
|
|
8451
|
-
import { writeFile as
|
|
8452
|
-
import { existsSync as
|
|
8453
|
-
import
|
|
8966
|
+
import { writeFile as writeFile20 } from "fs/promises";
|
|
8967
|
+
import { existsSync as existsSync38 } from "fs";
|
|
8968
|
+
import path21 from "path";
|
|
8454
8969
|
import "commander";
|
|
8455
8970
|
import {
|
|
8456
8971
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
|
|
8457
|
-
findProjectRoot as
|
|
8458
|
-
getUsage as
|
|
8972
|
+
findProjectRoot as findProjectRoot18,
|
|
8973
|
+
getUsage as getUsage12,
|
|
8459
8974
|
isAutoPromoteEligible as isAutoPromoteEligible3,
|
|
8460
|
-
loadUsageIndex as
|
|
8461
|
-
resolveHaivePaths as
|
|
8462
|
-
serializeMemory as
|
|
8975
|
+
loadUsageIndex as loadUsageIndex14,
|
|
8976
|
+
resolveHaivePaths as resolveHaivePaths15,
|
|
8977
|
+
serializeMemory as serializeMemory17
|
|
8463
8978
|
} from "@hiveai/core";
|
|
8464
8979
|
function registerMemoryAutoPromote(memory2) {
|
|
8465
8980
|
memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE3.minReads)).option(
|
|
@@ -8467,9 +8982,9 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
8467
8982
|
"memories with more rejections than this are skipped",
|
|
8468
8983
|
String(DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
8469
8984
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8470
|
-
const root =
|
|
8471
|
-
const paths =
|
|
8472
|
-
if (!
|
|
8985
|
+
const root = findProjectRoot18(opts.dir);
|
|
8986
|
+
const paths = resolveHaivePaths15(root);
|
|
8987
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
8473
8988
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8474
8989
|
process.exitCode = 1;
|
|
8475
8990
|
return;
|
|
@@ -8478,10 +8993,10 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
8478
8993
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
|
|
8479
8994
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
8480
8995
|
};
|
|
8481
|
-
const memories = await
|
|
8482
|
-
const usage = await
|
|
8996
|
+
const memories = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8997
|
+
const usage = await loadUsageIndex14(paths);
|
|
8483
8998
|
const eligible = memories.filter(
|
|
8484
|
-
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter,
|
|
8999
|
+
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
|
|
8485
9000
|
);
|
|
8486
9001
|
if (eligible.length === 0) {
|
|
8487
9002
|
ui.info(
|
|
@@ -8491,17 +9006,17 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
8491
9006
|
}
|
|
8492
9007
|
let written = 0;
|
|
8493
9008
|
for (const { memory: mem, filePath } of eligible) {
|
|
8494
|
-
const u =
|
|
9009
|
+
const u = getUsage12(usage, mem.frontmatter.id);
|
|
8495
9010
|
console.log(
|
|
8496
9011
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
8497
9012
|
);
|
|
8498
|
-
console.log(` ${ui.dim(
|
|
9013
|
+
console.log(` ${ui.dim(path21.relative(root, filePath))}`);
|
|
8499
9014
|
if (opts.apply) {
|
|
8500
9015
|
const next = {
|
|
8501
9016
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
8502
9017
|
body: mem.body
|
|
8503
9018
|
};
|
|
8504
|
-
await
|
|
9019
|
+
await writeFile20(filePath, serializeMemory17(next), "utf8");
|
|
8505
9020
|
written++;
|
|
8506
9021
|
}
|
|
8507
9022
|
}
|
|
@@ -8512,25 +9027,25 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
8512
9027
|
|
|
8513
9028
|
// src/commands/memory-edit.ts
|
|
8514
9029
|
import { spawn as spawn3 } from "child_process";
|
|
8515
|
-
import { existsSync as
|
|
8516
|
-
import { readFile as
|
|
8517
|
-
import
|
|
9030
|
+
import { existsSync as existsSync39 } from "fs";
|
|
9031
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
9032
|
+
import path23 from "path";
|
|
8518
9033
|
import "commander";
|
|
8519
9034
|
import {
|
|
8520
|
-
findProjectRoot as
|
|
9035
|
+
findProjectRoot as findProjectRoot19,
|
|
8521
9036
|
parseMemory,
|
|
8522
|
-
resolveHaivePaths as
|
|
9037
|
+
resolveHaivePaths as resolveHaivePaths16
|
|
8523
9038
|
} from "@hiveai/core";
|
|
8524
9039
|
function registerMemoryEdit(memory2) {
|
|
8525
9040
|
memory2.command("edit <id>").description("Open a memory in $EDITOR and re-validate when you save").option("-e, --editor <cmd>", "editor command (defaults to $EDITOR or 'vi')").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
8526
|
-
const root =
|
|
8527
|
-
const paths =
|
|
8528
|
-
if (!
|
|
9041
|
+
const root = findProjectRoot19(opts.dir);
|
|
9042
|
+
const paths = resolveHaivePaths16(root);
|
|
9043
|
+
if (!existsSync39(paths.memoriesDir)) {
|
|
8529
9044
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8530
9045
|
process.exitCode = 1;
|
|
8531
9046
|
return;
|
|
8532
9047
|
}
|
|
8533
|
-
const all = await
|
|
9048
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8534
9049
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
8535
9050
|
if (!found) {
|
|
8536
9051
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -8538,13 +9053,13 @@ function registerMemoryEdit(memory2) {
|
|
|
8538
9053
|
return;
|
|
8539
9054
|
}
|
|
8540
9055
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
8541
|
-
ui.info(`Opening ${
|
|
9056
|
+
ui.info(`Opening ${path23.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
8542
9057
|
const code = await runEditor(editor, found.filePath);
|
|
8543
9058
|
if (code !== 0) {
|
|
8544
9059
|
ui.warn(`Editor exited with status ${code}.`);
|
|
8545
9060
|
}
|
|
8546
9061
|
try {
|
|
8547
|
-
const fresh = await
|
|
9062
|
+
const fresh = await readFile11(found.filePath, "utf8");
|
|
8548
9063
|
parseMemory(fresh);
|
|
8549
9064
|
ui.success("Memory still parses cleanly.");
|
|
8550
9065
|
} catch (err) {
|
|
@@ -8565,29 +9080,29 @@ function runEditor(editor, file) {
|
|
|
8565
9080
|
}
|
|
8566
9081
|
|
|
8567
9082
|
// src/commands/memory-for-files.ts
|
|
8568
|
-
import { existsSync as
|
|
8569
|
-
import
|
|
9083
|
+
import { existsSync as existsSync40 } from "fs";
|
|
9084
|
+
import path24 from "path";
|
|
8570
9085
|
import "commander";
|
|
8571
9086
|
import {
|
|
8572
9087
|
deriveConfidence as deriveConfidence9,
|
|
8573
|
-
findProjectRoot as
|
|
8574
|
-
getUsage as
|
|
9088
|
+
findProjectRoot as findProjectRoot20,
|
|
9089
|
+
getUsage as getUsage13,
|
|
8575
9090
|
inferModulesFromPaths as inferModulesFromPaths4,
|
|
8576
|
-
loadUsageIndex as
|
|
9091
|
+
loadUsageIndex as loadUsageIndex15,
|
|
8577
9092
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
|
|
8578
|
-
resolveHaivePaths as
|
|
9093
|
+
resolveHaivePaths as resolveHaivePaths17
|
|
8579
9094
|
} from "@hiveai/core";
|
|
8580
9095
|
function registerMemoryForFiles(memory2) {
|
|
8581
9096
|
memory2.command("for-files <files...>").description("Show memories relevant to the given files (anchor overlap, module, domain)").option("-d, --dir <dir>", "project root").action(async (files, opts) => {
|
|
8582
|
-
const root =
|
|
8583
|
-
const paths =
|
|
8584
|
-
if (!
|
|
9097
|
+
const root = findProjectRoot20(opts.dir);
|
|
9098
|
+
const paths = resolveHaivePaths17(root);
|
|
9099
|
+
if (!existsSync40(paths.memoriesDir)) {
|
|
8585
9100
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8586
9101
|
process.exitCode = 1;
|
|
8587
9102
|
return;
|
|
8588
9103
|
}
|
|
8589
|
-
const all = await
|
|
8590
|
-
const usage = await
|
|
9104
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9105
|
+
const usage = await loadUsageIndex15(paths);
|
|
8591
9106
|
const inferred = inferModulesFromPaths4(files);
|
|
8592
9107
|
const byAnchor = [];
|
|
8593
9108
|
const byModule = [];
|
|
@@ -8685,44 +9200,44 @@ function printGroup(root, label, loaded, usage) {
|
|
|
8685
9200
|
\u2014 ${label} \u2014`));
|
|
8686
9201
|
for (const { memory: mem, filePath } of loaded) {
|
|
8687
9202
|
const fm = mem.frontmatter;
|
|
8688
|
-
const u =
|
|
9203
|
+
const u = getUsage13(usage, fm.id);
|
|
8689
9204
|
const conf = deriveConfidence9(fm, u);
|
|
8690
9205
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
8691
|
-
console.log(` ${ui.dim(
|
|
9206
|
+
console.log(` ${ui.dim(path24.relative(root, filePath))}`);
|
|
8692
9207
|
}
|
|
8693
9208
|
}
|
|
8694
9209
|
|
|
8695
9210
|
// src/commands/memory-hot.ts
|
|
8696
|
-
import { existsSync as
|
|
8697
|
-
import
|
|
9211
|
+
import { existsSync as existsSync41 } from "fs";
|
|
9212
|
+
import path25 from "path";
|
|
8698
9213
|
import "commander";
|
|
8699
9214
|
import {
|
|
8700
|
-
findProjectRoot as
|
|
8701
|
-
getUsage as
|
|
8702
|
-
loadUsageIndex as
|
|
8703
|
-
resolveHaivePaths as
|
|
9215
|
+
findProjectRoot as findProjectRoot21,
|
|
9216
|
+
getUsage as getUsage14,
|
|
9217
|
+
loadUsageIndex as loadUsageIndex16,
|
|
9218
|
+
resolveHaivePaths as resolveHaivePaths18
|
|
8704
9219
|
} from "@hiveai/core";
|
|
8705
9220
|
function registerMemoryHot(memory2) {
|
|
8706
9221
|
memory2.command("hot").description("List memories actively used but not yet validated (good promotion candidates)").option("--threshold <n>", "minimum read_count to qualify", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8707
|
-
const root =
|
|
8708
|
-
const paths =
|
|
8709
|
-
if (!
|
|
9222
|
+
const root = findProjectRoot21(opts.dir);
|
|
9223
|
+
const paths = resolveHaivePaths18(root);
|
|
9224
|
+
if (!existsSync41(paths.memoriesDir)) {
|
|
8710
9225
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8711
9226
|
process.exitCode = 1;
|
|
8712
9227
|
return;
|
|
8713
9228
|
}
|
|
8714
9229
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
8715
|
-
const all = await
|
|
8716
|
-
const usage = await
|
|
9230
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9231
|
+
const usage = await loadUsageIndex16(paths);
|
|
8717
9232
|
const candidates = all.filter(({ memory: mem }) => {
|
|
8718
9233
|
const fm = mem.frontmatter;
|
|
8719
9234
|
if (opts.status && fm.status !== opts.status) return false;
|
|
8720
9235
|
if (opts.status === void 0 && fm.status !== "draft" && fm.status !== "proposed") {
|
|
8721
9236
|
return false;
|
|
8722
9237
|
}
|
|
8723
|
-
return
|
|
9238
|
+
return getUsage14(usage, fm.id).read_count >= threshold;
|
|
8724
9239
|
}).sort(
|
|
8725
|
-
(a, b) =>
|
|
9240
|
+
(a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
|
|
8726
9241
|
);
|
|
8727
9242
|
if (candidates.length === 0) {
|
|
8728
9243
|
ui.info(`No hot memories (threshold=${threshold}).`);
|
|
@@ -8730,11 +9245,11 @@ function registerMemoryHot(memory2) {
|
|
|
8730
9245
|
}
|
|
8731
9246
|
for (const { memory: mem, filePath } of candidates) {
|
|
8732
9247
|
const fm = mem.frontmatter;
|
|
8733
|
-
const u =
|
|
9248
|
+
const u = getUsage14(usage, fm.id);
|
|
8734
9249
|
console.log(
|
|
8735
9250
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
8736
9251
|
);
|
|
8737
|
-
console.log(` ${ui.dim(
|
|
9252
|
+
console.log(` ${ui.dim(path25.relative(root, filePath))}`);
|
|
8738
9253
|
}
|
|
8739
9254
|
ui.info(
|
|
8740
9255
|
`${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
|
|
@@ -8743,16 +9258,16 @@ function registerMemoryHot(memory2) {
|
|
|
8743
9258
|
}
|
|
8744
9259
|
|
|
8745
9260
|
// src/commands/memory-tried.ts
|
|
8746
|
-
import { mkdir as mkdir13, writeFile as
|
|
8747
|
-
import { existsSync as
|
|
8748
|
-
import
|
|
9261
|
+
import { mkdir as mkdir13, writeFile as writeFile21 } from "fs/promises";
|
|
9262
|
+
import { existsSync as existsSync43 } from "fs";
|
|
9263
|
+
import path26 from "path";
|
|
8749
9264
|
import "commander";
|
|
8750
9265
|
import {
|
|
8751
9266
|
buildFrontmatter as buildFrontmatter8,
|
|
8752
|
-
findProjectRoot as
|
|
9267
|
+
findProjectRoot as findProjectRoot22,
|
|
8753
9268
|
memoryFilePath as memoryFilePath8,
|
|
8754
|
-
resolveHaivePaths as
|
|
8755
|
-
serializeMemory as
|
|
9269
|
+
resolveHaivePaths as resolveHaivePaths19,
|
|
9270
|
+
serializeMemory as serializeMemory18
|
|
8756
9271
|
} from "@hiveai/core";
|
|
8757
9272
|
function registerMemoryTried(memory2) {
|
|
8758
9273
|
memory2.command("tried").description(
|
|
@@ -8771,9 +9286,9 @@ function registerMemoryTried(memory2) {
|
|
|
8771
9286
|
--paths packages/cli/src/index.ts
|
|
8772
9287
|
`
|
|
8773
9288
|
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8774
|
-
const root =
|
|
8775
|
-
const paths =
|
|
8776
|
-
if (!
|
|
9289
|
+
const root = findProjectRoot22(opts.dir);
|
|
9290
|
+
const paths = resolveHaivePaths19(root);
|
|
9291
|
+
if (!existsSync43(paths.haiveDir)) {
|
|
8777
9292
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
8778
9293
|
process.exitCode = 1;
|
|
8779
9294
|
return;
|
|
@@ -8796,14 +9311,14 @@ function registerMemoryTried(memory2) {
|
|
|
8796
9311
|
}
|
|
8797
9312
|
const body = lines.join("\n") + "\n";
|
|
8798
9313
|
const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
8799
|
-
await mkdir13(
|
|
8800
|
-
if (
|
|
9314
|
+
await mkdir13(path26.dirname(file), { recursive: true });
|
|
9315
|
+
if (existsSync43(file)) {
|
|
8801
9316
|
ui.error(`Memory already exists at ${file}`);
|
|
8802
9317
|
process.exitCode = 1;
|
|
8803
9318
|
return;
|
|
8804
9319
|
}
|
|
8805
|
-
await
|
|
8806
|
-
ui.success(`Recorded: ${
|
|
9320
|
+
await writeFile21(file, serializeMemory18({ frontmatter, body }), "utf8");
|
|
9321
|
+
ui.success(`Recorded: ${path26.relative(root, file)}`);
|
|
8807
9322
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
8808
9323
|
});
|
|
8809
9324
|
}
|
|
@@ -8813,26 +9328,26 @@ function parseCsv4(value) {
|
|
|
8813
9328
|
}
|
|
8814
9329
|
|
|
8815
9330
|
// src/commands/memory-pending.ts
|
|
8816
|
-
import { existsSync as
|
|
8817
|
-
import
|
|
9331
|
+
import { existsSync as existsSync44 } from "fs";
|
|
9332
|
+
import path27 from "path";
|
|
8818
9333
|
import "commander";
|
|
8819
9334
|
import {
|
|
8820
|
-
findProjectRoot as
|
|
8821
|
-
getUsage as
|
|
8822
|
-
loadUsageIndex as
|
|
8823
|
-
resolveHaivePaths as
|
|
9335
|
+
findProjectRoot as findProjectRoot23,
|
|
9336
|
+
getUsage as getUsage15,
|
|
9337
|
+
loadUsageIndex as loadUsageIndex17,
|
|
9338
|
+
resolveHaivePaths as resolveHaivePaths20
|
|
8824
9339
|
} from "@hiveai/core";
|
|
8825
9340
|
function registerMemoryPending(memory2) {
|
|
8826
9341
|
memory2.command("pending").description("List 'proposed' memories awaiting review (sorted by reads desc)").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8827
|
-
const root =
|
|
8828
|
-
const paths =
|
|
8829
|
-
if (!
|
|
9342
|
+
const root = findProjectRoot23(opts.dir);
|
|
9343
|
+
const paths = resolveHaivePaths20(root);
|
|
9344
|
+
if (!existsSync44(paths.memoriesDir)) {
|
|
8830
9345
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8831
9346
|
process.exitCode = 1;
|
|
8832
9347
|
return;
|
|
8833
9348
|
}
|
|
8834
|
-
const all = await
|
|
8835
|
-
const usage = await
|
|
9349
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9350
|
+
const usage = await loadUsageIndex17(paths);
|
|
8836
9351
|
const proposed = all.filter(({ memory: mem }) => {
|
|
8837
9352
|
if (mem.frontmatter.status !== "proposed") return false;
|
|
8838
9353
|
if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
|
|
@@ -8843,42 +9358,42 @@ function registerMemoryPending(memory2) {
|
|
|
8843
9358
|
return;
|
|
8844
9359
|
}
|
|
8845
9360
|
proposed.sort(
|
|
8846
|
-
(a, b) =>
|
|
9361
|
+
(a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
|
|
8847
9362
|
);
|
|
8848
9363
|
const now = Date.now();
|
|
8849
9364
|
for (const { memory: mem, filePath } of proposed) {
|
|
8850
9365
|
const fm = mem.frontmatter;
|
|
8851
|
-
const u =
|
|
9366
|
+
const u = getUsage15(usage, fm.id);
|
|
8852
9367
|
const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
|
|
8853
9368
|
const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
|
|
8854
9369
|
console.log(
|
|
8855
9370
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
8856
9371
|
);
|
|
8857
|
-
console.log(` ${ui.dim(
|
|
9372
|
+
console.log(` ${ui.dim(path27.relative(root, filePath))}`);
|
|
8858
9373
|
}
|
|
8859
9374
|
ui.info(`${proposed.length} pending`);
|
|
8860
9375
|
});
|
|
8861
9376
|
}
|
|
8862
9377
|
|
|
8863
9378
|
// src/commands/memory-query.ts
|
|
8864
|
-
import { existsSync as
|
|
8865
|
-
import
|
|
9379
|
+
import { existsSync as existsSync45 } from "fs";
|
|
9380
|
+
import path28 from "path";
|
|
8866
9381
|
import "commander";
|
|
8867
9382
|
import {
|
|
8868
9383
|
extractSnippet as extractSnippet2,
|
|
8869
|
-
findProjectRoot as
|
|
9384
|
+
findProjectRoot as findProjectRoot24,
|
|
8870
9385
|
literalMatchesAllTokens as literalMatchesAllTokens3,
|
|
8871
9386
|
literalMatchesAnyToken as literalMatchesAnyToken4,
|
|
8872
9387
|
pickSnippetNeedle as pickSnippetNeedle2,
|
|
8873
|
-
resolveHaivePaths as
|
|
9388
|
+
resolveHaivePaths as resolveHaivePaths21,
|
|
8874
9389
|
tokenizeQuery as tokenizeQuery6,
|
|
8875
9390
|
trackReads as trackReads4
|
|
8876
9391
|
} from "@hiveai/core";
|
|
8877
9392
|
function registerMemoryQuery(memory2) {
|
|
8878
9393
|
memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
|
|
8879
|
-
const root =
|
|
8880
|
-
const paths =
|
|
8881
|
-
if (!
|
|
9394
|
+
const root = findProjectRoot24(opts.dir);
|
|
9395
|
+
const paths = resolveHaivePaths21(root);
|
|
9396
|
+
if (!existsSync45(paths.memoriesDir)) {
|
|
8882
9397
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
8883
9398
|
process.exitCode = 1;
|
|
8884
9399
|
return;
|
|
@@ -8889,7 +9404,7 @@ function registerMemoryQuery(memory2) {
|
|
|
8889
9404
|
return;
|
|
8890
9405
|
}
|
|
8891
9406
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
8892
|
-
const all = await
|
|
9407
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8893
9408
|
const passesFilters2 = (mem) => {
|
|
8894
9409
|
const fm = mem.frontmatter;
|
|
8895
9410
|
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
@@ -8919,7 +9434,7 @@ function registerMemoryQuery(memory2) {
|
|
|
8919
9434
|
const fm = mem.frontmatter;
|
|
8920
9435
|
const statusBadge = ui.statusBadge(fm.status);
|
|
8921
9436
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
8922
|
-
console.log(` ${ui.dim(
|
|
9437
|
+
console.log(` ${ui.dim(path28.relative(root, filePath))}`);
|
|
8923
9438
|
const snippet = extractSnippet2(mem.body, snippetNeedle);
|
|
8924
9439
|
if (snippet) console.log(` ${snippet}`);
|
|
8925
9440
|
}
|
|
@@ -8936,36 +9451,36 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
8936
9451
|
}
|
|
8937
9452
|
|
|
8938
9453
|
// src/commands/memory-reject.ts
|
|
8939
|
-
import { writeFile as
|
|
8940
|
-
import { existsSync as
|
|
9454
|
+
import { writeFile as writeFile23 } from "fs/promises";
|
|
9455
|
+
import { existsSync as existsSync46 } from "fs";
|
|
8941
9456
|
import "commander";
|
|
8942
9457
|
import {
|
|
8943
|
-
findProjectRoot as
|
|
8944
|
-
loadUsageIndex as
|
|
9458
|
+
findProjectRoot as findProjectRoot25,
|
|
9459
|
+
loadUsageIndex as loadUsageIndex18,
|
|
8945
9460
|
recordRejection as recordRejection2,
|
|
8946
|
-
resolveHaivePaths as
|
|
9461
|
+
resolveHaivePaths as resolveHaivePaths22,
|
|
8947
9462
|
saveUsageIndex as saveUsageIndex3,
|
|
8948
|
-
serializeMemory as
|
|
9463
|
+
serializeMemory as serializeMemory19
|
|
8949
9464
|
} from "@hiveai/core";
|
|
8950
9465
|
function registerMemoryReject(memory2) {
|
|
8951
9466
|
memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
8952
|
-
const root =
|
|
8953
|
-
const paths =
|
|
8954
|
-
if (!
|
|
9467
|
+
const root = findProjectRoot25(opts.dir);
|
|
9468
|
+
const paths = resolveHaivePaths22(root);
|
|
9469
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
8955
9470
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8956
9471
|
process.exitCode = 1;
|
|
8957
9472
|
return;
|
|
8958
9473
|
}
|
|
8959
|
-
const memories = await
|
|
9474
|
+
const memories = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8960
9475
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
8961
9476
|
if (!loaded) {
|
|
8962
9477
|
ui.error(`No memory with id "${id}".`);
|
|
8963
9478
|
process.exitCode = 1;
|
|
8964
9479
|
return;
|
|
8965
9480
|
}
|
|
8966
|
-
await
|
|
9481
|
+
await writeFile23(
|
|
8967
9482
|
loaded.filePath,
|
|
8968
|
-
|
|
9483
|
+
serializeMemory19({
|
|
8969
9484
|
frontmatter: {
|
|
8970
9485
|
...loaded.memory.frontmatter,
|
|
8971
9486
|
status: "rejected",
|
|
@@ -8975,7 +9490,7 @@ function registerMemoryReject(memory2) {
|
|
|
8975
9490
|
}),
|
|
8976
9491
|
"utf8"
|
|
8977
9492
|
);
|
|
8978
|
-
const idx = await
|
|
9493
|
+
const idx = await loadUsageIndex18(paths);
|
|
8979
9494
|
recordRejection2(idx, id, opts.reason ?? null);
|
|
8980
9495
|
await saveUsageIndex3(paths, idx);
|
|
8981
9496
|
const u = idx.by_id[id];
|
|
@@ -8987,34 +9502,34 @@ function registerMemoryReject(memory2) {
|
|
|
8987
9502
|
}
|
|
8988
9503
|
|
|
8989
9504
|
// src/commands/memory-rm.ts
|
|
8990
|
-
import { existsSync as
|
|
9505
|
+
import { existsSync as existsSync47 } from "fs";
|
|
8991
9506
|
import { unlink as unlink3 } from "fs/promises";
|
|
8992
|
-
import
|
|
9507
|
+
import path29 from "path";
|
|
8993
9508
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
8994
9509
|
import "commander";
|
|
8995
9510
|
import {
|
|
8996
|
-
findProjectRoot as
|
|
8997
|
-
loadUsageIndex as
|
|
8998
|
-
resolveHaivePaths as
|
|
9511
|
+
findProjectRoot as findProjectRoot26,
|
|
9512
|
+
loadUsageIndex as loadUsageIndex19,
|
|
9513
|
+
resolveHaivePaths as resolveHaivePaths23,
|
|
8999
9514
|
saveUsageIndex as saveUsageIndex4
|
|
9000
9515
|
} from "@hiveai/core";
|
|
9001
9516
|
function registerMemoryRm(memory2) {
|
|
9002
9517
|
memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").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) => {
|
|
9003
|
-
const root =
|
|
9004
|
-
const paths =
|
|
9005
|
-
if (!
|
|
9518
|
+
const root = findProjectRoot26(opts.dir);
|
|
9519
|
+
const paths = resolveHaivePaths23(root);
|
|
9520
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
9006
9521
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9007
9522
|
process.exitCode = 1;
|
|
9008
9523
|
return;
|
|
9009
9524
|
}
|
|
9010
|
-
const all = await
|
|
9525
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9011
9526
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9012
9527
|
if (!found) {
|
|
9013
9528
|
ui.error(`No memory with id "${id}".`);
|
|
9014
9529
|
process.exitCode = 1;
|
|
9015
9530
|
return;
|
|
9016
9531
|
}
|
|
9017
|
-
const rel =
|
|
9532
|
+
const rel = path29.relative(root, found.filePath);
|
|
9018
9533
|
if (!opts.yes) {
|
|
9019
9534
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
9020
9535
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -9027,7 +9542,7 @@ function registerMemoryRm(memory2) {
|
|
|
9027
9542
|
await unlink3(found.filePath);
|
|
9028
9543
|
ui.success(`Deleted ${rel}`);
|
|
9029
9544
|
if (!opts.keepUsage) {
|
|
9030
|
-
const idx = await
|
|
9545
|
+
const idx = await loadUsageIndex19(paths);
|
|
9031
9546
|
if (idx.by_id[id]) {
|
|
9032
9547
|
delete idx.by_id[id];
|
|
9033
9548
|
await saveUsageIndex4(paths, idx);
|
|
@@ -9038,27 +9553,27 @@ function registerMemoryRm(memory2) {
|
|
|
9038
9553
|
}
|
|
9039
9554
|
|
|
9040
9555
|
// src/commands/memory-show.ts
|
|
9041
|
-
import { existsSync as
|
|
9042
|
-
import { readFile as
|
|
9043
|
-
import
|
|
9556
|
+
import { existsSync as existsSync48 } from "fs";
|
|
9557
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
9558
|
+
import path30 from "path";
|
|
9044
9559
|
import "commander";
|
|
9045
9560
|
import {
|
|
9046
9561
|
deriveConfidence as deriveConfidence10,
|
|
9047
|
-
findProjectRoot as
|
|
9048
|
-
getUsage as
|
|
9049
|
-
loadUsageIndex as
|
|
9050
|
-
resolveHaivePaths as
|
|
9562
|
+
findProjectRoot as findProjectRoot27,
|
|
9563
|
+
getUsage as getUsage16,
|
|
9564
|
+
loadUsageIndex as loadUsageIndex20,
|
|
9565
|
+
resolveHaivePaths as resolveHaivePaths24
|
|
9051
9566
|
} from "@hiveai/core";
|
|
9052
9567
|
function registerMemoryShow(memory2) {
|
|
9053
9568
|
memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9054
|
-
const root =
|
|
9055
|
-
const paths =
|
|
9056
|
-
if (!
|
|
9569
|
+
const root = findProjectRoot27(opts.dir);
|
|
9570
|
+
const paths = resolveHaivePaths24(root);
|
|
9571
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
9057
9572
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9058
9573
|
process.exitCode = 1;
|
|
9059
9574
|
return;
|
|
9060
9575
|
}
|
|
9061
|
-
const all = await
|
|
9576
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9062
9577
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9063
9578
|
if (!found) {
|
|
9064
9579
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9066,12 +9581,12 @@ function registerMemoryShow(memory2) {
|
|
|
9066
9581
|
return;
|
|
9067
9582
|
}
|
|
9068
9583
|
if (opts.raw) {
|
|
9069
|
-
console.log(await
|
|
9584
|
+
console.log(await readFile12(found.filePath, "utf8"));
|
|
9070
9585
|
return;
|
|
9071
9586
|
}
|
|
9072
9587
|
const fm = found.memory.frontmatter;
|
|
9073
|
-
const usage = await
|
|
9074
|
-
const u =
|
|
9588
|
+
const usage = await loadUsageIndex20(paths);
|
|
9589
|
+
const u = getUsage16(usage, fm.id);
|
|
9075
9590
|
const conf = deriveConfidence10(fm, u);
|
|
9076
9591
|
console.log(ui.bold(fm.id));
|
|
9077
9592
|
console.log(`${ui.dim("scope:")} ${fm.scope}${fm.module ? ` / ${fm.module}` : ""}`);
|
|
@@ -9082,7 +9597,7 @@ function registerMemoryShow(memory2) {
|
|
|
9082
9597
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
9083
9598
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
9084
9599
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
9085
|
-
console.log(`${ui.dim("file:")} ${
|
|
9600
|
+
console.log(`${ui.dim("file:")} ${path30.relative(root, found.filePath)}`);
|
|
9086
9601
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
9087
9602
|
console.log(ui.dim("anchor:"));
|
|
9088
9603
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -9097,38 +9612,38 @@ function registerMemoryShow(memory2) {
|
|
|
9097
9612
|
}
|
|
9098
9613
|
|
|
9099
9614
|
// src/commands/memory-stats.ts
|
|
9100
|
-
import { existsSync as
|
|
9101
|
-
import
|
|
9615
|
+
import { existsSync as existsSync49 } from "fs";
|
|
9616
|
+
import path31 from "path";
|
|
9102
9617
|
import "commander";
|
|
9103
9618
|
import {
|
|
9104
9619
|
deriveConfidence as deriveConfidence11,
|
|
9105
|
-
findProjectRoot as
|
|
9106
|
-
getUsage as
|
|
9107
|
-
loadUsageIndex as
|
|
9108
|
-
resolveHaivePaths as
|
|
9620
|
+
findProjectRoot as findProjectRoot28,
|
|
9621
|
+
getUsage as getUsage17,
|
|
9622
|
+
loadUsageIndex as loadUsageIndex21,
|
|
9623
|
+
resolveHaivePaths as resolveHaivePaths25
|
|
9109
9624
|
} from "@hiveai/core";
|
|
9110
9625
|
function registerMemoryStats(memory2) {
|
|
9111
9626
|
memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9112
|
-
const root =
|
|
9113
|
-
const paths =
|
|
9114
|
-
if (!
|
|
9627
|
+
const root = findProjectRoot28(opts.dir);
|
|
9628
|
+
const paths = resolveHaivePaths25(root);
|
|
9629
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
9115
9630
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9116
9631
|
process.exitCode = 1;
|
|
9117
9632
|
return;
|
|
9118
9633
|
}
|
|
9119
|
-
const all = await
|
|
9120
|
-
const usage = await
|
|
9634
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9635
|
+
const usage = await loadUsageIndex21(paths);
|
|
9121
9636
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
9122
9637
|
if (target.length === 0) {
|
|
9123
9638
|
ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
|
|
9124
9639
|
return;
|
|
9125
9640
|
}
|
|
9126
9641
|
target.sort(
|
|
9127
|
-
(a, b) =>
|
|
9642
|
+
(a, b) => getUsage17(usage, b.memory.frontmatter.id).read_count - getUsage17(usage, a.memory.frontmatter.id).read_count
|
|
9128
9643
|
);
|
|
9129
9644
|
for (const { memory: mem, filePath } of target) {
|
|
9130
9645
|
const fm = mem.frontmatter;
|
|
9131
|
-
const u =
|
|
9646
|
+
const u = getUsage17(usage, fm.id);
|
|
9132
9647
|
const conf = deriveConfidence11(fm, u);
|
|
9133
9648
|
console.log(
|
|
9134
9649
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`
|
|
@@ -9136,34 +9651,34 @@ function registerMemoryStats(memory2) {
|
|
|
9136
9651
|
console.log(
|
|
9137
9652
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
9138
9653
|
);
|
|
9139
|
-
console.log(` ${ui.dim(
|
|
9654
|
+
console.log(` ${ui.dim(path31.relative(root, filePath))}`);
|
|
9140
9655
|
}
|
|
9141
9656
|
});
|
|
9142
9657
|
}
|
|
9143
9658
|
|
|
9144
9659
|
// src/commands/memory-verify.ts
|
|
9145
|
-
import { writeFile as
|
|
9146
|
-
import { existsSync as
|
|
9147
|
-
import
|
|
9660
|
+
import { writeFile as writeFile24 } from "fs/promises";
|
|
9661
|
+
import { existsSync as existsSync50 } from "fs";
|
|
9662
|
+
import path33 from "path";
|
|
9148
9663
|
import "commander";
|
|
9149
9664
|
import {
|
|
9150
|
-
findProjectRoot as
|
|
9151
|
-
resolveHaivePaths as
|
|
9152
|
-
serializeMemory as
|
|
9665
|
+
findProjectRoot as findProjectRoot29,
|
|
9666
|
+
resolveHaivePaths as resolveHaivePaths26,
|
|
9667
|
+
serializeMemory as serializeMemory20,
|
|
9153
9668
|
verifyAnchor as verifyAnchor3
|
|
9154
9669
|
} from "@hiveai/core";
|
|
9155
9670
|
function registerMemoryVerify(memory2) {
|
|
9156
9671
|
memory2.command("verify").description(
|
|
9157
9672
|
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
|
|
9158
9673
|
).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9159
|
-
const root =
|
|
9160
|
-
const paths =
|
|
9161
|
-
if (!
|
|
9674
|
+
const root = findProjectRoot29(opts.dir);
|
|
9675
|
+
const paths = resolveHaivePaths26(root);
|
|
9676
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
9162
9677
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9163
9678
|
process.exitCode = 1;
|
|
9164
9679
|
return;
|
|
9165
9680
|
}
|
|
9166
|
-
const all = await
|
|
9681
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9167
9682
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
9168
9683
|
if (opts.id && targets.length === 0) {
|
|
9169
9684
|
ui.error(`No memory with id "${opts.id}".`);
|
|
@@ -9181,7 +9696,7 @@ function registerMemoryVerify(memory2) {
|
|
|
9181
9696
|
anchorlessIds.push(mem.frontmatter.id);
|
|
9182
9697
|
continue;
|
|
9183
9698
|
}
|
|
9184
|
-
const rel =
|
|
9699
|
+
const rel = path33.relative(root, filePath);
|
|
9185
9700
|
if (result.stale) {
|
|
9186
9701
|
staleCount++;
|
|
9187
9702
|
console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
|
|
@@ -9196,7 +9711,7 @@ function registerMemoryVerify(memory2) {
|
|
|
9196
9711
|
}
|
|
9197
9712
|
if (opts.update) {
|
|
9198
9713
|
const next = applyVerification2(mem, result);
|
|
9199
|
-
await
|
|
9714
|
+
await writeFile24(filePath, serializeMemory20(next), "utf8");
|
|
9200
9715
|
updated++;
|
|
9201
9716
|
}
|
|
9202
9717
|
}
|
|
@@ -9244,30 +9759,30 @@ function applyVerification2(mem, result) {
|
|
|
9244
9759
|
}
|
|
9245
9760
|
|
|
9246
9761
|
// src/commands/memory-import.ts
|
|
9247
|
-
import { readFile as
|
|
9248
|
-
import { existsSync as
|
|
9762
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
9763
|
+
import { existsSync as existsSync51 } from "fs";
|
|
9249
9764
|
import "commander";
|
|
9250
9765
|
import {
|
|
9251
|
-
findProjectRoot as
|
|
9252
|
-
resolveHaivePaths as
|
|
9766
|
+
findProjectRoot as findProjectRoot30,
|
|
9767
|
+
resolveHaivePaths as resolveHaivePaths27
|
|
9253
9768
|
} from "@hiveai/core";
|
|
9254
9769
|
function registerMemoryImport(memory2) {
|
|
9255
9770
|
memory2.command("import").description(
|
|
9256
9771
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
9257
9772
|
).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9258
|
-
const root =
|
|
9259
|
-
const paths =
|
|
9260
|
-
if (!
|
|
9773
|
+
const root = findProjectRoot30(opts.dir);
|
|
9774
|
+
const paths = resolveHaivePaths27(root);
|
|
9775
|
+
if (!existsSync51(paths.haiveDir)) {
|
|
9261
9776
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9262
9777
|
process.exitCode = 1;
|
|
9263
9778
|
return;
|
|
9264
9779
|
}
|
|
9265
|
-
if (!
|
|
9780
|
+
if (!existsSync51(opts.from)) {
|
|
9266
9781
|
ui.error(`File not found: ${opts.from}`);
|
|
9267
9782
|
process.exitCode = 1;
|
|
9268
9783
|
return;
|
|
9269
9784
|
}
|
|
9270
|
-
const content = await
|
|
9785
|
+
const content = await readFile13(opts.from, "utf8");
|
|
9271
9786
|
const scope = opts.scope ?? "team";
|
|
9272
9787
|
ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
|
|
9273
9788
|
ui.info(`Content length: ${content.length} chars`);
|
|
@@ -9295,15 +9810,15 @@ function registerMemoryImport(memory2) {
|
|
|
9295
9810
|
}
|
|
9296
9811
|
|
|
9297
9812
|
// src/commands/memory-import-changelog.ts
|
|
9298
|
-
import { existsSync as
|
|
9299
|
-
import { readFile as
|
|
9300
|
-
import
|
|
9813
|
+
import { existsSync as existsSync53 } from "fs";
|
|
9814
|
+
import { readFile as readFile14, mkdir as mkdir14, writeFile as writeFile25 } from "fs/promises";
|
|
9815
|
+
import path34 from "path";
|
|
9301
9816
|
import "commander";
|
|
9302
9817
|
import {
|
|
9303
9818
|
buildFrontmatter as buildFrontmatter9,
|
|
9304
|
-
findProjectRoot as
|
|
9305
|
-
resolveHaivePaths as
|
|
9306
|
-
serializeMemory as
|
|
9819
|
+
findProjectRoot as findProjectRoot31,
|
|
9820
|
+
resolveHaivePaths as resolveHaivePaths28,
|
|
9821
|
+
serializeMemory as serializeMemory21
|
|
9307
9822
|
} from "@hiveai/core";
|
|
9308
9823
|
function parseChangelog(content) {
|
|
9309
9824
|
const entries = [];
|
|
@@ -9367,15 +9882,15 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
9367
9882
|
"--versions <csv>",
|
|
9368
9883
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
9369
9884
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9370
|
-
const root =
|
|
9371
|
-
const paths =
|
|
9372
|
-
const changelogPath =
|
|
9373
|
-
if (!
|
|
9885
|
+
const root = findProjectRoot31(opts.dir);
|
|
9886
|
+
const paths = resolveHaivePaths28(root);
|
|
9887
|
+
const changelogPath = path34.resolve(root, opts.fromChangelog);
|
|
9888
|
+
if (!existsSync53(changelogPath)) {
|
|
9374
9889
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
9375
9890
|
process.exitCode = 1;
|
|
9376
9891
|
return;
|
|
9377
9892
|
}
|
|
9378
|
-
const content = await
|
|
9893
|
+
const content = await readFile14(changelogPath, "utf8");
|
|
9379
9894
|
let entries = parseChangelog(content);
|
|
9380
9895
|
if (entries.length === 0) {
|
|
9381
9896
|
ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
|
|
@@ -9390,9 +9905,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
9390
9905
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
9391
9906
|
}
|
|
9392
9907
|
}
|
|
9393
|
-
const pkgName = opts.package ??
|
|
9908
|
+
const pkgName = opts.package ?? path34.basename(path34.dirname(changelogPath));
|
|
9394
9909
|
const scope = opts.scope ?? "team";
|
|
9395
|
-
const teamDir =
|
|
9910
|
+
const teamDir = path34.join(paths.memoriesDir, scope);
|
|
9396
9911
|
await mkdir14(teamDir, { recursive: true });
|
|
9397
9912
|
let saved = 0;
|
|
9398
9913
|
for (const entry of entries) {
|
|
@@ -9415,7 +9930,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
9415
9930
|
lines.push("");
|
|
9416
9931
|
}
|
|
9417
9932
|
lines.push(
|
|
9418
|
-
`**Source:** \`${
|
|
9933
|
+
`**Source:** \`${path34.relative(root, changelogPath)}\`
|
|
9419
9934
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
9420
9935
|
);
|
|
9421
9936
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
@@ -9430,12 +9945,12 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
9430
9945
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
9431
9946
|
`v${entry.version}`
|
|
9432
9947
|
],
|
|
9433
|
-
paths: [
|
|
9948
|
+
paths: [path34.relative(root, changelogPath)],
|
|
9434
9949
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
9435
9950
|
});
|
|
9436
|
-
await
|
|
9437
|
-
|
|
9438
|
-
|
|
9951
|
+
await writeFile25(
|
|
9952
|
+
path34.join(teamDir, `${fm.id}.md`),
|
|
9953
|
+
serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
|
|
9439
9954
|
"utf8"
|
|
9440
9955
|
);
|
|
9441
9956
|
console.log(ui.green(` \u2713 ${fm.id}`));
|
|
@@ -9457,17 +9972,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
9457
9972
|
}
|
|
9458
9973
|
|
|
9459
9974
|
// src/commands/memory-digest.ts
|
|
9460
|
-
import { existsSync as
|
|
9461
|
-
import { writeFile as
|
|
9462
|
-
import
|
|
9975
|
+
import { existsSync as existsSync54 } from "fs";
|
|
9976
|
+
import { writeFile as writeFile26 } from "fs/promises";
|
|
9977
|
+
import path35 from "path";
|
|
9463
9978
|
import "commander";
|
|
9464
9979
|
import {
|
|
9465
9980
|
deriveConfidence as deriveConfidence12,
|
|
9466
|
-
findProjectRoot as
|
|
9467
|
-
getUsage as
|
|
9468
|
-
loadMemoriesFromDir as
|
|
9469
|
-
loadUsageIndex as
|
|
9470
|
-
resolveHaivePaths as
|
|
9981
|
+
findProjectRoot as findProjectRoot32,
|
|
9982
|
+
getUsage as getUsage18,
|
|
9983
|
+
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
9984
|
+
loadUsageIndex as loadUsageIndex23,
|
|
9985
|
+
resolveHaivePaths as resolveHaivePaths29
|
|
9471
9986
|
} from "@hiveai/core";
|
|
9472
9987
|
var CONFIDENCE_EMOJI = {
|
|
9473
9988
|
unverified: "\u2B1C",
|
|
@@ -9480,9 +9995,9 @@ function registerMemoryDigest(program2) {
|
|
|
9480
9995
|
program2.command("digest").description(
|
|
9481
9996
|
"Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
|
|
9482
9997
|
).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9483
|
-
const root =
|
|
9484
|
-
const paths =
|
|
9485
|
-
if (!
|
|
9998
|
+
const root = findProjectRoot32(opts.dir);
|
|
9999
|
+
const paths = resolveHaivePaths29(root);
|
|
10000
|
+
if (!existsSync54(paths.memoriesDir)) {
|
|
9486
10001
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
9487
10002
|
process.exitCode = 1;
|
|
9488
10003
|
return;
|
|
@@ -9490,8 +10005,8 @@ function registerMemoryDigest(program2) {
|
|
|
9490
10005
|
const days = Math.max(1, Number(opts.days ?? 7));
|
|
9491
10006
|
const scopeFilter = opts.scope ?? "team";
|
|
9492
10007
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
9493
|
-
const all = await
|
|
9494
|
-
const usage = await
|
|
10008
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10009
|
+
const usage = await loadUsageIndex23(paths);
|
|
9495
10010
|
const recent = all.filter(({ memory: mem }) => {
|
|
9496
10011
|
const fm = mem.frontmatter;
|
|
9497
10012
|
if (fm.type === "session_recap") return false;
|
|
@@ -9522,7 +10037,7 @@ function registerMemoryDigest(program2) {
|
|
|
9522
10037
|
lines.push(``);
|
|
9523
10038
|
for (const { memory: mem } of mems) {
|
|
9524
10039
|
const fm = mem.frontmatter;
|
|
9525
|
-
const u =
|
|
10040
|
+
const u = getUsage18(usage, fm.id);
|
|
9526
10041
|
const confidence = deriveConfidence12(fm, u);
|
|
9527
10042
|
const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
|
|
9528
10043
|
const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
|
|
@@ -9554,8 +10069,8 @@ function registerMemoryDigest(program2) {
|
|
|
9554
10069
|
);
|
|
9555
10070
|
const digest = lines.join("\n");
|
|
9556
10071
|
if (opts.out) {
|
|
9557
|
-
const outPath =
|
|
9558
|
-
await
|
|
10072
|
+
const outPath = path35.resolve(process.cwd(), opts.out);
|
|
10073
|
+
await writeFile26(outPath, digest, "utf8");
|
|
9559
10074
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
9560
10075
|
} else {
|
|
9561
10076
|
console.log(digest);
|
|
@@ -9564,22 +10079,22 @@ function registerMemoryDigest(program2) {
|
|
|
9564
10079
|
}
|
|
9565
10080
|
|
|
9566
10081
|
// src/commands/session-end.ts
|
|
9567
|
-
import { writeFile as
|
|
9568
|
-
import { existsSync as
|
|
9569
|
-
import
|
|
10082
|
+
import { writeFile as writeFile27, mkdir as mkdir15, readFile as readFile15, rm as rm2 } from "fs/promises";
|
|
10083
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10084
|
+
import path36 from "path";
|
|
9570
10085
|
import "commander";
|
|
9571
10086
|
import {
|
|
9572
10087
|
buildFrontmatter as buildFrontmatter10,
|
|
9573
|
-
findProjectRoot as
|
|
9574
|
-
loadMemoriesFromDir as
|
|
10088
|
+
findProjectRoot as findProjectRoot33,
|
|
10089
|
+
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
9575
10090
|
memoryFilePath as memoryFilePath9,
|
|
9576
|
-
resolveHaivePaths as
|
|
9577
|
-
serializeMemory as
|
|
10091
|
+
resolveHaivePaths as resolveHaivePaths30,
|
|
10092
|
+
serializeMemory as serializeMemory23
|
|
9578
10093
|
} from "@hiveai/core";
|
|
9579
10094
|
async function buildAutoRecap(paths) {
|
|
9580
|
-
const obsFile =
|
|
9581
|
-
if (!
|
|
9582
|
-
const raw = await
|
|
10095
|
+
const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10096
|
+
if (!existsSync55(obsFile)) return null;
|
|
10097
|
+
const raw = await readFile15(obsFile, "utf8").catch(() => "");
|
|
9583
10098
|
if (!raw.trim()) return null;
|
|
9584
10099
|
const lines = raw.split("\n").filter(Boolean);
|
|
9585
10100
|
const obs = [];
|
|
@@ -9657,9 +10172,9 @@ function registerSessionEnd(session2) {
|
|
|
9657
10172
|
--next "Add integration tests for webhook signature validation"
|
|
9658
10173
|
`
|
|
9659
10174
|
).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9660
|
-
const root =
|
|
9661
|
-
const paths =
|
|
9662
|
-
if (!
|
|
10175
|
+
const root = findProjectRoot33(opts.dir);
|
|
10176
|
+
const paths = resolveHaivePaths30(root);
|
|
10177
|
+
if (!existsSync55(paths.haiveDir)) {
|
|
9663
10178
|
if (opts.auto || opts.quiet) return;
|
|
9664
10179
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9665
10180
|
process.exitCode = 1;
|
|
@@ -9691,19 +10206,19 @@ function registerSessionEnd(session2) {
|
|
|
9691
10206
|
});
|
|
9692
10207
|
const topic = recapTopic2(scope, opts.module);
|
|
9693
10208
|
const filesTouched = parseCsv5(resolvedFiles);
|
|
9694
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
10209
|
+
const missingPaths = filesTouched.filter((p) => !existsSync55(path36.resolve(root, p)));
|
|
9695
10210
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
9696
10211
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
9697
10212
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
9698
10213
|
}
|
|
9699
10214
|
const cleanupObservations = async () => {
|
|
9700
10215
|
if (!opts.auto) return;
|
|
9701
|
-
const obsFile =
|
|
9702
|
-
if (
|
|
10216
|
+
const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10217
|
+
if (existsSync55(obsFile)) await rm2(obsFile).catch(() => {
|
|
9703
10218
|
});
|
|
9704
10219
|
};
|
|
9705
|
-
if (
|
|
9706
|
-
const existing = await
|
|
10220
|
+
if (existsSync55(paths.memoriesDir)) {
|
|
10221
|
+
const existing = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
9707
10222
|
const topicMatch = existing.find(
|
|
9708
10223
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
9709
10224
|
);
|
|
@@ -9719,11 +10234,11 @@ function registerSessionEnd(session2) {
|
|
|
9719
10234
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
9720
10235
|
}
|
|
9721
10236
|
};
|
|
9722
|
-
await
|
|
10237
|
+
await writeFile27(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
|
|
9723
10238
|
await cleanupObservations();
|
|
9724
10239
|
if (!opts.quiet) {
|
|
9725
10240
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
9726
|
-
ui.info(`id=${fm.id} file=${
|
|
10241
|
+
ui.info(`id=${fm.id} file=${path36.relative(root, topicMatch.filePath)}`);
|
|
9727
10242
|
ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
|
|
9728
10243
|
}
|
|
9729
10244
|
return;
|
|
@@ -9740,12 +10255,12 @@ function registerSessionEnd(session2) {
|
|
|
9740
10255
|
status: "validated"
|
|
9741
10256
|
});
|
|
9742
10257
|
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9743
|
-
await mkdir15(
|
|
9744
|
-
await
|
|
10258
|
+
await mkdir15(path36.dirname(file), { recursive: true });
|
|
10259
|
+
await writeFile27(file, serializeMemory23({ frontmatter, body }), "utf8");
|
|
9745
10260
|
await cleanupObservations();
|
|
9746
10261
|
if (!opts.quiet) {
|
|
9747
10262
|
ui.success(`Session recap created`);
|
|
9748
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
10263
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path36.relative(root, file)}`);
|
|
9749
10264
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
9750
10265
|
ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
|
|
9751
10266
|
}
|
|
@@ -9757,15 +10272,15 @@ function parseCsv5(value) {
|
|
|
9757
10272
|
}
|
|
9758
10273
|
|
|
9759
10274
|
// src/commands/snapshot.ts
|
|
9760
|
-
import { existsSync as
|
|
10275
|
+
import { existsSync as existsSync56 } from "fs";
|
|
9761
10276
|
import { readdir as readdir4 } from "fs/promises";
|
|
9762
|
-
import
|
|
10277
|
+
import path37 from "path";
|
|
9763
10278
|
import "commander";
|
|
9764
10279
|
import {
|
|
9765
10280
|
diffContract,
|
|
9766
|
-
findProjectRoot as
|
|
9767
|
-
loadConfig as
|
|
9768
|
-
resolveHaivePaths as
|
|
10281
|
+
findProjectRoot as findProjectRoot34,
|
|
10282
|
+
loadConfig as loadConfig7,
|
|
10283
|
+
resolveHaivePaths as resolveHaivePaths31,
|
|
9769
10284
|
snapshotContract
|
|
9770
10285
|
} from "@hiveai/core";
|
|
9771
10286
|
function registerSnapshot(program2) {
|
|
@@ -9790,16 +10305,16 @@ function registerSnapshot(program2) {
|
|
|
9790
10305
|
"--format <format>",
|
|
9791
10306
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
9792
10307
|
).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9793
|
-
const root =
|
|
9794
|
-
const paths =
|
|
9795
|
-
if (!
|
|
10308
|
+
const root = findProjectRoot34(opts.dir);
|
|
10309
|
+
const paths = resolveHaivePaths31(root);
|
|
10310
|
+
if (!existsSync56(paths.haiveDir)) {
|
|
9796
10311
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
9797
10312
|
process.exitCode = 1;
|
|
9798
10313
|
return;
|
|
9799
10314
|
}
|
|
9800
10315
|
if (opts.list) {
|
|
9801
|
-
const contractsDir =
|
|
9802
|
-
if (!
|
|
10316
|
+
const contractsDir = path37.join(paths.haiveDir, "contracts");
|
|
10317
|
+
if (!existsSync56(contractsDir)) {
|
|
9803
10318
|
console.log(ui.dim("No contract snapshots found."));
|
|
9804
10319
|
return;
|
|
9805
10320
|
}
|
|
@@ -9819,7 +10334,7 @@ function registerSnapshot(program2) {
|
|
|
9819
10334
|
}
|
|
9820
10335
|
if (opts.diff) {
|
|
9821
10336
|
if (!opts.name) {
|
|
9822
|
-
const config2 = await
|
|
10337
|
+
const config2 = await loadConfig7(paths);
|
|
9823
10338
|
const contracts = config2.contractFiles ?? [];
|
|
9824
10339
|
if (contracts.length === 0) {
|
|
9825
10340
|
ui.error("--diff requires --name, or configure contractFiles in haive.config.json");
|
|
@@ -9831,7 +10346,7 @@ function registerSnapshot(program2) {
|
|
|
9831
10346
|
}
|
|
9832
10347
|
return;
|
|
9833
10348
|
}
|
|
9834
|
-
const config = await
|
|
10349
|
+
const config = await loadConfig7(paths);
|
|
9835
10350
|
const configured = (config.contractFiles ?? []).find((c) => c.name === opts.name);
|
|
9836
10351
|
if (!configured && !opts.contract) {
|
|
9837
10352
|
ui.error(
|
|
@@ -9854,7 +10369,7 @@ function registerSnapshot(program2) {
|
|
|
9854
10369
|
return;
|
|
9855
10370
|
}
|
|
9856
10371
|
const contractPath = opts.contract;
|
|
9857
|
-
const name = opts.name ??
|
|
10372
|
+
const name = opts.name ?? path37.basename(contractPath, path37.extname(contractPath));
|
|
9858
10373
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
9859
10374
|
const contract = { name, path: contractPath, format };
|
|
9860
10375
|
try {
|
|
@@ -9909,8 +10424,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
9909
10424
|
}
|
|
9910
10425
|
}
|
|
9911
10426
|
function detectFormat(filePath) {
|
|
9912
|
-
const ext =
|
|
9913
|
-
const base =
|
|
10427
|
+
const ext = path37.extname(filePath).toLowerCase();
|
|
10428
|
+
const base = path37.basename(filePath).toLowerCase();
|
|
9914
10429
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
9915
10430
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
9916
10431
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -9923,18 +10438,18 @@ function detectFormat(filePath) {
|
|
|
9923
10438
|
}
|
|
9924
10439
|
|
|
9925
10440
|
// src/commands/hub.ts
|
|
9926
|
-
import { existsSync as
|
|
9927
|
-
import { mkdir as mkdir16, readFile as
|
|
9928
|
-
import
|
|
9929
|
-
import { spawnSync as
|
|
10441
|
+
import { existsSync as existsSync57 } from "fs";
|
|
10442
|
+
import { mkdir as mkdir16, readFile as readFile16, writeFile as writeFile28, copyFile } from "fs/promises";
|
|
10443
|
+
import path38 from "path";
|
|
10444
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
9930
10445
|
import "commander";
|
|
9931
10446
|
import {
|
|
9932
|
-
findProjectRoot as
|
|
9933
|
-
loadConfig as
|
|
9934
|
-
loadMemoriesFromDir as
|
|
9935
|
-
resolveHaivePaths as
|
|
9936
|
-
saveConfig as
|
|
9937
|
-
serializeMemory as
|
|
10447
|
+
findProjectRoot as findProjectRoot35,
|
|
10448
|
+
loadConfig as loadConfig8,
|
|
10449
|
+
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
10450
|
+
resolveHaivePaths as resolveHaivePaths32,
|
|
10451
|
+
saveConfig as saveConfig3,
|
|
10452
|
+
serializeMemory as serializeMemory24
|
|
9938
10453
|
} from "@hiveai/core";
|
|
9939
10454
|
function registerHub(program2) {
|
|
9940
10455
|
const hub = program2.command("hub").description(
|
|
@@ -9944,21 +10459,21 @@ function registerHub(program2) {
|
|
|
9944
10459
|
hub.command("init <hubPath>").description(
|
|
9945
10460
|
"Initialize a new team-knowledge hub repo at <hubPath>.\n\n Creates a git repo with a .ai/ directory structure ready for shared memories.\n\n Example:\n haive hub init ../team-hub\n haive hub init /srv/git/team-knowledge\n"
|
|
9946
10461
|
).action(async (hubPath) => {
|
|
9947
|
-
const absPath =
|
|
10462
|
+
const absPath = path38.resolve(hubPath);
|
|
9948
10463
|
await mkdir16(absPath, { recursive: true });
|
|
9949
|
-
const gitCheck =
|
|
10464
|
+
const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
9950
10465
|
if (gitCheck.status !== 0) {
|
|
9951
|
-
const init =
|
|
10466
|
+
const init = spawnSync5("git", ["init"], { cwd: absPath, encoding: "utf8" });
|
|
9952
10467
|
if (init.status !== 0) {
|
|
9953
10468
|
ui.error(`git init failed: ${init.stderr}`);
|
|
9954
10469
|
process.exitCode = 1;
|
|
9955
10470
|
return;
|
|
9956
10471
|
}
|
|
9957
10472
|
}
|
|
9958
|
-
const sharedDir =
|
|
10473
|
+
const sharedDir = path38.join(absPath, ".ai", "memories", "shared");
|
|
9959
10474
|
await mkdir16(sharedDir, { recursive: true });
|
|
9960
|
-
await
|
|
9961
|
-
|
|
10475
|
+
await writeFile28(
|
|
10476
|
+
path38.join(absPath, ".ai", "README.md"),
|
|
9962
10477
|
`# hAIve Team Knowledge Hub
|
|
9963
10478
|
|
|
9964
10479
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -9979,13 +10494,13 @@ haive hub pull # import into a project
|
|
|
9979
10494
|
`,
|
|
9980
10495
|
"utf8"
|
|
9981
10496
|
);
|
|
9982
|
-
await
|
|
9983
|
-
|
|
10497
|
+
await writeFile28(
|
|
10498
|
+
path38.join(absPath, ".gitignore"),
|
|
9984
10499
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
9985
10500
|
"utf8"
|
|
9986
10501
|
);
|
|
9987
|
-
|
|
9988
|
-
|
|
10502
|
+
spawnSync5("git", ["add", "."], { cwd: absPath });
|
|
10503
|
+
spawnSync5("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
|
|
9989
10504
|
cwd: absPath,
|
|
9990
10505
|
encoding: "utf8"
|
|
9991
10506
|
});
|
|
@@ -9995,7 +10510,7 @@ haive hub pull # import into a project
|
|
|
9995
10510
|
`
|
|
9996
10511
|
Next steps:
|
|
9997
10512
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
9998
|
-
{ "hubPath": "${
|
|
10513
|
+
{ "hubPath": "${path38.relative(process.cwd(), absPath)}" }
|
|
9999
10514
|
2. Run \`haive hub push\` to publish your shared memories
|
|
10000
10515
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
10001
10516
|
`
|
|
@@ -10014,9 +10529,9 @@ Next steps:
|
|
|
10014
10529
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
10015
10530
|
`
|
|
10016
10531
|
).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
|
|
10017
|
-
const root =
|
|
10018
|
-
const paths =
|
|
10019
|
-
const config = await
|
|
10532
|
+
const root = findProjectRoot35(opts.dir);
|
|
10533
|
+
const paths = resolveHaivePaths32(root);
|
|
10534
|
+
const config = await loadConfig8(paths);
|
|
10020
10535
|
if (!config.hubPath) {
|
|
10021
10536
|
ui.error(
|
|
10022
10537
|
'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
|
|
@@ -10024,16 +10539,16 @@ Next steps:
|
|
|
10024
10539
|
process.exitCode = 1;
|
|
10025
10540
|
return;
|
|
10026
10541
|
}
|
|
10027
|
-
const hubRoot =
|
|
10028
|
-
if (!
|
|
10542
|
+
const hubRoot = path38.resolve(root, config.hubPath);
|
|
10543
|
+
if (!existsSync57(hubRoot)) {
|
|
10029
10544
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
10030
10545
|
process.exitCode = 1;
|
|
10031
10546
|
return;
|
|
10032
10547
|
}
|
|
10033
|
-
const projectName =
|
|
10034
|
-
const destDir =
|
|
10548
|
+
const projectName = path38.basename(root);
|
|
10549
|
+
const destDir = path38.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
10035
10550
|
await mkdir16(destDir, { recursive: true });
|
|
10036
|
-
const all = await
|
|
10551
|
+
const all = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
10037
10552
|
const shared = all.filter(
|
|
10038
10553
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
10039
10554
|
!memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
@@ -10050,18 +10565,18 @@ Next steps:
|
|
|
10050
10565
|
for (const { memory: memory2 } of shared) {
|
|
10051
10566
|
const fm = memory2.frontmatter;
|
|
10052
10567
|
const fileName = `${fm.id}.md`;
|
|
10053
|
-
const destPath =
|
|
10054
|
-
await
|
|
10568
|
+
const destPath = path38.join(destDir, fileName);
|
|
10569
|
+
await writeFile28(destPath, serializeMemory24(memory2), "utf8");
|
|
10055
10570
|
pushed++;
|
|
10056
10571
|
}
|
|
10057
10572
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
10058
10573
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
10059
10574
|
if (opts.commit) {
|
|
10060
10575
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
10061
|
-
|
|
10576
|
+
spawnSync5("git", ["add", path38.join(".ai", "memories", "shared", projectName)], {
|
|
10062
10577
|
cwd: hubRoot
|
|
10063
10578
|
});
|
|
10064
|
-
const commit =
|
|
10579
|
+
const commit = spawnSync5("git", ["commit", "-m", message], {
|
|
10065
10580
|
cwd: hubRoot,
|
|
10066
10581
|
encoding: "utf8"
|
|
10067
10582
|
});
|
|
@@ -10083,9 +10598,9 @@ Next steps:
|
|
|
10083
10598
|
hub.command("pull").description(
|
|
10084
10599
|
"Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
|
|
10085
10600
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10086
|
-
const root =
|
|
10087
|
-
const paths =
|
|
10088
|
-
const config = await
|
|
10601
|
+
const root = findProjectRoot35(opts.dir);
|
|
10602
|
+
const paths = resolveHaivePaths32(root);
|
|
10603
|
+
const config = await loadConfig8(paths);
|
|
10089
10604
|
if (!config.hubPath) {
|
|
10090
10605
|
ui.error(
|
|
10091
10606
|
'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
|
|
@@ -10093,13 +10608,13 @@ Next steps:
|
|
|
10093
10608
|
process.exitCode = 1;
|
|
10094
10609
|
return;
|
|
10095
10610
|
}
|
|
10096
|
-
const hubRoot =
|
|
10097
|
-
const hubSharedDir =
|
|
10098
|
-
if (!
|
|
10611
|
+
const hubRoot = path38.resolve(root, config.hubPath);
|
|
10612
|
+
const hubSharedDir = path38.join(hubRoot, ".ai", "memories", "shared");
|
|
10613
|
+
if (!existsSync57(hubSharedDir)) {
|
|
10099
10614
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
10100
10615
|
return;
|
|
10101
10616
|
}
|
|
10102
|
-
const projectName =
|
|
10617
|
+
const projectName = path38.basename(root);
|
|
10103
10618
|
const { readdir: readdir6 } = await import("fs/promises");
|
|
10104
10619
|
const projectDirs = (await readdir6(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
10105
10620
|
if (projectDirs.length === 0) {
|
|
@@ -10109,17 +10624,17 @@ Next steps:
|
|
|
10109
10624
|
let totalImported = 0;
|
|
10110
10625
|
let totalUpdated = 0;
|
|
10111
10626
|
for (const sourceName of projectDirs) {
|
|
10112
|
-
const sourceDir =
|
|
10113
|
-
const destDir =
|
|
10627
|
+
const sourceDir = path38.join(hubSharedDir, sourceName);
|
|
10628
|
+
const destDir = path38.join(paths.memoriesDir, "shared", sourceName);
|
|
10114
10629
|
await mkdir16(destDir, { recursive: true });
|
|
10115
10630
|
const sourceFiles = (await readdir6(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
10116
10631
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
10117
10632
|
const existingInDest = await loadDir(destDir);
|
|
10118
10633
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
10119
10634
|
for (const file of sourceFiles) {
|
|
10120
|
-
const srcPath =
|
|
10121
|
-
const destPath =
|
|
10122
|
-
const fileContent = await
|
|
10635
|
+
const srcPath = path38.join(sourceDir, file);
|
|
10636
|
+
const destPath = path38.join(destDir, file);
|
|
10637
|
+
const fileContent = await readFile16(srcPath, "utf8");
|
|
10123
10638
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
10124
10639
|
if (!alreadyTagged) {
|
|
10125
10640
|
await copyFile(srcPath, destPath);
|
|
@@ -10142,27 +10657,27 @@ Next steps:
|
|
|
10142
10657
|
);
|
|
10143
10658
|
});
|
|
10144
10659
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10145
|
-
const root =
|
|
10146
|
-
const paths =
|
|
10147
|
-
const config = await
|
|
10660
|
+
const root = findProjectRoot35(opts.dir);
|
|
10661
|
+
const paths = resolveHaivePaths32(root);
|
|
10662
|
+
const config = await loadConfig8(paths);
|
|
10148
10663
|
console.log(ui.bold("Hub status"));
|
|
10149
10664
|
console.log(
|
|
10150
10665
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
10151
10666
|
);
|
|
10152
|
-
const sharedDir =
|
|
10153
|
-
if (
|
|
10667
|
+
const sharedDir = path38.join(paths.memoriesDir, "shared");
|
|
10668
|
+
if (existsSync57(sharedDir)) {
|
|
10154
10669
|
const { readdir: readdir6 } = await import("fs/promises");
|
|
10155
10670
|
const sources = (await readdir6(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
10156
10671
|
console.log(`
|
|
10157
10672
|
Imported from ${sources.length} source(s):`);
|
|
10158
10673
|
for (const src of sources) {
|
|
10159
|
-
const files = (await readdir6(
|
|
10674
|
+
const files = (await readdir6(path38.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
10160
10675
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
10161
10676
|
}
|
|
10162
10677
|
} else {
|
|
10163
10678
|
console.log(ui.dim(" No imported shared memories yet."));
|
|
10164
10679
|
}
|
|
10165
|
-
const all = await
|
|
10680
|
+
const all = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
10166
10681
|
const outgoing = all.filter(
|
|
10167
10682
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
10168
10683
|
);
|
|
@@ -10171,25 +10686,25 @@ Next steps:
|
|
|
10171
10686
|
if (outgoing.length > 0) {
|
|
10172
10687
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
10173
10688
|
}
|
|
10174
|
-
void
|
|
10175
|
-
void
|
|
10176
|
-
void
|
|
10689
|
+
void readFile16;
|
|
10690
|
+
void writeFile28;
|
|
10691
|
+
void saveConfig3;
|
|
10177
10692
|
});
|
|
10178
10693
|
}
|
|
10179
10694
|
|
|
10180
10695
|
// src/commands/stats.ts
|
|
10181
10696
|
import "commander";
|
|
10182
|
-
import { existsSync as
|
|
10183
|
-
import { mkdir as mkdir17, writeFile as
|
|
10184
|
-
import
|
|
10697
|
+
import { existsSync as existsSync58 } from "fs";
|
|
10698
|
+
import { mkdir as mkdir17, writeFile as writeFile29 } from "fs/promises";
|
|
10699
|
+
import path39 from "path";
|
|
10185
10700
|
import {
|
|
10186
10701
|
aggregateUsage,
|
|
10187
|
-
findProjectRoot as
|
|
10188
|
-
loadMemoriesFromDir as
|
|
10189
|
-
loadUsageIndex as
|
|
10702
|
+
findProjectRoot as findProjectRoot36,
|
|
10703
|
+
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
10704
|
+
loadUsageIndex as loadUsageIndex24,
|
|
10190
10705
|
parseSince,
|
|
10191
10706
|
readUsageEvents as readUsageEvents2,
|
|
10192
|
-
resolveHaivePaths as
|
|
10707
|
+
resolveHaivePaths as resolveHaivePaths33,
|
|
10193
10708
|
usageLogSize
|
|
10194
10709
|
} from "@hiveai/core";
|
|
10195
10710
|
function registerStats(program2) {
|
|
@@ -10198,8 +10713,8 @@ function registerStats(program2) {
|
|
|
10198
10713
|
"write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
|
|
10199
10714
|
void 0
|
|
10200
10715
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10201
|
-
const root =
|
|
10202
|
-
const paths =
|
|
10716
|
+
const root = findProjectRoot36(opts.dir);
|
|
10717
|
+
const paths = resolveHaivePaths33(root);
|
|
10203
10718
|
if (opts.exportReport) {
|
|
10204
10719
|
await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
|
|
10205
10720
|
return;
|
|
@@ -10253,12 +10768,12 @@ function registerStats(program2) {
|
|
|
10253
10768
|
});
|
|
10254
10769
|
}
|
|
10255
10770
|
async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
10256
|
-
const outAbs =
|
|
10771
|
+
const outAbs = path39.isAbsolute(outRelative) ? path39.resolve(outRelative) : path39.resolve(root, outRelative);
|
|
10257
10772
|
const size = await usageLogSize(paths);
|
|
10258
10773
|
let events = await readUsageEvents2(paths);
|
|
10259
10774
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
10260
|
-
if (
|
|
10261
|
-
const mems = await
|
|
10775
|
+
if (existsSync58(paths.memoriesDir)) {
|
|
10776
|
+
const mems = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
10262
10777
|
for (const { memory: memory2 } of mems) {
|
|
10263
10778
|
const fm = memory2.frontmatter;
|
|
10264
10779
|
if (fm.type === "session_recap") memoryCount.total_skipped_session++;
|
|
@@ -10272,7 +10787,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
10272
10787
|
const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
|
|
10273
10788
|
let memoryHitsLeader = null;
|
|
10274
10789
|
try {
|
|
10275
|
-
const usageIdx = await
|
|
10790
|
+
const usageIdx = await loadUsageIndex24(paths);
|
|
10276
10791
|
const tops = Object.entries(usageIdx.by_id).map(([id, v]) => ({ id, read_count: v.read_count })).filter((x) => x.read_count > 0).sort((a, b) => b.read_count - a.read_count);
|
|
10277
10792
|
memoryHitsLeader = tops[0] ?? null;
|
|
10278
10793
|
} catch {
|
|
@@ -10288,7 +10803,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
10288
10803
|
ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
|
|
10289
10804
|
events = [];
|
|
10290
10805
|
}
|
|
10291
|
-
await mkdir17(
|
|
10806
|
+
await mkdir17(path39.dirname(outAbs), { recursive: true });
|
|
10292
10807
|
const payload = {
|
|
10293
10808
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10294
10809
|
project_root: root,
|
|
@@ -10300,11 +10815,11 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
10300
10815
|
top_memory_reads: memoryHitsLeader,
|
|
10301
10816
|
roi_hints: roiHints
|
|
10302
10817
|
};
|
|
10303
|
-
await
|
|
10818
|
+
await writeFile29(outAbs, JSON.stringify(payload, null, 2), "utf8");
|
|
10304
10819
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
10305
10820
|
}
|
|
10306
10821
|
async function renderMemoryHits(paths, opts) {
|
|
10307
|
-
const index = await
|
|
10822
|
+
const index = await loadUsageIndex24(paths);
|
|
10308
10823
|
const since = parseSince(opts.since ?? "30d");
|
|
10309
10824
|
const sinceMs = since ? new Date(since).getTime() : null;
|
|
10310
10825
|
const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
|
|
@@ -10352,13 +10867,13 @@ import { performance } from "perf_hooks";
|
|
|
10352
10867
|
import "commander";
|
|
10353
10868
|
import {
|
|
10354
10869
|
estimateTokens as estimateTokens3,
|
|
10355
|
-
findProjectRoot as
|
|
10356
|
-
resolveHaivePaths as
|
|
10870
|
+
findProjectRoot as findProjectRoot37,
|
|
10871
|
+
resolveHaivePaths as resolveHaivePaths34
|
|
10357
10872
|
} from "@hiveai/core";
|
|
10358
10873
|
function registerBench(program2) {
|
|
10359
10874
|
program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10360
|
-
const root =
|
|
10361
|
-
const paths =
|
|
10875
|
+
const root = findProjectRoot37(opts.dir);
|
|
10876
|
+
const paths = resolveHaivePaths34(root);
|
|
10362
10877
|
const ctx = { paths };
|
|
10363
10878
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
10364
10879
|
const scenarios = [
|
|
@@ -10477,11 +10992,11 @@ function summarize(name, t0, payload, notes) {
|
|
|
10477
10992
|
}
|
|
10478
10993
|
|
|
10479
10994
|
// src/commands/benchmark.ts
|
|
10480
|
-
import { existsSync as
|
|
10481
|
-
import { readdir as readdir5, readFile as
|
|
10482
|
-
import
|
|
10995
|
+
import { existsSync as existsSync59 } from "fs";
|
|
10996
|
+
import { readdir as readdir5, readFile as readFile17, writeFile as writeFile30 } from "fs/promises";
|
|
10997
|
+
import path40 from "path";
|
|
10483
10998
|
import "commander";
|
|
10484
|
-
import { estimateTokens as estimateTokens4, findProjectRoot as
|
|
10999
|
+
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot38 } from "@hiveai/core";
|
|
10485
11000
|
function registerBenchmark(program2) {
|
|
10486
11001
|
const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
|
|
10487
11002
|
benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
|
|
@@ -10494,9 +11009,9 @@ function registerBenchmark(program2) {
|
|
|
10494
11009
|
}
|
|
10495
11010
|
const markdown = renderMarkdown(root, summary, rows);
|
|
10496
11011
|
if (opts.out) {
|
|
10497
|
-
const outFile =
|
|
10498
|
-
await
|
|
10499
|
-
ui.success(`wrote ${
|
|
11012
|
+
const outFile = path40.isAbsolute(opts.out) ? opts.out : path40.join(root, opts.out);
|
|
11013
|
+
await writeFile30(outFile, markdown, "utf8");
|
|
11014
|
+
ui.success(`wrote ${path40.relative(process.cwd(), outFile)}`);
|
|
10500
11015
|
return;
|
|
10501
11016
|
}
|
|
10502
11017
|
console.log(markdown);
|
|
@@ -10520,20 +11035,20 @@ function registerBenchmark(program2) {
|
|
|
10520
11035
|
}
|
|
10521
11036
|
function resolveBenchmarkRoot(dir) {
|
|
10522
11037
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
10523
|
-
if (
|
|
10524
|
-
const projectRoot =
|
|
10525
|
-
return
|
|
11038
|
+
if (path40.isAbsolute(candidate)) return candidate;
|
|
11039
|
+
const projectRoot = findProjectRoot38(process.cwd());
|
|
11040
|
+
return path40.join(projectRoot, candidate);
|
|
10526
11041
|
}
|
|
10527
11042
|
async function collectRows(root) {
|
|
10528
|
-
if (!
|
|
11043
|
+
if (!existsSync59(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
10529
11044
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
10530
11045
|
const rows = [];
|
|
10531
11046
|
for (const entry of entries) {
|
|
10532
11047
|
if (!entry.isDirectory()) continue;
|
|
10533
|
-
const fixtureDir =
|
|
10534
|
-
const reportFile =
|
|
10535
|
-
if (!
|
|
10536
|
-
const report = await
|
|
11048
|
+
const fixtureDir = path40.join(root, entry.name);
|
|
11049
|
+
const reportFile = path40.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
11050
|
+
if (!existsSync59(reportFile)) continue;
|
|
11051
|
+
const report = await readFile17(reportFile, "utf8");
|
|
10537
11052
|
rows.push(parseAgentReport(entry.name, report));
|
|
10538
11053
|
}
|
|
10539
11054
|
return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
|
|
@@ -10622,20 +11137,20 @@ function escapeRegExp(value) {
|
|
|
10622
11137
|
}
|
|
10623
11138
|
|
|
10624
11139
|
// src/commands/memory-suggest.ts
|
|
10625
|
-
import { mkdir as mkdir18, writeFile as
|
|
10626
|
-
import { existsSync as
|
|
10627
|
-
import
|
|
11140
|
+
import { mkdir as mkdir18, writeFile as writeFile31 } from "fs/promises";
|
|
11141
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11142
|
+
import path41 from "path";
|
|
10628
11143
|
import "commander";
|
|
10629
11144
|
import {
|
|
10630
11145
|
aggregateUsage as aggregateUsage2,
|
|
10631
11146
|
buildFrontmatter as buildFrontmatter11,
|
|
10632
|
-
findProjectRoot as
|
|
10633
|
-
loadMemoriesFromDir as
|
|
11147
|
+
findProjectRoot as findProjectRoot39,
|
|
11148
|
+
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
10634
11149
|
memoryFilePath as memoryFilePath10,
|
|
10635
11150
|
parseSince as parseSince2,
|
|
10636
11151
|
readUsageEvents as readUsageEvents3,
|
|
10637
|
-
resolveHaivePaths as
|
|
10638
|
-
serializeMemory as
|
|
11152
|
+
resolveHaivePaths as resolveHaivePaths35,
|
|
11153
|
+
serializeMemory as serializeMemory25
|
|
10639
11154
|
} from "@hiveai/core";
|
|
10640
11155
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
10641
11156
|
"mem_search",
|
|
@@ -10647,8 +11162,8 @@ function registerMemorySuggest(memory2) {
|
|
|
10647
11162
|
memory2.command("suggest").description(
|
|
10648
11163
|
"Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to draft the top-N suggestions as draft memories. They land\n in personal scope by default with status=draft, ready for you to edit and promote."
|
|
10649
11164
|
).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of drafted memories (personal | team)", "personal").option("--auto-save", "draft top-N suggestions as draft memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10650
|
-
const root =
|
|
10651
|
-
const paths =
|
|
11165
|
+
const root = findProjectRoot39(opts.dir);
|
|
11166
|
+
const paths = resolveHaivePaths35(root);
|
|
10652
11167
|
const events = await readUsageEvents3(paths);
|
|
10653
11168
|
if (events.length === 0) {
|
|
10654
11169
|
if (opts.json) {
|
|
@@ -10694,7 +11209,7 @@ function registerMemorySuggest(memory2) {
|
|
|
10694
11209
|
}
|
|
10695
11210
|
const created = [];
|
|
10696
11211
|
const skipped = [];
|
|
10697
|
-
const existing =
|
|
11212
|
+
const existing = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
|
|
10698
11213
|
for (const s of top) {
|
|
10699
11214
|
const slug = slugify(s.query);
|
|
10700
11215
|
if (!slug) {
|
|
@@ -10717,13 +11232,13 @@ function registerMemorySuggest(memory2) {
|
|
|
10717
11232
|
fm.status = "draft";
|
|
10718
11233
|
const body = renderTemplate(s);
|
|
10719
11234
|
const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
|
|
10720
|
-
await mkdir18(
|
|
10721
|
-
if (
|
|
10722
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
11235
|
+
await mkdir18(path41.dirname(file), { recursive: true });
|
|
11236
|
+
if (existsSync60(file)) {
|
|
11237
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path41.relative(root, file)}` });
|
|
10723
11238
|
continue;
|
|
10724
11239
|
}
|
|
10725
|
-
await
|
|
10726
|
-
created.push({ id: fm.id, file:
|
|
11240
|
+
await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
|
|
11241
|
+
created.push({ id: fm.id, file: path41.relative(root, file), query: s.query });
|
|
10727
11242
|
}
|
|
10728
11243
|
if (opts.json) {
|
|
10729
11244
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -10816,26 +11331,26 @@ function truncate2(text, max) {
|
|
|
10816
11331
|
}
|
|
10817
11332
|
|
|
10818
11333
|
// src/commands/memory-archive.ts
|
|
10819
|
-
import { existsSync as
|
|
10820
|
-
import { writeFile as
|
|
10821
|
-
import
|
|
11334
|
+
import { existsSync as existsSync61 } from "fs";
|
|
11335
|
+
import { writeFile as writeFile33 } from "fs/promises";
|
|
11336
|
+
import path43 from "path";
|
|
10822
11337
|
import "commander";
|
|
10823
11338
|
import {
|
|
10824
|
-
findProjectRoot as
|
|
10825
|
-
getUsage as
|
|
10826
|
-
loadMemoriesFromDir as
|
|
10827
|
-
loadUsageIndex as
|
|
10828
|
-
resolveHaivePaths as
|
|
10829
|
-
serializeMemory as
|
|
11339
|
+
findProjectRoot as findProjectRoot40,
|
|
11340
|
+
getUsage as getUsage19,
|
|
11341
|
+
loadMemoriesFromDir as loadMemoriesFromDir32,
|
|
11342
|
+
loadUsageIndex as loadUsageIndex25,
|
|
11343
|
+
resolveHaivePaths as resolveHaivePaths36,
|
|
11344
|
+
serializeMemory as serializeMemory26
|
|
10830
11345
|
} from "@hiveai/core";
|
|
10831
11346
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
10832
11347
|
function registerMemoryArchive(memory2) {
|
|
10833
11348
|
memory2.command("archive").description(
|
|
10834
11349
|
"Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
|
|
10835
11350
|
).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10836
|
-
const root =
|
|
10837
|
-
const paths =
|
|
10838
|
-
if (!
|
|
11351
|
+
const root = findProjectRoot40(opts.dir);
|
|
11352
|
+
const paths = resolveHaivePaths36(root);
|
|
11353
|
+
if (!existsSync61(paths.memoriesDir)) {
|
|
10839
11354
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10840
11355
|
process.exitCode = 1;
|
|
10841
11356
|
return;
|
|
@@ -10847,8 +11362,8 @@ function registerMemoryArchive(memory2) {
|
|
|
10847
11362
|
return;
|
|
10848
11363
|
}
|
|
10849
11364
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
10850
|
-
const all = await
|
|
10851
|
-
const usage = await
|
|
11365
|
+
const all = await loadMemoriesFromDir32(paths.memoriesDir);
|
|
11366
|
+
const usage = await loadUsageIndex25(paths);
|
|
10852
11367
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
10853
11368
|
const candidates = [];
|
|
10854
11369
|
for (const { memory: mem, filePath } of all) {
|
|
@@ -10856,10 +11371,10 @@ function registerMemoryArchive(memory2) {
|
|
|
10856
11371
|
if (typeFilter && fm.type !== typeFilter) continue;
|
|
10857
11372
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
10858
11373
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
10859
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
11374
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync61(path43.join(paths.root, p)));
|
|
10860
11375
|
const isAnchorless = !hasAnyAnchor;
|
|
10861
11376
|
if (!isAnchorless && !allPathsGone) continue;
|
|
10862
|
-
const u =
|
|
11377
|
+
const u = getUsage19(usage, fm.id);
|
|
10863
11378
|
const lastSeen = u.last_read_at ?? fm.created_at;
|
|
10864
11379
|
if (Date.parse(lastSeen) >= cutoff) continue;
|
|
10865
11380
|
candidates.push({
|
|
@@ -10904,7 +11419,7 @@ function registerMemoryArchive(memory2) {
|
|
|
10904
11419
|
if (!found) continue;
|
|
10905
11420
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
10906
11421
|
try {
|
|
10907
|
-
await
|
|
11422
|
+
await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
10908
11423
|
archived++;
|
|
10909
11424
|
} catch (err) {
|
|
10910
11425
|
if (!opts.json) {
|
|
@@ -10930,31 +11445,32 @@ function parseDays(input) {
|
|
|
10930
11445
|
}
|
|
10931
11446
|
|
|
10932
11447
|
// src/commands/doctor.ts
|
|
10933
|
-
import { existsSync as
|
|
10934
|
-
import { readFile as
|
|
10935
|
-
import
|
|
11448
|
+
import { existsSync as existsSync63 } from "fs";
|
|
11449
|
+
import { readFile as readFile18, stat } from "fs/promises";
|
|
11450
|
+
import path44 from "path";
|
|
10936
11451
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
10937
11452
|
import "commander";
|
|
10938
11453
|
import {
|
|
10939
11454
|
codeMapPath as codeMapPath2,
|
|
10940
|
-
findProjectRoot as
|
|
10941
|
-
getUsage as
|
|
10942
|
-
loadCodeMap as
|
|
10943
|
-
loadConfig as
|
|
10944
|
-
loadMemoriesFromDir as
|
|
10945
|
-
loadUsageIndex as
|
|
11455
|
+
findProjectRoot as findProjectRoot41,
|
|
11456
|
+
getUsage as getUsage20,
|
|
11457
|
+
loadCodeMap as loadCodeMap7,
|
|
11458
|
+
loadConfig as loadConfig9,
|
|
11459
|
+
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
11460
|
+
loadUsageIndex as loadUsageIndex26,
|
|
10946
11461
|
readUsageEvents as readUsageEvents4,
|
|
10947
|
-
resolveHaivePaths as
|
|
11462
|
+
resolveHaivePaths as resolveHaivePaths37
|
|
10948
11463
|
} from "@hiveai/core";
|
|
10949
11464
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
|
|
10950
11465
|
function registerDoctor(program2) {
|
|
10951
11466
|
program2.command("doctor").description(
|
|
10952
11467
|
"Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to suggest commands you can copy-paste."
|
|
10953
11468
|
).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10954
|
-
const root =
|
|
10955
|
-
const paths =
|
|
11469
|
+
const root = findProjectRoot41(opts.dir);
|
|
11470
|
+
const paths = resolveHaivePaths37(root);
|
|
10956
11471
|
const findings = [];
|
|
10957
|
-
|
|
11472
|
+
const repairs = [];
|
|
11473
|
+
if (!existsSync63(paths.haiveDir)) {
|
|
10958
11474
|
findings.push({
|
|
10959
11475
|
severity: "error",
|
|
10960
11476
|
code: "not-initialized",
|
|
@@ -10963,7 +11479,18 @@ function registerDoctor(program2) {
|
|
|
10963
11479
|
});
|
|
10964
11480
|
return emit(findings, opts);
|
|
10965
11481
|
}
|
|
10966
|
-
if (!
|
|
11482
|
+
if (opts.fix && !opts.dryRun) {
|
|
11483
|
+
repairs.push(
|
|
11484
|
+
...await applyAutopilotRepairs(root, paths, {
|
|
11485
|
+
applyConfig: true,
|
|
11486
|
+
applyContext: true,
|
|
11487
|
+
applyCorpus: true,
|
|
11488
|
+
applyCodeMap: true,
|
|
11489
|
+
applyCodeSearch: true
|
|
11490
|
+
})
|
|
11491
|
+
);
|
|
11492
|
+
}
|
|
11493
|
+
if (!existsSync63(paths.projectContext)) {
|
|
10967
11494
|
findings.push({
|
|
10968
11495
|
severity: "warn",
|
|
10969
11496
|
code: "no-project-context",
|
|
@@ -10971,8 +11498,8 @@ function registerDoctor(program2) {
|
|
|
10971
11498
|
fix: "haive init"
|
|
10972
11499
|
});
|
|
10973
11500
|
} else {
|
|
10974
|
-
const { readFile:
|
|
10975
|
-
const content = await
|
|
11501
|
+
const { readFile: readFile20 } = await import("fs/promises");
|
|
11502
|
+
const content = await readFile20(paths.projectContext, "utf8");
|
|
10976
11503
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
10977
11504
|
if (isTemplate) {
|
|
10978
11505
|
findings.push({
|
|
@@ -10982,8 +11509,17 @@ function registerDoctor(program2) {
|
|
|
10982
11509
|
fix: "Invoke the bootstrap_project MCP prompt from your AI client."
|
|
10983
11510
|
});
|
|
10984
11511
|
}
|
|
11512
|
+
const versionStatus = await projectContextVersionStatus(root, paths);
|
|
11513
|
+
if (versionStatus.mismatch) {
|
|
11514
|
+
findings.push({
|
|
11515
|
+
severity: "warn",
|
|
11516
|
+
code: "project-context-version-mismatch",
|
|
11517
|
+
message: `.ai/project-context.md version metadata (${versionStatus.currentVersion ?? "missing"}) does not match package.json (${versionStatus.expectedVersion}).`,
|
|
11518
|
+
fix: "haive doctor --fix"
|
|
11519
|
+
});
|
|
11520
|
+
}
|
|
10985
11521
|
}
|
|
10986
|
-
const memories =
|
|
11522
|
+
const memories = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
|
|
10987
11523
|
const now = Date.now();
|
|
10988
11524
|
if (memories.length === 0) {
|
|
10989
11525
|
findings.push({
|
|
@@ -10992,7 +11528,7 @@ function registerDoctor(program2) {
|
|
|
10992
11528
|
message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
|
|
10993
11529
|
});
|
|
10994
11530
|
} else {
|
|
10995
|
-
const usage = await
|
|
11531
|
+
const usage = await loadUsageIndex26(paths);
|
|
10996
11532
|
const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
|
|
10997
11533
|
if (stale.length > 0) {
|
|
10998
11534
|
findings.push({
|
|
@@ -11024,7 +11560,7 @@ function registerDoctor(program2) {
|
|
|
11024
11560
|
}
|
|
11025
11561
|
const decayCandidates = memories.filter((m) => {
|
|
11026
11562
|
if (m.memory.frontmatter.status !== "validated") return false;
|
|
11027
|
-
const u =
|
|
11563
|
+
const u = getUsage20(usage, m.memory.frontmatter.id);
|
|
11028
11564
|
const last = u.last_read_at ?? m.memory.frontmatter.created_at;
|
|
11029
11565
|
return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
|
|
11030
11566
|
});
|
|
@@ -11037,7 +11573,7 @@ function registerDoctor(program2) {
|
|
|
11037
11573
|
});
|
|
11038
11574
|
}
|
|
11039
11575
|
}
|
|
11040
|
-
const codeMap = await
|
|
11576
|
+
const codeMap = await loadCodeMap7(paths);
|
|
11041
11577
|
if (!codeMap) {
|
|
11042
11578
|
findings.push({
|
|
11043
11579
|
severity: "warn",
|
|
@@ -11092,14 +11628,14 @@ function registerDoctor(program2) {
|
|
|
11092
11628
|
});
|
|
11093
11629
|
}
|
|
11094
11630
|
}
|
|
11095
|
-
const config = await
|
|
11631
|
+
const config = await loadConfig9(paths);
|
|
11096
11632
|
if (config.enforcement?.requireBriefingFirst) {
|
|
11097
|
-
const claudeSettings =
|
|
11633
|
+
const claudeSettings = path44.join(root, ".claude", "settings.local.json");
|
|
11098
11634
|
let hasClaudeEnforcement = false;
|
|
11099
|
-
if (
|
|
11635
|
+
if (existsSync63(claudeSettings)) {
|
|
11100
11636
|
try {
|
|
11101
|
-
const { readFile:
|
|
11102
|
-
const raw = await
|
|
11637
|
+
const { readFile: readFile20 } = await import("fs/promises");
|
|
11638
|
+
const raw = await readFile20(claudeSettings, "utf8");
|
|
11103
11639
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
11104
11640
|
} catch {
|
|
11105
11641
|
hasClaudeEnforcement = false;
|
|
@@ -11122,14 +11658,14 @@ function registerDoctor(program2) {
|
|
|
11122
11658
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
11123
11659
|
});
|
|
11124
11660
|
}
|
|
11125
|
-
findings.push(...await collectInstallFindings(root, "0.9.
|
|
11661
|
+
findings.push(...await collectInstallFindings(root, "0.9.18"));
|
|
11126
11662
|
try {
|
|
11127
11663
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
11128
11664
|
encoding: "utf8",
|
|
11129
11665
|
timeout: 3e3,
|
|
11130
11666
|
stdio: ["ignore", "pipe", "ignore"]
|
|
11131
11667
|
}).trim();
|
|
11132
|
-
const cliVersion = "0.9.
|
|
11668
|
+
const cliVersion = "0.9.18";
|
|
11133
11669
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
11134
11670
|
findings.push({
|
|
11135
11671
|
severity: "warn",
|
|
@@ -11142,10 +11678,18 @@ npm uninstall -g @hiveai/mcp`
|
|
|
11142
11678
|
}
|
|
11143
11679
|
} catch {
|
|
11144
11680
|
}
|
|
11145
|
-
|
|
11681
|
+
if (repairs.length > 0) {
|
|
11682
|
+
findings.push({
|
|
11683
|
+
severity: "info",
|
|
11684
|
+
code: "autopilot-repairs-applied",
|
|
11685
|
+
message: repairs.map((repair) => repair.message).join(" "),
|
|
11686
|
+
section: "Next actions"
|
|
11687
|
+
});
|
|
11688
|
+
}
|
|
11689
|
+
emit(findings, opts, repairs);
|
|
11146
11690
|
});
|
|
11147
11691
|
}
|
|
11148
|
-
function emit(findings, opts) {
|
|
11692
|
+
function emit(findings, opts, repairs = []) {
|
|
11149
11693
|
const classified = findings.map((finding) => ({
|
|
11150
11694
|
...finding,
|
|
11151
11695
|
section: finding.section ?? sectionForFinding(finding)
|
|
@@ -11157,12 +11701,13 @@ function emit(findings, opts) {
|
|
|
11157
11701
|
findings: classified,
|
|
11158
11702
|
sections: groupBySection(classified),
|
|
11159
11703
|
next_actions: nextActions(classified),
|
|
11160
|
-
|
|
11704
|
+
repairs,
|
|
11705
|
+
fix_mode: opts.fix ? opts.dryRun ? "dry-run" : "apply" : "off"
|
|
11161
11706
|
}, null, 2));
|
|
11162
11707
|
return;
|
|
11163
11708
|
}
|
|
11164
11709
|
if (classified.length === 0) {
|
|
11165
|
-
ui.success("hAIve doctor \u2014 no issues found.");
|
|
11710
|
+
ui.success(repairs.length > 0 ? "hAIve doctor \u2014 autopilot repairs applied." : "hAIve doctor \u2014 no issues found.");
|
|
11166
11711
|
return;
|
|
11167
11712
|
}
|
|
11168
11713
|
console.log(ui.bold(`hAIve doctor \u2014 ${classified.length} finding${classified.length === 1 ? "" : "s"}`));
|
|
@@ -11198,6 +11743,11 @@ function emit(findings, opts) {
|
|
|
11198
11743
|
}
|
|
11199
11744
|
console.log();
|
|
11200
11745
|
}
|
|
11746
|
+
if (repairs.length > 0) {
|
|
11747
|
+
console.log(ui.bold("Autopilot repairs applied"));
|
|
11748
|
+
for (const repair of repairs) console.log(` ${ui.dim("\u2713")} ${repair.message}`);
|
|
11749
|
+
console.log();
|
|
11750
|
+
}
|
|
11201
11751
|
const actions = nextActions(classified);
|
|
11202
11752
|
if (actions.length > 0) {
|
|
11203
11753
|
console.log(ui.bold("Next actions"));
|
|
@@ -11289,9 +11839,9 @@ which -a haive`
|
|
|
11289
11839
|
".vscode/mcp.json"
|
|
11290
11840
|
];
|
|
11291
11841
|
for (const rel of integrationFiles) {
|
|
11292
|
-
const file =
|
|
11293
|
-
if (!
|
|
11294
|
-
const text = await
|
|
11842
|
+
const file = path44.join(root, rel);
|
|
11843
|
+
if (!existsSync63(file)) continue;
|
|
11844
|
+
const text = await readFile18(file, "utf8").catch(() => "");
|
|
11295
11845
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
11296
11846
|
const version = versionForBinary(bin);
|
|
11297
11847
|
if (!version) {
|
|
@@ -11347,22 +11897,22 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
11347
11897
|
}
|
|
11348
11898
|
|
|
11349
11899
|
// src/commands/playback.ts
|
|
11350
|
-
import { existsSync as
|
|
11900
|
+
import { existsSync as existsSync64 } from "fs";
|
|
11351
11901
|
import "commander";
|
|
11352
11902
|
import {
|
|
11353
|
-
findProjectRoot as
|
|
11354
|
-
loadMemoriesFromDir as
|
|
11903
|
+
findProjectRoot as findProjectRoot42,
|
|
11904
|
+
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
11355
11905
|
parseSince as parseSince3,
|
|
11356
11906
|
readUsageEvents as readUsageEvents5,
|
|
11357
|
-
resolveHaivePaths as
|
|
11907
|
+
resolveHaivePaths as resolveHaivePaths38
|
|
11358
11908
|
} from "@hiveai/core";
|
|
11359
11909
|
var MS_PER_MINUTE = 6e4;
|
|
11360
11910
|
function registerPlayback(program2) {
|
|
11361
11911
|
program2.command("playback").description(
|
|
11362
11912
|
"Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
|
|
11363
11913
|
).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11364
|
-
const root =
|
|
11365
|
-
const paths =
|
|
11914
|
+
const root = findProjectRoot42(opts.dir);
|
|
11915
|
+
const paths = resolveHaivePaths38(root);
|
|
11366
11916
|
const events = await readUsageEvents5(paths);
|
|
11367
11917
|
if (events.length === 0) {
|
|
11368
11918
|
if (opts.json) {
|
|
@@ -11377,7 +11927,7 @@ function registerPlayback(program2) {
|
|
|
11377
11927
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
11378
11928
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
11379
11929
|
const sessions = bucketSessions(filtered, gapMs);
|
|
11380
|
-
const all =
|
|
11930
|
+
const all = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
|
|
11381
11931
|
const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
|
|
11382
11932
|
const enriched = sessions.map((s, i) => {
|
|
11383
11933
|
const startMs = Date.parse(s.start);
|
|
@@ -11467,8 +12017,8 @@ function truncate3(text, max) {
|
|
|
11467
12017
|
import { spawn as spawn4 } from "child_process";
|
|
11468
12018
|
import "commander";
|
|
11469
12019
|
import {
|
|
11470
|
-
findProjectRoot as
|
|
11471
|
-
resolveHaivePaths as
|
|
12020
|
+
findProjectRoot as findProjectRoot43,
|
|
12021
|
+
resolveHaivePaths as resolveHaivePaths39
|
|
11472
12022
|
} from "@hiveai/core";
|
|
11473
12023
|
function registerPrecommit(program2) {
|
|
11474
12024
|
program2.command("precommit").description(
|
|
@@ -11478,8 +12028,8 @@ function registerPrecommit(program2) {
|
|
|
11478
12028
|
"'any' | 'high-confidence' (default) | 'never' (report only)",
|
|
11479
12029
|
"high-confidence"
|
|
11480
12030
|
).option("--no-semantic", "disable semantic search in anti-patterns matching").option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11481
|
-
const root =
|
|
11482
|
-
const paths =
|
|
12031
|
+
const root = findProjectRoot43(opts.dir);
|
|
12032
|
+
const paths = resolveHaivePaths39(root);
|
|
11483
12033
|
const ctx = { paths };
|
|
11484
12034
|
let diff = "";
|
|
11485
12035
|
let touchedPaths = opts.paths ?? [];
|
|
@@ -11513,401 +12063,142 @@ function registerPrecommit(program2) {
|
|
|
11513
12063
|
` anti-patterns: ${result.summary.anti_patterns} blocking: ${result.summary.blocking_warnings ?? result.summary.anti_patterns} review: ${result.summary.review_warnings ?? 0} info: ${result.summary.info_warnings ?? 0} relevant memories: ${result.summary.relevant_memories} stale anchors: ${result.summary.stale_anchors}`
|
|
11514
12064
|
)
|
|
11515
12065
|
);
|
|
11516
|
-
console.log();
|
|
11517
|
-
const blocking = result.warnings.filter((w) => w.level === "blocking");
|
|
11518
|
-
const review = result.warnings.filter((w) => w.level === "review");
|
|
11519
|
-
const info = result.warnings.filter((w) => w.level === "info");
|
|
11520
|
-
printWarnings("Blocking anti-patterns", blocking, "error");
|
|
11521
|
-
printWarnings("Review anti-patterns", review.slice(0, 8), "warn");
|
|
11522
|
-
if (info.length > 0) {
|
|
11523
|
-
console.log(
|
|
11524
|
-
ui.dim(
|
|
11525
|
-
`${info.length} weak anti-pattern signal${info.length === 1 ? "" : "s"} hidden. Use --json to inspect FYI matches.`
|
|
11526
|
-
)
|
|
11527
|
-
);
|
|
11528
|
-
console.log();
|
|
11529
|
-
}
|
|
11530
|
-
if (result.relevant_memories.length > 0) {
|
|
11531
|
-
console.log(ui.bold("\u{1F4CC} Relevant conventions/decisions:"));
|
|
11532
|
-
for (const m of result.relevant_memories) {
|
|
11533
|
-
console.log(` \u2022 ${m.id} ${ui.dim(`(${m.type}, ${m.confidence})`)}`);
|
|
11534
|
-
}
|
|
11535
|
-
console.log();
|
|
11536
|
-
}
|
|
11537
|
-
if (result.stale_anchors.length > 0) {
|
|
11538
|
-
console.log(ui.bold("\u{1F552} Stale anchored memories:"));
|
|
11539
|
-
for (const s of result.stale_anchors) {
|
|
11540
|
-
console.log(` \u2022 ${s.id}`);
|
|
11541
|
-
if (s.body_preview) console.log(` ${ui.dim(s.body_preview)}`);
|
|
11542
|
-
}
|
|
11543
|
-
console.log();
|
|
11544
|
-
}
|
|
11545
|
-
if (result.should_block) {
|
|
11546
|
-
ui.error(`Blocking commit (block_on=${opts.blockOn ?? "high-confidence"}). Address the warnings above or pass --block-on never to bypass.`);
|
|
11547
|
-
process.exit(1);
|
|
11548
|
-
}
|
|
11549
|
-
if (result.warnings.length === 0 && result.stale_anchors.length === 0) {
|
|
11550
|
-
ui.success("No anti-patterns or stale anchors found.");
|
|
11551
|
-
} else {
|
|
11552
|
-
ui.success("Check passed (block_on threshold not met).");
|
|
11553
|
-
}
|
|
11554
|
-
});
|
|
11555
|
-
}
|
|
11556
|
-
function printWarnings(title, warnings, tone) {
|
|
11557
|
-
if (warnings.length === 0) return;
|
|
11558
|
-
console.log(ui.bold(tone === "error" ? `\u2717 ${title}:` : `\u26A0 ${title}:`));
|
|
11559
|
-
for (const w of warnings) {
|
|
11560
|
-
const marker = tone === "error" ? ui.red("\u2717") : ui.yellow("\u26A0");
|
|
11561
|
-
console.log(` ${marker} ${w.id} ${ui.dim(`(${w.type}, ${w.confidence})`)}`);
|
|
11562
|
-
for (const line of w.body_preview.split("\n").slice(0, 3)) {
|
|
11563
|
-
console.log(` ${ui.dim(line)}`);
|
|
11564
|
-
}
|
|
11565
|
-
console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
|
|
11566
|
-
if (w.affected_files && w.affected_files.length > 0) {
|
|
11567
|
-
console.log(` ${ui.dim("files:")} ${w.affected_files.slice(0, 4).join(", ")}`);
|
|
11568
|
-
}
|
|
11569
|
-
if (w.rationale) console.log(` ${ui.dim("why shown:")} ${w.rationale}`);
|
|
11570
|
-
if (w.repair_command) console.log(` ${ui.dim("repair:")} ${w.repair_command}`);
|
|
11571
|
-
}
|
|
11572
|
-
console.log();
|
|
11573
|
-
}
|
|
11574
|
-
function runCommand3(cmd, args, cwd) {
|
|
11575
|
-
return new Promise((resolve, reject) => {
|
|
11576
|
-
const proc = spawn4(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
11577
|
-
let stdout = "";
|
|
11578
|
-
let stderr = "";
|
|
11579
|
-
proc.stdout.on("data", (chunk) => {
|
|
11580
|
-
stdout += chunk.toString();
|
|
11581
|
-
});
|
|
11582
|
-
proc.stderr.on("data", (chunk) => {
|
|
11583
|
-
stderr += chunk.toString();
|
|
11584
|
-
});
|
|
11585
|
-
proc.on("error", reject);
|
|
11586
|
-
proc.on("close", (code) => {
|
|
11587
|
-
if (code === 0) resolve(stdout);
|
|
11588
|
-
else reject(new Error(stderr || `${cmd} exited with code ${code}`));
|
|
11589
|
-
});
|
|
11590
|
-
});
|
|
11591
|
-
}
|
|
11592
|
-
|
|
11593
|
-
// src/commands/welcome.ts
|
|
11594
|
-
import { existsSync as existsSync63 } from "fs";
|
|
11595
|
-
import "commander";
|
|
11596
|
-
import {
|
|
11597
|
-
findProjectRoot as findProjectRoot43,
|
|
11598
|
-
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
11599
|
-
resolveHaivePaths as resolveHaivePaths39
|
|
11600
|
-
} from "@hiveai/core";
|
|
11601
|
-
var TYPE_RANK = {
|
|
11602
|
-
decision: 0,
|
|
11603
|
-
architecture: 1,
|
|
11604
|
-
convention: 2,
|
|
11605
|
-
glossary: 3,
|
|
11606
|
-
gotcha: 4,
|
|
11607
|
-
attempt: 5
|
|
11608
|
-
};
|
|
11609
|
-
function registerWelcome(program2) {
|
|
11610
|
-
program2.command("welcome").description(
|
|
11611
|
-
"Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
|
|
11612
|
-
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11613
|
-
const root = findProjectRoot43(opts.dir);
|
|
11614
|
-
const paths = resolveHaivePaths39(root);
|
|
11615
|
-
if (!existsSync63(paths.memoriesDir)) {
|
|
11616
|
-
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
11617
|
-
process.exitCode = 1;
|
|
11618
|
-
return;
|
|
11619
|
-
}
|
|
11620
|
-
const all = await loadMemoriesFromDir34(paths.memoriesDir);
|
|
11621
|
-
const team = all.filter(
|
|
11622
|
-
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
11623
|
-
);
|
|
11624
|
-
team.sort((a, b) => {
|
|
11625
|
-
const ta = TYPE_RANK[a.memory.frontmatter.type] ?? 99;
|
|
11626
|
-
const tb = TYPE_RANK[b.memory.frontmatter.type] ?? 99;
|
|
11627
|
-
if (ta !== tb) return ta - tb;
|
|
11628
|
-
const sta = a.memory.frontmatter.status === "validated" ? 0 : 1;
|
|
11629
|
-
const stb = b.memory.frontmatter.status === "validated" ? 0 : 1;
|
|
11630
|
-
if (sta !== stb) return sta - stb;
|
|
11631
|
-
return b.memory.frontmatter.created_at.localeCompare(a.memory.frontmatter.created_at);
|
|
11632
|
-
});
|
|
11633
|
-
const cap = Math.max(1, Math.min(500, Number(opts.limit) || 20));
|
|
11634
|
-
const pick = team.slice(0, cap);
|
|
11635
|
-
console.log(ui.bold(`hAIve welcome \u2014 ${pick.length} team memories (${root})`));
|
|
11636
|
-
console.log(ui.dim(`Next: invoke get_briefing with your task or run 'haive briefing --task "\u2026"'`));
|
|
11637
|
-
if (pick.length === 0) {
|
|
11638
|
-
ui.warn("No team memories yet \u2014 add some with 'haive memory add' or promote personal ones.");
|
|
11639
|
-
return;
|
|
11640
|
-
}
|
|
11641
|
-
let i = 1;
|
|
11642
|
-
for (const { memory: memory2 } of pick) {
|
|
11643
|
-
const fm = memory2.frontmatter;
|
|
11644
|
-
const head = memory2.body.match(/^#\s+(.+)/m)?.[1]?.trim();
|
|
11645
|
-
const line = head ?? fm.id;
|
|
11646
|
-
console.log(
|
|
11647
|
-
`${String(i).padStart(2, " ")} ${fm.type.padEnd(12)} ${fm.status.padEnd(10)} ${ui.dim(fm.id)}
|
|
11648
|
-
${line}`
|
|
11649
|
-
);
|
|
11650
|
-
i++;
|
|
11651
|
-
}
|
|
11652
|
-
});
|
|
11653
|
-
}
|
|
11654
|
-
|
|
11655
|
-
// src/commands/memory-lint.ts
|
|
11656
|
-
import { existsSync as existsSync64 } from "fs";
|
|
11657
|
-
import { writeFile as writeFile31 } from "fs/promises";
|
|
11658
|
-
import path43 from "path";
|
|
11659
|
-
import "commander";
|
|
11660
|
-
import {
|
|
11661
|
-
findProjectRoot as findProjectRoot44,
|
|
11662
|
-
getUsage as getUsage20,
|
|
11663
|
-
loadCodeMap as loadCodeMap6,
|
|
11664
|
-
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
11665
|
-
loadUsageIndex as loadUsageIndex26,
|
|
11666
|
-
resolveHaivePaths as resolveHaivePaths40,
|
|
11667
|
-
serializeMemory as serializeMemory26
|
|
11668
|
-
} from "@hiveai/core";
|
|
11669
|
-
async function lintMemoriesAsync(root, options = {}) {
|
|
11670
|
-
const paths = resolveHaivePaths40(root);
|
|
11671
|
-
const out = [];
|
|
11672
|
-
const fixes = [];
|
|
11673
|
-
if (!existsSync64(paths.memoriesDir)) return { findings: out, fixes };
|
|
11674
|
-
const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
|
|
11675
|
-
const usage = await loadUsageIndex26(paths);
|
|
11676
|
-
const codeMap = await loadCodeMap6(paths);
|
|
11677
|
-
const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
|
|
11678
|
-
const actionableWords = /\b(always|never|prefer|use|avoid|because|instead|why|rationale|do not|must|should)\b/i;
|
|
11679
|
-
for (const { filePath, memory: memory2 } of loaded) {
|
|
11680
|
-
const fm = memory2.frontmatter;
|
|
11681
|
-
if (fm.type === "session_recap") continue;
|
|
11682
|
-
const body = memory2.body.trim();
|
|
11683
|
-
const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
|
|
11684
|
-
if (naked.length < 40 && fm.status !== "rejected") {
|
|
11685
|
-
out.push({
|
|
11686
|
-
file: filePath,
|
|
11687
|
-
id: fm.id,
|
|
11688
|
-
severity: "warn",
|
|
11689
|
-
code: "SHORT_BODY",
|
|
11690
|
-
message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
|
|
11691
|
-
});
|
|
11692
|
-
}
|
|
11693
|
-
if (["decision", "gotcha", "convention", "architecture", "attempt"].includes(fm.type) && fm.status !== "rejected" && !actionableWords.test(naked)) {
|
|
11694
|
-
out.push({
|
|
11695
|
-
file: filePath,
|
|
11696
|
-
id: fm.id,
|
|
11697
|
-
severity: "info",
|
|
11698
|
-
code: "LOW_ACTIONABILITY",
|
|
11699
|
-
message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
|
|
11700
|
-
});
|
|
11701
|
-
}
|
|
11702
|
-
const suggestedAnchors = suggestAnchors(root, { filePath, memory: memory2 }, codeMap);
|
|
11703
|
-
if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
|
|
11704
|
-
out.push({
|
|
11705
|
-
file: filePath,
|
|
11706
|
-
id: fm.id,
|
|
11707
|
-
severity: "warn",
|
|
11708
|
-
code: "MISSING_ANCHOR",
|
|
11709
|
-
message: `${fm.type} is validated without anchor paths \u2014 add anchor.paths so haive sync can flag staleness.`,
|
|
11710
|
-
...suggestedAnchors.paths.length > 0 || suggestedAnchors.symbols.length > 0 ? { suggested_anchors: suggestedAnchors } : {}
|
|
11711
|
-
});
|
|
11712
|
-
}
|
|
11713
|
-
if (fm.status === "stale" && !fm.stale_reason) {
|
|
11714
|
-
out.push({
|
|
11715
|
-
file: filePath,
|
|
11716
|
-
id: fm.id,
|
|
11717
|
-
severity: "info",
|
|
11718
|
-
code: "STALE_NO_REASON",
|
|
11719
|
-
message: "Status is stale but stale_reason is empty \u2014 document why when possible."
|
|
11720
|
-
});
|
|
11721
|
-
}
|
|
11722
|
-
if (fm.type === "glossary" && naked.length > 6e3) {
|
|
11723
|
-
out.push({
|
|
11724
|
-
file: filePath,
|
|
11725
|
-
id: fm.id,
|
|
11726
|
-
severity: "info",
|
|
11727
|
-
code: "LONG_GLOSSARY",
|
|
11728
|
-
message: "Very long glossary \u2014 consider splitting concepts for tighter briefings."
|
|
11729
|
-
});
|
|
11730
|
-
}
|
|
11731
|
-
const hasMarkdownHeading = /^#{1,3}\s+\S/m.test(memory2.body.trim());
|
|
11732
|
-
if (!hasMarkdownHeading) {
|
|
11733
|
-
out.push({
|
|
11734
|
-
file: filePath,
|
|
11735
|
-
id: fm.id,
|
|
11736
|
-
severity: "warn",
|
|
11737
|
-
code: "NO_MD_HEADING",
|
|
11738
|
-
message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
|
|
11739
|
-
});
|
|
11740
|
-
}
|
|
11741
|
-
const u = getUsage20(usage, fm.id);
|
|
11742
|
-
if (fm.status === "validated" && u.read_count === 0) {
|
|
11743
|
-
out.push({
|
|
11744
|
-
file: filePath,
|
|
11745
|
-
id: fm.id,
|
|
11746
|
-
severity: "info",
|
|
11747
|
-
code: "NEVER_READ",
|
|
11748
|
-
message: "Validated record has never been surfaced/read. Consider improving tags/anchors or archiving it if it is not useful."
|
|
11749
|
-
});
|
|
11750
|
-
}
|
|
11751
|
-
if (options.fix) {
|
|
11752
|
-
const actions = [];
|
|
11753
|
-
let nextBody = memory2.body;
|
|
11754
|
-
let nextFrontmatter = memory2.frontmatter;
|
|
11755
|
-
if (!hasMarkdownHeading) {
|
|
11756
|
-
nextBody = `# ${titleFromId(fm.id)}
|
|
11757
|
-
|
|
11758
|
-
${nextBody.trim()}`;
|
|
11759
|
-
actions.push("add missing Markdown heading");
|
|
11760
|
-
}
|
|
11761
|
-
if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.anchor.symbols.length === 0 && fm.status === "validated" && !fm.tags.includes("needs_anchor")) {
|
|
11762
|
-
nextFrontmatter = {
|
|
11763
|
-
...nextFrontmatter,
|
|
11764
|
-
tags: [...nextFrontmatter.tags, "needs_anchor"]
|
|
11765
|
-
};
|
|
11766
|
-
actions.push("tag validated anchorless record with needs_anchor");
|
|
11767
|
-
}
|
|
11768
|
-
if (actions.length > 0) {
|
|
11769
|
-
fixes.push({ file: filePath, id: fm.id, actions, applied: Boolean(options.apply) });
|
|
11770
|
-
if (options.apply) {
|
|
11771
|
-
await writeFile31(
|
|
11772
|
-
filePath,
|
|
11773
|
-
serializeMemory26({ frontmatter: nextFrontmatter, body: nextBody }),
|
|
11774
|
-
"utf8"
|
|
11775
|
-
);
|
|
11776
|
-
}
|
|
12066
|
+
console.log();
|
|
12067
|
+
const blocking = result.warnings.filter((w) => w.level === "blocking");
|
|
12068
|
+
const review = result.warnings.filter((w) => w.level === "review");
|
|
12069
|
+
const info = result.warnings.filter((w) => w.level === "info");
|
|
12070
|
+
printWarnings("Blocking anti-patterns", blocking, "error");
|
|
12071
|
+
printWarnings("Review anti-patterns", review.slice(0, 8), "warn");
|
|
12072
|
+
if (info.length > 0) {
|
|
12073
|
+
console.log(
|
|
12074
|
+
ui.dim(
|
|
12075
|
+
`${info.length} weak anti-pattern signal${info.length === 1 ? "" : "s"} hidden. Use --json to inspect FYI matches.`
|
|
12076
|
+
)
|
|
12077
|
+
);
|
|
12078
|
+
console.log();
|
|
12079
|
+
}
|
|
12080
|
+
if (result.relevant_memories.length > 0) {
|
|
12081
|
+
console.log(ui.bold("\u{1F4CC} Relevant conventions/decisions:"));
|
|
12082
|
+
for (const m of result.relevant_memories) {
|
|
12083
|
+
console.log(` \u2022 ${m.id} ${ui.dim(`(${m.type}, ${m.confidence})`)}`);
|
|
11777
12084
|
}
|
|
12085
|
+
console.log();
|
|
11778
12086
|
}
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
severity: "warn",
|
|
11785
|
-
code: "NEAR_DUPLICATE",
|
|
11786
|
-
message: `Body overlaps ~${Math.round(dup.score * 100)}% with ${dup.otherId}. Merge or deprecate one record to reduce briefing noise.`
|
|
11787
|
-
});
|
|
11788
|
-
}
|
|
11789
|
-
return { findings: out, fixes };
|
|
11790
|
-
}
|
|
11791
|
-
function titleFromId(id) {
|
|
11792
|
-
const withoutDate = id.replace(/^\d{4}-\d{2}-\d{2}-/, "");
|
|
11793
|
-
return withoutDate.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
11794
|
-
}
|
|
11795
|
-
function suggestAnchors(root, loaded, codeMap) {
|
|
11796
|
-
const body = loaded.memory.body;
|
|
11797
|
-
const paths = /* @__PURE__ */ new Set();
|
|
11798
|
-
const symbols = /* @__PURE__ */ new Set();
|
|
11799
|
-
for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
|
|
11800
|
-
const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
|
|
11801
|
-
if (!candidate || candidate.startsWith("http")) continue;
|
|
11802
|
-
if (existsSync64(path43.join(root, candidate))) paths.add(candidate);
|
|
11803
|
-
}
|
|
11804
|
-
if (codeMap) {
|
|
11805
|
-
const lowered = body.toLowerCase();
|
|
11806
|
-
for (const [file, entry] of Object.entries(codeMap.files)) {
|
|
11807
|
-
for (const exp of entry.exports) {
|
|
11808
|
-
if (!exp.name || exp.name.length < 4) continue;
|
|
11809
|
-
if (lowered.includes(exp.name.toLowerCase())) {
|
|
11810
|
-
paths.add(file);
|
|
11811
|
-
symbols.add(exp.name);
|
|
11812
|
-
}
|
|
11813
|
-
if (paths.size >= 5 && symbols.size >= 5) break;
|
|
12087
|
+
if (result.stale_anchors.length > 0) {
|
|
12088
|
+
console.log(ui.bold("\u{1F552} Stale anchored memories:"));
|
|
12089
|
+
for (const s of result.stale_anchors) {
|
|
12090
|
+
console.log(` \u2022 ${s.id}`);
|
|
12091
|
+
if (s.body_preview) console.log(` ${ui.dim(s.body_preview)}`);
|
|
11814
12092
|
}
|
|
11815
|
-
|
|
12093
|
+
console.log();
|
|
12094
|
+
}
|
|
12095
|
+
if (result.should_block) {
|
|
12096
|
+
ui.error(`Blocking commit (block_on=${opts.blockOn ?? "high-confidence"}). Address the warnings above or pass --block-on never to bypass.`);
|
|
12097
|
+
process.exit(1);
|
|
12098
|
+
}
|
|
12099
|
+
if (result.warnings.length === 0 && result.stale_anchors.length === 0) {
|
|
12100
|
+
ui.success("No anti-patterns or stale anchors found.");
|
|
12101
|
+
} else {
|
|
12102
|
+
ui.success("Check passed (block_on threshold not met).");
|
|
11816
12103
|
}
|
|
11817
|
-
}
|
|
11818
|
-
return {
|
|
11819
|
-
paths: [...paths].slice(0, 5),
|
|
11820
|
-
symbols: [...symbols].slice(0, 5)
|
|
11821
|
-
};
|
|
11822
|
-
}
|
|
11823
|
-
function nearDuplicatePairs(loaded) {
|
|
11824
|
-
const out = [];
|
|
11825
|
-
const candidates = loaded.filter(({ memory: memory2 }) => {
|
|
11826
|
-
const fm = memory2.frontmatter;
|
|
11827
|
-
return fm.type !== "session_recap" && fm.status !== "rejected" && fm.status !== "deprecated";
|
|
11828
12104
|
});
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
});
|
|
11843
|
-
}
|
|
12105
|
+
}
|
|
12106
|
+
function printWarnings(title, warnings, tone) {
|
|
12107
|
+
if (warnings.length === 0) return;
|
|
12108
|
+
console.log(ui.bold(tone === "error" ? `\u2717 ${title}:` : `\u26A0 ${title}:`));
|
|
12109
|
+
for (const w of warnings) {
|
|
12110
|
+
const marker = tone === "error" ? ui.red("\u2717") : ui.yellow("\u26A0");
|
|
12111
|
+
console.log(` ${marker} ${w.id} ${ui.dim(`(${w.type}, ${w.confidence})`)}`);
|
|
12112
|
+
for (const line of w.body_preview.split("\n").slice(0, 3)) {
|
|
12113
|
+
console.log(` ${ui.dim(line)}`);
|
|
12114
|
+
}
|
|
12115
|
+
console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
|
|
12116
|
+
if (w.affected_files && w.affected_files.length > 0) {
|
|
12117
|
+
console.log(` ${ui.dim("files:")} ${w.affected_files.slice(0, 4).join(", ")}`);
|
|
11844
12118
|
}
|
|
12119
|
+
if (w.rationale) console.log(` ${ui.dim("why shown:")} ${w.rationale}`);
|
|
12120
|
+
if (w.repair_command) console.log(` ${ui.dim("repair:")} ${w.repair_command}`);
|
|
11845
12121
|
}
|
|
11846
|
-
|
|
11847
|
-
}
|
|
11848
|
-
function tokenSet(body) {
|
|
11849
|
-
return new Set(
|
|
11850
|
-
(body.toLowerCase().match(/\b[a-z0-9]{4,}\b/g) ?? []).filter((word) => !["this", "that", "with", "from", "have"].includes(word))
|
|
11851
|
-
);
|
|
12122
|
+
console.log();
|
|
11852
12123
|
}
|
|
11853
|
-
function
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
12124
|
+
function runCommand3(cmd, args, cwd) {
|
|
12125
|
+
return new Promise((resolve, reject) => {
|
|
12126
|
+
const proc = spawn4(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
12127
|
+
let stdout = "";
|
|
12128
|
+
let stderr = "";
|
|
12129
|
+
proc.stdout.on("data", (chunk) => {
|
|
12130
|
+
stdout += chunk.toString();
|
|
12131
|
+
});
|
|
12132
|
+
proc.stderr.on("data", (chunk) => {
|
|
12133
|
+
stderr += chunk.toString();
|
|
12134
|
+
});
|
|
12135
|
+
proc.on("error", reject);
|
|
12136
|
+
proc.on("close", (code) => {
|
|
12137
|
+
if (code === 0) resolve(stdout);
|
|
12138
|
+
else reject(new Error(stderr || `${cmd} exited with code ${code}`));
|
|
12139
|
+
});
|
|
12140
|
+
});
|
|
11858
12141
|
}
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
12142
|
+
|
|
12143
|
+
// src/commands/welcome.ts
|
|
12144
|
+
import { existsSync as existsSync65 } from "fs";
|
|
12145
|
+
import "commander";
|
|
12146
|
+
import {
|
|
12147
|
+
findProjectRoot as findProjectRoot44,
|
|
12148
|
+
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
12149
|
+
resolveHaivePaths as resolveHaivePaths40
|
|
12150
|
+
} from "@hiveai/core";
|
|
12151
|
+
var TYPE_RANK = {
|
|
12152
|
+
decision: 0,
|
|
12153
|
+
architecture: 1,
|
|
12154
|
+
convention: 2,
|
|
12155
|
+
glossary: 3,
|
|
12156
|
+
gotcha: 4,
|
|
12157
|
+
attempt: 5
|
|
12158
|
+
};
|
|
12159
|
+
function registerWelcome(program2) {
|
|
12160
|
+
program2.command("welcome").description(
|
|
12161
|
+
"Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
|
|
12162
|
+
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11863
12163
|
const root = findProjectRoot44(opts.dir);
|
|
11864
|
-
const
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
if (opts.json) {
|
|
11869
|
-
console.log(JSON.stringify({
|
|
11870
|
-
findings_count: findings.length,
|
|
11871
|
-
findings,
|
|
11872
|
-
fixes_count: report.fixes.length,
|
|
11873
|
-
fixes: report.fixes,
|
|
11874
|
-
fix_mode: opts.fix ? apply ? "apply" : "dry-run" : "off"
|
|
11875
|
-
}, null, 2));
|
|
11876
|
-
process.exitCode = findings.some((f) => f.severity === "error") ? 1 : 0;
|
|
12164
|
+
const paths = resolveHaivePaths40(root);
|
|
12165
|
+
if (!existsSync65(paths.memoriesDir)) {
|
|
12166
|
+
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
12167
|
+
process.exitCode = 1;
|
|
11877
12168
|
return;
|
|
11878
12169
|
}
|
|
11879
|
-
|
|
11880
|
-
|
|
12170
|
+
const all = await loadMemoriesFromDir35(paths.memoriesDir);
|
|
12171
|
+
const team = all.filter(
|
|
12172
|
+
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
12173
|
+
);
|
|
12174
|
+
team.sort((a, b) => {
|
|
12175
|
+
const ta = TYPE_RANK[a.memory.frontmatter.type] ?? 99;
|
|
12176
|
+
const tb = TYPE_RANK[b.memory.frontmatter.type] ?? 99;
|
|
12177
|
+
if (ta !== tb) return ta - tb;
|
|
12178
|
+
const sta = a.memory.frontmatter.status === "validated" ? 0 : 1;
|
|
12179
|
+
const stb = b.memory.frontmatter.status === "validated" ? 0 : 1;
|
|
12180
|
+
if (sta !== stb) return sta - stb;
|
|
12181
|
+
return b.memory.frontmatter.created_at.localeCompare(a.memory.frontmatter.created_at);
|
|
12182
|
+
});
|
|
12183
|
+
const cap = Math.max(1, Math.min(500, Number(opts.limit) || 20));
|
|
12184
|
+
const pick = team.slice(0, cap);
|
|
12185
|
+
console.log(ui.bold(`hAIve welcome \u2014 ${pick.length} team memories (${root})`));
|
|
12186
|
+
console.log(ui.dim(`Next: invoke get_briefing with your task or run 'haive briefing --task "\u2026"'`));
|
|
12187
|
+
if (pick.length === 0) {
|
|
12188
|
+
ui.warn("No team memories yet \u2014 add some with 'haive memory add' or promote personal ones.");
|
|
11881
12189
|
return;
|
|
11882
12190
|
}
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
const
|
|
11887
|
-
const
|
|
11888
|
-
console.log(ui.bold(`fix ${mode}: ${report.fixes.length} file${report.fixes.length === 1 ? "" : "s"} ${verb}`));
|
|
11889
|
-
for (const fix of report.fixes) {
|
|
11890
|
-
console.log(` ${ui.dim(fix.id)} ${fix.actions.join("; ")}`);
|
|
11891
|
-
console.log(ui.dim(` \u2192 ${fix.file}`));
|
|
11892
|
-
}
|
|
11893
|
-
console.log();
|
|
11894
|
-
}
|
|
11895
|
-
const order = { error: 0, warn: 1, info: 2 };
|
|
11896
|
-
findings.sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
|
|
11897
|
-
for (const f of findings) {
|
|
11898
|
-
const color = f.severity === "error" ? ui.red : f.severity === "warn" ? ui.yellow : ui.dim;
|
|
12191
|
+
let i = 1;
|
|
12192
|
+
for (const { memory: memory2 } of pick) {
|
|
12193
|
+
const fm = memory2.frontmatter;
|
|
12194
|
+
const head = memory2.body.match(/^#\s+(.+)/m)?.[1]?.trim();
|
|
12195
|
+
const line = head ?? fm.id;
|
|
11899
12196
|
console.log(
|
|
11900
|
-
`${
|
|
12197
|
+
`${String(i).padStart(2, " ")} ${fm.type.padEnd(12)} ${fm.status.padEnd(10)} ${ui.dim(fm.id)}
|
|
12198
|
+
${line}`
|
|
11901
12199
|
);
|
|
11902
|
-
|
|
11903
|
-
if (f.suggested_anchors) {
|
|
11904
|
-
const pathHints = f.suggested_anchors.paths.length > 0 ? `paths: ${f.suggested_anchors.paths.join(", ")}` : "";
|
|
11905
|
-
const symbolHints = f.suggested_anchors.symbols.length > 0 ? `symbols: ${f.suggested_anchors.symbols.join(", ")}` : "";
|
|
11906
|
-
console.log(ui.dim(` suggested anchors: ${[pathHints, symbolHints].filter(Boolean).join(" \xB7 ")}`));
|
|
11907
|
-
}
|
|
11908
|
-
console.log(ui.dim(` \u2192 ${f.file}`));
|
|
12200
|
+
i++;
|
|
11909
12201
|
}
|
|
11910
|
-
process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
|
|
11911
12202
|
});
|
|
11912
12203
|
}
|
|
11913
12204
|
|
|
@@ -11930,21 +12221,21 @@ function registerMemorySuggestTopic(memory2) {
|
|
|
11930
12221
|
}
|
|
11931
12222
|
|
|
11932
12223
|
// src/commands/resolve-project.ts
|
|
11933
|
-
import
|
|
12224
|
+
import path45 from "path";
|
|
11934
12225
|
import "commander";
|
|
11935
12226
|
import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
|
|
11936
12227
|
function registerResolveProject(program2) {
|
|
11937
12228
|
program2.command("resolve-project").description(
|
|
11938
12229
|
"Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
11939
12230
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
11940
|
-
const info = resolveProjectInfo2({ cwd:
|
|
12231
|
+
const info = resolveProjectInfo2({ cwd: path45.resolve(opts.dir) });
|
|
11941
12232
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
11942
12233
|
});
|
|
11943
12234
|
}
|
|
11944
12235
|
|
|
11945
12236
|
// src/commands/runtime-journal.ts
|
|
11946
|
-
import { existsSync as
|
|
11947
|
-
import
|
|
12237
|
+
import { existsSync as existsSync66 } from "fs";
|
|
12238
|
+
import path46 from "path";
|
|
11948
12239
|
import "commander";
|
|
11949
12240
|
import {
|
|
11950
12241
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
@@ -11958,18 +12249,18 @@ function registerRuntime(program2) {
|
|
|
11958
12249
|
);
|
|
11959
12250
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
11960
12251
|
journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
|
|
11961
|
-
const root =
|
|
12252
|
+
const root = path46.resolve(opts.dir ?? process.cwd());
|
|
11962
12253
|
const paths = resolveHaivePaths41(findProjectRoot45(root));
|
|
11963
12254
|
const raw = opts.kind ?? "note";
|
|
11964
12255
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
11965
12256
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
11966
|
-
ui.success(`Appended to ${
|
|
12257
|
+
ui.success(`Appended to ${path46.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
|
|
11967
12258
|
});
|
|
11968
12259
|
journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
|
|
11969
|
-
const root =
|
|
12260
|
+
const root = path46.resolve(opts.dir ?? process.cwd());
|
|
11970
12261
|
const paths = resolveHaivePaths41(findProjectRoot45(root));
|
|
11971
12262
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
11972
|
-
if (!
|
|
12263
|
+
if (!existsSync66(paths.haiveDir)) {
|
|
11973
12264
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
11974
12265
|
process.exitCode = 1;
|
|
11975
12266
|
return;
|
|
@@ -11984,8 +12275,8 @@ function registerRuntime(program2) {
|
|
|
11984
12275
|
}
|
|
11985
12276
|
|
|
11986
12277
|
// src/commands/memory-timeline.ts
|
|
11987
|
-
import { existsSync as
|
|
11988
|
-
import
|
|
12278
|
+
import { existsSync as existsSync67 } from "fs";
|
|
12279
|
+
import path47 from "path";
|
|
11989
12280
|
import "commander";
|
|
11990
12281
|
import {
|
|
11991
12282
|
collectTimelineEntries as collectTimelineEntries2,
|
|
@@ -12001,15 +12292,15 @@ function registerMemoryTimeline(memory2) {
|
|
|
12001
12292
|
process.exitCode = 1;
|
|
12002
12293
|
return;
|
|
12003
12294
|
}
|
|
12004
|
-
const root =
|
|
12295
|
+
const root = path47.resolve(opts.dir ?? process.cwd());
|
|
12005
12296
|
const paths = resolveHaivePaths42(findProjectRoot46(root));
|
|
12006
|
-
if (!
|
|
12297
|
+
if (!existsSync67(paths.memoriesDir)) {
|
|
12007
12298
|
ui.error("No memories \u2014 run `haive init`.");
|
|
12008
12299
|
process.exitCode = 1;
|
|
12009
12300
|
return;
|
|
12010
12301
|
}
|
|
12011
12302
|
const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
12012
|
-
const all = await
|
|
12303
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
12013
12304
|
const { entries, notice } = collectTimelineEntries2(all, {
|
|
12014
12305
|
memoryId: opts.id,
|
|
12015
12306
|
topic: opts.topic,
|
|
@@ -12021,8 +12312,8 @@ function registerMemoryTimeline(memory2) {
|
|
|
12021
12312
|
}
|
|
12022
12313
|
|
|
12023
12314
|
// src/commands/memory-conflict-candidates.ts
|
|
12024
|
-
import { existsSync as
|
|
12025
|
-
import
|
|
12315
|
+
import { existsSync as existsSync68 } from "fs";
|
|
12316
|
+
import path48 from "path";
|
|
12026
12317
|
import "commander";
|
|
12027
12318
|
import {
|
|
12028
12319
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
@@ -12044,9 +12335,9 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
12044
12335
|
"decision,architecture,convention,gotcha (lexical scan)",
|
|
12045
12336
|
"decision,architecture"
|
|
12046
12337
|
).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
|
|
12047
|
-
const root =
|
|
12338
|
+
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
12048
12339
|
const paths = resolveHaivePaths43(findProjectRoot47(root));
|
|
12049
|
-
if (!
|
|
12340
|
+
if (!existsSync68(paths.memoriesDir)) {
|
|
12050
12341
|
ui.error("No memories \u2014 run `haive init`.");
|
|
12051
12342
|
process.exitCode = 1;
|
|
12052
12343
|
return;
|
|
@@ -12056,7 +12347,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
12056
12347
|
const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
|
|
12057
12348
|
const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
|
|
12058
12349
|
const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
|
|
12059
|
-
const all = await
|
|
12350
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
12060
12351
|
const lexical = findLexicalConflictPairs2(all, {
|
|
12061
12352
|
sinceDays,
|
|
12062
12353
|
types: parseTypes(opts.types),
|
|
@@ -12082,21 +12373,21 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
12082
12373
|
|
|
12083
12374
|
// src/commands/enforce.ts
|
|
12084
12375
|
import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
|
|
12085
|
-
import { existsSync as
|
|
12086
|
-
import { chmod as chmod2, mkdir as mkdir19, readFile as
|
|
12087
|
-
import
|
|
12376
|
+
import { existsSync as existsSync69 } from "fs";
|
|
12377
|
+
import { chmod as chmod2, mkdir as mkdir19, readFile as readFile19, rm as rm3, writeFile as writeFile34 } from "fs/promises";
|
|
12378
|
+
import path49 from "path";
|
|
12088
12379
|
import "commander";
|
|
12089
12380
|
import {
|
|
12090
12381
|
findProjectRoot as findProjectRoot48,
|
|
12091
12382
|
hasRecentBriefingMarker,
|
|
12092
12383
|
isFreshIsoDate,
|
|
12093
|
-
loadConfig as
|
|
12384
|
+
loadConfig as loadConfig10,
|
|
12094
12385
|
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
12095
12386
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
12096
12387
|
readRecentBriefingMarker,
|
|
12097
12388
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
12098
12389
|
resolveHaivePaths as resolveHaivePaths44,
|
|
12099
|
-
saveConfig as
|
|
12390
|
+
saveConfig as saveConfig4,
|
|
12100
12391
|
SESSION_RECAP_TTL_MS,
|
|
12101
12392
|
verifyAnchor as verifyAnchor4,
|
|
12102
12393
|
writeBriefingMarker as writeBriefingMarker2
|
|
@@ -12111,8 +12402,8 @@ function registerEnforce(program2) {
|
|
|
12111
12402
|
const root = findProjectRoot48(opts.dir);
|
|
12112
12403
|
const paths = resolveHaivePaths44(root);
|
|
12113
12404
|
await mkdir19(paths.haiveDir, { recursive: true });
|
|
12114
|
-
const current = await
|
|
12115
|
-
await
|
|
12405
|
+
const current = await loadConfig10(paths);
|
|
12406
|
+
await saveConfig4(paths, {
|
|
12116
12407
|
...current,
|
|
12117
12408
|
enforcement: {
|
|
12118
12409
|
...current.enforcement,
|
|
@@ -12134,7 +12425,7 @@ function registerEnforce(program2) {
|
|
|
12134
12425
|
if (opts.claude !== false) {
|
|
12135
12426
|
try {
|
|
12136
12427
|
const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
|
|
12137
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
12428
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path49.relative(root, result.settingsPath)})`);
|
|
12138
12429
|
} catch (err) {
|
|
12139
12430
|
ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
|
|
12140
12431
|
}
|
|
@@ -12156,12 +12447,12 @@ function registerEnforce(program2) {
|
|
|
12156
12447
|
const root = findProjectRoot48(opts.dir);
|
|
12157
12448
|
const paths = resolveHaivePaths44(root);
|
|
12158
12449
|
const targets = [
|
|
12159
|
-
|
|
12160
|
-
|
|
12450
|
+
path49.join(paths.haiveDir, ".cache"),
|
|
12451
|
+
path49.join(paths.haiveDir, ".runtime")
|
|
12161
12452
|
];
|
|
12162
12453
|
for (const target of targets) {
|
|
12163
|
-
if (!
|
|
12164
|
-
const rel =
|
|
12454
|
+
if (!existsSync69(target)) continue;
|
|
12455
|
+
const rel = path49.relative(root, target);
|
|
12165
12456
|
if (opts.dryRun) ui.info(`would remove ${rel}`);
|
|
12166
12457
|
else {
|
|
12167
12458
|
await rm3(target, { recursive: true, force: true });
|
|
@@ -12179,7 +12470,7 @@ function registerEnforce(program2) {
|
|
|
12179
12470
|
const root = resolveRoot(opts.dir, payload);
|
|
12180
12471
|
if (!root) return;
|
|
12181
12472
|
const paths = resolveHaivePaths44(root);
|
|
12182
|
-
if (!
|
|
12473
|
+
if (!existsSync69(paths.haiveDir)) return;
|
|
12183
12474
|
await mkdir19(paths.runtimeDir, { recursive: true });
|
|
12184
12475
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
12185
12476
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
@@ -12241,7 +12532,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
12241
12532
|
const root = resolveRoot(opts.dir, payload);
|
|
12242
12533
|
if (!root) return;
|
|
12243
12534
|
const paths = resolveHaivePaths44(root);
|
|
12244
|
-
if (!
|
|
12535
|
+
if (!existsSync69(paths.haiveDir)) return;
|
|
12245
12536
|
if (!isWriteLikeTool(payload)) return;
|
|
12246
12537
|
const ok = await hasRecentBriefingMarker(paths, payload.session_id);
|
|
12247
12538
|
if (ok) return;
|
|
@@ -12265,7 +12556,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
12265
12556
|
async function runWithEnforcement(command, args, opts) {
|
|
12266
12557
|
const root = findProjectRoot48(opts.dir);
|
|
12267
12558
|
const paths = resolveHaivePaths44(root);
|
|
12268
|
-
if (!
|
|
12559
|
+
if (!existsSync69(paths.haiveDir)) {
|
|
12269
12560
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
12270
12561
|
process.exit(1);
|
|
12271
12562
|
}
|
|
@@ -12284,7 +12575,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
12284
12575
|
process.exit(2);
|
|
12285
12576
|
}
|
|
12286
12577
|
ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
|
|
12287
|
-
ui.info(`Briefing written to ${
|
|
12578
|
+
ui.info(`Briefing written to ${path49.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
12288
12579
|
const child = spawn5(command, args, {
|
|
12289
12580
|
cwd: root,
|
|
12290
12581
|
stdio: "inherit",
|
|
@@ -12333,9 +12624,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
12333
12624
|
source: "haive-run",
|
|
12334
12625
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
12335
12626
|
});
|
|
12336
|
-
const dir =
|
|
12627
|
+
const dir = path49.join(paths.runtimeDir, "enforcement", "briefings");
|
|
12337
12628
|
await mkdir19(dir, { recursive: true });
|
|
12338
|
-
const file =
|
|
12629
|
+
const file = path49.join(dir, `${sessionId}.md`);
|
|
12339
12630
|
const parts = [
|
|
12340
12631
|
"# hAIve Briefing",
|
|
12341
12632
|
"",
|
|
@@ -12353,14 +12644,14 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
12353
12644
|
if (briefing.setup_warnings.length > 0) {
|
|
12354
12645
|
parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
|
|
12355
12646
|
}
|
|
12356
|
-
await
|
|
12647
|
+
await writeFile34(file, parts.join("\n") + "\n", "utf8");
|
|
12357
12648
|
return file;
|
|
12358
12649
|
}
|
|
12359
12650
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
12360
12651
|
const root = findProjectRoot48(dir);
|
|
12361
12652
|
const paths = resolveHaivePaths44(root);
|
|
12362
|
-
const initialized =
|
|
12363
|
-
const config = initialized ? await
|
|
12653
|
+
const initialized = existsSync69(paths.haiveDir);
|
|
12654
|
+
const config = initialized ? await loadConfig10(paths) : {};
|
|
12364
12655
|
const mode = config.enforcement?.mode ?? "strict";
|
|
12365
12656
|
const findings = [];
|
|
12366
12657
|
if (!initialized) {
|
|
@@ -12389,7 +12680,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
12389
12680
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
12390
12681
|
});
|
|
12391
12682
|
}
|
|
12392
|
-
findings.push(...await inspectIntegrationVersions(root, "0.9.
|
|
12683
|
+
findings.push(...await inspectIntegrationVersions(root, "0.9.18"));
|
|
12393
12684
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
12394
12685
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
12395
12686
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -12459,7 +12750,7 @@ function withCategories(report) {
|
|
|
12459
12750
|
};
|
|
12460
12751
|
}
|
|
12461
12752
|
async function hasRecentSessionRecap(paths) {
|
|
12462
|
-
if (!
|
|
12753
|
+
if (!existsSync69(paths.memoriesDir)) return false;
|
|
12463
12754
|
const all = await loadMemoriesFromDir36(paths.memoriesDir);
|
|
12464
12755
|
return all.some(({ memory: memory2 }) => {
|
|
12465
12756
|
const fm = memory2.frontmatter;
|
|
@@ -12468,7 +12759,7 @@ async function hasRecentSessionRecap(paths) {
|
|
|
12468
12759
|
});
|
|
12469
12760
|
}
|
|
12470
12761
|
async function verifyMemoryPolicy(paths, config) {
|
|
12471
|
-
if (!
|
|
12762
|
+
if (!existsSync69(paths.memoriesDir)) return [];
|
|
12472
12763
|
const all = await loadMemoriesFromDir36(paths.memoriesDir);
|
|
12473
12764
|
const findings = [];
|
|
12474
12765
|
const staleImportant = [];
|
|
@@ -12506,7 +12797,7 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
12506
12797
|
return findings;
|
|
12507
12798
|
}
|
|
12508
12799
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
12509
|
-
if (!
|
|
12800
|
+
if (!existsSync69(paths.memoriesDir)) return [];
|
|
12510
12801
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
12511
12802
|
if (changedFiles.length === 0) {
|
|
12512
12803
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
@@ -12599,9 +12890,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
12599
12890
|
];
|
|
12600
12891
|
const findings = [];
|
|
12601
12892
|
for (const rel of files) {
|
|
12602
|
-
const file =
|
|
12603
|
-
if (!
|
|
12604
|
-
const text = await
|
|
12893
|
+
const file = path49.join(root, rel);
|
|
12894
|
+
if (!existsSync69(file)) continue;
|
|
12895
|
+
const text = await readFile19(file, "utf8").catch(() => "");
|
|
12605
12896
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
12606
12897
|
const version = versionForBinary2(bin);
|
|
12607
12898
|
if (!version) {
|
|
@@ -12689,8 +12980,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
12689
12980
|
};
|
|
12690
12981
|
}
|
|
12691
12982
|
async function installGitEnforcement(root) {
|
|
12692
|
-
const hooksDir =
|
|
12693
|
-
if (!
|
|
12983
|
+
const hooksDir = path49.join(root, ".git", "hooks");
|
|
12984
|
+
if (!existsSync69(path49.join(root, ".git"))) {
|
|
12694
12985
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
12695
12986
|
return;
|
|
12696
12987
|
}
|
|
@@ -12712,31 +13003,31 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
12712
13003
|
}
|
|
12713
13004
|
];
|
|
12714
13005
|
for (const hook of hooks) {
|
|
12715
|
-
const file =
|
|
12716
|
-
if (
|
|
12717
|
-
const current = await
|
|
13006
|
+
const file = path49.join(hooksDir, hook.name);
|
|
13007
|
+
if (existsSync69(file)) {
|
|
13008
|
+
const current = await readFile19(file, "utf8").catch(() => "");
|
|
12718
13009
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
12719
|
-
await
|
|
13010
|
+
await writeFile34(file, hook.body, "utf8");
|
|
12720
13011
|
} else {
|
|
12721
|
-
await
|
|
13012
|
+
await writeFile34(file, `${current.trimEnd()}
|
|
12722
13013
|
|
|
12723
13014
|
${hook.body}`, "utf8");
|
|
12724
13015
|
}
|
|
12725
13016
|
} else {
|
|
12726
|
-
await
|
|
13017
|
+
await writeFile34(file, hook.body, "utf8");
|
|
12727
13018
|
}
|
|
12728
13019
|
await chmod2(file, 493);
|
|
12729
13020
|
}
|
|
12730
13021
|
ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
|
|
12731
13022
|
}
|
|
12732
13023
|
async function installCiEnforcement(root) {
|
|
12733
|
-
const workflowPath =
|
|
12734
|
-
await mkdir19(
|
|
12735
|
-
if (
|
|
13024
|
+
const workflowPath = path49.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
13025
|
+
await mkdir19(path49.dirname(workflowPath), { recursive: true });
|
|
13026
|
+
if (existsSync69(workflowPath)) {
|
|
12736
13027
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
12737
13028
|
return;
|
|
12738
13029
|
}
|
|
12739
|
-
await
|
|
13030
|
+
await writeFile34(workflowPath, `name: haive-enforcement
|
|
12740
13031
|
|
|
12741
13032
|
on:
|
|
12742
13033
|
pull_request:
|
|
@@ -12760,7 +13051,7 @@ jobs:
|
|
|
12760
13051
|
- name: Enforce hAIve policy
|
|
12761
13052
|
run: haive enforce ci
|
|
12762
13053
|
`, "utf8");
|
|
12763
|
-
ui.success(`Created ${
|
|
13054
|
+
ui.success(`Created ${path49.relative(root, workflowPath)}`);
|
|
12764
13055
|
}
|
|
12765
13056
|
function printReport(report, json, explain = false) {
|
|
12766
13057
|
if (json) {
|
|
@@ -12877,7 +13168,7 @@ function registerRun(program2) {
|
|
|
12877
13168
|
|
|
12878
13169
|
// src/index.ts
|
|
12879
13170
|
var program = new Command51();
|
|
12880
|
-
program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.
|
|
13171
|
+
program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.18").option("--advanced", "show maintenance and experimental commands in help");
|
|
12881
13172
|
registerInit(program);
|
|
12882
13173
|
registerWelcome(program);
|
|
12883
13174
|
registerResolveProject(program);
|