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