@hiveai/cli 0.12.1 → 0.12.3

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/README.md CHANGED
@@ -187,6 +187,35 @@ Autogenerated sensors are conservative: they start as `warn` and `autogen: true`
187
187
  high-confidence sensors to `severity: block`, which makes a deterministic pre-commit blocker when the
188
188
  sensor matches added diff lines.
189
189
 
190
+ ### `haive eval`
191
+
192
+ Run the repeatable quality gate for hAIve itself or for a project using hAIve:
193
+
194
+ ```bash
195
+ haive eval
196
+ haive eval --semantic-only
197
+ haive eval --spec .ai/eval/spec.json --fail-under 80
198
+ ```
199
+
200
+ Without `--spec`, hAIve synthesizes retrieval cases from anchored memories. If `.ai/eval/spec.json`
201
+ exists, it is loaded automatically and merged with those synthesized retrieval cases. Use that file
202
+ for labeled sensor cases and hard retrieval probes so CI measures both “did the right memory surface?”
203
+ and “did the executable guardrail fire?”.
204
+
205
+ ### `haive doctor`
206
+
207
+ `doctor` is the first stop when hAIve feels inconsistent locally:
208
+
209
+ ```bash
210
+ haive doctor
211
+ haive doctor --json
212
+ haive doctor --fix
213
+ ```
214
+
215
+ It reports missing `pnpm`, stale workspace `dist` artifacts after a pull, global CLI/MCP version skew,
216
+ outdated code-search indexes, memory-lint findings, and harness coverage. The output is intentionally
217
+ actionable: every setup finding should carry the exact command to run next.
218
+
190
219
  ### `haive benchmark`
191
220
 
192
221
  Turn hAIve-vs-plain agent trials into a repeatable demo/report.
package/dist/index.js CHANGED
@@ -2743,7 +2743,7 @@ ${SEED_FOOTER(stack)}` });
2743
2743
  }
2744
2744
 
2745
2745
  // src/commands/init.ts
2746
- var HAIVE_GITHUB_ACTION_REF = `v${"0.12.1"}`;
2746
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.12.3"}`;
2747
2747
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
2748
2748
 
2749
2749
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -7531,7 +7531,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7531
7531
  };
7532
7532
  }
7533
7533
  var SERVER_NAME = "haive";
7534
- var SERVER_VERSION = "0.12.1";
7534
+ var SERVER_VERSION = "0.12.3";
7535
7535
  function jsonResult(data) {
7536
7536
  return {
7537
7537
  content: [
@@ -12259,7 +12259,8 @@ function registerEval(program2) {
12259
12259
  }
12260
12260
  const k = Math.max(1, parseInt(opts.top ?? "8", 10) || 8);
12261
12261
  const ctx = { paths };
12262
- const spec = await resolveSpec(opts, paths.memoriesDir);
12262
+ const resolvedSpec = await resolveSpec(opts, root, paths.memoriesDir);
12263
+ const spec = resolvedSpec.spec;
12263
12264
  if ((spec.retrieval?.length ?? 0) === 0 && (spec.sensors?.length ?? 0) === 0) {
12264
12265
  ui.warn("No eval cases (no anchored memories and no --spec). Nothing to score.");
12265
12266
  return;
@@ -12284,9 +12285,9 @@ function registerEval(program2) {
12284
12285
  }
12285
12286
  const report = buildReport(retrievalAgg, sensorAgg);
12286
12287
  if (opts.json) {
12287
- console.log(JSON.stringify({ root, k, report }, null, 2));
12288
+ console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report }, null, 2));
12288
12289
  } else {
12289
- const md = renderMarkdown2(root, k, report);
12290
+ const md = renderMarkdown2(root, k, resolvedSpec.source, report);
12290
12291
  if (opts.out) {
12291
12292
  const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12292
12293
  await writeFile29(outFile, md, "utf8");
@@ -12307,14 +12308,31 @@ function registerEval(program2) {
12307
12308
  }
12308
12309
  });
12309
12310
  }
12310
- async function resolveSpec(opts, memoriesDir) {
12311
+ async function resolveSpec(opts, root, memoriesDir) {
12311
12312
  if (opts.spec) {
12312
12313
  const file = path43.resolve(opts.spec);
12313
12314
  const raw = await readFile20(file, "utf8");
12314
- return JSON.parse(raw);
12315
+ return { spec: JSON.parse(raw), source: file };
12316
+ }
12317
+ const defaultSpec = path43.join(root, ".ai", "eval", "spec.json");
12318
+ if (existsSync64(defaultSpec)) {
12319
+ const raw = await readFile20(defaultSpec, "utf8");
12320
+ const explicit = JSON.parse(raw);
12321
+ const memories2 = await loadMemoriesFromDir26(memoriesDir);
12322
+ const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
12323
+ return {
12324
+ spec: {
12325
+ retrieval: [...synthesized, ...explicit.retrieval ?? []],
12326
+ sensors: explicit.sensors ?? []
12327
+ },
12328
+ source: ".ai/eval/spec.json + synthesized anchored retrieval"
12329
+ };
12315
12330
  }
12316
12331
  const memories = await loadMemoriesFromDir26(memoriesDir);
12317
- return { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) };
12332
+ return {
12333
+ spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
12334
+ source: "synthesized anchored retrieval"
12335
+ };
12318
12336
  }
12319
12337
  async function runRetrieval(c, k, ctx) {
12320
12338
  const out = await getBriefing(
@@ -12346,11 +12364,12 @@ async function runSensorCase(c, ctx) {
12346
12364
  function pct(n) {
12347
12365
  return `${Math.round(n * 100)}%`;
12348
12366
  }
12349
- function renderMarkdown2(root, k, report) {
12367
+ function renderMarkdown2(root, k, source, report) {
12350
12368
  const lines = [
12351
12369
  "# hAIve eval report",
12352
12370
  "",
12353
12371
  `Project: \`${root}\` \xB7 top-k: ${k}`,
12372
+ `Spec: ${source}`,
12354
12373
  "",
12355
12374
  `## Overall score: ${report.score}/100`,
12356
12375
  ""
@@ -12982,14 +13001,15 @@ function registerDoctor(program2) {
12982
13001
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12983
13002
  });
12984
13003
  }
12985
- findings.push(...await collectInstallFindings(root, "0.12.1"));
13004
+ findings.push(...await collectInstallFindings(root, "0.12.3"));
13005
+ findings.push(...await collectToolchainFindings(root));
12986
13006
  try {
12987
13007
  const legacyRaw = execSync3("haive-mcp --version", {
12988
13008
  encoding: "utf8",
12989
13009
  timeout: 3e3,
12990
13010
  stdio: ["ignore", "pipe", "ignore"]
12991
13011
  }).trim();
12992
- const cliVersion = "0.12.1";
13012
+ const cliVersion = "0.12.3";
12993
13013
  if (legacyRaw && legacyRaw !== cliVersion) {
12994
13014
  findings.push({
12995
13015
  severity: "warn",
@@ -13266,6 +13286,7 @@ function isSearchTool(name) {
13266
13286
  async function collectInstallFindings(root, expectedVersion) {
13267
13287
  const findings = [];
13268
13288
  findings.push(...await collectWorkspaceVersionFindings(root, expectedVersion));
13289
+ findings.push(...await collectDistFreshnessFindings(root, expectedVersion));
13269
13290
  const haiveBins = listHaiveBins();
13270
13291
  if (haiveBins.length === 0) {
13271
13292
  findings.push({
@@ -13329,6 +13350,71 @@ which -a haive`
13329
13350
  }
13330
13351
  return findings;
13331
13352
  }
13353
+ async function collectToolchainFindings(root) {
13354
+ const findings = [];
13355
+ const pkg = await readJson(
13356
+ path46.join(root, "package.json")
13357
+ );
13358
+ const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
13359
+ if (!wantsPnpm) return findings;
13360
+ const expected = pkg?.packageManager?.replace(/^pnpm@/, "") ?? "9.14.2";
13361
+ if (!commandExists2("pnpm", ["--version"])) {
13362
+ const corepackAvailable = commandExists2("corepack", ["--version"]);
13363
+ findings.push({
13364
+ severity: "warn",
13365
+ code: "pnpm-not-on-path",
13366
+ message: `This workspace uses pnpm${expected ? ` ${expected}` : ""}, but no pnpm binary is available on PATH. Local build/test commands may fail even though CI works.`,
13367
+ fix: corepackAvailable ? `corepack prepare pnpm@${expected} --activate` : `npx pnpm@${expected} install --frozen-lockfile`,
13368
+ section: "Agent coverage"
13369
+ });
13370
+ }
13371
+ return findings;
13372
+ }
13373
+ async function collectDistFreshnessFindings(root, expectedVersion) {
13374
+ const findings = [];
13375
+ const isHaiveWorkspace = (await readJson(path46.join(root, "package.json")))?.name === "haive-monorepo";
13376
+ if (!isHaiveWorkspace) return findings;
13377
+ const cliDist = path46.join(root, "packages/cli/dist/index.js");
13378
+ if (!existsSync67(cliDist)) {
13379
+ findings.push({
13380
+ severity: "warn",
13381
+ code: "workspace-dist-missing",
13382
+ message: "packages/cli/dist/index.js is missing; local hAIve smoke commands cannot reflect source changes.",
13383
+ fix: "pnpm -r build",
13384
+ section: "Agent coverage"
13385
+ });
13386
+ return findings;
13387
+ }
13388
+ const distVersion = versionForNodeEntrypoint(cliDist);
13389
+ if (distVersion && distVersion !== expectedVersion) {
13390
+ findings.push({
13391
+ severity: "warn",
13392
+ code: "workspace-dist-version-mismatch",
13393
+ message: `packages/cli/dist/index.js reports ${distVersion}, but this source build expects ${expectedVersion}. Run a fresh workspace build after pull before trusting local doctor/enforce output.`,
13394
+ fix: "pnpm -r build\npnpm check:artifacts",
13395
+ section: "Agent coverage"
13396
+ });
13397
+ }
13398
+ const sourceFiles = [
13399
+ "packages/core/src/index.ts",
13400
+ "packages/mcp/src/server.ts",
13401
+ "packages/cli/src/index.ts"
13402
+ ].map((rel) => path46.join(root, rel)).filter(existsSync67);
13403
+ if (sourceFiles.length > 0) {
13404
+ const distMtime = statSync2(cliDist).mtimeMs;
13405
+ const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
13406
+ if (newestSource > distMtime + 1e3) {
13407
+ findings.push({
13408
+ severity: "info",
13409
+ code: "workspace-dist-older-than-source",
13410
+ message: "Built CLI artifacts are older than key source files; rebuild before running release smoke checks.",
13411
+ fix: "pnpm -r build\npnpm check:artifacts",
13412
+ section: "Agent coverage"
13413
+ });
13414
+ }
13415
+ }
13416
+ return findings;
13417
+ }
13332
13418
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
13333
13419
  const findings = [];
13334
13420
  const rootPkg = await readJson(path46.join(root, "package.json"));
@@ -13432,6 +13518,30 @@ function versionForBinary(bin) {
13432
13518
  return null;
13433
13519
  }
13434
13520
  }
13521
+ function versionForNodeEntrypoint(file) {
13522
+ try {
13523
+ const out = execFileSync(process.execPath, [file, "--version"], {
13524
+ encoding: "utf8",
13525
+ timeout: 3e3,
13526
+ stdio: ["ignore", "pipe", "ignore"]
13527
+ }).trim();
13528
+ return out.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
13529
+ } catch {
13530
+ return null;
13531
+ }
13532
+ }
13533
+ function commandExists2(command, args) {
13534
+ try {
13535
+ execFileSync(command, args, {
13536
+ encoding: "utf8",
13537
+ timeout: 3e3,
13538
+ stdio: ["ignore", "pipe", "ignore"]
13539
+ });
13540
+ return true;
13541
+ } catch {
13542
+ return false;
13543
+ }
13544
+ }
13435
13545
  function extractAbsoluteHaiveBins(text) {
13436
13546
  const out = /* @__PURE__ */ new Set();
13437
13547
  const re = /(["'\s])((?:\/[^"'\s]+)*\/haive)\b/g;
@@ -14490,7 +14600,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
14490
14600
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
14491
14601
  });
14492
14602
  }
14493
- findings.push(...await inspectIntegrationVersions(root, "0.12.1"));
14603
+ findings.push(...await inspectIntegrationVersions(root, "0.12.3"));
14494
14604
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
14495
14605
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
14496
14606
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -14628,6 +14738,15 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
14628
14738
  }];
14629
14739
  }
14630
14740
  const marker = await readRecentBriefingMarker(paths, sessionId);
14741
+ if (stage === "ci" && !marker) {
14742
+ return [{
14743
+ severity: "ok",
14744
+ code: "decision-coverage-ci-pass",
14745
+ 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.`,
14746
+ memory_ids: relevant.slice(0, 20).map((memory2) => memory2.frontmatter.id),
14747
+ affected_files: changedFiles.slice(0, 10)
14748
+ }];
14749
+ }
14631
14750
  const consulted = new Set(marker?.memory_ids ?? []);
14632
14751
  const missing = relevant.filter((memory2) => !consulted.has(memory2.frontmatter.id));
14633
14752
  if (missing.length === 0) {
@@ -15489,7 +15608,7 @@ function shellQuote(value) {
15489
15608
 
15490
15609
  // src/index.ts
15491
15610
  var program = new Command56();
15492
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.1").option("--advanced", "show maintenance and experimental commands in help");
15611
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.3").option("--advanced", "show maintenance and experimental commands in help");
15493
15612
  registerInit(program);
15494
15613
  registerWelcome(program);
15495
15614
  registerResolveProject(program);