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