@hiveai/cli 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +88 -15
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -834,7 +834,7 @@ var TokenBudgetWriter = class {
|
|
|
834
834
|
function registerBriefing(program2) {
|
|
835
835
|
program2.command("briefing").description(
|
|
836
836
|
'Print the full project briefing: last session recap + project context + relevant memories.\n Equivalent to calling get_briefing via MCP. Run before starting any task.\n\n Examples:\n haive briefing\n haive briefing --task "add Stripe payment" --files src/payments/PaymentService.ts\n haive briefing --budget quick --task "tiny fix"\n'
|
|
837
|
-
).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "8").option("--max-tokens <n>", "approximate token budget for the entire briefing (truncates if exceeded)").option("--explain-source", "annotate each memory with [source: <relative-path> \xB7 anchors: <files>] for traceable citations").option("--radar", "force project radar (recent commits, open TODOs, hot files) even when memories are plentiful").option("--no-radar", "disable the project radar even when memories are scarce").option(
|
|
837
|
+
).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "8").option("--max-tokens <n>", "approximate token budget for the entire briefing (truncates if exceeded)").option("--explain-source", "annotate each memory with [source: <relative-path> \xB7 anchors: <files>] for traceable citations").option("--radar", "force project radar (recent commits, open TODOs, hot files) even when memories are plentiful").option("--no-radar", "disable the project radar even when memories are scarce").option("--json", "emit the ranked briefing as JSON (memories + scores + priority), like the MCP get_briefing tool", false).option(
|
|
838
838
|
"--budget <preset>",
|
|
839
839
|
"align with MCP get_briefing budget_preset: quick | balanced | deep \u2014 sets cap + truncation budget (overrides --max-memories / replaces default open-ended output)",
|
|
840
840
|
void 0
|
|
@@ -897,8 +897,10 @@ function registerBriefing(program2) {
|
|
|
897
897
|
budgetTokensCap = presetNums.max_tokens;
|
|
898
898
|
maxMemories = presetNums.max_memories;
|
|
899
899
|
}
|
|
900
|
+
const json = opts.json === true;
|
|
900
901
|
const writer = budgetTokensCap !== null ? new TokenBudgetWriter(budgetTokensCap * CHARS_PER_TOKEN) : null;
|
|
901
902
|
const out = (text) => {
|
|
903
|
+
if (json) return true;
|
|
902
904
|
if (writer) return writer.write(text);
|
|
903
905
|
console.log(text);
|
|
904
906
|
return true;
|
|
@@ -1015,6 +1017,10 @@ function registerBriefing(program2) {
|
|
|
1015
1017
|
scored.sort((a, b) => b.score - a.score);
|
|
1016
1018
|
const top = scored.slice(0, maxMemories);
|
|
1017
1019
|
if (top.length === 0) {
|
|
1020
|
+
if (json) {
|
|
1021
|
+
console.log(JSON.stringify({ task: opts.task ?? null, memories: [], briefing_quality: "thin" }, null, 2));
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1018
1024
|
ui.info("No relevant memories found.");
|
|
1019
1025
|
const draftCount = all.filter(
|
|
1020
1026
|
(m) => m.memory.frontmatter.status === "draft" && (scopeFilter === "all" || m.memory.frontmatter.scope === scopeFilter)
|
|
@@ -1046,6 +1052,26 @@ function registerBriefing(program2) {
|
|
|
1046
1052
|
const usefulCount = priorities.filter((p) => p === "useful").length;
|
|
1047
1053
|
const backgroundCount = priorities.filter((p) => p === "background").length;
|
|
1048
1054
|
const quality = mustReadCount > 0 || usefulCount > 0 ? backgroundCount > mustReadCount + usefulCount && backgroundCount > 2 ? "noisy" : "strong" : "thin";
|
|
1055
|
+
if (json) {
|
|
1056
|
+
console.log(JSON.stringify({
|
|
1057
|
+
task: opts.task ?? null,
|
|
1058
|
+
files: filePaths,
|
|
1059
|
+
briefing_quality: quality,
|
|
1060
|
+
counts: { must_read: mustReadCount, useful: usefulCount, background: backgroundCount },
|
|
1061
|
+
recap_id: recaps[0]?.memory.frontmatter.id ?? null,
|
|
1062
|
+
memories: top.map((item, i) => ({
|
|
1063
|
+
id: item.memory.frontmatter.id,
|
|
1064
|
+
scope: item.memory.frontmatter.scope,
|
|
1065
|
+
type: item.memory.frontmatter.type,
|
|
1066
|
+
status: item.memory.frontmatter.status,
|
|
1067
|
+
priority: priorities[i],
|
|
1068
|
+
score: item.score,
|
|
1069
|
+
file: path3.relative(root, item.filePath),
|
|
1070
|
+
summary: (item.memory.body.split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l.length > 0) ?? "").slice(0, 140)
|
|
1071
|
+
}))
|
|
1072
|
+
}, null, 2));
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1049
1075
|
out(ui.dim(`briefing_quality: ${quality} \xB7 must_read=${mustReadCount} useful=${usefulCount} background=${backgroundCount}`));
|
|
1050
1076
|
out("");
|
|
1051
1077
|
for (const [idx, item] of top.entries()) {
|
|
@@ -2222,6 +2248,7 @@ import { existsSync as existsSync10 } from "fs";
|
|
|
2222
2248
|
import path10 from "path";
|
|
2223
2249
|
import {
|
|
2224
2250
|
buildFrontmatter,
|
|
2251
|
+
loadMemoriesFromDir as loadMemoriesFromDir5,
|
|
2225
2252
|
memoryFilePath,
|
|
2226
2253
|
serializeMemory as serializeMemory2,
|
|
2227
2254
|
STACK_PACK_TAG
|
|
@@ -3299,6 +3326,15 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3299
3326
|
const memories = PACKS[stack];
|
|
3300
3327
|
if (!memories) return { memories: 0, sensors: 0 };
|
|
3301
3328
|
await mkdir5(haivePaths.teamDir, { recursive: true });
|
|
3329
|
+
const DATE_PREFIX = /^\d{4}-\d{2}-\d{2}-/;
|
|
3330
|
+
const existingTopics = /* @__PURE__ */ new Set();
|
|
3331
|
+
const existingSignatures = /* @__PURE__ */ new Set();
|
|
3332
|
+
if (existsSync10(haivePaths.memoriesDir)) {
|
|
3333
|
+
for (const { memory: memory2 } of await loadMemoriesFromDir5(haivePaths.memoriesDir)) {
|
|
3334
|
+
if (memory2.frontmatter.topic) existingTopics.add(memory2.frontmatter.topic);
|
|
3335
|
+
existingSignatures.add(memory2.frontmatter.id.replace(DATE_PREFIX, ""));
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3302
3338
|
let memCount = 0;
|
|
3303
3339
|
let sensorCount = 0;
|
|
3304
3340
|
for (const mem of memories) {
|
|
@@ -3313,6 +3349,9 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3313
3349
|
last_fired: null
|
|
3314
3350
|
} : void 0;
|
|
3315
3351
|
const combinedSlug = mem.slug === stack || mem.slug.startsWith(`${stack}-`) ? mem.slug : `${stack}-${mem.slug}`;
|
|
3352
|
+
const topic = `stack-pack:${stack}:${mem.slug}`;
|
|
3353
|
+
const signature = `${mem.type}-${combinedSlug}`;
|
|
3354
|
+
if (existingTopics.has(topic) || existingSignatures.has(signature)) continue;
|
|
3316
3355
|
const fm = buildFrontmatter({
|
|
3317
3356
|
type: mem.type,
|
|
3318
3357
|
slug: combinedSlug,
|
|
@@ -3321,15 +3360,24 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3321
3360
|
// STACK_PACK_TAG marks this as generic seed knowledge so briefing ranking
|
|
3322
3361
|
// keeps it at `background` priority until it earns a repo-specific anchor.
|
|
3323
3362
|
tags: [...mem.tags, STACK_PACK_TAG],
|
|
3363
|
+
topic,
|
|
3324
3364
|
...sensor ? { sensor } : {}
|
|
3325
3365
|
});
|
|
3326
3366
|
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
3327
3367
|
if (existsSync10(filePath)) continue;
|
|
3328
|
-
const
|
|
3368
|
+
const ruleSlug = combinedSlug.startsWith(`${stack}-`) ? combinedSlug.slice(stack.length + 1) : combinedSlug;
|
|
3369
|
+
const titleCase = (s) => s.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3370
|
+
const heading = `${titleCase(stack)}: ${titleCase(ruleSlug)}`;
|
|
3371
|
+
const titledBody = /^#{1,3}\s+\S/m.test(mem.body.trim()) ? mem.body : `# ${heading}
|
|
3372
|
+
|
|
3373
|
+
${mem.body}`;
|
|
3374
|
+
const content = serializeMemory2({ frontmatter: fm, body: `${titledBody}
|
|
3329
3375
|
|
|
3330
3376
|
${SEED_FOOTER(stack)}` });
|
|
3331
3377
|
await mkdir5(path10.dirname(filePath), { recursive: true });
|
|
3332
3378
|
await writeFile6(filePath, content, "utf8");
|
|
3379
|
+
existingTopics.add(topic);
|
|
3380
|
+
existingSignatures.add(signature);
|
|
3333
3381
|
memCount++;
|
|
3334
3382
|
if (sensor) sensorCount++;
|
|
3335
3383
|
}
|
|
@@ -3338,7 +3386,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3338
3386
|
|
|
3339
3387
|
// src/commands/init.ts
|
|
3340
3388
|
var execFileAsync = promisify2(execFile2);
|
|
3341
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3389
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.21.0"}`;
|
|
3342
3390
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3343
3391
|
|
|
3344
3392
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -4459,7 +4507,7 @@ import { existsSync as existsSync22 } from "fs";
|
|
|
4459
4507
|
import path22 from "path";
|
|
4460
4508
|
import { z as z2 } from "zod";
|
|
4461
4509
|
import { existsSync as existsSync32 } from "fs";
|
|
4462
|
-
import { loadMemoriesFromDir as
|
|
4510
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir6 } from "@hiveai/core";
|
|
4463
4511
|
import { z as z3 } from "zod";
|
|
4464
4512
|
import { createHash } from "crypto";
|
|
4465
4513
|
import { mkdir as mkdir22, writeFile as writeFile22 } from "fs/promises";
|
|
@@ -4511,7 +4559,7 @@ import {
|
|
|
4511
4559
|
applyFeedbackAdjustment,
|
|
4512
4560
|
computeImpact,
|
|
4513
4561
|
getUsage as getUsage22,
|
|
4514
|
-
loadMemoriesFromDir as
|
|
4562
|
+
loadMemoriesFromDir as loadMemoriesFromDir62,
|
|
4515
4563
|
loadUsageIndex as loadUsageIndex32,
|
|
4516
4564
|
recordApplied,
|
|
4517
4565
|
recordRejection as recordRejection2,
|
|
@@ -4842,7 +4890,7 @@ async function memList(input, ctx) {
|
|
|
4842
4890
|
if (!existsSync32(ctx.paths.memoriesDir)) {
|
|
4843
4891
|
return { memories: [] };
|
|
4844
4892
|
}
|
|
4845
|
-
const all = await
|
|
4893
|
+
const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
|
|
4846
4894
|
const filtered = all.filter(({ memory: memory2 }) => {
|
|
4847
4895
|
const fm = memory2.frontmatter;
|
|
4848
4896
|
if (input.scope && fm.scope !== input.scope) return false;
|
|
@@ -5374,7 +5422,7 @@ async function memFeedback(input, ctx) {
|
|
|
5374
5422
|
if (!existsSync82(ctx.paths.memoriesDir)) {
|
|
5375
5423
|
return { ok: false, id: input.id, error: "No .ai/memories \u2014 run `haive init` first." };
|
|
5376
5424
|
}
|
|
5377
|
-
const all = await
|
|
5425
|
+
const all = await loadMemoriesFromDir62(ctx.paths.memoriesDir);
|
|
5378
5426
|
const target = all.find((m) => m.memory.frontmatter.id === input.id);
|
|
5379
5427
|
if (!target) {
|
|
5380
5428
|
return { ok: false, id: input.id, error: `No memory with id '${input.id}'.` };
|
|
@@ -8584,7 +8632,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
8584
8632
|
};
|
|
8585
8633
|
}
|
|
8586
8634
|
var SERVER_NAME = "haive";
|
|
8587
|
-
var SERVER_VERSION = "0.
|
|
8635
|
+
var SERVER_VERSION = "0.21.0";
|
|
8588
8636
|
function jsonResult(data) {
|
|
8589
8637
|
return {
|
|
8590
8638
|
content: [
|
|
@@ -14336,7 +14384,7 @@ function registerDoctor(program2) {
|
|
|
14336
14384
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
14337
14385
|
});
|
|
14338
14386
|
}
|
|
14339
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
14387
|
+
findings.push(...await collectInstallFindings(root, "0.21.0"));
|
|
14340
14388
|
findings.push(...await collectToolchainFindings(root));
|
|
14341
14389
|
try {
|
|
14342
14390
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -14344,7 +14392,7 @@ function registerDoctor(program2) {
|
|
|
14344
14392
|
timeout: 3e3,
|
|
14345
14393
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14346
14394
|
}).trim();
|
|
14347
|
-
const cliVersion = "0.
|
|
14395
|
+
const cliVersion = "0.21.0";
|
|
14348
14396
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
14349
14397
|
findings.push({
|
|
14350
14398
|
severity: "warn",
|
|
@@ -16044,7 +16092,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
16044
16092
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
16045
16093
|
});
|
|
16046
16094
|
}
|
|
16047
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
16095
|
+
findings.push(...await inspectIntegrationVersions(root, "0.21.0"));
|
|
16048
16096
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
16049
16097
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
16050
16098
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -16167,8 +16215,9 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
16167
16215
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
16168
16216
|
}
|
|
16169
16217
|
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16218
|
+
const changedSet = new Set(changedFiles);
|
|
16170
16219
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
16171
|
-
const relevant = all.
|
|
16220
|
+
const relevant = all.filter(({ memory: memory2 }) => {
|
|
16172
16221
|
const fm = memory2.frontmatter;
|
|
16173
16222
|
if (!policyTypes.has(fm.type)) return false;
|
|
16174
16223
|
if (fm.status !== "validated") return false;
|
|
@@ -16187,12 +16236,16 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
16187
16236
|
severity: "ok",
|
|
16188
16237
|
code: "decision-coverage-ci-pass",
|
|
16189
16238
|
message: `CI surfaced ${relevant.length} relevant anchored decision/polic${relevant.length === 1 ? "y" : "ies"} for ${changedFiles.length} changed file(s). Runtime briefing markers are local-only and are not expected on GitHub Actions.`,
|
|
16190
|
-
memory_ids: relevant.slice(0, 20).map((memory2) => memory2.frontmatter.id),
|
|
16239
|
+
memory_ids: relevant.slice(0, 20).map(({ memory: memory2 }) => memory2.frontmatter.id),
|
|
16191
16240
|
affected_files: changedFiles.slice(0, 10)
|
|
16192
16241
|
}];
|
|
16193
16242
|
}
|
|
16194
16243
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
16195
|
-
const missing = relevant.filter((memory2) =>
|
|
16244
|
+
const missing = relevant.filter(({ memory: memory2, filePath }) => {
|
|
16245
|
+
if (consulted.has(memory2.frontmatter.id)) return false;
|
|
16246
|
+
if (changedSet.has(path53.relative(paths.root, filePath))) return false;
|
|
16247
|
+
return true;
|
|
16248
|
+
}).map(({ memory: memory2 }) => memory2);
|
|
16196
16249
|
if (missing.length === 0) {
|
|
16197
16250
|
return [{
|
|
16198
16251
|
severity: "ok",
|
|
@@ -16200,6 +16253,26 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
16200
16253
|
message: `Relevant decisions/policies were surfaced for ${changedFiles.length} changed file(s): ${relevant.length}/${relevant.length}.`
|
|
16201
16254
|
}];
|
|
16202
16255
|
}
|
|
16256
|
+
if (stage === "pre-commit" || stage === "pre-push") {
|
|
16257
|
+
const cfg = await loadConfig14(paths).catch(() => ({}));
|
|
16258
|
+
if (cfg.enforcement?.autoBrief !== false) {
|
|
16259
|
+
await writeBriefingMarker3(paths, {
|
|
16260
|
+
sessionId,
|
|
16261
|
+
source: "haive-autobrief",
|
|
16262
|
+
task: "decision-coverage auto-surfaced at commit",
|
|
16263
|
+
memoryIds: relevant.map(({ memory: memory2 }) => memory2.frontmatter.id),
|
|
16264
|
+
files: changedFiles
|
|
16265
|
+
}).catch(() => {
|
|
16266
|
+
});
|
|
16267
|
+
return [{
|
|
16268
|
+
severity: "ok",
|
|
16269
|
+
code: "decision-coverage-autosurfaced",
|
|
16270
|
+
message: `Surfaced ${relevant.length} relevant decision/policy memor${relevant.length === 1 ? "y" : "ies"} for ${changedFiles.length} changed file(s) at commit time` + (missing.length > 0 ? ` (${missing.length} not previously briefed \u2014 now recorded)` : "") + ". Set enforcement.autoBrief=false to require a manual briefing first.",
|
|
16271
|
+
memory_ids: relevant.slice(0, 12).map(({ memory: memory2 }) => memory2.frontmatter.id),
|
|
16272
|
+
affected_files: changedFiles.slice(0, 10)
|
|
16273
|
+
}];
|
|
16274
|
+
}
|
|
16275
|
+
}
|
|
16203
16276
|
return [{
|
|
16204
16277
|
severity: stage === "local" ? "warn" : "error",
|
|
16205
16278
|
code: "decision-coverage-missing",
|
|
@@ -18079,7 +18152,7 @@ function registerBridges(program2) {
|
|
|
18079
18152
|
|
|
18080
18153
|
// src/index.ts
|
|
18081
18154
|
var program = new Command64();
|
|
18082
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
18155
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.21.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
18083
18156
|
registerInit(program);
|
|
18084
18157
|
registerWelcome(program);
|
|
18085
18158
|
registerResolveProject(program);
|