@hiveai/cli 0.9.19 → 0.9.21

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
@@ -3294,8 +3294,8 @@ async function memList(input, ctx) {
3294
3294
  return { memories };
3295
3295
  }
3296
3296
  var MemSaveInputSchema = {
3297
- type: z4.enum(["convention", "decision", "gotcha", "architecture", "glossary", "attempt", "session_recap"]).describe(
3298
- "Kind of memory being saved. Use 'attempt' for failed approaches (auto-validated). Use 'session_recap' via mem_session_end instead."
3297
+ type: z4.enum(["convention", "decision", "gotcha", "architecture", "glossary", "skill", "attempt", "session_recap"]).describe(
3298
+ "Kind of memory being saved. Use 'skill' for reusable procedures/playbooks agents should follow for recurring tasks (feedforward harness guide). Use 'attempt' for failed approaches (auto-validated). Use 'session_recap' via mem_session_end instead."
3299
3299
  ),
3300
3300
  slug: z4.string().min(1).describe("Short human-readable identifier \u2014 becomes part of the filename"),
3301
3301
  body: z4.string().describe("Markdown body of the memory"),
@@ -4895,10 +4895,10 @@ function classifyMemoryPriority(memory2, loaded, inputFiles, inputSymbols) {
4895
4895
  );
4896
4896
  const strongSemantic = (memory2.semantic_score ?? 0) >= 0.65;
4897
4897
  const usefulSemantic = (memory2.semantic_score ?? 0) >= 0.35;
4898
- if (fm?.requires_human_approval || directAnchor || directSymbol || memory2.type === "attempt" && (memory2.match_quality === "exact" || strongSemantic)) {
4898
+ if (fm?.requires_human_approval || directAnchor || directSymbol || memory2.type === "attempt" && (memory2.match_quality === "exact" || strongSemantic) || memory2.type === "skill" && (memory2.match_quality === "exact" || strongSemantic)) {
4899
4899
  return "must_read";
4900
4900
  }
4901
- if (memory2.reasons.includes("module") || memory2.reasons.includes("domain") || memory2.match_quality === "exact" || usefulSemantic) {
4901
+ if (memory2.type === "skill" || memory2.reasons.includes("module") || memory2.reasons.includes("domain") || memory2.match_quality === "exact" || usefulSemantic) {
4902
4902
  return "useful";
4903
4903
  }
4904
4904
  return "background";
@@ -4978,6 +4978,7 @@ function explainWhySurfaced(memory2, loaded, inputFiles, inferredModules) {
4978
4978
  }
4979
4979
  why.push(`Confidence: ${memory2.confidence}; read ${memory2.read_count} time${memory2.read_count === 1 ? "" : "s"}.`);
4980
4980
  if (memory2.type === "attempt") why.push("Failed-approach record; read before repeating the same path.");
4981
+ if (memory2.type === "skill") why.push("Skill (reusable procedure/playbook) \u2014 follow the steps described when doing this type of task.");
4981
4982
  if (memory2.status === "proposed" || memory2.status === "draft") {
4982
4983
  why.push("Unvalidated record; use cautiously or ask a human before treating it as policy.");
4983
4984
  }
@@ -6515,7 +6516,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
6515
6516
  };
6516
6517
  }
6517
6518
  var SERVER_NAME = "haive";
6518
- var SERVER_VERSION = "0.9.19";
6519
+ var SERVER_VERSION = "0.9.21";
6519
6520
  function jsonResult(data) {
6520
6521
  return {
6521
6522
  content: [
@@ -8496,6 +8497,7 @@ function registerMemoryAdd(memory2) {
8496
8497
  `Save a piece of knowledge as a persistent memory.
8497
8498
 
8498
8499
  Memory types:
8500
+ skill \u2014 reusable procedure/playbook agents follow for a recurring task (e.g. deploy, review)
8499
8501
  convention \u2014 how things are done here (naming, patterns, tooling)
8500
8502
  decision \u2014 a choice made and WHY (tradeoffs, constraints)
8501
8503
  gotcha \u2014 non-obvious behavior that surprises newcomers
@@ -8515,7 +8517,7 @@ function registerMemoryAdd(memory2) {
8515
8517
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
8516
8518
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
8517
8519
  `
8518
- ).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8520
+ ).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8519
8521
  const root = findProjectRoot13(opts.dir);
8520
8522
  const paths = resolveHaivePaths10(root);
8521
8523
  if (!existsSync33(paths.haiveDir)) {
@@ -8639,7 +8641,8 @@ TODO \u2014 write the memory body.
8639
8641
  if (inferredTags.length > 0) {
8640
8642
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
8641
8643
  }
8642
- if (anchorPaths.length === 0) {
8644
+ const typeNeedsAnchor = !["skill", "glossary", "session_recap"].includes(opts.type);
8645
+ if (anchorPaths.length === 0 && typeNeedsAnchor) {
8643
8646
  ui.warn(
8644
8647
  `This memory has no anchor paths \u2014 staleness cannot be detected automatically.
8645
8648
  Add file anchors: haive memory update ${frontmatter.id} --paths <file1,file2>`
@@ -8715,6 +8718,8 @@ function registerMemoryList(memory2) {
8715
8718
  console.log(
8716
8719
  `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
8717
8720
  );
8721
+ const title = mem.body.match(/^#\s+(.+)$/m)?.[1]?.trim();
8722
+ if (title && title !== fm.id) console.log(` ${title}`);
8718
8723
  console.log(` ${ui.dim(path17.relative(root, filePath))}`);
8719
8724
  }
8720
8725
  console.log(ui.dim(`
@@ -8880,7 +8885,7 @@ function registerMemoryApprove(memory2) {
8880
8885
  }
8881
8886
 
8882
8887
  // src/commands/memory-update.ts
8883
- import { writeFile as writeFile19 } from "fs/promises";
8888
+ import { readFile as readFile11, writeFile as writeFile19 } from "fs/promises";
8884
8889
  import { existsSync as existsSync37 } from "fs";
8885
8890
  import path20 from "path";
8886
8891
  import "commander";
@@ -8890,7 +8895,7 @@ import {
8890
8895
  serializeMemory as serializeMemory16
8891
8896
  } from "@hiveai/core";
8892
8897
  function registerMemoryUpdate(memory2) {
8893
- memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8898
+ memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--type <type>", "change the memory type (convention | decision | gotcha | architecture | glossary | skill | attempt)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--body-file <path>", "read new body from a Markdown file \u2014 for long content").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8894
8899
  const root = findProjectRoot17(opts.dir);
8895
8900
  const paths = resolveHaivePaths14(root);
8896
8901
  if (!existsSync37(paths.memoriesDir)) {
@@ -8923,19 +8928,34 @@ function registerMemoryUpdate(memory2) {
8923
8928
  const newFrontmatter = {
8924
8929
  ...frontmatter,
8925
8930
  anchor: newAnchor,
8931
+ ...opts.type !== void 0 ? { type: opts.type } : {},
8926
8932
  ...opts.tags !== void 0 ? { tags: parseCsv3(opts.tags) } : {},
8927
8933
  ...opts.domain !== void 0 ? { domain: opts.domain } : {},
8928
8934
  ...opts.author !== void 0 ? { author: opts.author } : {}
8929
8935
  };
8936
+ if (opts.type !== void 0) updated.push("type");
8930
8937
  if (opts.tags !== void 0) updated.push("tags");
8931
8938
  if (opts.domain !== void 0) updated.push("domain");
8932
8939
  if (opts.author !== void 0) updated.push("author");
8933
- let newBody = opts.body !== void 0 ? opts.body : body;
8940
+ let newBody;
8941
+ if (opts.bodyFile !== void 0) {
8942
+ if (!existsSync37(opts.bodyFile)) {
8943
+ ui.error(`--body-file not found: ${opts.bodyFile}`);
8944
+ process.exitCode = 1;
8945
+ return;
8946
+ }
8947
+ newBody = await readFile11(opts.bodyFile, "utf8");
8948
+ updated.push("body");
8949
+ } else if (opts.body !== void 0) {
8950
+ newBody = opts.body;
8951
+ updated.push("body");
8952
+ } else {
8953
+ newBody = body;
8954
+ }
8934
8955
  if (opts.title !== void 0) {
8935
8956
  newBody = replaceFirstHeading(newBody, opts.title);
8936
8957
  updated.push("title");
8937
8958
  }
8938
- if (opts.body !== void 0) updated.push("body");
8939
8959
  if (updated.length === 0) {
8940
8960
  ui.warn("Nothing to update \u2014 provide at least one option.");
8941
8961
  return;
@@ -9029,7 +9049,7 @@ function registerMemoryAutoPromote(memory2) {
9029
9049
  // src/commands/memory-edit.ts
9030
9050
  import { spawn as spawn3 } from "child_process";
9031
9051
  import { existsSync as existsSync39 } from "fs";
9032
- import { readFile as readFile11 } from "fs/promises";
9052
+ import { readFile as readFile12 } from "fs/promises";
9033
9053
  import path23 from "path";
9034
9054
  import "commander";
9035
9055
  import {
@@ -9060,7 +9080,7 @@ function registerMemoryEdit(memory2) {
9060
9080
  ui.warn(`Editor exited with status ${code}.`);
9061
9081
  }
9062
9082
  try {
9063
- const fresh = await readFile11(found.filePath, "utf8");
9083
+ const fresh = await readFile12(found.filePath, "utf8");
9064
9084
  parseMemory(fresh);
9065
9085
  ui.success("Memory still parses cleanly.");
9066
9086
  } catch (err) {
@@ -9286,7 +9306,7 @@ function registerMemoryTried(memory2) {
9286
9306
  --instead "use static import in the entry file" \\\\
9287
9307
  --paths packages/cli/src/index.ts
9288
9308
  `
9289
- ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
9309
+ ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
9290
9310
  const root = findProjectRoot22(opts.dir);
9291
9311
  const paths = resolveHaivePaths19(root);
9292
9312
  if (!existsSync43(paths.haiveDir)) {
@@ -9339,7 +9359,7 @@ import {
9339
9359
  resolveHaivePaths as resolveHaivePaths20
9340
9360
  } from "@hiveai/core";
9341
9361
  function registerMemoryPending(memory2) {
9342
- memory2.command("pending").description("List 'proposed' memories awaiting review (sorted by reads desc)").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9362
+ memory2.command("pending").description("List draft and proposed memories awaiting review (sorted by reads desc).\n\n draft = created but not yet activated \xB7 proposed = promoted, awaiting team validation").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9343
9363
  const root = findProjectRoot23(opts.dir);
9344
9364
  const paths = resolveHaivePaths20(root);
9345
9365
  if (!existsSync44(paths.memoriesDir)) {
@@ -9349,30 +9369,53 @@ function registerMemoryPending(memory2) {
9349
9369
  }
9350
9370
  const all = await loadMemoriesFromDir26(paths.memoriesDir);
9351
9371
  const usage = await loadUsageIndex17(paths);
9352
- const proposed = all.filter(({ memory: mem }) => {
9353
- if (mem.frontmatter.status !== "proposed") return false;
9372
+ const filterFn = ({ memory: mem }) => {
9373
+ if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
9354
9374
  if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
9355
9375
  return true;
9356
- });
9357
- if (proposed.length === 0) {
9358
- ui.info("No memories awaiting review.");
9376
+ };
9377
+ const pending = all.filter(filterFn);
9378
+ if (pending.length === 0) {
9379
+ ui.info("No draft or proposed memories awaiting review.");
9380
+ ui.info("Drafts are created by `haive memory add` without `--status validated`.");
9359
9381
  return;
9360
9382
  }
9361
- proposed.sort(
9383
+ pending.sort(
9362
9384
  (a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
9363
9385
  );
9364
9386
  const now = Date.now();
9365
- for (const { memory: mem, filePath } of proposed) {
9366
- const fm = mem.frontmatter;
9367
- const u = getUsage15(usage, fm.id);
9368
- const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
9369
- const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
9370
- console.log(
9371
- `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
9372
- );
9373
- console.log(` ${ui.dim(path27.relative(root, filePath))}`);
9387
+ const drafts = pending.filter((m) => m.memory.frontmatter.status === "draft");
9388
+ const proposed = pending.filter((m) => m.memory.frontmatter.status === "proposed");
9389
+ if (proposed.length > 0) {
9390
+ console.log(ui.bold(`Proposed (${proposed.length}) \u2014 awaiting team validation`));
9391
+ for (const { memory: mem, filePath } of proposed) {
9392
+ const fm = mem.frontmatter;
9393
+ const u = getUsage15(usage, fm.id);
9394
+ const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
9395
+ const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
9396
+ console.log(
9397
+ ` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
9398
+ );
9399
+ console.log(` ${ui.dim(path27.relative(root, filePath))}`);
9400
+ }
9401
+ if (proposed.length > 0) console.log(ui.dim(` \u2192 haive memory approve <id> or haive memory auto-promote`));
9402
+ console.log();
9403
+ }
9404
+ if (drafts.length > 0) {
9405
+ console.log(ui.bold(`Draft (${drafts.length}) \u2014 created but not yet activated`));
9406
+ for (const { memory: mem, filePath } of drafts) {
9407
+ const fm = mem.frontmatter;
9408
+ const u = getUsage15(usage, fm.id);
9409
+ const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
9410
+ const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
9411
+ console.log(
9412
+ ` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
9413
+ );
9414
+ console.log(` ${ui.dim(path27.relative(root, filePath))}`);
9415
+ }
9416
+ console.log(ui.dim(` \u2192 haive memory approve <id> (activate) | haive memory promote <id> (share with team)`));
9374
9417
  }
9375
- ui.info(`${proposed.length} pending`);
9418
+ ui.info(`${pending.length} total pending (${proposed.length} proposed \xB7 ${drafts.length} draft)`);
9376
9419
  });
9377
9420
  }
9378
9421
 
@@ -9555,7 +9598,7 @@ function registerMemoryRm(memory2) {
9555
9598
 
9556
9599
  // src/commands/memory-show.ts
9557
9600
  import { existsSync as existsSync48 } from "fs";
9558
- import { readFile as readFile12 } from "fs/promises";
9601
+ import { readFile as readFile13 } from "fs/promises";
9559
9602
  import path30 from "path";
9560
9603
  import "commander";
9561
9604
  import {
@@ -9582,7 +9625,7 @@ function registerMemoryShow(memory2) {
9582
9625
  return;
9583
9626
  }
9584
9627
  if (opts.raw) {
9585
- console.log(await readFile12(found.filePath, "utf8"));
9628
+ console.log(await readFile13(found.filePath, "utf8"));
9586
9629
  return;
9587
9630
  }
9588
9631
  const fm = found.memory.frontmatter;
@@ -9760,7 +9803,7 @@ function applyVerification2(mem, result) {
9760
9803
  }
9761
9804
 
9762
9805
  // src/commands/memory-import.ts
9763
- import { readFile as readFile13 } from "fs/promises";
9806
+ import { readFile as readFile14 } from "fs/promises";
9764
9807
  import { existsSync as existsSync51 } from "fs";
9765
9808
  import "commander";
9766
9809
  import {
@@ -9783,7 +9826,7 @@ function registerMemoryImport(memory2) {
9783
9826
  process.exitCode = 1;
9784
9827
  return;
9785
9828
  }
9786
- const content = await readFile13(opts.from, "utf8");
9829
+ const content = await readFile14(opts.from, "utf8");
9787
9830
  const scope = opts.scope ?? "team";
9788
9831
  ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
9789
9832
  ui.info(`Content length: ${content.length} chars`);
@@ -9812,7 +9855,7 @@ function registerMemoryImport(memory2) {
9812
9855
 
9813
9856
  // src/commands/memory-import-changelog.ts
9814
9857
  import { existsSync as existsSync53 } from "fs";
9815
- import { readFile as readFile14, mkdir as mkdir14, writeFile as writeFile25 } from "fs/promises";
9858
+ import { readFile as readFile15, mkdir as mkdir14, writeFile as writeFile25 } from "fs/promises";
9816
9859
  import path34 from "path";
9817
9860
  import "commander";
9818
9861
  import {
@@ -9891,7 +9934,7 @@ function registerMemoryImportChangelog(memory2) {
9891
9934
  process.exitCode = 1;
9892
9935
  return;
9893
9936
  }
9894
- const content = await readFile14(changelogPath, "utf8");
9937
+ const content = await readFile15(changelogPath, "utf8");
9895
9938
  let entries = parseChangelog(content);
9896
9939
  if (entries.length === 0) {
9897
9940
  ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
@@ -10080,7 +10123,7 @@ function registerMemoryDigest(program2) {
10080
10123
  }
10081
10124
 
10082
10125
  // src/commands/session-end.ts
10083
- import { writeFile as writeFile27, mkdir as mkdir15, readFile as readFile15, rm as rm2 } from "fs/promises";
10126
+ import { writeFile as writeFile27, mkdir as mkdir15, readFile as readFile16, rm as rm2 } from "fs/promises";
10084
10127
  import { existsSync as existsSync55 } from "fs";
10085
10128
  import path36 from "path";
10086
10129
  import "commander";
@@ -10095,7 +10138,7 @@ import {
10095
10138
  async function buildAutoRecap(paths) {
10096
10139
  const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10097
10140
  if (!existsSync55(obsFile)) return null;
10098
- const raw = await readFile15(obsFile, "utf8").catch(() => "");
10141
+ const raw = await readFile16(obsFile, "utf8").catch(() => "");
10099
10142
  if (!raw.trim()) return null;
10100
10143
  const lines = raw.split("\n").filter(Boolean);
10101
10144
  const obs = [];
@@ -10440,7 +10483,7 @@ function detectFormat(filePath) {
10440
10483
 
10441
10484
  // src/commands/hub.ts
10442
10485
  import { existsSync as existsSync57 } from "fs";
10443
- import { mkdir as mkdir16, readFile as readFile16, writeFile as writeFile28, copyFile } from "fs/promises";
10486
+ import { mkdir as mkdir16, readFile as readFile17, writeFile as writeFile28, copyFile } from "fs/promises";
10444
10487
  import path38 from "path";
10445
10488
  import { spawnSync as spawnSync5 } from "child_process";
10446
10489
  import "commander";
@@ -10635,7 +10678,7 @@ Next steps:
10635
10678
  for (const file of sourceFiles) {
10636
10679
  const srcPath = path38.join(sourceDir, file);
10637
10680
  const destPath = path38.join(destDir, file);
10638
- const fileContent = await readFile16(srcPath, "utf8");
10681
+ const fileContent = await readFile17(srcPath, "utf8");
10639
10682
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
10640
10683
  if (!alreadyTagged) {
10641
10684
  await copyFile(srcPath, destPath);
@@ -10687,7 +10730,7 @@ Next steps:
10687
10730
  if (outgoing.length > 0) {
10688
10731
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
10689
10732
  }
10690
- void readFile16;
10733
+ void readFile17;
10691
10734
  void writeFile28;
10692
10735
  void saveConfig3;
10693
10736
  });
@@ -10994,7 +11037,7 @@ function summarize(name, t0, payload, notes) {
10994
11037
 
10995
11038
  // src/commands/benchmark.ts
10996
11039
  import { existsSync as existsSync59 } from "fs";
10997
- import { readdir as readdir5, readFile as readFile17, writeFile as writeFile30 } from "fs/promises";
11040
+ import { readdir as readdir5, readFile as readFile18, writeFile as writeFile30 } from "fs/promises";
10998
11041
  import path40 from "path";
10999
11042
  import "commander";
11000
11043
  import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot38 } from "@hiveai/core";
@@ -11049,7 +11092,7 @@ async function collectRows(root) {
11049
11092
  const fixtureDir = path40.join(root, entry.name);
11050
11093
  const reportFile = path40.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11051
11094
  if (!existsSync59(reportFile)) continue;
11052
- const report = await readFile17(reportFile, "utf8");
11095
+ const report = await readFile18(reportFile, "utf8");
11053
11096
  rows.push(parseAgentReport(entry.name, report));
11054
11097
  }
11055
11098
  return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
@@ -11460,7 +11503,7 @@ function parseDays(input) {
11460
11503
 
11461
11504
  // src/commands/doctor.ts
11462
11505
  import { existsSync as existsSync63 } from "fs";
11463
- import { readFile as readFile18, stat } from "fs/promises";
11506
+ import { readFile as readFile19, stat } from "fs/promises";
11464
11507
  import path44 from "path";
11465
11508
  import { execFileSync, execSync as execSync3 } from "child_process";
11466
11509
  import "commander";
@@ -11513,8 +11556,8 @@ function registerDoctor(program2) {
11513
11556
  fix: "haive init"
11514
11557
  });
11515
11558
  } else {
11516
- const { readFile: readFile20 } = await import("fs/promises");
11517
- const content = await readFile20(paths.projectContext, "utf8");
11559
+ const { readFile: readFile21 } = await import("fs/promises");
11560
+ const content = await readFile21(paths.projectContext, "utf8");
11518
11561
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
11519
11562
  if (isTemplate) {
11520
11563
  findings.push({
@@ -11563,7 +11606,7 @@ function registerDoctor(program2) {
11563
11606
  });
11564
11607
  }
11565
11608
  const anchorless = memories.filter(
11566
- (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"
11609
+ (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"
11567
11610
  );
11568
11611
  if (anchorless.length / Math.max(memories.length, 1) > 0.3) {
11569
11612
  findings.push({
@@ -11621,6 +11664,7 @@ function registerDoctor(program2) {
11621
11664
  });
11622
11665
  }
11623
11666
  }
11667
+ findings.push(...await collectHarnessCoverageFindings(codeMap, memories));
11624
11668
  findings.push(...await collectSemanticIndexFindings(paths, config, memories.length, codeMap));
11625
11669
  const events = await readUsageEvents4(paths);
11626
11670
  if (events.length === 0) {
@@ -11662,8 +11706,8 @@ function registerDoctor(program2) {
11662
11706
  let hasClaudeEnforcement = false;
11663
11707
  if (existsSync63(claudeSettings)) {
11664
11708
  try {
11665
- const { readFile: readFile20 } = await import("fs/promises");
11666
- const raw = await readFile20(claudeSettings, "utf8");
11709
+ const { readFile: readFile21 } = await import("fs/promises");
11710
+ const raw = await readFile21(claudeSettings, "utf8");
11667
11711
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
11668
11712
  } catch {
11669
11713
  hasClaudeEnforcement = false;
@@ -11686,14 +11730,14 @@ function registerDoctor(program2) {
11686
11730
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
11687
11731
  });
11688
11732
  }
11689
- findings.push(...await collectInstallFindings(root, "0.9.19"));
11733
+ findings.push(...await collectInstallFindings(root, "0.9.21"));
11690
11734
  try {
11691
11735
  const legacyRaw = execSync3("haive-mcp --version", {
11692
11736
  encoding: "utf8",
11693
11737
  timeout: 3e3,
11694
11738
  stdio: ["ignore", "pipe", "ignore"]
11695
11739
  }).trim();
11696
- const cliVersion = "0.9.19";
11740
+ const cliVersion = "0.9.21";
11697
11741
  if (legacyRaw && legacyRaw !== cliVersion) {
11698
11742
  findings.push({
11699
11743
  severity: "warn",
@@ -11741,7 +11785,7 @@ function emit(findings, opts, repairs = []) {
11741
11785
  console.log(ui.bold(`hAIve doctor \u2014 ${classified.length} finding${classified.length === 1 ? "" : "s"}`));
11742
11786
  console.log(
11743
11787
  ui.dim(
11744
- ` protection=${scores.protection_score} context=${scores.context_quality_score} corpus=${scores.corpus_quality_score}`
11788
+ ` protection=${scores.protection_score} context=${scores.context_quality_score} corpus=${scores.corpus_quality_score} harness-coverage=${scores.harness_coverage_score}%`
11745
11789
  )
11746
11790
  );
11747
11791
  console.log();
@@ -11750,6 +11794,7 @@ function emit(findings, opts, repairs = []) {
11750
11794
  "Agent coverage",
11751
11795
  "Context quality",
11752
11796
  "Corpus health",
11797
+ "Harness coverage",
11753
11798
  "Index health",
11754
11799
  "Next actions"
11755
11800
  ];
@@ -11787,6 +11832,7 @@ function emit(findings, opts, repairs = []) {
11787
11832
  function sectionForFinding(finding) {
11788
11833
  if (finding.code.includes("haive") || finding.code.includes("integration") || finding.code.includes("claude") || finding.code.includes("autopilot")) return "Agent coverage";
11789
11834
  if (finding.code.includes("context") || finding.code.includes("briefing") || finding.code.includes("search")) return "Context quality";
11835
+ if (finding.code.includes("harness-coverage")) return "Harness coverage";
11790
11836
  if (finding.code.includes("code-map") || finding.code.includes("index")) return "Index health";
11791
11837
  if (finding.code.includes("memory") || finding.code.includes("anchor") || finding.code.includes("pending") || finding.code.includes("decay")) return "Corpus health";
11792
11838
  if (finding.severity === "error") return "Protection";
@@ -11802,10 +11848,13 @@ function computeDoctorScores(findings) {
11802
11848
  }, 0);
11803
11849
  return Math.max(0, 100 - penalty);
11804
11850
  };
11851
+ const coverageFinding = findings.find((f) => f.code === "harness-coverage");
11852
+ const harnessCoverageScore = coverageFinding ? parseInt((coverageFinding.message.match(/\((\d+)%\)/) ?? [])[1] ?? "0", 10) : 0;
11805
11853
  return {
11806
11854
  protection_score: scoreFor(["Protection", "Agent coverage"]),
11807
11855
  context_quality_score: scoreFor(["Context quality", "Index health"]),
11808
- corpus_quality_score: scoreFor(["Corpus health"])
11856
+ corpus_quality_score: scoreFor(["Corpus health"]),
11857
+ harness_coverage_score: harnessCoverageScore
11809
11858
  };
11810
11859
  }
11811
11860
  function groupBySection(findings) {
@@ -11814,6 +11863,7 @@ function groupBySection(findings) {
11814
11863
  "Agent coverage": [],
11815
11864
  "Context quality": [],
11816
11865
  "Corpus health": [],
11866
+ "Harness coverage": [],
11817
11867
  "Index health": [],
11818
11868
  "Next actions": []
11819
11869
  };
@@ -11823,6 +11873,37 @@ function groupBySection(findings) {
11823
11873
  function nextActions(findings) {
11824
11874
  return [...new Set(findings.flatMap((finding) => finding.fix ? finding.fix.split("\n") : []))].filter(Boolean);
11825
11875
  }
11876
+ async function collectHarnessCoverageFindings(codeMap, memories) {
11877
+ if (!codeMap) return [];
11878
+ const codeFiles = Object.keys(codeMap.files);
11879
+ const total = codeFiles.length;
11880
+ if (total === 0) return [];
11881
+ const validatedWithAnchors = memories.filter(
11882
+ (m) => (m.memory.frontmatter.status === "validated" || m.memory.frontmatter.status === "proposed") && m.memory.frontmatter.anchor.paths.length > 0
11883
+ );
11884
+ const coveredFiles = /* @__PURE__ */ new Set();
11885
+ for (const m of validatedWithAnchors) {
11886
+ for (const anchorPath of m.memory.frontmatter.anchor.paths) {
11887
+ const normalized = anchorPath.replace(/^\/+/, "");
11888
+ for (const codeFile of codeFiles) {
11889
+ if (codeFile === normalized || codeFile.startsWith(normalized + "/") || normalized.startsWith(codeFile + "/")) {
11890
+ coveredFiles.add(codeFile);
11891
+ }
11892
+ }
11893
+ }
11894
+ }
11895
+ const covered = coveredFiles.size;
11896
+ const pct = Math.round(covered / total * 100);
11897
+ const findings = [];
11898
+ findings.push({
11899
+ severity: pct < 10 && total > 10 ? "info" : "info",
11900
+ code: "harness-coverage",
11901
+ 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 < 30 ? "Partial coverage \u2014 consider anchoring critical modules and patterns." : "Good harness coverage."),
11902
+ fix: pct < 10 && total > 10 ? "haive memory add --type gotcha|convention|architecture --paths <key-file> --scope team" : void 0,
11903
+ section: "Harness coverage"
11904
+ });
11905
+ return findings;
11906
+ }
11826
11907
  async function collectSemanticIndexFindings(paths, config, memoryCount, codeMap) {
11827
11908
  const findings = [];
11828
11909
  const autoWantsCodeSearch = Boolean(config.autopilot || config.autoRepair?.codeSearch);
@@ -11919,7 +12000,7 @@ which -a haive`
11919
12000
  for (const rel of integrationFiles) {
11920
12001
  const file = path44.join(root, rel);
11921
12002
  if (!existsSync63(file)) continue;
11922
- const text = await readFile18(file, "utf8").catch(() => "");
12003
+ const text = await readFile19(file, "utf8").catch(() => "");
11923
12004
  for (const bin of extractAbsoluteHaiveBins(text)) {
11924
12005
  const version = versionForBinary(bin);
11925
12006
  if (!version) {
@@ -12227,12 +12308,13 @@ import {
12227
12308
  resolveHaivePaths as resolveHaivePaths40
12228
12309
  } from "@hiveai/core";
12229
12310
  var TYPE_RANK = {
12230
- decision: 0,
12231
- architecture: 1,
12232
- convention: 2,
12233
- glossary: 3,
12234
- gotcha: 4,
12235
- attempt: 5
12311
+ skill: 0,
12312
+ decision: 1,
12313
+ architecture: 2,
12314
+ convention: 3,
12315
+ glossary: 4,
12316
+ gotcha: 5,
12317
+ attempt: 6
12236
12318
  };
12237
12319
  function registerWelcome(program2) {
12238
12320
  program2.command("welcome").description(
@@ -12452,7 +12534,7 @@ function registerMemoryConflictCandidates(memory2) {
12452
12534
  // src/commands/enforce.ts
12453
12535
  import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
12454
12536
  import { existsSync as existsSync69 } from "fs";
12455
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile19, rm as rm3, writeFile as writeFile34 } from "fs/promises";
12537
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile20, rm as rm3, writeFile as writeFile34 } from "fs/promises";
12456
12538
  import path49 from "path";
12457
12539
  import "commander";
12458
12540
  import {
@@ -12758,7 +12840,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
12758
12840
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
12759
12841
  });
12760
12842
  }
12761
- findings.push(...await inspectIntegrationVersions(root, "0.9.19"));
12843
+ findings.push(...await inspectIntegrationVersions(root, "0.9.21"));
12762
12844
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
12763
12845
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
12764
12846
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -12970,7 +13052,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
12970
13052
  for (const rel of files) {
12971
13053
  const file = path49.join(root, rel);
12972
13054
  if (!existsSync69(file)) continue;
12973
- const text = await readFile19(file, "utf8").catch(() => "");
13055
+ const text = await readFile20(file, "utf8").catch(() => "");
12974
13056
  for (const bin of extractAbsoluteHaiveBins2(text)) {
12975
13057
  const version = versionForBinary2(bin);
12976
13058
  if (!version) {
@@ -13083,7 +13165,7 @@ haive enforce check --stage pre-push --dir . || exit $?
13083
13165
  for (const hook of hooks) {
13084
13166
  const file = path49.join(hooksDir, hook.name);
13085
13167
  if (existsSync69(file)) {
13086
- const current = await readFile19(file, "utf8").catch(() => "");
13168
+ const current = await readFile20(file, "utf8").catch(() => "");
13087
13169
  if (current.includes(ENFORCE_HOOK_MARKER)) {
13088
13170
  await writeFile34(file, hook.body, "utf8");
13089
13171
  } else {
@@ -13246,7 +13328,7 @@ function registerRun(program2) {
13246
13328
 
13247
13329
  // src/index.ts
13248
13330
  var program = new Command51();
13249
- program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.19").option("--advanced", "show maintenance and experimental commands in help");
13331
+ program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.21").option("--advanced", "show maintenance and experimental commands in help");
13250
13332
  registerInit(program);
13251
13333
  registerWelcome(program);
13252
13334
  registerResolveProject(program);
@@ -13289,7 +13371,9 @@ registerMemoryTimeline(memory);
13289
13371
  registerMemoryConflictCandidates(memory);
13290
13372
  registerMemoryArchive(memory);
13291
13373
  registerMemoryLint(memory);
13292
- var session = program.command("session").description("Manage session lifecycle");
13374
+ var session = program.command("session").description(
13375
+ "Manage session lifecycle.\n\n Session start is automatic \u2014 hAIve loads context via `get_briefing` at the start\n of each agent session (Claude Code SessionStart hook or MCP first call).\n Use `haive session end` to save a rich end-of-session recap for the next session."
13376
+ );
13293
13377
  registerSessionEnd(session);
13294
13378
  registerSnapshot(program);
13295
13379
  registerHub(program);
@@ -13302,6 +13386,7 @@ registerPrecommit(program);
13302
13386
  var CORE_ROOT_COMMANDS = /* @__PURE__ */ new Set([
13303
13387
  "init",
13304
13388
  "doctor",
13389
+ "tui",
13305
13390
  "agent",
13306
13391
  "enforce",
13307
13392
  "run",
@@ -13343,7 +13428,7 @@ function applySurfaceVisibility(root) {
13343
13428
  "after",
13344
13429
  [
13345
13430
  "",
13346
- "Default help shows the core hAIve harness: init, doctor, agent setup, briefing, enforcement,",
13431
+ "Default help shows the core harness workflow: init, doctor, agent setup, briefing, enforcement,",
13347
13432
  "sync, session recaps, and high-signal memory commands.",
13348
13433
  "Run `haive --advanced --help` or set HAIVE_SHOW_ADVANCED=1 to show maintenance and experimental commands."
13349
13434
  ].join("\n")