@hiveai/cli 0.9.26 → 0.9.28

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
@@ -11,6 +11,7 @@ import "commander";
11
11
  import {
12
12
  extractActionsBriefBody,
13
13
  findProjectRoot as findProjectRoot2,
14
+ isStackPackSeed,
14
15
  literalMatchesAllTokens,
15
16
  literalMatchesAnyToken,
16
17
  loadCodeMap as loadCodeMap3,
@@ -1098,6 +1099,7 @@ function classifyCliPriority(item, filePaths, tokens, exactTaskHit, partialTaskH
1098
1099
  const fm = item.memory.frontmatter;
1099
1100
  const anchored = filePaths.length > 0 && memoryMatchesAnchorPaths(item.memory, filePaths);
1100
1101
  if (anchored || fm.type === "attempt" && exactTaskHit) return "must_read";
1102
+ if (isStackPackSeed(fm)) return "background";
1101
1103
  if (exactTaskHit || partialTaskHit || item.score >= 4 || tokens && fm.tags.some((tag) => tokens.includes(tag))) {
1102
1104
  return "useful";
1103
1105
  }
@@ -2018,7 +2020,8 @@ import path9 from "path";
2018
2020
  import {
2019
2021
  buildFrontmatter,
2020
2022
  memoryFilePath,
2021
- serializeMemory as serializeMemory2
2023
+ serializeMemory as serializeMemory2,
2024
+ STACK_PACK_TAG
2022
2025
  } from "@hiveai/core";
2023
2026
  var PACKS = {
2024
2027
  nestjs: [
@@ -2593,6 +2596,7 @@ new ApolloServer({
2593
2596
  }
2594
2597
  ]
2595
2598
  };
2599
+ var SEED_FOOTER = (stack) => `> _Seeded by \`haive init\` from the **${stack}** stack pack \u2014 generic guidance, not repo-specific. Anchor it to a real file or replace it with a repo-specific note to raise it above background priority._`;
2596
2600
  var SUPPORTED_STACKS = Object.keys(PACKS);
2597
2601
  function isValidStack(name) {
2598
2602
  return name in PACKS;
@@ -2634,11 +2638,15 @@ async function seedStackPack(haivePaths, stack) {
2634
2638
  slug: `${stack}-${mem.slug}`,
2635
2639
  scope: "team",
2636
2640
  status: "validated",
2637
- tags: mem.tags
2641
+ // STACK_PACK_TAG marks this as generic seed knowledge so briefing ranking
2642
+ // keeps it at `background` priority until it earns a repo-specific anchor.
2643
+ tags: [...mem.tags, STACK_PACK_TAG]
2638
2644
  });
2639
2645
  const filePath = memoryFilePath(haivePaths, "team", fm.id);
2640
2646
  if (existsSync8(filePath)) continue;
2641
- const content = serializeMemory2({ frontmatter: fm, body: mem.body });
2647
+ const content = serializeMemory2({ frontmatter: fm, body: `${mem.body}
2648
+
2649
+ ${SEED_FOOTER(stack)}` });
2642
2650
  await mkdir4(path9.dirname(filePath), { recursive: true });
2643
2651
  await writeFile5(filePath, content, "utf8");
2644
2652
  count++;
@@ -2669,30 +2677,25 @@ TODO \u2014 domain terms and what they mean here.
2669
2677
  TODO \u2014 known traps, surprising behavior, things newcomers stub their toes on.
2670
2678
  `;
2671
2679
  var BRIDGE_BODY = `<!-- hAIve bridge file \u2014 do not edit by hand. -->
2672
- <!-- This file points your AI tool at the shared hAIve project context. -->
2673
-
2674
- See \`.ai/project-context.md\` for the full project context.
2675
- Memories live under \`.ai/memories/\` (personal/team/module).
2676
2680
 
2677
- ## hAIve \u2014 mandatory rules for AI agents
2681
+ This repo uses **hAIve** for shared context. The map:
2678
2682
 
2679
- Installing or initializing hAIve in this repository means **the team expects AI agents to drive work through hAIve**, not bypass it.
2683
+ - \`.ai/project-context.md\` \u2014 project overview, architecture, conventions.
2684
+ - \`.ai/memories/\` \u2014 decisions, gotchas, conventions, failed attempts (personal/team/module).
2685
+ - The breadcrumbs injected below (if any) are the top current memories.
2680
2686
 
2681
- ### MCP first (default \u2014 never optional unless the developer says so)
2682
- The **haive** MCP server must be enabled in your client. Until it is unavailable (misconfiguration report to the developer), you **must** use it for substantive work:
2687
+ ## Working through hAIve
2683
2688
 
2684
- - **Before** you edit project source files, configs (\`package.json\`, tooling), or workflows for the current goal, invoke \`get_briefing\` with a short **task** string (and **files** / **symbols** when you know them). If project context was already refreshed in this session and you only need ranked memories, you may call \`mem_relevant_to\` instead\u2014but **still** consult hAIve before trusting ungrounded guesses.
2685
- - **Before** declaring a coding task finished, invoke the MCP prompt \`post_task\` when the session produced learnings or substantive changes worth capturing.
2689
+ 1. **Before editing** for a goal, call \`get_briefing\` (task + files/symbols) to load ranked context \u2014 or \`mem_relevant_to\` if project context is already loaded this session.
2690
+ 2. **When an approach fails**, call \`mem_tried\` right away so the next agent skips the dead end.
2691
+ 3. **Before closing** a substantive session, run the \`post_task\` prompt to capture what was learned.
2686
2692
 
2687
- Do **not** treat these as nice-to-have: they are **default-required** for coding agents here.
2693
+ If the haive MCP server is not available, tell the developer rather than silently skipping it.
2688
2694
 
2689
- ### Session rules
2690
- - **Immediately** when an approach fails (wrong API, wrong pattern, wrong assumption), call \`mem_tried\` \u2014 do not wait until the end of the session.
2695
+ ## Safety
2691
2696
 
2692
- ### Safety rules \u2014 NEVER violate these
2693
- - If \`get_briefing\` returns an \`action_required\` list, **stop and show each item to the developer** before doing anything. Use the exact \`developer_message\` provided. Wait for explicit confirmation.
2694
- - **Never modify code autonomously** because of a breaking change detected in another project (dependency version bump, API contract change, removed field). Always ask first.
2695
- - When in doubt about a cross-repo change: ask, don't act.
2697
+ - If \`get_briefing\` returns \`action_required\`, surface each item to the developer (use its \`developer_message\`) and wait for confirmation before changing code.
2698
+ - Never act autonomously on a cross-repo breaking change (dep bump, contract/API diff) \u2014 ask first.
2696
2699
  `;
2697
2700
  var CURSOR_HAIVE_RULE_MDC = `---
2698
2701
  description: Require hAIve MCP (get_briefing / mem_relevant_to) before substantive repo edits
@@ -2918,7 +2921,10 @@ function registerInit(program2) {
2918
2921
  }
2919
2922
  if (totalSeeded > 0) {
2920
2923
  ui.success(
2921
- `${totalSeeded} validated team memories pre-seeded \u2014 haive is useful from J+0`
2924
+ `${totalSeeded} starter memories seeded (generic stack guidance, kept at background priority)`
2925
+ );
2926
+ ui.info(
2927
+ "Anchor them to real files or replace them with repo-specific notes to make them high-signal."
2922
2928
  );
2923
2929
  }
2924
2930
  }
@@ -3663,6 +3669,7 @@ import {
3663
3669
  isGlobPath,
3664
3670
  isAutoPromoteEligible,
3665
3671
  isDecaying,
3672
+ isStackPackSeed as isStackPackSeed2,
3666
3673
  literalMatchesAllTokens as literalMatchesAllTokens22,
3667
3674
  literalMatchesAnyToken as literalMatchesAnyToken22,
3668
3675
  loadCodeMap as loadCodeMap4,
@@ -3676,7 +3683,8 @@ import {
3676
3683
  serializeMemory as serializeMemory9,
3677
3684
  tokenizeQuery as tokenizeQuery22,
3678
3685
  trackReads as trackReads3,
3679
- truncateToTokens
3686
+ truncateToTokens,
3687
+ writeBriefingMarker as writeBriefingMarker2
3680
3688
  } from "@hiveai/core";
3681
3689
  import { z as z17 } from "zod";
3682
3690
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap22, queryCodeMap as queryCodeMap22 } from "@hiveai/core";
@@ -4815,7 +4823,8 @@ var SessionTracker = class {
4815
4823
  const ranPostTask = this.events.some(
4816
4824
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
4817
4825
  );
4818
- if (!ranPostTask && existsSync16(this.ctx.paths.haiveDir)) {
4826
+ const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
4827
+ if (!ranPostTask && isSubstantialSession && existsSync16(this.ctx.paths.haiveDir)) {
4819
4828
  try {
4820
4829
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
4821
4830
  const payload = {
@@ -5426,6 +5435,16 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5426
5435
  );
5427
5436
  }
5428
5437
  }
5438
+ if (existsSync18(ctx.paths.haiveDir)) {
5439
+ await writeBriefingMarker2(ctx.paths, {
5440
+ sessionId: process.env.HAIVE_SESSION_ID,
5441
+ ...input.task ? { task: input.task } : {},
5442
+ source: "mcp-get-briefing",
5443
+ files: input.files,
5444
+ memoryIds: outputMemories.map((m) => m.id)
5445
+ }).catch(() => {
5446
+ });
5447
+ }
5429
5448
  return {
5430
5449
  ...input.task ? { task: input.task } : {},
5431
5450
  search_mode: searchMode,
@@ -5480,6 +5499,9 @@ function classifyMemoryPriority(memory2, loaded, inputFiles, inputSymbols) {
5480
5499
  if (fm?.requires_human_approval || directAnchor || directSymbol || memory2.type === "attempt" && (memory2.match_quality === "exact" || strongSemantic) || memory2.type === "skill" && (memory2.match_quality === "exact" || strongSemantic)) {
5481
5500
  return "must_read";
5482
5501
  }
5502
+ if (isStackPackSeed2(fm)) {
5503
+ return "background";
5504
+ }
5483
5505
  if (memory2.type === "skill" || memory2.reasons.includes("module") || memory2.reasons.includes("domain") || memory2.match_quality === "exact" || usefulSemantic) {
5484
5506
  return "useful";
5485
5507
  }
@@ -5962,6 +5984,8 @@ async function antiPatternsCheck(input, ctx) {
5962
5984
  confidence: deriveConfidence6(fm, u),
5963
5985
  body_preview: body.split("\n").slice(0, 5).join("\n").slice(0, 400),
5964
5986
  reasons: [reason],
5987
+ tags: fm.tags ?? [],
5988
+ anchor_paths: fm.anchor?.paths ?? [],
5965
5989
  ...score !== void 0 ? { semantic_score: score } : {}
5966
5990
  });
5967
5991
  };
@@ -6552,8 +6576,45 @@ function fileTypeDowngradeReason(warning, paths) {
6552
6576
  if (configOnly && !warning.reasons.includes("anchor") && !hasStrongSemantic(warning)) {
6553
6577
  return "package/config-only change; warning has no anchor on these files and no strong semantic match \u2014 downgraded to info.";
6554
6578
  }
6579
+ const touchesBuildFile = paths.some(isPackageOrConfigPath);
6580
+ if (!touchesBuildFile && isBuildScopedWarning(warning) && !warning.reasons.includes("anchor") && !hasStrongSemantic(warning)) {
6581
+ return "build/packaging gotcha, but no package/build file changed \u2014 downgraded to info.";
6582
+ }
6555
6583
  return null;
6556
6584
  }
6585
+ function isBuildScopedWarning(warning) {
6586
+ const tags = warning.tags ?? [];
6587
+ if (tags.some((t) => BUILD_SCOPED_TAGS.has(t.toLowerCase()))) return true;
6588
+ const anchors = warning.anchor_paths ?? [];
6589
+ return anchors.length > 0 && anchors.every(isPackageOrConfigPath);
6590
+ }
6591
+ var BUILD_SCOPED_TAGS = /* @__PURE__ */ new Set([
6592
+ "npm",
6593
+ "pnpm",
6594
+ "yarn",
6595
+ "publish",
6596
+ "install",
6597
+ "packaging",
6598
+ "package",
6599
+ "build",
6600
+ "tsup",
6601
+ "bundler",
6602
+ "monorepo",
6603
+ "workspace",
6604
+ "versioning",
6605
+ "version",
6606
+ "dev-workflow",
6607
+ "hotswap",
6608
+ "ci",
6609
+ "workflow",
6610
+ "release",
6611
+ "changelog",
6612
+ "dependencies",
6613
+ "deps",
6614
+ "dependency",
6615
+ "tooling",
6616
+ "config"
6617
+ ]);
6557
6618
  function hasStrongSemantic(warning) {
6558
6619
  return warning.reasons.includes("semantic") && (warning.semantic_score ?? 0) >= 0.65;
6559
6620
  }
@@ -6564,9 +6625,36 @@ function isDocLikePath(file) {
6564
6625
  function isPackageOrConfigPath(file) {
6565
6626
  const lower = file.toLowerCase();
6566
6627
  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
6628
+ 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") || isJsonConfigFile(base) || 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
6629
  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");
6569
6630
  }
6631
+ function isJsonConfigFile(base) {
6632
+ const knownConfigs = /* @__PURE__ */ new Set([
6633
+ "tsconfig.json",
6634
+ "jsconfig.json",
6635
+ "deno.json",
6636
+ "deno.jsonc",
6637
+ "nx.json",
6638
+ "turbo.json",
6639
+ "lerna.json",
6640
+ "rush.json",
6641
+ "jest.config.json",
6642
+ "vitest.config.json",
6643
+ "babel.config.json",
6644
+ ".babelrc.json",
6645
+ ".swcrc",
6646
+ ".mocharc.json",
6647
+ "renovate.json",
6648
+ "dependabot.json",
6649
+ ".prettierrc.json",
6650
+ ".eslintrc.json",
6651
+ ".stylelintrc.json"
6652
+ ]);
6653
+ if (knownConfigs.has(base)) return true;
6654
+ if (/^tsconfig\..+\.json$/.test(base)) return true;
6655
+ if (/^\.[a-z]+rc\.json$/.test(base)) return true;
6656
+ return false;
6657
+ }
6570
6658
  function repairCommandForWarning(warning, paths) {
6571
6659
  const firstPath = paths[0];
6572
6660
  return firstPath ? `haive briefing --files "${firstPath}" --task "review ${warning.id}"` : `haive memory show ${warning.id}`;
@@ -7102,7 +7190,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7102
7190
  };
7103
7191
  }
7104
7192
  var SERVER_NAME = "haive";
7105
- var SERVER_VERSION = "0.9.26";
7193
+ var SERVER_VERSION = "0.9.28";
7106
7194
  function jsonResult(data) {
7107
7195
  return {
7108
7196
  content: [
@@ -8083,6 +8171,7 @@ import {
8083
8171
  getUsage as getUsage10,
8084
8172
  isAutoPromoteEligible as isAutoPromoteEligible2,
8085
8173
  isDecaying as isDecaying2,
8174
+ isStackPackSeed as isStackPackSeed3,
8086
8175
  loadCodeMap as loadCodeMap5,
8087
8176
  loadConfig as loadConfig4,
8088
8177
  loadMemoriesFromDir as loadMemoriesFromDir23,
@@ -8099,14 +8188,14 @@ var BRIDGE_START = "<!-- haive:memories-start -->";
8099
8188
  var BRIDGE_END = "<!-- haive:memories-end -->";
8100
8189
  function registerSync(program2) {
8101
8190
  program2.command("sync").description(
8102
- "Refresh memory state after a git pull or merge.\n What it does:\n 1. Verifies anchor paths \u2014 marks stale if files/symbols moved or deleted\n 2. Re-validates previously stale memories that are now fresh\n 3. Auto-promotes proposed memories (by usage count or time delay in autopilot)\n 4. Auto-refreshes code-map if source files changed\n 5. Reports decay warnings for memories unused >90 days\n\n Install git hooks to run sync automatically: haive install-hooks\n\n Examples:\n haive sync\n haive sync --since main # also report memories changed since main\n haive sync --embed # also rebuild embeddings index\n"
8191
+ "Refresh memory state after a git pull or merge.\n What it does:\n 1. Verifies anchor paths \u2014 marks stale if files/symbols moved or deleted\n 2. Re-validates previously stale memories that are now fresh\n 3. Auto-promotes proposed memories (by usage count or time delay in autopilot)\n 4. Auto-refreshes code-map if source files changed\n 5. Reports decay warnings for memories unused >90 days\n\n Install git hooks to run sync automatically: haive install-hooks\n\n Examples:\n haive sync\n haive sync --dry-run # preview what would change without writing\n haive sync --since main # also report memories changed since main\n haive sync --embed # also rebuild embeddings index\n"
8103
8192
  ).option("-d, --dir <dir>", "project root").option("--quiet", "minimal output (suitable for git hooks)").option(
8104
8193
  "--since <ref>",
8105
8194
  "git ref/commit to compare against; report memories added/modified/removed since"
8106
8195
  ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
8107
8196
  "--inject-bridge",
8108
8197
  "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
8109
- ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").action(async (opts) => {
8198
+ ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").option("--dry-run", "report what would change without writing any files").action(async (opts) => {
8110
8199
  const root = findProjectRoot12(opts.dir);
8111
8200
  const paths = resolveHaivePaths9(root);
8112
8201
  if (!existsSync29(paths.memoriesDir)) {
@@ -8121,6 +8210,8 @@ function registerSync(program2) {
8121
8210
  const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
8122
8211
  const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads;
8123
8212
  const autoRepair = config.autoRepair ?? {};
8213
+ const dryRun = opts.dryRun === true;
8214
+ if (dryRun) log(ui.yellow("(dry run \u2014 no files will be written)"));
8124
8215
  let staleMarked = 0;
8125
8216
  let revalidated = 0;
8126
8217
  let promoted = 0;
@@ -8130,19 +8221,21 @@ function registerSync(program2) {
8130
8221
  for (const { memory: memory2, filePath } of memories) {
8131
8222
  if (memory2.frontmatter.type === "session_recap") {
8132
8223
  if (memory2.frontmatter.status === "stale") {
8133
- await writeFile13(
8134
- filePath,
8135
- serializeMemory11({
8136
- frontmatter: {
8137
- ...memory2.frontmatter,
8138
- status: "validated",
8139
- stale_reason: null,
8140
- verified_at: (/* @__PURE__ */ new Date()).toISOString()
8141
- },
8142
- body: memory2.body
8143
- }),
8144
- "utf8"
8145
- );
8224
+ if (!dryRun) {
8225
+ await writeFile13(
8226
+ filePath,
8227
+ serializeMemory11({
8228
+ frontmatter: {
8229
+ ...memory2.frontmatter,
8230
+ status: "validated",
8231
+ stale_reason: null,
8232
+ verified_at: (/* @__PURE__ */ new Date()).toISOString()
8233
+ },
8234
+ body: memory2.body
8235
+ }),
8236
+ "utf8"
8237
+ );
8238
+ }
8146
8239
  revalidated++;
8147
8240
  }
8148
8241
  continue;
@@ -8153,35 +8246,39 @@ function registerSync(program2) {
8153
8246
  const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
8154
8247
  if (result.stale) {
8155
8248
  if (memory2.frontmatter.status !== "stale") {
8249
+ if (!dryRun) {
8250
+ await writeFile13(
8251
+ filePath,
8252
+ serializeMemory11({
8253
+ frontmatter: {
8254
+ ...memory2.frontmatter,
8255
+ status: "stale",
8256
+ verified_at: verifiedAt,
8257
+ stale_reason: result.reason
8258
+ },
8259
+ body: memory2.body
8260
+ }),
8261
+ "utf8"
8262
+ );
8263
+ }
8264
+ staleMarked++;
8265
+ }
8266
+ } else if (memory2.frontmatter.status === "stale") {
8267
+ if (!dryRun) {
8156
8268
  await writeFile13(
8157
8269
  filePath,
8158
8270
  serializeMemory11({
8159
8271
  frontmatter: {
8160
8272
  ...memory2.frontmatter,
8161
- status: "stale",
8273
+ status: "validated",
8162
8274
  verified_at: verifiedAt,
8163
- stale_reason: result.reason
8275
+ stale_reason: null
8164
8276
  },
8165
8277
  body: memory2.body
8166
8278
  }),
8167
8279
  "utf8"
8168
8280
  );
8169
- staleMarked++;
8170
8281
  }
8171
- } else if (memory2.frontmatter.status === "stale") {
8172
- await writeFile13(
8173
- filePath,
8174
- serializeMemory11({
8175
- frontmatter: {
8176
- ...memory2.frontmatter,
8177
- status: "validated",
8178
- verified_at: verifiedAt,
8179
- stale_reason: null
8180
- },
8181
- body: memory2.body
8182
- }),
8183
- "utf8"
8184
- );
8185
8282
  revalidated++;
8186
8283
  }
8187
8284
  }
@@ -8197,35 +8294,39 @@ function registerSync(program2) {
8197
8294
  minReads: autoPromoteMinReads,
8198
8295
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
8199
8296
  })) {
8200
- await writeFile13(
8201
- filePath,
8202
- serializeMemory11({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8203
- "utf8"
8204
- );
8297
+ if (!dryRun) {
8298
+ await writeFile13(
8299
+ filePath,
8300
+ serializeMemory11({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8301
+ "utf8"
8302
+ );
8303
+ }
8205
8304
  promoted++;
8206
8305
  continue;
8207
8306
  }
8208
8307
  if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
8209
8308
  const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
8210
8309
  if (ageHours >= autoApproveDelayHours) {
8211
- await writeFile13(
8212
- filePath,
8213
- serializeMemory11({
8214
- frontmatter: {
8215
- ...fm,
8216
- status: "validated",
8217
- verified_at: (/* @__PURE__ */ new Date()).toISOString()
8218
- },
8219
- body: memory2.body
8220
- }),
8221
- "utf8"
8222
- );
8310
+ if (!dryRun) {
8311
+ await writeFile13(
8312
+ filePath,
8313
+ serializeMemory11({
8314
+ frontmatter: {
8315
+ ...fm,
8316
+ status: "validated",
8317
+ verified_at: (/* @__PURE__ */ new Date()).toISOString()
8318
+ },
8319
+ body: memory2.body
8320
+ }),
8321
+ "utf8"
8322
+ );
8323
+ }
8223
8324
  autoApproved++;
8224
8325
  }
8225
8326
  }
8226
8327
  }
8227
8328
  }
8228
- if (config.autopilot || autoRepair.context || autoRepair.corpus) {
8329
+ if (!dryRun && (config.autopilot || autoRepair.context || autoRepair.corpus)) {
8229
8330
  const repairs = await applyAutopilotRepairs(root, paths, {
8230
8331
  applyContext: autoRepair.context ?? config.autopilot,
8231
8332
  applyCorpus: autoRepair.corpus ?? config.autopilot,
@@ -8357,14 +8458,16 @@ Attends une **confirmation explicite** avant d'agir.
8357
8458
  paths: [result.file],
8358
8459
  topic: `dep-bump-${slugParts}`
8359
8460
  });
8360
- const teamDir = path15.join(paths.memoriesDir, "team");
8361
- await mkdir10(teamDir, { recursive: true });
8362
- await writeFile13(
8363
- path15.join(teamDir, `${fm.id}.md`),
8364
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8365
- "utf8"
8366
- );
8367
- log(ui.yellow(` \u2192 memory created: ${fm.id}`));
8461
+ if (!dryRun) {
8462
+ const teamDir = path15.join(paths.memoriesDir, "team");
8463
+ await mkdir10(teamDir, { recursive: true });
8464
+ await writeFile13(
8465
+ path15.join(teamDir, `${fm.id}.md`),
8466
+ serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8467
+ "utf8"
8468
+ );
8469
+ }
8470
+ log(ui.yellow(` \u2192 memory${dryRun ? " would be" : ""} created: ${fm.id}`));
8368
8471
  }
8369
8472
  }
8370
8473
  }
@@ -8424,14 +8527,16 @@ Attends une **confirmation explicite** avant d'agir.
8424
8527
  paths: [diff.file],
8425
8528
  topic: `contract-breaking-${diff.contract}`
8426
8529
  });
8427
- const teamDir = path15.join(paths.memoriesDir, "team");
8428
- await mkdir10(teamDir, { recursive: true });
8429
- await writeFile13(
8430
- path15.join(teamDir, `${fm.id}.md`),
8431
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8432
- "utf8"
8433
- );
8434
- log(ui.yellow(` \u2192 memory created: ${fm.id}`));
8530
+ if (!dryRun) {
8531
+ const teamDir = path15.join(paths.memoriesDir, "team");
8532
+ await mkdir10(teamDir, { recursive: true });
8533
+ await writeFile13(
8534
+ path15.join(teamDir, `${fm.id}.md`),
8535
+ serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8536
+ "utf8"
8537
+ );
8538
+ }
8539
+ log(ui.yellow(` \u2192 memory${dryRun ? " would be" : ""} created: ${fm.id}`));
8435
8540
  }
8436
8541
  }
8437
8542
  } catch (err) {
@@ -8439,7 +8544,7 @@ Attends une **confirmation explicite** avant d'agir.
8439
8544
  }
8440
8545
  }
8441
8546
  const existingMap = await loadCodeMap5(paths);
8442
- if (!existingMap && (config.autopilot || autoRepair.codeMap)) {
8547
+ if (!dryRun && !existingMap && (config.autopilot || autoRepair.codeMap)) {
8443
8548
  try {
8444
8549
  const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
8445
8550
  log(ui.dim("code-map: missing \u2014 building index\u2026"));
@@ -8474,17 +8579,21 @@ Attends une **confirmation explicite** avant d'agir.
8474
8579
  );
8475
8580
  const changedSourceFiles = (gitResult.stdout ?? "").trim();
8476
8581
  if (changedSourceFiles.length > 0) {
8477
- try {
8478
- const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
8479
- log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
8480
- const newMap = await buildCodeMap4(root);
8481
- await saveCodeMap4(paths, newMap);
8482
- log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
8483
- } catch {
8582
+ if (!dryRun) {
8583
+ try {
8584
+ const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
8585
+ log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
8586
+ const newMap = await buildCodeMap4(root);
8587
+ await saveCodeMap4(paths, newMap);
8588
+ log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
8589
+ } catch {
8590
+ }
8591
+ } else {
8592
+ log(ui.dim("code-map: source files changed \u2014 would refresh index (skipped in dry-run)"));
8484
8593
  }
8485
8594
  }
8486
8595
  }
8487
- if (opts.embed || autoRepair.codeSearch) {
8596
+ if (!dryRun && (opts.embed || autoRepair.codeSearch)) {
8488
8597
  try {
8489
8598
  const { Embedder, rebuildCodeIndex, rebuildIndex } = await import("@hiveai/embeddings");
8490
8599
  log(ui.dim("embed: rebuilding index\u2026"));
@@ -8507,12 +8616,18 @@ Attends une **confirmation explicite** avant d'agir.
8507
8616
  }
8508
8617
  });
8509
8618
  }
8619
+ function bridgeSummaryLine(body) {
8620
+ const firstLine = body.split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l.length > 0) ?? "";
8621
+ const oneLine = firstLine.replace(/\s+/g, " ");
8622
+ return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
8623
+ }
8510
8624
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8511
8625
  if (!existsSync29(memoriesDir)) return;
8512
8626
  const all = await loadMemoriesFromDir23(memoriesDir);
8513
8627
  const top = all.filter(({ memory: memory2 }) => {
8514
8628
  const s = memory2.frontmatter.status;
8515
8629
  if (memory2.frontmatter.type === "session_recap") return false;
8630
+ if (isStackPackSeed3(memory2.frontmatter)) return false;
8516
8631
  return s === "validated" || s === "proposed";
8517
8632
  }).sort((a, b) => {
8518
8633
  const score = (m) => {
@@ -8524,11 +8639,11 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8524
8639
  const block = top.map((m) => {
8525
8640
  const fm = m.memory.frontmatter;
8526
8641
  const unverified = fm.status === "proposed" ? " [UNVERIFIED]" : "";
8527
- return `### ${fm.id} (${fm.scope}/${fm.type})${unverified}
8528
- ${m.memory.body.trim()}`;
8529
- }).join("\n\n---\n\n");
8642
+ return `- \`${fm.id}\` (${fm.scope}/${fm.type})${unverified} \u2014 ${bridgeSummaryLine(m.memory.body)}`;
8643
+ }).join("\n");
8530
8644
  const injected = `${BRIDGE_START}
8531
8645
  <!-- AUTO-GENERATED by haive sync --inject-bridge \u2014 do not edit between these markers -->
8646
+ <!-- Top memories \u2014 call get_briefing / mem_get for the full body. -->
8532
8647
 
8533
8648
  ` + block + `
8534
8649
 
@@ -8824,7 +8939,7 @@ import {
8824
8939
 
8825
8940
  // src/commands/memory-list.ts
8826
8941
  function registerMemoryList(memory2) {
8827
- memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8942
+ memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("--limit <n>", "max memories to display").option("-d, --dir <dir>", "project root").action(async (opts) => {
8828
8943
  const root = findProjectRoot14(opts.dir);
8829
8944
  const paths = resolveHaivePaths11(root);
8830
8945
  if (!existsSync31(paths.memoriesDir)) {
@@ -8834,6 +8949,7 @@ function registerMemoryList(memory2) {
8834
8949
  }
8835
8950
  const all = await loadMemoriesFromDir25(paths.memoriesDir);
8836
8951
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
8952
+ const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
8837
8953
  const filtered = all.filter((m) => {
8838
8954
  if (!matchesFilters(m, opts)) return false;
8839
8955
  const status = m.memory.frontmatter.status;
@@ -8851,7 +8967,9 @@ function registerMemoryList(memory2) {
8851
8967
  }
8852
8968
  return;
8853
8969
  }
8854
- for (const { memory: mem, filePath } of filtered) {
8970
+ const displayed = limit !== void 0 ? filtered.slice(0, limit) : filtered;
8971
+ const clipped = filtered.length - displayed.length;
8972
+ for (const { memory: mem, filePath } of displayed) {
8855
8973
  const fm = mem.frontmatter;
8856
8974
  const tagStr = fm.tags.length ? ui.dim(` [${fm.tags.join(", ")}]`) : "";
8857
8975
  const moduleStr = fm.module ? ui.dim(` (${fm.module})`) : "";
@@ -8863,8 +8981,10 @@ function registerMemoryList(memory2) {
8863
8981
  if (title && title !== fm.id) console.log(` ${title}`);
8864
8982
  console.log(` ${ui.dim(path17.relative(root, filePath))}`);
8865
8983
  }
8866
- console.log(ui.dim(`
8867
- ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
8984
+ const totalLabel = clipped > 0 ? `
8985
+ ${displayed.length} of ${filtered.length} memories shown (use --limit to adjust)` : `
8986
+ ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`;
8987
+ console.log(ui.dim(totalLabel));
8868
8988
  if (hiddenRejectedCount > 0) {
8869
8989
  console.log(
8870
8990
  ui.dim(`(${hiddenRejectedCount} rejected hidden \u2014 use --show-rejected to include)`)
@@ -9380,7 +9500,9 @@ import {
9380
9500
  resolveHaivePaths as resolveHaivePaths18
9381
9501
  } from "@hiveai/core";
9382
9502
  function registerMemoryHot(memory2) {
9383
- memory2.command("hot").description("List memories actively used but not yet validated (good promotion candidates)").option("--threshold <n>", "minimum read_count to qualify", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9503
+ memory2.command("hot").description(
9504
+ "List unvalidated memories with high read_count \u2014 proven-useful promotion candidates.\n\n Unlike `haive memory pending` (which lists ALL draft/proposed by status),\n `hot` filters by usage: only memories read \u2265N times qualify.\n Use it to quickly find memories that agents are already relying on\n but that haven't been formally validated yet."
9505
+ ).option("--threshold <n>", "minimum read_count to qualify (default: 3)", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9384
9506
  const root = findProjectRoot21(opts.dir);
9385
9507
  const paths = resolveHaivePaths18(root);
9386
9508
  if (!existsSync39(paths.memoriesDir)) {
@@ -9414,7 +9536,8 @@ function registerMemoryHot(memory2) {
9414
9536
  console.log(` ${ui.dim(path25.relative(root, filePath))}`);
9415
9537
  }
9416
9538
  ui.info(
9417
- `${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
9539
+ `${candidates.length} hot (read \u2265${threshold}\xD7) \u2014 agents rely on these; promote with \`haive memory promote <id>\`.
9540
+ Tip: \`haive memory pending\` lists ALL unvalidated memories regardless of read count.`
9418
9541
  );
9419
9542
  });
9420
9543
  }
@@ -11763,7 +11886,7 @@ function parseDays(input) {
11763
11886
 
11764
11887
  // src/commands/doctor.ts
11765
11888
  import { existsSync as existsSync60, statSync } from "fs";
11766
- import { readFile as readFile19, stat } from "fs/promises";
11889
+ import { readFile as readFile19, stat, writeFile as writeFile31 } from "fs/promises";
11767
11890
  import path44 from "path";
11768
11891
  import { execFileSync, execSync as execSync3 } from "child_process";
11769
11892
  import "commander";
@@ -12007,26 +12130,58 @@ function registerDoctor(program2) {
12007
12130
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12008
12131
  });
12009
12132
  }
12010
- findings.push(...await collectInstallFindings(root, "0.9.26"));
12133
+ findings.push(...await collectInstallFindings(root, "0.9.28"));
12011
12134
  try {
12012
12135
  const legacyRaw = execSync3("haive-mcp --version", {
12013
12136
  encoding: "utf8",
12014
12137
  timeout: 3e3,
12015
12138
  stdio: ["ignore", "pipe", "ignore"]
12016
12139
  }).trim();
12017
- const cliVersion = "0.9.26";
12140
+ const cliVersion = "0.9.28";
12018
12141
  if (legacyRaw && legacyRaw !== cliVersion) {
12019
12142
  findings.push({
12020
12143
  severity: "warn",
12021
12144
  code: "legacy-haive-mcp-stale",
12022
- message: `Standalone haive-mcp on PATH is v${legacyRaw} but haive CLI is v${cliVersion}. Prefer MCP client config with command "haive" and args ["mcp", "--stdio"] \u2014 then removing @hiveai/mcp avoids version skew.`,
12023
- fix: `npm install -g @hiveai/cli@${cliVersion}
12024
- # optionally uninstall duplicate:
12145
+ message: `Standalone haive-mcp on PATH is v${legacyRaw} but haive CLI is v${cliVersion}. MCP is now bundled in haive itself \u2014 switch your client configs to command "haive" + args ["mcp", "--stdio"], then uninstall @hiveai/mcp.`,
12146
+ fix: `# 1. Run haive init to regenerate MCP configs pointing to bundled server:
12147
+ haive init
12148
+ # 2. Optionally remove the now-redundant standalone package:
12025
12149
  npm uninstall -g @hiveai/mcp`
12026
12150
  });
12027
12151
  }
12028
12152
  } catch {
12029
12153
  }
12154
+ {
12155
+ const configPaths = [
12156
+ path44.join(root, ".mcp.json"),
12157
+ path44.join(root, ".cursor", "mcp.json"),
12158
+ path44.join(root, ".vscode", "mcp.json")
12159
+ ];
12160
+ const staleConfigs = [];
12161
+ for (const cfgPath of configPaths) {
12162
+ if (!existsSync60(cfgPath)) continue;
12163
+ try {
12164
+ const raw = await readFile19(cfgPath, "utf8");
12165
+ if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
12166
+ staleConfigs.push(path44.relative(root, cfgPath));
12167
+ if (opts.fix && !opts.dryRun) {
12168
+ const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
12169
+ await writeFile31(cfgPath, updated, "utf8");
12170
+ }
12171
+ }
12172
+ } catch {
12173
+ }
12174
+ }
12175
+ if (staleConfigs.length > 0) {
12176
+ findings.push({
12177
+ severity: "warn",
12178
+ code: "legacy-mcp-config",
12179
+ message: `${staleConfigs.length} MCP config file${staleConfigs.length === 1 ? "" : "s"} still reference the old "haive-mcp" command: ` + staleConfigs.join(", ") + `. Run \`haive doctor --fix\` to auto-migrate to the bundled server.`,
12180
+ fix: "haive doctor --fix",
12181
+ section: "Protection"
12182
+ });
12183
+ }
12184
+ }
12030
12185
  if (repairs.length > 0) {
12031
12186
  findings.push({
12032
12187
  severity: "info",
@@ -12150,9 +12305,21 @@ function groupBySection(findings) {
12150
12305
  function nextActions(findings) {
12151
12306
  return [...new Set(findings.flatMap((finding) => finding.fix ? finding.fix.split("\n") : []))].filter(Boolean);
12152
12307
  }
12308
+ function isLowValueCoverageFile(file) {
12309
+ const lower = file.toLowerCase();
12310
+ const base = lower.split("/").pop() ?? lower;
12311
+ if (lower.includes(".test.") || lower.includes(".spec.")) return true;
12312
+ if (lower.includes("/__tests__/") || lower.includes("/test/") || lower.includes("/tests/")) return true;
12313
+ if (lower.endsWith(".d.ts")) return true;
12314
+ if (base === "tsup.config.ts" || base === "vitest.config.ts" || base === "jest.config.ts") return true;
12315
+ if (base.endsWith(".config.ts") || base.endsWith(".config.js")) return true;
12316
+ if (base === "vite.config.ts" || base === "vite.config.js") return true;
12317
+ return false;
12318
+ }
12153
12319
  async function collectHarnessCoverageFindings(codeMap, memories) {
12154
12320
  if (!codeMap) return [];
12155
- const codeFiles = Object.keys(codeMap.files);
12321
+ const allFiles = Object.keys(codeMap.files);
12322
+ const codeFiles = allFiles.filter((f) => !isLowValueCoverageFile(f));
12156
12323
  const total = codeFiles.length;
12157
12324
  if (total === 0) return [];
12158
12325
  const validatedWithAnchors = memories.filter(
@@ -12926,7 +13093,7 @@ function registerMemoryConflictCandidates(memory2) {
12926
13093
  // src/commands/enforce.ts
12927
13094
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
12928
13095
  import { existsSync as existsSync67, statSync as statSync2 } from "fs";
12929
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile20, readdir as readdir6, rm as rm3, writeFile as writeFile31 } from "fs/promises";
13096
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile20, readdir as readdir6, rm as rm3, writeFile as writeFile33 } from "fs/promises";
12930
13097
  import path49 from "path";
12931
13098
  import "commander";
12932
13099
  import {
@@ -12942,7 +13109,7 @@ import {
12942
13109
  saveConfig as saveConfig4,
12943
13110
  SESSION_RECAP_TTL_MS,
12944
13111
  verifyAnchor as verifyAnchor4,
12945
- writeBriefingMarker as writeBriefingMarker2
13112
+ writeBriefingMarker as writeBriefingMarker3
12946
13113
  } from "@hiveai/core";
12947
13114
  var MAX_STDIN_BYTES2 = 256 * 1024;
12948
13115
  var ENFORCE_HOOK_MARKER = "# hAIve enforcement hook";
@@ -13052,7 +13219,7 @@ function registerEnforce(program2) {
13052
13219
  },
13053
13220
  { paths }
13054
13221
  );
13055
- await writeBriefingMarker2(paths, {
13222
+ await writeBriefingMarker3(paths, {
13056
13223
  sessionId,
13057
13224
  task,
13058
13225
  source: opts.source ?? "claude-session-start",
@@ -13137,7 +13304,7 @@ async function runWithEnforcement(command, args, opts) {
13137
13304
  }
13138
13305
  const sessionId = `haive-run-${process.pid}-${Date.now()}`;
13139
13306
  const task = opts.task ?? `Run agent command: ${[command, ...args].join(" ")}`;
13140
- await writeBriefingMarker2(paths, {
13307
+ await writeBriefingMarker3(paths, {
13141
13308
  sessionId,
13142
13309
  task,
13143
13310
  source: "haive-run"
@@ -13194,7 +13361,7 @@ async function writeWrapperBriefing(paths, sessionId, task) {
13194
13361
  min_semantic_score: 0.25,
13195
13362
  budget_preset: "quick"
13196
13363
  }, { paths });
13197
- await writeBriefingMarker2(paths, {
13364
+ await writeBriefingMarker3(paths, {
13198
13365
  sessionId,
13199
13366
  task,
13200
13367
  source: "haive-run",
@@ -13220,7 +13387,7 @@ async function writeWrapperBriefing(paths, sessionId, task) {
13220
13387
  if (briefing.setup_warnings.length > 0) {
13221
13388
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
13222
13389
  }
13223
- await writeFile31(file, parts.join("\n") + "\n", "utf8");
13390
+ await writeFile33(file, parts.join("\n") + "\n", "utf8");
13224
13391
  return file;
13225
13392
  }
13226
13393
  async function buildEnforcementReport(dir, stage, sessionId) {
@@ -13257,7 +13424,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13257
13424
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
13258
13425
  });
13259
13426
  }
13260
- findings.push(...await inspectIntegrationVersions(root, "0.9.26"));
13427
+ findings.push(...await inspectIntegrationVersions(root, "0.9.28"));
13261
13428
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
13262
13429
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
13263
13430
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -13479,9 +13646,9 @@ async function cleanupRuntimeDir(runtimeDir) {
13479
13646
  await rm3(path49.join(runtimeDir, entry.name), { recursive: true, force: true });
13480
13647
  removed++;
13481
13648
  }
13482
- await writeFile31(path49.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
13649
+ await writeFile33(path49.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
13483
13650
  if (!existsSync67(path49.join(runtimeDir, "README.md"))) {
13484
- await writeFile31(
13651
+ await writeFile33(
13485
13652
  path49.join(runtimeDir, "README.md"),
13486
13653
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
13487
13654
  "utf8"
@@ -13498,7 +13665,7 @@ async function cleanupCacheDir(cacheDir) {
13498
13665
  await rm3(path49.join(cacheDir, entry.name), { recursive: true, force: true });
13499
13666
  removed++;
13500
13667
  }
13501
- await writeFile31(path49.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
13668
+ await writeFile33(path49.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
13502
13669
  return removed;
13503
13670
  }
13504
13671
  async function cleanupEnforcementDir(enforcementDir) {
@@ -13645,14 +13812,14 @@ haive enforce check --stage pre-push --dir . || exit $?
13645
13812
  if (existsSync67(file)) {
13646
13813
  const current = await readFile20(file, "utf8").catch(() => "");
13647
13814
  if (current.includes(ENFORCE_HOOK_MARKER)) {
13648
- await writeFile31(file, hook.body, "utf8");
13815
+ await writeFile33(file, hook.body, "utf8");
13649
13816
  } else {
13650
- await writeFile31(file, `${current.trimEnd()}
13817
+ await writeFile33(file, `${current.trimEnd()}
13651
13818
 
13652
13819
  ${hook.body}`, "utf8");
13653
13820
  }
13654
13821
  } else {
13655
- await writeFile31(file, hook.body, "utf8");
13822
+ await writeFile33(file, hook.body, "utf8");
13656
13823
  }
13657
13824
  await chmod2(file, 493);
13658
13825
  }
@@ -13665,7 +13832,7 @@ async function installCiEnforcement(root) {
13665
13832
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
13666
13833
  return;
13667
13834
  }
13668
- await writeFile31(workflowPath, `name: haive-enforcement
13835
+ await writeFile33(workflowPath, `name: haive-enforcement
13669
13836
 
13670
13837
  on:
13671
13838
  pull_request:
@@ -13864,7 +14031,7 @@ function registerRun(program2) {
13864
14031
 
13865
14032
  // src/index.ts
13866
14033
  var program = new Command51();
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");
14034
+ program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.28").option("--advanced", "show maintenance and experimental commands in help");
13868
14035
  registerInit(program);
13869
14036
  registerWelcome(program);
13870
14037
  registerResolveProject(program);