@hiveai/cli 0.10.9 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1050 -701
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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,7 +2743,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
2743
2743
|
}
|
|
2744
2744
|
|
|
2745
2745
|
// src/commands/init.ts
|
|
2746
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
2746
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.11.0"}`;
|
|
2747
2747
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
2748
2748
|
|
|
2749
2749
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3663,55 +3663,66 @@ import {
|
|
|
3663
3663
|
serializeMemory as serializeMemory32
|
|
3664
3664
|
} from "@hiveai/core";
|
|
3665
3665
|
import { z as z7 } from "zod";
|
|
3666
|
-
import { readFile as readFile22, readdir as readdir22 } from "fs/promises";
|
|
3667
|
-
import { existsSync as existsSync82 } from "fs";
|
|
3668
|
-
import path42 from "path";
|
|
3669
3666
|
import {
|
|
3670
|
-
|
|
3667
|
+
computeImpact,
|
|
3671
3668
|
getUsage as getUsage22,
|
|
3672
|
-
inferModulesFromPaths,
|
|
3673
3669
|
loadMemoriesFromDir as loadMemoriesFromDir6,
|
|
3674
3670
|
loadUsageIndex as loadUsageIndex32,
|
|
3675
|
-
|
|
3676
|
-
|
|
3671
|
+
recordApplied,
|
|
3672
|
+
recordRejection as recordRejection2,
|
|
3673
|
+
saveUsageIndex as saveUsageIndex2
|
|
3677
3674
|
} from "@hiveai/core";
|
|
3675
|
+
import { existsSync as existsSync82 } from "fs";
|
|
3678
3676
|
import { z as z8 } from "zod";
|
|
3677
|
+
import { readFile as readFile22, readdir as readdir22 } from "fs/promises";
|
|
3679
3678
|
import { existsSync as existsSync92 } from "fs";
|
|
3679
|
+
import path42 from "path";
|
|
3680
3680
|
import {
|
|
3681
|
-
deriveConfidence as
|
|
3681
|
+
deriveConfidence as deriveConfidence2,
|
|
3682
3682
|
getUsage as getUsage3,
|
|
3683
|
+
inferModulesFromPaths,
|
|
3683
3684
|
loadMemoriesFromDir as loadMemoriesFromDir7,
|
|
3684
|
-
loadUsageIndex as loadUsageIndex4
|
|
3685
|
+
loadUsageIndex as loadUsageIndex4,
|
|
3686
|
+
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
3687
|
+
trackReads as trackReads22
|
|
3685
3688
|
} from "@hiveai/core";
|
|
3686
3689
|
import { z as z9 } from "zod";
|
|
3687
3690
|
import { existsSync as existsSync102 } from "fs";
|
|
3688
|
-
import { unlink } from "fs/promises";
|
|
3689
3691
|
import {
|
|
3692
|
+
deriveConfidence as deriveConfidence3,
|
|
3693
|
+
getUsage as getUsage4,
|
|
3690
3694
|
loadMemoriesFromDir as loadMemoriesFromDir8,
|
|
3691
|
-
loadUsageIndex as loadUsageIndex5
|
|
3692
|
-
saveUsageIndex as saveUsageIndex2
|
|
3695
|
+
loadUsageIndex as loadUsageIndex5
|
|
3693
3696
|
} from "@hiveai/core";
|
|
3694
3697
|
import { z as z10 } from "zod";
|
|
3695
|
-
import { writeFile as writeFile52 } from "fs/promises";
|
|
3696
3698
|
import { existsSync as existsSync112 } from "fs";
|
|
3697
|
-
import {
|
|
3698
|
-
import { z as z11 } from "zod";
|
|
3699
|
-
import { existsSync as existsSync122 } from "fs";
|
|
3699
|
+
import { unlink } from "fs/promises";
|
|
3700
3700
|
import {
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3701
|
+
loadMemoriesFromDir as loadMemoriesFromDir9,
|
|
3702
|
+
loadUsageIndex as loadUsageIndex6,
|
|
3703
|
+
saveUsageIndex as saveUsageIndex3
|
|
3704
3704
|
} from "@hiveai/core";
|
|
3705
|
+
import { z as z11 } from "zod";
|
|
3706
|
+
import { writeFile as writeFile52 } from "fs/promises";
|
|
3707
|
+
import { existsSync as existsSync122 } from "fs";
|
|
3708
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir10, serializeMemory as serializeMemory4 } from "@hiveai/core";
|
|
3705
3709
|
import { z as z12 } from "zod";
|
|
3706
|
-
import { writeFile as writeFile62 } from "fs/promises";
|
|
3707
3710
|
import { existsSync as existsSync132 } from "fs";
|
|
3708
3711
|
import {
|
|
3712
|
+
getUsage as getUsage5,
|
|
3709
3713
|
loadMemoriesFromDir as loadMemoriesFromDir11,
|
|
3710
|
-
|
|
3714
|
+
loadUsageIndex as loadUsageIndex7
|
|
3711
3715
|
} from "@hiveai/core";
|
|
3712
3716
|
import { z as z13 } from "zod";
|
|
3713
|
-
import {
|
|
3717
|
+
import { writeFile as writeFile62 } from "fs/promises";
|
|
3714
3718
|
import { existsSync as existsSync142 } from "fs";
|
|
3719
|
+
import {
|
|
3720
|
+
loadMemoriesFromDir as loadMemoriesFromDir12,
|
|
3721
|
+
serializeMemory as serializeMemory5
|
|
3722
|
+
} from "@hiveai/core";
|
|
3723
|
+
import { z as z14 } from "zod";
|
|
3724
|
+
import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
|
|
3725
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3715
3726
|
import path52 from "path";
|
|
3716
3727
|
import {
|
|
3717
3728
|
buildFrontmatter as buildFrontmatter22,
|
|
@@ -3719,9 +3730,9 @@ import {
|
|
|
3719
3730
|
serializeMemory as serializeMemory6,
|
|
3720
3731
|
suggestSensorFromMemory as suggestSensorFromMemory2
|
|
3721
3732
|
} from "@hiveai/core";
|
|
3722
|
-
import { z as
|
|
3733
|
+
import { z as z15 } from "zod";
|
|
3723
3734
|
import { mkdir as mkdir42, writeFile as writeFile82 } from "fs/promises";
|
|
3724
|
-
import { existsSync as
|
|
3735
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3725
3736
|
import path62 from "path";
|
|
3726
3737
|
import {
|
|
3727
3738
|
buildFrontmatter as buildFrontmatter3,
|
|
@@ -3729,35 +3740,37 @@ import {
|
|
|
3729
3740
|
memoryFilePath as memoryFilePath3,
|
|
3730
3741
|
serializeMemory as serializeMemory7
|
|
3731
3742
|
} from "@hiveai/core";
|
|
3732
|
-
import { z as
|
|
3743
|
+
import { z as z16 } from "zod";
|
|
3733
3744
|
import { writeFile as writeFile10, mkdir as mkdir62 } from "fs/promises";
|
|
3734
|
-
import { existsSync as
|
|
3745
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3735
3746
|
import path82 from "path";
|
|
3736
3747
|
import {
|
|
3737
3748
|
buildFrontmatter as buildFrontmatter4,
|
|
3738
|
-
loadMemoriesFromDir as
|
|
3749
|
+
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
3739
3750
|
memoryFilePath as memoryFilePath4,
|
|
3740
3751
|
serializeMemory as serializeMemory8
|
|
3741
3752
|
} from "@hiveai/core";
|
|
3742
|
-
import { z as
|
|
3753
|
+
import { z as z17 } from "zod";
|
|
3743
3754
|
import {
|
|
3744
3755
|
appendUsageEvent,
|
|
3745
3756
|
appendRuntimeJournalEntry,
|
|
3746
3757
|
loadConfig as loadConfig22
|
|
3747
3758
|
} from "@hiveai/core";
|
|
3748
3759
|
import { mkdir as mkdir52, writeFile as writeFile92, rm } from "fs/promises";
|
|
3749
|
-
import { existsSync as
|
|
3760
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3750
3761
|
import path72 from "path";
|
|
3751
3762
|
import { execSync } from "child_process";
|
|
3752
3763
|
import { readFile as readFile42, writeFile as writeFile11 } from "fs/promises";
|
|
3753
|
-
import { existsSync as
|
|
3764
|
+
import { existsSync as existsSync20 } from "fs";
|
|
3754
3765
|
import {
|
|
3755
3766
|
allocateBudget,
|
|
3767
|
+
computeImpact as computeImpact2,
|
|
3756
3768
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
3757
3769
|
deriveConfidence as deriveConfidence4,
|
|
3758
3770
|
estimateTokens,
|
|
3771
|
+
evaluateSkillActivation,
|
|
3759
3772
|
extractActionsBriefBody as extractActionsBriefBody2,
|
|
3760
|
-
getUsage as
|
|
3773
|
+
getUsage as getUsage6,
|
|
3761
3774
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
3762
3775
|
isAutoPromoteEligible,
|
|
3763
3776
|
isDecaying,
|
|
@@ -3766,8 +3779,8 @@ import {
|
|
|
3766
3779
|
literalMatchesAnyToken as literalMatchesAnyToken22,
|
|
3767
3780
|
loadCodeMap as loadCodeMap5,
|
|
3768
3781
|
loadConfig as loadConfig3,
|
|
3769
|
-
loadMemoriesFromDir as
|
|
3770
|
-
loadUsageIndex as
|
|
3782
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
3783
|
+
loadUsageIndex as loadUsageIndex8,
|
|
3771
3784
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
|
|
3772
3785
|
queryCodeMap as queryCodeMap2,
|
|
3773
3786
|
resolveBriefingBudget as resolveBriefingBudget2,
|
|
@@ -3779,77 +3792,77 @@ import {
|
|
|
3779
3792
|
truncateToTokens,
|
|
3780
3793
|
writeBriefingMarker as writeBriefingMarker2
|
|
3781
3794
|
} from "@hiveai/core";
|
|
3782
|
-
import { z as
|
|
3795
|
+
import { z as z18 } from "zod";
|
|
3783
3796
|
import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
|
|
3784
|
-
import { existsSync as
|
|
3797
|
+
import { existsSync as existsSync19 } from "fs";
|
|
3785
3798
|
import path92 from "path";
|
|
3786
3799
|
import { isGlobPath, isStackPackSeed as isStackPackSeed2, pathsOverlap } from "@hiveai/core";
|
|
3787
3800
|
import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap22, queryCodeMap as queryCodeMap22 } from "@hiveai/core";
|
|
3788
|
-
import { z as z18 } from "zod";
|
|
3789
|
-
import { existsSync as existsSync20 } from "fs";
|
|
3790
|
-
import { loadMemoriesFromDir as loadMemoriesFromDir14 } from "@hiveai/core";
|
|
3791
3801
|
import { z as z19 } from "zod";
|
|
3792
3802
|
import { existsSync as existsSync21 } from "fs";
|
|
3793
3803
|
import { loadMemoriesFromDir as loadMemoriesFromDir15 } from "@hiveai/core";
|
|
3794
3804
|
import { z as z20 } from "zod";
|
|
3805
|
+
import { existsSync as existsSync222 } from "fs";
|
|
3806
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir16 } from "@hiveai/core";
|
|
3795
3807
|
import { z as z21 } from "zod";
|
|
3796
3808
|
import { z as z22 } from "zod";
|
|
3797
|
-
import {
|
|
3809
|
+
import { z as z23 } from "zod";
|
|
3810
|
+
import { existsSync as existsSync23 } from "fs";
|
|
3798
3811
|
import { spawn } from "child_process";
|
|
3799
3812
|
import path102 from "path";
|
|
3800
3813
|
import {
|
|
3801
3814
|
deriveConfidence as deriveConfidence5,
|
|
3802
|
-
getUsage as
|
|
3815
|
+
getUsage as getUsage7,
|
|
3803
3816
|
loadCodeMap as loadCodeMap32,
|
|
3804
|
-
loadMemoriesFromDir as
|
|
3805
|
-
loadUsageIndex as
|
|
3817
|
+
loadMemoriesFromDir as loadMemoriesFromDir17,
|
|
3818
|
+
loadUsageIndex as loadUsageIndex9,
|
|
3806
3819
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
|
|
3807
3820
|
} from "@hiveai/core";
|
|
3808
|
-
import { z as
|
|
3809
|
-
import { existsSync as
|
|
3821
|
+
import { z as z24 } from "zod";
|
|
3822
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3810
3823
|
import {
|
|
3811
3824
|
addedLinesFromDiff,
|
|
3812
3825
|
deriveConfidence as deriveConfidence6,
|
|
3813
|
-
getUsage as
|
|
3826
|
+
getUsage as getUsage8,
|
|
3814
3827
|
isRetiredMemory as isRetiredMemory2,
|
|
3815
|
-
loadMemoriesFromDir as
|
|
3816
|
-
loadUsageIndex as
|
|
3828
|
+
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
3829
|
+
loadUsageIndex as loadUsageIndex10,
|
|
3817
3830
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
3818
3831
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
3819
3832
|
runSensors,
|
|
3820
3833
|
sensorTargetsFromDiff,
|
|
3821
3834
|
tokenizeQuery as tokenizeQuery3
|
|
3822
3835
|
} from "@hiveai/core";
|
|
3823
|
-
import { z as z24 } from "zod";
|
|
3824
|
-
import { existsSync as existsSync24 } from "fs";
|
|
3825
|
-
import {
|
|
3826
|
-
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
3827
|
-
tokenizeQuery as tokenizeQuery4
|
|
3828
|
-
} from "@hiveai/core";
|
|
3829
3836
|
import { z as z25 } from "zod";
|
|
3830
3837
|
import { existsSync as existsSync25 } from "fs";
|
|
3831
|
-
import { spawn as spawn2 } from "child_process";
|
|
3832
3838
|
import {
|
|
3833
|
-
deriveConfidence as deriveConfidence7,
|
|
3834
|
-
getUsage as getUsage8,
|
|
3835
3839
|
loadMemoriesFromDir as loadMemoriesFromDir19,
|
|
3836
|
-
|
|
3837
|
-
pathsOverlap as singlePathsOverlap
|
|
3840
|
+
tokenizeQuery as tokenizeQuery4
|
|
3838
3841
|
} from "@hiveai/core";
|
|
3839
3842
|
import { z as z26 } from "zod";
|
|
3840
3843
|
import { existsSync as existsSync26 } from "fs";
|
|
3844
|
+
import { spawn as spawn2 } from "child_process";
|
|
3841
3845
|
import {
|
|
3842
|
-
deriveConfidence as
|
|
3846
|
+
deriveConfidence as deriveConfidence7,
|
|
3843
3847
|
getUsage as getUsage9,
|
|
3844
3848
|
loadMemoriesFromDir as loadMemoriesFromDir20,
|
|
3845
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,
|
|
3846
3859
|
pathsOverlap as pathsOverlap2,
|
|
3847
3860
|
tokenizeQuery as tokenizeQuery5
|
|
3848
3861
|
} from "@hiveai/core";
|
|
3849
|
-
import { z as z27 } from "zod";
|
|
3850
3862
|
import { z as z28 } from "zod";
|
|
3863
|
+
import { z as z29 } from "zod";
|
|
3851
3864
|
import { mkdir as mkdir72, writeFile as writeFile12 } from "fs/promises";
|
|
3852
|
-
import { existsSync as
|
|
3865
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3853
3866
|
import path112 from "path";
|
|
3854
3867
|
import { execSync as execSync2 } from "child_process";
|
|
3855
3868
|
import {
|
|
@@ -3858,28 +3871,28 @@ import {
|
|
|
3858
3871
|
readUsageEvents,
|
|
3859
3872
|
serializeMemory as serializeMemory10
|
|
3860
3873
|
} from "@hiveai/core";
|
|
3861
|
-
import { z as
|
|
3862
|
-
import { existsSync as
|
|
3874
|
+
import { z as z30 } from "zod";
|
|
3875
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3863
3876
|
import {
|
|
3864
3877
|
findLexicalConflictPairs,
|
|
3865
3878
|
findTopicStatusConflictPairs,
|
|
3866
|
-
loadMemoriesFromDir as
|
|
3879
|
+
loadMemoriesFromDir as loadMemoriesFromDir222
|
|
3867
3880
|
} from "@hiveai/core";
|
|
3868
|
-
import { z as z30 } from "zod";
|
|
3869
|
-
import { resolveProjectInfo } from "@hiveai/core";
|
|
3870
3881
|
import { z as z31 } from "zod";
|
|
3871
|
-
import {
|
|
3882
|
+
import { resolveProjectInfo } from "@hiveai/core";
|
|
3872
3883
|
import { z as z32 } from "zod";
|
|
3873
|
-
import {
|
|
3874
|
-
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir222 } from "@hiveai/core";
|
|
3884
|
+
import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
|
|
3875
3885
|
import { z as z33 } from "zod";
|
|
3876
|
-
import {
|
|
3886
|
+
import { existsSync as existsSync30 } from "fs";
|
|
3887
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hiveai/core";
|
|
3877
3888
|
import { z as z34 } from "zod";
|
|
3878
|
-
import {
|
|
3889
|
+
import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
|
|
3879
3890
|
import { z as z35 } from "zod";
|
|
3891
|
+
import { readRuntimeJournalTail } from "@hiveai/core";
|
|
3880
3892
|
import { z as z36 } from "zod";
|
|
3881
3893
|
import { z as z37 } from "zod";
|
|
3882
3894
|
import { z as z38 } from "zod";
|
|
3895
|
+
import { z as z39 } from "zod";
|
|
3883
3896
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
|
|
3884
3897
|
function createContext(options = {}) {
|
|
3885
3898
|
const env = options.env ?? process.env;
|
|
@@ -3996,6 +4009,13 @@ var MemSaveInputSchema = {
|
|
|
3996
4009
|
commit: z4.string().optional().describe("Anchor commit SHA (for staleness detection later)"),
|
|
3997
4010
|
topic: z4.string().optional().describe(
|
|
3998
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."
|
|
3999
4019
|
)
|
|
4000
4020
|
};
|
|
4001
4021
|
function bodyHash(body) {
|
|
@@ -4114,7 +4134,8 @@ async function memSave(input, ctx) {
|
|
|
4114
4134
|
commit: input.commit,
|
|
4115
4135
|
topic: input.topic,
|
|
4116
4136
|
status: haiveConfig.defaultStatus === "validated" ? "validated" : void 0,
|
|
4117
|
-
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
|
|
4118
4139
|
});
|
|
4119
4140
|
const file = memoryFilePath2(
|
|
4120
4141
|
ctx.paths,
|
|
@@ -4463,14 +4484,51 @@ async function memReject(input, ctx) {
|
|
|
4463
4484
|
rejection_reason: u?.rejection_reason ?? null
|
|
4464
4485
|
};
|
|
4465
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
|
+
}
|
|
4466
4524
|
var MemForFilesInputSchema = {
|
|
4467
|
-
files:
|
|
4468
|
-
include_module_contexts:
|
|
4469
|
-
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")
|
|
4470
4528
|
};
|
|
4471
4529
|
async function memForFiles(input, ctx) {
|
|
4472
4530
|
const inferred = inferModulesFromPaths(input.files);
|
|
4473
|
-
if (!
|
|
4531
|
+
if (!existsSync92(ctx.paths.memoriesDir)) {
|
|
4474
4532
|
return {
|
|
4475
4533
|
inferred_modules: inferred,
|
|
4476
4534
|
by_anchor: [],
|
|
@@ -4479,8 +4537,8 @@ async function memForFiles(input, ctx) {
|
|
|
4479
4537
|
module_contexts: await loadModuleContexts(ctx, inferred, input.include_module_contexts)
|
|
4480
4538
|
};
|
|
4481
4539
|
}
|
|
4482
|
-
const all = await
|
|
4483
|
-
const usage = await
|
|
4540
|
+
const all = await loadMemoriesFromDir7(ctx.paths.memoriesDir);
|
|
4541
|
+
const usage = await loadUsageIndex4(ctx.paths);
|
|
4484
4542
|
const seen = /* @__PURE__ */ new Set();
|
|
4485
4543
|
const byAnchor = [];
|
|
4486
4544
|
const byModule = [];
|
|
@@ -4528,7 +4586,7 @@ async function memForFiles(input, ctx) {
|
|
|
4528
4586
|
}
|
|
4529
4587
|
function toMatch(loaded, reason, usage) {
|
|
4530
4588
|
const fm = loaded.memory.frontmatter;
|
|
4531
|
-
const u =
|
|
4589
|
+
const u = getUsage3(usage, fm.id);
|
|
4532
4590
|
return {
|
|
4533
4591
|
id: fm.id,
|
|
4534
4592
|
scope: fm.scope,
|
|
@@ -4592,7 +4650,7 @@ function extractPathSegments(files) {
|
|
|
4592
4650
|
}
|
|
4593
4651
|
async function loadModuleContexts(ctx, modules, enabled) {
|
|
4594
4652
|
if (!enabled || modules.length === 0) return [];
|
|
4595
|
-
if (!
|
|
4653
|
+
if (!existsSync92(ctx.paths.modulesContextDir)) return [];
|
|
4596
4654
|
const available = new Set(
|
|
4597
4655
|
(await readdir22(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
4598
4656
|
);
|
|
@@ -4600,24 +4658,24 @@ async function loadModuleContexts(ctx, modules, enabled) {
|
|
|
4600
4658
|
for (const m of modules) {
|
|
4601
4659
|
if (!available.has(m)) continue;
|
|
4602
4660
|
const file = path42.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
4603
|
-
if (
|
|
4661
|
+
if (existsSync92(file)) {
|
|
4604
4662
|
out.push({ name: m, content: await readFile22(file, "utf8") });
|
|
4605
4663
|
}
|
|
4606
4664
|
}
|
|
4607
4665
|
return out;
|
|
4608
4666
|
}
|
|
4609
4667
|
var MemGetInputSchema = {
|
|
4610
|
-
id:
|
|
4668
|
+
id: z10.string().min(1).describe("Memory id to fetch")
|
|
4611
4669
|
};
|
|
4612
4670
|
async function memGet(input, ctx) {
|
|
4613
|
-
if (!
|
|
4671
|
+
if (!existsSync102(ctx.paths.memoriesDir)) {
|
|
4614
4672
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
4615
4673
|
}
|
|
4616
|
-
const all = await
|
|
4674
|
+
const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
|
|
4617
4675
|
const found = all.find((m) => m.memory.frontmatter.id === input.id);
|
|
4618
4676
|
if (!found) throw new Error(`No memory with id "${input.id}".`);
|
|
4619
4677
|
const fm = found.memory.frontmatter;
|
|
4620
|
-
const u =
|
|
4678
|
+
const u = getUsage4(await loadUsageIndex5(ctx.paths), fm.id);
|
|
4621
4679
|
return {
|
|
4622
4680
|
id: fm.id,
|
|
4623
4681
|
scope: fm.scope,
|
|
@@ -4641,43 +4699,43 @@ async function memGet(input, ctx) {
|
|
|
4641
4699
|
};
|
|
4642
4700
|
}
|
|
4643
4701
|
var MemDeleteInputSchema = {
|
|
4644
|
-
id:
|
|
4645
|
-
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")
|
|
4646
4704
|
};
|
|
4647
4705
|
async function memDelete(input, ctx) {
|
|
4648
|
-
if (!
|
|
4706
|
+
if (!existsSync112(ctx.paths.memoriesDir)) {
|
|
4649
4707
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
4650
4708
|
}
|
|
4651
|
-
const all = await
|
|
4709
|
+
const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
|
|
4652
4710
|
const found = all.find((m) => m.memory.frontmatter.id === input.id);
|
|
4653
4711
|
if (!found) throw new Error(`No memory with id "${input.id}".`);
|
|
4654
4712
|
await unlink(found.filePath);
|
|
4655
4713
|
let usageRemoved = false;
|
|
4656
4714
|
if (!input.keep_usage) {
|
|
4657
|
-
const idx = await
|
|
4715
|
+
const idx = await loadUsageIndex6(ctx.paths);
|
|
4658
4716
|
if (idx.by_id[input.id]) {
|
|
4659
4717
|
delete idx.by_id[input.id];
|
|
4660
|
-
await
|
|
4718
|
+
await saveUsageIndex3(ctx.paths, idx);
|
|
4661
4719
|
usageRemoved = true;
|
|
4662
4720
|
}
|
|
4663
4721
|
}
|
|
4664
4722
|
return { id: input.id, deleted_file: found.filePath, usage_removed: usageRemoved };
|
|
4665
4723
|
}
|
|
4666
4724
|
var MemUpdateInputSchema = {
|
|
4667
|
-
id:
|
|
4668
|
-
body:
|
|
4669
|
-
tags:
|
|
4670
|
-
paths:
|
|
4671
|
-
symbols:
|
|
4672
|
-
commit:
|
|
4673
|
-
domain:
|
|
4674
|
-
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")
|
|
4675
4733
|
};
|
|
4676
4734
|
async function memUpdate(input, ctx) {
|
|
4677
|
-
if (!
|
|
4735
|
+
if (!existsSync122(ctx.paths.memoriesDir)) {
|
|
4678
4736
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
4679
4737
|
}
|
|
4680
|
-
const memories = await
|
|
4738
|
+
const memories = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
|
|
4681
4739
|
const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
|
|
4682
4740
|
if (!loaded) throw new Error(`No memory with id "${input.id}".`);
|
|
4683
4741
|
const { frontmatter, body } = loaded.memory;
|
|
@@ -4718,12 +4776,12 @@ async function memUpdate(input, ctx) {
|
|
|
4718
4776
|
return { id: input.id, file_path: loaded.filePath, updated_fields };
|
|
4719
4777
|
}
|
|
4720
4778
|
var MemPendingInputSchema = {
|
|
4721
|
-
scope:
|
|
4779
|
+
scope: z13.enum(["personal", "team", "module"]).optional()
|
|
4722
4780
|
};
|
|
4723
4781
|
async function memPending(input, ctx) {
|
|
4724
|
-
if (!
|
|
4725
|
-
const all = await
|
|
4726
|
-
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);
|
|
4727
4785
|
const now = Date.now();
|
|
4728
4786
|
const proposed = all.filter(({ memory: memory2 }) => {
|
|
4729
4787
|
if (memory2.frontmatter.status !== "proposed") return false;
|
|
@@ -4731,12 +4789,12 @@ async function memPending(input, ctx) {
|
|
|
4731
4789
|
return true;
|
|
4732
4790
|
});
|
|
4733
4791
|
proposed.sort(
|
|
4734
|
-
(a, b) =>
|
|
4792
|
+
(a, b) => getUsage5(usage, b.memory.frontmatter.id).read_count - getUsage5(usage, a.memory.frontmatter.id).read_count
|
|
4735
4793
|
);
|
|
4736
4794
|
return {
|
|
4737
4795
|
pending: proposed.map(({ memory: memory2, filePath }) => {
|
|
4738
4796
|
const fm = memory2.frontmatter;
|
|
4739
|
-
const u =
|
|
4797
|
+
const u = getUsage5(usage, fm.id);
|
|
4740
4798
|
return {
|
|
4741
4799
|
id: fm.id,
|
|
4742
4800
|
scope: fm.scope,
|
|
@@ -4752,13 +4810,13 @@ async function memPending(input, ctx) {
|
|
|
4752
4810
|
};
|
|
4753
4811
|
}
|
|
4754
4812
|
var MemApproveInputSchema = {
|
|
4755
|
-
id:
|
|
4813
|
+
id: z14.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
|
|
4756
4814
|
};
|
|
4757
4815
|
async function memApprove(input, ctx) {
|
|
4758
|
-
if (!
|
|
4816
|
+
if (!existsSync142(ctx.paths.memoriesDir)) {
|
|
4759
4817
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
4760
4818
|
}
|
|
4761
|
-
const all = await
|
|
4819
|
+
const all = await loadMemoriesFromDir12(ctx.paths.memoriesDir);
|
|
4762
4820
|
const found = all.find((m) => m.memory.frontmatter.id === input.id);
|
|
4763
4821
|
if (!found) throw new Error(`No memory with id "${input.id}".`);
|
|
4764
4822
|
const previous = found.memory.frontmatter.status;
|
|
@@ -4775,17 +4833,17 @@ async function memApprove(input, ctx) {
|
|
|
4775
4833
|
};
|
|
4776
4834
|
}
|
|
4777
4835
|
var MemTriedInputSchema = {
|
|
4778
|
-
what:
|
|
4779
|
-
why_failed:
|
|
4780
|
-
instead:
|
|
4781
|
-
scope:
|
|
4782
|
-
module:
|
|
4783
|
-
tags:
|
|
4784
|
-
paths:
|
|
4785
|
-
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")
|
|
4786
4844
|
};
|
|
4787
4845
|
async function memTried(input, ctx) {
|
|
4788
|
-
if (!
|
|
4846
|
+
if (!existsSync15(ctx.paths.haiveDir)) {
|
|
4789
4847
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
4790
4848
|
}
|
|
4791
4849
|
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
@@ -4811,27 +4869,27 @@ async function memTried(input, ctx) {
|
|
|
4811
4869
|
}
|
|
4812
4870
|
const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
4813
4871
|
await mkdir32(path52.dirname(file), { recursive: true });
|
|
4814
|
-
if (
|
|
4872
|
+
if (existsSync15(file)) {
|
|
4815
4873
|
throw new Error(`Memory already exists at ${file}`);
|
|
4816
4874
|
}
|
|
4817
4875
|
await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
|
|
4818
4876
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
4819
4877
|
}
|
|
4820
4878
|
var MemObserveInputSchema = {
|
|
4821
|
-
what:
|
|
4822
|
-
where:
|
|
4823
|
-
impact:
|
|
4824
|
-
fix:
|
|
4825
|
-
scope:
|
|
4826
|
-
module:
|
|
4827
|
-
tags:
|
|
4828
|
-
author:
|
|
4829
|
-
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(
|
|
4830
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."
|
|
4831
4889
|
)
|
|
4832
4890
|
};
|
|
4833
4891
|
async function memObserve(input, ctx) {
|
|
4834
|
-
if (!
|
|
4892
|
+
if (!existsSync16(ctx.paths.haiveDir)) {
|
|
4835
4893
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
4836
4894
|
}
|
|
4837
4895
|
const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
|
|
@@ -4865,7 +4923,7 @@ async function memObserve(input, ctx) {
|
|
|
4865
4923
|
const body = lines.join("\n") + "\n";
|
|
4866
4924
|
const file = memoryFilePath3(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
4867
4925
|
await mkdir42(path62.dirname(file), { recursive: true });
|
|
4868
|
-
if (
|
|
4926
|
+
if (existsSync16(file)) {
|
|
4869
4927
|
throw new Error(`Memory already exists at ${file}`);
|
|
4870
4928
|
}
|
|
4871
4929
|
await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
|
|
@@ -4951,7 +5009,7 @@ var SessionTracker = class {
|
|
|
4951
5009
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
4952
5010
|
);
|
|
4953
5011
|
const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
|
|
4954
|
-
if (!ranPostTask && isSubstantialSession &&
|
|
5012
|
+
if (!ranPostTask && isSubstantialSession && existsSync17(this.ctx.paths.haiveDir)) {
|
|
4955
5013
|
try {
|
|
4956
5014
|
const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
|
|
4957
5015
|
const payload = {
|
|
@@ -4985,7 +5043,7 @@ var SessionTracker = class {
|
|
|
4985
5043
|
};
|
|
4986
5044
|
async function clearPendingDistill(ctx) {
|
|
4987
5045
|
const p = pendingDistillPath(ctx);
|
|
4988
|
-
if (
|
|
5046
|
+
if (existsSync17(p)) {
|
|
4989
5047
|
try {
|
|
4990
5048
|
await rm(p);
|
|
4991
5049
|
} catch {
|
|
@@ -5000,15 +5058,15 @@ function summarizeTools(events) {
|
|
|
5000
5058
|
return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
|
|
5001
5059
|
}
|
|
5002
5060
|
var MemSessionEndInputSchema = {
|
|
5003
|
-
goal:
|
|
5004
|
-
accomplished:
|
|
5005
|
-
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(
|
|
5006
5064
|
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
5007
5065
|
),
|
|
5008
|
-
files_touched:
|
|
5009
|
-
next_steps:
|
|
5010
|
-
scope:
|
|
5011
|
-
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)")
|
|
5012
5070
|
};
|
|
5013
5071
|
function recapTopic(scope, module) {
|
|
5014
5072
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
@@ -5038,7 +5096,7 @@ ${input.next_steps}`);
|
|
|
5038
5096
|
return lines.join("\n");
|
|
5039
5097
|
}
|
|
5040
5098
|
async function memSessionEnd(input, ctx) {
|
|
5041
|
-
if (!
|
|
5099
|
+
if (!existsSync18(ctx.paths.haiveDir)) {
|
|
5042
5100
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
5043
5101
|
}
|
|
5044
5102
|
const body = buildBody(input);
|
|
@@ -5049,12 +5107,12 @@ async function memSessionEnd(input, ctx) {
|
|
|
5049
5107
|
return rel.startsWith("..") ? p : rel;
|
|
5050
5108
|
});
|
|
5051
5109
|
const invalidPaths = normalizedFiles.filter(
|
|
5052
|
-
(p) => !
|
|
5110
|
+
(p) => !existsSync18(path82.resolve(ctx.paths.root, p))
|
|
5053
5111
|
);
|
|
5054
5112
|
if (invalidPaths.length > 0) {
|
|
5055
5113
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
5056
5114
|
}
|
|
5057
|
-
const existing =
|
|
5115
|
+
const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
|
|
5058
5116
|
const topicMatch = existing.find(
|
|
5059
5117
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === input.scope && (!input.module || memory2.frontmatter.module === input.module)
|
|
5060
5118
|
);
|
|
@@ -5235,7 +5293,7 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
5235
5293
|
}
|
|
5236
5294
|
async function loadModuleContexts2(ctx, modules) {
|
|
5237
5295
|
if (modules.length === 0) return [];
|
|
5238
|
-
if (!
|
|
5296
|
+
if (!existsSync19(ctx.paths.modulesContextDir)) return [];
|
|
5239
5297
|
const available = new Set(
|
|
5240
5298
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
5241
5299
|
);
|
|
@@ -5243,42 +5301,42 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
5243
5301
|
for (const m of modules) {
|
|
5244
5302
|
if (!available.has(m)) continue;
|
|
5245
5303
|
const file = path92.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
5246
|
-
if (
|
|
5304
|
+
if (existsSync19(file)) {
|
|
5247
5305
|
out.push({ name: m, content: await readFile32(file, "utf8") });
|
|
5248
5306
|
}
|
|
5249
5307
|
}
|
|
5250
5308
|
return out;
|
|
5251
5309
|
}
|
|
5252
5310
|
var GetBriefingInputSchema = {
|
|
5253
|
-
task:
|
|
5311
|
+
task: z18.string().optional().describe(
|
|
5254
5312
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
5255
5313
|
),
|
|
5256
|
-
files:
|
|
5257
|
-
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(
|
|
5258
5316
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
5259
5317
|
),
|
|
5260
|
-
max_memories:
|
|
5261
|
-
include_project_context:
|
|
5262
|
-
include_module_contexts:
|
|
5263
|
-
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(
|
|
5264
5322
|
"Use semantic ranking when a task is provided (requires `haive embeddings index`)."
|
|
5265
5323
|
),
|
|
5266
|
-
include_stale:
|
|
5267
|
-
track:
|
|
5268
|
-
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(
|
|
5269
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."
|
|
5270
5328
|
),
|
|
5271
|
-
symbols:
|
|
5329
|
+
symbols: z18.array(z18.string()).default([]).describe(
|
|
5272
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."
|
|
5273
5331
|
),
|
|
5274
|
-
min_semantic_score:
|
|
5332
|
+
min_semantic_score: z18.number().min(0).max(1).default(0).describe(
|
|
5275
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."
|
|
5276
5334
|
),
|
|
5277
|
-
budget_preset:
|
|
5335
|
+
budget_preset: z18.enum(["quick", "balanced", "deep"]).optional().describe(
|
|
5278
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."
|
|
5279
5337
|
)
|
|
5280
5338
|
};
|
|
5281
|
-
var GetBriefingZod =
|
|
5339
|
+
var GetBriefingZod = z18.object(GetBriefingInputSchema);
|
|
5282
5340
|
async function getBriefing(input, ctx) {
|
|
5283
5341
|
const resolvedBudget = resolveBriefingBudget2(input.budget_preset, {
|
|
5284
5342
|
max_tokens: input.max_tokens,
|
|
@@ -5294,8 +5352,8 @@ async function getBriefing(input, ctx) {
|
|
|
5294
5352
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
5295
5353
|
let byId = /* @__PURE__ */ new Map();
|
|
5296
5354
|
let lastSession;
|
|
5297
|
-
if (
|
|
5298
|
-
const allLoaded = await
|
|
5355
|
+
if (existsSync20(ctx.paths.memoriesDir)) {
|
|
5356
|
+
const allLoaded = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
|
|
5299
5357
|
const recaps = allLoaded.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").sort(
|
|
5300
5358
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
5301
5359
|
);
|
|
@@ -5317,7 +5375,7 @@ async function getBriefing(input, ctx) {
|
|
|
5317
5375
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
5318
5376
|
return true;
|
|
5319
5377
|
});
|
|
5320
|
-
usage = await
|
|
5378
|
+
usage = await loadUsageIndex8(ctx.paths);
|
|
5321
5379
|
byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
|
|
5322
5380
|
const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
|
|
5323
5381
|
if (input.task && input.semantic) {
|
|
@@ -5339,7 +5397,8 @@ async function getBriefing(input, ctx) {
|
|
|
5339
5397
|
}
|
|
5340
5398
|
return;
|
|
5341
5399
|
}
|
|
5342
|
-
const u =
|
|
5400
|
+
const u = getUsage6(usage, fm.id);
|
|
5401
|
+
const imp = computeImpact2(fm, u);
|
|
5343
5402
|
seen.set(fm.id, {
|
|
5344
5403
|
id: fm.id,
|
|
5345
5404
|
scope: fm.scope,
|
|
@@ -5350,6 +5409,8 @@ async function getBriefing(input, ctx) {
|
|
|
5350
5409
|
confidence: deriveConfidence4(fm, u),
|
|
5351
5410
|
...fm.status === "draft" || fm.status === "proposed" ? { unverified: true } : {},
|
|
5352
5411
|
read_count: u.read_count,
|
|
5412
|
+
impact_score: imp.score,
|
|
5413
|
+
impact_tier: imp.tier,
|
|
5353
5414
|
reasons: [reason],
|
|
5354
5415
|
match_quality: matchQuality ?? "partial",
|
|
5355
5416
|
...score !== void 0 ? { semantic_score: score } : {},
|
|
@@ -5393,11 +5454,28 @@ async function getBriefing(input, ctx) {
|
|
|
5393
5454
|
}
|
|
5394
5455
|
}
|
|
5395
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
|
+
}
|
|
5396
5472
|
const ranked = [...seen.values()].sort((a, b) => {
|
|
5397
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);
|
|
5398
5474
|
const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
|
|
5399
|
-
const
|
|
5400
|
-
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);
|
|
5401
5479
|
return sb - sa;
|
|
5402
5480
|
});
|
|
5403
5481
|
for (const mem of ranked.slice(0, briefingMaxMemories)) {
|
|
@@ -5413,7 +5491,7 @@ async function getBriefing(input, ctx) {
|
|
|
5413
5491
|
memories.push(...ranked.slice(0, briefingMaxMemories));
|
|
5414
5492
|
if (input.track && memories.length > 0) {
|
|
5415
5493
|
await trackReads3(ctx.paths, memories.map((m) => m.id));
|
|
5416
|
-
const freshUsage = await
|
|
5494
|
+
const freshUsage = await loadUsageIndex8(ctx.paths);
|
|
5417
5495
|
const cfg = await loadConfig3(ctx.paths);
|
|
5418
5496
|
const rule = {
|
|
5419
5497
|
minReads: cfg.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads,
|
|
@@ -5422,7 +5500,7 @@ async function getBriefing(input, ctx) {
|
|
|
5422
5500
|
for (const m of memories) {
|
|
5423
5501
|
const loaded = byId.get(m.id);
|
|
5424
5502
|
if (!loaded) continue;
|
|
5425
|
-
const u =
|
|
5503
|
+
const u = getUsage6(freshUsage, m.id);
|
|
5426
5504
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
5427
5505
|
const newFm = { ...loaded.memory.frontmatter, status: "validated" };
|
|
5428
5506
|
try {
|
|
@@ -5434,12 +5512,12 @@ async function getBriefing(input, ctx) {
|
|
|
5434
5512
|
}
|
|
5435
5513
|
}
|
|
5436
5514
|
}
|
|
5437
|
-
const projectContextRaw = input.include_project_context &&
|
|
5515
|
+
const projectContextRaw = input.include_project_context && existsSync20(ctx.paths.projectContext) ? await readFile42(ctx.paths.projectContext, "utf8") : "";
|
|
5438
5516
|
const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
|
|
5439
5517
|
const setupWarnings = [];
|
|
5440
5518
|
let autoContextGenerated = false;
|
|
5441
5519
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
5442
|
-
if ((isTemplateContext || !
|
|
5520
|
+
if ((isTemplateContext || !existsSync20(ctx.paths.projectContext)) && input.include_project_context) {
|
|
5443
5521
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
5444
5522
|
if (haiveConfig.autoContext) {
|
|
5445
5523
|
const codeMap = await loadCodeMap5(ctx.paths);
|
|
@@ -5535,7 +5613,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5535
5613
|
const totalTokens = projectSlice.estimatedTokens + modulesSlice.estimatedTokens + memoriesSlice.estimatedTokens;
|
|
5536
5614
|
const decayWarnings = [];
|
|
5537
5615
|
for (const m of trimmedMemories) {
|
|
5538
|
-
const u =
|
|
5616
|
+
const u = getUsage6(usage, m.id);
|
|
5539
5617
|
const loaded = byId.get(m.id);
|
|
5540
5618
|
const createdAt = loaded?.memory.frontmatter.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5541
5619
|
if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
|
|
@@ -5600,8 +5678,8 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5600
5678
|
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
5601
5679
|
}
|
|
5602
5680
|
}
|
|
5603
|
-
if (
|
|
5604
|
-
const allMems = await
|
|
5681
|
+
if (existsSync20(ctx.paths.memoriesDir)) {
|
|
5682
|
+
const allMems = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
|
|
5605
5683
|
for (const { memory: memory2 } of allMems) {
|
|
5606
5684
|
const fm = memory2.frontmatter;
|
|
5607
5685
|
if (!fm.requires_human_approval) continue;
|
|
@@ -5611,7 +5689,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5611
5689
|
}
|
|
5612
5690
|
}
|
|
5613
5691
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
5614
|
-
if (
|
|
5692
|
+
if (existsSync20(pendingDistillFile)) {
|
|
5615
5693
|
try {
|
|
5616
5694
|
const raw = await readFile42(pendingDistillFile, "utf8");
|
|
5617
5695
|
const pd = JSON.parse(raw);
|
|
@@ -5640,7 +5718,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5640
5718
|
}
|
|
5641
5719
|
}
|
|
5642
5720
|
const memoriesEmpty = outputMemories.length === 0;
|
|
5643
|
-
const hasMemoriesDir =
|
|
5721
|
+
const hasMemoriesDir = existsSync20(ctx.paths.memoriesDir);
|
|
5644
5722
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
5645
5723
|
const hasUnguessableSignal = outputMemories.some(
|
|
5646
5724
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore2(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -5687,7 +5765,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5687
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."
|
|
5688
5766
|
);
|
|
5689
5767
|
}
|
|
5690
|
-
if (
|
|
5768
|
+
if (existsSync20(ctx.paths.haiveDir)) {
|
|
5691
5769
|
await writeBriefingMarker2(ctx.paths, {
|
|
5692
5770
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
5693
5771
|
...input.task ? { task: input.task } : {},
|
|
@@ -5736,17 +5814,17 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5736
5814
|
};
|
|
5737
5815
|
}
|
|
5738
5816
|
var CodeMapInputSchema = {
|
|
5739
|
-
file:
|
|
5740
|
-
symbol:
|
|
5741
|
-
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(
|
|
5742
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."
|
|
5743
5821
|
),
|
|
5744
|
-
max_files:
|
|
5745
|
-
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(
|
|
5746
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)."
|
|
5747
5825
|
)
|
|
5748
5826
|
};
|
|
5749
|
-
var CodeMapInputZod =
|
|
5827
|
+
var CodeMapInputZod = z19.object(CodeMapInputSchema);
|
|
5750
5828
|
async function codeMapTool(input, ctx) {
|
|
5751
5829
|
const map = await loadCodeMap22(ctx.paths);
|
|
5752
5830
|
if (!map) {
|
|
@@ -5814,14 +5892,14 @@ function estimateFileEntryTokens(f) {
|
|
|
5814
5892
|
return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
|
|
5815
5893
|
}
|
|
5816
5894
|
var MemDiffInputSchema = {
|
|
5817
|
-
id_a:
|
|
5818
|
-
id_b:
|
|
5895
|
+
id_a: z20.string().min(1).describe("First memory id"),
|
|
5896
|
+
id_b: z20.string().min(1).describe("Second memory id")
|
|
5819
5897
|
};
|
|
5820
5898
|
async function memDiff(input, ctx) {
|
|
5821
|
-
if (!
|
|
5899
|
+
if (!existsSync21(ctx.paths.memoriesDir)) {
|
|
5822
5900
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
5823
5901
|
}
|
|
5824
|
-
const all = await
|
|
5902
|
+
const all = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
5825
5903
|
const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
|
|
5826
5904
|
const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
|
|
5827
5905
|
if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
|
|
@@ -5854,15 +5932,15 @@ async function memDiff(input, ctx) {
|
|
|
5854
5932
|
};
|
|
5855
5933
|
}
|
|
5856
5934
|
var GetRecapInputSchema = {
|
|
5857
|
-
scope:
|
|
5935
|
+
scope: z21.enum(["personal", "team", "any"]).default("any").describe(
|
|
5858
5936
|
"Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
|
|
5859
5937
|
)
|
|
5860
5938
|
};
|
|
5861
5939
|
async function getRecap(input, ctx) {
|
|
5862
|
-
if (!
|
|
5940
|
+
if (!existsSync222(ctx.paths.memoriesDir)) {
|
|
5863
5941
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
5864
5942
|
}
|
|
5865
|
-
const all = await
|
|
5943
|
+
const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
|
|
5866
5944
|
const recaps = all.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").filter(({ memory: memory2 }) => input.scope === "any" || memory2.frontmatter.scope === input.scope).sort(
|
|
5867
5945
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
5868
5946
|
);
|
|
@@ -5885,11 +5963,11 @@ async function getRecap(input, ctx) {
|
|
|
5885
5963
|
};
|
|
5886
5964
|
}
|
|
5887
5965
|
var MemRelevantToInputSchema = {
|
|
5888
|
-
task:
|
|
5889
|
-
files:
|
|
5890
|
-
limit:
|
|
5891
|
-
min_semantic_score:
|
|
5892
|
-
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.")
|
|
5893
5971
|
};
|
|
5894
5972
|
async function memRelevantTo(input, ctx) {
|
|
5895
5973
|
const briefingInput = {
|
|
@@ -5918,11 +5996,11 @@ async function memRelevantTo(input, ctx) {
|
|
|
5918
5996
|
return out;
|
|
5919
5997
|
}
|
|
5920
5998
|
var CodeSearchInputSchema = {
|
|
5921
|
-
query:
|
|
5999
|
+
query: z23.string().min(1).describe(
|
|
5922
6000
|
"Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
|
|
5923
6001
|
),
|
|
5924
|
-
k:
|
|
5925
|
-
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(
|
|
5926
6004
|
"Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
|
|
5927
6005
|
)
|
|
5928
6006
|
};
|
|
@@ -5951,14 +6029,14 @@ async function codeSearch(input, ctx) {
|
|
|
5951
6029
|
return { available: true, hits: result.hits };
|
|
5952
6030
|
}
|
|
5953
6031
|
var WhyThisFileInputSchema = {
|
|
5954
|
-
path:
|
|
6032
|
+
path: z24.string().min(1).describe(
|
|
5955
6033
|
"Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
|
|
5956
6034
|
),
|
|
5957
|
-
git_log_limit:
|
|
5958
|
-
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.")
|
|
5959
6037
|
};
|
|
5960
6038
|
async function whyThisFile(input, ctx) {
|
|
5961
|
-
const fileExists =
|
|
6039
|
+
const fileExists = existsSync23(path102.join(ctx.paths.root, input.path));
|
|
5962
6040
|
const [commits, memories, codeMap] = await Promise.all([
|
|
5963
6041
|
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
5964
6042
|
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
@@ -5999,16 +6077,16 @@ async function whyThisFile(input, ctx) {
|
|
|
5999
6077
|
};
|
|
6000
6078
|
}
|
|
6001
6079
|
async function collectAnchoredMemories(ctx, filePath, limit) {
|
|
6002
|
-
if (!
|
|
6003
|
-
const all = await
|
|
6004
|
-
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);
|
|
6005
6083
|
const out = [];
|
|
6006
6084
|
for (const { memory: memory2 } of all) {
|
|
6007
6085
|
const fm = memory2.frontmatter;
|
|
6008
6086
|
if (fm.status === "rejected" || fm.status === "deprecated") continue;
|
|
6009
6087
|
if (fm.type === "session_recap") continue;
|
|
6010
6088
|
if (!memoryMatchesAnchorPaths3(memory2, [filePath])) continue;
|
|
6011
|
-
const u =
|
|
6089
|
+
const u = getUsage7(usage, fm.id);
|
|
6012
6090
|
out.push({
|
|
6013
6091
|
id: fm.id,
|
|
6014
6092
|
type: fm.type,
|
|
@@ -6053,17 +6131,17 @@ function runCommand(cmd, args, cwd) {
|
|
|
6053
6131
|
});
|
|
6054
6132
|
}
|
|
6055
6133
|
var AntiPatternsCheckInputSchema = {
|
|
6056
|
-
diff:
|
|
6134
|
+
diff: z25.string().optional().describe(
|
|
6057
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."
|
|
6058
6136
|
),
|
|
6059
|
-
paths:
|
|
6137
|
+
paths: z25.array(z25.string()).default([]).describe(
|
|
6060
6138
|
"File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
|
|
6061
6139
|
),
|
|
6062
|
-
limit:
|
|
6063
|
-
semantic:
|
|
6140
|
+
limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
|
|
6141
|
+
semantic: z25.boolean().default(true).describe(
|
|
6064
6142
|
"When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
|
|
6065
6143
|
),
|
|
6066
|
-
min_semantic_score:
|
|
6144
|
+
min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
|
|
6067
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."
|
|
6068
6146
|
)
|
|
6069
6147
|
};
|
|
@@ -6131,10 +6209,10 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6131
6209
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
6132
6210
|
};
|
|
6133
6211
|
}
|
|
6134
|
-
if (!
|
|
6212
|
+
if (!existsSync24(ctx.paths.memoriesDir)) {
|
|
6135
6213
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
6136
6214
|
}
|
|
6137
|
-
const all = await
|
|
6215
|
+
const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
|
|
6138
6216
|
const minSemanticScore = input.min_semantic_score ?? 0.45;
|
|
6139
6217
|
const negative = all.filter(({ memory: memory2 }) => {
|
|
6140
6218
|
const t = memory2.frontmatter.type;
|
|
@@ -6145,7 +6223,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6145
6223
|
if (negative.length === 0) {
|
|
6146
6224
|
return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
|
|
6147
6225
|
}
|
|
6148
|
-
const usage = await
|
|
6226
|
+
const usage = await loadUsageIndex10(ctx.paths);
|
|
6149
6227
|
const seen = /* @__PURE__ */ new Map();
|
|
6150
6228
|
const upsert = (fm, body, reason, score) => {
|
|
6151
6229
|
const existing = seen.get(fm.id);
|
|
@@ -6156,7 +6234,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6156
6234
|
}
|
|
6157
6235
|
return;
|
|
6158
6236
|
}
|
|
6159
|
-
const u =
|
|
6237
|
+
const u = getUsage8(usage, fm.id);
|
|
6160
6238
|
seen.set(fm.id, {
|
|
6161
6239
|
id: fm.id,
|
|
6162
6240
|
type: fm.type,
|
|
@@ -6234,12 +6312,12 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6234
6312
|
};
|
|
6235
6313
|
}
|
|
6236
6314
|
var MemDistillInputSchema = {
|
|
6237
|
-
since_days:
|
|
6238
|
-
min_cluster:
|
|
6239
|
-
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(
|
|
6240
6318
|
"Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
|
|
6241
6319
|
),
|
|
6242
|
-
scope:
|
|
6320
|
+
scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
|
|
6243
6321
|
};
|
|
6244
6322
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
6245
6323
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -6279,11 +6357,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
6279
6357
|
"error"
|
|
6280
6358
|
]);
|
|
6281
6359
|
async function memDistill(input, ctx) {
|
|
6282
|
-
if (!
|
|
6360
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
6283
6361
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
6284
6362
|
}
|
|
6285
6363
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
6286
|
-
const all = await
|
|
6364
|
+
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
6287
6365
|
const candidates = all.filter(({ memory: memory2 }) => {
|
|
6288
6366
|
const fm = memory2.frontmatter;
|
|
6289
6367
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -6386,11 +6464,11 @@ function firstHeading(body) {
|
|
|
6386
6464
|
return void 0;
|
|
6387
6465
|
}
|
|
6388
6466
|
var WhyThisDecisionInputSchema = {
|
|
6389
|
-
id:
|
|
6390
|
-
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.")
|
|
6391
6469
|
};
|
|
6392
6470
|
async function whyThisDecision(input, ctx) {
|
|
6393
|
-
if (!
|
|
6471
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
6394
6472
|
return {
|
|
6395
6473
|
found: false,
|
|
6396
6474
|
related: [],
|
|
@@ -6399,8 +6477,8 @@ async function whyThisDecision(input, ctx) {
|
|
|
6399
6477
|
notice: "No .ai/memories directory."
|
|
6400
6478
|
};
|
|
6401
6479
|
}
|
|
6402
|
-
const all = await
|
|
6403
|
-
const usage = await
|
|
6480
|
+
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
6481
|
+
const usage = await loadUsageIndex11(ctx.paths);
|
|
6404
6482
|
const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
|
|
6405
6483
|
if (!target) {
|
|
6406
6484
|
return {
|
|
@@ -6412,7 +6490,7 @@ async function whyThisDecision(input, ctx) {
|
|
|
6412
6490
|
};
|
|
6413
6491
|
}
|
|
6414
6492
|
const fm = target.memory.frontmatter;
|
|
6415
|
-
const targetUsage =
|
|
6493
|
+
const targetUsage = getUsage9(usage, fm.id);
|
|
6416
6494
|
const decision = {
|
|
6417
6495
|
id: fm.id,
|
|
6418
6496
|
type: fm.type,
|
|
@@ -6429,7 +6507,7 @@ async function whyThisDecision(input, ctx) {
|
|
|
6429
6507
|
const isExplicit = relatedSet.has(memory2.frontmatter.id);
|
|
6430
6508
|
const isBackLink = (memory2.frontmatter.related_ids ?? []).includes(fm.id);
|
|
6431
6509
|
if (!isExplicit && !isBackLink) continue;
|
|
6432
|
-
const u =
|
|
6510
|
+
const u = getUsage9(usage, memory2.frontmatter.id);
|
|
6433
6511
|
related.push({
|
|
6434
6512
|
id: memory2.frontmatter.id,
|
|
6435
6513
|
type: memory2.frontmatter.type,
|
|
@@ -6449,7 +6527,7 @@ async function whyThisDecision(input, ctx) {
|
|
|
6449
6527
|
(p) => targetPaths.some((tp) => singlePathsOverlap(p, tp))
|
|
6450
6528
|
);
|
|
6451
6529
|
if (overlappingPaths.length === 0) continue;
|
|
6452
|
-
const u =
|
|
6530
|
+
const u = getUsage9(usage, memory2.frontmatter.id);
|
|
6453
6531
|
path_neighbors.push({
|
|
6454
6532
|
id: memory2.frontmatter.id,
|
|
6455
6533
|
type: memory2.frontmatter.type,
|
|
@@ -6521,22 +6599,22 @@ function runCommand2(cmd, args, cwd) {
|
|
|
6521
6599
|
});
|
|
6522
6600
|
}
|
|
6523
6601
|
var MemConflictsInputSchema = {
|
|
6524
|
-
id:
|
|
6525
|
-
min_score:
|
|
6526
|
-
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.")
|
|
6527
6605
|
};
|
|
6528
6606
|
var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
|
|
6529
6607
|
var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
|
|
6530
6608
|
async function memConflicts(input, ctx) {
|
|
6531
|
-
if (!
|
|
6609
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
6532
6610
|
return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
|
|
6533
6611
|
}
|
|
6534
|
-
const all = await
|
|
6612
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
6535
6613
|
const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
|
|
6536
6614
|
if (!target) {
|
|
6537
6615
|
return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
|
|
6538
6616
|
}
|
|
6539
|
-
const usage = await
|
|
6617
|
+
const usage = await loadUsageIndex12(ctx.paths);
|
|
6540
6618
|
const others = all.filter(
|
|
6541
6619
|
({ memory: memory2 }) => memory2.frontmatter.id !== input.id && memory2.frontmatter.type !== "session_recap"
|
|
6542
6620
|
);
|
|
@@ -6577,7 +6655,7 @@ async function memConflicts(input, ctx) {
|
|
|
6577
6655
|
}
|
|
6578
6656
|
}
|
|
6579
6657
|
if (reasons.length === 0) continue;
|
|
6580
|
-
const u =
|
|
6658
|
+
const u = getUsage10(usage, fm.id);
|
|
6581
6659
|
conflicts.push({
|
|
6582
6660
|
id: fm.id,
|
|
6583
6661
|
type: fm.type,
|
|
@@ -6642,15 +6720,15 @@ async function trySemanticSimilarities(ctx, target, others) {
|
|
|
6642
6720
|
return map;
|
|
6643
6721
|
}
|
|
6644
6722
|
var PreCommitCheckInputSchema = {
|
|
6645
|
-
diff:
|
|
6723
|
+
diff: z29.string().optional().describe(
|
|
6646
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`."
|
|
6647
6725
|
),
|
|
6648
|
-
paths:
|
|
6649
|
-
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(
|
|
6650
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."
|
|
6651
6729
|
),
|
|
6652
|
-
semantic:
|
|
6653
|
-
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(
|
|
6654
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."
|
|
6655
6733
|
)
|
|
6656
6734
|
};
|
|
@@ -6951,12 +7029,12 @@ var CONFIG_PATTERNS = [
|
|
|
6951
7029
|
var MAX_DIFF_BYTES = 4096;
|
|
6952
7030
|
var HOT_FILE_MIN = 3;
|
|
6953
7031
|
var PatternDetectInputSchema = {
|
|
6954
|
-
since_days:
|
|
6955
|
-
dry_run:
|
|
6956
|
-
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.")
|
|
6957
7035
|
};
|
|
6958
7036
|
async function patternDetect(input, ctx) {
|
|
6959
|
-
if (!
|
|
7037
|
+
if (!existsSync28(ctx.paths.haiveDir)) {
|
|
6960
7038
|
return {
|
|
6961
7039
|
scanned_events: 0,
|
|
6962
7040
|
matches: [],
|
|
@@ -7085,7 +7163,7 @@ async function patternDetect(input, ctx) {
|
|
|
7085
7163
|
fm.id,
|
|
7086
7164
|
void 0
|
|
7087
7165
|
);
|
|
7088
|
-
if (
|
|
7166
|
+
if (existsSync28(file)) continue;
|
|
7089
7167
|
await mkdir72(path112.dirname(file), { recursive: true });
|
|
7090
7168
|
await writeFile12(
|
|
7091
7169
|
file,
|
|
@@ -7130,17 +7208,17 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
7130
7208
|
}
|
|
7131
7209
|
}
|
|
7132
7210
|
var MemConflictCandidatesInputSchema = {
|
|
7133
|
-
since_days:
|
|
7134
|
-
types:
|
|
7135
|
-
min_jaccard:
|
|
7136
|
-
max_pairs:
|
|
7137
|
-
max_scan:
|
|
7138
|
-
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(
|
|
7139
7217
|
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
7140
7218
|
)
|
|
7141
7219
|
};
|
|
7142
7220
|
async function memConflictCandidates(input, ctx) {
|
|
7143
|
-
if (!
|
|
7221
|
+
if (!existsSync29(ctx.paths.memoriesDir)) {
|
|
7144
7222
|
return {
|
|
7145
7223
|
pairs: [],
|
|
7146
7224
|
topic_status_pairs: [],
|
|
@@ -7149,7 +7227,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
7149
7227
|
notice: "No .ai/memories directory."
|
|
7150
7228
|
};
|
|
7151
7229
|
}
|
|
7152
|
-
const all = await
|
|
7230
|
+
const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
|
|
7153
7231
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
7154
7232
|
sinceDays: input.since_days,
|
|
7155
7233
|
types: input.types,
|
|
@@ -7162,7 +7240,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
7162
7240
|
return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
|
|
7163
7241
|
}
|
|
7164
7242
|
var MemResolveProjectInputSchema = {
|
|
7165
|
-
cwd:
|
|
7243
|
+
cwd: z32.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
7166
7244
|
};
|
|
7167
7245
|
async function memResolveProject(input, _ctx) {
|
|
7168
7246
|
void _ctx;
|
|
@@ -7175,7 +7253,7 @@ async function memResolveProject(input, _ctx) {
|
|
|
7175
7253
|
}
|
|
7176
7254
|
var MemSuggestTopicInputSchema = {
|
|
7177
7255
|
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
7178
|
-
title:
|
|
7256
|
+
title: z33.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
7179
7257
|
};
|
|
7180
7258
|
async function memSuggestTopic(input, _ctx) {
|
|
7181
7259
|
void _ctx;
|
|
@@ -7183,15 +7261,15 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
7183
7261
|
return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
|
|
7184
7262
|
}
|
|
7185
7263
|
var MemTimelineInputSchema = {
|
|
7186
|
-
memory_id:
|
|
7187
|
-
topic:
|
|
7188
|
-
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")
|
|
7189
7267
|
};
|
|
7190
7268
|
async function memTimeline(input, ctx) {
|
|
7191
|
-
if (!
|
|
7269
|
+
if (!existsSync30(ctx.paths.memoriesDir)) {
|
|
7192
7270
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
7193
7271
|
}
|
|
7194
|
-
const all = await
|
|
7272
|
+
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
7195
7273
|
const { entries, notice } = collectTimelineEntries(all, {
|
|
7196
7274
|
memoryId: input.memory_id,
|
|
7197
7275
|
topic: input.topic,
|
|
@@ -7200,9 +7278,9 @@ async function memTimeline(input, ctx) {
|
|
|
7200
7278
|
return { entries, total: entries.length, notice };
|
|
7201
7279
|
}
|
|
7202
7280
|
var RuntimeJournalAppendInputSchema = {
|
|
7203
|
-
message:
|
|
7204
|
-
kind:
|
|
7205
|
-
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)")
|
|
7206
7284
|
};
|
|
7207
7285
|
async function runtimeJournalAppend(input, ctx) {
|
|
7208
7286
|
await appendRuntimeJournalEntry2(ctx.paths, {
|
|
@@ -7216,7 +7294,7 @@ async function runtimeJournalAppend(input, ctx) {
|
|
|
7216
7294
|
};
|
|
7217
7295
|
}
|
|
7218
7296
|
var RuntimeJournalTailInputSchema = {
|
|
7219
|
-
limit:
|
|
7297
|
+
limit: z36.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
|
|
7220
7298
|
};
|
|
7221
7299
|
async function runtimeJournalTail(input, ctx) {
|
|
7222
7300
|
const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
|
|
@@ -7226,10 +7304,10 @@ async function runtimeJournalTail(input, ctx) {
|
|
|
7226
7304
|
return { entries };
|
|
7227
7305
|
}
|
|
7228
7306
|
var BootstrapProjectArgsSchema = {
|
|
7229
|
-
module:
|
|
7307
|
+
module: z37.string().optional().describe(
|
|
7230
7308
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
7231
7309
|
),
|
|
7232
|
-
focus:
|
|
7310
|
+
focus: z37.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
7233
7311
|
};
|
|
7234
7312
|
var ROOT_TEMPLATE = `# Project context
|
|
7235
7313
|
|
|
@@ -7310,8 +7388,8 @@ ${template}\`\`\`
|
|
|
7310
7388
|
};
|
|
7311
7389
|
}
|
|
7312
7390
|
var PostTaskArgsSchema = {
|
|
7313
|
-
task_summary:
|
|
7314
|
-
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")
|
|
7315
7393
|
};
|
|
7316
7394
|
function postTaskPrompt(args, ctx) {
|
|
7317
7395
|
const taskLine = args.task_summary ? `
|
|
@@ -7406,10 +7484,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
7406
7484
|
};
|
|
7407
7485
|
}
|
|
7408
7486
|
var ImportDocsArgsSchema = {
|
|
7409
|
-
content:
|
|
7410
|
-
source:
|
|
7411
|
-
scope:
|
|
7412
|
-
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")
|
|
7413
7491
|
};
|
|
7414
7492
|
function importDocsPrompt(args, ctx) {
|
|
7415
7493
|
const sourceLine = args.source ? `
|
|
@@ -7472,7 +7550,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7472
7550
|
};
|
|
7473
7551
|
}
|
|
7474
7552
|
var SERVER_NAME = "haive";
|
|
7475
|
-
var SERVER_VERSION = "0.
|
|
7553
|
+
var SERVER_VERSION = "0.11.0";
|
|
7476
7554
|
function jsonResult(data) {
|
|
7477
7555
|
return {
|
|
7478
7556
|
content: [
|
|
@@ -7514,7 +7592,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
|
|
|
7514
7592
|
"anti_patterns_check",
|
|
7515
7593
|
"mem_distill",
|
|
7516
7594
|
"mem_timeline",
|
|
7517
|
-
"mem_conflict_candidates"
|
|
7595
|
+
"mem_conflict_candidates",
|
|
7596
|
+
"mem_feedback"
|
|
7518
7597
|
];
|
|
7519
7598
|
var EXPERIMENTAL_PROFILE_TOOLS = [
|
|
7520
7599
|
...MAINTENANCE_PROFILE_TOOLS,
|
|
@@ -7546,6 +7625,7 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7546
7625
|
"mem_approve",
|
|
7547
7626
|
"mem_reject",
|
|
7548
7627
|
"mem_delete",
|
|
7628
|
+
"mem_feedback",
|
|
7549
7629
|
"runtime_journal_append",
|
|
7550
7630
|
"pattern_detect"
|
|
7551
7631
|
]);
|
|
@@ -8041,6 +8121,28 @@ function createHaiveServer(options = {}) {
|
|
|
8041
8121
|
MemRejectInputSchema,
|
|
8042
8122
|
async (input) => jsonResult(await memReject(input, context))
|
|
8043
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
|
+
);
|
|
8044
8146
|
registerTool(
|
|
8045
8147
|
"mem_pending",
|
|
8046
8148
|
[
|
|
@@ -8447,21 +8549,21 @@ function registerMcp(program2) {
|
|
|
8447
8549
|
// src/commands/sync.ts
|
|
8448
8550
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
8449
8551
|
import { readFile as readFile9, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
|
|
8450
|
-
import { existsSync as
|
|
8552
|
+
import { existsSync as existsSync31 } from "fs";
|
|
8451
8553
|
import path15 from "path";
|
|
8452
8554
|
import "commander";
|
|
8453
8555
|
import {
|
|
8454
8556
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
8455
8557
|
buildFrontmatter as buildFrontmatter6,
|
|
8456
8558
|
findProjectRoot as findProjectRoot12,
|
|
8457
|
-
getUsage as
|
|
8559
|
+
getUsage as getUsage11,
|
|
8458
8560
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
8459
8561
|
isDecaying as isDecaying2,
|
|
8460
8562
|
isStackPackSeed as isStackPackSeed3,
|
|
8461
8563
|
loadCodeMap as loadCodeMap6,
|
|
8462
8564
|
loadConfig as loadConfig4,
|
|
8463
|
-
loadMemoriesFromDir as
|
|
8464
|
-
loadUsageIndex as
|
|
8565
|
+
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
8566
|
+
loadUsageIndex as loadUsageIndex13,
|
|
8465
8567
|
pullCrossRepoSources,
|
|
8466
8568
|
resolveHaivePaths as resolveHaivePaths9,
|
|
8467
8569
|
resolveManifestFiles,
|
|
@@ -8484,7 +8586,7 @@ function registerSync(program2) {
|
|
|
8484
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) => {
|
|
8485
8587
|
const root = findProjectRoot12(opts.dir);
|
|
8486
8588
|
const paths = resolveHaivePaths9(root);
|
|
8487
|
-
if (!
|
|
8589
|
+
if (!existsSync31(paths.memoriesDir)) {
|
|
8488
8590
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8489
8591
|
process.exitCode = 1;
|
|
8490
8592
|
return;
|
|
@@ -8503,7 +8605,7 @@ function registerSync(program2) {
|
|
|
8503
8605
|
let promoted = 0;
|
|
8504
8606
|
let autoApproved = 0;
|
|
8505
8607
|
if (opts.verify !== false) {
|
|
8506
|
-
const memories = await
|
|
8608
|
+
const memories = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
8507
8609
|
for (const { memory: memory2, filePath } of memories) {
|
|
8508
8610
|
if (memory2.frontmatter.type === "session_recap") {
|
|
8509
8611
|
if (memory2.frontmatter.status === "stale") {
|
|
@@ -8570,13 +8672,13 @@ function registerSync(program2) {
|
|
|
8570
8672
|
}
|
|
8571
8673
|
}
|
|
8572
8674
|
if (opts.promote !== false) {
|
|
8573
|
-
const memories = await
|
|
8574
|
-
const usage = await
|
|
8675
|
+
const memories = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
8676
|
+
const usage = await loadUsageIndex13(paths);
|
|
8575
8677
|
const nowMs = Date.now();
|
|
8576
8678
|
for (const { memory: memory2, filePath } of memories) {
|
|
8577
8679
|
const fm = memory2.frontmatter;
|
|
8578
8680
|
if (fm.type === "session_recap") continue;
|
|
8579
|
-
if (isAutoPromoteEligible2(fm,
|
|
8681
|
+
if (isAutoPromoteEligible2(fm, getUsage11(usage, fm.id), {
|
|
8580
8682
|
minReads: autoPromoteMinReads,
|
|
8581
8683
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
|
|
8582
8684
|
})) {
|
|
@@ -8622,7 +8724,7 @@ function registerSync(program2) {
|
|
|
8622
8724
|
for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
|
|
8623
8725
|
}
|
|
8624
8726
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
8625
|
-
const draftMemories = (await
|
|
8727
|
+
const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
|
|
8626
8728
|
(m) => m.memory.frontmatter.status === "draft"
|
|
8627
8729
|
);
|
|
8628
8730
|
const draftCount = draftMemories.length;
|
|
@@ -8657,12 +8759,12 @@ function registerSync(program2) {
|
|
|
8657
8759
|
}
|
|
8658
8760
|
}
|
|
8659
8761
|
if (!opts.quiet) {
|
|
8660
|
-
const allForDecay = await
|
|
8661
|
-
const usageForDecay = await
|
|
8762
|
+
const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
8763
|
+
const usageForDecay = await loadUsageIndex13(paths);
|
|
8662
8764
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
8663
8765
|
const fm = memory2.frontmatter;
|
|
8664
8766
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
8665
|
-
const u =
|
|
8767
|
+
const u = getUsage11(usageForDecay, fm.id);
|
|
8666
8768
|
return isDecaying2(u, fm.created_at);
|
|
8667
8769
|
});
|
|
8668
8770
|
if (decaying.length > 0) {
|
|
@@ -8908,8 +9010,8 @@ function bridgeSummaryLine(body) {
|
|
|
8908
9010
|
return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
|
|
8909
9011
|
}
|
|
8910
9012
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
8911
|
-
if (!
|
|
8912
|
-
const all = await
|
|
9013
|
+
if (!existsSync31(memoriesDir)) return;
|
|
9014
|
+
const all = await loadMemoriesFromDir24(memoriesDir);
|
|
8913
9015
|
const top = all.filter(({ memory: memory2 }) => {
|
|
8914
9016
|
const s = memory2.frontmatter.status;
|
|
8915
9017
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
@@ -8934,7 +9036,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
|
8934
9036
|
` + block + `
|
|
8935
9037
|
|
|
8936
9038
|
${BRIDGE_END}`;
|
|
8937
|
-
const fileExists =
|
|
9039
|
+
const fileExists = existsSync31(bridgeFile);
|
|
8938
9040
|
let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
|
|
8939
9041
|
existing = existing.replace(/\r\n/g, "\n");
|
|
8940
9042
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
@@ -8985,7 +9087,7 @@ function collectSinceChanges(root, ref) {
|
|
|
8985
9087
|
// src/commands/memory-add.ts
|
|
8986
9088
|
import { createHash as createHash2 } from "crypto";
|
|
8987
9089
|
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
|
|
8988
|
-
import { existsSync as
|
|
9090
|
+
import { existsSync as existsSync33 } from "fs";
|
|
8989
9091
|
import path16 from "path";
|
|
8990
9092
|
import "commander";
|
|
8991
9093
|
import {
|
|
@@ -8993,7 +9095,7 @@ import {
|
|
|
8993
9095
|
findProjectRoot as findProjectRoot13,
|
|
8994
9096
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
8995
9097
|
loadConfig as loadConfig5,
|
|
8996
|
-
loadMemoriesFromDir as
|
|
9098
|
+
loadMemoriesFromDir as loadMemoriesFromDir25,
|
|
8997
9099
|
memoryFilePath as memoryFilePath6,
|
|
8998
9100
|
resolveHaivePaths as resolveHaivePaths10,
|
|
8999
9101
|
serializeMemory as serializeMemory12,
|
|
@@ -9027,7 +9129,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9027
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) => {
|
|
9028
9130
|
const root = findProjectRoot13(opts.dir);
|
|
9029
9131
|
const paths = resolveHaivePaths10(root);
|
|
9030
|
-
if (!
|
|
9132
|
+
if (!existsSync33(paths.haiveDir)) {
|
|
9031
9133
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9032
9134
|
process.exitCode = 1;
|
|
9033
9135
|
return;
|
|
@@ -9039,7 +9141,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9039
9141
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
9040
9142
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
9041
9143
|
if (anchorPaths.length > 0) {
|
|
9042
|
-
const missing = anchorPaths.filter((p) => !
|
|
9144
|
+
const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
|
|
9043
9145
|
if (missing.length > 0) {
|
|
9044
9146
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
9045
9147
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -9052,7 +9154,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9052
9154
|
const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
|
|
9053
9155
|
let body;
|
|
9054
9156
|
if (opts.bodyFile !== void 0) {
|
|
9055
|
-
if (!
|
|
9157
|
+
if (!existsSync33(opts.bodyFile)) {
|
|
9056
9158
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9057
9159
|
process.exitCode = 1;
|
|
9058
9160
|
return;
|
|
@@ -9068,9 +9170,9 @@ TODO \u2014 write the memory body.
|
|
|
9068
9170
|
`;
|
|
9069
9171
|
}
|
|
9070
9172
|
const scope = opts.scope ?? config.defaultScope ?? "personal";
|
|
9071
|
-
if (
|
|
9173
|
+
if (existsSync33(paths.memoriesDir)) {
|
|
9072
9174
|
const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
9073
|
-
const allForHash = await
|
|
9175
|
+
const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9074
9176
|
const hashDup = allForHash.find(
|
|
9075
9177
|
({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
|
|
9076
9178
|
);
|
|
@@ -9081,8 +9183,8 @@ TODO \u2014 write the memory body.
|
|
|
9081
9183
|
return;
|
|
9082
9184
|
}
|
|
9083
9185
|
}
|
|
9084
|
-
if (opts.topic &&
|
|
9085
|
-
const existing = await
|
|
9186
|
+
if (opts.topic && existsSync33(paths.memoriesDir)) {
|
|
9187
|
+
const existing = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9086
9188
|
const topicMatch = existing.find(
|
|
9087
9189
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
9088
9190
|
);
|
|
@@ -9126,13 +9228,13 @@ TODO \u2014 write the memory body.
|
|
|
9126
9228
|
});
|
|
9127
9229
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9128
9230
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
9129
|
-
if (
|
|
9231
|
+
if (existsSync33(file)) {
|
|
9130
9232
|
ui.error(`Memory already exists at ${file}`);
|
|
9131
9233
|
process.exitCode = 1;
|
|
9132
9234
|
return;
|
|
9133
9235
|
}
|
|
9134
|
-
if (
|
|
9135
|
-
const existing = await
|
|
9236
|
+
if (existsSync33(paths.memoriesDir)) {
|
|
9237
|
+
const existing = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9136
9238
|
const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
9137
9239
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
9138
9240
|
const id = memory3.frontmatter.id.toLowerCase();
|
|
@@ -9223,14 +9325,14 @@ function slugify(value) {
|
|
|
9223
9325
|
}
|
|
9224
9326
|
|
|
9225
9327
|
// src/commands/memory-list.ts
|
|
9226
|
-
import { existsSync as
|
|
9328
|
+
import { existsSync as existsSync34 } from "fs";
|
|
9227
9329
|
import path17 from "path";
|
|
9228
9330
|
import "commander";
|
|
9229
9331
|
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
9230
9332
|
|
|
9231
9333
|
// src/utils/fs.ts
|
|
9232
9334
|
import {
|
|
9233
|
-
loadMemoriesFromDir as
|
|
9335
|
+
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
9234
9336
|
loadMemory,
|
|
9235
9337
|
listMarkdownFilesRecursive
|
|
9236
9338
|
} from "@hiveai/core";
|
|
@@ -9240,12 +9342,12 @@ function registerMemoryList(memory2) {
|
|
|
9240
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) => {
|
|
9241
9343
|
const root = findProjectRoot14(opts.dir);
|
|
9242
9344
|
const paths = resolveHaivePaths11(root);
|
|
9243
|
-
if (!
|
|
9345
|
+
if (!existsSync34(paths.memoriesDir)) {
|
|
9244
9346
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9245
9347
|
process.exitCode = 1;
|
|
9246
9348
|
return;
|
|
9247
9349
|
}
|
|
9248
|
-
const all = await
|
|
9350
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9249
9351
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
9250
9352
|
const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
|
|
9251
9353
|
const filtered = all.filter((m) => {
|
|
@@ -9315,7 +9417,7 @@ function matchesFilters(loaded, opts) {
|
|
|
9315
9417
|
|
|
9316
9418
|
// src/commands/memory-promote.ts
|
|
9317
9419
|
import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
|
|
9318
|
-
import { existsSync as
|
|
9420
|
+
import { existsSync as existsSync35 } from "fs";
|
|
9319
9421
|
import path18 from "path";
|
|
9320
9422
|
import "commander";
|
|
9321
9423
|
import {
|
|
@@ -9328,12 +9430,12 @@ function registerMemoryPromote(memory2) {
|
|
|
9328
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) => {
|
|
9329
9431
|
const root = findProjectRoot15(opts.dir);
|
|
9330
9432
|
const paths = resolveHaivePaths12(root);
|
|
9331
|
-
if (!
|
|
9433
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
9332
9434
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9333
9435
|
process.exitCode = 1;
|
|
9334
9436
|
return;
|
|
9335
9437
|
}
|
|
9336
|
-
const teamAndModule = await
|
|
9438
|
+
const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9337
9439
|
const alreadyShared = teamAndModule.find(
|
|
9338
9440
|
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
9339
9441
|
);
|
|
@@ -9347,7 +9449,7 @@ function registerMemoryPromote(memory2) {
|
|
|
9347
9449
|
}
|
|
9348
9450
|
return;
|
|
9349
9451
|
}
|
|
9350
|
-
const all = await
|
|
9452
|
+
const all = await loadMemoriesFromDir26(paths.personalDir);
|
|
9351
9453
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9352
9454
|
if (!found) {
|
|
9353
9455
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -9373,7 +9475,7 @@ function registerMemoryPromote(memory2) {
|
|
|
9373
9475
|
}
|
|
9374
9476
|
|
|
9375
9477
|
// src/commands/memory-approve.ts
|
|
9376
|
-
import { existsSync as
|
|
9478
|
+
import { existsSync as existsSync36 } from "fs";
|
|
9377
9479
|
import { writeFile as writeFile16 } from "fs/promises";
|
|
9378
9480
|
import path19 from "path";
|
|
9379
9481
|
import "commander";
|
|
@@ -9386,12 +9488,12 @@ function registerMemoryApprove(memory2) {
|
|
|
9386
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) => {
|
|
9387
9489
|
const root = findProjectRoot16(opts.dir);
|
|
9388
9490
|
const paths = resolveHaivePaths13(root);
|
|
9389
|
-
if (!
|
|
9491
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
9390
9492
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9391
9493
|
process.exitCode = 1;
|
|
9392
9494
|
return;
|
|
9393
9495
|
}
|
|
9394
|
-
const all = await
|
|
9496
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9395
9497
|
if (opts.all || opts.pending) {
|
|
9396
9498
|
const candidates = all.filter((m) => {
|
|
9397
9499
|
const s = m.memory.frontmatter.status;
|
|
@@ -9445,7 +9547,7 @@ function registerMemoryApprove(memory2) {
|
|
|
9445
9547
|
|
|
9446
9548
|
// src/commands/memory-update.ts
|
|
9447
9549
|
import { readFile as readFile11, writeFile as writeFile17 } from "fs/promises";
|
|
9448
|
-
import { existsSync as
|
|
9550
|
+
import { existsSync as existsSync37 } from "fs";
|
|
9449
9551
|
import path20 from "path";
|
|
9450
9552
|
import "commander";
|
|
9451
9553
|
import {
|
|
@@ -9457,12 +9559,12 @@ function registerMemoryUpdate(memory2) {
|
|
|
9457
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) => {
|
|
9458
9560
|
const root = findProjectRoot17(opts.dir);
|
|
9459
9561
|
const paths = resolveHaivePaths14(root);
|
|
9460
|
-
if (!
|
|
9562
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
9461
9563
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9462
9564
|
process.exitCode = 1;
|
|
9463
9565
|
return;
|
|
9464
9566
|
}
|
|
9465
|
-
const memories = await
|
|
9567
|
+
const memories = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9466
9568
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
9467
9569
|
if (!loaded) {
|
|
9468
9570
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9499,7 +9601,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
9499
9601
|
if (opts.author !== void 0) updated.push("author");
|
|
9500
9602
|
let newBody;
|
|
9501
9603
|
if (opts.bodyFile !== void 0) {
|
|
9502
|
-
if (!
|
|
9604
|
+
if (!existsSync37(opts.bodyFile)) {
|
|
9503
9605
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9504
9606
|
process.exitCode = 1;
|
|
9505
9607
|
return;
|
|
@@ -9545,15 +9647,15 @@ function parseCsv3(value) {
|
|
|
9545
9647
|
|
|
9546
9648
|
// src/commands/memory-auto-promote.ts
|
|
9547
9649
|
import { writeFile as writeFile18 } from "fs/promises";
|
|
9548
|
-
import { existsSync as
|
|
9650
|
+
import { existsSync as existsSync38 } from "fs";
|
|
9549
9651
|
import path21 from "path";
|
|
9550
9652
|
import "commander";
|
|
9551
9653
|
import {
|
|
9552
9654
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
|
|
9553
9655
|
findProjectRoot as findProjectRoot18,
|
|
9554
|
-
getUsage as
|
|
9656
|
+
getUsage as getUsage12,
|
|
9555
9657
|
isAutoPromoteEligible as isAutoPromoteEligible3,
|
|
9556
|
-
loadUsageIndex as
|
|
9658
|
+
loadUsageIndex as loadUsageIndex14,
|
|
9557
9659
|
resolveHaivePaths as resolveHaivePaths15,
|
|
9558
9660
|
serializeMemory as serializeMemory16
|
|
9559
9661
|
} from "@hiveai/core";
|
|
@@ -9565,7 +9667,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9565
9667
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9566
9668
|
const root = findProjectRoot18(opts.dir);
|
|
9567
9669
|
const paths = resolveHaivePaths15(root);
|
|
9568
|
-
if (!
|
|
9670
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
9569
9671
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9570
9672
|
process.exitCode = 1;
|
|
9571
9673
|
return;
|
|
@@ -9574,10 +9676,10 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9574
9676
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
|
|
9575
9677
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
9576
9678
|
};
|
|
9577
|
-
const memories = await
|
|
9578
|
-
const usage = await
|
|
9679
|
+
const memories = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9680
|
+
const usage = await loadUsageIndex14(paths);
|
|
9579
9681
|
const eligible = memories.filter(
|
|
9580
|
-
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter,
|
|
9682
|
+
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
|
|
9581
9683
|
);
|
|
9582
9684
|
if (eligible.length === 0) {
|
|
9583
9685
|
ui.info(
|
|
@@ -9587,7 +9689,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9587
9689
|
}
|
|
9588
9690
|
let written = 0;
|
|
9589
9691
|
for (const { memory: mem, filePath } of eligible) {
|
|
9590
|
-
const u =
|
|
9692
|
+
const u = getUsage12(usage, mem.frontmatter.id);
|
|
9591
9693
|
console.log(
|
|
9592
9694
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
9593
9695
|
);
|
|
@@ -9608,7 +9710,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9608
9710
|
|
|
9609
9711
|
// src/commands/memory-edit.ts
|
|
9610
9712
|
import { spawn as spawn3 } from "child_process";
|
|
9611
|
-
import { existsSync as
|
|
9713
|
+
import { existsSync as existsSync39 } from "fs";
|
|
9612
9714
|
import { readFile as readFile12 } from "fs/promises";
|
|
9613
9715
|
import path23 from "path";
|
|
9614
9716
|
import "commander";
|
|
@@ -9621,12 +9723,12 @@ function registerMemoryEdit(memory2) {
|
|
|
9621
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) => {
|
|
9622
9724
|
const root = findProjectRoot19(opts.dir);
|
|
9623
9725
|
const paths = resolveHaivePaths16(root);
|
|
9624
|
-
if (!
|
|
9726
|
+
if (!existsSync39(paths.memoriesDir)) {
|
|
9625
9727
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9626
9728
|
process.exitCode = 1;
|
|
9627
9729
|
return;
|
|
9628
9730
|
}
|
|
9629
|
-
const all = await
|
|
9731
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9630
9732
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9631
9733
|
if (!found) {
|
|
9632
9734
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9661,15 +9763,15 @@ function runEditor(editor, file) {
|
|
|
9661
9763
|
}
|
|
9662
9764
|
|
|
9663
9765
|
// src/commands/memory-for-files.ts
|
|
9664
|
-
import { existsSync as
|
|
9766
|
+
import { existsSync as existsSync40 } from "fs";
|
|
9665
9767
|
import path24 from "path";
|
|
9666
9768
|
import "commander";
|
|
9667
9769
|
import {
|
|
9668
9770
|
deriveConfidence as deriveConfidence9,
|
|
9669
9771
|
findProjectRoot as findProjectRoot20,
|
|
9670
|
-
getUsage as
|
|
9772
|
+
getUsage as getUsage13,
|
|
9671
9773
|
inferModulesFromPaths as inferModulesFromPaths4,
|
|
9672
|
-
loadUsageIndex as
|
|
9774
|
+
loadUsageIndex as loadUsageIndex15,
|
|
9673
9775
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
|
|
9674
9776
|
resolveHaivePaths as resolveHaivePaths17
|
|
9675
9777
|
} from "@hiveai/core";
|
|
@@ -9677,13 +9779,13 @@ function registerMemoryForFiles(memory2) {
|
|
|
9677
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) => {
|
|
9678
9780
|
const root = findProjectRoot20(opts.dir);
|
|
9679
9781
|
const paths = resolveHaivePaths17(root);
|
|
9680
|
-
if (!
|
|
9782
|
+
if (!existsSync40(paths.memoriesDir)) {
|
|
9681
9783
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9682
9784
|
process.exitCode = 1;
|
|
9683
9785
|
return;
|
|
9684
9786
|
}
|
|
9685
|
-
const all = await
|
|
9686
|
-
const usage = await
|
|
9787
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9788
|
+
const usage = await loadUsageIndex15(paths);
|
|
9687
9789
|
const inferred = inferModulesFromPaths4(files);
|
|
9688
9790
|
const byAnchor = [];
|
|
9689
9791
|
const byModule = [];
|
|
@@ -9781,7 +9883,7 @@ function printGroup(root, label, loaded, usage) {
|
|
|
9781
9883
|
\u2014 ${label} \u2014`));
|
|
9782
9884
|
for (const { memory: mem, filePath } of loaded) {
|
|
9783
9885
|
const fm = mem.frontmatter;
|
|
9784
|
-
const u =
|
|
9886
|
+
const u = getUsage13(usage, fm.id);
|
|
9785
9887
|
const conf = deriveConfidence9(fm, u);
|
|
9786
9888
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
9787
9889
|
console.log(` ${ui.dim(path24.relative(root, filePath))}`);
|
|
@@ -9789,13 +9891,13 @@ function printGroup(root, label, loaded, usage) {
|
|
|
9789
9891
|
}
|
|
9790
9892
|
|
|
9791
9893
|
// src/commands/memory-hot.ts
|
|
9792
|
-
import { existsSync as
|
|
9894
|
+
import { existsSync as existsSync41 } from "fs";
|
|
9793
9895
|
import path25 from "path";
|
|
9794
9896
|
import "commander";
|
|
9795
9897
|
import {
|
|
9796
9898
|
findProjectRoot as findProjectRoot21,
|
|
9797
|
-
getUsage as
|
|
9798
|
-
loadUsageIndex as
|
|
9899
|
+
getUsage as getUsage14,
|
|
9900
|
+
loadUsageIndex as loadUsageIndex16,
|
|
9799
9901
|
resolveHaivePaths as resolveHaivePaths18
|
|
9800
9902
|
} from "@hiveai/core";
|
|
9801
9903
|
function registerMemoryHot(memory2) {
|
|
@@ -9804,23 +9906,23 @@ function registerMemoryHot(memory2) {
|
|
|
9804
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) => {
|
|
9805
9907
|
const root = findProjectRoot21(opts.dir);
|
|
9806
9908
|
const paths = resolveHaivePaths18(root);
|
|
9807
|
-
if (!
|
|
9909
|
+
if (!existsSync41(paths.memoriesDir)) {
|
|
9808
9910
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9809
9911
|
process.exitCode = 1;
|
|
9810
9912
|
return;
|
|
9811
9913
|
}
|
|
9812
9914
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
9813
|
-
const all = await
|
|
9814
|
-
const usage = await
|
|
9915
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9916
|
+
const usage = await loadUsageIndex16(paths);
|
|
9815
9917
|
const candidates = all.filter(({ memory: mem }) => {
|
|
9816
9918
|
const fm = mem.frontmatter;
|
|
9817
9919
|
if (opts.status && fm.status !== opts.status) return false;
|
|
9818
9920
|
if (opts.status === void 0 && fm.status !== "draft" && fm.status !== "proposed") {
|
|
9819
9921
|
return false;
|
|
9820
9922
|
}
|
|
9821
|
-
return
|
|
9923
|
+
return getUsage14(usage, fm.id).read_count >= threshold;
|
|
9822
9924
|
}).sort(
|
|
9823
|
-
(a, b) =>
|
|
9925
|
+
(a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
|
|
9824
9926
|
);
|
|
9825
9927
|
if (candidates.length === 0) {
|
|
9826
9928
|
ui.info(`No hot memories (threshold=${threshold}).`);
|
|
@@ -9828,7 +9930,7 @@ function registerMemoryHot(memory2) {
|
|
|
9828
9930
|
}
|
|
9829
9931
|
for (const { memory: mem, filePath } of candidates) {
|
|
9830
9932
|
const fm = mem.frontmatter;
|
|
9831
|
-
const u =
|
|
9933
|
+
const u = getUsage14(usage, fm.id);
|
|
9832
9934
|
console.log(
|
|
9833
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}`)}`
|
|
9834
9936
|
);
|
|
@@ -9843,7 +9945,7 @@ function registerMemoryHot(memory2) {
|
|
|
9843
9945
|
|
|
9844
9946
|
// src/commands/memory-tried.ts
|
|
9845
9947
|
import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
|
|
9846
|
-
import { existsSync as
|
|
9948
|
+
import { existsSync as existsSync43 } from "fs";
|
|
9847
9949
|
import path26 from "path";
|
|
9848
9950
|
import "commander";
|
|
9849
9951
|
import {
|
|
@@ -9873,7 +9975,7 @@ function registerMemoryTried(memory2) {
|
|
|
9873
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) => {
|
|
9874
9976
|
const root = findProjectRoot22(opts.dir);
|
|
9875
9977
|
const paths = resolveHaivePaths19(root);
|
|
9876
|
-
if (!
|
|
9978
|
+
if (!existsSync43(paths.haiveDir)) {
|
|
9877
9979
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9878
9980
|
process.exitCode = 1;
|
|
9879
9981
|
return;
|
|
@@ -9901,7 +10003,7 @@ function registerMemoryTried(memory2) {
|
|
|
9901
10003
|
}
|
|
9902
10004
|
const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9903
10005
|
await mkdir13(path26.dirname(file), { recursive: true });
|
|
9904
|
-
if (
|
|
10006
|
+
if (existsSync43(file)) {
|
|
9905
10007
|
ui.error(`Memory already exists at ${file}`);
|
|
9906
10008
|
process.exitCode = 1;
|
|
9907
10009
|
return;
|
|
@@ -9919,7 +10021,7 @@ function parseCsv4(value) {
|
|
|
9919
10021
|
|
|
9920
10022
|
// src/commands/memory-seed.ts
|
|
9921
10023
|
import { readFile as readFile13 } from "fs/promises";
|
|
9922
|
-
import { existsSync as
|
|
10024
|
+
import { existsSync as existsSync44 } from "fs";
|
|
9923
10025
|
import path27 from "path";
|
|
9924
10026
|
import "commander";
|
|
9925
10027
|
import {
|
|
@@ -9959,7 +10061,7 @@ function registerMemorySeed(memory2) {
|
|
|
9959
10061
|
}
|
|
9960
10062
|
return;
|
|
9961
10063
|
}
|
|
9962
|
-
if (!
|
|
10064
|
+
if (!existsSync44(paths.haiveDir)) {
|
|
9963
10065
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9964
10066
|
process.exitCode = 1;
|
|
9965
10067
|
return;
|
|
@@ -10015,26 +10117,26 @@ function registerMemorySeed(memory2) {
|
|
|
10015
10117
|
}
|
|
10016
10118
|
|
|
10017
10119
|
// src/commands/memory-pending.ts
|
|
10018
|
-
import { existsSync as
|
|
10120
|
+
import { existsSync as existsSync45 } from "fs";
|
|
10019
10121
|
import path28 from "path";
|
|
10020
10122
|
import "commander";
|
|
10021
10123
|
import {
|
|
10022
10124
|
findProjectRoot as findProjectRoot24,
|
|
10023
|
-
getUsage as
|
|
10024
|
-
loadUsageIndex as
|
|
10125
|
+
getUsage as getUsage15,
|
|
10126
|
+
loadUsageIndex as loadUsageIndex17,
|
|
10025
10127
|
resolveHaivePaths as resolveHaivePaths21
|
|
10026
10128
|
} from "@hiveai/core";
|
|
10027
10129
|
function registerMemoryPending(memory2) {
|
|
10028
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) => {
|
|
10029
10131
|
const root = findProjectRoot24(opts.dir);
|
|
10030
10132
|
const paths = resolveHaivePaths21(root);
|
|
10031
|
-
if (!
|
|
10133
|
+
if (!existsSync45(paths.memoriesDir)) {
|
|
10032
10134
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10033
10135
|
process.exitCode = 1;
|
|
10034
10136
|
return;
|
|
10035
10137
|
}
|
|
10036
|
-
const all = await
|
|
10037
|
-
const usage = await
|
|
10138
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10139
|
+
const usage = await loadUsageIndex17(paths);
|
|
10038
10140
|
const filterFn = ({ memory: mem }) => {
|
|
10039
10141
|
if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
|
|
10040
10142
|
if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
|
|
@@ -10047,7 +10149,7 @@ function registerMemoryPending(memory2) {
|
|
|
10047
10149
|
return;
|
|
10048
10150
|
}
|
|
10049
10151
|
pending.sort(
|
|
10050
|
-
(a, b) =>
|
|
10152
|
+
(a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
|
|
10051
10153
|
);
|
|
10052
10154
|
const now = Date.now();
|
|
10053
10155
|
const drafts = pending.filter((m) => m.memory.frontmatter.status === "draft");
|
|
@@ -10056,7 +10158,7 @@ function registerMemoryPending(memory2) {
|
|
|
10056
10158
|
console.log(ui.bold(`Proposed (${proposed.length}) \u2014 awaiting team validation`));
|
|
10057
10159
|
for (const { memory: mem, filePath } of proposed) {
|
|
10058
10160
|
const fm = mem.frontmatter;
|
|
10059
|
-
const u =
|
|
10161
|
+
const u = getUsage15(usage, fm.id);
|
|
10060
10162
|
const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
|
|
10061
10163
|
const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
|
|
10062
10164
|
console.log(
|
|
@@ -10071,7 +10173,7 @@ function registerMemoryPending(memory2) {
|
|
|
10071
10173
|
console.log(ui.bold(`Draft (${drafts.length}) \u2014 created but not yet activated`));
|
|
10072
10174
|
for (const { memory: mem, filePath } of drafts) {
|
|
10073
10175
|
const fm = mem.frontmatter;
|
|
10074
|
-
const u =
|
|
10176
|
+
const u = getUsage15(usage, fm.id);
|
|
10075
10177
|
const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
|
|
10076
10178
|
const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
|
|
10077
10179
|
console.log(
|
|
@@ -10086,7 +10188,7 @@ function registerMemoryPending(memory2) {
|
|
|
10086
10188
|
}
|
|
10087
10189
|
|
|
10088
10190
|
// src/commands/memory-query.ts
|
|
10089
|
-
import { existsSync as
|
|
10191
|
+
import { existsSync as existsSync46 } from "fs";
|
|
10090
10192
|
import path29 from "path";
|
|
10091
10193
|
import "commander";
|
|
10092
10194
|
import {
|
|
@@ -10103,7 +10205,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10103
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) => {
|
|
10104
10206
|
const root = findProjectRoot25(opts.dir);
|
|
10105
10207
|
const paths = resolveHaivePaths22(root);
|
|
10106
|
-
if (!
|
|
10208
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
10107
10209
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
10108
10210
|
process.exitCode = 1;
|
|
10109
10211
|
return;
|
|
@@ -10114,7 +10216,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10114
10216
|
return;
|
|
10115
10217
|
}
|
|
10116
10218
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
10117
|
-
const all = await
|
|
10219
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10118
10220
|
const passesFilters2 = (mem) => {
|
|
10119
10221
|
const fm = mem.frontmatter;
|
|
10120
10222
|
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
@@ -10162,26 +10264,26 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
10162
10264
|
|
|
10163
10265
|
// src/commands/memory-reject.ts
|
|
10164
10266
|
import { writeFile as writeFile20 } from "fs/promises";
|
|
10165
|
-
import { existsSync as
|
|
10267
|
+
import { existsSync as existsSync47 } from "fs";
|
|
10166
10268
|
import "commander";
|
|
10167
10269
|
import {
|
|
10168
10270
|
findProjectRoot as findProjectRoot26,
|
|
10169
|
-
loadUsageIndex as
|
|
10170
|
-
recordRejection as
|
|
10271
|
+
loadUsageIndex as loadUsageIndex18,
|
|
10272
|
+
recordRejection as recordRejection3,
|
|
10171
10273
|
resolveHaivePaths as resolveHaivePaths23,
|
|
10172
|
-
saveUsageIndex as
|
|
10274
|
+
saveUsageIndex as saveUsageIndex4,
|
|
10173
10275
|
serializeMemory as serializeMemory18
|
|
10174
10276
|
} from "@hiveai/core";
|
|
10175
10277
|
function registerMemoryReject(memory2) {
|
|
10176
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) => {
|
|
10177
10279
|
const root = findProjectRoot26(opts.dir);
|
|
10178
10280
|
const paths = resolveHaivePaths23(root);
|
|
10179
|
-
if (!
|
|
10281
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
10180
10282
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10181
10283
|
process.exitCode = 1;
|
|
10182
10284
|
return;
|
|
10183
10285
|
}
|
|
10184
|
-
const memories = await
|
|
10286
|
+
const memories = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10185
10287
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
10186
10288
|
if (!loaded) {
|
|
10187
10289
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10200,9 +10302,9 @@ function registerMemoryReject(memory2) {
|
|
|
10200
10302
|
}),
|
|
10201
10303
|
"utf8"
|
|
10202
10304
|
);
|
|
10203
|
-
const idx = await
|
|
10204
|
-
|
|
10205
|
-
await
|
|
10305
|
+
const idx = await loadUsageIndex18(paths);
|
|
10306
|
+
recordRejection3(idx, id, opts.reason ?? null);
|
|
10307
|
+
await saveUsageIndex4(paths, idx);
|
|
10206
10308
|
const u = idx.by_id[id];
|
|
10207
10309
|
ui.success(
|
|
10208
10310
|
`Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
|
|
@@ -10212,27 +10314,27 @@ function registerMemoryReject(memory2) {
|
|
|
10212
10314
|
}
|
|
10213
10315
|
|
|
10214
10316
|
// src/commands/memory-rm.ts
|
|
10215
|
-
import { existsSync as
|
|
10317
|
+
import { existsSync as existsSync48 } from "fs";
|
|
10216
10318
|
import { unlink as unlink3 } from "fs/promises";
|
|
10217
10319
|
import path30 from "path";
|
|
10218
10320
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
10219
10321
|
import "commander";
|
|
10220
10322
|
import {
|
|
10221
10323
|
findProjectRoot as findProjectRoot27,
|
|
10222
|
-
loadUsageIndex as
|
|
10324
|
+
loadUsageIndex as loadUsageIndex19,
|
|
10223
10325
|
resolveHaivePaths as resolveHaivePaths24,
|
|
10224
|
-
saveUsageIndex as
|
|
10326
|
+
saveUsageIndex as saveUsageIndex5
|
|
10225
10327
|
} from "@hiveai/core";
|
|
10226
10328
|
function registerMemoryRm(memory2) {
|
|
10227
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) => {
|
|
10228
10330
|
const root = findProjectRoot27(opts.dir);
|
|
10229
10331
|
const paths = resolveHaivePaths24(root);
|
|
10230
|
-
if (!
|
|
10332
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
10231
10333
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10232
10334
|
process.exitCode = 1;
|
|
10233
10335
|
return;
|
|
10234
10336
|
}
|
|
10235
|
-
const all = await
|
|
10337
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10236
10338
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10237
10339
|
if (!found) {
|
|
10238
10340
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10252,10 +10354,10 @@ function registerMemoryRm(memory2) {
|
|
|
10252
10354
|
await unlink3(found.filePath);
|
|
10253
10355
|
ui.success(`Deleted ${rel}`);
|
|
10254
10356
|
if (!opts.keepUsage) {
|
|
10255
|
-
const idx = await
|
|
10357
|
+
const idx = await loadUsageIndex19(paths);
|
|
10256
10358
|
if (idx.by_id[id]) {
|
|
10257
10359
|
delete idx.by_id[id];
|
|
10258
|
-
await
|
|
10360
|
+
await saveUsageIndex5(paths, idx);
|
|
10259
10361
|
ui.info("Removed usage entry");
|
|
10260
10362
|
}
|
|
10261
10363
|
}
|
|
@@ -10263,27 +10365,27 @@ function registerMemoryRm(memory2) {
|
|
|
10263
10365
|
}
|
|
10264
10366
|
|
|
10265
10367
|
// src/commands/memory-show.ts
|
|
10266
|
-
import { existsSync as
|
|
10368
|
+
import { existsSync as existsSync49 } from "fs";
|
|
10267
10369
|
import { readFile as readFile14 } from "fs/promises";
|
|
10268
10370
|
import path31 from "path";
|
|
10269
10371
|
import "commander";
|
|
10270
10372
|
import {
|
|
10271
10373
|
deriveConfidence as deriveConfidence10,
|
|
10272
10374
|
findProjectRoot as findProjectRoot28,
|
|
10273
|
-
getUsage as
|
|
10274
|
-
loadUsageIndex as
|
|
10375
|
+
getUsage as getUsage16,
|
|
10376
|
+
loadUsageIndex as loadUsageIndex20,
|
|
10275
10377
|
resolveHaivePaths as resolveHaivePaths25
|
|
10276
10378
|
} from "@hiveai/core";
|
|
10277
10379
|
function registerMemoryShow(memory2) {
|
|
10278
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) => {
|
|
10279
10381
|
const root = findProjectRoot28(opts.dir);
|
|
10280
10382
|
const paths = resolveHaivePaths25(root);
|
|
10281
|
-
if (!
|
|
10383
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
10282
10384
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10283
10385
|
process.exitCode = 1;
|
|
10284
10386
|
return;
|
|
10285
10387
|
}
|
|
10286
|
-
const all = await
|
|
10388
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10287
10389
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10288
10390
|
if (!found) {
|
|
10289
10391
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10295,8 +10397,8 @@ function registerMemoryShow(memory2) {
|
|
|
10295
10397
|
return;
|
|
10296
10398
|
}
|
|
10297
10399
|
const fm = found.memory.frontmatter;
|
|
10298
|
-
const usage = await
|
|
10299
|
-
const u =
|
|
10400
|
+
const usage = await loadUsageIndex20(paths);
|
|
10401
|
+
const u = getUsage16(usage, fm.id);
|
|
10300
10402
|
const conf = deriveConfidence10(fm, u);
|
|
10301
10403
|
console.log(ui.bold(fm.id));
|
|
10302
10404
|
console.log(`${ui.dim("scope:")} ${fm.scope}${fm.module ? ` / ${fm.module}` : ""}`);
|
|
@@ -10322,38 +10424,38 @@ function registerMemoryShow(memory2) {
|
|
|
10322
10424
|
}
|
|
10323
10425
|
|
|
10324
10426
|
// src/commands/memory-stats.ts
|
|
10325
|
-
import { existsSync as
|
|
10427
|
+
import { existsSync as existsSync50 } from "fs";
|
|
10326
10428
|
import path33 from "path";
|
|
10327
10429
|
import "commander";
|
|
10328
10430
|
import {
|
|
10329
10431
|
deriveConfidence as deriveConfidence11,
|
|
10330
10432
|
findProjectRoot as findProjectRoot29,
|
|
10331
|
-
getUsage as
|
|
10332
|
-
loadUsageIndex as
|
|
10433
|
+
getUsage as getUsage17,
|
|
10434
|
+
loadUsageIndex as loadUsageIndex21,
|
|
10333
10435
|
resolveHaivePaths as resolveHaivePaths26
|
|
10334
10436
|
} from "@hiveai/core";
|
|
10335
10437
|
function registerMemoryStats(memory2) {
|
|
10336
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) => {
|
|
10337
10439
|
const root = findProjectRoot29(opts.dir);
|
|
10338
10440
|
const paths = resolveHaivePaths26(root);
|
|
10339
|
-
if (!
|
|
10441
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
10340
10442
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10341
10443
|
process.exitCode = 1;
|
|
10342
10444
|
return;
|
|
10343
10445
|
}
|
|
10344
|
-
const all = await
|
|
10345
|
-
const usage = await
|
|
10446
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10447
|
+
const usage = await loadUsageIndex21(paths);
|
|
10346
10448
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10347
10449
|
if (target.length === 0) {
|
|
10348
10450
|
ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
|
|
10349
10451
|
return;
|
|
10350
10452
|
}
|
|
10351
10453
|
target.sort(
|
|
10352
|
-
(a, b) =>
|
|
10454
|
+
(a, b) => getUsage17(usage, b.memory.frontmatter.id).read_count - getUsage17(usage, a.memory.frontmatter.id).read_count
|
|
10353
10455
|
);
|
|
10354
10456
|
for (const { memory: mem, filePath } of target) {
|
|
10355
10457
|
const fm = mem.frontmatter;
|
|
10356
|
-
const u =
|
|
10458
|
+
const u = getUsage17(usage, fm.id);
|
|
10357
10459
|
const conf = deriveConfidence11(fm, u);
|
|
10358
10460
|
console.log(
|
|
10359
10461
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`
|
|
@@ -10366,14 +10468,102 @@ function registerMemoryStats(memory2) {
|
|
|
10366
10468
|
});
|
|
10367
10469
|
}
|
|
10368
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
|
+
|
|
10369
10559
|
// src/commands/memory-verify.ts
|
|
10370
10560
|
import { writeFile as writeFile21 } from "fs/promises";
|
|
10371
|
-
import { existsSync as
|
|
10561
|
+
import { existsSync as existsSync53 } from "fs";
|
|
10372
10562
|
import path34 from "path";
|
|
10373
10563
|
import "commander";
|
|
10374
10564
|
import {
|
|
10375
|
-
findProjectRoot as
|
|
10376
|
-
resolveHaivePaths as
|
|
10565
|
+
findProjectRoot as findProjectRoot31,
|
|
10566
|
+
resolveHaivePaths as resolveHaivePaths28,
|
|
10377
10567
|
serializeMemory as serializeMemory19,
|
|
10378
10568
|
verifyAnchor as verifyAnchor3
|
|
10379
10569
|
} from "@hiveai/core";
|
|
@@ -10381,9 +10571,9 @@ function registerMemoryVerify(memory2) {
|
|
|
10381
10571
|
memory2.command("verify").description(
|
|
10382
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"
|
|
10383
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) => {
|
|
10384
|
-
const root =
|
|
10385
|
-
const paths =
|
|
10386
|
-
if (!
|
|
10574
|
+
const root = findProjectRoot31(opts.dir);
|
|
10575
|
+
const paths = resolveHaivePaths28(root);
|
|
10576
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
10387
10577
|
if (opts.json) {
|
|
10388
10578
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
10389
10579
|
} else {
|
|
@@ -10392,7 +10582,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10392
10582
|
process.exitCode = 1;
|
|
10393
10583
|
return;
|
|
10394
10584
|
}
|
|
10395
|
-
const all = await
|
|
10585
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10396
10586
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10397
10587
|
if (opts.id && targets.length === 0) {
|
|
10398
10588
|
if (opts.json) {
|
|
@@ -10504,24 +10694,24 @@ function applyVerification2(mem, result) {
|
|
|
10504
10694
|
|
|
10505
10695
|
// src/commands/memory-import.ts
|
|
10506
10696
|
import { readFile as readFile15 } from "fs/promises";
|
|
10507
|
-
import { existsSync as
|
|
10697
|
+
import { existsSync as existsSync54 } from "fs";
|
|
10508
10698
|
import "commander";
|
|
10509
10699
|
import {
|
|
10510
|
-
findProjectRoot as
|
|
10511
|
-
resolveHaivePaths as
|
|
10700
|
+
findProjectRoot as findProjectRoot32,
|
|
10701
|
+
resolveHaivePaths as resolveHaivePaths29
|
|
10512
10702
|
} from "@hiveai/core";
|
|
10513
10703
|
function registerMemoryImport(memory2) {
|
|
10514
10704
|
memory2.command("import").description(
|
|
10515
10705
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
10516
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) => {
|
|
10517
|
-
const root =
|
|
10518
|
-
const paths =
|
|
10519
|
-
if (!
|
|
10707
|
+
const root = findProjectRoot32(opts.dir);
|
|
10708
|
+
const paths = resolveHaivePaths29(root);
|
|
10709
|
+
if (!existsSync54(paths.haiveDir)) {
|
|
10520
10710
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10521
10711
|
process.exitCode = 1;
|
|
10522
10712
|
return;
|
|
10523
10713
|
}
|
|
10524
|
-
if (!
|
|
10714
|
+
if (!existsSync54(opts.from)) {
|
|
10525
10715
|
ui.error(`File not found: ${opts.from}`);
|
|
10526
10716
|
process.exitCode = 1;
|
|
10527
10717
|
return;
|
|
@@ -10554,14 +10744,14 @@ function registerMemoryImport(memory2) {
|
|
|
10554
10744
|
}
|
|
10555
10745
|
|
|
10556
10746
|
// src/commands/memory-import-changelog.ts
|
|
10557
|
-
import { existsSync as
|
|
10747
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10558
10748
|
import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
|
|
10559
10749
|
import path35 from "path";
|
|
10560
10750
|
import "commander";
|
|
10561
10751
|
import {
|
|
10562
10752
|
buildFrontmatter as buildFrontmatter9,
|
|
10563
|
-
findProjectRoot as
|
|
10564
|
-
resolveHaivePaths as
|
|
10753
|
+
findProjectRoot as findProjectRoot33,
|
|
10754
|
+
resolveHaivePaths as resolveHaivePaths30,
|
|
10565
10755
|
serializeMemory as serializeMemory20
|
|
10566
10756
|
} from "@hiveai/core";
|
|
10567
10757
|
function parseChangelog(content) {
|
|
@@ -10626,10 +10816,10 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10626
10816
|
"--versions <csv>",
|
|
10627
10817
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
10628
10818
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10629
|
-
const root =
|
|
10630
|
-
const paths =
|
|
10819
|
+
const root = findProjectRoot33(opts.dir);
|
|
10820
|
+
const paths = resolveHaivePaths30(root);
|
|
10631
10821
|
const changelogPath = path35.resolve(root, opts.fromChangelog);
|
|
10632
|
-
if (!
|
|
10822
|
+
if (!existsSync55(changelogPath)) {
|
|
10633
10823
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
10634
10824
|
process.exitCode = 1;
|
|
10635
10825
|
return;
|
|
@@ -10716,17 +10906,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
10716
10906
|
}
|
|
10717
10907
|
|
|
10718
10908
|
// src/commands/memory-digest.ts
|
|
10719
|
-
import { existsSync as
|
|
10909
|
+
import { existsSync as existsSync56 } from "fs";
|
|
10720
10910
|
import { writeFile as writeFile24 } from "fs/promises";
|
|
10721
10911
|
import path36 from "path";
|
|
10722
10912
|
import "commander";
|
|
10723
10913
|
import {
|
|
10724
10914
|
deriveConfidence as deriveConfidence12,
|
|
10725
|
-
findProjectRoot as
|
|
10726
|
-
getUsage as
|
|
10727
|
-
loadMemoriesFromDir as
|
|
10728
|
-
loadUsageIndex as
|
|
10729
|
-
resolveHaivePaths as
|
|
10915
|
+
findProjectRoot as findProjectRoot34,
|
|
10916
|
+
getUsage as getUsage19,
|
|
10917
|
+
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
10918
|
+
loadUsageIndex as loadUsageIndex24,
|
|
10919
|
+
resolveHaivePaths as resolveHaivePaths31
|
|
10730
10920
|
} from "@hiveai/core";
|
|
10731
10921
|
var CONFIDENCE_EMOJI = {
|
|
10732
10922
|
unverified: "\u2B1C",
|
|
@@ -10739,9 +10929,9 @@ function registerMemoryDigest(program2) {
|
|
|
10739
10929
|
program2.command("digest").description(
|
|
10740
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"
|
|
10741
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) => {
|
|
10742
|
-
const root =
|
|
10743
|
-
const paths =
|
|
10744
|
-
if (!
|
|
10932
|
+
const root = findProjectRoot34(opts.dir);
|
|
10933
|
+
const paths = resolveHaivePaths31(root);
|
|
10934
|
+
if (!existsSync56(paths.memoriesDir)) {
|
|
10745
10935
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
10746
10936
|
process.exitCode = 1;
|
|
10747
10937
|
return;
|
|
@@ -10749,8 +10939,8 @@ function registerMemoryDigest(program2) {
|
|
|
10749
10939
|
const days = Math.max(1, Number(opts.days ?? 7));
|
|
10750
10940
|
const scopeFilter = opts.scope ?? "team";
|
|
10751
10941
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
10752
|
-
const all = await
|
|
10753
|
-
const usage = await
|
|
10942
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10943
|
+
const usage = await loadUsageIndex24(paths);
|
|
10754
10944
|
const recent = all.filter(({ memory: mem }) => {
|
|
10755
10945
|
const fm = mem.frontmatter;
|
|
10756
10946
|
if (fm.type === "session_recap") return false;
|
|
@@ -10781,7 +10971,7 @@ function registerMemoryDigest(program2) {
|
|
|
10781
10971
|
lines.push(``);
|
|
10782
10972
|
for (const { memory: mem } of mems) {
|
|
10783
10973
|
const fm = mem.frontmatter;
|
|
10784
|
-
const u =
|
|
10974
|
+
const u = getUsage19(usage, fm.id);
|
|
10785
10975
|
const confidence = deriveConfidence12(fm, u);
|
|
10786
10976
|
const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
|
|
10787
10977
|
const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
|
|
@@ -10824,21 +11014,21 @@ function registerMemoryDigest(program2) {
|
|
|
10824
11014
|
|
|
10825
11015
|
// src/commands/session-end.ts
|
|
10826
11016
|
import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
|
|
10827
|
-
import { existsSync as
|
|
11017
|
+
import { existsSync as existsSync57 } from "fs";
|
|
10828
11018
|
import { spawn as spawn4 } from "child_process";
|
|
10829
11019
|
import path37 from "path";
|
|
10830
11020
|
import "commander";
|
|
10831
11021
|
import {
|
|
10832
11022
|
buildFrontmatter as buildFrontmatter10,
|
|
10833
|
-
findProjectRoot as
|
|
10834
|
-
loadMemoriesFromDir as
|
|
11023
|
+
findProjectRoot as findProjectRoot35,
|
|
11024
|
+
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
10835
11025
|
memoryFilePath as memoryFilePath9,
|
|
10836
|
-
resolveHaivePaths as
|
|
11026
|
+
resolveHaivePaths as resolveHaivePaths32,
|
|
10837
11027
|
serializeMemory as serializeMemory21
|
|
10838
11028
|
} from "@hiveai/core";
|
|
10839
11029
|
async function buildAutoRecap(paths) {
|
|
10840
11030
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10841
|
-
if (!
|
|
11031
|
+
if (!existsSync57(obsFile)) return await buildGitAutoRecap(paths);
|
|
10842
11032
|
const raw = await readFile17(obsFile, "utf8").catch(() => "");
|
|
10843
11033
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
10844
11034
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -11027,9 +11217,9 @@ function registerSessionEnd(session2) {
|
|
|
11027
11217
|
--next "Add integration tests for webhook signature validation"
|
|
11028
11218
|
`
|
|
11029
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) => {
|
|
11030
|
-
const root =
|
|
11031
|
-
const paths =
|
|
11032
|
-
if (!
|
|
11220
|
+
const root = findProjectRoot35(opts.dir);
|
|
11221
|
+
const paths = resolveHaivePaths32(root);
|
|
11222
|
+
if (!existsSync57(paths.haiveDir)) {
|
|
11033
11223
|
if (opts.auto || opts.quiet) return;
|
|
11034
11224
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11035
11225
|
process.exitCode = 1;
|
|
@@ -11062,7 +11252,7 @@ function registerSessionEnd(session2) {
|
|
|
11062
11252
|
});
|
|
11063
11253
|
const topic = recapTopic2(scope, opts.module);
|
|
11064
11254
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
11065
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
11255
|
+
const missingPaths = filesTouched.filter((p) => !existsSync57(path37.resolve(root, p)));
|
|
11066
11256
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
11067
11257
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
11068
11258
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -11070,11 +11260,11 @@ function registerSessionEnd(session2) {
|
|
|
11070
11260
|
const cleanupObservations = async () => {
|
|
11071
11261
|
if (!opts.auto) return;
|
|
11072
11262
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11073
|
-
if (
|
|
11263
|
+
if (existsSync57(obsFile)) await rm2(obsFile).catch(() => {
|
|
11074
11264
|
});
|
|
11075
11265
|
};
|
|
11076
|
-
if (
|
|
11077
|
-
const existing = await
|
|
11266
|
+
if (existsSync57(paths.memoriesDir)) {
|
|
11267
|
+
const existing = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
11078
11268
|
const topicMatch = existing.find(
|
|
11079
11269
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
11080
11270
|
);
|
|
@@ -11135,15 +11325,15 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
11135
11325
|
}
|
|
11136
11326
|
|
|
11137
11327
|
// src/commands/snapshot.ts
|
|
11138
|
-
import { existsSync as
|
|
11328
|
+
import { existsSync as existsSync58 } from "fs";
|
|
11139
11329
|
import { readdir as readdir4 } from "fs/promises";
|
|
11140
11330
|
import path38 from "path";
|
|
11141
11331
|
import "commander";
|
|
11142
11332
|
import {
|
|
11143
11333
|
diffContract,
|
|
11144
|
-
findProjectRoot as
|
|
11334
|
+
findProjectRoot as findProjectRoot36,
|
|
11145
11335
|
loadConfig as loadConfig7,
|
|
11146
|
-
resolveHaivePaths as
|
|
11336
|
+
resolveHaivePaths as resolveHaivePaths33,
|
|
11147
11337
|
snapshotContract
|
|
11148
11338
|
} from "@hiveai/core";
|
|
11149
11339
|
function registerSnapshot(program2) {
|
|
@@ -11168,16 +11358,16 @@ function registerSnapshot(program2) {
|
|
|
11168
11358
|
"--format <format>",
|
|
11169
11359
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
11170
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) => {
|
|
11171
|
-
const root =
|
|
11172
|
-
const paths =
|
|
11173
|
-
if (!
|
|
11361
|
+
const root = findProjectRoot36(opts.dir);
|
|
11362
|
+
const paths = resolveHaivePaths33(root);
|
|
11363
|
+
if (!existsSync58(paths.haiveDir)) {
|
|
11174
11364
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
11175
11365
|
process.exitCode = 1;
|
|
11176
11366
|
return;
|
|
11177
11367
|
}
|
|
11178
11368
|
if (opts.list) {
|
|
11179
11369
|
const contractsDir = path38.join(paths.haiveDir, "contracts");
|
|
11180
|
-
if (!
|
|
11370
|
+
if (!existsSync58(contractsDir)) {
|
|
11181
11371
|
console.log(ui.dim("No contract snapshots found."));
|
|
11182
11372
|
return;
|
|
11183
11373
|
}
|
|
@@ -11301,16 +11491,16 @@ function detectFormat(filePath) {
|
|
|
11301
11491
|
}
|
|
11302
11492
|
|
|
11303
11493
|
// src/commands/hub.ts
|
|
11304
|
-
import { existsSync as
|
|
11494
|
+
import { existsSync as existsSync59 } from "fs";
|
|
11305
11495
|
import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
|
|
11306
11496
|
import path39 from "path";
|
|
11307
11497
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
11308
11498
|
import "commander";
|
|
11309
11499
|
import {
|
|
11310
|
-
findProjectRoot as
|
|
11500
|
+
findProjectRoot as findProjectRoot37,
|
|
11311
11501
|
loadConfig as loadConfig8,
|
|
11312
|
-
loadMemoriesFromDir as
|
|
11313
|
-
resolveHaivePaths as
|
|
11502
|
+
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
11503
|
+
resolveHaivePaths as resolveHaivePaths34,
|
|
11314
11504
|
saveConfig as saveConfig3,
|
|
11315
11505
|
serializeMemory as serializeMemory23
|
|
11316
11506
|
} from "@hiveai/core";
|
|
@@ -11392,8 +11582,8 @@ Next steps:
|
|
|
11392
11582
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
11393
11583
|
`
|
|
11394
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) => {
|
|
11395
|
-
const root =
|
|
11396
|
-
const paths =
|
|
11585
|
+
const root = findProjectRoot37(opts.dir);
|
|
11586
|
+
const paths = resolveHaivePaths34(root);
|
|
11397
11587
|
const config = await loadConfig8(paths);
|
|
11398
11588
|
if (!config.hubPath) {
|
|
11399
11589
|
ui.error(
|
|
@@ -11403,7 +11593,7 @@ Next steps:
|
|
|
11403
11593
|
return;
|
|
11404
11594
|
}
|
|
11405
11595
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11406
|
-
if (!
|
|
11596
|
+
if (!existsSync59(hubRoot)) {
|
|
11407
11597
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
11408
11598
|
process.exitCode = 1;
|
|
11409
11599
|
return;
|
|
@@ -11411,7 +11601,7 @@ Next steps:
|
|
|
11411
11601
|
const projectName = path39.basename(root);
|
|
11412
11602
|
const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
11413
11603
|
await mkdir16(destDir, { recursive: true });
|
|
11414
|
-
const all = await
|
|
11604
|
+
const all = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
11415
11605
|
const shared = all.filter(
|
|
11416
11606
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
11417
11607
|
!memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
@@ -11461,8 +11651,8 @@ Next steps:
|
|
|
11461
11651
|
hub.command("pull").description(
|
|
11462
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"
|
|
11463
11653
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11464
|
-
const root =
|
|
11465
|
-
const paths =
|
|
11654
|
+
const root = findProjectRoot37(opts.dir);
|
|
11655
|
+
const paths = resolveHaivePaths34(root);
|
|
11466
11656
|
const config = await loadConfig8(paths);
|
|
11467
11657
|
if (!config.hubPath) {
|
|
11468
11658
|
ui.error(
|
|
@@ -11473,7 +11663,7 @@ Next steps:
|
|
|
11473
11663
|
}
|
|
11474
11664
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11475
11665
|
const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
|
|
11476
|
-
if (!
|
|
11666
|
+
if (!existsSync59(hubSharedDir)) {
|
|
11477
11667
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
11478
11668
|
return;
|
|
11479
11669
|
}
|
|
@@ -11520,15 +11710,15 @@ Next steps:
|
|
|
11520
11710
|
);
|
|
11521
11711
|
});
|
|
11522
11712
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11523
|
-
const root =
|
|
11524
|
-
const paths =
|
|
11713
|
+
const root = findProjectRoot37(opts.dir);
|
|
11714
|
+
const paths = resolveHaivePaths34(root);
|
|
11525
11715
|
const config = await loadConfig8(paths);
|
|
11526
11716
|
console.log(ui.bold("Hub status"));
|
|
11527
11717
|
console.log(
|
|
11528
11718
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
11529
11719
|
);
|
|
11530
11720
|
const sharedDir = path39.join(paths.memoriesDir, "shared");
|
|
11531
|
-
if (
|
|
11721
|
+
if (existsSync59(sharedDir)) {
|
|
11532
11722
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11533
11723
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
11534
11724
|
console.log(`
|
|
@@ -11540,7 +11730,7 @@ Next steps:
|
|
|
11540
11730
|
} else {
|
|
11541
11731
|
console.log(ui.dim(" No imported shared memories yet."));
|
|
11542
11732
|
}
|
|
11543
|
-
const all = await
|
|
11733
|
+
const all = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
11544
11734
|
const outgoing = all.filter(
|
|
11545
11735
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
11546
11736
|
);
|
|
@@ -11557,17 +11747,17 @@ Next steps:
|
|
|
11557
11747
|
|
|
11558
11748
|
// src/commands/stats.ts
|
|
11559
11749
|
import "commander";
|
|
11560
|
-
import { existsSync as
|
|
11750
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11561
11751
|
import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
|
|
11562
11752
|
import path40 from "path";
|
|
11563
11753
|
import {
|
|
11564
11754
|
aggregateUsage,
|
|
11565
|
-
findProjectRoot as
|
|
11566
|
-
loadMemoriesFromDir as
|
|
11567
|
-
loadUsageIndex as
|
|
11755
|
+
findProjectRoot as findProjectRoot38,
|
|
11756
|
+
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
11757
|
+
loadUsageIndex as loadUsageIndex25,
|
|
11568
11758
|
parseSince,
|
|
11569
11759
|
readUsageEvents as readUsageEvents2,
|
|
11570
|
-
resolveHaivePaths as
|
|
11760
|
+
resolveHaivePaths as resolveHaivePaths35,
|
|
11571
11761
|
usageLogSize
|
|
11572
11762
|
} from "@hiveai/core";
|
|
11573
11763
|
function registerStats(program2) {
|
|
@@ -11576,8 +11766,8 @@ function registerStats(program2) {
|
|
|
11576
11766
|
"write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
|
|
11577
11767
|
void 0
|
|
11578
11768
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11579
|
-
const root =
|
|
11580
|
-
const paths =
|
|
11769
|
+
const root = findProjectRoot38(opts.dir);
|
|
11770
|
+
const paths = resolveHaivePaths35(root);
|
|
11581
11771
|
if (opts.exportReport) {
|
|
11582
11772
|
await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
|
|
11583
11773
|
return;
|
|
@@ -11623,9 +11813,9 @@ function registerStats(program2) {
|
|
|
11623
11813
|
const maxCount = aggregate.by_tool[0]?.count ?? 1;
|
|
11624
11814
|
for (const t of aggregate.by_tool.slice(0, 20)) {
|
|
11625
11815
|
const bar = "\u2588".repeat(Math.max(1, Math.round(t.count / maxCount * 30)));
|
|
11626
|
-
const
|
|
11816
|
+
const pct2 = (t.count / aggregate.total * 100).toFixed(1);
|
|
11627
11817
|
console.log(
|
|
11628
|
-
` ${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)})`)}`
|
|
11629
11819
|
);
|
|
11630
11820
|
}
|
|
11631
11821
|
});
|
|
@@ -11635,8 +11825,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11635
11825
|
const size = await usageLogSize(paths);
|
|
11636
11826
|
let events = await readUsageEvents2(paths);
|
|
11637
11827
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
11638
|
-
if (
|
|
11639
|
-
const mems = await
|
|
11828
|
+
if (existsSync60(paths.memoriesDir)) {
|
|
11829
|
+
const mems = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11640
11830
|
for (const { memory: memory2 } of mems) {
|
|
11641
11831
|
const fm = memory2.frontmatter;
|
|
11642
11832
|
if (fm.type === "session_recap") memoryCount.total_skipped_session++;
|
|
@@ -11650,7 +11840,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11650
11840
|
const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
|
|
11651
11841
|
let memoryHitsLeader = null;
|
|
11652
11842
|
try {
|
|
11653
|
-
const usageIdx = await
|
|
11843
|
+
const usageIdx = await loadUsageIndex25(paths);
|
|
11654
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);
|
|
11655
11845
|
memoryHitsLeader = tops[0] ?? null;
|
|
11656
11846
|
} catch {
|
|
@@ -11682,7 +11872,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11682
11872
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
11683
11873
|
}
|
|
11684
11874
|
async function renderMemoryHits(paths, opts) {
|
|
11685
|
-
const index = await
|
|
11875
|
+
const index = await loadUsageIndex25(paths);
|
|
11686
11876
|
const since = parseSince(opts.since ?? "30d");
|
|
11687
11877
|
const sinceMs = since ? new Date(since).getTime() : null;
|
|
11688
11878
|
const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
|
|
@@ -11730,13 +11920,13 @@ import { performance } from "perf_hooks";
|
|
|
11730
11920
|
import "commander";
|
|
11731
11921
|
import {
|
|
11732
11922
|
estimateTokens as estimateTokens3,
|
|
11733
|
-
findProjectRoot as
|
|
11734
|
-
resolveHaivePaths as
|
|
11923
|
+
findProjectRoot as findProjectRoot39,
|
|
11924
|
+
resolveHaivePaths as resolveHaivePaths36
|
|
11735
11925
|
} from "@hiveai/core";
|
|
11736
11926
|
function registerBench(program2) {
|
|
11737
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) => {
|
|
11738
|
-
const root =
|
|
11739
|
-
const paths =
|
|
11928
|
+
const root = findProjectRoot39(opts.dir);
|
|
11929
|
+
const paths = resolveHaivePaths36(root);
|
|
11740
11930
|
const ctx = { paths };
|
|
11741
11931
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
11742
11932
|
const scenarios = [
|
|
@@ -11855,11 +12045,11 @@ function summarize(name, t0, payload, notes) {
|
|
|
11855
12045
|
}
|
|
11856
12046
|
|
|
11857
12047
|
// src/commands/benchmark.ts
|
|
11858
|
-
import { existsSync as
|
|
12048
|
+
import { existsSync as existsSync61 } from "fs";
|
|
11859
12049
|
import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
|
|
11860
12050
|
import path41 from "path";
|
|
11861
12051
|
import "commander";
|
|
11862
|
-
import { estimateTokens as estimateTokens4, findProjectRoot as
|
|
12052
|
+
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot40 } from "@hiveai/core";
|
|
11863
12053
|
function registerBenchmark(program2) {
|
|
11864
12054
|
const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
|
|
11865
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) => {
|
|
@@ -11899,18 +12089,18 @@ function registerBenchmark(program2) {
|
|
|
11899
12089
|
function resolveBenchmarkRoot(dir) {
|
|
11900
12090
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
11901
12091
|
if (path41.isAbsolute(candidate)) return candidate;
|
|
11902
|
-
const projectRoot =
|
|
12092
|
+
const projectRoot = findProjectRoot40(process.cwd());
|
|
11903
12093
|
return path41.join(projectRoot, candidate);
|
|
11904
12094
|
}
|
|
11905
12095
|
async function collectRows(root) {
|
|
11906
|
-
if (!
|
|
12096
|
+
if (!existsSync61(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
11907
12097
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
11908
12098
|
const rows = [];
|
|
11909
12099
|
for (const entry of entries) {
|
|
11910
12100
|
if (!entry.isDirectory()) continue;
|
|
11911
12101
|
const fixtureDir = path41.join(root, entry.name);
|
|
11912
12102
|
const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
11913
|
-
if (!
|
|
12103
|
+
if (!existsSync61(reportFile)) continue;
|
|
11914
12104
|
const report = await readFile19(reportFile, "utf8");
|
|
11915
12105
|
rows.push(parseAgentReport(entry.name, report));
|
|
11916
12106
|
}
|
|
@@ -11999,21 +12189,178 @@ function escapeRegExp(value) {
|
|
|
11999
12189
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12000
12190
|
}
|
|
12001
12191
|
|
|
12002
|
-
// src/commands/
|
|
12003
|
-
import {
|
|
12004
|
-
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";
|
|
12005
12195
|
import path43 from "path";
|
|
12006
12196
|
import "commander";
|
|
12197
|
+
import {
|
|
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";
|
|
12007
12354
|
import {
|
|
12008
12355
|
aggregateUsage as aggregateUsage2,
|
|
12009
12356
|
buildFrontmatter as buildFrontmatter11,
|
|
12010
|
-
findProjectRoot as
|
|
12357
|
+
findProjectRoot as findProjectRoot42,
|
|
12011
12358
|
loadConfig as loadConfig9,
|
|
12012
|
-
loadMemoriesFromDir as
|
|
12359
|
+
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
12013
12360
|
memoryFilePath as memoryFilePath10,
|
|
12014
12361
|
parseSince as parseSince2,
|
|
12015
12362
|
readUsageEvents as readUsageEvents3,
|
|
12016
|
-
resolveHaivePaths as
|
|
12363
|
+
resolveHaivePaths as resolveHaivePaths38,
|
|
12017
12364
|
serializeMemory as serializeMemory24
|
|
12018
12365
|
} from "@hiveai/core";
|
|
12019
12366
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -12030,8 +12377,8 @@ function registerMemorySuggest(memory2) {
|
|
|
12030
12377
|
memory2.command("suggest").description(
|
|
12031
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."
|
|
12032
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) => {
|
|
12033
|
-
const root =
|
|
12034
|
-
const paths =
|
|
12380
|
+
const root = findProjectRoot42(opts.dir);
|
|
12381
|
+
const paths = resolveHaivePaths38(root);
|
|
12035
12382
|
const events = await readUsageEvents3(paths);
|
|
12036
12383
|
if (events.length === 0) {
|
|
12037
12384
|
if (opts.json) {
|
|
@@ -12080,7 +12427,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12080
12427
|
}
|
|
12081
12428
|
const created = [];
|
|
12082
12429
|
const skipped = [];
|
|
12083
|
-
const existing =
|
|
12430
|
+
const existing = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
|
|
12084
12431
|
for (const s of top) {
|
|
12085
12432
|
const slug = slugify2(s.query);
|
|
12086
12433
|
if (!slug) {
|
|
@@ -12103,13 +12450,13 @@ function registerMemorySuggest(memory2) {
|
|
|
12103
12450
|
});
|
|
12104
12451
|
const body = renderTemplate(s, fm.id, status);
|
|
12105
12452
|
const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
|
|
12106
|
-
await mkdir18(
|
|
12107
|
-
if (
|
|
12108
|
-
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)}` });
|
|
12109
12456
|
continue;
|
|
12110
12457
|
}
|
|
12111
|
-
await
|
|
12112
|
-
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 });
|
|
12113
12460
|
}
|
|
12114
12461
|
if (opts.json) {
|
|
12115
12462
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -12207,18 +12554,18 @@ function truncate2(text, max) {
|
|
|
12207
12554
|
}
|
|
12208
12555
|
|
|
12209
12556
|
// src/commands/memory-archive.ts
|
|
12210
|
-
import { existsSync as
|
|
12211
|
-
import { writeFile as
|
|
12212
|
-
import
|
|
12557
|
+
import { existsSync as existsSync65 } from "fs";
|
|
12558
|
+
import { writeFile as writeFile31 } from "fs/promises";
|
|
12559
|
+
import path45 from "path";
|
|
12213
12560
|
import "commander";
|
|
12214
12561
|
import {
|
|
12215
|
-
findProjectRoot as
|
|
12216
|
-
getUsage as
|
|
12562
|
+
findProjectRoot as findProjectRoot43,
|
|
12563
|
+
getUsage as getUsage20,
|
|
12217
12564
|
retirementSignal as retirementSignal2,
|
|
12218
12565
|
loadConfig as loadConfig10,
|
|
12219
|
-
loadMemoriesFromDir as
|
|
12220
|
-
loadUsageIndex as
|
|
12221
|
-
resolveHaivePaths as
|
|
12566
|
+
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
12567
|
+
loadUsageIndex as loadUsageIndex26,
|
|
12568
|
+
resolveHaivePaths as resolveHaivePaths39,
|
|
12222
12569
|
serializeMemory as serializeMemory25
|
|
12223
12570
|
} from "@hiveai/core";
|
|
12224
12571
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
@@ -12226,9 +12573,9 @@ function registerMemoryArchive(memory2) {
|
|
|
12226
12573
|
memory2.command("archive").description(
|
|
12227
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."
|
|
12228
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) => {
|
|
12229
|
-
const root =
|
|
12230
|
-
const paths =
|
|
12231
|
-
if (!
|
|
12576
|
+
const root = findProjectRoot43(opts.dir);
|
|
12577
|
+
const paths = resolveHaivePaths39(root);
|
|
12578
|
+
if (!existsSync65(paths.memoriesDir)) {
|
|
12232
12579
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12233
12580
|
process.exitCode = 1;
|
|
12234
12581
|
return;
|
|
@@ -12242,8 +12589,8 @@ function registerMemoryArchive(memory2) {
|
|
|
12242
12589
|
return;
|
|
12243
12590
|
}
|
|
12244
12591
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
12245
|
-
const all = await
|
|
12246
|
-
const usage = await
|
|
12592
|
+
const all = await loadMemoriesFromDir33(paths.memoriesDir);
|
|
12593
|
+
const usage = await loadUsageIndex26(paths);
|
|
12247
12594
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
12248
12595
|
const candidates = [];
|
|
12249
12596
|
for (const { memory: mem, filePath } of all) {
|
|
@@ -12253,10 +12600,10 @@ function registerMemoryArchive(memory2) {
|
|
|
12253
12600
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
12254
12601
|
const retired = retirementSignal2(fm, mem.body);
|
|
12255
12602
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
12256
|
-
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)));
|
|
12257
12604
|
const isAnchorless = !hasAnyAnchor;
|
|
12258
12605
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
12259
|
-
const u =
|
|
12606
|
+
const u = getUsage20(usage, fm.id);
|
|
12260
12607
|
const lastSeen = u.last_read_at ?? fm.created_at;
|
|
12261
12608
|
if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
|
|
12262
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)`;
|
|
@@ -12302,7 +12649,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12302
12649
|
if (!found) continue;
|
|
12303
12650
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
12304
12651
|
try {
|
|
12305
|
-
await
|
|
12652
|
+
await writeFile31(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
12306
12653
|
archived++;
|
|
12307
12654
|
} catch (err) {
|
|
12308
12655
|
if (!opts.json) {
|
|
@@ -12328,34 +12675,34 @@ function parseDays(input) {
|
|
|
12328
12675
|
}
|
|
12329
12676
|
|
|
12330
12677
|
// src/commands/doctor.ts
|
|
12331
|
-
import { existsSync as
|
|
12332
|
-
import { readFile as
|
|
12333
|
-
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";
|
|
12334
12681
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
12335
12682
|
import "commander";
|
|
12336
12683
|
import {
|
|
12337
12684
|
codeMapPath as codeMapPath2,
|
|
12338
|
-
findProjectRoot as
|
|
12339
|
-
getUsage as
|
|
12685
|
+
findProjectRoot as findProjectRoot44,
|
|
12686
|
+
getUsage as getUsage21,
|
|
12340
12687
|
isStackPackSeed as isStackPackSeed4,
|
|
12341
12688
|
loadCodeMap as loadCodeMap7,
|
|
12342
12689
|
loadConfig as loadConfig11,
|
|
12343
|
-
loadMemoriesFromDir as
|
|
12344
|
-
loadUsageIndex as
|
|
12690
|
+
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
12691
|
+
loadUsageIndex as loadUsageIndex27,
|
|
12345
12692
|
readUsageEvents as readUsageEvents4,
|
|
12346
|
-
resolveHaivePaths as
|
|
12693
|
+
resolveHaivePaths as resolveHaivePaths40
|
|
12347
12694
|
} from "@hiveai/core";
|
|
12348
12695
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
|
|
12349
12696
|
function registerDoctor(program2) {
|
|
12350
12697
|
program2.command("doctor").description(
|
|
12351
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."
|
|
12352
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) => {
|
|
12353
|
-
const root =
|
|
12354
|
-
const paths =
|
|
12700
|
+
const root = findProjectRoot44(opts.dir);
|
|
12701
|
+
const paths = resolveHaivePaths40(root);
|
|
12355
12702
|
const findings = [];
|
|
12356
12703
|
const repairs = [];
|
|
12357
12704
|
const config = await loadConfig11(paths);
|
|
12358
|
-
if (!
|
|
12705
|
+
if (!existsSync66(paths.haiveDir)) {
|
|
12359
12706
|
findings.push({
|
|
12360
12707
|
severity: "error",
|
|
12361
12708
|
code: "not-initialized",
|
|
@@ -12376,7 +12723,7 @@ function registerDoctor(program2) {
|
|
|
12376
12723
|
})
|
|
12377
12724
|
);
|
|
12378
12725
|
}
|
|
12379
|
-
if (!
|
|
12726
|
+
if (!existsSync66(paths.projectContext)) {
|
|
12380
12727
|
findings.push({
|
|
12381
12728
|
severity: "warn",
|
|
12382
12729
|
code: "no-project-context",
|
|
@@ -12384,8 +12731,8 @@ function registerDoctor(program2) {
|
|
|
12384
12731
|
fix: "haive init"
|
|
12385
12732
|
});
|
|
12386
12733
|
} else {
|
|
12387
|
-
const { readFile:
|
|
12388
|
-
const content = await
|
|
12734
|
+
const { readFile: readFile25 } = await import("fs/promises");
|
|
12735
|
+
const content = await readFile25(paths.projectContext, "utf8");
|
|
12389
12736
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
12390
12737
|
if (isTemplate) {
|
|
12391
12738
|
findings.push({
|
|
@@ -12405,7 +12752,7 @@ function registerDoctor(program2) {
|
|
|
12405
12752
|
});
|
|
12406
12753
|
}
|
|
12407
12754
|
}
|
|
12408
|
-
const memories =
|
|
12755
|
+
const memories = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
|
|
12409
12756
|
const now = Date.now();
|
|
12410
12757
|
if (memories.length === 0) {
|
|
12411
12758
|
findings.push({
|
|
@@ -12414,7 +12761,7 @@ function registerDoctor(program2) {
|
|
|
12414
12761
|
message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
|
|
12415
12762
|
});
|
|
12416
12763
|
} else {
|
|
12417
|
-
const usage = await
|
|
12764
|
+
const usage = await loadUsageIndex27(paths);
|
|
12418
12765
|
const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
|
|
12419
12766
|
if (stale.length > 0) {
|
|
12420
12767
|
findings.push({
|
|
@@ -12471,7 +12818,7 @@ function registerDoctor(program2) {
|
|
|
12471
12818
|
}
|
|
12472
12819
|
const decayCandidates = memories.filter((m) => {
|
|
12473
12820
|
if (m.memory.frontmatter.status !== "validated") return false;
|
|
12474
|
-
const u =
|
|
12821
|
+
const u = getUsage21(usage, m.memory.frontmatter.id);
|
|
12475
12822
|
const last = u.last_read_at ?? m.memory.frontmatter.created_at;
|
|
12476
12823
|
return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
|
|
12477
12824
|
});
|
|
@@ -12555,12 +12902,12 @@ function registerDoctor(program2) {
|
|
|
12555
12902
|
}
|
|
12556
12903
|
}
|
|
12557
12904
|
if (config.enforcement?.requireBriefingFirst) {
|
|
12558
|
-
const claudeSettings =
|
|
12905
|
+
const claudeSettings = path46.join(root, ".claude", "settings.local.json");
|
|
12559
12906
|
let hasClaudeEnforcement = false;
|
|
12560
|
-
if (
|
|
12907
|
+
if (existsSync66(claudeSettings)) {
|
|
12561
12908
|
try {
|
|
12562
|
-
const { readFile:
|
|
12563
|
-
const raw = await
|
|
12909
|
+
const { readFile: readFile25 } = await import("fs/promises");
|
|
12910
|
+
const raw = await readFile25(claudeSettings, "utf8");
|
|
12564
12911
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
12565
12912
|
} catch {
|
|
12566
12913
|
hasClaudeEnforcement = false;
|
|
@@ -12583,14 +12930,14 @@ function registerDoctor(program2) {
|
|
|
12583
12930
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
12584
12931
|
});
|
|
12585
12932
|
}
|
|
12586
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
12933
|
+
findings.push(...await collectInstallFindings(root, "0.11.0"));
|
|
12587
12934
|
try {
|
|
12588
12935
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
12589
12936
|
encoding: "utf8",
|
|
12590
12937
|
timeout: 3e3,
|
|
12591
12938
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12592
12939
|
}).trim();
|
|
12593
|
-
const cliVersion = "0.
|
|
12940
|
+
const cliVersion = "0.11.0";
|
|
12594
12941
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
12595
12942
|
findings.push({
|
|
12596
12943
|
severity: "warn",
|
|
@@ -12606,20 +12953,20 @@ npm uninstall -g @hiveai/mcp`
|
|
|
12606
12953
|
}
|
|
12607
12954
|
{
|
|
12608
12955
|
const configPaths = [
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12956
|
+
path46.join(root, ".mcp.json"),
|
|
12957
|
+
path46.join(root, ".cursor", "mcp.json"),
|
|
12958
|
+
path46.join(root, ".vscode", "mcp.json")
|
|
12612
12959
|
];
|
|
12613
12960
|
const staleConfigs = [];
|
|
12614
12961
|
for (const cfgPath of configPaths) {
|
|
12615
|
-
if (!
|
|
12962
|
+
if (!existsSync66(cfgPath)) continue;
|
|
12616
12963
|
try {
|
|
12617
|
-
const raw = await
|
|
12964
|
+
const raw = await readFile21(cfgPath, "utf8");
|
|
12618
12965
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
12619
|
-
staleConfigs.push(
|
|
12966
|
+
staleConfigs.push(path46.relative(root, cfgPath));
|
|
12620
12967
|
if (opts.fix && !opts.dryRun) {
|
|
12621
12968
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
12622
|
-
await
|
|
12969
|
+
await writeFile33(cfgPath, updated, "utf8");
|
|
12623
12970
|
}
|
|
12624
12971
|
}
|
|
12625
12972
|
} catch {
|
|
@@ -12790,23 +13137,23 @@ async function collectHarnessCoverageFindings(codeMap, memories) {
|
|
|
12790
13137
|
}
|
|
12791
13138
|
}
|
|
12792
13139
|
const covered = coveredFiles.size;
|
|
12793
|
-
const
|
|
13140
|
+
const pct2 = Math.round(covered / total * 100);
|
|
12794
13141
|
const uncovered = codeFiles.filter((f) => !coveredFiles.has(f)).sort((a, b) => {
|
|
12795
13142
|
const depthA = a.split("/").length;
|
|
12796
13143
|
const depthB = b.split("/").length;
|
|
12797
13144
|
if (depthA !== depthB) return depthA - depthB;
|
|
12798
13145
|
return a.localeCompare(b);
|
|
12799
13146
|
}).slice(0, 5);
|
|
12800
|
-
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.";
|
|
12801
13148
|
const uncoveredHint = uncovered.length > 0 ? `
|
|
12802
13149
|
Top uncovered: ${uncovered.map((f) => `\`${f}\``).join(", ")}` : "";
|
|
12803
13150
|
const findings = [];
|
|
12804
13151
|
findings.push({
|
|
12805
13152
|
severity: "info",
|
|
12806
13153
|
code: "harness-coverage",
|
|
12807
|
-
coverage_percent:
|
|
12808
|
-
message: `${covered}/${total} code-map files have validated memory anchors (${
|
|
12809
|
-
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,
|
|
12810
13157
|
section: "Harness coverage"
|
|
12811
13158
|
});
|
|
12812
13159
|
return findings;
|
|
@@ -12906,9 +13253,9 @@ which -a haive`
|
|
|
12906
13253
|
".vscode/mcp.json"
|
|
12907
13254
|
];
|
|
12908
13255
|
for (const rel of integrationFiles) {
|
|
12909
|
-
const file =
|
|
12910
|
-
if (!
|
|
12911
|
-
const text = await
|
|
13256
|
+
const file = path46.join(root, rel);
|
|
13257
|
+
if (!existsSync66(file)) continue;
|
|
13258
|
+
const text = await readFile21(file, "utf8").catch(() => "");
|
|
12912
13259
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
12913
13260
|
const version = versionForBinary(bin);
|
|
12914
13261
|
if (!version) {
|
|
@@ -12932,7 +13279,7 @@ which -a haive`
|
|
|
12932
13279
|
}
|
|
12933
13280
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
12934
13281
|
const findings = [];
|
|
12935
|
-
const rootPkg = await readJson(
|
|
13282
|
+
const rootPkg = await readJson(path46.join(root, "package.json"));
|
|
12936
13283
|
const workspacePackages = [
|
|
12937
13284
|
"packages/core/package.json",
|
|
12938
13285
|
"packages/embeddings/package.json",
|
|
@@ -12941,7 +13288,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
|
12941
13288
|
];
|
|
12942
13289
|
const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
|
|
12943
13290
|
rel,
|
|
12944
|
-
pkg: await readJson(
|
|
13291
|
+
pkg: await readJson(path46.join(root, rel))
|
|
12945
13292
|
})))).filter((item) => item.pkg);
|
|
12946
13293
|
const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
|
|
12947
13294
|
if (!isHaiveWorkspace) return findings;
|
|
@@ -13003,9 +13350,9 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
13003
13350
|
}
|
|
13004
13351
|
}
|
|
13005
13352
|
async function readJson(file) {
|
|
13006
|
-
if (!
|
|
13353
|
+
if (!existsSync66(file)) return null;
|
|
13007
13354
|
try {
|
|
13008
|
-
return JSON.parse(await
|
|
13355
|
+
return JSON.parse(await readFile21(file, "utf8"));
|
|
13009
13356
|
} catch {
|
|
13010
13357
|
return null;
|
|
13011
13358
|
}
|
|
@@ -13050,22 +13397,22 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
13050
13397
|
}
|
|
13051
13398
|
|
|
13052
13399
|
// src/commands/playback.ts
|
|
13053
|
-
import { existsSync as
|
|
13400
|
+
import { existsSync as existsSync67 } from "fs";
|
|
13054
13401
|
import "commander";
|
|
13055
13402
|
import {
|
|
13056
|
-
findProjectRoot as
|
|
13057
|
-
loadMemoriesFromDir as
|
|
13403
|
+
findProjectRoot as findProjectRoot45,
|
|
13404
|
+
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
13058
13405
|
parseSince as parseSince3,
|
|
13059
13406
|
readUsageEvents as readUsageEvents5,
|
|
13060
|
-
resolveHaivePaths as
|
|
13407
|
+
resolveHaivePaths as resolveHaivePaths41
|
|
13061
13408
|
} from "@hiveai/core";
|
|
13062
13409
|
var MS_PER_MINUTE = 6e4;
|
|
13063
13410
|
function registerPlayback(program2) {
|
|
13064
13411
|
program2.command("playback").description(
|
|
13065
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?'"
|
|
13066
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) => {
|
|
13067
|
-
const root =
|
|
13068
|
-
const paths =
|
|
13414
|
+
const root = findProjectRoot45(opts.dir);
|
|
13415
|
+
const paths = resolveHaivePaths41(root);
|
|
13069
13416
|
const events = await readUsageEvents5(paths);
|
|
13070
13417
|
if (events.length === 0) {
|
|
13071
13418
|
if (opts.json) {
|
|
@@ -13080,7 +13427,7 @@ function registerPlayback(program2) {
|
|
|
13080
13427
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
13081
13428
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
13082
13429
|
const sessions = bucketSessions(filtered, gapMs);
|
|
13083
|
-
const all =
|
|
13430
|
+
const all = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
|
|
13084
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);
|
|
13085
13432
|
const enriched = sessions.map((s, i) => {
|
|
13086
13433
|
const startMs = Date.parse(s.start);
|
|
@@ -13171,9 +13518,9 @@ import { spawn as spawn5 } from "child_process";
|
|
|
13171
13518
|
import "commander";
|
|
13172
13519
|
import {
|
|
13173
13520
|
antiPatternGateParams,
|
|
13174
|
-
findProjectRoot as
|
|
13521
|
+
findProjectRoot as findProjectRoot46,
|
|
13175
13522
|
loadConfig as loadConfig12,
|
|
13176
|
-
resolveHaivePaths as
|
|
13523
|
+
resolveHaivePaths as resolveHaivePaths42
|
|
13177
13524
|
} from "@hiveai/core";
|
|
13178
13525
|
function registerPrecommit(program2) {
|
|
13179
13526
|
program2.command("precommit").description(
|
|
@@ -13185,8 +13532,8 @@ function registerPrecommit(program2) {
|
|
|
13185
13532
|
"--no-anchored-blocks",
|
|
13186
13533
|
"do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
|
|
13187
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) => {
|
|
13188
|
-
const root =
|
|
13189
|
-
const paths =
|
|
13535
|
+
const root = findProjectRoot46(opts.dir);
|
|
13536
|
+
const paths = resolveHaivePaths42(root);
|
|
13190
13537
|
const ctx = { paths };
|
|
13191
13538
|
const config = await loadConfig12(paths);
|
|
13192
13539
|
const gate = config.enforcement?.antiPatternGate ?? "anchored";
|
|
@@ -13322,12 +13669,12 @@ function runCommand3(cmd, args, cwd) {
|
|
|
13322
13669
|
}
|
|
13323
13670
|
|
|
13324
13671
|
// src/commands/welcome.ts
|
|
13325
|
-
import { existsSync as
|
|
13672
|
+
import { existsSync as existsSync68 } from "fs";
|
|
13326
13673
|
import "commander";
|
|
13327
13674
|
import {
|
|
13328
|
-
findProjectRoot as
|
|
13329
|
-
loadMemoriesFromDir as
|
|
13330
|
-
resolveHaivePaths as
|
|
13675
|
+
findProjectRoot as findProjectRoot47,
|
|
13676
|
+
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
13677
|
+
resolveHaivePaths as resolveHaivePaths43
|
|
13331
13678
|
} from "@hiveai/core";
|
|
13332
13679
|
var TYPE_RANK = {
|
|
13333
13680
|
skill: 0,
|
|
@@ -13342,14 +13689,14 @@ function registerWelcome(program2) {
|
|
|
13342
13689
|
program2.command("welcome").description(
|
|
13343
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"
|
|
13344
13691
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13345
|
-
const root =
|
|
13346
|
-
const paths =
|
|
13347
|
-
if (!
|
|
13692
|
+
const root = findProjectRoot47(opts.dir);
|
|
13693
|
+
const paths = resolveHaivePaths43(root);
|
|
13694
|
+
if (!existsSync68(paths.memoriesDir)) {
|
|
13348
13695
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
13349
13696
|
process.exitCode = 1;
|
|
13350
13697
|
return;
|
|
13351
13698
|
}
|
|
13352
|
-
const all = await
|
|
13699
|
+
const all = await loadMemoriesFromDir36(paths.memoriesDir);
|
|
13353
13700
|
const team = all.filter(
|
|
13354
13701
|
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
13355
13702
|
);
|
|
@@ -13403,27 +13750,27 @@ function registerMemorySuggestTopic(memory2) {
|
|
|
13403
13750
|
}
|
|
13404
13751
|
|
|
13405
13752
|
// src/commands/resolve-project.ts
|
|
13406
|
-
import
|
|
13753
|
+
import path47 from "path";
|
|
13407
13754
|
import "commander";
|
|
13408
13755
|
import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
|
|
13409
13756
|
function registerResolveProject(program2) {
|
|
13410
13757
|
program2.command("resolve-project").description(
|
|
13411
13758
|
"Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
13412
13759
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
13413
|
-
const info = resolveProjectInfo2({ cwd:
|
|
13760
|
+
const info = resolveProjectInfo2({ cwd: path47.resolve(opts.dir) });
|
|
13414
13761
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
13415
13762
|
});
|
|
13416
13763
|
}
|
|
13417
13764
|
|
|
13418
13765
|
// src/commands/runtime-journal.ts
|
|
13419
|
-
import { existsSync as
|
|
13420
|
-
import
|
|
13766
|
+
import { existsSync as existsSync69 } from "fs";
|
|
13767
|
+
import path48 from "path";
|
|
13421
13768
|
import "commander";
|
|
13422
13769
|
import {
|
|
13423
13770
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
13424
|
-
findProjectRoot as
|
|
13771
|
+
findProjectRoot as findProjectRoot48,
|
|
13425
13772
|
readRuntimeJournalTail as readRuntimeJournalTail2,
|
|
13426
|
-
resolveHaivePaths as
|
|
13773
|
+
resolveHaivePaths as resolveHaivePaths44
|
|
13427
13774
|
} from "@hiveai/core";
|
|
13428
13775
|
function registerRuntime(program2) {
|
|
13429
13776
|
const runtime = program2.command("runtime").description(
|
|
@@ -13431,18 +13778,18 @@ function registerRuntime(program2) {
|
|
|
13431
13778
|
);
|
|
13432
13779
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
13433
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) => {
|
|
13434
|
-
const root =
|
|
13435
|
-
const paths =
|
|
13781
|
+
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13782
|
+
const paths = resolveHaivePaths44(findProjectRoot48(root));
|
|
13436
13783
|
const raw = opts.kind ?? "note";
|
|
13437
13784
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
13438
13785
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
13439
|
-
ui.success(`Appended to ${
|
|
13786
|
+
ui.success(`Appended to ${path48.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
|
|
13440
13787
|
});
|
|
13441
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) => {
|
|
13442
|
-
const root =
|
|
13443
|
-
const paths =
|
|
13789
|
+
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13790
|
+
const paths = resolveHaivePaths44(findProjectRoot48(root));
|
|
13444
13791
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13445
|
-
if (!
|
|
13792
|
+
if (!existsSync69(paths.haiveDir)) {
|
|
13446
13793
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
13447
13794
|
process.exitCode = 1;
|
|
13448
13795
|
return;
|
|
@@ -13457,13 +13804,13 @@ function registerRuntime(program2) {
|
|
|
13457
13804
|
}
|
|
13458
13805
|
|
|
13459
13806
|
// src/commands/memory-timeline.ts
|
|
13460
|
-
import { existsSync as
|
|
13461
|
-
import
|
|
13807
|
+
import { existsSync as existsSync70 } from "fs";
|
|
13808
|
+
import path49 from "path";
|
|
13462
13809
|
import "commander";
|
|
13463
13810
|
import {
|
|
13464
13811
|
collectTimelineEntries as collectTimelineEntries2,
|
|
13465
|
-
findProjectRoot as
|
|
13466
|
-
resolveHaivePaths as
|
|
13812
|
+
findProjectRoot as findProjectRoot49,
|
|
13813
|
+
resolveHaivePaths as resolveHaivePaths45
|
|
13467
13814
|
} from "@hiveai/core";
|
|
13468
13815
|
function registerMemoryTimeline(memory2) {
|
|
13469
13816
|
memory2.command("timeline").description(
|
|
@@ -13474,15 +13821,15 @@ function registerMemoryTimeline(memory2) {
|
|
|
13474
13821
|
process.exitCode = 1;
|
|
13475
13822
|
return;
|
|
13476
13823
|
}
|
|
13477
|
-
const root =
|
|
13478
|
-
const paths =
|
|
13479
|
-
if (!
|
|
13824
|
+
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
13825
|
+
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13826
|
+
if (!existsSync70(paths.memoriesDir)) {
|
|
13480
13827
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13481
13828
|
process.exitCode = 1;
|
|
13482
13829
|
return;
|
|
13483
13830
|
}
|
|
13484
13831
|
const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13485
|
-
const all = await
|
|
13832
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
13486
13833
|
const { entries, notice } = collectTimelineEntries2(all, {
|
|
13487
13834
|
memoryId: opts.id,
|
|
13488
13835
|
topic: opts.topic,
|
|
@@ -13494,14 +13841,14 @@ function registerMemoryTimeline(memory2) {
|
|
|
13494
13841
|
}
|
|
13495
13842
|
|
|
13496
13843
|
// src/commands/memory-conflict-candidates.ts
|
|
13497
|
-
import { existsSync as
|
|
13498
|
-
import
|
|
13844
|
+
import { existsSync as existsSync71 } from "fs";
|
|
13845
|
+
import path50 from "path";
|
|
13499
13846
|
import "commander";
|
|
13500
13847
|
import {
|
|
13501
13848
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
13502
13849
|
findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
|
|
13503
|
-
findProjectRoot as
|
|
13504
|
-
resolveHaivePaths as
|
|
13850
|
+
findProjectRoot as findProjectRoot50,
|
|
13851
|
+
resolveHaivePaths as resolveHaivePaths46
|
|
13505
13852
|
} from "@hiveai/core";
|
|
13506
13853
|
function parseTypes(csv) {
|
|
13507
13854
|
const allowed = ["decision", "architecture", "convention", "gotcha"];
|
|
@@ -13517,9 +13864,9 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13517
13864
|
"decision,architecture,convention,gotcha (lexical scan)",
|
|
13518
13865
|
"decision,architecture"
|
|
13519
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) => {
|
|
13520
|
-
const root =
|
|
13521
|
-
const paths =
|
|
13522
|
-
if (!
|
|
13867
|
+
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
13868
|
+
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
13869
|
+
if (!existsSync71(paths.memoriesDir)) {
|
|
13523
13870
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13524
13871
|
process.exitCode = 1;
|
|
13525
13872
|
return;
|
|
@@ -13529,7 +13876,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13529
13876
|
const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
|
|
13530
13877
|
const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
|
|
13531
13878
|
const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
|
|
13532
|
-
const all = await
|
|
13879
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
13533
13880
|
const lexical = findLexicalConflictPairs2(all, {
|
|
13534
13881
|
sinceDays,
|
|
13535
13882
|
types: parseTypes(opts.types),
|
|
@@ -13555,21 +13902,21 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13555
13902
|
|
|
13556
13903
|
// src/commands/enforce.ts
|
|
13557
13904
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
13558
|
-
import { existsSync as
|
|
13559
|
-
import { chmod as chmod2, mkdir as mkdir19, readFile as
|
|
13560
|
-
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";
|
|
13561
13908
|
import "commander";
|
|
13562
13909
|
import {
|
|
13563
13910
|
antiPatternGateParams as antiPatternGateParams2,
|
|
13564
|
-
findProjectRoot as
|
|
13911
|
+
findProjectRoot as findProjectRoot51,
|
|
13565
13912
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
13566
13913
|
isFreshIsoDate,
|
|
13567
13914
|
loadConfig as loadConfig13,
|
|
13568
|
-
loadMemoriesFromDir as
|
|
13915
|
+
loadMemoriesFromDir as loadMemoriesFromDir37,
|
|
13569
13916
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
13570
13917
|
readRecentBriefingMarker,
|
|
13571
13918
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
13572
|
-
resolveHaivePaths as
|
|
13919
|
+
resolveHaivePaths as resolveHaivePaths47,
|
|
13573
13920
|
saveConfig as saveConfig4,
|
|
13574
13921
|
SESSION_RECAP_TTL_MS,
|
|
13575
13922
|
verifyAnchor as verifyAnchor4,
|
|
@@ -13582,8 +13929,8 @@ function registerEnforce(program2) {
|
|
|
13582
13929
|
"Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
|
|
13583
13930
|
);
|
|
13584
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) => {
|
|
13585
|
-
const root =
|
|
13586
|
-
const paths =
|
|
13932
|
+
const root = findProjectRoot51(opts.dir);
|
|
13933
|
+
const paths = resolveHaivePaths47(root);
|
|
13587
13934
|
await mkdir19(paths.haiveDir, { recursive: true });
|
|
13588
13935
|
const current = await loadConfig13(paths);
|
|
13589
13936
|
await saveConfig4(paths, {
|
|
@@ -13608,7 +13955,7 @@ function registerEnforce(program2) {
|
|
|
13608
13955
|
if (opts.claude !== false) {
|
|
13609
13956
|
try {
|
|
13610
13957
|
const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
|
|
13611
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
13958
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path51.relative(root, result.settingsPath)})`);
|
|
13612
13959
|
} catch (err) {
|
|
13613
13960
|
ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
|
|
13614
13961
|
}
|
|
@@ -13627,21 +13974,21 @@ function registerEnforce(program2) {
|
|
|
13627
13974
|
if (report.should_block) process.exit(2);
|
|
13628
13975
|
});
|
|
13629
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) => {
|
|
13630
|
-
const root =
|
|
13631
|
-
const paths =
|
|
13632
|
-
const cacheDir =
|
|
13633
|
-
if (
|
|
13634
|
-
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)`);
|
|
13635
13982
|
else {
|
|
13636
13983
|
const removed = await cleanupCacheDir(cacheDir);
|
|
13637
|
-
ui.success(`cleaned ${
|
|
13984
|
+
ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
13638
13985
|
}
|
|
13639
13986
|
}
|
|
13640
|
-
if (
|
|
13641
|
-
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)`);
|
|
13642
13989
|
else {
|
|
13643
13990
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
13644
|
-
ui.success(`cleaned ${
|
|
13991
|
+
ui.success(`cleaned ${path51.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
13645
13992
|
}
|
|
13646
13993
|
}
|
|
13647
13994
|
});
|
|
@@ -13661,8 +14008,8 @@ function registerEnforce(program2) {
|
|
|
13661
14008
|
const payload = await readHookPayload();
|
|
13662
14009
|
const root = resolveRoot(opts.dir, payload);
|
|
13663
14010
|
if (!root) return;
|
|
13664
|
-
const paths =
|
|
13665
|
-
if (!
|
|
14011
|
+
const paths = resolveHaivePaths47(root);
|
|
14012
|
+
if (!existsSync73(paths.haiveDir)) return;
|
|
13666
14013
|
await mkdir19(paths.runtimeDir, { recursive: true });
|
|
13667
14014
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
13668
14015
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
@@ -13724,8 +14071,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
13724
14071
|
const payload = await readHookPayload();
|
|
13725
14072
|
const root = resolveRoot(opts.dir, payload);
|
|
13726
14073
|
if (!root) return;
|
|
13727
|
-
const paths =
|
|
13728
|
-
if (!
|
|
14074
|
+
const paths = resolveHaivePaths47(root);
|
|
14075
|
+
if (!existsSync73(paths.haiveDir)) return;
|
|
13729
14076
|
if (!isWriteLikeTool(payload)) return;
|
|
13730
14077
|
const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
|
|
13731
14078
|
if (ok) {
|
|
@@ -13767,9 +14114,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
13767
14114
|
});
|
|
13768
14115
|
}
|
|
13769
14116
|
async function buildFinishReport(dir) {
|
|
13770
|
-
const root =
|
|
13771
|
-
const paths =
|
|
13772
|
-
const initialized =
|
|
14117
|
+
const root = findProjectRoot51(dir);
|
|
14118
|
+
const paths = resolveHaivePaths47(root);
|
|
14119
|
+
const initialized = existsSync73(paths.haiveDir);
|
|
13773
14120
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
13774
14121
|
const mode = config.enforcement?.mode ?? "strict";
|
|
13775
14122
|
const findings = [];
|
|
@@ -13963,9 +14310,9 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
13963
14310
|
});
|
|
13964
14311
|
}
|
|
13965
14312
|
async function runWithEnforcement(command, args, opts) {
|
|
13966
|
-
const root =
|
|
13967
|
-
const paths =
|
|
13968
|
-
if (!
|
|
14313
|
+
const root = findProjectRoot51(opts.dir);
|
|
14314
|
+
const paths = resolveHaivePaths47(root);
|
|
14315
|
+
if (!existsSync73(paths.haiveDir)) {
|
|
13969
14316
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
13970
14317
|
process.exit(1);
|
|
13971
14318
|
}
|
|
@@ -13984,7 +14331,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
13984
14331
|
process.exit(2);
|
|
13985
14332
|
}
|
|
13986
14333
|
ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
|
|
13987
|
-
ui.info(`Briefing written to ${
|
|
14334
|
+
ui.info(`Briefing written to ${path51.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
13988
14335
|
const child = spawn6(command, args, {
|
|
13989
14336
|
cwd: root,
|
|
13990
14337
|
stdio: "inherit",
|
|
@@ -14034,9 +14381,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14034
14381
|
source: "haive-run",
|
|
14035
14382
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
14036
14383
|
});
|
|
14037
|
-
const dir =
|
|
14384
|
+
const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
|
|
14038
14385
|
await mkdir19(dir, { recursive: true });
|
|
14039
|
-
const file =
|
|
14386
|
+
const file = path51.join(dir, `${sessionId}.md`);
|
|
14040
14387
|
const parts = [
|
|
14041
14388
|
"# hAIve Briefing",
|
|
14042
14389
|
"",
|
|
@@ -14054,13 +14401,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14054
14401
|
if (briefing.setup_warnings.length > 0) {
|
|
14055
14402
|
parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
|
|
14056
14403
|
}
|
|
14057
|
-
await
|
|
14404
|
+
await writeFile34(file, parts.join("\n") + "\n", "utf8");
|
|
14058
14405
|
return file;
|
|
14059
14406
|
}
|
|
14060
14407
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
14061
|
-
const root =
|
|
14062
|
-
const paths =
|
|
14063
|
-
const initialized =
|
|
14408
|
+
const root = findProjectRoot51(dir);
|
|
14409
|
+
const paths = resolveHaivePaths47(root);
|
|
14410
|
+
const initialized = existsSync73(paths.haiveDir);
|
|
14064
14411
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14065
14412
|
if (initialized) await applyLightweightRepairs(root, paths);
|
|
14066
14413
|
const mode = config.enforcement?.mode ?? "strict";
|
|
@@ -14091,7 +14438,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14091
14438
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14092
14439
|
});
|
|
14093
14440
|
}
|
|
14094
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
14441
|
+
findings.push(...await inspectIntegrationVersions(root, "0.11.0"));
|
|
14095
14442
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14096
14443
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14097
14444
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14161,8 +14508,8 @@ function withCategories(report) {
|
|
|
14161
14508
|
};
|
|
14162
14509
|
}
|
|
14163
14510
|
async function hasRecentSessionRecap(paths) {
|
|
14164
|
-
if (!
|
|
14165
|
-
const all = await
|
|
14511
|
+
if (!existsSync73(paths.memoriesDir)) return false;
|
|
14512
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14166
14513
|
return all.some(({ memory: memory2 }) => {
|
|
14167
14514
|
const fm = memory2.frontmatter;
|
|
14168
14515
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -14170,8 +14517,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
14170
14517
|
});
|
|
14171
14518
|
}
|
|
14172
14519
|
async function verifyMemoryPolicy(paths, config) {
|
|
14173
|
-
if (!
|
|
14174
|
-
const all = await
|
|
14520
|
+
if (!existsSync73(paths.memoriesDir)) return [];
|
|
14521
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14175
14522
|
const findings = [];
|
|
14176
14523
|
const staleImportant = [];
|
|
14177
14524
|
let verified = 0;
|
|
@@ -14208,12 +14555,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
14208
14555
|
return findings;
|
|
14209
14556
|
}
|
|
14210
14557
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
14211
|
-
if (!
|
|
14558
|
+
if (!existsSync73(paths.memoriesDir)) return [];
|
|
14212
14559
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
14213
14560
|
if (changedFiles.length === 0) {
|
|
14214
14561
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
14215
14562
|
}
|
|
14216
|
-
const all = await
|
|
14563
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14217
14564
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
14218
14565
|
const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
14219
14566
|
const fm = memory2.frontmatter;
|
|
@@ -14313,16 +14660,16 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14313
14660
|
for (const entry of entries) {
|
|
14314
14661
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
14315
14662
|
if (entry.name === "enforcement") {
|
|
14316
|
-
removed += await cleanupEnforcementDir(
|
|
14663
|
+
removed += await cleanupEnforcementDir(path51.join(runtimeDir, entry.name));
|
|
14317
14664
|
continue;
|
|
14318
14665
|
}
|
|
14319
|
-
await rm3(
|
|
14666
|
+
await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
14320
14667
|
removed++;
|
|
14321
14668
|
}
|
|
14322
|
-
await
|
|
14323
|
-
if (!
|
|
14324
|
-
await
|
|
14325
|
-
|
|
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"),
|
|
14326
14673
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
14327
14674
|
"utf8"
|
|
14328
14675
|
);
|
|
@@ -14335,10 +14682,10 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
14335
14682
|
const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
14336
14683
|
for (const entry of entries) {
|
|
14337
14684
|
if (entry.name === ".gitignore") continue;
|
|
14338
|
-
await rm3(
|
|
14685
|
+
await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
14339
14686
|
removed++;
|
|
14340
14687
|
}
|
|
14341
|
-
await
|
|
14688
|
+
await writeFile34(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
14342
14689
|
return removed;
|
|
14343
14690
|
}
|
|
14344
14691
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -14346,7 +14693,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
14346
14693
|
const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
14347
14694
|
for (const entry of entries) {
|
|
14348
14695
|
if (entry.name === "briefings") continue;
|
|
14349
|
-
await rm3(
|
|
14696
|
+
await rm3(path51.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
14350
14697
|
removed++;
|
|
14351
14698
|
}
|
|
14352
14699
|
return removed;
|
|
@@ -14362,9 +14709,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
14362
14709
|
];
|
|
14363
14710
|
const findings = [];
|
|
14364
14711
|
for (const rel of files) {
|
|
14365
|
-
const file =
|
|
14366
|
-
if (!
|
|
14367
|
-
const text = await
|
|
14712
|
+
const file = path51.join(root, rel);
|
|
14713
|
+
if (!existsSync73(file)) continue;
|
|
14714
|
+
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14368
14715
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
14369
14716
|
const version = versionForBinary2(bin);
|
|
14370
14717
|
if (!version) {
|
|
@@ -14473,9 +14820,9 @@ async function resolveCiDiffRange(root) {
|
|
|
14473
14820
|
}
|
|
14474
14821
|
async function resolveGithubEventRange(root) {
|
|
14475
14822
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
14476
|
-
if (!eventPath || !
|
|
14823
|
+
if (!eventPath || !existsSync73(eventPath)) return null;
|
|
14477
14824
|
try {
|
|
14478
|
-
const event = JSON.parse(await
|
|
14825
|
+
const event = JSON.parse(await readFile23(eventPath, "utf8"));
|
|
14479
14826
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
14480
14827
|
const prHead = cleanGitSha(event.pull_request?.head?.sha ?? event.after ?? process.env.GITHUB_SHA) ?? "HEAD";
|
|
14481
14828
|
if (prBase && await gitCommitExists(root, prBase)) return `${prBase}...${prHead}`;
|
|
@@ -14592,7 +14939,7 @@ async function inspectReleaseVersionState(root, upstream) {
|
|
|
14592
14939
|
}
|
|
14593
14940
|
async function readPackageVersion(root, relPath) {
|
|
14594
14941
|
try {
|
|
14595
|
-
const data = JSON.parse(await
|
|
14942
|
+
const data = JSON.parse(await readFile23(path51.join(root, relPath), "utf8"));
|
|
14596
14943
|
return typeof data.version === "string" ? data.version : void 0;
|
|
14597
14944
|
} catch {
|
|
14598
14945
|
return void 0;
|
|
@@ -14653,8 +15000,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
14653
15000
|
};
|
|
14654
15001
|
}
|
|
14655
15002
|
async function installGitEnforcement(root) {
|
|
14656
|
-
const hooksDir =
|
|
14657
|
-
if (!
|
|
15003
|
+
const hooksDir = path51.join(root, ".git", "hooks");
|
|
15004
|
+
if (!existsSync73(path51.join(root, ".git"))) {
|
|
14658
15005
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
14659
15006
|
return;
|
|
14660
15007
|
}
|
|
@@ -14676,31 +15023,31 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
14676
15023
|
}
|
|
14677
15024
|
];
|
|
14678
15025
|
for (const hook of hooks) {
|
|
14679
|
-
const file =
|
|
14680
|
-
if (
|
|
14681
|
-
const current = await
|
|
15026
|
+
const file = path51.join(hooksDir, hook.name);
|
|
15027
|
+
if (existsSync73(file)) {
|
|
15028
|
+
const current = await readFile23(file, "utf8").catch(() => "");
|
|
14682
15029
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
14683
|
-
await
|
|
15030
|
+
await writeFile34(file, hook.body, "utf8");
|
|
14684
15031
|
} else {
|
|
14685
|
-
await
|
|
15032
|
+
await writeFile34(file, `${current.trimEnd()}
|
|
14686
15033
|
|
|
14687
15034
|
${hook.body}`, "utf8");
|
|
14688
15035
|
}
|
|
14689
15036
|
} else {
|
|
14690
|
-
await
|
|
15037
|
+
await writeFile34(file, hook.body, "utf8");
|
|
14691
15038
|
}
|
|
14692
15039
|
await chmod2(file, 493);
|
|
14693
15040
|
}
|
|
14694
15041
|
ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
|
|
14695
15042
|
}
|
|
14696
15043
|
async function installCiEnforcement(root) {
|
|
14697
|
-
const workflowPath =
|
|
14698
|
-
await mkdir19(
|
|
14699
|
-
if (
|
|
15044
|
+
const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
15045
|
+
await mkdir19(path51.dirname(workflowPath), { recursive: true });
|
|
15046
|
+
if (existsSync73(workflowPath)) {
|
|
14700
15047
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
14701
15048
|
return;
|
|
14702
15049
|
}
|
|
14703
|
-
await
|
|
15050
|
+
await writeFile34(workflowPath, `name: haive-enforcement
|
|
14704
15051
|
|
|
14705
15052
|
on:
|
|
14706
15053
|
pull_request:
|
|
@@ -14727,7 +15074,7 @@ jobs:
|
|
|
14727
15074
|
HAIVE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
14728
15075
|
run: haive enforce ci
|
|
14729
15076
|
`, "utf8");
|
|
14730
|
-
ui.success(`Created ${
|
|
15077
|
+
ui.success(`Created ${path51.relative(root, workflowPath)}`);
|
|
14731
15078
|
}
|
|
14732
15079
|
function printReport(report, json, explain = false) {
|
|
14733
15080
|
if (json) {
|
|
@@ -14787,7 +15134,7 @@ async function readHookPayload() {
|
|
|
14787
15134
|
}
|
|
14788
15135
|
function resolveRoot(dir, payload) {
|
|
14789
15136
|
try {
|
|
14790
|
-
return
|
|
15137
|
+
return findProjectRoot51(dir ?? payload.cwd);
|
|
14791
15138
|
} catch {
|
|
14792
15139
|
return null;
|
|
14793
15140
|
}
|
|
@@ -14824,15 +15171,15 @@ function extractToolPaths(payload, root) {
|
|
|
14824
15171
|
}
|
|
14825
15172
|
function normalizeToolPath(file, root) {
|
|
14826
15173
|
const normalized = file.replace(/\\/g, "/");
|
|
14827
|
-
if (!
|
|
14828
|
-
return
|
|
15174
|
+
if (!path51.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
|
|
15175
|
+
return path51.relative(root, normalized).replace(/\\/g, "/");
|
|
14829
15176
|
}
|
|
14830
15177
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
14831
|
-
if (!
|
|
15178
|
+
if (!existsSync73(paths.memoriesDir)) return [];
|
|
14832
15179
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
14833
15180
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
14834
15181
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
14835
|
-
const all = await
|
|
15182
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14836
15183
|
return all.filter(({ memory: memory2 }) => {
|
|
14837
15184
|
const fm = memory2.frontmatter;
|
|
14838
15185
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -14902,16 +15249,16 @@ function registerRun(program2) {
|
|
|
14902
15249
|
|
|
14903
15250
|
// src/commands/sensors.ts
|
|
14904
15251
|
import { execFile as execFile2 } from "child_process";
|
|
14905
|
-
import { existsSync as
|
|
14906
|
-
import { chmod as chmod3, mkdir as mkdir20, readFile as
|
|
14907
|
-
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";
|
|
14908
15255
|
import { promisify as promisify2 } from "util";
|
|
14909
15256
|
import "commander";
|
|
14910
15257
|
import {
|
|
14911
|
-
findProjectRoot as
|
|
15258
|
+
findProjectRoot as findProjectRoot52,
|
|
14912
15259
|
isRetiredMemory as isRetiredMemory3,
|
|
14913
|
-
loadMemoriesFromDir as
|
|
14914
|
-
resolveHaivePaths as
|
|
15260
|
+
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
15261
|
+
resolveHaivePaths as resolveHaivePaths48,
|
|
14915
15262
|
runSensors as runSensors2,
|
|
14916
15263
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
14917
15264
|
serializeMemory as serializeMemory26
|
|
@@ -14920,8 +15267,8 @@ var exec2 = promisify2(execFile2);
|
|
|
14920
15267
|
function registerSensors(program2) {
|
|
14921
15268
|
const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
|
|
14922
15269
|
sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
14923
|
-
const root =
|
|
14924
|
-
const paths =
|
|
15270
|
+
const root = findProjectRoot52(opts.dir);
|
|
15271
|
+
const paths = resolveHaivePaths48(root);
|
|
14925
15272
|
const rows = await sensorRows(paths);
|
|
14926
15273
|
if (opts.json) {
|
|
14927
15274
|
console.log(JSON.stringify(rows, null, 2));
|
|
@@ -14941,10 +15288,10 @@ function registerSensors(program2) {
|
|
|
14941
15288
|
}
|
|
14942
15289
|
});
|
|
14943
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) => {
|
|
14944
|
-
const root =
|
|
14945
|
-
const paths =
|
|
15291
|
+
const root = findProjectRoot52(opts.dir);
|
|
15292
|
+
const paths = resolveHaivePaths48(root);
|
|
14946
15293
|
const memories = await runnableSensorMemories(paths);
|
|
14947
|
-
const diff = opts.diffFile ? await
|
|
15294
|
+
const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
14948
15295
|
const targets = sensorTargetsFromDiff2(diff);
|
|
14949
15296
|
const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
|
|
14950
15297
|
const output = {
|
|
@@ -14983,9 +15330,9 @@ function registerSensors(program2) {
|
|
|
14983
15330
|
process.exitCode = 1;
|
|
14984
15331
|
return;
|
|
14985
15332
|
}
|
|
14986
|
-
const root =
|
|
14987
|
-
const paths =
|
|
14988
|
-
const loaded =
|
|
15333
|
+
const root = findProjectRoot52(opts.dir);
|
|
15334
|
+
const paths = resolveHaivePaths48(root);
|
|
15335
|
+
const loaded = existsSync74(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
|
|
14989
15336
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
14990
15337
|
if (!found) {
|
|
14991
15338
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -15005,7 +15352,7 @@ function registerSensors(program2) {
|
|
|
15005
15352
|
},
|
|
15006
15353
|
body: found.memory.body
|
|
15007
15354
|
};
|
|
15008
|
-
await
|
|
15355
|
+
await writeFile35(found.filePath, serializeMemory26(next), "utf8");
|
|
15009
15356
|
ui.success(`Updated ${id}: sensor severity=${severity}`);
|
|
15010
15357
|
if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
|
|
15011
15358
|
ui.info(`message=${sensor.message}`);
|
|
@@ -15017,16 +15364,16 @@ function registerSensors(program2) {
|
|
|
15017
15364
|
process.exitCode = 1;
|
|
15018
15365
|
return;
|
|
15019
15366
|
}
|
|
15020
|
-
const root =
|
|
15021
|
-
const paths =
|
|
15367
|
+
const root = findProjectRoot52(opts.dir);
|
|
15368
|
+
const paths = resolveHaivePaths48(root);
|
|
15022
15369
|
const rows = await sensorRows(paths);
|
|
15023
|
-
const outDir =
|
|
15370
|
+
const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
|
|
15024
15371
|
await mkdir20(outDir, { recursive: true });
|
|
15025
|
-
const outPath =
|
|
15372
|
+
const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
15026
15373
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
15027
|
-
await
|
|
15374
|
+
await writeFile35(outPath, content, "utf8");
|
|
15028
15375
|
if (format === "grep") await chmod3(outPath, 493);
|
|
15029
|
-
ui.success(`Exported ${rows.length} sensor(s): ${
|
|
15376
|
+
ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
|
|
15030
15377
|
});
|
|
15031
15378
|
}
|
|
15032
15379
|
async function sensorRows(paths) {
|
|
@@ -15047,8 +15394,8 @@ async function sensorRows(paths) {
|
|
|
15047
15394
|
});
|
|
15048
15395
|
}
|
|
15049
15396
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
15050
|
-
if (!
|
|
15051
|
-
const loaded = await
|
|
15397
|
+
if (!existsSync74(paths.memoriesDir)) return [];
|
|
15398
|
+
const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
15052
15399
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
15053
15400
|
const sensor = memory2.frontmatter.sensor;
|
|
15054
15401
|
if (!sensor) return false;
|
|
@@ -15089,8 +15436,8 @@ function shellQuote(value) {
|
|
|
15089
15436
|
}
|
|
15090
15437
|
|
|
15091
15438
|
// src/index.ts
|
|
15092
|
-
var program = new
|
|
15093
|
-
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");
|
|
15094
15441
|
registerInit(program);
|
|
15095
15442
|
registerWelcome(program);
|
|
15096
15443
|
registerResolveProject(program);
|
|
@@ -15114,6 +15461,7 @@ registerMemoryQuery(memory);
|
|
|
15114
15461
|
registerMemoryPromote(memory);
|
|
15115
15462
|
registerMemoryVerify(memory);
|
|
15116
15463
|
registerMemoryStats(memory);
|
|
15464
|
+
registerMemoryImpact(memory);
|
|
15117
15465
|
registerMemoryReject(memory);
|
|
15118
15466
|
registerMemoryAutoPromote(memory);
|
|
15119
15467
|
registerMemoryForFiles(memory);
|
|
@@ -15144,6 +15492,7 @@ registerHub(program);
|
|
|
15144
15492
|
registerStats(program);
|
|
15145
15493
|
registerBench(program);
|
|
15146
15494
|
registerBenchmark(program);
|
|
15495
|
+
registerEval(program);
|
|
15147
15496
|
registerDoctor(program);
|
|
15148
15497
|
registerPlayback(program);
|
|
15149
15498
|
registerPrecommit(program);
|