@hiveai/cli 0.10.8 → 0.11.0

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 Command53 } from "commander";
4
+ import { Command as Command55 } 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(([path53, changes]) => ({ path: path53, changes }));
202
+ let entries = [...counts.entries()].map(([path54, changes]) => ({ path: path54, 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)));
@@ -2743,6 +2743,7 @@ ${SEED_FOOTER(stack)}` });
2743
2743
  }
2744
2744
 
2745
2745
  // src/commands/init.ts
2746
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.11.0"}`;
2746
2747
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
2747
2748
 
2748
2749
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -2777,6 +2778,7 @@ This repo uses **hAIve** for shared context. The map:
2777
2778
  1. **Before editing** for a goal, call \`get_briefing\` (task + files/symbols) to load ranked context \u2014 or \`mem_relevant_to\` if project context is already loaded this session.
2778
2779
  2. **When an approach fails**, call \`mem_tried\` right away so the next agent skips the dead end.
2779
2780
  3. **Before closing** a substantive session, run the \`post_task\` prompt to capture what was learned.
2781
+ 4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, then rerun it.
2780
2782
 
2781
2783
  If the haive MCP server is not available, tell the developer rather than silently skipping it.
2782
2784
 
@@ -2803,6 +2805,7 @@ This repository uses **hAIve**. Running \`haive init\` means the team expects ag
2803
2805
 
2804
2806
  - On failure: **\`mem_tried\`** immediately.
2805
2807
  - Before closing a substantive session: MCP prompt **\`post_task\`** when there is something worth capturing.
2808
+ - Before final response: **\`haive enforce finish\`** must pass; it checks commit/push and release version/tag protocol.
2806
2809
 
2807
2810
  ## If haive MCP is missing
2808
2811
 
@@ -2898,7 +2901,7 @@ jobs:
2898
2901
  steps:
2899
2902
  - uses: actions/checkout@v4
2900
2903
 
2901
- - uses: Doucs91/hAIve/packages/github-action@main
2904
+ - uses: Doucs91/hAIve/packages/github-action@${HAIVE_GITHUB_ACTION_REF}
2902
2905
  with:
2903
2906
  github-token: \${{ secrets.GITHUB_TOKEN }}
2904
2907
  # post-if-empty: 'true' # uncomment to always post (even when no memories found)
@@ -3660,55 +3663,66 @@ import {
3660
3663
  serializeMemory as serializeMemory32
3661
3664
  } from "@hiveai/core";
3662
3665
  import { z as z7 } from "zod";
3663
- import { readFile as readFile22, readdir as readdir22 } from "fs/promises";
3664
- import { existsSync as existsSync82 } from "fs";
3665
- import path42 from "path";
3666
3666
  import {
3667
- deriveConfidence as deriveConfidence2,
3667
+ computeImpact,
3668
3668
  getUsage as getUsage22,
3669
- inferModulesFromPaths,
3670
3669
  loadMemoriesFromDir as loadMemoriesFromDir6,
3671
3670
  loadUsageIndex as loadUsageIndex32,
3672
- memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
3673
- trackReads as trackReads22
3671
+ recordApplied,
3672
+ recordRejection as recordRejection2,
3673
+ saveUsageIndex as saveUsageIndex2
3674
3674
  } from "@hiveai/core";
3675
+ import { existsSync as existsSync82 } from "fs";
3675
3676
  import { z as z8 } from "zod";
3677
+ import { readFile as readFile22, readdir as readdir22 } from "fs/promises";
3676
3678
  import { existsSync as existsSync92 } from "fs";
3679
+ import path42 from "path";
3677
3680
  import {
3678
- deriveConfidence as deriveConfidence3,
3681
+ deriveConfidence as deriveConfidence2,
3679
3682
  getUsage as getUsage3,
3683
+ inferModulesFromPaths,
3680
3684
  loadMemoriesFromDir as loadMemoriesFromDir7,
3681
- loadUsageIndex as loadUsageIndex4
3685
+ loadUsageIndex as loadUsageIndex4,
3686
+ memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
3687
+ trackReads as trackReads22
3682
3688
  } from "@hiveai/core";
3683
3689
  import { z as z9 } from "zod";
3684
3690
  import { existsSync as existsSync102 } from "fs";
3685
- import { unlink } from "fs/promises";
3686
3691
  import {
3692
+ deriveConfidence as deriveConfidence3,
3693
+ getUsage as getUsage4,
3687
3694
  loadMemoriesFromDir as loadMemoriesFromDir8,
3688
- loadUsageIndex as loadUsageIndex5,
3689
- saveUsageIndex as saveUsageIndex2
3695
+ loadUsageIndex as loadUsageIndex5
3690
3696
  } from "@hiveai/core";
3691
3697
  import { z as z10 } from "zod";
3692
- import { writeFile as writeFile52 } from "fs/promises";
3693
3698
  import { existsSync as existsSync112 } from "fs";
3694
- import { loadMemoriesFromDir as loadMemoriesFromDir9, serializeMemory as serializeMemory4 } from "@hiveai/core";
3695
- import { z as z11 } from "zod";
3696
- import { existsSync as existsSync122 } from "fs";
3699
+ import { unlink } from "fs/promises";
3697
3700
  import {
3698
- getUsage as getUsage4,
3699
- loadMemoriesFromDir as loadMemoriesFromDir10,
3700
- loadUsageIndex as loadUsageIndex6
3701
+ loadMemoriesFromDir as loadMemoriesFromDir9,
3702
+ loadUsageIndex as loadUsageIndex6,
3703
+ saveUsageIndex as saveUsageIndex3
3701
3704
  } from "@hiveai/core";
3705
+ import { z as z11 } from "zod";
3706
+ import { writeFile as writeFile52 } from "fs/promises";
3707
+ import { existsSync as existsSync122 } from "fs";
3708
+ import { loadMemoriesFromDir as loadMemoriesFromDir10, serializeMemory as serializeMemory4 } from "@hiveai/core";
3702
3709
  import { z as z12 } from "zod";
3703
- import { writeFile as writeFile62 } from "fs/promises";
3704
3710
  import { existsSync as existsSync132 } from "fs";
3705
3711
  import {
3712
+ getUsage as getUsage5,
3706
3713
  loadMemoriesFromDir as loadMemoriesFromDir11,
3707
- serializeMemory as serializeMemory5
3714
+ loadUsageIndex as loadUsageIndex7
3708
3715
  } from "@hiveai/core";
3709
3716
  import { z as z13 } from "zod";
3710
- import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
3717
+ import { writeFile as writeFile62 } from "fs/promises";
3711
3718
  import { existsSync as existsSync142 } from "fs";
3719
+ import {
3720
+ loadMemoriesFromDir as loadMemoriesFromDir12,
3721
+ serializeMemory as serializeMemory5
3722
+ } from "@hiveai/core";
3723
+ import { z as z14 } from "zod";
3724
+ import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
3725
+ import { existsSync as existsSync15 } from "fs";
3712
3726
  import path52 from "path";
3713
3727
  import {
3714
3728
  buildFrontmatter as buildFrontmatter22,
@@ -3716,9 +3730,9 @@ import {
3716
3730
  serializeMemory as serializeMemory6,
3717
3731
  suggestSensorFromMemory as suggestSensorFromMemory2
3718
3732
  } from "@hiveai/core";
3719
- import { z as z14 } from "zod";
3733
+ import { z as z15 } from "zod";
3720
3734
  import { mkdir as mkdir42, writeFile as writeFile82 } from "fs/promises";
3721
- import { existsSync as existsSync15 } from "fs";
3735
+ import { existsSync as existsSync16 } from "fs";
3722
3736
  import path62 from "path";
3723
3737
  import {
3724
3738
  buildFrontmatter as buildFrontmatter3,
@@ -3726,35 +3740,37 @@ import {
3726
3740
  memoryFilePath as memoryFilePath3,
3727
3741
  serializeMemory as serializeMemory7
3728
3742
  } from "@hiveai/core";
3729
- import { z as z15 } from "zod";
3743
+ import { z as z16 } from "zod";
3730
3744
  import { writeFile as writeFile10, mkdir as mkdir62 } from "fs/promises";
3731
- import { existsSync as existsSync17 } from "fs";
3745
+ import { existsSync as existsSync18 } from "fs";
3732
3746
  import path82 from "path";
3733
3747
  import {
3734
3748
  buildFrontmatter as buildFrontmatter4,
3735
- loadMemoriesFromDir as loadMemoriesFromDir12,
3749
+ loadMemoriesFromDir as loadMemoriesFromDir13,
3736
3750
  memoryFilePath as memoryFilePath4,
3737
3751
  serializeMemory as serializeMemory8
3738
3752
  } from "@hiveai/core";
3739
- import { z as z16 } from "zod";
3753
+ import { z as z17 } from "zod";
3740
3754
  import {
3741
3755
  appendUsageEvent,
3742
3756
  appendRuntimeJournalEntry,
3743
3757
  loadConfig as loadConfig22
3744
3758
  } from "@hiveai/core";
3745
3759
  import { mkdir as mkdir52, writeFile as writeFile92, rm } from "fs/promises";
3746
- import { existsSync as existsSync16 } from "fs";
3760
+ import { existsSync as existsSync17 } from "fs";
3747
3761
  import path72 from "path";
3748
3762
  import { execSync } from "child_process";
3749
3763
  import { readFile as readFile42, writeFile as writeFile11 } from "fs/promises";
3750
- import { existsSync as existsSync19 } from "fs";
3764
+ import { existsSync as existsSync20 } from "fs";
3751
3765
  import {
3752
3766
  allocateBudget,
3767
+ computeImpact as computeImpact2,
3753
3768
  DEFAULT_AUTO_PROMOTE_RULE,
3754
3769
  deriveConfidence as deriveConfidence4,
3755
3770
  estimateTokens,
3771
+ evaluateSkillActivation,
3756
3772
  extractActionsBriefBody as extractActionsBriefBody2,
3757
- getUsage as getUsage5,
3773
+ getUsage as getUsage6,
3758
3774
  inferModulesFromPaths as inferModulesFromPaths2,
3759
3775
  isAutoPromoteEligible,
3760
3776
  isDecaying,
@@ -3763,8 +3779,8 @@ import {
3763
3779
  literalMatchesAnyToken as literalMatchesAnyToken22,
3764
3780
  loadCodeMap as loadCodeMap5,
3765
3781
  loadConfig as loadConfig3,
3766
- loadMemoriesFromDir as loadMemoriesFromDir13,
3767
- loadUsageIndex as loadUsageIndex7,
3782
+ loadMemoriesFromDir as loadMemoriesFromDir14,
3783
+ loadUsageIndex as loadUsageIndex8,
3768
3784
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
3769
3785
  queryCodeMap as queryCodeMap2,
3770
3786
  resolveBriefingBudget as resolveBriefingBudget2,
@@ -3776,77 +3792,77 @@ import {
3776
3792
  truncateToTokens,
3777
3793
  writeBriefingMarker as writeBriefingMarker2
3778
3794
  } from "@hiveai/core";
3779
- import { z as z17 } from "zod";
3795
+ import { z as z18 } from "zod";
3780
3796
  import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
3781
- import { existsSync as existsSync18 } from "fs";
3797
+ import { existsSync as existsSync19 } from "fs";
3782
3798
  import path92 from "path";
3783
3799
  import { isGlobPath, isStackPackSeed as isStackPackSeed2, pathsOverlap } from "@hiveai/core";
3784
3800
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap22, queryCodeMap as queryCodeMap22 } from "@hiveai/core";
3785
- import { z as z18 } from "zod";
3786
- import { existsSync as existsSync20 } from "fs";
3787
- import { loadMemoriesFromDir as loadMemoriesFromDir14 } from "@hiveai/core";
3788
3801
  import { z as z19 } from "zod";
3789
3802
  import { existsSync as existsSync21 } from "fs";
3790
3803
  import { loadMemoriesFromDir as loadMemoriesFromDir15 } from "@hiveai/core";
3791
3804
  import { z as z20 } from "zod";
3805
+ import { existsSync as existsSync222 } from "fs";
3806
+ import { loadMemoriesFromDir as loadMemoriesFromDir16 } from "@hiveai/core";
3792
3807
  import { z as z21 } from "zod";
3793
3808
  import { z as z22 } from "zod";
3794
- import { existsSync as existsSync222 } from "fs";
3809
+ import { z as z23 } from "zod";
3810
+ import { existsSync as existsSync23 } from "fs";
3795
3811
  import { spawn } from "child_process";
3796
3812
  import path102 from "path";
3797
3813
  import {
3798
3814
  deriveConfidence as deriveConfidence5,
3799
- getUsage as getUsage6,
3815
+ getUsage as getUsage7,
3800
3816
  loadCodeMap as loadCodeMap32,
3801
- loadMemoriesFromDir as loadMemoriesFromDir16,
3802
- loadUsageIndex as loadUsageIndex8,
3817
+ loadMemoriesFromDir as loadMemoriesFromDir17,
3818
+ loadUsageIndex as loadUsageIndex9,
3803
3819
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
3804
3820
  } from "@hiveai/core";
3805
- import { z as z23 } from "zod";
3806
- import { existsSync as existsSync23 } from "fs";
3821
+ import { z as z24 } from "zod";
3822
+ import { existsSync as existsSync24 } from "fs";
3807
3823
  import {
3808
3824
  addedLinesFromDiff,
3809
3825
  deriveConfidence as deriveConfidence6,
3810
- getUsage as getUsage7,
3826
+ getUsage as getUsage8,
3811
3827
  isRetiredMemory as isRetiredMemory2,
3812
- loadMemoriesFromDir as loadMemoriesFromDir17,
3813
- loadUsageIndex as loadUsageIndex9,
3828
+ loadMemoriesFromDir as loadMemoriesFromDir18,
3829
+ loadUsageIndex as loadUsageIndex10,
3814
3830
  literalMatchesAnyToken as literalMatchesAnyToken3,
3815
3831
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
3816
3832
  runSensors,
3817
3833
  sensorTargetsFromDiff,
3818
3834
  tokenizeQuery as tokenizeQuery3
3819
3835
  } from "@hiveai/core";
3820
- import { z as z24 } from "zod";
3821
- import { existsSync as existsSync24 } from "fs";
3822
- import {
3823
- loadMemoriesFromDir as loadMemoriesFromDir18,
3824
- tokenizeQuery as tokenizeQuery4
3825
- } from "@hiveai/core";
3826
3836
  import { z as z25 } from "zod";
3827
3837
  import { existsSync as existsSync25 } from "fs";
3828
- import { spawn as spawn2 } from "child_process";
3829
3838
  import {
3830
- deriveConfidence as deriveConfidence7,
3831
- getUsage as getUsage8,
3832
3839
  loadMemoriesFromDir as loadMemoriesFromDir19,
3833
- loadUsageIndex as loadUsageIndex10,
3834
- pathsOverlap as singlePathsOverlap
3840
+ tokenizeQuery as tokenizeQuery4
3835
3841
  } from "@hiveai/core";
3836
3842
  import { z as z26 } from "zod";
3837
3843
  import { existsSync as existsSync26 } from "fs";
3844
+ import { spawn as spawn2 } from "child_process";
3838
3845
  import {
3839
- deriveConfidence as deriveConfidence8,
3846
+ deriveConfidence as deriveConfidence7,
3840
3847
  getUsage as getUsage9,
3841
3848
  loadMemoriesFromDir as loadMemoriesFromDir20,
3842
3849
  loadUsageIndex as loadUsageIndex11,
3850
+ pathsOverlap as singlePathsOverlap
3851
+ } from "@hiveai/core";
3852
+ import { z as z27 } from "zod";
3853
+ import { existsSync as existsSync27 } from "fs";
3854
+ import {
3855
+ deriveConfidence as deriveConfidence8,
3856
+ getUsage as getUsage10,
3857
+ loadMemoriesFromDir as loadMemoriesFromDir21,
3858
+ loadUsageIndex as loadUsageIndex12,
3843
3859
  pathsOverlap as pathsOverlap2,
3844
3860
  tokenizeQuery as tokenizeQuery5
3845
3861
  } from "@hiveai/core";
3846
- import { z as z27 } from "zod";
3847
3862
  import { z as z28 } from "zod";
3863
+ import { z as z29 } from "zod";
3848
3864
  import { mkdir as mkdir72, writeFile as writeFile12 } from "fs/promises";
3849
- import { existsSync as existsSync27 } from "fs";
3865
+ import { existsSync as existsSync28 } from "fs";
3850
3866
  import path112 from "path";
3851
3867
  import { execSync as execSync2 } from "child_process";
3852
3868
  import {
@@ -3855,28 +3871,28 @@ import {
3855
3871
  readUsageEvents,
3856
3872
  serializeMemory as serializeMemory10
3857
3873
  } from "@hiveai/core";
3858
- import { z as z29 } from "zod";
3859
- import { existsSync as existsSync28 } from "fs";
3874
+ import { z as z30 } from "zod";
3875
+ import { existsSync as existsSync29 } from "fs";
3860
3876
  import {
3861
3877
  findLexicalConflictPairs,
3862
3878
  findTopicStatusConflictPairs,
3863
- loadMemoriesFromDir as loadMemoriesFromDir21
3879
+ loadMemoriesFromDir as loadMemoriesFromDir222
3864
3880
  } from "@hiveai/core";
3865
- import { z as z30 } from "zod";
3866
- import { resolveProjectInfo } from "@hiveai/core";
3867
3881
  import { z as z31 } from "zod";
3868
- import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
3882
+ import { resolveProjectInfo } from "@hiveai/core";
3869
3883
  import { z as z32 } from "zod";
3870
- import { existsSync as existsSync29 } from "fs";
3871
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir222 } from "@hiveai/core";
3884
+ import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
3872
3885
  import { z as z33 } from "zod";
3873
- import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
3886
+ import { existsSync as existsSync30 } from "fs";
3887
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hiveai/core";
3874
3888
  import { z as z34 } from "zod";
3875
- import { readRuntimeJournalTail } from "@hiveai/core";
3889
+ import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
3876
3890
  import { z as z35 } from "zod";
3891
+ import { readRuntimeJournalTail } from "@hiveai/core";
3877
3892
  import { z as z36 } from "zod";
3878
3893
  import { z as z37 } from "zod";
3879
3894
  import { z as z38 } from "zod";
3895
+ import { z as z39 } from "zod";
3880
3896
  import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
3881
3897
  function createContext(options = {}) {
3882
3898
  const env = options.env ?? process.env;
@@ -3993,6 +4009,13 @@ var MemSaveInputSchema = {
3993
4009
  commit: z4.string().optional().describe("Anchor commit SHA (for staleness detection later)"),
3994
4010
  topic: z4.string().optional().describe(
3995
4011
  "Stable key for this memory. If a memory with the same topic already exists in this scope, it is updated in-place (revision_count++). Use for knowledge that evolves over time."
4012
+ ),
4013
+ activation: z4.object({
4014
+ keywords: z4.array(z4.string()).default([]),
4015
+ globs: z4.array(z4.string()).default([]),
4016
+ always: z4.boolean().default(false)
4017
+ }).optional().describe(
4018
+ "Only for type='skill'. Progressive-disclosure triggers: the skill is surfaced ONLY when a keyword matches the task or a glob matches the edited files (or always=true). Omit to keep the skill always-eligible."
3996
4019
  )
3997
4020
  };
3998
4021
  function bodyHash(body) {
@@ -4111,7 +4134,8 @@ async function memSave(input, ctx) {
4111
4134
  commit: input.commit,
4112
4135
  topic: input.topic,
4113
4136
  status: haiveConfig.defaultStatus === "validated" ? "validated" : void 0,
4114
- sensor: suggestSensorForSavedMemory(input.type, input.body, input.paths) ?? void 0
4137
+ sensor: suggestSensorForSavedMemory(input.type, input.body, input.paths) ?? void 0,
4138
+ activation: input.type === "skill" ? input.activation : void 0
4115
4139
  });
4116
4140
  const file = memoryFilePath2(
4117
4141
  ctx.paths,
@@ -4460,14 +4484,51 @@ async function memReject(input, ctx) {
4460
4484
  rejection_reason: u?.rejection_reason ?? null
4461
4485
  };
4462
4486
  }
4487
+ var MemFeedbackInputSchema = {
4488
+ id: z8.string().min(1).describe("Full memory id the feedback is about"),
4489
+ outcome: z8.enum(["applied", "rejected"]).describe(
4490
+ "'applied' = this memory changed what you did (strong positive utility signal); 'rejected' = it was wrong/outdated/unhelpful (negative signal, blocks auto-promotion)."
4491
+ ),
4492
+ reason: z8.string().optional().describe("Why it was rejected (stored on the memory's usage record). Only used for outcome='rejected'.")
4493
+ };
4494
+ async function memFeedback(input, ctx) {
4495
+ if (!existsSync82(ctx.paths.memoriesDir)) {
4496
+ return { ok: false, id: input.id, error: "No .ai/memories \u2014 run `haive init` first." };
4497
+ }
4498
+ const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
4499
+ const target = all.find((m) => m.memory.frontmatter.id === input.id);
4500
+ if (!target) {
4501
+ return { ok: false, id: input.id, error: `No memory with id '${input.id}'.` };
4502
+ }
4503
+ const index = await loadUsageIndex32(ctx.paths);
4504
+ if (input.outcome === "applied") {
4505
+ recordApplied(index, input.id);
4506
+ } else {
4507
+ recordRejection2(index, input.id, input.reason ?? null);
4508
+ }
4509
+ await saveUsageIndex2(ctx.paths, index);
4510
+ const usage = getUsage22(index, input.id);
4511
+ const impact = computeImpact(target.memory.frontmatter, usage);
4512
+ return {
4513
+ ok: true,
4514
+ id: input.id,
4515
+ outcome: input.outcome,
4516
+ usage: {
4517
+ read_count: usage.read_count,
4518
+ applied_count: usage.applied_count,
4519
+ rejected_count: usage.rejected_count
4520
+ },
4521
+ impact: { score: impact.score, tier: impact.tier, signals: impact.signals }
4522
+ };
4523
+ }
4463
4524
  var MemForFilesInputSchema = {
4464
- files: z8.array(z8.string()).min(1).describe("Project-relative file paths the agent is currently working on"),
4465
- include_module_contexts: z8.boolean().default(true).describe("Inline the matching .ai/modules/<name>/context.md contents"),
4466
- track: z8.boolean().default(true).describe("Increment read_count on returned memories")
4525
+ files: z9.array(z9.string()).min(1).describe("Project-relative file paths the agent is currently working on"),
4526
+ include_module_contexts: z9.boolean().default(true).describe("Inline the matching .ai/modules/<name>/context.md contents"),
4527
+ track: z9.boolean().default(true).describe("Increment read_count on returned memories")
4467
4528
  };
4468
4529
  async function memForFiles(input, ctx) {
4469
4530
  const inferred = inferModulesFromPaths(input.files);
4470
- if (!existsSync82(ctx.paths.memoriesDir)) {
4531
+ if (!existsSync92(ctx.paths.memoriesDir)) {
4471
4532
  return {
4472
4533
  inferred_modules: inferred,
4473
4534
  by_anchor: [],
@@ -4476,8 +4537,8 @@ async function memForFiles(input, ctx) {
4476
4537
  module_contexts: await loadModuleContexts(ctx, inferred, input.include_module_contexts)
4477
4538
  };
4478
4539
  }
4479
- const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
4480
- const usage = await loadUsageIndex32(ctx.paths);
4540
+ const all = await loadMemoriesFromDir7(ctx.paths.memoriesDir);
4541
+ const usage = await loadUsageIndex4(ctx.paths);
4481
4542
  const seen = /* @__PURE__ */ new Set();
4482
4543
  const byAnchor = [];
4483
4544
  const byModule = [];
@@ -4525,7 +4586,7 @@ async function memForFiles(input, ctx) {
4525
4586
  }
4526
4587
  function toMatch(loaded, reason, usage) {
4527
4588
  const fm = loaded.memory.frontmatter;
4528
- const u = getUsage22(usage, fm.id);
4589
+ const u = getUsage3(usage, fm.id);
4529
4590
  return {
4530
4591
  id: fm.id,
4531
4592
  scope: fm.scope,
@@ -4589,7 +4650,7 @@ function extractPathSegments(files) {
4589
4650
  }
4590
4651
  async function loadModuleContexts(ctx, modules, enabled) {
4591
4652
  if (!enabled || modules.length === 0) return [];
4592
- if (!existsSync82(ctx.paths.modulesContextDir)) return [];
4653
+ if (!existsSync92(ctx.paths.modulesContextDir)) return [];
4593
4654
  const available = new Set(
4594
4655
  (await readdir22(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
4595
4656
  );
@@ -4597,24 +4658,24 @@ async function loadModuleContexts(ctx, modules, enabled) {
4597
4658
  for (const m of modules) {
4598
4659
  if (!available.has(m)) continue;
4599
4660
  const file = path42.join(ctx.paths.modulesContextDir, m, "context.md");
4600
- if (existsSync82(file)) {
4661
+ if (existsSync92(file)) {
4601
4662
  out.push({ name: m, content: await readFile22(file, "utf8") });
4602
4663
  }
4603
4664
  }
4604
4665
  return out;
4605
4666
  }
4606
4667
  var MemGetInputSchema = {
4607
- id: z9.string().min(1).describe("Memory id to fetch")
4668
+ id: z10.string().min(1).describe("Memory id to fetch")
4608
4669
  };
4609
4670
  async function memGet(input, ctx) {
4610
- if (!existsSync92(ctx.paths.memoriesDir)) {
4671
+ if (!existsSync102(ctx.paths.memoriesDir)) {
4611
4672
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4612
4673
  }
4613
- const all = await loadMemoriesFromDir7(ctx.paths.memoriesDir);
4674
+ const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
4614
4675
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
4615
4676
  if (!found) throw new Error(`No memory with id "${input.id}".`);
4616
4677
  const fm = found.memory.frontmatter;
4617
- const u = getUsage3(await loadUsageIndex4(ctx.paths), fm.id);
4678
+ const u = getUsage4(await loadUsageIndex5(ctx.paths), fm.id);
4618
4679
  return {
4619
4680
  id: fm.id,
4620
4681
  scope: fm.scope,
@@ -4638,43 +4699,43 @@ async function memGet(input, ctx) {
4638
4699
  };
4639
4700
  }
4640
4701
  var MemDeleteInputSchema = {
4641
- id: z10.string().min(1).describe("Memory id to delete"),
4642
- keep_usage: z10.boolean().default(false).describe("Keep the usage.json entry instead of removing it alongside the file")
4702
+ id: z11.string().min(1).describe("Memory id to delete"),
4703
+ keep_usage: z11.boolean().default(false).describe("Keep the usage.json entry instead of removing it alongside the file")
4643
4704
  };
4644
4705
  async function memDelete(input, ctx) {
4645
- if (!existsSync102(ctx.paths.memoriesDir)) {
4706
+ if (!existsSync112(ctx.paths.memoriesDir)) {
4646
4707
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4647
4708
  }
4648
- const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
4709
+ const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
4649
4710
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
4650
4711
  if (!found) throw new Error(`No memory with id "${input.id}".`);
4651
4712
  await unlink(found.filePath);
4652
4713
  let usageRemoved = false;
4653
4714
  if (!input.keep_usage) {
4654
- const idx = await loadUsageIndex5(ctx.paths);
4715
+ const idx = await loadUsageIndex6(ctx.paths);
4655
4716
  if (idx.by_id[input.id]) {
4656
4717
  delete idx.by_id[input.id];
4657
- await saveUsageIndex2(ctx.paths, idx);
4718
+ await saveUsageIndex3(ctx.paths, idx);
4658
4719
  usageRemoved = true;
4659
4720
  }
4660
4721
  }
4661
4722
  return { id: input.id, deleted_file: found.filePath, usage_removed: usageRemoved };
4662
4723
  }
4663
4724
  var MemUpdateInputSchema = {
4664
- id: z11.string().min(1).describe("Id of the memory to update"),
4665
- body: z11.string().optional().describe("New Markdown body \u2014 replaces the existing body"),
4666
- tags: z11.array(z11.string()).optional().describe("New tags array \u2014 fully replaces existing tags"),
4667
- paths: z11.array(z11.string()).optional().describe("New anchor paths \u2014 fully replaces existing anchor.paths"),
4668
- symbols: z11.array(z11.string()).optional().describe("New anchor symbols \u2014 fully replaces existing anchor.symbols"),
4669
- commit: z11.string().optional().describe("New anchor commit SHA"),
4670
- domain: z11.string().optional().describe("New domain label"),
4671
- author: z11.string().optional().describe("New author handle or email")
4725
+ id: z12.string().min(1).describe("Id of the memory to update"),
4726
+ body: z12.string().optional().describe("New Markdown body \u2014 replaces the existing body"),
4727
+ tags: z12.array(z12.string()).optional().describe("New tags array \u2014 fully replaces existing tags"),
4728
+ paths: z12.array(z12.string()).optional().describe("New anchor paths \u2014 fully replaces existing anchor.paths"),
4729
+ symbols: z12.array(z12.string()).optional().describe("New anchor symbols \u2014 fully replaces existing anchor.symbols"),
4730
+ commit: z12.string().optional().describe("New anchor commit SHA"),
4731
+ domain: z12.string().optional().describe("New domain label"),
4732
+ author: z12.string().optional().describe("New author handle or email")
4672
4733
  };
4673
4734
  async function memUpdate(input, ctx) {
4674
- if (!existsSync112(ctx.paths.memoriesDir)) {
4735
+ if (!existsSync122(ctx.paths.memoriesDir)) {
4675
4736
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4676
4737
  }
4677
- const memories = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
4738
+ const memories = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
4678
4739
  const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
4679
4740
  if (!loaded) throw new Error(`No memory with id "${input.id}".`);
4680
4741
  const { frontmatter, body } = loaded.memory;
@@ -4715,12 +4776,12 @@ async function memUpdate(input, ctx) {
4715
4776
  return { id: input.id, file_path: loaded.filePath, updated_fields };
4716
4777
  }
4717
4778
  var MemPendingInputSchema = {
4718
- scope: z12.enum(["personal", "team", "module"]).optional()
4779
+ scope: z13.enum(["personal", "team", "module"]).optional()
4719
4780
  };
4720
4781
  async function memPending(input, ctx) {
4721
- if (!existsSync122(ctx.paths.memoriesDir)) return { pending: [] };
4722
- const all = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
4723
- const usage = await loadUsageIndex6(ctx.paths);
4782
+ if (!existsSync132(ctx.paths.memoriesDir)) return { pending: [] };
4783
+ const all = await loadMemoriesFromDir11(ctx.paths.memoriesDir);
4784
+ const usage = await loadUsageIndex7(ctx.paths);
4724
4785
  const now = Date.now();
4725
4786
  const proposed = all.filter(({ memory: memory2 }) => {
4726
4787
  if (memory2.frontmatter.status !== "proposed") return false;
@@ -4728,12 +4789,12 @@ async function memPending(input, ctx) {
4728
4789
  return true;
4729
4790
  });
4730
4791
  proposed.sort(
4731
- (a, b) => getUsage4(usage, b.memory.frontmatter.id).read_count - getUsage4(usage, a.memory.frontmatter.id).read_count
4792
+ (a, b) => getUsage5(usage, b.memory.frontmatter.id).read_count - getUsage5(usage, a.memory.frontmatter.id).read_count
4732
4793
  );
4733
4794
  return {
4734
4795
  pending: proposed.map(({ memory: memory2, filePath }) => {
4735
4796
  const fm = memory2.frontmatter;
4736
- const u = getUsage4(usage, fm.id);
4797
+ const u = getUsage5(usage, fm.id);
4737
4798
  return {
4738
4799
  id: fm.id,
4739
4800
  scope: fm.scope,
@@ -4749,13 +4810,13 @@ async function memPending(input, ctx) {
4749
4810
  };
4750
4811
  }
4751
4812
  var MemApproveInputSchema = {
4752
- id: z13.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
4813
+ id: z14.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
4753
4814
  };
4754
4815
  async function memApprove(input, ctx) {
4755
- if (!existsSync132(ctx.paths.memoriesDir)) {
4816
+ if (!existsSync142(ctx.paths.memoriesDir)) {
4756
4817
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4757
4818
  }
4758
- const all = await loadMemoriesFromDir11(ctx.paths.memoriesDir);
4819
+ const all = await loadMemoriesFromDir12(ctx.paths.memoriesDir);
4759
4820
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
4760
4821
  if (!found) throw new Error(`No memory with id "${input.id}".`);
4761
4822
  const previous = found.memory.frontmatter.status;
@@ -4772,17 +4833,17 @@ async function memApprove(input, ctx) {
4772
4833
  };
4773
4834
  }
4774
4835
  var MemTriedInputSchema = {
4775
- what: z14.string().min(1).describe("Brief description of the approach that was tried"),
4776
- why_failed: z14.string().min(1).describe("Why it failed or why it should NOT be used"),
4777
- instead: z14.string().optional().describe("What to use or do instead (recommended alternative)"),
4778
- scope: z14.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
4779
- module: z14.string().optional().describe("Module name (required when scope=module)"),
4780
- tags: z14.array(z14.string()).default([]).describe("Tags for filtering"),
4781
- paths: z14.array(z14.string()).default([]).describe("Anchor file paths this applies to"),
4782
- author: z14.string().optional().describe("Author handle or email")
4836
+ what: z15.string().min(1).describe("Brief description of the approach that was tried"),
4837
+ why_failed: z15.string().min(1).describe("Why it failed or why it should NOT be used"),
4838
+ instead: z15.string().optional().describe("What to use or do instead (recommended alternative)"),
4839
+ scope: z15.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
4840
+ module: z15.string().optional().describe("Module name (required when scope=module)"),
4841
+ tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
4842
+ paths: z15.array(z15.string()).default([]).describe("Anchor file paths this applies to"),
4843
+ author: z15.string().optional().describe("Author handle or email")
4783
4844
  };
4784
4845
  async function memTried(input, ctx) {
4785
- if (!existsSync142(ctx.paths.haiveDir)) {
4846
+ if (!existsSync15(ctx.paths.haiveDir)) {
4786
4847
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
4787
4848
  }
4788
4849
  const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
@@ -4808,27 +4869,27 @@ async function memTried(input, ctx) {
4808
4869
  }
4809
4870
  const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
4810
4871
  await mkdir32(path52.dirname(file), { recursive: true });
4811
- if (existsSync142(file)) {
4872
+ if (existsSync15(file)) {
4812
4873
  throw new Error(`Memory already exists at ${file}`);
4813
4874
  }
4814
4875
  await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
4815
4876
  return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
4816
4877
  }
4817
4878
  var MemObserveInputSchema = {
4818
- what: z15.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
4819
- where: z15.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
4820
- impact: z15.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
4821
- fix: z15.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
4822
- scope: z15.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
4823
- module: z15.string().optional().describe("Module name (required when scope=module)"),
4824
- tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
4825
- author: z15.string().optional().describe("Author handle or email"),
4826
- force: z15.boolean().default(false).describe(
4879
+ what: z16.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
4880
+ where: z16.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
4881
+ impact: z16.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
4882
+ fix: z16.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
4883
+ scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
4884
+ module: z16.string().optional().describe("Module name (required when scope=module)"),
4885
+ tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
4886
+ author: z16.string().optional().describe("Author handle or email"),
4887
+ force: z16.boolean().default(false).describe(
4827
4888
  "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."
4828
4889
  )
4829
4890
  };
4830
4891
  async function memObserve(input, ctx) {
4831
- if (!existsSync15(ctx.paths.haiveDir)) {
4892
+ if (!existsSync16(ctx.paths.haiveDir)) {
4832
4893
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
4833
4894
  }
4834
4895
  const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
@@ -4862,7 +4923,7 @@ async function memObserve(input, ctx) {
4862
4923
  const body = lines.join("\n") + "\n";
4863
4924
  const file = memoryFilePath3(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
4864
4925
  await mkdir42(path62.dirname(file), { recursive: true });
4865
- if (existsSync15(file)) {
4926
+ if (existsSync16(file)) {
4866
4927
  throw new Error(`Memory already exists at ${file}`);
4867
4928
  }
4868
4929
  await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
@@ -4948,7 +5009,7 @@ var SessionTracker = class {
4948
5009
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
4949
5010
  );
4950
5011
  const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
4951
- if (!ranPostTask && isSubstantialSession && existsSync16(this.ctx.paths.haiveDir)) {
5012
+ if (!ranPostTask && isSubstantialSession && existsSync17(this.ctx.paths.haiveDir)) {
4952
5013
  try {
4953
5014
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
4954
5015
  const payload = {
@@ -4982,7 +5043,7 @@ var SessionTracker = class {
4982
5043
  };
4983
5044
  async function clearPendingDistill(ctx) {
4984
5045
  const p = pendingDistillPath(ctx);
4985
- if (existsSync16(p)) {
5046
+ if (existsSync17(p)) {
4986
5047
  try {
4987
5048
  await rm(p);
4988
5049
  } catch {
@@ -4997,15 +5058,15 @@ function summarizeTools(events) {
4997
5058
  return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
4998
5059
  }
4999
5060
  var MemSessionEndInputSchema = {
5000
- goal: z16.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
5001
- accomplished: z16.string().describe("What was actually done \u2014 bullet list recommended"),
5002
- discoveries: z16.string().default("").describe(
5061
+ goal: z17.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
5062
+ accomplished: z17.string().describe("What was actually done \u2014 bullet list recommended"),
5063
+ discoveries: z17.string().default("").describe(
5003
5064
  "Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
5004
5065
  ),
5005
- files_touched: z16.array(z16.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
5006
- next_steps: z16.string().default("").describe("What should happen next (for the next session or a teammate)"),
5007
- scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
5008
- module: z16.string().optional().describe("Module name (required when scope=module)")
5066
+ files_touched: z17.array(z17.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
5067
+ next_steps: z17.string().default("").describe("What should happen next (for the next session or a teammate)"),
5068
+ scope: z17.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
5069
+ module: z17.string().optional().describe("Module name (required when scope=module)")
5009
5070
  };
5010
5071
  function recapTopic(scope, module) {
5011
5072
  return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
@@ -5035,7 +5096,7 @@ ${input.next_steps}`);
5035
5096
  return lines.join("\n");
5036
5097
  }
5037
5098
  async function memSessionEnd(input, ctx) {
5038
- if (!existsSync17(ctx.paths.haiveDir)) {
5099
+ if (!existsSync18(ctx.paths.haiveDir)) {
5039
5100
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
5040
5101
  }
5041
5102
  const body = buildBody(input);
@@ -5046,12 +5107,12 @@ async function memSessionEnd(input, ctx) {
5046
5107
  return rel.startsWith("..") ? p : rel;
5047
5108
  });
5048
5109
  const invalidPaths = normalizedFiles.filter(
5049
- (p) => !existsSync17(path82.resolve(ctx.paths.root, p))
5110
+ (p) => !existsSync18(path82.resolve(ctx.paths.root, p))
5050
5111
  );
5051
5112
  if (invalidPaths.length > 0) {
5052
5113
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
5053
5114
  }
5054
- const existing = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir12(ctx.paths.memoriesDir) : [];
5115
+ const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
5055
5116
  const topicMatch = existing.find(
5056
5117
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === input.scope && (!input.module || memory2.frontmatter.module === input.module)
5057
5118
  );
@@ -5232,7 +5293,7 @@ async function trySemanticHits(ctx, task, limit) {
5232
5293
  }
5233
5294
  async function loadModuleContexts2(ctx, modules) {
5234
5295
  if (modules.length === 0) return [];
5235
- if (!existsSync18(ctx.paths.modulesContextDir)) return [];
5296
+ if (!existsSync19(ctx.paths.modulesContextDir)) return [];
5236
5297
  const available = new Set(
5237
5298
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
5238
5299
  );
@@ -5240,42 +5301,42 @@ async function loadModuleContexts2(ctx, modules) {
5240
5301
  for (const m of modules) {
5241
5302
  if (!available.has(m)) continue;
5242
5303
  const file = path92.join(ctx.paths.modulesContextDir, m, "context.md");
5243
- if (existsSync18(file)) {
5304
+ if (existsSync19(file)) {
5244
5305
  out.push({ name: m, content: await readFile32(file, "utf8") });
5245
5306
  }
5246
5307
  }
5247
5308
  return out;
5248
5309
  }
5249
5310
  var GetBriefingInputSchema = {
5250
- task: z17.string().optional().describe(
5311
+ task: z18.string().optional().describe(
5251
5312
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
5252
5313
  ),
5253
- files: z17.array(z17.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
5254
- max_tokens: z17.number().int().positive().default(8e3).describe(
5314
+ files: z18.array(z18.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
5315
+ max_tokens: z18.number().int().positive().default(8e3).describe(
5255
5316
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
5256
5317
  ),
5257
- max_memories: z17.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
5258
- include_project_context: z17.boolean().default(true),
5259
- include_module_contexts: z17.boolean().default(true),
5260
- semantic: z17.boolean().default(true).describe(
5318
+ max_memories: z18.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
5319
+ include_project_context: z18.boolean().default(true),
5320
+ include_module_contexts: z18.boolean().default(true),
5321
+ semantic: z18.boolean().default(true).describe(
5261
5322
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
5262
5323
  ),
5263
- include_stale: z17.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
5264
- track: z17.boolean().default(true).describe("Increment read_count on returned memories"),
5265
- format: z17.enum(["full", "compact", "actions"]).default("full").describe(
5324
+ include_stale: z18.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
5325
+ track: z18.boolean().default(true).describe("Increment read_count on returned memories"),
5326
+ format: z18.enum(["full", "compact", "actions"]).default("full").describe(
5266
5327
  "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."
5267
5328
  ),
5268
- symbols: z17.array(z17.string()).default([]).describe(
5329
+ symbols: z18.array(z18.string()).default([]).describe(
5269
5330
  "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."
5270
5331
  ),
5271
- min_semantic_score: z17.number().min(0).max(1).default(0).describe(
5332
+ min_semantic_score: z18.number().min(0).max(1).default(0).describe(
5272
5333
  "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."
5273
5334
  ),
5274
- budget_preset: z17.enum(["quick", "balanced", "deep"]).optional().describe(
5335
+ budget_preset: z18.enum(["quick", "balanced", "deep"]).optional().describe(
5275
5336
  "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."
5276
5337
  )
5277
5338
  };
5278
- var GetBriefingZod = z17.object(GetBriefingInputSchema);
5339
+ var GetBriefingZod = z18.object(GetBriefingInputSchema);
5279
5340
  async function getBriefing(input, ctx) {
5280
5341
  const resolvedBudget = resolveBriefingBudget2(input.budget_preset, {
5281
5342
  max_tokens: input.max_tokens,
@@ -5291,8 +5352,8 @@ async function getBriefing(input, ctx) {
5291
5352
  let usage = { version: 1, updated_at: "", by_id: {} };
5292
5353
  let byId = /* @__PURE__ */ new Map();
5293
5354
  let lastSession;
5294
- if (existsSync19(ctx.paths.memoriesDir)) {
5295
- const allLoaded = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
5355
+ if (existsSync20(ctx.paths.memoriesDir)) {
5356
+ const allLoaded = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5296
5357
  const recaps = allLoaded.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").sort(
5297
5358
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
5298
5359
  );
@@ -5314,7 +5375,7 @@ async function getBriefing(input, ctx) {
5314
5375
  if (memory2.frontmatter.type === "session_recap") return false;
5315
5376
  return true;
5316
5377
  });
5317
- usage = await loadUsageIndex7(ctx.paths);
5378
+ usage = await loadUsageIndex8(ctx.paths);
5318
5379
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
5319
5380
  const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
5320
5381
  if (input.task && input.semantic) {
@@ -5336,7 +5397,8 @@ async function getBriefing(input, ctx) {
5336
5397
  }
5337
5398
  return;
5338
5399
  }
5339
- const u = getUsage5(usage, fm.id);
5400
+ const u = getUsage6(usage, fm.id);
5401
+ const imp = computeImpact2(fm, u);
5340
5402
  seen.set(fm.id, {
5341
5403
  id: fm.id,
5342
5404
  scope: fm.scope,
@@ -5347,6 +5409,8 @@ async function getBriefing(input, ctx) {
5347
5409
  confidence: deriveConfidence4(fm, u),
5348
5410
  ...fm.status === "draft" || fm.status === "proposed" ? { unverified: true } : {},
5349
5411
  read_count: u.read_count,
5412
+ impact_score: imp.score,
5413
+ impact_tier: imp.tier,
5350
5414
  reasons: [reason],
5351
5415
  match_quality: matchQuality ?? "partial",
5352
5416
  ...score !== void 0 ? { semantic_score: score } : {},
@@ -5390,11 +5454,28 @@ async function getBriefing(input, ctx) {
5390
5454
  }
5391
5455
  }
5392
5456
  }
5457
+ const activatedSkills = /* @__PURE__ */ new Set();
5458
+ for (const [id, m] of seen) {
5459
+ if (m.type !== "skill") continue;
5460
+ const loaded = byId.get(id);
5461
+ if (!loaded) continue;
5462
+ const act = evaluateSkillActivation(loaded.memory.frontmatter, {
5463
+ task: input.task,
5464
+ files: input.files
5465
+ });
5466
+ if (act.applicable && !act.activated) {
5467
+ seen.delete(id);
5468
+ continue;
5469
+ }
5470
+ if (act.applicable && act.activated) activatedSkills.add(id);
5471
+ }
5393
5472
  const ranked = [...seen.values()].sort((a, b) => {
5394
5473
  const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("symbol") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
5395
5474
  const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
5396
- const sa = priorityRank(classifyMemoryPriority(a, byId.get(a.id), input.files, input.symbols)) * 100 + reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
5397
- const sb = priorityRank(classifyMemoryPriority(b, byId.get(b.id), input.files, input.symbols)) * 100 + reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
5475
+ const impactScore = (m) => (m.impact_score ?? 0) * 3;
5476
+ const activationBoost = (m) => activatedSkills.has(m.id) ? 5 : 0;
5477
+ const sa = priorityRank(classifyMemoryPriority(a, byId.get(a.id), input.files, input.symbols)) * 100 + reasonScore(a) + confidenceScore(a) + impactScore(a) + activationBoost(a) + (a.semantic_score ?? 0);
5478
+ const sb = priorityRank(classifyMemoryPriority(b, byId.get(b.id), input.files, input.symbols)) * 100 + reasonScore(b) + confidenceScore(b) + impactScore(b) + activationBoost(b) + (b.semantic_score ?? 0);
5398
5479
  return sb - sa;
5399
5480
  });
5400
5481
  for (const mem of ranked.slice(0, briefingMaxMemories)) {
@@ -5410,7 +5491,7 @@ async function getBriefing(input, ctx) {
5410
5491
  memories.push(...ranked.slice(0, briefingMaxMemories));
5411
5492
  if (input.track && memories.length > 0) {
5412
5493
  await trackReads3(ctx.paths, memories.map((m) => m.id));
5413
- const freshUsage = await loadUsageIndex7(ctx.paths);
5494
+ const freshUsage = await loadUsageIndex8(ctx.paths);
5414
5495
  const cfg = await loadConfig3(ctx.paths);
5415
5496
  const rule = {
5416
5497
  minReads: cfg.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads,
@@ -5419,7 +5500,7 @@ async function getBriefing(input, ctx) {
5419
5500
  for (const m of memories) {
5420
5501
  const loaded = byId.get(m.id);
5421
5502
  if (!loaded) continue;
5422
- const u = getUsage5(freshUsage, m.id);
5503
+ const u = getUsage6(freshUsage, m.id);
5423
5504
  if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
5424
5505
  const newFm = { ...loaded.memory.frontmatter, status: "validated" };
5425
5506
  try {
@@ -5431,12 +5512,12 @@ async function getBriefing(input, ctx) {
5431
5512
  }
5432
5513
  }
5433
5514
  }
5434
- const projectContextRaw = input.include_project_context && existsSync19(ctx.paths.projectContext) ? await readFile42(ctx.paths.projectContext, "utf8") : "";
5515
+ const projectContextRaw = input.include_project_context && existsSync20(ctx.paths.projectContext) ? await readFile42(ctx.paths.projectContext, "utf8") : "";
5435
5516
  const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
5436
5517
  const setupWarnings = [];
5437
5518
  let autoContextGenerated = false;
5438
5519
  let projectContext = isTemplateContext ? "" : projectContextRaw;
5439
- if ((isTemplateContext || !existsSync19(ctx.paths.projectContext)) && input.include_project_context) {
5520
+ if ((isTemplateContext || !existsSync20(ctx.paths.projectContext)) && input.include_project_context) {
5440
5521
  const haiveConfig = await loadConfig3(ctx.paths);
5441
5522
  if (haiveConfig.autoContext) {
5442
5523
  const codeMap = await loadCodeMap5(ctx.paths);
@@ -5532,7 +5613,7 @@ ${m.content}`).join("\n\n---\n\n"),
5532
5613
  const totalTokens = projectSlice.estimatedTokens + modulesSlice.estimatedTokens + memoriesSlice.estimatedTokens;
5533
5614
  const decayWarnings = [];
5534
5615
  for (const m of trimmedMemories) {
5535
- const u = getUsage5(usage, m.id);
5616
+ const u = getUsage6(usage, m.id);
5536
5617
  const loaded = byId.get(m.id);
5537
5618
  const createdAt = loaded?.memory.frontmatter.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
5538
5619
  if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
@@ -5597,8 +5678,8 @@ ${m.content}`).join("\n\n---\n\n"),
5597
5678
  actionRequired.push(extractActionItem(m.id, loaded.memory.body));
5598
5679
  }
5599
5680
  }
5600
- if (existsSync19(ctx.paths.memoriesDir)) {
5601
- const allMems = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
5681
+ if (existsSync20(ctx.paths.memoriesDir)) {
5682
+ const allMems = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5602
5683
  for (const { memory: memory2 } of allMems) {
5603
5684
  const fm = memory2.frontmatter;
5604
5685
  if (!fm.requires_human_approval) continue;
@@ -5608,7 +5689,7 @@ ${m.content}`).join("\n\n---\n\n"),
5608
5689
  }
5609
5690
  }
5610
5691
  const pendingDistillFile = pendingDistillPath(ctx);
5611
- if (existsSync19(pendingDistillFile)) {
5692
+ if (existsSync20(pendingDistillFile)) {
5612
5693
  try {
5613
5694
  const raw = await readFile42(pendingDistillFile, "utf8");
5614
5695
  const pd = JSON.parse(raw);
@@ -5637,7 +5718,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5637
5718
  }
5638
5719
  }
5639
5720
  const memoriesEmpty = outputMemories.length === 0;
5640
- const hasMemoriesDir = existsSync19(ctx.paths.memoriesDir);
5721
+ const hasMemoriesDir = existsSync20(ctx.paths.memoriesDir);
5641
5722
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
5642
5723
  const hasUnguessableSignal = outputMemories.some(
5643
5724
  (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore2(m.body) >= GUESSABLE_THRESHOLD
@@ -5684,7 +5765,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5684
5765
  "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."
5685
5766
  );
5686
5767
  }
5687
- if (existsSync19(ctx.paths.haiveDir)) {
5768
+ if (existsSync20(ctx.paths.haiveDir)) {
5688
5769
  await writeBriefingMarker2(ctx.paths, {
5689
5770
  sessionId: process.env.HAIVE_SESSION_ID,
5690
5771
  ...input.task ? { task: input.task } : {},
@@ -5733,17 +5814,17 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5733
5814
  };
5734
5815
  }
5735
5816
  var CodeMapInputSchema = {
5736
- file: z18.string().optional().describe("Filter to files whose path contains this substring"),
5737
- symbol: z18.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
5738
- paths: z18.array(z18.string()).default([]).describe(
5817
+ file: z19.string().optional().describe("Filter to files whose path contains this substring"),
5818
+ symbol: z19.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
5819
+ paths: z19.array(z19.string()).default([]).describe(
5739
5820
  "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."
5740
5821
  ),
5741
- max_files: z18.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
5742
- max_tokens: z18.number().int().positive().optional().describe(
5822
+ max_files: z19.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
5823
+ max_tokens: z19.number().int().positive().optional().describe(
5743
5824
  "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)."
5744
5825
  )
5745
5826
  };
5746
- var CodeMapInputZod = z18.object(CodeMapInputSchema);
5827
+ var CodeMapInputZod = z19.object(CodeMapInputSchema);
5747
5828
  async function codeMapTool(input, ctx) {
5748
5829
  const map = await loadCodeMap22(ctx.paths);
5749
5830
  if (!map) {
@@ -5811,14 +5892,14 @@ function estimateFileEntryTokens(f) {
5811
5892
  return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
5812
5893
  }
5813
5894
  var MemDiffInputSchema = {
5814
- id_a: z19.string().min(1).describe("First memory id"),
5815
- id_b: z19.string().min(1).describe("Second memory id")
5895
+ id_a: z20.string().min(1).describe("First memory id"),
5896
+ id_b: z20.string().min(1).describe("Second memory id")
5816
5897
  };
5817
5898
  async function memDiff(input, ctx) {
5818
- if (!existsSync20(ctx.paths.memoriesDir)) {
5899
+ if (!existsSync21(ctx.paths.memoriesDir)) {
5819
5900
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
5820
5901
  }
5821
- const all = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5902
+ const all = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
5822
5903
  const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
5823
5904
  const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
5824
5905
  if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
@@ -5851,15 +5932,15 @@ async function memDiff(input, ctx) {
5851
5932
  };
5852
5933
  }
5853
5934
  var GetRecapInputSchema = {
5854
- scope: z20.enum(["personal", "team", "any"]).default("any").describe(
5935
+ scope: z21.enum(["personal", "team", "any"]).default("any").describe(
5855
5936
  "Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
5856
5937
  )
5857
5938
  };
5858
5939
  async function getRecap(input, ctx) {
5859
- if (!existsSync21(ctx.paths.memoriesDir)) {
5940
+ if (!existsSync222(ctx.paths.memoriesDir)) {
5860
5941
  return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
5861
5942
  }
5862
- const all = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
5943
+ const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
5863
5944
  const recaps = all.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").filter(({ memory: memory2 }) => input.scope === "any" || memory2.frontmatter.scope === input.scope).sort(
5864
5945
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
5865
5946
  );
@@ -5882,11 +5963,11 @@ async function getRecap(input, ctx) {
5882
5963
  };
5883
5964
  }
5884
5965
  var MemRelevantToInputSchema = {
5885
- task: z21.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
5886
- files: z21.array(z21.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
5887
- limit: z21.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
5888
- min_semantic_score: z21.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
5889
- format: z21.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
5966
+ task: z22.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
5967
+ files: z22.array(z22.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
5968
+ limit: z22.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
5969
+ min_semantic_score: z22.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
5970
+ format: z22.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
5890
5971
  };
5891
5972
  async function memRelevantTo(input, ctx) {
5892
5973
  const briefingInput = {
@@ -5915,11 +5996,11 @@ async function memRelevantTo(input, ctx) {
5915
5996
  return out;
5916
5997
  }
5917
5998
  var CodeSearchInputSchema = {
5918
- query: z22.string().min(1).describe(
5999
+ query: z23.string().min(1).describe(
5919
6000
  "Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
5920
6001
  ),
5921
- k: z22.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
5922
- min_score: z22.number().min(0).max(1).default(0.2).describe(
6002
+ k: z23.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
6003
+ min_score: z23.number().min(0).max(1).default(0.2).describe(
5923
6004
  "Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
5924
6005
  )
5925
6006
  };
@@ -5948,14 +6029,14 @@ async function codeSearch(input, ctx) {
5948
6029
  return { available: true, hits: result.hits };
5949
6030
  }
5950
6031
  var WhyThisFileInputSchema = {
5951
- path: z23.string().min(1).describe(
6032
+ path: z24.string().min(1).describe(
5952
6033
  "Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
5953
6034
  ),
5954
- git_log_limit: z23.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
5955
- memory_limit: z23.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
6035
+ git_log_limit: z24.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
6036
+ memory_limit: z24.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
5956
6037
  };
5957
6038
  async function whyThisFile(input, ctx) {
5958
- const fileExists = existsSync222(path102.join(ctx.paths.root, input.path));
6039
+ const fileExists = existsSync23(path102.join(ctx.paths.root, input.path));
5959
6040
  const [commits, memories, codeMap] = await Promise.all([
5960
6041
  runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
5961
6042
  collectAnchoredMemories(ctx, input.path, input.memory_limit),
@@ -5996,16 +6077,16 @@ async function whyThisFile(input, ctx) {
5996
6077
  };
5997
6078
  }
5998
6079
  async function collectAnchoredMemories(ctx, filePath, limit) {
5999
- if (!existsSync222(ctx.paths.memoriesDir)) return [];
6000
- const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
6001
- const usage = await loadUsageIndex8(ctx.paths);
6080
+ if (!existsSync23(ctx.paths.memoriesDir)) return [];
6081
+ const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
6082
+ const usage = await loadUsageIndex9(ctx.paths);
6002
6083
  const out = [];
6003
6084
  for (const { memory: memory2 } of all) {
6004
6085
  const fm = memory2.frontmatter;
6005
6086
  if (fm.status === "rejected" || fm.status === "deprecated") continue;
6006
6087
  if (fm.type === "session_recap") continue;
6007
6088
  if (!memoryMatchesAnchorPaths3(memory2, [filePath])) continue;
6008
- const u = getUsage6(usage, fm.id);
6089
+ const u = getUsage7(usage, fm.id);
6009
6090
  out.push({
6010
6091
  id: fm.id,
6011
6092
  type: fm.type,
@@ -6050,17 +6131,17 @@ function runCommand(cmd, args, cwd) {
6050
6131
  });
6051
6132
  }
6052
6133
  var AntiPatternsCheckInputSchema = {
6053
- diff: z24.string().optional().describe(
6134
+ diff: z25.string().optional().describe(
6054
6135
  "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."
6055
6136
  ),
6056
- paths: z24.array(z24.string()).default([]).describe(
6137
+ paths: z25.array(z25.string()).default([]).describe(
6057
6138
  "File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
6058
6139
  ),
6059
- limit: z24.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
6060
- semantic: z24.boolean().default(true).describe(
6140
+ limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
6141
+ semantic: z25.boolean().default(true).describe(
6061
6142
  "When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
6062
6143
  ),
6063
- min_semantic_score: z24.number().min(0).max(1).default(0.45).describe(
6144
+ min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
6064
6145
  "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."
6065
6146
  )
6066
6147
  };
@@ -6128,10 +6209,10 @@ async function antiPatternsCheck(input, ctx) {
6128
6209
  notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
6129
6210
  };
6130
6211
  }
6131
- if (!existsSync23(ctx.paths.memoriesDir)) {
6212
+ if (!existsSync24(ctx.paths.memoriesDir)) {
6132
6213
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
6133
6214
  }
6134
- const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
6215
+ const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
6135
6216
  const minSemanticScore = input.min_semantic_score ?? 0.45;
6136
6217
  const negative = all.filter(({ memory: memory2 }) => {
6137
6218
  const t = memory2.frontmatter.type;
@@ -6142,7 +6223,7 @@ async function antiPatternsCheck(input, ctx) {
6142
6223
  if (negative.length === 0) {
6143
6224
  return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
6144
6225
  }
6145
- const usage = await loadUsageIndex9(ctx.paths);
6226
+ const usage = await loadUsageIndex10(ctx.paths);
6146
6227
  const seen = /* @__PURE__ */ new Map();
6147
6228
  const upsert = (fm, body, reason, score) => {
6148
6229
  const existing = seen.get(fm.id);
@@ -6153,7 +6234,7 @@ async function antiPatternsCheck(input, ctx) {
6153
6234
  }
6154
6235
  return;
6155
6236
  }
6156
- const u = getUsage7(usage, fm.id);
6237
+ const u = getUsage8(usage, fm.id);
6157
6238
  seen.set(fm.id, {
6158
6239
  id: fm.id,
6159
6240
  type: fm.type,
@@ -6231,12 +6312,12 @@ async function antiPatternsCheck(input, ctx) {
6231
6312
  };
6232
6313
  }
6233
6314
  var MemDistillInputSchema = {
6234
- since_days: z25.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
6235
- min_cluster: z25.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
6236
- type_filter: z25.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
6315
+ since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
6316
+ min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
6317
+ type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
6237
6318
  "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
6238
6319
  ),
6239
- scope: z25.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
6320
+ scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
6240
6321
  };
6241
6322
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
6242
6323
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -6276,11 +6357,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
6276
6357
  "error"
6277
6358
  ]);
6278
6359
  async function memDistill(input, ctx) {
6279
- if (!existsSync24(ctx.paths.memoriesDir)) {
6360
+ if (!existsSync25(ctx.paths.memoriesDir)) {
6280
6361
  return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
6281
6362
  }
6282
6363
  const cutoff = Date.now() - input.since_days * MS_PER_DAY;
6283
- const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
6364
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
6284
6365
  const candidates = all.filter(({ memory: memory2 }) => {
6285
6366
  const fm = memory2.frontmatter;
6286
6367
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -6383,11 +6464,11 @@ function firstHeading(body) {
6383
6464
  return void 0;
6384
6465
  }
6385
6466
  var WhyThisDecisionInputSchema = {
6386
- id: z26.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
6387
- git_log_limit: z26.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
6467
+ id: z27.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
6468
+ git_log_limit: z27.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
6388
6469
  };
6389
6470
  async function whyThisDecision(input, ctx) {
6390
- if (!existsSync25(ctx.paths.memoriesDir)) {
6471
+ if (!existsSync26(ctx.paths.memoriesDir)) {
6391
6472
  return {
6392
6473
  found: false,
6393
6474
  related: [],
@@ -6396,8 +6477,8 @@ async function whyThisDecision(input, ctx) {
6396
6477
  notice: "No .ai/memories directory."
6397
6478
  };
6398
6479
  }
6399
- const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
6400
- const usage = await loadUsageIndex10(ctx.paths);
6480
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
6481
+ const usage = await loadUsageIndex11(ctx.paths);
6401
6482
  const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
6402
6483
  if (!target) {
6403
6484
  return {
@@ -6409,7 +6490,7 @@ async function whyThisDecision(input, ctx) {
6409
6490
  };
6410
6491
  }
6411
6492
  const fm = target.memory.frontmatter;
6412
- const targetUsage = getUsage8(usage, fm.id);
6493
+ const targetUsage = getUsage9(usage, fm.id);
6413
6494
  const decision = {
6414
6495
  id: fm.id,
6415
6496
  type: fm.type,
@@ -6426,7 +6507,7 @@ async function whyThisDecision(input, ctx) {
6426
6507
  const isExplicit = relatedSet.has(memory2.frontmatter.id);
6427
6508
  const isBackLink = (memory2.frontmatter.related_ids ?? []).includes(fm.id);
6428
6509
  if (!isExplicit && !isBackLink) continue;
6429
- const u = getUsage8(usage, memory2.frontmatter.id);
6510
+ const u = getUsage9(usage, memory2.frontmatter.id);
6430
6511
  related.push({
6431
6512
  id: memory2.frontmatter.id,
6432
6513
  type: memory2.frontmatter.type,
@@ -6446,7 +6527,7 @@ async function whyThisDecision(input, ctx) {
6446
6527
  (p) => targetPaths.some((tp) => singlePathsOverlap(p, tp))
6447
6528
  );
6448
6529
  if (overlappingPaths.length === 0) continue;
6449
- const u = getUsage8(usage, memory2.frontmatter.id);
6530
+ const u = getUsage9(usage, memory2.frontmatter.id);
6450
6531
  path_neighbors.push({
6451
6532
  id: memory2.frontmatter.id,
6452
6533
  type: memory2.frontmatter.type,
@@ -6518,22 +6599,22 @@ function runCommand2(cmd, args, cwd) {
6518
6599
  });
6519
6600
  }
6520
6601
  var MemConflictsInputSchema = {
6521
- id: z27.string().min(1).describe("Memory id to check for conflicts."),
6522
- min_score: z27.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
6523
- semantic: z27.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
6602
+ id: z28.string().min(1).describe("Memory id to check for conflicts."),
6603
+ 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)."),
6604
+ semantic: z28.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
6524
6605
  };
6525
6606
  var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
6526
6607
  var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
6527
6608
  async function memConflicts(input, ctx) {
6528
- if (!existsSync26(ctx.paths.memoriesDir)) {
6609
+ if (!existsSync27(ctx.paths.memoriesDir)) {
6529
6610
  return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
6530
6611
  }
6531
- const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
6612
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
6532
6613
  const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
6533
6614
  if (!target) {
6534
6615
  return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
6535
6616
  }
6536
- const usage = await loadUsageIndex11(ctx.paths);
6617
+ const usage = await loadUsageIndex12(ctx.paths);
6537
6618
  const others = all.filter(
6538
6619
  ({ memory: memory2 }) => memory2.frontmatter.id !== input.id && memory2.frontmatter.type !== "session_recap"
6539
6620
  );
@@ -6574,7 +6655,7 @@ async function memConflicts(input, ctx) {
6574
6655
  }
6575
6656
  }
6576
6657
  if (reasons.length === 0) continue;
6577
- const u = getUsage9(usage, fm.id);
6658
+ const u = getUsage10(usage, fm.id);
6578
6659
  conflicts.push({
6579
6660
  id: fm.id,
6580
6661
  type: fm.type,
@@ -6639,15 +6720,15 @@ async function trySemanticSimilarities(ctx, target, others) {
6639
6720
  return map;
6640
6721
  }
6641
6722
  var PreCommitCheckInputSchema = {
6642
- diff: z28.string().optional().describe(
6723
+ diff: z29.string().optional().describe(
6643
6724
  "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`."
6644
6725
  ),
6645
- paths: z28.array(z28.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
6646
- block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
6726
+ paths: z29.array(z29.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
6727
+ block_on: z29.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
6647
6728
  "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
6648
6729
  ),
6649
- semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
6650
- anchored_blocks: z28.boolean().default(false).describe(
6730
+ semantic: z29.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
6731
+ anchored_blocks: z29.boolean().default(false).describe(
6651
6732
  "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."
6652
6733
  )
6653
6734
  };
@@ -6715,7 +6796,9 @@ async function preCommitCheck(input, ctx) {
6715
6796
  };
6716
6797
  }
6717
6798
  function classifyWarning(warning, paths, anchoredBlocks = false) {
6718
- const affectedFiles = paths.filter((p) => !p.startsWith(".ai/.usage/"));
6799
+ const affectedFiles = paths.filter(
6800
+ (p) => !p.startsWith(".ai/.usage/") && !p.startsWith(".ai/.cache/") && !p.startsWith(".ai/.runtime/")
6801
+ );
6719
6802
  const repairCommand = repairCommandForWarning(warning, affectedFiles);
6720
6803
  const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
6721
6804
  if (fileDowngrade) {
@@ -6746,6 +6829,15 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
6746
6829
  };
6747
6830
  }
6748
6831
  if (isBlockingWarning(warning)) {
6832
+ if (warning.scope === "personal") {
6833
+ return {
6834
+ ...warning,
6835
+ level: "review",
6836
+ rationale: "personal anti-pattern memories are review guidance unless a deterministic block-severity sensor fires",
6837
+ affected_files: affectedFiles,
6838
+ repair_command: repairCommand
6839
+ };
6840
+ }
6749
6841
  if (warning.has_sensor && !warning.reasons.includes("sensor")) {
6750
6842
  return {
6751
6843
  ...warning,
@@ -6766,7 +6858,7 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
6766
6858
  const hasSemantic = warning.reasons.includes("semantic");
6767
6859
  const semanticScore = warning.semantic_score ?? 0;
6768
6860
  const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
6769
- if (anchoredBlocks && highConfidence && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
6861
+ if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
6770
6862
  if (warning.has_sensor && !warning.reasons.includes("sensor")) {
6771
6863
  return {
6772
6864
  ...warning,
@@ -6900,8 +6992,20 @@ function isJsonConfigFile(base) {
6900
6992
  return false;
6901
6993
  }
6902
6994
  function repairCommandForWarning(warning, paths) {
6903
- const firstPath = paths[0];
6904
- return firstPath ? `haive briefing --files "${firstPath}" --task "review ${warning.id}"` : `haive memory show ${warning.id}`;
6995
+ const targetPath = repairTargetPathForWarning(warning, paths);
6996
+ return targetPath ? `haive briefing --files "${targetPath}" --task "review ${warning.id}"` : `haive memory show ${warning.id}`;
6997
+ }
6998
+ function repairTargetPathForWarning(warning, paths) {
6999
+ const usablePaths = paths.filter(
7000
+ (p) => !p.startsWith(".ai/.usage/") && !p.startsWith(".ai/.cache/") && !p.startsWith(".ai/.runtime/")
7001
+ );
7002
+ const anchors = warning.anchor_paths ?? [];
7003
+ for (const file of usablePaths) {
7004
+ if (anchors.some((anchor) => anchor === file || file.startsWith(`${anchor}/`) || anchor.startsWith(`${file}/`))) {
7005
+ return file;
7006
+ }
7007
+ }
7008
+ return usablePaths[0];
6905
7009
  }
6906
7010
  var CONFIG_PATTERNS = [
6907
7011
  ".eslintrc",
@@ -6925,12 +7029,12 @@ var CONFIG_PATTERNS = [
6925
7029
  var MAX_DIFF_BYTES = 4096;
6926
7030
  var HOT_FILE_MIN = 3;
6927
7031
  var PatternDetectInputSchema = {
6928
- since_days: z29.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
6929
- dry_run: z29.boolean().default(false).describe("When true, report matches without writing any memory files."),
6930
- scope: z29.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
7032
+ since_days: z30.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
7033
+ dry_run: z30.boolean().default(false).describe("When true, report matches without writing any memory files."),
7034
+ scope: z30.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
6931
7035
  };
6932
7036
  async function patternDetect(input, ctx) {
6933
- if (!existsSync27(ctx.paths.haiveDir)) {
7037
+ if (!existsSync28(ctx.paths.haiveDir)) {
6934
7038
  return {
6935
7039
  scanned_events: 0,
6936
7040
  matches: [],
@@ -7059,7 +7163,7 @@ async function patternDetect(input, ctx) {
7059
7163
  fm.id,
7060
7164
  void 0
7061
7165
  );
7062
- if (existsSync27(file)) continue;
7166
+ if (existsSync28(file)) continue;
7063
7167
  await mkdir72(path112.dirname(file), { recursive: true });
7064
7168
  await writeFile12(
7065
7169
  file,
@@ -7104,17 +7208,17 @@ function gitFileDiff(root, file, sinceDays) {
7104
7208
  }
7105
7209
  }
7106
7210
  var MemConflictCandidatesInputSchema = {
7107
- since_days: z30.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
7108
- types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
7109
- min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
7110
- max_pairs: z30.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
7111
- max_scan: z30.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
7112
- max_topic_pairs: z30.number().int().positive().max(100).default(20).describe(
7211
+ since_days: z31.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
7212
+ types: z31.array(z31.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
7213
+ min_jaccard: z31.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
7214
+ max_pairs: z31.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
7215
+ 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."),
7216
+ max_topic_pairs: z31.number().int().positive().max(100).default(20).describe(
7113
7217
  "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
7114
7218
  )
7115
7219
  };
7116
7220
  async function memConflictCandidates(input, ctx) {
7117
- if (!existsSync28(ctx.paths.memoriesDir)) {
7221
+ if (!existsSync29(ctx.paths.memoriesDir)) {
7118
7222
  return {
7119
7223
  pairs: [],
7120
7224
  topic_status_pairs: [],
@@ -7123,7 +7227,7 @@ async function memConflictCandidates(input, ctx) {
7123
7227
  notice: "No .ai/memories directory."
7124
7228
  };
7125
7229
  }
7126
- const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
7230
+ const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
7127
7231
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
7128
7232
  sinceDays: input.since_days,
7129
7233
  types: input.types,
@@ -7136,7 +7240,7 @@ async function memConflictCandidates(input, ctx) {
7136
7240
  return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
7137
7241
  }
7138
7242
  var MemResolveProjectInputSchema = {
7139
- cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
7243
+ cwd: z32.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
7140
7244
  };
7141
7245
  async function memResolveProject(input, _ctx) {
7142
7246
  void _ctx;
@@ -7149,7 +7253,7 @@ async function memResolveProject(input, _ctx) {
7149
7253
  }
7150
7254
  var MemSuggestTopicInputSchema = {
7151
7255
  type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
7152
- title: z32.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
7256
+ title: z33.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
7153
7257
  };
7154
7258
  async function memSuggestTopic(input, _ctx) {
7155
7259
  void _ctx;
@@ -7157,15 +7261,15 @@ async function memSuggestTopic(input, _ctx) {
7157
7261
  return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
7158
7262
  }
7159
7263
  var MemTimelineInputSchema = {
7160
- memory_id: z33.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
7161
- topic: z33.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
7162
- limit: z33.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
7264
+ memory_id: z34.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
7265
+ topic: z34.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
7266
+ limit: z34.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
7163
7267
  };
7164
7268
  async function memTimeline(input, ctx) {
7165
- if (!existsSync29(ctx.paths.memoriesDir)) {
7269
+ if (!existsSync30(ctx.paths.memoriesDir)) {
7166
7270
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
7167
7271
  }
7168
- const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
7272
+ const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
7169
7273
  const { entries, notice } = collectTimelineEntries(all, {
7170
7274
  memoryId: input.memory_id,
7171
7275
  topic: input.topic,
@@ -7174,9 +7278,9 @@ async function memTimeline(input, ctx) {
7174
7278
  return { entries, total: entries.length, notice };
7175
7279
  }
7176
7280
  var RuntimeJournalAppendInputSchema = {
7177
- message: z34.string().min(1).describe("Short line to append to the runtime session journal"),
7178
- kind: z34.enum(["note", "session_end", "mcp"]).default("note"),
7179
- tool: z34.string().optional().describe("When kind=mcp, which tool name (optional)")
7281
+ message: z35.string().min(1).describe("Short line to append to the runtime session journal"),
7282
+ kind: z35.enum(["note", "session_end", "mcp"]).default("note"),
7283
+ tool: z35.string().optional().describe("When kind=mcp, which tool name (optional)")
7180
7284
  };
7181
7285
  async function runtimeJournalAppend(input, ctx) {
7182
7286
  await appendRuntimeJournalEntry2(ctx.paths, {
@@ -7190,7 +7294,7 @@ async function runtimeJournalAppend(input, ctx) {
7190
7294
  };
7191
7295
  }
7192
7296
  var RuntimeJournalTailInputSchema = {
7193
- limit: z35.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
7297
+ limit: z36.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
7194
7298
  };
7195
7299
  async function runtimeJournalTail(input, ctx) {
7196
7300
  const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
@@ -7200,10 +7304,10 @@ async function runtimeJournalTail(input, ctx) {
7200
7304
  return { entries };
7201
7305
  }
7202
7306
  var BootstrapProjectArgsSchema = {
7203
- module: z36.string().optional().describe(
7307
+ module: z37.string().optional().describe(
7204
7308
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
7205
7309
  ),
7206
- focus: z36.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
7310
+ focus: z37.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
7207
7311
  };
7208
7312
  var ROOT_TEMPLATE = `# Project context
7209
7313
 
@@ -7284,8 +7388,8 @@ ${template}\`\`\`
7284
7388
  };
7285
7389
  }
7286
7390
  var PostTaskArgsSchema = {
7287
- task_summary: z37.string().optional().describe("One sentence describing what you just did"),
7288
- files_touched: z37.array(z37.string()).optional().describe("Files you created or modified during the task")
7391
+ task_summary: z38.string().optional().describe("One sentence describing what you just did"),
7392
+ files_touched: z38.array(z38.string()).optional().describe("Files you created or modified during the task")
7289
7393
  };
7290
7394
  function postTaskPrompt(args, ctx) {
7291
7395
  const taskLine = args.task_summary ? `
@@ -7355,7 +7459,19 @@ This creates/updates a single rolling recap that **get_briefing automatically su
7355
7459
 
7356
7460
  Calling \`mem_session_end\` also **clears the pending-distill marker** (if any), confirming that this session's learnings have been properly captured rather than left as an auto-recap skeleton.
7357
7461
 
7358
- When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved."
7462
+ ### 7. Verify the git/release exit protocol \u2014 always
7463
+ Run **\`haive enforce finish\`** before your final response.
7464
+
7465
+ This executable gate checks the multi-agent git-sync decision:
7466
+ - no completed work is left as an uncommitted local diff
7467
+ - shippable package changes have a lockstep version bump
7468
+ - the release tag \`vX.Y.Z\` exists when a version was bumped
7469
+ - commits and tags have been pushed
7470
+ - agents never run \`npm publish\` (publication remains human-owned)
7471
+
7472
+ If it blocks, fix the reported Git/version/tag/push issue before telling the developer the task is done.
7473
+
7474
+ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed."
7359
7475
  `;
7360
7476
  return {
7361
7477
  description: "Post-task reflection: capture what you learned before closing the session",
@@ -7368,10 +7484,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
7368
7484
  };
7369
7485
  }
7370
7486
  var ImportDocsArgsSchema = {
7371
- content: z38.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
7372
- source: z38.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
7373
- scope: z38.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
7374
- dry_run: z38.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
7487
+ content: z39.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
7488
+ source: z39.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
7489
+ scope: z39.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
7490
+ dry_run: z39.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
7375
7491
  };
7376
7492
  function importDocsPrompt(args, ctx) {
7377
7493
  const sourceLine = args.source ? `
@@ -7434,7 +7550,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7434
7550
  };
7435
7551
  }
7436
7552
  var SERVER_NAME = "haive";
7437
- var SERVER_VERSION = "0.10.8";
7553
+ var SERVER_VERSION = "0.11.0";
7438
7554
  function jsonResult(data) {
7439
7555
  return {
7440
7556
  content: [
@@ -7476,7 +7592,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
7476
7592
  "anti_patterns_check",
7477
7593
  "mem_distill",
7478
7594
  "mem_timeline",
7479
- "mem_conflict_candidates"
7595
+ "mem_conflict_candidates",
7596
+ "mem_feedback"
7480
7597
  ];
7481
7598
  var EXPERIMENTAL_PROFILE_TOOLS = [
7482
7599
  ...MAINTENANCE_PROFILE_TOOLS,
@@ -7508,6 +7625,7 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
7508
7625
  "mem_approve",
7509
7626
  "mem_reject",
7510
7627
  "mem_delete",
7628
+ "mem_feedback",
7511
7629
  "runtime_journal_append",
7512
7630
  "pattern_detect"
7513
7631
  ]);
@@ -8003,6 +8121,28 @@ function createHaiveServer(options = {}) {
8003
8121
  MemRejectInputSchema,
8004
8122
  async (input) => jsonResult(await memReject(input, context))
8005
8123
  );
8124
+ registerTool(
8125
+ "mem_feedback",
8126
+ [
8127
+ "Record whether a memory actually HELPED \u2014 the closed-loop utility signal.",
8128
+ "",
8129
+ "USE right after a memory changed (or failed to change) what you did:",
8130
+ " - outcome='applied' \u2192 the memory steered your work (strong positive signal)",
8131
+ " - outcome='rejected' \u2192 it was wrong/outdated/unhelpful (negative signal)",
8132
+ "",
8133
+ "A read only means a memory was surfaced; 'applied' means it demonstrably helped.",
8134
+ "This powers `haive memory impact` (impact tiers + prune candidates) and future ranking.",
8135
+ "",
8136
+ "PARAMETERS:",
8137
+ " id \u2014 full memory id the feedback is about",
8138
+ " outcome \u2014 'applied' | 'rejected'",
8139
+ " reason \u2014 why it was rejected (optional, stored on the usage record)",
8140
+ "",
8141
+ "RETURNS: { ok, id, outcome, usage:{read_count,applied_count,rejected_count}, impact:{score,tier,signals} }"
8142
+ ].join("\n"),
8143
+ MemFeedbackInputSchema,
8144
+ async (input) => jsonResult(await memFeedback(input, context))
8145
+ );
8006
8146
  registerTool(
8007
8147
  "mem_pending",
8008
8148
  [
@@ -8409,21 +8549,21 @@ function registerMcp(program2) {
8409
8549
  // src/commands/sync.ts
8410
8550
  import { spawnSync as spawnSync4 } from "child_process";
8411
8551
  import { readFile as readFile9, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
8412
- import { existsSync as existsSync30 } from "fs";
8552
+ import { existsSync as existsSync31 } from "fs";
8413
8553
  import path15 from "path";
8414
8554
  import "commander";
8415
8555
  import {
8416
8556
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
8417
8557
  buildFrontmatter as buildFrontmatter6,
8418
8558
  findProjectRoot as findProjectRoot12,
8419
- getUsage as getUsage10,
8559
+ getUsage as getUsage11,
8420
8560
  isAutoPromoteEligible as isAutoPromoteEligible2,
8421
8561
  isDecaying as isDecaying2,
8422
8562
  isStackPackSeed as isStackPackSeed3,
8423
8563
  loadCodeMap as loadCodeMap6,
8424
8564
  loadConfig as loadConfig4,
8425
- loadMemoriesFromDir as loadMemoriesFromDir23,
8426
- loadUsageIndex as loadUsageIndex12,
8565
+ loadMemoriesFromDir as loadMemoriesFromDir24,
8566
+ loadUsageIndex as loadUsageIndex13,
8427
8567
  pullCrossRepoSources,
8428
8568
  resolveHaivePaths as resolveHaivePaths9,
8429
8569
  resolveManifestFiles,
@@ -8446,7 +8586,7 @@ function registerSync(program2) {
8446
8586
  ).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) => {
8447
8587
  const root = findProjectRoot12(opts.dir);
8448
8588
  const paths = resolveHaivePaths9(root);
8449
- if (!existsSync30(paths.memoriesDir)) {
8589
+ if (!existsSync31(paths.memoriesDir)) {
8450
8590
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8451
8591
  process.exitCode = 1;
8452
8592
  return;
@@ -8465,7 +8605,7 @@ function registerSync(program2) {
8465
8605
  let promoted = 0;
8466
8606
  let autoApproved = 0;
8467
8607
  if (opts.verify !== false) {
8468
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
8608
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8469
8609
  for (const { memory: memory2, filePath } of memories) {
8470
8610
  if (memory2.frontmatter.type === "session_recap") {
8471
8611
  if (memory2.frontmatter.status === "stale") {
@@ -8532,13 +8672,13 @@ function registerSync(program2) {
8532
8672
  }
8533
8673
  }
8534
8674
  if (opts.promote !== false) {
8535
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
8536
- const usage = await loadUsageIndex12(paths);
8675
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8676
+ const usage = await loadUsageIndex13(paths);
8537
8677
  const nowMs = Date.now();
8538
8678
  for (const { memory: memory2, filePath } of memories) {
8539
8679
  const fm = memory2.frontmatter;
8540
8680
  if (fm.type === "session_recap") continue;
8541
- if (isAutoPromoteEligible2(fm, getUsage10(usage, fm.id), {
8681
+ if (isAutoPromoteEligible2(fm, getUsage11(usage, fm.id), {
8542
8682
  minReads: autoPromoteMinReads,
8543
8683
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
8544
8684
  })) {
@@ -8584,7 +8724,7 @@ function registerSync(program2) {
8584
8724
  for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
8585
8725
  }
8586
8726
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
8587
- const draftMemories = (await loadMemoriesFromDir23(paths.memoriesDir)).filter(
8727
+ const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
8588
8728
  (m) => m.memory.frontmatter.status === "draft"
8589
8729
  );
8590
8730
  const draftCount = draftMemories.length;
@@ -8619,12 +8759,12 @@ function registerSync(program2) {
8619
8759
  }
8620
8760
  }
8621
8761
  if (!opts.quiet) {
8622
- const allForDecay = await loadMemoriesFromDir23(paths.memoriesDir);
8623
- const usageForDecay = await loadUsageIndex12(paths);
8762
+ const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
8763
+ const usageForDecay = await loadUsageIndex13(paths);
8624
8764
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
8625
8765
  const fm = memory2.frontmatter;
8626
8766
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
8627
- const u = getUsage10(usageForDecay, fm.id);
8767
+ const u = getUsage11(usageForDecay, fm.id);
8628
8768
  return isDecaying2(u, fm.created_at);
8629
8769
  });
8630
8770
  if (decaying.length > 0) {
@@ -8870,8 +9010,8 @@ function bridgeSummaryLine(body) {
8870
9010
  return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
8871
9011
  }
8872
9012
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8873
- if (!existsSync30(memoriesDir)) return;
8874
- const all = await loadMemoriesFromDir23(memoriesDir);
9013
+ if (!existsSync31(memoriesDir)) return;
9014
+ const all = await loadMemoriesFromDir24(memoriesDir);
8875
9015
  const top = all.filter(({ memory: memory2 }) => {
8876
9016
  const s = memory2.frontmatter.status;
8877
9017
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -8896,7 +9036,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8896
9036
  ` + block + `
8897
9037
 
8898
9038
  ${BRIDGE_END}`;
8899
- const fileExists = existsSync30(bridgeFile);
9039
+ const fileExists = existsSync31(bridgeFile);
8900
9040
  let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
8901
9041
  existing = existing.replace(/\r\n/g, "\n");
8902
9042
  const startIdx = existing.indexOf(BRIDGE_START);
@@ -8947,7 +9087,7 @@ function collectSinceChanges(root, ref) {
8947
9087
  // src/commands/memory-add.ts
8948
9088
  import { createHash as createHash2 } from "crypto";
8949
9089
  import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
8950
- import { existsSync as existsSync31 } from "fs";
9090
+ import { existsSync as existsSync33 } from "fs";
8951
9091
  import path16 from "path";
8952
9092
  import "commander";
8953
9093
  import {
@@ -8955,7 +9095,7 @@ import {
8955
9095
  findProjectRoot as findProjectRoot13,
8956
9096
  inferModulesFromPaths as inferModulesFromPaths3,
8957
9097
  loadConfig as loadConfig5,
8958
- loadMemoriesFromDir as loadMemoriesFromDir24,
9098
+ loadMemoriesFromDir as loadMemoriesFromDir25,
8959
9099
  memoryFilePath as memoryFilePath6,
8960
9100
  resolveHaivePaths as resolveHaivePaths10,
8961
9101
  serializeMemory as serializeMemory12,
@@ -8989,7 +9129,7 @@ function registerMemoryAdd(memory2) {
8989
9129
  ).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("-d, --dir <dir>", "project root").action(async (opts) => {
8990
9130
  const root = findProjectRoot13(opts.dir);
8991
9131
  const paths = resolveHaivePaths10(root);
8992
- if (!existsSync31(paths.haiveDir)) {
9132
+ if (!existsSync33(paths.haiveDir)) {
8993
9133
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8994
9134
  process.exitCode = 1;
8995
9135
  return;
@@ -9001,7 +9141,7 @@ function registerMemoryAdd(memory2) {
9001
9141
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
9002
9142
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
9003
9143
  if (anchorPaths.length > 0) {
9004
- const missing = anchorPaths.filter((p) => !existsSync31(path16.resolve(root, p)));
9144
+ const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
9005
9145
  if (missing.length > 0) {
9006
9146
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
9007
9147
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -9014,7 +9154,7 @@ function registerMemoryAdd(memory2) {
9014
9154
  const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
9015
9155
  let body;
9016
9156
  if (opts.bodyFile !== void 0) {
9017
- if (!existsSync31(opts.bodyFile)) {
9157
+ if (!existsSync33(opts.bodyFile)) {
9018
9158
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9019
9159
  process.exitCode = 1;
9020
9160
  return;
@@ -9030,9 +9170,9 @@ TODO \u2014 write the memory body.
9030
9170
  `;
9031
9171
  }
9032
9172
  const scope = opts.scope ?? config.defaultScope ?? "personal";
9033
- if (existsSync31(paths.memoriesDir)) {
9173
+ if (existsSync33(paths.memoriesDir)) {
9034
9174
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
9035
- const allForHash = await loadMemoriesFromDir24(paths.memoriesDir);
9175
+ const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
9036
9176
  const hashDup = allForHash.find(
9037
9177
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
9038
9178
  );
@@ -9043,8 +9183,8 @@ TODO \u2014 write the memory body.
9043
9183
  return;
9044
9184
  }
9045
9185
  }
9046
- if (opts.topic && existsSync31(paths.memoriesDir)) {
9047
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
9186
+ if (opts.topic && existsSync33(paths.memoriesDir)) {
9187
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9048
9188
  const topicMatch = existing.find(
9049
9189
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
9050
9190
  );
@@ -9088,13 +9228,13 @@ TODO \u2014 write the memory body.
9088
9228
  });
9089
9229
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9090
9230
  await mkdir11(path16.dirname(file), { recursive: true });
9091
- if (existsSync31(file)) {
9231
+ if (existsSync33(file)) {
9092
9232
  ui.error(`Memory already exists at ${file}`);
9093
9233
  process.exitCode = 1;
9094
9234
  return;
9095
9235
  }
9096
- if (existsSync31(paths.memoriesDir)) {
9097
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
9236
+ if (existsSync33(paths.memoriesDir)) {
9237
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9098
9238
  const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
9099
9239
  const similar = existing.filter(({ memory: memory3 }) => {
9100
9240
  const id = memory3.frontmatter.id.toLowerCase();
@@ -9185,14 +9325,14 @@ function slugify(value) {
9185
9325
  }
9186
9326
 
9187
9327
  // src/commands/memory-list.ts
9188
- import { existsSync as existsSync33 } from "fs";
9328
+ import { existsSync as existsSync34 } from "fs";
9189
9329
  import path17 from "path";
9190
9330
  import "commander";
9191
9331
  import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
9192
9332
 
9193
9333
  // src/utils/fs.ts
9194
9334
  import {
9195
- loadMemoriesFromDir as loadMemoriesFromDir25,
9335
+ loadMemoriesFromDir as loadMemoriesFromDir26,
9196
9336
  loadMemory,
9197
9337
  listMarkdownFilesRecursive
9198
9338
  } from "@hiveai/core";
@@ -9202,12 +9342,12 @@ function registerMemoryList(memory2) {
9202
9342
  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) => {
9203
9343
  const root = findProjectRoot14(opts.dir);
9204
9344
  const paths = resolveHaivePaths11(root);
9205
- if (!existsSync33(paths.memoriesDir)) {
9345
+ if (!existsSync34(paths.memoriesDir)) {
9206
9346
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9207
9347
  process.exitCode = 1;
9208
9348
  return;
9209
9349
  }
9210
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9350
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9211
9351
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
9212
9352
  const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
9213
9353
  const filtered = all.filter((m) => {
@@ -9277,7 +9417,7 @@ function matchesFilters(loaded, opts) {
9277
9417
 
9278
9418
  // src/commands/memory-promote.ts
9279
9419
  import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
9280
- import { existsSync as existsSync34 } from "fs";
9420
+ import { existsSync as existsSync35 } from "fs";
9281
9421
  import path18 from "path";
9282
9422
  import "commander";
9283
9423
  import {
@@ -9290,12 +9430,12 @@ function registerMemoryPromote(memory2) {
9290
9430
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9291
9431
  const root = findProjectRoot15(opts.dir);
9292
9432
  const paths = resolveHaivePaths12(root);
9293
- if (!existsSync34(paths.memoriesDir)) {
9433
+ if (!existsSync35(paths.memoriesDir)) {
9294
9434
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9295
9435
  process.exitCode = 1;
9296
9436
  return;
9297
9437
  }
9298
- const teamAndModule = await loadMemoriesFromDir25(paths.memoriesDir);
9438
+ const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
9299
9439
  const alreadyShared = teamAndModule.find(
9300
9440
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
9301
9441
  );
@@ -9309,7 +9449,7 @@ function registerMemoryPromote(memory2) {
9309
9449
  }
9310
9450
  return;
9311
9451
  }
9312
- const all = await loadMemoriesFromDir25(paths.personalDir);
9452
+ const all = await loadMemoriesFromDir26(paths.personalDir);
9313
9453
  const found = all.find((m) => m.memory.frontmatter.id === id);
9314
9454
  if (!found) {
9315
9455
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -9335,7 +9475,7 @@ function registerMemoryPromote(memory2) {
9335
9475
  }
9336
9476
 
9337
9477
  // src/commands/memory-approve.ts
9338
- import { existsSync as existsSync35 } from "fs";
9478
+ import { existsSync as existsSync36 } from "fs";
9339
9479
  import { writeFile as writeFile16 } from "fs/promises";
9340
9480
  import path19 from "path";
9341
9481
  import "commander";
@@ -9348,12 +9488,12 @@ function registerMemoryApprove(memory2) {
9348
9488
  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) => {
9349
9489
  const root = findProjectRoot16(opts.dir);
9350
9490
  const paths = resolveHaivePaths13(root);
9351
- if (!existsSync35(paths.memoriesDir)) {
9491
+ if (!existsSync36(paths.memoriesDir)) {
9352
9492
  ui.error(`No .ai/memories at ${root}.`);
9353
9493
  process.exitCode = 1;
9354
9494
  return;
9355
9495
  }
9356
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9496
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9357
9497
  if (opts.all || opts.pending) {
9358
9498
  const candidates = all.filter((m) => {
9359
9499
  const s = m.memory.frontmatter.status;
@@ -9407,7 +9547,7 @@ function registerMemoryApprove(memory2) {
9407
9547
 
9408
9548
  // src/commands/memory-update.ts
9409
9549
  import { readFile as readFile11, writeFile as writeFile17 } from "fs/promises";
9410
- import { existsSync as existsSync36 } from "fs";
9550
+ import { existsSync as existsSync37 } from "fs";
9411
9551
  import path20 from "path";
9412
9552
  import "commander";
9413
9553
  import {
@@ -9419,12 +9559,12 @@ function registerMemoryUpdate(memory2) {
9419
9559
  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) => {
9420
9560
  const root = findProjectRoot17(opts.dir);
9421
9561
  const paths = resolveHaivePaths14(root);
9422
- if (!existsSync36(paths.memoriesDir)) {
9562
+ if (!existsSync37(paths.memoriesDir)) {
9423
9563
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9424
9564
  process.exitCode = 1;
9425
9565
  return;
9426
9566
  }
9427
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
9567
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9428
9568
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
9429
9569
  if (!loaded) {
9430
9570
  ui.error(`No memory with id "${id}".`);
@@ -9461,7 +9601,7 @@ function registerMemoryUpdate(memory2) {
9461
9601
  if (opts.author !== void 0) updated.push("author");
9462
9602
  let newBody;
9463
9603
  if (opts.bodyFile !== void 0) {
9464
- if (!existsSync36(opts.bodyFile)) {
9604
+ if (!existsSync37(opts.bodyFile)) {
9465
9605
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9466
9606
  process.exitCode = 1;
9467
9607
  return;
@@ -9507,15 +9647,15 @@ function parseCsv3(value) {
9507
9647
 
9508
9648
  // src/commands/memory-auto-promote.ts
9509
9649
  import { writeFile as writeFile18 } from "fs/promises";
9510
- import { existsSync as existsSync37 } from "fs";
9650
+ import { existsSync as existsSync38 } from "fs";
9511
9651
  import path21 from "path";
9512
9652
  import "commander";
9513
9653
  import {
9514
9654
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
9515
9655
  findProjectRoot as findProjectRoot18,
9516
- getUsage as getUsage11,
9656
+ getUsage as getUsage12,
9517
9657
  isAutoPromoteEligible as isAutoPromoteEligible3,
9518
- loadUsageIndex as loadUsageIndex13,
9658
+ loadUsageIndex as loadUsageIndex14,
9519
9659
  resolveHaivePaths as resolveHaivePaths15,
9520
9660
  serializeMemory as serializeMemory16
9521
9661
  } from "@hiveai/core";
@@ -9527,7 +9667,7 @@ function registerMemoryAutoPromote(memory2) {
9527
9667
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9528
9668
  const root = findProjectRoot18(opts.dir);
9529
9669
  const paths = resolveHaivePaths15(root);
9530
- if (!existsSync37(paths.memoriesDir)) {
9670
+ if (!existsSync38(paths.memoriesDir)) {
9531
9671
  ui.error(`No .ai/memories at ${root}.`);
9532
9672
  process.exitCode = 1;
9533
9673
  return;
@@ -9536,10 +9676,10 @@ function registerMemoryAutoPromote(memory2) {
9536
9676
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
9537
9677
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
9538
9678
  };
9539
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
9540
- const usage = await loadUsageIndex13(paths);
9679
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9680
+ const usage = await loadUsageIndex14(paths);
9541
9681
  const eligible = memories.filter(
9542
- ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage11(usage, memory3.frontmatter.id), rule)
9682
+ ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
9543
9683
  );
9544
9684
  if (eligible.length === 0) {
9545
9685
  ui.info(
@@ -9549,7 +9689,7 @@ function registerMemoryAutoPromote(memory2) {
9549
9689
  }
9550
9690
  let written = 0;
9551
9691
  for (const { memory: mem, filePath } of eligible) {
9552
- const u = getUsage11(usage, mem.frontmatter.id);
9692
+ const u = getUsage12(usage, mem.frontmatter.id);
9553
9693
  console.log(
9554
9694
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
9555
9695
  );
@@ -9570,7 +9710,7 @@ function registerMemoryAutoPromote(memory2) {
9570
9710
 
9571
9711
  // src/commands/memory-edit.ts
9572
9712
  import { spawn as spawn3 } from "child_process";
9573
- import { existsSync as existsSync38 } from "fs";
9713
+ import { existsSync as existsSync39 } from "fs";
9574
9714
  import { readFile as readFile12 } from "fs/promises";
9575
9715
  import path23 from "path";
9576
9716
  import "commander";
@@ -9583,12 +9723,12 @@ function registerMemoryEdit(memory2) {
9583
9723
  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) => {
9584
9724
  const root = findProjectRoot19(opts.dir);
9585
9725
  const paths = resolveHaivePaths16(root);
9586
- if (!existsSync38(paths.memoriesDir)) {
9726
+ if (!existsSync39(paths.memoriesDir)) {
9587
9727
  ui.error(`No .ai/memories at ${root}.`);
9588
9728
  process.exitCode = 1;
9589
9729
  return;
9590
9730
  }
9591
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9731
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9592
9732
  const found = all.find((m) => m.memory.frontmatter.id === id);
9593
9733
  if (!found) {
9594
9734
  ui.error(`No memory with id "${id}".`);
@@ -9623,15 +9763,15 @@ function runEditor(editor, file) {
9623
9763
  }
9624
9764
 
9625
9765
  // src/commands/memory-for-files.ts
9626
- import { existsSync as existsSync39 } from "fs";
9766
+ import { existsSync as existsSync40 } from "fs";
9627
9767
  import path24 from "path";
9628
9768
  import "commander";
9629
9769
  import {
9630
9770
  deriveConfidence as deriveConfidence9,
9631
9771
  findProjectRoot as findProjectRoot20,
9632
- getUsage as getUsage12,
9772
+ getUsage as getUsage13,
9633
9773
  inferModulesFromPaths as inferModulesFromPaths4,
9634
- loadUsageIndex as loadUsageIndex14,
9774
+ loadUsageIndex as loadUsageIndex15,
9635
9775
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
9636
9776
  resolveHaivePaths as resolveHaivePaths17
9637
9777
  } from "@hiveai/core";
@@ -9639,13 +9779,13 @@ function registerMemoryForFiles(memory2) {
9639
9779
  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) => {
9640
9780
  const root = findProjectRoot20(opts.dir);
9641
9781
  const paths = resolveHaivePaths17(root);
9642
- if (!existsSync39(paths.memoriesDir)) {
9782
+ if (!existsSync40(paths.memoriesDir)) {
9643
9783
  ui.error(`No .ai/memories at ${root}.`);
9644
9784
  process.exitCode = 1;
9645
9785
  return;
9646
9786
  }
9647
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9648
- const usage = await loadUsageIndex14(paths);
9787
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9788
+ const usage = await loadUsageIndex15(paths);
9649
9789
  const inferred = inferModulesFromPaths4(files);
9650
9790
  const byAnchor = [];
9651
9791
  const byModule = [];
@@ -9743,7 +9883,7 @@ function printGroup(root, label, loaded, usage) {
9743
9883
  \u2014 ${label} \u2014`));
9744
9884
  for (const { memory: mem, filePath } of loaded) {
9745
9885
  const fm = mem.frontmatter;
9746
- const u = getUsage12(usage, fm.id);
9886
+ const u = getUsage13(usage, fm.id);
9747
9887
  const conf = deriveConfidence9(fm, u);
9748
9888
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
9749
9889
  console.log(` ${ui.dim(path24.relative(root, filePath))}`);
@@ -9751,13 +9891,13 @@ function printGroup(root, label, loaded, usage) {
9751
9891
  }
9752
9892
 
9753
9893
  // src/commands/memory-hot.ts
9754
- import { existsSync as existsSync40 } from "fs";
9894
+ import { existsSync as existsSync41 } from "fs";
9755
9895
  import path25 from "path";
9756
9896
  import "commander";
9757
9897
  import {
9758
9898
  findProjectRoot as findProjectRoot21,
9759
- getUsage as getUsage13,
9760
- loadUsageIndex as loadUsageIndex15,
9899
+ getUsage as getUsage14,
9900
+ loadUsageIndex as loadUsageIndex16,
9761
9901
  resolveHaivePaths as resolveHaivePaths18
9762
9902
  } from "@hiveai/core";
9763
9903
  function registerMemoryHot(memory2) {
@@ -9766,23 +9906,23 @@ function registerMemoryHot(memory2) {
9766
9906
  ).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) => {
9767
9907
  const root = findProjectRoot21(opts.dir);
9768
9908
  const paths = resolveHaivePaths18(root);
9769
- if (!existsSync40(paths.memoriesDir)) {
9909
+ if (!existsSync41(paths.memoriesDir)) {
9770
9910
  ui.error(`No .ai/memories at ${root}.`);
9771
9911
  process.exitCode = 1;
9772
9912
  return;
9773
9913
  }
9774
9914
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
9775
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9776
- const usage = await loadUsageIndex15(paths);
9915
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9916
+ const usage = await loadUsageIndex16(paths);
9777
9917
  const candidates = all.filter(({ memory: mem }) => {
9778
9918
  const fm = mem.frontmatter;
9779
9919
  if (opts.status && fm.status !== opts.status) return false;
9780
9920
  if (opts.status === void 0 && fm.status !== "draft" && fm.status !== "proposed") {
9781
9921
  return false;
9782
9922
  }
9783
- return getUsage13(usage, fm.id).read_count >= threshold;
9923
+ return getUsage14(usage, fm.id).read_count >= threshold;
9784
9924
  }).sort(
9785
- (a, b) => getUsage13(usage, b.memory.frontmatter.id).read_count - getUsage13(usage, a.memory.frontmatter.id).read_count
9925
+ (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
9786
9926
  );
9787
9927
  if (candidates.length === 0) {
9788
9928
  ui.info(`No hot memories (threshold=${threshold}).`);
@@ -9790,7 +9930,7 @@ function registerMemoryHot(memory2) {
9790
9930
  }
9791
9931
  for (const { memory: mem, filePath } of candidates) {
9792
9932
  const fm = mem.frontmatter;
9793
- const u = getUsage13(usage, fm.id);
9933
+ const u = getUsage14(usage, fm.id);
9794
9934
  console.log(
9795
9935
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
9796
9936
  );
@@ -9805,7 +9945,7 @@ function registerMemoryHot(memory2) {
9805
9945
 
9806
9946
  // src/commands/memory-tried.ts
9807
9947
  import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
9808
- import { existsSync as existsSync41 } from "fs";
9948
+ import { existsSync as existsSync43 } from "fs";
9809
9949
  import path26 from "path";
9810
9950
  import "commander";
9811
9951
  import {
@@ -9835,7 +9975,7 @@ function registerMemoryTried(memory2) {
9835
9975
  ).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) => {
9836
9976
  const root = findProjectRoot22(opts.dir);
9837
9977
  const paths = resolveHaivePaths19(root);
9838
- if (!existsSync41(paths.haiveDir)) {
9978
+ if (!existsSync43(paths.haiveDir)) {
9839
9979
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9840
9980
  process.exitCode = 1;
9841
9981
  return;
@@ -9863,7 +10003,7 @@ function registerMemoryTried(memory2) {
9863
10003
  }
9864
10004
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9865
10005
  await mkdir13(path26.dirname(file), { recursive: true });
9866
- if (existsSync41(file)) {
10006
+ if (existsSync43(file)) {
9867
10007
  ui.error(`Memory already exists at ${file}`);
9868
10008
  process.exitCode = 1;
9869
10009
  return;
@@ -9881,7 +10021,7 @@ function parseCsv4(value) {
9881
10021
 
9882
10022
  // src/commands/memory-seed.ts
9883
10023
  import { readFile as readFile13 } from "fs/promises";
9884
- import { existsSync as existsSync43 } from "fs";
10024
+ import { existsSync as existsSync44 } from "fs";
9885
10025
  import path27 from "path";
9886
10026
  import "commander";
9887
10027
  import {
@@ -9921,7 +10061,7 @@ function registerMemorySeed(memory2) {
9921
10061
  }
9922
10062
  return;
9923
10063
  }
9924
- if (!existsSync43(paths.haiveDir)) {
10064
+ if (!existsSync44(paths.haiveDir)) {
9925
10065
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9926
10066
  process.exitCode = 1;
9927
10067
  return;
@@ -9977,26 +10117,26 @@ function registerMemorySeed(memory2) {
9977
10117
  }
9978
10118
 
9979
10119
  // src/commands/memory-pending.ts
9980
- import { existsSync as existsSync44 } from "fs";
10120
+ import { existsSync as existsSync45 } from "fs";
9981
10121
  import path28 from "path";
9982
10122
  import "commander";
9983
10123
  import {
9984
10124
  findProjectRoot as findProjectRoot24,
9985
- getUsage as getUsage14,
9986
- loadUsageIndex as loadUsageIndex16,
10125
+ getUsage as getUsage15,
10126
+ loadUsageIndex as loadUsageIndex17,
9987
10127
  resolveHaivePaths as resolveHaivePaths21
9988
10128
  } from "@hiveai/core";
9989
10129
  function registerMemoryPending(memory2) {
9990
10130
  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) => {
9991
10131
  const root = findProjectRoot24(opts.dir);
9992
10132
  const paths = resolveHaivePaths21(root);
9993
- if (!existsSync44(paths.memoriesDir)) {
10133
+ if (!existsSync45(paths.memoriesDir)) {
9994
10134
  ui.error(`No .ai/memories at ${root}.`);
9995
10135
  process.exitCode = 1;
9996
10136
  return;
9997
10137
  }
9998
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9999
- const usage = await loadUsageIndex16(paths);
10138
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10139
+ const usage = await loadUsageIndex17(paths);
10000
10140
  const filterFn = ({ memory: mem }) => {
10001
10141
  if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
10002
10142
  if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
@@ -10009,7 +10149,7 @@ function registerMemoryPending(memory2) {
10009
10149
  return;
10010
10150
  }
10011
10151
  pending.sort(
10012
- (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
10152
+ (a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
10013
10153
  );
10014
10154
  const now = Date.now();
10015
10155
  const drafts = pending.filter((m) => m.memory.frontmatter.status === "draft");
@@ -10018,7 +10158,7 @@ function registerMemoryPending(memory2) {
10018
10158
  console.log(ui.bold(`Proposed (${proposed.length}) \u2014 awaiting team validation`));
10019
10159
  for (const { memory: mem, filePath } of proposed) {
10020
10160
  const fm = mem.frontmatter;
10021
- const u = getUsage14(usage, fm.id);
10161
+ const u = getUsage15(usage, fm.id);
10022
10162
  const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
10023
10163
  const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
10024
10164
  console.log(
@@ -10033,7 +10173,7 @@ function registerMemoryPending(memory2) {
10033
10173
  console.log(ui.bold(`Draft (${drafts.length}) \u2014 created but not yet activated`));
10034
10174
  for (const { memory: mem, filePath } of drafts) {
10035
10175
  const fm = mem.frontmatter;
10036
- const u = getUsage14(usage, fm.id);
10176
+ const u = getUsage15(usage, fm.id);
10037
10177
  const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
10038
10178
  const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
10039
10179
  console.log(
@@ -10048,7 +10188,7 @@ function registerMemoryPending(memory2) {
10048
10188
  }
10049
10189
 
10050
10190
  // src/commands/memory-query.ts
10051
- import { existsSync as existsSync45 } from "fs";
10191
+ import { existsSync as existsSync46 } from "fs";
10052
10192
  import path29 from "path";
10053
10193
  import "commander";
10054
10194
  import {
@@ -10065,7 +10205,7 @@ function registerMemoryQuery(memory2) {
10065
10205
  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) => {
10066
10206
  const root = findProjectRoot25(opts.dir);
10067
10207
  const paths = resolveHaivePaths22(root);
10068
- if (!existsSync45(paths.memoriesDir)) {
10208
+ if (!existsSync46(paths.memoriesDir)) {
10069
10209
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
10070
10210
  process.exitCode = 1;
10071
10211
  return;
@@ -10076,7 +10216,7 @@ function registerMemoryQuery(memory2) {
10076
10216
  return;
10077
10217
  }
10078
10218
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
10079
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10219
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10080
10220
  const passesFilters2 = (mem) => {
10081
10221
  const fm = mem.frontmatter;
10082
10222
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -10124,26 +10264,26 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
10124
10264
 
10125
10265
  // src/commands/memory-reject.ts
10126
10266
  import { writeFile as writeFile20 } from "fs/promises";
10127
- import { existsSync as existsSync46 } from "fs";
10267
+ import { existsSync as existsSync47 } from "fs";
10128
10268
  import "commander";
10129
10269
  import {
10130
10270
  findProjectRoot as findProjectRoot26,
10131
- loadUsageIndex as loadUsageIndex17,
10132
- recordRejection as recordRejection2,
10271
+ loadUsageIndex as loadUsageIndex18,
10272
+ recordRejection as recordRejection3,
10133
10273
  resolveHaivePaths as resolveHaivePaths23,
10134
- saveUsageIndex as saveUsageIndex3,
10274
+ saveUsageIndex as saveUsageIndex4,
10135
10275
  serializeMemory as serializeMemory18
10136
10276
  } from "@hiveai/core";
10137
10277
  function registerMemoryReject(memory2) {
10138
10278
  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) => {
10139
10279
  const root = findProjectRoot26(opts.dir);
10140
10280
  const paths = resolveHaivePaths23(root);
10141
- if (!existsSync46(paths.memoriesDir)) {
10281
+ if (!existsSync47(paths.memoriesDir)) {
10142
10282
  ui.error(`No .ai/memories at ${root}.`);
10143
10283
  process.exitCode = 1;
10144
10284
  return;
10145
10285
  }
10146
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
10286
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
10147
10287
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
10148
10288
  if (!loaded) {
10149
10289
  ui.error(`No memory with id "${id}".`);
@@ -10162,9 +10302,9 @@ function registerMemoryReject(memory2) {
10162
10302
  }),
10163
10303
  "utf8"
10164
10304
  );
10165
- const idx = await loadUsageIndex17(paths);
10166
- recordRejection2(idx, id, opts.reason ?? null);
10167
- await saveUsageIndex3(paths, idx);
10305
+ const idx = await loadUsageIndex18(paths);
10306
+ recordRejection3(idx, id, opts.reason ?? null);
10307
+ await saveUsageIndex4(paths, idx);
10168
10308
  const u = idx.by_id[id];
10169
10309
  ui.success(
10170
10310
  `Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
@@ -10174,27 +10314,27 @@ function registerMemoryReject(memory2) {
10174
10314
  }
10175
10315
 
10176
10316
  // src/commands/memory-rm.ts
10177
- import { existsSync as existsSync47 } from "fs";
10317
+ import { existsSync as existsSync48 } from "fs";
10178
10318
  import { unlink as unlink3 } from "fs/promises";
10179
10319
  import path30 from "path";
10180
10320
  import { createInterface as createInterface2 } from "readline/promises";
10181
10321
  import "commander";
10182
10322
  import {
10183
10323
  findProjectRoot as findProjectRoot27,
10184
- loadUsageIndex as loadUsageIndex18,
10324
+ loadUsageIndex as loadUsageIndex19,
10185
10325
  resolveHaivePaths as resolveHaivePaths24,
10186
- saveUsageIndex as saveUsageIndex4
10326
+ saveUsageIndex as saveUsageIndex5
10187
10327
  } from "@hiveai/core";
10188
10328
  function registerMemoryRm(memory2) {
10189
10329
  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) => {
10190
10330
  const root = findProjectRoot27(opts.dir);
10191
10331
  const paths = resolveHaivePaths24(root);
10192
- if (!existsSync47(paths.memoriesDir)) {
10332
+ if (!existsSync48(paths.memoriesDir)) {
10193
10333
  ui.error(`No .ai/memories at ${root}.`);
10194
10334
  process.exitCode = 1;
10195
10335
  return;
10196
10336
  }
10197
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10337
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10198
10338
  const found = all.find((m) => m.memory.frontmatter.id === id);
10199
10339
  if (!found) {
10200
10340
  ui.error(`No memory with id "${id}".`);
@@ -10214,10 +10354,10 @@ function registerMemoryRm(memory2) {
10214
10354
  await unlink3(found.filePath);
10215
10355
  ui.success(`Deleted ${rel}`);
10216
10356
  if (!opts.keepUsage) {
10217
- const idx = await loadUsageIndex18(paths);
10357
+ const idx = await loadUsageIndex19(paths);
10218
10358
  if (idx.by_id[id]) {
10219
10359
  delete idx.by_id[id];
10220
- await saveUsageIndex4(paths, idx);
10360
+ await saveUsageIndex5(paths, idx);
10221
10361
  ui.info("Removed usage entry");
10222
10362
  }
10223
10363
  }
@@ -10225,27 +10365,27 @@ function registerMemoryRm(memory2) {
10225
10365
  }
10226
10366
 
10227
10367
  // src/commands/memory-show.ts
10228
- import { existsSync as existsSync48 } from "fs";
10368
+ import { existsSync as existsSync49 } from "fs";
10229
10369
  import { readFile as readFile14 } from "fs/promises";
10230
10370
  import path31 from "path";
10231
10371
  import "commander";
10232
10372
  import {
10233
10373
  deriveConfidence as deriveConfidence10,
10234
10374
  findProjectRoot as findProjectRoot28,
10235
- getUsage as getUsage15,
10236
- loadUsageIndex as loadUsageIndex19,
10375
+ getUsage as getUsage16,
10376
+ loadUsageIndex as loadUsageIndex20,
10237
10377
  resolveHaivePaths as resolveHaivePaths25
10238
10378
  } from "@hiveai/core";
10239
10379
  function registerMemoryShow(memory2) {
10240
10380
  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) => {
10241
10381
  const root = findProjectRoot28(opts.dir);
10242
10382
  const paths = resolveHaivePaths25(root);
10243
- if (!existsSync48(paths.memoriesDir)) {
10383
+ if (!existsSync49(paths.memoriesDir)) {
10244
10384
  ui.error(`No .ai/memories at ${root}.`);
10245
10385
  process.exitCode = 1;
10246
10386
  return;
10247
10387
  }
10248
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10388
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10249
10389
  const found = all.find((m) => m.memory.frontmatter.id === id);
10250
10390
  if (!found) {
10251
10391
  ui.error(`No memory with id "${id}".`);
@@ -10257,8 +10397,8 @@ function registerMemoryShow(memory2) {
10257
10397
  return;
10258
10398
  }
10259
10399
  const fm = found.memory.frontmatter;
10260
- const usage = await loadUsageIndex19(paths);
10261
- const u = getUsage15(usage, fm.id);
10400
+ const usage = await loadUsageIndex20(paths);
10401
+ const u = getUsage16(usage, fm.id);
10262
10402
  const conf = deriveConfidence10(fm, u);
10263
10403
  console.log(ui.bold(fm.id));
10264
10404
  console.log(`${ui.dim("scope:")} ${fm.scope}${fm.module ? ` / ${fm.module}` : ""}`);
@@ -10284,38 +10424,38 @@ function registerMemoryShow(memory2) {
10284
10424
  }
10285
10425
 
10286
10426
  // src/commands/memory-stats.ts
10287
- import { existsSync as existsSync49 } from "fs";
10427
+ import { existsSync as existsSync50 } from "fs";
10288
10428
  import path33 from "path";
10289
10429
  import "commander";
10290
10430
  import {
10291
10431
  deriveConfidence as deriveConfidence11,
10292
10432
  findProjectRoot as findProjectRoot29,
10293
- getUsage as getUsage16,
10294
- loadUsageIndex as loadUsageIndex20,
10433
+ getUsage as getUsage17,
10434
+ loadUsageIndex as loadUsageIndex21,
10295
10435
  resolveHaivePaths as resolveHaivePaths26
10296
10436
  } from "@hiveai/core";
10297
10437
  function registerMemoryStats(memory2) {
10298
10438
  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) => {
10299
10439
  const root = findProjectRoot29(opts.dir);
10300
10440
  const paths = resolveHaivePaths26(root);
10301
- if (!existsSync49(paths.memoriesDir)) {
10441
+ if (!existsSync50(paths.memoriesDir)) {
10302
10442
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10303
10443
  process.exitCode = 1;
10304
10444
  return;
10305
10445
  }
10306
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10307
- const usage = await loadUsageIndex20(paths);
10446
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10447
+ const usage = await loadUsageIndex21(paths);
10308
10448
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10309
10449
  if (target.length === 0) {
10310
10450
  ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
10311
10451
  return;
10312
10452
  }
10313
10453
  target.sort(
10314
- (a, b) => getUsage16(usage, b.memory.frontmatter.id).read_count - getUsage16(usage, a.memory.frontmatter.id).read_count
10454
+ (a, b) => getUsage17(usage, b.memory.frontmatter.id).read_count - getUsage17(usage, a.memory.frontmatter.id).read_count
10315
10455
  );
10316
10456
  for (const { memory: mem, filePath } of target) {
10317
10457
  const fm = mem.frontmatter;
10318
- const u = getUsage16(usage, fm.id);
10458
+ const u = getUsage17(usage, fm.id);
10319
10459
  const conf = deriveConfidence11(fm, u);
10320
10460
  console.log(
10321
10461
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`
@@ -10328,14 +10468,102 @@ function registerMemoryStats(memory2) {
10328
10468
  });
10329
10469
  }
10330
10470
 
10471
+ // src/commands/memory-impact.ts
10472
+ import { existsSync as existsSync51 } from "fs";
10473
+ import "commander";
10474
+ import {
10475
+ compareImpact,
10476
+ computeImpact as computeImpact3,
10477
+ findProjectRoot as findProjectRoot30,
10478
+ getUsage as getUsage18,
10479
+ loadUsageIndex as loadUsageIndex23,
10480
+ resolveHaivePaths as resolveHaivePaths27,
10481
+ summarizeImpact
10482
+ } from "@hiveai/core";
10483
+ function registerMemoryImpact(memory2) {
10484
+ memory2.command("impact").description(
10485
+ "Score memories by demonstrated utility (reads + applied outcomes + sensor fires vs rejections, staleness, dormancy) and surface prune candidates."
10486
+ ).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) => {
10487
+ const root = findProjectRoot30(opts.dir);
10488
+ const paths = resolveHaivePaths27(root);
10489
+ if (!existsSync51(paths.memoriesDir)) {
10490
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10491
+ process.exitCode = 1;
10492
+ return;
10493
+ }
10494
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10495
+ const usageIndex = await loadUsageIndex23(paths);
10496
+ let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
10497
+ const fm = mem.frontmatter;
10498
+ return {
10499
+ id: fm.id,
10500
+ type: fm.type,
10501
+ scope: fm.scope,
10502
+ status: fm.status,
10503
+ impact: computeImpact3(fm, getUsage18(usageIndex, fm.id))
10504
+ };
10505
+ });
10506
+ if (opts.prune) rows = rows.filter((r) => r.impact.pruneCandidate);
10507
+ if (opts.tier) {
10508
+ const tier = opts.tier;
10509
+ rows = rows.filter((r) => r.impact.tier === tier);
10510
+ }
10511
+ rows.sort((a, b) => compareImpact(a.impact, b.impact));
10512
+ const summary = summarizeImpact(all.map((m) => computeImpact3(m.memory.frontmatter, getUsage18(usageIndex, m.memory.frontmatter.id))));
10513
+ if (opts.json) {
10514
+ console.log(JSON.stringify({ root, summary, rows }, null, 2));
10515
+ return;
10516
+ }
10517
+ if (rows.length === 0) {
10518
+ ui.info(opts.prune ? "No prune candidates \u2014 every memory earns its keep." : "No memories matched.");
10519
+ return;
10520
+ }
10521
+ console.log(ui.bold(`hAIve memory impact \u2014 ${root}`));
10522
+ console.log(
10523
+ ui.dim(
10524
+ `${summary.total} memories \xB7 ${summary.high} high \xB7 ${summary.medium} medium \xB7 ${summary.low} low \xB7 ${summary.dormant} dormant \xB7 ${summary.prune_candidates} prune candidates`
10525
+ )
10526
+ );
10527
+ console.log();
10528
+ console.log(`${"score".padStart(5)} ${"tier".padEnd(7)} ${pad("id", 52)} ${"prune".padEnd(5)} signals`);
10529
+ console.log("\u2500".repeat(108));
10530
+ for (const r of rows) {
10531
+ const score = r.impact.score.toFixed(2).padStart(5);
10532
+ const tier = tierBadge(r.impact.tier).padEnd(7);
10533
+ const prune = r.impact.pruneCandidate ? ui.yellow("prune") : " ";
10534
+ console.log(`${score} ${tier} ${pad(r.id, 52)} ${prune} ${ui.dim(r.impact.signals.join(", ") || "no signals")}`);
10535
+ }
10536
+ if (!opts.prune && summary.prune_candidates > 0) {
10537
+ console.log();
10538
+ console.log(ui.dim(`Tip: \`haive memory impact --prune\` lists the ${summary.prune_candidates} prune candidate(s).`));
10539
+ }
10540
+ });
10541
+ }
10542
+ function tierBadge(tier) {
10543
+ switch (tier) {
10544
+ case "high":
10545
+ return ui.green(tier);
10546
+ case "medium":
10547
+ return ui.yellow(tier);
10548
+ case "dormant":
10549
+ return ui.dim(tier);
10550
+ default:
10551
+ return tier;
10552
+ }
10553
+ }
10554
+ function pad(value, width) {
10555
+ if (value.length <= width) return value.padEnd(width);
10556
+ return value.slice(0, width - 1) + "\u2026";
10557
+ }
10558
+
10331
10559
  // src/commands/memory-verify.ts
10332
10560
  import { writeFile as writeFile21 } from "fs/promises";
10333
- import { existsSync as existsSync50 } from "fs";
10561
+ import { existsSync as existsSync53 } from "fs";
10334
10562
  import path34 from "path";
10335
10563
  import "commander";
10336
10564
  import {
10337
- findProjectRoot as findProjectRoot30,
10338
- resolveHaivePaths as resolveHaivePaths27,
10565
+ findProjectRoot as findProjectRoot31,
10566
+ resolveHaivePaths as resolveHaivePaths28,
10339
10567
  serializeMemory as serializeMemory19,
10340
10568
  verifyAnchor as verifyAnchor3
10341
10569
  } from "@hiveai/core";
@@ -10343,9 +10571,9 @@ function registerMemoryVerify(memory2) {
10343
10571
  memory2.command("verify").description(
10344
10572
  "Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
10345
10573
  ).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) => {
10346
- const root = findProjectRoot30(opts.dir);
10347
- const paths = resolveHaivePaths27(root);
10348
- if (!existsSync50(paths.memoriesDir)) {
10574
+ const root = findProjectRoot31(opts.dir);
10575
+ const paths = resolveHaivePaths28(root);
10576
+ if (!existsSync53(paths.memoriesDir)) {
10349
10577
  if (opts.json) {
10350
10578
  console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
10351
10579
  } else {
@@ -10354,7 +10582,7 @@ function registerMemoryVerify(memory2) {
10354
10582
  process.exitCode = 1;
10355
10583
  return;
10356
10584
  }
10357
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10585
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10358
10586
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10359
10587
  if (opts.id && targets.length === 0) {
10360
10588
  if (opts.json) {
@@ -10466,24 +10694,24 @@ function applyVerification2(mem, result) {
10466
10694
 
10467
10695
  // src/commands/memory-import.ts
10468
10696
  import { readFile as readFile15 } from "fs/promises";
10469
- import { existsSync as existsSync51 } from "fs";
10697
+ import { existsSync as existsSync54 } from "fs";
10470
10698
  import "commander";
10471
10699
  import {
10472
- findProjectRoot as findProjectRoot31,
10473
- resolveHaivePaths as resolveHaivePaths28
10700
+ findProjectRoot as findProjectRoot32,
10701
+ resolveHaivePaths as resolveHaivePaths29
10474
10702
  } from "@hiveai/core";
10475
10703
  function registerMemoryImport(memory2) {
10476
10704
  memory2.command("import").description(
10477
10705
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
10478
10706
  ).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) => {
10479
- const root = findProjectRoot31(opts.dir);
10480
- const paths = resolveHaivePaths28(root);
10481
- if (!existsSync51(paths.haiveDir)) {
10707
+ const root = findProjectRoot32(opts.dir);
10708
+ const paths = resolveHaivePaths29(root);
10709
+ if (!existsSync54(paths.haiveDir)) {
10482
10710
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10483
10711
  process.exitCode = 1;
10484
10712
  return;
10485
10713
  }
10486
- if (!existsSync51(opts.from)) {
10714
+ if (!existsSync54(opts.from)) {
10487
10715
  ui.error(`File not found: ${opts.from}`);
10488
10716
  process.exitCode = 1;
10489
10717
  return;
@@ -10516,14 +10744,14 @@ function registerMemoryImport(memory2) {
10516
10744
  }
10517
10745
 
10518
10746
  // src/commands/memory-import-changelog.ts
10519
- import { existsSync as existsSync53 } from "fs";
10747
+ import { existsSync as existsSync55 } from "fs";
10520
10748
  import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
10521
10749
  import path35 from "path";
10522
10750
  import "commander";
10523
10751
  import {
10524
10752
  buildFrontmatter as buildFrontmatter9,
10525
- findProjectRoot as findProjectRoot32,
10526
- resolveHaivePaths as resolveHaivePaths29,
10753
+ findProjectRoot as findProjectRoot33,
10754
+ resolveHaivePaths as resolveHaivePaths30,
10527
10755
  serializeMemory as serializeMemory20
10528
10756
  } from "@hiveai/core";
10529
10757
  function parseChangelog(content) {
@@ -10588,10 +10816,10 @@ function registerMemoryImportChangelog(memory2) {
10588
10816
  "--versions <csv>",
10589
10817
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
10590
10818
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10591
- const root = findProjectRoot32(opts.dir);
10592
- const paths = resolveHaivePaths29(root);
10819
+ const root = findProjectRoot33(opts.dir);
10820
+ const paths = resolveHaivePaths30(root);
10593
10821
  const changelogPath = path35.resolve(root, opts.fromChangelog);
10594
- if (!existsSync53(changelogPath)) {
10822
+ if (!existsSync55(changelogPath)) {
10595
10823
  ui.error(`CHANGELOG not found: ${changelogPath}`);
10596
10824
  process.exitCode = 1;
10597
10825
  return;
@@ -10678,17 +10906,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
10678
10906
  }
10679
10907
 
10680
10908
  // src/commands/memory-digest.ts
10681
- import { existsSync as existsSync54 } from "fs";
10909
+ import { existsSync as existsSync56 } from "fs";
10682
10910
  import { writeFile as writeFile24 } from "fs/promises";
10683
10911
  import path36 from "path";
10684
10912
  import "commander";
10685
10913
  import {
10686
10914
  deriveConfidence as deriveConfidence12,
10687
- findProjectRoot as findProjectRoot33,
10688
- getUsage as getUsage17,
10689
- loadMemoriesFromDir as loadMemoriesFromDir26,
10690
- loadUsageIndex as loadUsageIndex21,
10691
- resolveHaivePaths as resolveHaivePaths30
10915
+ findProjectRoot as findProjectRoot34,
10916
+ getUsage as getUsage19,
10917
+ loadMemoriesFromDir as loadMemoriesFromDir27,
10918
+ loadUsageIndex as loadUsageIndex24,
10919
+ resolveHaivePaths as resolveHaivePaths31
10692
10920
  } from "@hiveai/core";
10693
10921
  var CONFIDENCE_EMOJI = {
10694
10922
  unverified: "\u2B1C",
@@ -10701,9 +10929,9 @@ function registerMemoryDigest(program2) {
10701
10929
  program2.command("digest").description(
10702
10930
  "Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
10703
10931
  ).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) => {
10704
- const root = findProjectRoot33(opts.dir);
10705
- const paths = resolveHaivePaths30(root);
10706
- if (!existsSync54(paths.memoriesDir)) {
10932
+ const root = findProjectRoot34(opts.dir);
10933
+ const paths = resolveHaivePaths31(root);
10934
+ if (!existsSync56(paths.memoriesDir)) {
10707
10935
  ui.error("No .ai/memories found. Run `haive init` first.");
10708
10936
  process.exitCode = 1;
10709
10937
  return;
@@ -10711,8 +10939,8 @@ function registerMemoryDigest(program2) {
10711
10939
  const days = Math.max(1, Number(opts.days ?? 7));
10712
10940
  const scopeFilter = opts.scope ?? "team";
10713
10941
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
10714
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10715
- const usage = await loadUsageIndex21(paths);
10942
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10943
+ const usage = await loadUsageIndex24(paths);
10716
10944
  const recent = all.filter(({ memory: mem }) => {
10717
10945
  const fm = mem.frontmatter;
10718
10946
  if (fm.type === "session_recap") return false;
@@ -10743,7 +10971,7 @@ function registerMemoryDigest(program2) {
10743
10971
  lines.push(``);
10744
10972
  for (const { memory: mem } of mems) {
10745
10973
  const fm = mem.frontmatter;
10746
- const u = getUsage17(usage, fm.id);
10974
+ const u = getUsage19(usage, fm.id);
10747
10975
  const confidence = deriveConfidence12(fm, u);
10748
10976
  const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
10749
10977
  const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
@@ -10786,21 +11014,21 @@ function registerMemoryDigest(program2) {
10786
11014
 
10787
11015
  // src/commands/session-end.ts
10788
11016
  import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
10789
- import { existsSync as existsSync55 } from "fs";
11017
+ import { existsSync as existsSync57 } from "fs";
10790
11018
  import { spawn as spawn4 } from "child_process";
10791
11019
  import path37 from "path";
10792
11020
  import "commander";
10793
11021
  import {
10794
11022
  buildFrontmatter as buildFrontmatter10,
10795
- findProjectRoot as findProjectRoot34,
10796
- loadMemoriesFromDir as loadMemoriesFromDir27,
11023
+ findProjectRoot as findProjectRoot35,
11024
+ loadMemoriesFromDir as loadMemoriesFromDir28,
10797
11025
  memoryFilePath as memoryFilePath9,
10798
- resolveHaivePaths as resolveHaivePaths31,
11026
+ resolveHaivePaths as resolveHaivePaths32,
10799
11027
  serializeMemory as serializeMemory21
10800
11028
  } from "@hiveai/core";
10801
11029
  async function buildAutoRecap(paths) {
10802
11030
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
10803
- if (!existsSync55(obsFile)) return await buildGitAutoRecap(paths);
11031
+ if (!existsSync57(obsFile)) return await buildGitAutoRecap(paths);
10804
11032
  const raw = await readFile17(obsFile, "utf8").catch(() => "");
10805
11033
  if (!raw.trim()) return await buildGitAutoRecap(paths);
10806
11034
  const lines = raw.split("\n").filter(Boolean);
@@ -10989,9 +11217,9 @@ function registerSessionEnd(session2) {
10989
11217
  --next "Add integration tests for webhook signature validation"
10990
11218
  `
10991
11219
  ).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) => {
10992
- const root = findProjectRoot34(opts.dir);
10993
- const paths = resolveHaivePaths31(root);
10994
- if (!existsSync55(paths.haiveDir)) {
11220
+ const root = findProjectRoot35(opts.dir);
11221
+ const paths = resolveHaivePaths32(root);
11222
+ if (!existsSync57(paths.haiveDir)) {
10995
11223
  if (opts.auto || opts.quiet) return;
10996
11224
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10997
11225
  process.exitCode = 1;
@@ -11024,7 +11252,7 @@ function registerSessionEnd(session2) {
11024
11252
  });
11025
11253
  const topic = recapTopic2(scope, opts.module);
11026
11254
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
11027
- const missingPaths = filesTouched.filter((p) => !existsSync55(path37.resolve(root, p)));
11255
+ const missingPaths = filesTouched.filter((p) => !existsSync57(path37.resolve(root, p)));
11028
11256
  if (missingPaths.length > 0 && !opts.quiet) {
11029
11257
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
11030
11258
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
@@ -11032,11 +11260,11 @@ function registerSessionEnd(session2) {
11032
11260
  const cleanupObservations = async () => {
11033
11261
  if (!opts.auto) return;
11034
11262
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11035
- if (existsSync55(obsFile)) await rm2(obsFile).catch(() => {
11263
+ if (existsSync57(obsFile)) await rm2(obsFile).catch(() => {
11036
11264
  });
11037
11265
  };
11038
- if (existsSync55(paths.memoriesDir)) {
11039
- const existing = await loadMemoriesFromDir27(paths.memoriesDir);
11266
+ if (existsSync57(paths.memoriesDir)) {
11267
+ const existing = await loadMemoriesFromDir28(paths.memoriesDir);
11040
11268
  const topicMatch = existing.find(
11041
11269
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
11042
11270
  );
@@ -11097,15 +11325,15 @@ function normalizeAnchorPath(root, filePath) {
11097
11325
  }
11098
11326
 
11099
11327
  // src/commands/snapshot.ts
11100
- import { existsSync as existsSync56 } from "fs";
11328
+ import { existsSync as existsSync58 } from "fs";
11101
11329
  import { readdir as readdir4 } from "fs/promises";
11102
11330
  import path38 from "path";
11103
11331
  import "commander";
11104
11332
  import {
11105
11333
  diffContract,
11106
- findProjectRoot as findProjectRoot35,
11334
+ findProjectRoot as findProjectRoot36,
11107
11335
  loadConfig as loadConfig7,
11108
- resolveHaivePaths as resolveHaivePaths32,
11336
+ resolveHaivePaths as resolveHaivePaths33,
11109
11337
  snapshotContract
11110
11338
  } from "@hiveai/core";
11111
11339
  function registerSnapshot(program2) {
@@ -11130,16 +11358,16 @@ function registerSnapshot(program2) {
11130
11358
  "--format <format>",
11131
11359
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
11132
11360
  ).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) => {
11133
- const root = findProjectRoot35(opts.dir);
11134
- const paths = resolveHaivePaths32(root);
11135
- if (!existsSync56(paths.haiveDir)) {
11361
+ const root = findProjectRoot36(opts.dir);
11362
+ const paths = resolveHaivePaths33(root);
11363
+ if (!existsSync58(paths.haiveDir)) {
11136
11364
  ui.error("No .ai/ found. Run `haive init` first.");
11137
11365
  process.exitCode = 1;
11138
11366
  return;
11139
11367
  }
11140
11368
  if (opts.list) {
11141
11369
  const contractsDir = path38.join(paths.haiveDir, "contracts");
11142
- if (!existsSync56(contractsDir)) {
11370
+ if (!existsSync58(contractsDir)) {
11143
11371
  console.log(ui.dim("No contract snapshots found."));
11144
11372
  return;
11145
11373
  }
@@ -11263,16 +11491,16 @@ function detectFormat(filePath) {
11263
11491
  }
11264
11492
 
11265
11493
  // src/commands/hub.ts
11266
- import { existsSync as existsSync57 } from "fs";
11494
+ import { existsSync as existsSync59 } from "fs";
11267
11495
  import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
11268
11496
  import path39 from "path";
11269
11497
  import { spawnSync as spawnSync5 } from "child_process";
11270
11498
  import "commander";
11271
11499
  import {
11272
- findProjectRoot as findProjectRoot36,
11500
+ findProjectRoot as findProjectRoot37,
11273
11501
  loadConfig as loadConfig8,
11274
- loadMemoriesFromDir as loadMemoriesFromDir28,
11275
- resolveHaivePaths as resolveHaivePaths33,
11502
+ loadMemoriesFromDir as loadMemoriesFromDir29,
11503
+ resolveHaivePaths as resolveHaivePaths34,
11276
11504
  saveConfig as saveConfig3,
11277
11505
  serializeMemory as serializeMemory23
11278
11506
  } from "@hiveai/core";
@@ -11354,8 +11582,8 @@ Next steps:
11354
11582
  haive hub push --commit --message "feat: add payment API contract memories"
11355
11583
  `
11356
11584
  ).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
11357
- const root = findProjectRoot36(opts.dir);
11358
- const paths = resolveHaivePaths33(root);
11585
+ const root = findProjectRoot37(opts.dir);
11586
+ const paths = resolveHaivePaths34(root);
11359
11587
  const config = await loadConfig8(paths);
11360
11588
  if (!config.hubPath) {
11361
11589
  ui.error(
@@ -11365,7 +11593,7 @@ Next steps:
11365
11593
  return;
11366
11594
  }
11367
11595
  const hubRoot = path39.resolve(root, config.hubPath);
11368
- if (!existsSync57(hubRoot)) {
11596
+ if (!existsSync59(hubRoot)) {
11369
11597
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
11370
11598
  process.exitCode = 1;
11371
11599
  return;
@@ -11373,7 +11601,7 @@ Next steps:
11373
11601
  const projectName = path39.basename(root);
11374
11602
  const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
11375
11603
  await mkdir16(destDir, { recursive: true });
11376
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
11604
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
11377
11605
  const shared = all.filter(
11378
11606
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
11379
11607
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -11423,8 +11651,8 @@ Next steps:
11423
11651
  hub.command("pull").description(
11424
11652
  "Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
11425
11653
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11426
- const root = findProjectRoot36(opts.dir);
11427
- const paths = resolveHaivePaths33(root);
11654
+ const root = findProjectRoot37(opts.dir);
11655
+ const paths = resolveHaivePaths34(root);
11428
11656
  const config = await loadConfig8(paths);
11429
11657
  if (!config.hubPath) {
11430
11658
  ui.error(
@@ -11435,7 +11663,7 @@ Next steps:
11435
11663
  }
11436
11664
  const hubRoot = path39.resolve(root, config.hubPath);
11437
11665
  const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
11438
- if (!existsSync57(hubSharedDir)) {
11666
+ if (!existsSync59(hubSharedDir)) {
11439
11667
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
11440
11668
  return;
11441
11669
  }
@@ -11482,15 +11710,15 @@ Next steps:
11482
11710
  );
11483
11711
  });
11484
11712
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
11485
- const root = findProjectRoot36(opts.dir);
11486
- const paths = resolveHaivePaths33(root);
11713
+ const root = findProjectRoot37(opts.dir);
11714
+ const paths = resolveHaivePaths34(root);
11487
11715
  const config = await loadConfig8(paths);
11488
11716
  console.log(ui.bold("Hub status"));
11489
11717
  console.log(
11490
11718
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
11491
11719
  );
11492
11720
  const sharedDir = path39.join(paths.memoriesDir, "shared");
11493
- if (existsSync57(sharedDir)) {
11721
+ if (existsSync59(sharedDir)) {
11494
11722
  const { readdir: readdir7 } = await import("fs/promises");
11495
11723
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
11496
11724
  console.log(`
@@ -11502,7 +11730,7 @@ Next steps:
11502
11730
  } else {
11503
11731
  console.log(ui.dim(" No imported shared memories yet."));
11504
11732
  }
11505
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
11733
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
11506
11734
  const outgoing = all.filter(
11507
11735
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
11508
11736
  );
@@ -11519,17 +11747,17 @@ Next steps:
11519
11747
 
11520
11748
  // src/commands/stats.ts
11521
11749
  import "commander";
11522
- import { existsSync as existsSync58 } from "fs";
11750
+ import { existsSync as existsSync60 } from "fs";
11523
11751
  import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
11524
11752
  import path40 from "path";
11525
11753
  import {
11526
11754
  aggregateUsage,
11527
- findProjectRoot as findProjectRoot37,
11528
- loadMemoriesFromDir as loadMemoriesFromDir29,
11529
- loadUsageIndex as loadUsageIndex23,
11755
+ findProjectRoot as findProjectRoot38,
11756
+ loadMemoriesFromDir as loadMemoriesFromDir30,
11757
+ loadUsageIndex as loadUsageIndex25,
11530
11758
  parseSince,
11531
11759
  readUsageEvents as readUsageEvents2,
11532
- resolveHaivePaths as resolveHaivePaths34,
11760
+ resolveHaivePaths as resolveHaivePaths35,
11533
11761
  usageLogSize
11534
11762
  } from "@hiveai/core";
11535
11763
  function registerStats(program2) {
@@ -11538,8 +11766,8 @@ function registerStats(program2) {
11538
11766
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
11539
11767
  void 0
11540
11768
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11541
- const root = findProjectRoot37(opts.dir);
11542
- const paths = resolveHaivePaths34(root);
11769
+ const root = findProjectRoot38(opts.dir);
11770
+ const paths = resolveHaivePaths35(root);
11543
11771
  if (opts.exportReport) {
11544
11772
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
11545
11773
  return;
@@ -11585,9 +11813,9 @@ function registerStats(program2) {
11585
11813
  const maxCount = aggregate.by_tool[0]?.count ?? 1;
11586
11814
  for (const t of aggregate.by_tool.slice(0, 20)) {
11587
11815
  const bar = "\u2588".repeat(Math.max(1, Math.round(t.count / maxCount * 30)));
11588
- const pct = (t.count / aggregate.total * 100).toFixed(1);
11816
+ const pct2 = (t.count / aggregate.total * 100).toFixed(1);
11589
11817
  console.log(
11590
- ` ${t.tool.padEnd(28)} ${ui.green(bar)} ${ui.bold(String(t.count))} ${ui.dim(`(${pct}%, last ${t.last_used.slice(0, 19)})`)}`
11818
+ ` ${t.tool.padEnd(28)} ${ui.green(bar)} ${ui.bold(String(t.count))} ${ui.dim(`(${pct2}%, last ${t.last_used.slice(0, 19)})`)}`
11591
11819
  );
11592
11820
  }
11593
11821
  });
@@ -11597,8 +11825,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11597
11825
  const size = await usageLogSize(paths);
11598
11826
  let events = await readUsageEvents2(paths);
11599
11827
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
11600
- if (existsSync58(paths.memoriesDir)) {
11601
- const mems = await loadMemoriesFromDir29(paths.memoriesDir);
11828
+ if (existsSync60(paths.memoriesDir)) {
11829
+ const mems = await loadMemoriesFromDir30(paths.memoriesDir);
11602
11830
  for (const { memory: memory2 } of mems) {
11603
11831
  const fm = memory2.frontmatter;
11604
11832
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -11612,7 +11840,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11612
11840
  const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
11613
11841
  let memoryHitsLeader = null;
11614
11842
  try {
11615
- const usageIdx = await loadUsageIndex23(paths);
11843
+ const usageIdx = await loadUsageIndex25(paths);
11616
11844
  const tops = Object.entries(usageIdx.by_id).map(([id, v]) => ({ id, read_count: v.read_count })).filter((x) => x.read_count > 0).sort((a, b) => b.read_count - a.read_count);
11617
11845
  memoryHitsLeader = tops[0] ?? null;
11618
11846
  } catch {
@@ -11644,7 +11872,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11644
11872
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
11645
11873
  }
11646
11874
  async function renderMemoryHits(paths, opts) {
11647
- const index = await loadUsageIndex23(paths);
11875
+ const index = await loadUsageIndex25(paths);
11648
11876
  const since = parseSince(opts.since ?? "30d");
11649
11877
  const sinceMs = since ? new Date(since).getTime() : null;
11650
11878
  const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
@@ -11692,13 +11920,13 @@ import { performance } from "perf_hooks";
11692
11920
  import "commander";
11693
11921
  import {
11694
11922
  estimateTokens as estimateTokens3,
11695
- findProjectRoot as findProjectRoot38,
11696
- resolveHaivePaths as resolveHaivePaths35
11923
+ findProjectRoot as findProjectRoot39,
11924
+ resolveHaivePaths as resolveHaivePaths36
11697
11925
  } from "@hiveai/core";
11698
11926
  function registerBench(program2) {
11699
11927
  program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11700
- const root = findProjectRoot38(opts.dir);
11701
- const paths = resolveHaivePaths35(root);
11928
+ const root = findProjectRoot39(opts.dir);
11929
+ const paths = resolveHaivePaths36(root);
11702
11930
  const ctx = { paths };
11703
11931
  const task = opts.task ?? "audit dependencies for security risks";
11704
11932
  const scenarios = [
@@ -11817,11 +12045,11 @@ function summarize(name, t0, payload, notes) {
11817
12045
  }
11818
12046
 
11819
12047
  // src/commands/benchmark.ts
11820
- import { existsSync as existsSync59 } from "fs";
12048
+ import { existsSync as existsSync61 } from "fs";
11821
12049
  import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
11822
12050
  import path41 from "path";
11823
12051
  import "commander";
11824
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot39 } from "@hiveai/core";
12052
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot40 } from "@hiveai/core";
11825
12053
  function registerBenchmark(program2) {
11826
12054
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
11827
12055
  benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
@@ -11861,18 +12089,18 @@ function registerBenchmark(program2) {
11861
12089
  function resolveBenchmarkRoot(dir) {
11862
12090
  const candidate = dir ?? "benchmarks/agent-benchmark";
11863
12091
  if (path41.isAbsolute(candidate)) return candidate;
11864
- const projectRoot = findProjectRoot39(process.cwd());
12092
+ const projectRoot = findProjectRoot40(process.cwd());
11865
12093
  return path41.join(projectRoot, candidate);
11866
12094
  }
11867
12095
  async function collectRows(root) {
11868
- if (!existsSync59(root)) throw new Error(`Benchmark directory not found: ${root}`);
12096
+ if (!existsSync61(root)) throw new Error(`Benchmark directory not found: ${root}`);
11869
12097
  const entries = await readdir5(root, { withFileTypes: true });
11870
12098
  const rows = [];
11871
12099
  for (const entry of entries) {
11872
12100
  if (!entry.isDirectory()) continue;
11873
12101
  const fixtureDir = path41.join(root, entry.name);
11874
12102
  const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11875
- if (!existsSync59(reportFile)) continue;
12103
+ if (!existsSync61(reportFile)) continue;
11876
12104
  const report = await readFile19(reportFile, "utf8");
11877
12105
  rows.push(parseAgentReport(entry.name, report));
11878
12106
  }
@@ -11961,22 +12189,179 @@ function escapeRegExp(value) {
11961
12189
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11962
12190
  }
11963
12191
 
11964
- // src/commands/memory-suggest.ts
11965
- import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
11966
- import { existsSync as existsSync60 } from "fs";
12192
+ // src/commands/eval.ts
12193
+ import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
12194
+ import { existsSync as existsSync63 } from "fs";
11967
12195
  import path43 from "path";
11968
12196
  import "commander";
11969
12197
  import {
11970
- aggregateUsage as aggregateUsage2,
11971
- buildFrontmatter as buildFrontmatter11,
11972
- findProjectRoot as findProjectRoot40,
11973
- loadConfig as loadConfig9,
11974
- loadMemoriesFromDir as loadMemoriesFromDir30,
11975
- memoryFilePath as memoryFilePath10,
11976
- parseSince as parseSince2,
11977
- readUsageEvents as readUsageEvents3,
11978
- resolveHaivePaths as resolveHaivePaths36,
11979
- serializeMemory as serializeMemory24
12198
+ aggregateRetrieval,
12199
+ aggregateSensors,
12200
+ buildReport,
12201
+ findProjectRoot as findProjectRoot41,
12202
+ resolveHaivePaths as resolveHaivePaths37,
12203
+ scoreRetrievalCase,
12204
+ scoreSensorCase,
12205
+ synthesizeSelfEvalCases
12206
+ } from "@hiveai/core";
12207
+ function registerEval(program2) {
12208
+ program2.command("eval").description(
12209
+ "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."
12210
+ ).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("-d, --dir <dir>", "project root").action(async (opts) => {
12211
+ const root = findProjectRoot41(opts.dir);
12212
+ const paths = resolveHaivePaths37(root);
12213
+ if (!existsSync63(paths.memoriesDir)) {
12214
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12215
+ process.exitCode = 1;
12216
+ return;
12217
+ }
12218
+ const k = Math.max(1, parseInt(opts.top ?? "8", 10) || 8);
12219
+ const ctx = { paths };
12220
+ const spec = await resolveSpec(opts, paths.memoriesDir);
12221
+ if ((spec.retrieval?.length ?? 0) === 0 && (spec.sensors?.length ?? 0) === 0) {
12222
+ ui.warn("No eval cases (no anchored memories and no --spec). Nothing to score.");
12223
+ return;
12224
+ }
12225
+ let retrievalAgg = null;
12226
+ if (spec.retrieval && spec.retrieval.length > 0) {
12227
+ const results = [];
12228
+ for (const c of spec.retrieval) {
12229
+ const surfaced = await runRetrieval(c, k, ctx);
12230
+ results.push(scoreRetrievalCase(c.name, c.expect_ids, surfaced));
12231
+ }
12232
+ retrievalAgg = aggregateRetrieval(results);
12233
+ }
12234
+ let sensorAgg = null;
12235
+ if (spec.sensors && spec.sensors.length > 0) {
12236
+ const results = [];
12237
+ for (const c of spec.sensors) {
12238
+ const fired = await runSensorCase(c, ctx);
12239
+ results.push(scoreSensorCase(c.name, c.expect_fire_ids, fired));
12240
+ }
12241
+ sensorAgg = aggregateSensors(results);
12242
+ }
12243
+ const report = buildReport(retrievalAgg, sensorAgg);
12244
+ if (opts.json) {
12245
+ console.log(JSON.stringify({ root, k, report }, null, 2));
12246
+ return;
12247
+ }
12248
+ const md = renderMarkdown2(root, k, report);
12249
+ if (opts.out) {
12250
+ const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12251
+ await writeFile29(outFile, md, "utf8");
12252
+ ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12253
+ return;
12254
+ }
12255
+ console.log(md);
12256
+ });
12257
+ }
12258
+ async function resolveSpec(opts, memoriesDir) {
12259
+ if (opts.spec) {
12260
+ const file = path43.resolve(opts.spec);
12261
+ const raw = await readFile20(file, "utf8");
12262
+ return JSON.parse(raw);
12263
+ }
12264
+ const memories = await loadMemoriesFromDir26(memoriesDir);
12265
+ return { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) };
12266
+ }
12267
+ async function runRetrieval(c, k, ctx) {
12268
+ const out = await getBriefing(
12269
+ {
12270
+ task: c.task,
12271
+ files: c.files ?? [],
12272
+ symbols: c.symbols ?? [],
12273
+ max_tokens: 6e3,
12274
+ max_memories: k,
12275
+ include_project_context: false,
12276
+ include_module_contexts: false,
12277
+ semantic: true,
12278
+ include_stale: false,
12279
+ track: false,
12280
+ format: "compact",
12281
+ min_semantic_score: 0
12282
+ },
12283
+ ctx
12284
+ );
12285
+ return out.memories.map((m) => m.id);
12286
+ }
12287
+ async function runSensorCase(c, ctx) {
12288
+ const out = await antiPatternsCheck(
12289
+ { diff: c.diff, paths: c.paths ?? [], limit: 50, semantic: false },
12290
+ ctx
12291
+ );
12292
+ return out.warnings.filter((w) => w.reasons.includes("sensor")).map((w) => w.id);
12293
+ }
12294
+ function pct(n) {
12295
+ return `${Math.round(n * 100)}%`;
12296
+ }
12297
+ function renderMarkdown2(root, k, report) {
12298
+ const lines = [
12299
+ "# hAIve eval report",
12300
+ "",
12301
+ `Project: \`${root}\` \xB7 top-k: ${k}`,
12302
+ "",
12303
+ `## Overall score: ${report.score}/100`,
12304
+ ""
12305
+ ];
12306
+ if (report.retrieval) {
12307
+ const r = report.retrieval;
12308
+ lines.push(
12309
+ "## Retrieval",
12310
+ "",
12311
+ `- cases: ${r.cases.length}`,
12312
+ `- mean recall: ${pct(r.mean_recall)}`,
12313
+ `- mean precision: ${pct(r.mean_precision)}`,
12314
+ `- MRR: ${r.mrr.toFixed(3)}`,
12315
+ ""
12316
+ );
12317
+ const misses = r.cases.filter((c) => c.misses.length > 0);
12318
+ if (misses.length > 0) {
12319
+ lines.push(`### ${misses.length} retrieval miss(es)`, "");
12320
+ for (const c of misses.slice(0, 25)) {
12321
+ lines.push(`- \`${c.name}\` \u2014 expected not in top-${k}`);
12322
+ }
12323
+ lines.push("");
12324
+ }
12325
+ }
12326
+ if (report.sensors) {
12327
+ const s = report.sensors;
12328
+ lines.push("## Sensors", "", `- cases: ${s.cases.length}`, `- catch-rate: ${pct(s.catch_rate)}`, "");
12329
+ const misses = s.cases.filter((c) => c.misses.length > 0);
12330
+ if (misses.length > 0) {
12331
+ lines.push(`### ${misses.length} sensor miss(es)`, "");
12332
+ for (const c of misses.slice(0, 25)) {
12333
+ lines.push(`- \`${c.name}\` \u2014 sensor did not fire (expected: ${c.misses.join(", ")})`);
12334
+ }
12335
+ lines.push("");
12336
+ }
12337
+ }
12338
+ lines.push(
12339
+ "## Reading",
12340
+ "",
12341
+ "Retrieval recall = share of expected memories that surfaced in the briefing top-k.",
12342
+ "MRR rewards ranking the right memory high. Catch-rate = share of known-bad diffs a sensor flagged.",
12343
+ "Run in CI to fail the build on a ranking/sensor regression.",
12344
+ ""
12345
+ );
12346
+ return lines.join("\n");
12347
+ }
12348
+
12349
+ // src/commands/memory-suggest.ts
12350
+ import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
12351
+ import { existsSync as existsSync64 } from "fs";
12352
+ import path44 from "path";
12353
+ import "commander";
12354
+ import {
12355
+ aggregateUsage as aggregateUsage2,
12356
+ buildFrontmatter as buildFrontmatter11,
12357
+ findProjectRoot as findProjectRoot42,
12358
+ loadConfig as loadConfig9,
12359
+ loadMemoriesFromDir as loadMemoriesFromDir31,
12360
+ memoryFilePath as memoryFilePath10,
12361
+ parseSince as parseSince2,
12362
+ readUsageEvents as readUsageEvents3,
12363
+ resolveHaivePaths as resolveHaivePaths38,
12364
+ serializeMemory as serializeMemory24
11980
12365
  } from "@hiveai/core";
11981
12366
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
11982
12367
  "mem_search",
@@ -11992,8 +12377,8 @@ function registerMemorySuggest(memory2) {
11992
12377
  memory2.command("suggest").description(
11993
12378
  "Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to save the top-N suggestions using the project defaults.\n In autopilot, suggestions land as validated team records; in manual mode they stay draft."
11994
12379
  ).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of saved memories (personal | team; default: config default)").option("--auto-save", "save top-N suggestions as memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11995
- const root = findProjectRoot40(opts.dir);
11996
- const paths = resolveHaivePaths36(root);
12380
+ const root = findProjectRoot42(opts.dir);
12381
+ const paths = resolveHaivePaths38(root);
11997
12382
  const events = await readUsageEvents3(paths);
11998
12383
  if (events.length === 0) {
11999
12384
  if (opts.json) {
@@ -12042,7 +12427,7 @@ function registerMemorySuggest(memory2) {
12042
12427
  }
12043
12428
  const created = [];
12044
12429
  const skipped = [];
12045
- const existing = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
12430
+ const existing = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12046
12431
  for (const s of top) {
12047
12432
  const slug = slugify2(s.query);
12048
12433
  if (!slug) {
@@ -12065,13 +12450,13 @@ function registerMemorySuggest(memory2) {
12065
12450
  });
12066
12451
  const body = renderTemplate(s, fm.id, status);
12067
12452
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
12068
- await mkdir18(path43.dirname(file), { recursive: true });
12069
- if (existsSync60(file)) {
12070
- skipped.push({ query: s.query, reason: `file already exists at ${path43.relative(root, file)}` });
12453
+ await mkdir18(path44.dirname(file), { recursive: true });
12454
+ if (existsSync64(file)) {
12455
+ skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
12071
12456
  continue;
12072
12457
  }
12073
- await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
12074
- created.push({ id: fm.id, file: path43.relative(root, file), query: s.query });
12458
+ await writeFile30(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
12459
+ created.push({ id: fm.id, file: path44.relative(root, file), query: s.query });
12075
12460
  }
12076
12461
  if (opts.json) {
12077
12462
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -12169,18 +12554,18 @@ function truncate2(text, max) {
12169
12554
  }
12170
12555
 
12171
12556
  // src/commands/memory-archive.ts
12172
- import { existsSync as existsSync61 } from "fs";
12173
- import { writeFile as writeFile30 } from "fs/promises";
12174
- import path44 from "path";
12557
+ import { existsSync as existsSync65 } from "fs";
12558
+ import { writeFile as writeFile31 } from "fs/promises";
12559
+ import path45 from "path";
12175
12560
  import "commander";
12176
12561
  import {
12177
- findProjectRoot as findProjectRoot41,
12178
- getUsage as getUsage18,
12562
+ findProjectRoot as findProjectRoot43,
12563
+ getUsage as getUsage20,
12179
12564
  retirementSignal as retirementSignal2,
12180
12565
  loadConfig as loadConfig10,
12181
- loadMemoriesFromDir as loadMemoriesFromDir31,
12182
- loadUsageIndex as loadUsageIndex24,
12183
- resolveHaivePaths as resolveHaivePaths37,
12566
+ loadMemoriesFromDir as loadMemoriesFromDir33,
12567
+ loadUsageIndex as loadUsageIndex26,
12568
+ resolveHaivePaths as resolveHaivePaths39,
12184
12569
  serializeMemory as serializeMemory25
12185
12570
  } from "@hiveai/core";
12186
12571
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
@@ -12188,9 +12573,9 @@ function registerMemoryArchive(memory2) {
12188
12573
  memory2.command("archive").description(
12189
12574
  "Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
12190
12575
  ).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) => {
12191
- const root = findProjectRoot41(opts.dir);
12192
- const paths = resolveHaivePaths37(root);
12193
- if (!existsSync61(paths.memoriesDir)) {
12576
+ const root = findProjectRoot43(opts.dir);
12577
+ const paths = resolveHaivePaths39(root);
12578
+ if (!existsSync65(paths.memoriesDir)) {
12194
12579
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12195
12580
  process.exitCode = 1;
12196
12581
  return;
@@ -12204,8 +12589,8 @@ function registerMemoryArchive(memory2) {
12204
12589
  return;
12205
12590
  }
12206
12591
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
12207
- const all = await loadMemoriesFromDir31(paths.memoriesDir);
12208
- const usage = await loadUsageIndex24(paths);
12592
+ const all = await loadMemoriesFromDir33(paths.memoriesDir);
12593
+ const usage = await loadUsageIndex26(paths);
12209
12594
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
12210
12595
  const candidates = [];
12211
12596
  for (const { memory: mem, filePath } of all) {
@@ -12215,10 +12600,10 @@ function registerMemoryArchive(memory2) {
12215
12600
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
12216
12601
  const retired = retirementSignal2(fm, mem.body);
12217
12602
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
12218
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync61(path44.join(paths.root, p)));
12603
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync65(path45.join(paths.root, p)));
12219
12604
  const isAnchorless = !hasAnyAnchor;
12220
12605
  if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
12221
- const u = getUsage18(usage, fm.id);
12606
+ const u = getUsage20(usage, fm.id);
12222
12607
  const lastSeen = u.last_read_at ?? fm.created_at;
12223
12608
  if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
12224
12609
  const reason = retired.retired ? `retired lifecycle signal: ${retired.reason ?? "unknown"}` : isAnchorless ? `anchorless and not read since ${lastSeen.slice(0, 10)}` : allPathsGone ? `all ${fm.anchor.paths.length} anchored path(s) missing and not read since ${lastSeen.slice(0, 10)}` : `not read since ${lastSeen.slice(0, 10)} (unread decay)`;
@@ -12264,7 +12649,7 @@ function registerMemoryArchive(memory2) {
12264
12649
  if (!found) continue;
12265
12650
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
12266
12651
  try {
12267
- await writeFile30(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
12652
+ await writeFile31(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
12268
12653
  archived++;
12269
12654
  } catch (err) {
12270
12655
  if (!opts.json) {
@@ -12290,33 +12675,34 @@ function parseDays(input) {
12290
12675
  }
12291
12676
 
12292
12677
  // src/commands/doctor.ts
12293
- import { existsSync as existsSync63, statSync as statSync2 } from "fs";
12294
- import { readFile as readFile20, stat, writeFile as writeFile31 } from "fs/promises";
12295
- import path45 from "path";
12678
+ import { existsSync as existsSync66, statSync as statSync2 } from "fs";
12679
+ import { readFile as readFile21, stat, writeFile as writeFile33 } from "fs/promises";
12680
+ import path46 from "path";
12296
12681
  import { execFileSync, execSync as execSync3 } from "child_process";
12297
12682
  import "commander";
12298
12683
  import {
12299
12684
  codeMapPath as codeMapPath2,
12300
- findProjectRoot as findProjectRoot42,
12301
- getUsage as getUsage19,
12685
+ findProjectRoot as findProjectRoot44,
12686
+ getUsage as getUsage21,
12687
+ isStackPackSeed as isStackPackSeed4,
12302
12688
  loadCodeMap as loadCodeMap7,
12303
12689
  loadConfig as loadConfig11,
12304
- loadMemoriesFromDir as loadMemoriesFromDir33,
12305
- loadUsageIndex as loadUsageIndex25,
12690
+ loadMemoriesFromDir as loadMemoriesFromDir34,
12691
+ loadUsageIndex as loadUsageIndex27,
12306
12692
  readUsageEvents as readUsageEvents4,
12307
- resolveHaivePaths as resolveHaivePaths38
12693
+ resolveHaivePaths as resolveHaivePaths40
12308
12694
  } from "@hiveai/core";
12309
12695
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
12310
12696
  function registerDoctor(program2) {
12311
12697
  program2.command("doctor").description(
12312
12698
  "Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to apply safe autopilot repairs."
12313
12699
  ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
12314
- const root = findProjectRoot42(opts.dir);
12315
- const paths = resolveHaivePaths38(root);
12700
+ const root = findProjectRoot44(opts.dir);
12701
+ const paths = resolveHaivePaths40(root);
12316
12702
  const findings = [];
12317
12703
  const repairs = [];
12318
12704
  const config = await loadConfig11(paths);
12319
- if (!existsSync63(paths.haiveDir)) {
12705
+ if (!existsSync66(paths.haiveDir)) {
12320
12706
  findings.push({
12321
12707
  severity: "error",
12322
12708
  code: "not-initialized",
@@ -12337,7 +12723,7 @@ function registerDoctor(program2) {
12337
12723
  })
12338
12724
  );
12339
12725
  }
12340
- if (!existsSync63(paths.projectContext)) {
12726
+ if (!existsSync66(paths.projectContext)) {
12341
12727
  findings.push({
12342
12728
  severity: "warn",
12343
12729
  code: "no-project-context",
@@ -12345,8 +12731,8 @@ function registerDoctor(program2) {
12345
12731
  fix: "haive init"
12346
12732
  });
12347
12733
  } else {
12348
- const { readFile: readFile24 } = await import("fs/promises");
12349
- const content = await readFile24(paths.projectContext, "utf8");
12734
+ const { readFile: readFile25 } = await import("fs/promises");
12735
+ const content = await readFile25(paths.projectContext, "utf8");
12350
12736
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
12351
12737
  if (isTemplate) {
12352
12738
  findings.push({
@@ -12366,7 +12752,7 @@ function registerDoctor(program2) {
12366
12752
  });
12367
12753
  }
12368
12754
  }
12369
- const memories = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
12755
+ const memories = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12370
12756
  const now = Date.now();
12371
12757
  if (memories.length === 0) {
12372
12758
  findings.push({
@@ -12375,7 +12761,7 @@ function registerDoctor(program2) {
12375
12761
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
12376
12762
  });
12377
12763
  } else {
12378
- const usage = await loadUsageIndex25(paths);
12764
+ const usage = await loadUsageIndex27(paths);
12379
12765
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
12380
12766
  if (stale.length > 0) {
12381
12767
  findings.push({
@@ -12410,20 +12796,29 @@ function registerDoctor(program2) {
12410
12796
  fix: "haive memory approve <id> # activate\nhaive memory rm <id> # or delete if obsolete"
12411
12797
  });
12412
12798
  }
12413
- const anchorless = memories.filter(
12799
+ const policyMemories = memories.filter((m) => !isStackPackSeed4(m.memory.frontmatter));
12800
+ const anchorless = policyMemories.filter(
12414
12801
  (m) => m.memory.frontmatter.anchor.paths.length === 0 && m.memory.frontmatter.anchor.symbols.length === 0 && m.memory.frontmatter.type !== "session_recap" && m.memory.frontmatter.type !== "glossary" && m.memory.frontmatter.type !== "skill"
12415
12802
  );
12416
- if (anchorless.length / Math.max(memories.length, 1) > 0.3) {
12803
+ const stackSeeds = memories.filter((m) => isStackPackSeed4(m.memory.frontmatter));
12804
+ if (anchorless.length / Math.max(policyMemories.length, 1) > 0.3) {
12417
12805
  findings.push({
12418
12806
  severity: "warn",
12419
12807
  code: "anchorless-majority",
12420
- message: `${anchorless.length}/${memories.length} memories have no anchor path/symbol \u2014 staleness undetectable.`,
12808
+ message: `${anchorless.length}/${policyMemories.length} repo-specific memories have no anchor path/symbol \u2014 staleness undetectable.`,
12421
12809
  fix: "Add `paths:` + `symbols:` to mem_save calls to enable haive memory verify."
12422
12810
  });
12811
+ } else if (stackSeeds.length > 0 && policyMemories.length === 0) {
12812
+ findings.push({
12813
+ severity: "info",
12814
+ code: "stack-pack-seeds",
12815
+ message: `${stackSeeds.length} starter stack memor${stackSeeds.length === 1 ? "y is" : "ies are"} present as generic background guidance.`,
12816
+ fix: "Replace or anchor stack-pack seeds when they become repo-specific policy."
12817
+ });
12423
12818
  }
12424
12819
  const decayCandidates = memories.filter((m) => {
12425
12820
  if (m.memory.frontmatter.status !== "validated") return false;
12426
- const u = getUsage19(usage, m.memory.frontmatter.id);
12821
+ const u = getUsage21(usage, m.memory.frontmatter.id);
12427
12822
  const last = u.last_read_at ?? m.memory.frontmatter.created_at;
12428
12823
  return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
12429
12824
  });
@@ -12507,12 +12902,12 @@ function registerDoctor(program2) {
12507
12902
  }
12508
12903
  }
12509
12904
  if (config.enforcement?.requireBriefingFirst) {
12510
- const claudeSettings = path45.join(root, ".claude", "settings.local.json");
12905
+ const claudeSettings = path46.join(root, ".claude", "settings.local.json");
12511
12906
  let hasClaudeEnforcement = false;
12512
- if (existsSync63(claudeSettings)) {
12907
+ if (existsSync66(claudeSettings)) {
12513
12908
  try {
12514
- const { readFile: readFile24 } = await import("fs/promises");
12515
- const raw = await readFile24(claudeSettings, "utf8");
12909
+ const { readFile: readFile25 } = await import("fs/promises");
12910
+ const raw = await readFile25(claudeSettings, "utf8");
12516
12911
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
12517
12912
  } catch {
12518
12913
  hasClaudeEnforcement = false;
@@ -12535,14 +12930,14 @@ function registerDoctor(program2) {
12535
12930
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12536
12931
  });
12537
12932
  }
12538
- findings.push(...await collectInstallFindings(root, "0.10.8"));
12933
+ findings.push(...await collectInstallFindings(root, "0.11.0"));
12539
12934
  try {
12540
12935
  const legacyRaw = execSync3("haive-mcp --version", {
12541
12936
  encoding: "utf8",
12542
12937
  timeout: 3e3,
12543
12938
  stdio: ["ignore", "pipe", "ignore"]
12544
12939
  }).trim();
12545
- const cliVersion = "0.10.8";
12940
+ const cliVersion = "0.11.0";
12546
12941
  if (legacyRaw && legacyRaw !== cliVersion) {
12547
12942
  findings.push({
12548
12943
  severity: "warn",
@@ -12558,20 +12953,20 @@ npm uninstall -g @hiveai/mcp`
12558
12953
  }
12559
12954
  {
12560
12955
  const configPaths = [
12561
- path45.join(root, ".mcp.json"),
12562
- path45.join(root, ".cursor", "mcp.json"),
12563
- path45.join(root, ".vscode", "mcp.json")
12956
+ path46.join(root, ".mcp.json"),
12957
+ path46.join(root, ".cursor", "mcp.json"),
12958
+ path46.join(root, ".vscode", "mcp.json")
12564
12959
  ];
12565
12960
  const staleConfigs = [];
12566
12961
  for (const cfgPath of configPaths) {
12567
- if (!existsSync63(cfgPath)) continue;
12962
+ if (!existsSync66(cfgPath)) continue;
12568
12963
  try {
12569
- const raw = await readFile20(cfgPath, "utf8");
12964
+ const raw = await readFile21(cfgPath, "utf8");
12570
12965
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
12571
- staleConfigs.push(path45.relative(root, cfgPath));
12966
+ staleConfigs.push(path46.relative(root, cfgPath));
12572
12967
  if (opts.fix && !opts.dryRun) {
12573
12968
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
12574
- await writeFile31(cfgPath, updated, "utf8");
12969
+ await writeFile33(cfgPath, updated, "utf8");
12575
12970
  }
12576
12971
  }
12577
12972
  } catch {
@@ -12742,23 +13137,23 @@ async function collectHarnessCoverageFindings(codeMap, memories) {
12742
13137
  }
12743
13138
  }
12744
13139
  const covered = coveredFiles.size;
12745
- const pct = Math.round(covered / total * 100);
13140
+ const pct2 = Math.round(covered / total * 100);
12746
13141
  const uncovered = codeFiles.filter((f) => !coveredFiles.has(f)).sort((a, b) => {
12747
13142
  const depthA = a.split("/").length;
12748
13143
  const depthB = b.split("/").length;
12749
13144
  if (depthA !== depthB) return depthA - depthB;
12750
13145
  return a.localeCompare(b);
12751
13146
  }).slice(0, 5);
12752
- const coverageDesc = pct < 10 && total > 10 ? "Low coverage \u2014 add memory anchors on key modules to improve harness enforcement." : pct < 50 ? "Partial coverage \u2014 useful but not yet broad enough to call the harness mature." : pct < 80 ? "Good coverage \u2014 critical modules are increasingly protected." : "Good harness coverage.";
13147
+ const coverageDesc = pct2 < 10 && total > 10 ? "Low coverage \u2014 add memory anchors on key modules to improve harness enforcement." : pct2 < 50 ? "Partial coverage \u2014 useful but not yet broad enough to call the harness mature." : pct2 < 80 ? "Good coverage \u2014 critical modules are increasingly protected." : "Good harness coverage.";
12753
13148
  const uncoveredHint = uncovered.length > 0 ? `
12754
13149
  Top uncovered: ${uncovered.map((f) => `\`${f}\``).join(", ")}` : "";
12755
13150
  const findings = [];
12756
13151
  findings.push({
12757
13152
  severity: "info",
12758
13153
  code: "harness-coverage",
12759
- coverage_percent: pct,
12760
- message: `${covered}/${total} code-map files have validated memory anchors (${pct}%). ` + coverageDesc + uncoveredHint,
12761
- fix: pct < 50 && total > 10 ? `haive memory add --type gotcha|convention|architecture --paths <key-file> --scope team` : void 0,
13154
+ coverage_percent: pct2,
13155
+ message: `${covered}/${total} code-map files have validated memory anchors (${pct2}%). ` + coverageDesc + uncoveredHint,
13156
+ fix: pct2 < 50 && total > 10 ? `haive memory add --type gotcha|convention|architecture --paths <key-file> --scope team` : void 0,
12762
13157
  section: "Harness coverage"
12763
13158
  });
12764
13159
  return findings;
@@ -12858,9 +13253,9 @@ which -a haive`
12858
13253
  ".vscode/mcp.json"
12859
13254
  ];
12860
13255
  for (const rel of integrationFiles) {
12861
- const file = path45.join(root, rel);
12862
- if (!existsSync63(file)) continue;
12863
- const text = await readFile20(file, "utf8").catch(() => "");
13256
+ const file = path46.join(root, rel);
13257
+ if (!existsSync66(file)) continue;
13258
+ const text = await readFile21(file, "utf8").catch(() => "");
12864
13259
  for (const bin of extractAbsoluteHaiveBins(text)) {
12865
13260
  const version = versionForBinary(bin);
12866
13261
  if (!version) {
@@ -12884,7 +13279,7 @@ which -a haive`
12884
13279
  }
12885
13280
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
12886
13281
  const findings = [];
12887
- const rootPkg = await readJson(path45.join(root, "package.json"));
13282
+ const rootPkg = await readJson(path46.join(root, "package.json"));
12888
13283
  const workspacePackages = [
12889
13284
  "packages/core/package.json",
12890
13285
  "packages/embeddings/package.json",
@@ -12893,7 +13288,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
12893
13288
  ];
12894
13289
  const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
12895
13290
  rel,
12896
- pkg: await readJson(path45.join(root, rel))
13291
+ pkg: await readJson(path46.join(root, rel))
12897
13292
  })))).filter((item) => item.pkg);
12898
13293
  const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
12899
13294
  if (!isHaiveWorkspace) return findings;
@@ -12955,9 +13350,9 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
12955
13350
  }
12956
13351
  }
12957
13352
  async function readJson(file) {
12958
- if (!existsSync63(file)) return null;
13353
+ if (!existsSync66(file)) return null;
12959
13354
  try {
12960
- return JSON.parse(await readFile20(file, "utf8"));
13355
+ return JSON.parse(await readFile21(file, "utf8"));
12961
13356
  } catch {
12962
13357
  return null;
12963
13358
  }
@@ -13002,22 +13397,22 @@ function extractAbsoluteHaiveBins(text) {
13002
13397
  }
13003
13398
 
13004
13399
  // src/commands/playback.ts
13005
- import { existsSync as existsSync64 } from "fs";
13400
+ import { existsSync as existsSync67 } from "fs";
13006
13401
  import "commander";
13007
13402
  import {
13008
- findProjectRoot as findProjectRoot43,
13009
- loadMemoriesFromDir as loadMemoriesFromDir34,
13403
+ findProjectRoot as findProjectRoot45,
13404
+ loadMemoriesFromDir as loadMemoriesFromDir35,
13010
13405
  parseSince as parseSince3,
13011
13406
  readUsageEvents as readUsageEvents5,
13012
- resolveHaivePaths as resolveHaivePaths39
13407
+ resolveHaivePaths as resolveHaivePaths41
13013
13408
  } from "@hiveai/core";
13014
13409
  var MS_PER_MINUTE = 6e4;
13015
13410
  function registerPlayback(program2) {
13016
13411
  program2.command("playback").description(
13017
13412
  "Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
13018
13413
  ).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
13019
- const root = findProjectRoot43(opts.dir);
13020
- const paths = resolveHaivePaths39(root);
13414
+ const root = findProjectRoot45(opts.dir);
13415
+ const paths = resolveHaivePaths41(root);
13021
13416
  const events = await readUsageEvents5(paths);
13022
13417
  if (events.length === 0) {
13023
13418
  if (opts.json) {
@@ -13032,7 +13427,7 @@ function registerPlayback(program2) {
13032
13427
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
13033
13428
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
13034
13429
  const sessions = bucketSessions(filtered, gapMs);
13035
- const all = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
13430
+ const all = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13036
13431
  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);
13037
13432
  const enriched = sessions.map((s, i) => {
13038
13433
  const startMs = Date.parse(s.start);
@@ -13123,9 +13518,9 @@ import { spawn as spawn5 } from "child_process";
13123
13518
  import "commander";
13124
13519
  import {
13125
13520
  antiPatternGateParams,
13126
- findProjectRoot as findProjectRoot44,
13521
+ findProjectRoot as findProjectRoot46,
13127
13522
  loadConfig as loadConfig12,
13128
- resolveHaivePaths as resolveHaivePaths40
13523
+ resolveHaivePaths as resolveHaivePaths42
13129
13524
  } from "@hiveai/core";
13130
13525
  function registerPrecommit(program2) {
13131
13526
  program2.command("precommit").description(
@@ -13137,8 +13532,8 @@ function registerPrecommit(program2) {
13137
13532
  "--no-anchored-blocks",
13138
13533
  "do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
13139
13534
  ).option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
13140
- const root = findProjectRoot44(opts.dir);
13141
- const paths = resolveHaivePaths40(root);
13535
+ const root = findProjectRoot46(opts.dir);
13536
+ const paths = resolveHaivePaths42(root);
13142
13537
  const ctx = { paths };
13143
13538
  const config = await loadConfig12(paths);
13144
13539
  const gate = config.enforcement?.antiPatternGate ?? "anchored";
@@ -13274,12 +13669,12 @@ function runCommand3(cmd, args, cwd) {
13274
13669
  }
13275
13670
 
13276
13671
  // src/commands/welcome.ts
13277
- import { existsSync as existsSync65 } from "fs";
13672
+ import { existsSync as existsSync68 } from "fs";
13278
13673
  import "commander";
13279
13674
  import {
13280
- findProjectRoot as findProjectRoot45,
13281
- loadMemoriesFromDir as loadMemoriesFromDir35,
13282
- resolveHaivePaths as resolveHaivePaths41
13675
+ findProjectRoot as findProjectRoot47,
13676
+ loadMemoriesFromDir as loadMemoriesFromDir36,
13677
+ resolveHaivePaths as resolveHaivePaths43
13283
13678
  } from "@hiveai/core";
13284
13679
  var TYPE_RANK = {
13285
13680
  skill: 0,
@@ -13294,14 +13689,14 @@ function registerWelcome(program2) {
13294
13689
  program2.command("welcome").description(
13295
13690
  "Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
13296
13691
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
13297
- const root = findProjectRoot45(opts.dir);
13298
- const paths = resolveHaivePaths41(root);
13299
- if (!existsSync65(paths.memoriesDir)) {
13692
+ const root = findProjectRoot47(opts.dir);
13693
+ const paths = resolveHaivePaths43(root);
13694
+ if (!existsSync68(paths.memoriesDir)) {
13300
13695
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
13301
13696
  process.exitCode = 1;
13302
13697
  return;
13303
13698
  }
13304
- const all = await loadMemoriesFromDir35(paths.memoriesDir);
13699
+ const all = await loadMemoriesFromDir36(paths.memoriesDir);
13305
13700
  const team = all.filter(
13306
13701
  ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
13307
13702
  );
@@ -13355,27 +13750,27 @@ function registerMemorySuggestTopic(memory2) {
13355
13750
  }
13356
13751
 
13357
13752
  // src/commands/resolve-project.ts
13358
- import path46 from "path";
13753
+ import path47 from "path";
13359
13754
  import "commander";
13360
13755
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
13361
13756
  function registerResolveProject(program2) {
13362
13757
  program2.command("resolve-project").description(
13363
13758
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
13364
13759
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
13365
- const info = resolveProjectInfo2({ cwd: path46.resolve(opts.dir) });
13760
+ const info = resolveProjectInfo2({ cwd: path47.resolve(opts.dir) });
13366
13761
  console.log(JSON.stringify({ ok: true, info }, null, 2));
13367
13762
  });
13368
13763
  }
13369
13764
 
13370
13765
  // src/commands/runtime-journal.ts
13371
- import { existsSync as existsSync66 } from "fs";
13372
- import path47 from "path";
13766
+ import { existsSync as existsSync69 } from "fs";
13767
+ import path48 from "path";
13373
13768
  import "commander";
13374
13769
  import {
13375
13770
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
13376
- findProjectRoot as findProjectRoot46,
13771
+ findProjectRoot as findProjectRoot48,
13377
13772
  readRuntimeJournalTail as readRuntimeJournalTail2,
13378
- resolveHaivePaths as resolveHaivePaths42
13773
+ resolveHaivePaths as resolveHaivePaths44
13379
13774
  } from "@hiveai/core";
13380
13775
  function registerRuntime(program2) {
13381
13776
  const runtime = program2.command("runtime").description(
@@ -13383,18 +13778,18 @@ function registerRuntime(program2) {
13383
13778
  );
13384
13779
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
13385
13780
  journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
13386
- const root = path47.resolve(opts.dir ?? process.cwd());
13387
- const paths = resolveHaivePaths42(findProjectRoot46(root));
13781
+ const root = path48.resolve(opts.dir ?? process.cwd());
13782
+ const paths = resolveHaivePaths44(findProjectRoot48(root));
13388
13783
  const raw = opts.kind ?? "note";
13389
13784
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
13390
13785
  await appendRuntimeJournalEntry3(paths, { kind, message });
13391
- ui.success(`Appended to ${path47.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
13786
+ ui.success(`Appended to ${path48.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
13392
13787
  });
13393
13788
  journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
13394
- const root = path47.resolve(opts.dir ?? process.cwd());
13395
- const paths = resolveHaivePaths42(findProjectRoot46(root));
13789
+ const root = path48.resolve(opts.dir ?? process.cwd());
13790
+ const paths = resolveHaivePaths44(findProjectRoot48(root));
13396
13791
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
13397
- if (!existsSync66(paths.haiveDir)) {
13792
+ if (!existsSync69(paths.haiveDir)) {
13398
13793
  ui.error("No .ai/ \u2014 run `haive init` first.");
13399
13794
  process.exitCode = 1;
13400
13795
  return;
@@ -13409,13 +13804,13 @@ function registerRuntime(program2) {
13409
13804
  }
13410
13805
 
13411
13806
  // src/commands/memory-timeline.ts
13412
- import { existsSync as existsSync67 } from "fs";
13413
- import path48 from "path";
13807
+ import { existsSync as existsSync70 } from "fs";
13808
+ import path49 from "path";
13414
13809
  import "commander";
13415
13810
  import {
13416
13811
  collectTimelineEntries as collectTimelineEntries2,
13417
- findProjectRoot as findProjectRoot47,
13418
- resolveHaivePaths as resolveHaivePaths43
13812
+ findProjectRoot as findProjectRoot49,
13813
+ resolveHaivePaths as resolveHaivePaths45
13419
13814
  } from "@hiveai/core";
13420
13815
  function registerMemoryTimeline(memory2) {
13421
13816
  memory2.command("timeline").description(
@@ -13426,15 +13821,15 @@ function registerMemoryTimeline(memory2) {
13426
13821
  process.exitCode = 1;
13427
13822
  return;
13428
13823
  }
13429
- const root = path48.resolve(opts.dir ?? process.cwd());
13430
- const paths = resolveHaivePaths43(findProjectRoot47(root));
13431
- if (!existsSync67(paths.memoriesDir)) {
13824
+ const root = path49.resolve(opts.dir ?? process.cwd());
13825
+ const paths = resolveHaivePaths45(findProjectRoot49(root));
13826
+ if (!existsSync70(paths.memoriesDir)) {
13432
13827
  ui.error("No memories \u2014 run `haive init`.");
13433
13828
  process.exitCode = 1;
13434
13829
  return;
13435
13830
  }
13436
13831
  const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
13437
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
13832
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
13438
13833
  const { entries, notice } = collectTimelineEntries2(all, {
13439
13834
  memoryId: opts.id,
13440
13835
  topic: opts.topic,
@@ -13446,14 +13841,14 @@ function registerMemoryTimeline(memory2) {
13446
13841
  }
13447
13842
 
13448
13843
  // src/commands/memory-conflict-candidates.ts
13449
- import { existsSync as existsSync68 } from "fs";
13450
- import path49 from "path";
13844
+ import { existsSync as existsSync71 } from "fs";
13845
+ import path50 from "path";
13451
13846
  import "commander";
13452
13847
  import {
13453
13848
  findLexicalConflictPairs as findLexicalConflictPairs2,
13454
13849
  findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
13455
- findProjectRoot as findProjectRoot48,
13456
- resolveHaivePaths as resolveHaivePaths44
13850
+ findProjectRoot as findProjectRoot50,
13851
+ resolveHaivePaths as resolveHaivePaths46
13457
13852
  } from "@hiveai/core";
13458
13853
  function parseTypes(csv) {
13459
13854
  const allowed = ["decision", "architecture", "convention", "gotcha"];
@@ -13469,9 +13864,9 @@ function registerMemoryConflictCandidates(memory2) {
13469
13864
  "decision,architecture,convention,gotcha (lexical scan)",
13470
13865
  "decision,architecture"
13471
13866
  ).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) => {
13472
- const root = path49.resolve(opts.dir ?? process.cwd());
13473
- const paths = resolveHaivePaths44(findProjectRoot48(root));
13474
- if (!existsSync68(paths.memoriesDir)) {
13867
+ const root = path50.resolve(opts.dir ?? process.cwd());
13868
+ const paths = resolveHaivePaths46(findProjectRoot50(root));
13869
+ if (!existsSync71(paths.memoriesDir)) {
13475
13870
  ui.error("No memories \u2014 run `haive init`.");
13476
13871
  process.exitCode = 1;
13477
13872
  return;
@@ -13481,7 +13876,7 @@ function registerMemoryConflictCandidates(memory2) {
13481
13876
  const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
13482
13877
  const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
13483
13878
  const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
13484
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
13879
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
13485
13880
  const lexical = findLexicalConflictPairs2(all, {
13486
13881
  sinceDays,
13487
13882
  types: parseTypes(opts.types),
@@ -13507,21 +13902,21 @@ function registerMemoryConflictCandidates(memory2) {
13507
13902
 
13508
13903
  // src/commands/enforce.ts
13509
13904
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
13510
- import { existsSync as existsSync69, statSync as statSync3 } from "fs";
13511
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile21, readdir as readdir6, rm as rm3, writeFile as writeFile33 } from "fs/promises";
13512
- import path50 from "path";
13905
+ import { existsSync as existsSync73, statSync as statSync3 } from "fs";
13906
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile34 } from "fs/promises";
13907
+ import path51 from "path";
13513
13908
  import "commander";
13514
13909
  import {
13515
13910
  antiPatternGateParams as antiPatternGateParams2,
13516
- findProjectRoot as findProjectRoot49,
13911
+ findProjectRoot as findProjectRoot51,
13517
13912
  hasRecentBriefingMarker as hasRecentBriefingMarker2,
13518
13913
  isFreshIsoDate,
13519
13914
  loadConfig as loadConfig13,
13520
- loadMemoriesFromDir as loadMemoriesFromDir36,
13915
+ loadMemoriesFromDir as loadMemoriesFromDir37,
13521
13916
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
13522
13917
  readRecentBriefingMarker,
13523
13918
  resolveBriefingBudget as resolveBriefingBudget3,
13524
- resolveHaivePaths as resolveHaivePaths45,
13919
+ resolveHaivePaths as resolveHaivePaths47,
13525
13920
  saveConfig as saveConfig4,
13526
13921
  SESSION_RECAP_TTL_MS,
13527
13922
  verifyAnchor as verifyAnchor4,
@@ -13534,8 +13929,8 @@ function registerEnforce(program2) {
13534
13929
  "Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
13535
13930
  );
13536
13931
  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) => {
13537
- const root = findProjectRoot49(opts.dir);
13538
- const paths = resolveHaivePaths45(root);
13932
+ const root = findProjectRoot51(opts.dir);
13933
+ const paths = resolveHaivePaths47(root);
13539
13934
  await mkdir19(paths.haiveDir, { recursive: true });
13540
13935
  const current = await loadConfig13(paths);
13541
13936
  await saveConfig4(paths, {
@@ -13560,7 +13955,7 @@ function registerEnforce(program2) {
13560
13955
  if (opts.claude !== false) {
13561
13956
  try {
13562
13957
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
13563
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path50.relative(root, result.settingsPath)})`);
13958
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path51.relative(root, result.settingsPath)})`);
13564
13959
  } catch (err) {
13565
13960
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
13566
13961
  }
@@ -13579,21 +13974,21 @@ function registerEnforce(program2) {
13579
13974
  if (report.should_block) process.exit(2);
13580
13975
  });
13581
13976
  enforce.command("cleanup").description("Remove generated hAIve runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
13582
- const root = findProjectRoot49(opts.dir);
13583
- const paths = resolveHaivePaths45(root);
13584
- const cacheDir = path50.join(paths.haiveDir, ".cache");
13585
- if (existsSync69(cacheDir)) {
13586
- if (opts.dryRun) ui.info(`would clean ${path50.relative(root, cacheDir)} (preserving .gitignore)`);
13977
+ const root = findProjectRoot51(opts.dir);
13978
+ const paths = resolveHaivePaths47(root);
13979
+ const cacheDir = path51.join(paths.haiveDir, ".cache");
13980
+ if (existsSync73(cacheDir)) {
13981
+ if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
13587
13982
  else {
13588
13983
  const removed = await cleanupCacheDir(cacheDir);
13589
- ui.success(`cleaned ${path50.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13984
+ ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13590
13985
  }
13591
13986
  }
13592
- if (existsSync69(paths.runtimeDir)) {
13593
- if (opts.dryRun) ui.info(`would clean ${path50.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
13987
+ if (existsSync73(paths.runtimeDir)) {
13988
+ if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
13594
13989
  else {
13595
13990
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
13596
- ui.success(`cleaned ${path50.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13991
+ ui.success(`cleaned ${path51.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13597
13992
  }
13598
13993
  }
13599
13994
  });
@@ -13602,12 +13997,19 @@ function registerEnforce(program2) {
13602
13997
  printReport(report, Boolean(opts.json), Boolean(opts.explain));
13603
13998
  if (report.should_block) process.exit(2);
13604
13999
  });
14000
+ enforce.command("finish").alias("completion").description(
14001
+ "Final agent-exit gate: verify the git sync/release protocol before reporting a task done."
14002
+ ).option("-d, --dir <dir>", "project root").option("--explain", "group findings by blocking/review/info and show repair commands", false).option("--json", "emit JSON", false).action(async (opts) => {
14003
+ const report = await buildFinishReport(opts.dir);
14004
+ printReport(report, Boolean(opts.json), Boolean(opts.explain));
14005
+ if (report.should_block) process.exit(2);
14006
+ });
13605
14007
  enforce.command("session-start").description("Claude Code SessionStart hook: inject briefing and write a local briefing marker.").option("-d, --dir <dir>", "project root").option("--task <text>", "task text to rank memories").option("--source <name>", "marker source", "claude-session-start").option("--session-id <id>", "agent session id").action(async (opts) => {
13606
14008
  const payload = await readHookPayload();
13607
14009
  const root = resolveRoot(opts.dir, payload);
13608
14010
  if (!root) return;
13609
- const paths = resolveHaivePaths45(root);
13610
- if (!existsSync69(paths.haiveDir)) return;
14011
+ const paths = resolveHaivePaths47(root);
14012
+ if (!existsSync73(paths.haiveDir)) return;
13611
14013
  await mkdir19(paths.runtimeDir, { recursive: true });
13612
14014
  const sessionId = opts.sessionId ?? payload.session_id;
13613
14015
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -13669,8 +14071,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
13669
14071
  const payload = await readHookPayload();
13670
14072
  const root = resolveRoot(opts.dir, payload);
13671
14073
  if (!root) return;
13672
- const paths = resolveHaivePaths45(root);
13673
- if (!existsSync69(paths.haiveDir)) return;
14074
+ const paths = resolveHaivePaths47(root);
14075
+ if (!existsSync73(paths.haiveDir)) return;
13674
14076
  if (!isWriteLikeTool(payload)) return;
13675
14077
  const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
13676
14078
  if (ok) {
@@ -13711,10 +14113,206 @@ ${briefing.project_context.content.slice(0, 1800)}`);
13711
14113
  process.exit(2);
13712
14114
  });
13713
14115
  }
14116
+ async function buildFinishReport(dir) {
14117
+ const root = findProjectRoot51(dir);
14118
+ const paths = resolveHaivePaths47(root);
14119
+ const initialized = existsSync73(paths.haiveDir);
14120
+ const config = initialized ? await loadConfig13(paths) : {};
14121
+ const mode = config.enforcement?.mode ?? "strict";
14122
+ const findings = [];
14123
+ if (!initialized) {
14124
+ return withCategories({
14125
+ root,
14126
+ initialized,
14127
+ mode,
14128
+ score: buildScore([], config.enforcement?.scoreThreshold),
14129
+ should_block: true,
14130
+ findings: [{
14131
+ severity: "error",
14132
+ code: "not-initialized",
14133
+ message: "This repository is not initialized with hAIve.",
14134
+ fix: "Run `haive init` or `haive enforce install`.",
14135
+ impact: 100
14136
+ }]
14137
+ });
14138
+ }
14139
+ const status = await getGitSyncStatus(root);
14140
+ if (!status.available) {
14141
+ findings.push({
14142
+ severity: "error",
14143
+ code: "git-unavailable",
14144
+ message: "Git status could not be inspected, so hAIve cannot verify the exit protocol.",
14145
+ fix: "Run `git status` manually, then commit/push according to the hAIve git-sync protocol.",
14146
+ impact: 100
14147
+ });
14148
+ return finishReport(root, initialized, mode, findings, config);
14149
+ }
14150
+ const shippableDirty = status.dirtyFiles.filter(isShippablePath);
14151
+ if (status.dirtyFiles.length > 0) {
14152
+ findings.push({
14153
+ severity: "error",
14154
+ code: shippableDirty.length > 0 ? "git-sync-uncommitted-shippable" : "git-sync-uncommitted-changes",
14155
+ message: shippableDirty.length > 0 ? `${shippableDirty.length} shippable file(s) are modified but not committed.` : `${status.dirtyFiles.length} file(s) are modified but not committed.`,
14156
+ fix: shippableDirty.length > 0 ? "Bump the lockstep package version if needed, then `git add`, `git commit`, `git tag vX.Y.Z`, `git push && git push --tags`." : "Commit and push these changes before reporting the task done.",
14157
+ reason: "The multi-agent git-sync decision requires agents to leave completed work committed and pushed, not as a local diff.",
14158
+ affected_files: status.dirtyFiles.slice(0, 12),
14159
+ impact: 100
14160
+ });
14161
+ return finishReport(root, initialized, mode, findings, config);
14162
+ }
14163
+ findings.push({
14164
+ severity: "ok",
14165
+ code: "git-worktree-clean",
14166
+ message: "No uncommitted worktree changes remain."
14167
+ });
14168
+ if (!status.upstream) {
14169
+ findings.push({
14170
+ severity: "warn",
14171
+ code: "git-sync-no-upstream",
14172
+ message: "This branch has no upstream, so hAIve cannot verify that commits/tags were pushed.",
14173
+ fix: "Set an upstream with `git push -u origin <branch>`.",
14174
+ impact: 15
14175
+ });
14176
+ return finishReport(root, initialized, mode, findings, config);
14177
+ }
14178
+ if (status.behind > 0) {
14179
+ findings.push({
14180
+ severity: "error",
14181
+ code: "git-sync-behind-upstream",
14182
+ message: `This branch is ${status.behind} commit(s) behind ${status.upstream}.`,
14183
+ fix: "Run `git pull --ff-only` and resolve any conflicts before finishing.",
14184
+ impact: 40
14185
+ });
14186
+ }
14187
+ if (status.ahead > 0) {
14188
+ findings.push({
14189
+ severity: "error",
14190
+ code: "git-sync-unpushed-commits",
14191
+ message: `This branch is ${status.ahead} commit(s) ahead of ${status.upstream}.`,
14192
+ fix: "Run `git push` before reporting the task done.",
14193
+ reason: "The multi-agent git-sync decision requires agents to push completed commits.",
14194
+ impact: 60
14195
+ });
14196
+ } else {
14197
+ findings.push({
14198
+ severity: "ok",
14199
+ code: "git-sync-pushed",
14200
+ message: `Branch is not ahead of ${status.upstream}.`
14201
+ });
14202
+ }
14203
+ const releaseChangedFiles = status.releaseChangedFiles ?? status.changedSinceUpstream;
14204
+ const releaseBaseRef = status.releaseBaseRef ?? status.upstream;
14205
+ const shippableChanged = releaseChangedFiles.filter(isShippablePath);
14206
+ if (shippableChanged.length === 0) {
14207
+ findings.push({
14208
+ severity: "ok",
14209
+ code: "release-version-not-required",
14210
+ message: "No shippable package code changed since upstream; no version/tag required."
14211
+ });
14212
+ return finishReport(root, initialized, mode, findings, config);
14213
+ }
14214
+ findings.push({
14215
+ severity: "info",
14216
+ code: "release-shippable-changes",
14217
+ message: `${shippableChanged.length} shippable file(s) changed since ${releaseBaseRef}.`,
14218
+ affected_files: shippableChanged.slice(0, 12)
14219
+ });
14220
+ const versionState = await inspectReleaseVersionState(root, releaseBaseRef);
14221
+ if (!versionState.lockstep) {
14222
+ findings.push({
14223
+ severity: "error",
14224
+ code: "release-version-not-lockstep",
14225
+ message: `Publishable package versions are not in lockstep: ${versionState.localVersionsLabel}.`,
14226
+ fix: "Set root, core, cli, mcp, and embeddings package.json versions to the same X.Y.Z.",
14227
+ impact: 60
14228
+ });
14229
+ return finishReport(root, initialized, mode, findings, config);
14230
+ }
14231
+ const version = versionState.version;
14232
+ if (!version) {
14233
+ findings.push({
14234
+ severity: "error",
14235
+ code: "release-version-unreadable",
14236
+ message: "Could not read the lockstep package version.",
14237
+ fix: "Verify package.json files are valid JSON.",
14238
+ impact: 60
14239
+ });
14240
+ return finishReport(root, initialized, mode, findings, config);
14241
+ }
14242
+ if (versionState.baseVersion && compareSemver(version, versionState.baseVersion) <= 0) {
14243
+ findings.push({
14244
+ severity: "error",
14245
+ code: "release-version-missing",
14246
+ message: `Shippable code changed, but version stayed at ${version} (base: ${versionState.baseVersion}).`,
14247
+ fix: "Bump the lockstep package version (patch by default), commit the bump, tag it, then push code and tags.",
14248
+ impact: 70
14249
+ });
14250
+ } else {
14251
+ findings.push({
14252
+ severity: "ok",
14253
+ code: "release-version-bumped",
14254
+ message: versionState.baseVersion ? `Lockstep version bumped from ${versionState.baseVersion} to ${version}.` : `Lockstep version is ${version}.`
14255
+ });
14256
+ }
14257
+ const tag = `v${version}`;
14258
+ const localTagAtHead = await tagPointsAtHead(root, tag);
14259
+ if (!localTagAtHead) {
14260
+ findings.push({
14261
+ severity: "error",
14262
+ code: "release-tag-missing",
14263
+ message: `Expected git tag ${tag} to point at HEAD.`,
14264
+ fix: `Run \`git tag ${tag}\` after committing the version bump.`,
14265
+ impact: 50
14266
+ });
14267
+ } else {
14268
+ findings.push({
14269
+ severity: "ok",
14270
+ code: "release-tag-present",
14271
+ message: `Tag ${tag} points at HEAD.`
14272
+ });
14273
+ }
14274
+ const remoteTag = await remoteTagExists(root, tag);
14275
+ if (remoteTag === false) {
14276
+ findings.push({
14277
+ severity: "error",
14278
+ code: "release-tag-unpushed",
14279
+ message: `Tag ${tag} is not present on the remote.`,
14280
+ fix: "Run `git push --tags`.",
14281
+ impact: 50
14282
+ });
14283
+ } else if (remoteTag === true) {
14284
+ findings.push({
14285
+ severity: "ok",
14286
+ code: "release-tag-pushed",
14287
+ message: `Tag ${tag} exists on the remote.`
14288
+ });
14289
+ } else {
14290
+ findings.push({
14291
+ severity: "warn",
14292
+ code: "release-tag-remote-unverified",
14293
+ message: `Could not verify whether tag ${tag} exists on the remote.`,
14294
+ fix: "Run `git push --tags` if you have not already.",
14295
+ impact: 10
14296
+ });
14297
+ }
14298
+ return finishReport(root, initialized, mode, findings, config);
14299
+ }
14300
+ function finishReport(root, initialized, mode, findings, config) {
14301
+ const score = buildScore(findings, config.enforcement?.scoreThreshold);
14302
+ const hasErrors = findings.some((f) => f.severity === "error");
14303
+ return withCategories({
14304
+ root,
14305
+ initialized,
14306
+ mode,
14307
+ score,
14308
+ should_block: mode === "strict" && hasErrors,
14309
+ findings
14310
+ });
14311
+ }
13714
14312
  async function runWithEnforcement(command, args, opts) {
13715
- const root = findProjectRoot49(opts.dir);
13716
- const paths = resolveHaivePaths45(root);
13717
- if (!existsSync69(paths.haiveDir)) {
14313
+ const root = findProjectRoot51(opts.dir);
14314
+ const paths = resolveHaivePaths47(root);
14315
+ if (!existsSync73(paths.haiveDir)) {
13718
14316
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
13719
14317
  process.exit(1);
13720
14318
  }
@@ -13733,7 +14331,7 @@ async function runWithEnforcement(command, args, opts) {
13733
14331
  process.exit(2);
13734
14332
  }
13735
14333
  ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
13736
- ui.info(`Briefing written to ${path50.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
14334
+ ui.info(`Briefing written to ${path51.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
13737
14335
  const child = spawn6(command, args, {
13738
14336
  cwd: root,
13739
14337
  stdio: "inherit",
@@ -13783,9 +14381,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
13783
14381
  source: "haive-run",
13784
14382
  memoryIds: briefing.memories.map((m) => m.id)
13785
14383
  });
13786
- const dir = path50.join(paths.runtimeDir, "enforcement", "briefings");
14384
+ const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
13787
14385
  await mkdir19(dir, { recursive: true });
13788
- const file = path50.join(dir, `${sessionId}.md`);
14386
+ const file = path51.join(dir, `${sessionId}.md`);
13789
14387
  const parts = [
13790
14388
  "# hAIve Briefing",
13791
14389
  "",
@@ -13803,13 +14401,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
13803
14401
  if (briefing.setup_warnings.length > 0) {
13804
14402
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
13805
14403
  }
13806
- await writeFile33(file, parts.join("\n") + "\n", "utf8");
14404
+ await writeFile34(file, parts.join("\n") + "\n", "utf8");
13807
14405
  return file;
13808
14406
  }
13809
14407
  async function buildEnforcementReport(dir, stage, sessionId) {
13810
- const root = findProjectRoot49(dir);
13811
- const paths = resolveHaivePaths45(root);
13812
- const initialized = existsSync69(paths.haiveDir);
14408
+ const root = findProjectRoot51(dir);
14409
+ const paths = resolveHaivePaths47(root);
14410
+ const initialized = existsSync73(paths.haiveDir);
13813
14411
  const config = initialized ? await loadConfig13(paths) : {};
13814
14412
  if (initialized) await applyLightweightRepairs(root, paths);
13815
14413
  const mode = config.enforcement?.mode ?? "strict";
@@ -13840,7 +14438,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13840
14438
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
13841
14439
  });
13842
14440
  }
13843
- findings.push(...await inspectIntegrationVersions(root, "0.10.8"));
14441
+ findings.push(...await inspectIntegrationVersions(root, "0.11.0"));
13844
14442
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
13845
14443
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
13846
14444
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -13874,7 +14472,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13874
14472
  findings.push(...await verifyDecisionCoverage(paths, stage, sessionId));
13875
14473
  }
13876
14474
  if (stage === "pre-commit" || stage === "ci") {
13877
- findings.push(...await runPrecommitPolicy(paths, config.enforcement?.antiPatternGate ?? "anchored"));
14475
+ findings.push(...await runPrecommitPolicy(paths, config.enforcement?.antiPatternGate ?? "anchored", stage));
13878
14476
  }
13879
14477
  if (config.enforcement?.cleanupGeneratedArtifacts !== false) {
13880
14478
  findings.push(...await findGeneratedArtifacts(paths));
@@ -13910,8 +14508,8 @@ function withCategories(report) {
13910
14508
  };
13911
14509
  }
13912
14510
  async function hasRecentSessionRecap(paths) {
13913
- if (!existsSync69(paths.memoriesDir)) return false;
13914
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14511
+ if (!existsSync73(paths.memoriesDir)) return false;
14512
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
13915
14513
  return all.some(({ memory: memory2 }) => {
13916
14514
  const fm = memory2.frontmatter;
13917
14515
  const freshnessDate = fm.verified_at ?? fm.created_at;
@@ -13919,8 +14517,8 @@ async function hasRecentSessionRecap(paths) {
13919
14517
  });
13920
14518
  }
13921
14519
  async function verifyMemoryPolicy(paths, config) {
13922
- if (!existsSync69(paths.memoriesDir)) return [];
13923
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14520
+ if (!existsSync73(paths.memoriesDir)) return [];
14521
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
13924
14522
  const findings = [];
13925
14523
  const staleImportant = [];
13926
14524
  let verified = 0;
@@ -13957,12 +14555,12 @@ async function verifyMemoryPolicy(paths, config) {
13957
14555
  return findings;
13958
14556
  }
13959
14557
  async function verifyDecisionCoverage(paths, stage, sessionId) {
13960
- if (!existsSync69(paths.memoriesDir)) return [];
14558
+ if (!existsSync73(paths.memoriesDir)) return [];
13961
14559
  const changedFiles = await getChangedFiles(paths.root, stage);
13962
14560
  if (changedFiles.length === 0) {
13963
14561
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
13964
14562
  }
13965
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14563
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
13966
14564
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
13967
14565
  const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
13968
14566
  const fm = memory2.frontmatter;
@@ -13998,19 +14596,20 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
13998
14596
  impact: Math.min(35, 10 + missing.length * 5)
13999
14597
  }];
14000
14598
  }
14001
- async function runPrecommitPolicy(paths, gate) {
14599
+ async function runPrecommitPolicy(paths, gate, stage) {
14002
14600
  if (gate === "off") {
14003
14601
  return [{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." }];
14004
14602
  }
14005
- const staged = await runCommand4("git", ["diff", "--cached", "--name-only"], paths.root).catch(() => "");
14006
- const touchedPaths = staged.split("\n").map((s) => s.trim()).filter(Boolean);
14603
+ const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
14604
+ const touchedPaths = snapshot.paths;
14007
14605
  if (touchedPaths.length === 0) {
14008
- return [{ severity: "info", code: "no-staged-changes", message: "No staged changes found for pre-commit policy." }];
14606
+ const code = stage === "ci" ? "no-ci-diff-changes" : "no-staged-changes";
14607
+ const message = stage === "ci" ? "No changed files found for CI policy diff." : "No staged changes found for pre-commit policy.";
14608
+ return [{ severity: "info", code, message }];
14009
14609
  }
14010
- const diff = await runCommand4("git", ["diff", "--cached"], paths.root).catch(() => "");
14011
14610
  const { block_on, anchored_blocks } = antiPatternGateParams2(gate);
14012
14611
  const result = await preCommitCheck({
14013
- diff,
14612
+ diff: snapshot.diff,
14014
14613
  paths: touchedPaths,
14015
14614
  block_on,
14016
14615
  anchored_blocks,
@@ -14020,7 +14619,7 @@ async function runPrecommitPolicy(paths, gate) {
14020
14619
  return [{
14021
14620
  severity: "ok",
14022
14621
  code: "precommit-policy-pass",
14023
- message: `Pre-commit policy passed for ${touchedPaths.length} staged file(s).`
14622
+ message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
14024
14623
  }];
14025
14624
  }
14026
14625
  return [{
@@ -14061,16 +14660,16 @@ async function cleanupRuntimeDir(runtimeDir) {
14061
14660
  for (const entry of entries) {
14062
14661
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
14063
14662
  if (entry.name === "enforcement") {
14064
- removed += await cleanupEnforcementDir(path50.join(runtimeDir, entry.name));
14663
+ removed += await cleanupEnforcementDir(path51.join(runtimeDir, entry.name));
14065
14664
  continue;
14066
14665
  }
14067
- await rm3(path50.join(runtimeDir, entry.name), { recursive: true, force: true });
14666
+ await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
14068
14667
  removed++;
14069
14668
  }
14070
- await writeFile33(path50.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14071
- if (!existsSync69(path50.join(runtimeDir, "README.md"))) {
14072
- await writeFile33(
14073
- path50.join(runtimeDir, "README.md"),
14669
+ await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14670
+ if (!existsSync73(path51.join(runtimeDir, "README.md"))) {
14671
+ await writeFile34(
14672
+ path51.join(runtimeDir, "README.md"),
14074
14673
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
14075
14674
  "utf8"
14076
14675
  );
@@ -14083,10 +14682,10 @@ async function cleanupCacheDir(cacheDir) {
14083
14682
  const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
14084
14683
  for (const entry of entries) {
14085
14684
  if (entry.name === ".gitignore") continue;
14086
- await rm3(path50.join(cacheDir, entry.name), { recursive: true, force: true });
14685
+ await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
14087
14686
  removed++;
14088
14687
  }
14089
- await writeFile33(path50.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
14688
+ await writeFile34(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
14090
14689
  return removed;
14091
14690
  }
14092
14691
  async function cleanupEnforcementDir(enforcementDir) {
@@ -14094,7 +14693,7 @@ async function cleanupEnforcementDir(enforcementDir) {
14094
14693
  const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
14095
14694
  for (const entry of entries) {
14096
14695
  if (entry.name === "briefings") continue;
14097
- await rm3(path50.join(enforcementDir, entry.name), { recursive: true, force: true });
14696
+ await rm3(path51.join(enforcementDir, entry.name), { recursive: true, force: true });
14098
14697
  removed++;
14099
14698
  }
14100
14699
  return removed;
@@ -14110,9 +14709,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
14110
14709
  ];
14111
14710
  const findings = [];
14112
14711
  for (const rel of files) {
14113
- const file = path50.join(root, rel);
14114
- if (!existsSync69(file)) continue;
14115
- const text = await readFile21(file, "utf8").catch(() => "");
14712
+ const file = path51.join(root, rel);
14713
+ if (!existsSync73(file)) continue;
14714
+ const text = await readFile23(file, "utf8").catch(() => "");
14116
14715
  for (const bin of extractAbsoluteHaiveBins2(text)) {
14117
14716
  const version = versionForBinary2(bin);
14118
14717
  if (!version) {
@@ -14171,22 +14770,217 @@ function versionForBinary2(bin) {
14171
14770
  }
14172
14771
  }
14173
14772
  async function getChangedFiles(root, stage) {
14174
- const commands = stage === "pre-commit" ? [["diff", "--cached", "--name-only"]] : [
14175
- ["diff", "--cached", "--name-only"],
14176
- ["diff", "--name-only"]
14177
- ];
14773
+ if (stage === "ci") {
14774
+ return (await getPolicyDiffSnapshot(root, "ci")).paths;
14775
+ }
14776
+ if (stage === "pre-commit") {
14777
+ return normalizeChangedFileList(
14778
+ await runCommand4("git", ["diff", "--cached", "--name-only"], root).catch(() => "")
14779
+ );
14780
+ }
14178
14781
  const files = /* @__PURE__ */ new Set();
14179
- for (const args of commands) {
14180
- const out = await runCommand4("git", args, root).catch(() => "");
14181
- for (const line of out.split("\n")) {
14182
- const file = line.trim();
14183
- if (file) files.add(file);
14184
- }
14782
+ for (const args of [["diff", "--cached", "--name-only"], ["diff", "--name-only"]]) {
14783
+ for (const file of normalizeChangedFileList(await runCommand4("git", args, root).catch(() => ""))) {
14784
+ files.add(file);
14785
+ }
14786
+ }
14787
+ return [...files];
14788
+ }
14789
+ async function getPolicyDiffSnapshot(root, stage) {
14790
+ if (stage === "pre-commit") {
14791
+ const diff = await runCommand4("git", ["diff", "--cached"], root).catch(() => "");
14792
+ const names = await runCommand4("git", ["diff", "--cached", "--name-only"], root).catch(() => "");
14793
+ return { diff, paths: normalizeChangedFileList(names), source: "staged" };
14794
+ }
14795
+ const range = await resolveCiDiffRange(root);
14796
+ if (range) {
14797
+ const diff = await runCommand4("git", ["diff", range], root).catch(() => "");
14798
+ const names = await runCommand4("git", ["diff", "--name-only", range], root).catch(() => "");
14799
+ return { diff, paths: normalizeChangedFileList(names), source: range };
14800
+ }
14801
+ return { diff: "", paths: [], source: "none" };
14802
+ }
14803
+ async function resolveCiDiffRange(root) {
14804
+ const explicitBase = cleanGitSha(process.env.HAIVE_BASE_SHA ?? process.env.HAIVE_BASE_REF);
14805
+ const explicitHead = cleanGitSha(process.env.HAIVE_HEAD_SHA ?? process.env.GITHUB_SHA) ?? "HEAD";
14806
+ if (explicitBase && await gitCommitExists(root, explicitBase)) {
14807
+ return `${explicitBase}...${explicitHead}`;
14808
+ }
14809
+ const eventRange = await resolveGithubEventRange(root);
14810
+ if (eventRange) return eventRange;
14811
+ const baseRef = process.env.GITHUB_BASE_REF?.trim();
14812
+ if (baseRef) {
14813
+ const remoteRef = `origin/${baseRef}`;
14814
+ if (await gitCommitExists(root, remoteRef)) return `${remoteRef}...${explicitHead}`;
14815
+ }
14816
+ if (await gitCommitExists(root, "origin/main")) return `origin/main...${explicitHead}`;
14817
+ if (await gitCommitExists(root, "origin/master")) return `origin/master...${explicitHead}`;
14818
+ if (await gitCommitExists(root, "HEAD^")) return `HEAD^..${explicitHead}`;
14819
+ return null;
14820
+ }
14821
+ async function resolveGithubEventRange(root) {
14822
+ const eventPath = process.env.GITHUB_EVENT_PATH;
14823
+ if (!eventPath || !existsSync73(eventPath)) return null;
14824
+ try {
14825
+ const event = JSON.parse(await readFile23(eventPath, "utf8"));
14826
+ const prBase = cleanGitSha(event.pull_request?.base?.sha);
14827
+ const prHead = cleanGitSha(event.pull_request?.head?.sha ?? event.after ?? process.env.GITHUB_SHA) ?? "HEAD";
14828
+ if (prBase && await gitCommitExists(root, prBase)) return `${prBase}...${prHead}`;
14829
+ const pushBase = cleanGitSha(event.before);
14830
+ const pushHead = cleanGitSha(event.after ?? process.env.GITHUB_SHA) ?? "HEAD";
14831
+ if (pushBase && await gitCommitExists(root, pushBase)) return `${pushBase}..${pushHead}`;
14832
+ } catch {
14833
+ return null;
14834
+ }
14835
+ return null;
14836
+ }
14837
+ function cleanGitSha(value) {
14838
+ const trimmed = value?.trim();
14839
+ if (!trimmed || /^0+$/.test(trimmed)) return null;
14840
+ return trimmed;
14841
+ }
14842
+ async function gitCommitExists(root, ref) {
14843
+ try {
14844
+ await runCommand4("git", ["rev-parse", "--verify", `${ref}^{commit}`], root);
14845
+ return true;
14846
+ } catch {
14847
+ return false;
14185
14848
  }
14186
- return [...files].filter(
14849
+ }
14850
+ function normalizeChangedFileList(raw) {
14851
+ return raw.split("\n").map((s) => s.trim()).filter(Boolean).filter(
14187
14852
  (file) => !file.startsWith(".ai/.runtime/") && !file.startsWith(".ai/.cache/") && !file.startsWith(".ai/.usage/") && file !== ".ai/.usage/tool-usage.jsonl"
14188
14853
  );
14189
14854
  }
14855
+ async function getGitSyncStatus(root) {
14856
+ const dirty = (await runCommand4("git", ["status", "--short", "--untracked-files=all"], root).catch(() => "")).split("\n").map((line) => statusLineToPath(line.trim())).filter(Boolean).filter((file) => normalizeChangedFileList(file).length > 0);
14857
+ const branch = (await runCommand4("git", ["branch", "--show-current"], root).catch(() => "")).trim() || void 0;
14858
+ const upstream = (await runCommand4("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], root).catch(() => "")).trim() || void 0;
14859
+ if (!branch && !upstream) {
14860
+ const inside = (await runCommand4("git", ["rev-parse", "--is-inside-work-tree"], root).catch(() => "")).trim();
14861
+ if (inside !== "true") return { available: false, ahead: 0, behind: 0, dirtyFiles: [], changedSinceUpstream: [] };
14862
+ }
14863
+ let ahead = 0;
14864
+ let behind = 0;
14865
+ let changedSinceUpstream = [];
14866
+ let releaseBaseRef;
14867
+ let releaseChangedFiles;
14868
+ if (upstream) {
14869
+ const counts = (await runCommand4("git", ["rev-list", "--left-right", "--count", `${upstream}...HEAD`], root).catch(() => "")).trim();
14870
+ const [behindRaw, aheadRaw] = counts.split(/\s+/);
14871
+ behind = Number.parseInt(behindRaw ?? "0", 10) || 0;
14872
+ ahead = Number.parseInt(aheadRaw ?? "0", 10) || 0;
14873
+ changedSinceUpstream = normalizeChangedFileList(
14874
+ await runCommand4("git", ["diff", "--name-only", `${upstream}...HEAD`], root).catch(() => "")
14875
+ );
14876
+ if (changedSinceUpstream.length > 0) {
14877
+ releaseBaseRef = upstream;
14878
+ releaseChangedFiles = changedSinceUpstream;
14879
+ }
14880
+ }
14881
+ if (!releaseChangedFiles || releaseChangedFiles.length === 0) {
14882
+ const hasParent = (await runCommand4("git", ["rev-parse", "--verify", "--quiet", "HEAD^"], root).catch(() => "")).trim().length > 0;
14883
+ if (hasParent) {
14884
+ const changedSinceParent = normalizeChangedFileList(
14885
+ await runCommand4("git", ["diff", "--name-only", "HEAD^..HEAD"], root).catch(() => "")
14886
+ );
14887
+ if (changedSinceParent.length > 0) {
14888
+ releaseBaseRef = "HEAD^";
14889
+ releaseChangedFiles = changedSinceParent;
14890
+ }
14891
+ }
14892
+ }
14893
+ return {
14894
+ available: true,
14895
+ branch,
14896
+ upstream,
14897
+ ahead,
14898
+ behind,
14899
+ dirtyFiles: dirty,
14900
+ changedSinceUpstream,
14901
+ ...releaseBaseRef ? { releaseBaseRef } : {},
14902
+ ...releaseChangedFiles ? { releaseChangedFiles } : {}
14903
+ };
14904
+ }
14905
+ function statusLineToPath(line) {
14906
+ const body = line.replace(/^[ MADRCU?!]{1,2}\s+/, "").trim();
14907
+ const renamed = body.match(/.+ -> (.+)$/);
14908
+ return renamed?.[1]?.trim() ?? body;
14909
+ }
14910
+ var VERSION_FILES = [
14911
+ "package.json",
14912
+ "packages/core/package.json",
14913
+ "packages/cli/package.json",
14914
+ "packages/mcp/package.json",
14915
+ "packages/embeddings/package.json"
14916
+ ];
14917
+ var SHIPPABLE_PATH_PREFIXES = [
14918
+ "packages/core/src/",
14919
+ "packages/cli/src/",
14920
+ "packages/mcp/src/",
14921
+ "packages/embeddings/src/"
14922
+ ];
14923
+ function isShippablePath(file) {
14924
+ return SHIPPABLE_PATH_PREFIXES.some((prefix) => file.startsWith(prefix)) || VERSION_FILES.includes(file);
14925
+ }
14926
+ async function inspectReleaseVersionState(root, upstream) {
14927
+ const localEntries = await Promise.all(VERSION_FILES.map(async (file) => [file, await readPackageVersion(root, file)]));
14928
+ const localVersions = new Map(localEntries);
14929
+ const unique = new Set([...localVersions.values()].filter(Boolean));
14930
+ const version = unique.size === 1 ? [...unique][0] : void 0;
14931
+ const localVersionsLabel = VERSION_FILES.map((file) => `${file}=${localVersions.get(file) ?? "unreadable"}`).join(", ");
14932
+ const baseVersion = await readPackageVersionAtRef(root, upstream, "package.json");
14933
+ return {
14934
+ lockstep: unique.size === 1 && localVersions.size === VERSION_FILES.length,
14935
+ ...version ? { version } : {},
14936
+ ...baseVersion ? { baseVersion } : {},
14937
+ localVersionsLabel
14938
+ };
14939
+ }
14940
+ async function readPackageVersion(root, relPath) {
14941
+ try {
14942
+ const data = JSON.parse(await readFile23(path51.join(root, relPath), "utf8"));
14943
+ return typeof data.version === "string" ? data.version : void 0;
14944
+ } catch {
14945
+ return void 0;
14946
+ }
14947
+ }
14948
+ async function readPackageVersionAtRef(root, ref, relPath) {
14949
+ try {
14950
+ const raw = await runCommand4("git", ["show", `${ref}:${relPath}`], root);
14951
+ const data = JSON.parse(raw);
14952
+ return typeof data.version === "string" ? data.version : void 0;
14953
+ } catch {
14954
+ return void 0;
14955
+ }
14956
+ }
14957
+ function compareSemver(a, b) {
14958
+ const pa = a.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);
14959
+ const pb = b.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);
14960
+ const len = Math.max(pa.length, pb.length, 3);
14961
+ for (let i = 0; i < len; i++) {
14962
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
14963
+ if (diff !== 0) return diff;
14964
+ }
14965
+ return 0;
14966
+ }
14967
+ async function tagPointsAtHead(root, tag) {
14968
+ const tags = await runCommand4("git", ["tag", "--points-at", "HEAD"], root).catch(() => "");
14969
+ return tags.split("\n").map((line) => line.trim()).includes(tag);
14970
+ }
14971
+ async function remoteTagExists(root, tag) {
14972
+ const branch = (await runCommand4("git", ["branch", "--show-current"], root).catch(() => "")).trim();
14973
+ const branchRemote = branch ? (await runCommand4("git", ["config", "--get", `branch.${branch}.remote`], root).catch(() => "")).trim() : "";
14974
+ const hasOrigin = (await runCommand4("git", ["config", "--get", "remote.origin.url"], root).catch(() => "")).trim().length > 0;
14975
+ const remote = branchRemote || (hasOrigin ? "origin" : "");
14976
+ if (!remote) return null;
14977
+ try {
14978
+ const out = await runCommand4("git", ["ls-remote", "--tags", remote, `refs/tags/${tag}`], root);
14979
+ return out.trim().length > 0;
14980
+ } catch {
14981
+ return null;
14982
+ }
14983
+ }
14190
14984
  function buildScore(findings, threshold = 80) {
14191
14985
  const checks = {
14192
14986
  total: findings.length,
@@ -14206,8 +15000,8 @@ function buildScore(findings, threshold = 80) {
14206
15000
  };
14207
15001
  }
14208
15002
  async function installGitEnforcement(root) {
14209
- const hooksDir = path50.join(root, ".git", "hooks");
14210
- if (!existsSync69(path50.join(root, ".git"))) {
15003
+ const hooksDir = path51.join(root, ".git", "hooks");
15004
+ if (!existsSync73(path51.join(root, ".git"))) {
14211
15005
  ui.warn("No .git directory found; git enforcement hooks skipped.");
14212
15006
  return;
14213
15007
  }
@@ -14229,31 +15023,31 @@ haive enforce check --stage pre-push --dir . || exit $?
14229
15023
  }
14230
15024
  ];
14231
15025
  for (const hook of hooks) {
14232
- const file = path50.join(hooksDir, hook.name);
14233
- if (existsSync69(file)) {
14234
- const current = await readFile21(file, "utf8").catch(() => "");
15026
+ const file = path51.join(hooksDir, hook.name);
15027
+ if (existsSync73(file)) {
15028
+ const current = await readFile23(file, "utf8").catch(() => "");
14235
15029
  if (current.includes(ENFORCE_HOOK_MARKER)) {
14236
- await writeFile33(file, hook.body, "utf8");
15030
+ await writeFile34(file, hook.body, "utf8");
14237
15031
  } else {
14238
- await writeFile33(file, `${current.trimEnd()}
15032
+ await writeFile34(file, `${current.trimEnd()}
14239
15033
 
14240
15034
  ${hook.body}`, "utf8");
14241
15035
  }
14242
15036
  } else {
14243
- await writeFile33(file, hook.body, "utf8");
15037
+ await writeFile34(file, hook.body, "utf8");
14244
15038
  }
14245
15039
  await chmod2(file, 493);
14246
15040
  }
14247
15041
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
14248
15042
  }
14249
15043
  async function installCiEnforcement(root) {
14250
- const workflowPath = path50.join(root, ".github", "workflows", "haive-enforcement.yml");
14251
- await mkdir19(path50.dirname(workflowPath), { recursive: true });
14252
- if (existsSync69(workflowPath)) {
15044
+ const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
15045
+ await mkdir19(path51.dirname(workflowPath), { recursive: true });
15046
+ if (existsSync73(workflowPath)) {
14253
15047
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
14254
15048
  return;
14255
15049
  }
14256
- await writeFile33(workflowPath, `name: haive-enforcement
15050
+ await writeFile34(workflowPath, `name: haive-enforcement
14257
15051
 
14258
15052
  on:
14259
15053
  pull_request:
@@ -14275,9 +15069,12 @@ jobs:
14275
15069
  - name: Install hAIve
14276
15070
  run: npm install -g @hiveai/cli
14277
15071
  - name: Enforce hAIve policy
15072
+ env:
15073
+ HAIVE_BASE_SHA: \${{ github.event.pull_request.base.sha || github.event.before }}
15074
+ HAIVE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
14278
15075
  run: haive enforce ci
14279
15076
  `, "utf8");
14280
- ui.success(`Created ${path50.relative(root, workflowPath)}`);
15077
+ ui.success(`Created ${path51.relative(root, workflowPath)}`);
14281
15078
  }
14282
15079
  function printReport(report, json, explain = false) {
14283
15080
  if (json) {
@@ -14337,7 +15134,7 @@ async function readHookPayload() {
14337
15134
  }
14338
15135
  function resolveRoot(dir, payload) {
14339
15136
  try {
14340
- return findProjectRoot49(dir ?? payload.cwd);
15137
+ return findProjectRoot51(dir ?? payload.cwd);
14341
15138
  } catch {
14342
15139
  return null;
14343
15140
  }
@@ -14374,15 +15171,15 @@ function extractToolPaths(payload, root) {
14374
15171
  }
14375
15172
  function normalizeToolPath(file, root) {
14376
15173
  const normalized = file.replace(/\\/g, "/");
14377
- if (!path50.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
14378
- return path50.relative(root, normalized).replace(/\\/g, "/");
15174
+ if (!path51.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
15175
+ return path51.relative(root, normalized).replace(/\\/g, "/");
14379
15176
  }
14380
15177
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
14381
- if (!existsSync69(paths.memoriesDir)) return [];
15178
+ if (!existsSync73(paths.memoriesDir)) return [];
14382
15179
  const marker = await readRecentBriefingMarker(paths, sessionId);
14383
15180
  const consulted = new Set(marker?.memory_ids ?? []);
14384
15181
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
14385
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
15182
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
14386
15183
  return all.filter(({ memory: memory2 }) => {
14387
15184
  const fm = memory2.frontmatter;
14388
15185
  if (!policyTypes.has(fm.type)) return false;
@@ -14452,16 +15249,16 @@ function registerRun(program2) {
14452
15249
 
14453
15250
  // src/commands/sensors.ts
14454
15251
  import { execFile as execFile2 } from "child_process";
14455
- import { existsSync as existsSync70 } from "fs";
14456
- import { chmod as chmod3, mkdir as mkdir20, readFile as readFile23, writeFile as writeFile34 } from "fs/promises";
14457
- import path51 from "path";
15252
+ import { existsSync as existsSync74 } from "fs";
15253
+ import { chmod as chmod3, mkdir as mkdir20, readFile as readFile24, writeFile as writeFile35 } from "fs/promises";
15254
+ import path53 from "path";
14458
15255
  import { promisify as promisify2 } from "util";
14459
15256
  import "commander";
14460
15257
  import {
14461
- findProjectRoot as findProjectRoot50,
15258
+ findProjectRoot as findProjectRoot52,
14462
15259
  isRetiredMemory as isRetiredMemory3,
14463
- loadMemoriesFromDir as loadMemoriesFromDir37,
14464
- resolveHaivePaths as resolveHaivePaths46,
15260
+ loadMemoriesFromDir as loadMemoriesFromDir38,
15261
+ resolveHaivePaths as resolveHaivePaths48,
14465
15262
  runSensors as runSensors2,
14466
15263
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
14467
15264
  serializeMemory as serializeMemory26
@@ -14470,8 +15267,8 @@ var exec2 = promisify2(execFile2);
14470
15267
  function registerSensors(program2) {
14471
15268
  const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
14472
15269
  sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
14473
- const root = findProjectRoot50(opts.dir);
14474
- const paths = resolveHaivePaths46(root);
15270
+ const root = findProjectRoot52(opts.dir);
15271
+ const paths = resolveHaivePaths48(root);
14475
15272
  const rows = await sensorRows(paths);
14476
15273
  if (opts.json) {
14477
15274
  console.log(JSON.stringify(rows, null, 2));
@@ -14491,10 +15288,10 @@ function registerSensors(program2) {
14491
15288
  }
14492
15289
  });
14493
15290
  sensors.command("check").description("Run regex sensors against a diff; defaults to `git diff --cached`").option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
14494
- const root = findProjectRoot50(opts.dir);
14495
- const paths = resolveHaivePaths46(root);
15291
+ const root = findProjectRoot52(opts.dir);
15292
+ const paths = resolveHaivePaths48(root);
14496
15293
  const memories = await runnableSensorMemories(paths);
14497
- const diff = opts.diffFile ? await readFile23(path51.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
15294
+ const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
14498
15295
  const targets = sensorTargetsFromDiff2(diff);
14499
15296
  const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
14500
15297
  const output = {
@@ -14533,9 +15330,9 @@ function registerSensors(program2) {
14533
15330
  process.exitCode = 1;
14534
15331
  return;
14535
15332
  }
14536
- const root = findProjectRoot50(opts.dir);
14537
- const paths = resolveHaivePaths46(root);
14538
- const loaded = existsSync70(paths.memoriesDir) ? await loadMemoriesFromDir37(paths.memoriesDir) : [];
15333
+ const root = findProjectRoot52(opts.dir);
15334
+ const paths = resolveHaivePaths48(root);
15335
+ const loaded = existsSync74(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
14539
15336
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
14540
15337
  if (!found) {
14541
15338
  ui.error(`No memory found with id ${id}`);
@@ -14555,7 +15352,7 @@ function registerSensors(program2) {
14555
15352
  },
14556
15353
  body: found.memory.body
14557
15354
  };
14558
- await writeFile34(found.filePath, serializeMemory26(next), "utf8");
15355
+ await writeFile35(found.filePath, serializeMemory26(next), "utf8");
14559
15356
  ui.success(`Updated ${id}: sensor severity=${severity}`);
14560
15357
  if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
14561
15358
  ui.info(`message=${sensor.message}`);
@@ -14567,16 +15364,16 @@ function registerSensors(program2) {
14567
15364
  process.exitCode = 1;
14568
15365
  return;
14569
15366
  }
14570
- const root = findProjectRoot50(opts.dir);
14571
- const paths = resolveHaivePaths46(root);
15367
+ const root = findProjectRoot52(opts.dir);
15368
+ const paths = resolveHaivePaths48(root);
14572
15369
  const rows = await sensorRows(paths);
14573
- const outDir = path51.resolve(root, opts.outDir ?? ".ai/generated");
15370
+ const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
14574
15371
  await mkdir20(outDir, { recursive: true });
14575
- const outPath = path51.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
15372
+ const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
14576
15373
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
14577
- await writeFile34(outPath, content, "utf8");
15374
+ await writeFile35(outPath, content, "utf8");
14578
15375
  if (format === "grep") await chmod3(outPath, 493);
14579
- ui.success(`Exported ${rows.length} sensor(s): ${path51.relative(root, outPath)}`);
15376
+ ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
14580
15377
  });
14581
15378
  }
14582
15379
  async function sensorRows(paths) {
@@ -14597,8 +15394,8 @@ async function sensorRows(paths) {
14597
15394
  });
14598
15395
  }
14599
15396
  async function runnableSensorMemories(paths, regexOnly = true) {
14600
- if (!existsSync70(paths.memoriesDir)) return [];
14601
- const loaded = await loadMemoriesFromDir37(paths.memoriesDir);
15397
+ if (!existsSync74(paths.memoriesDir)) return [];
15398
+ const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
14602
15399
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
14603
15400
  const sensor = memory2.frontmatter.sensor;
14604
15401
  if (!sensor) return false;
@@ -14639,8 +15436,8 @@ function shellQuote(value) {
14639
15436
  }
14640
15437
 
14641
15438
  // src/index.ts
14642
- var program = new Command53();
14643
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.10.8").option("--advanced", "show maintenance and experimental commands in help");
15439
+ var program = new Command55();
15440
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.11.0").option("--advanced", "show maintenance and experimental commands in help");
14644
15441
  registerInit(program);
14645
15442
  registerWelcome(program);
14646
15443
  registerResolveProject(program);
@@ -14664,6 +15461,7 @@ registerMemoryQuery(memory);
14664
15461
  registerMemoryPromote(memory);
14665
15462
  registerMemoryVerify(memory);
14666
15463
  registerMemoryStats(memory);
15464
+ registerMemoryImpact(memory);
14667
15465
  registerMemoryReject(memory);
14668
15466
  registerMemoryAutoPromote(memory);
14669
15467
  registerMemoryForFiles(memory);
@@ -14694,6 +15492,7 @@ registerHub(program);
14694
15492
  registerStats(program);
14695
15493
  registerBench(program);
14696
15494
  registerBenchmark(program);
15495
+ registerEval(program);
14697
15496
  registerDoctor(program);
14698
15497
  registerPlayback(program);
14699
15498
  registerPrecommit(program);