@hiveai/cli 0.12.4 → 0.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.js +1137 -543
- 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 Command58 } 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(([path55, changes]) => ({ path: path55, 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)));
|
|
@@ -2183,7 +2183,11 @@ causes a cryptic runtime error. Check for browser globals (window, document, loc
|
|
|
2183
2183
|
body: `Only environment variables prefixed with NEXT_PUBLIC_ are exposed to the browser.
|
|
2184
2184
|
|
|
2185
2185
|
Never put secrets in NEXT_PUBLIC_* variables \u2014 they are bundled into the client JS.
|
|
2186
|
-
Variables without the prefix are server-only and safe for API keys, database URLs, etc
|
|
2186
|
+
Variables without the prefix are server-only and safe for API keys, database URLs, etc.`,
|
|
2187
|
+
sensor: {
|
|
2188
|
+
pattern: "NEXT_PUBLIC_[A-Z0-9_]*(SECRET|PRIVATE|TOKEN|PASSWORD|API_?KEY)",
|
|
2189
|
+
message: "A NEXT_PUBLIC_ env var with a secret-looking name is bundled into client JS \u2014 move the secret to a server-only (non-NEXT_PUBLIC_) variable."
|
|
2190
|
+
}
|
|
2187
2191
|
},
|
|
2188
2192
|
{
|
|
2189
2193
|
slug: "nextjs-fetch-cache-defaults",
|
|
@@ -2251,7 +2255,11 @@ and event listeners that accumulate across re-renders.`
|
|
|
2251
2255
|
|
|
2252
2256
|
Using index as key causes React to re-render wrong items on reorder/filter,
|
|
2253
2257
|
corrupts form state, and triggers avoidable DOM mutations.
|
|
2254
|
-
Use item.id or a stable hash \u2014 never Math.random()
|
|
2258
|
+
Use item.id or a stable hash \u2014 never Math.random().`,
|
|
2259
|
+
sensor: {
|
|
2260
|
+
pattern: "key=\\{\\s*index\\s*\\}",
|
|
2261
|
+
message: "Array index used as a React key \u2014 switch to a stable unique id to avoid state corruption on reorder."
|
|
2262
|
+
}
|
|
2255
2263
|
},
|
|
2256
2264
|
{
|
|
2257
2265
|
slug: "react-avoid-use-effect-for-derived-state",
|
|
@@ -2682,6 +2690,151 @@ new ApolloServer({
|
|
|
2682
2690
|
});
|
|
2683
2691
|
\`\`\``
|
|
2684
2692
|
}
|
|
2693
|
+
],
|
|
2694
|
+
fastapi: [
|
|
2695
|
+
{
|
|
2696
|
+
slug: "fastapi-validate-with-pydantic",
|
|
2697
|
+
type: "convention",
|
|
2698
|
+
tags: ["fastapi", "python", "validation"],
|
|
2699
|
+
body: `Declare request/response models with Pydantic \u2014 never read raw dict bodies.
|
|
2700
|
+
|
|
2701
|
+
\`\`\`py
|
|
2702
|
+
class CreateUser(BaseModel):
|
|
2703
|
+
email: EmailStr
|
|
2704
|
+
age: int = Field(ge=0)
|
|
2705
|
+
|
|
2706
|
+
@app.post("/users")
|
|
2707
|
+
def create(user: CreateUser): ...
|
|
2708
|
+
\`\`\`
|
|
2709
|
+
|
|
2710
|
+
Pydantic validates and coerces at the boundary; raw \`dict\` bodies bypass validation and typing.`
|
|
2711
|
+
},
|
|
2712
|
+
{
|
|
2713
|
+
slug: "fastapi-no-blocking-io-in-async",
|
|
2714
|
+
type: "gotcha",
|
|
2715
|
+
tags: ["fastapi", "python", "async", "performance"],
|
|
2716
|
+
body: `Never call blocking I/O (requests, time.sleep, sync DB drivers) inside an \`async def\` route.
|
|
2717
|
+
|
|
2718
|
+
A blocking call inside the event loop freezes the whole worker for every concurrent request.
|
|
2719
|
+
Use an async client (httpx.AsyncClient, asyncpg) or run blocking work in a threadpool
|
|
2720
|
+
(\`await run_in_threadpool(...)\` / \`def\` route, which FastAPI runs in a threadpool).`
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
slug: "fastapi-uvicorn-reload-not-in-prod",
|
|
2724
|
+
type: "gotcha",
|
|
2725
|
+
tags: ["fastapi", "python", "deployment"],
|
|
2726
|
+
body: `\`uvicorn.run(..., reload=True)\` is a dev-only feature \u2014 never ship it to production.
|
|
2727
|
+
|
|
2728
|
+
Reload spawns a file-watcher process and disables multi-worker scaling.
|
|
2729
|
+
In production run \`uvicorn app:app --workers N\` (no reload) behind a process manager.`,
|
|
2730
|
+
sensor: {
|
|
2731
|
+
pattern: "uvicorn\\.run\\([^)]*reload\\s*=\\s*True",
|
|
2732
|
+
message: "uvicorn reload=True is dev-only \u2014 remove it from production entrypoints."
|
|
2733
|
+
}
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
slug: "fastapi-no-bare-except",
|
|
2737
|
+
type: "convention",
|
|
2738
|
+
tags: ["fastapi", "python", "error-handling"],
|
|
2739
|
+
body: `Never use a bare \`except:\` \u2014 it swallows KeyboardInterrupt/SystemExit and hides real bugs.
|
|
2740
|
+
|
|
2741
|
+
Catch the specific exception you expect, or \`except Exception as e:\` at most, and log it.`,
|
|
2742
|
+
sensor: {
|
|
2743
|
+
pattern: "except\\s*:",
|
|
2744
|
+
message: "Bare `except:` swallows everything (incl. KeyboardInterrupt) \u2014 catch a specific exception type."
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
],
|
|
2748
|
+
django: [
|
|
2749
|
+
{
|
|
2750
|
+
slug: "django-debug-false-in-prod",
|
|
2751
|
+
type: "gotcha",
|
|
2752
|
+
tags: ["django", "python", "security", "deployment"],
|
|
2753
|
+
body: `\`DEBUG = True\` in production leaks stack traces, settings, and SQL to any visitor.
|
|
2754
|
+
|
|
2755
|
+
Drive it from the environment and default to safe:
|
|
2756
|
+
|
|
2757
|
+
\`\`\`py
|
|
2758
|
+
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
|
|
2759
|
+
\`\`\``,
|
|
2760
|
+
sensor: {
|
|
2761
|
+
pattern: "DEBUG\\s*=\\s*True",
|
|
2762
|
+
message: "DEBUG = True leaks internals in production \u2014 read it from the environment and default to False."
|
|
2763
|
+
}
|
|
2764
|
+
},
|
|
2765
|
+
{
|
|
2766
|
+
slug: "django-secret-key-from-env",
|
|
2767
|
+
type: "gotcha",
|
|
2768
|
+
tags: ["django", "python", "security"],
|
|
2769
|
+
body: `Never hardcode SECRET_KEY in settings \u2014 load it from the environment.
|
|
2770
|
+
|
|
2771
|
+
A committed SECRET_KEY lets anyone forge sessions and signed tokens.
|
|
2772
|
+
|
|
2773
|
+
\`\`\`py
|
|
2774
|
+
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
|
|
2775
|
+
\`\`\``,
|
|
2776
|
+
sensor: {
|
|
2777
|
+
pattern: `SECRET_KEY\\s*=\\s*["'][^"']+["']`,
|
|
2778
|
+
message: "Hardcoded SECRET_KEY \u2014 load it from os.environ instead of committing a literal."
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
slug: "django-select-related-n-plus-one",
|
|
2783
|
+
type: "gotcha",
|
|
2784
|
+
tags: ["django", "python", "orm", "performance"],
|
|
2785
|
+
body: `Accessing a ForeignKey in a loop triggers one query per row (N+1).
|
|
2786
|
+
|
|
2787
|
+
Use \`select_related\` (FK / one-to-one, SQL JOIN) and \`prefetch_related\` (M2M / reverse FK):
|
|
2788
|
+
|
|
2789
|
+
\`\`\`py
|
|
2790
|
+
for order in Order.objects.select_related("customer").all():
|
|
2791
|
+
order.customer.name # no extra query
|
|
2792
|
+
\`\`\``
|
|
2793
|
+
}
|
|
2794
|
+
],
|
|
2795
|
+
go: [
|
|
2796
|
+
{
|
|
2797
|
+
slug: "go-check-every-error",
|
|
2798
|
+
type: "convention",
|
|
2799
|
+
tags: ["go", "error-handling"],
|
|
2800
|
+
body: `Check every returned error \u2014 never discard it with \`_\`.
|
|
2801
|
+
|
|
2802
|
+
\`\`\`go
|
|
2803
|
+
// \u274C silently ignores failure
|
|
2804
|
+
val, _ := doThing()
|
|
2805
|
+
|
|
2806
|
+
// \u2705
|
|
2807
|
+
val, err := doThing()
|
|
2808
|
+
if err != nil {
|
|
2809
|
+
return fmt.Errorf("doThing: %w", err)
|
|
2810
|
+
}
|
|
2811
|
+
\`\`\`
|
|
2812
|
+
Wrap with \`%w\` to preserve the chain for errors.Is/As.`
|
|
2813
|
+
},
|
|
2814
|
+
{
|
|
2815
|
+
slug: "go-defer-close-after-error-check",
|
|
2816
|
+
type: "gotcha",
|
|
2817
|
+
tags: ["go", "resources"],
|
|
2818
|
+
body: `Place \`defer rows.Close()\` (or file/body Close) AFTER checking the open error, not before.
|
|
2819
|
+
|
|
2820
|
+
\`\`\`go
|
|
2821
|
+
rows, err := db.Query(q)
|
|
2822
|
+
if err != nil { return err }
|
|
2823
|
+
defer rows.Close() // only reached when rows is non-nil
|
|
2824
|
+
\`\`\`
|
|
2825
|
+
Deferring before the error check can call Close on a nil resource and panic.`
|
|
2826
|
+
},
|
|
2827
|
+
{
|
|
2828
|
+
slug: "go-context-first-param",
|
|
2829
|
+
type: "convention",
|
|
2830
|
+
tags: ["go", "context", "api-design"],
|
|
2831
|
+
body: `context.Context is always the FIRST parameter and is never stored in a struct.
|
|
2832
|
+
|
|
2833
|
+
\`\`\`go
|
|
2834
|
+
func Fetch(ctx context.Context, id string) (*User, error)
|
|
2835
|
+
\`\`\`
|
|
2836
|
+
Pass it explicitly down the call chain so cancellation and deadlines propagate.`
|
|
2837
|
+
}
|
|
2685
2838
|
]
|
|
2686
2839
|
};
|
|
2687
2840
|
var SEED_FOOTER = (stack) => `> _Seeded by \`haive init\` from the **${stack}** stack pack \u2014 generic guidance, not repo-specific. Anchor it to a real file or replace it with a repo-specific note to raise it above background priority._`;
|
|
@@ -2721,6 +2874,16 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
2721
2874
|
await mkdir4(haivePaths.teamDir, { recursive: true });
|
|
2722
2875
|
let count = 0;
|
|
2723
2876
|
for (const mem of memories) {
|
|
2877
|
+
const sensor = mem.sensor ? {
|
|
2878
|
+
kind: "regex",
|
|
2879
|
+
pattern: mem.sensor.pattern,
|
|
2880
|
+
...mem.sensor.flags ? { flags: mem.sensor.flags } : {},
|
|
2881
|
+
paths: mem.sensor.paths ?? [],
|
|
2882
|
+
message: mem.sensor.message,
|
|
2883
|
+
severity: "warn",
|
|
2884
|
+
autogen: false,
|
|
2885
|
+
last_fired: null
|
|
2886
|
+
} : void 0;
|
|
2724
2887
|
const fm = buildFrontmatter({
|
|
2725
2888
|
type: mem.type,
|
|
2726
2889
|
slug: `${stack}-${mem.slug}`,
|
|
@@ -2728,7 +2891,8 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
2728
2891
|
status: "validated",
|
|
2729
2892
|
// STACK_PACK_TAG marks this as generic seed knowledge so briefing ranking
|
|
2730
2893
|
// keeps it at `background` priority until it earns a repo-specific anchor.
|
|
2731
|
-
tags: [...mem.tags, STACK_PACK_TAG]
|
|
2894
|
+
tags: [...mem.tags, STACK_PACK_TAG],
|
|
2895
|
+
...sensor ? { sensor } : {}
|
|
2732
2896
|
});
|
|
2733
2897
|
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
2734
2898
|
if (existsSync9(filePath)) continue;
|
|
@@ -2743,7 +2907,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
2743
2907
|
}
|
|
2744
2908
|
|
|
2745
2909
|
// src/commands/init.ts
|
|
2746
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.12.
|
|
2910
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.12.9"}`;
|
|
2747
2911
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
2748
2912
|
|
|
2749
2913
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -2931,7 +3095,7 @@ jobs:
|
|
|
2931
3095
|
function registerInit(program2) {
|
|
2932
3096
|
program2.command("init").description(
|
|
2933
3097
|
"Initialize a hAIve project \u2014 autopilot mode ON by default (zero human intervention).\n Auto-bootstraps project-context.md from local files and seeds detected stack packs.\n Add --manual to control memory approval and session recaps yourself.\n Add --no-bootstrap and --stack none to disable the auto-features."
|
|
2934
|
-
).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md / .cursor/rules/haive-mcp-required.mdc").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
|
|
3098
|
+
).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / AGENTS.md / .cursorrules / copilot-instructions.md / .cursor/rules/haive-mcp-required.mdc").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
|
|
2935
3099
|
"--manual",
|
|
2936
3100
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
2937
3101
|
).option(
|
|
@@ -2994,6 +3158,7 @@ function registerInit(program2) {
|
|
|
2994
3158
|
}
|
|
2995
3159
|
if (opts.bridges) {
|
|
2996
3160
|
await writeBridge(root, "CLAUDE.md");
|
|
3161
|
+
await writeBridge(root, "AGENTS.md");
|
|
2997
3162
|
await writeBridge(root, ".cursorrules");
|
|
2998
3163
|
await writeBridge(root, path10.join(".github", "copilot-instructions.md"));
|
|
2999
3164
|
await writeCursorHaiveRule(root);
|
|
@@ -3731,37 +3896,49 @@ import {
|
|
|
3731
3896
|
suggestSensorFromMemory as suggestSensorFromMemory2
|
|
3732
3897
|
} from "@hiveai/core";
|
|
3733
3898
|
import { z as z15 } from "zod";
|
|
3734
|
-
import { mkdir as mkdir42, writeFile as writeFile82 } from "fs/promises";
|
|
3735
3899
|
import { existsSync as existsSync16 } from "fs";
|
|
3900
|
+
import { mkdir as mkdir42, readFile as readFile32, writeFile as writeFile82 } from "fs/promises";
|
|
3736
3901
|
import path62 from "path";
|
|
3737
3902
|
import {
|
|
3738
|
-
|
|
3739
|
-
|
|
3903
|
+
draftsFromFindings,
|
|
3904
|
+
filterNewDrafts,
|
|
3905
|
+
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
3740
3906
|
memoryFilePath as memoryFilePath3,
|
|
3907
|
+
parseFindings,
|
|
3741
3908
|
serializeMemory as serializeMemory7
|
|
3742
3909
|
} from "@hiveai/core";
|
|
3743
3910
|
import { z as z16 } from "zod";
|
|
3744
|
-
import {
|
|
3745
|
-
import { existsSync as
|
|
3746
|
-
import
|
|
3911
|
+
import { mkdir as mkdir52, writeFile as writeFile92 } from "fs/promises";
|
|
3912
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3913
|
+
import path72 from "path";
|
|
3747
3914
|
import {
|
|
3748
|
-
buildFrontmatter as
|
|
3749
|
-
|
|
3915
|
+
buildFrontmatter as buildFrontmatter3,
|
|
3916
|
+
isLikelyGuessable,
|
|
3750
3917
|
memoryFilePath as memoryFilePath4,
|
|
3751
3918
|
serializeMemory as serializeMemory8
|
|
3752
3919
|
} from "@hiveai/core";
|
|
3753
3920
|
import { z as z17 } from "zod";
|
|
3921
|
+
import { writeFile as writeFile11, mkdir as mkdir72 } from "fs/promises";
|
|
3922
|
+
import { existsSync as existsSync19 } from "fs";
|
|
3923
|
+
import path92 from "path";
|
|
3924
|
+
import {
|
|
3925
|
+
buildFrontmatter as buildFrontmatter4,
|
|
3926
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
3927
|
+
memoryFilePath as memoryFilePath5,
|
|
3928
|
+
serializeMemory as serializeMemory9
|
|
3929
|
+
} from "@hiveai/core";
|
|
3930
|
+
import { z as z18 } from "zod";
|
|
3754
3931
|
import {
|
|
3755
3932
|
appendUsageEvent,
|
|
3756
3933
|
appendRuntimeJournalEntry,
|
|
3757
3934
|
loadConfig as loadConfig22
|
|
3758
3935
|
} from "@hiveai/core";
|
|
3759
|
-
import { mkdir as
|
|
3760
|
-
import { existsSync as
|
|
3761
|
-
import
|
|
3936
|
+
import { mkdir as mkdir62, writeFile as writeFile10, rm } from "fs/promises";
|
|
3937
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3938
|
+
import path82 from "path";
|
|
3762
3939
|
import { execSync } from "child_process";
|
|
3763
|
-
import { readFile as
|
|
3764
|
-
import { existsSync as
|
|
3940
|
+
import { readFile as readFile52, writeFile as writeFile12 } from "fs/promises";
|
|
3941
|
+
import { existsSync as existsSync21 } from "fs";
|
|
3765
3942
|
import {
|
|
3766
3943
|
allocateBudget,
|
|
3767
3944
|
computeImpact as computeImpact2,
|
|
@@ -3779,13 +3956,13 @@ import {
|
|
|
3779
3956
|
literalMatchesAnyToken as literalMatchesAnyToken22,
|
|
3780
3957
|
loadCodeMap as loadCodeMap5,
|
|
3781
3958
|
loadConfig as loadConfig3,
|
|
3782
|
-
loadMemoriesFromDir as
|
|
3959
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
3783
3960
|
loadUsageIndex as loadUsageIndex8,
|
|
3784
3961
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
|
|
3785
3962
|
rankMemoriesLexical as rankMemoriesLexical2,
|
|
3786
3963
|
queryCodeMap as queryCodeMap2,
|
|
3787
3964
|
resolveBriefingBudget as resolveBriefingBudget2,
|
|
3788
|
-
serializeMemory as
|
|
3965
|
+
serializeMemory as serializeMemory10,
|
|
3789
3966
|
specificityScore as specificityScore2,
|
|
3790
3967
|
GUESSABLE_THRESHOLD,
|
|
3791
3968
|
tokenizeQuery as tokenizeQuery22,
|
|
@@ -3793,34 +3970,34 @@ import {
|
|
|
3793
3970
|
truncateToTokens,
|
|
3794
3971
|
writeBriefingMarker as writeBriefingMarker2
|
|
3795
3972
|
} from "@hiveai/core";
|
|
3796
|
-
import { z as
|
|
3797
|
-
import { readdir as readdir3, readFile as
|
|
3798
|
-
import { existsSync as
|
|
3799
|
-
import
|
|
3973
|
+
import { z as z19 } from "zod";
|
|
3974
|
+
import { readdir as readdir3, readFile as readFile42 } from "fs/promises";
|
|
3975
|
+
import { existsSync as existsSync20 } from "fs";
|
|
3976
|
+
import path102 from "path";
|
|
3800
3977
|
import { isGlobPath, isStackPackSeed as isStackPackSeed2, pathsOverlap } from "@hiveai/core";
|
|
3801
3978
|
import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap22, queryCodeMap as queryCodeMap22 } from "@hiveai/core";
|
|
3802
|
-
import { z as z19 } from "zod";
|
|
3803
|
-
import { existsSync as existsSync21 } from "fs";
|
|
3804
|
-
import { loadMemoriesFromDir as loadMemoriesFromDir15 } from "@hiveai/core";
|
|
3805
3979
|
import { z as z20 } from "zod";
|
|
3806
3980
|
import { existsSync as existsSync222 } from "fs";
|
|
3807
3981
|
import { loadMemoriesFromDir as loadMemoriesFromDir16 } from "@hiveai/core";
|
|
3808
3982
|
import { z as z21 } from "zod";
|
|
3983
|
+
import { existsSync as existsSync23 } from "fs";
|
|
3984
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hiveai/core";
|
|
3809
3985
|
import { z as z22 } from "zod";
|
|
3810
3986
|
import { z as z23 } from "zod";
|
|
3811
|
-
import {
|
|
3987
|
+
import { z as z24 } from "zod";
|
|
3988
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3812
3989
|
import { spawn } from "child_process";
|
|
3813
|
-
import
|
|
3990
|
+
import path112 from "path";
|
|
3814
3991
|
import {
|
|
3815
3992
|
deriveConfidence as deriveConfidence5,
|
|
3816
3993
|
getUsage as getUsage7,
|
|
3817
3994
|
loadCodeMap as loadCodeMap32,
|
|
3818
|
-
loadMemoriesFromDir as
|
|
3995
|
+
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
3819
3996
|
loadUsageIndex as loadUsageIndex9,
|
|
3820
3997
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
|
|
3821
3998
|
} from "@hiveai/core";
|
|
3822
|
-
import { z as
|
|
3823
|
-
import { existsSync as
|
|
3999
|
+
import { z as z25 } from "zod";
|
|
4000
|
+
import { existsSync as existsSync25 } from "fs";
|
|
3824
4001
|
import {
|
|
3825
4002
|
addedLinesFromDiff,
|
|
3826
4003
|
buildDocFrequency,
|
|
@@ -3829,7 +4006,7 @@ import {
|
|
|
3829
4006
|
diffHasDistinctiveOverlap,
|
|
3830
4007
|
getUsage as getUsage8,
|
|
3831
4008
|
isRetiredMemory as isRetiredMemory2,
|
|
3832
|
-
loadMemoriesFromDir as
|
|
4009
|
+
loadMemoriesFromDir as loadMemoriesFromDir19,
|
|
3833
4010
|
loadUsageIndex as loadUsageIndex10,
|
|
3834
4011
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
3835
4012
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
@@ -3837,66 +4014,66 @@ import {
|
|
|
3837
4014
|
sensorTargetsFromDiff,
|
|
3838
4015
|
tokenizeQuery as tokenizeQuery3
|
|
3839
4016
|
} from "@hiveai/core";
|
|
3840
|
-
import { z as
|
|
3841
|
-
import { existsSync as
|
|
4017
|
+
import { z as z26 } from "zod";
|
|
4018
|
+
import { existsSync as existsSync26 } from "fs";
|
|
3842
4019
|
import {
|
|
3843
|
-
loadMemoriesFromDir as
|
|
4020
|
+
loadMemoriesFromDir as loadMemoriesFromDir20,
|
|
3844
4021
|
tokenizeQuery as tokenizeQuery4
|
|
3845
4022
|
} from "@hiveai/core";
|
|
3846
|
-
import { z as
|
|
3847
|
-
import { existsSync as
|
|
4023
|
+
import { z as z27 } from "zod";
|
|
4024
|
+
import { existsSync as existsSync27 } from "fs";
|
|
3848
4025
|
import { spawn as spawn2 } from "child_process";
|
|
3849
4026
|
import {
|
|
3850
4027
|
deriveConfidence as deriveConfidence7,
|
|
3851
4028
|
getUsage as getUsage9,
|
|
3852
|
-
loadMemoriesFromDir as
|
|
4029
|
+
loadMemoriesFromDir as loadMemoriesFromDir21,
|
|
3853
4030
|
loadUsageIndex as loadUsageIndex11,
|
|
3854
4031
|
pathsOverlap as singlePathsOverlap
|
|
3855
4032
|
} from "@hiveai/core";
|
|
3856
|
-
import { z as
|
|
3857
|
-
import { existsSync as
|
|
4033
|
+
import { z as z28 } from "zod";
|
|
4034
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3858
4035
|
import {
|
|
3859
4036
|
deriveConfidence as deriveConfidence8,
|
|
3860
4037
|
getUsage as getUsage10,
|
|
3861
|
-
loadMemoriesFromDir as
|
|
4038
|
+
loadMemoriesFromDir as loadMemoriesFromDir222,
|
|
3862
4039
|
loadUsageIndex as loadUsageIndex12,
|
|
3863
4040
|
pathsOverlap as pathsOverlap2,
|
|
3864
4041
|
tokenizeQuery as tokenizeQuery5
|
|
3865
4042
|
} from "@hiveai/core";
|
|
3866
|
-
import { z as z28 } from "zod";
|
|
3867
4043
|
import { z as z29 } from "zod";
|
|
3868
|
-
import {
|
|
3869
|
-
import {
|
|
3870
|
-
import
|
|
4044
|
+
import { z as z30 } from "zod";
|
|
4045
|
+
import { mkdir as mkdir82, writeFile as writeFile13 } from "fs/promises";
|
|
4046
|
+
import { existsSync as existsSync29 } from "fs";
|
|
4047
|
+
import path122 from "path";
|
|
3871
4048
|
import { execSync as execSync2 } from "child_process";
|
|
3872
4049
|
import {
|
|
3873
4050
|
buildFrontmatter as buildFrontmatter5,
|
|
3874
|
-
memoryFilePath as
|
|
4051
|
+
memoryFilePath as memoryFilePath6,
|
|
3875
4052
|
readUsageEvents,
|
|
3876
|
-
serializeMemory as
|
|
4053
|
+
serializeMemory as serializeMemory11
|
|
3877
4054
|
} from "@hiveai/core";
|
|
3878
|
-
import { z as
|
|
3879
|
-
import { existsSync as
|
|
4055
|
+
import { z as z31 } from "zod";
|
|
4056
|
+
import { existsSync as existsSync30 } from "fs";
|
|
3880
4057
|
import {
|
|
3881
4058
|
findLexicalConflictPairs,
|
|
3882
4059
|
findTopicStatusConflictPairs,
|
|
3883
|
-
loadMemoriesFromDir as
|
|
4060
|
+
loadMemoriesFromDir as loadMemoriesFromDir23
|
|
3884
4061
|
} from "@hiveai/core";
|
|
3885
|
-
import { z as z31 } from "zod";
|
|
3886
|
-
import { resolveProjectInfo } from "@hiveai/core";
|
|
3887
4062
|
import { z as z32 } from "zod";
|
|
3888
|
-
import {
|
|
4063
|
+
import { resolveProjectInfo } from "@hiveai/core";
|
|
3889
4064
|
import { z as z33 } from "zod";
|
|
3890
|
-
import {
|
|
3891
|
-
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hiveai/core";
|
|
4065
|
+
import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
|
|
3892
4066
|
import { z as z34 } from "zod";
|
|
3893
|
-
import {
|
|
4067
|
+
import { existsSync as existsSync31 } from "fs";
|
|
4068
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir24 } from "@hiveai/core";
|
|
3894
4069
|
import { z as z35 } from "zod";
|
|
3895
|
-
import {
|
|
4070
|
+
import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
|
|
3896
4071
|
import { z as z36 } from "zod";
|
|
4072
|
+
import { readRuntimeJournalTail } from "@hiveai/core";
|
|
3897
4073
|
import { z as z37 } from "zod";
|
|
3898
4074
|
import { z as z38 } from "zod";
|
|
3899
4075
|
import { z as z39 } from "zod";
|
|
4076
|
+
import { z as z40 } from "zod";
|
|
3900
4077
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
|
|
3901
4078
|
function createContext(options = {}) {
|
|
3902
4079
|
const env = options.env ?? process.env;
|
|
@@ -4879,21 +5056,98 @@ async function memTried(input, ctx) {
|
|
|
4879
5056
|
await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
|
|
4880
5057
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
4881
5058
|
}
|
|
4882
|
-
var
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope
|
|
5059
|
+
var IngestFindingsInputSchema = {
|
|
5060
|
+
format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
|
|
5061
|
+
report_path: z16.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
|
|
5062
|
+
report: z16.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
|
|
5063
|
+
type: z16.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
|
|
5064
|
+
scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
|
|
4888
5065
|
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
4889
|
-
|
|
5066
|
+
min_severity: z16.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
|
|
5067
|
+
limit: z16.number().int().positive().optional().describe("Cap the number of memories created"),
|
|
4890
5068
|
author: z16.string().optional().describe("Author handle or email"),
|
|
4891
|
-
|
|
5069
|
+
dry_run: z16.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
|
|
5070
|
+
};
|
|
5071
|
+
async function ingestFindings(input, ctx) {
|
|
5072
|
+
if (!existsSync16(ctx.paths.haiveDir)) {
|
|
5073
|
+
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
5074
|
+
}
|
|
5075
|
+
let raw;
|
|
5076
|
+
if (input.report && input.report.trim()) {
|
|
5077
|
+
raw = input.report;
|
|
5078
|
+
} else if (input.report_path) {
|
|
5079
|
+
const file = path62.resolve(ctx.paths.root, input.report_path);
|
|
5080
|
+
if (!existsSync16(file)) throw new Error(`Report file not found: ${file}`);
|
|
5081
|
+
raw = await readFile32(file, "utf8");
|
|
5082
|
+
} else {
|
|
5083
|
+
throw new Error("Provide either `report_path` or `report`.");
|
|
5084
|
+
}
|
|
5085
|
+
const findings = parseFindings(input.format, raw);
|
|
5086
|
+
const drafts = draftsFromFindings(findings, {
|
|
5087
|
+
type: input.type,
|
|
5088
|
+
scope: input.scope,
|
|
5089
|
+
module: input.module,
|
|
5090
|
+
author: input.author,
|
|
5091
|
+
...input.min_severity ? { minSeverity: input.min_severity } : {},
|
|
5092
|
+
...input.limit ? { limit: input.limit } : {}
|
|
5093
|
+
});
|
|
5094
|
+
const existing = existsSync16(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
|
|
5095
|
+
const existingTopics = new Set(
|
|
5096
|
+
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
5097
|
+
);
|
|
5098
|
+
const fresh = filterNewDrafts(drafts, existingTopics);
|
|
5099
|
+
const skipped = drafts.length - fresh.length;
|
|
5100
|
+
const created = [];
|
|
5101
|
+
for (const draft of fresh) {
|
|
5102
|
+
let filePath;
|
|
5103
|
+
if (!input.dry_run) filePath = await writeDraft(ctx, draft);
|
|
5104
|
+
created.push({
|
|
5105
|
+
id: draft.frontmatter.id,
|
|
5106
|
+
topic: draft.topic,
|
|
5107
|
+
path: draft.finding.path,
|
|
5108
|
+
rule: draft.finding.ruleId,
|
|
5109
|
+
severity: draft.finding.severity,
|
|
5110
|
+
has_sensor: draft.has_sensor,
|
|
5111
|
+
...filePath ? { file_path: filePath } : {}
|
|
5112
|
+
});
|
|
5113
|
+
}
|
|
5114
|
+
const notice = input.dry_run ? `Dry run \u2014 ${fresh.length} memory(ies) would be created (status=proposed). Re-run with dry_run=false to write them.` : `Created ${fresh.length} proposed memory(ies). They are NOT validated and their sensors are warn-only \u2014 review with mem_pending and promote with 'haive sensors promote'.`;
|
|
5115
|
+
return {
|
|
5116
|
+
format: input.format,
|
|
5117
|
+
parsed: drafts.length,
|
|
5118
|
+
new: fresh.length,
|
|
5119
|
+
skipped_existing: skipped,
|
|
5120
|
+
dry_run: input.dry_run,
|
|
5121
|
+
created,
|
|
5122
|
+
notice
|
|
5123
|
+
};
|
|
5124
|
+
}
|
|
5125
|
+
async function writeDraft(ctx, draft) {
|
|
5126
|
+
const file = memoryFilePath3(
|
|
5127
|
+
ctx.paths,
|
|
5128
|
+
draft.frontmatter.scope,
|
|
5129
|
+
draft.frontmatter.id,
|
|
5130
|
+
draft.frontmatter.module
|
|
5131
|
+
);
|
|
5132
|
+
await mkdir42(path62.dirname(file), { recursive: true });
|
|
5133
|
+
await writeFile82(file, serializeMemory7({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
5134
|
+
return file;
|
|
5135
|
+
}
|
|
5136
|
+
var MemObserveInputSchema = {
|
|
5137
|
+
what: z17.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
|
|
5138
|
+
where: z17.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
|
|
5139
|
+
impact: z17.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
|
|
5140
|
+
fix: z17.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
|
|
5141
|
+
scope: z17.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
|
|
5142
|
+
module: z17.string().optional().describe("Module name (required when scope=module)"),
|
|
5143
|
+
tags: z17.array(z17.string()).default([]).describe("Tags for filtering"),
|
|
5144
|
+
author: z17.string().optional().describe("Author handle or email"),
|
|
5145
|
+
force: z17.boolean().default(false).describe(
|
|
4892
5146
|
"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."
|
|
4893
5147
|
)
|
|
4894
5148
|
};
|
|
4895
5149
|
async function memObserve(input, ctx) {
|
|
4896
|
-
if (!
|
|
5150
|
+
if (!existsSync17(ctx.paths.haiveDir)) {
|
|
4897
5151
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
4898
5152
|
}
|
|
4899
5153
|
const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
|
|
@@ -4925,16 +5179,16 @@ async function memObserve(input, ctx) {
|
|
|
4925
5179
|
lines.push("", `**Fix/workaround:** ${input.fix}`);
|
|
4926
5180
|
}
|
|
4927
5181
|
const body = lines.join("\n") + "\n";
|
|
4928
|
-
const file =
|
|
4929
|
-
await
|
|
4930
|
-
if (
|
|
5182
|
+
const file = memoryFilePath4(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
5183
|
+
await mkdir52(path72.dirname(file), { recursive: true });
|
|
5184
|
+
if (existsSync17(file)) {
|
|
4931
5185
|
throw new Error(`Memory already exists at ${file}`);
|
|
4932
5186
|
}
|
|
4933
|
-
await
|
|
5187
|
+
await writeFile92(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
4934
5188
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
4935
5189
|
}
|
|
4936
5190
|
function pendingDistillPath(ctx) {
|
|
4937
|
-
return
|
|
5191
|
+
return path82.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
4938
5192
|
}
|
|
4939
5193
|
var SessionTracker = class {
|
|
4940
5194
|
events = [];
|
|
@@ -5013,7 +5267,7 @@ var SessionTracker = class {
|
|
|
5013
5267
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
5014
5268
|
);
|
|
5015
5269
|
const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
|
|
5016
|
-
if (!ranPostTask && isSubstantialSession &&
|
|
5270
|
+
if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
|
|
5017
5271
|
try {
|
|
5018
5272
|
const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
|
|
5019
5273
|
const payload = {
|
|
@@ -5026,9 +5280,9 @@ var SessionTracker = class {
|
|
|
5026
5280
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
5027
5281
|
...recapId ? { recap_id: recapId } : {}
|
|
5028
5282
|
};
|
|
5029
|
-
const cacheDir =
|
|
5030
|
-
await
|
|
5031
|
-
await
|
|
5283
|
+
const cacheDir = path82.join(this.ctx.paths.haiveDir, ".cache");
|
|
5284
|
+
await mkdir62(cacheDir, { recursive: true });
|
|
5285
|
+
await writeFile10(
|
|
5032
5286
|
pendingDistillPath(this.ctx),
|
|
5033
5287
|
JSON.stringify(payload, null, 2) + "\n",
|
|
5034
5288
|
"utf8"
|
|
@@ -5047,7 +5301,7 @@ var SessionTracker = class {
|
|
|
5047
5301
|
};
|
|
5048
5302
|
async function clearPendingDistill(ctx) {
|
|
5049
5303
|
const p = pendingDistillPath(ctx);
|
|
5050
|
-
if (
|
|
5304
|
+
if (existsSync18(p)) {
|
|
5051
5305
|
try {
|
|
5052
5306
|
await rm(p);
|
|
5053
5307
|
} catch {
|
|
@@ -5062,15 +5316,15 @@ function summarizeTools(events) {
|
|
|
5062
5316
|
return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
|
|
5063
5317
|
}
|
|
5064
5318
|
var MemSessionEndInputSchema = {
|
|
5065
|
-
goal:
|
|
5066
|
-
accomplished:
|
|
5067
|
-
discoveries:
|
|
5319
|
+
goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
|
|
5320
|
+
accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
|
|
5321
|
+
discoveries: z18.string().default("").describe(
|
|
5068
5322
|
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
5069
5323
|
),
|
|
5070
|
-
files_touched:
|
|
5071
|
-
next_steps:
|
|
5072
|
-
scope:
|
|
5073
|
-
module:
|
|
5324
|
+
files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
|
|
5325
|
+
next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
|
|
5326
|
+
scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
|
|
5327
|
+
module: z18.string().optional().describe("Module name (required when scope=module)")
|
|
5074
5328
|
};
|
|
5075
5329
|
function recapTopic(scope, module) {
|
|
5076
5330
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
@@ -5100,23 +5354,23 @@ ${input.next_steps}`);
|
|
|
5100
5354
|
return lines.join("\n");
|
|
5101
5355
|
}
|
|
5102
5356
|
async function memSessionEnd(input, ctx) {
|
|
5103
|
-
if (!
|
|
5357
|
+
if (!existsSync19(ctx.paths.haiveDir)) {
|
|
5104
5358
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
5105
5359
|
}
|
|
5106
5360
|
const body = buildBody(input);
|
|
5107
5361
|
const topic = recapTopic(input.scope, input.module);
|
|
5108
5362
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
5109
|
-
if (!p || !
|
|
5110
|
-
const rel =
|
|
5363
|
+
if (!p || !path92.isAbsolute(p)) return p;
|
|
5364
|
+
const rel = path92.relative(ctx.paths.root, p);
|
|
5111
5365
|
return rel.startsWith("..") ? p : rel;
|
|
5112
5366
|
});
|
|
5113
5367
|
const invalidPaths = normalizedFiles.filter(
|
|
5114
|
-
(p) => !
|
|
5368
|
+
(p) => !existsSync19(path92.resolve(ctx.paths.root, p))
|
|
5115
5369
|
);
|
|
5116
5370
|
if (invalidPaths.length > 0) {
|
|
5117
5371
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
5118
5372
|
}
|
|
5119
|
-
const existing =
|
|
5373
|
+
const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
|
|
5120
5374
|
const topicMatch = existing.find(
|
|
5121
5375
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === input.scope && (!input.module || memory2.frontmatter.module === input.module)
|
|
5122
5376
|
);
|
|
@@ -5132,9 +5386,9 @@ async function memSessionEnd(input, ctx) {
|
|
|
5132
5386
|
paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
|
|
5133
5387
|
}
|
|
5134
5388
|
};
|
|
5135
|
-
await
|
|
5389
|
+
await writeFile11(
|
|
5136
5390
|
topicMatch.filePath,
|
|
5137
|
-
|
|
5391
|
+
serializeMemory9({ frontmatter: newFrontmatter, body }),
|
|
5138
5392
|
"utf8"
|
|
5139
5393
|
);
|
|
5140
5394
|
await clearPendingDistill(ctx);
|
|
@@ -5156,14 +5410,14 @@ async function memSessionEnd(input, ctx) {
|
|
|
5156
5410
|
topic,
|
|
5157
5411
|
status: "validated"
|
|
5158
5412
|
});
|
|
5159
|
-
const file =
|
|
5413
|
+
const file = memoryFilePath5(
|
|
5160
5414
|
ctx.paths,
|
|
5161
5415
|
frontmatter.scope,
|
|
5162
5416
|
frontmatter.id,
|
|
5163
5417
|
frontmatter.module
|
|
5164
5418
|
);
|
|
5165
|
-
await
|
|
5166
|
-
await
|
|
5419
|
+
await mkdir72(path92.dirname(file), { recursive: true });
|
|
5420
|
+
await writeFile11(file, serializeMemory9({ frontmatter, body }), "utf8");
|
|
5167
5421
|
await clearPendingDistill(ctx);
|
|
5168
5422
|
return {
|
|
5169
5423
|
id: frontmatter.id,
|
|
@@ -5297,50 +5551,50 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
5297
5551
|
}
|
|
5298
5552
|
async function loadModuleContexts2(ctx, modules) {
|
|
5299
5553
|
if (modules.length === 0) return [];
|
|
5300
|
-
if (!
|
|
5554
|
+
if (!existsSync20(ctx.paths.modulesContextDir)) return [];
|
|
5301
5555
|
const available = new Set(
|
|
5302
5556
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
5303
5557
|
);
|
|
5304
5558
|
const out = [];
|
|
5305
5559
|
for (const m of modules) {
|
|
5306
5560
|
if (!available.has(m)) continue;
|
|
5307
|
-
const file =
|
|
5308
|
-
if (
|
|
5309
|
-
out.push({ name: m, content: await
|
|
5561
|
+
const file = path102.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
5562
|
+
if (existsSync20(file)) {
|
|
5563
|
+
out.push({ name: m, content: await readFile42(file, "utf8") });
|
|
5310
5564
|
}
|
|
5311
5565
|
}
|
|
5312
5566
|
return out;
|
|
5313
5567
|
}
|
|
5314
5568
|
var GetBriefingInputSchema = {
|
|
5315
|
-
task:
|
|
5569
|
+
task: z19.string().optional().describe(
|
|
5316
5570
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
5317
5571
|
),
|
|
5318
|
-
files:
|
|
5319
|
-
max_tokens:
|
|
5572
|
+
files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
|
|
5573
|
+
max_tokens: z19.number().int().positive().default(8e3).describe(
|
|
5320
5574
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
5321
5575
|
),
|
|
5322
|
-
max_memories:
|
|
5323
|
-
include_project_context:
|
|
5324
|
-
include_module_contexts:
|
|
5325
|
-
semantic:
|
|
5576
|
+
max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
5577
|
+
include_project_context: z19.boolean().default(true),
|
|
5578
|
+
include_module_contexts: z19.boolean().default(true),
|
|
5579
|
+
semantic: z19.boolean().default(true).describe(
|
|
5326
5580
|
"Use semantic ranking when a task is provided (requires `haive embeddings index`)."
|
|
5327
5581
|
),
|
|
5328
|
-
include_stale:
|
|
5329
|
-
track:
|
|
5330
|
-
format:
|
|
5582
|
+
include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
5583
|
+
track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
5584
|
+
format: z19.enum(["full", "compact", "actions"]).default("full").describe(
|
|
5331
5585
|
"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."
|
|
5332
5586
|
),
|
|
5333
|
-
symbols:
|
|
5587
|
+
symbols: z19.array(z19.string()).default([]).describe(
|
|
5334
5588
|
"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."
|
|
5335
5589
|
),
|
|
5336
|
-
min_semantic_score:
|
|
5590
|
+
min_semantic_score: z19.number().min(0).max(1).default(0).describe(
|
|
5337
5591
|
"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."
|
|
5338
5592
|
),
|
|
5339
|
-
budget_preset:
|
|
5593
|
+
budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
|
|
5340
5594
|
"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."
|
|
5341
5595
|
)
|
|
5342
5596
|
};
|
|
5343
|
-
var GetBriefingZod =
|
|
5597
|
+
var GetBriefingZod = z19.object(GetBriefingInputSchema);
|
|
5344
5598
|
async function getBriefing(input, ctx) {
|
|
5345
5599
|
const resolvedBudget = resolveBriefingBudget2(input.budget_preset, {
|
|
5346
5600
|
max_tokens: input.max_tokens,
|
|
@@ -5356,8 +5610,8 @@ async function getBriefing(input, ctx) {
|
|
|
5356
5610
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
5357
5611
|
let byId = /* @__PURE__ */ new Map();
|
|
5358
5612
|
let lastSession;
|
|
5359
|
-
if (
|
|
5360
|
-
const allLoaded = await
|
|
5613
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
5614
|
+
const allLoaded = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
5361
5615
|
const recaps = allLoaded.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").sort(
|
|
5362
5616
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
5363
5617
|
);
|
|
@@ -5520,7 +5774,7 @@ async function getBriefing(input, ctx) {
|
|
|
5520
5774
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
5521
5775
|
const newFm = { ...loaded.memory.frontmatter, status: "validated" };
|
|
5522
5776
|
try {
|
|
5523
|
-
await
|
|
5777
|
+
await writeFile12(loaded.filePath, serializeMemory10({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
|
|
5524
5778
|
m.status = "validated";
|
|
5525
5779
|
m.confidence = "trusted";
|
|
5526
5780
|
} catch {
|
|
@@ -5528,12 +5782,12 @@ async function getBriefing(input, ctx) {
|
|
|
5528
5782
|
}
|
|
5529
5783
|
}
|
|
5530
5784
|
}
|
|
5531
|
-
const projectContextRaw = input.include_project_context &&
|
|
5785
|
+
const projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile52(ctx.paths.projectContext, "utf8") : "";
|
|
5532
5786
|
const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
|
|
5533
5787
|
const setupWarnings = [];
|
|
5534
5788
|
let autoContextGenerated = false;
|
|
5535
5789
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
5536
|
-
if ((isTemplateContext || !
|
|
5790
|
+
if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
|
|
5537
5791
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
5538
5792
|
if (haiveConfig.autoContext) {
|
|
5539
5793
|
const codeMap = await loadCodeMap5(ctx.paths);
|
|
@@ -5694,8 +5948,8 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5694
5948
|
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
5695
5949
|
}
|
|
5696
5950
|
}
|
|
5697
|
-
if (
|
|
5698
|
-
const allMems = await
|
|
5951
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
5952
|
+
const allMems = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
5699
5953
|
for (const { memory: memory2 } of allMems) {
|
|
5700
5954
|
const fm = memory2.frontmatter;
|
|
5701
5955
|
if (!fm.requires_human_approval) continue;
|
|
@@ -5705,9 +5959,9 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5705
5959
|
}
|
|
5706
5960
|
}
|
|
5707
5961
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
5708
|
-
if (
|
|
5962
|
+
if (existsSync21(pendingDistillFile)) {
|
|
5709
5963
|
try {
|
|
5710
|
-
const raw = await
|
|
5964
|
+
const raw = await readFile52(pendingDistillFile, "utf8");
|
|
5711
5965
|
const pd = JSON.parse(raw);
|
|
5712
5966
|
const ageMs = Date.now() - new Date(pd.session_end).getTime();
|
|
5713
5967
|
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -5734,7 +5988,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5734
5988
|
}
|
|
5735
5989
|
}
|
|
5736
5990
|
const memoriesEmpty = outputMemories.length === 0;
|
|
5737
|
-
const hasMemoriesDir =
|
|
5991
|
+
const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
|
|
5738
5992
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
5739
5993
|
const hasUnguessableSignal = outputMemories.some(
|
|
5740
5994
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore2(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -5781,7 +6035,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5781
6035
|
"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."
|
|
5782
6036
|
);
|
|
5783
6037
|
}
|
|
5784
|
-
if (
|
|
6038
|
+
if (existsSync21(ctx.paths.haiveDir)) {
|
|
5785
6039
|
await writeBriefingMarker2(ctx.paths, {
|
|
5786
6040
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
5787
6041
|
...input.task ? { task: input.task } : {},
|
|
@@ -5830,17 +6084,17 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5830
6084
|
};
|
|
5831
6085
|
}
|
|
5832
6086
|
var CodeMapInputSchema = {
|
|
5833
|
-
file:
|
|
5834
|
-
symbol:
|
|
5835
|
-
paths:
|
|
6087
|
+
file: z20.string().optional().describe("Filter to files whose path contains this substring"),
|
|
6088
|
+
symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
6089
|
+
paths: z20.array(z20.string()).default([]).describe(
|
|
5836
6090
|
"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."
|
|
5837
6091
|
),
|
|
5838
|
-
max_files:
|
|
5839
|
-
max_tokens:
|
|
6092
|
+
max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
|
|
6093
|
+
max_tokens: z20.number().int().positive().optional().describe(
|
|
5840
6094
|
"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)."
|
|
5841
6095
|
)
|
|
5842
6096
|
};
|
|
5843
|
-
var CodeMapInputZod =
|
|
6097
|
+
var CodeMapInputZod = z20.object(CodeMapInputSchema);
|
|
5844
6098
|
async function codeMapTool(input, ctx) {
|
|
5845
6099
|
const map = await loadCodeMap22(ctx.paths);
|
|
5846
6100
|
if (!map) {
|
|
@@ -5908,14 +6162,14 @@ function estimateFileEntryTokens(f) {
|
|
|
5908
6162
|
return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
|
|
5909
6163
|
}
|
|
5910
6164
|
var MemDiffInputSchema = {
|
|
5911
|
-
id_a:
|
|
5912
|
-
id_b:
|
|
6165
|
+
id_a: z21.string().min(1).describe("First memory id"),
|
|
6166
|
+
id_b: z21.string().min(1).describe("Second memory id")
|
|
5913
6167
|
};
|
|
5914
6168
|
async function memDiff(input, ctx) {
|
|
5915
|
-
if (!
|
|
6169
|
+
if (!existsSync222(ctx.paths.memoriesDir)) {
|
|
5916
6170
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
5917
6171
|
}
|
|
5918
|
-
const all = await
|
|
6172
|
+
const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
|
|
5919
6173
|
const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
|
|
5920
6174
|
const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
|
|
5921
6175
|
if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
|
|
@@ -5948,15 +6202,15 @@ async function memDiff(input, ctx) {
|
|
|
5948
6202
|
};
|
|
5949
6203
|
}
|
|
5950
6204
|
var GetRecapInputSchema = {
|
|
5951
|
-
scope:
|
|
6205
|
+
scope: z22.enum(["personal", "team", "any"]).default("any").describe(
|
|
5952
6206
|
"Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
|
|
5953
6207
|
)
|
|
5954
6208
|
};
|
|
5955
6209
|
async function getRecap(input, ctx) {
|
|
5956
|
-
if (!
|
|
6210
|
+
if (!existsSync23(ctx.paths.memoriesDir)) {
|
|
5957
6211
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
5958
6212
|
}
|
|
5959
|
-
const all = await
|
|
6213
|
+
const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
5960
6214
|
const recaps = all.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").filter(({ memory: memory2 }) => input.scope === "any" || memory2.frontmatter.scope === input.scope).sort(
|
|
5961
6215
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
5962
6216
|
);
|
|
@@ -5979,11 +6233,11 @@ async function getRecap(input, ctx) {
|
|
|
5979
6233
|
};
|
|
5980
6234
|
}
|
|
5981
6235
|
var MemRelevantToInputSchema = {
|
|
5982
|
-
task:
|
|
5983
|
-
files:
|
|
5984
|
-
limit:
|
|
5985
|
-
min_semantic_score:
|
|
5986
|
-
format:
|
|
6236
|
+
task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
|
|
6237
|
+
files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
|
|
6238
|
+
limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
|
|
6239
|
+
min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
|
|
6240
|
+
format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
|
|
5987
6241
|
};
|
|
5988
6242
|
async function memRelevantTo(input, ctx) {
|
|
5989
6243
|
const briefingInput = {
|
|
@@ -6012,11 +6266,11 @@ async function memRelevantTo(input, ctx) {
|
|
|
6012
6266
|
return out;
|
|
6013
6267
|
}
|
|
6014
6268
|
var CodeSearchInputSchema = {
|
|
6015
|
-
query:
|
|
6269
|
+
query: z24.string().min(1).describe(
|
|
6016
6270
|
"Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
|
|
6017
6271
|
),
|
|
6018
|
-
k:
|
|
6019
|
-
min_score:
|
|
6272
|
+
k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
|
|
6273
|
+
min_score: z24.number().min(0).max(1).default(0.2).describe(
|
|
6020
6274
|
"Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
|
|
6021
6275
|
)
|
|
6022
6276
|
};
|
|
@@ -6045,14 +6299,14 @@ async function codeSearch(input, ctx) {
|
|
|
6045
6299
|
return { available: true, hits: result.hits };
|
|
6046
6300
|
}
|
|
6047
6301
|
var WhyThisFileInputSchema = {
|
|
6048
|
-
path:
|
|
6302
|
+
path: z25.string().min(1).describe(
|
|
6049
6303
|
"Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
|
|
6050
6304
|
),
|
|
6051
|
-
git_log_limit:
|
|
6052
|
-
memory_limit:
|
|
6305
|
+
git_log_limit: z25.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
|
|
6306
|
+
memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
|
|
6053
6307
|
};
|
|
6054
6308
|
async function whyThisFile(input, ctx) {
|
|
6055
|
-
const fileExists =
|
|
6309
|
+
const fileExists = existsSync24(path112.join(ctx.paths.root, input.path));
|
|
6056
6310
|
const [commits, memories, codeMap] = await Promise.all([
|
|
6057
6311
|
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
6058
6312
|
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
@@ -6093,8 +6347,8 @@ async function whyThisFile(input, ctx) {
|
|
|
6093
6347
|
};
|
|
6094
6348
|
}
|
|
6095
6349
|
async function collectAnchoredMemories(ctx, filePath, limit) {
|
|
6096
|
-
if (!
|
|
6097
|
-
const all = await
|
|
6350
|
+
if (!existsSync24(ctx.paths.memoriesDir)) return [];
|
|
6351
|
+
const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
|
|
6098
6352
|
const usage = await loadUsageIndex9(ctx.paths);
|
|
6099
6353
|
const out = [];
|
|
6100
6354
|
for (const { memory: memory2 } of all) {
|
|
@@ -6147,17 +6401,17 @@ function runCommand(cmd, args, cwd) {
|
|
|
6147
6401
|
});
|
|
6148
6402
|
}
|
|
6149
6403
|
var AntiPatternsCheckInputSchema = {
|
|
6150
|
-
diff:
|
|
6404
|
+
diff: z26.string().optional().describe(
|
|
6151
6405
|
"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."
|
|
6152
6406
|
),
|
|
6153
|
-
paths:
|
|
6407
|
+
paths: z26.array(z26.string()).default([]).describe(
|
|
6154
6408
|
"File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
|
|
6155
6409
|
),
|
|
6156
|
-
limit:
|
|
6157
|
-
semantic:
|
|
6410
|
+
limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
|
|
6411
|
+
semantic: z26.boolean().default(true).describe(
|
|
6158
6412
|
"When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
|
|
6159
6413
|
),
|
|
6160
|
-
min_semantic_score:
|
|
6414
|
+
min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
|
|
6161
6415
|
"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."
|
|
6162
6416
|
)
|
|
6163
6417
|
};
|
|
@@ -6178,10 +6432,10 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6178
6432
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
6179
6433
|
};
|
|
6180
6434
|
}
|
|
6181
|
-
if (!
|
|
6435
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
6182
6436
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
6183
6437
|
}
|
|
6184
|
-
const all = await
|
|
6438
|
+
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
6185
6439
|
const minSemanticScore = input.min_semantic_score ?? 0.45;
|
|
6186
6440
|
const negative = all.filter(({ memory: memory2 }) => {
|
|
6187
6441
|
const t = memory2.frontmatter.type;
|
|
@@ -6288,12 +6542,12 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6288
6542
|
};
|
|
6289
6543
|
}
|
|
6290
6544
|
var MemDistillInputSchema = {
|
|
6291
|
-
since_days:
|
|
6292
|
-
min_cluster:
|
|
6293
|
-
type_filter:
|
|
6545
|
+
since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
|
|
6546
|
+
min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
|
|
6547
|
+
type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
|
|
6294
6548
|
"Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
|
|
6295
6549
|
),
|
|
6296
|
-
scope:
|
|
6550
|
+
scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
|
|
6297
6551
|
};
|
|
6298
6552
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
6299
6553
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -6333,11 +6587,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
6333
6587
|
"error"
|
|
6334
6588
|
]);
|
|
6335
6589
|
async function memDistill(input, ctx) {
|
|
6336
|
-
if (!
|
|
6590
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
6337
6591
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
6338
6592
|
}
|
|
6339
6593
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
6340
|
-
const all = await
|
|
6594
|
+
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
6341
6595
|
const candidates = all.filter(({ memory: memory2 }) => {
|
|
6342
6596
|
const fm = memory2.frontmatter;
|
|
6343
6597
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -6440,11 +6694,11 @@ function firstHeading(body) {
|
|
|
6440
6694
|
return void 0;
|
|
6441
6695
|
}
|
|
6442
6696
|
var WhyThisDecisionInputSchema = {
|
|
6443
|
-
id:
|
|
6444
|
-
git_log_limit:
|
|
6697
|
+
id: z28.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
|
|
6698
|
+
git_log_limit: z28.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
|
|
6445
6699
|
};
|
|
6446
6700
|
async function whyThisDecision(input, ctx) {
|
|
6447
|
-
if (!
|
|
6701
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
6448
6702
|
return {
|
|
6449
6703
|
found: false,
|
|
6450
6704
|
related: [],
|
|
@@ -6453,7 +6707,7 @@ async function whyThisDecision(input, ctx) {
|
|
|
6453
6707
|
notice: "No .ai/memories directory."
|
|
6454
6708
|
};
|
|
6455
6709
|
}
|
|
6456
|
-
const all = await
|
|
6710
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
6457
6711
|
const usage = await loadUsageIndex11(ctx.paths);
|
|
6458
6712
|
const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
|
|
6459
6713
|
if (!target) {
|
|
@@ -6575,17 +6829,17 @@ function runCommand2(cmd, args, cwd) {
|
|
|
6575
6829
|
});
|
|
6576
6830
|
}
|
|
6577
6831
|
var MemConflictsInputSchema = {
|
|
6578
|
-
id:
|
|
6579
|
-
min_score:
|
|
6580
|
-
semantic:
|
|
6832
|
+
id: z29.string().min(1).describe("Memory id to check for conflicts."),
|
|
6833
|
+
min_score: z29.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
|
|
6834
|
+
semantic: z29.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
|
|
6581
6835
|
};
|
|
6582
6836
|
var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
|
|
6583
6837
|
var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
|
|
6584
6838
|
async function memConflicts(input, ctx) {
|
|
6585
|
-
if (!
|
|
6839
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
6586
6840
|
return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
|
|
6587
6841
|
}
|
|
6588
|
-
const all = await
|
|
6842
|
+
const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
|
|
6589
6843
|
const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
|
|
6590
6844
|
if (!target) {
|
|
6591
6845
|
return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
|
|
@@ -6696,15 +6950,15 @@ async function trySemanticSimilarities(ctx, target, others) {
|
|
|
6696
6950
|
return map;
|
|
6697
6951
|
}
|
|
6698
6952
|
var PreCommitCheckInputSchema = {
|
|
6699
|
-
diff:
|
|
6953
|
+
diff: z30.string().optional().describe(
|
|
6700
6954
|
"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`."
|
|
6701
6955
|
),
|
|
6702
|
-
paths:
|
|
6703
|
-
block_on:
|
|
6956
|
+
paths: z30.array(z30.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
|
|
6957
|
+
block_on: z30.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
|
|
6704
6958
|
"When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
|
|
6705
6959
|
),
|
|
6706
|
-
semantic:
|
|
6707
|
-
anchored_blocks:
|
|
6960
|
+
semantic: z30.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
|
|
6961
|
+
anchored_blocks: z30.boolean().default(false).describe(
|
|
6708
6962
|
"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."
|
|
6709
6963
|
)
|
|
6710
6964
|
};
|
|
@@ -7010,12 +7264,12 @@ var CONFIG_PATTERNS = [
|
|
|
7010
7264
|
var MAX_DIFF_BYTES = 4096;
|
|
7011
7265
|
var HOT_FILE_MIN = 3;
|
|
7012
7266
|
var PatternDetectInputSchema = {
|
|
7013
|
-
since_days:
|
|
7014
|
-
dry_run:
|
|
7015
|
-
scope:
|
|
7267
|
+
since_days: z31.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
|
|
7268
|
+
dry_run: z31.boolean().default(false).describe("When true, report matches without writing any memory files."),
|
|
7269
|
+
scope: z31.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
|
|
7016
7270
|
};
|
|
7017
7271
|
async function patternDetect(input, ctx) {
|
|
7018
|
-
if (!
|
|
7272
|
+
if (!existsSync29(ctx.paths.haiveDir)) {
|
|
7019
7273
|
return {
|
|
7020
7274
|
scanned_events: 0,
|
|
7021
7275
|
matches: [],
|
|
@@ -7028,13 +7282,13 @@ async function patternDetect(input, ctx) {
|
|
|
7028
7282
|
try {
|
|
7029
7283
|
const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
|
|
7030
7284
|
const configFiles = changedFiles.filter(
|
|
7031
|
-
(f) => CONFIG_PATTERNS.some((p) =>
|
|
7285
|
+
(f) => CONFIG_PATTERNS.some((p) => path122.basename(f.toLowerCase()).includes(p))
|
|
7032
7286
|
);
|
|
7033
7287
|
for (const file of configFiles.slice(0, 5)) {
|
|
7034
7288
|
const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
|
|
7035
7289
|
if (!diff) continue;
|
|
7036
|
-
const parentDir =
|
|
7037
|
-
const baseName =
|
|
7290
|
+
const parentDir = path122.basename(path122.dirname(file));
|
|
7291
|
+
const baseName = path122.basename(file).replace(/\.[^.]+$/, "");
|
|
7038
7292
|
const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
|
|
7039
7293
|
matches.push({
|
|
7040
7294
|
kind: "config_change",
|
|
@@ -7098,7 +7352,7 @@ async function patternDetect(input, ctx) {
|
|
|
7098
7352
|
for (const [p, { count, tools }] of pathCounts) {
|
|
7099
7353
|
if (count < HOT_FILE_MIN) continue;
|
|
7100
7354
|
if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
|
|
7101
|
-
if (CONFIG_PATTERNS.some((cp) =>
|
|
7355
|
+
if (CONFIG_PATTERNS.some((cp) => path122.basename(p).includes(cp))) continue;
|
|
7102
7356
|
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
7103
7357
|
matches.push({
|
|
7104
7358
|
kind: "hot_file",
|
|
@@ -7138,17 +7392,17 @@ async function patternDetect(input, ctx) {
|
|
|
7138
7392
|
paths: match.anchor_paths,
|
|
7139
7393
|
status: "proposed"
|
|
7140
7394
|
});
|
|
7141
|
-
const file =
|
|
7395
|
+
const file = memoryFilePath6(
|
|
7142
7396
|
ctx.paths,
|
|
7143
7397
|
fm.scope === "shared" ? "team" : fm.scope,
|
|
7144
7398
|
fm.id,
|
|
7145
7399
|
void 0
|
|
7146
7400
|
);
|
|
7147
|
-
if (
|
|
7148
|
-
await
|
|
7149
|
-
await
|
|
7401
|
+
if (existsSync29(file)) continue;
|
|
7402
|
+
await mkdir82(path122.dirname(file), { recursive: true });
|
|
7403
|
+
await writeFile13(
|
|
7150
7404
|
file,
|
|
7151
|
-
|
|
7405
|
+
serializeMemory11({ frontmatter: fm, body: match.proposed_body }),
|
|
7152
7406
|
"utf8"
|
|
7153
7407
|
);
|
|
7154
7408
|
savedIds.push(fm.id);
|
|
@@ -7189,17 +7443,17 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
7189
7443
|
}
|
|
7190
7444
|
}
|
|
7191
7445
|
var MemConflictCandidatesInputSchema = {
|
|
7192
|
-
since_days:
|
|
7193
|
-
types:
|
|
7194
|
-
min_jaccard:
|
|
7195
|
-
max_pairs:
|
|
7196
|
-
max_scan:
|
|
7197
|
-
max_topic_pairs:
|
|
7446
|
+
since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
7447
|
+
types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
7448
|
+
min_jaccard: z32.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
7449
|
+
max_pairs: z32.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
7450
|
+
max_scan: z32.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
|
|
7451
|
+
max_topic_pairs: z32.number().int().positive().max(100).default(20).describe(
|
|
7198
7452
|
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
7199
7453
|
)
|
|
7200
7454
|
};
|
|
7201
7455
|
async function memConflictCandidates(input, ctx) {
|
|
7202
|
-
if (!
|
|
7456
|
+
if (!existsSync30(ctx.paths.memoriesDir)) {
|
|
7203
7457
|
return {
|
|
7204
7458
|
pairs: [],
|
|
7205
7459
|
topic_status_pairs: [],
|
|
@@ -7208,7 +7462,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
7208
7462
|
notice: "No .ai/memories directory."
|
|
7209
7463
|
};
|
|
7210
7464
|
}
|
|
7211
|
-
const all = await
|
|
7465
|
+
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
7212
7466
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
7213
7467
|
sinceDays: input.since_days,
|
|
7214
7468
|
types: input.types,
|
|
@@ -7221,7 +7475,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
7221
7475
|
return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
|
|
7222
7476
|
}
|
|
7223
7477
|
var MemResolveProjectInputSchema = {
|
|
7224
|
-
cwd:
|
|
7478
|
+
cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
7225
7479
|
};
|
|
7226
7480
|
async function memResolveProject(input, _ctx) {
|
|
7227
7481
|
void _ctx;
|
|
@@ -7234,7 +7488,7 @@ async function memResolveProject(input, _ctx) {
|
|
|
7234
7488
|
}
|
|
7235
7489
|
var MemSuggestTopicInputSchema = {
|
|
7236
7490
|
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
7237
|
-
title:
|
|
7491
|
+
title: z34.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
7238
7492
|
};
|
|
7239
7493
|
async function memSuggestTopic(input, _ctx) {
|
|
7240
7494
|
void _ctx;
|
|
@@ -7242,15 +7496,15 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
7242
7496
|
return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
|
|
7243
7497
|
}
|
|
7244
7498
|
var MemTimelineInputSchema = {
|
|
7245
|
-
memory_id:
|
|
7246
|
-
topic:
|
|
7247
|
-
limit:
|
|
7499
|
+
memory_id: z35.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
7500
|
+
topic: z35.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
7501
|
+
limit: z35.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
7248
7502
|
};
|
|
7249
7503
|
async function memTimeline(input, ctx) {
|
|
7250
|
-
if (!
|
|
7504
|
+
if (!existsSync31(ctx.paths.memoriesDir)) {
|
|
7251
7505
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
7252
7506
|
}
|
|
7253
|
-
const all = await
|
|
7507
|
+
const all = await loadMemoriesFromDir24(ctx.paths.memoriesDir);
|
|
7254
7508
|
const { entries, notice } = collectTimelineEntries(all, {
|
|
7255
7509
|
memoryId: input.memory_id,
|
|
7256
7510
|
topic: input.topic,
|
|
@@ -7259,9 +7513,9 @@ async function memTimeline(input, ctx) {
|
|
|
7259
7513
|
return { entries, total: entries.length, notice };
|
|
7260
7514
|
}
|
|
7261
7515
|
var RuntimeJournalAppendInputSchema = {
|
|
7262
|
-
message:
|
|
7263
|
-
kind:
|
|
7264
|
-
tool:
|
|
7516
|
+
message: z36.string().min(1).describe("Short line to append to the runtime session journal"),
|
|
7517
|
+
kind: z36.enum(["note", "session_end", "mcp"]).default("note"),
|
|
7518
|
+
tool: z36.string().optional().describe("When kind=mcp, which tool name (optional)")
|
|
7265
7519
|
};
|
|
7266
7520
|
async function runtimeJournalAppend(input, ctx) {
|
|
7267
7521
|
await appendRuntimeJournalEntry2(ctx.paths, {
|
|
@@ -7275,7 +7529,7 @@ async function runtimeJournalAppend(input, ctx) {
|
|
|
7275
7529
|
};
|
|
7276
7530
|
}
|
|
7277
7531
|
var RuntimeJournalTailInputSchema = {
|
|
7278
|
-
limit:
|
|
7532
|
+
limit: z37.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
|
|
7279
7533
|
};
|
|
7280
7534
|
async function runtimeJournalTail(input, ctx) {
|
|
7281
7535
|
const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
|
|
@@ -7285,10 +7539,10 @@ async function runtimeJournalTail(input, ctx) {
|
|
|
7285
7539
|
return { entries };
|
|
7286
7540
|
}
|
|
7287
7541
|
var BootstrapProjectArgsSchema = {
|
|
7288
|
-
module:
|
|
7542
|
+
module: z38.string().optional().describe(
|
|
7289
7543
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
7290
7544
|
),
|
|
7291
|
-
focus:
|
|
7545
|
+
focus: z38.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
7292
7546
|
};
|
|
7293
7547
|
var ROOT_TEMPLATE = `# Project context
|
|
7294
7548
|
|
|
@@ -7369,8 +7623,8 @@ ${template}\`\`\`
|
|
|
7369
7623
|
};
|
|
7370
7624
|
}
|
|
7371
7625
|
var PostTaskArgsSchema = {
|
|
7372
|
-
task_summary:
|
|
7373
|
-
files_touched:
|
|
7626
|
+
task_summary: z39.string().optional().describe("One sentence describing what you just did"),
|
|
7627
|
+
files_touched: z39.array(z39.string()).optional().describe("Files you created or modified during the task")
|
|
7374
7628
|
};
|
|
7375
7629
|
function postTaskPrompt(args, ctx) {
|
|
7376
7630
|
const taskLine = args.task_summary ? `
|
|
@@ -7466,10 +7720,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
7466
7720
|
};
|
|
7467
7721
|
}
|
|
7468
7722
|
var ImportDocsArgsSchema = {
|
|
7469
|
-
content:
|
|
7470
|
-
source:
|
|
7471
|
-
scope:
|
|
7472
|
-
dry_run:
|
|
7723
|
+
content: z40.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
7724
|
+
source: z40.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
7725
|
+
scope: z40.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
7726
|
+
dry_run: z40.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
7473
7727
|
};
|
|
7474
7728
|
function importDocsPrompt(args, ctx) {
|
|
7475
7729
|
const sourceLine = args.source ? `
|
|
@@ -7532,7 +7786,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7532
7786
|
};
|
|
7533
7787
|
}
|
|
7534
7788
|
var SERVER_NAME = "haive";
|
|
7535
|
-
var SERVER_VERSION = "0.12.
|
|
7789
|
+
var SERVER_VERSION = "0.12.9";
|
|
7536
7790
|
function jsonResult(data) {
|
|
7537
7791
|
return {
|
|
7538
7792
|
content: [
|
|
@@ -7575,7 +7829,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
|
|
|
7575
7829
|
"mem_distill",
|
|
7576
7830
|
"mem_timeline",
|
|
7577
7831
|
"mem_conflict_candidates",
|
|
7578
|
-
"mem_feedback"
|
|
7832
|
+
"mem_feedback",
|
|
7833
|
+
"ingest_findings"
|
|
7579
7834
|
];
|
|
7580
7835
|
var EXPERIMENTAL_PROFILE_TOOLS = [
|
|
7581
7836
|
...MAINTENANCE_PROFILE_TOOLS,
|
|
@@ -7609,7 +7864,8 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7609
7864
|
"mem_delete",
|
|
7610
7865
|
"mem_feedback",
|
|
7611
7866
|
"runtime_journal_append",
|
|
7612
|
-
"pattern_detect"
|
|
7867
|
+
"pattern_detect",
|
|
7868
|
+
"ingest_findings"
|
|
7613
7869
|
]);
|
|
7614
7870
|
function createHaiveServer(options = {}) {
|
|
7615
7871
|
const context = createContext(options);
|
|
@@ -7734,6 +7990,36 @@ function createHaiveServer(options = {}) {
|
|
|
7734
7990
|
return jsonResult(await memTried(input, context));
|
|
7735
7991
|
}
|
|
7736
7992
|
);
|
|
7993
|
+
registerTool(
|
|
7994
|
+
"ingest_findings",
|
|
7995
|
+
[
|
|
7996
|
+
"Turn scanner findings (SonarQube / SARIF) into proposed, anchored memories with sensors.",
|
|
7997
|
+
"",
|
|
7998
|
+
"USE THIS to seed hAIve from your existing quality tooling: each real defect a scanner",
|
|
7999
|
+
"found becomes a `gotcha`/`convention` memory anchored to the file, pre-filled with a",
|
|
8000
|
+
"conservative `warn` sensor \u2014 so the next agent is steered away from it before re-writing it.",
|
|
8001
|
+
"This closes the review\u2194memory loop and kills the cold-start problem.",
|
|
8002
|
+
"",
|
|
8003
|
+
"SAFETY: drafts are status=proposed and sensors are warn-only + autogen. This tool NEVER",
|
|
8004
|
+
"auto-validates and NEVER auto-blocks. A human reviews (mem_pending) and promotes the sensor.",
|
|
8005
|
+
"",
|
|
8006
|
+
"PARAMETERS:",
|
|
8007
|
+
" format \u2014 'sarif' (ESLint/Semgrep/CodeQL) | 'sonar' (SonarQube issues JSON)",
|
|
8008
|
+
" report_path \u2014 project-relative path to the report file (OR pass `report` inline)",
|
|
8009
|
+
" report \u2014 inline JSON content (OR pass `report_path`)",
|
|
8010
|
+
" type \u2014 gotcha (default) | convention",
|
|
8011
|
+
" scope \u2014 team (default) | personal | module",
|
|
8012
|
+
" min_severity \u2014 drop findings below this severity",
|
|
8013
|
+
" dry_run \u2014 preview what would be created without writing",
|
|
8014
|
+
"",
|
|
8015
|
+
"RETURNS: { format, parsed, new, skipped_existing, created[], notice }"
|
|
8016
|
+
].join("\n"),
|
|
8017
|
+
IngestFindingsInputSchema,
|
|
8018
|
+
async (input) => {
|
|
8019
|
+
tracker.record("ingest_findings", `${input.format}:${input.report_path ?? "inline"}`);
|
|
8020
|
+
return jsonResult(await ingestFindings(input, context));
|
|
8021
|
+
}
|
|
8022
|
+
);
|
|
7737
8023
|
registerTool(
|
|
7738
8024
|
"mem_observe",
|
|
7739
8025
|
[
|
|
@@ -8530,8 +8816,8 @@ function registerMcp(program2) {
|
|
|
8530
8816
|
|
|
8531
8817
|
// src/commands/sync.ts
|
|
8532
8818
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
8533
|
-
import { readFile as readFile9, writeFile as
|
|
8534
|
-
import { existsSync as
|
|
8819
|
+
import { readFile as readFile9, writeFile as writeFile14, mkdir as mkdir10 } from "fs/promises";
|
|
8820
|
+
import { existsSync as existsSync33 } from "fs";
|
|
8535
8821
|
import path15 from "path";
|
|
8536
8822
|
import "commander";
|
|
8537
8823
|
import {
|
|
@@ -8544,12 +8830,12 @@ import {
|
|
|
8544
8830
|
isStackPackSeed as isStackPackSeed3,
|
|
8545
8831
|
loadCodeMap as loadCodeMap6,
|
|
8546
8832
|
loadConfig as loadConfig4,
|
|
8547
|
-
loadMemoriesFromDir as
|
|
8833
|
+
loadMemoriesFromDir as loadMemoriesFromDir25,
|
|
8548
8834
|
loadUsageIndex as loadUsageIndex13,
|
|
8549
8835
|
pullCrossRepoSources,
|
|
8550
8836
|
resolveHaivePaths as resolveHaivePaths9,
|
|
8551
8837
|
resolveManifestFiles,
|
|
8552
|
-
serializeMemory as
|
|
8838
|
+
serializeMemory as serializeMemory12,
|
|
8553
8839
|
trackDependencies,
|
|
8554
8840
|
verifyAnchor as verifyAnchor2,
|
|
8555
8841
|
watchContracts
|
|
@@ -8564,11 +8850,11 @@ function registerSync(program2) {
|
|
|
8564
8850
|
"git ref/commit to compare against; report memories added/modified/removed since"
|
|
8565
8851
|
).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
|
|
8566
8852
|
"--inject-bridge",
|
|
8567
|
-
"inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
8853
|
+
"inject top validated memories into CLAUDE.md + AGENTS.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
8568
8854
|
).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) => {
|
|
8569
8855
|
const root = findProjectRoot12(opts.dir);
|
|
8570
8856
|
const paths = resolveHaivePaths9(root);
|
|
8571
|
-
if (!
|
|
8857
|
+
if (!existsSync33(paths.memoriesDir)) {
|
|
8572
8858
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8573
8859
|
process.exitCode = 1;
|
|
8574
8860
|
return;
|
|
@@ -8587,14 +8873,14 @@ function registerSync(program2) {
|
|
|
8587
8873
|
let promoted = 0;
|
|
8588
8874
|
let autoApproved = 0;
|
|
8589
8875
|
if (opts.verify !== false) {
|
|
8590
|
-
const memories = await
|
|
8876
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8591
8877
|
for (const { memory: memory2, filePath } of memories) {
|
|
8592
8878
|
if (memory2.frontmatter.type === "session_recap") {
|
|
8593
8879
|
if (memory2.frontmatter.status === "stale") {
|
|
8594
8880
|
if (!dryRun) {
|
|
8595
|
-
await
|
|
8881
|
+
await writeFile14(
|
|
8596
8882
|
filePath,
|
|
8597
|
-
|
|
8883
|
+
serializeMemory12({
|
|
8598
8884
|
frontmatter: {
|
|
8599
8885
|
...memory2.frontmatter,
|
|
8600
8886
|
status: "validated",
|
|
@@ -8617,9 +8903,9 @@ function registerSync(program2) {
|
|
|
8617
8903
|
if (result.stale) {
|
|
8618
8904
|
if (memory2.frontmatter.status !== "stale") {
|
|
8619
8905
|
if (!dryRun) {
|
|
8620
|
-
await
|
|
8906
|
+
await writeFile14(
|
|
8621
8907
|
filePath,
|
|
8622
|
-
|
|
8908
|
+
serializeMemory12({
|
|
8623
8909
|
frontmatter: {
|
|
8624
8910
|
...memory2.frontmatter,
|
|
8625
8911
|
status: "stale",
|
|
@@ -8635,9 +8921,9 @@ function registerSync(program2) {
|
|
|
8635
8921
|
}
|
|
8636
8922
|
} else if (memory2.frontmatter.status === "stale") {
|
|
8637
8923
|
if (!dryRun) {
|
|
8638
|
-
await
|
|
8924
|
+
await writeFile14(
|
|
8639
8925
|
filePath,
|
|
8640
|
-
|
|
8926
|
+
serializeMemory12({
|
|
8641
8927
|
frontmatter: {
|
|
8642
8928
|
...memory2.frontmatter,
|
|
8643
8929
|
status: "validated",
|
|
@@ -8654,7 +8940,7 @@ function registerSync(program2) {
|
|
|
8654
8940
|
}
|
|
8655
8941
|
}
|
|
8656
8942
|
if (opts.promote !== false) {
|
|
8657
|
-
const memories = await
|
|
8943
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8658
8944
|
const usage = await loadUsageIndex13(paths);
|
|
8659
8945
|
const nowMs = Date.now();
|
|
8660
8946
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -8665,9 +8951,9 @@ function registerSync(program2) {
|
|
|
8665
8951
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
|
|
8666
8952
|
})) {
|
|
8667
8953
|
if (!dryRun) {
|
|
8668
|
-
await
|
|
8954
|
+
await writeFile14(
|
|
8669
8955
|
filePath,
|
|
8670
|
-
|
|
8956
|
+
serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
8671
8957
|
"utf8"
|
|
8672
8958
|
);
|
|
8673
8959
|
}
|
|
@@ -8678,9 +8964,9 @@ function registerSync(program2) {
|
|
|
8678
8964
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
8679
8965
|
if (ageHours >= autoApproveDelayHours) {
|
|
8680
8966
|
if (!dryRun) {
|
|
8681
|
-
await
|
|
8967
|
+
await writeFile14(
|
|
8682
8968
|
filePath,
|
|
8683
|
-
|
|
8969
|
+
serializeMemory12({
|
|
8684
8970
|
frontmatter: {
|
|
8685
8971
|
...fm,
|
|
8686
8972
|
status: "validated",
|
|
@@ -8706,7 +8992,7 @@ function registerSync(program2) {
|
|
|
8706
8992
|
for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
|
|
8707
8993
|
}
|
|
8708
8994
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
8709
|
-
const draftMemories = (await
|
|
8995
|
+
const draftMemories = (await loadMemoriesFromDir25(paths.memoriesDir)).filter(
|
|
8710
8996
|
(m) => m.memory.frontmatter.status === "draft"
|
|
8711
8997
|
);
|
|
8712
8998
|
const draftCount = draftMemories.length;
|
|
@@ -8722,9 +9008,18 @@ function registerSync(program2) {
|
|
|
8722
9008
|
);
|
|
8723
9009
|
}
|
|
8724
9010
|
if (opts.injectBridge) {
|
|
8725
|
-
const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
|
|
8726
9011
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
8727
|
-
|
|
9012
|
+
let bridgeTargets;
|
|
9013
|
+
if (opts.bridgeFile) {
|
|
9014
|
+
bridgeTargets = [path15.resolve(opts.bridgeFile)];
|
|
9015
|
+
} else {
|
|
9016
|
+
const agentsMd = path15.join(root, "AGENTS.md");
|
|
9017
|
+
bridgeTargets = [path15.join(root, "CLAUDE.md")];
|
|
9018
|
+
if (existsSync33(agentsMd)) bridgeTargets.push(agentsMd);
|
|
9019
|
+
}
|
|
9020
|
+
for (const bridgeFile of bridgeTargets) {
|
|
9021
|
+
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
9022
|
+
}
|
|
8728
9023
|
}
|
|
8729
9024
|
if (sinceReport && !opts.quiet) {
|
|
8730
9025
|
if (sinceReport.added.length > 0) {
|
|
@@ -8741,7 +9036,7 @@ function registerSync(program2) {
|
|
|
8741
9036
|
}
|
|
8742
9037
|
}
|
|
8743
9038
|
if (!opts.quiet) {
|
|
8744
|
-
const allForDecay = await
|
|
9039
|
+
const allForDecay = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8745
9040
|
const usageForDecay = await loadUsageIndex13(paths);
|
|
8746
9041
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
8747
9042
|
const fm = memory2.frontmatter;
|
|
@@ -8831,9 +9126,9 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
8831
9126
|
if (!dryRun) {
|
|
8832
9127
|
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
8833
9128
|
await mkdir10(teamDir, { recursive: true });
|
|
8834
|
-
await
|
|
9129
|
+
await writeFile14(
|
|
8835
9130
|
path15.join(teamDir, `${fm.id}.md`),
|
|
8836
|
-
|
|
9131
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
8837
9132
|
"utf8"
|
|
8838
9133
|
);
|
|
8839
9134
|
}
|
|
@@ -8900,9 +9195,9 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
8900
9195
|
if (!dryRun) {
|
|
8901
9196
|
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
8902
9197
|
await mkdir10(teamDir, { recursive: true });
|
|
8903
|
-
await
|
|
9198
|
+
await writeFile14(
|
|
8904
9199
|
path15.join(teamDir, `${fm.id}.md`),
|
|
8905
|
-
|
|
9200
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
8906
9201
|
"utf8"
|
|
8907
9202
|
);
|
|
8908
9203
|
}
|
|
@@ -8992,8 +9287,8 @@ function bridgeSummaryLine(body) {
|
|
|
8992
9287
|
return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
|
|
8993
9288
|
}
|
|
8994
9289
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
8995
|
-
if (!
|
|
8996
|
-
const all = await
|
|
9290
|
+
if (!existsSync33(memoriesDir)) return;
|
|
9291
|
+
const all = await loadMemoriesFromDir25(memoriesDir);
|
|
8997
9292
|
const top = all.filter(({ memory: memory2 }) => {
|
|
8998
9293
|
const s = memory2.frontmatter.status;
|
|
8999
9294
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
@@ -9018,7 +9313,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
|
9018
9313
|
` + block + `
|
|
9019
9314
|
|
|
9020
9315
|
${BRIDGE_END}`;
|
|
9021
|
-
const fileExists =
|
|
9316
|
+
const fileExists = existsSync33(bridgeFile);
|
|
9022
9317
|
let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
|
|
9023
9318
|
existing = existing.replace(/\r\n/g, "\n");
|
|
9024
9319
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
@@ -9040,7 +9335,7 @@ ${BRIDGE_END}`;
|
|
|
9040
9335
|
}
|
|
9041
9336
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
9042
9337
|
}
|
|
9043
|
-
await
|
|
9338
|
+
await writeFile14(bridgeFile, updated, "utf8");
|
|
9044
9339
|
if (!quiet) {
|
|
9045
9340
|
console.log(
|
|
9046
9341
|
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
|
|
@@ -9068,8 +9363,8 @@ function collectSinceChanges(root, ref) {
|
|
|
9068
9363
|
|
|
9069
9364
|
// src/commands/memory-add.ts
|
|
9070
9365
|
import { createHash as createHash2 } from "crypto";
|
|
9071
|
-
import { mkdir as mkdir11, readFile as readFile10, writeFile as
|
|
9072
|
-
import { existsSync as
|
|
9366
|
+
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile15 } from "fs/promises";
|
|
9367
|
+
import { existsSync as existsSync34 } from "fs";
|
|
9073
9368
|
import path16 from "path";
|
|
9074
9369
|
import "commander";
|
|
9075
9370
|
import {
|
|
@@ -9077,10 +9372,10 @@ import {
|
|
|
9077
9372
|
findProjectRoot as findProjectRoot13,
|
|
9078
9373
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
9079
9374
|
loadConfig as loadConfig5,
|
|
9080
|
-
loadMemoriesFromDir as
|
|
9081
|
-
memoryFilePath as
|
|
9375
|
+
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
9376
|
+
memoryFilePath as memoryFilePath7,
|
|
9082
9377
|
resolveHaivePaths as resolveHaivePaths10,
|
|
9083
|
-
serializeMemory as
|
|
9378
|
+
serializeMemory as serializeMemory13,
|
|
9084
9379
|
suggestSensorFromMemory as suggestSensorFromMemory3
|
|
9085
9380
|
} from "@hiveai/core";
|
|
9086
9381
|
function registerMemoryAdd(memory2) {
|
|
@@ -9111,7 +9406,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9111
9406
|
).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) => {
|
|
9112
9407
|
const root = findProjectRoot13(opts.dir);
|
|
9113
9408
|
const paths = resolveHaivePaths10(root);
|
|
9114
|
-
if (!
|
|
9409
|
+
if (!existsSync34(paths.haiveDir)) {
|
|
9115
9410
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9116
9411
|
process.exitCode = 1;
|
|
9117
9412
|
return;
|
|
@@ -9128,7 +9423,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9128
9423
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
9129
9424
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
9130
9425
|
if (anchorPaths.length > 0) {
|
|
9131
|
-
const missing = anchorPaths.filter((p) => !
|
|
9426
|
+
const missing = anchorPaths.filter((p) => !existsSync34(path16.resolve(root, p)));
|
|
9132
9427
|
if (missing.length > 0) {
|
|
9133
9428
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
9134
9429
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -9141,7 +9436,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9141
9436
|
const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
|
|
9142
9437
|
let body;
|
|
9143
9438
|
if (opts.bodyFile !== void 0) {
|
|
9144
|
-
if (!
|
|
9439
|
+
if (!existsSync34(opts.bodyFile)) {
|
|
9145
9440
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9146
9441
|
process.exitCode = 1;
|
|
9147
9442
|
return;
|
|
@@ -9157,9 +9452,9 @@ TODO \u2014 write the memory body.
|
|
|
9157
9452
|
`;
|
|
9158
9453
|
}
|
|
9159
9454
|
const scope = opts.scope ?? config.defaultScope ?? "personal";
|
|
9160
|
-
if (
|
|
9455
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
9161
9456
|
const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
9162
|
-
const allForHash = await
|
|
9457
|
+
const allForHash = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9163
9458
|
const hashDup = allForHash.find(
|
|
9164
9459
|
({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
|
|
9165
9460
|
);
|
|
@@ -9170,8 +9465,8 @@ TODO \u2014 write the memory body.
|
|
|
9170
9465
|
return;
|
|
9171
9466
|
}
|
|
9172
9467
|
}
|
|
9173
|
-
if (opts.topic &&
|
|
9174
|
-
const existing = await
|
|
9468
|
+
if (opts.topic && existsSync34(paths.memoriesDir)) {
|
|
9469
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9175
9470
|
const topicMatch = existing.find(
|
|
9176
9471
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
9177
9472
|
);
|
|
@@ -9191,7 +9486,7 @@ TODO \u2014 write the memory body.
|
|
|
9191
9486
|
};
|
|
9192
9487
|
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
9193
9488
|
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
9194
|
-
await
|
|
9489
|
+
await writeFile15(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
|
|
9195
9490
|
ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
|
|
9196
9491
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
9197
9492
|
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
@@ -9215,15 +9510,15 @@ TODO \u2014 write the memory body.
|
|
|
9215
9510
|
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
|
|
9216
9511
|
activation
|
|
9217
9512
|
});
|
|
9218
|
-
const file =
|
|
9513
|
+
const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9219
9514
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
9220
|
-
if (
|
|
9515
|
+
if (existsSync34(file)) {
|
|
9221
9516
|
ui.error(`Memory already exists at ${file}`);
|
|
9222
9517
|
process.exitCode = 1;
|
|
9223
9518
|
return;
|
|
9224
9519
|
}
|
|
9225
|
-
if (
|
|
9226
|
-
const existing = await
|
|
9520
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
9521
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9227
9522
|
const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
9228
9523
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
9229
9524
|
const id = memory3.frontmatter.id.toLowerCase();
|
|
@@ -9234,7 +9529,7 @@ TODO \u2014 write the memory body.
|
|
|
9234
9529
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
9235
9530
|
}
|
|
9236
9531
|
}
|
|
9237
|
-
await
|
|
9532
|
+
await writeFile15(file, serializeMemory13({ frontmatter, body }), "utf8");
|
|
9238
9533
|
ui.success(`Created ${path16.relative(root, file)}`);
|
|
9239
9534
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
9240
9535
|
if (frontmatter.sensor?.autogen) {
|
|
@@ -9314,14 +9609,14 @@ function slugify(value) {
|
|
|
9314
9609
|
}
|
|
9315
9610
|
|
|
9316
9611
|
// src/commands/memory-list.ts
|
|
9317
|
-
import { existsSync as
|
|
9612
|
+
import { existsSync as existsSync35 } from "fs";
|
|
9318
9613
|
import path17 from "path";
|
|
9319
9614
|
import "commander";
|
|
9320
9615
|
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
9321
9616
|
|
|
9322
9617
|
// src/utils/fs.ts
|
|
9323
9618
|
import {
|
|
9324
|
-
loadMemoriesFromDir as
|
|
9619
|
+
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
9325
9620
|
loadMemory,
|
|
9326
9621
|
listMarkdownFilesRecursive
|
|
9327
9622
|
} from "@hiveai/core";
|
|
@@ -9331,12 +9626,12 @@ function registerMemoryList(memory2) {
|
|
|
9331
9626
|
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) => {
|
|
9332
9627
|
const root = findProjectRoot14(opts.dir);
|
|
9333
9628
|
const paths = resolveHaivePaths11(root);
|
|
9334
|
-
if (!
|
|
9629
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
9335
9630
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9336
9631
|
process.exitCode = 1;
|
|
9337
9632
|
return;
|
|
9338
9633
|
}
|
|
9339
|
-
const all = await
|
|
9634
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9340
9635
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
9341
9636
|
const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
|
|
9342
9637
|
const filtered = all.filter((m) => {
|
|
@@ -9405,26 +9700,26 @@ function matchesFilters(loaded, opts) {
|
|
|
9405
9700
|
}
|
|
9406
9701
|
|
|
9407
9702
|
// src/commands/memory-promote.ts
|
|
9408
|
-
import { mkdir as mkdir12, unlink as unlink2, writeFile as
|
|
9409
|
-
import { existsSync as
|
|
9703
|
+
import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile16 } from "fs/promises";
|
|
9704
|
+
import { existsSync as existsSync36 } from "fs";
|
|
9410
9705
|
import path18 from "path";
|
|
9411
9706
|
import "commander";
|
|
9412
9707
|
import {
|
|
9413
9708
|
findProjectRoot as findProjectRoot15,
|
|
9414
|
-
memoryFilePath as
|
|
9709
|
+
memoryFilePath as memoryFilePath8,
|
|
9415
9710
|
resolveHaivePaths as resolveHaivePaths12,
|
|
9416
|
-
serializeMemory as
|
|
9711
|
+
serializeMemory as serializeMemory14
|
|
9417
9712
|
} from "@hiveai/core";
|
|
9418
9713
|
function registerMemoryPromote(memory2) {
|
|
9419
9714
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9420
9715
|
const root = findProjectRoot15(opts.dir);
|
|
9421
9716
|
const paths = resolveHaivePaths12(root);
|
|
9422
|
-
if (!
|
|
9717
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
9423
9718
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9424
9719
|
process.exitCode = 1;
|
|
9425
9720
|
return;
|
|
9426
9721
|
}
|
|
9427
|
-
const teamAndModule = await
|
|
9722
|
+
const teamAndModule = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9428
9723
|
const alreadyShared = teamAndModule.find(
|
|
9429
9724
|
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
9430
9725
|
);
|
|
@@ -9438,7 +9733,7 @@ function registerMemoryPromote(memory2) {
|
|
|
9438
9733
|
}
|
|
9439
9734
|
return;
|
|
9440
9735
|
}
|
|
9441
|
-
const all = await
|
|
9736
|
+
const all = await loadMemoriesFromDir27(paths.personalDir);
|
|
9442
9737
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9443
9738
|
if (!found) {
|
|
9444
9739
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -9453,9 +9748,9 @@ function registerMemoryPromote(memory2) {
|
|
|
9453
9748
|
},
|
|
9454
9749
|
body: found.memory.body
|
|
9455
9750
|
};
|
|
9456
|
-
const newPath =
|
|
9751
|
+
const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
|
|
9457
9752
|
await mkdir12(path18.dirname(newPath), { recursive: true });
|
|
9458
|
-
await
|
|
9753
|
+
await writeFile16(newPath, serializeMemory14(updated), "utf8");
|
|
9459
9754
|
await unlink2(found.filePath);
|
|
9460
9755
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
9461
9756
|
ui.info(`Now at ${path18.relative(root, newPath)}`);
|
|
@@ -9464,25 +9759,25 @@ function registerMemoryPromote(memory2) {
|
|
|
9464
9759
|
}
|
|
9465
9760
|
|
|
9466
9761
|
// src/commands/memory-approve.ts
|
|
9467
|
-
import { existsSync as
|
|
9468
|
-
import { writeFile as
|
|
9762
|
+
import { existsSync as existsSync37 } from "fs";
|
|
9763
|
+
import { writeFile as writeFile17 } from "fs/promises";
|
|
9469
9764
|
import path19 from "path";
|
|
9470
9765
|
import "commander";
|
|
9471
9766
|
import {
|
|
9472
9767
|
findProjectRoot as findProjectRoot16,
|
|
9473
9768
|
resolveHaivePaths as resolveHaivePaths13,
|
|
9474
|
-
serializeMemory as
|
|
9769
|
+
serializeMemory as serializeMemory15
|
|
9475
9770
|
} from "@hiveai/core";
|
|
9476
9771
|
function registerMemoryApprove(memory2) {
|
|
9477
9772
|
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) => {
|
|
9478
9773
|
const root = findProjectRoot16(opts.dir);
|
|
9479
9774
|
const paths = resolveHaivePaths13(root);
|
|
9480
|
-
if (!
|
|
9775
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
9481
9776
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9482
9777
|
process.exitCode = 1;
|
|
9483
9778
|
return;
|
|
9484
9779
|
}
|
|
9485
|
-
const all = await
|
|
9780
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9486
9781
|
if (opts.all || opts.pending) {
|
|
9487
9782
|
const candidates = all.filter((m) => {
|
|
9488
9783
|
const s = m.memory.frontmatter.status;
|
|
@@ -9499,7 +9794,7 @@ function registerMemoryApprove(memory2) {
|
|
|
9499
9794
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
9500
9795
|
body: found2.memory.body
|
|
9501
9796
|
};
|
|
9502
|
-
await
|
|
9797
|
+
await writeFile17(found2.filePath, serializeMemory15(next2), "utf8");
|
|
9503
9798
|
count++;
|
|
9504
9799
|
}
|
|
9505
9800
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -9528,32 +9823,32 @@ function registerMemoryApprove(memory2) {
|
|
|
9528
9823
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
9529
9824
|
body: found.memory.body
|
|
9530
9825
|
};
|
|
9531
|
-
await
|
|
9826
|
+
await writeFile17(found.filePath, serializeMemory15(next), "utf8");
|
|
9532
9827
|
ui.success(`Approved ${id} (status=validated)`);
|
|
9533
9828
|
ui.info(path19.relative(root, found.filePath));
|
|
9534
9829
|
});
|
|
9535
9830
|
}
|
|
9536
9831
|
|
|
9537
9832
|
// src/commands/memory-update.ts
|
|
9538
|
-
import { readFile as readFile11, writeFile as
|
|
9539
|
-
import { existsSync as
|
|
9833
|
+
import { readFile as readFile11, writeFile as writeFile18 } from "fs/promises";
|
|
9834
|
+
import { existsSync as existsSync38 } from "fs";
|
|
9540
9835
|
import path20 from "path";
|
|
9541
9836
|
import "commander";
|
|
9542
9837
|
import {
|
|
9543
9838
|
findProjectRoot as findProjectRoot17,
|
|
9544
9839
|
resolveHaivePaths as resolveHaivePaths14,
|
|
9545
|
-
serializeMemory as
|
|
9840
|
+
serializeMemory as serializeMemory16
|
|
9546
9841
|
} from "@hiveai/core";
|
|
9547
9842
|
function registerMemoryUpdate(memory2) {
|
|
9548
9843
|
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) => {
|
|
9549
9844
|
const root = findProjectRoot17(opts.dir);
|
|
9550
9845
|
const paths = resolveHaivePaths14(root);
|
|
9551
|
-
if (!
|
|
9846
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
9552
9847
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9553
9848
|
process.exitCode = 1;
|
|
9554
9849
|
return;
|
|
9555
9850
|
}
|
|
9556
|
-
const memories = await
|
|
9851
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9557
9852
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
9558
9853
|
if (!loaded) {
|
|
9559
9854
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9590,7 +9885,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
9590
9885
|
if (opts.author !== void 0) updated.push("author");
|
|
9591
9886
|
let newBody;
|
|
9592
9887
|
if (opts.bodyFile !== void 0) {
|
|
9593
|
-
if (!
|
|
9888
|
+
if (!existsSync38(opts.bodyFile)) {
|
|
9594
9889
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9595
9890
|
process.exitCode = 1;
|
|
9596
9891
|
return;
|
|
@@ -9611,9 +9906,9 @@ function registerMemoryUpdate(memory2) {
|
|
|
9611
9906
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
9612
9907
|
return;
|
|
9613
9908
|
}
|
|
9614
|
-
await
|
|
9909
|
+
await writeFile18(
|
|
9615
9910
|
loaded.filePath,
|
|
9616
|
-
|
|
9911
|
+
serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
|
|
9617
9912
|
"utf8"
|
|
9618
9913
|
);
|
|
9619
9914
|
ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
|
|
@@ -9635,8 +9930,8 @@ function parseCsv3(value) {
|
|
|
9635
9930
|
}
|
|
9636
9931
|
|
|
9637
9932
|
// src/commands/memory-auto-promote.ts
|
|
9638
|
-
import { writeFile as
|
|
9639
|
-
import { existsSync as
|
|
9933
|
+
import { writeFile as writeFile19 } from "fs/promises";
|
|
9934
|
+
import { existsSync as existsSync39 } from "fs";
|
|
9640
9935
|
import path21 from "path";
|
|
9641
9936
|
import "commander";
|
|
9642
9937
|
import {
|
|
@@ -9646,7 +9941,7 @@ import {
|
|
|
9646
9941
|
isAutoPromoteEligible as isAutoPromoteEligible3,
|
|
9647
9942
|
loadUsageIndex as loadUsageIndex14,
|
|
9648
9943
|
resolveHaivePaths as resolveHaivePaths15,
|
|
9649
|
-
serializeMemory as
|
|
9944
|
+
serializeMemory as serializeMemory17
|
|
9650
9945
|
} from "@hiveai/core";
|
|
9651
9946
|
function registerMemoryAutoPromote(memory2) {
|
|
9652
9947
|
memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE3.minReads)).option(
|
|
@@ -9656,7 +9951,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9656
9951
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9657
9952
|
const root = findProjectRoot18(opts.dir);
|
|
9658
9953
|
const paths = resolveHaivePaths15(root);
|
|
9659
|
-
if (!
|
|
9954
|
+
if (!existsSync39(paths.memoriesDir)) {
|
|
9660
9955
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9661
9956
|
process.exitCode = 1;
|
|
9662
9957
|
return;
|
|
@@ -9665,7 +9960,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9665
9960
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
|
|
9666
9961
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
9667
9962
|
};
|
|
9668
|
-
const memories = await
|
|
9963
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9669
9964
|
const usage = await loadUsageIndex14(paths);
|
|
9670
9965
|
const eligible = memories.filter(
|
|
9671
9966
|
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
|
|
@@ -9688,7 +9983,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9688
9983
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
9689
9984
|
body: mem.body
|
|
9690
9985
|
};
|
|
9691
|
-
await
|
|
9986
|
+
await writeFile19(filePath, serializeMemory17(next), "utf8");
|
|
9692
9987
|
written++;
|
|
9693
9988
|
}
|
|
9694
9989
|
}
|
|
@@ -9699,7 +9994,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9699
9994
|
|
|
9700
9995
|
// src/commands/memory-edit.ts
|
|
9701
9996
|
import { spawn as spawn3 } from "child_process";
|
|
9702
|
-
import { existsSync as
|
|
9997
|
+
import { existsSync as existsSync40 } from "fs";
|
|
9703
9998
|
import { readFile as readFile12 } from "fs/promises";
|
|
9704
9999
|
import path23 from "path";
|
|
9705
10000
|
import "commander";
|
|
@@ -9712,12 +10007,12 @@ function registerMemoryEdit(memory2) {
|
|
|
9712
10007
|
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) => {
|
|
9713
10008
|
const root = findProjectRoot19(opts.dir);
|
|
9714
10009
|
const paths = resolveHaivePaths16(root);
|
|
9715
|
-
if (!
|
|
10010
|
+
if (!existsSync40(paths.memoriesDir)) {
|
|
9716
10011
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9717
10012
|
process.exitCode = 1;
|
|
9718
10013
|
return;
|
|
9719
10014
|
}
|
|
9720
|
-
const all = await
|
|
10015
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9721
10016
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9722
10017
|
if (!found) {
|
|
9723
10018
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9752,7 +10047,7 @@ function runEditor(editor, file) {
|
|
|
9752
10047
|
}
|
|
9753
10048
|
|
|
9754
10049
|
// src/commands/memory-for-files.ts
|
|
9755
|
-
import { existsSync as
|
|
10050
|
+
import { existsSync as existsSync41 } from "fs";
|
|
9756
10051
|
import path24 from "path";
|
|
9757
10052
|
import "commander";
|
|
9758
10053
|
import {
|
|
@@ -9768,12 +10063,12 @@ function registerMemoryForFiles(memory2) {
|
|
|
9768
10063
|
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) => {
|
|
9769
10064
|
const root = findProjectRoot20(opts.dir);
|
|
9770
10065
|
const paths = resolveHaivePaths17(root);
|
|
9771
|
-
if (!
|
|
10066
|
+
if (!existsSync41(paths.memoriesDir)) {
|
|
9772
10067
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9773
10068
|
process.exitCode = 1;
|
|
9774
10069
|
return;
|
|
9775
10070
|
}
|
|
9776
|
-
const all = await
|
|
10071
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9777
10072
|
const usage = await loadUsageIndex15(paths);
|
|
9778
10073
|
const inferred = inferModulesFromPaths4(files);
|
|
9779
10074
|
const byAnchor = [];
|
|
@@ -9880,7 +10175,7 @@ function printGroup(root, label, loaded, usage) {
|
|
|
9880
10175
|
}
|
|
9881
10176
|
|
|
9882
10177
|
// src/commands/memory-hot.ts
|
|
9883
|
-
import { existsSync as
|
|
10178
|
+
import { existsSync as existsSync43 } from "fs";
|
|
9884
10179
|
import path25 from "path";
|
|
9885
10180
|
import "commander";
|
|
9886
10181
|
import {
|
|
@@ -9895,13 +10190,13 @@ function registerMemoryHot(memory2) {
|
|
|
9895
10190
|
).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) => {
|
|
9896
10191
|
const root = findProjectRoot21(opts.dir);
|
|
9897
10192
|
const paths = resolveHaivePaths18(root);
|
|
9898
|
-
if (!
|
|
10193
|
+
if (!existsSync43(paths.memoriesDir)) {
|
|
9899
10194
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9900
10195
|
process.exitCode = 1;
|
|
9901
10196
|
return;
|
|
9902
10197
|
}
|
|
9903
10198
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
9904
|
-
const all = await
|
|
10199
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9905
10200
|
const usage = await loadUsageIndex16(paths);
|
|
9906
10201
|
const candidates = all.filter(({ memory: mem }) => {
|
|
9907
10202
|
const fm = mem.frontmatter;
|
|
@@ -9933,16 +10228,16 @@ function registerMemoryHot(memory2) {
|
|
|
9933
10228
|
}
|
|
9934
10229
|
|
|
9935
10230
|
// src/commands/memory-tried.ts
|
|
9936
|
-
import { mkdir as mkdir13, writeFile as
|
|
9937
|
-
import { existsSync as
|
|
10231
|
+
import { mkdir as mkdir13, writeFile as writeFile20 } from "fs/promises";
|
|
10232
|
+
import { existsSync as existsSync44 } from "fs";
|
|
9938
10233
|
import path26 from "path";
|
|
9939
10234
|
import "commander";
|
|
9940
10235
|
import {
|
|
9941
10236
|
buildFrontmatter as buildFrontmatter8,
|
|
9942
10237
|
findProjectRoot as findProjectRoot22,
|
|
9943
|
-
memoryFilePath as
|
|
10238
|
+
memoryFilePath as memoryFilePath9,
|
|
9944
10239
|
resolveHaivePaths as resolveHaivePaths19,
|
|
9945
|
-
serializeMemory as
|
|
10240
|
+
serializeMemory as serializeMemory18,
|
|
9946
10241
|
suggestSensorFromMemory as suggestSensorFromMemory4
|
|
9947
10242
|
} from "@hiveai/core";
|
|
9948
10243
|
function registerMemoryTried(memory2) {
|
|
@@ -9964,7 +10259,7 @@ function registerMemoryTried(memory2) {
|
|
|
9964
10259
|
).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) => {
|
|
9965
10260
|
const root = findProjectRoot22(opts.dir);
|
|
9966
10261
|
const paths = resolveHaivePaths19(root);
|
|
9967
|
-
if (!
|
|
10262
|
+
if (!existsSync44(paths.haiveDir)) {
|
|
9968
10263
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9969
10264
|
process.exitCode = 1;
|
|
9970
10265
|
return;
|
|
@@ -9990,14 +10285,14 @@ function registerMemoryTried(memory2) {
|
|
|
9990
10285
|
if (sensor) {
|
|
9991
10286
|
frontmatter.sensor = sensor;
|
|
9992
10287
|
}
|
|
9993
|
-
const file =
|
|
10288
|
+
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9994
10289
|
await mkdir13(path26.dirname(file), { recursive: true });
|
|
9995
|
-
if (
|
|
10290
|
+
if (existsSync44(file)) {
|
|
9996
10291
|
ui.error(`Memory already exists at ${file}`);
|
|
9997
10292
|
process.exitCode = 1;
|
|
9998
10293
|
return;
|
|
9999
10294
|
}
|
|
10000
|
-
await
|
|
10295
|
+
await writeFile20(file, serializeMemory18({ frontmatter, body }), "utf8");
|
|
10001
10296
|
ui.success(`Recorded: ${path26.relative(root, file)}`);
|
|
10002
10297
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
10003
10298
|
if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
@@ -10010,7 +10305,7 @@ function parseCsv4(value) {
|
|
|
10010
10305
|
|
|
10011
10306
|
// src/commands/memory-seed.ts
|
|
10012
10307
|
import { readFile as readFile13 } from "fs/promises";
|
|
10013
|
-
import { existsSync as
|
|
10308
|
+
import { existsSync as existsSync45 } from "fs";
|
|
10014
10309
|
import path27 from "path";
|
|
10015
10310
|
import "commander";
|
|
10016
10311
|
import {
|
|
@@ -10050,7 +10345,7 @@ function registerMemorySeed(memory2) {
|
|
|
10050
10345
|
}
|
|
10051
10346
|
return;
|
|
10052
10347
|
}
|
|
10053
|
-
if (!
|
|
10348
|
+
if (!existsSync45(paths.haiveDir)) {
|
|
10054
10349
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10055
10350
|
process.exitCode = 1;
|
|
10056
10351
|
return;
|
|
@@ -10106,7 +10401,7 @@ function registerMemorySeed(memory2) {
|
|
|
10106
10401
|
}
|
|
10107
10402
|
|
|
10108
10403
|
// src/commands/memory-pending.ts
|
|
10109
|
-
import { existsSync as
|
|
10404
|
+
import { existsSync as existsSync46 } from "fs";
|
|
10110
10405
|
import path28 from "path";
|
|
10111
10406
|
import "commander";
|
|
10112
10407
|
import {
|
|
@@ -10119,12 +10414,12 @@ function registerMemoryPending(memory2) {
|
|
|
10119
10414
|
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) => {
|
|
10120
10415
|
const root = findProjectRoot24(opts.dir);
|
|
10121
10416
|
const paths = resolveHaivePaths21(root);
|
|
10122
|
-
if (!
|
|
10417
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
10123
10418
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10124
10419
|
process.exitCode = 1;
|
|
10125
10420
|
return;
|
|
10126
10421
|
}
|
|
10127
|
-
const all = await
|
|
10422
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10128
10423
|
const usage = await loadUsageIndex17(paths);
|
|
10129
10424
|
const filterFn = ({ memory: mem }) => {
|
|
10130
10425
|
if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
|
|
@@ -10177,7 +10472,7 @@ function registerMemoryPending(memory2) {
|
|
|
10177
10472
|
}
|
|
10178
10473
|
|
|
10179
10474
|
// src/commands/memory-query.ts
|
|
10180
|
-
import { existsSync as
|
|
10475
|
+
import { existsSync as existsSync47 } from "fs";
|
|
10181
10476
|
import path29 from "path";
|
|
10182
10477
|
import "commander";
|
|
10183
10478
|
import {
|
|
@@ -10194,7 +10489,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10194
10489
|
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) => {
|
|
10195
10490
|
const root = findProjectRoot25(opts.dir);
|
|
10196
10491
|
const paths = resolveHaivePaths22(root);
|
|
10197
|
-
if (!
|
|
10492
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
10198
10493
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
10199
10494
|
process.exitCode = 1;
|
|
10200
10495
|
return;
|
|
@@ -10205,7 +10500,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10205
10500
|
return;
|
|
10206
10501
|
}
|
|
10207
10502
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
10208
|
-
const all = await
|
|
10503
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10209
10504
|
const passesFilters2 = (mem) => {
|
|
10210
10505
|
const fm = mem.frontmatter;
|
|
10211
10506
|
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
@@ -10252,8 +10547,8 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
10252
10547
|
}
|
|
10253
10548
|
|
|
10254
10549
|
// src/commands/memory-reject.ts
|
|
10255
|
-
import { writeFile as
|
|
10256
|
-
import { existsSync as
|
|
10550
|
+
import { writeFile as writeFile21 } from "fs/promises";
|
|
10551
|
+
import { existsSync as existsSync48 } from "fs";
|
|
10257
10552
|
import "commander";
|
|
10258
10553
|
import {
|
|
10259
10554
|
findProjectRoot as findProjectRoot26,
|
|
@@ -10261,27 +10556,27 @@ import {
|
|
|
10261
10556
|
recordRejection as recordRejection3,
|
|
10262
10557
|
resolveHaivePaths as resolveHaivePaths23,
|
|
10263
10558
|
saveUsageIndex as saveUsageIndex4,
|
|
10264
|
-
serializeMemory as
|
|
10559
|
+
serializeMemory as serializeMemory19
|
|
10265
10560
|
} from "@hiveai/core";
|
|
10266
10561
|
function registerMemoryReject(memory2) {
|
|
10267
10562
|
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) => {
|
|
10268
10563
|
const root = findProjectRoot26(opts.dir);
|
|
10269
10564
|
const paths = resolveHaivePaths23(root);
|
|
10270
|
-
if (!
|
|
10565
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
10271
10566
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10272
10567
|
process.exitCode = 1;
|
|
10273
10568
|
return;
|
|
10274
10569
|
}
|
|
10275
|
-
const memories = await
|
|
10570
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10276
10571
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
10277
10572
|
if (!loaded) {
|
|
10278
10573
|
ui.error(`No memory with id "${id}".`);
|
|
10279
10574
|
process.exitCode = 1;
|
|
10280
10575
|
return;
|
|
10281
10576
|
}
|
|
10282
|
-
await
|
|
10577
|
+
await writeFile21(
|
|
10283
10578
|
loaded.filePath,
|
|
10284
|
-
|
|
10579
|
+
serializeMemory19({
|
|
10285
10580
|
frontmatter: {
|
|
10286
10581
|
...loaded.memory.frontmatter,
|
|
10287
10582
|
status: "rejected",
|
|
@@ -10303,7 +10598,7 @@ function registerMemoryReject(memory2) {
|
|
|
10303
10598
|
}
|
|
10304
10599
|
|
|
10305
10600
|
// src/commands/memory-rm.ts
|
|
10306
|
-
import { existsSync as
|
|
10601
|
+
import { existsSync as existsSync49 } from "fs";
|
|
10307
10602
|
import { unlink as unlink3 } from "fs/promises";
|
|
10308
10603
|
import path30 from "path";
|
|
10309
10604
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
@@ -10318,12 +10613,12 @@ function registerMemoryRm(memory2) {
|
|
|
10318
10613
|
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) => {
|
|
10319
10614
|
const root = findProjectRoot27(opts.dir);
|
|
10320
10615
|
const paths = resolveHaivePaths24(root);
|
|
10321
|
-
if (!
|
|
10616
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
10322
10617
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10323
10618
|
process.exitCode = 1;
|
|
10324
10619
|
return;
|
|
10325
10620
|
}
|
|
10326
|
-
const all = await
|
|
10621
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10327
10622
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10328
10623
|
if (!found) {
|
|
10329
10624
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10354,7 +10649,7 @@ function registerMemoryRm(memory2) {
|
|
|
10354
10649
|
}
|
|
10355
10650
|
|
|
10356
10651
|
// src/commands/memory-show.ts
|
|
10357
|
-
import { existsSync as
|
|
10652
|
+
import { existsSync as existsSync50 } from "fs";
|
|
10358
10653
|
import { readFile as readFile14 } from "fs/promises";
|
|
10359
10654
|
import path31 from "path";
|
|
10360
10655
|
import "commander";
|
|
@@ -10369,12 +10664,12 @@ function registerMemoryShow(memory2) {
|
|
|
10369
10664
|
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) => {
|
|
10370
10665
|
const root = findProjectRoot28(opts.dir);
|
|
10371
10666
|
const paths = resolveHaivePaths25(root);
|
|
10372
|
-
if (!
|
|
10667
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
10373
10668
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10374
10669
|
process.exitCode = 1;
|
|
10375
10670
|
return;
|
|
10376
10671
|
}
|
|
10377
|
-
const all = await
|
|
10672
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10378
10673
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10379
10674
|
if (!found) {
|
|
10380
10675
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10413,7 +10708,7 @@ function registerMemoryShow(memory2) {
|
|
|
10413
10708
|
}
|
|
10414
10709
|
|
|
10415
10710
|
// src/commands/memory-stats.ts
|
|
10416
|
-
import { existsSync as
|
|
10711
|
+
import { existsSync as existsSync51 } from "fs";
|
|
10417
10712
|
import path33 from "path";
|
|
10418
10713
|
import "commander";
|
|
10419
10714
|
import {
|
|
@@ -10427,12 +10722,12 @@ function registerMemoryStats(memory2) {
|
|
|
10427
10722
|
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) => {
|
|
10428
10723
|
const root = findProjectRoot29(opts.dir);
|
|
10429
10724
|
const paths = resolveHaivePaths26(root);
|
|
10430
|
-
if (!
|
|
10725
|
+
if (!existsSync51(paths.memoriesDir)) {
|
|
10431
10726
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10432
10727
|
process.exitCode = 1;
|
|
10433
10728
|
return;
|
|
10434
10729
|
}
|
|
10435
|
-
const all = await
|
|
10730
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10436
10731
|
const usage = await loadUsageIndex21(paths);
|
|
10437
10732
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10438
10733
|
if (target.length === 0) {
|
|
@@ -10458,7 +10753,7 @@ function registerMemoryStats(memory2) {
|
|
|
10458
10753
|
}
|
|
10459
10754
|
|
|
10460
10755
|
// src/commands/memory-impact.ts
|
|
10461
|
-
import { existsSync as
|
|
10756
|
+
import { existsSync as existsSync53 } from "fs";
|
|
10462
10757
|
import "commander";
|
|
10463
10758
|
import {
|
|
10464
10759
|
compareImpact,
|
|
@@ -10475,12 +10770,12 @@ function registerMemoryImpact(memory2) {
|
|
|
10475
10770
|
).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) => {
|
|
10476
10771
|
const root = findProjectRoot30(opts.dir);
|
|
10477
10772
|
const paths = resolveHaivePaths27(root);
|
|
10478
|
-
if (!
|
|
10773
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
10479
10774
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10480
10775
|
process.exitCode = 1;
|
|
10481
10776
|
return;
|
|
10482
10777
|
}
|
|
10483
|
-
const all = await
|
|
10778
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10484
10779
|
const usageIndex = await loadUsageIndex23(paths);
|
|
10485
10780
|
let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
|
|
10486
10781
|
const fm = mem.frontmatter;
|
|
@@ -10546,7 +10841,7 @@ function pad(value, width) {
|
|
|
10546
10841
|
}
|
|
10547
10842
|
|
|
10548
10843
|
// src/commands/memory-feedback.ts
|
|
10549
|
-
import { existsSync as
|
|
10844
|
+
import { existsSync as existsSync54 } from "fs";
|
|
10550
10845
|
import "commander";
|
|
10551
10846
|
import {
|
|
10552
10847
|
computeImpact as computeImpact4,
|
|
@@ -10569,12 +10864,12 @@ function registerMemoryFeedback(memory2) {
|
|
|
10569
10864
|
}
|
|
10570
10865
|
const root = findProjectRoot31(opts.dir);
|
|
10571
10866
|
const paths = resolveHaivePaths28(root);
|
|
10572
|
-
if (!
|
|
10867
|
+
if (!existsSync54(paths.memoriesDir)) {
|
|
10573
10868
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10574
10869
|
process.exitCode = 1;
|
|
10575
10870
|
return;
|
|
10576
10871
|
}
|
|
10577
|
-
const all = await
|
|
10872
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10578
10873
|
const target = all.find((m) => m.memory.frontmatter.id === id);
|
|
10579
10874
|
if (!target) {
|
|
10580
10875
|
ui.error(`No memory with id '${id}'.`);
|
|
@@ -10600,14 +10895,14 @@ function registerMemoryFeedback(memory2) {
|
|
|
10600
10895
|
}
|
|
10601
10896
|
|
|
10602
10897
|
// src/commands/memory-verify.ts
|
|
10603
|
-
import { writeFile as
|
|
10604
|
-
import { existsSync as
|
|
10898
|
+
import { writeFile as writeFile23 } from "fs/promises";
|
|
10899
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10605
10900
|
import path34 from "path";
|
|
10606
10901
|
import "commander";
|
|
10607
10902
|
import {
|
|
10608
10903
|
findProjectRoot as findProjectRoot32,
|
|
10609
10904
|
resolveHaivePaths as resolveHaivePaths29,
|
|
10610
|
-
serializeMemory as
|
|
10905
|
+
serializeMemory as serializeMemory20,
|
|
10611
10906
|
verifyAnchor as verifyAnchor3
|
|
10612
10907
|
} from "@hiveai/core";
|
|
10613
10908
|
function registerMemoryVerify(memory2) {
|
|
@@ -10616,7 +10911,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10616
10911
|
).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) => {
|
|
10617
10912
|
const root = findProjectRoot32(opts.dir);
|
|
10618
10913
|
const paths = resolveHaivePaths29(root);
|
|
10619
|
-
if (!
|
|
10914
|
+
if (!existsSync55(paths.memoriesDir)) {
|
|
10620
10915
|
if (opts.json) {
|
|
10621
10916
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
10622
10917
|
} else {
|
|
@@ -10625,7 +10920,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10625
10920
|
process.exitCode = 1;
|
|
10626
10921
|
return;
|
|
10627
10922
|
}
|
|
10628
|
-
const all = await
|
|
10923
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10629
10924
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10630
10925
|
if (opts.id && targets.length === 0) {
|
|
10631
10926
|
if (opts.json) {
|
|
@@ -10674,7 +10969,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10674
10969
|
}
|
|
10675
10970
|
if (opts.update) {
|
|
10676
10971
|
const next = applyVerification2(mem, result);
|
|
10677
|
-
await
|
|
10972
|
+
await writeFile23(filePath, serializeMemory20(next), "utf8");
|
|
10678
10973
|
updated++;
|
|
10679
10974
|
}
|
|
10680
10975
|
}
|
|
@@ -10737,7 +11032,7 @@ function applyVerification2(mem, result) {
|
|
|
10737
11032
|
|
|
10738
11033
|
// src/commands/memory-import.ts
|
|
10739
11034
|
import { readFile as readFile15 } from "fs/promises";
|
|
10740
|
-
import { existsSync as
|
|
11035
|
+
import { existsSync as existsSync56 } from "fs";
|
|
10741
11036
|
import "commander";
|
|
10742
11037
|
import {
|
|
10743
11038
|
findProjectRoot as findProjectRoot33,
|
|
@@ -10749,12 +11044,12 @@ function registerMemoryImport(memory2) {
|
|
|
10749
11044
|
).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) => {
|
|
10750
11045
|
const root = findProjectRoot33(opts.dir);
|
|
10751
11046
|
const paths = resolveHaivePaths30(root);
|
|
10752
|
-
if (!
|
|
11047
|
+
if (!existsSync56(paths.haiveDir)) {
|
|
10753
11048
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10754
11049
|
process.exitCode = 1;
|
|
10755
11050
|
return;
|
|
10756
11051
|
}
|
|
10757
|
-
if (!
|
|
11052
|
+
if (!existsSync56(opts.from)) {
|
|
10758
11053
|
ui.error(`File not found: ${opts.from}`);
|
|
10759
11054
|
process.exitCode = 1;
|
|
10760
11055
|
return;
|
|
@@ -10787,15 +11082,15 @@ function registerMemoryImport(memory2) {
|
|
|
10787
11082
|
}
|
|
10788
11083
|
|
|
10789
11084
|
// src/commands/memory-import-changelog.ts
|
|
10790
|
-
import { existsSync as
|
|
10791
|
-
import { readFile as readFile16, mkdir as mkdir14, writeFile as
|
|
11085
|
+
import { existsSync as existsSync57 } from "fs";
|
|
11086
|
+
import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile24 } from "fs/promises";
|
|
10792
11087
|
import path35 from "path";
|
|
10793
11088
|
import "commander";
|
|
10794
11089
|
import {
|
|
10795
11090
|
buildFrontmatter as buildFrontmatter9,
|
|
10796
11091
|
findProjectRoot as findProjectRoot34,
|
|
10797
11092
|
resolveHaivePaths as resolveHaivePaths31,
|
|
10798
|
-
serializeMemory as
|
|
11093
|
+
serializeMemory as serializeMemory21
|
|
10799
11094
|
} from "@hiveai/core";
|
|
10800
11095
|
function parseChangelog(content) {
|
|
10801
11096
|
const entries = [];
|
|
@@ -10862,7 +11157,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10862
11157
|
const root = findProjectRoot34(opts.dir);
|
|
10863
11158
|
const paths = resolveHaivePaths31(root);
|
|
10864
11159
|
const changelogPath = path35.resolve(root, opts.fromChangelog);
|
|
10865
|
-
if (!
|
|
11160
|
+
if (!existsSync57(changelogPath)) {
|
|
10866
11161
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
10867
11162
|
process.exitCode = 1;
|
|
10868
11163
|
return;
|
|
@@ -10925,9 +11220,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10925
11220
|
paths: [path35.relative(root, changelogPath)],
|
|
10926
11221
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
10927
11222
|
});
|
|
10928
|
-
await
|
|
11223
|
+
await writeFile24(
|
|
10929
11224
|
path35.join(teamDir, `${fm.id}.md`),
|
|
10930
|
-
|
|
11225
|
+
serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
|
|
10931
11226
|
"utf8"
|
|
10932
11227
|
);
|
|
10933
11228
|
console.log(ui.green(` \u2713 ${fm.id}`));
|
|
@@ -10949,15 +11244,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
10949
11244
|
}
|
|
10950
11245
|
|
|
10951
11246
|
// src/commands/memory-digest.ts
|
|
10952
|
-
import { existsSync as
|
|
10953
|
-
import { writeFile as
|
|
11247
|
+
import { existsSync as existsSync58 } from "fs";
|
|
11248
|
+
import { writeFile as writeFile25 } from "fs/promises";
|
|
10954
11249
|
import path36 from "path";
|
|
10955
11250
|
import "commander";
|
|
10956
11251
|
import {
|
|
10957
11252
|
deriveConfidence as deriveConfidence12,
|
|
10958
11253
|
findProjectRoot as findProjectRoot35,
|
|
10959
11254
|
getUsage as getUsage20,
|
|
10960
|
-
loadMemoriesFromDir as
|
|
11255
|
+
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
10961
11256
|
loadUsageIndex as loadUsageIndex25,
|
|
10962
11257
|
resolveHaivePaths as resolveHaivePaths32
|
|
10963
11258
|
} from "@hiveai/core";
|
|
@@ -10974,7 +11269,7 @@ function registerMemoryDigest(program2) {
|
|
|
10974
11269
|
).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) => {
|
|
10975
11270
|
const root = findProjectRoot35(opts.dir);
|
|
10976
11271
|
const paths = resolveHaivePaths32(root);
|
|
10977
|
-
if (!
|
|
11272
|
+
if (!existsSync58(paths.memoriesDir)) {
|
|
10978
11273
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
10979
11274
|
process.exitCode = 1;
|
|
10980
11275
|
return;
|
|
@@ -10982,7 +11277,7 @@ function registerMemoryDigest(program2) {
|
|
|
10982
11277
|
const days = Math.max(1, Number(opts.days ?? 7));
|
|
10983
11278
|
const scopeFilter = opts.scope ?? "team";
|
|
10984
11279
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
10985
|
-
const all = await
|
|
11280
|
+
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
10986
11281
|
const usage = await loadUsageIndex25(paths);
|
|
10987
11282
|
const recent = all.filter(({ memory: mem }) => {
|
|
10988
11283
|
const fm = mem.frontmatter;
|
|
@@ -11047,7 +11342,7 @@ function registerMemoryDigest(program2) {
|
|
|
11047
11342
|
const digest = lines.join("\n");
|
|
11048
11343
|
if (opts.out) {
|
|
11049
11344
|
const outPath = path36.resolve(process.cwd(), opts.out);
|
|
11050
|
-
await
|
|
11345
|
+
await writeFile25(outPath, digest, "utf8");
|
|
11051
11346
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
11052
11347
|
} else {
|
|
11053
11348
|
console.log(digest);
|
|
@@ -11056,22 +11351,22 @@ function registerMemoryDigest(program2) {
|
|
|
11056
11351
|
}
|
|
11057
11352
|
|
|
11058
11353
|
// src/commands/session-end.ts
|
|
11059
|
-
import { writeFile as
|
|
11060
|
-
import { existsSync as
|
|
11354
|
+
import { writeFile as writeFile26, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
|
|
11355
|
+
import { existsSync as existsSync59 } from "fs";
|
|
11061
11356
|
import { spawn as spawn4 } from "child_process";
|
|
11062
11357
|
import path37 from "path";
|
|
11063
11358
|
import "commander";
|
|
11064
11359
|
import {
|
|
11065
11360
|
buildFrontmatter as buildFrontmatter10,
|
|
11066
11361
|
findProjectRoot as findProjectRoot36,
|
|
11067
|
-
loadMemoriesFromDir as
|
|
11068
|
-
memoryFilePath as
|
|
11362
|
+
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
11363
|
+
memoryFilePath as memoryFilePath10,
|
|
11069
11364
|
resolveHaivePaths as resolveHaivePaths33,
|
|
11070
|
-
serializeMemory as
|
|
11365
|
+
serializeMemory as serializeMemory23
|
|
11071
11366
|
} from "@hiveai/core";
|
|
11072
11367
|
async function buildAutoRecap(paths) {
|
|
11073
11368
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11074
|
-
if (!
|
|
11369
|
+
if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
|
|
11075
11370
|
const raw = await readFile17(obsFile, "utf8").catch(() => "");
|
|
11076
11371
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
11077
11372
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -11262,7 +11557,7 @@ function registerSessionEnd(session2) {
|
|
|
11262
11557
|
).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) => {
|
|
11263
11558
|
const root = findProjectRoot36(opts.dir);
|
|
11264
11559
|
const paths = resolveHaivePaths33(root);
|
|
11265
|
-
if (!
|
|
11560
|
+
if (!existsSync59(paths.haiveDir)) {
|
|
11266
11561
|
if (opts.auto || opts.quiet) return;
|
|
11267
11562
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11268
11563
|
process.exitCode = 1;
|
|
@@ -11295,7 +11590,7 @@ function registerSessionEnd(session2) {
|
|
|
11295
11590
|
});
|
|
11296
11591
|
const topic = recapTopic2(scope, opts.module);
|
|
11297
11592
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
11298
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
11593
|
+
const missingPaths = filesTouched.filter((p) => !existsSync59(path37.resolve(root, p)));
|
|
11299
11594
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
11300
11595
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
11301
11596
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -11303,11 +11598,11 @@ function registerSessionEnd(session2) {
|
|
|
11303
11598
|
const cleanupObservations = async () => {
|
|
11304
11599
|
if (!opts.auto) return;
|
|
11305
11600
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11306
|
-
if (
|
|
11601
|
+
if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
|
|
11307
11602
|
});
|
|
11308
11603
|
};
|
|
11309
|
-
if (
|
|
11310
|
-
const existing = await
|
|
11604
|
+
if (existsSync59(paths.memoriesDir)) {
|
|
11605
|
+
const existing = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
11311
11606
|
const topicMatch = existing.find(
|
|
11312
11607
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
11313
11608
|
);
|
|
@@ -11323,7 +11618,7 @@ function registerSessionEnd(session2) {
|
|
|
11323
11618
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
11324
11619
|
}
|
|
11325
11620
|
};
|
|
11326
|
-
await
|
|
11621
|
+
await writeFile26(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
|
|
11327
11622
|
await cleanupObservations();
|
|
11328
11623
|
if (!opts.quiet) {
|
|
11329
11624
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
@@ -11343,9 +11638,9 @@ function registerSessionEnd(session2) {
|
|
|
11343
11638
|
topic,
|
|
11344
11639
|
status: "validated"
|
|
11345
11640
|
});
|
|
11346
|
-
const file =
|
|
11641
|
+
const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
11347
11642
|
await mkdir15(path37.dirname(file), { recursive: true });
|
|
11348
|
-
await
|
|
11643
|
+
await writeFile26(file, serializeMemory23({ frontmatter, body }), "utf8");
|
|
11349
11644
|
await cleanupObservations();
|
|
11350
11645
|
if (!opts.quiet) {
|
|
11351
11646
|
ui.success(`Session recap created`);
|
|
@@ -11368,7 +11663,7 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
11368
11663
|
}
|
|
11369
11664
|
|
|
11370
11665
|
// src/commands/snapshot.ts
|
|
11371
|
-
import { existsSync as
|
|
11666
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11372
11667
|
import { readdir as readdir4 } from "fs/promises";
|
|
11373
11668
|
import path38 from "path";
|
|
11374
11669
|
import "commander";
|
|
@@ -11403,14 +11698,14 @@ function registerSnapshot(program2) {
|
|
|
11403
11698
|
).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) => {
|
|
11404
11699
|
const root = findProjectRoot37(opts.dir);
|
|
11405
11700
|
const paths = resolveHaivePaths34(root);
|
|
11406
|
-
if (!
|
|
11701
|
+
if (!existsSync60(paths.haiveDir)) {
|
|
11407
11702
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
11408
11703
|
process.exitCode = 1;
|
|
11409
11704
|
return;
|
|
11410
11705
|
}
|
|
11411
11706
|
if (opts.list) {
|
|
11412
11707
|
const contractsDir = path38.join(paths.haiveDir, "contracts");
|
|
11413
|
-
if (!
|
|
11708
|
+
if (!existsSync60(contractsDir)) {
|
|
11414
11709
|
console.log(ui.dim("No contract snapshots found."));
|
|
11415
11710
|
return;
|
|
11416
11711
|
}
|
|
@@ -11534,18 +11829,18 @@ function detectFormat(filePath) {
|
|
|
11534
11829
|
}
|
|
11535
11830
|
|
|
11536
11831
|
// src/commands/hub.ts
|
|
11537
|
-
import { existsSync as
|
|
11538
|
-
import { mkdir as mkdir16, readFile as readFile18, writeFile as
|
|
11832
|
+
import { existsSync as existsSync61 } from "fs";
|
|
11833
|
+
import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile27, copyFile } from "fs/promises";
|
|
11539
11834
|
import path39 from "path";
|
|
11540
11835
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
11541
11836
|
import "commander";
|
|
11542
11837
|
import {
|
|
11543
11838
|
findProjectRoot as findProjectRoot38,
|
|
11544
11839
|
loadConfig as loadConfig8,
|
|
11545
|
-
loadMemoriesFromDir as
|
|
11840
|
+
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
11546
11841
|
resolveHaivePaths as resolveHaivePaths35,
|
|
11547
11842
|
saveConfig as saveConfig3,
|
|
11548
|
-
serializeMemory as
|
|
11843
|
+
serializeMemory as serializeMemory24
|
|
11549
11844
|
} from "@hiveai/core";
|
|
11550
11845
|
function registerHub(program2) {
|
|
11551
11846
|
const hub = program2.command("hub").description(
|
|
@@ -11568,7 +11863,7 @@ function registerHub(program2) {
|
|
|
11568
11863
|
}
|
|
11569
11864
|
const sharedDir = path39.join(absPath, ".ai", "memories", "shared");
|
|
11570
11865
|
await mkdir16(sharedDir, { recursive: true });
|
|
11571
|
-
await
|
|
11866
|
+
await writeFile27(
|
|
11572
11867
|
path39.join(absPath, ".ai", "README.md"),
|
|
11573
11868
|
`# hAIve Team Knowledge Hub
|
|
11574
11869
|
|
|
@@ -11590,7 +11885,7 @@ haive hub pull # import into a project
|
|
|
11590
11885
|
`,
|
|
11591
11886
|
"utf8"
|
|
11592
11887
|
);
|
|
11593
|
-
await
|
|
11888
|
+
await writeFile27(
|
|
11594
11889
|
path39.join(absPath, ".gitignore"),
|
|
11595
11890
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
11596
11891
|
"utf8"
|
|
@@ -11636,7 +11931,7 @@ Next steps:
|
|
|
11636
11931
|
return;
|
|
11637
11932
|
}
|
|
11638
11933
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11639
|
-
if (!
|
|
11934
|
+
if (!existsSync61(hubRoot)) {
|
|
11640
11935
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
11641
11936
|
process.exitCode = 1;
|
|
11642
11937
|
return;
|
|
@@ -11644,7 +11939,7 @@ Next steps:
|
|
|
11644
11939
|
const projectName = path39.basename(root);
|
|
11645
11940
|
const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
11646
11941
|
await mkdir16(destDir, { recursive: true });
|
|
11647
|
-
const all = await
|
|
11942
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11648
11943
|
const shared = all.filter(
|
|
11649
11944
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
11650
11945
|
!memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
@@ -11662,7 +11957,7 @@ Next steps:
|
|
|
11662
11957
|
const fm = memory2.frontmatter;
|
|
11663
11958
|
const fileName = `${fm.id}.md`;
|
|
11664
11959
|
const destPath = path39.join(destDir, fileName);
|
|
11665
|
-
await
|
|
11960
|
+
await writeFile27(destPath, serializeMemory24(memory2), "utf8");
|
|
11666
11961
|
pushed++;
|
|
11667
11962
|
}
|
|
11668
11963
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
@@ -11706,7 +12001,7 @@ Next steps:
|
|
|
11706
12001
|
}
|
|
11707
12002
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11708
12003
|
const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
|
|
11709
|
-
if (!
|
|
12004
|
+
if (!existsSync61(hubSharedDir)) {
|
|
11710
12005
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
11711
12006
|
return;
|
|
11712
12007
|
}
|
|
@@ -11761,7 +12056,7 @@ Next steps:
|
|
|
11761
12056
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
11762
12057
|
);
|
|
11763
12058
|
const sharedDir = path39.join(paths.memoriesDir, "shared");
|
|
11764
|
-
if (
|
|
12059
|
+
if (existsSync61(sharedDir)) {
|
|
11765
12060
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11766
12061
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
11767
12062
|
console.log(`
|
|
@@ -11773,7 +12068,7 @@ Next steps:
|
|
|
11773
12068
|
} else {
|
|
11774
12069
|
console.log(ui.dim(" No imported shared memories yet."));
|
|
11775
12070
|
}
|
|
11776
|
-
const all = await
|
|
12071
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11777
12072
|
const outgoing = all.filter(
|
|
11778
12073
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
11779
12074
|
);
|
|
@@ -11783,20 +12078,20 @@ Next steps:
|
|
|
11783
12078
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
11784
12079
|
}
|
|
11785
12080
|
void readFile18;
|
|
11786
|
-
void
|
|
12081
|
+
void writeFile27;
|
|
11787
12082
|
void saveConfig3;
|
|
11788
12083
|
});
|
|
11789
12084
|
}
|
|
11790
12085
|
|
|
11791
12086
|
// src/commands/stats.ts
|
|
11792
12087
|
import "commander";
|
|
11793
|
-
import { existsSync as
|
|
11794
|
-
import { mkdir as mkdir17, writeFile as
|
|
12088
|
+
import { existsSync as existsSync63 } from "fs";
|
|
12089
|
+
import { mkdir as mkdir17, writeFile as writeFile28 } from "fs/promises";
|
|
11795
12090
|
import path40 from "path";
|
|
11796
12091
|
import {
|
|
11797
12092
|
aggregateUsage,
|
|
11798
12093
|
findProjectRoot as findProjectRoot39,
|
|
11799
|
-
loadMemoriesFromDir as
|
|
12094
|
+
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
11800
12095
|
loadUsageIndex as loadUsageIndex26,
|
|
11801
12096
|
parseSince,
|
|
11802
12097
|
readUsageEvents as readUsageEvents2,
|
|
@@ -11868,8 +12163,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11868
12163
|
const size = await usageLogSize(paths);
|
|
11869
12164
|
let events = await readUsageEvents2(paths);
|
|
11870
12165
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
11871
|
-
if (
|
|
11872
|
-
const mems = await
|
|
12166
|
+
if (existsSync63(paths.memoriesDir)) {
|
|
12167
|
+
const mems = await loadMemoriesFromDir31(paths.memoriesDir);
|
|
11873
12168
|
for (const { memory: memory2 } of mems) {
|
|
11874
12169
|
const fm = memory2.frontmatter;
|
|
11875
12170
|
if (fm.type === "session_recap") memoryCount.total_skipped_session++;
|
|
@@ -11911,7 +12206,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11911
12206
|
top_memory_reads: memoryHitsLeader,
|
|
11912
12207
|
roi_hints: roiHints
|
|
11913
12208
|
};
|
|
11914
|
-
await
|
|
12209
|
+
await writeFile28(outAbs, JSON.stringify(payload, null, 2), "utf8");
|
|
11915
12210
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
11916
12211
|
}
|
|
11917
12212
|
async function renderMemoryHits(paths, opts) {
|
|
@@ -12088,8 +12383,8 @@ function summarize(name, t0, payload, notes) {
|
|
|
12088
12383
|
}
|
|
12089
12384
|
|
|
12090
12385
|
// src/commands/benchmark.ts
|
|
12091
|
-
import { existsSync as
|
|
12092
|
-
import { readdir as readdir5, readFile as readFile19, writeFile as
|
|
12386
|
+
import { existsSync as existsSync64 } from "fs";
|
|
12387
|
+
import { readdir as readdir5, readFile as readFile19, writeFile as writeFile29 } from "fs/promises";
|
|
12093
12388
|
import path41 from "path";
|
|
12094
12389
|
import "commander";
|
|
12095
12390
|
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
@@ -12106,7 +12401,7 @@ function registerBenchmark(program2) {
|
|
|
12106
12401
|
const markdown = renderMarkdown(root, summary, rows);
|
|
12107
12402
|
if (opts.out) {
|
|
12108
12403
|
const outFile = path41.isAbsolute(opts.out) ? opts.out : path41.join(root, opts.out);
|
|
12109
|
-
await
|
|
12404
|
+
await writeFile29(outFile, markdown, "utf8");
|
|
12110
12405
|
ui.success(`wrote ${path41.relative(process.cwd(), outFile)}`);
|
|
12111
12406
|
return;
|
|
12112
12407
|
}
|
|
@@ -12136,14 +12431,14 @@ function resolveBenchmarkRoot(dir) {
|
|
|
12136
12431
|
return path41.join(projectRoot, candidate);
|
|
12137
12432
|
}
|
|
12138
12433
|
async function collectRows(root) {
|
|
12139
|
-
if (!
|
|
12434
|
+
if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
12140
12435
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
12141
12436
|
const rows = [];
|
|
12142
12437
|
for (const entry of entries) {
|
|
12143
12438
|
if (!entry.isDirectory()) continue;
|
|
12144
12439
|
const fixtureDir = path41.join(root, entry.name);
|
|
12145
12440
|
const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
12146
|
-
if (!
|
|
12441
|
+
if (!existsSync64(reportFile)) continue;
|
|
12147
12442
|
const report = await readFile19(reportFile, "utf8");
|
|
12148
12443
|
rows.push(parseAgentReport(entry.name, report));
|
|
12149
12444
|
}
|
|
@@ -12233,14 +12528,15 @@ function escapeRegExp(value) {
|
|
|
12233
12528
|
}
|
|
12234
12529
|
|
|
12235
12530
|
// src/commands/eval.ts
|
|
12236
|
-
import { readFile as readFile20, writeFile as
|
|
12237
|
-
import { existsSync as
|
|
12531
|
+
import { mkdir as mkdir18, readFile as readFile20, writeFile as writeFile30 } from "fs/promises";
|
|
12532
|
+
import { existsSync as existsSync65 } from "fs";
|
|
12238
12533
|
import path43 from "path";
|
|
12239
12534
|
import "commander";
|
|
12240
12535
|
import {
|
|
12241
12536
|
aggregateRetrieval,
|
|
12242
12537
|
aggregateSensors,
|
|
12243
12538
|
buildReport,
|
|
12539
|
+
compareEvalReports,
|
|
12244
12540
|
findProjectRoot as findProjectRoot42,
|
|
12245
12541
|
resolveHaivePaths as resolveHaivePaths38,
|
|
12246
12542
|
scoreRetrievalCase,
|
|
@@ -12250,10 +12546,10 @@ import {
|
|
|
12250
12546
|
function registerEval(program2) {
|
|
12251
12547
|
program2.command("eval").description(
|
|
12252
12548
|
"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."
|
|
12253
|
-
).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) => {
|
|
12549
|
+
).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("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12254
12550
|
const root = findProjectRoot42(opts.dir);
|
|
12255
12551
|
const paths = resolveHaivePaths38(root);
|
|
12256
|
-
if (!
|
|
12552
|
+
if (!existsSync65(paths.memoriesDir)) {
|
|
12257
12553
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12258
12554
|
process.exitCode = 1;
|
|
12259
12555
|
return;
|
|
@@ -12285,30 +12581,82 @@ function registerEval(program2) {
|
|
|
12285
12581
|
sensorAgg = aggregateSensors(results);
|
|
12286
12582
|
}
|
|
12287
12583
|
const report = buildReport(retrievalAgg, sensorAgg);
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
}
|
|
12299
|
-
}
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
if (
|
|
12303
|
-
ui.error(
|
|
12304
|
-
process.exitCode = 1;
|
|
12305
|
-
} else if (report.score < threshold) {
|
|
12306
|
-
ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
|
|
12584
|
+
const baselineFile = opts.baselineFile ? path43.isAbsolute(opts.baselineFile) ? opts.baselineFile : path43.join(root, opts.baselineFile) : path43.join(root, ".ai", "eval", "baseline.json");
|
|
12585
|
+
if (opts.baseline) {
|
|
12586
|
+
const snapshot = {
|
|
12587
|
+
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12588
|
+
k,
|
|
12589
|
+
spec_source: resolvedSpec.source,
|
|
12590
|
+
report
|
|
12591
|
+
};
|
|
12592
|
+
await mkdir18(path43.dirname(baselineFile), { recursive: true });
|
|
12593
|
+
await writeFile30(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
|
|
12594
|
+
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path43.relative(root, baselineFile)}`);
|
|
12595
|
+
}
|
|
12596
|
+
let delta = null;
|
|
12597
|
+
if (opts.compare) {
|
|
12598
|
+
if (!existsSync65(baselineFile)) {
|
|
12599
|
+
ui.error(`No baseline at ${path43.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
|
|
12307
12600
|
process.exitCode = 1;
|
|
12601
|
+
return;
|
|
12308
12602
|
}
|
|
12603
|
+
const snapshot = JSON.parse(await readFile20(baselineFile, "utf8"));
|
|
12604
|
+
delta = compareEvalReports(snapshot.report, report);
|
|
12605
|
+
}
|
|
12606
|
+
if (opts.json) {
|
|
12607
|
+
console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report, ...delta ? { delta } : {} }, null, 2));
|
|
12608
|
+
applyExitGates(opts, report, delta);
|
|
12609
|
+
return;
|
|
12610
|
+
}
|
|
12611
|
+
if (delta) {
|
|
12612
|
+
console.log(renderDelta(delta));
|
|
12309
12613
|
}
|
|
12614
|
+
const md = renderMarkdown2(root, k, resolvedSpec.source, report);
|
|
12615
|
+
if (opts.out) {
|
|
12616
|
+
const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
|
|
12617
|
+
await writeFile30(outFile, md, "utf8");
|
|
12618
|
+
ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
|
|
12619
|
+
} else {
|
|
12620
|
+
console.log(md);
|
|
12621
|
+
}
|
|
12622
|
+
applyExitGates(opts, report, delta);
|
|
12310
12623
|
});
|
|
12311
12624
|
}
|
|
12625
|
+
function applyExitGates(opts, report, delta) {
|
|
12626
|
+
if (opts.failUnder !== void 0) {
|
|
12627
|
+
const threshold = Number(opts.failUnder);
|
|
12628
|
+
if (Number.isNaN(threshold)) {
|
|
12629
|
+
ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
|
|
12630
|
+
process.exitCode = 1;
|
|
12631
|
+
} else if (report.score < threshold) {
|
|
12632
|
+
ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
|
|
12633
|
+
process.exitCode = 1;
|
|
12634
|
+
}
|
|
12635
|
+
}
|
|
12636
|
+
if (opts.failOnRegression && delta?.regressed) {
|
|
12637
|
+
ui.error(`eval score regressed ${delta.score.baseline} \u2192 ${delta.score.current} (\u0394 ${delta.score.delta}) vs baseline`);
|
|
12638
|
+
process.exitCode = 1;
|
|
12639
|
+
}
|
|
12640
|
+
}
|
|
12641
|
+
function fmtDelta(label, m) {
|
|
12642
|
+
if (!m) return null;
|
|
12643
|
+
const sign = m.delta > 0 ? "+" : "";
|
|
12644
|
+
const arrow = m.delta > 0 ? ui.green("\u25B2") : m.delta < 0 ? ui.red("\u25BC") : ui.dim("=");
|
|
12645
|
+
return ` ${arrow} ${label.padEnd(12)} ${m.baseline} \u2192 ${m.current} ${ui.dim(`(${sign}${m.delta})`)}`;
|
|
12646
|
+
}
|
|
12647
|
+
function renderDelta(delta) {
|
|
12648
|
+
const verdict = delta.regressed ? ui.red("REGRESSED") : delta.improved ? ui.green("IMPROVED") : ui.dim("UNCHANGED");
|
|
12649
|
+
const lines = [ui.bold(`Eval vs baseline \u2014 ${verdict}`)];
|
|
12650
|
+
for (const line of [
|
|
12651
|
+
fmtDelta("score", delta.score),
|
|
12652
|
+
fmtDelta("mean recall", delta.mean_recall),
|
|
12653
|
+
fmtDelta("mrr", delta.mrr),
|
|
12654
|
+
fmtDelta("catch-rate", delta.catch_rate)
|
|
12655
|
+
]) {
|
|
12656
|
+
if (line) lines.push(line);
|
|
12657
|
+
}
|
|
12658
|
+
return lines.join("\n");
|
|
12659
|
+
}
|
|
12312
12660
|
async function resolveSpec(opts, root, memoriesDir) {
|
|
12313
12661
|
if (opts.spec) {
|
|
12314
12662
|
const file = path43.resolve(opts.spec);
|
|
@@ -12316,10 +12664,10 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
12316
12664
|
return { spec: JSON.parse(raw), source: file };
|
|
12317
12665
|
}
|
|
12318
12666
|
const defaultSpec = path43.join(root, ".ai", "eval", "spec.json");
|
|
12319
|
-
if (
|
|
12667
|
+
if (existsSync65(defaultSpec)) {
|
|
12320
12668
|
const raw = await readFile20(defaultSpec, "utf8");
|
|
12321
12669
|
const explicit = JSON.parse(raw);
|
|
12322
|
-
const memories2 = await
|
|
12670
|
+
const memories2 = await loadMemoriesFromDir27(memoriesDir);
|
|
12323
12671
|
const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
|
|
12324
12672
|
return {
|
|
12325
12673
|
spec: {
|
|
@@ -12329,7 +12677,7 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
12329
12677
|
source: ".ai/eval/spec.json + synthesized anchored retrieval"
|
|
12330
12678
|
};
|
|
12331
12679
|
}
|
|
12332
|
-
const memories = await
|
|
12680
|
+
const memories = await loadMemoriesFromDir27(memoriesDir);
|
|
12333
12681
|
return {
|
|
12334
12682
|
spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
|
|
12335
12683
|
source: "synthesized anchored retrieval"
|
|
@@ -12419,8 +12767,8 @@ function renderMarkdown2(root, k, source, report) {
|
|
|
12419
12767
|
}
|
|
12420
12768
|
|
|
12421
12769
|
// src/commands/memory-suggest.ts
|
|
12422
|
-
import { mkdir as
|
|
12423
|
-
import { existsSync as
|
|
12770
|
+
import { mkdir as mkdir19, writeFile as writeFile31 } from "fs/promises";
|
|
12771
|
+
import { existsSync as existsSync66 } from "fs";
|
|
12424
12772
|
import path44 from "path";
|
|
12425
12773
|
import "commander";
|
|
12426
12774
|
import {
|
|
@@ -12428,12 +12776,12 @@ import {
|
|
|
12428
12776
|
buildFrontmatter as buildFrontmatter11,
|
|
12429
12777
|
findProjectRoot as findProjectRoot43,
|
|
12430
12778
|
loadConfig as loadConfig9,
|
|
12431
|
-
loadMemoriesFromDir as
|
|
12432
|
-
memoryFilePath as
|
|
12779
|
+
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
12780
|
+
memoryFilePath as memoryFilePath11,
|
|
12433
12781
|
parseSince as parseSince2,
|
|
12434
12782
|
readUsageEvents as readUsageEvents3,
|
|
12435
12783
|
resolveHaivePaths as resolveHaivePaths39,
|
|
12436
|
-
serializeMemory as
|
|
12784
|
+
serializeMemory as serializeMemory25
|
|
12437
12785
|
} from "@hiveai/core";
|
|
12438
12786
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
12439
12787
|
"mem_search",
|
|
@@ -12499,7 +12847,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12499
12847
|
}
|
|
12500
12848
|
const created = [];
|
|
12501
12849
|
const skipped = [];
|
|
12502
|
-
const existing =
|
|
12850
|
+
const existing = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
|
|
12503
12851
|
for (const s of top) {
|
|
12504
12852
|
const slug = slugify2(s.query);
|
|
12505
12853
|
if (!slug) {
|
|
@@ -12521,13 +12869,13 @@ function registerMemorySuggest(memory2) {
|
|
|
12521
12869
|
status
|
|
12522
12870
|
});
|
|
12523
12871
|
const body = renderTemplate(s, fm.id, status);
|
|
12524
|
-
const file =
|
|
12525
|
-
await
|
|
12526
|
-
if (
|
|
12872
|
+
const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
|
|
12873
|
+
await mkdir19(path44.dirname(file), { recursive: true });
|
|
12874
|
+
if (existsSync66(file)) {
|
|
12527
12875
|
skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
|
|
12528
12876
|
continue;
|
|
12529
12877
|
}
|
|
12530
|
-
await
|
|
12878
|
+
await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
|
|
12531
12879
|
created.push({ id: fm.id, file: path44.relative(root, file), query: s.query });
|
|
12532
12880
|
}
|
|
12533
12881
|
if (opts.json) {
|
|
@@ -12626,8 +12974,8 @@ function truncate2(text, max) {
|
|
|
12626
12974
|
}
|
|
12627
12975
|
|
|
12628
12976
|
// src/commands/memory-archive.ts
|
|
12629
|
-
import { existsSync as
|
|
12630
|
-
import { writeFile as
|
|
12977
|
+
import { existsSync as existsSync67 } from "fs";
|
|
12978
|
+
import { writeFile as writeFile33 } from "fs/promises";
|
|
12631
12979
|
import path45 from "path";
|
|
12632
12980
|
import "commander";
|
|
12633
12981
|
import {
|
|
@@ -12635,10 +12983,10 @@ import {
|
|
|
12635
12983
|
getUsage as getUsage21,
|
|
12636
12984
|
retirementSignal as retirementSignal2,
|
|
12637
12985
|
loadConfig as loadConfig10,
|
|
12638
|
-
loadMemoriesFromDir as
|
|
12986
|
+
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
12639
12987
|
loadUsageIndex as loadUsageIndex27,
|
|
12640
12988
|
resolveHaivePaths as resolveHaivePaths40,
|
|
12641
|
-
serializeMemory as
|
|
12989
|
+
serializeMemory as serializeMemory26
|
|
12642
12990
|
} from "@hiveai/core";
|
|
12643
12991
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
12644
12992
|
function registerMemoryArchive(memory2) {
|
|
@@ -12647,7 +12995,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12647
12995
|
).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) => {
|
|
12648
12996
|
const root = findProjectRoot44(opts.dir);
|
|
12649
12997
|
const paths = resolveHaivePaths40(root);
|
|
12650
|
-
if (!
|
|
12998
|
+
if (!existsSync67(paths.memoriesDir)) {
|
|
12651
12999
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12652
13000
|
process.exitCode = 1;
|
|
12653
13001
|
return;
|
|
@@ -12661,7 +13009,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12661
13009
|
return;
|
|
12662
13010
|
}
|
|
12663
13011
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
12664
|
-
const all = await
|
|
13012
|
+
const all = await loadMemoriesFromDir34(paths.memoriesDir);
|
|
12665
13013
|
const usage = await loadUsageIndex27(paths);
|
|
12666
13014
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
12667
13015
|
const candidates = [];
|
|
@@ -12672,7 +13020,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12672
13020
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
12673
13021
|
const retired = retirementSignal2(fm, mem.body);
|
|
12674
13022
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
12675
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
13023
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path45.join(paths.root, p)));
|
|
12676
13024
|
const isAnchorless = !hasAnyAnchor;
|
|
12677
13025
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
12678
13026
|
const u = getUsage21(usage, fm.id);
|
|
@@ -12721,7 +13069,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12721
13069
|
if (!found) continue;
|
|
12722
13070
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
12723
13071
|
try {
|
|
12724
|
-
await
|
|
13072
|
+
await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
12725
13073
|
archived++;
|
|
12726
13074
|
} catch (err) {
|
|
12727
13075
|
if (!opts.json) {
|
|
@@ -12747,8 +13095,8 @@ function parseDays(input) {
|
|
|
12747
13095
|
}
|
|
12748
13096
|
|
|
12749
13097
|
// src/commands/doctor.ts
|
|
12750
|
-
import { existsSync as
|
|
12751
|
-
import { readFile as readFile21, stat, writeFile as
|
|
13098
|
+
import { existsSync as existsSync68, statSync as statSync2 } from "fs";
|
|
13099
|
+
import { readFile as readFile21, stat, writeFile as writeFile34 } from "fs/promises";
|
|
12752
13100
|
import path46 from "path";
|
|
12753
13101
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
12754
13102
|
import "commander";
|
|
@@ -12759,7 +13107,7 @@ import {
|
|
|
12759
13107
|
isStackPackSeed as isStackPackSeed4,
|
|
12760
13108
|
loadCodeMap as loadCodeMap7,
|
|
12761
13109
|
loadConfig as loadConfig11,
|
|
12762
|
-
loadMemoriesFromDir as
|
|
13110
|
+
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
12763
13111
|
loadUsageIndex as loadUsageIndex28,
|
|
12764
13112
|
readUsageEvents as readUsageEvents4,
|
|
12765
13113
|
resolveHaivePaths as resolveHaivePaths41
|
|
@@ -12774,7 +13122,7 @@ function registerDoctor(program2) {
|
|
|
12774
13122
|
const findings = [];
|
|
12775
13123
|
const repairs = [];
|
|
12776
13124
|
const config = await loadConfig11(paths);
|
|
12777
|
-
if (!
|
|
13125
|
+
if (!existsSync68(paths.haiveDir)) {
|
|
12778
13126
|
findings.push({
|
|
12779
13127
|
severity: "error",
|
|
12780
13128
|
code: "not-initialized",
|
|
@@ -12795,7 +13143,7 @@ function registerDoctor(program2) {
|
|
|
12795
13143
|
})
|
|
12796
13144
|
);
|
|
12797
13145
|
}
|
|
12798
|
-
if (!
|
|
13146
|
+
if (!existsSync68(paths.projectContext)) {
|
|
12799
13147
|
findings.push({
|
|
12800
13148
|
severity: "warn",
|
|
12801
13149
|
code: "no-project-context",
|
|
@@ -12803,8 +13151,8 @@ function registerDoctor(program2) {
|
|
|
12803
13151
|
fix: "haive init"
|
|
12804
13152
|
});
|
|
12805
13153
|
} else {
|
|
12806
|
-
const { readFile:
|
|
12807
|
-
const content = await
|
|
13154
|
+
const { readFile: readFile26 } = await import("fs/promises");
|
|
13155
|
+
const content = await readFile26(paths.projectContext, "utf8");
|
|
12808
13156
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
12809
13157
|
if (isTemplate) {
|
|
12810
13158
|
findings.push({
|
|
@@ -12824,7 +13172,7 @@ function registerDoctor(program2) {
|
|
|
12824
13172
|
});
|
|
12825
13173
|
}
|
|
12826
13174
|
}
|
|
12827
|
-
const memories =
|
|
13175
|
+
const memories = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
|
|
12828
13176
|
const now = Date.now();
|
|
12829
13177
|
if (memories.length === 0) {
|
|
12830
13178
|
findings.push({
|
|
@@ -12976,10 +13324,10 @@ function registerDoctor(program2) {
|
|
|
12976
13324
|
if (config.enforcement?.requireBriefingFirst) {
|
|
12977
13325
|
const claudeSettings = path46.join(root, ".claude", "settings.local.json");
|
|
12978
13326
|
let hasClaudeEnforcement = false;
|
|
12979
|
-
if (
|
|
13327
|
+
if (existsSync68(claudeSettings)) {
|
|
12980
13328
|
try {
|
|
12981
|
-
const { readFile:
|
|
12982
|
-
const raw = await
|
|
13329
|
+
const { readFile: readFile26 } = await import("fs/promises");
|
|
13330
|
+
const raw = await readFile26(claudeSettings, "utf8");
|
|
12983
13331
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
12984
13332
|
} catch {
|
|
12985
13333
|
hasClaudeEnforcement = false;
|
|
@@ -13002,7 +13350,7 @@ function registerDoctor(program2) {
|
|
|
13002
13350
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
13003
13351
|
});
|
|
13004
13352
|
}
|
|
13005
|
-
findings.push(...await collectInstallFindings(root, "0.12.
|
|
13353
|
+
findings.push(...await collectInstallFindings(root, "0.12.9"));
|
|
13006
13354
|
findings.push(...await collectToolchainFindings(root));
|
|
13007
13355
|
try {
|
|
13008
13356
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -13010,7 +13358,7 @@ function registerDoctor(program2) {
|
|
|
13010
13358
|
timeout: 3e3,
|
|
13011
13359
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13012
13360
|
}).trim();
|
|
13013
|
-
const cliVersion = "0.12.
|
|
13361
|
+
const cliVersion = "0.12.9";
|
|
13014
13362
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
13015
13363
|
findings.push({
|
|
13016
13364
|
severity: "warn",
|
|
@@ -13032,14 +13380,14 @@ npm uninstall -g @hiveai/mcp`
|
|
|
13032
13380
|
];
|
|
13033
13381
|
const staleConfigs = [];
|
|
13034
13382
|
for (const cfgPath of configPaths) {
|
|
13035
|
-
if (!
|
|
13383
|
+
if (!existsSync68(cfgPath)) continue;
|
|
13036
13384
|
try {
|
|
13037
13385
|
const raw = await readFile21(cfgPath, "utf8");
|
|
13038
13386
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
13039
13387
|
staleConfigs.push(path46.relative(root, cfgPath));
|
|
13040
13388
|
if (opts.fix && !opts.dryRun) {
|
|
13041
13389
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
13042
|
-
await
|
|
13390
|
+
await writeFile34(cfgPath, updated, "utf8");
|
|
13043
13391
|
}
|
|
13044
13392
|
}
|
|
13045
13393
|
} catch {
|
|
@@ -13328,7 +13676,7 @@ which -a haive`
|
|
|
13328
13676
|
];
|
|
13329
13677
|
for (const rel of integrationFiles) {
|
|
13330
13678
|
const file = path46.join(root, rel);
|
|
13331
|
-
if (!
|
|
13679
|
+
if (!existsSync68(file)) continue;
|
|
13332
13680
|
const text = await readFile21(file, "utf8").catch(() => "");
|
|
13333
13681
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
13334
13682
|
const version = versionForBinary(bin);
|
|
@@ -13376,7 +13724,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
13376
13724
|
const isHaiveWorkspace = (await readJson(path46.join(root, "package.json")))?.name === "haive-monorepo";
|
|
13377
13725
|
if (!isHaiveWorkspace) return findings;
|
|
13378
13726
|
const cliDist = path46.join(root, "packages/cli/dist/index.js");
|
|
13379
|
-
if (!
|
|
13727
|
+
if (!existsSync68(cliDist)) {
|
|
13380
13728
|
findings.push({
|
|
13381
13729
|
severity: "warn",
|
|
13382
13730
|
code: "workspace-dist-missing",
|
|
@@ -13400,7 +13748,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
13400
13748
|
"packages/core/src/index.ts",
|
|
13401
13749
|
"packages/mcp/src/server.ts",
|
|
13402
13750
|
"packages/cli/src/index.ts"
|
|
13403
|
-
].map((rel) => path46.join(root, rel)).filter(
|
|
13751
|
+
].map((rel) => path46.join(root, rel)).filter(existsSync68);
|
|
13404
13752
|
if (sourceFiles.length > 0) {
|
|
13405
13753
|
const distMtime = statSync2(cliDist).mtimeMs;
|
|
13406
13754
|
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
@@ -13489,7 +13837,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
13489
13837
|
}
|
|
13490
13838
|
}
|
|
13491
13839
|
async function readJson(file) {
|
|
13492
|
-
if (!
|
|
13840
|
+
if (!existsSync68(file)) return null;
|
|
13493
13841
|
try {
|
|
13494
13842
|
return JSON.parse(await readFile21(file, "utf8"));
|
|
13495
13843
|
} catch {
|
|
@@ -13560,11 +13908,11 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
13560
13908
|
}
|
|
13561
13909
|
|
|
13562
13910
|
// src/commands/playback.ts
|
|
13563
|
-
import { existsSync as
|
|
13911
|
+
import { existsSync as existsSync69 } from "fs";
|
|
13564
13912
|
import "commander";
|
|
13565
13913
|
import {
|
|
13566
13914
|
findProjectRoot as findProjectRoot46,
|
|
13567
|
-
loadMemoriesFromDir as
|
|
13915
|
+
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
13568
13916
|
parseSince as parseSince3,
|
|
13569
13917
|
readUsageEvents as readUsageEvents5,
|
|
13570
13918
|
resolveHaivePaths as resolveHaivePaths42
|
|
@@ -13590,7 +13938,7 @@ function registerPlayback(program2) {
|
|
|
13590
13938
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
13591
13939
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
13592
13940
|
const sessions = bucketSessions(filtered, gapMs);
|
|
13593
|
-
const all =
|
|
13941
|
+
const all = existsSync69(paths.memoriesDir) ? await loadMemoriesFromDir36(paths.memoriesDir) : [];
|
|
13594
13942
|
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);
|
|
13595
13943
|
const enriched = sessions.map((s, i) => {
|
|
13596
13944
|
const startMs = Date.parse(s.start);
|
|
@@ -13832,11 +14180,11 @@ function runCommand3(cmd, args, cwd) {
|
|
|
13832
14180
|
}
|
|
13833
14181
|
|
|
13834
14182
|
// src/commands/welcome.ts
|
|
13835
|
-
import { existsSync as
|
|
14183
|
+
import { existsSync as existsSync70 } from "fs";
|
|
13836
14184
|
import "commander";
|
|
13837
14185
|
import {
|
|
13838
14186
|
findProjectRoot as findProjectRoot48,
|
|
13839
|
-
loadMemoriesFromDir as
|
|
14187
|
+
loadMemoriesFromDir as loadMemoriesFromDir37,
|
|
13840
14188
|
resolveHaivePaths as resolveHaivePaths44
|
|
13841
14189
|
} from "@hiveai/core";
|
|
13842
14190
|
var TYPE_RANK = {
|
|
@@ -13854,12 +14202,12 @@ function registerWelcome(program2) {
|
|
|
13854
14202
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13855
14203
|
const root = findProjectRoot48(opts.dir);
|
|
13856
14204
|
const paths = resolveHaivePaths44(root);
|
|
13857
|
-
if (!
|
|
14205
|
+
if (!existsSync70(paths.memoriesDir)) {
|
|
13858
14206
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
13859
14207
|
process.exitCode = 1;
|
|
13860
14208
|
return;
|
|
13861
14209
|
}
|
|
13862
|
-
const all = await
|
|
14210
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
13863
14211
|
const team = all.filter(
|
|
13864
14212
|
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
13865
14213
|
);
|
|
@@ -13926,7 +14274,7 @@ function registerResolveProject(program2) {
|
|
|
13926
14274
|
}
|
|
13927
14275
|
|
|
13928
14276
|
// src/commands/runtime-journal.ts
|
|
13929
|
-
import { existsSync as
|
|
14277
|
+
import { existsSync as existsSync71 } from "fs";
|
|
13930
14278
|
import path48 from "path";
|
|
13931
14279
|
import "commander";
|
|
13932
14280
|
import {
|
|
@@ -13952,7 +14300,7 @@ function registerRuntime(program2) {
|
|
|
13952
14300
|
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13953
14301
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13954
14302
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13955
|
-
if (!
|
|
14303
|
+
if (!existsSync71(paths.haiveDir)) {
|
|
13956
14304
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
13957
14305
|
process.exitCode = 1;
|
|
13958
14306
|
return;
|
|
@@ -13967,7 +14315,7 @@ function registerRuntime(program2) {
|
|
|
13967
14315
|
}
|
|
13968
14316
|
|
|
13969
14317
|
// src/commands/memory-timeline.ts
|
|
13970
|
-
import { existsSync as
|
|
14318
|
+
import { existsSync as existsSync73 } from "fs";
|
|
13971
14319
|
import path49 from "path";
|
|
13972
14320
|
import "commander";
|
|
13973
14321
|
import {
|
|
@@ -13986,13 +14334,13 @@ function registerMemoryTimeline(memory2) {
|
|
|
13986
14334
|
}
|
|
13987
14335
|
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
13988
14336
|
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
13989
|
-
if (!
|
|
14337
|
+
if (!existsSync73(paths.memoriesDir)) {
|
|
13990
14338
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13991
14339
|
process.exitCode = 1;
|
|
13992
14340
|
return;
|
|
13993
14341
|
}
|
|
13994
14342
|
const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13995
|
-
const all = await
|
|
14343
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
13996
14344
|
const { entries, notice } = collectTimelineEntries2(all, {
|
|
13997
14345
|
memoryId: opts.id,
|
|
13998
14346
|
topic: opts.topic,
|
|
@@ -14004,7 +14352,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
14004
14352
|
}
|
|
14005
14353
|
|
|
14006
14354
|
// src/commands/memory-conflict-candidates.ts
|
|
14007
|
-
import { existsSync as
|
|
14355
|
+
import { existsSync as existsSync74 } from "fs";
|
|
14008
14356
|
import path50 from "path";
|
|
14009
14357
|
import "commander";
|
|
14010
14358
|
import {
|
|
@@ -14029,7 +14377,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14029
14377
|
).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) => {
|
|
14030
14378
|
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
14031
14379
|
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
14032
|
-
if (!
|
|
14380
|
+
if (!existsSync74(paths.memoriesDir)) {
|
|
14033
14381
|
ui.error("No memories \u2014 run `haive init`.");
|
|
14034
14382
|
process.exitCode = 1;
|
|
14035
14383
|
return;
|
|
@@ -14039,7 +14387,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14039
14387
|
const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
|
|
14040
14388
|
const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
|
|
14041
14389
|
const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
|
|
14042
|
-
const all = await
|
|
14390
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
14043
14391
|
const lexical = findLexicalConflictPairs2(all, {
|
|
14044
14392
|
sinceDays,
|
|
14045
14393
|
types: parseTypes(opts.types),
|
|
@@ -14065,8 +14413,8 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14065
14413
|
|
|
14066
14414
|
// src/commands/enforce.ts
|
|
14067
14415
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
14068
|
-
import { existsSync as
|
|
14069
|
-
import { chmod as chmod2, mkdir as
|
|
14416
|
+
import { existsSync as existsSync75, statSync as statSync3 } from "fs";
|
|
14417
|
+
import { chmod as chmod2, mkdir as mkdir20, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile35 } from "fs/promises";
|
|
14070
14418
|
import path51 from "path";
|
|
14071
14419
|
import "commander";
|
|
14072
14420
|
import {
|
|
@@ -14075,7 +14423,7 @@ import {
|
|
|
14075
14423
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
14076
14424
|
isFreshIsoDate,
|
|
14077
14425
|
loadConfig as loadConfig13,
|
|
14078
|
-
loadMemoriesFromDir as
|
|
14426
|
+
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
14079
14427
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
14080
14428
|
readRecentBriefingMarker,
|
|
14081
14429
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
@@ -14094,7 +14442,7 @@ function registerEnforce(program2) {
|
|
|
14094
14442
|
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) => {
|
|
14095
14443
|
const root = findProjectRoot52(opts.dir);
|
|
14096
14444
|
const paths = resolveHaivePaths48(root);
|
|
14097
|
-
await
|
|
14445
|
+
await mkdir20(paths.haiveDir, { recursive: true });
|
|
14098
14446
|
const current = await loadConfig13(paths);
|
|
14099
14447
|
await saveConfig4(paths, {
|
|
14100
14448
|
...current,
|
|
@@ -14140,14 +14488,14 @@ function registerEnforce(program2) {
|
|
|
14140
14488
|
const root = findProjectRoot52(opts.dir);
|
|
14141
14489
|
const paths = resolveHaivePaths48(root);
|
|
14142
14490
|
const cacheDir = path51.join(paths.haiveDir, ".cache");
|
|
14143
|
-
if (
|
|
14491
|
+
if (existsSync75(cacheDir)) {
|
|
14144
14492
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
14145
14493
|
else {
|
|
14146
14494
|
const removed = await cleanupCacheDir(cacheDir);
|
|
14147
14495
|
ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
14148
14496
|
}
|
|
14149
14497
|
}
|
|
14150
|
-
if (
|
|
14498
|
+
if (existsSync75(paths.runtimeDir)) {
|
|
14151
14499
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
14152
14500
|
else {
|
|
14153
14501
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
@@ -14172,8 +14520,8 @@ function registerEnforce(program2) {
|
|
|
14172
14520
|
const root = resolveRoot(opts.dir, payload);
|
|
14173
14521
|
if (!root) return;
|
|
14174
14522
|
const paths = resolveHaivePaths48(root);
|
|
14175
|
-
if (!
|
|
14176
|
-
await
|
|
14523
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
14524
|
+
await mkdir20(paths.runtimeDir, { recursive: true });
|
|
14177
14525
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
14178
14526
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
14179
14527
|
await applyLightweightRepairs(root, paths);
|
|
@@ -14235,7 +14583,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14235
14583
|
const root = resolveRoot(opts.dir, payload);
|
|
14236
14584
|
if (!root) return;
|
|
14237
14585
|
const paths = resolveHaivePaths48(root);
|
|
14238
|
-
if (!
|
|
14586
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
14239
14587
|
if (!isWriteLikeTool(payload)) return;
|
|
14240
14588
|
const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
|
|
14241
14589
|
if (ok) {
|
|
@@ -14279,7 +14627,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14279
14627
|
async function buildFinishReport(dir) {
|
|
14280
14628
|
const root = findProjectRoot52(dir);
|
|
14281
14629
|
const paths = resolveHaivePaths48(root);
|
|
14282
|
-
const initialized =
|
|
14630
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
14283
14631
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14284
14632
|
const mode = config.enforcement?.mode ?? "strict";
|
|
14285
14633
|
const findings = [];
|
|
@@ -14477,7 +14825,7 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
14477
14825
|
async function runWithEnforcement(command, args, opts) {
|
|
14478
14826
|
const root = findProjectRoot52(opts.dir);
|
|
14479
14827
|
const paths = resolveHaivePaths48(root);
|
|
14480
|
-
if (!
|
|
14828
|
+
if (!existsSync75(paths.haiveDir)) {
|
|
14481
14829
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
14482
14830
|
process.exit(1);
|
|
14483
14831
|
}
|
|
@@ -14547,7 +14895,7 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14547
14895
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
14548
14896
|
});
|
|
14549
14897
|
const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
|
|
14550
|
-
await
|
|
14898
|
+
await mkdir20(dir, { recursive: true });
|
|
14551
14899
|
const file = path51.join(dir, `${sessionId}.md`);
|
|
14552
14900
|
const parts = [
|
|
14553
14901
|
"# hAIve Briefing",
|
|
@@ -14566,13 +14914,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14566
14914
|
if (briefing.setup_warnings.length > 0) {
|
|
14567
14915
|
parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
|
|
14568
14916
|
}
|
|
14569
|
-
await
|
|
14917
|
+
await writeFile35(file, parts.join("\n") + "\n", "utf8");
|
|
14570
14918
|
return file;
|
|
14571
14919
|
}
|
|
14572
14920
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
14573
14921
|
const root = findProjectRoot52(dir);
|
|
14574
14922
|
const paths = resolveHaivePaths48(root);
|
|
14575
|
-
const initialized =
|
|
14923
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
14576
14924
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14577
14925
|
if (initialized) await applyLightweightRepairs(root, paths);
|
|
14578
14926
|
const mode = config.enforcement?.mode ?? "strict";
|
|
@@ -14603,7 +14951,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14603
14951
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14604
14952
|
});
|
|
14605
14953
|
}
|
|
14606
|
-
findings.push(...await inspectIntegrationVersions(root, "0.12.
|
|
14954
|
+
findings.push(...await inspectIntegrationVersions(root, "0.12.9"));
|
|
14607
14955
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14608
14956
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14609
14957
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14673,8 +15021,8 @@ function withCategories(report) {
|
|
|
14673
15021
|
};
|
|
14674
15022
|
}
|
|
14675
15023
|
async function hasRecentSessionRecap(paths) {
|
|
14676
|
-
if (!
|
|
14677
|
-
const all = await
|
|
15024
|
+
if (!existsSync75(paths.memoriesDir)) return false;
|
|
15025
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14678
15026
|
return all.some(({ memory: memory2 }) => {
|
|
14679
15027
|
const fm = memory2.frontmatter;
|
|
14680
15028
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -14682,8 +15030,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
14682
15030
|
});
|
|
14683
15031
|
}
|
|
14684
15032
|
async function verifyMemoryPolicy(paths, config) {
|
|
14685
|
-
if (!
|
|
14686
|
-
const all = await
|
|
15033
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15034
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14687
15035
|
const findings = [];
|
|
14688
15036
|
const staleImportant = [];
|
|
14689
15037
|
let verified = 0;
|
|
@@ -14720,12 +15068,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
14720
15068
|
return findings;
|
|
14721
15069
|
}
|
|
14722
15070
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
14723
|
-
if (!
|
|
15071
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
14724
15072
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
14725
15073
|
if (changedFiles.length === 0) {
|
|
14726
15074
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
14727
15075
|
}
|
|
14728
|
-
const all = await
|
|
15076
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14729
15077
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
14730
15078
|
const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
14731
15079
|
const fm = memory2.frontmatter;
|
|
@@ -14829,7 +15177,7 @@ function isGeneratedArtifactStatusLine(line) {
|
|
|
14829
15177
|
}
|
|
14830
15178
|
async function cleanupRuntimeDir(runtimeDir) {
|
|
14831
15179
|
let removed = 0;
|
|
14832
|
-
await
|
|
15180
|
+
await mkdir20(runtimeDir, { recursive: true });
|
|
14833
15181
|
const entries = await readdir6(runtimeDir, { withFileTypes: true }).catch(() => []);
|
|
14834
15182
|
for (const entry of entries) {
|
|
14835
15183
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
@@ -14840,9 +15188,9 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14840
15188
|
await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
14841
15189
|
removed++;
|
|
14842
15190
|
}
|
|
14843
|
-
await
|
|
14844
|
-
if (!
|
|
14845
|
-
await
|
|
15191
|
+
await writeFile35(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
15192
|
+
if (!existsSync75(path51.join(runtimeDir, "README.md"))) {
|
|
15193
|
+
await writeFile35(
|
|
14846
15194
|
path51.join(runtimeDir, "README.md"),
|
|
14847
15195
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
14848
15196
|
"utf8"
|
|
@@ -14852,14 +15200,14 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14852
15200
|
}
|
|
14853
15201
|
async function cleanupCacheDir(cacheDir) {
|
|
14854
15202
|
let removed = 0;
|
|
14855
|
-
await
|
|
15203
|
+
await mkdir20(cacheDir, { recursive: true });
|
|
14856
15204
|
const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
14857
15205
|
for (const entry of entries) {
|
|
14858
15206
|
if (entry.name === ".gitignore") continue;
|
|
14859
15207
|
await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
14860
15208
|
removed++;
|
|
14861
15209
|
}
|
|
14862
|
-
await
|
|
15210
|
+
await writeFile35(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
14863
15211
|
return removed;
|
|
14864
15212
|
}
|
|
14865
15213
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -14884,7 +15232,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
14884
15232
|
const findings = [];
|
|
14885
15233
|
for (const rel of files) {
|
|
14886
15234
|
const file = path51.join(root, rel);
|
|
14887
|
-
if (!
|
|
15235
|
+
if (!existsSync75(file)) continue;
|
|
14888
15236
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14889
15237
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
14890
15238
|
const version = versionForBinary2(bin);
|
|
@@ -14994,7 +15342,7 @@ async function resolveCiDiffRange(root) {
|
|
|
14994
15342
|
}
|
|
14995
15343
|
async function resolveGithubEventRange(root) {
|
|
14996
15344
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
14997
|
-
if (!eventPath || !
|
|
15345
|
+
if (!eventPath || !existsSync75(eventPath)) return null;
|
|
14998
15346
|
try {
|
|
14999
15347
|
const event = JSON.parse(await readFile23(eventPath, "utf8"));
|
|
15000
15348
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -15277,11 +15625,11 @@ function buildScore(findings, threshold = 80) {
|
|
|
15277
15625
|
}
|
|
15278
15626
|
async function installGitEnforcement(root) {
|
|
15279
15627
|
const hooksDir = path51.join(root, ".git", "hooks");
|
|
15280
|
-
if (!
|
|
15628
|
+
if (!existsSync75(path51.join(root, ".git"))) {
|
|
15281
15629
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
15282
15630
|
return;
|
|
15283
15631
|
}
|
|
15284
|
-
await
|
|
15632
|
+
await mkdir20(hooksDir, { recursive: true });
|
|
15285
15633
|
const hooks = [
|
|
15286
15634
|
{
|
|
15287
15635
|
name: "pre-commit",
|
|
@@ -15300,17 +15648,17 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
15300
15648
|
];
|
|
15301
15649
|
for (const hook of hooks) {
|
|
15302
15650
|
const file = path51.join(hooksDir, hook.name);
|
|
15303
|
-
if (
|
|
15651
|
+
if (existsSync75(file)) {
|
|
15304
15652
|
const current = await readFile23(file, "utf8").catch(() => "");
|
|
15305
15653
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
15306
|
-
await
|
|
15654
|
+
await writeFile35(file, hook.body, "utf8");
|
|
15307
15655
|
} else {
|
|
15308
|
-
await
|
|
15656
|
+
await writeFile35(file, `${current.trimEnd()}
|
|
15309
15657
|
|
|
15310
15658
|
${hook.body}`, "utf8");
|
|
15311
15659
|
}
|
|
15312
15660
|
} else {
|
|
15313
|
-
await
|
|
15661
|
+
await writeFile35(file, hook.body, "utf8");
|
|
15314
15662
|
}
|
|
15315
15663
|
await chmod2(file, 493);
|
|
15316
15664
|
}
|
|
@@ -15318,12 +15666,12 @@ ${hook.body}`, "utf8");
|
|
|
15318
15666
|
}
|
|
15319
15667
|
async function installCiEnforcement(root) {
|
|
15320
15668
|
const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
15321
|
-
await
|
|
15322
|
-
if (
|
|
15669
|
+
await mkdir20(path51.dirname(workflowPath), { recursive: true });
|
|
15670
|
+
if (existsSync75(workflowPath)) {
|
|
15323
15671
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
15324
15672
|
return;
|
|
15325
15673
|
}
|
|
15326
|
-
await
|
|
15674
|
+
await writeFile35(workflowPath, `name: haive-enforcement
|
|
15327
15675
|
|
|
15328
15676
|
on:
|
|
15329
15677
|
pull_request:
|
|
@@ -15451,11 +15799,11 @@ function normalizeToolPath(file, root) {
|
|
|
15451
15799
|
return path51.relative(root, normalized).replace(/\\/g, "/");
|
|
15452
15800
|
}
|
|
15453
15801
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
15454
|
-
if (!
|
|
15802
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15455
15803
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
15456
15804
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
15457
15805
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
15458
|
-
const all = await
|
|
15806
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
15459
15807
|
return all.filter(({ memory: memory2 }) => {
|
|
15460
15808
|
const fm = memory2.frontmatter;
|
|
15461
15809
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -15525,19 +15873,19 @@ function registerRun(program2) {
|
|
|
15525
15873
|
|
|
15526
15874
|
// src/commands/sensors.ts
|
|
15527
15875
|
import { execFile as execFile2 } from "child_process";
|
|
15528
|
-
import { existsSync as
|
|
15529
|
-
import { chmod as chmod3, mkdir as
|
|
15876
|
+
import { existsSync as existsSync76 } from "fs";
|
|
15877
|
+
import { chmod as chmod3, mkdir as mkdir21, readFile as readFile24, writeFile as writeFile36 } from "fs/promises";
|
|
15530
15878
|
import path53 from "path";
|
|
15531
15879
|
import { promisify as promisify2 } from "util";
|
|
15532
15880
|
import "commander";
|
|
15533
15881
|
import {
|
|
15534
15882
|
findProjectRoot as findProjectRoot53,
|
|
15535
15883
|
isRetiredMemory as isRetiredMemory3,
|
|
15536
|
-
loadMemoriesFromDir as
|
|
15884
|
+
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
15537
15885
|
resolveHaivePaths as resolveHaivePaths49,
|
|
15538
15886
|
runSensors as runSensors2,
|
|
15539
15887
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15540
|
-
serializeMemory as
|
|
15888
|
+
serializeMemory as serializeMemory27
|
|
15541
15889
|
} from "@hiveai/core";
|
|
15542
15890
|
var exec2 = promisify2(execFile2);
|
|
15543
15891
|
function registerSensors(program2) {
|
|
@@ -15608,7 +15956,7 @@ function registerSensors(program2) {
|
|
|
15608
15956
|
}
|
|
15609
15957
|
const root = findProjectRoot53(opts.dir);
|
|
15610
15958
|
const paths = resolveHaivePaths49(root);
|
|
15611
|
-
const loaded =
|
|
15959
|
+
const loaded = existsSync76(paths.memoriesDir) ? await loadMemoriesFromDir39(paths.memoriesDir) : [];
|
|
15612
15960
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
15613
15961
|
if (!found) {
|
|
15614
15962
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -15628,7 +15976,7 @@ function registerSensors(program2) {
|
|
|
15628
15976
|
},
|
|
15629
15977
|
body: found.memory.body
|
|
15630
15978
|
};
|
|
15631
|
-
await
|
|
15979
|
+
await writeFile36(found.filePath, serializeMemory27(next), "utf8");
|
|
15632
15980
|
ui.success(`Updated ${id}: sensor severity=${severity}`);
|
|
15633
15981
|
if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
|
|
15634
15982
|
ui.info(`message=${sensor.message}`);
|
|
@@ -15644,10 +15992,10 @@ function registerSensors(program2) {
|
|
|
15644
15992
|
const paths = resolveHaivePaths49(root);
|
|
15645
15993
|
const rows = await sensorRows(paths);
|
|
15646
15994
|
const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
|
|
15647
|
-
await
|
|
15995
|
+
await mkdir21(outDir, { recursive: true });
|
|
15648
15996
|
const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
15649
15997
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
15650
|
-
await
|
|
15998
|
+
await writeFile36(outPath, content, "utf8");
|
|
15651
15999
|
if (format === "grep") await chmod3(outPath, 493);
|
|
15652
16000
|
ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
|
|
15653
16001
|
});
|
|
@@ -15670,8 +16018,8 @@ async function sensorRows(paths) {
|
|
|
15670
16018
|
});
|
|
15671
16019
|
}
|
|
15672
16020
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
15673
|
-
if (!
|
|
15674
|
-
const loaded = await
|
|
16021
|
+
if (!existsSync76(paths.memoriesDir)) return [];
|
|
16022
|
+
const loaded = await loadMemoriesFromDir39(paths.memoriesDir);
|
|
15675
16023
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
15676
16024
|
const sensor = memory2.frontmatter.sensor;
|
|
15677
16025
|
if (!sensor) return false;
|
|
@@ -15711,9 +16059,253 @@ function shellQuote(value) {
|
|
|
15711
16059
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
15712
16060
|
}
|
|
15713
16061
|
|
|
16062
|
+
// src/commands/ingest.ts
|
|
16063
|
+
import { existsSync as existsSync77 } from "fs";
|
|
16064
|
+
import { mkdir as mkdir23, readFile as readFile25, writeFile as writeFile37 } from "fs/promises";
|
|
16065
|
+
import path54 from "path";
|
|
16066
|
+
import "commander";
|
|
16067
|
+
import {
|
|
16068
|
+
draftsFromFindings as draftsFromFindings2,
|
|
16069
|
+
filterNewDrafts as filterNewDrafts2,
|
|
16070
|
+
findProjectRoot as findProjectRoot54,
|
|
16071
|
+
loadMemoriesFromDir as loadMemoriesFromDir40,
|
|
16072
|
+
memoryFilePath as memoryFilePath12,
|
|
16073
|
+
parseFindings as parseFindings2,
|
|
16074
|
+
resolveHaivePaths as resolveHaivePaths50,
|
|
16075
|
+
serializeMemory as serializeMemory28
|
|
16076
|
+
} from "@hiveai/core";
|
|
16077
|
+
var SEVERITIES = ["info", "minor", "major", "critical", "blocker"];
|
|
16078
|
+
function registerIngest(program2) {
|
|
16079
|
+
program2.command("ingest").description(
|
|
16080
|
+
"Ingest scanner findings (SonarQube / SARIF) as proposed, anchored memories with sensors.\n\n Closes the review\u2194memory loop: a real defect a scanner found becomes a `gotcha`/`convention`\n memory anchored to the file, pre-filled with a conservative `warn` sensor, so the next agent\n is steered away from it. Drafts are status=proposed; a human validates/promotes them.\n\n Example:\n haive ingest --from sarif eslint.sarif --dry-run\n haive ingest --from sonar sonar-issues.json --scope team --min-severity major\n"
|
|
16081
|
+
).argument("<file>", "path to the findings report (JSON)").requiredOption("--from <format>", "report format: sarif | sonar").option("--dry-run", "show what would be created without writing", false).option("--scope <scope>", "memory scope: personal | team | module", "team").option("--module <name>", "module name (required when scope=module)").option("--type <type>", "memory type: gotcha | convention", "gotcha").option("--min-severity <severity>", "ignore findings below this severity (info|minor|major|critical|blocker)").option("--limit <n>", "cap the number of memories created").option("--author <author>", "author email or handle").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (file, opts) => {
|
|
16082
|
+
const format = opts.from;
|
|
16083
|
+
if (format !== "sarif" && format !== "sonar") {
|
|
16084
|
+
ui.error("--from must be sarif or sonar");
|
|
16085
|
+
process.exitCode = 1;
|
|
16086
|
+
return;
|
|
16087
|
+
}
|
|
16088
|
+
if (opts.type && opts.type !== "gotcha" && opts.type !== "convention") {
|
|
16089
|
+
ui.error("--type must be gotcha or convention");
|
|
16090
|
+
process.exitCode = 1;
|
|
16091
|
+
return;
|
|
16092
|
+
}
|
|
16093
|
+
if (opts.minSeverity && !SEVERITIES.includes(opts.minSeverity)) {
|
|
16094
|
+
ui.error(`--min-severity must be one of: ${SEVERITIES.join(", ")}`);
|
|
16095
|
+
process.exitCode = 1;
|
|
16096
|
+
return;
|
|
16097
|
+
}
|
|
16098
|
+
const root = findProjectRoot54(opts.dir);
|
|
16099
|
+
const paths = resolveHaivePaths50(root);
|
|
16100
|
+
if (!existsSync77(paths.haiveDir)) {
|
|
16101
|
+
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
16102
|
+
process.exitCode = 1;
|
|
16103
|
+
return;
|
|
16104
|
+
}
|
|
16105
|
+
const reportPath = path54.resolve(root, file);
|
|
16106
|
+
if (!existsSync77(reportPath)) {
|
|
16107
|
+
ui.error(`Report file not found: ${reportPath}`);
|
|
16108
|
+
process.exitCode = 1;
|
|
16109
|
+
return;
|
|
16110
|
+
}
|
|
16111
|
+
let raw;
|
|
16112
|
+
try {
|
|
16113
|
+
raw = await readFile25(reportPath, "utf8");
|
|
16114
|
+
} catch (err) {
|
|
16115
|
+
ui.error(`Could not read ${reportPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16116
|
+
process.exitCode = 1;
|
|
16117
|
+
return;
|
|
16118
|
+
}
|
|
16119
|
+
let drafts;
|
|
16120
|
+
try {
|
|
16121
|
+
const findings = parseFindings2(format, raw);
|
|
16122
|
+
drafts = draftsFromFindings2(findings, {
|
|
16123
|
+
type: opts.type ?? "gotcha",
|
|
16124
|
+
scope: opts.scope ?? "team",
|
|
16125
|
+
module: opts.module,
|
|
16126
|
+
author: opts.author,
|
|
16127
|
+
...opts.minSeverity ? { minSeverity: opts.minSeverity } : {},
|
|
16128
|
+
...opts.limit ? { limit: Math.max(0, Number.parseInt(opts.limit, 10) || 0) } : {}
|
|
16129
|
+
});
|
|
16130
|
+
} catch (err) {
|
|
16131
|
+
ui.error(`Failed to parse ${format} report: ${err instanceof Error ? err.message : String(err)}`);
|
|
16132
|
+
process.exitCode = 1;
|
|
16133
|
+
return;
|
|
16134
|
+
}
|
|
16135
|
+
const existing = existsSync77(paths.memoriesDir) ? await loadMemoriesFromDir40(paths.memoriesDir) : [];
|
|
16136
|
+
const existingTopics = new Set(
|
|
16137
|
+
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
16138
|
+
);
|
|
16139
|
+
const fresh = filterNewDrafts2(drafts, existingTopics);
|
|
16140
|
+
const skipped = drafts.length - fresh.length;
|
|
16141
|
+
if (opts.json) {
|
|
16142
|
+
const created2 = [];
|
|
16143
|
+
if (!opts.dryRun) {
|
|
16144
|
+
for (const draft of fresh) created2.push(await writeDraft2(paths, draft));
|
|
16145
|
+
}
|
|
16146
|
+
console.log(
|
|
16147
|
+
JSON.stringify(
|
|
16148
|
+
{
|
|
16149
|
+
format,
|
|
16150
|
+
parsed: drafts.length,
|
|
16151
|
+
new: fresh.length,
|
|
16152
|
+
skipped_existing: skipped,
|
|
16153
|
+
dry_run: Boolean(opts.dryRun),
|
|
16154
|
+
drafts: fresh.map((d) => ({
|
|
16155
|
+
id: d.frontmatter.id,
|
|
16156
|
+
topic: d.topic,
|
|
16157
|
+
path: d.finding.path,
|
|
16158
|
+
rule: d.finding.ruleId,
|
|
16159
|
+
severity: d.finding.severity,
|
|
16160
|
+
has_sensor: d.has_sensor
|
|
16161
|
+
}))
|
|
16162
|
+
},
|
|
16163
|
+
null,
|
|
16164
|
+
2
|
|
16165
|
+
)
|
|
16166
|
+
);
|
|
16167
|
+
return;
|
|
16168
|
+
}
|
|
16169
|
+
console.log(
|
|
16170
|
+
ui.bold(
|
|
16171
|
+
`hAIve ingest (${format}) \u2014 ${drafts.length} finding(s), ${fresh.length} new` + (skipped > 0 ? `, ${skipped} already ingested` : "")
|
|
16172
|
+
)
|
|
16173
|
+
);
|
|
16174
|
+
if (fresh.length === 0) {
|
|
16175
|
+
ui.info("Nothing to ingest.");
|
|
16176
|
+
return;
|
|
16177
|
+
}
|
|
16178
|
+
for (const draft of fresh) {
|
|
16179
|
+
const sensorTag = draft.has_sensor ? ui.dim(" +sensor") : "";
|
|
16180
|
+
console.log(
|
|
16181
|
+
` \u2022 ${draft.finding.ruleId} ${ui.dim(`(${draft.finding.severity})`)} \u2192 ${draft.finding.path}${sensorTag}`
|
|
16182
|
+
);
|
|
16183
|
+
if (opts.dryRun) console.log(` ${ui.dim("would create:")} ${draft.frontmatter.id}`);
|
|
16184
|
+
}
|
|
16185
|
+
if (opts.dryRun) {
|
|
16186
|
+
ui.info(`Dry run \u2014 nothing written. Re-run without --dry-run to create ${fresh.length} proposed memory(ies).`);
|
|
16187
|
+
return;
|
|
16188
|
+
}
|
|
16189
|
+
let created = 0;
|
|
16190
|
+
for (const draft of fresh) {
|
|
16191
|
+
await writeDraft2(paths, draft);
|
|
16192
|
+
created++;
|
|
16193
|
+
}
|
|
16194
|
+
ui.success(`Created ${created} proposed memory(ies) under ${path54.relative(root, paths.memoriesDir)}/`);
|
|
16195
|
+
ui.info("Review with `haive memory pending`; promote sensors with `haive sensors promote <id> --yes`.");
|
|
16196
|
+
});
|
|
16197
|
+
}
|
|
16198
|
+
async function writeDraft2(paths, draft) {
|
|
16199
|
+
const file = memoryFilePath12(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
16200
|
+
await mkdir23(path54.dirname(file), { recursive: true });
|
|
16201
|
+
await writeFile37(file, serializeMemory28({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
16202
|
+
return file;
|
|
16203
|
+
}
|
|
16204
|
+
|
|
16205
|
+
// src/commands/dashboard.ts
|
|
16206
|
+
import { existsSync as existsSync78 } from "fs";
|
|
16207
|
+
import "commander";
|
|
16208
|
+
import {
|
|
16209
|
+
buildDashboard,
|
|
16210
|
+
findProjectRoot as findProjectRoot55,
|
|
16211
|
+
loadMemoriesFromDir as loadMemoriesFromDir41,
|
|
16212
|
+
loadUsageIndex as loadUsageIndex29,
|
|
16213
|
+
resolveHaivePaths as resolveHaivePaths51
|
|
16214
|
+
} from "@hiveai/core";
|
|
16215
|
+
function registerDashboard(program2) {
|
|
16216
|
+
program2.command("dashboard").description(
|
|
16217
|
+
"Non-interactive observability snapshot of the memory corpus.\n\n One-shot rollup an agent or CI can read (unlike `haive tui`, no TTY needed):\n inventory, impact tiers + top memories, sensors (and which ones fired),\n health (stale / anchorless / pending / prune candidates), decay, and corpus weight.\n Use --json to pipe it into other tooling."
|
|
16218
|
+
).option("--json", "emit the full report as JSON", false).option("--top <n>", "rows per top-list", "10").option("--dormant-days <n>", "dormancy window for impact scoring").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
16219
|
+
const root = findProjectRoot55(opts.dir);
|
|
16220
|
+
const paths = resolveHaivePaths51(root);
|
|
16221
|
+
if (!existsSync78(paths.haiveDir)) {
|
|
16222
|
+
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
16223
|
+
process.exitCode = 1;
|
|
16224
|
+
return;
|
|
16225
|
+
}
|
|
16226
|
+
const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
|
|
16227
|
+
const usage = await loadUsageIndex29(paths);
|
|
16228
|
+
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
16229
|
+
const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
|
|
16230
|
+
const report = buildDashboard(memories, usage, {
|
|
16231
|
+
top,
|
|
16232
|
+
...dormantDays !== void 0 && Number.isFinite(dormantDays) ? { dormantDays } : {}
|
|
16233
|
+
});
|
|
16234
|
+
if (opts.json) {
|
|
16235
|
+
console.log(JSON.stringify(report, null, 2));
|
|
16236
|
+
return;
|
|
16237
|
+
}
|
|
16238
|
+
renderDashboard(report);
|
|
16239
|
+
});
|
|
16240
|
+
}
|
|
16241
|
+
function renderDashboard(r) {
|
|
16242
|
+
const { inventory: inv, impact, sensors, health, decay, corpus } = r;
|
|
16243
|
+
console.log(ui.bold("hAIve dashboard"));
|
|
16244
|
+
console.log(
|
|
16245
|
+
` ${ui.dim("corpus:")} ${inv.total} policy memor${inv.total === 1 ? "y" : "ies"} (${inv.active} active, ${inv.retired} retired) \xB7 ${inv.session_recaps} recap(s) \xB7 ~${corpus.est_tokens.toLocaleString()} tokens`
|
|
16246
|
+
);
|
|
16247
|
+
console.log(` ${ui.dim("scopes:")} ${formatCounts(inv.by_scope)}`);
|
|
16248
|
+
console.log(` ${ui.dim("types: ")} ${formatCounts(inv.by_type)}`);
|
|
16249
|
+
console.log();
|
|
16250
|
+
console.log(ui.bold("Impact"));
|
|
16251
|
+
console.log(
|
|
16252
|
+
` ${ui.green(`high ${impact.high}`)} \xB7 ${ui.yellow(`medium ${impact.medium}`)} \xB7 low ${impact.low} \xB7 ${ui.dim(`dormant ${impact.dormant}`)} \xB7 ${impact.prune_candidates > 0 ? ui.red(`prune ${impact.prune_candidates}`) : "prune 0"}`
|
|
16253
|
+
);
|
|
16254
|
+
if (impact.top.length > 0) {
|
|
16255
|
+
console.log(ui.dim(" top by demonstrated utility:"));
|
|
16256
|
+
for (const row of impact.top.filter((x) => x.score > 0).slice(0, 8)) {
|
|
16257
|
+
console.log(
|
|
16258
|
+
` ${tierMark(row.tier)} ${row.score.toFixed(2)} ${row.id}` + (row.signals.length ? ui.dim(` [${row.signals.join(", ")}]`) : "")
|
|
16259
|
+
);
|
|
16260
|
+
}
|
|
16261
|
+
}
|
|
16262
|
+
console.log();
|
|
16263
|
+
console.log(ui.bold("Sensors"));
|
|
16264
|
+
console.log(
|
|
16265
|
+
` ${sensors.total} total \xB7 ${sensors.block} block \xB7 ${sensors.warn} warn \xB7 ${ui.dim(`${sensors.autogen} autogen`)} \xB7 ${sensors.fired > 0 ? ui.green(`${sensors.fired} fired`) : "0 fired"}`
|
|
16266
|
+
);
|
|
16267
|
+
for (const s of sensors.recently_fired.slice(0, 5)) {
|
|
16268
|
+
const marker = s.severity === "block" ? ui.red("\u2717") : ui.yellow("\u26A0");
|
|
16269
|
+
console.log(` ${marker} ${s.id} ${ui.dim(`last fired ${s.last_fired.slice(0, 10)}`)}`);
|
|
16270
|
+
}
|
|
16271
|
+
console.log();
|
|
16272
|
+
console.log(ui.bold("Health"));
|
|
16273
|
+
console.log(
|
|
16274
|
+
` stale ${warnNum(health.stale)} \xB7 anchorless ${warnNum(health.anchorless)} \xB7 pending ${health.pending} \xB7 prune candidates ${warnNum(health.prune_candidates)}`
|
|
16275
|
+
);
|
|
16276
|
+
if (health.anchorless > 0) {
|
|
16277
|
+
ui.info("Anchorless validated decisions/gotchas can't detect drift \u2014 add `paths`/`symbols`.");
|
|
16278
|
+
}
|
|
16279
|
+
console.log();
|
|
16280
|
+
console.log(ui.bold(`Decay (>${decay.decay_days}d)`));
|
|
16281
|
+
console.log(` ${decay.decaying} decaying memor${decay.decaying === 1 ? "y" : "ies"}`);
|
|
16282
|
+
for (const d of decay.top_dormant.slice(0, 5)) {
|
|
16283
|
+
const last = d.last_read_at ? d.last_read_at.slice(0, 10) : "never read";
|
|
16284
|
+
console.log(` ${ui.dim(String(d.age_days).padStart(4) + "d")} ${d.id} ${ui.dim(`(${last})`)}`);
|
|
16285
|
+
}
|
|
16286
|
+
if (health.prune_candidates > 0 || decay.decaying > 0) {
|
|
16287
|
+
console.log();
|
|
16288
|
+
ui.info("Review low-value memories with `haive memory impact` and `haive memory lint`.");
|
|
16289
|
+
}
|
|
16290
|
+
}
|
|
16291
|
+
function formatCounts(map) {
|
|
16292
|
+
const entries = Object.entries(map).sort((a, b) => b[1] - a[1]);
|
|
16293
|
+
if (entries.length === 0) return "none";
|
|
16294
|
+
return entries.map(([k, v]) => `${k} ${v}`).join(", ");
|
|
16295
|
+
}
|
|
16296
|
+
function tierMark(tier) {
|
|
16297
|
+
if (tier === "high") return ui.green("\u25CF");
|
|
16298
|
+
if (tier === "medium") return ui.yellow("\u25CF");
|
|
16299
|
+
if (tier === "dormant") return ui.dim("\u25CB");
|
|
16300
|
+
return "\xB7";
|
|
16301
|
+
}
|
|
16302
|
+
function warnNum(n) {
|
|
16303
|
+
return n > 0 ? ui.yellow(String(n)) : String(n);
|
|
16304
|
+
}
|
|
16305
|
+
|
|
15714
16306
|
// src/index.ts
|
|
15715
|
-
var program = new
|
|
15716
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.
|
|
16307
|
+
var program = new Command58();
|
|
16308
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.9").option("--advanced", "show maintenance and experimental commands in help");
|
|
15717
16309
|
registerInit(program);
|
|
15718
16310
|
registerWelcome(program);
|
|
15719
16311
|
registerResolveProject(program);
|
|
@@ -15722,6 +16314,8 @@ registerEnforce(program);
|
|
|
15722
16314
|
registerRun(program);
|
|
15723
16315
|
registerAgent(program);
|
|
15724
16316
|
registerSensors(program);
|
|
16317
|
+
registerIngest(program);
|
|
16318
|
+
registerDashboard(program);
|
|
15725
16319
|
registerMcp(program);
|
|
15726
16320
|
registerBriefing(program);
|
|
15727
16321
|
registerTui(program);
|