@hiveai/cli 0.10.9 → 0.12.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 Command56 } 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,7 +2743,7 @@ ${SEED_FOOTER(stack)}` });
2743
2743
  }
2744
2744
 
2745
2745
  // src/commands/init.ts
2746
- var HAIVE_GITHUB_ACTION_REF = `v${"0.10.9"}`;
2746
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.12.0"}`;
2747
2747
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
2748
2748
 
2749
2749
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -3663,55 +3663,66 @@ import {
3663
3663
  serializeMemory as serializeMemory32
3664
3664
  } from "@hiveai/core";
3665
3665
  import { z as z7 } from "zod";
3666
- import { readFile as readFile22, readdir as readdir22 } from "fs/promises";
3667
- import { existsSync as existsSync82 } from "fs";
3668
- import path42 from "path";
3669
3666
  import {
3670
- deriveConfidence as deriveConfidence2,
3667
+ computeImpact,
3671
3668
  getUsage as getUsage22,
3672
- inferModulesFromPaths,
3673
3669
  loadMemoriesFromDir as loadMemoriesFromDir6,
3674
3670
  loadUsageIndex as loadUsageIndex32,
3675
- memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
3676
- trackReads as trackReads22
3671
+ recordApplied,
3672
+ recordRejection as recordRejection2,
3673
+ saveUsageIndex as saveUsageIndex2
3677
3674
  } from "@hiveai/core";
3675
+ import { existsSync as existsSync82 } from "fs";
3678
3676
  import { z as z8 } from "zod";
3677
+ import { readFile as readFile22, readdir as readdir22 } from "fs/promises";
3679
3678
  import { existsSync as existsSync92 } from "fs";
3679
+ import path42 from "path";
3680
3680
  import {
3681
- deriveConfidence as deriveConfidence3,
3681
+ deriveConfidence as deriveConfidence2,
3682
3682
  getUsage as getUsage3,
3683
+ inferModulesFromPaths,
3683
3684
  loadMemoriesFromDir as loadMemoriesFromDir7,
3684
- loadUsageIndex as loadUsageIndex4
3685
+ loadUsageIndex as loadUsageIndex4,
3686
+ memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
3687
+ trackReads as trackReads22
3685
3688
  } from "@hiveai/core";
3686
3689
  import { z as z9 } from "zod";
3687
3690
  import { existsSync as existsSync102 } from "fs";
3688
- import { unlink } from "fs/promises";
3689
3691
  import {
3692
+ deriveConfidence as deriveConfidence3,
3693
+ getUsage as getUsage4,
3690
3694
  loadMemoriesFromDir as loadMemoriesFromDir8,
3691
- loadUsageIndex as loadUsageIndex5,
3692
- saveUsageIndex as saveUsageIndex2
3695
+ loadUsageIndex as loadUsageIndex5
3693
3696
  } from "@hiveai/core";
3694
3697
  import { z as z10 } from "zod";
3695
- import { writeFile as writeFile52 } from "fs/promises";
3696
3698
  import { existsSync as existsSync112 } from "fs";
3697
- import { loadMemoriesFromDir as loadMemoriesFromDir9, serializeMemory as serializeMemory4 } from "@hiveai/core";
3698
- import { z as z11 } from "zod";
3699
- import { existsSync as existsSync122 } from "fs";
3699
+ import { unlink } from "fs/promises";
3700
3700
  import {
3701
- getUsage as getUsage4,
3702
- loadMemoriesFromDir as loadMemoriesFromDir10,
3703
- loadUsageIndex as loadUsageIndex6
3701
+ loadMemoriesFromDir as loadMemoriesFromDir9,
3702
+ loadUsageIndex as loadUsageIndex6,
3703
+ saveUsageIndex as saveUsageIndex3
3704
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";
3705
3709
  import { z as z12 } from "zod";
3706
- import { writeFile as writeFile62 } from "fs/promises";
3707
3710
  import { existsSync as existsSync132 } from "fs";
3708
3711
  import {
3712
+ getUsage as getUsage5,
3709
3713
  loadMemoriesFromDir as loadMemoriesFromDir11,
3710
- serializeMemory as serializeMemory5
3714
+ loadUsageIndex as loadUsageIndex7
3711
3715
  } from "@hiveai/core";
3712
3716
  import { z as z13 } from "zod";
3713
- import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
3717
+ import { writeFile as writeFile62 } from "fs/promises";
3714
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";
3715
3726
  import path52 from "path";
3716
3727
  import {
3717
3728
  buildFrontmatter as buildFrontmatter22,
@@ -3719,9 +3730,9 @@ import {
3719
3730
  serializeMemory as serializeMemory6,
3720
3731
  suggestSensorFromMemory as suggestSensorFromMemory2
3721
3732
  } from "@hiveai/core";
3722
- import { z as z14 } from "zod";
3733
+ import { z as z15 } from "zod";
3723
3734
  import { mkdir as mkdir42, writeFile as writeFile82 } from "fs/promises";
3724
- import { existsSync as existsSync15 } from "fs";
3735
+ import { existsSync as existsSync16 } from "fs";
3725
3736
  import path62 from "path";
3726
3737
  import {
3727
3738
  buildFrontmatter as buildFrontmatter3,
@@ -3729,35 +3740,37 @@ import {
3729
3740
  memoryFilePath as memoryFilePath3,
3730
3741
  serializeMemory as serializeMemory7
3731
3742
  } from "@hiveai/core";
3732
- import { z as z15 } from "zod";
3743
+ import { z as z16 } from "zod";
3733
3744
  import { writeFile as writeFile10, mkdir as mkdir62 } from "fs/promises";
3734
- import { existsSync as existsSync17 } from "fs";
3745
+ import { existsSync as existsSync18 } from "fs";
3735
3746
  import path82 from "path";
3736
3747
  import {
3737
3748
  buildFrontmatter as buildFrontmatter4,
3738
- loadMemoriesFromDir as loadMemoriesFromDir12,
3749
+ loadMemoriesFromDir as loadMemoriesFromDir13,
3739
3750
  memoryFilePath as memoryFilePath4,
3740
3751
  serializeMemory as serializeMemory8
3741
3752
  } from "@hiveai/core";
3742
- import { z as z16 } from "zod";
3753
+ import { z as z17 } from "zod";
3743
3754
  import {
3744
3755
  appendUsageEvent,
3745
3756
  appendRuntimeJournalEntry,
3746
3757
  loadConfig as loadConfig22
3747
3758
  } from "@hiveai/core";
3748
3759
  import { mkdir as mkdir52, writeFile as writeFile92, rm } from "fs/promises";
3749
- import { existsSync as existsSync16 } from "fs";
3760
+ import { existsSync as existsSync17 } from "fs";
3750
3761
  import path72 from "path";
3751
3762
  import { execSync } from "child_process";
3752
3763
  import { readFile as readFile42, writeFile as writeFile11 } from "fs/promises";
3753
- import { existsSync as existsSync19 } from "fs";
3764
+ import { existsSync as existsSync20 } from "fs";
3754
3765
  import {
3755
3766
  allocateBudget,
3767
+ computeImpact as computeImpact2,
3756
3768
  DEFAULT_AUTO_PROMOTE_RULE,
3757
3769
  deriveConfidence as deriveConfidence4,
3758
3770
  estimateTokens,
3771
+ evaluateSkillActivation,
3759
3772
  extractActionsBriefBody as extractActionsBriefBody2,
3760
- getUsage as getUsage5,
3773
+ getUsage as getUsage6,
3761
3774
  inferModulesFromPaths as inferModulesFromPaths2,
3762
3775
  isAutoPromoteEligible,
3763
3776
  isDecaying,
@@ -3766,8 +3779,8 @@ import {
3766
3779
  literalMatchesAnyToken as literalMatchesAnyToken22,
3767
3780
  loadCodeMap as loadCodeMap5,
3768
3781
  loadConfig as loadConfig3,
3769
- loadMemoriesFromDir as loadMemoriesFromDir13,
3770
- loadUsageIndex as loadUsageIndex7,
3782
+ loadMemoriesFromDir as loadMemoriesFromDir14,
3783
+ loadUsageIndex as loadUsageIndex8,
3771
3784
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
3772
3785
  queryCodeMap as queryCodeMap2,
3773
3786
  resolveBriefingBudget as resolveBriefingBudget2,
@@ -3779,77 +3792,80 @@ import {
3779
3792
  truncateToTokens,
3780
3793
  writeBriefingMarker as writeBriefingMarker2
3781
3794
  } from "@hiveai/core";
3782
- import { z as z17 } from "zod";
3795
+ import { z as z18 } from "zod";
3783
3796
  import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
3784
- import { existsSync as existsSync18 } from "fs";
3797
+ import { existsSync as existsSync19 } from "fs";
3785
3798
  import path92 from "path";
3786
3799
  import { isGlobPath, isStackPackSeed as isStackPackSeed2, pathsOverlap } from "@hiveai/core";
3787
3800
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap22, queryCodeMap as queryCodeMap22 } from "@hiveai/core";
3788
- import { z as z18 } from "zod";
3789
- import { existsSync as existsSync20 } from "fs";
3790
- import { loadMemoriesFromDir as loadMemoriesFromDir14 } from "@hiveai/core";
3791
3801
  import { z as z19 } from "zod";
3792
3802
  import { existsSync as existsSync21 } from "fs";
3793
3803
  import { loadMemoriesFromDir as loadMemoriesFromDir15 } from "@hiveai/core";
3794
3804
  import { z as z20 } from "zod";
3805
+ import { existsSync as existsSync222 } from "fs";
3806
+ import { loadMemoriesFromDir as loadMemoriesFromDir16 } from "@hiveai/core";
3795
3807
  import { z as z21 } from "zod";
3796
3808
  import { z as z22 } from "zod";
3797
- import { existsSync as existsSync222 } from "fs";
3809
+ import { z as z23 } from "zod";
3810
+ import { existsSync as existsSync23 } from "fs";
3798
3811
  import { spawn } from "child_process";
3799
3812
  import path102 from "path";
3800
3813
  import {
3801
3814
  deriveConfidence as deriveConfidence5,
3802
- getUsage as getUsage6,
3815
+ getUsage as getUsage7,
3803
3816
  loadCodeMap as loadCodeMap32,
3804
- loadMemoriesFromDir as loadMemoriesFromDir16,
3805
- loadUsageIndex as loadUsageIndex8,
3817
+ loadMemoriesFromDir as loadMemoriesFromDir17,
3818
+ loadUsageIndex as loadUsageIndex9,
3806
3819
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
3807
3820
  } from "@hiveai/core";
3808
- import { z as z23 } from "zod";
3809
- import { existsSync as existsSync23 } from "fs";
3821
+ import { z as z24 } from "zod";
3822
+ import { existsSync as existsSync24 } from "fs";
3810
3823
  import {
3811
3824
  addedLinesFromDiff,
3825
+ buildDocFrequency,
3826
+ CODE_STOPWORDS,
3812
3827
  deriveConfidence as deriveConfidence6,
3813
- getUsage as getUsage7,
3828
+ diffHasDistinctiveOverlap,
3829
+ getUsage as getUsage8,
3814
3830
  isRetiredMemory as isRetiredMemory2,
3815
- loadMemoriesFromDir as loadMemoriesFromDir17,
3816
- loadUsageIndex as loadUsageIndex9,
3831
+ loadMemoriesFromDir as loadMemoriesFromDir18,
3832
+ loadUsageIndex as loadUsageIndex10,
3817
3833
  literalMatchesAnyToken as literalMatchesAnyToken3,
3818
3834
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
3819
3835
  runSensors,
3820
3836
  sensorTargetsFromDiff,
3821
3837
  tokenizeQuery as tokenizeQuery3
3822
3838
  } from "@hiveai/core";
3823
- import { z as z24 } from "zod";
3824
- import { existsSync as existsSync24 } from "fs";
3825
- import {
3826
- loadMemoriesFromDir as loadMemoriesFromDir18,
3827
- tokenizeQuery as tokenizeQuery4
3828
- } from "@hiveai/core";
3829
3839
  import { z as z25 } from "zod";
3830
3840
  import { existsSync as existsSync25 } from "fs";
3831
- import { spawn as spawn2 } from "child_process";
3832
3841
  import {
3833
- deriveConfidence as deriveConfidence7,
3834
- getUsage as getUsage8,
3835
3842
  loadMemoriesFromDir as loadMemoriesFromDir19,
3836
- loadUsageIndex as loadUsageIndex10,
3837
- pathsOverlap as singlePathsOverlap
3843
+ tokenizeQuery as tokenizeQuery4
3838
3844
  } from "@hiveai/core";
3839
3845
  import { z as z26 } from "zod";
3840
3846
  import { existsSync as existsSync26 } from "fs";
3847
+ import { spawn as spawn2 } from "child_process";
3841
3848
  import {
3842
- deriveConfidence as deriveConfidence8,
3849
+ deriveConfidence as deriveConfidence7,
3843
3850
  getUsage as getUsage9,
3844
3851
  loadMemoriesFromDir as loadMemoriesFromDir20,
3845
3852
  loadUsageIndex as loadUsageIndex11,
3853
+ pathsOverlap as singlePathsOverlap
3854
+ } from "@hiveai/core";
3855
+ import { z as z27 } from "zod";
3856
+ import { existsSync as existsSync27 } from "fs";
3857
+ import {
3858
+ deriveConfidence as deriveConfidence8,
3859
+ getUsage as getUsage10,
3860
+ loadMemoriesFromDir as loadMemoriesFromDir21,
3861
+ loadUsageIndex as loadUsageIndex12,
3846
3862
  pathsOverlap as pathsOverlap2,
3847
3863
  tokenizeQuery as tokenizeQuery5
3848
3864
  } from "@hiveai/core";
3849
- import { z as z27 } from "zod";
3850
3865
  import { z as z28 } from "zod";
3866
+ import { z as z29 } from "zod";
3851
3867
  import { mkdir as mkdir72, writeFile as writeFile12 } from "fs/promises";
3852
- import { existsSync as existsSync27 } from "fs";
3868
+ import { existsSync as existsSync28 } from "fs";
3853
3869
  import path112 from "path";
3854
3870
  import { execSync as execSync2 } from "child_process";
3855
3871
  import {
@@ -3858,28 +3874,28 @@ import {
3858
3874
  readUsageEvents,
3859
3875
  serializeMemory as serializeMemory10
3860
3876
  } from "@hiveai/core";
3861
- import { z as z29 } from "zod";
3862
- import { existsSync as existsSync28 } from "fs";
3877
+ import { z as z30 } from "zod";
3878
+ import { existsSync as existsSync29 } from "fs";
3863
3879
  import {
3864
3880
  findLexicalConflictPairs,
3865
3881
  findTopicStatusConflictPairs,
3866
- loadMemoriesFromDir as loadMemoriesFromDir21
3882
+ loadMemoriesFromDir as loadMemoriesFromDir222
3867
3883
  } from "@hiveai/core";
3868
- import { z as z30 } from "zod";
3869
- import { resolveProjectInfo } from "@hiveai/core";
3870
3884
  import { z as z31 } from "zod";
3871
- import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
3885
+ import { resolveProjectInfo } from "@hiveai/core";
3872
3886
  import { z as z32 } from "zod";
3873
- import { existsSync as existsSync29 } from "fs";
3874
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir222 } from "@hiveai/core";
3887
+ import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
3875
3888
  import { z as z33 } from "zod";
3876
- import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
3889
+ import { existsSync as existsSync30 } from "fs";
3890
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hiveai/core";
3877
3891
  import { z as z34 } from "zod";
3878
- import { readRuntimeJournalTail } from "@hiveai/core";
3892
+ import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
3879
3893
  import { z as z35 } from "zod";
3894
+ import { readRuntimeJournalTail } from "@hiveai/core";
3880
3895
  import { z as z36 } from "zod";
3881
3896
  import { z as z37 } from "zod";
3882
3897
  import { z as z38 } from "zod";
3898
+ import { z as z39 } from "zod";
3883
3899
  import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
3884
3900
  function createContext(options = {}) {
3885
3901
  const env = options.env ?? process.env;
@@ -3996,6 +4012,13 @@ var MemSaveInputSchema = {
3996
4012
  commit: z4.string().optional().describe("Anchor commit SHA (for staleness detection later)"),
3997
4013
  topic: z4.string().optional().describe(
3998
4014
  "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."
4015
+ ),
4016
+ activation: z4.object({
4017
+ keywords: z4.array(z4.string()).default([]),
4018
+ globs: z4.array(z4.string()).default([]),
4019
+ always: z4.boolean().default(false)
4020
+ }).optional().describe(
4021
+ "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."
3999
4022
  )
4000
4023
  };
4001
4024
  function bodyHash(body) {
@@ -4114,7 +4137,8 @@ async function memSave(input, ctx) {
4114
4137
  commit: input.commit,
4115
4138
  topic: input.topic,
4116
4139
  status: haiveConfig.defaultStatus === "validated" ? "validated" : void 0,
4117
- sensor: suggestSensorForSavedMemory(input.type, input.body, input.paths) ?? void 0
4140
+ sensor: suggestSensorForSavedMemory(input.type, input.body, input.paths) ?? void 0,
4141
+ activation: input.type === "skill" ? input.activation : void 0
4118
4142
  });
4119
4143
  const file = memoryFilePath2(
4120
4144
  ctx.paths,
@@ -4463,14 +4487,51 @@ async function memReject(input, ctx) {
4463
4487
  rejection_reason: u?.rejection_reason ?? null
4464
4488
  };
4465
4489
  }
4490
+ var MemFeedbackInputSchema = {
4491
+ id: z8.string().min(1).describe("Full memory id the feedback is about"),
4492
+ outcome: z8.enum(["applied", "rejected"]).describe(
4493
+ "'applied' = this memory changed what you did (strong positive utility signal); 'rejected' = it was wrong/outdated/unhelpful (negative signal, blocks auto-promotion)."
4494
+ ),
4495
+ reason: z8.string().optional().describe("Why it was rejected (stored on the memory's usage record). Only used for outcome='rejected'.")
4496
+ };
4497
+ async function memFeedback(input, ctx) {
4498
+ if (!existsSync82(ctx.paths.memoriesDir)) {
4499
+ return { ok: false, id: input.id, error: "No .ai/memories \u2014 run `haive init` first." };
4500
+ }
4501
+ const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
4502
+ const target = all.find((m) => m.memory.frontmatter.id === input.id);
4503
+ if (!target) {
4504
+ return { ok: false, id: input.id, error: `No memory with id '${input.id}'.` };
4505
+ }
4506
+ const index = await loadUsageIndex32(ctx.paths);
4507
+ if (input.outcome === "applied") {
4508
+ recordApplied(index, input.id);
4509
+ } else {
4510
+ recordRejection2(index, input.id, input.reason ?? null);
4511
+ }
4512
+ await saveUsageIndex2(ctx.paths, index);
4513
+ const usage = getUsage22(index, input.id);
4514
+ const impact = computeImpact(target.memory.frontmatter, usage);
4515
+ return {
4516
+ ok: true,
4517
+ id: input.id,
4518
+ outcome: input.outcome,
4519
+ usage: {
4520
+ read_count: usage.read_count,
4521
+ applied_count: usage.applied_count,
4522
+ rejected_count: usage.rejected_count
4523
+ },
4524
+ impact: { score: impact.score, tier: impact.tier, signals: impact.signals }
4525
+ };
4526
+ }
4466
4527
  var MemForFilesInputSchema = {
4467
- files: z8.array(z8.string()).min(1).describe("Project-relative file paths the agent is currently working on"),
4468
- include_module_contexts: z8.boolean().default(true).describe("Inline the matching .ai/modules/<name>/context.md contents"),
4469
- track: z8.boolean().default(true).describe("Increment read_count on returned memories")
4528
+ files: z9.array(z9.string()).min(1).describe("Project-relative file paths the agent is currently working on"),
4529
+ include_module_contexts: z9.boolean().default(true).describe("Inline the matching .ai/modules/<name>/context.md contents"),
4530
+ track: z9.boolean().default(true).describe("Increment read_count on returned memories")
4470
4531
  };
4471
4532
  async function memForFiles(input, ctx) {
4472
4533
  const inferred = inferModulesFromPaths(input.files);
4473
- if (!existsSync82(ctx.paths.memoriesDir)) {
4534
+ if (!existsSync92(ctx.paths.memoriesDir)) {
4474
4535
  return {
4475
4536
  inferred_modules: inferred,
4476
4537
  by_anchor: [],
@@ -4479,8 +4540,8 @@ async function memForFiles(input, ctx) {
4479
4540
  module_contexts: await loadModuleContexts(ctx, inferred, input.include_module_contexts)
4480
4541
  };
4481
4542
  }
4482
- const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
4483
- const usage = await loadUsageIndex32(ctx.paths);
4543
+ const all = await loadMemoriesFromDir7(ctx.paths.memoriesDir);
4544
+ const usage = await loadUsageIndex4(ctx.paths);
4484
4545
  const seen = /* @__PURE__ */ new Set();
4485
4546
  const byAnchor = [];
4486
4547
  const byModule = [];
@@ -4528,7 +4589,7 @@ async function memForFiles(input, ctx) {
4528
4589
  }
4529
4590
  function toMatch(loaded, reason, usage) {
4530
4591
  const fm = loaded.memory.frontmatter;
4531
- const u = getUsage22(usage, fm.id);
4592
+ const u = getUsage3(usage, fm.id);
4532
4593
  return {
4533
4594
  id: fm.id,
4534
4595
  scope: fm.scope,
@@ -4592,7 +4653,7 @@ function extractPathSegments(files) {
4592
4653
  }
4593
4654
  async function loadModuleContexts(ctx, modules, enabled) {
4594
4655
  if (!enabled || modules.length === 0) return [];
4595
- if (!existsSync82(ctx.paths.modulesContextDir)) return [];
4656
+ if (!existsSync92(ctx.paths.modulesContextDir)) return [];
4596
4657
  const available = new Set(
4597
4658
  (await readdir22(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
4598
4659
  );
@@ -4600,24 +4661,24 @@ async function loadModuleContexts(ctx, modules, enabled) {
4600
4661
  for (const m of modules) {
4601
4662
  if (!available.has(m)) continue;
4602
4663
  const file = path42.join(ctx.paths.modulesContextDir, m, "context.md");
4603
- if (existsSync82(file)) {
4664
+ if (existsSync92(file)) {
4604
4665
  out.push({ name: m, content: await readFile22(file, "utf8") });
4605
4666
  }
4606
4667
  }
4607
4668
  return out;
4608
4669
  }
4609
4670
  var MemGetInputSchema = {
4610
- id: z9.string().min(1).describe("Memory id to fetch")
4671
+ id: z10.string().min(1).describe("Memory id to fetch")
4611
4672
  };
4612
4673
  async function memGet(input, ctx) {
4613
- if (!existsSync92(ctx.paths.memoriesDir)) {
4674
+ if (!existsSync102(ctx.paths.memoriesDir)) {
4614
4675
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4615
4676
  }
4616
- const all = await loadMemoriesFromDir7(ctx.paths.memoriesDir);
4677
+ const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
4617
4678
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
4618
4679
  if (!found) throw new Error(`No memory with id "${input.id}".`);
4619
4680
  const fm = found.memory.frontmatter;
4620
- const u = getUsage3(await loadUsageIndex4(ctx.paths), fm.id);
4681
+ const u = getUsage4(await loadUsageIndex5(ctx.paths), fm.id);
4621
4682
  return {
4622
4683
  id: fm.id,
4623
4684
  scope: fm.scope,
@@ -4641,43 +4702,43 @@ async function memGet(input, ctx) {
4641
4702
  };
4642
4703
  }
4643
4704
  var MemDeleteInputSchema = {
4644
- id: z10.string().min(1).describe("Memory id to delete"),
4645
- keep_usage: z10.boolean().default(false).describe("Keep the usage.json entry instead of removing it alongside the file")
4705
+ id: z11.string().min(1).describe("Memory id to delete"),
4706
+ keep_usage: z11.boolean().default(false).describe("Keep the usage.json entry instead of removing it alongside the file")
4646
4707
  };
4647
4708
  async function memDelete(input, ctx) {
4648
- if (!existsSync102(ctx.paths.memoriesDir)) {
4709
+ if (!existsSync112(ctx.paths.memoriesDir)) {
4649
4710
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4650
4711
  }
4651
- const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
4712
+ const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
4652
4713
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
4653
4714
  if (!found) throw new Error(`No memory with id "${input.id}".`);
4654
4715
  await unlink(found.filePath);
4655
4716
  let usageRemoved = false;
4656
4717
  if (!input.keep_usage) {
4657
- const idx = await loadUsageIndex5(ctx.paths);
4718
+ const idx = await loadUsageIndex6(ctx.paths);
4658
4719
  if (idx.by_id[input.id]) {
4659
4720
  delete idx.by_id[input.id];
4660
- await saveUsageIndex2(ctx.paths, idx);
4721
+ await saveUsageIndex3(ctx.paths, idx);
4661
4722
  usageRemoved = true;
4662
4723
  }
4663
4724
  }
4664
4725
  return { id: input.id, deleted_file: found.filePath, usage_removed: usageRemoved };
4665
4726
  }
4666
4727
  var MemUpdateInputSchema = {
4667
- id: z11.string().min(1).describe("Id of the memory to update"),
4668
- body: z11.string().optional().describe("New Markdown body \u2014 replaces the existing body"),
4669
- tags: z11.array(z11.string()).optional().describe("New tags array \u2014 fully replaces existing tags"),
4670
- paths: z11.array(z11.string()).optional().describe("New anchor paths \u2014 fully replaces existing anchor.paths"),
4671
- symbols: z11.array(z11.string()).optional().describe("New anchor symbols \u2014 fully replaces existing anchor.symbols"),
4672
- commit: z11.string().optional().describe("New anchor commit SHA"),
4673
- domain: z11.string().optional().describe("New domain label"),
4674
- author: z11.string().optional().describe("New author handle or email")
4728
+ id: z12.string().min(1).describe("Id of the memory to update"),
4729
+ body: z12.string().optional().describe("New Markdown body \u2014 replaces the existing body"),
4730
+ tags: z12.array(z12.string()).optional().describe("New tags array \u2014 fully replaces existing tags"),
4731
+ paths: z12.array(z12.string()).optional().describe("New anchor paths \u2014 fully replaces existing anchor.paths"),
4732
+ symbols: z12.array(z12.string()).optional().describe("New anchor symbols \u2014 fully replaces existing anchor.symbols"),
4733
+ commit: z12.string().optional().describe("New anchor commit SHA"),
4734
+ domain: z12.string().optional().describe("New domain label"),
4735
+ author: z12.string().optional().describe("New author handle or email")
4675
4736
  };
4676
4737
  async function memUpdate(input, ctx) {
4677
- if (!existsSync112(ctx.paths.memoriesDir)) {
4738
+ if (!existsSync122(ctx.paths.memoriesDir)) {
4678
4739
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4679
4740
  }
4680
- const memories = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
4741
+ const memories = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
4681
4742
  const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
4682
4743
  if (!loaded) throw new Error(`No memory with id "${input.id}".`);
4683
4744
  const { frontmatter, body } = loaded.memory;
@@ -4718,12 +4779,12 @@ async function memUpdate(input, ctx) {
4718
4779
  return { id: input.id, file_path: loaded.filePath, updated_fields };
4719
4780
  }
4720
4781
  var MemPendingInputSchema = {
4721
- scope: z12.enum(["personal", "team", "module"]).optional()
4782
+ scope: z13.enum(["personal", "team", "module"]).optional()
4722
4783
  };
4723
4784
  async function memPending(input, ctx) {
4724
- if (!existsSync122(ctx.paths.memoriesDir)) return { pending: [] };
4725
- const all = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
4726
- const usage = await loadUsageIndex6(ctx.paths);
4785
+ if (!existsSync132(ctx.paths.memoriesDir)) return { pending: [] };
4786
+ const all = await loadMemoriesFromDir11(ctx.paths.memoriesDir);
4787
+ const usage = await loadUsageIndex7(ctx.paths);
4727
4788
  const now = Date.now();
4728
4789
  const proposed = all.filter(({ memory: memory2 }) => {
4729
4790
  if (memory2.frontmatter.status !== "proposed") return false;
@@ -4731,12 +4792,12 @@ async function memPending(input, ctx) {
4731
4792
  return true;
4732
4793
  });
4733
4794
  proposed.sort(
4734
- (a, b) => getUsage4(usage, b.memory.frontmatter.id).read_count - getUsage4(usage, a.memory.frontmatter.id).read_count
4795
+ (a, b) => getUsage5(usage, b.memory.frontmatter.id).read_count - getUsage5(usage, a.memory.frontmatter.id).read_count
4735
4796
  );
4736
4797
  return {
4737
4798
  pending: proposed.map(({ memory: memory2, filePath }) => {
4738
4799
  const fm = memory2.frontmatter;
4739
- const u = getUsage4(usage, fm.id);
4800
+ const u = getUsage5(usage, fm.id);
4740
4801
  return {
4741
4802
  id: fm.id,
4742
4803
  scope: fm.scope,
@@ -4752,13 +4813,13 @@ async function memPending(input, ctx) {
4752
4813
  };
4753
4814
  }
4754
4815
  var MemApproveInputSchema = {
4755
- id: z13.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
4816
+ id: z14.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
4756
4817
  };
4757
4818
  async function memApprove(input, ctx) {
4758
- if (!existsSync132(ctx.paths.memoriesDir)) {
4819
+ if (!existsSync142(ctx.paths.memoriesDir)) {
4759
4820
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
4760
4821
  }
4761
- const all = await loadMemoriesFromDir11(ctx.paths.memoriesDir);
4822
+ const all = await loadMemoriesFromDir12(ctx.paths.memoriesDir);
4762
4823
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
4763
4824
  if (!found) throw new Error(`No memory with id "${input.id}".`);
4764
4825
  const previous = found.memory.frontmatter.status;
@@ -4775,17 +4836,17 @@ async function memApprove(input, ctx) {
4775
4836
  };
4776
4837
  }
4777
4838
  var MemTriedInputSchema = {
4778
- what: z14.string().min(1).describe("Brief description of the approach that was tried"),
4779
- why_failed: z14.string().min(1).describe("Why it failed or why it should NOT be used"),
4780
- instead: z14.string().optional().describe("What to use or do instead (recommended alternative)"),
4781
- scope: z14.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
4782
- module: z14.string().optional().describe("Module name (required when scope=module)"),
4783
- tags: z14.array(z14.string()).default([]).describe("Tags for filtering"),
4784
- paths: z14.array(z14.string()).default([]).describe("Anchor file paths this applies to"),
4785
- author: z14.string().optional().describe("Author handle or email")
4839
+ what: z15.string().min(1).describe("Brief description of the approach that was tried"),
4840
+ why_failed: z15.string().min(1).describe("Why it failed or why it should NOT be used"),
4841
+ instead: z15.string().optional().describe("What to use or do instead (recommended alternative)"),
4842
+ scope: z15.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
4843
+ module: z15.string().optional().describe("Module name (required when scope=module)"),
4844
+ tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
4845
+ paths: z15.array(z15.string()).default([]).describe("Anchor file paths this applies to"),
4846
+ author: z15.string().optional().describe("Author handle or email")
4786
4847
  };
4787
4848
  async function memTried(input, ctx) {
4788
- if (!existsSync142(ctx.paths.haiveDir)) {
4849
+ if (!existsSync15(ctx.paths.haiveDir)) {
4789
4850
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
4790
4851
  }
4791
4852
  const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
@@ -4811,27 +4872,27 @@ async function memTried(input, ctx) {
4811
4872
  }
4812
4873
  const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
4813
4874
  await mkdir32(path52.dirname(file), { recursive: true });
4814
- if (existsSync142(file)) {
4875
+ if (existsSync15(file)) {
4815
4876
  throw new Error(`Memory already exists at ${file}`);
4816
4877
  }
4817
4878
  await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
4818
4879
  return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
4819
4880
  }
4820
4881
  var MemObserveInputSchema = {
4821
- what: z15.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
4822
- where: z15.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
4823
- impact: z15.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
4824
- fix: z15.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
4825
- scope: z15.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
4826
- module: z15.string().optional().describe("Module name (required when scope=module)"),
4827
- tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
4828
- author: z15.string().optional().describe("Author handle or email"),
4829
- force: z15.boolean().default(false).describe(
4882
+ what: z16.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
4883
+ where: z16.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
4884
+ impact: z16.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
4885
+ fix: z16.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
4886
+ scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
4887
+ module: z16.string().optional().describe("Module name (required when scope=module)"),
4888
+ tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
4889
+ author: z16.string().optional().describe("Author handle or email"),
4890
+ force: z16.boolean().default(false).describe(
4830
4891
  "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."
4831
4892
  )
4832
4893
  };
4833
4894
  async function memObserve(input, ctx) {
4834
- if (!existsSync15(ctx.paths.haiveDir)) {
4895
+ if (!existsSync16(ctx.paths.haiveDir)) {
4835
4896
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
4836
4897
  }
4837
4898
  const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
@@ -4865,7 +4926,7 @@ async function memObserve(input, ctx) {
4865
4926
  const body = lines.join("\n") + "\n";
4866
4927
  const file = memoryFilePath3(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
4867
4928
  await mkdir42(path62.dirname(file), { recursive: true });
4868
- if (existsSync15(file)) {
4929
+ if (existsSync16(file)) {
4869
4930
  throw new Error(`Memory already exists at ${file}`);
4870
4931
  }
4871
4932
  await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
@@ -4951,7 +5012,7 @@ var SessionTracker = class {
4951
5012
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
4952
5013
  );
4953
5014
  const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
4954
- if (!ranPostTask && isSubstantialSession && existsSync16(this.ctx.paths.haiveDir)) {
5015
+ if (!ranPostTask && isSubstantialSession && existsSync17(this.ctx.paths.haiveDir)) {
4955
5016
  try {
4956
5017
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
4957
5018
  const payload = {
@@ -4985,7 +5046,7 @@ var SessionTracker = class {
4985
5046
  };
4986
5047
  async function clearPendingDistill(ctx) {
4987
5048
  const p = pendingDistillPath(ctx);
4988
- if (existsSync16(p)) {
5049
+ if (existsSync17(p)) {
4989
5050
  try {
4990
5051
  await rm(p);
4991
5052
  } catch {
@@ -5000,15 +5061,15 @@ function summarizeTools(events) {
5000
5061
  return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
5001
5062
  }
5002
5063
  var MemSessionEndInputSchema = {
5003
- goal: z16.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
5004
- accomplished: z16.string().describe("What was actually done \u2014 bullet list recommended"),
5005
- discoveries: z16.string().default("").describe(
5064
+ goal: z17.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
5065
+ accomplished: z17.string().describe("What was actually done \u2014 bullet list recommended"),
5066
+ discoveries: z17.string().default("").describe(
5006
5067
  "Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
5007
5068
  ),
5008
- files_touched: z16.array(z16.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
5009
- next_steps: z16.string().default("").describe("What should happen next (for the next session or a teammate)"),
5010
- scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
5011
- module: z16.string().optional().describe("Module name (required when scope=module)")
5069
+ files_touched: z17.array(z17.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
5070
+ next_steps: z17.string().default("").describe("What should happen next (for the next session or a teammate)"),
5071
+ scope: z17.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
5072
+ module: z17.string().optional().describe("Module name (required when scope=module)")
5012
5073
  };
5013
5074
  function recapTopic(scope, module) {
5014
5075
  return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
@@ -5038,7 +5099,7 @@ ${input.next_steps}`);
5038
5099
  return lines.join("\n");
5039
5100
  }
5040
5101
  async function memSessionEnd(input, ctx) {
5041
- if (!existsSync17(ctx.paths.haiveDir)) {
5102
+ if (!existsSync18(ctx.paths.haiveDir)) {
5042
5103
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
5043
5104
  }
5044
5105
  const body = buildBody(input);
@@ -5049,12 +5110,12 @@ async function memSessionEnd(input, ctx) {
5049
5110
  return rel.startsWith("..") ? p : rel;
5050
5111
  });
5051
5112
  const invalidPaths = normalizedFiles.filter(
5052
- (p) => !existsSync17(path82.resolve(ctx.paths.root, p))
5113
+ (p) => !existsSync18(path82.resolve(ctx.paths.root, p))
5053
5114
  );
5054
5115
  if (invalidPaths.length > 0) {
5055
5116
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
5056
5117
  }
5057
- const existing = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir12(ctx.paths.memoriesDir) : [];
5118
+ const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
5058
5119
  const topicMatch = existing.find(
5059
5120
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === input.scope && (!input.module || memory2.frontmatter.module === input.module)
5060
5121
  );
@@ -5235,7 +5296,7 @@ async function trySemanticHits(ctx, task, limit) {
5235
5296
  }
5236
5297
  async function loadModuleContexts2(ctx, modules) {
5237
5298
  if (modules.length === 0) return [];
5238
- if (!existsSync18(ctx.paths.modulesContextDir)) return [];
5299
+ if (!existsSync19(ctx.paths.modulesContextDir)) return [];
5239
5300
  const available = new Set(
5240
5301
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
5241
5302
  );
@@ -5243,42 +5304,42 @@ async function loadModuleContexts2(ctx, modules) {
5243
5304
  for (const m of modules) {
5244
5305
  if (!available.has(m)) continue;
5245
5306
  const file = path92.join(ctx.paths.modulesContextDir, m, "context.md");
5246
- if (existsSync18(file)) {
5307
+ if (existsSync19(file)) {
5247
5308
  out.push({ name: m, content: await readFile32(file, "utf8") });
5248
5309
  }
5249
5310
  }
5250
5311
  return out;
5251
5312
  }
5252
5313
  var GetBriefingInputSchema = {
5253
- task: z17.string().optional().describe(
5314
+ task: z18.string().optional().describe(
5254
5315
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
5255
5316
  ),
5256
- files: z17.array(z17.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
5257
- max_tokens: z17.number().int().positive().default(8e3).describe(
5317
+ files: z18.array(z18.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
5318
+ max_tokens: z18.number().int().positive().default(8e3).describe(
5258
5319
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
5259
5320
  ),
5260
- max_memories: z17.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
5261
- include_project_context: z17.boolean().default(true),
5262
- include_module_contexts: z17.boolean().default(true),
5263
- semantic: z17.boolean().default(true).describe(
5321
+ max_memories: z18.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
5322
+ include_project_context: z18.boolean().default(true),
5323
+ include_module_contexts: z18.boolean().default(true),
5324
+ semantic: z18.boolean().default(true).describe(
5264
5325
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
5265
5326
  ),
5266
- include_stale: z17.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
5267
- track: z17.boolean().default(true).describe("Increment read_count on returned memories"),
5268
- format: z17.enum(["full", "compact", "actions"]).default("full").describe(
5327
+ include_stale: z18.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
5328
+ track: z18.boolean().default(true).describe("Increment read_count on returned memories"),
5329
+ format: z18.enum(["full", "compact", "actions"]).default("full").describe(
5269
5330
  "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."
5270
5331
  ),
5271
- symbols: z17.array(z17.string()).default([]).describe(
5332
+ symbols: z18.array(z18.string()).default([]).describe(
5272
5333
  "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."
5273
5334
  ),
5274
- min_semantic_score: z17.number().min(0).max(1).default(0).describe(
5335
+ min_semantic_score: z18.number().min(0).max(1).default(0).describe(
5275
5336
  "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."
5276
5337
  ),
5277
- budget_preset: z17.enum(["quick", "balanced", "deep"]).optional().describe(
5338
+ budget_preset: z18.enum(["quick", "balanced", "deep"]).optional().describe(
5278
5339
  "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."
5279
5340
  )
5280
5341
  };
5281
- var GetBriefingZod = z17.object(GetBriefingInputSchema);
5342
+ var GetBriefingZod = z18.object(GetBriefingInputSchema);
5282
5343
  async function getBriefing(input, ctx) {
5283
5344
  const resolvedBudget = resolveBriefingBudget2(input.budget_preset, {
5284
5345
  max_tokens: input.max_tokens,
@@ -5294,8 +5355,8 @@ async function getBriefing(input, ctx) {
5294
5355
  let usage = { version: 1, updated_at: "", by_id: {} };
5295
5356
  let byId = /* @__PURE__ */ new Map();
5296
5357
  let lastSession;
5297
- if (existsSync19(ctx.paths.memoriesDir)) {
5298
- const allLoaded = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
5358
+ if (existsSync20(ctx.paths.memoriesDir)) {
5359
+ const allLoaded = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5299
5360
  const recaps = allLoaded.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").sort(
5300
5361
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
5301
5362
  );
@@ -5317,7 +5378,7 @@ async function getBriefing(input, ctx) {
5317
5378
  if (memory2.frontmatter.type === "session_recap") return false;
5318
5379
  return true;
5319
5380
  });
5320
- usage = await loadUsageIndex7(ctx.paths);
5381
+ usage = await loadUsageIndex8(ctx.paths);
5321
5382
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
5322
5383
  const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
5323
5384
  if (input.task && input.semantic) {
@@ -5339,7 +5400,8 @@ async function getBriefing(input, ctx) {
5339
5400
  }
5340
5401
  return;
5341
5402
  }
5342
- const u = getUsage5(usage, fm.id);
5403
+ const u = getUsage6(usage, fm.id);
5404
+ const imp = computeImpact2(fm, u);
5343
5405
  seen.set(fm.id, {
5344
5406
  id: fm.id,
5345
5407
  scope: fm.scope,
@@ -5350,6 +5412,8 @@ async function getBriefing(input, ctx) {
5350
5412
  confidence: deriveConfidence4(fm, u),
5351
5413
  ...fm.status === "draft" || fm.status === "proposed" ? { unverified: true } : {},
5352
5414
  read_count: u.read_count,
5415
+ impact_score: imp.score,
5416
+ impact_tier: imp.tier,
5353
5417
  reasons: [reason],
5354
5418
  match_quality: matchQuality ?? "partial",
5355
5419
  ...score !== void 0 ? { semantic_score: score } : {},
@@ -5393,11 +5457,28 @@ async function getBriefing(input, ctx) {
5393
5457
  }
5394
5458
  }
5395
5459
  }
5460
+ const activatedSkills = /* @__PURE__ */ new Set();
5461
+ for (const [id, m] of seen) {
5462
+ if (m.type !== "skill") continue;
5463
+ const loaded = byId.get(id);
5464
+ if (!loaded) continue;
5465
+ const act = evaluateSkillActivation(loaded.memory.frontmatter, {
5466
+ task: input.task,
5467
+ files: input.files
5468
+ });
5469
+ if (act.applicable && !act.activated) {
5470
+ seen.delete(id);
5471
+ continue;
5472
+ }
5473
+ if (act.applicable && act.activated) activatedSkills.add(id);
5474
+ }
5396
5475
  const ranked = [...seen.values()].sort((a, b) => {
5397
5476
  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);
5398
5477
  const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
5399
- const sa = priorityRank(classifyMemoryPriority(a, byId.get(a.id), input.files, input.symbols)) * 100 + reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
5400
- const sb = priorityRank(classifyMemoryPriority(b, byId.get(b.id), input.files, input.symbols)) * 100 + reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
5478
+ const impactScore = (m) => (m.impact_score ?? 0) * 3;
5479
+ const activationBoost = (m) => activatedSkills.has(m.id) ? 5 : 0;
5480
+ 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);
5481
+ 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);
5401
5482
  return sb - sa;
5402
5483
  });
5403
5484
  for (const mem of ranked.slice(0, briefingMaxMemories)) {
@@ -5413,7 +5494,7 @@ async function getBriefing(input, ctx) {
5413
5494
  memories.push(...ranked.slice(0, briefingMaxMemories));
5414
5495
  if (input.track && memories.length > 0) {
5415
5496
  await trackReads3(ctx.paths, memories.map((m) => m.id));
5416
- const freshUsage = await loadUsageIndex7(ctx.paths);
5497
+ const freshUsage = await loadUsageIndex8(ctx.paths);
5417
5498
  const cfg = await loadConfig3(ctx.paths);
5418
5499
  const rule = {
5419
5500
  minReads: cfg.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads,
@@ -5422,7 +5503,7 @@ async function getBriefing(input, ctx) {
5422
5503
  for (const m of memories) {
5423
5504
  const loaded = byId.get(m.id);
5424
5505
  if (!loaded) continue;
5425
- const u = getUsage5(freshUsage, m.id);
5506
+ const u = getUsage6(freshUsage, m.id);
5426
5507
  if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
5427
5508
  const newFm = { ...loaded.memory.frontmatter, status: "validated" };
5428
5509
  try {
@@ -5434,12 +5515,12 @@ async function getBriefing(input, ctx) {
5434
5515
  }
5435
5516
  }
5436
5517
  }
5437
- const projectContextRaw = input.include_project_context && existsSync19(ctx.paths.projectContext) ? await readFile42(ctx.paths.projectContext, "utf8") : "";
5518
+ const projectContextRaw = input.include_project_context && existsSync20(ctx.paths.projectContext) ? await readFile42(ctx.paths.projectContext, "utf8") : "";
5438
5519
  const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
5439
5520
  const setupWarnings = [];
5440
5521
  let autoContextGenerated = false;
5441
5522
  let projectContext = isTemplateContext ? "" : projectContextRaw;
5442
- if ((isTemplateContext || !existsSync19(ctx.paths.projectContext)) && input.include_project_context) {
5523
+ if ((isTemplateContext || !existsSync20(ctx.paths.projectContext)) && input.include_project_context) {
5443
5524
  const haiveConfig = await loadConfig3(ctx.paths);
5444
5525
  if (haiveConfig.autoContext) {
5445
5526
  const codeMap = await loadCodeMap5(ctx.paths);
@@ -5535,7 +5616,7 @@ ${m.content}`).join("\n\n---\n\n"),
5535
5616
  const totalTokens = projectSlice.estimatedTokens + modulesSlice.estimatedTokens + memoriesSlice.estimatedTokens;
5536
5617
  const decayWarnings = [];
5537
5618
  for (const m of trimmedMemories) {
5538
- const u = getUsage5(usage, m.id);
5619
+ const u = getUsage6(usage, m.id);
5539
5620
  const loaded = byId.get(m.id);
5540
5621
  const createdAt = loaded?.memory.frontmatter.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
5541
5622
  if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
@@ -5600,8 +5681,8 @@ ${m.content}`).join("\n\n---\n\n"),
5600
5681
  actionRequired.push(extractActionItem(m.id, loaded.memory.body));
5601
5682
  }
5602
5683
  }
5603
- if (existsSync19(ctx.paths.memoriesDir)) {
5604
- const allMems = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
5684
+ if (existsSync20(ctx.paths.memoriesDir)) {
5685
+ const allMems = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5605
5686
  for (const { memory: memory2 } of allMems) {
5606
5687
  const fm = memory2.frontmatter;
5607
5688
  if (!fm.requires_human_approval) continue;
@@ -5611,7 +5692,7 @@ ${m.content}`).join("\n\n---\n\n"),
5611
5692
  }
5612
5693
  }
5613
5694
  const pendingDistillFile = pendingDistillPath(ctx);
5614
- if (existsSync19(pendingDistillFile)) {
5695
+ if (existsSync20(pendingDistillFile)) {
5615
5696
  try {
5616
5697
  const raw = await readFile42(pendingDistillFile, "utf8");
5617
5698
  const pd = JSON.parse(raw);
@@ -5640,7 +5721,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5640
5721
  }
5641
5722
  }
5642
5723
  const memoriesEmpty = outputMemories.length === 0;
5643
- const hasMemoriesDir = existsSync19(ctx.paths.memoriesDir);
5724
+ const hasMemoriesDir = existsSync20(ctx.paths.memoriesDir);
5644
5725
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
5645
5726
  const hasUnguessableSignal = outputMemories.some(
5646
5727
  (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore2(m.body) >= GUESSABLE_THRESHOLD
@@ -5687,7 +5768,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5687
5768
  "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."
5688
5769
  );
5689
5770
  }
5690
- if (existsSync19(ctx.paths.haiveDir)) {
5771
+ if (existsSync20(ctx.paths.haiveDir)) {
5691
5772
  await writeBriefingMarker2(ctx.paths, {
5692
5773
  sessionId: process.env.HAIVE_SESSION_ID,
5693
5774
  ...input.task ? { task: input.task } : {},
@@ -5736,17 +5817,17 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5736
5817
  };
5737
5818
  }
5738
5819
  var CodeMapInputSchema = {
5739
- file: z18.string().optional().describe("Filter to files whose path contains this substring"),
5740
- symbol: z18.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
5741
- paths: z18.array(z18.string()).default([]).describe(
5820
+ file: z19.string().optional().describe("Filter to files whose path contains this substring"),
5821
+ symbol: z19.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
5822
+ paths: z19.array(z19.string()).default([]).describe(
5742
5823
  "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."
5743
5824
  ),
5744
- max_files: z18.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
5745
- max_tokens: z18.number().int().positive().optional().describe(
5825
+ max_files: z19.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
5826
+ max_tokens: z19.number().int().positive().optional().describe(
5746
5827
  "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)."
5747
5828
  )
5748
5829
  };
5749
- var CodeMapInputZod = z18.object(CodeMapInputSchema);
5830
+ var CodeMapInputZod = z19.object(CodeMapInputSchema);
5750
5831
  async function codeMapTool(input, ctx) {
5751
5832
  const map = await loadCodeMap22(ctx.paths);
5752
5833
  if (!map) {
@@ -5814,14 +5895,14 @@ function estimateFileEntryTokens(f) {
5814
5895
  return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
5815
5896
  }
5816
5897
  var MemDiffInputSchema = {
5817
- id_a: z19.string().min(1).describe("First memory id"),
5818
- id_b: z19.string().min(1).describe("Second memory id")
5898
+ id_a: z20.string().min(1).describe("First memory id"),
5899
+ id_b: z20.string().min(1).describe("Second memory id")
5819
5900
  };
5820
5901
  async function memDiff(input, ctx) {
5821
- if (!existsSync20(ctx.paths.memoriesDir)) {
5902
+ if (!existsSync21(ctx.paths.memoriesDir)) {
5822
5903
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
5823
5904
  }
5824
- const all = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
5905
+ const all = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
5825
5906
  const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
5826
5907
  const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
5827
5908
  if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
@@ -5854,15 +5935,15 @@ async function memDiff(input, ctx) {
5854
5935
  };
5855
5936
  }
5856
5937
  var GetRecapInputSchema = {
5857
- scope: z20.enum(["personal", "team", "any"]).default("any").describe(
5938
+ scope: z21.enum(["personal", "team", "any"]).default("any").describe(
5858
5939
  "Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
5859
5940
  )
5860
5941
  };
5861
5942
  async function getRecap(input, ctx) {
5862
- if (!existsSync21(ctx.paths.memoriesDir)) {
5943
+ if (!existsSync222(ctx.paths.memoriesDir)) {
5863
5944
  return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
5864
5945
  }
5865
- const all = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
5946
+ const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
5866
5947
  const recaps = all.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").filter(({ memory: memory2 }) => input.scope === "any" || memory2.frontmatter.scope === input.scope).sort(
5867
5948
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
5868
5949
  );
@@ -5885,11 +5966,11 @@ async function getRecap(input, ctx) {
5885
5966
  };
5886
5967
  }
5887
5968
  var MemRelevantToInputSchema = {
5888
- task: z21.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
5889
- files: z21.array(z21.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
5890
- limit: z21.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
5891
- min_semantic_score: z21.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
5892
- format: z21.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
5969
+ task: z22.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
5970
+ files: z22.array(z22.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
5971
+ limit: z22.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
5972
+ min_semantic_score: z22.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
5973
+ format: z22.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
5893
5974
  };
5894
5975
  async function memRelevantTo(input, ctx) {
5895
5976
  const briefingInput = {
@@ -5918,11 +5999,11 @@ async function memRelevantTo(input, ctx) {
5918
5999
  return out;
5919
6000
  }
5920
6001
  var CodeSearchInputSchema = {
5921
- query: z22.string().min(1).describe(
6002
+ query: z23.string().min(1).describe(
5922
6003
  "Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
5923
6004
  ),
5924
- k: z22.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
5925
- min_score: z22.number().min(0).max(1).default(0.2).describe(
6005
+ k: z23.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
6006
+ min_score: z23.number().min(0).max(1).default(0.2).describe(
5926
6007
  "Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
5927
6008
  )
5928
6009
  };
@@ -5951,14 +6032,14 @@ async function codeSearch(input, ctx) {
5951
6032
  return { available: true, hits: result.hits };
5952
6033
  }
5953
6034
  var WhyThisFileInputSchema = {
5954
- path: z23.string().min(1).describe(
6035
+ path: z24.string().min(1).describe(
5955
6036
  "Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
5956
6037
  ),
5957
- git_log_limit: z23.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
5958
- memory_limit: z23.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
6038
+ git_log_limit: z24.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
6039
+ memory_limit: z24.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
5959
6040
  };
5960
6041
  async function whyThisFile(input, ctx) {
5961
- const fileExists = existsSync222(path102.join(ctx.paths.root, input.path));
6042
+ const fileExists = existsSync23(path102.join(ctx.paths.root, input.path));
5962
6043
  const [commits, memories, codeMap] = await Promise.all([
5963
6044
  runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
5964
6045
  collectAnchoredMemories(ctx, input.path, input.memory_limit),
@@ -5999,16 +6080,16 @@ async function whyThisFile(input, ctx) {
5999
6080
  };
6000
6081
  }
6001
6082
  async function collectAnchoredMemories(ctx, filePath, limit) {
6002
- if (!existsSync222(ctx.paths.memoriesDir)) return [];
6003
- const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
6004
- const usage = await loadUsageIndex8(ctx.paths);
6083
+ if (!existsSync23(ctx.paths.memoriesDir)) return [];
6084
+ const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
6085
+ const usage = await loadUsageIndex9(ctx.paths);
6005
6086
  const out = [];
6006
6087
  for (const { memory: memory2 } of all) {
6007
6088
  const fm = memory2.frontmatter;
6008
6089
  if (fm.status === "rejected" || fm.status === "deprecated") continue;
6009
6090
  if (fm.type === "session_recap") continue;
6010
6091
  if (!memoryMatchesAnchorPaths3(memory2, [filePath])) continue;
6011
- const u = getUsage6(usage, fm.id);
6092
+ const u = getUsage7(usage, fm.id);
6012
6093
  out.push({
6013
6094
  id: fm.id,
6014
6095
  type: fm.type,
@@ -6053,67 +6134,20 @@ function runCommand(cmd, args, cwd) {
6053
6134
  });
6054
6135
  }
6055
6136
  var AntiPatternsCheckInputSchema = {
6056
- diff: z24.string().optional().describe(
6137
+ diff: z25.string().optional().describe(
6057
6138
  "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."
6058
6139
  ),
6059
- paths: z24.array(z24.string()).default([]).describe(
6140
+ paths: z25.array(z25.string()).default([]).describe(
6060
6141
  "File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
6061
6142
  ),
6062
- limit: z24.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
6063
- semantic: z24.boolean().default(true).describe(
6143
+ limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
6144
+ semantic: z25.boolean().default(true).describe(
6064
6145
  "When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
6065
6146
  ),
6066
- min_semantic_score: z24.number().min(0).max(1).default(0.45).describe(
6147
+ min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
6067
6148
  "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."
6068
6149
  )
6069
6150
  };
6070
- var CODE_STOPWORDS = /* @__PURE__ */ new Set([
6071
- "import",
6072
- "export",
6073
- "function",
6074
- "return",
6075
- "const",
6076
- "let",
6077
- "var",
6078
- "class",
6079
- "public",
6080
- "private",
6081
- "protected",
6082
- "static",
6083
- "this",
6084
- "true",
6085
- "false",
6086
- "null",
6087
- "undefined",
6088
- "void",
6089
- "async",
6090
- "await",
6091
- "from",
6092
- "type",
6093
- "interface",
6094
- "extends",
6095
- "implements",
6096
- "number",
6097
- "string",
6098
- "boolean",
6099
- "value",
6100
- "default",
6101
- "case",
6102
- "break",
6103
- "continue",
6104
- "throw",
6105
- "catch",
6106
- "finally",
6107
- "else",
6108
- "while",
6109
- "for",
6110
- "new",
6111
- "super",
6112
- "yield",
6113
- "module",
6114
- "require",
6115
- "console"
6116
- ]);
6117
6151
  function tokenizeDiffForLiteral(diff) {
6118
6152
  const lines = diff.split("\n");
6119
6153
  const looksLikeDiff = lines.some((l) => /^[+-]/.test(l));
@@ -6131,10 +6165,10 @@ async function antiPatternsCheck(input, ctx) {
6131
6165
  notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
6132
6166
  };
6133
6167
  }
6134
- if (!existsSync23(ctx.paths.memoriesDir)) {
6168
+ if (!existsSync24(ctx.paths.memoriesDir)) {
6135
6169
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
6136
6170
  }
6137
- const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
6171
+ const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
6138
6172
  const minSemanticScore = input.min_semantic_score ?? 0.45;
6139
6173
  const negative = all.filter(({ memory: memory2 }) => {
6140
6174
  const t = memory2.frontmatter.type;
@@ -6145,7 +6179,8 @@ async function antiPatternsCheck(input, ctx) {
6145
6179
  if (negative.length === 0) {
6146
6180
  return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
6147
6181
  }
6148
- const usage = await loadUsageIndex9(ctx.paths);
6182
+ const usage = await loadUsageIndex10(ctx.paths);
6183
+ const docFreq = buildDocFrequency(negative.map(({ memory: memory2 }) => memory2.body));
6149
6184
  const seen = /* @__PURE__ */ new Map();
6150
6185
  const upsert = (fm, body, reason, score) => {
6151
6186
  const existing = seen.get(fm.id);
@@ -6156,7 +6191,7 @@ async function antiPatternsCheck(input, ctx) {
6156
6191
  }
6157
6192
  return;
6158
6193
  }
6159
- const u = getUsage7(usage, fm.id);
6194
+ const u = getUsage8(usage, fm.id);
6160
6195
  seen.set(fm.id, {
6161
6196
  id: fm.id,
6162
6197
  type: fm.type,
@@ -6179,10 +6214,16 @@ async function antiPatternsCheck(input, ctx) {
6179
6214
  }
6180
6215
  if (input.diff) {
6181
6216
  const tokens = tokenizeDiffForLiteral(input.diff);
6217
+ const added = addedLinesFromDiff(input.diff);
6218
+ const addedText = added.trim().length > 0 ? added : input.diff;
6182
6219
  if (tokens.length > 0) {
6183
6220
  for (const { memory: memory2 } of negative) {
6184
6221
  if (literalMatchesAnyToken3(memory2, tokens)) {
6185
6222
  upsert(memory2.frontmatter, memory2.body, "literal");
6223
+ if (diffHasDistinctiveOverlap(addedText, memory2.body, docFreq)) {
6224
+ const w = seen.get(memory2.frontmatter.id);
6225
+ if (w) w.distinctive_literal = true;
6226
+ }
6186
6227
  }
6187
6228
  }
6188
6229
  }
@@ -6234,12 +6275,12 @@ async function antiPatternsCheck(input, ctx) {
6234
6275
  };
6235
6276
  }
6236
6277
  var MemDistillInputSchema = {
6237
- since_days: z25.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
6238
- min_cluster: z25.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
6239
- type_filter: z25.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
6278
+ since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
6279
+ min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
6280
+ type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
6240
6281
  "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
6241
6282
  ),
6242
- scope: z25.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
6283
+ scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
6243
6284
  };
6244
6285
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
6245
6286
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -6279,11 +6320,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
6279
6320
  "error"
6280
6321
  ]);
6281
6322
  async function memDistill(input, ctx) {
6282
- if (!existsSync24(ctx.paths.memoriesDir)) {
6323
+ if (!existsSync25(ctx.paths.memoriesDir)) {
6283
6324
  return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
6284
6325
  }
6285
6326
  const cutoff = Date.now() - input.since_days * MS_PER_DAY;
6286
- const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
6327
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
6287
6328
  const candidates = all.filter(({ memory: memory2 }) => {
6288
6329
  const fm = memory2.frontmatter;
6289
6330
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -6386,11 +6427,11 @@ function firstHeading(body) {
6386
6427
  return void 0;
6387
6428
  }
6388
6429
  var WhyThisDecisionInputSchema = {
6389
- id: z26.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
6390
- git_log_limit: z26.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
6430
+ id: z27.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
6431
+ git_log_limit: z27.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
6391
6432
  };
6392
6433
  async function whyThisDecision(input, ctx) {
6393
- if (!existsSync25(ctx.paths.memoriesDir)) {
6434
+ if (!existsSync26(ctx.paths.memoriesDir)) {
6394
6435
  return {
6395
6436
  found: false,
6396
6437
  related: [],
@@ -6399,8 +6440,8 @@ async function whyThisDecision(input, ctx) {
6399
6440
  notice: "No .ai/memories directory."
6400
6441
  };
6401
6442
  }
6402
- const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
6403
- const usage = await loadUsageIndex10(ctx.paths);
6443
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
6444
+ const usage = await loadUsageIndex11(ctx.paths);
6404
6445
  const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
6405
6446
  if (!target) {
6406
6447
  return {
@@ -6412,7 +6453,7 @@ async function whyThisDecision(input, ctx) {
6412
6453
  };
6413
6454
  }
6414
6455
  const fm = target.memory.frontmatter;
6415
- const targetUsage = getUsage8(usage, fm.id);
6456
+ const targetUsage = getUsage9(usage, fm.id);
6416
6457
  const decision = {
6417
6458
  id: fm.id,
6418
6459
  type: fm.type,
@@ -6429,7 +6470,7 @@ async function whyThisDecision(input, ctx) {
6429
6470
  const isExplicit = relatedSet.has(memory2.frontmatter.id);
6430
6471
  const isBackLink = (memory2.frontmatter.related_ids ?? []).includes(fm.id);
6431
6472
  if (!isExplicit && !isBackLink) continue;
6432
- const u = getUsage8(usage, memory2.frontmatter.id);
6473
+ const u = getUsage9(usage, memory2.frontmatter.id);
6433
6474
  related.push({
6434
6475
  id: memory2.frontmatter.id,
6435
6476
  type: memory2.frontmatter.type,
@@ -6449,7 +6490,7 @@ async function whyThisDecision(input, ctx) {
6449
6490
  (p) => targetPaths.some((tp) => singlePathsOverlap(p, tp))
6450
6491
  );
6451
6492
  if (overlappingPaths.length === 0) continue;
6452
- const u = getUsage8(usage, memory2.frontmatter.id);
6493
+ const u = getUsage9(usage, memory2.frontmatter.id);
6453
6494
  path_neighbors.push({
6454
6495
  id: memory2.frontmatter.id,
6455
6496
  type: memory2.frontmatter.type,
@@ -6521,22 +6562,22 @@ function runCommand2(cmd, args, cwd) {
6521
6562
  });
6522
6563
  }
6523
6564
  var MemConflictsInputSchema = {
6524
- id: z27.string().min(1).describe("Memory id to check for conflicts."),
6525
- 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)."),
6526
- semantic: z27.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
6565
+ id: z28.string().min(1).describe("Memory id to check for conflicts."),
6566
+ 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)."),
6567
+ semantic: z28.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
6527
6568
  };
6528
6569
  var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
6529
6570
  var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
6530
6571
  async function memConflicts(input, ctx) {
6531
- if (!existsSync26(ctx.paths.memoriesDir)) {
6572
+ if (!existsSync27(ctx.paths.memoriesDir)) {
6532
6573
  return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
6533
6574
  }
6534
- const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
6575
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
6535
6576
  const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
6536
6577
  if (!target) {
6537
6578
  return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
6538
6579
  }
6539
- const usage = await loadUsageIndex11(ctx.paths);
6580
+ const usage = await loadUsageIndex12(ctx.paths);
6540
6581
  const others = all.filter(
6541
6582
  ({ memory: memory2 }) => memory2.frontmatter.id !== input.id && memory2.frontmatter.type !== "session_recap"
6542
6583
  );
@@ -6577,7 +6618,7 @@ async function memConflicts(input, ctx) {
6577
6618
  }
6578
6619
  }
6579
6620
  if (reasons.length === 0) continue;
6580
- const u = getUsage9(usage, fm.id);
6621
+ const u = getUsage10(usage, fm.id);
6581
6622
  conflicts.push({
6582
6623
  id: fm.id,
6583
6624
  type: fm.type,
@@ -6642,15 +6683,15 @@ async function trySemanticSimilarities(ctx, target, others) {
6642
6683
  return map;
6643
6684
  }
6644
6685
  var PreCommitCheckInputSchema = {
6645
- diff: z28.string().optional().describe(
6686
+ diff: z29.string().optional().describe(
6646
6687
  "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`."
6647
6688
  ),
6648
- paths: z28.array(z28.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
6649
- block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
6689
+ paths: z29.array(z29.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
6690
+ block_on: z29.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
6650
6691
  "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
6651
6692
  ),
6652
- semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
6653
- anchored_blocks: z28.boolean().default(false).describe(
6693
+ semantic: z29.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
6694
+ anchored_blocks: z29.boolean().default(false).describe(
6654
6695
  "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."
6655
6696
  )
6656
6697
  };
@@ -6780,7 +6821,12 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
6780
6821
  const hasSemantic = warning.reasons.includes("semantic");
6781
6822
  const semanticScore = warning.semantic_score ?? 0;
6782
6823
  const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
6783
- if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
6824
+ if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && // A literal overlap only corroborates a BLOCK when it is on a token *distinctive*
6825
+ // to this gotcha (rare in the corpus). Sharing a common domain word ("memory",
6826
+ // "scope", "version") — or a version-bump diff — no longer hard-blocks; it falls
6827
+ // through to `review` below. This kills the incidental-token false positives that
6828
+ // made agents work for nothing. A moderate semantic match still corroborates.
6829
+ (warning.distinctive_literal === true || hasSemantic && semanticScore >= 0.45)) {
6784
6830
  if (warning.has_sensor && !warning.reasons.includes("sensor")) {
6785
6831
  return {
6786
6832
  ...warning,
@@ -6951,12 +6997,12 @@ var CONFIG_PATTERNS = [
6951
6997
  var MAX_DIFF_BYTES = 4096;
6952
6998
  var HOT_FILE_MIN = 3;
6953
6999
  var PatternDetectInputSchema = {
6954
- since_days: z29.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
6955
- dry_run: z29.boolean().default(false).describe("When true, report matches without writing any memory files."),
6956
- scope: z29.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
7000
+ since_days: z30.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
7001
+ dry_run: z30.boolean().default(false).describe("When true, report matches without writing any memory files."),
7002
+ scope: z30.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
6957
7003
  };
6958
7004
  async function patternDetect(input, ctx) {
6959
- if (!existsSync27(ctx.paths.haiveDir)) {
7005
+ if (!existsSync28(ctx.paths.haiveDir)) {
6960
7006
  return {
6961
7007
  scanned_events: 0,
6962
7008
  matches: [],
@@ -7085,7 +7131,7 @@ async function patternDetect(input, ctx) {
7085
7131
  fm.id,
7086
7132
  void 0
7087
7133
  );
7088
- if (existsSync27(file)) continue;
7134
+ if (existsSync28(file)) continue;
7089
7135
  await mkdir72(path112.dirname(file), { recursive: true });
7090
7136
  await writeFile12(
7091
7137
  file,
@@ -7130,17 +7176,17 @@ function gitFileDiff(root, file, sinceDays) {
7130
7176
  }
7131
7177
  }
7132
7178
  var MemConflictCandidatesInputSchema = {
7133
- since_days: z30.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
7134
- types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
7135
- min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
7136
- max_pairs: z30.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
7137
- 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."),
7138
- max_topic_pairs: z30.number().int().positive().max(100).default(20).describe(
7179
+ since_days: z31.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
7180
+ types: z31.array(z31.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
7181
+ min_jaccard: z31.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
7182
+ max_pairs: z31.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
7183
+ 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."),
7184
+ max_topic_pairs: z31.number().int().positive().max(100).default(20).describe(
7139
7185
  "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
7140
7186
  )
7141
7187
  };
7142
7188
  async function memConflictCandidates(input, ctx) {
7143
- if (!existsSync28(ctx.paths.memoriesDir)) {
7189
+ if (!existsSync29(ctx.paths.memoriesDir)) {
7144
7190
  return {
7145
7191
  pairs: [],
7146
7192
  topic_status_pairs: [],
@@ -7149,7 +7195,7 @@ async function memConflictCandidates(input, ctx) {
7149
7195
  notice: "No .ai/memories directory."
7150
7196
  };
7151
7197
  }
7152
- const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
7198
+ const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
7153
7199
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
7154
7200
  sinceDays: input.since_days,
7155
7201
  types: input.types,
@@ -7162,7 +7208,7 @@ async function memConflictCandidates(input, ctx) {
7162
7208
  return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
7163
7209
  }
7164
7210
  var MemResolveProjectInputSchema = {
7165
- cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
7211
+ cwd: z32.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
7166
7212
  };
7167
7213
  async function memResolveProject(input, _ctx) {
7168
7214
  void _ctx;
@@ -7175,7 +7221,7 @@ async function memResolveProject(input, _ctx) {
7175
7221
  }
7176
7222
  var MemSuggestTopicInputSchema = {
7177
7223
  type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
7178
- title: z32.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
7224
+ title: z33.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
7179
7225
  };
7180
7226
  async function memSuggestTopic(input, _ctx) {
7181
7227
  void _ctx;
@@ -7183,15 +7229,15 @@ async function memSuggestTopic(input, _ctx) {
7183
7229
  return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
7184
7230
  }
7185
7231
  var MemTimelineInputSchema = {
7186
- memory_id: z33.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
7187
- topic: z33.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
7188
- limit: z33.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
7232
+ memory_id: z34.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
7233
+ topic: z34.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
7234
+ limit: z34.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
7189
7235
  };
7190
7236
  async function memTimeline(input, ctx) {
7191
- if (!existsSync29(ctx.paths.memoriesDir)) {
7237
+ if (!existsSync30(ctx.paths.memoriesDir)) {
7192
7238
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
7193
7239
  }
7194
- const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
7240
+ const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
7195
7241
  const { entries, notice } = collectTimelineEntries(all, {
7196
7242
  memoryId: input.memory_id,
7197
7243
  topic: input.topic,
@@ -7200,9 +7246,9 @@ async function memTimeline(input, ctx) {
7200
7246
  return { entries, total: entries.length, notice };
7201
7247
  }
7202
7248
  var RuntimeJournalAppendInputSchema = {
7203
- message: z34.string().min(1).describe("Short line to append to the runtime session journal"),
7204
- kind: z34.enum(["note", "session_end", "mcp"]).default("note"),
7205
- tool: z34.string().optional().describe("When kind=mcp, which tool name (optional)")
7249
+ message: z35.string().min(1).describe("Short line to append to the runtime session journal"),
7250
+ kind: z35.enum(["note", "session_end", "mcp"]).default("note"),
7251
+ tool: z35.string().optional().describe("When kind=mcp, which tool name (optional)")
7206
7252
  };
7207
7253
  async function runtimeJournalAppend(input, ctx) {
7208
7254
  await appendRuntimeJournalEntry2(ctx.paths, {
@@ -7216,7 +7262,7 @@ async function runtimeJournalAppend(input, ctx) {
7216
7262
  };
7217
7263
  }
7218
7264
  var RuntimeJournalTailInputSchema = {
7219
- limit: z35.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
7265
+ limit: z36.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
7220
7266
  };
7221
7267
  async function runtimeJournalTail(input, ctx) {
7222
7268
  const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
@@ -7226,10 +7272,10 @@ async function runtimeJournalTail(input, ctx) {
7226
7272
  return { entries };
7227
7273
  }
7228
7274
  var BootstrapProjectArgsSchema = {
7229
- module: z36.string().optional().describe(
7275
+ module: z37.string().optional().describe(
7230
7276
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
7231
7277
  ),
7232
- focus: z36.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
7278
+ focus: z37.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
7233
7279
  };
7234
7280
  var ROOT_TEMPLATE = `# Project context
7235
7281
 
@@ -7310,8 +7356,8 @@ ${template}\`\`\`
7310
7356
  };
7311
7357
  }
7312
7358
  var PostTaskArgsSchema = {
7313
- task_summary: z37.string().optional().describe("One sentence describing what you just did"),
7314
- files_touched: z37.array(z37.string()).optional().describe("Files you created or modified during the task")
7359
+ task_summary: z38.string().optional().describe("One sentence describing what you just did"),
7360
+ files_touched: z38.array(z38.string()).optional().describe("Files you created or modified during the task")
7315
7361
  };
7316
7362
  function postTaskPrompt(args, ctx) {
7317
7363
  const taskLine = args.task_summary ? `
@@ -7406,10 +7452,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
7406
7452
  };
7407
7453
  }
7408
7454
  var ImportDocsArgsSchema = {
7409
- content: z38.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
7410
- source: z38.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
7411
- scope: z38.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
7412
- dry_run: z38.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
7455
+ content: z39.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
7456
+ source: z39.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
7457
+ scope: z39.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
7458
+ dry_run: z39.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
7413
7459
  };
7414
7460
  function importDocsPrompt(args, ctx) {
7415
7461
  const sourceLine = args.source ? `
@@ -7472,7 +7518,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7472
7518
  };
7473
7519
  }
7474
7520
  var SERVER_NAME = "haive";
7475
- var SERVER_VERSION = "0.10.9";
7521
+ var SERVER_VERSION = "0.12.0";
7476
7522
  function jsonResult(data) {
7477
7523
  return {
7478
7524
  content: [
@@ -7514,7 +7560,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
7514
7560
  "anti_patterns_check",
7515
7561
  "mem_distill",
7516
7562
  "mem_timeline",
7517
- "mem_conflict_candidates"
7563
+ "mem_conflict_candidates",
7564
+ "mem_feedback"
7518
7565
  ];
7519
7566
  var EXPERIMENTAL_PROFILE_TOOLS = [
7520
7567
  ...MAINTENANCE_PROFILE_TOOLS,
@@ -7546,6 +7593,7 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
7546
7593
  "mem_approve",
7547
7594
  "mem_reject",
7548
7595
  "mem_delete",
7596
+ "mem_feedback",
7549
7597
  "runtime_journal_append",
7550
7598
  "pattern_detect"
7551
7599
  ]);
@@ -8041,6 +8089,28 @@ function createHaiveServer(options = {}) {
8041
8089
  MemRejectInputSchema,
8042
8090
  async (input) => jsonResult(await memReject(input, context))
8043
8091
  );
8092
+ registerTool(
8093
+ "mem_feedback",
8094
+ [
8095
+ "Record whether a memory actually HELPED \u2014 the closed-loop utility signal.",
8096
+ "",
8097
+ "USE right after a memory changed (or failed to change) what you did:",
8098
+ " - outcome='applied' \u2192 the memory steered your work (strong positive signal)",
8099
+ " - outcome='rejected' \u2192 it was wrong/outdated/unhelpful (negative signal)",
8100
+ "",
8101
+ "A read only means a memory was surfaced; 'applied' means it demonstrably helped.",
8102
+ "This powers `haive memory impact` (impact tiers + prune candidates) and future ranking.",
8103
+ "",
8104
+ "PARAMETERS:",
8105
+ " id \u2014 full memory id the feedback is about",
8106
+ " outcome \u2014 'applied' | 'rejected'",
8107
+ " reason \u2014 why it was rejected (optional, stored on the usage record)",
8108
+ "",
8109
+ "RETURNS: { ok, id, outcome, usage:{read_count,applied_count,rejected_count}, impact:{score,tier,signals} }"
8110
+ ].join("\n"),
8111
+ MemFeedbackInputSchema,
8112
+ async (input) => jsonResult(await memFeedback(input, context))
8113
+ );
8044
8114
  registerTool(
8045
8115
  "mem_pending",
8046
8116
  [
@@ -8447,21 +8517,21 @@ function registerMcp(program2) {
8447
8517
  // src/commands/sync.ts
8448
8518
  import { spawnSync as spawnSync4 } from "child_process";
8449
8519
  import { readFile as readFile9, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
8450
- import { existsSync as existsSync30 } from "fs";
8520
+ import { existsSync as existsSync31 } from "fs";
8451
8521
  import path15 from "path";
8452
8522
  import "commander";
8453
8523
  import {
8454
8524
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
8455
8525
  buildFrontmatter as buildFrontmatter6,
8456
8526
  findProjectRoot as findProjectRoot12,
8457
- getUsage as getUsage10,
8527
+ getUsage as getUsage11,
8458
8528
  isAutoPromoteEligible as isAutoPromoteEligible2,
8459
8529
  isDecaying as isDecaying2,
8460
8530
  isStackPackSeed as isStackPackSeed3,
8461
8531
  loadCodeMap as loadCodeMap6,
8462
8532
  loadConfig as loadConfig4,
8463
- loadMemoriesFromDir as loadMemoriesFromDir23,
8464
- loadUsageIndex as loadUsageIndex12,
8533
+ loadMemoriesFromDir as loadMemoriesFromDir24,
8534
+ loadUsageIndex as loadUsageIndex13,
8465
8535
  pullCrossRepoSources,
8466
8536
  resolveHaivePaths as resolveHaivePaths9,
8467
8537
  resolveManifestFiles,
@@ -8484,7 +8554,7 @@ function registerSync(program2) {
8484
8554
  ).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) => {
8485
8555
  const root = findProjectRoot12(opts.dir);
8486
8556
  const paths = resolveHaivePaths9(root);
8487
- if (!existsSync30(paths.memoriesDir)) {
8557
+ if (!existsSync31(paths.memoriesDir)) {
8488
8558
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8489
8559
  process.exitCode = 1;
8490
8560
  return;
@@ -8503,7 +8573,7 @@ function registerSync(program2) {
8503
8573
  let promoted = 0;
8504
8574
  let autoApproved = 0;
8505
8575
  if (opts.verify !== false) {
8506
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
8576
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8507
8577
  for (const { memory: memory2, filePath } of memories) {
8508
8578
  if (memory2.frontmatter.type === "session_recap") {
8509
8579
  if (memory2.frontmatter.status === "stale") {
@@ -8570,13 +8640,13 @@ function registerSync(program2) {
8570
8640
  }
8571
8641
  }
8572
8642
  if (opts.promote !== false) {
8573
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
8574
- const usage = await loadUsageIndex12(paths);
8643
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8644
+ const usage = await loadUsageIndex13(paths);
8575
8645
  const nowMs = Date.now();
8576
8646
  for (const { memory: memory2, filePath } of memories) {
8577
8647
  const fm = memory2.frontmatter;
8578
8648
  if (fm.type === "session_recap") continue;
8579
- if (isAutoPromoteEligible2(fm, getUsage10(usage, fm.id), {
8649
+ if (isAutoPromoteEligible2(fm, getUsage11(usage, fm.id), {
8580
8650
  minReads: autoPromoteMinReads,
8581
8651
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
8582
8652
  })) {
@@ -8622,7 +8692,7 @@ function registerSync(program2) {
8622
8692
  for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
8623
8693
  }
8624
8694
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
8625
- const draftMemories = (await loadMemoriesFromDir23(paths.memoriesDir)).filter(
8695
+ const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
8626
8696
  (m) => m.memory.frontmatter.status === "draft"
8627
8697
  );
8628
8698
  const draftCount = draftMemories.length;
@@ -8657,12 +8727,12 @@ function registerSync(program2) {
8657
8727
  }
8658
8728
  }
8659
8729
  if (!opts.quiet) {
8660
- const allForDecay = await loadMemoriesFromDir23(paths.memoriesDir);
8661
- const usageForDecay = await loadUsageIndex12(paths);
8730
+ const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
8731
+ const usageForDecay = await loadUsageIndex13(paths);
8662
8732
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
8663
8733
  const fm = memory2.frontmatter;
8664
8734
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
8665
- const u = getUsage10(usageForDecay, fm.id);
8735
+ const u = getUsage11(usageForDecay, fm.id);
8666
8736
  return isDecaying2(u, fm.created_at);
8667
8737
  });
8668
8738
  if (decaying.length > 0) {
@@ -8908,8 +8978,8 @@ function bridgeSummaryLine(body) {
8908
8978
  return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
8909
8979
  }
8910
8980
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8911
- if (!existsSync30(memoriesDir)) return;
8912
- const all = await loadMemoriesFromDir23(memoriesDir);
8981
+ if (!existsSync31(memoriesDir)) return;
8982
+ const all = await loadMemoriesFromDir24(memoriesDir);
8913
8983
  const top = all.filter(({ memory: memory2 }) => {
8914
8984
  const s = memory2.frontmatter.status;
8915
8985
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -8934,7 +9004,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
8934
9004
  ` + block + `
8935
9005
 
8936
9006
  ${BRIDGE_END}`;
8937
- const fileExists = existsSync30(bridgeFile);
9007
+ const fileExists = existsSync31(bridgeFile);
8938
9008
  let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
8939
9009
  existing = existing.replace(/\r\n/g, "\n");
8940
9010
  const startIdx = existing.indexOf(BRIDGE_START);
@@ -8985,7 +9055,7 @@ function collectSinceChanges(root, ref) {
8985
9055
  // src/commands/memory-add.ts
8986
9056
  import { createHash as createHash2 } from "crypto";
8987
9057
  import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
8988
- import { existsSync as existsSync31 } from "fs";
9058
+ import { existsSync as existsSync33 } from "fs";
8989
9059
  import path16 from "path";
8990
9060
  import "commander";
8991
9061
  import {
@@ -8993,7 +9063,7 @@ import {
8993
9063
  findProjectRoot as findProjectRoot13,
8994
9064
  inferModulesFromPaths as inferModulesFromPaths3,
8995
9065
  loadConfig as loadConfig5,
8996
- loadMemoriesFromDir as loadMemoriesFromDir24,
9066
+ loadMemoriesFromDir as loadMemoriesFromDir25,
8997
9067
  memoryFilePath as memoryFilePath6,
8998
9068
  resolveHaivePaths as resolveHaivePaths10,
8999
9069
  serializeMemory as serializeMemory12,
@@ -9024,10 +9094,10 @@ function registerMemoryAdd(memory2) {
9024
9094
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
9025
9095
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
9026
9096
  `
9027
- ).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) => {
9097
+ ).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("--activation-keyword <csv>", "skill only: comma-separated keywords that trigger progressive disclosure of this skill").option("--activation-glob <csv>", "skill only: comma-separated path globs that trigger this skill").option("--activation-always", "skill only: always surface this skill (no triggers needed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9028
9098
  const root = findProjectRoot13(opts.dir);
9029
9099
  const paths = resolveHaivePaths10(root);
9030
- if (!existsSync31(paths.haiveDir)) {
9100
+ if (!existsSync33(paths.haiveDir)) {
9031
9101
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9032
9102
  process.exitCode = 1;
9033
9103
  return;
@@ -9035,11 +9105,16 @@ function registerMemoryAdd(memory2) {
9035
9105
  const config = await loadConfig5(paths);
9036
9106
  const userTags = parseCsv2(opts.tags);
9037
9107
  const anchorPaths = parseCsv2(opts.paths ?? opts.files);
9108
+ const activation = opts.type === "skill" && (opts.activationKeyword || opts.activationGlob || opts.activationAlways) ? {
9109
+ keywords: parseCsv2(opts.activationKeyword),
9110
+ globs: parseCsv2(opts.activationGlob),
9111
+ always: Boolean(opts.activationAlways)
9112
+ } : void 0;
9038
9113
  const autoTagsEnabled = opts.autoTag !== false;
9039
9114
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
9040
9115
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
9041
9116
  if (anchorPaths.length > 0) {
9042
- const missing = anchorPaths.filter((p) => !existsSync31(path16.resolve(root, p)));
9117
+ const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
9043
9118
  if (missing.length > 0) {
9044
9119
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
9045
9120
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -9052,7 +9127,7 @@ function registerMemoryAdd(memory2) {
9052
9127
  const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
9053
9128
  let body;
9054
9129
  if (opts.bodyFile !== void 0) {
9055
- if (!existsSync31(opts.bodyFile)) {
9130
+ if (!existsSync33(opts.bodyFile)) {
9056
9131
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9057
9132
  process.exitCode = 1;
9058
9133
  return;
@@ -9068,9 +9143,9 @@ TODO \u2014 write the memory body.
9068
9143
  `;
9069
9144
  }
9070
9145
  const scope = opts.scope ?? config.defaultScope ?? "personal";
9071
- if (existsSync31(paths.memoriesDir)) {
9146
+ if (existsSync33(paths.memoriesDir)) {
9072
9147
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
9073
- const allForHash = await loadMemoriesFromDir24(paths.memoriesDir);
9148
+ const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
9074
9149
  const hashDup = allForHash.find(
9075
9150
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
9076
9151
  );
@@ -9081,8 +9156,8 @@ TODO \u2014 write the memory body.
9081
9156
  return;
9082
9157
  }
9083
9158
  }
9084
- if (opts.topic && existsSync31(paths.memoriesDir)) {
9085
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
9159
+ if (opts.topic && existsSync33(paths.memoriesDir)) {
9160
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9086
9161
  const topicMatch = existing.find(
9087
9162
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
9088
9163
  );
@@ -9092,6 +9167,7 @@ TODO \u2014 write the memory body.
9092
9167
  const newFrontmatter = {
9093
9168
  ...fm,
9094
9169
  revision_count: revisionCount,
9170
+ ...activation ? { activation } : {},
9095
9171
  tags: mergedTags.length ? mergedTags : fm.tags,
9096
9172
  anchor: {
9097
9173
  commit: opts.commit ?? fm.anchor.commit,
@@ -9122,17 +9198,18 @@ TODO \u2014 write the memory body.
9122
9198
  commit: opts.commit,
9123
9199
  topic: opts.topic,
9124
9200
  status: config.defaultStatus === "validated" ? "validated" : void 0,
9125
- sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0
9201
+ sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
9202
+ activation
9126
9203
  });
9127
9204
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9128
9205
  await mkdir11(path16.dirname(file), { recursive: true });
9129
- if (existsSync31(file)) {
9206
+ if (existsSync33(file)) {
9130
9207
  ui.error(`Memory already exists at ${file}`);
9131
9208
  process.exitCode = 1;
9132
9209
  return;
9133
9210
  }
9134
- if (existsSync31(paths.memoriesDir)) {
9135
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
9211
+ if (existsSync33(paths.memoriesDir)) {
9212
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
9136
9213
  const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
9137
9214
  const similar = existing.filter(({ memory: memory3 }) => {
9138
9215
  const id = memory3.frontmatter.id.toLowerCase();
@@ -9223,14 +9300,14 @@ function slugify(value) {
9223
9300
  }
9224
9301
 
9225
9302
  // src/commands/memory-list.ts
9226
- import { existsSync as existsSync33 } from "fs";
9303
+ import { existsSync as existsSync34 } from "fs";
9227
9304
  import path17 from "path";
9228
9305
  import "commander";
9229
9306
  import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
9230
9307
 
9231
9308
  // src/utils/fs.ts
9232
9309
  import {
9233
- loadMemoriesFromDir as loadMemoriesFromDir25,
9310
+ loadMemoriesFromDir as loadMemoriesFromDir26,
9234
9311
  loadMemory,
9235
9312
  listMarkdownFilesRecursive
9236
9313
  } from "@hiveai/core";
@@ -9240,12 +9317,12 @@ function registerMemoryList(memory2) {
9240
9317
  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) => {
9241
9318
  const root = findProjectRoot14(opts.dir);
9242
9319
  const paths = resolveHaivePaths11(root);
9243
- if (!existsSync33(paths.memoriesDir)) {
9320
+ if (!existsSync34(paths.memoriesDir)) {
9244
9321
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9245
9322
  process.exitCode = 1;
9246
9323
  return;
9247
9324
  }
9248
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9325
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9249
9326
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
9250
9327
  const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
9251
9328
  const filtered = all.filter((m) => {
@@ -9315,7 +9392,7 @@ function matchesFilters(loaded, opts) {
9315
9392
 
9316
9393
  // src/commands/memory-promote.ts
9317
9394
  import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
9318
- import { existsSync as existsSync34 } from "fs";
9395
+ import { existsSync as existsSync35 } from "fs";
9319
9396
  import path18 from "path";
9320
9397
  import "commander";
9321
9398
  import {
@@ -9328,12 +9405,12 @@ function registerMemoryPromote(memory2) {
9328
9405
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9329
9406
  const root = findProjectRoot15(opts.dir);
9330
9407
  const paths = resolveHaivePaths12(root);
9331
- if (!existsSync34(paths.memoriesDir)) {
9408
+ if (!existsSync35(paths.memoriesDir)) {
9332
9409
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9333
9410
  process.exitCode = 1;
9334
9411
  return;
9335
9412
  }
9336
- const teamAndModule = await loadMemoriesFromDir25(paths.memoriesDir);
9413
+ const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
9337
9414
  const alreadyShared = teamAndModule.find(
9338
9415
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
9339
9416
  );
@@ -9347,7 +9424,7 @@ function registerMemoryPromote(memory2) {
9347
9424
  }
9348
9425
  return;
9349
9426
  }
9350
- const all = await loadMemoriesFromDir25(paths.personalDir);
9427
+ const all = await loadMemoriesFromDir26(paths.personalDir);
9351
9428
  const found = all.find((m) => m.memory.frontmatter.id === id);
9352
9429
  if (!found) {
9353
9430
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -9373,7 +9450,7 @@ function registerMemoryPromote(memory2) {
9373
9450
  }
9374
9451
 
9375
9452
  // src/commands/memory-approve.ts
9376
- import { existsSync as existsSync35 } from "fs";
9453
+ import { existsSync as existsSync36 } from "fs";
9377
9454
  import { writeFile as writeFile16 } from "fs/promises";
9378
9455
  import path19 from "path";
9379
9456
  import "commander";
@@ -9386,12 +9463,12 @@ function registerMemoryApprove(memory2) {
9386
9463
  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) => {
9387
9464
  const root = findProjectRoot16(opts.dir);
9388
9465
  const paths = resolveHaivePaths13(root);
9389
- if (!existsSync35(paths.memoriesDir)) {
9466
+ if (!existsSync36(paths.memoriesDir)) {
9390
9467
  ui.error(`No .ai/memories at ${root}.`);
9391
9468
  process.exitCode = 1;
9392
9469
  return;
9393
9470
  }
9394
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9471
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9395
9472
  if (opts.all || opts.pending) {
9396
9473
  const candidates = all.filter((m) => {
9397
9474
  const s = m.memory.frontmatter.status;
@@ -9445,7 +9522,7 @@ function registerMemoryApprove(memory2) {
9445
9522
 
9446
9523
  // src/commands/memory-update.ts
9447
9524
  import { readFile as readFile11, writeFile as writeFile17 } from "fs/promises";
9448
- import { existsSync as existsSync36 } from "fs";
9525
+ import { existsSync as existsSync37 } from "fs";
9449
9526
  import path20 from "path";
9450
9527
  import "commander";
9451
9528
  import {
@@ -9457,12 +9534,12 @@ function registerMemoryUpdate(memory2) {
9457
9534
  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) => {
9458
9535
  const root = findProjectRoot17(opts.dir);
9459
9536
  const paths = resolveHaivePaths14(root);
9460
- if (!existsSync36(paths.memoriesDir)) {
9537
+ if (!existsSync37(paths.memoriesDir)) {
9461
9538
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9462
9539
  process.exitCode = 1;
9463
9540
  return;
9464
9541
  }
9465
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
9542
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9466
9543
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
9467
9544
  if (!loaded) {
9468
9545
  ui.error(`No memory with id "${id}".`);
@@ -9499,7 +9576,7 @@ function registerMemoryUpdate(memory2) {
9499
9576
  if (opts.author !== void 0) updated.push("author");
9500
9577
  let newBody;
9501
9578
  if (opts.bodyFile !== void 0) {
9502
- if (!existsSync36(opts.bodyFile)) {
9579
+ if (!existsSync37(opts.bodyFile)) {
9503
9580
  ui.error(`--body-file not found: ${opts.bodyFile}`);
9504
9581
  process.exitCode = 1;
9505
9582
  return;
@@ -9545,15 +9622,15 @@ function parseCsv3(value) {
9545
9622
 
9546
9623
  // src/commands/memory-auto-promote.ts
9547
9624
  import { writeFile as writeFile18 } from "fs/promises";
9548
- import { existsSync as existsSync37 } from "fs";
9625
+ import { existsSync as existsSync38 } from "fs";
9549
9626
  import path21 from "path";
9550
9627
  import "commander";
9551
9628
  import {
9552
9629
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
9553
9630
  findProjectRoot as findProjectRoot18,
9554
- getUsage as getUsage11,
9631
+ getUsage as getUsage12,
9555
9632
  isAutoPromoteEligible as isAutoPromoteEligible3,
9556
- loadUsageIndex as loadUsageIndex13,
9633
+ loadUsageIndex as loadUsageIndex14,
9557
9634
  resolveHaivePaths as resolveHaivePaths15,
9558
9635
  serializeMemory as serializeMemory16
9559
9636
  } from "@hiveai/core";
@@ -9565,7 +9642,7 @@ function registerMemoryAutoPromote(memory2) {
9565
9642
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9566
9643
  const root = findProjectRoot18(opts.dir);
9567
9644
  const paths = resolveHaivePaths15(root);
9568
- if (!existsSync37(paths.memoriesDir)) {
9645
+ if (!existsSync38(paths.memoriesDir)) {
9569
9646
  ui.error(`No .ai/memories at ${root}.`);
9570
9647
  process.exitCode = 1;
9571
9648
  return;
@@ -9574,10 +9651,10 @@ function registerMemoryAutoPromote(memory2) {
9574
9651
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
9575
9652
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
9576
9653
  };
9577
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
9578
- const usage = await loadUsageIndex13(paths);
9654
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
9655
+ const usage = await loadUsageIndex14(paths);
9579
9656
  const eligible = memories.filter(
9580
- ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage11(usage, memory3.frontmatter.id), rule)
9657
+ ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
9581
9658
  );
9582
9659
  if (eligible.length === 0) {
9583
9660
  ui.info(
@@ -9587,7 +9664,7 @@ function registerMemoryAutoPromote(memory2) {
9587
9664
  }
9588
9665
  let written = 0;
9589
9666
  for (const { memory: mem, filePath } of eligible) {
9590
- const u = getUsage11(usage, mem.frontmatter.id);
9667
+ const u = getUsage12(usage, mem.frontmatter.id);
9591
9668
  console.log(
9592
9669
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
9593
9670
  );
@@ -9608,7 +9685,7 @@ function registerMemoryAutoPromote(memory2) {
9608
9685
 
9609
9686
  // src/commands/memory-edit.ts
9610
9687
  import { spawn as spawn3 } from "child_process";
9611
- import { existsSync as existsSync38 } from "fs";
9688
+ import { existsSync as existsSync39 } from "fs";
9612
9689
  import { readFile as readFile12 } from "fs/promises";
9613
9690
  import path23 from "path";
9614
9691
  import "commander";
@@ -9621,12 +9698,12 @@ function registerMemoryEdit(memory2) {
9621
9698
  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) => {
9622
9699
  const root = findProjectRoot19(opts.dir);
9623
9700
  const paths = resolveHaivePaths16(root);
9624
- if (!existsSync38(paths.memoriesDir)) {
9701
+ if (!existsSync39(paths.memoriesDir)) {
9625
9702
  ui.error(`No .ai/memories at ${root}.`);
9626
9703
  process.exitCode = 1;
9627
9704
  return;
9628
9705
  }
9629
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9706
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9630
9707
  const found = all.find((m) => m.memory.frontmatter.id === id);
9631
9708
  if (!found) {
9632
9709
  ui.error(`No memory with id "${id}".`);
@@ -9661,15 +9738,15 @@ function runEditor(editor, file) {
9661
9738
  }
9662
9739
 
9663
9740
  // src/commands/memory-for-files.ts
9664
- import { existsSync as existsSync39 } from "fs";
9741
+ import { existsSync as existsSync40 } from "fs";
9665
9742
  import path24 from "path";
9666
9743
  import "commander";
9667
9744
  import {
9668
9745
  deriveConfidence as deriveConfidence9,
9669
9746
  findProjectRoot as findProjectRoot20,
9670
- getUsage as getUsage12,
9747
+ getUsage as getUsage13,
9671
9748
  inferModulesFromPaths as inferModulesFromPaths4,
9672
- loadUsageIndex as loadUsageIndex14,
9749
+ loadUsageIndex as loadUsageIndex15,
9673
9750
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
9674
9751
  resolveHaivePaths as resolveHaivePaths17
9675
9752
  } from "@hiveai/core";
@@ -9677,13 +9754,13 @@ function registerMemoryForFiles(memory2) {
9677
9754
  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) => {
9678
9755
  const root = findProjectRoot20(opts.dir);
9679
9756
  const paths = resolveHaivePaths17(root);
9680
- if (!existsSync39(paths.memoriesDir)) {
9757
+ if (!existsSync40(paths.memoriesDir)) {
9681
9758
  ui.error(`No .ai/memories at ${root}.`);
9682
9759
  process.exitCode = 1;
9683
9760
  return;
9684
9761
  }
9685
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9686
- const usage = await loadUsageIndex14(paths);
9762
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9763
+ const usage = await loadUsageIndex15(paths);
9687
9764
  const inferred = inferModulesFromPaths4(files);
9688
9765
  const byAnchor = [];
9689
9766
  const byModule = [];
@@ -9781,7 +9858,7 @@ function printGroup(root, label, loaded, usage) {
9781
9858
  \u2014 ${label} \u2014`));
9782
9859
  for (const { memory: mem, filePath } of loaded) {
9783
9860
  const fm = mem.frontmatter;
9784
- const u = getUsage12(usage, fm.id);
9861
+ const u = getUsage13(usage, fm.id);
9785
9862
  const conf = deriveConfidence9(fm, u);
9786
9863
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
9787
9864
  console.log(` ${ui.dim(path24.relative(root, filePath))}`);
@@ -9789,13 +9866,13 @@ function printGroup(root, label, loaded, usage) {
9789
9866
  }
9790
9867
 
9791
9868
  // src/commands/memory-hot.ts
9792
- import { existsSync as existsSync40 } from "fs";
9869
+ import { existsSync as existsSync41 } from "fs";
9793
9870
  import path25 from "path";
9794
9871
  import "commander";
9795
9872
  import {
9796
9873
  findProjectRoot as findProjectRoot21,
9797
- getUsage as getUsage13,
9798
- loadUsageIndex as loadUsageIndex15,
9874
+ getUsage as getUsage14,
9875
+ loadUsageIndex as loadUsageIndex16,
9799
9876
  resolveHaivePaths as resolveHaivePaths18
9800
9877
  } from "@hiveai/core";
9801
9878
  function registerMemoryHot(memory2) {
@@ -9804,23 +9881,23 @@ function registerMemoryHot(memory2) {
9804
9881
  ).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) => {
9805
9882
  const root = findProjectRoot21(opts.dir);
9806
9883
  const paths = resolveHaivePaths18(root);
9807
- if (!existsSync40(paths.memoriesDir)) {
9884
+ if (!existsSync41(paths.memoriesDir)) {
9808
9885
  ui.error(`No .ai/memories at ${root}.`);
9809
9886
  process.exitCode = 1;
9810
9887
  return;
9811
9888
  }
9812
9889
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
9813
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9814
- const usage = await loadUsageIndex15(paths);
9890
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9891
+ const usage = await loadUsageIndex16(paths);
9815
9892
  const candidates = all.filter(({ memory: mem }) => {
9816
9893
  const fm = mem.frontmatter;
9817
9894
  if (opts.status && fm.status !== opts.status) return false;
9818
9895
  if (opts.status === void 0 && fm.status !== "draft" && fm.status !== "proposed") {
9819
9896
  return false;
9820
9897
  }
9821
- return getUsage13(usage, fm.id).read_count >= threshold;
9898
+ return getUsage14(usage, fm.id).read_count >= threshold;
9822
9899
  }).sort(
9823
- (a, b) => getUsage13(usage, b.memory.frontmatter.id).read_count - getUsage13(usage, a.memory.frontmatter.id).read_count
9900
+ (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
9824
9901
  );
9825
9902
  if (candidates.length === 0) {
9826
9903
  ui.info(`No hot memories (threshold=${threshold}).`);
@@ -9828,7 +9905,7 @@ function registerMemoryHot(memory2) {
9828
9905
  }
9829
9906
  for (const { memory: mem, filePath } of candidates) {
9830
9907
  const fm = mem.frontmatter;
9831
- const u = getUsage13(usage, fm.id);
9908
+ const u = getUsage14(usage, fm.id);
9832
9909
  console.log(
9833
9910
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
9834
9911
  );
@@ -9843,7 +9920,7 @@ function registerMemoryHot(memory2) {
9843
9920
 
9844
9921
  // src/commands/memory-tried.ts
9845
9922
  import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
9846
- import { existsSync as existsSync41 } from "fs";
9923
+ import { existsSync as existsSync43 } from "fs";
9847
9924
  import path26 from "path";
9848
9925
  import "commander";
9849
9926
  import {
@@ -9873,7 +9950,7 @@ function registerMemoryTried(memory2) {
9873
9950
  ).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) => {
9874
9951
  const root = findProjectRoot22(opts.dir);
9875
9952
  const paths = resolveHaivePaths19(root);
9876
- if (!existsSync41(paths.haiveDir)) {
9953
+ if (!existsSync43(paths.haiveDir)) {
9877
9954
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9878
9955
  process.exitCode = 1;
9879
9956
  return;
@@ -9901,7 +9978,7 @@ function registerMemoryTried(memory2) {
9901
9978
  }
9902
9979
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9903
9980
  await mkdir13(path26.dirname(file), { recursive: true });
9904
- if (existsSync41(file)) {
9981
+ if (existsSync43(file)) {
9905
9982
  ui.error(`Memory already exists at ${file}`);
9906
9983
  process.exitCode = 1;
9907
9984
  return;
@@ -9919,7 +9996,7 @@ function parseCsv4(value) {
9919
9996
 
9920
9997
  // src/commands/memory-seed.ts
9921
9998
  import { readFile as readFile13 } from "fs/promises";
9922
- import { existsSync as existsSync43 } from "fs";
9999
+ import { existsSync as existsSync44 } from "fs";
9923
10000
  import path27 from "path";
9924
10001
  import "commander";
9925
10002
  import {
@@ -9959,7 +10036,7 @@ function registerMemorySeed(memory2) {
9959
10036
  }
9960
10037
  return;
9961
10038
  }
9962
- if (!existsSync43(paths.haiveDir)) {
10039
+ if (!existsSync44(paths.haiveDir)) {
9963
10040
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9964
10041
  process.exitCode = 1;
9965
10042
  return;
@@ -10015,26 +10092,26 @@ function registerMemorySeed(memory2) {
10015
10092
  }
10016
10093
 
10017
10094
  // src/commands/memory-pending.ts
10018
- import { existsSync as existsSync44 } from "fs";
10095
+ import { existsSync as existsSync45 } from "fs";
10019
10096
  import path28 from "path";
10020
10097
  import "commander";
10021
10098
  import {
10022
10099
  findProjectRoot as findProjectRoot24,
10023
- getUsage as getUsage14,
10024
- loadUsageIndex as loadUsageIndex16,
10100
+ getUsage as getUsage15,
10101
+ loadUsageIndex as loadUsageIndex17,
10025
10102
  resolveHaivePaths as resolveHaivePaths21
10026
10103
  } from "@hiveai/core";
10027
10104
  function registerMemoryPending(memory2) {
10028
10105
  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) => {
10029
10106
  const root = findProjectRoot24(opts.dir);
10030
10107
  const paths = resolveHaivePaths21(root);
10031
- if (!existsSync44(paths.memoriesDir)) {
10108
+ if (!existsSync45(paths.memoriesDir)) {
10032
10109
  ui.error(`No .ai/memories at ${root}.`);
10033
10110
  process.exitCode = 1;
10034
10111
  return;
10035
10112
  }
10036
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10037
- const usage = await loadUsageIndex16(paths);
10113
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10114
+ const usage = await loadUsageIndex17(paths);
10038
10115
  const filterFn = ({ memory: mem }) => {
10039
10116
  if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
10040
10117
  if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
@@ -10047,7 +10124,7 @@ function registerMemoryPending(memory2) {
10047
10124
  return;
10048
10125
  }
10049
10126
  pending.sort(
10050
- (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
10127
+ (a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
10051
10128
  );
10052
10129
  const now = Date.now();
10053
10130
  const drafts = pending.filter((m) => m.memory.frontmatter.status === "draft");
@@ -10056,7 +10133,7 @@ function registerMemoryPending(memory2) {
10056
10133
  console.log(ui.bold(`Proposed (${proposed.length}) \u2014 awaiting team validation`));
10057
10134
  for (const { memory: mem, filePath } of proposed) {
10058
10135
  const fm = mem.frontmatter;
10059
- const u = getUsage14(usage, fm.id);
10136
+ const u = getUsage15(usage, fm.id);
10060
10137
  const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
10061
10138
  const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
10062
10139
  console.log(
@@ -10071,7 +10148,7 @@ function registerMemoryPending(memory2) {
10071
10148
  console.log(ui.bold(`Draft (${drafts.length}) \u2014 created but not yet activated`));
10072
10149
  for (const { memory: mem, filePath } of drafts) {
10073
10150
  const fm = mem.frontmatter;
10074
- const u = getUsage14(usage, fm.id);
10151
+ const u = getUsage15(usage, fm.id);
10075
10152
  const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
10076
10153
  const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
10077
10154
  console.log(
@@ -10086,7 +10163,7 @@ function registerMemoryPending(memory2) {
10086
10163
  }
10087
10164
 
10088
10165
  // src/commands/memory-query.ts
10089
- import { existsSync as existsSync45 } from "fs";
10166
+ import { existsSync as existsSync46 } from "fs";
10090
10167
  import path29 from "path";
10091
10168
  import "commander";
10092
10169
  import {
@@ -10103,7 +10180,7 @@ function registerMemoryQuery(memory2) {
10103
10180
  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) => {
10104
10181
  const root = findProjectRoot25(opts.dir);
10105
10182
  const paths = resolveHaivePaths22(root);
10106
- if (!existsSync45(paths.memoriesDir)) {
10183
+ if (!existsSync46(paths.memoriesDir)) {
10107
10184
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
10108
10185
  process.exitCode = 1;
10109
10186
  return;
@@ -10114,7 +10191,7 @@ function registerMemoryQuery(memory2) {
10114
10191
  return;
10115
10192
  }
10116
10193
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
10117
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10194
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10118
10195
  const passesFilters2 = (mem) => {
10119
10196
  const fm = mem.frontmatter;
10120
10197
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -10162,26 +10239,26 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
10162
10239
 
10163
10240
  // src/commands/memory-reject.ts
10164
10241
  import { writeFile as writeFile20 } from "fs/promises";
10165
- import { existsSync as existsSync46 } from "fs";
10242
+ import { existsSync as existsSync47 } from "fs";
10166
10243
  import "commander";
10167
10244
  import {
10168
10245
  findProjectRoot as findProjectRoot26,
10169
- loadUsageIndex as loadUsageIndex17,
10170
- recordRejection as recordRejection2,
10246
+ loadUsageIndex as loadUsageIndex18,
10247
+ recordRejection as recordRejection3,
10171
10248
  resolveHaivePaths as resolveHaivePaths23,
10172
- saveUsageIndex as saveUsageIndex3,
10249
+ saveUsageIndex as saveUsageIndex4,
10173
10250
  serializeMemory as serializeMemory18
10174
10251
  } from "@hiveai/core";
10175
10252
  function registerMemoryReject(memory2) {
10176
10253
  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) => {
10177
10254
  const root = findProjectRoot26(opts.dir);
10178
10255
  const paths = resolveHaivePaths23(root);
10179
- if (!existsSync46(paths.memoriesDir)) {
10256
+ if (!existsSync47(paths.memoriesDir)) {
10180
10257
  ui.error(`No .ai/memories at ${root}.`);
10181
10258
  process.exitCode = 1;
10182
10259
  return;
10183
10260
  }
10184
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
10261
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
10185
10262
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
10186
10263
  if (!loaded) {
10187
10264
  ui.error(`No memory with id "${id}".`);
@@ -10200,9 +10277,9 @@ function registerMemoryReject(memory2) {
10200
10277
  }),
10201
10278
  "utf8"
10202
10279
  );
10203
- const idx = await loadUsageIndex17(paths);
10204
- recordRejection2(idx, id, opts.reason ?? null);
10205
- await saveUsageIndex3(paths, idx);
10280
+ const idx = await loadUsageIndex18(paths);
10281
+ recordRejection3(idx, id, opts.reason ?? null);
10282
+ await saveUsageIndex4(paths, idx);
10206
10283
  const u = idx.by_id[id];
10207
10284
  ui.success(
10208
10285
  `Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
@@ -10212,27 +10289,27 @@ function registerMemoryReject(memory2) {
10212
10289
  }
10213
10290
 
10214
10291
  // src/commands/memory-rm.ts
10215
- import { existsSync as existsSync47 } from "fs";
10292
+ import { existsSync as existsSync48 } from "fs";
10216
10293
  import { unlink as unlink3 } from "fs/promises";
10217
10294
  import path30 from "path";
10218
10295
  import { createInterface as createInterface2 } from "readline/promises";
10219
10296
  import "commander";
10220
10297
  import {
10221
10298
  findProjectRoot as findProjectRoot27,
10222
- loadUsageIndex as loadUsageIndex18,
10299
+ loadUsageIndex as loadUsageIndex19,
10223
10300
  resolveHaivePaths as resolveHaivePaths24,
10224
- saveUsageIndex as saveUsageIndex4
10301
+ saveUsageIndex as saveUsageIndex5
10225
10302
  } from "@hiveai/core";
10226
10303
  function registerMemoryRm(memory2) {
10227
10304
  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) => {
10228
10305
  const root = findProjectRoot27(opts.dir);
10229
10306
  const paths = resolveHaivePaths24(root);
10230
- if (!existsSync47(paths.memoriesDir)) {
10307
+ if (!existsSync48(paths.memoriesDir)) {
10231
10308
  ui.error(`No .ai/memories at ${root}.`);
10232
10309
  process.exitCode = 1;
10233
10310
  return;
10234
10311
  }
10235
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10312
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10236
10313
  const found = all.find((m) => m.memory.frontmatter.id === id);
10237
10314
  if (!found) {
10238
10315
  ui.error(`No memory with id "${id}".`);
@@ -10252,10 +10329,10 @@ function registerMemoryRm(memory2) {
10252
10329
  await unlink3(found.filePath);
10253
10330
  ui.success(`Deleted ${rel}`);
10254
10331
  if (!opts.keepUsage) {
10255
- const idx = await loadUsageIndex18(paths);
10332
+ const idx = await loadUsageIndex19(paths);
10256
10333
  if (idx.by_id[id]) {
10257
10334
  delete idx.by_id[id];
10258
- await saveUsageIndex4(paths, idx);
10335
+ await saveUsageIndex5(paths, idx);
10259
10336
  ui.info("Removed usage entry");
10260
10337
  }
10261
10338
  }
@@ -10263,27 +10340,27 @@ function registerMemoryRm(memory2) {
10263
10340
  }
10264
10341
 
10265
10342
  // src/commands/memory-show.ts
10266
- import { existsSync as existsSync48 } from "fs";
10343
+ import { existsSync as existsSync49 } from "fs";
10267
10344
  import { readFile as readFile14 } from "fs/promises";
10268
10345
  import path31 from "path";
10269
10346
  import "commander";
10270
10347
  import {
10271
10348
  deriveConfidence as deriveConfidence10,
10272
10349
  findProjectRoot as findProjectRoot28,
10273
- getUsage as getUsage15,
10274
- loadUsageIndex as loadUsageIndex19,
10350
+ getUsage as getUsage16,
10351
+ loadUsageIndex as loadUsageIndex20,
10275
10352
  resolveHaivePaths as resolveHaivePaths25
10276
10353
  } from "@hiveai/core";
10277
10354
  function registerMemoryShow(memory2) {
10278
10355
  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) => {
10279
10356
  const root = findProjectRoot28(opts.dir);
10280
10357
  const paths = resolveHaivePaths25(root);
10281
- if (!existsSync48(paths.memoriesDir)) {
10358
+ if (!existsSync49(paths.memoriesDir)) {
10282
10359
  ui.error(`No .ai/memories at ${root}.`);
10283
10360
  process.exitCode = 1;
10284
10361
  return;
10285
10362
  }
10286
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10363
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10287
10364
  const found = all.find((m) => m.memory.frontmatter.id === id);
10288
10365
  if (!found) {
10289
10366
  ui.error(`No memory with id "${id}".`);
@@ -10295,8 +10372,8 @@ function registerMemoryShow(memory2) {
10295
10372
  return;
10296
10373
  }
10297
10374
  const fm = found.memory.frontmatter;
10298
- const usage = await loadUsageIndex19(paths);
10299
- const u = getUsage15(usage, fm.id);
10375
+ const usage = await loadUsageIndex20(paths);
10376
+ const u = getUsage16(usage, fm.id);
10300
10377
  const conf = deriveConfidence10(fm, u);
10301
10378
  console.log(ui.bold(fm.id));
10302
10379
  console.log(`${ui.dim("scope:")} ${fm.scope}${fm.module ? ` / ${fm.module}` : ""}`);
@@ -10322,38 +10399,38 @@ function registerMemoryShow(memory2) {
10322
10399
  }
10323
10400
 
10324
10401
  // src/commands/memory-stats.ts
10325
- import { existsSync as existsSync49 } from "fs";
10402
+ import { existsSync as existsSync50 } from "fs";
10326
10403
  import path33 from "path";
10327
10404
  import "commander";
10328
10405
  import {
10329
10406
  deriveConfidence as deriveConfidence11,
10330
10407
  findProjectRoot as findProjectRoot29,
10331
- getUsage as getUsage16,
10332
- loadUsageIndex as loadUsageIndex20,
10408
+ getUsage as getUsage17,
10409
+ loadUsageIndex as loadUsageIndex21,
10333
10410
  resolveHaivePaths as resolveHaivePaths26
10334
10411
  } from "@hiveai/core";
10335
10412
  function registerMemoryStats(memory2) {
10336
10413
  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) => {
10337
10414
  const root = findProjectRoot29(opts.dir);
10338
10415
  const paths = resolveHaivePaths26(root);
10339
- if (!existsSync49(paths.memoriesDir)) {
10416
+ if (!existsSync50(paths.memoriesDir)) {
10340
10417
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10341
10418
  process.exitCode = 1;
10342
10419
  return;
10343
10420
  }
10344
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10345
- const usage = await loadUsageIndex20(paths);
10421
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10422
+ const usage = await loadUsageIndex21(paths);
10346
10423
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10347
10424
  if (target.length === 0) {
10348
10425
  ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
10349
10426
  return;
10350
10427
  }
10351
10428
  target.sort(
10352
- (a, b) => getUsage16(usage, b.memory.frontmatter.id).read_count - getUsage16(usage, a.memory.frontmatter.id).read_count
10429
+ (a, b) => getUsage17(usage, b.memory.frontmatter.id).read_count - getUsage17(usage, a.memory.frontmatter.id).read_count
10353
10430
  );
10354
10431
  for (const { memory: mem, filePath } of target) {
10355
10432
  const fm = mem.frontmatter;
10356
- const u = getUsage16(usage, fm.id);
10433
+ const u = getUsage17(usage, fm.id);
10357
10434
  const conf = deriveConfidence11(fm, u);
10358
10435
  console.log(
10359
10436
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`
@@ -10366,14 +10443,156 @@ function registerMemoryStats(memory2) {
10366
10443
  });
10367
10444
  }
10368
10445
 
10446
+ // src/commands/memory-impact.ts
10447
+ import { existsSync as existsSync51 } from "fs";
10448
+ import "commander";
10449
+ import {
10450
+ compareImpact,
10451
+ computeImpact as computeImpact3,
10452
+ findProjectRoot as findProjectRoot30,
10453
+ getUsage as getUsage18,
10454
+ loadUsageIndex as loadUsageIndex23,
10455
+ resolveHaivePaths as resolveHaivePaths27,
10456
+ summarizeImpact
10457
+ } from "@hiveai/core";
10458
+ function registerMemoryImpact(memory2) {
10459
+ memory2.command("impact").description(
10460
+ "Score memories by demonstrated utility (reads + applied outcomes + sensor fires vs rejections, staleness, dormancy) and surface prune candidates."
10461
+ ).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) => {
10462
+ const root = findProjectRoot30(opts.dir);
10463
+ const paths = resolveHaivePaths27(root);
10464
+ if (!existsSync51(paths.memoriesDir)) {
10465
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10466
+ process.exitCode = 1;
10467
+ return;
10468
+ }
10469
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10470
+ const usageIndex = await loadUsageIndex23(paths);
10471
+ let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
10472
+ const fm = mem.frontmatter;
10473
+ return {
10474
+ id: fm.id,
10475
+ type: fm.type,
10476
+ scope: fm.scope,
10477
+ status: fm.status,
10478
+ impact: computeImpact3(fm, getUsage18(usageIndex, fm.id))
10479
+ };
10480
+ });
10481
+ if (opts.prune) rows = rows.filter((r) => r.impact.pruneCandidate);
10482
+ if (opts.tier) {
10483
+ const tier = opts.tier;
10484
+ rows = rows.filter((r) => r.impact.tier === tier);
10485
+ }
10486
+ rows.sort((a, b) => compareImpact(a.impact, b.impact));
10487
+ const summary = summarizeImpact(all.map((m) => computeImpact3(m.memory.frontmatter, getUsage18(usageIndex, m.memory.frontmatter.id))));
10488
+ if (opts.json) {
10489
+ console.log(JSON.stringify({ root, summary, rows }, null, 2));
10490
+ return;
10491
+ }
10492
+ if (rows.length === 0) {
10493
+ ui.info(opts.prune ? "No prune candidates \u2014 every memory earns its keep." : "No memories matched.");
10494
+ return;
10495
+ }
10496
+ console.log(ui.bold(`hAIve memory impact \u2014 ${root}`));
10497
+ console.log(
10498
+ ui.dim(
10499
+ `${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`
10500
+ )
10501
+ );
10502
+ console.log();
10503
+ console.log(`${"score".padStart(5)} ${"tier".padEnd(7)} ${pad("id", 52)} ${"prune".padEnd(5)} signals`);
10504
+ console.log("\u2500".repeat(108));
10505
+ for (const r of rows) {
10506
+ const score = r.impact.score.toFixed(2).padStart(5);
10507
+ const tier = tierBadge(r.impact.tier).padEnd(7);
10508
+ const prune = r.impact.pruneCandidate ? ui.yellow("prune") : " ";
10509
+ console.log(`${score} ${tier} ${pad(r.id, 52)} ${prune} ${ui.dim(r.impact.signals.join(", ") || "no signals")}`);
10510
+ }
10511
+ if (!opts.prune && summary.prune_candidates > 0) {
10512
+ console.log();
10513
+ console.log(ui.dim(`Tip: \`haive memory impact --prune\` lists the ${summary.prune_candidates} prune candidate(s).`));
10514
+ }
10515
+ });
10516
+ }
10517
+ function tierBadge(tier) {
10518
+ switch (tier) {
10519
+ case "high":
10520
+ return ui.green(tier);
10521
+ case "medium":
10522
+ return ui.yellow(tier);
10523
+ case "dormant":
10524
+ return ui.dim(tier);
10525
+ default:
10526
+ return tier;
10527
+ }
10528
+ }
10529
+ function pad(value, width) {
10530
+ if (value.length <= width) return value.padEnd(width);
10531
+ return value.slice(0, width - 1) + "\u2026";
10532
+ }
10533
+
10534
+ // src/commands/memory-feedback.ts
10535
+ import { existsSync as existsSync53 } from "fs";
10536
+ import "commander";
10537
+ import {
10538
+ computeImpact as computeImpact4,
10539
+ findProjectRoot as findProjectRoot31,
10540
+ getUsage as getUsage19,
10541
+ loadUsageIndex as loadUsageIndex24,
10542
+ recordApplied as recordApplied2,
10543
+ recordRejection as recordRejection4,
10544
+ resolveHaivePaths as resolveHaivePaths28,
10545
+ saveUsageIndex as saveUsageIndex6
10546
+ } from "@hiveai/core";
10547
+ function registerMemoryFeedback(memory2) {
10548
+ memory2.command("feedback <id>").description(
10549
+ "Record whether a memory actually helped \u2014 the closed-loop utility signal (mirror of the mem_feedback MCP tool). 'applied' = it steered your work; 'rejected' = it was wrong/unhelpful. Feeds `haive memory impact`."
10550
+ ).option("--applied", "the memory changed what you did (positive signal)", false).option("--rejected", "the memory was wrong/outdated/unhelpful (negative signal)", false).option("--reason <text>", "why it was rejected (stored on the usage record)").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
10551
+ if (opts.applied === opts.rejected) {
10552
+ ui.error("Specify exactly one of --applied or --rejected.");
10553
+ process.exitCode = 1;
10554
+ return;
10555
+ }
10556
+ const root = findProjectRoot31(opts.dir);
10557
+ const paths = resolveHaivePaths28(root);
10558
+ if (!existsSync53(paths.memoriesDir)) {
10559
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10560
+ process.exitCode = 1;
10561
+ return;
10562
+ }
10563
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10564
+ const target = all.find((m) => m.memory.frontmatter.id === id);
10565
+ if (!target) {
10566
+ ui.error(`No memory with id '${id}'.`);
10567
+ process.exitCode = 1;
10568
+ return;
10569
+ }
10570
+ const index = await loadUsageIndex24(paths);
10571
+ const outcome = opts.applied ? "applied" : "rejected";
10572
+ if (opts.applied) recordApplied2(index, id);
10573
+ else recordRejection4(index, id, opts.reason ?? null);
10574
+ await saveUsageIndex6(paths, index);
10575
+ const usage = getUsage19(index, id);
10576
+ const impact = computeImpact4(target.memory.frontmatter, usage);
10577
+ if (opts.json) {
10578
+ console.log(JSON.stringify({ id, outcome, usage, impact }, null, 2));
10579
+ return;
10580
+ }
10581
+ ui.success(`Recorded '${outcome}' for ${id}`);
10582
+ ui.info(
10583
+ `applied=${usage.applied_count} \xB7 rejected=${usage.rejected_count} \xB7 read=${usage.read_count} \u2192 impact ${impact.score.toFixed(2)} (${impact.tier})`
10584
+ );
10585
+ });
10586
+ }
10587
+
10369
10588
  // src/commands/memory-verify.ts
10370
10589
  import { writeFile as writeFile21 } from "fs/promises";
10371
- import { existsSync as existsSync50 } from "fs";
10590
+ import { existsSync as existsSync54 } from "fs";
10372
10591
  import path34 from "path";
10373
10592
  import "commander";
10374
10593
  import {
10375
- findProjectRoot as findProjectRoot30,
10376
- resolveHaivePaths as resolveHaivePaths27,
10594
+ findProjectRoot as findProjectRoot32,
10595
+ resolveHaivePaths as resolveHaivePaths29,
10377
10596
  serializeMemory as serializeMemory19,
10378
10597
  verifyAnchor as verifyAnchor3
10379
10598
  } from "@hiveai/core";
@@ -10381,9 +10600,9 @@ function registerMemoryVerify(memory2) {
10381
10600
  memory2.command("verify").description(
10382
10601
  "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"
10383
10602
  ).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) => {
10384
- const root = findProjectRoot30(opts.dir);
10385
- const paths = resolveHaivePaths27(root);
10386
- if (!existsSync50(paths.memoriesDir)) {
10603
+ const root = findProjectRoot32(opts.dir);
10604
+ const paths = resolveHaivePaths29(root);
10605
+ if (!existsSync54(paths.memoriesDir)) {
10387
10606
  if (opts.json) {
10388
10607
  console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
10389
10608
  } else {
@@ -10392,7 +10611,7 @@ function registerMemoryVerify(memory2) {
10392
10611
  process.exitCode = 1;
10393
10612
  return;
10394
10613
  }
10395
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
10614
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10396
10615
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10397
10616
  if (opts.id && targets.length === 0) {
10398
10617
  if (opts.json) {
@@ -10504,24 +10723,24 @@ function applyVerification2(mem, result) {
10504
10723
 
10505
10724
  // src/commands/memory-import.ts
10506
10725
  import { readFile as readFile15 } from "fs/promises";
10507
- import { existsSync as existsSync51 } from "fs";
10726
+ import { existsSync as existsSync55 } from "fs";
10508
10727
  import "commander";
10509
10728
  import {
10510
- findProjectRoot as findProjectRoot31,
10511
- resolveHaivePaths as resolveHaivePaths28
10729
+ findProjectRoot as findProjectRoot33,
10730
+ resolveHaivePaths as resolveHaivePaths30
10512
10731
  } from "@hiveai/core";
10513
10732
  function registerMemoryImport(memory2) {
10514
10733
  memory2.command("import").description(
10515
10734
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
10516
10735
  ).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) => {
10517
- const root = findProjectRoot31(opts.dir);
10518
- const paths = resolveHaivePaths28(root);
10519
- if (!existsSync51(paths.haiveDir)) {
10736
+ const root = findProjectRoot33(opts.dir);
10737
+ const paths = resolveHaivePaths30(root);
10738
+ if (!existsSync55(paths.haiveDir)) {
10520
10739
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10521
10740
  process.exitCode = 1;
10522
10741
  return;
10523
10742
  }
10524
- if (!existsSync51(opts.from)) {
10743
+ if (!existsSync55(opts.from)) {
10525
10744
  ui.error(`File not found: ${opts.from}`);
10526
10745
  process.exitCode = 1;
10527
10746
  return;
@@ -10554,14 +10773,14 @@ function registerMemoryImport(memory2) {
10554
10773
  }
10555
10774
 
10556
10775
  // src/commands/memory-import-changelog.ts
10557
- import { existsSync as existsSync53 } from "fs";
10776
+ import { existsSync as existsSync56 } from "fs";
10558
10777
  import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
10559
10778
  import path35 from "path";
10560
10779
  import "commander";
10561
10780
  import {
10562
10781
  buildFrontmatter as buildFrontmatter9,
10563
- findProjectRoot as findProjectRoot32,
10564
- resolveHaivePaths as resolveHaivePaths29,
10782
+ findProjectRoot as findProjectRoot34,
10783
+ resolveHaivePaths as resolveHaivePaths31,
10565
10784
  serializeMemory as serializeMemory20
10566
10785
  } from "@hiveai/core";
10567
10786
  function parseChangelog(content) {
@@ -10626,10 +10845,10 @@ function registerMemoryImportChangelog(memory2) {
10626
10845
  "--versions <csv>",
10627
10846
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
10628
10847
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10629
- const root = findProjectRoot32(opts.dir);
10630
- const paths = resolveHaivePaths29(root);
10848
+ const root = findProjectRoot34(opts.dir);
10849
+ const paths = resolveHaivePaths31(root);
10631
10850
  const changelogPath = path35.resolve(root, opts.fromChangelog);
10632
- if (!existsSync53(changelogPath)) {
10851
+ if (!existsSync56(changelogPath)) {
10633
10852
  ui.error(`CHANGELOG not found: ${changelogPath}`);
10634
10853
  process.exitCode = 1;
10635
10854
  return;
@@ -10716,17 +10935,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
10716
10935
  }
10717
10936
 
10718
10937
  // src/commands/memory-digest.ts
10719
- import { existsSync as existsSync54 } from "fs";
10938
+ import { existsSync as existsSync57 } from "fs";
10720
10939
  import { writeFile as writeFile24 } from "fs/promises";
10721
10940
  import path36 from "path";
10722
10941
  import "commander";
10723
10942
  import {
10724
10943
  deriveConfidence as deriveConfidence12,
10725
- findProjectRoot as findProjectRoot33,
10726
- getUsage as getUsage17,
10727
- loadMemoriesFromDir as loadMemoriesFromDir26,
10728
- loadUsageIndex as loadUsageIndex21,
10729
- resolveHaivePaths as resolveHaivePaths30
10944
+ findProjectRoot as findProjectRoot35,
10945
+ getUsage as getUsage20,
10946
+ loadMemoriesFromDir as loadMemoriesFromDir27,
10947
+ loadUsageIndex as loadUsageIndex25,
10948
+ resolveHaivePaths as resolveHaivePaths32
10730
10949
  } from "@hiveai/core";
10731
10950
  var CONFIDENCE_EMOJI = {
10732
10951
  unverified: "\u2B1C",
@@ -10739,9 +10958,9 @@ function registerMemoryDigest(program2) {
10739
10958
  program2.command("digest").description(
10740
10959
  "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"
10741
10960
  ).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) => {
10742
- const root = findProjectRoot33(opts.dir);
10743
- const paths = resolveHaivePaths30(root);
10744
- if (!existsSync54(paths.memoriesDir)) {
10961
+ const root = findProjectRoot35(opts.dir);
10962
+ const paths = resolveHaivePaths32(root);
10963
+ if (!existsSync57(paths.memoriesDir)) {
10745
10964
  ui.error("No .ai/memories found. Run `haive init` first.");
10746
10965
  process.exitCode = 1;
10747
10966
  return;
@@ -10749,8 +10968,8 @@ function registerMemoryDigest(program2) {
10749
10968
  const days = Math.max(1, Number(opts.days ?? 7));
10750
10969
  const scopeFilter = opts.scope ?? "team";
10751
10970
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
10752
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
10753
- const usage = await loadUsageIndex21(paths);
10971
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10972
+ const usage = await loadUsageIndex25(paths);
10754
10973
  const recent = all.filter(({ memory: mem }) => {
10755
10974
  const fm = mem.frontmatter;
10756
10975
  if (fm.type === "session_recap") return false;
@@ -10781,7 +11000,7 @@ function registerMemoryDigest(program2) {
10781
11000
  lines.push(``);
10782
11001
  for (const { memory: mem } of mems) {
10783
11002
  const fm = mem.frontmatter;
10784
- const u = getUsage17(usage, fm.id);
11003
+ const u = getUsage20(usage, fm.id);
10785
11004
  const confidence = deriveConfidence12(fm, u);
10786
11005
  const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
10787
11006
  const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
@@ -10824,21 +11043,21 @@ function registerMemoryDigest(program2) {
10824
11043
 
10825
11044
  // src/commands/session-end.ts
10826
11045
  import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
10827
- import { existsSync as existsSync55 } from "fs";
11046
+ import { existsSync as existsSync58 } from "fs";
10828
11047
  import { spawn as spawn4 } from "child_process";
10829
11048
  import path37 from "path";
10830
11049
  import "commander";
10831
11050
  import {
10832
11051
  buildFrontmatter as buildFrontmatter10,
10833
- findProjectRoot as findProjectRoot34,
10834
- loadMemoriesFromDir as loadMemoriesFromDir27,
11052
+ findProjectRoot as findProjectRoot36,
11053
+ loadMemoriesFromDir as loadMemoriesFromDir28,
10835
11054
  memoryFilePath as memoryFilePath9,
10836
- resolveHaivePaths as resolveHaivePaths31,
11055
+ resolveHaivePaths as resolveHaivePaths33,
10837
11056
  serializeMemory as serializeMemory21
10838
11057
  } from "@hiveai/core";
10839
11058
  async function buildAutoRecap(paths) {
10840
11059
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
10841
- if (!existsSync55(obsFile)) return await buildGitAutoRecap(paths);
11060
+ if (!existsSync58(obsFile)) return await buildGitAutoRecap(paths);
10842
11061
  const raw = await readFile17(obsFile, "utf8").catch(() => "");
10843
11062
  if (!raw.trim()) return await buildGitAutoRecap(paths);
10844
11063
  const lines = raw.split("\n").filter(Boolean);
@@ -11027,9 +11246,9 @@ function registerSessionEnd(session2) {
11027
11246
  --next "Add integration tests for webhook signature validation"
11028
11247
  `
11029
11248
  ).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) => {
11030
- const root = findProjectRoot34(opts.dir);
11031
- const paths = resolveHaivePaths31(root);
11032
- if (!existsSync55(paths.haiveDir)) {
11249
+ const root = findProjectRoot36(opts.dir);
11250
+ const paths = resolveHaivePaths33(root);
11251
+ if (!existsSync58(paths.haiveDir)) {
11033
11252
  if (opts.auto || opts.quiet) return;
11034
11253
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11035
11254
  process.exitCode = 1;
@@ -11062,7 +11281,7 @@ function registerSessionEnd(session2) {
11062
11281
  });
11063
11282
  const topic = recapTopic2(scope, opts.module);
11064
11283
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
11065
- const missingPaths = filesTouched.filter((p) => !existsSync55(path37.resolve(root, p)));
11284
+ const missingPaths = filesTouched.filter((p) => !existsSync58(path37.resolve(root, p)));
11066
11285
  if (missingPaths.length > 0 && !opts.quiet) {
11067
11286
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
11068
11287
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
@@ -11070,11 +11289,11 @@ function registerSessionEnd(session2) {
11070
11289
  const cleanupObservations = async () => {
11071
11290
  if (!opts.auto) return;
11072
11291
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11073
- if (existsSync55(obsFile)) await rm2(obsFile).catch(() => {
11292
+ if (existsSync58(obsFile)) await rm2(obsFile).catch(() => {
11074
11293
  });
11075
11294
  };
11076
- if (existsSync55(paths.memoriesDir)) {
11077
- const existing = await loadMemoriesFromDir27(paths.memoriesDir);
11295
+ if (existsSync58(paths.memoriesDir)) {
11296
+ const existing = await loadMemoriesFromDir28(paths.memoriesDir);
11078
11297
  const topicMatch = existing.find(
11079
11298
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
11080
11299
  );
@@ -11135,15 +11354,15 @@ function normalizeAnchorPath(root, filePath) {
11135
11354
  }
11136
11355
 
11137
11356
  // src/commands/snapshot.ts
11138
- import { existsSync as existsSync56 } from "fs";
11357
+ import { existsSync as existsSync59 } from "fs";
11139
11358
  import { readdir as readdir4 } from "fs/promises";
11140
11359
  import path38 from "path";
11141
11360
  import "commander";
11142
11361
  import {
11143
11362
  diffContract,
11144
- findProjectRoot as findProjectRoot35,
11363
+ findProjectRoot as findProjectRoot37,
11145
11364
  loadConfig as loadConfig7,
11146
- resolveHaivePaths as resolveHaivePaths32,
11365
+ resolveHaivePaths as resolveHaivePaths34,
11147
11366
  snapshotContract
11148
11367
  } from "@hiveai/core";
11149
11368
  function registerSnapshot(program2) {
@@ -11168,16 +11387,16 @@ function registerSnapshot(program2) {
11168
11387
  "--format <format>",
11169
11388
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
11170
11389
  ).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) => {
11171
- const root = findProjectRoot35(opts.dir);
11172
- const paths = resolveHaivePaths32(root);
11173
- if (!existsSync56(paths.haiveDir)) {
11390
+ const root = findProjectRoot37(opts.dir);
11391
+ const paths = resolveHaivePaths34(root);
11392
+ if (!existsSync59(paths.haiveDir)) {
11174
11393
  ui.error("No .ai/ found. Run `haive init` first.");
11175
11394
  process.exitCode = 1;
11176
11395
  return;
11177
11396
  }
11178
11397
  if (opts.list) {
11179
11398
  const contractsDir = path38.join(paths.haiveDir, "contracts");
11180
- if (!existsSync56(contractsDir)) {
11399
+ if (!existsSync59(contractsDir)) {
11181
11400
  console.log(ui.dim("No contract snapshots found."));
11182
11401
  return;
11183
11402
  }
@@ -11301,16 +11520,16 @@ function detectFormat(filePath) {
11301
11520
  }
11302
11521
 
11303
11522
  // src/commands/hub.ts
11304
- import { existsSync as existsSync57 } from "fs";
11523
+ import { existsSync as existsSync60 } from "fs";
11305
11524
  import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
11306
11525
  import path39 from "path";
11307
11526
  import { spawnSync as spawnSync5 } from "child_process";
11308
11527
  import "commander";
11309
11528
  import {
11310
- findProjectRoot as findProjectRoot36,
11529
+ findProjectRoot as findProjectRoot38,
11311
11530
  loadConfig as loadConfig8,
11312
- loadMemoriesFromDir as loadMemoriesFromDir28,
11313
- resolveHaivePaths as resolveHaivePaths33,
11531
+ loadMemoriesFromDir as loadMemoriesFromDir29,
11532
+ resolveHaivePaths as resolveHaivePaths35,
11314
11533
  saveConfig as saveConfig3,
11315
11534
  serializeMemory as serializeMemory23
11316
11535
  } from "@hiveai/core";
@@ -11392,8 +11611,8 @@ Next steps:
11392
11611
  haive hub push --commit --message "feat: add payment API contract memories"
11393
11612
  `
11394
11613
  ).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) => {
11395
- const root = findProjectRoot36(opts.dir);
11396
- const paths = resolveHaivePaths33(root);
11614
+ const root = findProjectRoot38(opts.dir);
11615
+ const paths = resolveHaivePaths35(root);
11397
11616
  const config = await loadConfig8(paths);
11398
11617
  if (!config.hubPath) {
11399
11618
  ui.error(
@@ -11403,7 +11622,7 @@ Next steps:
11403
11622
  return;
11404
11623
  }
11405
11624
  const hubRoot = path39.resolve(root, config.hubPath);
11406
- if (!existsSync57(hubRoot)) {
11625
+ if (!existsSync60(hubRoot)) {
11407
11626
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
11408
11627
  process.exitCode = 1;
11409
11628
  return;
@@ -11411,7 +11630,7 @@ Next steps:
11411
11630
  const projectName = path39.basename(root);
11412
11631
  const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
11413
11632
  await mkdir16(destDir, { recursive: true });
11414
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
11633
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
11415
11634
  const shared = all.filter(
11416
11635
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
11417
11636
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -11461,8 +11680,8 @@ Next steps:
11461
11680
  hub.command("pull").description(
11462
11681
  "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"
11463
11682
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11464
- const root = findProjectRoot36(opts.dir);
11465
- const paths = resolveHaivePaths33(root);
11683
+ const root = findProjectRoot38(opts.dir);
11684
+ const paths = resolveHaivePaths35(root);
11466
11685
  const config = await loadConfig8(paths);
11467
11686
  if (!config.hubPath) {
11468
11687
  ui.error(
@@ -11473,7 +11692,7 @@ Next steps:
11473
11692
  }
11474
11693
  const hubRoot = path39.resolve(root, config.hubPath);
11475
11694
  const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
11476
- if (!existsSync57(hubSharedDir)) {
11695
+ if (!existsSync60(hubSharedDir)) {
11477
11696
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
11478
11697
  return;
11479
11698
  }
@@ -11520,15 +11739,15 @@ Next steps:
11520
11739
  );
11521
11740
  });
11522
11741
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
11523
- const root = findProjectRoot36(opts.dir);
11524
- const paths = resolveHaivePaths33(root);
11742
+ const root = findProjectRoot38(opts.dir);
11743
+ const paths = resolveHaivePaths35(root);
11525
11744
  const config = await loadConfig8(paths);
11526
11745
  console.log(ui.bold("Hub status"));
11527
11746
  console.log(
11528
11747
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
11529
11748
  );
11530
11749
  const sharedDir = path39.join(paths.memoriesDir, "shared");
11531
- if (existsSync57(sharedDir)) {
11750
+ if (existsSync60(sharedDir)) {
11532
11751
  const { readdir: readdir7 } = await import("fs/promises");
11533
11752
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
11534
11753
  console.log(`
@@ -11540,7 +11759,7 @@ Next steps:
11540
11759
  } else {
11541
11760
  console.log(ui.dim(" No imported shared memories yet."));
11542
11761
  }
11543
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
11762
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
11544
11763
  const outgoing = all.filter(
11545
11764
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
11546
11765
  );
@@ -11557,17 +11776,17 @@ Next steps:
11557
11776
 
11558
11777
  // src/commands/stats.ts
11559
11778
  import "commander";
11560
- import { existsSync as existsSync58 } from "fs";
11779
+ import { existsSync as existsSync61 } from "fs";
11561
11780
  import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
11562
11781
  import path40 from "path";
11563
11782
  import {
11564
11783
  aggregateUsage,
11565
- findProjectRoot as findProjectRoot37,
11566
- loadMemoriesFromDir as loadMemoriesFromDir29,
11567
- loadUsageIndex as loadUsageIndex23,
11784
+ findProjectRoot as findProjectRoot39,
11785
+ loadMemoriesFromDir as loadMemoriesFromDir30,
11786
+ loadUsageIndex as loadUsageIndex26,
11568
11787
  parseSince,
11569
11788
  readUsageEvents as readUsageEvents2,
11570
- resolveHaivePaths as resolveHaivePaths34,
11789
+ resolveHaivePaths as resolveHaivePaths36,
11571
11790
  usageLogSize
11572
11791
  } from "@hiveai/core";
11573
11792
  function registerStats(program2) {
@@ -11576,8 +11795,8 @@ function registerStats(program2) {
11576
11795
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
11577
11796
  void 0
11578
11797
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11579
- const root = findProjectRoot37(opts.dir);
11580
- const paths = resolveHaivePaths34(root);
11798
+ const root = findProjectRoot39(opts.dir);
11799
+ const paths = resolveHaivePaths36(root);
11581
11800
  if (opts.exportReport) {
11582
11801
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
11583
11802
  return;
@@ -11623,9 +11842,9 @@ function registerStats(program2) {
11623
11842
  const maxCount = aggregate.by_tool[0]?.count ?? 1;
11624
11843
  for (const t of aggregate.by_tool.slice(0, 20)) {
11625
11844
  const bar = "\u2588".repeat(Math.max(1, Math.round(t.count / maxCount * 30)));
11626
- const pct = (t.count / aggregate.total * 100).toFixed(1);
11845
+ const pct2 = (t.count / aggregate.total * 100).toFixed(1);
11627
11846
  console.log(
11628
- ` ${t.tool.padEnd(28)} ${ui.green(bar)} ${ui.bold(String(t.count))} ${ui.dim(`(${pct}%, last ${t.last_used.slice(0, 19)})`)}`
11847
+ ` ${t.tool.padEnd(28)} ${ui.green(bar)} ${ui.bold(String(t.count))} ${ui.dim(`(${pct2}%, last ${t.last_used.slice(0, 19)})`)}`
11629
11848
  );
11630
11849
  }
11631
11850
  });
@@ -11635,8 +11854,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11635
11854
  const size = await usageLogSize(paths);
11636
11855
  let events = await readUsageEvents2(paths);
11637
11856
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
11638
- if (existsSync58(paths.memoriesDir)) {
11639
- const mems = await loadMemoriesFromDir29(paths.memoriesDir);
11857
+ if (existsSync61(paths.memoriesDir)) {
11858
+ const mems = await loadMemoriesFromDir30(paths.memoriesDir);
11640
11859
  for (const { memory: memory2 } of mems) {
11641
11860
  const fm = memory2.frontmatter;
11642
11861
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -11650,7 +11869,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11650
11869
  const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
11651
11870
  let memoryHitsLeader = null;
11652
11871
  try {
11653
- const usageIdx = await loadUsageIndex23(paths);
11872
+ const usageIdx = await loadUsageIndex26(paths);
11654
11873
  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);
11655
11874
  memoryHitsLeader = tops[0] ?? null;
11656
11875
  } catch {
@@ -11682,7 +11901,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11682
11901
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
11683
11902
  }
11684
11903
  async function renderMemoryHits(paths, opts) {
11685
- const index = await loadUsageIndex23(paths);
11904
+ const index = await loadUsageIndex26(paths);
11686
11905
  const since = parseSince(opts.since ?? "30d");
11687
11906
  const sinceMs = since ? new Date(since).getTime() : null;
11688
11907
  const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
@@ -11730,13 +11949,13 @@ import { performance } from "perf_hooks";
11730
11949
  import "commander";
11731
11950
  import {
11732
11951
  estimateTokens as estimateTokens3,
11733
- findProjectRoot as findProjectRoot38,
11734
- resolveHaivePaths as resolveHaivePaths35
11952
+ findProjectRoot as findProjectRoot40,
11953
+ resolveHaivePaths as resolveHaivePaths37
11735
11954
  } from "@hiveai/core";
11736
11955
  function registerBench(program2) {
11737
11956
  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) => {
11738
- const root = findProjectRoot38(opts.dir);
11739
- const paths = resolveHaivePaths35(root);
11957
+ const root = findProjectRoot40(opts.dir);
11958
+ const paths = resolveHaivePaths37(root);
11740
11959
  const ctx = { paths };
11741
11960
  const task = opts.task ?? "audit dependencies for security risks";
11742
11961
  const scenarios = [
@@ -11855,11 +12074,11 @@ function summarize(name, t0, payload, notes) {
11855
12074
  }
11856
12075
 
11857
12076
  // src/commands/benchmark.ts
11858
- import { existsSync as existsSync59 } from "fs";
12077
+ import { existsSync as existsSync63 } from "fs";
11859
12078
  import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
11860
12079
  import path41 from "path";
11861
12080
  import "commander";
11862
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot39 } from "@hiveai/core";
12081
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
11863
12082
  function registerBenchmark(program2) {
11864
12083
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
11865
12084
  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) => {
@@ -11899,18 +12118,18 @@ function registerBenchmark(program2) {
11899
12118
  function resolveBenchmarkRoot(dir) {
11900
12119
  const candidate = dir ?? "benchmarks/agent-benchmark";
11901
12120
  if (path41.isAbsolute(candidate)) return candidate;
11902
- const projectRoot = findProjectRoot39(process.cwd());
12121
+ const projectRoot = findProjectRoot41(process.cwd());
11903
12122
  return path41.join(projectRoot, candidate);
11904
12123
  }
11905
12124
  async function collectRows(root) {
11906
- if (!existsSync59(root)) throw new Error(`Benchmark directory not found: ${root}`);
12125
+ if (!existsSync63(root)) throw new Error(`Benchmark directory not found: ${root}`);
11907
12126
  const entries = await readdir5(root, { withFileTypes: true });
11908
12127
  const rows = [];
11909
12128
  for (const entry of entries) {
11910
12129
  if (!entry.isDirectory()) continue;
11911
12130
  const fixtureDir = path41.join(root, entry.name);
11912
12131
  const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11913
- if (!existsSync59(reportFile)) continue;
12132
+ if (!existsSync63(reportFile)) continue;
11914
12133
  const report = await readFile19(reportFile, "utf8");
11915
12134
  rows.push(parseAgentReport(entry.name, report));
11916
12135
  }
@@ -11999,21 +12218,188 @@ function escapeRegExp(value) {
11999
12218
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12000
12219
  }
12001
12220
 
12002
- // src/commands/memory-suggest.ts
12003
- import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
12004
- import { existsSync as existsSync60 } from "fs";
12221
+ // src/commands/eval.ts
12222
+ import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
12223
+ import { existsSync as existsSync64 } from "fs";
12005
12224
  import path43 from "path";
12006
12225
  import "commander";
12226
+ import {
12227
+ aggregateRetrieval,
12228
+ aggregateSensors,
12229
+ buildReport,
12230
+ findProjectRoot as findProjectRoot42,
12231
+ resolveHaivePaths as resolveHaivePaths38,
12232
+ scoreRetrievalCase,
12233
+ scoreSensorCase,
12234
+ synthesizeSelfEvalCases
12235
+ } from "@hiveai/core";
12236
+ function registerEval(program2) {
12237
+ program2.command("eval").description(
12238
+ "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."
12239
+ ).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("-d, --dir <dir>", "project root").action(async (opts) => {
12240
+ const root = findProjectRoot42(opts.dir);
12241
+ const paths = resolveHaivePaths38(root);
12242
+ if (!existsSync64(paths.memoriesDir)) {
12243
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12244
+ process.exitCode = 1;
12245
+ return;
12246
+ }
12247
+ const k = Math.max(1, parseInt(opts.top ?? "8", 10) || 8);
12248
+ const ctx = { paths };
12249
+ const spec = await resolveSpec(opts, paths.memoriesDir);
12250
+ if ((spec.retrieval?.length ?? 0) === 0 && (spec.sensors?.length ?? 0) === 0) {
12251
+ ui.warn("No eval cases (no anchored memories and no --spec). Nothing to score.");
12252
+ return;
12253
+ }
12254
+ let retrievalAgg = null;
12255
+ if (spec.retrieval && spec.retrieval.length > 0) {
12256
+ const results = [];
12257
+ for (const c of spec.retrieval) {
12258
+ const surfaced = await runRetrieval(c, k, ctx);
12259
+ results.push(scoreRetrievalCase(c.name, c.expect_ids, surfaced));
12260
+ }
12261
+ retrievalAgg = aggregateRetrieval(results);
12262
+ }
12263
+ let sensorAgg = null;
12264
+ if (spec.sensors && spec.sensors.length > 0) {
12265
+ const results = [];
12266
+ for (const c of spec.sensors) {
12267
+ const fired = await runSensorCase(c, ctx);
12268
+ results.push(scoreSensorCase(c.name, c.expect_fire_ids, fired));
12269
+ }
12270
+ sensorAgg = aggregateSensors(results);
12271
+ }
12272
+ const report = buildReport(retrievalAgg, sensorAgg);
12273
+ if (opts.json) {
12274
+ console.log(JSON.stringify({ root, k, report }, null, 2));
12275
+ } else {
12276
+ const md = renderMarkdown2(root, k, report);
12277
+ if (opts.out) {
12278
+ const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12279
+ await writeFile29(outFile, md, "utf8");
12280
+ ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12281
+ } else {
12282
+ console.log(md);
12283
+ }
12284
+ }
12285
+ if (opts.failUnder !== void 0) {
12286
+ const threshold = Number(opts.failUnder);
12287
+ if (Number.isNaN(threshold)) {
12288
+ ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
12289
+ process.exitCode = 1;
12290
+ } else if (report.score < threshold) {
12291
+ ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
12292
+ process.exitCode = 1;
12293
+ }
12294
+ }
12295
+ });
12296
+ }
12297
+ async function resolveSpec(opts, memoriesDir) {
12298
+ if (opts.spec) {
12299
+ const file = path43.resolve(opts.spec);
12300
+ const raw = await readFile20(file, "utf8");
12301
+ return JSON.parse(raw);
12302
+ }
12303
+ const memories = await loadMemoriesFromDir26(memoriesDir);
12304
+ return { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) };
12305
+ }
12306
+ async function runRetrieval(c, k, ctx) {
12307
+ const out = await getBriefing(
12308
+ {
12309
+ task: c.task,
12310
+ files: c.files ?? [],
12311
+ symbols: c.symbols ?? [],
12312
+ max_tokens: 6e3,
12313
+ max_memories: k,
12314
+ include_project_context: false,
12315
+ include_module_contexts: false,
12316
+ semantic: true,
12317
+ include_stale: false,
12318
+ track: false,
12319
+ format: "compact",
12320
+ min_semantic_score: 0
12321
+ },
12322
+ ctx
12323
+ );
12324
+ return out.memories.map((m) => m.id);
12325
+ }
12326
+ async function runSensorCase(c, ctx) {
12327
+ const out = await antiPatternsCheck(
12328
+ { diff: c.diff, paths: c.paths ?? [], limit: 50, semantic: false },
12329
+ ctx
12330
+ );
12331
+ return out.warnings.filter((w) => w.reasons.includes("sensor")).map((w) => w.id);
12332
+ }
12333
+ function pct(n) {
12334
+ return `${Math.round(n * 100)}%`;
12335
+ }
12336
+ function renderMarkdown2(root, k, report) {
12337
+ const lines = [
12338
+ "# hAIve eval report",
12339
+ "",
12340
+ `Project: \`${root}\` \xB7 top-k: ${k}`,
12341
+ "",
12342
+ `## Overall score: ${report.score}/100`,
12343
+ ""
12344
+ ];
12345
+ if (report.retrieval) {
12346
+ const r = report.retrieval;
12347
+ lines.push(
12348
+ "## Retrieval",
12349
+ "",
12350
+ `- cases: ${r.cases.length}`,
12351
+ `- mean recall: ${pct(r.mean_recall)}`,
12352
+ `- mean precision: ${pct(r.mean_precision)}`,
12353
+ `- MRR: ${r.mrr.toFixed(3)}`,
12354
+ ""
12355
+ );
12356
+ const misses = r.cases.filter((c) => c.misses.length > 0);
12357
+ if (misses.length > 0) {
12358
+ lines.push(`### ${misses.length} retrieval miss(es)`, "");
12359
+ for (const c of misses.slice(0, 25)) {
12360
+ lines.push(`- \`${c.name}\` \u2014 expected not in top-${k}`);
12361
+ }
12362
+ lines.push("");
12363
+ }
12364
+ }
12365
+ if (report.sensors) {
12366
+ const s = report.sensors;
12367
+ lines.push("## Sensors", "", `- cases: ${s.cases.length}`, `- catch-rate: ${pct(s.catch_rate)}`, "");
12368
+ const misses = s.cases.filter((c) => c.misses.length > 0);
12369
+ if (misses.length > 0) {
12370
+ lines.push(`### ${misses.length} sensor miss(es)`, "");
12371
+ for (const c of misses.slice(0, 25)) {
12372
+ lines.push(`- \`${c.name}\` \u2014 sensor did not fire (expected: ${c.misses.join(", ")})`);
12373
+ }
12374
+ lines.push("");
12375
+ }
12376
+ }
12377
+ lines.push(
12378
+ "## Reading",
12379
+ "",
12380
+ "Retrieval recall = share of expected memories that surfaced in the briefing top-k.",
12381
+ "MRR rewards ranking the right memory high. Catch-rate = share of known-bad diffs a sensor flagged.",
12382
+ "Run in CI to fail the build on a ranking/sensor regression.",
12383
+ ""
12384
+ );
12385
+ return lines.join("\n");
12386
+ }
12387
+
12388
+ // src/commands/memory-suggest.ts
12389
+ import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
12390
+ import { existsSync as existsSync65 } from "fs";
12391
+ import path44 from "path";
12392
+ import "commander";
12007
12393
  import {
12008
12394
  aggregateUsage as aggregateUsage2,
12009
12395
  buildFrontmatter as buildFrontmatter11,
12010
- findProjectRoot as findProjectRoot40,
12396
+ findProjectRoot as findProjectRoot43,
12011
12397
  loadConfig as loadConfig9,
12012
- loadMemoriesFromDir as loadMemoriesFromDir30,
12398
+ loadMemoriesFromDir as loadMemoriesFromDir31,
12013
12399
  memoryFilePath as memoryFilePath10,
12014
12400
  parseSince as parseSince2,
12015
12401
  readUsageEvents as readUsageEvents3,
12016
- resolveHaivePaths as resolveHaivePaths36,
12402
+ resolveHaivePaths as resolveHaivePaths39,
12017
12403
  serializeMemory as serializeMemory24
12018
12404
  } from "@hiveai/core";
12019
12405
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
@@ -12030,8 +12416,8 @@ function registerMemorySuggest(memory2) {
12030
12416
  memory2.command("suggest").description(
12031
12417
  "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."
12032
12418
  ).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) => {
12033
- const root = findProjectRoot40(opts.dir);
12034
- const paths = resolveHaivePaths36(root);
12419
+ const root = findProjectRoot43(opts.dir);
12420
+ const paths = resolveHaivePaths39(root);
12035
12421
  const events = await readUsageEvents3(paths);
12036
12422
  if (events.length === 0) {
12037
12423
  if (opts.json) {
@@ -12080,7 +12466,7 @@ function registerMemorySuggest(memory2) {
12080
12466
  }
12081
12467
  const created = [];
12082
12468
  const skipped = [];
12083
- const existing = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
12469
+ const existing = existsSync65(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12084
12470
  for (const s of top) {
12085
12471
  const slug = slugify2(s.query);
12086
12472
  if (!slug) {
@@ -12103,13 +12489,13 @@ function registerMemorySuggest(memory2) {
12103
12489
  });
12104
12490
  const body = renderTemplate(s, fm.id, status);
12105
12491
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
12106
- await mkdir18(path43.dirname(file), { recursive: true });
12107
- if (existsSync60(file)) {
12108
- skipped.push({ query: s.query, reason: `file already exists at ${path43.relative(root, file)}` });
12492
+ await mkdir18(path44.dirname(file), { recursive: true });
12493
+ if (existsSync65(file)) {
12494
+ skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
12109
12495
  continue;
12110
12496
  }
12111
- await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
12112
- created.push({ id: fm.id, file: path43.relative(root, file), query: s.query });
12497
+ await writeFile30(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
12498
+ created.push({ id: fm.id, file: path44.relative(root, file), query: s.query });
12113
12499
  }
12114
12500
  if (opts.json) {
12115
12501
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -12207,18 +12593,18 @@ function truncate2(text, max) {
12207
12593
  }
12208
12594
 
12209
12595
  // src/commands/memory-archive.ts
12210
- import { existsSync as existsSync61 } from "fs";
12211
- import { writeFile as writeFile30 } from "fs/promises";
12212
- import path44 from "path";
12596
+ import { existsSync as existsSync66 } from "fs";
12597
+ import { writeFile as writeFile31 } from "fs/promises";
12598
+ import path45 from "path";
12213
12599
  import "commander";
12214
12600
  import {
12215
- findProjectRoot as findProjectRoot41,
12216
- getUsage as getUsage18,
12601
+ findProjectRoot as findProjectRoot44,
12602
+ getUsage as getUsage21,
12217
12603
  retirementSignal as retirementSignal2,
12218
12604
  loadConfig as loadConfig10,
12219
- loadMemoriesFromDir as loadMemoriesFromDir31,
12220
- loadUsageIndex as loadUsageIndex24,
12221
- resolveHaivePaths as resolveHaivePaths37,
12605
+ loadMemoriesFromDir as loadMemoriesFromDir33,
12606
+ loadUsageIndex as loadUsageIndex27,
12607
+ resolveHaivePaths as resolveHaivePaths40,
12222
12608
  serializeMemory as serializeMemory25
12223
12609
  } from "@hiveai/core";
12224
12610
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
@@ -12226,9 +12612,9 @@ function registerMemoryArchive(memory2) {
12226
12612
  memory2.command("archive").description(
12227
12613
  "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."
12228
12614
  ).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) => {
12229
- const root = findProjectRoot41(opts.dir);
12230
- const paths = resolveHaivePaths37(root);
12231
- if (!existsSync61(paths.memoriesDir)) {
12615
+ const root = findProjectRoot44(opts.dir);
12616
+ const paths = resolveHaivePaths40(root);
12617
+ if (!existsSync66(paths.memoriesDir)) {
12232
12618
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12233
12619
  process.exitCode = 1;
12234
12620
  return;
@@ -12242,8 +12628,8 @@ function registerMemoryArchive(memory2) {
12242
12628
  return;
12243
12629
  }
12244
12630
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
12245
- const all = await loadMemoriesFromDir31(paths.memoriesDir);
12246
- const usage = await loadUsageIndex24(paths);
12631
+ const all = await loadMemoriesFromDir33(paths.memoriesDir);
12632
+ const usage = await loadUsageIndex27(paths);
12247
12633
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
12248
12634
  const candidates = [];
12249
12635
  for (const { memory: mem, filePath } of all) {
@@ -12253,10 +12639,10 @@ function registerMemoryArchive(memory2) {
12253
12639
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
12254
12640
  const retired = retirementSignal2(fm, mem.body);
12255
12641
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
12256
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync61(path44.join(paths.root, p)));
12642
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync66(path45.join(paths.root, p)));
12257
12643
  const isAnchorless = !hasAnyAnchor;
12258
12644
  if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
12259
- const u = getUsage18(usage, fm.id);
12645
+ const u = getUsage21(usage, fm.id);
12260
12646
  const lastSeen = u.last_read_at ?? fm.created_at;
12261
12647
  if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
12262
12648
  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)`;
@@ -12302,7 +12688,7 @@ function registerMemoryArchive(memory2) {
12302
12688
  if (!found) continue;
12303
12689
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
12304
12690
  try {
12305
- await writeFile30(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
12691
+ await writeFile31(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
12306
12692
  archived++;
12307
12693
  } catch (err) {
12308
12694
  if (!opts.json) {
@@ -12328,34 +12714,34 @@ function parseDays(input) {
12328
12714
  }
12329
12715
 
12330
12716
  // src/commands/doctor.ts
12331
- import { existsSync as existsSync63, statSync as statSync2 } from "fs";
12332
- import { readFile as readFile20, stat, writeFile as writeFile31 } from "fs/promises";
12333
- import path45 from "path";
12717
+ import { existsSync as existsSync67, statSync as statSync2 } from "fs";
12718
+ import { readFile as readFile21, stat, writeFile as writeFile33 } from "fs/promises";
12719
+ import path46 from "path";
12334
12720
  import { execFileSync, execSync as execSync3 } from "child_process";
12335
12721
  import "commander";
12336
12722
  import {
12337
12723
  codeMapPath as codeMapPath2,
12338
- findProjectRoot as findProjectRoot42,
12339
- getUsage as getUsage19,
12724
+ findProjectRoot as findProjectRoot45,
12725
+ getUsage as getUsage23,
12340
12726
  isStackPackSeed as isStackPackSeed4,
12341
12727
  loadCodeMap as loadCodeMap7,
12342
12728
  loadConfig as loadConfig11,
12343
- loadMemoriesFromDir as loadMemoriesFromDir33,
12344
- loadUsageIndex as loadUsageIndex25,
12729
+ loadMemoriesFromDir as loadMemoriesFromDir34,
12730
+ loadUsageIndex as loadUsageIndex28,
12345
12731
  readUsageEvents as readUsageEvents4,
12346
- resolveHaivePaths as resolveHaivePaths38
12732
+ resolveHaivePaths as resolveHaivePaths41
12347
12733
  } from "@hiveai/core";
12348
12734
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
12349
12735
  function registerDoctor(program2) {
12350
12736
  program2.command("doctor").description(
12351
12737
  "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."
12352
12738
  ).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) => {
12353
- const root = findProjectRoot42(opts.dir);
12354
- const paths = resolveHaivePaths38(root);
12739
+ const root = findProjectRoot45(opts.dir);
12740
+ const paths = resolveHaivePaths41(root);
12355
12741
  const findings = [];
12356
12742
  const repairs = [];
12357
12743
  const config = await loadConfig11(paths);
12358
- if (!existsSync63(paths.haiveDir)) {
12744
+ if (!existsSync67(paths.haiveDir)) {
12359
12745
  findings.push({
12360
12746
  severity: "error",
12361
12747
  code: "not-initialized",
@@ -12376,7 +12762,7 @@ function registerDoctor(program2) {
12376
12762
  })
12377
12763
  );
12378
12764
  }
12379
- if (!existsSync63(paths.projectContext)) {
12765
+ if (!existsSync67(paths.projectContext)) {
12380
12766
  findings.push({
12381
12767
  severity: "warn",
12382
12768
  code: "no-project-context",
@@ -12384,8 +12770,8 @@ function registerDoctor(program2) {
12384
12770
  fix: "haive init"
12385
12771
  });
12386
12772
  } else {
12387
- const { readFile: readFile24 } = await import("fs/promises");
12388
- const content = await readFile24(paths.projectContext, "utf8");
12773
+ const { readFile: readFile25 } = await import("fs/promises");
12774
+ const content = await readFile25(paths.projectContext, "utf8");
12389
12775
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
12390
12776
  if (isTemplate) {
12391
12777
  findings.push({
@@ -12405,7 +12791,7 @@ function registerDoctor(program2) {
12405
12791
  });
12406
12792
  }
12407
12793
  }
12408
- const memories = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
12794
+ const memories = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12409
12795
  const now = Date.now();
12410
12796
  if (memories.length === 0) {
12411
12797
  findings.push({
@@ -12414,7 +12800,7 @@ function registerDoctor(program2) {
12414
12800
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
12415
12801
  });
12416
12802
  } else {
12417
- const usage = await loadUsageIndex25(paths);
12803
+ const usage = await loadUsageIndex28(paths);
12418
12804
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
12419
12805
  if (stale.length > 0) {
12420
12806
  findings.push({
@@ -12471,7 +12857,7 @@ function registerDoctor(program2) {
12471
12857
  }
12472
12858
  const decayCandidates = memories.filter((m) => {
12473
12859
  if (m.memory.frontmatter.status !== "validated") return false;
12474
- const u = getUsage19(usage, m.memory.frontmatter.id);
12860
+ const u = getUsage23(usage, m.memory.frontmatter.id);
12475
12861
  const last = u.last_read_at ?? m.memory.frontmatter.created_at;
12476
12862
  return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
12477
12863
  });
@@ -12555,12 +12941,12 @@ function registerDoctor(program2) {
12555
12941
  }
12556
12942
  }
12557
12943
  if (config.enforcement?.requireBriefingFirst) {
12558
- const claudeSettings = path45.join(root, ".claude", "settings.local.json");
12944
+ const claudeSettings = path46.join(root, ".claude", "settings.local.json");
12559
12945
  let hasClaudeEnforcement = false;
12560
- if (existsSync63(claudeSettings)) {
12946
+ if (existsSync67(claudeSettings)) {
12561
12947
  try {
12562
- const { readFile: readFile24 } = await import("fs/promises");
12563
- const raw = await readFile24(claudeSettings, "utf8");
12948
+ const { readFile: readFile25 } = await import("fs/promises");
12949
+ const raw = await readFile25(claudeSettings, "utf8");
12564
12950
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
12565
12951
  } catch {
12566
12952
  hasClaudeEnforcement = false;
@@ -12583,14 +12969,14 @@ function registerDoctor(program2) {
12583
12969
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12584
12970
  });
12585
12971
  }
12586
- findings.push(...await collectInstallFindings(root, "0.10.9"));
12972
+ findings.push(...await collectInstallFindings(root, "0.12.0"));
12587
12973
  try {
12588
12974
  const legacyRaw = execSync3("haive-mcp --version", {
12589
12975
  encoding: "utf8",
12590
12976
  timeout: 3e3,
12591
12977
  stdio: ["ignore", "pipe", "ignore"]
12592
12978
  }).trim();
12593
- const cliVersion = "0.10.9";
12979
+ const cliVersion = "0.12.0";
12594
12980
  if (legacyRaw && legacyRaw !== cliVersion) {
12595
12981
  findings.push({
12596
12982
  severity: "warn",
@@ -12606,20 +12992,20 @@ npm uninstall -g @hiveai/mcp`
12606
12992
  }
12607
12993
  {
12608
12994
  const configPaths = [
12609
- path45.join(root, ".mcp.json"),
12610
- path45.join(root, ".cursor", "mcp.json"),
12611
- path45.join(root, ".vscode", "mcp.json")
12995
+ path46.join(root, ".mcp.json"),
12996
+ path46.join(root, ".cursor", "mcp.json"),
12997
+ path46.join(root, ".vscode", "mcp.json")
12612
12998
  ];
12613
12999
  const staleConfigs = [];
12614
13000
  for (const cfgPath of configPaths) {
12615
- if (!existsSync63(cfgPath)) continue;
13001
+ if (!existsSync67(cfgPath)) continue;
12616
13002
  try {
12617
- const raw = await readFile20(cfgPath, "utf8");
13003
+ const raw = await readFile21(cfgPath, "utf8");
12618
13004
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
12619
- staleConfigs.push(path45.relative(root, cfgPath));
13005
+ staleConfigs.push(path46.relative(root, cfgPath));
12620
13006
  if (opts.fix && !opts.dryRun) {
12621
13007
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
12622
- await writeFile31(cfgPath, updated, "utf8");
13008
+ await writeFile33(cfgPath, updated, "utf8");
12623
13009
  }
12624
13010
  }
12625
13011
  } catch {
@@ -12790,23 +13176,23 @@ async function collectHarnessCoverageFindings(codeMap, memories) {
12790
13176
  }
12791
13177
  }
12792
13178
  const covered = coveredFiles.size;
12793
- const pct = Math.round(covered / total * 100);
13179
+ const pct2 = Math.round(covered / total * 100);
12794
13180
  const uncovered = codeFiles.filter((f) => !coveredFiles.has(f)).sort((a, b) => {
12795
13181
  const depthA = a.split("/").length;
12796
13182
  const depthB = b.split("/").length;
12797
13183
  if (depthA !== depthB) return depthA - depthB;
12798
13184
  return a.localeCompare(b);
12799
13185
  }).slice(0, 5);
12800
- 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.";
13186
+ 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.";
12801
13187
  const uncoveredHint = uncovered.length > 0 ? `
12802
13188
  Top uncovered: ${uncovered.map((f) => `\`${f}\``).join(", ")}` : "";
12803
13189
  const findings = [];
12804
13190
  findings.push({
12805
13191
  severity: "info",
12806
13192
  code: "harness-coverage",
12807
- coverage_percent: pct,
12808
- message: `${covered}/${total} code-map files have validated memory anchors (${pct}%). ` + coverageDesc + uncoveredHint,
12809
- fix: pct < 50 && total > 10 ? `haive memory add --type gotcha|convention|architecture --paths <key-file> --scope team` : void 0,
13193
+ coverage_percent: pct2,
13194
+ message: `${covered}/${total} code-map files have validated memory anchors (${pct2}%). ` + coverageDesc + uncoveredHint,
13195
+ fix: pct2 < 50 && total > 10 ? `haive memory add --type gotcha|convention|architecture --paths <key-file> --scope team` : void 0,
12810
13196
  section: "Harness coverage"
12811
13197
  });
12812
13198
  return findings;
@@ -12906,9 +13292,9 @@ which -a haive`
12906
13292
  ".vscode/mcp.json"
12907
13293
  ];
12908
13294
  for (const rel of integrationFiles) {
12909
- const file = path45.join(root, rel);
12910
- if (!existsSync63(file)) continue;
12911
- const text = await readFile20(file, "utf8").catch(() => "");
13295
+ const file = path46.join(root, rel);
13296
+ if (!existsSync67(file)) continue;
13297
+ const text = await readFile21(file, "utf8").catch(() => "");
12912
13298
  for (const bin of extractAbsoluteHaiveBins(text)) {
12913
13299
  const version = versionForBinary(bin);
12914
13300
  if (!version) {
@@ -12932,7 +13318,7 @@ which -a haive`
12932
13318
  }
12933
13319
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
12934
13320
  const findings = [];
12935
- const rootPkg = await readJson(path45.join(root, "package.json"));
13321
+ const rootPkg = await readJson(path46.join(root, "package.json"));
12936
13322
  const workspacePackages = [
12937
13323
  "packages/core/package.json",
12938
13324
  "packages/embeddings/package.json",
@@ -12941,7 +13327,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
12941
13327
  ];
12942
13328
  const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
12943
13329
  rel,
12944
- pkg: await readJson(path45.join(root, rel))
13330
+ pkg: await readJson(path46.join(root, rel))
12945
13331
  })))).filter((item) => item.pkg);
12946
13332
  const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
12947
13333
  if (!isHaiveWorkspace) return findings;
@@ -13003,9 +13389,9 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
13003
13389
  }
13004
13390
  }
13005
13391
  async function readJson(file) {
13006
- if (!existsSync63(file)) return null;
13392
+ if (!existsSync67(file)) return null;
13007
13393
  try {
13008
- return JSON.parse(await readFile20(file, "utf8"));
13394
+ return JSON.parse(await readFile21(file, "utf8"));
13009
13395
  } catch {
13010
13396
  return null;
13011
13397
  }
@@ -13050,22 +13436,22 @@ function extractAbsoluteHaiveBins(text) {
13050
13436
  }
13051
13437
 
13052
13438
  // src/commands/playback.ts
13053
- import { existsSync as existsSync64 } from "fs";
13439
+ import { existsSync as existsSync68 } from "fs";
13054
13440
  import "commander";
13055
13441
  import {
13056
- findProjectRoot as findProjectRoot43,
13057
- loadMemoriesFromDir as loadMemoriesFromDir34,
13442
+ findProjectRoot as findProjectRoot46,
13443
+ loadMemoriesFromDir as loadMemoriesFromDir35,
13058
13444
  parseSince as parseSince3,
13059
13445
  readUsageEvents as readUsageEvents5,
13060
- resolveHaivePaths as resolveHaivePaths39
13446
+ resolveHaivePaths as resolveHaivePaths42
13061
13447
  } from "@hiveai/core";
13062
13448
  var MS_PER_MINUTE = 6e4;
13063
13449
  function registerPlayback(program2) {
13064
13450
  program2.command("playback").description(
13065
13451
  "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?'"
13066
13452
  ).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) => {
13067
- const root = findProjectRoot43(opts.dir);
13068
- const paths = resolveHaivePaths39(root);
13453
+ const root = findProjectRoot46(opts.dir);
13454
+ const paths = resolveHaivePaths42(root);
13069
13455
  const events = await readUsageEvents5(paths);
13070
13456
  if (events.length === 0) {
13071
13457
  if (opts.json) {
@@ -13080,7 +13466,7 @@ function registerPlayback(program2) {
13080
13466
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
13081
13467
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
13082
13468
  const sessions = bucketSessions(filtered, gapMs);
13083
- const all = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
13469
+ const all = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13084
13470
  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);
13085
13471
  const enriched = sessions.map((s, i) => {
13086
13472
  const startMs = Date.parse(s.start);
@@ -13171,9 +13557,9 @@ import { spawn as spawn5 } from "child_process";
13171
13557
  import "commander";
13172
13558
  import {
13173
13559
  antiPatternGateParams,
13174
- findProjectRoot as findProjectRoot44,
13560
+ findProjectRoot as findProjectRoot47,
13175
13561
  loadConfig as loadConfig12,
13176
- resolveHaivePaths as resolveHaivePaths40
13562
+ resolveHaivePaths as resolveHaivePaths43
13177
13563
  } from "@hiveai/core";
13178
13564
  function registerPrecommit(program2) {
13179
13565
  program2.command("precommit").description(
@@ -13185,8 +13571,8 @@ function registerPrecommit(program2) {
13185
13571
  "--no-anchored-blocks",
13186
13572
  "do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
13187
13573
  ).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) => {
13188
- const root = findProjectRoot44(opts.dir);
13189
- const paths = resolveHaivePaths40(root);
13574
+ const root = findProjectRoot47(opts.dir);
13575
+ const paths = resolveHaivePaths43(root);
13190
13576
  const ctx = { paths };
13191
13577
  const config = await loadConfig12(paths);
13192
13578
  const gate = config.enforcement?.antiPatternGate ?? "anchored";
@@ -13322,12 +13708,12 @@ function runCommand3(cmd, args, cwd) {
13322
13708
  }
13323
13709
 
13324
13710
  // src/commands/welcome.ts
13325
- import { existsSync as existsSync65 } from "fs";
13711
+ import { existsSync as existsSync69 } from "fs";
13326
13712
  import "commander";
13327
13713
  import {
13328
- findProjectRoot as findProjectRoot45,
13329
- loadMemoriesFromDir as loadMemoriesFromDir35,
13330
- resolveHaivePaths as resolveHaivePaths41
13714
+ findProjectRoot as findProjectRoot48,
13715
+ loadMemoriesFromDir as loadMemoriesFromDir36,
13716
+ resolveHaivePaths as resolveHaivePaths44
13331
13717
  } from "@hiveai/core";
13332
13718
  var TYPE_RANK = {
13333
13719
  skill: 0,
@@ -13342,14 +13728,14 @@ function registerWelcome(program2) {
13342
13728
  program2.command("welcome").description(
13343
13729
  "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"
13344
13730
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
13345
- const root = findProjectRoot45(opts.dir);
13346
- const paths = resolveHaivePaths41(root);
13347
- if (!existsSync65(paths.memoriesDir)) {
13731
+ const root = findProjectRoot48(opts.dir);
13732
+ const paths = resolveHaivePaths44(root);
13733
+ if (!existsSync69(paths.memoriesDir)) {
13348
13734
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
13349
13735
  process.exitCode = 1;
13350
13736
  return;
13351
13737
  }
13352
- const all = await loadMemoriesFromDir35(paths.memoriesDir);
13738
+ const all = await loadMemoriesFromDir36(paths.memoriesDir);
13353
13739
  const team = all.filter(
13354
13740
  ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
13355
13741
  );
@@ -13403,27 +13789,27 @@ function registerMemorySuggestTopic(memory2) {
13403
13789
  }
13404
13790
 
13405
13791
  // src/commands/resolve-project.ts
13406
- import path46 from "path";
13792
+ import path47 from "path";
13407
13793
  import "commander";
13408
13794
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
13409
13795
  function registerResolveProject(program2) {
13410
13796
  program2.command("resolve-project").description(
13411
13797
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
13412
13798
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
13413
- const info = resolveProjectInfo2({ cwd: path46.resolve(opts.dir) });
13799
+ const info = resolveProjectInfo2({ cwd: path47.resolve(opts.dir) });
13414
13800
  console.log(JSON.stringify({ ok: true, info }, null, 2));
13415
13801
  });
13416
13802
  }
13417
13803
 
13418
13804
  // src/commands/runtime-journal.ts
13419
- import { existsSync as existsSync66 } from "fs";
13420
- import path47 from "path";
13805
+ import { existsSync as existsSync70 } from "fs";
13806
+ import path48 from "path";
13421
13807
  import "commander";
13422
13808
  import {
13423
13809
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
13424
- findProjectRoot as findProjectRoot46,
13810
+ findProjectRoot as findProjectRoot49,
13425
13811
  readRuntimeJournalTail as readRuntimeJournalTail2,
13426
- resolveHaivePaths as resolveHaivePaths42
13812
+ resolveHaivePaths as resolveHaivePaths45
13427
13813
  } from "@hiveai/core";
13428
13814
  function registerRuntime(program2) {
13429
13815
  const runtime = program2.command("runtime").description(
@@ -13431,18 +13817,18 @@ function registerRuntime(program2) {
13431
13817
  );
13432
13818
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
13433
13819
  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) => {
13434
- const root = path47.resolve(opts.dir ?? process.cwd());
13435
- const paths = resolveHaivePaths42(findProjectRoot46(root));
13820
+ const root = path48.resolve(opts.dir ?? process.cwd());
13821
+ const paths = resolveHaivePaths45(findProjectRoot49(root));
13436
13822
  const raw = opts.kind ?? "note";
13437
13823
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
13438
13824
  await appendRuntimeJournalEntry3(paths, { kind, message });
13439
- ui.success(`Appended to ${path47.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
13825
+ ui.success(`Appended to ${path48.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
13440
13826
  });
13441
13827
  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) => {
13442
- const root = path47.resolve(opts.dir ?? process.cwd());
13443
- const paths = resolveHaivePaths42(findProjectRoot46(root));
13828
+ const root = path48.resolve(opts.dir ?? process.cwd());
13829
+ const paths = resolveHaivePaths45(findProjectRoot49(root));
13444
13830
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
13445
- if (!existsSync66(paths.haiveDir)) {
13831
+ if (!existsSync70(paths.haiveDir)) {
13446
13832
  ui.error("No .ai/ \u2014 run `haive init` first.");
13447
13833
  process.exitCode = 1;
13448
13834
  return;
@@ -13457,13 +13843,13 @@ function registerRuntime(program2) {
13457
13843
  }
13458
13844
 
13459
13845
  // src/commands/memory-timeline.ts
13460
- import { existsSync as existsSync67 } from "fs";
13461
- import path48 from "path";
13846
+ import { existsSync as existsSync71 } from "fs";
13847
+ import path49 from "path";
13462
13848
  import "commander";
13463
13849
  import {
13464
13850
  collectTimelineEntries as collectTimelineEntries2,
13465
- findProjectRoot as findProjectRoot47,
13466
- resolveHaivePaths as resolveHaivePaths43
13851
+ findProjectRoot as findProjectRoot50,
13852
+ resolveHaivePaths as resolveHaivePaths46
13467
13853
  } from "@hiveai/core";
13468
13854
  function registerMemoryTimeline(memory2) {
13469
13855
  memory2.command("timeline").description(
@@ -13474,15 +13860,15 @@ function registerMemoryTimeline(memory2) {
13474
13860
  process.exitCode = 1;
13475
13861
  return;
13476
13862
  }
13477
- const root = path48.resolve(opts.dir ?? process.cwd());
13478
- const paths = resolveHaivePaths43(findProjectRoot47(root));
13479
- if (!existsSync67(paths.memoriesDir)) {
13863
+ const root = path49.resolve(opts.dir ?? process.cwd());
13864
+ const paths = resolveHaivePaths46(findProjectRoot50(root));
13865
+ if (!existsSync71(paths.memoriesDir)) {
13480
13866
  ui.error("No memories \u2014 run `haive init`.");
13481
13867
  process.exitCode = 1;
13482
13868
  return;
13483
13869
  }
13484
13870
  const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
13485
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
13871
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
13486
13872
  const { entries, notice } = collectTimelineEntries2(all, {
13487
13873
  memoryId: opts.id,
13488
13874
  topic: opts.topic,
@@ -13494,14 +13880,14 @@ function registerMemoryTimeline(memory2) {
13494
13880
  }
13495
13881
 
13496
13882
  // src/commands/memory-conflict-candidates.ts
13497
- import { existsSync as existsSync68 } from "fs";
13498
- import path49 from "path";
13883
+ import { existsSync as existsSync73 } from "fs";
13884
+ import path50 from "path";
13499
13885
  import "commander";
13500
13886
  import {
13501
13887
  findLexicalConflictPairs as findLexicalConflictPairs2,
13502
13888
  findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
13503
- findProjectRoot as findProjectRoot48,
13504
- resolveHaivePaths as resolveHaivePaths44
13889
+ findProjectRoot as findProjectRoot51,
13890
+ resolveHaivePaths as resolveHaivePaths47
13505
13891
  } from "@hiveai/core";
13506
13892
  function parseTypes(csv) {
13507
13893
  const allowed = ["decision", "architecture", "convention", "gotcha"];
@@ -13517,9 +13903,9 @@ function registerMemoryConflictCandidates(memory2) {
13517
13903
  "decision,architecture,convention,gotcha (lexical scan)",
13518
13904
  "decision,architecture"
13519
13905
  ).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) => {
13520
- const root = path49.resolve(opts.dir ?? process.cwd());
13521
- const paths = resolveHaivePaths44(findProjectRoot48(root));
13522
- if (!existsSync68(paths.memoriesDir)) {
13906
+ const root = path50.resolve(opts.dir ?? process.cwd());
13907
+ const paths = resolveHaivePaths47(findProjectRoot51(root));
13908
+ if (!existsSync73(paths.memoriesDir)) {
13523
13909
  ui.error("No memories \u2014 run `haive init`.");
13524
13910
  process.exitCode = 1;
13525
13911
  return;
@@ -13529,7 +13915,7 @@ function registerMemoryConflictCandidates(memory2) {
13529
13915
  const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
13530
13916
  const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
13531
13917
  const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
13532
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
13918
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
13533
13919
  const lexical = findLexicalConflictPairs2(all, {
13534
13920
  sinceDays,
13535
13921
  types: parseTypes(opts.types),
@@ -13555,21 +13941,21 @@ function registerMemoryConflictCandidates(memory2) {
13555
13941
 
13556
13942
  // src/commands/enforce.ts
13557
13943
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
13558
- import { existsSync as existsSync69, statSync as statSync3 } from "fs";
13559
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile21, readdir as readdir6, rm as rm3, writeFile as writeFile33 } from "fs/promises";
13560
- import path50 from "path";
13944
+ import { existsSync as existsSync74, statSync as statSync3 } from "fs";
13945
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile34 } from "fs/promises";
13946
+ import path51 from "path";
13561
13947
  import "commander";
13562
13948
  import {
13563
13949
  antiPatternGateParams as antiPatternGateParams2,
13564
- findProjectRoot as findProjectRoot49,
13950
+ findProjectRoot as findProjectRoot52,
13565
13951
  hasRecentBriefingMarker as hasRecentBriefingMarker2,
13566
13952
  isFreshIsoDate,
13567
13953
  loadConfig as loadConfig13,
13568
- loadMemoriesFromDir as loadMemoriesFromDir36,
13954
+ loadMemoriesFromDir as loadMemoriesFromDir37,
13569
13955
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
13570
13956
  readRecentBriefingMarker,
13571
13957
  resolveBriefingBudget as resolveBriefingBudget3,
13572
- resolveHaivePaths as resolveHaivePaths45,
13958
+ resolveHaivePaths as resolveHaivePaths48,
13573
13959
  saveConfig as saveConfig4,
13574
13960
  SESSION_RECAP_TTL_MS,
13575
13961
  verifyAnchor as verifyAnchor4,
@@ -13582,8 +13968,8 @@ function registerEnforce(program2) {
13582
13968
  "Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
13583
13969
  );
13584
13970
  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) => {
13585
- const root = findProjectRoot49(opts.dir);
13586
- const paths = resolveHaivePaths45(root);
13971
+ const root = findProjectRoot52(opts.dir);
13972
+ const paths = resolveHaivePaths48(root);
13587
13973
  await mkdir19(paths.haiveDir, { recursive: true });
13588
13974
  const current = await loadConfig13(paths);
13589
13975
  await saveConfig4(paths, {
@@ -13608,7 +13994,7 @@ function registerEnforce(program2) {
13608
13994
  if (opts.claude !== false) {
13609
13995
  try {
13610
13996
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
13611
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path50.relative(root, result.settingsPath)})`);
13997
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path51.relative(root, result.settingsPath)})`);
13612
13998
  } catch (err) {
13613
13999
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
13614
14000
  }
@@ -13627,21 +14013,21 @@ function registerEnforce(program2) {
13627
14013
  if (report.should_block) process.exit(2);
13628
14014
  });
13629
14015
  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) => {
13630
- const root = findProjectRoot49(opts.dir);
13631
- const paths = resolveHaivePaths45(root);
13632
- const cacheDir = path50.join(paths.haiveDir, ".cache");
13633
- if (existsSync69(cacheDir)) {
13634
- if (opts.dryRun) ui.info(`would clean ${path50.relative(root, cacheDir)} (preserving .gitignore)`);
14016
+ const root = findProjectRoot52(opts.dir);
14017
+ const paths = resolveHaivePaths48(root);
14018
+ const cacheDir = path51.join(paths.haiveDir, ".cache");
14019
+ if (existsSync74(cacheDir)) {
14020
+ if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
13635
14021
  else {
13636
14022
  const removed = await cleanupCacheDir(cacheDir);
13637
- ui.success(`cleaned ${path50.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
14023
+ ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13638
14024
  }
13639
14025
  }
13640
- if (existsSync69(paths.runtimeDir)) {
13641
- if (opts.dryRun) ui.info(`would clean ${path50.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
14026
+ if (existsSync74(paths.runtimeDir)) {
14027
+ if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
13642
14028
  else {
13643
14029
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
13644
- ui.success(`cleaned ${path50.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
14030
+ ui.success(`cleaned ${path51.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13645
14031
  }
13646
14032
  }
13647
14033
  });
@@ -13661,8 +14047,8 @@ function registerEnforce(program2) {
13661
14047
  const payload = await readHookPayload();
13662
14048
  const root = resolveRoot(opts.dir, payload);
13663
14049
  if (!root) return;
13664
- const paths = resolveHaivePaths45(root);
13665
- if (!existsSync69(paths.haiveDir)) return;
14050
+ const paths = resolveHaivePaths48(root);
14051
+ if (!existsSync74(paths.haiveDir)) return;
13666
14052
  await mkdir19(paths.runtimeDir, { recursive: true });
13667
14053
  const sessionId = opts.sessionId ?? payload.session_id;
13668
14054
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -13724,8 +14110,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
13724
14110
  const payload = await readHookPayload();
13725
14111
  const root = resolveRoot(opts.dir, payload);
13726
14112
  if (!root) return;
13727
- const paths = resolveHaivePaths45(root);
13728
- if (!existsSync69(paths.haiveDir)) return;
14113
+ const paths = resolveHaivePaths48(root);
14114
+ if (!existsSync74(paths.haiveDir)) return;
13729
14115
  if (!isWriteLikeTool(payload)) return;
13730
14116
  const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
13731
14117
  if (ok) {
@@ -13767,9 +14153,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
13767
14153
  });
13768
14154
  }
13769
14155
  async function buildFinishReport(dir) {
13770
- const root = findProjectRoot49(dir);
13771
- const paths = resolveHaivePaths45(root);
13772
- const initialized = existsSync69(paths.haiveDir);
14156
+ const root = findProjectRoot52(dir);
14157
+ const paths = resolveHaivePaths48(root);
14158
+ const initialized = existsSync74(paths.haiveDir);
13773
14159
  const config = initialized ? await loadConfig13(paths) : {};
13774
14160
  const mode = config.enforcement?.mode ?? "strict";
13775
14161
  const findings = [];
@@ -13963,9 +14349,9 @@ function finishReport(root, initialized, mode, findings, config) {
13963
14349
  });
13964
14350
  }
13965
14351
  async function runWithEnforcement(command, args, opts) {
13966
- const root = findProjectRoot49(opts.dir);
13967
- const paths = resolveHaivePaths45(root);
13968
- if (!existsSync69(paths.haiveDir)) {
14352
+ const root = findProjectRoot52(opts.dir);
14353
+ const paths = resolveHaivePaths48(root);
14354
+ if (!existsSync74(paths.haiveDir)) {
13969
14355
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
13970
14356
  process.exit(1);
13971
14357
  }
@@ -13984,7 +14370,7 @@ async function runWithEnforcement(command, args, opts) {
13984
14370
  process.exit(2);
13985
14371
  }
13986
14372
  ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
13987
- ui.info(`Briefing written to ${path50.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
14373
+ ui.info(`Briefing written to ${path51.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
13988
14374
  const child = spawn6(command, args, {
13989
14375
  cwd: root,
13990
14376
  stdio: "inherit",
@@ -14034,9 +14420,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14034
14420
  source: "haive-run",
14035
14421
  memoryIds: briefing.memories.map((m) => m.id)
14036
14422
  });
14037
- const dir = path50.join(paths.runtimeDir, "enforcement", "briefings");
14423
+ const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
14038
14424
  await mkdir19(dir, { recursive: true });
14039
- const file = path50.join(dir, `${sessionId}.md`);
14425
+ const file = path51.join(dir, `${sessionId}.md`);
14040
14426
  const parts = [
14041
14427
  "# hAIve Briefing",
14042
14428
  "",
@@ -14054,13 +14440,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14054
14440
  if (briefing.setup_warnings.length > 0) {
14055
14441
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
14056
14442
  }
14057
- await writeFile33(file, parts.join("\n") + "\n", "utf8");
14443
+ await writeFile34(file, parts.join("\n") + "\n", "utf8");
14058
14444
  return file;
14059
14445
  }
14060
14446
  async function buildEnforcementReport(dir, stage, sessionId) {
14061
- const root = findProjectRoot49(dir);
14062
- const paths = resolveHaivePaths45(root);
14063
- const initialized = existsSync69(paths.haiveDir);
14447
+ const root = findProjectRoot52(dir);
14448
+ const paths = resolveHaivePaths48(root);
14449
+ const initialized = existsSync74(paths.haiveDir);
14064
14450
  const config = initialized ? await loadConfig13(paths) : {};
14065
14451
  if (initialized) await applyLightweightRepairs(root, paths);
14066
14452
  const mode = config.enforcement?.mode ?? "strict";
@@ -14091,7 +14477,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
14091
14477
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
14092
14478
  });
14093
14479
  }
14094
- findings.push(...await inspectIntegrationVersions(root, "0.10.9"));
14480
+ findings.push(...await inspectIntegrationVersions(root, "0.12.0"));
14095
14481
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
14096
14482
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
14097
14483
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -14161,8 +14547,8 @@ function withCategories(report) {
14161
14547
  };
14162
14548
  }
14163
14549
  async function hasRecentSessionRecap(paths) {
14164
- if (!existsSync69(paths.memoriesDir)) return false;
14165
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14550
+ if (!existsSync74(paths.memoriesDir)) return false;
14551
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
14166
14552
  return all.some(({ memory: memory2 }) => {
14167
14553
  const fm = memory2.frontmatter;
14168
14554
  const freshnessDate = fm.verified_at ?? fm.created_at;
@@ -14170,8 +14556,8 @@ async function hasRecentSessionRecap(paths) {
14170
14556
  });
14171
14557
  }
14172
14558
  async function verifyMemoryPolicy(paths, config) {
14173
- if (!existsSync69(paths.memoriesDir)) return [];
14174
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14559
+ if (!existsSync74(paths.memoriesDir)) return [];
14560
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
14175
14561
  const findings = [];
14176
14562
  const staleImportant = [];
14177
14563
  let verified = 0;
@@ -14208,12 +14594,12 @@ async function verifyMemoryPolicy(paths, config) {
14208
14594
  return findings;
14209
14595
  }
14210
14596
  async function verifyDecisionCoverage(paths, stage, sessionId) {
14211
- if (!existsSync69(paths.memoriesDir)) return [];
14597
+ if (!existsSync74(paths.memoriesDir)) return [];
14212
14598
  const changedFiles = await getChangedFiles(paths.root, stage);
14213
14599
  if (changedFiles.length === 0) {
14214
14600
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
14215
14601
  }
14216
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
14602
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
14217
14603
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
14218
14604
  const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
14219
14605
  const fm = memory2.frontmatter;
@@ -14313,16 +14699,16 @@ async function cleanupRuntimeDir(runtimeDir) {
14313
14699
  for (const entry of entries) {
14314
14700
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
14315
14701
  if (entry.name === "enforcement") {
14316
- removed += await cleanupEnforcementDir(path50.join(runtimeDir, entry.name));
14702
+ removed += await cleanupEnforcementDir(path51.join(runtimeDir, entry.name));
14317
14703
  continue;
14318
14704
  }
14319
- await rm3(path50.join(runtimeDir, entry.name), { recursive: true, force: true });
14705
+ await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
14320
14706
  removed++;
14321
14707
  }
14322
- await writeFile33(path50.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14323
- if (!existsSync69(path50.join(runtimeDir, "README.md"))) {
14324
- await writeFile33(
14325
- path50.join(runtimeDir, "README.md"),
14708
+ await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14709
+ if (!existsSync74(path51.join(runtimeDir, "README.md"))) {
14710
+ await writeFile34(
14711
+ path51.join(runtimeDir, "README.md"),
14326
14712
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
14327
14713
  "utf8"
14328
14714
  );
@@ -14335,10 +14721,10 @@ async function cleanupCacheDir(cacheDir) {
14335
14721
  const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
14336
14722
  for (const entry of entries) {
14337
14723
  if (entry.name === ".gitignore") continue;
14338
- await rm3(path50.join(cacheDir, entry.name), { recursive: true, force: true });
14724
+ await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
14339
14725
  removed++;
14340
14726
  }
14341
- await writeFile33(path50.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
14727
+ await writeFile34(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
14342
14728
  return removed;
14343
14729
  }
14344
14730
  async function cleanupEnforcementDir(enforcementDir) {
@@ -14346,7 +14732,7 @@ async function cleanupEnforcementDir(enforcementDir) {
14346
14732
  const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
14347
14733
  for (const entry of entries) {
14348
14734
  if (entry.name === "briefings") continue;
14349
- await rm3(path50.join(enforcementDir, entry.name), { recursive: true, force: true });
14735
+ await rm3(path51.join(enforcementDir, entry.name), { recursive: true, force: true });
14350
14736
  removed++;
14351
14737
  }
14352
14738
  return removed;
@@ -14362,9 +14748,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
14362
14748
  ];
14363
14749
  const findings = [];
14364
14750
  for (const rel of files) {
14365
- const file = path50.join(root, rel);
14366
- if (!existsSync69(file)) continue;
14367
- const text = await readFile21(file, "utf8").catch(() => "");
14751
+ const file = path51.join(root, rel);
14752
+ if (!existsSync74(file)) continue;
14753
+ const text = await readFile23(file, "utf8").catch(() => "");
14368
14754
  for (const bin of extractAbsoluteHaiveBins2(text)) {
14369
14755
  const version = versionForBinary2(bin);
14370
14756
  if (!version) {
@@ -14473,9 +14859,9 @@ async function resolveCiDiffRange(root) {
14473
14859
  }
14474
14860
  async function resolveGithubEventRange(root) {
14475
14861
  const eventPath = process.env.GITHUB_EVENT_PATH;
14476
- if (!eventPath || !existsSync69(eventPath)) return null;
14862
+ if (!eventPath || !existsSync74(eventPath)) return null;
14477
14863
  try {
14478
- const event = JSON.parse(await readFile21(eventPath, "utf8"));
14864
+ const event = JSON.parse(await readFile23(eventPath, "utf8"));
14479
14865
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
14480
14866
  const prHead = cleanGitSha(event.pull_request?.head?.sha ?? event.after ?? process.env.GITHUB_SHA) ?? "HEAD";
14481
14867
  if (prBase && await gitCommitExists(root, prBase)) return `${prBase}...${prHead}`;
@@ -14592,7 +14978,7 @@ async function inspectReleaseVersionState(root, upstream) {
14592
14978
  }
14593
14979
  async function readPackageVersion(root, relPath) {
14594
14980
  try {
14595
- const data = JSON.parse(await readFile21(path50.join(root, relPath), "utf8"));
14981
+ const data = JSON.parse(await readFile23(path51.join(root, relPath), "utf8"));
14596
14982
  return typeof data.version === "string" ? data.version : void 0;
14597
14983
  } catch {
14598
14984
  return void 0;
@@ -14653,8 +15039,8 @@ function buildScore(findings, threshold = 80) {
14653
15039
  };
14654
15040
  }
14655
15041
  async function installGitEnforcement(root) {
14656
- const hooksDir = path50.join(root, ".git", "hooks");
14657
- if (!existsSync69(path50.join(root, ".git"))) {
15042
+ const hooksDir = path51.join(root, ".git", "hooks");
15043
+ if (!existsSync74(path51.join(root, ".git"))) {
14658
15044
  ui.warn("No .git directory found; git enforcement hooks skipped.");
14659
15045
  return;
14660
15046
  }
@@ -14676,31 +15062,31 @@ haive enforce check --stage pre-push --dir . || exit $?
14676
15062
  }
14677
15063
  ];
14678
15064
  for (const hook of hooks) {
14679
- const file = path50.join(hooksDir, hook.name);
14680
- if (existsSync69(file)) {
14681
- const current = await readFile21(file, "utf8").catch(() => "");
15065
+ const file = path51.join(hooksDir, hook.name);
15066
+ if (existsSync74(file)) {
15067
+ const current = await readFile23(file, "utf8").catch(() => "");
14682
15068
  if (current.includes(ENFORCE_HOOK_MARKER)) {
14683
- await writeFile33(file, hook.body, "utf8");
15069
+ await writeFile34(file, hook.body, "utf8");
14684
15070
  } else {
14685
- await writeFile33(file, `${current.trimEnd()}
15071
+ await writeFile34(file, `${current.trimEnd()}
14686
15072
 
14687
15073
  ${hook.body}`, "utf8");
14688
15074
  }
14689
15075
  } else {
14690
- await writeFile33(file, hook.body, "utf8");
15076
+ await writeFile34(file, hook.body, "utf8");
14691
15077
  }
14692
15078
  await chmod2(file, 493);
14693
15079
  }
14694
15080
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
14695
15081
  }
14696
15082
  async function installCiEnforcement(root) {
14697
- const workflowPath = path50.join(root, ".github", "workflows", "haive-enforcement.yml");
14698
- await mkdir19(path50.dirname(workflowPath), { recursive: true });
14699
- if (existsSync69(workflowPath)) {
15083
+ const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
15084
+ await mkdir19(path51.dirname(workflowPath), { recursive: true });
15085
+ if (existsSync74(workflowPath)) {
14700
15086
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
14701
15087
  return;
14702
15088
  }
14703
- await writeFile33(workflowPath, `name: haive-enforcement
15089
+ await writeFile34(workflowPath, `name: haive-enforcement
14704
15090
 
14705
15091
  on:
14706
15092
  pull_request:
@@ -14727,7 +15113,7 @@ jobs:
14727
15113
  HAIVE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
14728
15114
  run: haive enforce ci
14729
15115
  `, "utf8");
14730
- ui.success(`Created ${path50.relative(root, workflowPath)}`);
15116
+ ui.success(`Created ${path51.relative(root, workflowPath)}`);
14731
15117
  }
14732
15118
  function printReport(report, json, explain = false) {
14733
15119
  if (json) {
@@ -14787,7 +15173,7 @@ async function readHookPayload() {
14787
15173
  }
14788
15174
  function resolveRoot(dir, payload) {
14789
15175
  try {
14790
- return findProjectRoot49(dir ?? payload.cwd);
15176
+ return findProjectRoot52(dir ?? payload.cwd);
14791
15177
  } catch {
14792
15178
  return null;
14793
15179
  }
@@ -14824,15 +15210,15 @@ function extractToolPaths(payload, root) {
14824
15210
  }
14825
15211
  function normalizeToolPath(file, root) {
14826
15212
  const normalized = file.replace(/\\/g, "/");
14827
- if (!path50.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
14828
- return path50.relative(root, normalized).replace(/\\/g, "/");
15213
+ if (!path51.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
15214
+ return path51.relative(root, normalized).replace(/\\/g, "/");
14829
15215
  }
14830
15216
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
14831
- if (!existsSync69(paths.memoriesDir)) return [];
15217
+ if (!existsSync74(paths.memoriesDir)) return [];
14832
15218
  const marker = await readRecentBriefingMarker(paths, sessionId);
14833
15219
  const consulted = new Set(marker?.memory_ids ?? []);
14834
15220
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
14835
- const all = await loadMemoriesFromDir36(paths.memoriesDir);
15221
+ const all = await loadMemoriesFromDir37(paths.memoriesDir);
14836
15222
  return all.filter(({ memory: memory2 }) => {
14837
15223
  const fm = memory2.frontmatter;
14838
15224
  if (!policyTypes.has(fm.type)) return false;
@@ -14902,16 +15288,16 @@ function registerRun(program2) {
14902
15288
 
14903
15289
  // src/commands/sensors.ts
14904
15290
  import { execFile as execFile2 } from "child_process";
14905
- import { existsSync as existsSync70 } from "fs";
14906
- import { chmod as chmod3, mkdir as mkdir20, readFile as readFile23, writeFile as writeFile34 } from "fs/promises";
14907
- import path51 from "path";
15291
+ import { existsSync as existsSync75 } from "fs";
15292
+ import { chmod as chmod3, mkdir as mkdir20, readFile as readFile24, writeFile as writeFile35 } from "fs/promises";
15293
+ import path53 from "path";
14908
15294
  import { promisify as promisify2 } from "util";
14909
15295
  import "commander";
14910
15296
  import {
14911
- findProjectRoot as findProjectRoot50,
15297
+ findProjectRoot as findProjectRoot53,
14912
15298
  isRetiredMemory as isRetiredMemory3,
14913
- loadMemoriesFromDir as loadMemoriesFromDir37,
14914
- resolveHaivePaths as resolveHaivePaths46,
15299
+ loadMemoriesFromDir as loadMemoriesFromDir38,
15300
+ resolveHaivePaths as resolveHaivePaths49,
14915
15301
  runSensors as runSensors2,
14916
15302
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
14917
15303
  serializeMemory as serializeMemory26
@@ -14920,8 +15306,8 @@ var exec2 = promisify2(execFile2);
14920
15306
  function registerSensors(program2) {
14921
15307
  const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
14922
15308
  sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
14923
- const root = findProjectRoot50(opts.dir);
14924
- const paths = resolveHaivePaths46(root);
15309
+ const root = findProjectRoot53(opts.dir);
15310
+ const paths = resolveHaivePaths49(root);
14925
15311
  const rows = await sensorRows(paths);
14926
15312
  if (opts.json) {
14927
15313
  console.log(JSON.stringify(rows, null, 2));
@@ -14941,10 +15327,10 @@ function registerSensors(program2) {
14941
15327
  }
14942
15328
  });
14943
15329
  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) => {
14944
- const root = findProjectRoot50(opts.dir);
14945
- const paths = resolveHaivePaths46(root);
15330
+ const root = findProjectRoot53(opts.dir);
15331
+ const paths = resolveHaivePaths49(root);
14946
15332
  const memories = await runnableSensorMemories(paths);
14947
- const diff = opts.diffFile ? await readFile23(path51.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
15333
+ const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
14948
15334
  const targets = sensorTargetsFromDiff2(diff);
14949
15335
  const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
14950
15336
  const output = {
@@ -14983,9 +15369,9 @@ function registerSensors(program2) {
14983
15369
  process.exitCode = 1;
14984
15370
  return;
14985
15371
  }
14986
- const root = findProjectRoot50(opts.dir);
14987
- const paths = resolveHaivePaths46(root);
14988
- const loaded = existsSync70(paths.memoriesDir) ? await loadMemoriesFromDir37(paths.memoriesDir) : [];
15372
+ const root = findProjectRoot53(opts.dir);
15373
+ const paths = resolveHaivePaths49(root);
15374
+ const loaded = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
14989
15375
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
14990
15376
  if (!found) {
14991
15377
  ui.error(`No memory found with id ${id}`);
@@ -15005,7 +15391,7 @@ function registerSensors(program2) {
15005
15391
  },
15006
15392
  body: found.memory.body
15007
15393
  };
15008
- await writeFile34(found.filePath, serializeMemory26(next), "utf8");
15394
+ await writeFile35(found.filePath, serializeMemory26(next), "utf8");
15009
15395
  ui.success(`Updated ${id}: sensor severity=${severity}`);
15010
15396
  if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
15011
15397
  ui.info(`message=${sensor.message}`);
@@ -15017,16 +15403,16 @@ function registerSensors(program2) {
15017
15403
  process.exitCode = 1;
15018
15404
  return;
15019
15405
  }
15020
- const root = findProjectRoot50(opts.dir);
15021
- const paths = resolveHaivePaths46(root);
15406
+ const root = findProjectRoot53(opts.dir);
15407
+ const paths = resolveHaivePaths49(root);
15022
15408
  const rows = await sensorRows(paths);
15023
- const outDir = path51.resolve(root, opts.outDir ?? ".ai/generated");
15409
+ const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
15024
15410
  await mkdir20(outDir, { recursive: true });
15025
- const outPath = path51.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
15411
+ const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
15026
15412
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
15027
- await writeFile34(outPath, content, "utf8");
15413
+ await writeFile35(outPath, content, "utf8");
15028
15414
  if (format === "grep") await chmod3(outPath, 493);
15029
- ui.success(`Exported ${rows.length} sensor(s): ${path51.relative(root, outPath)}`);
15415
+ ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
15030
15416
  });
15031
15417
  }
15032
15418
  async function sensorRows(paths) {
@@ -15047,8 +15433,8 @@ async function sensorRows(paths) {
15047
15433
  });
15048
15434
  }
15049
15435
  async function runnableSensorMemories(paths, regexOnly = true) {
15050
- if (!existsSync70(paths.memoriesDir)) return [];
15051
- const loaded = await loadMemoriesFromDir37(paths.memoriesDir);
15436
+ if (!existsSync75(paths.memoriesDir)) return [];
15437
+ const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
15052
15438
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
15053
15439
  const sensor = memory2.frontmatter.sensor;
15054
15440
  if (!sensor) return false;
@@ -15089,8 +15475,8 @@ function shellQuote(value) {
15089
15475
  }
15090
15476
 
15091
15477
  // src/index.ts
15092
- var program = new Command53();
15093
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.10.9").option("--advanced", "show maintenance and experimental commands in help");
15478
+ var program = new Command56();
15479
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.0").option("--advanced", "show maintenance and experimental commands in help");
15094
15480
  registerInit(program);
15095
15481
  registerWelcome(program);
15096
15482
  registerResolveProject(program);
@@ -15114,6 +15500,8 @@ registerMemoryQuery(memory);
15114
15500
  registerMemoryPromote(memory);
15115
15501
  registerMemoryVerify(memory);
15116
15502
  registerMemoryStats(memory);
15503
+ registerMemoryImpact(memory);
15504
+ registerMemoryFeedback(memory);
15117
15505
  registerMemoryReject(memory);
15118
15506
  registerMemoryAutoPromote(memory);
15119
15507
  registerMemoryForFiles(memory);
@@ -15144,6 +15532,7 @@ registerHub(program);
15144
15532
  registerStats(program);
15145
15533
  registerBench(program);
15146
15534
  registerBenchmark(program);
15535
+ registerEval(program);
15147
15536
  registerDoctor(program);
15148
15537
  registerPlayback(program);
15149
15538
  registerPrecommit(program);