@hiveai/cli 0.12.4 → 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.4"}`;
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,
@@ -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 ? `
@@ -7466,10 +7720,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
7466
7720
  };
7467
7721
  }
7468
7722
  var ImportDocsArgsSchema = {
7469
- content: z39.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
7470
- source: z39.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
7471
- scope: z39.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
7472
- 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")
7473
7727
  };
7474
7728
  function importDocsPrompt(args, ctx) {
7475
7729
  const sourceLine = args.source ? `
@@ -7532,7 +7786,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7532
7786
  };
7533
7787
  }
7534
7788
  var SERVER_NAME = "haive";
7535
- var SERVER_VERSION = "0.12.4";
7789
+ var SERVER_VERSION = "0.12.9";
7536
7790
  function jsonResult(data) {
7537
7791
  return {
7538
7792
  content: [
@@ -7575,7 +7829,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
7575
7829
  "mem_distill",
7576
7830
  "mem_timeline",
7577
7831
  "mem_conflict_candidates",
7578
- "mem_feedback"
7832
+ "mem_feedback",
7833
+ "ingest_findings"
7579
7834
  ];
7580
7835
  var EXPERIMENTAL_PROFILE_TOOLS = [
7581
7836
  ...MAINTENANCE_PROFILE_TOOLS,
@@ -7609,7 +7864,8 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
7609
7864
  "mem_delete",
7610
7865
  "mem_feedback",
7611
7866
  "runtime_journal_append",
7612
- "pattern_detect"
7867
+ "pattern_detect",
7868
+ "ingest_findings"
7613
7869
  ]);
7614
7870
  function createHaiveServer(options = {}) {
7615
7871
  const context = createContext(options);
@@ -7734,6 +7990,36 @@ function createHaiveServer(options = {}) {
7734
7990
  return jsonResult(await memTried(input, context));
7735
7991
  }
7736
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
+ );
7737
8023
  registerTool(
7738
8024
  "mem_observe",
7739
8025
  [
@@ -8530,8 +8816,8 @@ function registerMcp(program2) {
8530
8816
 
8531
8817
  // src/commands/sync.ts
8532
8818
  import { spawnSync as spawnSync4 } from "child_process";
8533
- import { readFile as readFile9, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
8534
- 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";
8535
8821
  import path15 from "path";
8536
8822
  import "commander";
8537
8823
  import {
@@ -8544,12 +8830,12 @@ import {
8544
8830
  isStackPackSeed as isStackPackSeed3,
8545
8831
  loadCodeMap as loadCodeMap6,
8546
8832
  loadConfig as loadConfig4,
8547
- loadMemoriesFromDir as loadMemoriesFromDir24,
8833
+ loadMemoriesFromDir as loadMemoriesFromDir25,
8548
8834
  loadUsageIndex as loadUsageIndex13,
8549
8835
  pullCrossRepoSources,
8550
8836
  resolveHaivePaths as resolveHaivePaths9,
8551
8837
  resolveManifestFiles,
8552
- serializeMemory as serializeMemory11,
8838
+ serializeMemory as serializeMemory12,
8553
8839
  trackDependencies,
8554
8840
  verifyAnchor as verifyAnchor2,
8555
8841
  watchContracts
@@ -8564,11 +8850,11 @@ function registerSync(program2) {
8564
8850
  "git ref/commit to compare against; report memories added/modified/removed since"
8565
8851
  ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
8566
8852
  "--inject-bridge",
8567
- "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"
8568
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) => {
8569
8855
  const root = findProjectRoot12(opts.dir);
8570
8856
  const paths = resolveHaivePaths9(root);
8571
- if (!existsSync31(paths.memoriesDir)) {
8857
+ if (!existsSync33(paths.memoriesDir)) {
8572
8858
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8573
8859
  process.exitCode = 1;
8574
8860
  return;
@@ -8587,14 +8873,14 @@ function registerSync(program2) {
8587
8873
  let promoted = 0;
8588
8874
  let autoApproved = 0;
8589
8875
  if (opts.verify !== false) {
8590
- const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8876
+ const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8591
8877
  for (const { memory: memory2, filePath } of memories) {
8592
8878
  if (memory2.frontmatter.type === "session_recap") {
8593
8879
  if (memory2.frontmatter.status === "stale") {
8594
8880
  if (!dryRun) {
8595
- await writeFile13(
8881
+ await writeFile14(
8596
8882
  filePath,
8597
- serializeMemory11({
8883
+ serializeMemory12({
8598
8884
  frontmatter: {
8599
8885
  ...memory2.frontmatter,
8600
8886
  status: "validated",
@@ -8617,9 +8903,9 @@ function registerSync(program2) {
8617
8903
  if (result.stale) {
8618
8904
  if (memory2.frontmatter.status !== "stale") {
8619
8905
  if (!dryRun) {
8620
- await writeFile13(
8906
+ await writeFile14(
8621
8907
  filePath,
8622
- serializeMemory11({
8908
+ serializeMemory12({
8623
8909
  frontmatter: {
8624
8910
  ...memory2.frontmatter,
8625
8911
  status: "stale",
@@ -8635,9 +8921,9 @@ function registerSync(program2) {
8635
8921
  }
8636
8922
  } else if (memory2.frontmatter.status === "stale") {
8637
8923
  if (!dryRun) {
8638
- await writeFile13(
8924
+ await writeFile14(
8639
8925
  filePath,
8640
- serializeMemory11({
8926
+ serializeMemory12({
8641
8927
  frontmatter: {
8642
8928
  ...memory2.frontmatter,
8643
8929
  status: "validated",
@@ -8654,7 +8940,7 @@ function registerSync(program2) {
8654
8940
  }
8655
8941
  }
8656
8942
  if (opts.promote !== false) {
8657
- const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8943
+ const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8658
8944
  const usage = await loadUsageIndex13(paths);
8659
8945
  const nowMs = Date.now();
8660
8946
  for (const { memory: memory2, filePath } of memories) {
@@ -8665,9 +8951,9 @@ function registerSync(program2) {
8665
8951
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
8666
8952
  })) {
8667
8953
  if (!dryRun) {
8668
- await writeFile13(
8954
+ await writeFile14(
8669
8955
  filePath,
8670
- serializeMemory11({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8956
+ serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8671
8957
  "utf8"
8672
8958
  );
8673
8959
  }
@@ -8678,9 +8964,9 @@ function registerSync(program2) {
8678
8964
  const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
8679
8965
  if (ageHours >= autoApproveDelayHours) {
8680
8966
  if (!dryRun) {
8681
- await writeFile13(
8967
+ await writeFile14(
8682
8968
  filePath,
8683
- serializeMemory11({
8969
+ serializeMemory12({
8684
8970
  frontmatter: {
8685
8971
  ...fm,
8686
8972
  status: "validated",
@@ -8706,7 +8992,7 @@ function registerSync(program2) {
8706
8992
  for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
8707
8993
  }
8708
8994
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
8709
- const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
8995
+ const draftMemories = (await loadMemoriesFromDir25(paths.memoriesDir)).filter(
8710
8996
  (m) => m.memory.frontmatter.status === "draft"
8711
8997
  );
8712
8998
  const draftCount = draftMemories.length;
@@ -8722,9 +9008,18 @@ function registerSync(program2) {
8722
9008
  );
8723
9009
  }
8724
9010
  if (opts.injectBridge) {
8725
- const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
8726
9011
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
8727
- 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
+ }
8728
9023
  }
8729
9024
  if (sinceReport && !opts.quiet) {
8730
9025
  if (sinceReport.added.length > 0) {
@@ -8741,7 +9036,7 @@ function registerSync(program2) {
8741
9036
  }
8742
9037
  }
8743
9038
  if (!opts.quiet) {
8744
- const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
9039
+ const allForDecay = await loadMemoriesFromDir25(paths.memoriesDir);
8745
9040
  const usageForDecay = await loadUsageIndex13(paths);
8746
9041
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
8747
9042
  const fm = memory2.frontmatter;
@@ -8831,9 +9126,9 @@ Attends une **confirmation explicite** avant d'agir.
8831
9126
  if (!dryRun) {
8832
9127
  const teamDir = path15.join(paths.memoriesDir, "team");
8833
9128
  await mkdir10(teamDir, { recursive: true });
8834
- await writeFile13(
9129
+ await writeFile14(
8835
9130
  path15.join(teamDir, `${fm.id}.md`),
8836
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
9131
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8837
9132
  "utf8"
8838
9133
  );
8839
9134
  }
@@ -8900,9 +9195,9 @@ Attends une **confirmation explicite** avant d'agir.
8900
9195
  if (!dryRun) {
8901
9196
  const teamDir = path15.join(paths.memoriesDir, "team");
8902
9197
  await mkdir10(teamDir, { recursive: true });
8903
- await writeFile13(
9198
+ await writeFile14(
8904
9199
  path15.join(teamDir, `${fm.id}.md`),
8905
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
9200
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8906
9201
  "utf8"
8907
9202
  );
8908
9203
  }
@@ -8992,8 +9287,8 @@ function bridgeSummaryLine(body) {
8992
9287
  return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
8993
9288
  }
8994
9289
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8995
- if (!existsSync31(memoriesDir)) return;
8996
- const all = await loadMemoriesFromDir24(memoriesDir);
9290
+ if (!existsSync33(memoriesDir)) return;
9291
+ const all = await loadMemoriesFromDir25(memoriesDir);
8997
9292
  const top = all.filter(({ memory: memory2 }) => {
8998
9293
  const s = memory2.frontmatter.status;
8999
9294
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -9018,7 +9313,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
9018
9313
  ` + block + `
9019
9314
 
9020
9315
  ${BRIDGE_END}`;
9021
- const fileExists = existsSync31(bridgeFile);
9316
+ const fileExists = existsSync33(bridgeFile);
9022
9317
  let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
9023
9318
  existing = existing.replace(/\r\n/g, "\n");
9024
9319
  const startIdx = existing.indexOf(BRIDGE_START);
@@ -9040,7 +9335,7 @@ ${BRIDGE_END}`;
9040
9335
  }
9041
9336
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
9042
9337
  }
9043
- await writeFile13(bridgeFile, updated, "utf8");
9338
+ await writeFile14(bridgeFile, updated, "utf8");
9044
9339
  if (!quiet) {
9045
9340
  console.log(
9046
9341
  ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
@@ -9068,8 +9363,8 @@ function collectSinceChanges(root, ref) {
9068
9363
 
9069
9364
  // src/commands/memory-add.ts
9070
9365
  import { createHash as createHash2 } from "crypto";
9071
- import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
9072
- 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";
9073
9368
  import path16 from "path";
9074
9369
  import "commander";
9075
9370
  import {
@@ -9077,10 +9372,10 @@ import {
9077
9372
  findProjectRoot as findProjectRoot13,
9078
9373
  inferModulesFromPaths as inferModulesFromPaths3,
9079
9374
  loadConfig as loadConfig5,
9080
- loadMemoriesFromDir as loadMemoriesFromDir25,
9081
- memoryFilePath as memoryFilePath6,
9375
+ loadMemoriesFromDir as loadMemoriesFromDir26,
9376
+ memoryFilePath as memoryFilePath7,
9082
9377
  resolveHaivePaths as resolveHaivePaths10,
9083
- serializeMemory as serializeMemory12,
9378
+ serializeMemory as serializeMemory13,
9084
9379
  suggestSensorFromMemory as suggestSensorFromMemory3
9085
9380
  } from "@hiveai/core";
9086
9381
  function registerMemoryAdd(memory2) {
@@ -9111,7 +9406,7 @@ function registerMemoryAdd(memory2) {
9111
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) => {
9112
9407
  const root = findProjectRoot13(opts.dir);
9113
9408
  const paths = resolveHaivePaths10(root);
9114
- if (!existsSync33(paths.haiveDir)) {
9409
+ if (!existsSync34(paths.haiveDir)) {
9115
9410
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9116
9411
  process.exitCode = 1;
9117
9412
  return;
@@ -9128,7 +9423,7 @@ function registerMemoryAdd(memory2) {
9128
9423
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
9129
9424
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
9130
9425
  if (anchorPaths.length > 0) {
9131
- const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
9426
+ const missing = anchorPaths.filter((p) => !existsSync34(path16.resolve(root, p)));
9132
9427
  if (missing.length > 0) {
9133
9428
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
9134
9429
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -9141,7 +9436,7 @@ function registerMemoryAdd(memory2) {
9141
9436
  const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
9142
9437
  let body;
9143
9438
  if (opts.bodyFile !== void 0) {
9144
- if (!existsSync33(opts.bodyFile)) {
9439
+ if (!existsSync34(opts.bodyFile)) {
9145
9440
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9146
9441
  process.exitCode = 1;
9147
9442
  return;
@@ -9157,9 +9452,9 @@ TODO \u2014 write the memory body.
9157
9452
  `;
9158
9453
  }
9159
9454
  const scope = opts.scope ?? config.defaultScope ?? "personal";
9160
- if (existsSync33(paths.memoriesDir)) {
9455
+ if (existsSync34(paths.memoriesDir)) {
9161
9456
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
9162
- const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
9457
+ const allForHash = await loadMemoriesFromDir26(paths.memoriesDir);
9163
9458
  const hashDup = allForHash.find(
9164
9459
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
9165
9460
  );
@@ -9170,8 +9465,8 @@ TODO \u2014 write the memory body.
9170
9465
  return;
9171
9466
  }
9172
9467
  }
9173
- if (opts.topic && existsSync33(paths.memoriesDir)) {
9174
- const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9468
+ if (opts.topic && existsSync34(paths.memoriesDir)) {
9469
+ const existing = await loadMemoriesFromDir26(paths.memoriesDir);
9175
9470
  const topicMatch = existing.find(
9176
9471
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
9177
9472
  );
@@ -9191,7 +9486,7 @@ TODO \u2014 write the memory body.
9191
9486
  };
9192
9487
  const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
9193
9488
  if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
9194
- await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
9489
+ await writeFile15(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
9195
9490
  ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
9196
9491
  ui.info(`id=${fm.id} revision=${revisionCount}`);
9197
9492
  if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
@@ -9215,15 +9510,15 @@ TODO \u2014 write the memory body.
9215
9510
  sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
9216
9511
  activation
9217
9512
  });
9218
- const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9513
+ const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9219
9514
  await mkdir11(path16.dirname(file), { recursive: true });
9220
- if (existsSync33(file)) {
9515
+ if (existsSync34(file)) {
9221
9516
  ui.error(`Memory already exists at ${file}`);
9222
9517
  process.exitCode = 1;
9223
9518
  return;
9224
9519
  }
9225
- if (existsSync33(paths.memoriesDir)) {
9226
- const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9520
+ if (existsSync34(paths.memoriesDir)) {
9521
+ const existing = await loadMemoriesFromDir26(paths.memoriesDir);
9227
9522
  const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
9228
9523
  const similar = existing.filter(({ memory: memory3 }) => {
9229
9524
  const id = memory3.frontmatter.id.toLowerCase();
@@ -9234,7 +9529,7 @@ TODO \u2014 write the memory body.
9234
9529
  ui.warn("Consider updating one of these with `haive memory update` instead.");
9235
9530
  }
9236
9531
  }
9237
- await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
9532
+ await writeFile15(file, serializeMemory13({ frontmatter, body }), "utf8");
9238
9533
  ui.success(`Created ${path16.relative(root, file)}`);
9239
9534
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
9240
9535
  if (frontmatter.sensor?.autogen) {
@@ -9314,14 +9609,14 @@ function slugify(value) {
9314
9609
  }
9315
9610
 
9316
9611
  // src/commands/memory-list.ts
9317
- import { existsSync as existsSync34 } from "fs";
9612
+ import { existsSync as existsSync35 } from "fs";
9318
9613
  import path17 from "path";
9319
9614
  import "commander";
9320
9615
  import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
9321
9616
 
9322
9617
  // src/utils/fs.ts
9323
9618
  import {
9324
- loadMemoriesFromDir as loadMemoriesFromDir26,
9619
+ loadMemoriesFromDir as loadMemoriesFromDir27,
9325
9620
  loadMemory,
9326
9621
  listMarkdownFilesRecursive
9327
9622
  } from "@hiveai/core";
@@ -9331,12 +9626,12 @@ function registerMemoryList(memory2) {
9331
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) => {
9332
9627
  const root = findProjectRoot14(opts.dir);
9333
9628
  const paths = resolveHaivePaths11(root);
9334
- if (!existsSync34(paths.memoriesDir)) {
9629
+ if (!existsSync35(paths.memoriesDir)) {
9335
9630
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9336
9631
  process.exitCode = 1;
9337
9632
  return;
9338
9633
  }
9339
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
9634
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9340
9635
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
9341
9636
  const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
9342
9637
  const filtered = all.filter((m) => {
@@ -9405,26 +9700,26 @@ function matchesFilters(loaded, opts) {
9405
9700
  }
9406
9701
 
9407
9702
  // src/commands/memory-promote.ts
9408
- import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
9409
- 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";
9410
9705
  import path18 from "path";
9411
9706
  import "commander";
9412
9707
  import {
9413
9708
  findProjectRoot as findProjectRoot15,
9414
- memoryFilePath as memoryFilePath7,
9709
+ memoryFilePath as memoryFilePath8,
9415
9710
  resolveHaivePaths as resolveHaivePaths12,
9416
- serializeMemory as serializeMemory13
9711
+ serializeMemory as serializeMemory14
9417
9712
  } from "@hiveai/core";
9418
9713
  function registerMemoryPromote(memory2) {
9419
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) => {
9420
9715
  const root = findProjectRoot15(opts.dir);
9421
9716
  const paths = resolveHaivePaths12(root);
9422
- if (!existsSync35(paths.memoriesDir)) {
9717
+ if (!existsSync36(paths.memoriesDir)) {
9423
9718
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9424
9719
  process.exitCode = 1;
9425
9720
  return;
9426
9721
  }
9427
- const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
9722
+ const teamAndModule = await loadMemoriesFromDir27(paths.memoriesDir);
9428
9723
  const alreadyShared = teamAndModule.find(
9429
9724
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
9430
9725
  );
@@ -9438,7 +9733,7 @@ function registerMemoryPromote(memory2) {
9438
9733
  }
9439
9734
  return;
9440
9735
  }
9441
- const all = await loadMemoriesFromDir26(paths.personalDir);
9736
+ const all = await loadMemoriesFromDir27(paths.personalDir);
9442
9737
  const found = all.find((m) => m.memory.frontmatter.id === id);
9443
9738
  if (!found) {
9444
9739
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -9453,9 +9748,9 @@ function registerMemoryPromote(memory2) {
9453
9748
  },
9454
9749
  body: found.memory.body
9455
9750
  };
9456
- const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
9751
+ const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
9457
9752
  await mkdir12(path18.dirname(newPath), { recursive: true });
9458
- await writeFile15(newPath, serializeMemory13(updated), "utf8");
9753
+ await writeFile16(newPath, serializeMemory14(updated), "utf8");
9459
9754
  await unlink2(found.filePath);
9460
9755
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
9461
9756
  ui.info(`Now at ${path18.relative(root, newPath)}`);
@@ -9464,25 +9759,25 @@ function registerMemoryPromote(memory2) {
9464
9759
  }
9465
9760
 
9466
9761
  // src/commands/memory-approve.ts
9467
- import { existsSync as existsSync36 } from "fs";
9468
- import { writeFile as writeFile16 } from "fs/promises";
9762
+ import { existsSync as existsSync37 } from "fs";
9763
+ import { writeFile as writeFile17 } from "fs/promises";
9469
9764
  import path19 from "path";
9470
9765
  import "commander";
9471
9766
  import {
9472
9767
  findProjectRoot as findProjectRoot16,
9473
9768
  resolveHaivePaths as resolveHaivePaths13,
9474
- serializeMemory as serializeMemory14
9769
+ serializeMemory as serializeMemory15
9475
9770
  } from "@hiveai/core";
9476
9771
  function registerMemoryApprove(memory2) {
9477
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) => {
9478
9773
  const root = findProjectRoot16(opts.dir);
9479
9774
  const paths = resolveHaivePaths13(root);
9480
- if (!existsSync36(paths.memoriesDir)) {
9775
+ if (!existsSync37(paths.memoriesDir)) {
9481
9776
  ui.error(`No .ai/memories at ${root}.`);
9482
9777
  process.exitCode = 1;
9483
9778
  return;
9484
9779
  }
9485
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
9780
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9486
9781
  if (opts.all || opts.pending) {
9487
9782
  const candidates = all.filter((m) => {
9488
9783
  const s = m.memory.frontmatter.status;
@@ -9499,7 +9794,7 @@ function registerMemoryApprove(memory2) {
9499
9794
  frontmatter: { ...found2.memory.frontmatter, status: "validated" },
9500
9795
  body: found2.memory.body
9501
9796
  };
9502
- await writeFile16(found2.filePath, serializeMemory14(next2), "utf8");
9797
+ await writeFile17(found2.filePath, serializeMemory15(next2), "utf8");
9503
9798
  count++;
9504
9799
  }
9505
9800
  ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
@@ -9528,32 +9823,32 @@ function registerMemoryApprove(memory2) {
9528
9823
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
9529
9824
  body: found.memory.body
9530
9825
  };
9531
- await writeFile16(found.filePath, serializeMemory14(next), "utf8");
9826
+ await writeFile17(found.filePath, serializeMemory15(next), "utf8");
9532
9827
  ui.success(`Approved ${id} (status=validated)`);
9533
9828
  ui.info(path19.relative(root, found.filePath));
9534
9829
  });
9535
9830
  }
9536
9831
 
9537
9832
  // src/commands/memory-update.ts
9538
- import { readFile as readFile11, writeFile as writeFile17 } from "fs/promises";
9539
- import { existsSync as existsSync37 } from "fs";
9833
+ import { readFile as readFile11, writeFile as writeFile18 } from "fs/promises";
9834
+ import { existsSync as existsSync38 } from "fs";
9540
9835
  import path20 from "path";
9541
9836
  import "commander";
9542
9837
  import {
9543
9838
  findProjectRoot as findProjectRoot17,
9544
9839
  resolveHaivePaths as resolveHaivePaths14,
9545
- serializeMemory as serializeMemory15
9840
+ serializeMemory as serializeMemory16
9546
9841
  } from "@hiveai/core";
9547
9842
  function registerMemoryUpdate(memory2) {
9548
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) => {
9549
9844
  const root = findProjectRoot17(opts.dir);
9550
9845
  const paths = resolveHaivePaths14(root);
9551
- if (!existsSync37(paths.memoriesDir)) {
9846
+ if (!existsSync38(paths.memoriesDir)) {
9552
9847
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9553
9848
  process.exitCode = 1;
9554
9849
  return;
9555
9850
  }
9556
- const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9851
+ const memories = await loadMemoriesFromDir27(paths.memoriesDir);
9557
9852
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
9558
9853
  if (!loaded) {
9559
9854
  ui.error(`No memory with id "${id}".`);
@@ -9590,7 +9885,7 @@ function registerMemoryUpdate(memory2) {
9590
9885
  if (opts.author !== void 0) updated.push("author");
9591
9886
  let newBody;
9592
9887
  if (opts.bodyFile !== void 0) {
9593
- if (!existsSync37(opts.bodyFile)) {
9888
+ if (!existsSync38(opts.bodyFile)) {
9594
9889
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9595
9890
  process.exitCode = 1;
9596
9891
  return;
@@ -9611,9 +9906,9 @@ function registerMemoryUpdate(memory2) {
9611
9906
  ui.warn("Nothing to update \u2014 provide at least one option.");
9612
9907
  return;
9613
9908
  }
9614
- await writeFile17(
9909
+ await writeFile18(
9615
9910
  loaded.filePath,
9616
- serializeMemory15({ frontmatter: newFrontmatter, body: newBody }),
9911
+ serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
9617
9912
  "utf8"
9618
9913
  );
9619
9914
  ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
@@ -9635,8 +9930,8 @@ function parseCsv3(value) {
9635
9930
  }
9636
9931
 
9637
9932
  // src/commands/memory-auto-promote.ts
9638
- import { writeFile as writeFile18 } from "fs/promises";
9639
- import { existsSync as existsSync38 } from "fs";
9933
+ import { writeFile as writeFile19 } from "fs/promises";
9934
+ import { existsSync as existsSync39 } from "fs";
9640
9935
  import path21 from "path";
9641
9936
  import "commander";
9642
9937
  import {
@@ -9646,7 +9941,7 @@ import {
9646
9941
  isAutoPromoteEligible as isAutoPromoteEligible3,
9647
9942
  loadUsageIndex as loadUsageIndex14,
9648
9943
  resolveHaivePaths as resolveHaivePaths15,
9649
- serializeMemory as serializeMemory16
9944
+ serializeMemory as serializeMemory17
9650
9945
  } from "@hiveai/core";
9651
9946
  function registerMemoryAutoPromote(memory2) {
9652
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(
@@ -9656,7 +9951,7 @@ function registerMemoryAutoPromote(memory2) {
9656
9951
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9657
9952
  const root = findProjectRoot18(opts.dir);
9658
9953
  const paths = resolveHaivePaths15(root);
9659
- if (!existsSync38(paths.memoriesDir)) {
9954
+ if (!existsSync39(paths.memoriesDir)) {
9660
9955
  ui.error(`No .ai/memories at ${root}.`);
9661
9956
  process.exitCode = 1;
9662
9957
  return;
@@ -9665,7 +9960,7 @@ function registerMemoryAutoPromote(memory2) {
9665
9960
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
9666
9961
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
9667
9962
  };
9668
- const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9963
+ const memories = await loadMemoriesFromDir27(paths.memoriesDir);
9669
9964
  const usage = await loadUsageIndex14(paths);
9670
9965
  const eligible = memories.filter(
9671
9966
  ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
@@ -9688,7 +9983,7 @@ function registerMemoryAutoPromote(memory2) {
9688
9983
  frontmatter: { ...mem.frontmatter, status: "validated" },
9689
9984
  body: mem.body
9690
9985
  };
9691
- await writeFile18(filePath, serializeMemory16(next), "utf8");
9986
+ await writeFile19(filePath, serializeMemory17(next), "utf8");
9692
9987
  written++;
9693
9988
  }
9694
9989
  }
@@ -9699,7 +9994,7 @@ function registerMemoryAutoPromote(memory2) {
9699
9994
 
9700
9995
  // src/commands/memory-edit.ts
9701
9996
  import { spawn as spawn3 } from "child_process";
9702
- import { existsSync as existsSync39 } from "fs";
9997
+ import { existsSync as existsSync40 } from "fs";
9703
9998
  import { readFile as readFile12 } from "fs/promises";
9704
9999
  import path23 from "path";
9705
10000
  import "commander";
@@ -9712,12 +10007,12 @@ function registerMemoryEdit(memory2) {
9712
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) => {
9713
10008
  const root = findProjectRoot19(opts.dir);
9714
10009
  const paths = resolveHaivePaths16(root);
9715
- if (!existsSync39(paths.memoriesDir)) {
10010
+ if (!existsSync40(paths.memoriesDir)) {
9716
10011
  ui.error(`No .ai/memories at ${root}.`);
9717
10012
  process.exitCode = 1;
9718
10013
  return;
9719
10014
  }
9720
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10015
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9721
10016
  const found = all.find((m) => m.memory.frontmatter.id === id);
9722
10017
  if (!found) {
9723
10018
  ui.error(`No memory with id "${id}".`);
@@ -9752,7 +10047,7 @@ function runEditor(editor, file) {
9752
10047
  }
9753
10048
 
9754
10049
  // src/commands/memory-for-files.ts
9755
- import { existsSync as existsSync40 } from "fs";
10050
+ import { existsSync as existsSync41 } from "fs";
9756
10051
  import path24 from "path";
9757
10052
  import "commander";
9758
10053
  import {
@@ -9768,12 +10063,12 @@ function registerMemoryForFiles(memory2) {
9768
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) => {
9769
10064
  const root = findProjectRoot20(opts.dir);
9770
10065
  const paths = resolveHaivePaths17(root);
9771
- if (!existsSync40(paths.memoriesDir)) {
10066
+ if (!existsSync41(paths.memoriesDir)) {
9772
10067
  ui.error(`No .ai/memories at ${root}.`);
9773
10068
  process.exitCode = 1;
9774
10069
  return;
9775
10070
  }
9776
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10071
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9777
10072
  const usage = await loadUsageIndex15(paths);
9778
10073
  const inferred = inferModulesFromPaths4(files);
9779
10074
  const byAnchor = [];
@@ -9880,7 +10175,7 @@ function printGroup(root, label, loaded, usage) {
9880
10175
  }
9881
10176
 
9882
10177
  // src/commands/memory-hot.ts
9883
- import { existsSync as existsSync41 } from "fs";
10178
+ import { existsSync as existsSync43 } from "fs";
9884
10179
  import path25 from "path";
9885
10180
  import "commander";
9886
10181
  import {
@@ -9895,13 +10190,13 @@ function registerMemoryHot(memory2) {
9895
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) => {
9896
10191
  const root = findProjectRoot21(opts.dir);
9897
10192
  const paths = resolveHaivePaths18(root);
9898
- if (!existsSync41(paths.memoriesDir)) {
10193
+ if (!existsSync43(paths.memoriesDir)) {
9899
10194
  ui.error(`No .ai/memories at ${root}.`);
9900
10195
  process.exitCode = 1;
9901
10196
  return;
9902
10197
  }
9903
10198
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
9904
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10199
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
9905
10200
  const usage = await loadUsageIndex16(paths);
9906
10201
  const candidates = all.filter(({ memory: mem }) => {
9907
10202
  const fm = mem.frontmatter;
@@ -9933,16 +10228,16 @@ function registerMemoryHot(memory2) {
9933
10228
  }
9934
10229
 
9935
10230
  // src/commands/memory-tried.ts
9936
- import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
9937
- import { existsSync as existsSync43 } from "fs";
10231
+ import { mkdir as mkdir13, writeFile as writeFile20 } from "fs/promises";
10232
+ import { existsSync as existsSync44 } from "fs";
9938
10233
  import path26 from "path";
9939
10234
  import "commander";
9940
10235
  import {
9941
10236
  buildFrontmatter as buildFrontmatter8,
9942
10237
  findProjectRoot as findProjectRoot22,
9943
- memoryFilePath as memoryFilePath8,
10238
+ memoryFilePath as memoryFilePath9,
9944
10239
  resolveHaivePaths as resolveHaivePaths19,
9945
- serializeMemory as serializeMemory17,
10240
+ serializeMemory as serializeMemory18,
9946
10241
  suggestSensorFromMemory as suggestSensorFromMemory4
9947
10242
  } from "@hiveai/core";
9948
10243
  function registerMemoryTried(memory2) {
@@ -9964,7 +10259,7 @@ function registerMemoryTried(memory2) {
9964
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) => {
9965
10260
  const root = findProjectRoot22(opts.dir);
9966
10261
  const paths = resolveHaivePaths19(root);
9967
- if (!existsSync43(paths.haiveDir)) {
10262
+ if (!existsSync44(paths.haiveDir)) {
9968
10263
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9969
10264
  process.exitCode = 1;
9970
10265
  return;
@@ -9990,14 +10285,14 @@ function registerMemoryTried(memory2) {
9990
10285
  if (sensor) {
9991
10286
  frontmatter.sensor = sensor;
9992
10287
  }
9993
- const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
10288
+ const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9994
10289
  await mkdir13(path26.dirname(file), { recursive: true });
9995
- if (existsSync43(file)) {
10290
+ if (existsSync44(file)) {
9996
10291
  ui.error(`Memory already exists at ${file}`);
9997
10292
  process.exitCode = 1;
9998
10293
  return;
9999
10294
  }
10000
- await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
10295
+ await writeFile20(file, serializeMemory18({ frontmatter, body }), "utf8");
10001
10296
  ui.success(`Recorded: ${path26.relative(root, file)}`);
10002
10297
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
10003
10298
  if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
@@ -10010,7 +10305,7 @@ function parseCsv4(value) {
10010
10305
 
10011
10306
  // src/commands/memory-seed.ts
10012
10307
  import { readFile as readFile13 } from "fs/promises";
10013
- import { existsSync as existsSync44 } from "fs";
10308
+ import { existsSync as existsSync45 } from "fs";
10014
10309
  import path27 from "path";
10015
10310
  import "commander";
10016
10311
  import {
@@ -10050,7 +10345,7 @@ function registerMemorySeed(memory2) {
10050
10345
  }
10051
10346
  return;
10052
10347
  }
10053
- if (!existsSync44(paths.haiveDir)) {
10348
+ if (!existsSync45(paths.haiveDir)) {
10054
10349
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10055
10350
  process.exitCode = 1;
10056
10351
  return;
@@ -10106,7 +10401,7 @@ function registerMemorySeed(memory2) {
10106
10401
  }
10107
10402
 
10108
10403
  // src/commands/memory-pending.ts
10109
- import { existsSync as existsSync45 } from "fs";
10404
+ import { existsSync as existsSync46 } from "fs";
10110
10405
  import path28 from "path";
10111
10406
  import "commander";
10112
10407
  import {
@@ -10119,12 +10414,12 @@ function registerMemoryPending(memory2) {
10119
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) => {
10120
10415
  const root = findProjectRoot24(opts.dir);
10121
10416
  const paths = resolveHaivePaths21(root);
10122
- if (!existsSync45(paths.memoriesDir)) {
10417
+ if (!existsSync46(paths.memoriesDir)) {
10123
10418
  ui.error(`No .ai/memories at ${root}.`);
10124
10419
  process.exitCode = 1;
10125
10420
  return;
10126
10421
  }
10127
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10422
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10128
10423
  const usage = await loadUsageIndex17(paths);
10129
10424
  const filterFn = ({ memory: mem }) => {
10130
10425
  if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
@@ -10177,7 +10472,7 @@ function registerMemoryPending(memory2) {
10177
10472
  }
10178
10473
 
10179
10474
  // src/commands/memory-query.ts
10180
- import { existsSync as existsSync46 } from "fs";
10475
+ import { existsSync as existsSync47 } from "fs";
10181
10476
  import path29 from "path";
10182
10477
  import "commander";
10183
10478
  import {
@@ -10194,7 +10489,7 @@ function registerMemoryQuery(memory2) {
10194
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) => {
10195
10490
  const root = findProjectRoot25(opts.dir);
10196
10491
  const paths = resolveHaivePaths22(root);
10197
- if (!existsSync46(paths.memoriesDir)) {
10492
+ if (!existsSync47(paths.memoriesDir)) {
10198
10493
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
10199
10494
  process.exitCode = 1;
10200
10495
  return;
@@ -10205,7 +10500,7 @@ function registerMemoryQuery(memory2) {
10205
10500
  return;
10206
10501
  }
10207
10502
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
10208
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10503
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10209
10504
  const passesFilters2 = (mem) => {
10210
10505
  const fm = mem.frontmatter;
10211
10506
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -10252,8 +10547,8 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
10252
10547
  }
10253
10548
 
10254
10549
  // src/commands/memory-reject.ts
10255
- import { writeFile as writeFile20 } from "fs/promises";
10256
- import { existsSync as existsSync47 } from "fs";
10550
+ import { writeFile as writeFile21 } from "fs/promises";
10551
+ import { existsSync as existsSync48 } from "fs";
10257
10552
  import "commander";
10258
10553
  import {
10259
10554
  findProjectRoot as findProjectRoot26,
@@ -10261,27 +10556,27 @@ import {
10261
10556
  recordRejection as recordRejection3,
10262
10557
  resolveHaivePaths as resolveHaivePaths23,
10263
10558
  saveUsageIndex as saveUsageIndex4,
10264
- serializeMemory as serializeMemory18
10559
+ serializeMemory as serializeMemory19
10265
10560
  } from "@hiveai/core";
10266
10561
  function registerMemoryReject(memory2) {
10267
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) => {
10268
10563
  const root = findProjectRoot26(opts.dir);
10269
10564
  const paths = resolveHaivePaths23(root);
10270
- if (!existsSync47(paths.memoriesDir)) {
10565
+ if (!existsSync48(paths.memoriesDir)) {
10271
10566
  ui.error(`No .ai/memories at ${root}.`);
10272
10567
  process.exitCode = 1;
10273
10568
  return;
10274
10569
  }
10275
- const memories = await loadMemoriesFromDir26(paths.memoriesDir);
10570
+ const memories = await loadMemoriesFromDir27(paths.memoriesDir);
10276
10571
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
10277
10572
  if (!loaded) {
10278
10573
  ui.error(`No memory with id "${id}".`);
10279
10574
  process.exitCode = 1;
10280
10575
  return;
10281
10576
  }
10282
- await writeFile20(
10577
+ await writeFile21(
10283
10578
  loaded.filePath,
10284
- serializeMemory18({
10579
+ serializeMemory19({
10285
10580
  frontmatter: {
10286
10581
  ...loaded.memory.frontmatter,
10287
10582
  status: "rejected",
@@ -10303,7 +10598,7 @@ function registerMemoryReject(memory2) {
10303
10598
  }
10304
10599
 
10305
10600
  // src/commands/memory-rm.ts
10306
- import { existsSync as existsSync48 } from "fs";
10601
+ import { existsSync as existsSync49 } from "fs";
10307
10602
  import { unlink as unlink3 } from "fs/promises";
10308
10603
  import path30 from "path";
10309
10604
  import { createInterface as createInterface2 } from "readline/promises";
@@ -10318,12 +10613,12 @@ function registerMemoryRm(memory2) {
10318
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) => {
10319
10614
  const root = findProjectRoot27(opts.dir);
10320
10615
  const paths = resolveHaivePaths24(root);
10321
- if (!existsSync48(paths.memoriesDir)) {
10616
+ if (!existsSync49(paths.memoriesDir)) {
10322
10617
  ui.error(`No .ai/memories at ${root}.`);
10323
10618
  process.exitCode = 1;
10324
10619
  return;
10325
10620
  }
10326
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10621
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10327
10622
  const found = all.find((m) => m.memory.frontmatter.id === id);
10328
10623
  if (!found) {
10329
10624
  ui.error(`No memory with id "${id}".`);
@@ -10354,7 +10649,7 @@ function registerMemoryRm(memory2) {
10354
10649
  }
10355
10650
 
10356
10651
  // src/commands/memory-show.ts
10357
- import { existsSync as existsSync49 } from "fs";
10652
+ import { existsSync as existsSync50 } from "fs";
10358
10653
  import { readFile as readFile14 } from "fs/promises";
10359
10654
  import path31 from "path";
10360
10655
  import "commander";
@@ -10369,12 +10664,12 @@ function registerMemoryShow(memory2) {
10369
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) => {
10370
10665
  const root = findProjectRoot28(opts.dir);
10371
10666
  const paths = resolveHaivePaths25(root);
10372
- if (!existsSync49(paths.memoriesDir)) {
10667
+ if (!existsSync50(paths.memoriesDir)) {
10373
10668
  ui.error(`No .ai/memories at ${root}.`);
10374
10669
  process.exitCode = 1;
10375
10670
  return;
10376
10671
  }
10377
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10672
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10378
10673
  const found = all.find((m) => m.memory.frontmatter.id === id);
10379
10674
  if (!found) {
10380
10675
  ui.error(`No memory with id "${id}".`);
@@ -10413,7 +10708,7 @@ function registerMemoryShow(memory2) {
10413
10708
  }
10414
10709
 
10415
10710
  // src/commands/memory-stats.ts
10416
- import { existsSync as existsSync50 } from "fs";
10711
+ import { existsSync as existsSync51 } from "fs";
10417
10712
  import path33 from "path";
10418
10713
  import "commander";
10419
10714
  import {
@@ -10427,12 +10722,12 @@ function registerMemoryStats(memory2) {
10427
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) => {
10428
10723
  const root = findProjectRoot29(opts.dir);
10429
10724
  const paths = resolveHaivePaths26(root);
10430
- if (!existsSync50(paths.memoriesDir)) {
10725
+ if (!existsSync51(paths.memoriesDir)) {
10431
10726
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10432
10727
  process.exitCode = 1;
10433
10728
  return;
10434
10729
  }
10435
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10730
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10436
10731
  const usage = await loadUsageIndex21(paths);
10437
10732
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10438
10733
  if (target.length === 0) {
@@ -10458,7 +10753,7 @@ function registerMemoryStats(memory2) {
10458
10753
  }
10459
10754
 
10460
10755
  // src/commands/memory-impact.ts
10461
- import { existsSync as existsSync51 } from "fs";
10756
+ import { existsSync as existsSync53 } from "fs";
10462
10757
  import "commander";
10463
10758
  import {
10464
10759
  compareImpact,
@@ -10475,12 +10770,12 @@ function registerMemoryImpact(memory2) {
10475
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) => {
10476
10771
  const root = findProjectRoot30(opts.dir);
10477
10772
  const paths = resolveHaivePaths27(root);
10478
- if (!existsSync51(paths.memoriesDir)) {
10773
+ if (!existsSync53(paths.memoriesDir)) {
10479
10774
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10480
10775
  process.exitCode = 1;
10481
10776
  return;
10482
10777
  }
10483
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10778
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10484
10779
  const usageIndex = await loadUsageIndex23(paths);
10485
10780
  let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
10486
10781
  const fm = mem.frontmatter;
@@ -10546,7 +10841,7 @@ function pad(value, width) {
10546
10841
  }
10547
10842
 
10548
10843
  // src/commands/memory-feedback.ts
10549
- import { existsSync as existsSync53 } from "fs";
10844
+ import { existsSync as existsSync54 } from "fs";
10550
10845
  import "commander";
10551
10846
  import {
10552
10847
  computeImpact as computeImpact4,
@@ -10569,12 +10864,12 @@ function registerMemoryFeedback(memory2) {
10569
10864
  }
10570
10865
  const root = findProjectRoot31(opts.dir);
10571
10866
  const paths = resolveHaivePaths28(root);
10572
- if (!existsSync53(paths.memoriesDir)) {
10867
+ if (!existsSync54(paths.memoriesDir)) {
10573
10868
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10574
10869
  process.exitCode = 1;
10575
10870
  return;
10576
10871
  }
10577
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10872
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10578
10873
  const target = all.find((m) => m.memory.frontmatter.id === id);
10579
10874
  if (!target) {
10580
10875
  ui.error(`No memory with id '${id}'.`);
@@ -10600,14 +10895,14 @@ function registerMemoryFeedback(memory2) {
10600
10895
  }
10601
10896
 
10602
10897
  // src/commands/memory-verify.ts
10603
- import { writeFile as writeFile21 } from "fs/promises";
10604
- import { existsSync as existsSync54 } from "fs";
10898
+ import { writeFile as writeFile23 } from "fs/promises";
10899
+ import { existsSync as existsSync55 } from "fs";
10605
10900
  import path34 from "path";
10606
10901
  import "commander";
10607
10902
  import {
10608
10903
  findProjectRoot as findProjectRoot32,
10609
10904
  resolveHaivePaths as resolveHaivePaths29,
10610
- serializeMemory as serializeMemory19,
10905
+ serializeMemory as serializeMemory20,
10611
10906
  verifyAnchor as verifyAnchor3
10612
10907
  } from "@hiveai/core";
10613
10908
  function registerMemoryVerify(memory2) {
@@ -10616,7 +10911,7 @@ function registerMemoryVerify(memory2) {
10616
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) => {
10617
10912
  const root = findProjectRoot32(opts.dir);
10618
10913
  const paths = resolveHaivePaths29(root);
10619
- if (!existsSync54(paths.memoriesDir)) {
10914
+ if (!existsSync55(paths.memoriesDir)) {
10620
10915
  if (opts.json) {
10621
10916
  console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
10622
10917
  } else {
@@ -10625,7 +10920,7 @@ function registerMemoryVerify(memory2) {
10625
10920
  process.exitCode = 1;
10626
10921
  return;
10627
10922
  }
10628
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10923
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10629
10924
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10630
10925
  if (opts.id && targets.length === 0) {
10631
10926
  if (opts.json) {
@@ -10674,7 +10969,7 @@ function registerMemoryVerify(memory2) {
10674
10969
  }
10675
10970
  if (opts.update) {
10676
10971
  const next = applyVerification2(mem, result);
10677
- await writeFile21(filePath, serializeMemory19(next), "utf8");
10972
+ await writeFile23(filePath, serializeMemory20(next), "utf8");
10678
10973
  updated++;
10679
10974
  }
10680
10975
  }
@@ -10737,7 +11032,7 @@ function applyVerification2(mem, result) {
10737
11032
 
10738
11033
  // src/commands/memory-import.ts
10739
11034
  import { readFile as readFile15 } from "fs/promises";
10740
- import { existsSync as existsSync55 } from "fs";
11035
+ import { existsSync as existsSync56 } from "fs";
10741
11036
  import "commander";
10742
11037
  import {
10743
11038
  findProjectRoot as findProjectRoot33,
@@ -10749,12 +11044,12 @@ function registerMemoryImport(memory2) {
10749
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) => {
10750
11045
  const root = findProjectRoot33(opts.dir);
10751
11046
  const paths = resolveHaivePaths30(root);
10752
- if (!existsSync55(paths.haiveDir)) {
11047
+ if (!existsSync56(paths.haiveDir)) {
10753
11048
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10754
11049
  process.exitCode = 1;
10755
11050
  return;
10756
11051
  }
10757
- if (!existsSync55(opts.from)) {
11052
+ if (!existsSync56(opts.from)) {
10758
11053
  ui.error(`File not found: ${opts.from}`);
10759
11054
  process.exitCode = 1;
10760
11055
  return;
@@ -10787,15 +11082,15 @@ function registerMemoryImport(memory2) {
10787
11082
  }
10788
11083
 
10789
11084
  // src/commands/memory-import-changelog.ts
10790
- import { existsSync as existsSync56 } from "fs";
10791
- 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";
10792
11087
  import path35 from "path";
10793
11088
  import "commander";
10794
11089
  import {
10795
11090
  buildFrontmatter as buildFrontmatter9,
10796
11091
  findProjectRoot as findProjectRoot34,
10797
11092
  resolveHaivePaths as resolveHaivePaths31,
10798
- serializeMemory as serializeMemory20
11093
+ serializeMemory as serializeMemory21
10799
11094
  } from "@hiveai/core";
10800
11095
  function parseChangelog(content) {
10801
11096
  const entries = [];
@@ -10862,7 +11157,7 @@ function registerMemoryImportChangelog(memory2) {
10862
11157
  const root = findProjectRoot34(opts.dir);
10863
11158
  const paths = resolveHaivePaths31(root);
10864
11159
  const changelogPath = path35.resolve(root, opts.fromChangelog);
10865
- if (!existsSync56(changelogPath)) {
11160
+ if (!existsSync57(changelogPath)) {
10866
11161
  ui.error(`CHANGELOG not found: ${changelogPath}`);
10867
11162
  process.exitCode = 1;
10868
11163
  return;
@@ -10925,9 +11220,9 @@ function registerMemoryImportChangelog(memory2) {
10925
11220
  paths: [path35.relative(root, changelogPath)],
10926
11221
  topic: `changelog-${pkgName}-${entry.version}`
10927
11222
  });
10928
- await writeFile23(
11223
+ await writeFile24(
10929
11224
  path35.join(teamDir, `${fm.id}.md`),
10930
- serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
11225
+ serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
10931
11226
  "utf8"
10932
11227
  );
10933
11228
  console.log(ui.green(` \u2713 ${fm.id}`));
@@ -10949,15 +11244,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
10949
11244
  }
10950
11245
 
10951
11246
  // src/commands/memory-digest.ts
10952
- import { existsSync as existsSync57 } from "fs";
10953
- import { writeFile as writeFile24 } from "fs/promises";
11247
+ import { existsSync as existsSync58 } from "fs";
11248
+ import { writeFile as writeFile25 } from "fs/promises";
10954
11249
  import path36 from "path";
10955
11250
  import "commander";
10956
11251
  import {
10957
11252
  deriveConfidence as deriveConfidence12,
10958
11253
  findProjectRoot as findProjectRoot35,
10959
11254
  getUsage as getUsage20,
10960
- loadMemoriesFromDir as loadMemoriesFromDir27,
11255
+ loadMemoriesFromDir as loadMemoriesFromDir28,
10961
11256
  loadUsageIndex as loadUsageIndex25,
10962
11257
  resolveHaivePaths as resolveHaivePaths32
10963
11258
  } from "@hiveai/core";
@@ -10974,7 +11269,7 @@ function registerMemoryDigest(program2) {
10974
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) => {
10975
11270
  const root = findProjectRoot35(opts.dir);
10976
11271
  const paths = resolveHaivePaths32(root);
10977
- if (!existsSync57(paths.memoriesDir)) {
11272
+ if (!existsSync58(paths.memoriesDir)) {
10978
11273
  ui.error("No .ai/memories found. Run `haive init` first.");
10979
11274
  process.exitCode = 1;
10980
11275
  return;
@@ -10982,7 +11277,7 @@ function registerMemoryDigest(program2) {
10982
11277
  const days = Math.max(1, Number(opts.days ?? 7));
10983
11278
  const scopeFilter = opts.scope ?? "team";
10984
11279
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
10985
- const all = await loadMemoriesFromDir27(paths.memoriesDir);
11280
+ const all = await loadMemoriesFromDir28(paths.memoriesDir);
10986
11281
  const usage = await loadUsageIndex25(paths);
10987
11282
  const recent = all.filter(({ memory: mem }) => {
10988
11283
  const fm = mem.frontmatter;
@@ -11047,7 +11342,7 @@ function registerMemoryDigest(program2) {
11047
11342
  const digest = lines.join("\n");
11048
11343
  if (opts.out) {
11049
11344
  const outPath = path36.resolve(process.cwd(), opts.out);
11050
- await writeFile24(outPath, digest, "utf8");
11345
+ await writeFile25(outPath, digest, "utf8");
11051
11346
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
11052
11347
  } else {
11053
11348
  console.log(digest);
@@ -11056,22 +11351,22 @@ function registerMemoryDigest(program2) {
11056
11351
  }
11057
11352
 
11058
11353
  // src/commands/session-end.ts
11059
- import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
11060
- 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";
11061
11356
  import { spawn as spawn4 } from "child_process";
11062
11357
  import path37 from "path";
11063
11358
  import "commander";
11064
11359
  import {
11065
11360
  buildFrontmatter as buildFrontmatter10,
11066
11361
  findProjectRoot as findProjectRoot36,
11067
- loadMemoriesFromDir as loadMemoriesFromDir28,
11068
- memoryFilePath as memoryFilePath9,
11362
+ loadMemoriesFromDir as loadMemoriesFromDir29,
11363
+ memoryFilePath as memoryFilePath10,
11069
11364
  resolveHaivePaths as resolveHaivePaths33,
11070
- serializeMemory as serializeMemory21
11365
+ serializeMemory as serializeMemory23
11071
11366
  } from "@hiveai/core";
11072
11367
  async function buildAutoRecap(paths) {
11073
11368
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11074
- if (!existsSync58(obsFile)) return await buildGitAutoRecap(paths);
11369
+ if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
11075
11370
  const raw = await readFile17(obsFile, "utf8").catch(() => "");
11076
11371
  if (!raw.trim()) return await buildGitAutoRecap(paths);
11077
11372
  const lines = raw.split("\n").filter(Boolean);
@@ -11262,7 +11557,7 @@ function registerSessionEnd(session2) {
11262
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) => {
11263
11558
  const root = findProjectRoot36(opts.dir);
11264
11559
  const paths = resolveHaivePaths33(root);
11265
- if (!existsSync58(paths.haiveDir)) {
11560
+ if (!existsSync59(paths.haiveDir)) {
11266
11561
  if (opts.auto || opts.quiet) return;
11267
11562
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11268
11563
  process.exitCode = 1;
@@ -11295,7 +11590,7 @@ function registerSessionEnd(session2) {
11295
11590
  });
11296
11591
  const topic = recapTopic2(scope, opts.module);
11297
11592
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
11298
- const missingPaths = filesTouched.filter((p) => !existsSync58(path37.resolve(root, p)));
11593
+ const missingPaths = filesTouched.filter((p) => !existsSync59(path37.resolve(root, p)));
11299
11594
  if (missingPaths.length > 0 && !opts.quiet) {
11300
11595
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
11301
11596
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
@@ -11303,11 +11598,11 @@ function registerSessionEnd(session2) {
11303
11598
  const cleanupObservations = async () => {
11304
11599
  if (!opts.auto) return;
11305
11600
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11306
- if (existsSync58(obsFile)) await rm2(obsFile).catch(() => {
11601
+ if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
11307
11602
  });
11308
11603
  };
11309
- if (existsSync58(paths.memoriesDir)) {
11310
- const existing = await loadMemoriesFromDir28(paths.memoriesDir);
11604
+ if (existsSync59(paths.memoriesDir)) {
11605
+ const existing = await loadMemoriesFromDir29(paths.memoriesDir);
11311
11606
  const topicMatch = existing.find(
11312
11607
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
11313
11608
  );
@@ -11323,7 +11618,7 @@ function registerSessionEnd(session2) {
11323
11618
  paths: filesTouched.length ? filesTouched : fm.anchor.paths
11324
11619
  }
11325
11620
  };
11326
- await writeFile25(topicMatch.filePath, serializeMemory21({ frontmatter: newFrontmatter, body }), "utf8");
11621
+ await writeFile26(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
11327
11622
  await cleanupObservations();
11328
11623
  if (!opts.quiet) {
11329
11624
  ui.success(`Session recap updated (revision #${revisionCount})`);
@@ -11343,9 +11638,9 @@ function registerSessionEnd(session2) {
11343
11638
  topic,
11344
11639
  status: "validated"
11345
11640
  });
11346
- const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
11641
+ const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
11347
11642
  await mkdir15(path37.dirname(file), { recursive: true });
11348
- await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
11643
+ await writeFile26(file, serializeMemory23({ frontmatter, body }), "utf8");
11349
11644
  await cleanupObservations();
11350
11645
  if (!opts.quiet) {
11351
11646
  ui.success(`Session recap created`);
@@ -11368,7 +11663,7 @@ function normalizeAnchorPath(root, filePath) {
11368
11663
  }
11369
11664
 
11370
11665
  // src/commands/snapshot.ts
11371
- import { existsSync as existsSync59 } from "fs";
11666
+ import { existsSync as existsSync60 } from "fs";
11372
11667
  import { readdir as readdir4 } from "fs/promises";
11373
11668
  import path38 from "path";
11374
11669
  import "commander";
@@ -11403,14 +11698,14 @@ function registerSnapshot(program2) {
11403
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) => {
11404
11699
  const root = findProjectRoot37(opts.dir);
11405
11700
  const paths = resolveHaivePaths34(root);
11406
- if (!existsSync59(paths.haiveDir)) {
11701
+ if (!existsSync60(paths.haiveDir)) {
11407
11702
  ui.error("No .ai/ found. Run `haive init` first.");
11408
11703
  process.exitCode = 1;
11409
11704
  return;
11410
11705
  }
11411
11706
  if (opts.list) {
11412
11707
  const contractsDir = path38.join(paths.haiveDir, "contracts");
11413
- if (!existsSync59(contractsDir)) {
11708
+ if (!existsSync60(contractsDir)) {
11414
11709
  console.log(ui.dim("No contract snapshots found."));
11415
11710
  return;
11416
11711
  }
@@ -11534,18 +11829,18 @@ function detectFormat(filePath) {
11534
11829
  }
11535
11830
 
11536
11831
  // src/commands/hub.ts
11537
- import { existsSync as existsSync60 } from "fs";
11538
- 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";
11539
11834
  import path39 from "path";
11540
11835
  import { spawnSync as spawnSync5 } from "child_process";
11541
11836
  import "commander";
11542
11837
  import {
11543
11838
  findProjectRoot as findProjectRoot38,
11544
11839
  loadConfig as loadConfig8,
11545
- loadMemoriesFromDir as loadMemoriesFromDir29,
11840
+ loadMemoriesFromDir as loadMemoriesFromDir30,
11546
11841
  resolveHaivePaths as resolveHaivePaths35,
11547
11842
  saveConfig as saveConfig3,
11548
- serializeMemory as serializeMemory23
11843
+ serializeMemory as serializeMemory24
11549
11844
  } from "@hiveai/core";
11550
11845
  function registerHub(program2) {
11551
11846
  const hub = program2.command("hub").description(
@@ -11568,7 +11863,7 @@ function registerHub(program2) {
11568
11863
  }
11569
11864
  const sharedDir = path39.join(absPath, ".ai", "memories", "shared");
11570
11865
  await mkdir16(sharedDir, { recursive: true });
11571
- await writeFile26(
11866
+ await writeFile27(
11572
11867
  path39.join(absPath, ".ai", "README.md"),
11573
11868
  `# hAIve Team Knowledge Hub
11574
11869
 
@@ -11590,7 +11885,7 @@ haive hub pull # import into a project
11590
11885
  `,
11591
11886
  "utf8"
11592
11887
  );
11593
- await writeFile26(
11888
+ await writeFile27(
11594
11889
  path39.join(absPath, ".gitignore"),
11595
11890
  ".ai/.cache/\n.ai/memories/personal/\n",
11596
11891
  "utf8"
@@ -11636,7 +11931,7 @@ Next steps:
11636
11931
  return;
11637
11932
  }
11638
11933
  const hubRoot = path39.resolve(root, config.hubPath);
11639
- if (!existsSync60(hubRoot)) {
11934
+ if (!existsSync61(hubRoot)) {
11640
11935
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
11641
11936
  process.exitCode = 1;
11642
11937
  return;
@@ -11644,7 +11939,7 @@ Next steps:
11644
11939
  const projectName = path39.basename(root);
11645
11940
  const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
11646
11941
  await mkdir16(destDir, { recursive: true });
11647
- const all = await loadMemoriesFromDir29(paths.memoriesDir);
11942
+ const all = await loadMemoriesFromDir30(paths.memoriesDir);
11648
11943
  const shared = all.filter(
11649
11944
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
11650
11945
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -11662,7 +11957,7 @@ Next steps:
11662
11957
  const fm = memory2.frontmatter;
11663
11958
  const fileName = `${fm.id}.md`;
11664
11959
  const destPath = path39.join(destDir, fileName);
11665
- await writeFile26(destPath, serializeMemory23(memory2), "utf8");
11960
+ await writeFile27(destPath, serializeMemory24(memory2), "utf8");
11666
11961
  pushed++;
11667
11962
  }
11668
11963
  console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
@@ -11706,7 +12001,7 @@ Next steps:
11706
12001
  }
11707
12002
  const hubRoot = path39.resolve(root, config.hubPath);
11708
12003
  const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
11709
- if (!existsSync60(hubSharedDir)) {
12004
+ if (!existsSync61(hubSharedDir)) {
11710
12005
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
11711
12006
  return;
11712
12007
  }
@@ -11761,7 +12056,7 @@ Next steps:
11761
12056
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
11762
12057
  );
11763
12058
  const sharedDir = path39.join(paths.memoriesDir, "shared");
11764
- if (existsSync60(sharedDir)) {
12059
+ if (existsSync61(sharedDir)) {
11765
12060
  const { readdir: readdir7 } = await import("fs/promises");
11766
12061
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
11767
12062
  console.log(`
@@ -11773,7 +12068,7 @@ Next steps:
11773
12068
  } else {
11774
12069
  console.log(ui.dim(" No imported shared memories yet."));
11775
12070
  }
11776
- const all = await loadMemoriesFromDir29(paths.memoriesDir);
12071
+ const all = await loadMemoriesFromDir30(paths.memoriesDir);
11777
12072
  const outgoing = all.filter(
11778
12073
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
11779
12074
  );
@@ -11783,20 +12078,20 @@ Next steps:
11783
12078
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
11784
12079
  }
11785
12080
  void readFile18;
11786
- void writeFile26;
12081
+ void writeFile27;
11787
12082
  void saveConfig3;
11788
12083
  });
11789
12084
  }
11790
12085
 
11791
12086
  // src/commands/stats.ts
11792
12087
  import "commander";
11793
- import { existsSync as existsSync61 } from "fs";
11794
- 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";
11795
12090
  import path40 from "path";
11796
12091
  import {
11797
12092
  aggregateUsage,
11798
12093
  findProjectRoot as findProjectRoot39,
11799
- loadMemoriesFromDir as loadMemoriesFromDir30,
12094
+ loadMemoriesFromDir as loadMemoriesFromDir31,
11800
12095
  loadUsageIndex as loadUsageIndex26,
11801
12096
  parseSince,
11802
12097
  readUsageEvents as readUsageEvents2,
@@ -11868,8 +12163,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11868
12163
  const size = await usageLogSize(paths);
11869
12164
  let events = await readUsageEvents2(paths);
11870
12165
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
11871
- if (existsSync61(paths.memoriesDir)) {
11872
- const mems = await loadMemoriesFromDir30(paths.memoriesDir);
12166
+ if (existsSync63(paths.memoriesDir)) {
12167
+ const mems = await loadMemoriesFromDir31(paths.memoriesDir);
11873
12168
  for (const { memory: memory2 } of mems) {
11874
12169
  const fm = memory2.frontmatter;
11875
12170
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -11911,7 +12206,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11911
12206
  top_memory_reads: memoryHitsLeader,
11912
12207
  roi_hints: roiHints
11913
12208
  };
11914
- await writeFile27(outAbs, JSON.stringify(payload, null, 2), "utf8");
12209
+ await writeFile28(outAbs, JSON.stringify(payload, null, 2), "utf8");
11915
12210
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
11916
12211
  }
11917
12212
  async function renderMemoryHits(paths, opts) {
@@ -12088,8 +12383,8 @@ function summarize(name, t0, payload, notes) {
12088
12383
  }
12089
12384
 
12090
12385
  // src/commands/benchmark.ts
12091
- import { existsSync as existsSync63 } from "fs";
12092
- 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";
12093
12388
  import path41 from "path";
12094
12389
  import "commander";
12095
12390
  import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
@@ -12106,7 +12401,7 @@ function registerBenchmark(program2) {
12106
12401
  const markdown = renderMarkdown(root, summary, rows);
12107
12402
  if (opts.out) {
12108
12403
  const outFile = path41.isAbsolute(opts.out) ? opts.out : path41.join(root, opts.out);
12109
- await writeFile28(outFile, markdown, "utf8");
12404
+ await writeFile29(outFile, markdown, "utf8");
12110
12405
  ui.success(`wrote ${path41.relative(process.cwd(), outFile)}`);
12111
12406
  return;
12112
12407
  }
@@ -12136,14 +12431,14 @@ function resolveBenchmarkRoot(dir) {
12136
12431
  return path41.join(projectRoot, candidate);
12137
12432
  }
12138
12433
  async function collectRows(root) {
12139
- if (!existsSync63(root)) throw new Error(`Benchmark directory not found: ${root}`);
12434
+ if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
12140
12435
  const entries = await readdir5(root, { withFileTypes: true });
12141
12436
  const rows = [];
12142
12437
  for (const entry of entries) {
12143
12438
  if (!entry.isDirectory()) continue;
12144
12439
  const fixtureDir = path41.join(root, entry.name);
12145
12440
  const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
12146
- if (!existsSync63(reportFile)) continue;
12441
+ if (!existsSync64(reportFile)) continue;
12147
12442
  const report = await readFile19(reportFile, "utf8");
12148
12443
  rows.push(parseAgentReport(entry.name, report));
12149
12444
  }
@@ -12233,14 +12528,15 @@ function escapeRegExp(value) {
12233
12528
  }
12234
12529
 
12235
12530
  // src/commands/eval.ts
12236
- import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
12237
- 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";
12238
12533
  import path43 from "path";
12239
12534
  import "commander";
12240
12535
  import {
12241
12536
  aggregateRetrieval,
12242
12537
  aggregateSensors,
12243
12538
  buildReport,
12539
+ compareEvalReports,
12244
12540
  findProjectRoot as findProjectRoot42,
12245
12541
  resolveHaivePaths as resolveHaivePaths38,
12246
12542
  scoreRetrievalCase,
@@ -12250,10 +12546,10 @@ import {
12250
12546
  function registerEval(program2) {
12251
12547
  program2.command("eval").description(
12252
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."
12253
- ).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) => {
12254
12550
  const root = findProjectRoot42(opts.dir);
12255
12551
  const paths = resolveHaivePaths38(root);
12256
- if (!existsSync64(paths.memoriesDir)) {
12552
+ if (!existsSync65(paths.memoriesDir)) {
12257
12553
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12258
12554
  process.exitCode = 1;
12259
12555
  return;
@@ -12285,30 +12581,82 @@ function registerEval(program2) {
12285
12581
  sensorAgg = aggregateSensors(results);
12286
12582
  }
12287
12583
  const report = buildReport(retrievalAgg, sensorAgg);
12288
- if (opts.json) {
12289
- console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report }, null, 2));
12290
- } else {
12291
- const md = renderMarkdown2(root, k, resolvedSpec.source, report);
12292
- if (opts.out) {
12293
- const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12294
- await writeFile29(outFile, md, "utf8");
12295
- ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12296
- } else {
12297
- console.log(md);
12298
- }
12299
- }
12300
- if (opts.failUnder !== void 0) {
12301
- const threshold = Number(opts.failUnder);
12302
- if (Number.isNaN(threshold)) {
12303
- ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
12304
- process.exitCode = 1;
12305
- } else if (report.score < threshold) {
12306
- 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.`);
12307
12600
  process.exitCode = 1;
12601
+ return;
12308
12602
  }
12603
+ const snapshot = JSON.parse(await readFile20(baselineFile, "utf8"));
12604
+ delta = compareEvalReports(snapshot.report, report);
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));
12309
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);
12310
12623
  });
12311
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
+ }
12312
12660
  async function resolveSpec(opts, root, memoriesDir) {
12313
12661
  if (opts.spec) {
12314
12662
  const file = path43.resolve(opts.spec);
@@ -12316,10 +12664,10 @@ async function resolveSpec(opts, root, memoriesDir) {
12316
12664
  return { spec: JSON.parse(raw), source: file };
12317
12665
  }
12318
12666
  const defaultSpec = path43.join(root, ".ai", "eval", "spec.json");
12319
- if (existsSync64(defaultSpec)) {
12667
+ if (existsSync65(defaultSpec)) {
12320
12668
  const raw = await readFile20(defaultSpec, "utf8");
12321
12669
  const explicit = JSON.parse(raw);
12322
- const memories2 = await loadMemoriesFromDir26(memoriesDir);
12670
+ const memories2 = await loadMemoriesFromDir27(memoriesDir);
12323
12671
  const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
12324
12672
  return {
12325
12673
  spec: {
@@ -12329,7 +12677,7 @@ async function resolveSpec(opts, root, memoriesDir) {
12329
12677
  source: ".ai/eval/spec.json + synthesized anchored retrieval"
12330
12678
  };
12331
12679
  }
12332
- const memories = await loadMemoriesFromDir26(memoriesDir);
12680
+ const memories = await loadMemoriesFromDir27(memoriesDir);
12333
12681
  return {
12334
12682
  spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
12335
12683
  source: "synthesized anchored retrieval"
@@ -12419,8 +12767,8 @@ function renderMarkdown2(root, k, source, report) {
12419
12767
  }
12420
12768
 
12421
12769
  // src/commands/memory-suggest.ts
12422
- import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
12423
- import { existsSync as existsSync65 } from "fs";
12770
+ import { mkdir as mkdir19, writeFile as writeFile31 } from "fs/promises";
12771
+ import { existsSync as existsSync66 } from "fs";
12424
12772
  import path44 from "path";
12425
12773
  import "commander";
12426
12774
  import {
@@ -12428,12 +12776,12 @@ import {
12428
12776
  buildFrontmatter as buildFrontmatter11,
12429
12777
  findProjectRoot as findProjectRoot43,
12430
12778
  loadConfig as loadConfig9,
12431
- loadMemoriesFromDir as loadMemoriesFromDir31,
12432
- memoryFilePath as memoryFilePath10,
12779
+ loadMemoriesFromDir as loadMemoriesFromDir33,
12780
+ memoryFilePath as memoryFilePath11,
12433
12781
  parseSince as parseSince2,
12434
12782
  readUsageEvents as readUsageEvents3,
12435
12783
  resolveHaivePaths as resolveHaivePaths39,
12436
- serializeMemory as serializeMemory24
12784
+ serializeMemory as serializeMemory25
12437
12785
  } from "@hiveai/core";
12438
12786
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
12439
12787
  "mem_search",
@@ -12499,7 +12847,7 @@ function registerMemorySuggest(memory2) {
12499
12847
  }
12500
12848
  const created = [];
12501
12849
  const skipped = [];
12502
- const existing = existsSync65(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12850
+ const existing = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
12503
12851
  for (const s of top) {
12504
12852
  const slug = slugify2(s.query);
12505
12853
  if (!slug) {
@@ -12521,13 +12869,13 @@ function registerMemorySuggest(memory2) {
12521
12869
  status
12522
12870
  });
12523
12871
  const body = renderTemplate(s, fm.id, status);
12524
- const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
12525
- await mkdir18(path44.dirname(file), { recursive: true });
12526
- 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)) {
12527
12875
  skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
12528
12876
  continue;
12529
12877
  }
12530
- await writeFile30(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
12878
+ await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
12531
12879
  created.push({ id: fm.id, file: path44.relative(root, file), query: s.query });
12532
12880
  }
12533
12881
  if (opts.json) {
@@ -12626,8 +12974,8 @@ function truncate2(text, max) {
12626
12974
  }
12627
12975
 
12628
12976
  // src/commands/memory-archive.ts
12629
- import { existsSync as existsSync66 } from "fs";
12630
- import { writeFile as writeFile31 } from "fs/promises";
12977
+ import { existsSync as existsSync67 } from "fs";
12978
+ import { writeFile as writeFile33 } from "fs/promises";
12631
12979
  import path45 from "path";
12632
12980
  import "commander";
12633
12981
  import {
@@ -12635,10 +12983,10 @@ import {
12635
12983
  getUsage as getUsage21,
12636
12984
  retirementSignal as retirementSignal2,
12637
12985
  loadConfig as loadConfig10,
12638
- loadMemoriesFromDir as loadMemoriesFromDir33,
12986
+ loadMemoriesFromDir as loadMemoriesFromDir34,
12639
12987
  loadUsageIndex as loadUsageIndex27,
12640
12988
  resolveHaivePaths as resolveHaivePaths40,
12641
- serializeMemory as serializeMemory25
12989
+ serializeMemory as serializeMemory26
12642
12990
  } from "@hiveai/core";
12643
12991
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
12644
12992
  function registerMemoryArchive(memory2) {
@@ -12647,7 +12995,7 @@ function registerMemoryArchive(memory2) {
12647
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) => {
12648
12996
  const root = findProjectRoot44(opts.dir);
12649
12997
  const paths = resolveHaivePaths40(root);
12650
- if (!existsSync66(paths.memoriesDir)) {
12998
+ if (!existsSync67(paths.memoriesDir)) {
12651
12999
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12652
13000
  process.exitCode = 1;
12653
13001
  return;
@@ -12661,7 +13009,7 @@ function registerMemoryArchive(memory2) {
12661
13009
  return;
12662
13010
  }
12663
13011
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
12664
- const all = await loadMemoriesFromDir33(paths.memoriesDir);
13012
+ const all = await loadMemoriesFromDir34(paths.memoriesDir);
12665
13013
  const usage = await loadUsageIndex27(paths);
12666
13014
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
12667
13015
  const candidates = [];
@@ -12672,7 +13020,7 @@ function registerMemoryArchive(memory2) {
12672
13020
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
12673
13021
  const retired = retirementSignal2(fm, mem.body);
12674
13022
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
12675
- 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)));
12676
13024
  const isAnchorless = !hasAnyAnchor;
12677
13025
  if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
12678
13026
  const u = getUsage21(usage, fm.id);
@@ -12721,7 +13069,7 @@ function registerMemoryArchive(memory2) {
12721
13069
  if (!found) continue;
12722
13070
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
12723
13071
  try {
12724
- 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");
12725
13073
  archived++;
12726
13074
  } catch (err) {
12727
13075
  if (!opts.json) {
@@ -12747,8 +13095,8 @@ function parseDays(input) {
12747
13095
  }
12748
13096
 
12749
13097
  // src/commands/doctor.ts
12750
- import { existsSync as existsSync67, statSync as statSync2 } from "fs";
12751
- 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";
12752
13100
  import path46 from "path";
12753
13101
  import { execFileSync, execSync as execSync3 } from "child_process";
12754
13102
  import "commander";
@@ -12759,7 +13107,7 @@ import {
12759
13107
  isStackPackSeed as isStackPackSeed4,
12760
13108
  loadCodeMap as loadCodeMap7,
12761
13109
  loadConfig as loadConfig11,
12762
- loadMemoriesFromDir as loadMemoriesFromDir34,
13110
+ loadMemoriesFromDir as loadMemoriesFromDir35,
12763
13111
  loadUsageIndex as loadUsageIndex28,
12764
13112
  readUsageEvents as readUsageEvents4,
12765
13113
  resolveHaivePaths as resolveHaivePaths41
@@ -12774,7 +13122,7 @@ function registerDoctor(program2) {
12774
13122
  const findings = [];
12775
13123
  const repairs = [];
12776
13124
  const config = await loadConfig11(paths);
12777
- if (!existsSync67(paths.haiveDir)) {
13125
+ if (!existsSync68(paths.haiveDir)) {
12778
13126
  findings.push({
12779
13127
  severity: "error",
12780
13128
  code: "not-initialized",
@@ -12795,7 +13143,7 @@ function registerDoctor(program2) {
12795
13143
  })
12796
13144
  );
12797
13145
  }
12798
- if (!existsSync67(paths.projectContext)) {
13146
+ if (!existsSync68(paths.projectContext)) {
12799
13147
  findings.push({
12800
13148
  severity: "warn",
12801
13149
  code: "no-project-context",
@@ -12803,8 +13151,8 @@ function registerDoctor(program2) {
12803
13151
  fix: "haive init"
12804
13152
  });
12805
13153
  } else {
12806
- const { readFile: readFile25 } = await import("fs/promises");
12807
- const content = await readFile25(paths.projectContext, "utf8");
13154
+ const { readFile: readFile26 } = await import("fs/promises");
13155
+ const content = await readFile26(paths.projectContext, "utf8");
12808
13156
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
12809
13157
  if (isTemplate) {
12810
13158
  findings.push({
@@ -12824,7 +13172,7 @@ function registerDoctor(program2) {
12824
13172
  });
12825
13173
  }
12826
13174
  }
12827
- const memories = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
13175
+ const memories = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
12828
13176
  const now = Date.now();
12829
13177
  if (memories.length === 0) {
12830
13178
  findings.push({
@@ -12976,10 +13324,10 @@ function registerDoctor(program2) {
12976
13324
  if (config.enforcement?.requireBriefingFirst) {
12977
13325
  const claudeSettings = path46.join(root, ".claude", "settings.local.json");
12978
13326
  let hasClaudeEnforcement = false;
12979
- if (existsSync67(claudeSettings)) {
13327
+ if (existsSync68(claudeSettings)) {
12980
13328
  try {
12981
- const { readFile: readFile25 } = await import("fs/promises");
12982
- const raw = await readFile25(claudeSettings, "utf8");
13329
+ const { readFile: readFile26 } = await import("fs/promises");
13330
+ const raw = await readFile26(claudeSettings, "utf8");
12983
13331
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
12984
13332
  } catch {
12985
13333
  hasClaudeEnforcement = false;
@@ -13002,7 +13350,7 @@ function registerDoctor(program2) {
13002
13350
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
13003
13351
  });
13004
13352
  }
13005
- findings.push(...await collectInstallFindings(root, "0.12.4"));
13353
+ findings.push(...await collectInstallFindings(root, "0.12.9"));
13006
13354
  findings.push(...await collectToolchainFindings(root));
13007
13355
  try {
13008
13356
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -13010,7 +13358,7 @@ function registerDoctor(program2) {
13010
13358
  timeout: 3e3,
13011
13359
  stdio: ["ignore", "pipe", "ignore"]
13012
13360
  }).trim();
13013
- const cliVersion = "0.12.4";
13361
+ const cliVersion = "0.12.9";
13014
13362
  if (legacyRaw && legacyRaw !== cliVersion) {
13015
13363
  findings.push({
13016
13364
  severity: "warn",
@@ -13032,14 +13380,14 @@ npm uninstall -g @hiveai/mcp`
13032
13380
  ];
13033
13381
  const staleConfigs = [];
13034
13382
  for (const cfgPath of configPaths) {
13035
- if (!existsSync67(cfgPath)) continue;
13383
+ if (!existsSync68(cfgPath)) continue;
13036
13384
  try {
13037
13385
  const raw = await readFile21(cfgPath, "utf8");
13038
13386
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
13039
13387
  staleConfigs.push(path46.relative(root, cfgPath));
13040
13388
  if (opts.fix && !opts.dryRun) {
13041
13389
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
13042
- await writeFile33(cfgPath, updated, "utf8");
13390
+ await writeFile34(cfgPath, updated, "utf8");
13043
13391
  }
13044
13392
  }
13045
13393
  } catch {
@@ -13328,7 +13676,7 @@ which -a haive`
13328
13676
  ];
13329
13677
  for (const rel of integrationFiles) {
13330
13678
  const file = path46.join(root, rel);
13331
- if (!existsSync67(file)) continue;
13679
+ if (!existsSync68(file)) continue;
13332
13680
  const text = await readFile21(file, "utf8").catch(() => "");
13333
13681
  for (const bin of extractAbsoluteHaiveBins(text)) {
13334
13682
  const version = versionForBinary(bin);
@@ -13376,7 +13724,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
13376
13724
  const isHaiveWorkspace = (await readJson(path46.join(root, "package.json")))?.name === "haive-monorepo";
13377
13725
  if (!isHaiveWorkspace) return findings;
13378
13726
  const cliDist = path46.join(root, "packages/cli/dist/index.js");
13379
- if (!existsSync67(cliDist)) {
13727
+ if (!existsSync68(cliDist)) {
13380
13728
  findings.push({
13381
13729
  severity: "warn",
13382
13730
  code: "workspace-dist-missing",
@@ -13400,7 +13748,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
13400
13748
  "packages/core/src/index.ts",
13401
13749
  "packages/mcp/src/server.ts",
13402
13750
  "packages/cli/src/index.ts"
13403
- ].map((rel) => path46.join(root, rel)).filter(existsSync67);
13751
+ ].map((rel) => path46.join(root, rel)).filter(existsSync68);
13404
13752
  if (sourceFiles.length > 0) {
13405
13753
  const distMtime = statSync2(cliDist).mtimeMs;
13406
13754
  const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
@@ -13489,7 +13837,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
13489
13837
  }
13490
13838
  }
13491
13839
  async function readJson(file) {
13492
- if (!existsSync67(file)) return null;
13840
+ if (!existsSync68(file)) return null;
13493
13841
  try {
13494
13842
  return JSON.parse(await readFile21(file, "utf8"));
13495
13843
  } catch {
@@ -13560,11 +13908,11 @@ function extractAbsoluteHaiveBins(text) {
13560
13908
  }
13561
13909
 
13562
13910
  // src/commands/playback.ts
13563
- import { existsSync as existsSync68 } from "fs";
13911
+ import { existsSync as existsSync69 } from "fs";
13564
13912
  import "commander";
13565
13913
  import {
13566
13914
  findProjectRoot as findProjectRoot46,
13567
- loadMemoriesFromDir as loadMemoriesFromDir35,
13915
+ loadMemoriesFromDir as loadMemoriesFromDir36,
13568
13916
  parseSince as parseSince3,
13569
13917
  readUsageEvents as readUsageEvents5,
13570
13918
  resolveHaivePaths as resolveHaivePaths42
@@ -13590,7 +13938,7 @@ function registerPlayback(program2) {
13590
13938
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
13591
13939
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
13592
13940
  const sessions = bucketSessions(filtered, gapMs);
13593
- const all = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13941
+ const all = existsSync69(paths.memoriesDir) ? await loadMemoriesFromDir36(paths.memoriesDir) : [];
13594
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);
13595
13943
  const enriched = sessions.map((s, i) => {
13596
13944
  const startMs = Date.parse(s.start);
@@ -13832,11 +14180,11 @@ function runCommand3(cmd, args, cwd) {
13832
14180
  }
13833
14181
 
13834
14182
  // src/commands/welcome.ts
13835
- import { existsSync as existsSync69 } from "fs";
14183
+ import { existsSync as existsSync70 } from "fs";
13836
14184
  import "commander";
13837
14185
  import {
13838
14186
  findProjectRoot as findProjectRoot48,
13839
- loadMemoriesFromDir as loadMemoriesFromDir36,
14187
+ loadMemoriesFromDir as loadMemoriesFromDir37,
13840
14188
  resolveHaivePaths as resolveHaivePaths44
13841
14189
  } from "@hiveai/core";
13842
14190
  var TYPE_RANK = {
@@ -13854,12 +14202,12 @@ function registerWelcome(program2) {
13854
14202
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
13855
14203
  const root = findProjectRoot48(opts.dir);
13856
14204
  const paths = resolveHaivePaths44(root);
13857
- if (!existsSync69(paths.memoriesDir)) {
14205
+ if (!existsSync70(paths.memoriesDir)) {
13858
14206
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
13859
14207
  process.exitCode = 1;
13860
14208
  return;
13861
14209
  }
13862
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14210
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
13863
14211
  const team = all.filter(
13864
14212
  ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
13865
14213
  );
@@ -13926,7 +14274,7 @@ function registerResolveProject(program2) {
13926
14274
  }
13927
14275
 
13928
14276
  // src/commands/runtime-journal.ts
13929
- import { existsSync as existsSync70 } from "fs";
14277
+ import { existsSync as existsSync71 } from "fs";
13930
14278
  import path48 from "path";
13931
14279
  import "commander";
13932
14280
  import {
@@ -13952,7 +14300,7 @@ function registerRuntime(program2) {
13952
14300
  const root = path48.resolve(opts.dir ?? process.cwd());
13953
14301
  const paths = resolveHaivePaths45(findProjectRoot49(root));
13954
14302
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
13955
- if (!existsSync70(paths.haiveDir)) {
14303
+ if (!existsSync71(paths.haiveDir)) {
13956
14304
  ui.error("No .ai/ \u2014 run `haive init` first.");
13957
14305
  process.exitCode = 1;
13958
14306
  return;
@@ -13967,7 +14315,7 @@ function registerRuntime(program2) {
13967
14315
  }
13968
14316
 
13969
14317
  // src/commands/memory-timeline.ts
13970
- import { existsSync as existsSync71 } from "fs";
14318
+ import { existsSync as existsSync73 } from "fs";
13971
14319
  import path49 from "path";
13972
14320
  import "commander";
13973
14321
  import {
@@ -13986,13 +14334,13 @@ function registerMemoryTimeline(memory2) {
13986
14334
  }
13987
14335
  const root = path49.resolve(opts.dir ?? process.cwd());
13988
14336
  const paths = resolveHaivePaths46(findProjectRoot50(root));
13989
- if (!existsSync71(paths.memoriesDir)) {
14337
+ if (!existsSync73(paths.memoriesDir)) {
13990
14338
  ui.error("No memories \u2014 run `haive init`.");
13991
14339
  process.exitCode = 1;
13992
14340
  return;
13993
14341
  }
13994
14342
  const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
13995
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
14343
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
13996
14344
  const { entries, notice } = collectTimelineEntries2(all, {
13997
14345
  memoryId: opts.id,
13998
14346
  topic: opts.topic,
@@ -14004,7 +14352,7 @@ function registerMemoryTimeline(memory2) {
14004
14352
  }
14005
14353
 
14006
14354
  // src/commands/memory-conflict-candidates.ts
14007
- import { existsSync as existsSync73 } from "fs";
14355
+ import { existsSync as existsSync74 } from "fs";
14008
14356
  import path50 from "path";
14009
14357
  import "commander";
14010
14358
  import {
@@ -14029,7 +14377,7 @@ function registerMemoryConflictCandidates(memory2) {
14029
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) => {
14030
14378
  const root = path50.resolve(opts.dir ?? process.cwd());
14031
14379
  const paths = resolveHaivePaths47(findProjectRoot51(root));
14032
- if (!existsSync73(paths.memoriesDir)) {
14380
+ if (!existsSync74(paths.memoriesDir)) {
14033
14381
  ui.error("No memories \u2014 run `haive init`.");
14034
14382
  process.exitCode = 1;
14035
14383
  return;
@@ -14039,7 +14387,7 @@ function registerMemoryConflictCandidates(memory2) {
14039
14387
  const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
14040
14388
  const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
14041
14389
  const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
14042
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
14390
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
14043
14391
  const lexical = findLexicalConflictPairs2(all, {
14044
14392
  sinceDays,
14045
14393
  types: parseTypes(opts.types),
@@ -14065,8 +14413,8 @@ function registerMemoryConflictCandidates(memory2) {
14065
14413
 
14066
14414
  // src/commands/enforce.ts
14067
14415
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
14068
- import { existsSync as existsSync74, statSync as statSync3 } from "fs";
14069
- 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";
14070
14418
  import path51 from "path";
14071
14419
  import "commander";
14072
14420
  import {
@@ -14075,7 +14423,7 @@ import {
14075
14423
  hasRecentBriefingMarker as hasRecentBriefingMarker2,
14076
14424
  isFreshIsoDate,
14077
14425
  loadConfig as loadConfig13,
14078
- loadMemoriesFromDir as loadMemoriesFromDir37,
14426
+ loadMemoriesFromDir as loadMemoriesFromDir38,
14079
14427
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
14080
14428
  readRecentBriefingMarker,
14081
14429
  resolveBriefingBudget as resolveBriefingBudget3,
@@ -14094,7 +14442,7 @@ function registerEnforce(program2) {
14094
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) => {
14095
14443
  const root = findProjectRoot52(opts.dir);
14096
14444
  const paths = resolveHaivePaths48(root);
14097
- await mkdir19(paths.haiveDir, { recursive: true });
14445
+ await mkdir20(paths.haiveDir, { recursive: true });
14098
14446
  const current = await loadConfig13(paths);
14099
14447
  await saveConfig4(paths, {
14100
14448
  ...current,
@@ -14140,14 +14488,14 @@ function registerEnforce(program2) {
14140
14488
  const root = findProjectRoot52(opts.dir);
14141
14489
  const paths = resolveHaivePaths48(root);
14142
14490
  const cacheDir = path51.join(paths.haiveDir, ".cache");
14143
- if (existsSync74(cacheDir)) {
14491
+ if (existsSync75(cacheDir)) {
14144
14492
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
14145
14493
  else {
14146
14494
  const removed = await cleanupCacheDir(cacheDir);
14147
14495
  ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
14148
14496
  }
14149
14497
  }
14150
- if (existsSync74(paths.runtimeDir)) {
14498
+ if (existsSync75(paths.runtimeDir)) {
14151
14499
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
14152
14500
  else {
14153
14501
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
@@ -14172,8 +14520,8 @@ function registerEnforce(program2) {
14172
14520
  const root = resolveRoot(opts.dir, payload);
14173
14521
  if (!root) return;
14174
14522
  const paths = resolveHaivePaths48(root);
14175
- if (!existsSync74(paths.haiveDir)) return;
14176
- await mkdir19(paths.runtimeDir, { recursive: true });
14523
+ if (!existsSync75(paths.haiveDir)) return;
14524
+ await mkdir20(paths.runtimeDir, { recursive: true });
14177
14525
  const sessionId = opts.sessionId ?? payload.session_id;
14178
14526
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
14179
14527
  await applyLightweightRepairs(root, paths);
@@ -14235,7 +14583,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14235
14583
  const root = resolveRoot(opts.dir, payload);
14236
14584
  if (!root) return;
14237
14585
  const paths = resolveHaivePaths48(root);
14238
- if (!existsSync74(paths.haiveDir)) return;
14586
+ if (!existsSync75(paths.haiveDir)) return;
14239
14587
  if (!isWriteLikeTool(payload)) return;
14240
14588
  const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
14241
14589
  if (ok) {
@@ -14279,7 +14627,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14279
14627
  async function buildFinishReport(dir) {
14280
14628
  const root = findProjectRoot52(dir);
14281
14629
  const paths = resolveHaivePaths48(root);
14282
- const initialized = existsSync74(paths.haiveDir);
14630
+ const initialized = existsSync75(paths.haiveDir);
14283
14631
  const config = initialized ? await loadConfig13(paths) : {};
14284
14632
  const mode = config.enforcement?.mode ?? "strict";
14285
14633
  const findings = [];
@@ -14477,7 +14825,7 @@ function finishReport(root, initialized, mode, findings, config) {
14477
14825
  async function runWithEnforcement(command, args, opts) {
14478
14826
  const root = findProjectRoot52(opts.dir);
14479
14827
  const paths = resolveHaivePaths48(root);
14480
- if (!existsSync74(paths.haiveDir)) {
14828
+ if (!existsSync75(paths.haiveDir)) {
14481
14829
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
14482
14830
  process.exit(1);
14483
14831
  }
@@ -14547,7 +14895,7 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14547
14895
  memoryIds: briefing.memories.map((m) => m.id)
14548
14896
  });
14549
14897
  const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
14550
- await mkdir19(dir, { recursive: true });
14898
+ await mkdir20(dir, { recursive: true });
14551
14899
  const file = path51.join(dir, `${sessionId}.md`);
14552
14900
  const parts = [
14553
14901
  "# hAIve Briefing",
@@ -14566,13 +14914,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14566
14914
  if (briefing.setup_warnings.length > 0) {
14567
14915
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
14568
14916
  }
14569
- await writeFile34(file, parts.join("\n") + "\n", "utf8");
14917
+ await writeFile35(file, parts.join("\n") + "\n", "utf8");
14570
14918
  return file;
14571
14919
  }
14572
14920
  async function buildEnforcementReport(dir, stage, sessionId) {
14573
14921
  const root = findProjectRoot52(dir);
14574
14922
  const paths = resolveHaivePaths48(root);
14575
- const initialized = existsSync74(paths.haiveDir);
14923
+ const initialized = existsSync75(paths.haiveDir);
14576
14924
  const config = initialized ? await loadConfig13(paths) : {};
14577
14925
  if (initialized) await applyLightweightRepairs(root, paths);
14578
14926
  const mode = config.enforcement?.mode ?? "strict";
@@ -14603,7 +14951,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
14603
14951
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
14604
14952
  });
14605
14953
  }
14606
- findings.push(...await inspectIntegrationVersions(root, "0.12.4"));
14954
+ findings.push(...await inspectIntegrationVersions(root, "0.12.9"));
14607
14955
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
14608
14956
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
14609
14957
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -14673,8 +15021,8 @@ function withCategories(report) {
14673
15021
  };
14674
15022
  }
14675
15023
  async function hasRecentSessionRecap(paths) {
14676
- if (!existsSync74(paths.memoriesDir)) return false;
14677
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15024
+ if (!existsSync75(paths.memoriesDir)) return false;
15025
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
14678
15026
  return all.some(({ memory: memory2 }) => {
14679
15027
  const fm = memory2.frontmatter;
14680
15028
  const freshnessDate = fm.verified_at ?? fm.created_at;
@@ -14682,8 +15030,8 @@ async function hasRecentSessionRecap(paths) {
14682
15030
  });
14683
15031
  }
14684
15032
  async function verifyMemoryPolicy(paths, config) {
14685
- if (!existsSync74(paths.memoriesDir)) return [];
14686
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15033
+ if (!existsSync75(paths.memoriesDir)) return [];
15034
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
14687
15035
  const findings = [];
14688
15036
  const staleImportant = [];
14689
15037
  let verified = 0;
@@ -14720,12 +15068,12 @@ async function verifyMemoryPolicy(paths, config) {
14720
15068
  return findings;
14721
15069
  }
14722
15070
  async function verifyDecisionCoverage(paths, stage, sessionId) {
14723
- if (!existsSync74(paths.memoriesDir)) return [];
15071
+ if (!existsSync75(paths.memoriesDir)) return [];
14724
15072
  const changedFiles = await getChangedFiles(paths.root, stage);
14725
15073
  if (changedFiles.length === 0) {
14726
15074
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
14727
15075
  }
14728
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15076
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
14729
15077
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
14730
15078
  const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
14731
15079
  const fm = memory2.frontmatter;
@@ -14829,7 +15177,7 @@ function isGeneratedArtifactStatusLine(line) {
14829
15177
  }
14830
15178
  async function cleanupRuntimeDir(runtimeDir) {
14831
15179
  let removed = 0;
14832
- await mkdir19(runtimeDir, { recursive: true });
15180
+ await mkdir20(runtimeDir, { recursive: true });
14833
15181
  const entries = await readdir6(runtimeDir, { withFileTypes: true }).catch(() => []);
14834
15182
  for (const entry of entries) {
14835
15183
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
@@ -14840,9 +15188,9 @@ async function cleanupRuntimeDir(runtimeDir) {
14840
15188
  await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
14841
15189
  removed++;
14842
15190
  }
14843
- await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14844
- if (!existsSync74(path51.join(runtimeDir, "README.md"))) {
14845
- 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(
14846
15194
  path51.join(runtimeDir, "README.md"),
14847
15195
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
14848
15196
  "utf8"
@@ -14852,14 +15200,14 @@ async function cleanupRuntimeDir(runtimeDir) {
14852
15200
  }
14853
15201
  async function cleanupCacheDir(cacheDir) {
14854
15202
  let removed = 0;
14855
- await mkdir19(cacheDir, { recursive: true });
15203
+ await mkdir20(cacheDir, { recursive: true });
14856
15204
  const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
14857
15205
  for (const entry of entries) {
14858
15206
  if (entry.name === ".gitignore") continue;
14859
15207
  await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
14860
15208
  removed++;
14861
15209
  }
14862
- await writeFile34(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
15210
+ await writeFile35(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
14863
15211
  return removed;
14864
15212
  }
14865
15213
  async function cleanupEnforcementDir(enforcementDir) {
@@ -14884,7 +15232,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
14884
15232
  const findings = [];
14885
15233
  for (const rel of files) {
14886
15234
  const file = path51.join(root, rel);
14887
- if (!existsSync74(file)) continue;
15235
+ if (!existsSync75(file)) continue;
14888
15236
  const text = await readFile23(file, "utf8").catch(() => "");
14889
15237
  for (const bin of extractAbsoluteHaiveBins2(text)) {
14890
15238
  const version = versionForBinary2(bin);
@@ -14994,7 +15342,7 @@ async function resolveCiDiffRange(root) {
14994
15342
  }
14995
15343
  async function resolveGithubEventRange(root) {
14996
15344
  const eventPath = process.env.GITHUB_EVENT_PATH;
14997
- if (!eventPath || !existsSync74(eventPath)) return null;
15345
+ if (!eventPath || !existsSync75(eventPath)) return null;
14998
15346
  try {
14999
15347
  const event = JSON.parse(await readFile23(eventPath, "utf8"));
15000
15348
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
@@ -15277,11 +15625,11 @@ function buildScore(findings, threshold = 80) {
15277
15625
  }
15278
15626
  async function installGitEnforcement(root) {
15279
15627
  const hooksDir = path51.join(root, ".git", "hooks");
15280
- if (!existsSync74(path51.join(root, ".git"))) {
15628
+ if (!existsSync75(path51.join(root, ".git"))) {
15281
15629
  ui.warn("No .git directory found; git enforcement hooks skipped.");
15282
15630
  return;
15283
15631
  }
15284
- await mkdir19(hooksDir, { recursive: true });
15632
+ await mkdir20(hooksDir, { recursive: true });
15285
15633
  const hooks = [
15286
15634
  {
15287
15635
  name: "pre-commit",
@@ -15300,17 +15648,17 @@ haive enforce check --stage pre-push --dir . || exit $?
15300
15648
  ];
15301
15649
  for (const hook of hooks) {
15302
15650
  const file = path51.join(hooksDir, hook.name);
15303
- if (existsSync74(file)) {
15651
+ if (existsSync75(file)) {
15304
15652
  const current = await readFile23(file, "utf8").catch(() => "");
15305
15653
  if (current.includes(ENFORCE_HOOK_MARKER)) {
15306
- await writeFile34(file, hook.body, "utf8");
15654
+ await writeFile35(file, hook.body, "utf8");
15307
15655
  } else {
15308
- await writeFile34(file, `${current.trimEnd()}
15656
+ await writeFile35(file, `${current.trimEnd()}
15309
15657
 
15310
15658
  ${hook.body}`, "utf8");
15311
15659
  }
15312
15660
  } else {
15313
- await writeFile34(file, hook.body, "utf8");
15661
+ await writeFile35(file, hook.body, "utf8");
15314
15662
  }
15315
15663
  await chmod2(file, 493);
15316
15664
  }
@@ -15318,12 +15666,12 @@ ${hook.body}`, "utf8");
15318
15666
  }
15319
15667
  async function installCiEnforcement(root) {
15320
15668
  const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
15321
- await mkdir19(path51.dirname(workflowPath), { recursive: true });
15322
- if (existsSync74(workflowPath)) {
15669
+ await mkdir20(path51.dirname(workflowPath), { recursive: true });
15670
+ if (existsSync75(workflowPath)) {
15323
15671
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
15324
15672
  return;
15325
15673
  }
15326
- await writeFile34(workflowPath, `name: haive-enforcement
15674
+ await writeFile35(workflowPath, `name: haive-enforcement
15327
15675
 
15328
15676
  on:
15329
15677
  pull_request:
@@ -15451,11 +15799,11 @@ function normalizeToolPath(file, root) {
15451
15799
  return path51.relative(root, normalized).replace(/\\/g, "/");
15452
15800
  }
15453
15801
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
15454
- if (!existsSync74(paths.memoriesDir)) return [];
15802
+ if (!existsSync75(paths.memoriesDir)) return [];
15455
15803
  const marker = await readRecentBriefingMarker(paths, sessionId);
15456
15804
  const consulted = new Set(marker?.memory_ids ?? []);
15457
15805
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
15458
- const all = await loadMemoriesFromDir37(paths.memoriesDir);
15806
+ const all = await loadMemoriesFromDir38(paths.memoriesDir);
15459
15807
  return all.filter(({ memory: memory2 }) => {
15460
15808
  const fm = memory2.frontmatter;
15461
15809
  if (!policyTypes.has(fm.type)) return false;
@@ -15525,19 +15873,19 @@ function registerRun(program2) {
15525
15873
 
15526
15874
  // src/commands/sensors.ts
15527
15875
  import { execFile as execFile2 } from "child_process";
15528
- import { existsSync as existsSync75 } from "fs";
15529
- 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";
15530
15878
  import path53 from "path";
15531
15879
  import { promisify as promisify2 } from "util";
15532
15880
  import "commander";
15533
15881
  import {
15534
15882
  findProjectRoot as findProjectRoot53,
15535
15883
  isRetiredMemory as isRetiredMemory3,
15536
- loadMemoriesFromDir as loadMemoriesFromDir38,
15884
+ loadMemoriesFromDir as loadMemoriesFromDir39,
15537
15885
  resolveHaivePaths as resolveHaivePaths49,
15538
15886
  runSensors as runSensors2,
15539
15887
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
15540
- serializeMemory as serializeMemory26
15888
+ serializeMemory as serializeMemory27
15541
15889
  } from "@hiveai/core";
15542
15890
  var exec2 = promisify2(execFile2);
15543
15891
  function registerSensors(program2) {
@@ -15608,7 +15956,7 @@ function registerSensors(program2) {
15608
15956
  }
15609
15957
  const root = findProjectRoot53(opts.dir);
15610
15958
  const paths = resolveHaivePaths49(root);
15611
- const loaded = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
15959
+ const loaded = existsSync76(paths.memoriesDir) ? await loadMemoriesFromDir39(paths.memoriesDir) : [];
15612
15960
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
15613
15961
  if (!found) {
15614
15962
  ui.error(`No memory found with id ${id}`);
@@ -15628,7 +15976,7 @@ function registerSensors(program2) {
15628
15976
  },
15629
15977
  body: found.memory.body
15630
15978
  };
15631
- await writeFile35(found.filePath, serializeMemory26(next), "utf8");
15979
+ await writeFile36(found.filePath, serializeMemory27(next), "utf8");
15632
15980
  ui.success(`Updated ${id}: sensor severity=${severity}`);
15633
15981
  if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
15634
15982
  ui.info(`message=${sensor.message}`);
@@ -15644,10 +15992,10 @@ function registerSensors(program2) {
15644
15992
  const paths = resolveHaivePaths49(root);
15645
15993
  const rows = await sensorRows(paths);
15646
15994
  const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
15647
- await mkdir20(outDir, { recursive: true });
15995
+ await mkdir21(outDir, { recursive: true });
15648
15996
  const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
15649
15997
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
15650
- await writeFile35(outPath, content, "utf8");
15998
+ await writeFile36(outPath, content, "utf8");
15651
15999
  if (format === "grep") await chmod3(outPath, 493);
15652
16000
  ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
15653
16001
  });
@@ -15670,8 +16018,8 @@ async function sensorRows(paths) {
15670
16018
  });
15671
16019
  }
15672
16020
  async function runnableSensorMemories(paths, regexOnly = true) {
15673
- if (!existsSync75(paths.memoriesDir)) return [];
15674
- const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
16021
+ if (!existsSync76(paths.memoriesDir)) return [];
16022
+ const loaded = await loadMemoriesFromDir39(paths.memoriesDir);
15675
16023
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
15676
16024
  const sensor = memory2.frontmatter.sensor;
15677
16025
  if (!sensor) return false;
@@ -15711,9 +16059,253 @@ function shellQuote(value) {
15711
16059
  return `'${value.replace(/'/g, "'\\''")}'`;
15712
16060
  }
15713
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
+
15714
16306
  // src/index.ts
15715
- var program = new Command56();
15716
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.4").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");
15717
16309
  registerInit(program);
15718
16310
  registerWelcome(program);
15719
16311
  registerResolveProject(program);
@@ -15722,6 +16314,8 @@ registerEnforce(program);
15722
16314
  registerRun(program);
15723
16315
  registerAgent(program);
15724
16316
  registerSensors(program);
16317
+ registerIngest(program);
16318
+ registerDashboard(program);
15725
16319
  registerMcp(program);
15726
16320
  registerBriefing(program);
15727
16321
  registerTui(program);