@hiveai/cli 0.12.3 → 0.12.9

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command56 } from "commander";
4
+ import { Command as Command58 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync as existsSync3 } from "fs";
@@ -199,7 +199,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
199
199
  if (!f) continue;
200
200
  counts.set(f, (counts.get(f) ?? 0) + 1);
201
201
  }
202
- let entries = [...counts.entries()].map(([path54, changes]) => ({ path: path54, changes }));
202
+ let entries = [...counts.entries()].map(([path55, changes]) => ({ path: path55, changes }));
203
203
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
204
204
  if (lowerPaths.length > 0) {
205
205
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -2183,7 +2183,11 @@ causes a cryptic runtime error. Check for browser globals (window, document, loc
2183
2183
  body: `Only environment variables prefixed with NEXT_PUBLIC_ are exposed to the browser.
2184
2184
 
2185
2185
  Never put secrets in NEXT_PUBLIC_* variables \u2014 they are bundled into the client JS.
2186
- Variables without the prefix are server-only and safe for API keys, database URLs, etc.`
2186
+ Variables without the prefix are server-only and safe for API keys, database URLs, etc.`,
2187
+ sensor: {
2188
+ pattern: "NEXT_PUBLIC_[A-Z0-9_]*(SECRET|PRIVATE|TOKEN|PASSWORD|API_?KEY)",
2189
+ message: "A NEXT_PUBLIC_ env var with a secret-looking name is bundled into client JS \u2014 move the secret to a server-only (non-NEXT_PUBLIC_) variable."
2190
+ }
2187
2191
  },
2188
2192
  {
2189
2193
  slug: "nextjs-fetch-cache-defaults",
@@ -2251,7 +2255,11 @@ and event listeners that accumulate across re-renders.`
2251
2255
 
2252
2256
  Using index as key causes React to re-render wrong items on reorder/filter,
2253
2257
  corrupts form state, and triggers avoidable DOM mutations.
2254
- Use item.id or a stable hash \u2014 never Math.random().`
2258
+ Use item.id or a stable hash \u2014 never Math.random().`,
2259
+ sensor: {
2260
+ pattern: "key=\\{\\s*index\\s*\\}",
2261
+ message: "Array index used as a React key \u2014 switch to a stable unique id to avoid state corruption on reorder."
2262
+ }
2255
2263
  },
2256
2264
  {
2257
2265
  slug: "react-avoid-use-effect-for-derived-state",
@@ -2682,6 +2690,151 @@ new ApolloServer({
2682
2690
  });
2683
2691
  \`\`\``
2684
2692
  }
2693
+ ],
2694
+ fastapi: [
2695
+ {
2696
+ slug: "fastapi-validate-with-pydantic",
2697
+ type: "convention",
2698
+ tags: ["fastapi", "python", "validation"],
2699
+ body: `Declare request/response models with Pydantic \u2014 never read raw dict bodies.
2700
+
2701
+ \`\`\`py
2702
+ class CreateUser(BaseModel):
2703
+ email: EmailStr
2704
+ age: int = Field(ge=0)
2705
+
2706
+ @app.post("/users")
2707
+ def create(user: CreateUser): ...
2708
+ \`\`\`
2709
+
2710
+ Pydantic validates and coerces at the boundary; raw \`dict\` bodies bypass validation and typing.`
2711
+ },
2712
+ {
2713
+ slug: "fastapi-no-blocking-io-in-async",
2714
+ type: "gotcha",
2715
+ tags: ["fastapi", "python", "async", "performance"],
2716
+ body: `Never call blocking I/O (requests, time.sleep, sync DB drivers) inside an \`async def\` route.
2717
+
2718
+ A blocking call inside the event loop freezes the whole worker for every concurrent request.
2719
+ Use an async client (httpx.AsyncClient, asyncpg) or run blocking work in a threadpool
2720
+ (\`await run_in_threadpool(...)\` / \`def\` route, which FastAPI runs in a threadpool).`
2721
+ },
2722
+ {
2723
+ slug: "fastapi-uvicorn-reload-not-in-prod",
2724
+ type: "gotcha",
2725
+ tags: ["fastapi", "python", "deployment"],
2726
+ body: `\`uvicorn.run(..., reload=True)\` is a dev-only feature \u2014 never ship it to production.
2727
+
2728
+ Reload spawns a file-watcher process and disables multi-worker scaling.
2729
+ In production run \`uvicorn app:app --workers N\` (no reload) behind a process manager.`,
2730
+ sensor: {
2731
+ pattern: "uvicorn\\.run\\([^)]*reload\\s*=\\s*True",
2732
+ message: "uvicorn reload=True is dev-only \u2014 remove it from production entrypoints."
2733
+ }
2734
+ },
2735
+ {
2736
+ slug: "fastapi-no-bare-except",
2737
+ type: "convention",
2738
+ tags: ["fastapi", "python", "error-handling"],
2739
+ body: `Never use a bare \`except:\` \u2014 it swallows KeyboardInterrupt/SystemExit and hides real bugs.
2740
+
2741
+ Catch the specific exception you expect, or \`except Exception as e:\` at most, and log it.`,
2742
+ sensor: {
2743
+ pattern: "except\\s*:",
2744
+ message: "Bare `except:` swallows everything (incl. KeyboardInterrupt) \u2014 catch a specific exception type."
2745
+ }
2746
+ }
2747
+ ],
2748
+ django: [
2749
+ {
2750
+ slug: "django-debug-false-in-prod",
2751
+ type: "gotcha",
2752
+ tags: ["django", "python", "security", "deployment"],
2753
+ body: `\`DEBUG = True\` in production leaks stack traces, settings, and SQL to any visitor.
2754
+
2755
+ Drive it from the environment and default to safe:
2756
+
2757
+ \`\`\`py
2758
+ DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
2759
+ \`\`\``,
2760
+ sensor: {
2761
+ pattern: "DEBUG\\s*=\\s*True",
2762
+ message: "DEBUG = True leaks internals in production \u2014 read it from the environment and default to False."
2763
+ }
2764
+ },
2765
+ {
2766
+ slug: "django-secret-key-from-env",
2767
+ type: "gotcha",
2768
+ tags: ["django", "python", "security"],
2769
+ body: `Never hardcode SECRET_KEY in settings \u2014 load it from the environment.
2770
+
2771
+ A committed SECRET_KEY lets anyone forge sessions and signed tokens.
2772
+
2773
+ \`\`\`py
2774
+ SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
2775
+ \`\`\``,
2776
+ sensor: {
2777
+ pattern: `SECRET_KEY\\s*=\\s*["'][^"']+["']`,
2778
+ message: "Hardcoded SECRET_KEY \u2014 load it from os.environ instead of committing a literal."
2779
+ }
2780
+ },
2781
+ {
2782
+ slug: "django-select-related-n-plus-one",
2783
+ type: "gotcha",
2784
+ tags: ["django", "python", "orm", "performance"],
2785
+ body: `Accessing a ForeignKey in a loop triggers one query per row (N+1).
2786
+
2787
+ Use \`select_related\` (FK / one-to-one, SQL JOIN) and \`prefetch_related\` (M2M / reverse FK):
2788
+
2789
+ \`\`\`py
2790
+ for order in Order.objects.select_related("customer").all():
2791
+ order.customer.name # no extra query
2792
+ \`\`\``
2793
+ }
2794
+ ],
2795
+ go: [
2796
+ {
2797
+ slug: "go-check-every-error",
2798
+ type: "convention",
2799
+ tags: ["go", "error-handling"],
2800
+ body: `Check every returned error \u2014 never discard it with \`_\`.
2801
+
2802
+ \`\`\`go
2803
+ // \u274C silently ignores failure
2804
+ val, _ := doThing()
2805
+
2806
+ // \u2705
2807
+ val, err := doThing()
2808
+ if err != nil {
2809
+ return fmt.Errorf("doThing: %w", err)
2810
+ }
2811
+ \`\`\`
2812
+ Wrap with \`%w\` to preserve the chain for errors.Is/As.`
2813
+ },
2814
+ {
2815
+ slug: "go-defer-close-after-error-check",
2816
+ type: "gotcha",
2817
+ tags: ["go", "resources"],
2818
+ body: `Place \`defer rows.Close()\` (or file/body Close) AFTER checking the open error, not before.
2819
+
2820
+ \`\`\`go
2821
+ rows, err := db.Query(q)
2822
+ if err != nil { return err }
2823
+ defer rows.Close() // only reached when rows is non-nil
2824
+ \`\`\`
2825
+ Deferring before the error check can call Close on a nil resource and panic.`
2826
+ },
2827
+ {
2828
+ slug: "go-context-first-param",
2829
+ type: "convention",
2830
+ tags: ["go", "context", "api-design"],
2831
+ body: `context.Context is always the FIRST parameter and is never stored in a struct.
2832
+
2833
+ \`\`\`go
2834
+ func Fetch(ctx context.Context, id string) (*User, error)
2835
+ \`\`\`
2836
+ Pass it explicitly down the call chain so cancellation and deadlines propagate.`
2837
+ }
2685
2838
  ]
2686
2839
  };
2687
2840
  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._`;
@@ -2721,6 +2874,16 @@ async function seedStackPack(haivePaths, stack) {
2721
2874
  await mkdir4(haivePaths.teamDir, { recursive: true });
2722
2875
  let count = 0;
2723
2876
  for (const mem of memories) {
2877
+ const sensor = mem.sensor ? {
2878
+ kind: "regex",
2879
+ pattern: mem.sensor.pattern,
2880
+ ...mem.sensor.flags ? { flags: mem.sensor.flags } : {},
2881
+ paths: mem.sensor.paths ?? [],
2882
+ message: mem.sensor.message,
2883
+ severity: "warn",
2884
+ autogen: false,
2885
+ last_fired: null
2886
+ } : void 0;
2724
2887
  const fm = buildFrontmatter({
2725
2888
  type: mem.type,
2726
2889
  slug: `${stack}-${mem.slug}`,
@@ -2728,7 +2891,8 @@ async function seedStackPack(haivePaths, stack) {
2728
2891
  status: "validated",
2729
2892
  // STACK_PACK_TAG marks this as generic seed knowledge so briefing ranking
2730
2893
  // keeps it at `background` priority until it earns a repo-specific anchor.
2731
- tags: [...mem.tags, STACK_PACK_TAG]
2894
+ tags: [...mem.tags, STACK_PACK_TAG],
2895
+ ...sensor ? { sensor } : {}
2732
2896
  });
2733
2897
  const filePath = memoryFilePath(haivePaths, "team", fm.id);
2734
2898
  if (existsSync9(filePath)) continue;
@@ -2743,7 +2907,7 @@ ${SEED_FOOTER(stack)}` });
2743
2907
  }
2744
2908
 
2745
2909
  // src/commands/init.ts
2746
- var HAIVE_GITHUB_ACTION_REF = `v${"0.12.3"}`;
2910
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.12.9"}`;
2747
2911
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
2748
2912
 
2749
2913
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -2778,7 +2942,7 @@ This repo uses **hAIve** for shared context. The map:
2778
2942
  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.
2779
2943
  2. **When an approach fails**, call \`mem_tried\` right away so the next agent skips the dead end.
2780
2944
  3. **Before closing** a substantive session, run the \`post_task\` prompt to capture what was learned.
2781
- 4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, then rerun it.
2945
+ 4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, wait for GitHub Actions to pass when applicable, then rerun it.
2782
2946
 
2783
2947
  If the haive MCP server is not available, tell the developer rather than silently skipping it.
2784
2948
 
@@ -2805,7 +2969,7 @@ This repository uses **hAIve**. Running \`haive init\` means the team expects ag
2805
2969
 
2806
2970
  - On failure: **\`mem_tried\`** immediately.
2807
2971
  - Before closing a substantive session: MCP prompt **\`post_task\`** when there is something worth capturing.
2808
- - Before final response: **\`haive enforce finish\`** must pass; it checks commit/push and release version/tag protocol.
2972
+ - Before final response: **\`haive enforce finish\`** must pass; it checks commit/push, release version/tag protocol, and GitHub Actions success for pushed HEAD when the repo has a GitHub remote.
2809
2973
 
2810
2974
  ## If haive MCP is missing
2811
2975
 
@@ -2931,7 +3095,7 @@ jobs:
2931
3095
  function registerInit(program2) {
2932
3096
  program2.command("init").description(
2933
3097
  "Initialize a hAIve project \u2014 autopilot mode ON by default (zero human intervention).\n Auto-bootstraps project-context.md from local files and seeds detected stack packs.\n Add --manual to control memory approval and session recaps yourself.\n Add --no-bootstrap and --stack none to disable the auto-features."
2934
- ).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md / .cursor/rules/haive-mcp-required.mdc").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
3098
+ ).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / AGENTS.md / .cursorrules / copilot-instructions.md / .cursor/rules/haive-mcp-required.mdc").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
2935
3099
  "--manual",
2936
3100
  "opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
2937
3101
  ).option(
@@ -2994,6 +3158,7 @@ function registerInit(program2) {
2994
3158
  }
2995
3159
  if (opts.bridges) {
2996
3160
  await writeBridge(root, "CLAUDE.md");
3161
+ await writeBridge(root, "AGENTS.md");
2997
3162
  await writeBridge(root, ".cursorrules");
2998
3163
  await writeBridge(root, path10.join(".github", "copilot-instructions.md"));
2999
3164
  await writeCursorHaiveRule(root);
@@ -3731,37 +3896,49 @@ import {
3731
3896
  suggestSensorFromMemory as suggestSensorFromMemory2
3732
3897
  } from "@hiveai/core";
3733
3898
  import { z as z15 } from "zod";
3734
- import { mkdir as mkdir42, writeFile as writeFile82 } from "fs/promises";
3735
3899
  import { existsSync as existsSync16 } from "fs";
3900
+ import { mkdir as mkdir42, readFile as readFile32, writeFile as writeFile82 } from "fs/promises";
3736
3901
  import path62 from "path";
3737
3902
  import {
3738
- buildFrontmatter as buildFrontmatter3,
3739
- isLikelyGuessable,
3903
+ draftsFromFindings,
3904
+ filterNewDrafts,
3905
+ loadMemoriesFromDir as loadMemoriesFromDir13,
3740
3906
  memoryFilePath as memoryFilePath3,
3907
+ parseFindings,
3741
3908
  serializeMemory as serializeMemory7
3742
3909
  } from "@hiveai/core";
3743
3910
  import { z as z16 } from "zod";
3744
- import { writeFile as writeFile10, mkdir as mkdir62 } from "fs/promises";
3745
- import { existsSync as existsSync18 } from "fs";
3746
- import path82 from "path";
3911
+ import { mkdir as mkdir52, writeFile as writeFile92 } from "fs/promises";
3912
+ import { existsSync as existsSync17 } from "fs";
3913
+ import path72 from "path";
3747
3914
  import {
3748
- buildFrontmatter as buildFrontmatter4,
3749
- loadMemoriesFromDir as loadMemoriesFromDir13,
3915
+ buildFrontmatter as buildFrontmatter3,
3916
+ isLikelyGuessable,
3750
3917
  memoryFilePath as memoryFilePath4,
3751
3918
  serializeMemory as serializeMemory8
3752
3919
  } from "@hiveai/core";
3753
3920
  import { z as z17 } from "zod";
3921
+ import { writeFile as writeFile11, mkdir as mkdir72 } from "fs/promises";
3922
+ import { existsSync as existsSync19 } from "fs";
3923
+ import path92 from "path";
3924
+ import {
3925
+ buildFrontmatter as buildFrontmatter4,
3926
+ loadMemoriesFromDir as loadMemoriesFromDir14,
3927
+ memoryFilePath as memoryFilePath5,
3928
+ serializeMemory as serializeMemory9
3929
+ } from "@hiveai/core";
3930
+ import { z as z18 } from "zod";
3754
3931
  import {
3755
3932
  appendUsageEvent,
3756
3933
  appendRuntimeJournalEntry,
3757
3934
  loadConfig as loadConfig22
3758
3935
  } from "@hiveai/core";
3759
- import { mkdir as mkdir52, writeFile as writeFile92, rm } from "fs/promises";
3760
- import { existsSync as existsSync17 } from "fs";
3761
- import path72 from "path";
3936
+ import { mkdir as mkdir62, writeFile as writeFile10, rm } from "fs/promises";
3937
+ import { existsSync as existsSync18 } from "fs";
3938
+ import path82 from "path";
3762
3939
  import { execSync } from "child_process";
3763
- import { readFile as readFile42, writeFile as writeFile11 } from "fs/promises";
3764
- import { existsSync as existsSync20 } from "fs";
3940
+ import { readFile as readFile52, writeFile as writeFile12 } from "fs/promises";
3941
+ import { existsSync as existsSync21 } from "fs";
3765
3942
  import {
3766
3943
  allocateBudget,
3767
3944
  computeImpact as computeImpact2,
@@ -3779,13 +3956,13 @@ import {
3779
3956
  literalMatchesAnyToken as literalMatchesAnyToken22,
3780
3957
  loadCodeMap as loadCodeMap5,
3781
3958
  loadConfig as loadConfig3,
3782
- loadMemoriesFromDir as loadMemoriesFromDir14,
3959
+ loadMemoriesFromDir as loadMemoriesFromDir15,
3783
3960
  loadUsageIndex as loadUsageIndex8,
3784
3961
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
3785
3962
  rankMemoriesLexical as rankMemoriesLexical2,
3786
3963
  queryCodeMap as queryCodeMap2,
3787
3964
  resolveBriefingBudget as resolveBriefingBudget2,
3788
- serializeMemory as serializeMemory9,
3965
+ serializeMemory as serializeMemory10,
3789
3966
  specificityScore as specificityScore2,
3790
3967
  GUESSABLE_THRESHOLD,
3791
3968
  tokenizeQuery as tokenizeQuery22,
@@ -3793,34 +3970,34 @@ import {
3793
3970
  truncateToTokens,
3794
3971
  writeBriefingMarker as writeBriefingMarker2
3795
3972
  } from "@hiveai/core";
3796
- import { z as z18 } from "zod";
3797
- import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
3798
- import { existsSync as existsSync19 } from "fs";
3799
- import path92 from "path";
3973
+ import { z as z19 } from "zod";
3974
+ import { readdir as readdir3, readFile as readFile42 } from "fs/promises";
3975
+ import { existsSync as existsSync20 } from "fs";
3976
+ import path102 from "path";
3800
3977
  import { isGlobPath, isStackPackSeed as isStackPackSeed2, pathsOverlap } from "@hiveai/core";
3801
3978
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap22, queryCodeMap as queryCodeMap22 } from "@hiveai/core";
3802
- import { z as z19 } from "zod";
3803
- import { existsSync as existsSync21 } from "fs";
3804
- import { loadMemoriesFromDir as loadMemoriesFromDir15 } from "@hiveai/core";
3805
3979
  import { z as z20 } from "zod";
3806
3980
  import { existsSync as existsSync222 } from "fs";
3807
3981
  import { loadMemoriesFromDir as loadMemoriesFromDir16 } from "@hiveai/core";
3808
3982
  import { z as z21 } from "zod";
3983
+ import { existsSync as existsSync23 } from "fs";
3984
+ import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hiveai/core";
3809
3985
  import { z as z22 } from "zod";
3810
3986
  import { z as z23 } from "zod";
3811
- import { existsSync as existsSync23 } from "fs";
3987
+ import { z as z24 } from "zod";
3988
+ import { existsSync as existsSync24 } from "fs";
3812
3989
  import { spawn } from "child_process";
3813
- import path102 from "path";
3990
+ import path112 from "path";
3814
3991
  import {
3815
3992
  deriveConfidence as deriveConfidence5,
3816
3993
  getUsage as getUsage7,
3817
3994
  loadCodeMap as loadCodeMap32,
3818
- loadMemoriesFromDir as loadMemoriesFromDir17,
3995
+ loadMemoriesFromDir as loadMemoriesFromDir18,
3819
3996
  loadUsageIndex as loadUsageIndex9,
3820
3997
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
3821
3998
  } from "@hiveai/core";
3822
- import { z as z24 } from "zod";
3823
- import { existsSync as existsSync24 } from "fs";
3999
+ import { z as z25 } from "zod";
4000
+ import { existsSync as existsSync25 } from "fs";
3824
4001
  import {
3825
4002
  addedLinesFromDiff,
3826
4003
  buildDocFrequency,
@@ -3829,7 +4006,7 @@ import {
3829
4006
  diffHasDistinctiveOverlap,
3830
4007
  getUsage as getUsage8,
3831
4008
  isRetiredMemory as isRetiredMemory2,
3832
- loadMemoriesFromDir as loadMemoriesFromDir18,
4009
+ loadMemoriesFromDir as loadMemoriesFromDir19,
3833
4010
  loadUsageIndex as loadUsageIndex10,
3834
4011
  literalMatchesAnyToken as literalMatchesAnyToken3,
3835
4012
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
@@ -3837,66 +4014,66 @@ import {
3837
4014
  sensorTargetsFromDiff,
3838
4015
  tokenizeQuery as tokenizeQuery3
3839
4016
  } from "@hiveai/core";
3840
- import { z as z25 } from "zod";
3841
- import { existsSync as existsSync25 } from "fs";
4017
+ import { z as z26 } from "zod";
4018
+ import { existsSync as existsSync26 } from "fs";
3842
4019
  import {
3843
- loadMemoriesFromDir as loadMemoriesFromDir19,
4020
+ loadMemoriesFromDir as loadMemoriesFromDir20,
3844
4021
  tokenizeQuery as tokenizeQuery4
3845
4022
  } from "@hiveai/core";
3846
- import { z as z26 } from "zod";
3847
- import { existsSync as existsSync26 } from "fs";
4023
+ import { z as z27 } from "zod";
4024
+ import { existsSync as existsSync27 } from "fs";
3848
4025
  import { spawn as spawn2 } from "child_process";
3849
4026
  import {
3850
4027
  deriveConfidence as deriveConfidence7,
3851
4028
  getUsage as getUsage9,
3852
- loadMemoriesFromDir as loadMemoriesFromDir20,
4029
+ loadMemoriesFromDir as loadMemoriesFromDir21,
3853
4030
  loadUsageIndex as loadUsageIndex11,
3854
4031
  pathsOverlap as singlePathsOverlap
3855
4032
  } from "@hiveai/core";
3856
- import { z as z27 } from "zod";
3857
- import { existsSync as existsSync27 } from "fs";
4033
+ import { z as z28 } from "zod";
4034
+ import { existsSync as existsSync28 } from "fs";
3858
4035
  import {
3859
4036
  deriveConfidence as deriveConfidence8,
3860
4037
  getUsage as getUsage10,
3861
- loadMemoriesFromDir as loadMemoriesFromDir21,
4038
+ loadMemoriesFromDir as loadMemoriesFromDir222,
3862
4039
  loadUsageIndex as loadUsageIndex12,
3863
4040
  pathsOverlap as pathsOverlap2,
3864
4041
  tokenizeQuery as tokenizeQuery5
3865
4042
  } from "@hiveai/core";
3866
- import { z as z28 } from "zod";
3867
4043
  import { z as z29 } from "zod";
3868
- import { mkdir as mkdir72, writeFile as writeFile12 } from "fs/promises";
3869
- import { existsSync as existsSync28 } from "fs";
3870
- import path112 from "path";
4044
+ import { z as z30 } from "zod";
4045
+ import { mkdir as mkdir82, writeFile as writeFile13 } from "fs/promises";
4046
+ import { existsSync as existsSync29 } from "fs";
4047
+ import path122 from "path";
3871
4048
  import { execSync as execSync2 } from "child_process";
3872
4049
  import {
3873
4050
  buildFrontmatter as buildFrontmatter5,
3874
- memoryFilePath as memoryFilePath5,
4051
+ memoryFilePath as memoryFilePath6,
3875
4052
  readUsageEvents,
3876
- serializeMemory as serializeMemory10
4053
+ serializeMemory as serializeMemory11
3877
4054
  } from "@hiveai/core";
3878
- import { z as z30 } from "zod";
3879
- import { existsSync as existsSync29 } from "fs";
4055
+ import { z as z31 } from "zod";
4056
+ import { existsSync as existsSync30 } from "fs";
3880
4057
  import {
3881
4058
  findLexicalConflictPairs,
3882
4059
  findTopicStatusConflictPairs,
3883
- loadMemoriesFromDir as loadMemoriesFromDir222
4060
+ loadMemoriesFromDir as loadMemoriesFromDir23
3884
4061
  } from "@hiveai/core";
3885
- import { z as z31 } from "zod";
3886
- import { resolveProjectInfo } from "@hiveai/core";
3887
4062
  import { z as z32 } from "zod";
3888
- import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
4063
+ import { resolveProjectInfo } from "@hiveai/core";
3889
4064
  import { z as z33 } from "zod";
3890
- import { existsSync as existsSync30 } from "fs";
3891
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hiveai/core";
4065
+ import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
3892
4066
  import { z as z34 } from "zod";
3893
- import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
4067
+ import { existsSync as existsSync31 } from "fs";
4068
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir24 } from "@hiveai/core";
3894
4069
  import { z as z35 } from "zod";
3895
- import { readRuntimeJournalTail } from "@hiveai/core";
4070
+ import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
3896
4071
  import { z as z36 } from "zod";
4072
+ import { readRuntimeJournalTail } from "@hiveai/core";
3897
4073
  import { z as z37 } from "zod";
3898
4074
  import { z as z38 } from "zod";
3899
4075
  import { z as z39 } from "zod";
4076
+ import { z as z40 } from "zod";
3900
4077
  import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
3901
4078
  function createContext(options = {}) {
3902
4079
  const env = options.env ?? process.env;
@@ -4879,21 +5056,98 @@ async function memTried(input, ctx) {
4879
5056
  await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
4880
5057
  return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
4881
5058
  }
4882
- var MemObserveInputSchema = {
4883
- what: z16.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
4884
- where: z16.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
4885
- impact: z16.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
4886
- fix: z16.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
4887
- scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
5059
+ var IngestFindingsInputSchema = {
5060
+ format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
5061
+ report_path: z16.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
5062
+ report: z16.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
5063
+ type: z16.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
5064
+ scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
4888
5065
  module: z16.string().optional().describe("Module name (required when scope=module)"),
4889
- tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
5066
+ min_severity: z16.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
5067
+ limit: z16.number().int().positive().optional().describe("Cap the number of memories created"),
4890
5068
  author: z16.string().optional().describe("Author handle or email"),
4891
- force: z16.boolean().default(false).describe(
5069
+ dry_run: z16.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
5070
+ };
5071
+ async function ingestFindings(input, ctx) {
5072
+ if (!existsSync16(ctx.paths.haiveDir)) {
5073
+ throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
5074
+ }
5075
+ let raw;
5076
+ if (input.report && input.report.trim()) {
5077
+ raw = input.report;
5078
+ } else if (input.report_path) {
5079
+ const file = path62.resolve(ctx.paths.root, input.report_path);
5080
+ if (!existsSync16(file)) throw new Error(`Report file not found: ${file}`);
5081
+ raw = await readFile32(file, "utf8");
5082
+ } else {
5083
+ throw new Error("Provide either `report_path` or `report`.");
5084
+ }
5085
+ const findings = parseFindings(input.format, raw);
5086
+ const drafts = draftsFromFindings(findings, {
5087
+ type: input.type,
5088
+ scope: input.scope,
5089
+ module: input.module,
5090
+ author: input.author,
5091
+ ...input.min_severity ? { minSeverity: input.min_severity } : {},
5092
+ ...input.limit ? { limit: input.limit } : {}
5093
+ });
5094
+ const existing = existsSync16(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
5095
+ const existingTopics = new Set(
5096
+ existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
5097
+ );
5098
+ const fresh = filterNewDrafts(drafts, existingTopics);
5099
+ const skipped = drafts.length - fresh.length;
5100
+ const created = [];
5101
+ for (const draft of fresh) {
5102
+ let filePath;
5103
+ if (!input.dry_run) filePath = await writeDraft(ctx, draft);
5104
+ created.push({
5105
+ id: draft.frontmatter.id,
5106
+ topic: draft.topic,
5107
+ path: draft.finding.path,
5108
+ rule: draft.finding.ruleId,
5109
+ severity: draft.finding.severity,
5110
+ has_sensor: draft.has_sensor,
5111
+ ...filePath ? { file_path: filePath } : {}
5112
+ });
5113
+ }
5114
+ const notice = input.dry_run ? `Dry run \u2014 ${fresh.length} memory(ies) would be created (status=proposed). Re-run with dry_run=false to write them.` : `Created ${fresh.length} proposed memory(ies). They are NOT validated and their sensors are warn-only \u2014 review with mem_pending and promote with 'haive sensors promote'.`;
5115
+ return {
5116
+ format: input.format,
5117
+ parsed: drafts.length,
5118
+ new: fresh.length,
5119
+ skipped_existing: skipped,
5120
+ dry_run: input.dry_run,
5121
+ created,
5122
+ notice
5123
+ };
5124
+ }
5125
+ async function writeDraft(ctx, draft) {
5126
+ const file = memoryFilePath3(
5127
+ ctx.paths,
5128
+ draft.frontmatter.scope,
5129
+ draft.frontmatter.id,
5130
+ draft.frontmatter.module
5131
+ );
5132
+ await mkdir42(path62.dirname(file), { recursive: true });
5133
+ await writeFile82(file, serializeMemory7({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
5134
+ return file;
5135
+ }
5136
+ var MemObserveInputSchema = {
5137
+ what: z17.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
5138
+ where: z17.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
5139
+ impact: z17.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
5140
+ fix: z17.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
5141
+ scope: z17.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
5142
+ module: z17.string().optional().describe("Module name (required when scope=module)"),
5143
+ tags: z17.array(z17.string()).default([]).describe("Tags for filtering"),
5144
+ author: z17.string().optional().describe("Author handle or email"),
5145
+ force: z17.boolean().default(false).describe(
4892
5146
  "Save even if the observation looks like generic, guessable knowledge. By default, low-specificity observations (things a capable model already knows) are SKIPPED to keep the corpus high-signal \u2014 only unguessable, team-specific discoveries are worth storing."
4893
5147
  )
4894
5148
  };
4895
5149
  async function memObserve(input, ctx) {
4896
- if (!existsSync16(ctx.paths.haiveDir)) {
5150
+ if (!existsSync17(ctx.paths.haiveDir)) {
4897
5151
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
4898
5152
  }
4899
5153
  const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
@@ -4925,16 +5179,16 @@ async function memObserve(input, ctx) {
4925
5179
  lines.push("", `**Fix/workaround:** ${input.fix}`);
4926
5180
  }
4927
5181
  const body = lines.join("\n") + "\n";
4928
- const file = memoryFilePath3(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
4929
- await mkdir42(path62.dirname(file), { recursive: true });
4930
- if (existsSync16(file)) {
5182
+ const file = memoryFilePath4(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
5183
+ await mkdir52(path72.dirname(file), { recursive: true });
5184
+ if (existsSync17(file)) {
4931
5185
  throw new Error(`Memory already exists at ${file}`);
4932
5186
  }
4933
- await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
5187
+ await writeFile92(file, serializeMemory8({ frontmatter, body }), "utf8");
4934
5188
  return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
4935
5189
  }
4936
5190
  function pendingDistillPath(ctx) {
4937
- return path72.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
5191
+ return path82.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
4938
5192
  }
4939
5193
  var SessionTracker = class {
4940
5194
  events = [];
@@ -5013,7 +5267,7 @@ var SessionTracker = class {
5013
5267
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
5014
5268
  );
5015
5269
  const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
5016
- if (!ranPostTask && isSubstantialSession && existsSync17(this.ctx.paths.haiveDir)) {
5270
+ if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
5017
5271
  try {
5018
5272
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
5019
5273
  const payload = {
@@ -5026,9 +5280,9 @@ var SessionTracker = class {
5026
5280
  ...gitDiff ? { git_diff: gitDiff } : {},
5027
5281
  ...recapId ? { recap_id: recapId } : {}
5028
5282
  };
5029
- const cacheDir = path72.join(this.ctx.paths.haiveDir, ".cache");
5030
- await mkdir52(cacheDir, { recursive: true });
5031
- await writeFile92(
5283
+ const cacheDir = path82.join(this.ctx.paths.haiveDir, ".cache");
5284
+ await mkdir62(cacheDir, { recursive: true });
5285
+ await writeFile10(
5032
5286
  pendingDistillPath(this.ctx),
5033
5287
  JSON.stringify(payload, null, 2) + "\n",
5034
5288
  "utf8"
@@ -5047,7 +5301,7 @@ var SessionTracker = class {
5047
5301
  };
5048
5302
  async function clearPendingDistill(ctx) {
5049
5303
  const p = pendingDistillPath(ctx);
5050
- if (existsSync17(p)) {
5304
+ if (existsSync18(p)) {
5051
5305
  try {
5052
5306
  await rm(p);
5053
5307
  } catch {
@@ -5062,15 +5316,15 @@ function summarizeTools(events) {
5062
5316
  return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
5063
5317
  }
5064
5318
  var MemSessionEndInputSchema = {
5065
- goal: z17.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
5066
- accomplished: z17.string().describe("What was actually done \u2014 bullet list recommended"),
5067
- discoveries: z17.string().default("").describe(
5319
+ goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
5320
+ accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
5321
+ discoveries: z18.string().default("").describe(
5068
5322
  "Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
5069
5323
  ),
5070
- files_touched: z17.array(z17.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
5071
- next_steps: z17.string().default("").describe("What should happen next (for the next session or a teammate)"),
5072
- scope: z17.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
5073
- module: z17.string().optional().describe("Module name (required when scope=module)")
5324
+ files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
5325
+ next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
5326
+ scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
5327
+ module: z18.string().optional().describe("Module name (required when scope=module)")
5074
5328
  };
5075
5329
  function recapTopic(scope, module) {
5076
5330
  return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
@@ -5100,23 +5354,23 @@ ${input.next_steps}`);
5100
5354
  return lines.join("\n");
5101
5355
  }
5102
5356
  async function memSessionEnd(input, ctx) {
5103
- if (!existsSync18(ctx.paths.haiveDir)) {
5357
+ if (!existsSync19(ctx.paths.haiveDir)) {
5104
5358
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
5105
5359
  }
5106
5360
  const body = buildBody(input);
5107
5361
  const topic = recapTopic(input.scope, input.module);
5108
5362
  const normalizedFiles = input.files_touched.map((p) => {
5109
- if (!p || !path82.isAbsolute(p)) return p;
5110
- const rel = path82.relative(ctx.paths.root, p);
5363
+ if (!p || !path92.isAbsolute(p)) return p;
5364
+ const rel = path92.relative(ctx.paths.root, p);
5111
5365
  return rel.startsWith("..") ? p : rel;
5112
5366
  });
5113
5367
  const invalidPaths = normalizedFiles.filter(
5114
- (p) => !existsSync18(path82.resolve(ctx.paths.root, p))
5368
+ (p) => !existsSync19(path92.resolve(ctx.paths.root, p))
5115
5369
  );
5116
5370
  if (invalidPaths.length > 0) {
5117
5371
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
5118
5372
  }
5119
- const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
5373
+ const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
5120
5374
  const topicMatch = existing.find(
5121
5375
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === input.scope && (!input.module || memory2.frontmatter.module === input.module)
5122
5376
  );
@@ -5132,9 +5386,9 @@ async function memSessionEnd(input, ctx) {
5132
5386
  paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
5133
5387
  }
5134
5388
  };
5135
- await writeFile10(
5389
+ await writeFile11(
5136
5390
  topicMatch.filePath,
5137
- serializeMemory8({ frontmatter: newFrontmatter, body }),
5391
+ serializeMemory9({ frontmatter: newFrontmatter, body }),
5138
5392
  "utf8"
5139
5393
  );
5140
5394
  await clearPendingDistill(ctx);
@@ -5156,14 +5410,14 @@ async function memSessionEnd(input, ctx) {
5156
5410
  topic,
5157
5411
  status: "validated"
5158
5412
  });
5159
- const file = memoryFilePath4(
5413
+ const file = memoryFilePath5(
5160
5414
  ctx.paths,
5161
5415
  frontmatter.scope,
5162
5416
  frontmatter.id,
5163
5417
  frontmatter.module
5164
5418
  );
5165
- await mkdir62(path82.dirname(file), { recursive: true });
5166
- await writeFile10(file, serializeMemory8({ frontmatter, body }), "utf8");
5419
+ await mkdir72(path92.dirname(file), { recursive: true });
5420
+ await writeFile11(file, serializeMemory9({ frontmatter, body }), "utf8");
5167
5421
  await clearPendingDistill(ctx);
5168
5422
  return {
5169
5423
  id: frontmatter.id,
@@ -5297,50 +5551,50 @@ async function trySemanticHits(ctx, task, limit) {
5297
5551
  }
5298
5552
  async function loadModuleContexts2(ctx, modules) {
5299
5553
  if (modules.length === 0) return [];
5300
- if (!existsSync19(ctx.paths.modulesContextDir)) return [];
5554
+ if (!existsSync20(ctx.paths.modulesContextDir)) return [];
5301
5555
  const available = new Set(
5302
5556
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
5303
5557
  );
5304
5558
  const out = [];
5305
5559
  for (const m of modules) {
5306
5560
  if (!available.has(m)) continue;
5307
- const file = path92.join(ctx.paths.modulesContextDir, m, "context.md");
5308
- if (existsSync19(file)) {
5309
- out.push({ name: m, content: await readFile32(file, "utf8") });
5561
+ const file = path102.join(ctx.paths.modulesContextDir, m, "context.md");
5562
+ if (existsSync20(file)) {
5563
+ out.push({ name: m, content: await readFile42(file, "utf8") });
5310
5564
  }
5311
5565
  }
5312
5566
  return out;
5313
5567
  }
5314
5568
  var GetBriefingInputSchema = {
5315
- task: z18.string().optional().describe(
5569
+ task: z19.string().optional().describe(
5316
5570
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
5317
5571
  ),
5318
- files: z18.array(z18.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
5319
- max_tokens: z18.number().int().positive().default(8e3).describe(
5572
+ files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
5573
+ max_tokens: z19.number().int().positive().default(8e3).describe(
5320
5574
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
5321
5575
  ),
5322
- max_memories: z18.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
5323
- include_project_context: z18.boolean().default(true),
5324
- include_module_contexts: z18.boolean().default(true),
5325
- semantic: z18.boolean().default(true).describe(
5576
+ max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
5577
+ include_project_context: z19.boolean().default(true),
5578
+ include_module_contexts: z19.boolean().default(true),
5579
+ semantic: z19.boolean().default(true).describe(
5326
5580
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
5327
5581
  ),
5328
- include_stale: z18.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
5329
- track: z18.boolean().default(true).describe("Increment read_count on returned memories"),
5330
- format: z18.enum(["full", "compact", "actions"]).default("full").describe(
5582
+ include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
5583
+ track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
5584
+ format: z19.enum(["full", "compact", "actions"]).default("full").describe(
5331
5585
  "Output format: 'full' returns memory bodies (honors token budget via truncation); 'compact' returns a 1-line summary per memory (call mem_get for detail); 'actions' squeezes bodies to actionable bullet lines \u2014 fewer tokens vs full."
5332
5586
  ),
5333
- symbols: z18.array(z18.string()).default([]).describe(
5587
+ symbols: z19.array(z19.string()).default([]).describe(
5334
5588
  "Symbol names to look up in the code-map (e.g. ['PaymentService', 'TenantFilter']). Returns the file(s) exporting each symbol so agents don't need to grep. Requires `haive index code` to have been run."
5335
5589
  ),
5336
- min_semantic_score: z18.number().min(0).max(1).default(0).describe(
5590
+ min_semantic_score: z19.number().min(0).max(1).default(0).describe(
5337
5591
  "Drop semantic-only memory hits whose cosine score is below this threshold. Useful to avoid weakly-related noise when the task is short or the corpus is broad. Has no effect on memories matched via anchor/module/literal \u2014 those are always kept. Try 0.25\u20130.4 for stricter matching."
5338
5592
  ),
5339
- budget_preset: z18.enum(["quick", "balanced", "deep"]).optional().describe(
5593
+ budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
5340
5594
  "Shortcut token budget: 'quick' minimizes tokens/skip module CONTEXT slices; 'balanced' mirrors historical defaults; 'deep' uses a larger briefing. When set, overrides max_tokens, max_memories, and include_module_contexts."
5341
5595
  )
5342
5596
  };
5343
- var GetBriefingZod = z18.object(GetBriefingInputSchema);
5597
+ var GetBriefingZod = z19.object(GetBriefingInputSchema);
5344
5598
  async function getBriefing(input, ctx) {
5345
5599
  const resolvedBudget = resolveBriefingBudget2(input.budget_preset, {
5346
5600
  max_tokens: input.max_tokens,
@@ -5356,8 +5610,8 @@ async function getBriefing(input, ctx) {
5356
5610
  let usage = { version: 1, updated_at: "", by_id: {} };
5357
5611
  let byId = /* @__PURE__ */ new Map();
5358
5612
  let lastSession;
5359
- if (existsSync20(ctx.paths.memoriesDir)) {
5360
- const allLoaded = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5613
+ if (existsSync21(ctx.paths.memoriesDir)) {
5614
+ const allLoaded = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
5361
5615
  const recaps = allLoaded.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").sort(
5362
5616
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
5363
5617
  );
@@ -5520,7 +5774,7 @@ async function getBriefing(input, ctx) {
5520
5774
  if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
5521
5775
  const newFm = { ...loaded.memory.frontmatter, status: "validated" };
5522
5776
  try {
5523
- await writeFile11(loaded.filePath, serializeMemory9({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
5777
+ await writeFile12(loaded.filePath, serializeMemory10({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
5524
5778
  m.status = "validated";
5525
5779
  m.confidence = "trusted";
5526
5780
  } catch {
@@ -5528,12 +5782,12 @@ async function getBriefing(input, ctx) {
5528
5782
  }
5529
5783
  }
5530
5784
  }
5531
- const projectContextRaw = input.include_project_context && existsSync20(ctx.paths.projectContext) ? await readFile42(ctx.paths.projectContext, "utf8") : "";
5785
+ const projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile52(ctx.paths.projectContext, "utf8") : "";
5532
5786
  const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
5533
5787
  const setupWarnings = [];
5534
5788
  let autoContextGenerated = false;
5535
5789
  let projectContext = isTemplateContext ? "" : projectContextRaw;
5536
- if ((isTemplateContext || !existsSync20(ctx.paths.projectContext)) && input.include_project_context) {
5790
+ if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
5537
5791
  const haiveConfig = await loadConfig3(ctx.paths);
5538
5792
  if (haiveConfig.autoContext) {
5539
5793
  const codeMap = await loadCodeMap5(ctx.paths);
@@ -5694,8 +5948,8 @@ ${m.content}`).join("\n\n---\n\n"),
5694
5948
  actionRequired.push(extractActionItem(m.id, loaded.memory.body));
5695
5949
  }
5696
5950
  }
5697
- if (existsSync20(ctx.paths.memoriesDir)) {
5698
- const allMems = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5951
+ if (existsSync21(ctx.paths.memoriesDir)) {
5952
+ const allMems = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
5699
5953
  for (const { memory: memory2 } of allMems) {
5700
5954
  const fm = memory2.frontmatter;
5701
5955
  if (!fm.requires_human_approval) continue;
@@ -5705,9 +5959,9 @@ ${m.content}`).join("\n\n---\n\n"),
5705
5959
  }
5706
5960
  }
5707
5961
  const pendingDistillFile = pendingDistillPath(ctx);
5708
- if (existsSync20(pendingDistillFile)) {
5962
+ if (existsSync21(pendingDistillFile)) {
5709
5963
  try {
5710
- const raw = await readFile42(pendingDistillFile, "utf8");
5964
+ const raw = await readFile52(pendingDistillFile, "utf8");
5711
5965
  const pd = JSON.parse(raw);
5712
5966
  const ageMs = Date.now() - new Date(pd.session_end).getTime();
5713
5967
  const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
@@ -5734,7 +5988,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5734
5988
  }
5735
5989
  }
5736
5990
  const memoriesEmpty = outputMemories.length === 0;
5737
- const hasMemoriesDir = existsSync20(ctx.paths.memoriesDir);
5991
+ const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
5738
5992
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
5739
5993
  const hasUnguessableSignal = outputMemories.some(
5740
5994
  (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore2(m.body) >= GUESSABLE_THRESHOLD
@@ -5781,7 +6035,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5781
6035
  "No team-specific policy matched these files/task \u2014 nothing here a capable model can't infer. The auto-generated project context was trimmed to keep this briefing near-zero-cost; proceed with normal Read/Grep."
5782
6036
  );
5783
6037
  }
5784
- if (existsSync20(ctx.paths.haiveDir)) {
6038
+ if (existsSync21(ctx.paths.haiveDir)) {
5785
6039
  await writeBriefingMarker2(ctx.paths, {
5786
6040
  sessionId: process.env.HAIVE_SESSION_ID,
5787
6041
  ...input.task ? { task: input.task } : {},
@@ -5830,17 +6084,17 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5830
6084
  };
5831
6085
  }
5832
6086
  var CodeMapInputSchema = {
5833
- file: z19.string().optional().describe("Filter to files whose path contains this substring"),
5834
- symbol: z19.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
5835
- paths: z19.array(z19.string()).default([]).describe(
6087
+ file: z20.string().optional().describe("Filter to files whose path contains this substring"),
6088
+ symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
6089
+ paths: z20.array(z20.string()).default([]).describe(
5836
6090
  "Filter to files under any of these path prefixes (e.g. ['packages/mcp/src/tools/', 'src/auth/']). OR-joined with `file` substring; useful to get a focused view of one module."
5837
6091
  ),
5838
- max_files: z19.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
5839
- max_tokens: z19.number().int().positive().optional().describe(
6092
+ max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
6093
+ max_tokens: z20.number().int().positive().optional().describe(
5840
6094
  "Approximate token budget for the response. When the matching set exceeds it, files are ranked by export density (exports per LOC) and the highest-signal ones are kept first. Omit to disable budgeting (legacy behavior)."
5841
6095
  )
5842
6096
  };
5843
- var CodeMapInputZod = z19.object(CodeMapInputSchema);
6097
+ var CodeMapInputZod = z20.object(CodeMapInputSchema);
5844
6098
  async function codeMapTool(input, ctx) {
5845
6099
  const map = await loadCodeMap22(ctx.paths);
5846
6100
  if (!map) {
@@ -5908,14 +6162,14 @@ function estimateFileEntryTokens(f) {
5908
6162
  return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
5909
6163
  }
5910
6164
  var MemDiffInputSchema = {
5911
- id_a: z20.string().min(1).describe("First memory id"),
5912
- id_b: z20.string().min(1).describe("Second memory id")
6165
+ id_a: z21.string().min(1).describe("First memory id"),
6166
+ id_b: z21.string().min(1).describe("Second memory id")
5913
6167
  };
5914
6168
  async function memDiff(input, ctx) {
5915
- if (!existsSync21(ctx.paths.memoriesDir)) {
6169
+ if (!existsSync222(ctx.paths.memoriesDir)) {
5916
6170
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
5917
6171
  }
5918
- const all = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
6172
+ const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
5919
6173
  const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
5920
6174
  const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
5921
6175
  if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
@@ -5948,15 +6202,15 @@ async function memDiff(input, ctx) {
5948
6202
  };
5949
6203
  }
5950
6204
  var GetRecapInputSchema = {
5951
- scope: z21.enum(["personal", "team", "any"]).default("any").describe(
6205
+ scope: z22.enum(["personal", "team", "any"]).default("any").describe(
5952
6206
  "Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
5953
6207
  )
5954
6208
  };
5955
6209
  async function getRecap(input, ctx) {
5956
- if (!existsSync222(ctx.paths.memoriesDir)) {
6210
+ if (!existsSync23(ctx.paths.memoriesDir)) {
5957
6211
  return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
5958
6212
  }
5959
- const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
6213
+ const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
5960
6214
  const recaps = all.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").filter(({ memory: memory2 }) => input.scope === "any" || memory2.frontmatter.scope === input.scope).sort(
5961
6215
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
5962
6216
  );
@@ -5979,11 +6233,11 @@ async function getRecap(input, ctx) {
5979
6233
  };
5980
6234
  }
5981
6235
  var MemRelevantToInputSchema = {
5982
- task: z22.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
5983
- files: z22.array(z22.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
5984
- limit: z22.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
5985
- min_semantic_score: z22.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
5986
- format: z22.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
6236
+ task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
6237
+ files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
6238
+ limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
6239
+ min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
6240
+ format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
5987
6241
  };
5988
6242
  async function memRelevantTo(input, ctx) {
5989
6243
  const briefingInput = {
@@ -6012,11 +6266,11 @@ async function memRelevantTo(input, ctx) {
6012
6266
  return out;
6013
6267
  }
6014
6268
  var CodeSearchInputSchema = {
6015
- query: z23.string().min(1).describe(
6269
+ query: z24.string().min(1).describe(
6016
6270
  "Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
6017
6271
  ),
6018
- k: z23.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
6019
- min_score: z23.number().min(0).max(1).default(0.2).describe(
6272
+ k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
6273
+ min_score: z24.number().min(0).max(1).default(0.2).describe(
6020
6274
  "Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
6021
6275
  )
6022
6276
  };
@@ -6045,14 +6299,14 @@ async function codeSearch(input, ctx) {
6045
6299
  return { available: true, hits: result.hits };
6046
6300
  }
6047
6301
  var WhyThisFileInputSchema = {
6048
- path: z24.string().min(1).describe(
6302
+ path: z25.string().min(1).describe(
6049
6303
  "Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
6050
6304
  ),
6051
- git_log_limit: z24.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
6052
- memory_limit: z24.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
6305
+ git_log_limit: z25.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
6306
+ memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
6053
6307
  };
6054
6308
  async function whyThisFile(input, ctx) {
6055
- const fileExists = existsSync23(path102.join(ctx.paths.root, input.path));
6309
+ const fileExists = existsSync24(path112.join(ctx.paths.root, input.path));
6056
6310
  const [commits, memories, codeMap] = await Promise.all([
6057
6311
  runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
6058
6312
  collectAnchoredMemories(ctx, input.path, input.memory_limit),
@@ -6093,8 +6347,8 @@ async function whyThisFile(input, ctx) {
6093
6347
  };
6094
6348
  }
6095
6349
  async function collectAnchoredMemories(ctx, filePath, limit) {
6096
- if (!existsSync23(ctx.paths.memoriesDir)) return [];
6097
- const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
6350
+ if (!existsSync24(ctx.paths.memoriesDir)) return [];
6351
+ const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
6098
6352
  const usage = await loadUsageIndex9(ctx.paths);
6099
6353
  const out = [];
6100
6354
  for (const { memory: memory2 } of all) {
@@ -6147,17 +6401,17 @@ function runCommand(cmd, args, cwd) {
6147
6401
  });
6148
6402
  }
6149
6403
  var AntiPatternsCheckInputSchema = {
6150
- diff: z25.string().optional().describe(
6404
+ diff: z26.string().optional().describe(
6151
6405
  "Raw unified diff text (or any code/text snippet) to scan for previously documented anti-patterns. Tokens from the diff are used to match memory bodies and the embeddings index."
6152
6406
  ),
6153
- paths: z25.array(z25.string()).default([]).describe(
6407
+ paths: z26.array(z26.string()).default([]).describe(
6154
6408
  "File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
6155
6409
  ),
6156
- limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
6157
- semantic: z25.boolean().default(true).describe(
6410
+ limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
6411
+ semantic: z26.boolean().default(true).describe(
6158
6412
  "When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
6159
6413
  ),
6160
- min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
6414
+ min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
6161
6415
  "Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
6162
6416
  )
6163
6417
  };
@@ -6178,10 +6432,10 @@ async function antiPatternsCheck(input, ctx) {
6178
6432
  notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
6179
6433
  };
6180
6434
  }
6181
- if (!existsSync24(ctx.paths.memoriesDir)) {
6435
+ if (!existsSync25(ctx.paths.memoriesDir)) {
6182
6436
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
6183
6437
  }
6184
- const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
6438
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
6185
6439
  const minSemanticScore = input.min_semantic_score ?? 0.45;
6186
6440
  const negative = all.filter(({ memory: memory2 }) => {
6187
6441
  const t = memory2.frontmatter.type;
@@ -6288,12 +6542,12 @@ async function antiPatternsCheck(input, ctx) {
6288
6542
  };
6289
6543
  }
6290
6544
  var MemDistillInputSchema = {
6291
- since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
6292
- min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
6293
- type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
6545
+ since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
6546
+ min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
6547
+ type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
6294
6548
  "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
6295
6549
  ),
6296
- scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
6550
+ scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
6297
6551
  };
6298
6552
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
6299
6553
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -6333,11 +6587,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
6333
6587
  "error"
6334
6588
  ]);
6335
6589
  async function memDistill(input, ctx) {
6336
- if (!existsSync25(ctx.paths.memoriesDir)) {
6590
+ if (!existsSync26(ctx.paths.memoriesDir)) {
6337
6591
  return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
6338
6592
  }
6339
6593
  const cutoff = Date.now() - input.since_days * MS_PER_DAY;
6340
- const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
6594
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
6341
6595
  const candidates = all.filter(({ memory: memory2 }) => {
6342
6596
  const fm = memory2.frontmatter;
6343
6597
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -6440,11 +6694,11 @@ function firstHeading(body) {
6440
6694
  return void 0;
6441
6695
  }
6442
6696
  var WhyThisDecisionInputSchema = {
6443
- id: z27.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
6444
- git_log_limit: z27.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
6697
+ id: z28.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
6698
+ git_log_limit: z28.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
6445
6699
  };
6446
6700
  async function whyThisDecision(input, ctx) {
6447
- if (!existsSync26(ctx.paths.memoriesDir)) {
6701
+ if (!existsSync27(ctx.paths.memoriesDir)) {
6448
6702
  return {
6449
6703
  found: false,
6450
6704
  related: [],
@@ -6453,7 +6707,7 @@ async function whyThisDecision(input, ctx) {
6453
6707
  notice: "No .ai/memories directory."
6454
6708
  };
6455
6709
  }
6456
- const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
6710
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
6457
6711
  const usage = await loadUsageIndex11(ctx.paths);
6458
6712
  const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
6459
6713
  if (!target) {
@@ -6575,17 +6829,17 @@ function runCommand2(cmd, args, cwd) {
6575
6829
  });
6576
6830
  }
6577
6831
  var MemConflictsInputSchema = {
6578
- id: z28.string().min(1).describe("Memory id to check for conflicts."),
6579
- min_score: z28.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
6580
- semantic: z28.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
6832
+ id: z29.string().min(1).describe("Memory id to check for conflicts."),
6833
+ min_score: z29.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
6834
+ semantic: z29.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
6581
6835
  };
6582
6836
  var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
6583
6837
  var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
6584
6838
  async function memConflicts(input, ctx) {
6585
- if (!existsSync27(ctx.paths.memoriesDir)) {
6839
+ if (!existsSync28(ctx.paths.memoriesDir)) {
6586
6840
  return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
6587
6841
  }
6588
- const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
6842
+ const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
6589
6843
  const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
6590
6844
  if (!target) {
6591
6845
  return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
@@ -6696,15 +6950,15 @@ async function trySemanticSimilarities(ctx, target, others) {
6696
6950
  return map;
6697
6951
  }
6698
6952
  var PreCommitCheckInputSchema = {
6699
- diff: z29.string().optional().describe(
6953
+ diff: z30.string().optional().describe(
6700
6954
  "Raw unified diff text to scan. If omitted, only `paths` is used. When called from a pre-commit hook, pipe the output of `git diff --cached`."
6701
6955
  ),
6702
- paths: z29.array(z29.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
6703
- block_on: z29.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
6956
+ paths: z30.array(z30.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
6957
+ block_on: z30.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
6704
6958
  "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
6705
6959
  ),
6706
- semantic: z29.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
6707
- anchored_blocks: z29.boolean().default(false).describe(
6960
+ semantic: z30.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
6961
+ anchored_blocks: z30.boolean().default(false).describe(
6708
6962
  "When true, ALSO block a high-confidence anti-pattern (attempt/gotcha) that is anchored to a touched file AND corroborated by the diff (literal token overlap, or semantic >= 0.45) \u2014 not just very strong semantic matches. Powers the 'anchored' enforcement gate. Config/docs-only commits are still downgraded. Default false preserves the soft, semantic-only blocking behavior."
6709
6963
  )
6710
6964
  };
@@ -7010,12 +7264,12 @@ var CONFIG_PATTERNS = [
7010
7264
  var MAX_DIFF_BYTES = 4096;
7011
7265
  var HOT_FILE_MIN = 3;
7012
7266
  var PatternDetectInputSchema = {
7013
- since_days: z30.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
7014
- dry_run: z30.boolean().default(false).describe("When true, report matches without writing any memory files."),
7015
- scope: z30.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
7267
+ since_days: z31.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
7268
+ dry_run: z31.boolean().default(false).describe("When true, report matches without writing any memory files."),
7269
+ scope: z31.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
7016
7270
  };
7017
7271
  async function patternDetect(input, ctx) {
7018
- if (!existsSync28(ctx.paths.haiveDir)) {
7272
+ if (!existsSync29(ctx.paths.haiveDir)) {
7019
7273
  return {
7020
7274
  scanned_events: 0,
7021
7275
  matches: [],
@@ -7028,13 +7282,13 @@ async function patternDetect(input, ctx) {
7028
7282
  try {
7029
7283
  const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
7030
7284
  const configFiles = changedFiles.filter(
7031
- (f) => CONFIG_PATTERNS.some((p) => path112.basename(f.toLowerCase()).includes(p))
7285
+ (f) => CONFIG_PATTERNS.some((p) => path122.basename(f.toLowerCase()).includes(p))
7032
7286
  );
7033
7287
  for (const file of configFiles.slice(0, 5)) {
7034
7288
  const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
7035
7289
  if (!diff) continue;
7036
- const parentDir = path112.basename(path112.dirname(file));
7037
- const baseName = path112.basename(file).replace(/\.[^.]+$/, "");
7290
+ const parentDir = path122.basename(path122.dirname(file));
7291
+ const baseName = path122.basename(file).replace(/\.[^.]+$/, "");
7038
7292
  const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
7039
7293
  matches.push({
7040
7294
  kind: "config_change",
@@ -7098,7 +7352,7 @@ async function patternDetect(input, ctx) {
7098
7352
  for (const [p, { count, tools }] of pathCounts) {
7099
7353
  if (count < HOT_FILE_MIN) continue;
7100
7354
  if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
7101
- if (CONFIG_PATTERNS.some((cp) => path112.basename(p).includes(cp))) continue;
7355
+ if (CONFIG_PATTERNS.some((cp) => path122.basename(p).includes(cp))) continue;
7102
7356
  const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
7103
7357
  matches.push({
7104
7358
  kind: "hot_file",
@@ -7138,17 +7392,17 @@ async function patternDetect(input, ctx) {
7138
7392
  paths: match.anchor_paths,
7139
7393
  status: "proposed"
7140
7394
  });
7141
- const file = memoryFilePath5(
7395
+ const file = memoryFilePath6(
7142
7396
  ctx.paths,
7143
7397
  fm.scope === "shared" ? "team" : fm.scope,
7144
7398
  fm.id,
7145
7399
  void 0
7146
7400
  );
7147
- if (existsSync28(file)) continue;
7148
- await mkdir72(path112.dirname(file), { recursive: true });
7149
- await writeFile12(
7401
+ if (existsSync29(file)) continue;
7402
+ await mkdir82(path122.dirname(file), { recursive: true });
7403
+ await writeFile13(
7150
7404
  file,
7151
- serializeMemory10({ frontmatter: fm, body: match.proposed_body }),
7405
+ serializeMemory11({ frontmatter: fm, body: match.proposed_body }),
7152
7406
  "utf8"
7153
7407
  );
7154
7408
  savedIds.push(fm.id);
@@ -7189,17 +7443,17 @@ function gitFileDiff(root, file, sinceDays) {
7189
7443
  }
7190
7444
  }
7191
7445
  var MemConflictCandidatesInputSchema = {
7192
- since_days: z31.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
7193
- types: z31.array(z31.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
7194
- min_jaccard: z31.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
7195
- max_pairs: z31.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
7196
- max_scan: z31.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
7197
- max_topic_pairs: z31.number().int().positive().max(100).default(20).describe(
7446
+ since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
7447
+ types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
7448
+ min_jaccard: z32.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
7449
+ max_pairs: z32.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
7450
+ max_scan: z32.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
7451
+ max_topic_pairs: z32.number().int().positive().max(100).default(20).describe(
7198
7452
  "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
7199
7453
  )
7200
7454
  };
7201
7455
  async function memConflictCandidates(input, ctx) {
7202
- if (!existsSync29(ctx.paths.memoriesDir)) {
7456
+ if (!existsSync30(ctx.paths.memoriesDir)) {
7203
7457
  return {
7204
7458
  pairs: [],
7205
7459
  topic_status_pairs: [],
@@ -7208,7 +7462,7 @@ async function memConflictCandidates(input, ctx) {
7208
7462
  notice: "No .ai/memories directory."
7209
7463
  };
7210
7464
  }
7211
- const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
7465
+ const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
7212
7466
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
7213
7467
  sinceDays: input.since_days,
7214
7468
  types: input.types,
@@ -7221,7 +7475,7 @@ async function memConflictCandidates(input, ctx) {
7221
7475
  return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
7222
7476
  }
7223
7477
  var MemResolveProjectInputSchema = {
7224
- cwd: z32.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
7478
+ cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
7225
7479
  };
7226
7480
  async function memResolveProject(input, _ctx) {
7227
7481
  void _ctx;
@@ -7234,7 +7488,7 @@ async function memResolveProject(input, _ctx) {
7234
7488
  }
7235
7489
  var MemSuggestTopicInputSchema = {
7236
7490
  type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
7237
- title: z33.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
7491
+ title: z34.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
7238
7492
  };
7239
7493
  async function memSuggestTopic(input, _ctx) {
7240
7494
  void _ctx;
@@ -7242,15 +7496,15 @@ async function memSuggestTopic(input, _ctx) {
7242
7496
  return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
7243
7497
  }
7244
7498
  var MemTimelineInputSchema = {
7245
- memory_id: z34.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
7246
- topic: z34.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
7247
- limit: z34.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
7499
+ memory_id: z35.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
7500
+ topic: z35.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
7501
+ limit: z35.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
7248
7502
  };
7249
7503
  async function memTimeline(input, ctx) {
7250
- if (!existsSync30(ctx.paths.memoriesDir)) {
7504
+ if (!existsSync31(ctx.paths.memoriesDir)) {
7251
7505
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
7252
7506
  }
7253
- const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
7507
+ const all = await loadMemoriesFromDir24(ctx.paths.memoriesDir);
7254
7508
  const { entries, notice } = collectTimelineEntries(all, {
7255
7509
  memoryId: input.memory_id,
7256
7510
  topic: input.topic,
@@ -7259,9 +7513,9 @@ async function memTimeline(input, ctx) {
7259
7513
  return { entries, total: entries.length, notice };
7260
7514
  }
7261
7515
  var RuntimeJournalAppendInputSchema = {
7262
- message: z35.string().min(1).describe("Short line to append to the runtime session journal"),
7263
- kind: z35.enum(["note", "session_end", "mcp"]).default("note"),
7264
- tool: z35.string().optional().describe("When kind=mcp, which tool name (optional)")
7516
+ message: z36.string().min(1).describe("Short line to append to the runtime session journal"),
7517
+ kind: z36.enum(["note", "session_end", "mcp"]).default("note"),
7518
+ tool: z36.string().optional().describe("When kind=mcp, which tool name (optional)")
7265
7519
  };
7266
7520
  async function runtimeJournalAppend(input, ctx) {
7267
7521
  await appendRuntimeJournalEntry2(ctx.paths, {
@@ -7275,7 +7529,7 @@ async function runtimeJournalAppend(input, ctx) {
7275
7529
  };
7276
7530
  }
7277
7531
  var RuntimeJournalTailInputSchema = {
7278
- limit: z36.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
7532
+ limit: z37.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
7279
7533
  };
7280
7534
  async function runtimeJournalTail(input, ctx) {
7281
7535
  const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
@@ -7285,10 +7539,10 @@ async function runtimeJournalTail(input, ctx) {
7285
7539
  return { entries };
7286
7540
  }
7287
7541
  var BootstrapProjectArgsSchema = {
7288
- module: z37.string().optional().describe(
7542
+ module: z38.string().optional().describe(
7289
7543
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
7290
7544
  ),
7291
- focus: z37.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
7545
+ focus: z38.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
7292
7546
  };
7293
7547
  var ROOT_TEMPLATE = `# Project context
7294
7548
 
@@ -7369,8 +7623,8 @@ ${template}\`\`\`
7369
7623
  };
7370
7624
  }
7371
7625
  var PostTaskArgsSchema = {
7372
- task_summary: z38.string().optional().describe("One sentence describing what you just did"),
7373
- files_touched: z38.array(z38.string()).optional().describe("Files you created or modified during the task")
7626
+ task_summary: z39.string().optional().describe("One sentence describing what you just did"),
7627
+ files_touched: z39.array(z39.string()).optional().describe("Files you created or modified during the task")
7374
7628
  };
7375
7629
  function postTaskPrompt(args, ctx) {
7376
7630
  const taskLine = args.task_summary ? `
@@ -7440,7 +7694,7 @@ This creates/updates a single rolling recap that **get_briefing automatically su
7440
7694
 
7441
7695
  Calling \`mem_session_end\` also **clears the pending-distill marker** (if any), confirming that this session's learnings have been properly captured rather than left as an auto-recap skeleton.
7442
7696
 
7443
- ### 7. Verify the git/release exit protocol \u2014 always
7697
+ ### 7. Verify the git/release/pipeline exit protocol \u2014 always
7444
7698
  Run **\`haive enforce finish\`** before your final response.
7445
7699
 
7446
7700
  This executable gate checks the multi-agent git-sync decision:
@@ -7448,11 +7702,12 @@ This executable gate checks the multi-agent git-sync decision:
7448
7702
  - shippable package changes have a lockstep version bump
7449
7703
  - the release tag \`vX.Y.Z\` exists when a version was bumped
7450
7704
  - commits and tags have been pushed
7705
+ - the pushed HEAD's GitHub Actions workflow runs have completed successfully when the repo has a GitHub remote
7451
7706
  - agents never run \`npm publish\` (publication remains human-owned)
7452
7707
 
7453
- If it blocks, fix the reported Git/version/tag/push issue before telling the developer the task is done.
7708
+ If it blocks, fix the reported Git/version/tag/push/pipeline issue before telling the developer the task is done.
7454
7709
 
7455
- When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed."
7710
+ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed; GitHub Actions passed when applicable."
7456
7711
  `;
7457
7712
  return {
7458
7713
  description: "Post-task reflection: capture what you learned before closing the session",
@@ -7465,10 +7720,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
7465
7720
  };
7466
7721
  }
7467
7722
  var ImportDocsArgsSchema = {
7468
- content: z39.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
7469
- source: z39.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
7470
- scope: z39.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
7471
- dry_run: z39.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
7723
+ content: z40.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
7724
+ source: z40.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
7725
+ scope: z40.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
7726
+ dry_run: z40.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
7472
7727
  };
7473
7728
  function importDocsPrompt(args, ctx) {
7474
7729
  const sourceLine = args.source ? `
@@ -7531,7 +7786,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7531
7786
  };
7532
7787
  }
7533
7788
  var SERVER_NAME = "haive";
7534
- var SERVER_VERSION = "0.12.3";
7789
+ var SERVER_VERSION = "0.12.9";
7535
7790
  function jsonResult(data) {
7536
7791
  return {
7537
7792
  content: [
@@ -7574,7 +7829,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
7574
7829
  "mem_distill",
7575
7830
  "mem_timeline",
7576
7831
  "mem_conflict_candidates",
7577
- "mem_feedback"
7832
+ "mem_feedback",
7833
+ "ingest_findings"
7578
7834
  ];
7579
7835
  var EXPERIMENTAL_PROFILE_TOOLS = [
7580
7836
  ...MAINTENANCE_PROFILE_TOOLS,
@@ -7608,7 +7864,8 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
7608
7864
  "mem_delete",
7609
7865
  "mem_feedback",
7610
7866
  "runtime_journal_append",
7611
- "pattern_detect"
7867
+ "pattern_detect",
7868
+ "ingest_findings"
7612
7869
  ]);
7613
7870
  function createHaiveServer(options = {}) {
7614
7871
  const context = createContext(options);
@@ -7733,6 +7990,36 @@ function createHaiveServer(options = {}) {
7733
7990
  return jsonResult(await memTried(input, context));
7734
7991
  }
7735
7992
  );
7993
+ registerTool(
7994
+ "ingest_findings",
7995
+ [
7996
+ "Turn scanner findings (SonarQube / SARIF) into proposed, anchored memories with sensors.",
7997
+ "",
7998
+ "USE THIS to seed hAIve from your existing quality tooling: each real defect a scanner",
7999
+ "found becomes a `gotcha`/`convention` memory anchored to the file, pre-filled with a",
8000
+ "conservative `warn` sensor \u2014 so the next agent is steered away from it before re-writing it.",
8001
+ "This closes the review\u2194memory loop and kills the cold-start problem.",
8002
+ "",
8003
+ "SAFETY: drafts are status=proposed and sensors are warn-only + autogen. This tool NEVER",
8004
+ "auto-validates and NEVER auto-blocks. A human reviews (mem_pending) and promotes the sensor.",
8005
+ "",
8006
+ "PARAMETERS:",
8007
+ " format \u2014 'sarif' (ESLint/Semgrep/CodeQL) | 'sonar' (SonarQube issues JSON)",
8008
+ " report_path \u2014 project-relative path to the report file (OR pass `report` inline)",
8009
+ " report \u2014 inline JSON content (OR pass `report_path`)",
8010
+ " type \u2014 gotcha (default) | convention",
8011
+ " scope \u2014 team (default) | personal | module",
8012
+ " min_severity \u2014 drop findings below this severity",
8013
+ " dry_run \u2014 preview what would be created without writing",
8014
+ "",
8015
+ "RETURNS: { format, parsed, new, skipped_existing, created[], notice }"
8016
+ ].join("\n"),
8017
+ IngestFindingsInputSchema,
8018
+ async (input) => {
8019
+ tracker.record("ingest_findings", `${input.format}:${input.report_path ?? "inline"}`);
8020
+ return jsonResult(await ingestFindings(input, context));
8021
+ }
8022
+ );
7736
8023
  registerTool(
7737
8024
  "mem_observe",
7738
8025
  [
@@ -8529,8 +8816,8 @@ function registerMcp(program2) {
8529
8816
 
8530
8817
  // src/commands/sync.ts
8531
8818
  import { spawnSync as spawnSync4 } from "child_process";
8532
- import { readFile as readFile9, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
8533
- import { existsSync as existsSync31 } from "fs";
8819
+ import { readFile as readFile9, writeFile as writeFile14, mkdir as mkdir10 } from "fs/promises";
8820
+ import { existsSync as existsSync33 } from "fs";
8534
8821
  import path15 from "path";
8535
8822
  import "commander";
8536
8823
  import {
@@ -8543,12 +8830,12 @@ import {
8543
8830
  isStackPackSeed as isStackPackSeed3,
8544
8831
  loadCodeMap as loadCodeMap6,
8545
8832
  loadConfig as loadConfig4,
8546
- loadMemoriesFromDir as loadMemoriesFromDir24,
8833
+ loadMemoriesFromDir as loadMemoriesFromDir25,
8547
8834
  loadUsageIndex as loadUsageIndex13,
8548
8835
  pullCrossRepoSources,
8549
8836
  resolveHaivePaths as resolveHaivePaths9,
8550
8837
  resolveManifestFiles,
8551
- serializeMemory as serializeMemory11,
8838
+ serializeMemory as serializeMemory12,
8552
8839
  trackDependencies,
8553
8840
  verifyAnchor as verifyAnchor2,
8554
8841
  watchContracts
@@ -8563,11 +8850,11 @@ function registerSync(program2) {
8563
8850
  "git ref/commit to compare against; report memories added/modified/removed since"
8564
8851
  ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
8565
8852
  "--inject-bridge",
8566
- "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
8853
+ "inject top validated memories into CLAUDE.md + AGENTS.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
8567
8854
  ).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) => {
8568
8855
  const root = findProjectRoot12(opts.dir);
8569
8856
  const paths = resolveHaivePaths9(root);
8570
- if (!existsSync31(paths.memoriesDir)) {
8857
+ if (!existsSync33(paths.memoriesDir)) {
8571
8858
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8572
8859
  process.exitCode = 1;
8573
8860
  return;
@@ -8586,14 +8873,14 @@ function registerSync(program2) {
8586
8873
  let promoted = 0;
8587
8874
  let autoApproved = 0;
8588
8875
  if (opts.verify !== false) {
8589
- const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8876
+ const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8590
8877
  for (const { memory: memory2, filePath } of memories) {
8591
8878
  if (memory2.frontmatter.type === "session_recap") {
8592
8879
  if (memory2.frontmatter.status === "stale") {
8593
8880
  if (!dryRun) {
8594
- await writeFile13(
8881
+ await writeFile14(
8595
8882
  filePath,
8596
- serializeMemory11({
8883
+ serializeMemory12({
8597
8884
  frontmatter: {
8598
8885
  ...memory2.frontmatter,
8599
8886
  status: "validated",
@@ -8616,9 +8903,9 @@ function registerSync(program2) {
8616
8903
  if (result.stale) {
8617
8904
  if (memory2.frontmatter.status !== "stale") {
8618
8905
  if (!dryRun) {
8619
- await writeFile13(
8906
+ await writeFile14(
8620
8907
  filePath,
8621
- serializeMemory11({
8908
+ serializeMemory12({
8622
8909
  frontmatter: {
8623
8910
  ...memory2.frontmatter,
8624
8911
  status: "stale",
@@ -8634,9 +8921,9 @@ function registerSync(program2) {
8634
8921
  }
8635
8922
  } else if (memory2.frontmatter.status === "stale") {
8636
8923
  if (!dryRun) {
8637
- await writeFile13(
8924
+ await writeFile14(
8638
8925
  filePath,
8639
- serializeMemory11({
8926
+ serializeMemory12({
8640
8927
  frontmatter: {
8641
8928
  ...memory2.frontmatter,
8642
8929
  status: "validated",
@@ -8653,7 +8940,7 @@ function registerSync(program2) {
8653
8940
  }
8654
8941
  }
8655
8942
  if (opts.promote !== false) {
8656
- const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8943
+ const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8657
8944
  const usage = await loadUsageIndex13(paths);
8658
8945
  const nowMs = Date.now();
8659
8946
  for (const { memory: memory2, filePath } of memories) {
@@ -8664,9 +8951,9 @@ function registerSync(program2) {
8664
8951
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
8665
8952
  })) {
8666
8953
  if (!dryRun) {
8667
- await writeFile13(
8954
+ await writeFile14(
8668
8955
  filePath,
8669
- serializeMemory11({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8956
+ serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8670
8957
  "utf8"
8671
8958
  );
8672
8959
  }
@@ -8677,9 +8964,9 @@ function registerSync(program2) {
8677
8964
  const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
8678
8965
  if (ageHours >= autoApproveDelayHours) {
8679
8966
  if (!dryRun) {
8680
- await writeFile13(
8967
+ await writeFile14(
8681
8968
  filePath,
8682
- serializeMemory11({
8969
+ serializeMemory12({
8683
8970
  frontmatter: {
8684
8971
  ...fm,
8685
8972
  status: "validated",
@@ -8705,7 +8992,7 @@ function registerSync(program2) {
8705
8992
  for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
8706
8993
  }
8707
8994
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
8708
- const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
8995
+ const draftMemories = (await loadMemoriesFromDir25(paths.memoriesDir)).filter(
8709
8996
  (m) => m.memory.frontmatter.status === "draft"
8710
8997
  );
8711
8998
  const draftCount = draftMemories.length;
@@ -8721,9 +9008,18 @@ function registerSync(program2) {
8721
9008
  );
8722
9009
  }
8723
9010
  if (opts.injectBridge) {
8724
- const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
8725
9011
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
8726
- await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
9012
+ let bridgeTargets;
9013
+ if (opts.bridgeFile) {
9014
+ bridgeTargets = [path15.resolve(opts.bridgeFile)];
9015
+ } else {
9016
+ const agentsMd = path15.join(root, "AGENTS.md");
9017
+ bridgeTargets = [path15.join(root, "CLAUDE.md")];
9018
+ if (existsSync33(agentsMd)) bridgeTargets.push(agentsMd);
9019
+ }
9020
+ for (const bridgeFile of bridgeTargets) {
9021
+ await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
9022
+ }
8727
9023
  }
8728
9024
  if (sinceReport && !opts.quiet) {
8729
9025
  if (sinceReport.added.length > 0) {
@@ -8740,7 +9036,7 @@ function registerSync(program2) {
8740
9036
  }
8741
9037
  }
8742
9038
  if (!opts.quiet) {
8743
- const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
9039
+ const allForDecay = await loadMemoriesFromDir25(paths.memoriesDir);
8744
9040
  const usageForDecay = await loadUsageIndex13(paths);
8745
9041
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
8746
9042
  const fm = memory2.frontmatter;
@@ -8830,9 +9126,9 @@ Attends une **confirmation explicite** avant d'agir.
8830
9126
  if (!dryRun) {
8831
9127
  const teamDir = path15.join(paths.memoriesDir, "team");
8832
9128
  await mkdir10(teamDir, { recursive: true });
8833
- await writeFile13(
9129
+ await writeFile14(
8834
9130
  path15.join(teamDir, `${fm.id}.md`),
8835
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
9131
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8836
9132
  "utf8"
8837
9133
  );
8838
9134
  }
@@ -8899,9 +9195,9 @@ Attends une **confirmation explicite** avant d'agir.
8899
9195
  if (!dryRun) {
8900
9196
  const teamDir = path15.join(paths.memoriesDir, "team");
8901
9197
  await mkdir10(teamDir, { recursive: true });
8902
- await writeFile13(
9198
+ await writeFile14(
8903
9199
  path15.join(teamDir, `${fm.id}.md`),
8904
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
9200
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8905
9201
  "utf8"
8906
9202
  );
8907
9203
  }
@@ -8991,8 +9287,8 @@ function bridgeSummaryLine(body) {
8991
9287
  return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
8992
9288
  }
8993
9289
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8994
- if (!existsSync31(memoriesDir)) return;
8995
- const all = await loadMemoriesFromDir24(memoriesDir);
9290
+ if (!existsSync33(memoriesDir)) return;
9291
+ const all = await loadMemoriesFromDir25(memoriesDir);
8996
9292
  const top = all.filter(({ memory: memory2 }) => {
8997
9293
  const s = memory2.frontmatter.status;
8998
9294
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -9017,7 +9313,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
9017
9313
  ` + block + `
9018
9314
 
9019
9315
  ${BRIDGE_END}`;
9020
- const fileExists = existsSync31(bridgeFile);
9316
+ const fileExists = existsSync33(bridgeFile);
9021
9317
  let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
9022
9318
  existing = existing.replace(/\r\n/g, "\n");
9023
9319
  const startIdx = existing.indexOf(BRIDGE_START);
@@ -9039,7 +9335,7 @@ ${BRIDGE_END}`;
9039
9335
  }
9040
9336
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
9041
9337
  }
9042
- await writeFile13(bridgeFile, updated, "utf8");
9338
+ await writeFile14(bridgeFile, updated, "utf8");
9043
9339
  if (!quiet) {
9044
9340
  console.log(
9045
9341
  ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
@@ -9067,8 +9363,8 @@ function collectSinceChanges(root, ref) {
9067
9363
 
9068
9364
  // src/commands/memory-add.ts
9069
9365
  import { createHash as createHash2 } from "crypto";
9070
- import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
9071
- import { existsSync as existsSync33 } from "fs";
9366
+ import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile15 } from "fs/promises";
9367
+ import { existsSync as existsSync34 } from "fs";
9072
9368
  import path16 from "path";
9073
9369
  import "commander";
9074
9370
  import {
@@ -9076,10 +9372,10 @@ import {
9076
9372
  findProjectRoot as findProjectRoot13,
9077
9373
  inferModulesFromPaths as inferModulesFromPaths3,
9078
9374
  loadConfig as loadConfig5,
9079
- loadMemoriesFromDir as loadMemoriesFromDir25,
9080
- memoryFilePath as memoryFilePath6,
9375
+ loadMemoriesFromDir as loadMemoriesFromDir26,
9376
+ memoryFilePath as memoryFilePath7,
9081
9377
  resolveHaivePaths as resolveHaivePaths10,
9082
- serializeMemory as serializeMemory12,
9378
+ serializeMemory as serializeMemory13,
9083
9379
  suggestSensorFromMemory as suggestSensorFromMemory3
9084
9380
  } from "@hiveai/core";
9085
9381
  function registerMemoryAdd(memory2) {
@@ -9110,7 +9406,7 @@ function registerMemoryAdd(memory2) {
9110
9406
  ).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").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("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").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("--activation-keyword <csv>", "skill only: comma-separated keywords that trigger progressive disclosure of this skill").option("--activation-glob <csv>", "skill only: comma-separated path globs that trigger this skill").option("--activation-always", "skill only: always surface this skill (no triggers needed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9111
9407
  const root = findProjectRoot13(opts.dir);
9112
9408
  const paths = resolveHaivePaths10(root);
9113
- if (!existsSync33(paths.haiveDir)) {
9409
+ if (!existsSync34(paths.haiveDir)) {
9114
9410
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9115
9411
  process.exitCode = 1;
9116
9412
  return;
@@ -9127,7 +9423,7 @@ function registerMemoryAdd(memory2) {
9127
9423
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
9128
9424
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
9129
9425
  if (anchorPaths.length > 0) {
9130
- const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
9426
+ const missing = anchorPaths.filter((p) => !existsSync34(path16.resolve(root, p)));
9131
9427
  if (missing.length > 0) {
9132
9428
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
9133
9429
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -9140,7 +9436,7 @@ function registerMemoryAdd(memory2) {
9140
9436
  const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
9141
9437
  let body;
9142
9438
  if (opts.bodyFile !== void 0) {
9143
- if (!existsSync33(opts.bodyFile)) {
9439
+ if (!existsSync34(opts.bodyFile)) {
9144
9440
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9145
9441
  process.exitCode = 1;
9146
9442
  return;
@@ -9156,9 +9452,9 @@ TODO \u2014 write the memory body.
9156
9452
  `;
9157
9453
  }
9158
9454
  const scope = opts.scope ?? config.defaultScope ?? "personal";
9159
- if (existsSync33(paths.memoriesDir)) {
9455
+ if (existsSync34(paths.memoriesDir)) {
9160
9456
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
9161
- const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
9457
+ const allForHash = await loadMemoriesFromDir26(paths.memoriesDir);
9162
9458
  const hashDup = allForHash.find(
9163
9459
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
9164
9460
  );
@@ -9169,8 +9465,8 @@ TODO \u2014 write the memory body.
9169
9465
  return;
9170
9466
  }
9171
9467
  }
9172
- if (opts.topic && existsSync33(paths.memoriesDir)) {
9173
- const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9468
+ if (opts.topic && existsSync34(paths.memoriesDir)) {
9469
+ const existing = await loadMemoriesFromDir26(paths.memoriesDir);
9174
9470
  const topicMatch = existing.find(
9175
9471
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
9176
9472
  );
@@ -9190,7 +9486,7 @@ TODO \u2014 write the memory body.
9190
9486
  };
9191
9487
  const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
9192
9488
  if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
9193
- await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
9489
+ await writeFile15(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
9194
9490
  ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
9195
9491
  ui.info(`id=${fm.id} revision=${revisionCount}`);
9196
9492
  if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
@@ -9214,15 +9510,15 @@ TODO \u2014 write the memory body.
9214
9510
  sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
9215
9511
  activation
9216
9512
  });
9217
- const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9513
+ const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9218
9514
  await mkdir11(path16.dirname(file), { recursive: true });
9219
- if (existsSync33(file)) {
9515
+ if (existsSync34(file)) {
9220
9516
  ui.error(`Memory already exists at ${file}`);
9221
9517
  process.exitCode = 1;
9222
9518
  return;
9223
9519
  }
9224
- if (existsSync33(paths.memoriesDir)) {
9225
- const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9520
+ if (existsSync34(paths.memoriesDir)) {
9521
+ const existing = await loadMemoriesFromDir26(paths.memoriesDir);
9226
9522
  const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
9227
9523
  const similar = existing.filter(({ memory: memory3 }) => {
9228
9524
  const id = memory3.frontmatter.id.toLowerCase();
@@ -9233,7 +9529,7 @@ TODO \u2014 write the memory body.
9233
9529
  ui.warn("Consider updating one of these with `haive memory update` instead.");
9234
9530
  }
9235
9531
  }
9236
- await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
9532
+ await writeFile15(file, serializeMemory13({ frontmatter, body }), "utf8");
9237
9533
  ui.success(`Created ${path16.relative(root, file)}`);
9238
9534
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
9239
9535
  if (frontmatter.sensor?.autogen) {
@@ -9313,14 +9609,14 @@ function slugify(value) {
9313
9609
  }
9314
9610
 
9315
9611
  // src/commands/memory-list.ts
9316
- import { existsSync as existsSync34 } from "fs";
9612
+ import { existsSync as existsSync35 } from "fs";
9317
9613
  import path17 from "path";
9318
9614
  import "commander";
9319
9615
  import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
9320
9616
 
9321
9617
  // src/utils/fs.ts
9322
9618
  import {
9323
- loadMemoriesFromDir as loadMemoriesFromDir26,
9619
+ loadMemoriesFromDir as loadMemoriesFromDir27,
9324
9620
  loadMemory,
9325
9621
  listMarkdownFilesRecursive
9326
9622
  } from "@hiveai/core";
@@ -9330,12 +9626,12 @@ function registerMemoryList(memory2) {
9330
9626
  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) => {
9331
9627
  const root = findProjectRoot14(opts.dir);
9332
9628
  const paths = resolveHaivePaths11(root);
9333
- if (!existsSync34(paths.memoriesDir)) {
9629
+ if (!existsSync35(paths.memoriesDir)) {
9334
9630
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9335
9631
  process.exitCode = 1;
9336
9632
  return;
9337
9633
  }
9338
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
9634
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9339
9635
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
9340
9636
  const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
9341
9637
  const filtered = all.filter((m) => {
@@ -9404,26 +9700,26 @@ function matchesFilters(loaded, opts) {
9404
9700
  }
9405
9701
 
9406
9702
  // src/commands/memory-promote.ts
9407
- import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
9408
- import { existsSync as existsSync35 } from "fs";
9703
+ import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile16 } from "fs/promises";
9704
+ import { existsSync as existsSync36 } from "fs";
9409
9705
  import path18 from "path";
9410
9706
  import "commander";
9411
9707
  import {
9412
9708
  findProjectRoot as findProjectRoot15,
9413
- memoryFilePath as memoryFilePath7,
9709
+ memoryFilePath as memoryFilePath8,
9414
9710
  resolveHaivePaths as resolveHaivePaths12,
9415
- serializeMemory as serializeMemory13
9711
+ serializeMemory as serializeMemory14
9416
9712
  } from "@hiveai/core";
9417
9713
  function registerMemoryPromote(memory2) {
9418
9714
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9419
9715
  const root = findProjectRoot15(opts.dir);
9420
9716
  const paths = resolveHaivePaths12(root);
9421
- if (!existsSync35(paths.memoriesDir)) {
9717
+ if (!existsSync36(paths.memoriesDir)) {
9422
9718
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9423
9719
  process.exitCode = 1;
9424
9720
  return;
9425
9721
  }
9426
- const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
9722
+ const teamAndModule = await loadMemoriesFromDir27(paths.memoriesDir);
9427
9723
  const alreadyShared = teamAndModule.find(
9428
9724
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
9429
9725
  );
@@ -9437,7 +9733,7 @@ function registerMemoryPromote(memory2) {
9437
9733
  }
9438
9734
  return;
9439
9735
  }
9440
- const all = await loadMemoriesFromDir26(paths.personalDir);
9736
+ const all = await loadMemoriesFromDir27(paths.personalDir);
9441
9737
  const found = all.find((m) => m.memory.frontmatter.id === id);
9442
9738
  if (!found) {
9443
9739
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -9452,9 +9748,9 @@ function registerMemoryPromote(memory2) {
9452
9748
  },
9453
9749
  body: found.memory.body
9454
9750
  };
9455
- const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
9751
+ const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
9456
9752
  await mkdir12(path18.dirname(newPath), { recursive: true });
9457
- await writeFile15(newPath, serializeMemory13(updated), "utf8");
9753
+ await writeFile16(newPath, serializeMemory14(updated), "utf8");
9458
9754
  await unlink2(found.filePath);
9459
9755
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
9460
9756
  ui.info(`Now at ${path18.relative(root, newPath)}`);
@@ -9463,25 +9759,25 @@ function registerMemoryPromote(memory2) {
9463
9759
  }
9464
9760
 
9465
9761
  // src/commands/memory-approve.ts
9466
- import { existsSync as existsSync36 } from "fs";
9467
- import { writeFile as writeFile16 } from "fs/promises";
9762
+ import { existsSync as existsSync37 } from "fs";
9763
+ import { writeFile as writeFile17 } from "fs/promises";
9468
9764
  import path19 from "path";
9469
9765
  import "commander";
9470
9766
  import {
9471
9767
  findProjectRoot as findProjectRoot16,
9472
9768
  resolveHaivePaths as resolveHaivePaths13,
9473
- serializeMemory as serializeMemory14
9769
+ serializeMemory as serializeMemory15
9474
9770
  } from "@hiveai/core";
9475
9771
  function registerMemoryApprove(memory2) {
9476
9772
  memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9477
9773
  const root = findProjectRoot16(opts.dir);
9478
9774
  const paths = resolveHaivePaths13(root);
9479
- if (!existsSync36(paths.memoriesDir)) {
9775
+ if (!existsSync37(paths.memoriesDir)) {
9480
9776
  ui.error(`No .ai/memories at ${root}.`);
9481
9777
  process.exitCode = 1;
9482
9778
  return;
9483
9779
  }
9484
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
9780
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9485
9781
  if (opts.all || opts.pending) {
9486
9782
  const candidates = all.filter((m) => {
9487
9783
  const s = m.memory.frontmatter.status;
@@ -9498,7 +9794,7 @@ function registerMemoryApprove(memory2) {
9498
9794
  frontmatter: { ...found2.memory.frontmatter, status: "validated" },
9499
9795
  body: found2.memory.body
9500
9796
  };
9501
- await writeFile16(found2.filePath, serializeMemory14(next2), "utf8");
9797
+ await writeFile17(found2.filePath, serializeMemory15(next2), "utf8");
9502
9798
  count++;
9503
9799
  }
9504
9800
  ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
@@ -9527,32 +9823,32 @@ function registerMemoryApprove(memory2) {
9527
9823
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
9528
9824
  body: found.memory.body
9529
9825
  };
9530
- await writeFile16(found.filePath, serializeMemory14(next), "utf8");
9826
+ await writeFile17(found.filePath, serializeMemory15(next), "utf8");
9531
9827
  ui.success(`Approved ${id} (status=validated)`);
9532
9828
  ui.info(path19.relative(root, found.filePath));
9533
9829
  });
9534
9830
  }
9535
9831
 
9536
9832
  // src/commands/memory-update.ts
9537
- import { readFile as readFile11, writeFile as writeFile17 } from "fs/promises";
9538
- import { existsSync as existsSync37 } from "fs";
9833
+ import { readFile as readFile11, writeFile as writeFile18 } from "fs/promises";
9834
+ import { existsSync as existsSync38 } from "fs";
9539
9835
  import path20 from "path";
9540
9836
  import "commander";
9541
9837
  import {
9542
9838
  findProjectRoot as findProjectRoot17,
9543
9839
  resolveHaivePaths as resolveHaivePaths14,
9544
- serializeMemory as serializeMemory15
9840
+ serializeMemory as serializeMemory16
9545
9841
  } from "@hiveai/core";
9546
9842
  function registerMemoryUpdate(memory2) {
9547
9843
  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("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").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) => {
9548
9844
  const root = findProjectRoot17(opts.dir);
9549
9845
  const paths = resolveHaivePaths14(root);
9550
- if (!existsSync37(paths.memoriesDir)) {
9846
+ if (!existsSync38(paths.memoriesDir)) {
9551
9847
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9552
9848
  process.exitCode = 1;
9553
9849
  return;
9554
9850
  }
9555
- const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9851
+ const memories = await loadMemoriesFromDir27(paths.memoriesDir);
9556
9852
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
9557
9853
  if (!loaded) {
9558
9854
  ui.error(`No memory with id "${id}".`);
@@ -9589,7 +9885,7 @@ function registerMemoryUpdate(memory2) {
9589
9885
  if (opts.author !== void 0) updated.push("author");
9590
9886
  let newBody;
9591
9887
  if (opts.bodyFile !== void 0) {
9592
- if (!existsSync37(opts.bodyFile)) {
9888
+ if (!existsSync38(opts.bodyFile)) {
9593
9889
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9594
9890
  process.exitCode = 1;
9595
9891
  return;
@@ -9610,9 +9906,9 @@ function registerMemoryUpdate(memory2) {
9610
9906
  ui.warn("Nothing to update \u2014 provide at least one option.");
9611
9907
  return;
9612
9908
  }
9613
- await writeFile17(
9909
+ await writeFile18(
9614
9910
  loaded.filePath,
9615
- serializeMemory15({ frontmatter: newFrontmatter, body: newBody }),
9911
+ serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
9616
9912
  "utf8"
9617
9913
  );
9618
9914
  ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
@@ -9634,8 +9930,8 @@ function parseCsv3(value) {
9634
9930
  }
9635
9931
 
9636
9932
  // src/commands/memory-auto-promote.ts
9637
- import { writeFile as writeFile18 } from "fs/promises";
9638
- import { existsSync as existsSync38 } from "fs";
9933
+ import { writeFile as writeFile19 } from "fs/promises";
9934
+ import { existsSync as existsSync39 } from "fs";
9639
9935
  import path21 from "path";
9640
9936
  import "commander";
9641
9937
  import {
@@ -9645,7 +9941,7 @@ import {
9645
9941
  isAutoPromoteEligible as isAutoPromoteEligible3,
9646
9942
  loadUsageIndex as loadUsageIndex14,
9647
9943
  resolveHaivePaths as resolveHaivePaths15,
9648
- serializeMemory as serializeMemory16
9944
+ serializeMemory as serializeMemory17
9649
9945
  } from "@hiveai/core";
9650
9946
  function registerMemoryAutoPromote(memory2) {
9651
9947
  memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE3.minReads)).option(
@@ -9655,7 +9951,7 @@ function registerMemoryAutoPromote(memory2) {
9655
9951
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9656
9952
  const root = findProjectRoot18(opts.dir);
9657
9953
  const paths = resolveHaivePaths15(root);
9658
- if (!existsSync38(paths.memoriesDir)) {
9954
+ if (!existsSync39(paths.memoriesDir)) {
9659
9955
  ui.error(`No .ai/memories at ${root}.`);
9660
9956
  process.exitCode = 1;
9661
9957
  return;
@@ -9664,7 +9960,7 @@ function registerMemoryAutoPromote(memory2) {
9664
9960
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
9665
9961
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
9666
9962
  };
9667
- const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9963
+ const memories = await loadMemoriesFromDir27(paths.memoriesDir);
9668
9964
  const usage = await loadUsageIndex14(paths);
9669
9965
  const eligible = memories.filter(
9670
9966
  ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
@@ -9687,7 +9983,7 @@ function registerMemoryAutoPromote(memory2) {
9687
9983
  frontmatter: { ...mem.frontmatter, status: "validated" },
9688
9984
  body: mem.body
9689
9985
  };
9690
- await writeFile18(filePath, serializeMemory16(next), "utf8");
9986
+ await writeFile19(filePath, serializeMemory17(next), "utf8");
9691
9987
  written++;
9692
9988
  }
9693
9989
  }
@@ -9698,7 +9994,7 @@ function registerMemoryAutoPromote(memory2) {
9698
9994
 
9699
9995
  // src/commands/memory-edit.ts
9700
9996
  import { spawn as spawn3 } from "child_process";
9701
- import { existsSync as existsSync39 } from "fs";
9997
+ import { existsSync as existsSync40 } from "fs";
9702
9998
  import { readFile as readFile12 } from "fs/promises";
9703
9999
  import path23 from "path";
9704
10000
  import "commander";
@@ -9711,12 +10007,12 @@ function registerMemoryEdit(memory2) {
9711
10007
  memory2.command("edit <id>").description("Open a memory in $EDITOR and re-validate when you save").option("-e, --editor <cmd>", "editor command (defaults to $EDITOR or 'vi')").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9712
10008
  const root = findProjectRoot19(opts.dir);
9713
10009
  const paths = resolveHaivePaths16(root);
9714
- if (!existsSync39(paths.memoriesDir)) {
10010
+ if (!existsSync40(paths.memoriesDir)) {
9715
10011
  ui.error(`No .ai/memories at ${root}.`);
9716
10012
  process.exitCode = 1;
9717
10013
  return;
9718
10014
  }
9719
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10015
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9720
10016
  const found = all.find((m) => m.memory.frontmatter.id === id);
9721
10017
  if (!found) {
9722
10018
  ui.error(`No memory with id "${id}".`);
@@ -9751,7 +10047,7 @@ function runEditor(editor, file) {
9751
10047
  }
9752
10048
 
9753
10049
  // src/commands/memory-for-files.ts
9754
- import { existsSync as existsSync40 } from "fs";
10050
+ import { existsSync as existsSync41 } from "fs";
9755
10051
  import path24 from "path";
9756
10052
  import "commander";
9757
10053
  import {
@@ -9767,12 +10063,12 @@ function registerMemoryForFiles(memory2) {
9767
10063
  memory2.command("for-files <files...>").description("Show memories relevant to the given files (anchor overlap, module, domain)").option("-d, --dir <dir>", "project root").action(async (files, opts) => {
9768
10064
  const root = findProjectRoot20(opts.dir);
9769
10065
  const paths = resolveHaivePaths17(root);
9770
- if (!existsSync40(paths.memoriesDir)) {
10066
+ if (!existsSync41(paths.memoriesDir)) {
9771
10067
  ui.error(`No .ai/memories at ${root}.`);
9772
10068
  process.exitCode = 1;
9773
10069
  return;
9774
10070
  }
9775
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10071
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9776
10072
  const usage = await loadUsageIndex15(paths);
9777
10073
  const inferred = inferModulesFromPaths4(files);
9778
10074
  const byAnchor = [];
@@ -9879,7 +10175,7 @@ function printGroup(root, label, loaded, usage) {
9879
10175
  }
9880
10176
 
9881
10177
  // src/commands/memory-hot.ts
9882
- import { existsSync as existsSync41 } from "fs";
10178
+ import { existsSync as existsSync43 } from "fs";
9883
10179
  import path25 from "path";
9884
10180
  import "commander";
9885
10181
  import {
@@ -9894,13 +10190,13 @@ function registerMemoryHot(memory2) {
9894
10190
  ).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) => {
9895
10191
  const root = findProjectRoot21(opts.dir);
9896
10192
  const paths = resolveHaivePaths18(root);
9897
- if (!existsSync41(paths.memoriesDir)) {
10193
+ if (!existsSync43(paths.memoriesDir)) {
9898
10194
  ui.error(`No .ai/memories at ${root}.`);
9899
10195
  process.exitCode = 1;
9900
10196
  return;
9901
10197
  }
9902
10198
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
9903
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10199
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9904
10200
  const usage = await loadUsageIndex16(paths);
9905
10201
  const candidates = all.filter(({ memory: mem }) => {
9906
10202
  const fm = mem.frontmatter;
@@ -9932,16 +10228,16 @@ function registerMemoryHot(memory2) {
9932
10228
  }
9933
10229
 
9934
10230
  // src/commands/memory-tried.ts
9935
- import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
9936
- import { existsSync as existsSync43 } from "fs";
10231
+ import { mkdir as mkdir13, writeFile as writeFile20 } from "fs/promises";
10232
+ import { existsSync as existsSync44 } from "fs";
9937
10233
  import path26 from "path";
9938
10234
  import "commander";
9939
10235
  import {
9940
10236
  buildFrontmatter as buildFrontmatter8,
9941
10237
  findProjectRoot as findProjectRoot22,
9942
- memoryFilePath as memoryFilePath8,
10238
+ memoryFilePath as memoryFilePath9,
9943
10239
  resolveHaivePaths as resolveHaivePaths19,
9944
- serializeMemory as serializeMemory17,
10240
+ serializeMemory as serializeMemory18,
9945
10241
  suggestSensorFromMemory as suggestSensorFromMemory4
9946
10242
  } from "@hiveai/core";
9947
10243
  function registerMemoryTried(memory2) {
@@ -9963,7 +10259,7 @@ function registerMemoryTried(memory2) {
9963
10259
  ).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("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
9964
10260
  const root = findProjectRoot22(opts.dir);
9965
10261
  const paths = resolveHaivePaths19(root);
9966
- if (!existsSync43(paths.haiveDir)) {
10262
+ if (!existsSync44(paths.haiveDir)) {
9967
10263
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9968
10264
  process.exitCode = 1;
9969
10265
  return;
@@ -9989,14 +10285,14 @@ function registerMemoryTried(memory2) {
9989
10285
  if (sensor) {
9990
10286
  frontmatter.sensor = sensor;
9991
10287
  }
9992
- const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
10288
+ const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9993
10289
  await mkdir13(path26.dirname(file), { recursive: true });
9994
- if (existsSync43(file)) {
10290
+ if (existsSync44(file)) {
9995
10291
  ui.error(`Memory already exists at ${file}`);
9996
10292
  process.exitCode = 1;
9997
10293
  return;
9998
10294
  }
9999
- await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
10295
+ await writeFile20(file, serializeMemory18({ frontmatter, body }), "utf8");
10000
10296
  ui.success(`Recorded: ${path26.relative(root, file)}`);
10001
10297
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
10002
10298
  if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
@@ -10009,7 +10305,7 @@ function parseCsv4(value) {
10009
10305
 
10010
10306
  // src/commands/memory-seed.ts
10011
10307
  import { readFile as readFile13 } from "fs/promises";
10012
- import { existsSync as existsSync44 } from "fs";
10308
+ import { existsSync as existsSync45 } from "fs";
10013
10309
  import path27 from "path";
10014
10310
  import "commander";
10015
10311
  import {
@@ -10049,7 +10345,7 @@ function registerMemorySeed(memory2) {
10049
10345
  }
10050
10346
  return;
10051
10347
  }
10052
- if (!existsSync44(paths.haiveDir)) {
10348
+ if (!existsSync45(paths.haiveDir)) {
10053
10349
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10054
10350
  process.exitCode = 1;
10055
10351
  return;
@@ -10105,7 +10401,7 @@ function registerMemorySeed(memory2) {
10105
10401
  }
10106
10402
 
10107
10403
  // src/commands/memory-pending.ts
10108
- import { existsSync as existsSync45 } from "fs";
10404
+ import { existsSync as existsSync46 } from "fs";
10109
10405
  import path28 from "path";
10110
10406
  import "commander";
10111
10407
  import {
@@ -10118,12 +10414,12 @@ function registerMemoryPending(memory2) {
10118
10414
  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) => {
10119
10415
  const root = findProjectRoot24(opts.dir);
10120
10416
  const paths = resolveHaivePaths21(root);
10121
- if (!existsSync45(paths.memoriesDir)) {
10417
+ if (!existsSync46(paths.memoriesDir)) {
10122
10418
  ui.error(`No .ai/memories at ${root}.`);
10123
10419
  process.exitCode = 1;
10124
10420
  return;
10125
10421
  }
10126
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10422
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10127
10423
  const usage = await loadUsageIndex17(paths);
10128
10424
  const filterFn = ({ memory: mem }) => {
10129
10425
  if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
@@ -10176,7 +10472,7 @@ function registerMemoryPending(memory2) {
10176
10472
  }
10177
10473
 
10178
10474
  // src/commands/memory-query.ts
10179
- import { existsSync as existsSync46 } from "fs";
10475
+ import { existsSync as existsSync47 } from "fs";
10180
10476
  import path29 from "path";
10181
10477
  import "commander";
10182
10478
  import {
@@ -10193,7 +10489,7 @@ function registerMemoryQuery(memory2) {
10193
10489
  memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
10194
10490
  const root = findProjectRoot25(opts.dir);
10195
10491
  const paths = resolveHaivePaths22(root);
10196
- if (!existsSync46(paths.memoriesDir)) {
10492
+ if (!existsSync47(paths.memoriesDir)) {
10197
10493
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
10198
10494
  process.exitCode = 1;
10199
10495
  return;
@@ -10204,7 +10500,7 @@ function registerMemoryQuery(memory2) {
10204
10500
  return;
10205
10501
  }
10206
10502
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
10207
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10503
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10208
10504
  const passesFilters2 = (mem) => {
10209
10505
  const fm = mem.frontmatter;
10210
10506
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -10251,8 +10547,8 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
10251
10547
  }
10252
10548
 
10253
10549
  // src/commands/memory-reject.ts
10254
- import { writeFile as writeFile20 } from "fs/promises";
10255
- import { existsSync as existsSync47 } from "fs";
10550
+ import { writeFile as writeFile21 } from "fs/promises";
10551
+ import { existsSync as existsSync48 } from "fs";
10256
10552
  import "commander";
10257
10553
  import {
10258
10554
  findProjectRoot as findProjectRoot26,
@@ -10260,27 +10556,27 @@ import {
10260
10556
  recordRejection as recordRejection3,
10261
10557
  resolveHaivePaths as resolveHaivePaths23,
10262
10558
  saveUsageIndex as saveUsageIndex4,
10263
- serializeMemory as serializeMemory18
10559
+ serializeMemory as serializeMemory19
10264
10560
  } from "@hiveai/core";
10265
10561
  function registerMemoryReject(memory2) {
10266
10562
  memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
10267
10563
  const root = findProjectRoot26(opts.dir);
10268
10564
  const paths = resolveHaivePaths23(root);
10269
- if (!existsSync47(paths.memoriesDir)) {
10565
+ if (!existsSync48(paths.memoriesDir)) {
10270
10566
  ui.error(`No .ai/memories at ${root}.`);
10271
10567
  process.exitCode = 1;
10272
10568
  return;
10273
10569
  }
10274
- const memories = await loadMemoriesFromDir26(paths.memoriesDir);
10570
+ const memories = await loadMemoriesFromDir27(paths.memoriesDir);
10275
10571
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
10276
10572
  if (!loaded) {
10277
10573
  ui.error(`No memory with id "${id}".`);
10278
10574
  process.exitCode = 1;
10279
10575
  return;
10280
10576
  }
10281
- await writeFile20(
10577
+ await writeFile21(
10282
10578
  loaded.filePath,
10283
- serializeMemory18({
10579
+ serializeMemory19({
10284
10580
  frontmatter: {
10285
10581
  ...loaded.memory.frontmatter,
10286
10582
  status: "rejected",
@@ -10302,7 +10598,7 @@ function registerMemoryReject(memory2) {
10302
10598
  }
10303
10599
 
10304
10600
  // src/commands/memory-rm.ts
10305
- import { existsSync as existsSync48 } from "fs";
10601
+ import { existsSync as existsSync49 } from "fs";
10306
10602
  import { unlink as unlink3 } from "fs/promises";
10307
10603
  import path30 from "path";
10308
10604
  import { createInterface as createInterface2 } from "readline/promises";
@@ -10317,12 +10613,12 @@ function registerMemoryRm(memory2) {
10317
10613
  memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
10318
10614
  const root = findProjectRoot27(opts.dir);
10319
10615
  const paths = resolveHaivePaths24(root);
10320
- if (!existsSync48(paths.memoriesDir)) {
10616
+ if (!existsSync49(paths.memoriesDir)) {
10321
10617
  ui.error(`No .ai/memories at ${root}.`);
10322
10618
  process.exitCode = 1;
10323
10619
  return;
10324
10620
  }
10325
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10621
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10326
10622
  const found = all.find((m) => m.memory.frontmatter.id === id);
10327
10623
  if (!found) {
10328
10624
  ui.error(`No memory with id "${id}".`);
@@ -10353,7 +10649,7 @@ function registerMemoryRm(memory2) {
10353
10649
  }
10354
10650
 
10355
10651
  // src/commands/memory-show.ts
10356
- import { existsSync as existsSync49 } from "fs";
10652
+ import { existsSync as existsSync50 } from "fs";
10357
10653
  import { readFile as readFile14 } from "fs/promises";
10358
10654
  import path31 from "path";
10359
10655
  import "commander";
@@ -10368,12 +10664,12 @@ function registerMemoryShow(memory2) {
10368
10664
  memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
10369
10665
  const root = findProjectRoot28(opts.dir);
10370
10666
  const paths = resolveHaivePaths25(root);
10371
- if (!existsSync49(paths.memoriesDir)) {
10667
+ if (!existsSync50(paths.memoriesDir)) {
10372
10668
  ui.error(`No .ai/memories at ${root}.`);
10373
10669
  process.exitCode = 1;
10374
10670
  return;
10375
10671
  }
10376
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10672
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10377
10673
  const found = all.find((m) => m.memory.frontmatter.id === id);
10378
10674
  if (!found) {
10379
10675
  ui.error(`No memory with id "${id}".`);
@@ -10412,7 +10708,7 @@ function registerMemoryShow(memory2) {
10412
10708
  }
10413
10709
 
10414
10710
  // src/commands/memory-stats.ts
10415
- import { existsSync as existsSync50 } from "fs";
10711
+ import { existsSync as existsSync51 } from "fs";
10416
10712
  import path33 from "path";
10417
10713
  import "commander";
10418
10714
  import {
@@ -10426,12 +10722,12 @@ function registerMemoryStats(memory2) {
10426
10722
  memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
10427
10723
  const root = findProjectRoot29(opts.dir);
10428
10724
  const paths = resolveHaivePaths26(root);
10429
- if (!existsSync50(paths.memoriesDir)) {
10725
+ if (!existsSync51(paths.memoriesDir)) {
10430
10726
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10431
10727
  process.exitCode = 1;
10432
10728
  return;
10433
10729
  }
10434
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10730
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10435
10731
  const usage = await loadUsageIndex21(paths);
10436
10732
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10437
10733
  if (target.length === 0) {
@@ -10457,7 +10753,7 @@ function registerMemoryStats(memory2) {
10457
10753
  }
10458
10754
 
10459
10755
  // src/commands/memory-impact.ts
10460
- import { existsSync as existsSync51 } from "fs";
10756
+ import { existsSync as existsSync53 } from "fs";
10461
10757
  import "commander";
10462
10758
  import {
10463
10759
  compareImpact,
@@ -10474,12 +10770,12 @@ function registerMemoryImpact(memory2) {
10474
10770
  ).option("--id <id>", "show impact for a single memory id").option("--prune", "list only prune candidates (dead weight worth reviewing)", false).option("--tier <tier>", "filter to a tier: high | medium | low | dormant").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10475
10771
  const root = findProjectRoot30(opts.dir);
10476
10772
  const paths = resolveHaivePaths27(root);
10477
- if (!existsSync51(paths.memoriesDir)) {
10773
+ if (!existsSync53(paths.memoriesDir)) {
10478
10774
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10479
10775
  process.exitCode = 1;
10480
10776
  return;
10481
10777
  }
10482
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10778
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10483
10779
  const usageIndex = await loadUsageIndex23(paths);
10484
10780
  let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
10485
10781
  const fm = mem.frontmatter;
@@ -10545,7 +10841,7 @@ function pad(value, width) {
10545
10841
  }
10546
10842
 
10547
10843
  // src/commands/memory-feedback.ts
10548
- import { existsSync as existsSync53 } from "fs";
10844
+ import { existsSync as existsSync54 } from "fs";
10549
10845
  import "commander";
10550
10846
  import {
10551
10847
  computeImpact as computeImpact4,
@@ -10568,12 +10864,12 @@ function registerMemoryFeedback(memory2) {
10568
10864
  }
10569
10865
  const root = findProjectRoot31(opts.dir);
10570
10866
  const paths = resolveHaivePaths28(root);
10571
- if (!existsSync53(paths.memoriesDir)) {
10867
+ if (!existsSync54(paths.memoriesDir)) {
10572
10868
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10573
10869
  process.exitCode = 1;
10574
10870
  return;
10575
10871
  }
10576
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10872
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10577
10873
  const target = all.find((m) => m.memory.frontmatter.id === id);
10578
10874
  if (!target) {
10579
10875
  ui.error(`No memory with id '${id}'.`);
@@ -10599,14 +10895,14 @@ function registerMemoryFeedback(memory2) {
10599
10895
  }
10600
10896
 
10601
10897
  // src/commands/memory-verify.ts
10602
- import { writeFile as writeFile21 } from "fs/promises";
10603
- import { existsSync as existsSync54 } from "fs";
10898
+ import { writeFile as writeFile23 } from "fs/promises";
10899
+ import { existsSync as existsSync55 } from "fs";
10604
10900
  import path34 from "path";
10605
10901
  import "commander";
10606
10902
  import {
10607
10903
  findProjectRoot as findProjectRoot32,
10608
10904
  resolveHaivePaths as resolveHaivePaths29,
10609
- serializeMemory as serializeMemory19,
10905
+ serializeMemory as serializeMemory20,
10610
10906
  verifyAnchor as verifyAnchor3
10611
10907
  } from "@hiveai/core";
10612
10908
  function registerMemoryVerify(memory2) {
@@ -10615,7 +10911,7 @@ function registerMemoryVerify(memory2) {
10615
10911
  ).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
10616
10912
  const root = findProjectRoot32(opts.dir);
10617
10913
  const paths = resolveHaivePaths29(root);
10618
- if (!existsSync54(paths.memoriesDir)) {
10914
+ if (!existsSync55(paths.memoriesDir)) {
10619
10915
  if (opts.json) {
10620
10916
  console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
10621
10917
  } else {
@@ -10624,7 +10920,7 @@ function registerMemoryVerify(memory2) {
10624
10920
  process.exitCode = 1;
10625
10921
  return;
10626
10922
  }
10627
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10923
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10628
10924
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10629
10925
  if (opts.id && targets.length === 0) {
10630
10926
  if (opts.json) {
@@ -10673,7 +10969,7 @@ function registerMemoryVerify(memory2) {
10673
10969
  }
10674
10970
  if (opts.update) {
10675
10971
  const next = applyVerification2(mem, result);
10676
- await writeFile21(filePath, serializeMemory19(next), "utf8");
10972
+ await writeFile23(filePath, serializeMemory20(next), "utf8");
10677
10973
  updated++;
10678
10974
  }
10679
10975
  }
@@ -10736,7 +11032,7 @@ function applyVerification2(mem, result) {
10736
11032
 
10737
11033
  // src/commands/memory-import.ts
10738
11034
  import { readFile as readFile15 } from "fs/promises";
10739
- import { existsSync as existsSync55 } from "fs";
11035
+ import { existsSync as existsSync56 } from "fs";
10740
11036
  import "commander";
10741
11037
  import {
10742
11038
  findProjectRoot as findProjectRoot33,
@@ -10748,12 +11044,12 @@ function registerMemoryImport(memory2) {
10748
11044
  ).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
10749
11045
  const root = findProjectRoot33(opts.dir);
10750
11046
  const paths = resolveHaivePaths30(root);
10751
- if (!existsSync55(paths.haiveDir)) {
11047
+ if (!existsSync56(paths.haiveDir)) {
10752
11048
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10753
11049
  process.exitCode = 1;
10754
11050
  return;
10755
11051
  }
10756
- if (!existsSync55(opts.from)) {
11052
+ if (!existsSync56(opts.from)) {
10757
11053
  ui.error(`File not found: ${opts.from}`);
10758
11054
  process.exitCode = 1;
10759
11055
  return;
@@ -10786,15 +11082,15 @@ function registerMemoryImport(memory2) {
10786
11082
  }
10787
11083
 
10788
11084
  // src/commands/memory-import-changelog.ts
10789
- import { existsSync as existsSync56 } from "fs";
10790
- import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
11085
+ import { existsSync as existsSync57 } from "fs";
11086
+ import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile24 } from "fs/promises";
10791
11087
  import path35 from "path";
10792
11088
  import "commander";
10793
11089
  import {
10794
11090
  buildFrontmatter as buildFrontmatter9,
10795
11091
  findProjectRoot as findProjectRoot34,
10796
11092
  resolveHaivePaths as resolveHaivePaths31,
10797
- serializeMemory as serializeMemory20
11093
+ serializeMemory as serializeMemory21
10798
11094
  } from "@hiveai/core";
10799
11095
  function parseChangelog(content) {
10800
11096
  const entries = [];
@@ -10861,7 +11157,7 @@ function registerMemoryImportChangelog(memory2) {
10861
11157
  const root = findProjectRoot34(opts.dir);
10862
11158
  const paths = resolveHaivePaths31(root);
10863
11159
  const changelogPath = path35.resolve(root, opts.fromChangelog);
10864
- if (!existsSync56(changelogPath)) {
11160
+ if (!existsSync57(changelogPath)) {
10865
11161
  ui.error(`CHANGELOG not found: ${changelogPath}`);
10866
11162
  process.exitCode = 1;
10867
11163
  return;
@@ -10924,9 +11220,9 @@ function registerMemoryImportChangelog(memory2) {
10924
11220
  paths: [path35.relative(root, changelogPath)],
10925
11221
  topic: `changelog-${pkgName}-${entry.version}`
10926
11222
  });
10927
- await writeFile23(
11223
+ await writeFile24(
10928
11224
  path35.join(teamDir, `${fm.id}.md`),
10929
- serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
11225
+ serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
10930
11226
  "utf8"
10931
11227
  );
10932
11228
  console.log(ui.green(` \u2713 ${fm.id}`));
@@ -10948,15 +11244,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
10948
11244
  }
10949
11245
 
10950
11246
  // src/commands/memory-digest.ts
10951
- import { existsSync as existsSync57 } from "fs";
10952
- import { writeFile as writeFile24 } from "fs/promises";
11247
+ import { existsSync as existsSync58 } from "fs";
11248
+ import { writeFile as writeFile25 } from "fs/promises";
10953
11249
  import path36 from "path";
10954
11250
  import "commander";
10955
11251
  import {
10956
11252
  deriveConfidence as deriveConfidence12,
10957
11253
  findProjectRoot as findProjectRoot35,
10958
11254
  getUsage as getUsage20,
10959
- loadMemoriesFromDir as loadMemoriesFromDir27,
11255
+ loadMemoriesFromDir as loadMemoriesFromDir28,
10960
11256
  loadUsageIndex as loadUsageIndex25,
10961
11257
  resolveHaivePaths as resolveHaivePaths32
10962
11258
  } from "@hiveai/core";
@@ -10973,7 +11269,7 @@ function registerMemoryDigest(program2) {
10973
11269
  ).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
10974
11270
  const root = findProjectRoot35(opts.dir);
10975
11271
  const paths = resolveHaivePaths32(root);
10976
- if (!existsSync57(paths.memoriesDir)) {
11272
+ if (!existsSync58(paths.memoriesDir)) {
10977
11273
  ui.error("No .ai/memories found. Run `haive init` first.");
10978
11274
  process.exitCode = 1;
10979
11275
  return;
@@ -10981,7 +11277,7 @@ function registerMemoryDigest(program2) {
10981
11277
  const days = Math.max(1, Number(opts.days ?? 7));
10982
11278
  const scopeFilter = opts.scope ?? "team";
10983
11279
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
10984
- const all = await loadMemoriesFromDir27(paths.memoriesDir);
11280
+ const all = await loadMemoriesFromDir28(paths.memoriesDir);
10985
11281
  const usage = await loadUsageIndex25(paths);
10986
11282
  const recent = all.filter(({ memory: mem }) => {
10987
11283
  const fm = mem.frontmatter;
@@ -11046,7 +11342,7 @@ function registerMemoryDigest(program2) {
11046
11342
  const digest = lines.join("\n");
11047
11343
  if (opts.out) {
11048
11344
  const outPath = path36.resolve(process.cwd(), opts.out);
11049
- await writeFile24(outPath, digest, "utf8");
11345
+ await writeFile25(outPath, digest, "utf8");
11050
11346
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
11051
11347
  } else {
11052
11348
  console.log(digest);
@@ -11055,22 +11351,22 @@ function registerMemoryDigest(program2) {
11055
11351
  }
11056
11352
 
11057
11353
  // src/commands/session-end.ts
11058
- import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
11059
- import { existsSync as existsSync58 } from "fs";
11354
+ import { writeFile as writeFile26, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
11355
+ import { existsSync as existsSync59 } from "fs";
11060
11356
  import { spawn as spawn4 } from "child_process";
11061
11357
  import path37 from "path";
11062
11358
  import "commander";
11063
11359
  import {
11064
11360
  buildFrontmatter as buildFrontmatter10,
11065
11361
  findProjectRoot as findProjectRoot36,
11066
- loadMemoriesFromDir as loadMemoriesFromDir28,
11067
- memoryFilePath as memoryFilePath9,
11362
+ loadMemoriesFromDir as loadMemoriesFromDir29,
11363
+ memoryFilePath as memoryFilePath10,
11068
11364
  resolveHaivePaths as resolveHaivePaths33,
11069
- serializeMemory as serializeMemory21
11365
+ serializeMemory as serializeMemory23
11070
11366
  } from "@hiveai/core";
11071
11367
  async function buildAutoRecap(paths) {
11072
11368
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11073
- if (!existsSync58(obsFile)) return await buildGitAutoRecap(paths);
11369
+ if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
11074
11370
  const raw = await readFile17(obsFile, "utf8").catch(() => "");
11075
11371
  if (!raw.trim()) return await buildGitAutoRecap(paths);
11076
11372
  const lines = raw.split("\n").filter(Boolean);
@@ -11261,7 +11557,7 @@ function registerSessionEnd(session2) {
11261
11557
  ).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
11262
11558
  const root = findProjectRoot36(opts.dir);
11263
11559
  const paths = resolveHaivePaths33(root);
11264
- if (!existsSync58(paths.haiveDir)) {
11560
+ if (!existsSync59(paths.haiveDir)) {
11265
11561
  if (opts.auto || opts.quiet) return;
11266
11562
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11267
11563
  process.exitCode = 1;
@@ -11294,7 +11590,7 @@ function registerSessionEnd(session2) {
11294
11590
  });
11295
11591
  const topic = recapTopic2(scope, opts.module);
11296
11592
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
11297
- const missingPaths = filesTouched.filter((p) => !existsSync58(path37.resolve(root, p)));
11593
+ const missingPaths = filesTouched.filter((p) => !existsSync59(path37.resolve(root, p)));
11298
11594
  if (missingPaths.length > 0 && !opts.quiet) {
11299
11595
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
11300
11596
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
@@ -11302,11 +11598,11 @@ function registerSessionEnd(session2) {
11302
11598
  const cleanupObservations = async () => {
11303
11599
  if (!opts.auto) return;
11304
11600
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11305
- if (existsSync58(obsFile)) await rm2(obsFile).catch(() => {
11601
+ if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
11306
11602
  });
11307
11603
  };
11308
- if (existsSync58(paths.memoriesDir)) {
11309
- const existing = await loadMemoriesFromDir28(paths.memoriesDir);
11604
+ if (existsSync59(paths.memoriesDir)) {
11605
+ const existing = await loadMemoriesFromDir29(paths.memoriesDir);
11310
11606
  const topicMatch = existing.find(
11311
11607
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
11312
11608
  );
@@ -11322,7 +11618,7 @@ function registerSessionEnd(session2) {
11322
11618
  paths: filesTouched.length ? filesTouched : fm.anchor.paths
11323
11619
  }
11324
11620
  };
11325
- await writeFile25(topicMatch.filePath, serializeMemory21({ frontmatter: newFrontmatter, body }), "utf8");
11621
+ await writeFile26(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
11326
11622
  await cleanupObservations();
11327
11623
  if (!opts.quiet) {
11328
11624
  ui.success(`Session recap updated (revision #${revisionCount})`);
@@ -11342,9 +11638,9 @@ function registerSessionEnd(session2) {
11342
11638
  topic,
11343
11639
  status: "validated"
11344
11640
  });
11345
- const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
11641
+ const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
11346
11642
  await mkdir15(path37.dirname(file), { recursive: true });
11347
- await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
11643
+ await writeFile26(file, serializeMemory23({ frontmatter, body }), "utf8");
11348
11644
  await cleanupObservations();
11349
11645
  if (!opts.quiet) {
11350
11646
  ui.success(`Session recap created`);
@@ -11367,7 +11663,7 @@ function normalizeAnchorPath(root, filePath) {
11367
11663
  }
11368
11664
 
11369
11665
  // src/commands/snapshot.ts
11370
- import { existsSync as existsSync59 } from "fs";
11666
+ import { existsSync as existsSync60 } from "fs";
11371
11667
  import { readdir as readdir4 } from "fs/promises";
11372
11668
  import path38 from "path";
11373
11669
  import "commander";
@@ -11402,14 +11698,14 @@ function registerSnapshot(program2) {
11402
11698
  ).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
11403
11699
  const root = findProjectRoot37(opts.dir);
11404
11700
  const paths = resolveHaivePaths34(root);
11405
- if (!existsSync59(paths.haiveDir)) {
11701
+ if (!existsSync60(paths.haiveDir)) {
11406
11702
  ui.error("No .ai/ found. Run `haive init` first.");
11407
11703
  process.exitCode = 1;
11408
11704
  return;
11409
11705
  }
11410
11706
  if (opts.list) {
11411
11707
  const contractsDir = path38.join(paths.haiveDir, "contracts");
11412
- if (!existsSync59(contractsDir)) {
11708
+ if (!existsSync60(contractsDir)) {
11413
11709
  console.log(ui.dim("No contract snapshots found."));
11414
11710
  return;
11415
11711
  }
@@ -11533,18 +11829,18 @@ function detectFormat(filePath) {
11533
11829
  }
11534
11830
 
11535
11831
  // src/commands/hub.ts
11536
- import { existsSync as existsSync60 } from "fs";
11537
- import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
11832
+ import { existsSync as existsSync61 } from "fs";
11833
+ import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile27, copyFile } from "fs/promises";
11538
11834
  import path39 from "path";
11539
11835
  import { spawnSync as spawnSync5 } from "child_process";
11540
11836
  import "commander";
11541
11837
  import {
11542
11838
  findProjectRoot as findProjectRoot38,
11543
11839
  loadConfig as loadConfig8,
11544
- loadMemoriesFromDir as loadMemoriesFromDir29,
11840
+ loadMemoriesFromDir as loadMemoriesFromDir30,
11545
11841
  resolveHaivePaths as resolveHaivePaths35,
11546
11842
  saveConfig as saveConfig3,
11547
- serializeMemory as serializeMemory23
11843
+ serializeMemory as serializeMemory24
11548
11844
  } from "@hiveai/core";
11549
11845
  function registerHub(program2) {
11550
11846
  const hub = program2.command("hub").description(
@@ -11567,7 +11863,7 @@ function registerHub(program2) {
11567
11863
  }
11568
11864
  const sharedDir = path39.join(absPath, ".ai", "memories", "shared");
11569
11865
  await mkdir16(sharedDir, { recursive: true });
11570
- await writeFile26(
11866
+ await writeFile27(
11571
11867
  path39.join(absPath, ".ai", "README.md"),
11572
11868
  `# hAIve Team Knowledge Hub
11573
11869
 
@@ -11589,7 +11885,7 @@ haive hub pull # import into a project
11589
11885
  `,
11590
11886
  "utf8"
11591
11887
  );
11592
- await writeFile26(
11888
+ await writeFile27(
11593
11889
  path39.join(absPath, ".gitignore"),
11594
11890
  ".ai/.cache/\n.ai/memories/personal/\n",
11595
11891
  "utf8"
@@ -11635,7 +11931,7 @@ Next steps:
11635
11931
  return;
11636
11932
  }
11637
11933
  const hubRoot = path39.resolve(root, config.hubPath);
11638
- if (!existsSync60(hubRoot)) {
11934
+ if (!existsSync61(hubRoot)) {
11639
11935
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
11640
11936
  process.exitCode = 1;
11641
11937
  return;
@@ -11643,7 +11939,7 @@ Next steps:
11643
11939
  const projectName = path39.basename(root);
11644
11940
  const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
11645
11941
  await mkdir16(destDir, { recursive: true });
11646
- const all = await loadMemoriesFromDir29(paths.memoriesDir);
11942
+ const all = await loadMemoriesFromDir30(paths.memoriesDir);
11647
11943
  const shared = all.filter(
11648
11944
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
11649
11945
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -11661,7 +11957,7 @@ Next steps:
11661
11957
  const fm = memory2.frontmatter;
11662
11958
  const fileName = `${fm.id}.md`;
11663
11959
  const destPath = path39.join(destDir, fileName);
11664
- await writeFile26(destPath, serializeMemory23(memory2), "utf8");
11960
+ await writeFile27(destPath, serializeMemory24(memory2), "utf8");
11665
11961
  pushed++;
11666
11962
  }
11667
11963
  console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
@@ -11705,7 +12001,7 @@ Next steps:
11705
12001
  }
11706
12002
  const hubRoot = path39.resolve(root, config.hubPath);
11707
12003
  const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
11708
- if (!existsSync60(hubSharedDir)) {
12004
+ if (!existsSync61(hubSharedDir)) {
11709
12005
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
11710
12006
  return;
11711
12007
  }
@@ -11760,7 +12056,7 @@ Next steps:
11760
12056
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
11761
12057
  );
11762
12058
  const sharedDir = path39.join(paths.memoriesDir, "shared");
11763
- if (existsSync60(sharedDir)) {
12059
+ if (existsSync61(sharedDir)) {
11764
12060
  const { readdir: readdir7 } = await import("fs/promises");
11765
12061
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
11766
12062
  console.log(`
@@ -11772,7 +12068,7 @@ Next steps:
11772
12068
  } else {
11773
12069
  console.log(ui.dim(" No imported shared memories yet."));
11774
12070
  }
11775
- const all = await loadMemoriesFromDir29(paths.memoriesDir);
12071
+ const all = await loadMemoriesFromDir30(paths.memoriesDir);
11776
12072
  const outgoing = all.filter(
11777
12073
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
11778
12074
  );
@@ -11782,20 +12078,20 @@ Next steps:
11782
12078
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
11783
12079
  }
11784
12080
  void readFile18;
11785
- void writeFile26;
12081
+ void writeFile27;
11786
12082
  void saveConfig3;
11787
12083
  });
11788
12084
  }
11789
12085
 
11790
12086
  // src/commands/stats.ts
11791
12087
  import "commander";
11792
- import { existsSync as existsSync61 } from "fs";
11793
- import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
12088
+ import { existsSync as existsSync63 } from "fs";
12089
+ import { mkdir as mkdir17, writeFile as writeFile28 } from "fs/promises";
11794
12090
  import path40 from "path";
11795
12091
  import {
11796
12092
  aggregateUsage,
11797
12093
  findProjectRoot as findProjectRoot39,
11798
- loadMemoriesFromDir as loadMemoriesFromDir30,
12094
+ loadMemoriesFromDir as loadMemoriesFromDir31,
11799
12095
  loadUsageIndex as loadUsageIndex26,
11800
12096
  parseSince,
11801
12097
  readUsageEvents as readUsageEvents2,
@@ -11867,8 +12163,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11867
12163
  const size = await usageLogSize(paths);
11868
12164
  let events = await readUsageEvents2(paths);
11869
12165
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
11870
- if (existsSync61(paths.memoriesDir)) {
11871
- const mems = await loadMemoriesFromDir30(paths.memoriesDir);
12166
+ if (existsSync63(paths.memoriesDir)) {
12167
+ const mems = await loadMemoriesFromDir31(paths.memoriesDir);
11872
12168
  for (const { memory: memory2 } of mems) {
11873
12169
  const fm = memory2.frontmatter;
11874
12170
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -11910,7 +12206,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11910
12206
  top_memory_reads: memoryHitsLeader,
11911
12207
  roi_hints: roiHints
11912
12208
  };
11913
- await writeFile27(outAbs, JSON.stringify(payload, null, 2), "utf8");
12209
+ await writeFile28(outAbs, JSON.stringify(payload, null, 2), "utf8");
11914
12210
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
11915
12211
  }
11916
12212
  async function renderMemoryHits(paths, opts) {
@@ -12087,8 +12383,8 @@ function summarize(name, t0, payload, notes) {
12087
12383
  }
12088
12384
 
12089
12385
  // src/commands/benchmark.ts
12090
- import { existsSync as existsSync63 } from "fs";
12091
- import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
12386
+ import { existsSync as existsSync64 } from "fs";
12387
+ import { readdir as readdir5, readFile as readFile19, writeFile as writeFile29 } from "fs/promises";
12092
12388
  import path41 from "path";
12093
12389
  import "commander";
12094
12390
  import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
@@ -12105,7 +12401,7 @@ function registerBenchmark(program2) {
12105
12401
  const markdown = renderMarkdown(root, summary, rows);
12106
12402
  if (opts.out) {
12107
12403
  const outFile = path41.isAbsolute(opts.out) ? opts.out : path41.join(root, opts.out);
12108
- await writeFile28(outFile, markdown, "utf8");
12404
+ await writeFile29(outFile, markdown, "utf8");
12109
12405
  ui.success(`wrote ${path41.relative(process.cwd(), outFile)}`);
12110
12406
  return;
12111
12407
  }
@@ -12135,14 +12431,14 @@ function resolveBenchmarkRoot(dir) {
12135
12431
  return path41.join(projectRoot, candidate);
12136
12432
  }
12137
12433
  async function collectRows(root) {
12138
- if (!existsSync63(root)) throw new Error(`Benchmark directory not found: ${root}`);
12434
+ if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
12139
12435
  const entries = await readdir5(root, { withFileTypes: true });
12140
12436
  const rows = [];
12141
12437
  for (const entry of entries) {
12142
12438
  if (!entry.isDirectory()) continue;
12143
12439
  const fixtureDir = path41.join(root, entry.name);
12144
12440
  const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
12145
- if (!existsSync63(reportFile)) continue;
12441
+ if (!existsSync64(reportFile)) continue;
12146
12442
  const report = await readFile19(reportFile, "utf8");
12147
12443
  rows.push(parseAgentReport(entry.name, report));
12148
12444
  }
@@ -12232,14 +12528,15 @@ function escapeRegExp(value) {
12232
12528
  }
12233
12529
 
12234
12530
  // src/commands/eval.ts
12235
- import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
12236
- import { existsSync as existsSync64 } from "fs";
12531
+ import { mkdir as mkdir18, readFile as readFile20, writeFile as writeFile30 } from "fs/promises";
12532
+ import { existsSync as existsSync65 } from "fs";
12237
12533
  import path43 from "path";
12238
12534
  import "commander";
12239
12535
  import {
12240
12536
  aggregateRetrieval,
12241
12537
  aggregateSensors,
12242
12538
  buildReport,
12539
+ compareEvalReports,
12243
12540
  findProjectRoot as findProjectRoot42,
12244
12541
  resolveHaivePaths as resolveHaivePaths38,
12245
12542
  scoreRetrievalCase,
@@ -12249,10 +12546,10 @@ import {
12249
12546
  function registerEval(program2) {
12250
12547
  program2.command("eval").description(
12251
12548
  "Rigorous, repeatable quality eval: do the right memories surface (retrieval) and do the right sensors fire (catch-rate)? Emits a chiffr\xE9 0\u2013100 score. Uses .ai/eval cases via --spec, or auto-synthesizes cases from anchored memories."
12252
- ).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("-d, --dir <dir>", "project root").action(async (opts) => {
12549
+ ).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
12253
12550
  const root = findProjectRoot42(opts.dir);
12254
12551
  const paths = resolveHaivePaths38(root);
12255
- if (!existsSync64(paths.memoriesDir)) {
12552
+ if (!existsSync65(paths.memoriesDir)) {
12256
12553
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12257
12554
  process.exitCode = 1;
12258
12555
  return;
@@ -12284,30 +12581,82 @@ function registerEval(program2) {
12284
12581
  sensorAgg = aggregateSensors(results);
12285
12582
  }
12286
12583
  const report = buildReport(retrievalAgg, sensorAgg);
12287
- if (opts.json) {
12288
- console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report }, null, 2));
12289
- } else {
12290
- const md = renderMarkdown2(root, k, resolvedSpec.source, report);
12291
- if (opts.out) {
12292
- const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12293
- await writeFile29(outFile, md, "utf8");
12294
- ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12295
- } else {
12296
- console.log(md);
12297
- }
12298
- }
12299
- if (opts.failUnder !== void 0) {
12300
- const threshold = Number(opts.failUnder);
12301
- if (Number.isNaN(threshold)) {
12302
- ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
12303
- process.exitCode = 1;
12304
- } else if (report.score < threshold) {
12305
- ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
12584
+ const baselineFile = opts.baselineFile ? path43.isAbsolute(opts.baselineFile) ? opts.baselineFile : path43.join(root, opts.baselineFile) : path43.join(root, ".ai", "eval", "baseline.json");
12585
+ if (opts.baseline) {
12586
+ const snapshot = {
12587
+ saved_at: (/* @__PURE__ */ new Date()).toISOString(),
12588
+ k,
12589
+ spec_source: resolvedSpec.source,
12590
+ report
12591
+ };
12592
+ await mkdir18(path43.dirname(baselineFile), { recursive: true });
12593
+ await writeFile30(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
12594
+ if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path43.relative(root, baselineFile)}`);
12595
+ }
12596
+ let delta = null;
12597
+ if (opts.compare) {
12598
+ if (!existsSync65(baselineFile)) {
12599
+ ui.error(`No baseline at ${path43.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
12306
12600
  process.exitCode = 1;
12601
+ return;
12307
12602
  }
12603
+ const snapshot = JSON.parse(await readFile20(baselineFile, "utf8"));
12604
+ delta = compareEvalReports(snapshot.report, report);
12308
12605
  }
12606
+ if (opts.json) {
12607
+ console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report, ...delta ? { delta } : {} }, null, 2));
12608
+ applyExitGates(opts, report, delta);
12609
+ return;
12610
+ }
12611
+ if (delta) {
12612
+ console.log(renderDelta(delta));
12613
+ }
12614
+ const md = renderMarkdown2(root, k, resolvedSpec.source, report);
12615
+ if (opts.out) {
12616
+ const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12617
+ await writeFile30(outFile, md, "utf8");
12618
+ ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12619
+ } else {
12620
+ console.log(md);
12621
+ }
12622
+ applyExitGates(opts, report, delta);
12309
12623
  });
12310
12624
  }
12625
+ function applyExitGates(opts, report, delta) {
12626
+ if (opts.failUnder !== void 0) {
12627
+ const threshold = Number(opts.failUnder);
12628
+ if (Number.isNaN(threshold)) {
12629
+ ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
12630
+ process.exitCode = 1;
12631
+ } else if (report.score < threshold) {
12632
+ ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
12633
+ process.exitCode = 1;
12634
+ }
12635
+ }
12636
+ if (opts.failOnRegression && delta?.regressed) {
12637
+ ui.error(`eval score regressed ${delta.score.baseline} \u2192 ${delta.score.current} (\u0394 ${delta.score.delta}) vs baseline`);
12638
+ process.exitCode = 1;
12639
+ }
12640
+ }
12641
+ function fmtDelta(label, m) {
12642
+ if (!m) return null;
12643
+ const sign = m.delta > 0 ? "+" : "";
12644
+ const arrow = m.delta > 0 ? ui.green("\u25B2") : m.delta < 0 ? ui.red("\u25BC") : ui.dim("=");
12645
+ return ` ${arrow} ${label.padEnd(12)} ${m.baseline} \u2192 ${m.current} ${ui.dim(`(${sign}${m.delta})`)}`;
12646
+ }
12647
+ function renderDelta(delta) {
12648
+ const verdict = delta.regressed ? ui.red("REGRESSED") : delta.improved ? ui.green("IMPROVED") : ui.dim("UNCHANGED");
12649
+ const lines = [ui.bold(`Eval vs baseline \u2014 ${verdict}`)];
12650
+ for (const line of [
12651
+ fmtDelta("score", delta.score),
12652
+ fmtDelta("mean recall", delta.mean_recall),
12653
+ fmtDelta("mrr", delta.mrr),
12654
+ fmtDelta("catch-rate", delta.catch_rate)
12655
+ ]) {
12656
+ if (line) lines.push(line);
12657
+ }
12658
+ return lines.join("\n");
12659
+ }
12311
12660
  async function resolveSpec(opts, root, memoriesDir) {
12312
12661
  if (opts.spec) {
12313
12662
  const file = path43.resolve(opts.spec);
@@ -12315,10 +12664,10 @@ async function resolveSpec(opts, root, memoriesDir) {
12315
12664
  return { spec: JSON.parse(raw), source: file };
12316
12665
  }
12317
12666
  const defaultSpec = path43.join(root, ".ai", "eval", "spec.json");
12318
- if (existsSync64(defaultSpec)) {
12667
+ if (existsSync65(defaultSpec)) {
12319
12668
  const raw = await readFile20(defaultSpec, "utf8");
12320
12669
  const explicit = JSON.parse(raw);
12321
- const memories2 = await loadMemoriesFromDir26(memoriesDir);
12670
+ const memories2 = await loadMemoriesFromDir27(memoriesDir);
12322
12671
  const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
12323
12672
  return {
12324
12673
  spec: {
@@ -12328,7 +12677,7 @@ async function resolveSpec(opts, root, memoriesDir) {
12328
12677
  source: ".ai/eval/spec.json + synthesized anchored retrieval"
12329
12678
  };
12330
12679
  }
12331
- const memories = await loadMemoriesFromDir26(memoriesDir);
12680
+ const memories = await loadMemoriesFromDir27(memoriesDir);
12332
12681
  return {
12333
12682
  spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
12334
12683
  source: "synthesized anchored retrieval"
@@ -12418,8 +12767,8 @@ function renderMarkdown2(root, k, source, report) {
12418
12767
  }
12419
12768
 
12420
12769
  // src/commands/memory-suggest.ts
12421
- import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
12422
- import { existsSync as existsSync65 } from "fs";
12770
+ import { mkdir as mkdir19, writeFile as writeFile31 } from "fs/promises";
12771
+ import { existsSync as existsSync66 } from "fs";
12423
12772
  import path44 from "path";
12424
12773
  import "commander";
12425
12774
  import {
@@ -12427,12 +12776,12 @@ import {
12427
12776
  buildFrontmatter as buildFrontmatter11,
12428
12777
  findProjectRoot as findProjectRoot43,
12429
12778
  loadConfig as loadConfig9,
12430
- loadMemoriesFromDir as loadMemoriesFromDir31,
12431
- memoryFilePath as memoryFilePath10,
12779
+ loadMemoriesFromDir as loadMemoriesFromDir33,
12780
+ memoryFilePath as memoryFilePath11,
12432
12781
  parseSince as parseSince2,
12433
12782
  readUsageEvents as readUsageEvents3,
12434
12783
  resolveHaivePaths as resolveHaivePaths39,
12435
- serializeMemory as serializeMemory24
12784
+ serializeMemory as serializeMemory25
12436
12785
  } from "@hiveai/core";
12437
12786
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
12438
12787
  "mem_search",
@@ -12498,7 +12847,7 @@ function registerMemorySuggest(memory2) {
12498
12847
  }
12499
12848
  const created = [];
12500
12849
  const skipped = [];
12501
- const existing = existsSync65(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12850
+ const existing = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
12502
12851
  for (const s of top) {
12503
12852
  const slug = slugify2(s.query);
12504
12853
  if (!slug) {
@@ -12520,13 +12869,13 @@ function registerMemorySuggest(memory2) {
12520
12869
  status
12521
12870
  });
12522
12871
  const body = renderTemplate(s, fm.id, status);
12523
- const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
12524
- await mkdir18(path44.dirname(file), { recursive: true });
12525
- if (existsSync65(file)) {
12872
+ const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
12873
+ await mkdir19(path44.dirname(file), { recursive: true });
12874
+ if (existsSync66(file)) {
12526
12875
  skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
12527
12876
  continue;
12528
12877
  }
12529
- await writeFile30(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
12878
+ await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
12530
12879
  created.push({ id: fm.id, file: path44.relative(root, file), query: s.query });
12531
12880
  }
12532
12881
  if (opts.json) {
@@ -12625,8 +12974,8 @@ function truncate2(text, max) {
12625
12974
  }
12626
12975
 
12627
12976
  // src/commands/memory-archive.ts
12628
- import { existsSync as existsSync66 } from "fs";
12629
- import { writeFile as writeFile31 } from "fs/promises";
12977
+ import { existsSync as existsSync67 } from "fs";
12978
+ import { writeFile as writeFile33 } from "fs/promises";
12630
12979
  import path45 from "path";
12631
12980
  import "commander";
12632
12981
  import {
@@ -12634,10 +12983,10 @@ import {
12634
12983
  getUsage as getUsage21,
12635
12984
  retirementSignal as retirementSignal2,
12636
12985
  loadConfig as loadConfig10,
12637
- loadMemoriesFromDir as loadMemoriesFromDir33,
12986
+ loadMemoriesFromDir as loadMemoriesFromDir34,
12638
12987
  loadUsageIndex as loadUsageIndex27,
12639
12988
  resolveHaivePaths as resolveHaivePaths40,
12640
- serializeMemory as serializeMemory25
12989
+ serializeMemory as serializeMemory26
12641
12990
  } from "@hiveai/core";
12642
12991
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
12643
12992
  function registerMemoryArchive(memory2) {
@@ -12646,7 +12995,7 @@ function registerMemoryArchive(memory2) {
12646
12995
  ).option("--since <window>", "minimum age since last read (e.g. '180d', '6m'). Default: enforcement.decayAfterDays or 180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--unread", "decay by unread-age ALONE (ignore anchor status) \u2014 more aggressive corpus hygiene", false).option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
12647
12996
  const root = findProjectRoot44(opts.dir);
12648
12997
  const paths = resolveHaivePaths40(root);
12649
- if (!existsSync66(paths.memoriesDir)) {
12998
+ if (!existsSync67(paths.memoriesDir)) {
12650
12999
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12651
13000
  process.exitCode = 1;
12652
13001
  return;
@@ -12660,7 +13009,7 @@ function registerMemoryArchive(memory2) {
12660
13009
  return;
12661
13010
  }
12662
13011
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
12663
- const all = await loadMemoriesFromDir33(paths.memoriesDir);
13012
+ const all = await loadMemoriesFromDir34(paths.memoriesDir);
12664
13013
  const usage = await loadUsageIndex27(paths);
12665
13014
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
12666
13015
  const candidates = [];
@@ -12671,7 +13020,7 @@ function registerMemoryArchive(memory2) {
12671
13020
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
12672
13021
  const retired = retirementSignal2(fm, mem.body);
12673
13022
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
12674
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync66(path45.join(paths.root, p)));
13023
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path45.join(paths.root, p)));
12675
13024
  const isAnchorless = !hasAnyAnchor;
12676
13025
  if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
12677
13026
  const u = getUsage21(usage, fm.id);
@@ -12720,7 +13069,7 @@ function registerMemoryArchive(memory2) {
12720
13069
  if (!found) continue;
12721
13070
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
12722
13071
  try {
12723
- await writeFile31(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
13072
+ await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
12724
13073
  archived++;
12725
13074
  } catch (err) {
12726
13075
  if (!opts.json) {
@@ -12746,8 +13095,8 @@ function parseDays(input) {
12746
13095
  }
12747
13096
 
12748
13097
  // src/commands/doctor.ts
12749
- import { existsSync as existsSync67, statSync as statSync2 } from "fs";
12750
- import { readFile as readFile21, stat, writeFile as writeFile33 } from "fs/promises";
13098
+ import { existsSync as existsSync68, statSync as statSync2 } from "fs";
13099
+ import { readFile as readFile21, stat, writeFile as writeFile34 } from "fs/promises";
12751
13100
  import path46 from "path";
12752
13101
  import { execFileSync, execSync as execSync3 } from "child_process";
12753
13102
  import "commander";
@@ -12758,7 +13107,7 @@ import {
12758
13107
  isStackPackSeed as isStackPackSeed4,
12759
13108
  loadCodeMap as loadCodeMap7,
12760
13109
  loadConfig as loadConfig11,
12761
- loadMemoriesFromDir as loadMemoriesFromDir34,
13110
+ loadMemoriesFromDir as loadMemoriesFromDir35,
12762
13111
  loadUsageIndex as loadUsageIndex28,
12763
13112
  readUsageEvents as readUsageEvents4,
12764
13113
  resolveHaivePaths as resolveHaivePaths41
@@ -12773,7 +13122,7 @@ function registerDoctor(program2) {
12773
13122
  const findings = [];
12774
13123
  const repairs = [];
12775
13124
  const config = await loadConfig11(paths);
12776
- if (!existsSync67(paths.haiveDir)) {
13125
+ if (!existsSync68(paths.haiveDir)) {
12777
13126
  findings.push({
12778
13127
  severity: "error",
12779
13128
  code: "not-initialized",
@@ -12794,7 +13143,7 @@ function registerDoctor(program2) {
12794
13143
  })
12795
13144
  );
12796
13145
  }
12797
- if (!existsSync67(paths.projectContext)) {
13146
+ if (!existsSync68(paths.projectContext)) {
12798
13147
  findings.push({
12799
13148
  severity: "warn",
12800
13149
  code: "no-project-context",
@@ -12802,8 +13151,8 @@ function registerDoctor(program2) {
12802
13151
  fix: "haive init"
12803
13152
  });
12804
13153
  } else {
12805
- const { readFile: readFile25 } = await import("fs/promises");
12806
- const content = await readFile25(paths.projectContext, "utf8");
13154
+ const { readFile: readFile26 } = await import("fs/promises");
13155
+ const content = await readFile26(paths.projectContext, "utf8");
12807
13156
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
12808
13157
  if (isTemplate) {
12809
13158
  findings.push({
@@ -12823,7 +13172,7 @@ function registerDoctor(program2) {
12823
13172
  });
12824
13173
  }
12825
13174
  }
12826
- const memories = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
13175
+ const memories = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
12827
13176
  const now = Date.now();
12828
13177
  if (memories.length === 0) {
12829
13178
  findings.push({
@@ -12975,10 +13324,10 @@ function registerDoctor(program2) {
12975
13324
  if (config.enforcement?.requireBriefingFirst) {
12976
13325
  const claudeSettings = path46.join(root, ".claude", "settings.local.json");
12977
13326
  let hasClaudeEnforcement = false;
12978
- if (existsSync67(claudeSettings)) {
13327
+ if (existsSync68(claudeSettings)) {
12979
13328
  try {
12980
- const { readFile: readFile25 } = await import("fs/promises");
12981
- const raw = await readFile25(claudeSettings, "utf8");
13329
+ const { readFile: readFile26 } = await import("fs/promises");
13330
+ const raw = await readFile26(claudeSettings, "utf8");
12982
13331
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
12983
13332
  } catch {
12984
13333
  hasClaudeEnforcement = false;
@@ -13001,7 +13350,7 @@ function registerDoctor(program2) {
13001
13350
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
13002
13351
  });
13003
13352
  }
13004
- findings.push(...await collectInstallFindings(root, "0.12.3"));
13353
+ findings.push(...await collectInstallFindings(root, "0.12.9"));
13005
13354
  findings.push(...await collectToolchainFindings(root));
13006
13355
  try {
13007
13356
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -13009,7 +13358,7 @@ function registerDoctor(program2) {
13009
13358
  timeout: 3e3,
13010
13359
  stdio: ["ignore", "pipe", "ignore"]
13011
13360
  }).trim();
13012
- const cliVersion = "0.12.3";
13361
+ const cliVersion = "0.12.9";
13013
13362
  if (legacyRaw && legacyRaw !== cliVersion) {
13014
13363
  findings.push({
13015
13364
  severity: "warn",
@@ -13031,14 +13380,14 @@ npm uninstall -g @hiveai/mcp`
13031
13380
  ];
13032
13381
  const staleConfigs = [];
13033
13382
  for (const cfgPath of configPaths) {
13034
- if (!existsSync67(cfgPath)) continue;
13383
+ if (!existsSync68(cfgPath)) continue;
13035
13384
  try {
13036
13385
  const raw = await readFile21(cfgPath, "utf8");
13037
13386
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
13038
13387
  staleConfigs.push(path46.relative(root, cfgPath));
13039
13388
  if (opts.fix && !opts.dryRun) {
13040
13389
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
13041
- await writeFile33(cfgPath, updated, "utf8");
13390
+ await writeFile34(cfgPath, updated, "utf8");
13042
13391
  }
13043
13392
  }
13044
13393
  } catch {
@@ -13327,7 +13676,7 @@ which -a haive`
13327
13676
  ];
13328
13677
  for (const rel of integrationFiles) {
13329
13678
  const file = path46.join(root, rel);
13330
- if (!existsSync67(file)) continue;
13679
+ if (!existsSync68(file)) continue;
13331
13680
  const text = await readFile21(file, "utf8").catch(() => "");
13332
13681
  for (const bin of extractAbsoluteHaiveBins(text)) {
13333
13682
  const version = versionForBinary(bin);
@@ -13375,7 +13724,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
13375
13724
  const isHaiveWorkspace = (await readJson(path46.join(root, "package.json")))?.name === "haive-monorepo";
13376
13725
  if (!isHaiveWorkspace) return findings;
13377
13726
  const cliDist = path46.join(root, "packages/cli/dist/index.js");
13378
- if (!existsSync67(cliDist)) {
13727
+ if (!existsSync68(cliDist)) {
13379
13728
  findings.push({
13380
13729
  severity: "warn",
13381
13730
  code: "workspace-dist-missing",
@@ -13399,7 +13748,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
13399
13748
  "packages/core/src/index.ts",
13400
13749
  "packages/mcp/src/server.ts",
13401
13750
  "packages/cli/src/index.ts"
13402
- ].map((rel) => path46.join(root, rel)).filter(existsSync67);
13751
+ ].map((rel) => path46.join(root, rel)).filter(existsSync68);
13403
13752
  if (sourceFiles.length > 0) {
13404
13753
  const distMtime = statSync2(cliDist).mtimeMs;
13405
13754
  const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
@@ -13488,7 +13837,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
13488
13837
  }
13489
13838
  }
13490
13839
  async function readJson(file) {
13491
- if (!existsSync67(file)) return null;
13840
+ if (!existsSync68(file)) return null;
13492
13841
  try {
13493
13842
  return JSON.parse(await readFile21(file, "utf8"));
13494
13843
  } catch {
@@ -13559,11 +13908,11 @@ function extractAbsoluteHaiveBins(text) {
13559
13908
  }
13560
13909
 
13561
13910
  // src/commands/playback.ts
13562
- import { existsSync as existsSync68 } from "fs";
13911
+ import { existsSync as existsSync69 } from "fs";
13563
13912
  import "commander";
13564
13913
  import {
13565
13914
  findProjectRoot as findProjectRoot46,
13566
- loadMemoriesFromDir as loadMemoriesFromDir35,
13915
+ loadMemoriesFromDir as loadMemoriesFromDir36,
13567
13916
  parseSince as parseSince3,
13568
13917
  readUsageEvents as readUsageEvents5,
13569
13918
  resolveHaivePaths as resolveHaivePaths42
@@ -13589,7 +13938,7 @@ function registerPlayback(program2) {
13589
13938
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
13590
13939
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
13591
13940
  const sessions = bucketSessions(filtered, gapMs);
13592
- const all = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13941
+ const all = existsSync69(paths.memoriesDir) ? await loadMemoriesFromDir36(paths.memoriesDir) : [];
13593
13942
  const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
13594
13943
  const enriched = sessions.map((s, i) => {
13595
13944
  const startMs = Date.parse(s.start);
@@ -13831,11 +14180,11 @@ function runCommand3(cmd, args, cwd) {
13831
14180
  }
13832
14181
 
13833
14182
  // src/commands/welcome.ts
13834
- import { existsSync as existsSync69 } from "fs";
14183
+ import { existsSync as existsSync70 } from "fs";
13835
14184
  import "commander";
13836
14185
  import {
13837
14186
  findProjectRoot as findProjectRoot48,
13838
- loadMemoriesFromDir as loadMemoriesFromDir36,
14187
+ loadMemoriesFromDir as loadMemoriesFromDir37,
13839
14188
  resolveHaivePaths as resolveHaivePaths44
13840
14189
  } from "@hiveai/core";
13841
14190
  var TYPE_RANK = {
@@ -13853,12 +14202,12 @@ function registerWelcome(program2) {
13853
14202
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
13854
14203
  const root = findProjectRoot48(opts.dir);
13855
14204
  const paths = resolveHaivePaths44(root);
13856
- if (!existsSync69(paths.memoriesDir)) {
14205
+ if (!existsSync70(paths.memoriesDir)) {
13857
14206
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
13858
14207
  process.exitCode = 1;
13859
14208
  return;
13860
14209
  }
13861
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14210
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
13862
14211
  const team = all.filter(
13863
14212
  ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
13864
14213
  );
@@ -13925,7 +14274,7 @@ function registerResolveProject(program2) {
13925
14274
  }
13926
14275
 
13927
14276
  // src/commands/runtime-journal.ts
13928
- import { existsSync as existsSync70 } from "fs";
14277
+ import { existsSync as existsSync71 } from "fs";
13929
14278
  import path48 from "path";
13930
14279
  import "commander";
13931
14280
  import {
@@ -13951,7 +14300,7 @@ function registerRuntime(program2) {
13951
14300
  const root = path48.resolve(opts.dir ?? process.cwd());
13952
14301
  const paths = resolveHaivePaths45(findProjectRoot49(root));
13953
14302
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
13954
- if (!existsSync70(paths.haiveDir)) {
14303
+ if (!existsSync71(paths.haiveDir)) {
13955
14304
  ui.error("No .ai/ \u2014 run `haive init` first.");
13956
14305
  process.exitCode = 1;
13957
14306
  return;
@@ -13966,7 +14315,7 @@ function registerRuntime(program2) {
13966
14315
  }
13967
14316
 
13968
14317
  // src/commands/memory-timeline.ts
13969
- import { existsSync as existsSync71 } from "fs";
14318
+ import { existsSync as existsSync73 } from "fs";
13970
14319
  import path49 from "path";
13971
14320
  import "commander";
13972
14321
  import {
@@ -13985,13 +14334,13 @@ function registerMemoryTimeline(memory2) {
13985
14334
  }
13986
14335
  const root = path49.resolve(opts.dir ?? process.cwd());
13987
14336
  const paths = resolveHaivePaths46(findProjectRoot50(root));
13988
- if (!existsSync71(paths.memoriesDir)) {
14337
+ if (!existsSync73(paths.memoriesDir)) {
13989
14338
  ui.error("No memories \u2014 run `haive init`.");
13990
14339
  process.exitCode = 1;
13991
14340
  return;
13992
14341
  }
13993
14342
  const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
13994
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
14343
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
13995
14344
  const { entries, notice } = collectTimelineEntries2(all, {
13996
14345
  memoryId: opts.id,
13997
14346
  topic: opts.topic,
@@ -14003,7 +14352,7 @@ function registerMemoryTimeline(memory2) {
14003
14352
  }
14004
14353
 
14005
14354
  // src/commands/memory-conflict-candidates.ts
14006
- import { existsSync as existsSync73 } from "fs";
14355
+ import { existsSync as existsSync74 } from "fs";
14007
14356
  import path50 from "path";
14008
14357
  import "commander";
14009
14358
  import {
@@ -14028,7 +14377,7 @@ function registerMemoryConflictCandidates(memory2) {
14028
14377
  ).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
14029
14378
  const root = path50.resolve(opts.dir ?? process.cwd());
14030
14379
  const paths = resolveHaivePaths47(findProjectRoot51(root));
14031
- if (!existsSync73(paths.memoriesDir)) {
14380
+ if (!existsSync74(paths.memoriesDir)) {
14032
14381
  ui.error("No memories \u2014 run `haive init`.");
14033
14382
  process.exitCode = 1;
14034
14383
  return;
@@ -14038,7 +14387,7 @@ function registerMemoryConflictCandidates(memory2) {
14038
14387
  const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
14039
14388
  const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
14040
14389
  const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
14041
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
14390
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
14042
14391
  const lexical = findLexicalConflictPairs2(all, {
14043
14392
  sinceDays,
14044
14393
  types: parseTypes(opts.types),
@@ -14064,8 +14413,8 @@ function registerMemoryConflictCandidates(memory2) {
14064
14413
 
14065
14414
  // src/commands/enforce.ts
14066
14415
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
14067
- import { existsSync as existsSync74, statSync as statSync3 } from "fs";
14068
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile34 } from "fs/promises";
14416
+ import { existsSync as existsSync75, statSync as statSync3 } from "fs";
14417
+ import { chmod as chmod2, mkdir as mkdir20, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile35 } from "fs/promises";
14069
14418
  import path51 from "path";
14070
14419
  import "commander";
14071
14420
  import {
@@ -14074,7 +14423,7 @@ import {
14074
14423
  hasRecentBriefingMarker as hasRecentBriefingMarker2,
14075
14424
  isFreshIsoDate,
14076
14425
  loadConfig as loadConfig13,
14077
- loadMemoriesFromDir as loadMemoriesFromDir37,
14426
+ loadMemoriesFromDir as loadMemoriesFromDir38,
14078
14427
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
14079
14428
  readRecentBriefingMarker,
14080
14429
  resolveBriefingBudget as resolveBriefingBudget3,
@@ -14093,7 +14442,7 @@ function registerEnforce(program2) {
14093
14442
  enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
14094
14443
  const root = findProjectRoot52(opts.dir);
14095
14444
  const paths = resolveHaivePaths48(root);
14096
- await mkdir19(paths.haiveDir, { recursive: true });
14445
+ await mkdir20(paths.haiveDir, { recursive: true });
14097
14446
  const current = await loadConfig13(paths);
14098
14447
  await saveConfig4(paths, {
14099
14448
  ...current,
@@ -14139,14 +14488,14 @@ function registerEnforce(program2) {
14139
14488
  const root = findProjectRoot52(opts.dir);
14140
14489
  const paths = resolveHaivePaths48(root);
14141
14490
  const cacheDir = path51.join(paths.haiveDir, ".cache");
14142
- if (existsSync74(cacheDir)) {
14491
+ if (existsSync75(cacheDir)) {
14143
14492
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
14144
14493
  else {
14145
14494
  const removed = await cleanupCacheDir(cacheDir);
14146
14495
  ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
14147
14496
  }
14148
14497
  }
14149
- if (existsSync74(paths.runtimeDir)) {
14498
+ if (existsSync75(paths.runtimeDir)) {
14150
14499
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
14151
14500
  else {
14152
14501
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
@@ -14171,8 +14520,8 @@ function registerEnforce(program2) {
14171
14520
  const root = resolveRoot(opts.dir, payload);
14172
14521
  if (!root) return;
14173
14522
  const paths = resolveHaivePaths48(root);
14174
- if (!existsSync74(paths.haiveDir)) return;
14175
- await mkdir19(paths.runtimeDir, { recursive: true });
14523
+ if (!existsSync75(paths.haiveDir)) return;
14524
+ await mkdir20(paths.runtimeDir, { recursive: true });
14176
14525
  const sessionId = opts.sessionId ?? payload.session_id;
14177
14526
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
14178
14527
  await applyLightweightRepairs(root, paths);
@@ -14234,7 +14583,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14234
14583
  const root = resolveRoot(opts.dir, payload);
14235
14584
  if (!root) return;
14236
14585
  const paths = resolveHaivePaths48(root);
14237
- if (!existsSync74(paths.haiveDir)) return;
14586
+ if (!existsSync75(paths.haiveDir)) return;
14238
14587
  if (!isWriteLikeTool(payload)) return;
14239
14588
  const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
14240
14589
  if (ok) {
@@ -14278,7 +14627,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14278
14627
  async function buildFinishReport(dir) {
14279
14628
  const root = findProjectRoot52(dir);
14280
14629
  const paths = resolveHaivePaths48(root);
14281
- const initialized = existsSync74(paths.haiveDir);
14630
+ const initialized = existsSync75(paths.haiveDir);
14282
14631
  const config = initialized ? await loadConfig13(paths) : {};
14283
14632
  const mode = config.enforcement?.mode ?? "strict";
14284
14633
  const findings = [];
@@ -14371,6 +14720,7 @@ async function buildFinishReport(dir) {
14371
14720
  code: "release-version-not-required",
14372
14721
  message: "No shippable package code changed since upstream; no version/tag required."
14373
14722
  });
14723
+ findings.push(...await verifyGithubActionsForHead(root, status));
14374
14724
  return finishReport(root, initialized, mode, findings, config);
14375
14725
  }
14376
14726
  findings.push({
@@ -14457,6 +14807,7 @@ async function buildFinishReport(dir) {
14457
14807
  impact: 10
14458
14808
  });
14459
14809
  }
14810
+ findings.push(...await verifyGithubActionsForHead(root, status));
14460
14811
  return finishReport(root, initialized, mode, findings, config);
14461
14812
  }
14462
14813
  function finishReport(root, initialized, mode, findings, config) {
@@ -14474,7 +14825,7 @@ function finishReport(root, initialized, mode, findings, config) {
14474
14825
  async function runWithEnforcement(command, args, opts) {
14475
14826
  const root = findProjectRoot52(opts.dir);
14476
14827
  const paths = resolveHaivePaths48(root);
14477
- if (!existsSync74(paths.haiveDir)) {
14828
+ if (!existsSync75(paths.haiveDir)) {
14478
14829
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
14479
14830
  process.exit(1);
14480
14831
  }
@@ -14544,7 +14895,7 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14544
14895
  memoryIds: briefing.memories.map((m) => m.id)
14545
14896
  });
14546
14897
  const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
14547
- await mkdir19(dir, { recursive: true });
14898
+ await mkdir20(dir, { recursive: true });
14548
14899
  const file = path51.join(dir, `${sessionId}.md`);
14549
14900
  const parts = [
14550
14901
  "# hAIve Briefing",
@@ -14563,13 +14914,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14563
14914
  if (briefing.setup_warnings.length > 0) {
14564
14915
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
14565
14916
  }
14566
- await writeFile34(file, parts.join("\n") + "\n", "utf8");
14917
+ await writeFile35(file, parts.join("\n") + "\n", "utf8");
14567
14918
  return file;
14568
14919
  }
14569
14920
  async function buildEnforcementReport(dir, stage, sessionId) {
14570
14921
  const root = findProjectRoot52(dir);
14571
14922
  const paths = resolveHaivePaths48(root);
14572
- const initialized = existsSync74(paths.haiveDir);
14923
+ const initialized = existsSync75(paths.haiveDir);
14573
14924
  const config = initialized ? await loadConfig13(paths) : {};
14574
14925
  if (initialized) await applyLightweightRepairs(root, paths);
14575
14926
  const mode = config.enforcement?.mode ?? "strict";
@@ -14600,7 +14951,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
14600
14951
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
14601
14952
  });
14602
14953
  }
14603
- findings.push(...await inspectIntegrationVersions(root, "0.12.3"));
14954
+ findings.push(...await inspectIntegrationVersions(root, "0.12.9"));
14604
14955
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
14605
14956
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
14606
14957
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -14670,8 +15021,8 @@ function withCategories(report) {
14670
15021
  };
14671
15022
  }
14672
15023
  async function hasRecentSessionRecap(paths) {
14673
- if (!existsSync74(paths.memoriesDir)) return false;
14674
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15024
+ if (!existsSync75(paths.memoriesDir)) return false;
15025
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
14675
15026
  return all.some(({ memory: memory2 }) => {
14676
15027
  const fm = memory2.frontmatter;
14677
15028
  const freshnessDate = fm.verified_at ?? fm.created_at;
@@ -14679,8 +15030,8 @@ async function hasRecentSessionRecap(paths) {
14679
15030
  });
14680
15031
  }
14681
15032
  async function verifyMemoryPolicy(paths, config) {
14682
- if (!existsSync74(paths.memoriesDir)) return [];
14683
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15033
+ if (!existsSync75(paths.memoriesDir)) return [];
15034
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
14684
15035
  const findings = [];
14685
15036
  const staleImportant = [];
14686
15037
  let verified = 0;
@@ -14717,12 +15068,12 @@ async function verifyMemoryPolicy(paths, config) {
14717
15068
  return findings;
14718
15069
  }
14719
15070
  async function verifyDecisionCoverage(paths, stage, sessionId) {
14720
- if (!existsSync74(paths.memoriesDir)) return [];
15071
+ if (!existsSync75(paths.memoriesDir)) return [];
14721
15072
  const changedFiles = await getChangedFiles(paths.root, stage);
14722
15073
  if (changedFiles.length === 0) {
14723
15074
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
14724
15075
  }
14725
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15076
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
14726
15077
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
14727
15078
  const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
14728
15079
  const fm = memory2.frontmatter;
@@ -14826,7 +15177,7 @@ function isGeneratedArtifactStatusLine(line) {
14826
15177
  }
14827
15178
  async function cleanupRuntimeDir(runtimeDir) {
14828
15179
  let removed = 0;
14829
- await mkdir19(runtimeDir, { recursive: true });
15180
+ await mkdir20(runtimeDir, { recursive: true });
14830
15181
  const entries = await readdir6(runtimeDir, { withFileTypes: true }).catch(() => []);
14831
15182
  for (const entry of entries) {
14832
15183
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
@@ -14837,9 +15188,9 @@ async function cleanupRuntimeDir(runtimeDir) {
14837
15188
  await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
14838
15189
  removed++;
14839
15190
  }
14840
- await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14841
- if (!existsSync74(path51.join(runtimeDir, "README.md"))) {
14842
- await writeFile34(
15191
+ await writeFile35(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
15192
+ if (!existsSync75(path51.join(runtimeDir, "README.md"))) {
15193
+ await writeFile35(
14843
15194
  path51.join(runtimeDir, "README.md"),
14844
15195
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
14845
15196
  "utf8"
@@ -14849,14 +15200,14 @@ async function cleanupRuntimeDir(runtimeDir) {
14849
15200
  }
14850
15201
  async function cleanupCacheDir(cacheDir) {
14851
15202
  let removed = 0;
14852
- await mkdir19(cacheDir, { recursive: true });
15203
+ await mkdir20(cacheDir, { recursive: true });
14853
15204
  const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
14854
15205
  for (const entry of entries) {
14855
15206
  if (entry.name === ".gitignore") continue;
14856
15207
  await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
14857
15208
  removed++;
14858
15209
  }
14859
- await writeFile34(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
15210
+ await writeFile35(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
14860
15211
  return removed;
14861
15212
  }
14862
15213
  async function cleanupEnforcementDir(enforcementDir) {
@@ -14881,7 +15232,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
14881
15232
  const findings = [];
14882
15233
  for (const rel of files) {
14883
15234
  const file = path51.join(root, rel);
14884
- if (!existsSync74(file)) continue;
15235
+ if (!existsSync75(file)) continue;
14885
15236
  const text = await readFile23(file, "utf8").catch(() => "");
14886
15237
  for (const bin of extractAbsoluteHaiveBins2(text)) {
14887
15238
  const version = versionForBinary2(bin);
@@ -14991,7 +15342,7 @@ async function resolveCiDiffRange(root) {
14991
15342
  }
14992
15343
  async function resolveGithubEventRange(root) {
14993
15344
  const eventPath = process.env.GITHUB_EVENT_PATH;
14994
- if (!eventPath || !existsSync74(eventPath)) return null;
15345
+ if (!eventPath || !existsSync75(eventPath)) return null;
14995
15346
  try {
14996
15347
  const event = JSON.parse(await readFile23(eventPath, "utf8"));
14997
15348
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
@@ -15152,6 +15503,108 @@ async function remoteTagExists(root, tag) {
15152
15503
  return null;
15153
15504
  }
15154
15505
  }
15506
+ async function verifyGithubActionsForHead(root, status) {
15507
+ if (!status.upstream) return [];
15508
+ if (status.ahead > 0) {
15509
+ return [{
15510
+ severity: "info",
15511
+ code: "github-actions-waiting-for-push",
15512
+ message: "GitHub Actions verification waits until HEAD is pushed."
15513
+ }];
15514
+ }
15515
+ const remote = await githubRemoteForCurrentBranch(root);
15516
+ if (!remote) {
15517
+ return [{
15518
+ severity: "info",
15519
+ code: "github-actions-not-applicable",
15520
+ message: "No GitHub remote was detected; GitHub Actions pipeline verification was skipped."
15521
+ }];
15522
+ }
15523
+ const sha = (await runCommand4("git", ["rev-parse", "HEAD"], root).catch(() => "")).trim();
15524
+ if (!sha) {
15525
+ return [{
15526
+ severity: "error",
15527
+ code: "github-actions-head-unreadable",
15528
+ message: "Could not read HEAD SHA for GitHub Actions verification.",
15529
+ fix: "Run `git rev-parse HEAD`, then verify GitHub Actions manually before finishing.",
15530
+ impact: 30
15531
+ }];
15532
+ }
15533
+ let runs;
15534
+ try {
15535
+ const raw = await runCommand4("gh", [
15536
+ "run",
15537
+ "list",
15538
+ "--commit",
15539
+ sha,
15540
+ "--limit",
15541
+ "50",
15542
+ "--json",
15543
+ "conclusion,databaseId,name,status,workflowName"
15544
+ ], root);
15545
+ runs = JSON.parse(raw);
15546
+ } catch {
15547
+ return [{
15548
+ severity: "error",
15549
+ code: "github-actions-unverified",
15550
+ message: "Could not verify GitHub Actions runs for HEAD.",
15551
+ fix: "Install/authenticate GitHub CLI, then run `gh run list --commit $(git rev-parse HEAD)` and ensure every workflow is successful before finishing.",
15552
+ reason: `Detected GitHub remote ${remote}, but hAIve could not query workflow runs.`,
15553
+ impact: 50
15554
+ }];
15555
+ }
15556
+ if (runs.length === 0) {
15557
+ return [{
15558
+ severity: "error",
15559
+ code: "github-actions-runs-missing",
15560
+ message: "No GitHub Actions runs were found for HEAD.",
15561
+ fix: "Wait for GitHub to create the workflow runs, or verify that the push was not skipped by a `[skip ci]` head commit; rerun `haive enforce finish` after the runs appear.",
15562
+ impact: 50
15563
+ }];
15564
+ }
15565
+ const pending = runs.filter((run) => run.status !== "completed");
15566
+ if (pending.length > 0) {
15567
+ return [{
15568
+ severity: "error",
15569
+ code: "github-actions-pending",
15570
+ message: `${pending.length}/${runs.length} GitHub Actions workflow run(s) for HEAD are still pending: ${formatGithubRunNames(pending)}.`,
15571
+ fix: "Wait for the runs to finish (`gh run watch <run-id> --exit-status`), then rerun `haive enforce finish`.",
15572
+ impact: 50
15573
+ }];
15574
+ }
15575
+ const failed = runs.filter((run) => run.conclusion !== "success");
15576
+ if (failed.length > 0) {
15577
+ return [{
15578
+ severity: "error",
15579
+ code: "github-actions-failed",
15580
+ message: `${failed.length}/${runs.length} GitHub Actions workflow run(s) for HEAD did not pass: ${formatGithubRunNames(failed)}.`,
15581
+ fix: "Inspect the failed run logs with `gh run view <run-id> --log`, fix the issue, push the fix, then rerun `haive enforce finish`.",
15582
+ impact: 80
15583
+ }];
15584
+ }
15585
+ return [{
15586
+ severity: "ok",
15587
+ code: "github-actions-pass",
15588
+ message: `All ${runs.length} GitHub Actions workflow run(s) for HEAD completed successfully.`
15589
+ }];
15590
+ }
15591
+ async function githubRemoteForCurrentBranch(root) {
15592
+ const branch = (await runCommand4("git", ["branch", "--show-current"], root).catch(() => "")).trim();
15593
+ const branchRemote = branch ? (await runCommand4("git", ["config", "--get", `branch.${branch}.remote`], root).catch(() => "")).trim() : "";
15594
+ const remoteName = branchRemote || "origin";
15595
+ const remoteUrl = (await runCommand4("git", ["config", "--get", `remote.${remoteName}.url`], root).catch(() => "")).trim();
15596
+ if (!isGithubRemoteUrl(remoteUrl)) return null;
15597
+ return remoteUrl;
15598
+ }
15599
+ function isGithubRemoteUrl(url) {
15600
+ return /(^git@github\.com:|github\.com[/:])/.test(url);
15601
+ }
15602
+ function formatGithubRunNames(runs) {
15603
+ return runs.slice(0, 6).map((run) => {
15604
+ const label = run.workflowName ?? run.name ?? "workflow";
15605
+ return run.databaseId ? `${label}#${run.databaseId}` : label;
15606
+ }).join(", ");
15607
+ }
15155
15608
  function buildScore(findings, threshold = 80) {
15156
15609
  const checks = {
15157
15610
  total: findings.length,
@@ -15172,11 +15625,11 @@ function buildScore(findings, threshold = 80) {
15172
15625
  }
15173
15626
  async function installGitEnforcement(root) {
15174
15627
  const hooksDir = path51.join(root, ".git", "hooks");
15175
- if (!existsSync74(path51.join(root, ".git"))) {
15628
+ if (!existsSync75(path51.join(root, ".git"))) {
15176
15629
  ui.warn("No .git directory found; git enforcement hooks skipped.");
15177
15630
  return;
15178
15631
  }
15179
- await mkdir19(hooksDir, { recursive: true });
15632
+ await mkdir20(hooksDir, { recursive: true });
15180
15633
  const hooks = [
15181
15634
  {
15182
15635
  name: "pre-commit",
@@ -15195,17 +15648,17 @@ haive enforce check --stage pre-push --dir . || exit $?
15195
15648
  ];
15196
15649
  for (const hook of hooks) {
15197
15650
  const file = path51.join(hooksDir, hook.name);
15198
- if (existsSync74(file)) {
15651
+ if (existsSync75(file)) {
15199
15652
  const current = await readFile23(file, "utf8").catch(() => "");
15200
15653
  if (current.includes(ENFORCE_HOOK_MARKER)) {
15201
- await writeFile34(file, hook.body, "utf8");
15654
+ await writeFile35(file, hook.body, "utf8");
15202
15655
  } else {
15203
- await writeFile34(file, `${current.trimEnd()}
15656
+ await writeFile35(file, `${current.trimEnd()}
15204
15657
 
15205
15658
  ${hook.body}`, "utf8");
15206
15659
  }
15207
15660
  } else {
15208
- await writeFile34(file, hook.body, "utf8");
15661
+ await writeFile35(file, hook.body, "utf8");
15209
15662
  }
15210
15663
  await chmod2(file, 493);
15211
15664
  }
@@ -15213,12 +15666,12 @@ ${hook.body}`, "utf8");
15213
15666
  }
15214
15667
  async function installCiEnforcement(root) {
15215
15668
  const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
15216
- await mkdir19(path51.dirname(workflowPath), { recursive: true });
15217
- if (existsSync74(workflowPath)) {
15669
+ await mkdir20(path51.dirname(workflowPath), { recursive: true });
15670
+ if (existsSync75(workflowPath)) {
15218
15671
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
15219
15672
  return;
15220
15673
  }
15221
- await writeFile34(workflowPath, `name: haive-enforcement
15674
+ await writeFile35(workflowPath, `name: haive-enforcement
15222
15675
 
15223
15676
  on:
15224
15677
  pull_request:
@@ -15346,11 +15799,11 @@ function normalizeToolPath(file, root) {
15346
15799
  return path51.relative(root, normalized).replace(/\\/g, "/");
15347
15800
  }
15348
15801
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
15349
- if (!existsSync74(paths.memoriesDir)) return [];
15802
+ if (!existsSync75(paths.memoriesDir)) return [];
15350
15803
  const marker = await readRecentBriefingMarker(paths, sessionId);
15351
15804
  const consulted = new Set(marker?.memory_ids ?? []);
15352
15805
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
15353
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15806
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
15354
15807
  return all.filter(({ memory: memory2 }) => {
15355
15808
  const fm = memory2.frontmatter;
15356
15809
  if (!policyTypes.has(fm.type)) return false;
@@ -15420,19 +15873,19 @@ function registerRun(program2) {
15420
15873
 
15421
15874
  // src/commands/sensors.ts
15422
15875
  import { execFile as execFile2 } from "child_process";
15423
- import { existsSync as existsSync75 } from "fs";
15424
- import { chmod as chmod3, mkdir as mkdir20, readFile as readFile24, writeFile as writeFile35 } from "fs/promises";
15876
+ import { existsSync as existsSync76 } from "fs";
15877
+ import { chmod as chmod3, mkdir as mkdir21, readFile as readFile24, writeFile as writeFile36 } from "fs/promises";
15425
15878
  import path53 from "path";
15426
15879
  import { promisify as promisify2 } from "util";
15427
15880
  import "commander";
15428
15881
  import {
15429
15882
  findProjectRoot as findProjectRoot53,
15430
15883
  isRetiredMemory as isRetiredMemory3,
15431
- loadMemoriesFromDir as loadMemoriesFromDir38,
15884
+ loadMemoriesFromDir as loadMemoriesFromDir39,
15432
15885
  resolveHaivePaths as resolveHaivePaths49,
15433
15886
  runSensors as runSensors2,
15434
15887
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
15435
- serializeMemory as serializeMemory26
15888
+ serializeMemory as serializeMemory27
15436
15889
  } from "@hiveai/core";
15437
15890
  var exec2 = promisify2(execFile2);
15438
15891
  function registerSensors(program2) {
@@ -15503,7 +15956,7 @@ function registerSensors(program2) {
15503
15956
  }
15504
15957
  const root = findProjectRoot53(opts.dir);
15505
15958
  const paths = resolveHaivePaths49(root);
15506
- const loaded = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
15959
+ const loaded = existsSync76(paths.memoriesDir) ? await loadMemoriesFromDir39(paths.memoriesDir) : [];
15507
15960
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
15508
15961
  if (!found) {
15509
15962
  ui.error(`No memory found with id ${id}`);
@@ -15523,7 +15976,7 @@ function registerSensors(program2) {
15523
15976
  },
15524
15977
  body: found.memory.body
15525
15978
  };
15526
- await writeFile35(found.filePath, serializeMemory26(next), "utf8");
15979
+ await writeFile36(found.filePath, serializeMemory27(next), "utf8");
15527
15980
  ui.success(`Updated ${id}: sensor severity=${severity}`);
15528
15981
  if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
15529
15982
  ui.info(`message=${sensor.message}`);
@@ -15539,10 +15992,10 @@ function registerSensors(program2) {
15539
15992
  const paths = resolveHaivePaths49(root);
15540
15993
  const rows = await sensorRows(paths);
15541
15994
  const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
15542
- await mkdir20(outDir, { recursive: true });
15995
+ await mkdir21(outDir, { recursive: true });
15543
15996
  const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
15544
15997
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
15545
- await writeFile35(outPath, content, "utf8");
15998
+ await writeFile36(outPath, content, "utf8");
15546
15999
  if (format === "grep") await chmod3(outPath, 493);
15547
16000
  ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
15548
16001
  });
@@ -15565,8 +16018,8 @@ async function sensorRows(paths) {
15565
16018
  });
15566
16019
  }
15567
16020
  async function runnableSensorMemories(paths, regexOnly = true) {
15568
- if (!existsSync75(paths.memoriesDir)) return [];
15569
- const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
16021
+ if (!existsSync76(paths.memoriesDir)) return [];
16022
+ const loaded = await loadMemoriesFromDir39(paths.memoriesDir);
15570
16023
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
15571
16024
  const sensor = memory2.frontmatter.sensor;
15572
16025
  if (!sensor) return false;
@@ -15606,9 +16059,253 @@ function shellQuote(value) {
15606
16059
  return `'${value.replace(/'/g, "'\\''")}'`;
15607
16060
  }
15608
16061
 
16062
+ // src/commands/ingest.ts
16063
+ import { existsSync as existsSync77 } from "fs";
16064
+ import { mkdir as mkdir23, readFile as readFile25, writeFile as writeFile37 } from "fs/promises";
16065
+ import path54 from "path";
16066
+ import "commander";
16067
+ import {
16068
+ draftsFromFindings as draftsFromFindings2,
16069
+ filterNewDrafts as filterNewDrafts2,
16070
+ findProjectRoot as findProjectRoot54,
16071
+ loadMemoriesFromDir as loadMemoriesFromDir40,
16072
+ memoryFilePath as memoryFilePath12,
16073
+ parseFindings as parseFindings2,
16074
+ resolveHaivePaths as resolveHaivePaths50,
16075
+ serializeMemory as serializeMemory28
16076
+ } from "@hiveai/core";
16077
+ var SEVERITIES = ["info", "minor", "major", "critical", "blocker"];
16078
+ function registerIngest(program2) {
16079
+ program2.command("ingest").description(
16080
+ "Ingest scanner findings (SonarQube / SARIF) as proposed, anchored memories with sensors.\n\n Closes the review\u2194memory loop: a real defect a scanner found becomes a `gotcha`/`convention`\n memory anchored to the file, pre-filled with a conservative `warn` sensor, so the next agent\n is steered away from it. Drafts are status=proposed; a human validates/promotes them.\n\n Example:\n haive ingest --from sarif eslint.sarif --dry-run\n haive ingest --from sonar sonar-issues.json --scope team --min-severity major\n"
16081
+ ).argument("<file>", "path to the findings report (JSON)").requiredOption("--from <format>", "report format: sarif | sonar").option("--dry-run", "show what would be created without writing", false).option("--scope <scope>", "memory scope: personal | team | module", "team").option("--module <name>", "module name (required when scope=module)").option("--type <type>", "memory type: gotcha | convention", "gotcha").option("--min-severity <severity>", "ignore findings below this severity (info|minor|major|critical|blocker)").option("--limit <n>", "cap the number of memories created").option("--author <author>", "author email or handle").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (file, opts) => {
16082
+ const format = opts.from;
16083
+ if (format !== "sarif" && format !== "sonar") {
16084
+ ui.error("--from must be sarif or sonar");
16085
+ process.exitCode = 1;
16086
+ return;
16087
+ }
16088
+ if (opts.type && opts.type !== "gotcha" && opts.type !== "convention") {
16089
+ ui.error("--type must be gotcha or convention");
16090
+ process.exitCode = 1;
16091
+ return;
16092
+ }
16093
+ if (opts.minSeverity && !SEVERITIES.includes(opts.minSeverity)) {
16094
+ ui.error(`--min-severity must be one of: ${SEVERITIES.join(", ")}`);
16095
+ process.exitCode = 1;
16096
+ return;
16097
+ }
16098
+ const root = findProjectRoot54(opts.dir);
16099
+ const paths = resolveHaivePaths50(root);
16100
+ if (!existsSync77(paths.haiveDir)) {
16101
+ ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
16102
+ process.exitCode = 1;
16103
+ return;
16104
+ }
16105
+ const reportPath = path54.resolve(root, file);
16106
+ if (!existsSync77(reportPath)) {
16107
+ ui.error(`Report file not found: ${reportPath}`);
16108
+ process.exitCode = 1;
16109
+ return;
16110
+ }
16111
+ let raw;
16112
+ try {
16113
+ raw = await readFile25(reportPath, "utf8");
16114
+ } catch (err) {
16115
+ ui.error(`Could not read ${reportPath}: ${err instanceof Error ? err.message : String(err)}`);
16116
+ process.exitCode = 1;
16117
+ return;
16118
+ }
16119
+ let drafts;
16120
+ try {
16121
+ const findings = parseFindings2(format, raw);
16122
+ drafts = draftsFromFindings2(findings, {
16123
+ type: opts.type ?? "gotcha",
16124
+ scope: opts.scope ?? "team",
16125
+ module: opts.module,
16126
+ author: opts.author,
16127
+ ...opts.minSeverity ? { minSeverity: opts.minSeverity } : {},
16128
+ ...opts.limit ? { limit: Math.max(0, Number.parseInt(opts.limit, 10) || 0) } : {}
16129
+ });
16130
+ } catch (err) {
16131
+ ui.error(`Failed to parse ${format} report: ${err instanceof Error ? err.message : String(err)}`);
16132
+ process.exitCode = 1;
16133
+ return;
16134
+ }
16135
+ const existing = existsSync77(paths.memoriesDir) ? await loadMemoriesFromDir40(paths.memoriesDir) : [];
16136
+ const existingTopics = new Set(
16137
+ existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
16138
+ );
16139
+ const fresh = filterNewDrafts2(drafts, existingTopics);
16140
+ const skipped = drafts.length - fresh.length;
16141
+ if (opts.json) {
16142
+ const created2 = [];
16143
+ if (!opts.dryRun) {
16144
+ for (const draft of fresh) created2.push(await writeDraft2(paths, draft));
16145
+ }
16146
+ console.log(
16147
+ JSON.stringify(
16148
+ {
16149
+ format,
16150
+ parsed: drafts.length,
16151
+ new: fresh.length,
16152
+ skipped_existing: skipped,
16153
+ dry_run: Boolean(opts.dryRun),
16154
+ drafts: fresh.map((d) => ({
16155
+ id: d.frontmatter.id,
16156
+ topic: d.topic,
16157
+ path: d.finding.path,
16158
+ rule: d.finding.ruleId,
16159
+ severity: d.finding.severity,
16160
+ has_sensor: d.has_sensor
16161
+ }))
16162
+ },
16163
+ null,
16164
+ 2
16165
+ )
16166
+ );
16167
+ return;
16168
+ }
16169
+ console.log(
16170
+ ui.bold(
16171
+ `hAIve ingest (${format}) \u2014 ${drafts.length} finding(s), ${fresh.length} new` + (skipped > 0 ? `, ${skipped} already ingested` : "")
16172
+ )
16173
+ );
16174
+ if (fresh.length === 0) {
16175
+ ui.info("Nothing to ingest.");
16176
+ return;
16177
+ }
16178
+ for (const draft of fresh) {
16179
+ const sensorTag = draft.has_sensor ? ui.dim(" +sensor") : "";
16180
+ console.log(
16181
+ ` \u2022 ${draft.finding.ruleId} ${ui.dim(`(${draft.finding.severity})`)} \u2192 ${draft.finding.path}${sensorTag}`
16182
+ );
16183
+ if (opts.dryRun) console.log(` ${ui.dim("would create:")} ${draft.frontmatter.id}`);
16184
+ }
16185
+ if (opts.dryRun) {
16186
+ ui.info(`Dry run \u2014 nothing written. Re-run without --dry-run to create ${fresh.length} proposed memory(ies).`);
16187
+ return;
16188
+ }
16189
+ let created = 0;
16190
+ for (const draft of fresh) {
16191
+ await writeDraft2(paths, draft);
16192
+ created++;
16193
+ }
16194
+ ui.success(`Created ${created} proposed memory(ies) under ${path54.relative(root, paths.memoriesDir)}/`);
16195
+ ui.info("Review with `haive memory pending`; promote sensors with `haive sensors promote <id> --yes`.");
16196
+ });
16197
+ }
16198
+ async function writeDraft2(paths, draft) {
16199
+ const file = memoryFilePath12(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
16200
+ await mkdir23(path54.dirname(file), { recursive: true });
16201
+ await writeFile37(file, serializeMemory28({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
16202
+ return file;
16203
+ }
16204
+
16205
+ // src/commands/dashboard.ts
16206
+ import { existsSync as existsSync78 } from "fs";
16207
+ import "commander";
16208
+ import {
16209
+ buildDashboard,
16210
+ findProjectRoot as findProjectRoot55,
16211
+ loadMemoriesFromDir as loadMemoriesFromDir41,
16212
+ loadUsageIndex as loadUsageIndex29,
16213
+ resolveHaivePaths as resolveHaivePaths51
16214
+ } from "@hiveai/core";
16215
+ function registerDashboard(program2) {
16216
+ program2.command("dashboard").description(
16217
+ "Non-interactive observability snapshot of the memory corpus.\n\n One-shot rollup an agent or CI can read (unlike `haive tui`, no TTY needed):\n inventory, impact tiers + top memories, sensors (and which ones fired),\n health (stale / anchorless / pending / prune candidates), decay, and corpus weight.\n Use --json to pipe it into other tooling."
16218
+ ).option("--json", "emit the full report as JSON", false).option("--top <n>", "rows per top-list", "10").option("--dormant-days <n>", "dormancy window for impact scoring").option("-d, --dir <dir>", "project root").action(async (opts) => {
16219
+ const root = findProjectRoot55(opts.dir);
16220
+ const paths = resolveHaivePaths51(root);
16221
+ if (!existsSync78(paths.haiveDir)) {
16222
+ ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
16223
+ process.exitCode = 1;
16224
+ return;
16225
+ }
16226
+ const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
16227
+ const usage = await loadUsageIndex29(paths);
16228
+ const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
16229
+ const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
16230
+ const report = buildDashboard(memories, usage, {
16231
+ top,
16232
+ ...dormantDays !== void 0 && Number.isFinite(dormantDays) ? { dormantDays } : {}
16233
+ });
16234
+ if (opts.json) {
16235
+ console.log(JSON.stringify(report, null, 2));
16236
+ return;
16237
+ }
16238
+ renderDashboard(report);
16239
+ });
16240
+ }
16241
+ function renderDashboard(r) {
16242
+ const { inventory: inv, impact, sensors, health, decay, corpus } = r;
16243
+ console.log(ui.bold("hAIve dashboard"));
16244
+ console.log(
16245
+ ` ${ui.dim("corpus:")} ${inv.total} policy memor${inv.total === 1 ? "y" : "ies"} (${inv.active} active, ${inv.retired} retired) \xB7 ${inv.session_recaps} recap(s) \xB7 ~${corpus.est_tokens.toLocaleString()} tokens`
16246
+ );
16247
+ console.log(` ${ui.dim("scopes:")} ${formatCounts(inv.by_scope)}`);
16248
+ console.log(` ${ui.dim("types: ")} ${formatCounts(inv.by_type)}`);
16249
+ console.log();
16250
+ console.log(ui.bold("Impact"));
16251
+ console.log(
16252
+ ` ${ui.green(`high ${impact.high}`)} \xB7 ${ui.yellow(`medium ${impact.medium}`)} \xB7 low ${impact.low} \xB7 ${ui.dim(`dormant ${impact.dormant}`)} \xB7 ${impact.prune_candidates > 0 ? ui.red(`prune ${impact.prune_candidates}`) : "prune 0"}`
16253
+ );
16254
+ if (impact.top.length > 0) {
16255
+ console.log(ui.dim(" top by demonstrated utility:"));
16256
+ for (const row of impact.top.filter((x) => x.score > 0).slice(0, 8)) {
16257
+ console.log(
16258
+ ` ${tierMark(row.tier)} ${row.score.toFixed(2)} ${row.id}` + (row.signals.length ? ui.dim(` [${row.signals.join(", ")}]`) : "")
16259
+ );
16260
+ }
16261
+ }
16262
+ console.log();
16263
+ console.log(ui.bold("Sensors"));
16264
+ console.log(
16265
+ ` ${sensors.total} total \xB7 ${sensors.block} block \xB7 ${sensors.warn} warn \xB7 ${ui.dim(`${sensors.autogen} autogen`)} \xB7 ${sensors.fired > 0 ? ui.green(`${sensors.fired} fired`) : "0 fired"}`
16266
+ );
16267
+ for (const s of sensors.recently_fired.slice(0, 5)) {
16268
+ const marker = s.severity === "block" ? ui.red("\u2717") : ui.yellow("\u26A0");
16269
+ console.log(` ${marker} ${s.id} ${ui.dim(`last fired ${s.last_fired.slice(0, 10)}`)}`);
16270
+ }
16271
+ console.log();
16272
+ console.log(ui.bold("Health"));
16273
+ console.log(
16274
+ ` stale ${warnNum(health.stale)} \xB7 anchorless ${warnNum(health.anchorless)} \xB7 pending ${health.pending} \xB7 prune candidates ${warnNum(health.prune_candidates)}`
16275
+ );
16276
+ if (health.anchorless > 0) {
16277
+ ui.info("Anchorless validated decisions/gotchas can't detect drift \u2014 add `paths`/`symbols`.");
16278
+ }
16279
+ console.log();
16280
+ console.log(ui.bold(`Decay (>${decay.decay_days}d)`));
16281
+ console.log(` ${decay.decaying} decaying memor${decay.decaying === 1 ? "y" : "ies"}`);
16282
+ for (const d of decay.top_dormant.slice(0, 5)) {
16283
+ const last = d.last_read_at ? d.last_read_at.slice(0, 10) : "never read";
16284
+ console.log(` ${ui.dim(String(d.age_days).padStart(4) + "d")} ${d.id} ${ui.dim(`(${last})`)}`);
16285
+ }
16286
+ if (health.prune_candidates > 0 || decay.decaying > 0) {
16287
+ console.log();
16288
+ ui.info("Review low-value memories with `haive memory impact` and `haive memory lint`.");
16289
+ }
16290
+ }
16291
+ function formatCounts(map) {
16292
+ const entries = Object.entries(map).sort((a, b) => b[1] - a[1]);
16293
+ if (entries.length === 0) return "none";
16294
+ return entries.map(([k, v]) => `${k} ${v}`).join(", ");
16295
+ }
16296
+ function tierMark(tier) {
16297
+ if (tier === "high") return ui.green("\u25CF");
16298
+ if (tier === "medium") return ui.yellow("\u25CF");
16299
+ if (tier === "dormant") return ui.dim("\u25CB");
16300
+ return "\xB7";
16301
+ }
16302
+ function warnNum(n) {
16303
+ return n > 0 ? ui.yellow(String(n)) : String(n);
16304
+ }
16305
+
15609
16306
  // src/index.ts
15610
- var program = new Command56();
15611
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.3").option("--advanced", "show maintenance and experimental commands in help");
16307
+ var program = new Command58();
16308
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.9").option("--advanced", "show maintenance and experimental commands in help");
15612
16309
  registerInit(program);
15613
16310
  registerWelcome(program);
15614
16311
  registerResolveProject(program);
@@ -15617,6 +16314,8 @@ registerEnforce(program);
15617
16314
  registerRun(program);
15618
16315
  registerAgent(program);
15619
16316
  registerSensors(program);
16317
+ registerIngest(program);
16318
+ registerDashboard(program);
15620
16319
  registerMcp(program);
15621
16320
  registerBriefing(program);
15622
16321
  registerTui(program);