@hiveai/cli 0.9.25 → 0.9.26

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
@@ -4907,7 +4907,12 @@ async function memSessionEnd(input, ctx) {
4907
4907
  }
4908
4908
  const body = buildBody(input);
4909
4909
  const topic = recapTopic(input.scope, input.module);
4910
- const invalidPaths = input.files_touched.filter(
4910
+ const normalizedFiles = input.files_touched.map((p) => {
4911
+ if (!p || !path82.isAbsolute(p)) return p;
4912
+ const rel = path82.relative(ctx.paths.root, p);
4913
+ return rel.startsWith("..") ? p : rel;
4914
+ });
4915
+ const invalidPaths = normalizedFiles.filter(
4911
4916
  (p) => !existsSync17(path82.resolve(ctx.paths.root, p))
4912
4917
  );
4913
4918
  if (invalidPaths.length > 0) {
@@ -4926,7 +4931,7 @@ async function memSessionEnd(input, ctx) {
4926
4931
  revision_count: revisionCount,
4927
4932
  anchor: {
4928
4933
  ...fm.anchor,
4929
- paths: input.files_touched.length ? input.files_touched : fm.anchor.paths
4934
+ paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
4930
4935
  }
4931
4936
  };
4932
4937
  await writeFile10(
@@ -4949,7 +4954,7 @@ async function memSessionEnd(input, ctx) {
4949
4954
  scope: input.scope,
4950
4955
  module: input.module,
4951
4956
  tags: ["session", "recap"],
4952
- paths: input.files_touched,
4957
+ paths: normalizedFiles,
4953
4958
  topic,
4954
4959
  status: "validated"
4955
4960
  });
@@ -6558,7 +6563,9 @@ function isDocLikePath(file) {
6558
6563
  }
6559
6564
  function isPackageOrConfigPath(file) {
6560
6565
  const lower = file.toLowerCase();
6561
- return lower.endsWith("package.json") || lower.endsWith("package-lock.json") || lower.endsWith("pnpm-lock.yaml") || lower.endsWith("yarn.lock") || lower.endsWith("bun.lockb") || lower.endsWith(".config.ts") || lower.endsWith(".config.js") || lower.endsWith(".json") || lower.endsWith(".yml") || lower.endsWith(".yaml") || lower.startsWith(".github/workflows/");
6566
+ const base = lower.split("/").pop() ?? lower;
6567
+ return lower.endsWith("package.json") || lower.endsWith("package-lock.json") || lower.endsWith("pnpm-lock.yaml") || lower.endsWith("yarn.lock") || lower.endsWith("bun.lockb") || lower.endsWith(".config.ts") || lower.endsWith(".config.js") || lower.endsWith(".json") || lower.endsWith(".yml") || lower.endsWith(".yaml") || lower.endsWith(".toml") || lower.startsWith(".github/workflows/") || lower.startsWith(".github/") && lower.endsWith(".yml") || // Dotfiles that are pure configuration/tooling — never trigger runtime gotchas
6568
+ base === ".gitignore" || base === ".gitattributes" || base === ".gitmodules" || base === ".editorconfig" || base === ".nvmrc" || base === ".node-version" || base === ".npmrc" || base === ".yarnrc" || base === ".yarnrc.yml" || base === ".dockerignore" || base === "dockerfile" || base.startsWith("dockerfile.") || base === ".env.example" || base === ".env.template" || lower.endsWith(".prettierrc") || lower.endsWith(".eslintrc") || lower.endsWith(".eslintignore") || lower.endsWith(".prettierignore") || lower.endsWith(".stylelintrc") || lower.endsWith(".browserslistrc");
6562
6569
  }
6563
6570
  function repairCommandForWarning(warning, paths) {
6564
6571
  const firstPath = paths[0];
@@ -7095,7 +7102,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7095
7102
  };
7096
7103
  }
7097
7104
  var SERVER_NAME = "haive";
7098
- var SERVER_VERSION = "0.9.25";
7105
+ var SERVER_VERSION = "0.9.26";
7099
7106
  function jsonResult(data) {
7100
7107
  return {
7101
7108
  content: [
@@ -10285,18 +10292,37 @@ async function buildAutoRecap(paths) {
10285
10292
  }
10286
10293
  if (obs.length === 0) return await buildGitAutoRecap(paths);
10287
10294
  const toolCounts = /* @__PURE__ */ new Map();
10288
- const fileCounts = /* @__PURE__ */ new Map();
10289
- const summaries = [];
10295
+ const writeFiles = /* @__PURE__ */ new Set();
10296
+ const readFiles = /* @__PURE__ */ new Set();
10290
10297
  for (const o of obs) {
10291
10298
  toolCounts.set(o.tool, (toolCounts.get(o.tool) ?? 0) + 1);
10292
- for (const f of o.files ?? []) fileCounts.set(f, (fileCounts.get(f) ?? 0) + 1);
10293
- if (summaries.length < 10) summaries.push(`- ${o.summary}`);
10299
+ const isWrite = ["Edit", "Write", "NotebookEdit"].includes(o.tool);
10300
+ for (const f of o.files ?? []) {
10301
+ const rel = normalizeAnchorPath(paths.root, f);
10302
+ if (isWrite) writeFiles.add(rel);
10303
+ else readFiles.add(rel);
10304
+ }
10294
10305
  }
10306
+ for (const f of writeFiles) readFiles.delete(f);
10295
10307
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t} \xD7${c}`).join(", ");
10296
- const topFiles = [...fileCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
10297
- const goal = `Auto-captured session \u2014 ${obs.length} tool calls (${topTools})`;
10298
- const accomplished = summaries.length ? `Recent activity:
10299
- ${summaries.join("\n")}` : `Activity captured but no parseable summaries.`;
10308
+ const recentCommits = await runGit(paths.root, ["log", "--oneline", "-5"]).catch(() => "");
10309
+ const accomplishedParts = [];
10310
+ if (writeFiles.size > 0) {
10311
+ accomplishedParts.push(
10312
+ `**Files modified (${writeFiles.size}):**`,
10313
+ ...[...writeFiles].slice(0, 10).map((f) => `- \`${f}\``),
10314
+ ...writeFiles.size > 10 ? [`- ...and ${writeFiles.size - 10} more`] : []
10315
+ );
10316
+ }
10317
+ if (recentCommits.trim()) {
10318
+ accomplishedParts.push("", "**Recent commits:**");
10319
+ for (const line of recentCommits.trim().split("\n").slice(0, 5)) {
10320
+ accomplishedParts.push(`- ${line}`);
10321
+ }
10322
+ }
10323
+ if (accomplishedParts.length === 0) {
10324
+ accomplishedParts.push(`${obs.length} tool calls (${topTools}) \u2014 no file writes detected.`);
10325
+ }
10300
10326
  const failures = obs.filter((o) => o.failure_hint);
10301
10327
  const discoveriesParts = [];
10302
10328
  if (failures.length > 0) {
@@ -10305,37 +10331,75 @@ ${summaries.join("\n")}` : `Activity captured but no parseable summaries.`;
10305
10331
  ...failures.slice(0, 8).map((o) => `- ${o.summary.slice(0, 180)}`)
10306
10332
  );
10307
10333
  }
10334
+ const goal = writeFiles.size > 0 ? `Edited ${writeFiles.size} file${writeFiles.size === 1 ? "" : "s"} across ${obs.length} tool calls` : `Session with ${obs.length} tool calls (${topTools}) \u2014 read-only or no writes captured`;
10308
10335
  return {
10309
10336
  goal,
10310
- accomplished,
10337
+ accomplished: accomplishedParts.join("\n"),
10311
10338
  ...discoveriesParts.length > 0 ? { discoveries: discoveriesParts.join("\n") } : {},
10312
- files: topFiles.map(([f]) => f),
10339
+ files: [...writeFiles].slice(0, 12),
10313
10340
  rawCount: obs.length
10314
10341
  };
10315
10342
  }
10316
10343
  async function buildGitAutoRecap(paths) {
10317
10344
  const changed = await runGit(paths.root, ["diff", "--name-only"]).catch(() => "");
10318
10345
  const staged = await runGit(paths.root, ["diff", "--cached", "--name-only"]).catch(() => "");
10319
- const status = await runGit(paths.root, ["status", "--porcelain", "--untracked-files=all"]).catch(() => "");
10346
+ const statusRaw = await runGit(paths.root, ["status", "--porcelain"]).catch(() => "");
10347
+ const recentLog = await runGit(paths.root, ["log", "--oneline", "-5"]).catch(() => "");
10348
+ const diffStat = await runGit(paths.root, ["diff", "--stat", "HEAD"]).catch(() => "");
10320
10349
  const files = Array.from(new Set(
10321
10350
  [
10322
10351
  ...changed.split("\n"),
10323
10352
  ...staged.split("\n"),
10324
- ...status.split("\n").map((line) => line.replace(/^[ MADRCU?!]{1,2}\s+/, ""))
10353
+ ...statusRaw.split("\n").map((line) => line.replace(/^[ MADRCU?!]{1,2}\s+/, ""))
10325
10354
  ].map((s) => s.trim()).filter(Boolean).filter((file) => !file.startsWith(".ai/.runtime/") && !file.startsWith(".ai/.cache/"))
10326
10355
  )).sort();
10327
- if (files.length === 0) return null;
10328
- const diffStat = await runGit(paths.root, ["diff", "--stat"]).catch(() => "");
10356
+ const modified = [];
10357
+ const added = [];
10358
+ const deleted = [];
10359
+ for (const line of statusRaw.split("\n")) {
10360
+ const code = line.substring(0, 2).trim();
10361
+ const file = line.substring(3).trim().replace(/".+"/g, (m) => m.slice(1, -1));
10362
+ if (!file || file.startsWith(".ai/.runtime/") || file.startsWith(".ai/.cache/")) continue;
10363
+ if (code === "D" || code === "DD") deleted.push(file);
10364
+ else if (code === "A" || code === "??") added.push(file);
10365
+ else if (file) modified.push(file);
10366
+ }
10367
+ const accomplishedParts = [];
10368
+ if (modified.length > 0) {
10369
+ accomplishedParts.push(`**Modified (${modified.length}):**`);
10370
+ for (const f of modified.slice(0, 8)) accomplishedParts.push(`- \`${f}\``);
10371
+ if (modified.length > 8) accomplishedParts.push(`- ...and ${modified.length - 8} more`);
10372
+ }
10373
+ if (added.length > 0) {
10374
+ accomplishedParts.push(`
10375
+ **Added (${added.length}):**`);
10376
+ for (const f of added.slice(0, 5)) accomplishedParts.push(`- \`${f}\``);
10377
+ if (added.length > 5) accomplishedParts.push(`- ...and ${added.length - 5} more`);
10378
+ }
10379
+ if (deleted.length > 0) {
10380
+ accomplishedParts.push(`
10381
+ **Deleted (${deleted.length}):**`);
10382
+ for (const f of deleted.slice(0, 5)) accomplishedParts.push(`- \`${f}\``);
10383
+ }
10384
+ if (recentLog.trim()) {
10385
+ accomplishedParts.push("\n**Recent commits:**");
10386
+ for (const line of recentLog.trim().split("\n").slice(0, 5)) {
10387
+ accomplishedParts.push(`- ${line}`);
10388
+ }
10389
+ }
10390
+ if (accomplishedParts.length === 0 && files.length === 0) return null;
10391
+ if (accomplishedParts.length === 0) {
10392
+ accomplishedParts.push(...files.slice(0, 12).map((f) => `- \`${f}\``));
10393
+ if (files.length > 12) accomplishedParts.push(`- ...and ${files.length - 12} more`);
10394
+ }
10329
10395
  return {
10330
- goal: `Auto-captured session \u2014 ${files.length} changed file${files.length === 1 ? "" : "s"}`,
10331
- accomplished: [
10332
- "Detected local changes:",
10333
- ...files.slice(0, 12).map((file) => `- ${file}`),
10334
- files.length > 12 ? `- ...and ${files.length - 12} more` : ""
10335
- ].filter(Boolean).join("\n"),
10336
- discoveries: diffStat.trim() ? `Git diff summary:
10337
- ${diffStat.trim()}` : void 0,
10338
- files,
10396
+ goal: files.length > 0 ? `Session with ${files.length} changed file${files.length === 1 ? "" : "s"}` : `Session with recent commits (no uncommitted changes)`,
10397
+ accomplished: accomplishedParts.join("\n"),
10398
+ discoveries: diffStat.trim() ? `Git diff stat:
10399
+ \`\`\`
10400
+ ${diffStat.trim()}
10401
+ \`\`\`` : void 0,
10402
+ files: files.slice(0, 12),
10339
10403
  rawCount: files.length
10340
10404
  };
10341
10405
  }
@@ -10438,7 +10502,7 @@ function registerSessionEnd(session2) {
10438
10502
  next: opts.next
10439
10503
  });
10440
10504
  const topic = recapTopic2(scope, opts.module);
10441
- const filesTouched = parseCsv5(resolvedFiles);
10505
+ const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
10442
10506
  const missingPaths = filesTouched.filter((p) => !existsSync53(path36.resolve(root, p)));
10443
10507
  if (missingPaths.length > 0 && !opts.quiet) {
10444
10508
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
@@ -10503,6 +10567,13 @@ function parseCsv5(value) {
10503
10567
  if (!value) return [];
10504
10568
  return value.split(",").map((s) => s.trim()).filter(Boolean);
10505
10569
  }
10570
+ function normalizeAnchorPath(root, filePath) {
10571
+ if (!filePath) return filePath;
10572
+ if (!path36.isAbsolute(filePath)) return filePath;
10573
+ const rel = path36.relative(root, filePath);
10574
+ if (rel.startsWith("..")) return filePath;
10575
+ return rel;
10576
+ }
10506
10577
 
10507
10578
  // src/commands/snapshot.ts
10508
10579
  import { existsSync as existsSync54 } from "fs";
@@ -11691,7 +11762,7 @@ function parseDays(input) {
11691
11762
  }
11692
11763
 
11693
11764
  // src/commands/doctor.ts
11694
- import { existsSync as existsSync60 } from "fs";
11765
+ import { existsSync as existsSync60, statSync } from "fs";
11695
11766
  import { readFile as readFile19, stat } from "fs/promises";
11696
11767
  import path44 from "path";
11697
11768
  import { execFileSync, execSync as execSync3 } from "child_process";
@@ -11795,6 +11866,22 @@ function registerDoctor(program2) {
11795
11866
  fix: "haive memory pending # list them\nhaive memory auto-promote # promote those with high read_count"
11796
11867
  });
11797
11868
  }
11869
+ const OLD_DRAFT_DAYS = 30;
11870
+ const oldDrafts = memories.filter((m) => {
11871
+ if (m.memory.frontmatter.status !== "draft") return false;
11872
+ const age = (now - Date.parse(m.memory.frontmatter.created_at)) / MS_PER_DAY3;
11873
+ return age > OLD_DRAFT_DAYS;
11874
+ });
11875
+ if (oldDrafts.length > 0) {
11876
+ const ids = oldDrafts.slice(0, 4).map((m) => m.memory.frontmatter.id).join(", ");
11877
+ const more = oldDrafts.length > 4 ? ` (+${oldDrafts.length - 4} more)` : "";
11878
+ findings.push({
11879
+ severity: "warn",
11880
+ code: "stale-draft-memories",
11881
+ message: `${oldDrafts.length} draft memor${oldDrafts.length === 1 ? "y has" : "ies have"} been in draft status for 30+ days: ${ids}${more}`,
11882
+ fix: "haive memory approve <id> # activate\nhaive memory rm <id> # or delete if obsolete"
11883
+ });
11884
+ }
11798
11885
  const anchorless = memories.filter(
11799
11886
  (m) => m.memory.frontmatter.anchor.paths.length === 0 && m.memory.frontmatter.anchor.symbols.length === 0 && m.memory.frontmatter.type !== "session_recap" && m.memory.frontmatter.type !== "glossary" && m.memory.frontmatter.type !== "skill"
11800
11887
  );
@@ -11920,14 +12007,14 @@ function registerDoctor(program2) {
11920
12007
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
11921
12008
  });
11922
12009
  }
11923
- findings.push(...await collectInstallFindings(root, "0.9.25"));
12010
+ findings.push(...await collectInstallFindings(root, "0.9.26"));
11924
12011
  try {
11925
12012
  const legacyRaw = execSync3("haive-mcp --version", {
11926
12013
  encoding: "utf8",
11927
12014
  timeout: 3e3,
11928
12015
  stdio: ["ignore", "pipe", "ignore"]
11929
12016
  }).trim();
11930
- const cliVersion = "0.9.25";
12017
+ const cliVersion = "0.9.26";
11931
12018
  if (legacyRaw && legacyRaw !== cliVersion) {
11932
12019
  findings.push({
11933
12020
  severity: "warn",
@@ -12084,13 +12171,22 @@ async function collectHarnessCoverageFindings(codeMap, memories) {
12084
12171
  }
12085
12172
  const covered = coveredFiles.size;
12086
12173
  const pct = Math.round(covered / total * 100);
12174
+ const uncovered = codeFiles.filter((f) => !coveredFiles.has(f)).sort((a, b) => {
12175
+ const depthA = a.split("/").length;
12176
+ const depthB = b.split("/").length;
12177
+ if (depthA !== depthB) return depthA - depthB;
12178
+ return a.localeCompare(b);
12179
+ }).slice(0, 5);
12180
+ const coverageDesc = pct < 10 && total > 10 ? "Low coverage \u2014 add memory anchors on key modules to improve harness enforcement." : pct < 50 ? "Partial coverage \u2014 useful but not yet broad enough to call the harness mature." : pct < 80 ? "Good coverage \u2014 critical modules are increasingly protected." : "Good harness coverage.";
12181
+ const uncoveredHint = uncovered.length > 0 ? `
12182
+ Top uncovered: ${uncovered.map((f) => `\`${f}\``).join(", ")}` : "";
12087
12183
  const findings = [];
12088
12184
  findings.push({
12089
12185
  severity: "info",
12090
12186
  code: "harness-coverage",
12091
12187
  coverage_percent: pct,
12092
- message: `${covered}/${total} code-map files have validated memory anchors (${pct}%). ` + (pct < 10 && total > 10 ? "Low coverage \u2014 add memory anchors on key modules to improve harness enforcement." : pct < 50 ? "Partial coverage \u2014 useful but not yet broad enough to call the harness mature." : pct < 80 ? "Good coverage \u2014 critical modules are increasingly protected." : "Good harness coverage."),
12093
- fix: pct < 50 && total > 10 ? "haive memory add --type gotcha|convention|architecture --paths <key-file> --scope team" : void 0,
12188
+ message: `${covered}/${total} code-map files have validated memory anchors (${pct}%). ` + coverageDesc + uncoveredHint,
12189
+ fix: pct < 50 && total > 10 ? `haive memory add --type gotcha|convention|architecture --paths <key-file> --scope team` : void 0,
12094
12190
  section: "Harness coverage"
12095
12191
  });
12096
12192
  return findings;
@@ -12322,7 +12418,13 @@ function extractAbsoluteHaiveBins(text) {
12322
12418
  const re = /(["'\s])((?:\/[^"'\s]+)*\/haive)\b/g;
12323
12419
  let match;
12324
12420
  while (match = re.exec(text)) {
12325
- if (match[2]) out.add(match[2]);
12421
+ const p = match[2];
12422
+ if (!p) continue;
12423
+ try {
12424
+ if (statSync(p).isDirectory()) continue;
12425
+ } catch {
12426
+ }
12427
+ out.add(p);
12326
12428
  }
12327
12429
  return [...out].sort();
12328
12430
  }
@@ -12823,7 +12925,7 @@ function registerMemoryConflictCandidates(memory2) {
12823
12925
 
12824
12926
  // src/commands/enforce.ts
12825
12927
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
12826
- import { existsSync as existsSync67 } from "fs";
12928
+ import { existsSync as existsSync67, statSync as statSync2 } from "fs";
12827
12929
  import { chmod as chmod2, mkdir as mkdir19, readFile as readFile20, readdir as readdir6, rm as rm3, writeFile as writeFile31 } from "fs/promises";
12828
12930
  import path49 from "path";
12829
12931
  import "commander";
@@ -13155,7 +13257,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13155
13257
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
13156
13258
  });
13157
13259
  }
13158
- findings.push(...await inspectIntegrationVersions(root, "0.9.25"));
13260
+ findings.push(...await inspectIntegrationVersions(root, "0.9.26"));
13159
13261
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
13160
13262
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
13161
13263
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -13458,7 +13560,13 @@ function extractAbsoluteHaiveBins2(text) {
13458
13560
  const re = /(["'\s])((?:\/[^"'\s]+)*\/haive)\b/g;
13459
13561
  let match;
13460
13562
  while (match = re.exec(text)) {
13461
- if (match[2]) out.add(match[2]);
13563
+ const p = match[2];
13564
+ if (!p) continue;
13565
+ try {
13566
+ if (statSync2(p).isDirectory()) continue;
13567
+ } catch {
13568
+ }
13569
+ out.add(p);
13462
13570
  }
13463
13571
  return [...out].sort();
13464
13572
  }
@@ -13756,7 +13864,7 @@ function registerRun(program2) {
13756
13864
 
13757
13865
  // src/index.ts
13758
13866
  var program = new Command51();
13759
- program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.25").option("--advanced", "show maintenance and experimental commands in help");
13867
+ program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.26").option("--advanced", "show maintenance and experimental commands in help");
13760
13868
  registerInit(program);
13761
13869
  registerWelcome(program);
13762
13870
  registerResolveProject(program);