@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 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 content = serializeMemory2({ frontmatter: fm, body: `${mem.body}
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.20.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 loadMemoriesFromDir5 } from "@hiveai/core";
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 loadMemoriesFromDir6,
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 loadMemoriesFromDir5(ctx.paths.memoriesDir);
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 loadMemoriesFromDir6(ctx.paths.memoriesDir);
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.20.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.20.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.20.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.20.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.map(({ memory: memory2 }) => memory2).filter((memory2) => {
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) => !consulted.has(memory2.frontmatter.id));
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.20.0").option("--advanced", "show maintenance and experimental commands in help");
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);