@hiveai/cli 0.19.0 → 0.20.1

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
@@ -262,6 +262,7 @@ import {
262
262
  loadCodeMap,
263
263
  loadMemoriesFromDir,
264
264
  loadUsageIndex,
265
+ looksLikeGenericAdvice,
265
266
  resolveHaivePaths,
266
267
  serializeMemory,
267
268
  specificityScore
@@ -310,7 +311,9 @@ async function lintMemoriesAsync(root, options = {}) {
310
311
  message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
311
312
  });
312
313
  }
313
- if (["decision", "gotcha", "convention", "architecture"].includes(fm.type) && fm.status !== "rejected" && naked.length >= 40 && specificityScore(naked) < 0.2) {
314
+ if (["decision", "gotcha", "convention", "architecture"].includes(fm.type) && fm.status !== "rejected" && naked.length >= 40 && specificityScore(naked) < 0.2 && // High-precision gate: only flag when there is positive evidence of generic advice.
315
+ // A low-density but arbitrary team policy (unguessable prose) must not be flagged.
316
+ looksLikeGenericAdvice(naked)) {
314
317
  out.push({
315
318
  file: filePath,
316
319
  id: fm.id,
@@ -2219,6 +2222,7 @@ import { existsSync as existsSync10 } from "fs";
2219
2222
  import path10 from "path";
2220
2223
  import {
2221
2224
  buildFrontmatter,
2225
+ loadMemoriesFromDir as loadMemoriesFromDir5,
2222
2226
  memoryFilePath,
2223
2227
  serializeMemory as serializeMemory2,
2224
2228
  STACK_PACK_TAG
@@ -3296,6 +3300,15 @@ async function seedStackPack(haivePaths, stack) {
3296
3300
  const memories = PACKS[stack];
3297
3301
  if (!memories) return { memories: 0, sensors: 0 };
3298
3302
  await mkdir5(haivePaths.teamDir, { recursive: true });
3303
+ const DATE_PREFIX = /^\d{4}-\d{2}-\d{2}-/;
3304
+ const existingTopics = /* @__PURE__ */ new Set();
3305
+ const existingSignatures = /* @__PURE__ */ new Set();
3306
+ if (existsSync10(haivePaths.memoriesDir)) {
3307
+ for (const { memory: memory2 } of await loadMemoriesFromDir5(haivePaths.memoriesDir)) {
3308
+ if (memory2.frontmatter.topic) existingTopics.add(memory2.frontmatter.topic);
3309
+ existingSignatures.add(memory2.frontmatter.id.replace(DATE_PREFIX, ""));
3310
+ }
3311
+ }
3299
3312
  let memCount = 0;
3300
3313
  let sensorCount = 0;
3301
3314
  for (const mem of memories) {
@@ -3309,14 +3322,19 @@ async function seedStackPack(haivePaths, stack) {
3309
3322
  autogen: false,
3310
3323
  last_fired: null
3311
3324
  } : void 0;
3325
+ const combinedSlug = mem.slug === stack || mem.slug.startsWith(`${stack}-`) ? mem.slug : `${stack}-${mem.slug}`;
3326
+ const topic = `stack-pack:${stack}:${mem.slug}`;
3327
+ const signature = `${mem.type}-${combinedSlug}`;
3328
+ if (existingTopics.has(topic) || existingSignatures.has(signature)) continue;
3312
3329
  const fm = buildFrontmatter({
3313
3330
  type: mem.type,
3314
- slug: `${stack}-${mem.slug}`,
3331
+ slug: combinedSlug,
3315
3332
  scope: "team",
3316
3333
  status: "validated",
3317
3334
  // STACK_PACK_TAG marks this as generic seed knowledge so briefing ranking
3318
3335
  // keeps it at `background` priority until it earns a repo-specific anchor.
3319
3336
  tags: [...mem.tags, STACK_PACK_TAG],
3337
+ topic,
3320
3338
  ...sensor ? { sensor } : {}
3321
3339
  });
3322
3340
  const filePath = memoryFilePath(haivePaths, "team", fm.id);
@@ -3326,6 +3344,8 @@ async function seedStackPack(haivePaths, stack) {
3326
3344
  ${SEED_FOOTER(stack)}` });
3327
3345
  await mkdir5(path10.dirname(filePath), { recursive: true });
3328
3346
  await writeFile6(filePath, content, "utf8");
3347
+ existingTopics.add(topic);
3348
+ existingSignatures.add(signature);
3329
3349
  memCount++;
3330
3350
  if (sensor) sensorCount++;
3331
3351
  }
@@ -3334,7 +3354,7 @@ ${SEED_FOOTER(stack)}` });
3334
3354
 
3335
3355
  // src/commands/init.ts
3336
3356
  var execFileAsync = promisify2(execFile2);
3337
- var HAIVE_GITHUB_ACTION_REF = `v${"0.19.0"}`;
3357
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.20.1"}`;
3338
3358
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3339
3359
 
3340
3360
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -4455,7 +4475,7 @@ import { existsSync as existsSync22 } from "fs";
4455
4475
  import path22 from "path";
4456
4476
  import { z as z2 } from "zod";
4457
4477
  import { existsSync as existsSync32 } from "fs";
4458
- import { loadMemoriesFromDir as loadMemoriesFromDir5 } from "@hiveai/core";
4478
+ import { loadMemoriesFromDir as loadMemoriesFromDir6 } from "@hiveai/core";
4459
4479
  import { z as z3 } from "zod";
4460
4480
  import { createHash } from "crypto";
4461
4481
  import { mkdir as mkdir22, writeFile as writeFile22 } from "fs/promises";
@@ -4507,7 +4527,7 @@ import {
4507
4527
  applyFeedbackAdjustment,
4508
4528
  computeImpact,
4509
4529
  getUsage as getUsage22,
4510
- loadMemoriesFromDir as loadMemoriesFromDir6,
4530
+ loadMemoriesFromDir as loadMemoriesFromDir62,
4511
4531
  loadUsageIndex as loadUsageIndex32,
4512
4532
  recordApplied,
4513
4533
  recordRejection as recordRejection2,
@@ -4838,7 +4858,7 @@ async function memList(input, ctx) {
4838
4858
  if (!existsSync32(ctx.paths.memoriesDir)) {
4839
4859
  return { memories: [] };
4840
4860
  }
4841
- const all = await loadMemoriesFromDir5(ctx.paths.memoriesDir);
4861
+ const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
4842
4862
  const filtered = all.filter(({ memory: memory2 }) => {
4843
4863
  const fm = memory2.frontmatter;
4844
4864
  if (input.scope && fm.scope !== input.scope) return false;
@@ -5370,7 +5390,7 @@ async function memFeedback(input, ctx) {
5370
5390
  if (!existsSync82(ctx.paths.memoriesDir)) {
5371
5391
  return { ok: false, id: input.id, error: "No .ai/memories \u2014 run `haive init` first." };
5372
5392
  }
5373
- const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
5393
+ const all = await loadMemoriesFromDir62(ctx.paths.memoriesDir);
5374
5394
  const target = all.find((m) => m.memory.frontmatter.id === input.id);
5375
5395
  if (!target) {
5376
5396
  return { ok: false, id: input.id, error: `No memory with id '${input.id}'.` };
@@ -7894,8 +7914,17 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
7894
7914
  repair_command: repairCommand
7895
7915
  };
7896
7916
  }
7917
+ if (warning.has_sensor && !warning.reasons.includes("sensor") && !warning.reasons.includes("anchor")) {
7918
+ return {
7919
+ ...warning,
7920
+ level: "info",
7921
+ rationale: "memory has a deterministic sensor that did not fire and is not anchored to a touched file \u2014 treated as non-violation noise",
7922
+ affected_files: affectedFiles,
7923
+ repair_command: repairCommand
7924
+ };
7925
+ }
7897
7926
  const corroborated = warning.reasons.includes("anchor") || warning.distinctive_literal === true;
7898
- const semanticReviewFloor = corroborated ? 0.45 : 0.6;
7927
+ const semanticReviewFloor = corroborated ? 0.45 : 0.65;
7899
7928
  if (hasSemantic && semanticScore >= semanticReviewFloor || highConfidence && warning.reasons.includes("anchor") && warning.reasons.includes("literal")) {
7900
7929
  return {
7901
7930
  ...warning,
@@ -8571,7 +8600,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
8571
8600
  };
8572
8601
  }
8573
8602
  var SERVER_NAME = "haive";
8574
- var SERVER_VERSION = "0.19.0";
8603
+ var SERVER_VERSION = "0.20.1";
8575
8604
  function jsonResult(data) {
8576
8605
  return {
8577
8606
  content: [
@@ -14323,7 +14352,7 @@ function registerDoctor(program2) {
14323
14352
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
14324
14353
  });
14325
14354
  }
14326
- findings.push(...await collectInstallFindings(root, "0.19.0"));
14355
+ findings.push(...await collectInstallFindings(root, "0.20.1"));
14327
14356
  findings.push(...await collectToolchainFindings(root));
14328
14357
  try {
14329
14358
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -14331,7 +14360,7 @@ function registerDoctor(program2) {
14331
14360
  timeout: 3e3,
14332
14361
  stdio: ["ignore", "pipe", "ignore"]
14333
14362
  }).trim();
14334
- const cliVersion = "0.19.0";
14363
+ const cliVersion = "0.20.1";
14335
14364
  if (legacyRaw && legacyRaw !== cliVersion) {
14336
14365
  findings.push({
14337
14366
  severity: "warn",
@@ -15411,17 +15440,21 @@ import path53 from "path";
15411
15440
  import "commander";
15412
15441
  import {
15413
15442
  antiPatternGateParams as antiPatternGateParams2,
15443
+ BRIDGE_TARGET_PATH as BRIDGE_TARGET_PATH2,
15414
15444
  findProjectRoot as findProjectRoot52,
15415
15445
  findUncapturedFailures,
15416
15446
  hasRecentBriefingMarker as hasRecentBriefingMarker2,
15417
15447
  isFreshIsoDate,
15448
+ isRetiredMemory as isRetiredMemory3,
15418
15449
  loadConfig as loadConfig14,
15419
15450
  loadMemoriesFromDir as loadMemoriesFromDir38,
15420
15451
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
15421
15452
  readRecentBriefingMarker,
15422
15453
  resolveBriefingBudget as resolveBriefingBudget3,
15423
15454
  resolveHaivePaths as resolveHaivePaths48,
15455
+ runSensors as runSensors2,
15424
15456
  saveConfig as saveConfig4,
15457
+ sensorTargetsFromDiff as sensorTargetsFromDiff2,
15425
15458
  SESSION_RECAP_TTL_MS,
15426
15459
  verifyAnchor as verifyAnchor4,
15427
15460
  writeBriefingMarker as writeBriefingMarker3
@@ -16027,7 +16060,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
16027
16060
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
16028
16061
  });
16029
16062
  }
16030
- findings.push(...await inspectIntegrationVersions(root, "0.19.0"));
16063
+ findings.push(...await inspectIntegrationVersions(root, "0.20.1"));
16031
16064
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
16032
16065
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
16033
16066
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -16150,8 +16183,9 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
16150
16183
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
16151
16184
  }
16152
16185
  const all = await loadMemoriesFromDir38(paths.memoriesDir);
16186
+ const changedSet = new Set(changedFiles);
16153
16187
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
16154
- const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
16188
+ const relevant = all.filter(({ memory: memory2 }) => {
16155
16189
  const fm = memory2.frontmatter;
16156
16190
  if (!policyTypes.has(fm.type)) return false;
16157
16191
  if (fm.status !== "validated") return false;
@@ -16170,12 +16204,16 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
16170
16204
  severity: "ok",
16171
16205
  code: "decision-coverage-ci-pass",
16172
16206
  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.`,
16173
- memory_ids: relevant.slice(0, 20).map((memory2) => memory2.frontmatter.id),
16207
+ memory_ids: relevant.slice(0, 20).map(({ memory: memory2 }) => memory2.frontmatter.id),
16174
16208
  affected_files: changedFiles.slice(0, 10)
16175
16209
  }];
16176
16210
  }
16177
16211
  const consulted = new Set(marker?.memory_ids ?? []);
16178
- const missing = relevant.filter((memory2) => !consulted.has(memory2.frontmatter.id));
16212
+ const missing = relevant.filter(({ memory: memory2, filePath }) => {
16213
+ if (consulted.has(memory2.frontmatter.id)) return false;
16214
+ if (changedSet.has(path53.relative(paths.root, filePath))) return false;
16215
+ return true;
16216
+ }).map(({ memory: memory2 }) => memory2);
16179
16217
  if (missing.length === 0) {
16180
16218
  return [{
16181
16219
  severity: "ok",
@@ -16213,20 +16251,83 @@ async function runPrecommitPolicy(paths, gate, stage) {
16213
16251
  anchored_blocks,
16214
16252
  semantic: true
16215
16253
  }, { paths });
16254
+ const sensorFindings = await runSensorGate(paths, snapshot.diff);
16216
16255
  if (!result.should_block) {
16217
- return [{
16218
- severity: "ok",
16219
- code: "precommit-policy-pass",
16220
- message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
16221
- }];
16256
+ return [
16257
+ {
16258
+ severity: "ok",
16259
+ code: "precommit-policy-pass",
16260
+ message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
16261
+ },
16262
+ ...sensorFindings
16263
+ ];
16264
+ }
16265
+ return [
16266
+ {
16267
+ severity: "error",
16268
+ code: "precommit-policy-block",
16269
+ message: `Pre-commit policy matched ${result.summary.blocking_warnings ?? result.summary.anti_patterns} blocking anti-pattern(s), ${result.summary.stale_anchors} stale anchor(s).`,
16270
+ fix: "Review the hAIve warnings, then update the code or the relevant memories.",
16271
+ impact: 45
16272
+ },
16273
+ ...sensorFindings
16274
+ ];
16275
+ }
16276
+ var SENSOR_OWNED_FILES = /* @__PURE__ */ new Set([
16277
+ ...Object.values(BRIDGE_TARGET_PATH2),
16278
+ "CLAUDE.md",
16279
+ ".cursorrules",
16280
+ ".gitignore",
16281
+ ".mcp.json",
16282
+ ".cursor/mcp.json",
16283
+ ".vscode/mcp.json",
16284
+ ".cursor/rules/haive-mcp-required.mdc"
16285
+ ]);
16286
+ function isSensorScannablePath(p) {
16287
+ if (!p) return false;
16288
+ if (p.startsWith(".ai/")) return false;
16289
+ return !SENSOR_OWNED_FILES.has(p);
16290
+ }
16291
+ async function runSensorGate(paths, diff) {
16292
+ if (!diff || !existsSync75(paths.memoriesDir)) return [];
16293
+ try {
16294
+ const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
16295
+ const sensorMemories = loaded.map((l) => l.memory).filter((m) => {
16296
+ const s = m.frontmatter.sensor;
16297
+ return Boolean(s) && s.kind === "regex" && !isRetiredMemory3(m.frontmatter, m.body);
16298
+ });
16299
+ if (sensorMemories.length === 0) return [];
16300
+ const targets = sensorTargetsFromDiff2(diff).filter((t) => isSensorScannablePath(t.path));
16301
+ if (targets.length === 0) return [];
16302
+ const hits = runSensors2(sensorMemories, targets);
16303
+ const findings = [];
16304
+ const seen = /* @__PURE__ */ new Set();
16305
+ for (const hit of hits) {
16306
+ if (seen.has(hit.memory_id)) continue;
16307
+ seen.add(hit.memory_id);
16308
+ const where = hit.file ? ` (${hit.file})` : "";
16309
+ if (hit.severity === "block") {
16310
+ findings.push({
16311
+ severity: "error",
16312
+ code: "sensor-block",
16313
+ message: `Block sensor fired \u2014 ${hit.memory_id}: ${hit.message}${where}`,
16314
+ fix: "Remove the flagged pattern, or run `haive sensors check` to inspect the match.",
16315
+ impact: 45
16316
+ });
16317
+ } else {
16318
+ findings.push({
16319
+ severity: "warn",
16320
+ code: "sensor-warn",
16321
+ message: `Sensor flagged ${hit.memory_id}: ${hit.message}${where}`,
16322
+ fix: "Review the flagged line; `haive sensors check` shows the matched code.",
16323
+ impact: 5
16324
+ });
16325
+ }
16326
+ }
16327
+ return findings;
16328
+ } catch {
16329
+ return [];
16222
16330
  }
16223
- return [{
16224
- severity: "error",
16225
- code: "precommit-policy-block",
16226
- message: `Pre-commit policy matched ${result.summary.blocking_warnings ?? result.summary.anti_patterns} blocking anti-pattern(s), ${result.summary.stale_anchors} stale anchor(s).`,
16227
- fix: "Review the hAIve warnings, then update the code or the relevant memories.",
16228
- impact: 45
16229
- }];
16230
16331
  }
16231
16332
  async function findGeneratedArtifacts(paths) {
16232
16333
  const dirty = await runCommand4("git", ["status", "--short", "--untracked-files=all"], paths.root).catch(() => "");
@@ -17022,16 +17123,16 @@ import "commander";
17022
17123
  import {
17023
17124
  appendPreventionEvent as appendPreventionEvent2,
17024
17125
  findProjectRoot as findProjectRoot53,
17025
- isRetiredMemory as isRetiredMemory3,
17126
+ isRetiredMemory as isRetiredMemory4,
17026
17127
  loadConfig as loadConfig15,
17027
17128
  loadMemoriesFromDir as loadMemoriesFromDir39,
17028
17129
  loadUsageIndex as loadUsageIndex31,
17029
17130
  recordPrevention as recordPrevention2,
17030
17131
  resolveHaivePaths as resolveHaivePaths49,
17031
- runSensors as runSensors2,
17132
+ runSensors as runSensors3,
17032
17133
  saveUsageIndex as saveUsageIndex8,
17033
17134
  selectCommandSensors,
17034
- sensorTargetsFromDiff as sensorTargetsFromDiff2,
17135
+ sensorTargetsFromDiff as sensorTargetsFromDiff3,
17035
17136
  serializeMemory as serializeMemory29
17036
17137
  } from "@hiveai/core";
17037
17138
  var exec2 = promisify3(execFile3);
@@ -17065,8 +17166,8 @@ function registerSensors(program2) {
17065
17166
  const paths = resolveHaivePaths49(root);
17066
17167
  const memories = await runnableSensorMemories(paths);
17067
17168
  const diff = opts.diffFile ? await readFile25(path54.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
17068
- const targets = sensorTargetsFromDiff2(diff);
17069
- const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
17169
+ const targets = sensorTargetsFromDiff3(diff);
17170
+ const hits = runSensors3(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
17070
17171
  const config = await loadConfig15(paths);
17071
17172
  const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
17072
17173
  const changedPaths = targets.map((t) => t.path).filter(Boolean);
@@ -17222,7 +17323,7 @@ async function runnableSensorMemories(paths, regexOnly = true) {
17222
17323
  const sensor = memory2.frontmatter.sensor;
17223
17324
  if (!sensor) return false;
17224
17325
  if (regexOnly && sensor.kind !== "regex") return false;
17225
- return !isRetiredMemory3(memory2.frontmatter, memory2.body);
17326
+ return !isRetiredMemory4(memory2.frontmatter, memory2.body);
17226
17327
  });
17227
17328
  }
17228
17329
  async function runCommandSensor(spec, root) {
@@ -17924,7 +18025,7 @@ import "commander";
17924
18025
  import {
17925
18026
  findProjectRoot as findProjectRoot61,
17926
18027
  resolveHaivePaths as resolveHaivePaths55,
17927
- BRIDGE_TARGET_PATH as BRIDGE_TARGET_PATH2,
18028
+ BRIDGE_TARGET_PATH as BRIDGE_TARGET_PATH3,
17928
18029
  BRIDGE_TARGETS as BRIDGE_TARGETS3
17929
18030
  } from "@hiveai/core";
17930
18031
  function registerBridges(program2) {
@@ -17959,7 +18060,7 @@ function registerBridges(program2) {
17959
18060
  targets = BRIDGE_TARGETS3;
17960
18061
  } else {
17961
18062
  targets = BRIDGE_TARGETS3.filter(
17962
- (t) => existsSync84(path59.join(root, BRIDGE_TARGET_PATH2[t]))
18063
+ (t) => existsSync84(path59.join(root, BRIDGE_TARGET_PATH3[t]))
17963
18064
  );
17964
18065
  if (targets.length === 0) {
17965
18066
  ui.info(
@@ -17987,7 +18088,7 @@ function registerBridges(program2) {
17987
18088
  const root = findProjectRoot61(opts.dir);
17988
18089
  console.log(ui.bold("hAIve bridge targets:"));
17989
18090
  for (const target of BRIDGE_TARGETS3) {
17990
- const relPath = BRIDGE_TARGET_PATH2[target];
18091
+ const relPath = BRIDGE_TARGET_PATH3[target];
17991
18092
  const exists = existsSync84(path59.join(root, relPath));
17992
18093
  const marker = exists ? ui.dim("\u2713") : ui.dim("\xB7");
17993
18094
  console.log(` ${marker} ${target.padEnd(10)} ${relPath}${exists ? "" : " (not present)"}`);
@@ -17999,7 +18100,7 @@ function registerBridges(program2) {
17999
18100
 
18000
18101
  // src/index.ts
18001
18102
  var program = new Command64();
18002
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.19.0").option("--advanced", "show maintenance and experimental commands in help");
18103
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.20.1").option("--advanced", "show maintenance and experimental commands in help");
18003
18104
  registerInit(program);
18004
18105
  registerWelcome(program);
18005
18106
  registerResolveProject(program);