@hiveai/cli 0.12.3 → 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 +1247 -548
- 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,
|
|
@@ -2778,7 +2942,7 @@ This repo uses **hAIve** for shared context. The map:
|
|
|
2778
2942
|
1. **Before editing** for a goal, call \`get_briefing\` (task + files/symbols) to load ranked context \u2014 or \`mem_relevant_to\` if project context is already loaded this session.
|
|
2779
2943
|
2. **When an approach fails**, call \`mem_tried\` right away so the next agent skips the dead end.
|
|
2780
2944
|
3. **Before closing** a substantive session, run the \`post_task\` prompt to capture what was learned.
|
|
2781
|
-
4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, then rerun it.
|
|
2945
|
+
4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, wait for GitHub Actions to pass when applicable, then rerun it.
|
|
2782
2946
|
|
|
2783
2947
|
If the haive MCP server is not available, tell the developer rather than silently skipping it.
|
|
2784
2948
|
|
|
@@ -2805,7 +2969,7 @@ This repository uses **hAIve**. Running \`haive init\` means the team expects ag
|
|
|
2805
2969
|
|
|
2806
2970
|
- On failure: **\`mem_tried\`** immediately.
|
|
2807
2971
|
- Before closing a substantive session: MCP prompt **\`post_task\`** when there is something worth capturing.
|
|
2808
|
-
- Before final response: **\`haive enforce finish\`** must pass; it checks commit/push
|
|
2972
|
+
- Before final response: **\`haive enforce finish\`** must pass; it checks commit/push, release version/tag protocol, and GitHub Actions success for pushed HEAD when the repo has a GitHub remote.
|
|
2809
2973
|
|
|
2810
2974
|
## If haive MCP is missing
|
|
2811
2975
|
|
|
@@ -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 ? `
|
|
@@ -7440,7 +7694,7 @@ This creates/updates a single rolling recap that **get_briefing automatically su
|
|
|
7440
7694
|
|
|
7441
7695
|
Calling \`mem_session_end\` also **clears the pending-distill marker** (if any), confirming that this session's learnings have been properly captured rather than left as an auto-recap skeleton.
|
|
7442
7696
|
|
|
7443
|
-
### 7. Verify the git/release exit protocol \u2014 always
|
|
7697
|
+
### 7. Verify the git/release/pipeline exit protocol \u2014 always
|
|
7444
7698
|
Run **\`haive enforce finish\`** before your final response.
|
|
7445
7699
|
|
|
7446
7700
|
This executable gate checks the multi-agent git-sync decision:
|
|
@@ -7448,11 +7702,12 @@ This executable gate checks the multi-agent git-sync decision:
|
|
|
7448
7702
|
- shippable package changes have a lockstep version bump
|
|
7449
7703
|
- the release tag \`vX.Y.Z\` exists when a version was bumped
|
|
7450
7704
|
- commits and tags have been pushed
|
|
7705
|
+
- the pushed HEAD's GitHub Actions workflow runs have completed successfully when the repo has a GitHub remote
|
|
7451
7706
|
- agents never run \`npm publish\` (publication remains human-owned)
|
|
7452
7707
|
|
|
7453
|
-
If it blocks, fix the reported Git/version/tag/push issue before telling the developer the task is done.
|
|
7708
|
+
If it blocks, fix the reported Git/version/tag/push/pipeline issue before telling the developer the task is done.
|
|
7454
7709
|
|
|
7455
|
-
When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed."
|
|
7710
|
+
When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed; GitHub Actions passed when applicable."
|
|
7456
7711
|
`;
|
|
7457
7712
|
return {
|
|
7458
7713
|
description: "Post-task reflection: capture what you learned before closing the session",
|
|
@@ -7465,10 +7720,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
7465
7720
|
};
|
|
7466
7721
|
}
|
|
7467
7722
|
var ImportDocsArgsSchema = {
|
|
7468
|
-
content:
|
|
7469
|
-
source:
|
|
7470
|
-
scope:
|
|
7471
|
-
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")
|
|
7472
7727
|
};
|
|
7473
7728
|
function importDocsPrompt(args, ctx) {
|
|
7474
7729
|
const sourceLine = args.source ? `
|
|
@@ -7531,7 +7786,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7531
7786
|
};
|
|
7532
7787
|
}
|
|
7533
7788
|
var SERVER_NAME = "haive";
|
|
7534
|
-
var SERVER_VERSION = "0.12.
|
|
7789
|
+
var SERVER_VERSION = "0.12.9";
|
|
7535
7790
|
function jsonResult(data) {
|
|
7536
7791
|
return {
|
|
7537
7792
|
content: [
|
|
@@ -7574,7 +7829,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
|
|
|
7574
7829
|
"mem_distill",
|
|
7575
7830
|
"mem_timeline",
|
|
7576
7831
|
"mem_conflict_candidates",
|
|
7577
|
-
"mem_feedback"
|
|
7832
|
+
"mem_feedback",
|
|
7833
|
+
"ingest_findings"
|
|
7578
7834
|
];
|
|
7579
7835
|
var EXPERIMENTAL_PROFILE_TOOLS = [
|
|
7580
7836
|
...MAINTENANCE_PROFILE_TOOLS,
|
|
@@ -7608,7 +7864,8 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7608
7864
|
"mem_delete",
|
|
7609
7865
|
"mem_feedback",
|
|
7610
7866
|
"runtime_journal_append",
|
|
7611
|
-
"pattern_detect"
|
|
7867
|
+
"pattern_detect",
|
|
7868
|
+
"ingest_findings"
|
|
7612
7869
|
]);
|
|
7613
7870
|
function createHaiveServer(options = {}) {
|
|
7614
7871
|
const context = createContext(options);
|
|
@@ -7733,6 +7990,36 @@ function createHaiveServer(options = {}) {
|
|
|
7733
7990
|
return jsonResult(await memTried(input, context));
|
|
7734
7991
|
}
|
|
7735
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
|
+
);
|
|
7736
8023
|
registerTool(
|
|
7737
8024
|
"mem_observe",
|
|
7738
8025
|
[
|
|
@@ -8529,8 +8816,8 @@ function registerMcp(program2) {
|
|
|
8529
8816
|
|
|
8530
8817
|
// src/commands/sync.ts
|
|
8531
8818
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
8532
|
-
import { readFile as readFile9, writeFile as
|
|
8533
|
-
import { existsSync as
|
|
8819
|
+
import { readFile as readFile9, writeFile as writeFile14, mkdir as mkdir10 } from "fs/promises";
|
|
8820
|
+
import { existsSync as existsSync33 } from "fs";
|
|
8534
8821
|
import path15 from "path";
|
|
8535
8822
|
import "commander";
|
|
8536
8823
|
import {
|
|
@@ -8543,12 +8830,12 @@ import {
|
|
|
8543
8830
|
isStackPackSeed as isStackPackSeed3,
|
|
8544
8831
|
loadCodeMap as loadCodeMap6,
|
|
8545
8832
|
loadConfig as loadConfig4,
|
|
8546
|
-
loadMemoriesFromDir as
|
|
8833
|
+
loadMemoriesFromDir as loadMemoriesFromDir25,
|
|
8547
8834
|
loadUsageIndex as loadUsageIndex13,
|
|
8548
8835
|
pullCrossRepoSources,
|
|
8549
8836
|
resolveHaivePaths as resolveHaivePaths9,
|
|
8550
8837
|
resolveManifestFiles,
|
|
8551
|
-
serializeMemory as
|
|
8838
|
+
serializeMemory as serializeMemory12,
|
|
8552
8839
|
trackDependencies,
|
|
8553
8840
|
verifyAnchor as verifyAnchor2,
|
|
8554
8841
|
watchContracts
|
|
@@ -8563,11 +8850,11 @@ function registerSync(program2) {
|
|
|
8563
8850
|
"git ref/commit to compare against; report memories added/modified/removed since"
|
|
8564
8851
|
).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
|
|
8565
8852
|
"--inject-bridge",
|
|
8566
|
-
"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"
|
|
8567
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) => {
|
|
8568
8855
|
const root = findProjectRoot12(opts.dir);
|
|
8569
8856
|
const paths = resolveHaivePaths9(root);
|
|
8570
|
-
if (!
|
|
8857
|
+
if (!existsSync33(paths.memoriesDir)) {
|
|
8571
8858
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8572
8859
|
process.exitCode = 1;
|
|
8573
8860
|
return;
|
|
@@ -8586,14 +8873,14 @@ function registerSync(program2) {
|
|
|
8586
8873
|
let promoted = 0;
|
|
8587
8874
|
let autoApproved = 0;
|
|
8588
8875
|
if (opts.verify !== false) {
|
|
8589
|
-
const memories = await
|
|
8876
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8590
8877
|
for (const { memory: memory2, filePath } of memories) {
|
|
8591
8878
|
if (memory2.frontmatter.type === "session_recap") {
|
|
8592
8879
|
if (memory2.frontmatter.status === "stale") {
|
|
8593
8880
|
if (!dryRun) {
|
|
8594
|
-
await
|
|
8881
|
+
await writeFile14(
|
|
8595
8882
|
filePath,
|
|
8596
|
-
|
|
8883
|
+
serializeMemory12({
|
|
8597
8884
|
frontmatter: {
|
|
8598
8885
|
...memory2.frontmatter,
|
|
8599
8886
|
status: "validated",
|
|
@@ -8616,9 +8903,9 @@ function registerSync(program2) {
|
|
|
8616
8903
|
if (result.stale) {
|
|
8617
8904
|
if (memory2.frontmatter.status !== "stale") {
|
|
8618
8905
|
if (!dryRun) {
|
|
8619
|
-
await
|
|
8906
|
+
await writeFile14(
|
|
8620
8907
|
filePath,
|
|
8621
|
-
|
|
8908
|
+
serializeMemory12({
|
|
8622
8909
|
frontmatter: {
|
|
8623
8910
|
...memory2.frontmatter,
|
|
8624
8911
|
status: "stale",
|
|
@@ -8634,9 +8921,9 @@ function registerSync(program2) {
|
|
|
8634
8921
|
}
|
|
8635
8922
|
} else if (memory2.frontmatter.status === "stale") {
|
|
8636
8923
|
if (!dryRun) {
|
|
8637
|
-
await
|
|
8924
|
+
await writeFile14(
|
|
8638
8925
|
filePath,
|
|
8639
|
-
|
|
8926
|
+
serializeMemory12({
|
|
8640
8927
|
frontmatter: {
|
|
8641
8928
|
...memory2.frontmatter,
|
|
8642
8929
|
status: "validated",
|
|
@@ -8653,7 +8940,7 @@ function registerSync(program2) {
|
|
|
8653
8940
|
}
|
|
8654
8941
|
}
|
|
8655
8942
|
if (opts.promote !== false) {
|
|
8656
|
-
const memories = await
|
|
8943
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8657
8944
|
const usage = await loadUsageIndex13(paths);
|
|
8658
8945
|
const nowMs = Date.now();
|
|
8659
8946
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -8664,9 +8951,9 @@ function registerSync(program2) {
|
|
|
8664
8951
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
|
|
8665
8952
|
})) {
|
|
8666
8953
|
if (!dryRun) {
|
|
8667
|
-
await
|
|
8954
|
+
await writeFile14(
|
|
8668
8955
|
filePath,
|
|
8669
|
-
|
|
8956
|
+
serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
8670
8957
|
"utf8"
|
|
8671
8958
|
);
|
|
8672
8959
|
}
|
|
@@ -8677,9 +8964,9 @@ function registerSync(program2) {
|
|
|
8677
8964
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
8678
8965
|
if (ageHours >= autoApproveDelayHours) {
|
|
8679
8966
|
if (!dryRun) {
|
|
8680
|
-
await
|
|
8967
|
+
await writeFile14(
|
|
8681
8968
|
filePath,
|
|
8682
|
-
|
|
8969
|
+
serializeMemory12({
|
|
8683
8970
|
frontmatter: {
|
|
8684
8971
|
...fm,
|
|
8685
8972
|
status: "validated",
|
|
@@ -8705,7 +8992,7 @@ function registerSync(program2) {
|
|
|
8705
8992
|
for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
|
|
8706
8993
|
}
|
|
8707
8994
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
8708
|
-
const draftMemories = (await
|
|
8995
|
+
const draftMemories = (await loadMemoriesFromDir25(paths.memoriesDir)).filter(
|
|
8709
8996
|
(m) => m.memory.frontmatter.status === "draft"
|
|
8710
8997
|
);
|
|
8711
8998
|
const draftCount = draftMemories.length;
|
|
@@ -8721,9 +9008,18 @@ function registerSync(program2) {
|
|
|
8721
9008
|
);
|
|
8722
9009
|
}
|
|
8723
9010
|
if (opts.injectBridge) {
|
|
8724
|
-
const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
|
|
8725
9011
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
8726
|
-
|
|
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
|
+
}
|
|
8727
9023
|
}
|
|
8728
9024
|
if (sinceReport && !opts.quiet) {
|
|
8729
9025
|
if (sinceReport.added.length > 0) {
|
|
@@ -8740,7 +9036,7 @@ function registerSync(program2) {
|
|
|
8740
9036
|
}
|
|
8741
9037
|
}
|
|
8742
9038
|
if (!opts.quiet) {
|
|
8743
|
-
const allForDecay = await
|
|
9039
|
+
const allForDecay = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8744
9040
|
const usageForDecay = await loadUsageIndex13(paths);
|
|
8745
9041
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
8746
9042
|
const fm = memory2.frontmatter;
|
|
@@ -8830,9 +9126,9 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
8830
9126
|
if (!dryRun) {
|
|
8831
9127
|
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
8832
9128
|
await mkdir10(teamDir, { recursive: true });
|
|
8833
|
-
await
|
|
9129
|
+
await writeFile14(
|
|
8834
9130
|
path15.join(teamDir, `${fm.id}.md`),
|
|
8835
|
-
|
|
9131
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
8836
9132
|
"utf8"
|
|
8837
9133
|
);
|
|
8838
9134
|
}
|
|
@@ -8899,9 +9195,9 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
8899
9195
|
if (!dryRun) {
|
|
8900
9196
|
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
8901
9197
|
await mkdir10(teamDir, { recursive: true });
|
|
8902
|
-
await
|
|
9198
|
+
await writeFile14(
|
|
8903
9199
|
path15.join(teamDir, `${fm.id}.md`),
|
|
8904
|
-
|
|
9200
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
8905
9201
|
"utf8"
|
|
8906
9202
|
);
|
|
8907
9203
|
}
|
|
@@ -8991,8 +9287,8 @@ function bridgeSummaryLine(body) {
|
|
|
8991
9287
|
return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
|
|
8992
9288
|
}
|
|
8993
9289
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
8994
|
-
if (!
|
|
8995
|
-
const all = await
|
|
9290
|
+
if (!existsSync33(memoriesDir)) return;
|
|
9291
|
+
const all = await loadMemoriesFromDir25(memoriesDir);
|
|
8996
9292
|
const top = all.filter(({ memory: memory2 }) => {
|
|
8997
9293
|
const s = memory2.frontmatter.status;
|
|
8998
9294
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
@@ -9017,7 +9313,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
|
9017
9313
|
` + block + `
|
|
9018
9314
|
|
|
9019
9315
|
${BRIDGE_END}`;
|
|
9020
|
-
const fileExists =
|
|
9316
|
+
const fileExists = existsSync33(bridgeFile);
|
|
9021
9317
|
let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
|
|
9022
9318
|
existing = existing.replace(/\r\n/g, "\n");
|
|
9023
9319
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
@@ -9039,7 +9335,7 @@ ${BRIDGE_END}`;
|
|
|
9039
9335
|
}
|
|
9040
9336
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
9041
9337
|
}
|
|
9042
|
-
await
|
|
9338
|
+
await writeFile14(bridgeFile, updated, "utf8");
|
|
9043
9339
|
if (!quiet) {
|
|
9044
9340
|
console.log(
|
|
9045
9341
|
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
|
|
@@ -9067,8 +9363,8 @@ function collectSinceChanges(root, ref) {
|
|
|
9067
9363
|
|
|
9068
9364
|
// src/commands/memory-add.ts
|
|
9069
9365
|
import { createHash as createHash2 } from "crypto";
|
|
9070
|
-
import { mkdir as mkdir11, readFile as readFile10, writeFile as
|
|
9071
|
-
import { existsSync as
|
|
9366
|
+
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile15 } from "fs/promises";
|
|
9367
|
+
import { existsSync as existsSync34 } from "fs";
|
|
9072
9368
|
import path16 from "path";
|
|
9073
9369
|
import "commander";
|
|
9074
9370
|
import {
|
|
@@ -9076,10 +9372,10 @@ import {
|
|
|
9076
9372
|
findProjectRoot as findProjectRoot13,
|
|
9077
9373
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
9078
9374
|
loadConfig as loadConfig5,
|
|
9079
|
-
loadMemoriesFromDir as
|
|
9080
|
-
memoryFilePath as
|
|
9375
|
+
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
9376
|
+
memoryFilePath as memoryFilePath7,
|
|
9081
9377
|
resolveHaivePaths as resolveHaivePaths10,
|
|
9082
|
-
serializeMemory as
|
|
9378
|
+
serializeMemory as serializeMemory13,
|
|
9083
9379
|
suggestSensorFromMemory as suggestSensorFromMemory3
|
|
9084
9380
|
} from "@hiveai/core";
|
|
9085
9381
|
function registerMemoryAdd(memory2) {
|
|
@@ -9110,7 +9406,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9110
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) => {
|
|
9111
9407
|
const root = findProjectRoot13(opts.dir);
|
|
9112
9408
|
const paths = resolveHaivePaths10(root);
|
|
9113
|
-
if (!
|
|
9409
|
+
if (!existsSync34(paths.haiveDir)) {
|
|
9114
9410
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9115
9411
|
process.exitCode = 1;
|
|
9116
9412
|
return;
|
|
@@ -9127,7 +9423,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9127
9423
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
9128
9424
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
9129
9425
|
if (anchorPaths.length > 0) {
|
|
9130
|
-
const missing = anchorPaths.filter((p) => !
|
|
9426
|
+
const missing = anchorPaths.filter((p) => !existsSync34(path16.resolve(root, p)));
|
|
9131
9427
|
if (missing.length > 0) {
|
|
9132
9428
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
9133
9429
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -9140,7 +9436,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9140
9436
|
const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
|
|
9141
9437
|
let body;
|
|
9142
9438
|
if (opts.bodyFile !== void 0) {
|
|
9143
|
-
if (!
|
|
9439
|
+
if (!existsSync34(opts.bodyFile)) {
|
|
9144
9440
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9145
9441
|
process.exitCode = 1;
|
|
9146
9442
|
return;
|
|
@@ -9156,9 +9452,9 @@ TODO \u2014 write the memory body.
|
|
|
9156
9452
|
`;
|
|
9157
9453
|
}
|
|
9158
9454
|
const scope = opts.scope ?? config.defaultScope ?? "personal";
|
|
9159
|
-
if (
|
|
9455
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
9160
9456
|
const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
9161
|
-
const allForHash = await
|
|
9457
|
+
const allForHash = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9162
9458
|
const hashDup = allForHash.find(
|
|
9163
9459
|
({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
|
|
9164
9460
|
);
|
|
@@ -9169,8 +9465,8 @@ TODO \u2014 write the memory body.
|
|
|
9169
9465
|
return;
|
|
9170
9466
|
}
|
|
9171
9467
|
}
|
|
9172
|
-
if (opts.topic &&
|
|
9173
|
-
const existing = await
|
|
9468
|
+
if (opts.topic && existsSync34(paths.memoriesDir)) {
|
|
9469
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9174
9470
|
const topicMatch = existing.find(
|
|
9175
9471
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
9176
9472
|
);
|
|
@@ -9190,7 +9486,7 @@ TODO \u2014 write the memory body.
|
|
|
9190
9486
|
};
|
|
9191
9487
|
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
9192
9488
|
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
9193
|
-
await
|
|
9489
|
+
await writeFile15(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
|
|
9194
9490
|
ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
|
|
9195
9491
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
9196
9492
|
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
@@ -9214,15 +9510,15 @@ TODO \u2014 write the memory body.
|
|
|
9214
9510
|
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
|
|
9215
9511
|
activation
|
|
9216
9512
|
});
|
|
9217
|
-
const file =
|
|
9513
|
+
const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9218
9514
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
9219
|
-
if (
|
|
9515
|
+
if (existsSync34(file)) {
|
|
9220
9516
|
ui.error(`Memory already exists at ${file}`);
|
|
9221
9517
|
process.exitCode = 1;
|
|
9222
9518
|
return;
|
|
9223
9519
|
}
|
|
9224
|
-
if (
|
|
9225
|
-
const existing = await
|
|
9520
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
9521
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9226
9522
|
const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
9227
9523
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
9228
9524
|
const id = memory3.frontmatter.id.toLowerCase();
|
|
@@ -9233,7 +9529,7 @@ TODO \u2014 write the memory body.
|
|
|
9233
9529
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
9234
9530
|
}
|
|
9235
9531
|
}
|
|
9236
|
-
await
|
|
9532
|
+
await writeFile15(file, serializeMemory13({ frontmatter, body }), "utf8");
|
|
9237
9533
|
ui.success(`Created ${path16.relative(root, file)}`);
|
|
9238
9534
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
9239
9535
|
if (frontmatter.sensor?.autogen) {
|
|
@@ -9313,14 +9609,14 @@ function slugify(value) {
|
|
|
9313
9609
|
}
|
|
9314
9610
|
|
|
9315
9611
|
// src/commands/memory-list.ts
|
|
9316
|
-
import { existsSync as
|
|
9612
|
+
import { existsSync as existsSync35 } from "fs";
|
|
9317
9613
|
import path17 from "path";
|
|
9318
9614
|
import "commander";
|
|
9319
9615
|
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
9320
9616
|
|
|
9321
9617
|
// src/utils/fs.ts
|
|
9322
9618
|
import {
|
|
9323
|
-
loadMemoriesFromDir as
|
|
9619
|
+
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
9324
9620
|
loadMemory,
|
|
9325
9621
|
listMarkdownFilesRecursive
|
|
9326
9622
|
} from "@hiveai/core";
|
|
@@ -9330,12 +9626,12 @@ function registerMemoryList(memory2) {
|
|
|
9330
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) => {
|
|
9331
9627
|
const root = findProjectRoot14(opts.dir);
|
|
9332
9628
|
const paths = resolveHaivePaths11(root);
|
|
9333
|
-
if (!
|
|
9629
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
9334
9630
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9335
9631
|
process.exitCode = 1;
|
|
9336
9632
|
return;
|
|
9337
9633
|
}
|
|
9338
|
-
const all = await
|
|
9634
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9339
9635
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
9340
9636
|
const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
|
|
9341
9637
|
const filtered = all.filter((m) => {
|
|
@@ -9404,26 +9700,26 @@ function matchesFilters(loaded, opts) {
|
|
|
9404
9700
|
}
|
|
9405
9701
|
|
|
9406
9702
|
// src/commands/memory-promote.ts
|
|
9407
|
-
import { mkdir as mkdir12, unlink as unlink2, writeFile as
|
|
9408
|
-
import { existsSync as
|
|
9703
|
+
import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile16 } from "fs/promises";
|
|
9704
|
+
import { existsSync as existsSync36 } from "fs";
|
|
9409
9705
|
import path18 from "path";
|
|
9410
9706
|
import "commander";
|
|
9411
9707
|
import {
|
|
9412
9708
|
findProjectRoot as findProjectRoot15,
|
|
9413
|
-
memoryFilePath as
|
|
9709
|
+
memoryFilePath as memoryFilePath8,
|
|
9414
9710
|
resolveHaivePaths as resolveHaivePaths12,
|
|
9415
|
-
serializeMemory as
|
|
9711
|
+
serializeMemory as serializeMemory14
|
|
9416
9712
|
} from "@hiveai/core";
|
|
9417
9713
|
function registerMemoryPromote(memory2) {
|
|
9418
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) => {
|
|
9419
9715
|
const root = findProjectRoot15(opts.dir);
|
|
9420
9716
|
const paths = resolveHaivePaths12(root);
|
|
9421
|
-
if (!
|
|
9717
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
9422
9718
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9423
9719
|
process.exitCode = 1;
|
|
9424
9720
|
return;
|
|
9425
9721
|
}
|
|
9426
|
-
const teamAndModule = await
|
|
9722
|
+
const teamAndModule = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9427
9723
|
const alreadyShared = teamAndModule.find(
|
|
9428
9724
|
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
9429
9725
|
);
|
|
@@ -9437,7 +9733,7 @@ function registerMemoryPromote(memory2) {
|
|
|
9437
9733
|
}
|
|
9438
9734
|
return;
|
|
9439
9735
|
}
|
|
9440
|
-
const all = await
|
|
9736
|
+
const all = await loadMemoriesFromDir27(paths.personalDir);
|
|
9441
9737
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9442
9738
|
if (!found) {
|
|
9443
9739
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -9452,9 +9748,9 @@ function registerMemoryPromote(memory2) {
|
|
|
9452
9748
|
},
|
|
9453
9749
|
body: found.memory.body
|
|
9454
9750
|
};
|
|
9455
|
-
const newPath =
|
|
9751
|
+
const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
|
|
9456
9752
|
await mkdir12(path18.dirname(newPath), { recursive: true });
|
|
9457
|
-
await
|
|
9753
|
+
await writeFile16(newPath, serializeMemory14(updated), "utf8");
|
|
9458
9754
|
await unlink2(found.filePath);
|
|
9459
9755
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
9460
9756
|
ui.info(`Now at ${path18.relative(root, newPath)}`);
|
|
@@ -9463,25 +9759,25 @@ function registerMemoryPromote(memory2) {
|
|
|
9463
9759
|
}
|
|
9464
9760
|
|
|
9465
9761
|
// src/commands/memory-approve.ts
|
|
9466
|
-
import { existsSync as
|
|
9467
|
-
import { writeFile as
|
|
9762
|
+
import { existsSync as existsSync37 } from "fs";
|
|
9763
|
+
import { writeFile as writeFile17 } from "fs/promises";
|
|
9468
9764
|
import path19 from "path";
|
|
9469
9765
|
import "commander";
|
|
9470
9766
|
import {
|
|
9471
9767
|
findProjectRoot as findProjectRoot16,
|
|
9472
9768
|
resolveHaivePaths as resolveHaivePaths13,
|
|
9473
|
-
serializeMemory as
|
|
9769
|
+
serializeMemory as serializeMemory15
|
|
9474
9770
|
} from "@hiveai/core";
|
|
9475
9771
|
function registerMemoryApprove(memory2) {
|
|
9476
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) => {
|
|
9477
9773
|
const root = findProjectRoot16(opts.dir);
|
|
9478
9774
|
const paths = resolveHaivePaths13(root);
|
|
9479
|
-
if (!
|
|
9775
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
9480
9776
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9481
9777
|
process.exitCode = 1;
|
|
9482
9778
|
return;
|
|
9483
9779
|
}
|
|
9484
|
-
const all = await
|
|
9780
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9485
9781
|
if (opts.all || opts.pending) {
|
|
9486
9782
|
const candidates = all.filter((m) => {
|
|
9487
9783
|
const s = m.memory.frontmatter.status;
|
|
@@ -9498,7 +9794,7 @@ function registerMemoryApprove(memory2) {
|
|
|
9498
9794
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
9499
9795
|
body: found2.memory.body
|
|
9500
9796
|
};
|
|
9501
|
-
await
|
|
9797
|
+
await writeFile17(found2.filePath, serializeMemory15(next2), "utf8");
|
|
9502
9798
|
count++;
|
|
9503
9799
|
}
|
|
9504
9800
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -9527,32 +9823,32 @@ function registerMemoryApprove(memory2) {
|
|
|
9527
9823
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
9528
9824
|
body: found.memory.body
|
|
9529
9825
|
};
|
|
9530
|
-
await
|
|
9826
|
+
await writeFile17(found.filePath, serializeMemory15(next), "utf8");
|
|
9531
9827
|
ui.success(`Approved ${id} (status=validated)`);
|
|
9532
9828
|
ui.info(path19.relative(root, found.filePath));
|
|
9533
9829
|
});
|
|
9534
9830
|
}
|
|
9535
9831
|
|
|
9536
9832
|
// src/commands/memory-update.ts
|
|
9537
|
-
import { readFile as readFile11, writeFile as
|
|
9538
|
-
import { existsSync as
|
|
9833
|
+
import { readFile as readFile11, writeFile as writeFile18 } from "fs/promises";
|
|
9834
|
+
import { existsSync as existsSync38 } from "fs";
|
|
9539
9835
|
import path20 from "path";
|
|
9540
9836
|
import "commander";
|
|
9541
9837
|
import {
|
|
9542
9838
|
findProjectRoot as findProjectRoot17,
|
|
9543
9839
|
resolveHaivePaths as resolveHaivePaths14,
|
|
9544
|
-
serializeMemory as
|
|
9840
|
+
serializeMemory as serializeMemory16
|
|
9545
9841
|
} from "@hiveai/core";
|
|
9546
9842
|
function registerMemoryUpdate(memory2) {
|
|
9547
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) => {
|
|
9548
9844
|
const root = findProjectRoot17(opts.dir);
|
|
9549
9845
|
const paths = resolveHaivePaths14(root);
|
|
9550
|
-
if (!
|
|
9846
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
9551
9847
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9552
9848
|
process.exitCode = 1;
|
|
9553
9849
|
return;
|
|
9554
9850
|
}
|
|
9555
|
-
const memories = await
|
|
9851
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9556
9852
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
9557
9853
|
if (!loaded) {
|
|
9558
9854
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9589,7 +9885,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
9589
9885
|
if (opts.author !== void 0) updated.push("author");
|
|
9590
9886
|
let newBody;
|
|
9591
9887
|
if (opts.bodyFile !== void 0) {
|
|
9592
|
-
if (!
|
|
9888
|
+
if (!existsSync38(opts.bodyFile)) {
|
|
9593
9889
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9594
9890
|
process.exitCode = 1;
|
|
9595
9891
|
return;
|
|
@@ -9610,9 +9906,9 @@ function registerMemoryUpdate(memory2) {
|
|
|
9610
9906
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
9611
9907
|
return;
|
|
9612
9908
|
}
|
|
9613
|
-
await
|
|
9909
|
+
await writeFile18(
|
|
9614
9910
|
loaded.filePath,
|
|
9615
|
-
|
|
9911
|
+
serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
|
|
9616
9912
|
"utf8"
|
|
9617
9913
|
);
|
|
9618
9914
|
ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
|
|
@@ -9634,8 +9930,8 @@ function parseCsv3(value) {
|
|
|
9634
9930
|
}
|
|
9635
9931
|
|
|
9636
9932
|
// src/commands/memory-auto-promote.ts
|
|
9637
|
-
import { writeFile as
|
|
9638
|
-
import { existsSync as
|
|
9933
|
+
import { writeFile as writeFile19 } from "fs/promises";
|
|
9934
|
+
import { existsSync as existsSync39 } from "fs";
|
|
9639
9935
|
import path21 from "path";
|
|
9640
9936
|
import "commander";
|
|
9641
9937
|
import {
|
|
@@ -9645,7 +9941,7 @@ import {
|
|
|
9645
9941
|
isAutoPromoteEligible as isAutoPromoteEligible3,
|
|
9646
9942
|
loadUsageIndex as loadUsageIndex14,
|
|
9647
9943
|
resolveHaivePaths as resolveHaivePaths15,
|
|
9648
|
-
serializeMemory as
|
|
9944
|
+
serializeMemory as serializeMemory17
|
|
9649
9945
|
} from "@hiveai/core";
|
|
9650
9946
|
function registerMemoryAutoPromote(memory2) {
|
|
9651
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(
|
|
@@ -9655,7 +9951,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9655
9951
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9656
9952
|
const root = findProjectRoot18(opts.dir);
|
|
9657
9953
|
const paths = resolveHaivePaths15(root);
|
|
9658
|
-
if (!
|
|
9954
|
+
if (!existsSync39(paths.memoriesDir)) {
|
|
9659
9955
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9660
9956
|
process.exitCode = 1;
|
|
9661
9957
|
return;
|
|
@@ -9664,7 +9960,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9664
9960
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
|
|
9665
9961
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
9666
9962
|
};
|
|
9667
|
-
const memories = await
|
|
9963
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9668
9964
|
const usage = await loadUsageIndex14(paths);
|
|
9669
9965
|
const eligible = memories.filter(
|
|
9670
9966
|
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
|
|
@@ -9687,7 +9983,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9687
9983
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
9688
9984
|
body: mem.body
|
|
9689
9985
|
};
|
|
9690
|
-
await
|
|
9986
|
+
await writeFile19(filePath, serializeMemory17(next), "utf8");
|
|
9691
9987
|
written++;
|
|
9692
9988
|
}
|
|
9693
9989
|
}
|
|
@@ -9698,7 +9994,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9698
9994
|
|
|
9699
9995
|
// src/commands/memory-edit.ts
|
|
9700
9996
|
import { spawn as spawn3 } from "child_process";
|
|
9701
|
-
import { existsSync as
|
|
9997
|
+
import { existsSync as existsSync40 } from "fs";
|
|
9702
9998
|
import { readFile as readFile12 } from "fs/promises";
|
|
9703
9999
|
import path23 from "path";
|
|
9704
10000
|
import "commander";
|
|
@@ -9711,12 +10007,12 @@ function registerMemoryEdit(memory2) {
|
|
|
9711
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) => {
|
|
9712
10008
|
const root = findProjectRoot19(opts.dir);
|
|
9713
10009
|
const paths = resolveHaivePaths16(root);
|
|
9714
|
-
if (!
|
|
10010
|
+
if (!existsSync40(paths.memoriesDir)) {
|
|
9715
10011
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9716
10012
|
process.exitCode = 1;
|
|
9717
10013
|
return;
|
|
9718
10014
|
}
|
|
9719
|
-
const all = await
|
|
10015
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9720
10016
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9721
10017
|
if (!found) {
|
|
9722
10018
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9751,7 +10047,7 @@ function runEditor(editor, file) {
|
|
|
9751
10047
|
}
|
|
9752
10048
|
|
|
9753
10049
|
// src/commands/memory-for-files.ts
|
|
9754
|
-
import { existsSync as
|
|
10050
|
+
import { existsSync as existsSync41 } from "fs";
|
|
9755
10051
|
import path24 from "path";
|
|
9756
10052
|
import "commander";
|
|
9757
10053
|
import {
|
|
@@ -9767,12 +10063,12 @@ function registerMemoryForFiles(memory2) {
|
|
|
9767
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) => {
|
|
9768
10064
|
const root = findProjectRoot20(opts.dir);
|
|
9769
10065
|
const paths = resolveHaivePaths17(root);
|
|
9770
|
-
if (!
|
|
10066
|
+
if (!existsSync41(paths.memoriesDir)) {
|
|
9771
10067
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9772
10068
|
process.exitCode = 1;
|
|
9773
10069
|
return;
|
|
9774
10070
|
}
|
|
9775
|
-
const all = await
|
|
10071
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9776
10072
|
const usage = await loadUsageIndex15(paths);
|
|
9777
10073
|
const inferred = inferModulesFromPaths4(files);
|
|
9778
10074
|
const byAnchor = [];
|
|
@@ -9879,7 +10175,7 @@ function printGroup(root, label, loaded, usage) {
|
|
|
9879
10175
|
}
|
|
9880
10176
|
|
|
9881
10177
|
// src/commands/memory-hot.ts
|
|
9882
|
-
import { existsSync as
|
|
10178
|
+
import { existsSync as existsSync43 } from "fs";
|
|
9883
10179
|
import path25 from "path";
|
|
9884
10180
|
import "commander";
|
|
9885
10181
|
import {
|
|
@@ -9894,13 +10190,13 @@ function registerMemoryHot(memory2) {
|
|
|
9894
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) => {
|
|
9895
10191
|
const root = findProjectRoot21(opts.dir);
|
|
9896
10192
|
const paths = resolveHaivePaths18(root);
|
|
9897
|
-
if (!
|
|
10193
|
+
if (!existsSync43(paths.memoriesDir)) {
|
|
9898
10194
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9899
10195
|
process.exitCode = 1;
|
|
9900
10196
|
return;
|
|
9901
10197
|
}
|
|
9902
10198
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
9903
|
-
const all = await
|
|
10199
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9904
10200
|
const usage = await loadUsageIndex16(paths);
|
|
9905
10201
|
const candidates = all.filter(({ memory: mem }) => {
|
|
9906
10202
|
const fm = mem.frontmatter;
|
|
@@ -9932,16 +10228,16 @@ function registerMemoryHot(memory2) {
|
|
|
9932
10228
|
}
|
|
9933
10229
|
|
|
9934
10230
|
// src/commands/memory-tried.ts
|
|
9935
|
-
import { mkdir as mkdir13, writeFile as
|
|
9936
|
-
import { existsSync as
|
|
10231
|
+
import { mkdir as mkdir13, writeFile as writeFile20 } from "fs/promises";
|
|
10232
|
+
import { existsSync as existsSync44 } from "fs";
|
|
9937
10233
|
import path26 from "path";
|
|
9938
10234
|
import "commander";
|
|
9939
10235
|
import {
|
|
9940
10236
|
buildFrontmatter as buildFrontmatter8,
|
|
9941
10237
|
findProjectRoot as findProjectRoot22,
|
|
9942
|
-
memoryFilePath as
|
|
10238
|
+
memoryFilePath as memoryFilePath9,
|
|
9943
10239
|
resolveHaivePaths as resolveHaivePaths19,
|
|
9944
|
-
serializeMemory as
|
|
10240
|
+
serializeMemory as serializeMemory18,
|
|
9945
10241
|
suggestSensorFromMemory as suggestSensorFromMemory4
|
|
9946
10242
|
} from "@hiveai/core";
|
|
9947
10243
|
function registerMemoryTried(memory2) {
|
|
@@ -9963,7 +10259,7 @@ function registerMemoryTried(memory2) {
|
|
|
9963
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) => {
|
|
9964
10260
|
const root = findProjectRoot22(opts.dir);
|
|
9965
10261
|
const paths = resolveHaivePaths19(root);
|
|
9966
|
-
if (!
|
|
10262
|
+
if (!existsSync44(paths.haiveDir)) {
|
|
9967
10263
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9968
10264
|
process.exitCode = 1;
|
|
9969
10265
|
return;
|
|
@@ -9989,14 +10285,14 @@ function registerMemoryTried(memory2) {
|
|
|
9989
10285
|
if (sensor) {
|
|
9990
10286
|
frontmatter.sensor = sensor;
|
|
9991
10287
|
}
|
|
9992
|
-
const file =
|
|
10288
|
+
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9993
10289
|
await mkdir13(path26.dirname(file), { recursive: true });
|
|
9994
|
-
if (
|
|
10290
|
+
if (existsSync44(file)) {
|
|
9995
10291
|
ui.error(`Memory already exists at ${file}`);
|
|
9996
10292
|
process.exitCode = 1;
|
|
9997
10293
|
return;
|
|
9998
10294
|
}
|
|
9999
|
-
await
|
|
10295
|
+
await writeFile20(file, serializeMemory18({ frontmatter, body }), "utf8");
|
|
10000
10296
|
ui.success(`Recorded: ${path26.relative(root, file)}`);
|
|
10001
10297
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
10002
10298
|
if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
@@ -10009,7 +10305,7 @@ function parseCsv4(value) {
|
|
|
10009
10305
|
|
|
10010
10306
|
// src/commands/memory-seed.ts
|
|
10011
10307
|
import { readFile as readFile13 } from "fs/promises";
|
|
10012
|
-
import { existsSync as
|
|
10308
|
+
import { existsSync as existsSync45 } from "fs";
|
|
10013
10309
|
import path27 from "path";
|
|
10014
10310
|
import "commander";
|
|
10015
10311
|
import {
|
|
@@ -10049,7 +10345,7 @@ function registerMemorySeed(memory2) {
|
|
|
10049
10345
|
}
|
|
10050
10346
|
return;
|
|
10051
10347
|
}
|
|
10052
|
-
if (!
|
|
10348
|
+
if (!existsSync45(paths.haiveDir)) {
|
|
10053
10349
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10054
10350
|
process.exitCode = 1;
|
|
10055
10351
|
return;
|
|
@@ -10105,7 +10401,7 @@ function registerMemorySeed(memory2) {
|
|
|
10105
10401
|
}
|
|
10106
10402
|
|
|
10107
10403
|
// src/commands/memory-pending.ts
|
|
10108
|
-
import { existsSync as
|
|
10404
|
+
import { existsSync as existsSync46 } from "fs";
|
|
10109
10405
|
import path28 from "path";
|
|
10110
10406
|
import "commander";
|
|
10111
10407
|
import {
|
|
@@ -10118,12 +10414,12 @@ function registerMemoryPending(memory2) {
|
|
|
10118
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) => {
|
|
10119
10415
|
const root = findProjectRoot24(opts.dir);
|
|
10120
10416
|
const paths = resolveHaivePaths21(root);
|
|
10121
|
-
if (!
|
|
10417
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
10122
10418
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10123
10419
|
process.exitCode = 1;
|
|
10124
10420
|
return;
|
|
10125
10421
|
}
|
|
10126
|
-
const all = await
|
|
10422
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10127
10423
|
const usage = await loadUsageIndex17(paths);
|
|
10128
10424
|
const filterFn = ({ memory: mem }) => {
|
|
10129
10425
|
if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
|
|
@@ -10176,7 +10472,7 @@ function registerMemoryPending(memory2) {
|
|
|
10176
10472
|
}
|
|
10177
10473
|
|
|
10178
10474
|
// src/commands/memory-query.ts
|
|
10179
|
-
import { existsSync as
|
|
10475
|
+
import { existsSync as existsSync47 } from "fs";
|
|
10180
10476
|
import path29 from "path";
|
|
10181
10477
|
import "commander";
|
|
10182
10478
|
import {
|
|
@@ -10193,7 +10489,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10193
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) => {
|
|
10194
10490
|
const root = findProjectRoot25(opts.dir);
|
|
10195
10491
|
const paths = resolveHaivePaths22(root);
|
|
10196
|
-
if (!
|
|
10492
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
10197
10493
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
10198
10494
|
process.exitCode = 1;
|
|
10199
10495
|
return;
|
|
@@ -10204,7 +10500,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10204
10500
|
return;
|
|
10205
10501
|
}
|
|
10206
10502
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
10207
|
-
const all = await
|
|
10503
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10208
10504
|
const passesFilters2 = (mem) => {
|
|
10209
10505
|
const fm = mem.frontmatter;
|
|
10210
10506
|
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
@@ -10251,8 +10547,8 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
10251
10547
|
}
|
|
10252
10548
|
|
|
10253
10549
|
// src/commands/memory-reject.ts
|
|
10254
|
-
import { writeFile as
|
|
10255
|
-
import { existsSync as
|
|
10550
|
+
import { writeFile as writeFile21 } from "fs/promises";
|
|
10551
|
+
import { existsSync as existsSync48 } from "fs";
|
|
10256
10552
|
import "commander";
|
|
10257
10553
|
import {
|
|
10258
10554
|
findProjectRoot as findProjectRoot26,
|
|
@@ -10260,27 +10556,27 @@ import {
|
|
|
10260
10556
|
recordRejection as recordRejection3,
|
|
10261
10557
|
resolveHaivePaths as resolveHaivePaths23,
|
|
10262
10558
|
saveUsageIndex as saveUsageIndex4,
|
|
10263
|
-
serializeMemory as
|
|
10559
|
+
serializeMemory as serializeMemory19
|
|
10264
10560
|
} from "@hiveai/core";
|
|
10265
10561
|
function registerMemoryReject(memory2) {
|
|
10266
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) => {
|
|
10267
10563
|
const root = findProjectRoot26(opts.dir);
|
|
10268
10564
|
const paths = resolveHaivePaths23(root);
|
|
10269
|
-
if (!
|
|
10565
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
10270
10566
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10271
10567
|
process.exitCode = 1;
|
|
10272
10568
|
return;
|
|
10273
10569
|
}
|
|
10274
|
-
const memories = await
|
|
10570
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10275
10571
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
10276
10572
|
if (!loaded) {
|
|
10277
10573
|
ui.error(`No memory with id "${id}".`);
|
|
10278
10574
|
process.exitCode = 1;
|
|
10279
10575
|
return;
|
|
10280
10576
|
}
|
|
10281
|
-
await
|
|
10577
|
+
await writeFile21(
|
|
10282
10578
|
loaded.filePath,
|
|
10283
|
-
|
|
10579
|
+
serializeMemory19({
|
|
10284
10580
|
frontmatter: {
|
|
10285
10581
|
...loaded.memory.frontmatter,
|
|
10286
10582
|
status: "rejected",
|
|
@@ -10302,7 +10598,7 @@ function registerMemoryReject(memory2) {
|
|
|
10302
10598
|
}
|
|
10303
10599
|
|
|
10304
10600
|
// src/commands/memory-rm.ts
|
|
10305
|
-
import { existsSync as
|
|
10601
|
+
import { existsSync as existsSync49 } from "fs";
|
|
10306
10602
|
import { unlink as unlink3 } from "fs/promises";
|
|
10307
10603
|
import path30 from "path";
|
|
10308
10604
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
@@ -10317,12 +10613,12 @@ function registerMemoryRm(memory2) {
|
|
|
10317
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) => {
|
|
10318
10614
|
const root = findProjectRoot27(opts.dir);
|
|
10319
10615
|
const paths = resolveHaivePaths24(root);
|
|
10320
|
-
if (!
|
|
10616
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
10321
10617
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10322
10618
|
process.exitCode = 1;
|
|
10323
10619
|
return;
|
|
10324
10620
|
}
|
|
10325
|
-
const all = await
|
|
10621
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10326
10622
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10327
10623
|
if (!found) {
|
|
10328
10624
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10353,7 +10649,7 @@ function registerMemoryRm(memory2) {
|
|
|
10353
10649
|
}
|
|
10354
10650
|
|
|
10355
10651
|
// src/commands/memory-show.ts
|
|
10356
|
-
import { existsSync as
|
|
10652
|
+
import { existsSync as existsSync50 } from "fs";
|
|
10357
10653
|
import { readFile as readFile14 } from "fs/promises";
|
|
10358
10654
|
import path31 from "path";
|
|
10359
10655
|
import "commander";
|
|
@@ -10368,12 +10664,12 @@ function registerMemoryShow(memory2) {
|
|
|
10368
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) => {
|
|
10369
10665
|
const root = findProjectRoot28(opts.dir);
|
|
10370
10666
|
const paths = resolveHaivePaths25(root);
|
|
10371
|
-
if (!
|
|
10667
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
10372
10668
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10373
10669
|
process.exitCode = 1;
|
|
10374
10670
|
return;
|
|
10375
10671
|
}
|
|
10376
|
-
const all = await
|
|
10672
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10377
10673
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10378
10674
|
if (!found) {
|
|
10379
10675
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10412,7 +10708,7 @@ function registerMemoryShow(memory2) {
|
|
|
10412
10708
|
}
|
|
10413
10709
|
|
|
10414
10710
|
// src/commands/memory-stats.ts
|
|
10415
|
-
import { existsSync as
|
|
10711
|
+
import { existsSync as existsSync51 } from "fs";
|
|
10416
10712
|
import path33 from "path";
|
|
10417
10713
|
import "commander";
|
|
10418
10714
|
import {
|
|
@@ -10426,12 +10722,12 @@ function registerMemoryStats(memory2) {
|
|
|
10426
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) => {
|
|
10427
10723
|
const root = findProjectRoot29(opts.dir);
|
|
10428
10724
|
const paths = resolveHaivePaths26(root);
|
|
10429
|
-
if (!
|
|
10725
|
+
if (!existsSync51(paths.memoriesDir)) {
|
|
10430
10726
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10431
10727
|
process.exitCode = 1;
|
|
10432
10728
|
return;
|
|
10433
10729
|
}
|
|
10434
|
-
const all = await
|
|
10730
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10435
10731
|
const usage = await loadUsageIndex21(paths);
|
|
10436
10732
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10437
10733
|
if (target.length === 0) {
|
|
@@ -10457,7 +10753,7 @@ function registerMemoryStats(memory2) {
|
|
|
10457
10753
|
}
|
|
10458
10754
|
|
|
10459
10755
|
// src/commands/memory-impact.ts
|
|
10460
|
-
import { existsSync as
|
|
10756
|
+
import { existsSync as existsSync53 } from "fs";
|
|
10461
10757
|
import "commander";
|
|
10462
10758
|
import {
|
|
10463
10759
|
compareImpact,
|
|
@@ -10474,12 +10770,12 @@ function registerMemoryImpact(memory2) {
|
|
|
10474
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) => {
|
|
10475
10771
|
const root = findProjectRoot30(opts.dir);
|
|
10476
10772
|
const paths = resolveHaivePaths27(root);
|
|
10477
|
-
if (!
|
|
10773
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
10478
10774
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10479
10775
|
process.exitCode = 1;
|
|
10480
10776
|
return;
|
|
10481
10777
|
}
|
|
10482
|
-
const all = await
|
|
10778
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10483
10779
|
const usageIndex = await loadUsageIndex23(paths);
|
|
10484
10780
|
let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
|
|
10485
10781
|
const fm = mem.frontmatter;
|
|
@@ -10545,7 +10841,7 @@ function pad(value, width) {
|
|
|
10545
10841
|
}
|
|
10546
10842
|
|
|
10547
10843
|
// src/commands/memory-feedback.ts
|
|
10548
|
-
import { existsSync as
|
|
10844
|
+
import { existsSync as existsSync54 } from "fs";
|
|
10549
10845
|
import "commander";
|
|
10550
10846
|
import {
|
|
10551
10847
|
computeImpact as computeImpact4,
|
|
@@ -10568,12 +10864,12 @@ function registerMemoryFeedback(memory2) {
|
|
|
10568
10864
|
}
|
|
10569
10865
|
const root = findProjectRoot31(opts.dir);
|
|
10570
10866
|
const paths = resolveHaivePaths28(root);
|
|
10571
|
-
if (!
|
|
10867
|
+
if (!existsSync54(paths.memoriesDir)) {
|
|
10572
10868
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10573
10869
|
process.exitCode = 1;
|
|
10574
10870
|
return;
|
|
10575
10871
|
}
|
|
10576
|
-
const all = await
|
|
10872
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10577
10873
|
const target = all.find((m) => m.memory.frontmatter.id === id);
|
|
10578
10874
|
if (!target) {
|
|
10579
10875
|
ui.error(`No memory with id '${id}'.`);
|
|
@@ -10599,14 +10895,14 @@ function registerMemoryFeedback(memory2) {
|
|
|
10599
10895
|
}
|
|
10600
10896
|
|
|
10601
10897
|
// src/commands/memory-verify.ts
|
|
10602
|
-
import { writeFile as
|
|
10603
|
-
import { existsSync as
|
|
10898
|
+
import { writeFile as writeFile23 } from "fs/promises";
|
|
10899
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10604
10900
|
import path34 from "path";
|
|
10605
10901
|
import "commander";
|
|
10606
10902
|
import {
|
|
10607
10903
|
findProjectRoot as findProjectRoot32,
|
|
10608
10904
|
resolveHaivePaths as resolveHaivePaths29,
|
|
10609
|
-
serializeMemory as
|
|
10905
|
+
serializeMemory as serializeMemory20,
|
|
10610
10906
|
verifyAnchor as verifyAnchor3
|
|
10611
10907
|
} from "@hiveai/core";
|
|
10612
10908
|
function registerMemoryVerify(memory2) {
|
|
@@ -10615,7 +10911,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10615
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) => {
|
|
10616
10912
|
const root = findProjectRoot32(opts.dir);
|
|
10617
10913
|
const paths = resolveHaivePaths29(root);
|
|
10618
|
-
if (!
|
|
10914
|
+
if (!existsSync55(paths.memoriesDir)) {
|
|
10619
10915
|
if (opts.json) {
|
|
10620
10916
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
10621
10917
|
} else {
|
|
@@ -10624,7 +10920,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10624
10920
|
process.exitCode = 1;
|
|
10625
10921
|
return;
|
|
10626
10922
|
}
|
|
10627
|
-
const all = await
|
|
10923
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10628
10924
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10629
10925
|
if (opts.id && targets.length === 0) {
|
|
10630
10926
|
if (opts.json) {
|
|
@@ -10673,7 +10969,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10673
10969
|
}
|
|
10674
10970
|
if (opts.update) {
|
|
10675
10971
|
const next = applyVerification2(mem, result);
|
|
10676
|
-
await
|
|
10972
|
+
await writeFile23(filePath, serializeMemory20(next), "utf8");
|
|
10677
10973
|
updated++;
|
|
10678
10974
|
}
|
|
10679
10975
|
}
|
|
@@ -10736,7 +11032,7 @@ function applyVerification2(mem, result) {
|
|
|
10736
11032
|
|
|
10737
11033
|
// src/commands/memory-import.ts
|
|
10738
11034
|
import { readFile as readFile15 } from "fs/promises";
|
|
10739
|
-
import { existsSync as
|
|
11035
|
+
import { existsSync as existsSync56 } from "fs";
|
|
10740
11036
|
import "commander";
|
|
10741
11037
|
import {
|
|
10742
11038
|
findProjectRoot as findProjectRoot33,
|
|
@@ -10748,12 +11044,12 @@ function registerMemoryImport(memory2) {
|
|
|
10748
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) => {
|
|
10749
11045
|
const root = findProjectRoot33(opts.dir);
|
|
10750
11046
|
const paths = resolveHaivePaths30(root);
|
|
10751
|
-
if (!
|
|
11047
|
+
if (!existsSync56(paths.haiveDir)) {
|
|
10752
11048
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10753
11049
|
process.exitCode = 1;
|
|
10754
11050
|
return;
|
|
10755
11051
|
}
|
|
10756
|
-
if (!
|
|
11052
|
+
if (!existsSync56(opts.from)) {
|
|
10757
11053
|
ui.error(`File not found: ${opts.from}`);
|
|
10758
11054
|
process.exitCode = 1;
|
|
10759
11055
|
return;
|
|
@@ -10786,15 +11082,15 @@ function registerMemoryImport(memory2) {
|
|
|
10786
11082
|
}
|
|
10787
11083
|
|
|
10788
11084
|
// src/commands/memory-import-changelog.ts
|
|
10789
|
-
import { existsSync as
|
|
10790
|
-
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";
|
|
10791
11087
|
import path35 from "path";
|
|
10792
11088
|
import "commander";
|
|
10793
11089
|
import {
|
|
10794
11090
|
buildFrontmatter as buildFrontmatter9,
|
|
10795
11091
|
findProjectRoot as findProjectRoot34,
|
|
10796
11092
|
resolveHaivePaths as resolveHaivePaths31,
|
|
10797
|
-
serializeMemory as
|
|
11093
|
+
serializeMemory as serializeMemory21
|
|
10798
11094
|
} from "@hiveai/core";
|
|
10799
11095
|
function parseChangelog(content) {
|
|
10800
11096
|
const entries = [];
|
|
@@ -10861,7 +11157,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10861
11157
|
const root = findProjectRoot34(opts.dir);
|
|
10862
11158
|
const paths = resolveHaivePaths31(root);
|
|
10863
11159
|
const changelogPath = path35.resolve(root, opts.fromChangelog);
|
|
10864
|
-
if (!
|
|
11160
|
+
if (!existsSync57(changelogPath)) {
|
|
10865
11161
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
10866
11162
|
process.exitCode = 1;
|
|
10867
11163
|
return;
|
|
@@ -10924,9 +11220,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10924
11220
|
paths: [path35.relative(root, changelogPath)],
|
|
10925
11221
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
10926
11222
|
});
|
|
10927
|
-
await
|
|
11223
|
+
await writeFile24(
|
|
10928
11224
|
path35.join(teamDir, `${fm.id}.md`),
|
|
10929
|
-
|
|
11225
|
+
serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
|
|
10930
11226
|
"utf8"
|
|
10931
11227
|
);
|
|
10932
11228
|
console.log(ui.green(` \u2713 ${fm.id}`));
|
|
@@ -10948,15 +11244,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
10948
11244
|
}
|
|
10949
11245
|
|
|
10950
11246
|
// src/commands/memory-digest.ts
|
|
10951
|
-
import { existsSync as
|
|
10952
|
-
import { writeFile as
|
|
11247
|
+
import { existsSync as existsSync58 } from "fs";
|
|
11248
|
+
import { writeFile as writeFile25 } from "fs/promises";
|
|
10953
11249
|
import path36 from "path";
|
|
10954
11250
|
import "commander";
|
|
10955
11251
|
import {
|
|
10956
11252
|
deriveConfidence as deriveConfidence12,
|
|
10957
11253
|
findProjectRoot as findProjectRoot35,
|
|
10958
11254
|
getUsage as getUsage20,
|
|
10959
|
-
loadMemoriesFromDir as
|
|
11255
|
+
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
10960
11256
|
loadUsageIndex as loadUsageIndex25,
|
|
10961
11257
|
resolveHaivePaths as resolveHaivePaths32
|
|
10962
11258
|
} from "@hiveai/core";
|
|
@@ -10973,7 +11269,7 @@ function registerMemoryDigest(program2) {
|
|
|
10973
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) => {
|
|
10974
11270
|
const root = findProjectRoot35(opts.dir);
|
|
10975
11271
|
const paths = resolveHaivePaths32(root);
|
|
10976
|
-
if (!
|
|
11272
|
+
if (!existsSync58(paths.memoriesDir)) {
|
|
10977
11273
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
10978
11274
|
process.exitCode = 1;
|
|
10979
11275
|
return;
|
|
@@ -10981,7 +11277,7 @@ function registerMemoryDigest(program2) {
|
|
|
10981
11277
|
const days = Math.max(1, Number(opts.days ?? 7));
|
|
10982
11278
|
const scopeFilter = opts.scope ?? "team";
|
|
10983
11279
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
10984
|
-
const all = await
|
|
11280
|
+
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
10985
11281
|
const usage = await loadUsageIndex25(paths);
|
|
10986
11282
|
const recent = all.filter(({ memory: mem }) => {
|
|
10987
11283
|
const fm = mem.frontmatter;
|
|
@@ -11046,7 +11342,7 @@ function registerMemoryDigest(program2) {
|
|
|
11046
11342
|
const digest = lines.join("\n");
|
|
11047
11343
|
if (opts.out) {
|
|
11048
11344
|
const outPath = path36.resolve(process.cwd(), opts.out);
|
|
11049
|
-
await
|
|
11345
|
+
await writeFile25(outPath, digest, "utf8");
|
|
11050
11346
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
11051
11347
|
} else {
|
|
11052
11348
|
console.log(digest);
|
|
@@ -11055,22 +11351,22 @@ function registerMemoryDigest(program2) {
|
|
|
11055
11351
|
}
|
|
11056
11352
|
|
|
11057
11353
|
// src/commands/session-end.ts
|
|
11058
|
-
import { writeFile as
|
|
11059
|
-
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";
|
|
11060
11356
|
import { spawn as spawn4 } from "child_process";
|
|
11061
11357
|
import path37 from "path";
|
|
11062
11358
|
import "commander";
|
|
11063
11359
|
import {
|
|
11064
11360
|
buildFrontmatter as buildFrontmatter10,
|
|
11065
11361
|
findProjectRoot as findProjectRoot36,
|
|
11066
|
-
loadMemoriesFromDir as
|
|
11067
|
-
memoryFilePath as
|
|
11362
|
+
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
11363
|
+
memoryFilePath as memoryFilePath10,
|
|
11068
11364
|
resolveHaivePaths as resolveHaivePaths33,
|
|
11069
|
-
serializeMemory as
|
|
11365
|
+
serializeMemory as serializeMemory23
|
|
11070
11366
|
} from "@hiveai/core";
|
|
11071
11367
|
async function buildAutoRecap(paths) {
|
|
11072
11368
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11073
|
-
if (!
|
|
11369
|
+
if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
|
|
11074
11370
|
const raw = await readFile17(obsFile, "utf8").catch(() => "");
|
|
11075
11371
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
11076
11372
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -11261,7 +11557,7 @@ function registerSessionEnd(session2) {
|
|
|
11261
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) => {
|
|
11262
11558
|
const root = findProjectRoot36(opts.dir);
|
|
11263
11559
|
const paths = resolveHaivePaths33(root);
|
|
11264
|
-
if (!
|
|
11560
|
+
if (!existsSync59(paths.haiveDir)) {
|
|
11265
11561
|
if (opts.auto || opts.quiet) return;
|
|
11266
11562
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11267
11563
|
process.exitCode = 1;
|
|
@@ -11294,7 +11590,7 @@ function registerSessionEnd(session2) {
|
|
|
11294
11590
|
});
|
|
11295
11591
|
const topic = recapTopic2(scope, opts.module);
|
|
11296
11592
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
11297
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
11593
|
+
const missingPaths = filesTouched.filter((p) => !existsSync59(path37.resolve(root, p)));
|
|
11298
11594
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
11299
11595
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
11300
11596
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -11302,11 +11598,11 @@ function registerSessionEnd(session2) {
|
|
|
11302
11598
|
const cleanupObservations = async () => {
|
|
11303
11599
|
if (!opts.auto) return;
|
|
11304
11600
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11305
|
-
if (
|
|
11601
|
+
if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
|
|
11306
11602
|
});
|
|
11307
11603
|
};
|
|
11308
|
-
if (
|
|
11309
|
-
const existing = await
|
|
11604
|
+
if (existsSync59(paths.memoriesDir)) {
|
|
11605
|
+
const existing = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
11310
11606
|
const topicMatch = existing.find(
|
|
11311
11607
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
11312
11608
|
);
|
|
@@ -11322,7 +11618,7 @@ function registerSessionEnd(session2) {
|
|
|
11322
11618
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
11323
11619
|
}
|
|
11324
11620
|
};
|
|
11325
|
-
await
|
|
11621
|
+
await writeFile26(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
|
|
11326
11622
|
await cleanupObservations();
|
|
11327
11623
|
if (!opts.quiet) {
|
|
11328
11624
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
@@ -11342,9 +11638,9 @@ function registerSessionEnd(session2) {
|
|
|
11342
11638
|
topic,
|
|
11343
11639
|
status: "validated"
|
|
11344
11640
|
});
|
|
11345
|
-
const file =
|
|
11641
|
+
const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
11346
11642
|
await mkdir15(path37.dirname(file), { recursive: true });
|
|
11347
|
-
await
|
|
11643
|
+
await writeFile26(file, serializeMemory23({ frontmatter, body }), "utf8");
|
|
11348
11644
|
await cleanupObservations();
|
|
11349
11645
|
if (!opts.quiet) {
|
|
11350
11646
|
ui.success(`Session recap created`);
|
|
@@ -11367,7 +11663,7 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
11367
11663
|
}
|
|
11368
11664
|
|
|
11369
11665
|
// src/commands/snapshot.ts
|
|
11370
|
-
import { existsSync as
|
|
11666
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11371
11667
|
import { readdir as readdir4 } from "fs/promises";
|
|
11372
11668
|
import path38 from "path";
|
|
11373
11669
|
import "commander";
|
|
@@ -11402,14 +11698,14 @@ function registerSnapshot(program2) {
|
|
|
11402
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) => {
|
|
11403
11699
|
const root = findProjectRoot37(opts.dir);
|
|
11404
11700
|
const paths = resolveHaivePaths34(root);
|
|
11405
|
-
if (!
|
|
11701
|
+
if (!existsSync60(paths.haiveDir)) {
|
|
11406
11702
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
11407
11703
|
process.exitCode = 1;
|
|
11408
11704
|
return;
|
|
11409
11705
|
}
|
|
11410
11706
|
if (opts.list) {
|
|
11411
11707
|
const contractsDir = path38.join(paths.haiveDir, "contracts");
|
|
11412
|
-
if (!
|
|
11708
|
+
if (!existsSync60(contractsDir)) {
|
|
11413
11709
|
console.log(ui.dim("No contract snapshots found."));
|
|
11414
11710
|
return;
|
|
11415
11711
|
}
|
|
@@ -11533,18 +11829,18 @@ function detectFormat(filePath) {
|
|
|
11533
11829
|
}
|
|
11534
11830
|
|
|
11535
11831
|
// src/commands/hub.ts
|
|
11536
|
-
import { existsSync as
|
|
11537
|
-
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";
|
|
11538
11834
|
import path39 from "path";
|
|
11539
11835
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
11540
11836
|
import "commander";
|
|
11541
11837
|
import {
|
|
11542
11838
|
findProjectRoot as findProjectRoot38,
|
|
11543
11839
|
loadConfig as loadConfig8,
|
|
11544
|
-
loadMemoriesFromDir as
|
|
11840
|
+
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
11545
11841
|
resolveHaivePaths as resolveHaivePaths35,
|
|
11546
11842
|
saveConfig as saveConfig3,
|
|
11547
|
-
serializeMemory as
|
|
11843
|
+
serializeMemory as serializeMemory24
|
|
11548
11844
|
} from "@hiveai/core";
|
|
11549
11845
|
function registerHub(program2) {
|
|
11550
11846
|
const hub = program2.command("hub").description(
|
|
@@ -11567,7 +11863,7 @@ function registerHub(program2) {
|
|
|
11567
11863
|
}
|
|
11568
11864
|
const sharedDir = path39.join(absPath, ".ai", "memories", "shared");
|
|
11569
11865
|
await mkdir16(sharedDir, { recursive: true });
|
|
11570
|
-
await
|
|
11866
|
+
await writeFile27(
|
|
11571
11867
|
path39.join(absPath, ".ai", "README.md"),
|
|
11572
11868
|
`# hAIve Team Knowledge Hub
|
|
11573
11869
|
|
|
@@ -11589,7 +11885,7 @@ haive hub pull # import into a project
|
|
|
11589
11885
|
`,
|
|
11590
11886
|
"utf8"
|
|
11591
11887
|
);
|
|
11592
|
-
await
|
|
11888
|
+
await writeFile27(
|
|
11593
11889
|
path39.join(absPath, ".gitignore"),
|
|
11594
11890
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
11595
11891
|
"utf8"
|
|
@@ -11635,7 +11931,7 @@ Next steps:
|
|
|
11635
11931
|
return;
|
|
11636
11932
|
}
|
|
11637
11933
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11638
|
-
if (!
|
|
11934
|
+
if (!existsSync61(hubRoot)) {
|
|
11639
11935
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
11640
11936
|
process.exitCode = 1;
|
|
11641
11937
|
return;
|
|
@@ -11643,7 +11939,7 @@ Next steps:
|
|
|
11643
11939
|
const projectName = path39.basename(root);
|
|
11644
11940
|
const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
11645
11941
|
await mkdir16(destDir, { recursive: true });
|
|
11646
|
-
const all = await
|
|
11942
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11647
11943
|
const shared = all.filter(
|
|
11648
11944
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
11649
11945
|
!memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
@@ -11661,7 +11957,7 @@ Next steps:
|
|
|
11661
11957
|
const fm = memory2.frontmatter;
|
|
11662
11958
|
const fileName = `${fm.id}.md`;
|
|
11663
11959
|
const destPath = path39.join(destDir, fileName);
|
|
11664
|
-
await
|
|
11960
|
+
await writeFile27(destPath, serializeMemory24(memory2), "utf8");
|
|
11665
11961
|
pushed++;
|
|
11666
11962
|
}
|
|
11667
11963
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
@@ -11705,7 +12001,7 @@ Next steps:
|
|
|
11705
12001
|
}
|
|
11706
12002
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11707
12003
|
const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
|
|
11708
|
-
if (!
|
|
12004
|
+
if (!existsSync61(hubSharedDir)) {
|
|
11709
12005
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
11710
12006
|
return;
|
|
11711
12007
|
}
|
|
@@ -11760,7 +12056,7 @@ Next steps:
|
|
|
11760
12056
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
11761
12057
|
);
|
|
11762
12058
|
const sharedDir = path39.join(paths.memoriesDir, "shared");
|
|
11763
|
-
if (
|
|
12059
|
+
if (existsSync61(sharedDir)) {
|
|
11764
12060
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11765
12061
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
11766
12062
|
console.log(`
|
|
@@ -11772,7 +12068,7 @@ Next steps:
|
|
|
11772
12068
|
} else {
|
|
11773
12069
|
console.log(ui.dim(" No imported shared memories yet."));
|
|
11774
12070
|
}
|
|
11775
|
-
const all = await
|
|
12071
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11776
12072
|
const outgoing = all.filter(
|
|
11777
12073
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
11778
12074
|
);
|
|
@@ -11782,20 +12078,20 @@ Next steps:
|
|
|
11782
12078
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
11783
12079
|
}
|
|
11784
12080
|
void readFile18;
|
|
11785
|
-
void
|
|
12081
|
+
void writeFile27;
|
|
11786
12082
|
void saveConfig3;
|
|
11787
12083
|
});
|
|
11788
12084
|
}
|
|
11789
12085
|
|
|
11790
12086
|
// src/commands/stats.ts
|
|
11791
12087
|
import "commander";
|
|
11792
|
-
import { existsSync as
|
|
11793
|
-
import { mkdir as mkdir17, writeFile as
|
|
12088
|
+
import { existsSync as existsSync63 } from "fs";
|
|
12089
|
+
import { mkdir as mkdir17, writeFile as writeFile28 } from "fs/promises";
|
|
11794
12090
|
import path40 from "path";
|
|
11795
12091
|
import {
|
|
11796
12092
|
aggregateUsage,
|
|
11797
12093
|
findProjectRoot as findProjectRoot39,
|
|
11798
|
-
loadMemoriesFromDir as
|
|
12094
|
+
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
11799
12095
|
loadUsageIndex as loadUsageIndex26,
|
|
11800
12096
|
parseSince,
|
|
11801
12097
|
readUsageEvents as readUsageEvents2,
|
|
@@ -11867,8 +12163,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11867
12163
|
const size = await usageLogSize(paths);
|
|
11868
12164
|
let events = await readUsageEvents2(paths);
|
|
11869
12165
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
11870
|
-
if (
|
|
11871
|
-
const mems = await
|
|
12166
|
+
if (existsSync63(paths.memoriesDir)) {
|
|
12167
|
+
const mems = await loadMemoriesFromDir31(paths.memoriesDir);
|
|
11872
12168
|
for (const { memory: memory2 } of mems) {
|
|
11873
12169
|
const fm = memory2.frontmatter;
|
|
11874
12170
|
if (fm.type === "session_recap") memoryCount.total_skipped_session++;
|
|
@@ -11910,7 +12206,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11910
12206
|
top_memory_reads: memoryHitsLeader,
|
|
11911
12207
|
roi_hints: roiHints
|
|
11912
12208
|
};
|
|
11913
|
-
await
|
|
12209
|
+
await writeFile28(outAbs, JSON.stringify(payload, null, 2), "utf8");
|
|
11914
12210
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
11915
12211
|
}
|
|
11916
12212
|
async function renderMemoryHits(paths, opts) {
|
|
@@ -12087,8 +12383,8 @@ function summarize(name, t0, payload, notes) {
|
|
|
12087
12383
|
}
|
|
12088
12384
|
|
|
12089
12385
|
// src/commands/benchmark.ts
|
|
12090
|
-
import { existsSync as
|
|
12091
|
-
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";
|
|
12092
12388
|
import path41 from "path";
|
|
12093
12389
|
import "commander";
|
|
12094
12390
|
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
@@ -12105,7 +12401,7 @@ function registerBenchmark(program2) {
|
|
|
12105
12401
|
const markdown = renderMarkdown(root, summary, rows);
|
|
12106
12402
|
if (opts.out) {
|
|
12107
12403
|
const outFile = path41.isAbsolute(opts.out) ? opts.out : path41.join(root, opts.out);
|
|
12108
|
-
await
|
|
12404
|
+
await writeFile29(outFile, markdown, "utf8");
|
|
12109
12405
|
ui.success(`wrote ${path41.relative(process.cwd(), outFile)}`);
|
|
12110
12406
|
return;
|
|
12111
12407
|
}
|
|
@@ -12135,14 +12431,14 @@ function resolveBenchmarkRoot(dir) {
|
|
|
12135
12431
|
return path41.join(projectRoot, candidate);
|
|
12136
12432
|
}
|
|
12137
12433
|
async function collectRows(root) {
|
|
12138
|
-
if (!
|
|
12434
|
+
if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
12139
12435
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
12140
12436
|
const rows = [];
|
|
12141
12437
|
for (const entry of entries) {
|
|
12142
12438
|
if (!entry.isDirectory()) continue;
|
|
12143
12439
|
const fixtureDir = path41.join(root, entry.name);
|
|
12144
12440
|
const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
12145
|
-
if (!
|
|
12441
|
+
if (!existsSync64(reportFile)) continue;
|
|
12146
12442
|
const report = await readFile19(reportFile, "utf8");
|
|
12147
12443
|
rows.push(parseAgentReport(entry.name, report));
|
|
12148
12444
|
}
|
|
@@ -12232,14 +12528,15 @@ function escapeRegExp(value) {
|
|
|
12232
12528
|
}
|
|
12233
12529
|
|
|
12234
12530
|
// src/commands/eval.ts
|
|
12235
|
-
import { readFile as readFile20, writeFile as
|
|
12236
|
-
import { existsSync as
|
|
12531
|
+
import { mkdir as mkdir18, readFile as readFile20, writeFile as writeFile30 } from "fs/promises";
|
|
12532
|
+
import { existsSync as existsSync65 } from "fs";
|
|
12237
12533
|
import path43 from "path";
|
|
12238
12534
|
import "commander";
|
|
12239
12535
|
import {
|
|
12240
12536
|
aggregateRetrieval,
|
|
12241
12537
|
aggregateSensors,
|
|
12242
12538
|
buildReport,
|
|
12539
|
+
compareEvalReports,
|
|
12243
12540
|
findProjectRoot as findProjectRoot42,
|
|
12244
12541
|
resolveHaivePaths as resolveHaivePaths38,
|
|
12245
12542
|
scoreRetrievalCase,
|
|
@@ -12249,10 +12546,10 @@ import {
|
|
|
12249
12546
|
function registerEval(program2) {
|
|
12250
12547
|
program2.command("eval").description(
|
|
12251
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."
|
|
12252
|
-
).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) => {
|
|
12253
12550
|
const root = findProjectRoot42(opts.dir);
|
|
12254
12551
|
const paths = resolveHaivePaths38(root);
|
|
12255
|
-
if (!
|
|
12552
|
+
if (!existsSync65(paths.memoriesDir)) {
|
|
12256
12553
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12257
12554
|
process.exitCode = 1;
|
|
12258
12555
|
return;
|
|
@@ -12284,30 +12581,82 @@ function registerEval(program2) {
|
|
|
12284
12581
|
sensorAgg = aggregateSensors(results);
|
|
12285
12582
|
}
|
|
12286
12583
|
const report = buildReport(retrievalAgg, sensorAgg);
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
}
|
|
12298
|
-
}
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
if (
|
|
12302
|
-
ui.error(
|
|
12303
|
-
process.exitCode = 1;
|
|
12304
|
-
} else if (report.score < threshold) {
|
|
12305
|
-
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.`);
|
|
12306
12600
|
process.exitCode = 1;
|
|
12601
|
+
return;
|
|
12307
12602
|
}
|
|
12603
|
+
const snapshot = JSON.parse(await readFile20(baselineFile, "utf8"));
|
|
12604
|
+
delta = compareEvalReports(snapshot.report, report);
|
|
12308
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));
|
|
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);
|
|
12309
12623
|
});
|
|
12310
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
|
+
}
|
|
12311
12660
|
async function resolveSpec(opts, root, memoriesDir) {
|
|
12312
12661
|
if (opts.spec) {
|
|
12313
12662
|
const file = path43.resolve(opts.spec);
|
|
@@ -12315,10 +12664,10 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
12315
12664
|
return { spec: JSON.parse(raw), source: file };
|
|
12316
12665
|
}
|
|
12317
12666
|
const defaultSpec = path43.join(root, ".ai", "eval", "spec.json");
|
|
12318
|
-
if (
|
|
12667
|
+
if (existsSync65(defaultSpec)) {
|
|
12319
12668
|
const raw = await readFile20(defaultSpec, "utf8");
|
|
12320
12669
|
const explicit = JSON.parse(raw);
|
|
12321
|
-
const memories2 = await
|
|
12670
|
+
const memories2 = await loadMemoriesFromDir27(memoriesDir);
|
|
12322
12671
|
const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
|
|
12323
12672
|
return {
|
|
12324
12673
|
spec: {
|
|
@@ -12328,7 +12677,7 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
12328
12677
|
source: ".ai/eval/spec.json + synthesized anchored retrieval"
|
|
12329
12678
|
};
|
|
12330
12679
|
}
|
|
12331
|
-
const memories = await
|
|
12680
|
+
const memories = await loadMemoriesFromDir27(memoriesDir);
|
|
12332
12681
|
return {
|
|
12333
12682
|
spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
|
|
12334
12683
|
source: "synthesized anchored retrieval"
|
|
@@ -12418,8 +12767,8 @@ function renderMarkdown2(root, k, source, report) {
|
|
|
12418
12767
|
}
|
|
12419
12768
|
|
|
12420
12769
|
// src/commands/memory-suggest.ts
|
|
12421
|
-
import { mkdir as
|
|
12422
|
-
import { existsSync as
|
|
12770
|
+
import { mkdir as mkdir19, writeFile as writeFile31 } from "fs/promises";
|
|
12771
|
+
import { existsSync as existsSync66 } from "fs";
|
|
12423
12772
|
import path44 from "path";
|
|
12424
12773
|
import "commander";
|
|
12425
12774
|
import {
|
|
@@ -12427,12 +12776,12 @@ import {
|
|
|
12427
12776
|
buildFrontmatter as buildFrontmatter11,
|
|
12428
12777
|
findProjectRoot as findProjectRoot43,
|
|
12429
12778
|
loadConfig as loadConfig9,
|
|
12430
|
-
loadMemoriesFromDir as
|
|
12431
|
-
memoryFilePath as
|
|
12779
|
+
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
12780
|
+
memoryFilePath as memoryFilePath11,
|
|
12432
12781
|
parseSince as parseSince2,
|
|
12433
12782
|
readUsageEvents as readUsageEvents3,
|
|
12434
12783
|
resolveHaivePaths as resolveHaivePaths39,
|
|
12435
|
-
serializeMemory as
|
|
12784
|
+
serializeMemory as serializeMemory25
|
|
12436
12785
|
} from "@hiveai/core";
|
|
12437
12786
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
12438
12787
|
"mem_search",
|
|
@@ -12498,7 +12847,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12498
12847
|
}
|
|
12499
12848
|
const created = [];
|
|
12500
12849
|
const skipped = [];
|
|
12501
|
-
const existing =
|
|
12850
|
+
const existing = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
|
|
12502
12851
|
for (const s of top) {
|
|
12503
12852
|
const slug = slugify2(s.query);
|
|
12504
12853
|
if (!slug) {
|
|
@@ -12520,13 +12869,13 @@ function registerMemorySuggest(memory2) {
|
|
|
12520
12869
|
status
|
|
12521
12870
|
});
|
|
12522
12871
|
const body = renderTemplate(s, fm.id, status);
|
|
12523
|
-
const file =
|
|
12524
|
-
await
|
|
12525
|
-
if (
|
|
12872
|
+
const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
|
|
12873
|
+
await mkdir19(path44.dirname(file), { recursive: true });
|
|
12874
|
+
if (existsSync66(file)) {
|
|
12526
12875
|
skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
|
|
12527
12876
|
continue;
|
|
12528
12877
|
}
|
|
12529
|
-
await
|
|
12878
|
+
await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
|
|
12530
12879
|
created.push({ id: fm.id, file: path44.relative(root, file), query: s.query });
|
|
12531
12880
|
}
|
|
12532
12881
|
if (opts.json) {
|
|
@@ -12625,8 +12974,8 @@ function truncate2(text, max) {
|
|
|
12625
12974
|
}
|
|
12626
12975
|
|
|
12627
12976
|
// src/commands/memory-archive.ts
|
|
12628
|
-
import { existsSync as
|
|
12629
|
-
import { writeFile as
|
|
12977
|
+
import { existsSync as existsSync67 } from "fs";
|
|
12978
|
+
import { writeFile as writeFile33 } from "fs/promises";
|
|
12630
12979
|
import path45 from "path";
|
|
12631
12980
|
import "commander";
|
|
12632
12981
|
import {
|
|
@@ -12634,10 +12983,10 @@ import {
|
|
|
12634
12983
|
getUsage as getUsage21,
|
|
12635
12984
|
retirementSignal as retirementSignal2,
|
|
12636
12985
|
loadConfig as loadConfig10,
|
|
12637
|
-
loadMemoriesFromDir as
|
|
12986
|
+
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
12638
12987
|
loadUsageIndex as loadUsageIndex27,
|
|
12639
12988
|
resolveHaivePaths as resolveHaivePaths40,
|
|
12640
|
-
serializeMemory as
|
|
12989
|
+
serializeMemory as serializeMemory26
|
|
12641
12990
|
} from "@hiveai/core";
|
|
12642
12991
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
12643
12992
|
function registerMemoryArchive(memory2) {
|
|
@@ -12646,7 +12995,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12646
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) => {
|
|
12647
12996
|
const root = findProjectRoot44(opts.dir);
|
|
12648
12997
|
const paths = resolveHaivePaths40(root);
|
|
12649
|
-
if (!
|
|
12998
|
+
if (!existsSync67(paths.memoriesDir)) {
|
|
12650
12999
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12651
13000
|
process.exitCode = 1;
|
|
12652
13001
|
return;
|
|
@@ -12660,7 +13009,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12660
13009
|
return;
|
|
12661
13010
|
}
|
|
12662
13011
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
12663
|
-
const all = await
|
|
13012
|
+
const all = await loadMemoriesFromDir34(paths.memoriesDir);
|
|
12664
13013
|
const usage = await loadUsageIndex27(paths);
|
|
12665
13014
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
12666
13015
|
const candidates = [];
|
|
@@ -12671,7 +13020,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12671
13020
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
12672
13021
|
const retired = retirementSignal2(fm, mem.body);
|
|
12673
13022
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
12674
|
-
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)));
|
|
12675
13024
|
const isAnchorless = !hasAnyAnchor;
|
|
12676
13025
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
12677
13026
|
const u = getUsage21(usage, fm.id);
|
|
@@ -12720,7 +13069,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12720
13069
|
if (!found) continue;
|
|
12721
13070
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
12722
13071
|
try {
|
|
12723
|
-
await
|
|
13072
|
+
await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
12724
13073
|
archived++;
|
|
12725
13074
|
} catch (err) {
|
|
12726
13075
|
if (!opts.json) {
|
|
@@ -12746,8 +13095,8 @@ function parseDays(input) {
|
|
|
12746
13095
|
}
|
|
12747
13096
|
|
|
12748
13097
|
// src/commands/doctor.ts
|
|
12749
|
-
import { existsSync as
|
|
12750
|
-
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";
|
|
12751
13100
|
import path46 from "path";
|
|
12752
13101
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
12753
13102
|
import "commander";
|
|
@@ -12758,7 +13107,7 @@ import {
|
|
|
12758
13107
|
isStackPackSeed as isStackPackSeed4,
|
|
12759
13108
|
loadCodeMap as loadCodeMap7,
|
|
12760
13109
|
loadConfig as loadConfig11,
|
|
12761
|
-
loadMemoriesFromDir as
|
|
13110
|
+
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
12762
13111
|
loadUsageIndex as loadUsageIndex28,
|
|
12763
13112
|
readUsageEvents as readUsageEvents4,
|
|
12764
13113
|
resolveHaivePaths as resolveHaivePaths41
|
|
@@ -12773,7 +13122,7 @@ function registerDoctor(program2) {
|
|
|
12773
13122
|
const findings = [];
|
|
12774
13123
|
const repairs = [];
|
|
12775
13124
|
const config = await loadConfig11(paths);
|
|
12776
|
-
if (!
|
|
13125
|
+
if (!existsSync68(paths.haiveDir)) {
|
|
12777
13126
|
findings.push({
|
|
12778
13127
|
severity: "error",
|
|
12779
13128
|
code: "not-initialized",
|
|
@@ -12794,7 +13143,7 @@ function registerDoctor(program2) {
|
|
|
12794
13143
|
})
|
|
12795
13144
|
);
|
|
12796
13145
|
}
|
|
12797
|
-
if (!
|
|
13146
|
+
if (!existsSync68(paths.projectContext)) {
|
|
12798
13147
|
findings.push({
|
|
12799
13148
|
severity: "warn",
|
|
12800
13149
|
code: "no-project-context",
|
|
@@ -12802,8 +13151,8 @@ function registerDoctor(program2) {
|
|
|
12802
13151
|
fix: "haive init"
|
|
12803
13152
|
});
|
|
12804
13153
|
} else {
|
|
12805
|
-
const { readFile:
|
|
12806
|
-
const content = await
|
|
13154
|
+
const { readFile: readFile26 } = await import("fs/promises");
|
|
13155
|
+
const content = await readFile26(paths.projectContext, "utf8");
|
|
12807
13156
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
12808
13157
|
if (isTemplate) {
|
|
12809
13158
|
findings.push({
|
|
@@ -12823,7 +13172,7 @@ function registerDoctor(program2) {
|
|
|
12823
13172
|
});
|
|
12824
13173
|
}
|
|
12825
13174
|
}
|
|
12826
|
-
const memories =
|
|
13175
|
+
const memories = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
|
|
12827
13176
|
const now = Date.now();
|
|
12828
13177
|
if (memories.length === 0) {
|
|
12829
13178
|
findings.push({
|
|
@@ -12975,10 +13324,10 @@ function registerDoctor(program2) {
|
|
|
12975
13324
|
if (config.enforcement?.requireBriefingFirst) {
|
|
12976
13325
|
const claudeSettings = path46.join(root, ".claude", "settings.local.json");
|
|
12977
13326
|
let hasClaudeEnforcement = false;
|
|
12978
|
-
if (
|
|
13327
|
+
if (existsSync68(claudeSettings)) {
|
|
12979
13328
|
try {
|
|
12980
|
-
const { readFile:
|
|
12981
|
-
const raw = await
|
|
13329
|
+
const { readFile: readFile26 } = await import("fs/promises");
|
|
13330
|
+
const raw = await readFile26(claudeSettings, "utf8");
|
|
12982
13331
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
12983
13332
|
} catch {
|
|
12984
13333
|
hasClaudeEnforcement = false;
|
|
@@ -13001,7 +13350,7 @@ function registerDoctor(program2) {
|
|
|
13001
13350
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
13002
13351
|
});
|
|
13003
13352
|
}
|
|
13004
|
-
findings.push(...await collectInstallFindings(root, "0.12.
|
|
13353
|
+
findings.push(...await collectInstallFindings(root, "0.12.9"));
|
|
13005
13354
|
findings.push(...await collectToolchainFindings(root));
|
|
13006
13355
|
try {
|
|
13007
13356
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -13009,7 +13358,7 @@ function registerDoctor(program2) {
|
|
|
13009
13358
|
timeout: 3e3,
|
|
13010
13359
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13011
13360
|
}).trim();
|
|
13012
|
-
const cliVersion = "0.12.
|
|
13361
|
+
const cliVersion = "0.12.9";
|
|
13013
13362
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
13014
13363
|
findings.push({
|
|
13015
13364
|
severity: "warn",
|
|
@@ -13031,14 +13380,14 @@ npm uninstall -g @hiveai/mcp`
|
|
|
13031
13380
|
];
|
|
13032
13381
|
const staleConfigs = [];
|
|
13033
13382
|
for (const cfgPath of configPaths) {
|
|
13034
|
-
if (!
|
|
13383
|
+
if (!existsSync68(cfgPath)) continue;
|
|
13035
13384
|
try {
|
|
13036
13385
|
const raw = await readFile21(cfgPath, "utf8");
|
|
13037
13386
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
13038
13387
|
staleConfigs.push(path46.relative(root, cfgPath));
|
|
13039
13388
|
if (opts.fix && !opts.dryRun) {
|
|
13040
13389
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
13041
|
-
await
|
|
13390
|
+
await writeFile34(cfgPath, updated, "utf8");
|
|
13042
13391
|
}
|
|
13043
13392
|
}
|
|
13044
13393
|
} catch {
|
|
@@ -13327,7 +13676,7 @@ which -a haive`
|
|
|
13327
13676
|
];
|
|
13328
13677
|
for (const rel of integrationFiles) {
|
|
13329
13678
|
const file = path46.join(root, rel);
|
|
13330
|
-
if (!
|
|
13679
|
+
if (!existsSync68(file)) continue;
|
|
13331
13680
|
const text = await readFile21(file, "utf8").catch(() => "");
|
|
13332
13681
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
13333
13682
|
const version = versionForBinary(bin);
|
|
@@ -13375,7 +13724,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
13375
13724
|
const isHaiveWorkspace = (await readJson(path46.join(root, "package.json")))?.name === "haive-monorepo";
|
|
13376
13725
|
if (!isHaiveWorkspace) return findings;
|
|
13377
13726
|
const cliDist = path46.join(root, "packages/cli/dist/index.js");
|
|
13378
|
-
if (!
|
|
13727
|
+
if (!existsSync68(cliDist)) {
|
|
13379
13728
|
findings.push({
|
|
13380
13729
|
severity: "warn",
|
|
13381
13730
|
code: "workspace-dist-missing",
|
|
@@ -13399,7 +13748,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
13399
13748
|
"packages/core/src/index.ts",
|
|
13400
13749
|
"packages/mcp/src/server.ts",
|
|
13401
13750
|
"packages/cli/src/index.ts"
|
|
13402
|
-
].map((rel) => path46.join(root, rel)).filter(
|
|
13751
|
+
].map((rel) => path46.join(root, rel)).filter(existsSync68);
|
|
13403
13752
|
if (sourceFiles.length > 0) {
|
|
13404
13753
|
const distMtime = statSync2(cliDist).mtimeMs;
|
|
13405
13754
|
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
@@ -13488,7 +13837,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
13488
13837
|
}
|
|
13489
13838
|
}
|
|
13490
13839
|
async function readJson(file) {
|
|
13491
|
-
if (!
|
|
13840
|
+
if (!existsSync68(file)) return null;
|
|
13492
13841
|
try {
|
|
13493
13842
|
return JSON.parse(await readFile21(file, "utf8"));
|
|
13494
13843
|
} catch {
|
|
@@ -13559,11 +13908,11 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
13559
13908
|
}
|
|
13560
13909
|
|
|
13561
13910
|
// src/commands/playback.ts
|
|
13562
|
-
import { existsSync as
|
|
13911
|
+
import { existsSync as existsSync69 } from "fs";
|
|
13563
13912
|
import "commander";
|
|
13564
13913
|
import {
|
|
13565
13914
|
findProjectRoot as findProjectRoot46,
|
|
13566
|
-
loadMemoriesFromDir as
|
|
13915
|
+
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
13567
13916
|
parseSince as parseSince3,
|
|
13568
13917
|
readUsageEvents as readUsageEvents5,
|
|
13569
13918
|
resolveHaivePaths as resolveHaivePaths42
|
|
@@ -13589,7 +13938,7 @@ function registerPlayback(program2) {
|
|
|
13589
13938
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
13590
13939
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
13591
13940
|
const sessions = bucketSessions(filtered, gapMs);
|
|
13592
|
-
const all =
|
|
13941
|
+
const all = existsSync69(paths.memoriesDir) ? await loadMemoriesFromDir36(paths.memoriesDir) : [];
|
|
13593
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);
|
|
13594
13943
|
const enriched = sessions.map((s, i) => {
|
|
13595
13944
|
const startMs = Date.parse(s.start);
|
|
@@ -13831,11 +14180,11 @@ function runCommand3(cmd, args, cwd) {
|
|
|
13831
14180
|
}
|
|
13832
14181
|
|
|
13833
14182
|
// src/commands/welcome.ts
|
|
13834
|
-
import { existsSync as
|
|
14183
|
+
import { existsSync as existsSync70 } from "fs";
|
|
13835
14184
|
import "commander";
|
|
13836
14185
|
import {
|
|
13837
14186
|
findProjectRoot as findProjectRoot48,
|
|
13838
|
-
loadMemoriesFromDir as
|
|
14187
|
+
loadMemoriesFromDir as loadMemoriesFromDir37,
|
|
13839
14188
|
resolveHaivePaths as resolveHaivePaths44
|
|
13840
14189
|
} from "@hiveai/core";
|
|
13841
14190
|
var TYPE_RANK = {
|
|
@@ -13853,12 +14202,12 @@ function registerWelcome(program2) {
|
|
|
13853
14202
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13854
14203
|
const root = findProjectRoot48(opts.dir);
|
|
13855
14204
|
const paths = resolveHaivePaths44(root);
|
|
13856
|
-
if (!
|
|
14205
|
+
if (!existsSync70(paths.memoriesDir)) {
|
|
13857
14206
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
13858
14207
|
process.exitCode = 1;
|
|
13859
14208
|
return;
|
|
13860
14209
|
}
|
|
13861
|
-
const all = await
|
|
14210
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
13862
14211
|
const team = all.filter(
|
|
13863
14212
|
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
13864
14213
|
);
|
|
@@ -13925,7 +14274,7 @@ function registerResolveProject(program2) {
|
|
|
13925
14274
|
}
|
|
13926
14275
|
|
|
13927
14276
|
// src/commands/runtime-journal.ts
|
|
13928
|
-
import { existsSync as
|
|
14277
|
+
import { existsSync as existsSync71 } from "fs";
|
|
13929
14278
|
import path48 from "path";
|
|
13930
14279
|
import "commander";
|
|
13931
14280
|
import {
|
|
@@ -13951,7 +14300,7 @@ function registerRuntime(program2) {
|
|
|
13951
14300
|
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13952
14301
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13953
14302
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13954
|
-
if (!
|
|
14303
|
+
if (!existsSync71(paths.haiveDir)) {
|
|
13955
14304
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
13956
14305
|
process.exitCode = 1;
|
|
13957
14306
|
return;
|
|
@@ -13966,7 +14315,7 @@ function registerRuntime(program2) {
|
|
|
13966
14315
|
}
|
|
13967
14316
|
|
|
13968
14317
|
// src/commands/memory-timeline.ts
|
|
13969
|
-
import { existsSync as
|
|
14318
|
+
import { existsSync as existsSync73 } from "fs";
|
|
13970
14319
|
import path49 from "path";
|
|
13971
14320
|
import "commander";
|
|
13972
14321
|
import {
|
|
@@ -13985,13 +14334,13 @@ function registerMemoryTimeline(memory2) {
|
|
|
13985
14334
|
}
|
|
13986
14335
|
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
13987
14336
|
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
13988
|
-
if (!
|
|
14337
|
+
if (!existsSync73(paths.memoriesDir)) {
|
|
13989
14338
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13990
14339
|
process.exitCode = 1;
|
|
13991
14340
|
return;
|
|
13992
14341
|
}
|
|
13993
14342
|
const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13994
|
-
const all = await
|
|
14343
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
13995
14344
|
const { entries, notice } = collectTimelineEntries2(all, {
|
|
13996
14345
|
memoryId: opts.id,
|
|
13997
14346
|
topic: opts.topic,
|
|
@@ -14003,7 +14352,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
14003
14352
|
}
|
|
14004
14353
|
|
|
14005
14354
|
// src/commands/memory-conflict-candidates.ts
|
|
14006
|
-
import { existsSync as
|
|
14355
|
+
import { existsSync as existsSync74 } from "fs";
|
|
14007
14356
|
import path50 from "path";
|
|
14008
14357
|
import "commander";
|
|
14009
14358
|
import {
|
|
@@ -14028,7 +14377,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14028
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) => {
|
|
14029
14378
|
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
14030
14379
|
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
14031
|
-
if (!
|
|
14380
|
+
if (!existsSync74(paths.memoriesDir)) {
|
|
14032
14381
|
ui.error("No memories \u2014 run `haive init`.");
|
|
14033
14382
|
process.exitCode = 1;
|
|
14034
14383
|
return;
|
|
@@ -14038,7 +14387,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14038
14387
|
const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
|
|
14039
14388
|
const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
|
|
14040
14389
|
const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
|
|
14041
|
-
const all = await
|
|
14390
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
14042
14391
|
const lexical = findLexicalConflictPairs2(all, {
|
|
14043
14392
|
sinceDays,
|
|
14044
14393
|
types: parseTypes(opts.types),
|
|
@@ -14064,8 +14413,8 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14064
14413
|
|
|
14065
14414
|
// src/commands/enforce.ts
|
|
14066
14415
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
14067
|
-
import { existsSync as
|
|
14068
|
-
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";
|
|
14069
14418
|
import path51 from "path";
|
|
14070
14419
|
import "commander";
|
|
14071
14420
|
import {
|
|
@@ -14074,7 +14423,7 @@ import {
|
|
|
14074
14423
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
14075
14424
|
isFreshIsoDate,
|
|
14076
14425
|
loadConfig as loadConfig13,
|
|
14077
|
-
loadMemoriesFromDir as
|
|
14426
|
+
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
14078
14427
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
14079
14428
|
readRecentBriefingMarker,
|
|
14080
14429
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
@@ -14093,7 +14442,7 @@ function registerEnforce(program2) {
|
|
|
14093
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) => {
|
|
14094
14443
|
const root = findProjectRoot52(opts.dir);
|
|
14095
14444
|
const paths = resolveHaivePaths48(root);
|
|
14096
|
-
await
|
|
14445
|
+
await mkdir20(paths.haiveDir, { recursive: true });
|
|
14097
14446
|
const current = await loadConfig13(paths);
|
|
14098
14447
|
await saveConfig4(paths, {
|
|
14099
14448
|
...current,
|
|
@@ -14139,14 +14488,14 @@ function registerEnforce(program2) {
|
|
|
14139
14488
|
const root = findProjectRoot52(opts.dir);
|
|
14140
14489
|
const paths = resolveHaivePaths48(root);
|
|
14141
14490
|
const cacheDir = path51.join(paths.haiveDir, ".cache");
|
|
14142
|
-
if (
|
|
14491
|
+
if (existsSync75(cacheDir)) {
|
|
14143
14492
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
14144
14493
|
else {
|
|
14145
14494
|
const removed = await cleanupCacheDir(cacheDir);
|
|
14146
14495
|
ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
14147
14496
|
}
|
|
14148
14497
|
}
|
|
14149
|
-
if (
|
|
14498
|
+
if (existsSync75(paths.runtimeDir)) {
|
|
14150
14499
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
14151
14500
|
else {
|
|
14152
14501
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
@@ -14171,8 +14520,8 @@ function registerEnforce(program2) {
|
|
|
14171
14520
|
const root = resolveRoot(opts.dir, payload);
|
|
14172
14521
|
if (!root) return;
|
|
14173
14522
|
const paths = resolveHaivePaths48(root);
|
|
14174
|
-
if (!
|
|
14175
|
-
await
|
|
14523
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
14524
|
+
await mkdir20(paths.runtimeDir, { recursive: true });
|
|
14176
14525
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
14177
14526
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
14178
14527
|
await applyLightweightRepairs(root, paths);
|
|
@@ -14234,7 +14583,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14234
14583
|
const root = resolveRoot(opts.dir, payload);
|
|
14235
14584
|
if (!root) return;
|
|
14236
14585
|
const paths = resolveHaivePaths48(root);
|
|
14237
|
-
if (!
|
|
14586
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
14238
14587
|
if (!isWriteLikeTool(payload)) return;
|
|
14239
14588
|
const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
|
|
14240
14589
|
if (ok) {
|
|
@@ -14278,7 +14627,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14278
14627
|
async function buildFinishReport(dir) {
|
|
14279
14628
|
const root = findProjectRoot52(dir);
|
|
14280
14629
|
const paths = resolveHaivePaths48(root);
|
|
14281
|
-
const initialized =
|
|
14630
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
14282
14631
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14283
14632
|
const mode = config.enforcement?.mode ?? "strict";
|
|
14284
14633
|
const findings = [];
|
|
@@ -14371,6 +14720,7 @@ async function buildFinishReport(dir) {
|
|
|
14371
14720
|
code: "release-version-not-required",
|
|
14372
14721
|
message: "No shippable package code changed since upstream; no version/tag required."
|
|
14373
14722
|
});
|
|
14723
|
+
findings.push(...await verifyGithubActionsForHead(root, status));
|
|
14374
14724
|
return finishReport(root, initialized, mode, findings, config);
|
|
14375
14725
|
}
|
|
14376
14726
|
findings.push({
|
|
@@ -14457,6 +14807,7 @@ async function buildFinishReport(dir) {
|
|
|
14457
14807
|
impact: 10
|
|
14458
14808
|
});
|
|
14459
14809
|
}
|
|
14810
|
+
findings.push(...await verifyGithubActionsForHead(root, status));
|
|
14460
14811
|
return finishReport(root, initialized, mode, findings, config);
|
|
14461
14812
|
}
|
|
14462
14813
|
function finishReport(root, initialized, mode, findings, config) {
|
|
@@ -14474,7 +14825,7 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
14474
14825
|
async function runWithEnforcement(command, args, opts) {
|
|
14475
14826
|
const root = findProjectRoot52(opts.dir);
|
|
14476
14827
|
const paths = resolveHaivePaths48(root);
|
|
14477
|
-
if (!
|
|
14828
|
+
if (!existsSync75(paths.haiveDir)) {
|
|
14478
14829
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
14479
14830
|
process.exit(1);
|
|
14480
14831
|
}
|
|
@@ -14544,7 +14895,7 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14544
14895
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
14545
14896
|
});
|
|
14546
14897
|
const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
|
|
14547
|
-
await
|
|
14898
|
+
await mkdir20(dir, { recursive: true });
|
|
14548
14899
|
const file = path51.join(dir, `${sessionId}.md`);
|
|
14549
14900
|
const parts = [
|
|
14550
14901
|
"# hAIve Briefing",
|
|
@@ -14563,13 +14914,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14563
14914
|
if (briefing.setup_warnings.length > 0) {
|
|
14564
14915
|
parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
|
|
14565
14916
|
}
|
|
14566
|
-
await
|
|
14917
|
+
await writeFile35(file, parts.join("\n") + "\n", "utf8");
|
|
14567
14918
|
return file;
|
|
14568
14919
|
}
|
|
14569
14920
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
14570
14921
|
const root = findProjectRoot52(dir);
|
|
14571
14922
|
const paths = resolveHaivePaths48(root);
|
|
14572
|
-
const initialized =
|
|
14923
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
14573
14924
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14574
14925
|
if (initialized) await applyLightweightRepairs(root, paths);
|
|
14575
14926
|
const mode = config.enforcement?.mode ?? "strict";
|
|
@@ -14600,7 +14951,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14600
14951
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14601
14952
|
});
|
|
14602
14953
|
}
|
|
14603
|
-
findings.push(...await inspectIntegrationVersions(root, "0.12.
|
|
14954
|
+
findings.push(...await inspectIntegrationVersions(root, "0.12.9"));
|
|
14604
14955
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14605
14956
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14606
14957
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14670,8 +15021,8 @@ function withCategories(report) {
|
|
|
14670
15021
|
};
|
|
14671
15022
|
}
|
|
14672
15023
|
async function hasRecentSessionRecap(paths) {
|
|
14673
|
-
if (!
|
|
14674
|
-
const all = await
|
|
15024
|
+
if (!existsSync75(paths.memoriesDir)) return false;
|
|
15025
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14675
15026
|
return all.some(({ memory: memory2 }) => {
|
|
14676
15027
|
const fm = memory2.frontmatter;
|
|
14677
15028
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -14679,8 +15030,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
14679
15030
|
});
|
|
14680
15031
|
}
|
|
14681
15032
|
async function verifyMemoryPolicy(paths, config) {
|
|
14682
|
-
if (!
|
|
14683
|
-
const all = await
|
|
15033
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15034
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14684
15035
|
const findings = [];
|
|
14685
15036
|
const staleImportant = [];
|
|
14686
15037
|
let verified = 0;
|
|
@@ -14717,12 +15068,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
14717
15068
|
return findings;
|
|
14718
15069
|
}
|
|
14719
15070
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
14720
|
-
if (!
|
|
15071
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
14721
15072
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
14722
15073
|
if (changedFiles.length === 0) {
|
|
14723
15074
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
14724
15075
|
}
|
|
14725
|
-
const all = await
|
|
15076
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14726
15077
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
14727
15078
|
const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
14728
15079
|
const fm = memory2.frontmatter;
|
|
@@ -14826,7 +15177,7 @@ function isGeneratedArtifactStatusLine(line) {
|
|
|
14826
15177
|
}
|
|
14827
15178
|
async function cleanupRuntimeDir(runtimeDir) {
|
|
14828
15179
|
let removed = 0;
|
|
14829
|
-
await
|
|
15180
|
+
await mkdir20(runtimeDir, { recursive: true });
|
|
14830
15181
|
const entries = await readdir6(runtimeDir, { withFileTypes: true }).catch(() => []);
|
|
14831
15182
|
for (const entry of entries) {
|
|
14832
15183
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
@@ -14837,9 +15188,9 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14837
15188
|
await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
14838
15189
|
removed++;
|
|
14839
15190
|
}
|
|
14840
|
-
await
|
|
14841
|
-
if (!
|
|
14842
|
-
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(
|
|
14843
15194
|
path51.join(runtimeDir, "README.md"),
|
|
14844
15195
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
14845
15196
|
"utf8"
|
|
@@ -14849,14 +15200,14 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14849
15200
|
}
|
|
14850
15201
|
async function cleanupCacheDir(cacheDir) {
|
|
14851
15202
|
let removed = 0;
|
|
14852
|
-
await
|
|
15203
|
+
await mkdir20(cacheDir, { recursive: true });
|
|
14853
15204
|
const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
14854
15205
|
for (const entry of entries) {
|
|
14855
15206
|
if (entry.name === ".gitignore") continue;
|
|
14856
15207
|
await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
14857
15208
|
removed++;
|
|
14858
15209
|
}
|
|
14859
|
-
await
|
|
15210
|
+
await writeFile35(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
14860
15211
|
return removed;
|
|
14861
15212
|
}
|
|
14862
15213
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -14881,7 +15232,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
14881
15232
|
const findings = [];
|
|
14882
15233
|
for (const rel of files) {
|
|
14883
15234
|
const file = path51.join(root, rel);
|
|
14884
|
-
if (!
|
|
15235
|
+
if (!existsSync75(file)) continue;
|
|
14885
15236
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14886
15237
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
14887
15238
|
const version = versionForBinary2(bin);
|
|
@@ -14991,7 +15342,7 @@ async function resolveCiDiffRange(root) {
|
|
|
14991
15342
|
}
|
|
14992
15343
|
async function resolveGithubEventRange(root) {
|
|
14993
15344
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
14994
|
-
if (!eventPath || !
|
|
15345
|
+
if (!eventPath || !existsSync75(eventPath)) return null;
|
|
14995
15346
|
try {
|
|
14996
15347
|
const event = JSON.parse(await readFile23(eventPath, "utf8"));
|
|
14997
15348
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -15152,6 +15503,108 @@ async function remoteTagExists(root, tag) {
|
|
|
15152
15503
|
return null;
|
|
15153
15504
|
}
|
|
15154
15505
|
}
|
|
15506
|
+
async function verifyGithubActionsForHead(root, status) {
|
|
15507
|
+
if (!status.upstream) return [];
|
|
15508
|
+
if (status.ahead > 0) {
|
|
15509
|
+
return [{
|
|
15510
|
+
severity: "info",
|
|
15511
|
+
code: "github-actions-waiting-for-push",
|
|
15512
|
+
message: "GitHub Actions verification waits until HEAD is pushed."
|
|
15513
|
+
}];
|
|
15514
|
+
}
|
|
15515
|
+
const remote = await githubRemoteForCurrentBranch(root);
|
|
15516
|
+
if (!remote) {
|
|
15517
|
+
return [{
|
|
15518
|
+
severity: "info",
|
|
15519
|
+
code: "github-actions-not-applicable",
|
|
15520
|
+
message: "No GitHub remote was detected; GitHub Actions pipeline verification was skipped."
|
|
15521
|
+
}];
|
|
15522
|
+
}
|
|
15523
|
+
const sha = (await runCommand4("git", ["rev-parse", "HEAD"], root).catch(() => "")).trim();
|
|
15524
|
+
if (!sha) {
|
|
15525
|
+
return [{
|
|
15526
|
+
severity: "error",
|
|
15527
|
+
code: "github-actions-head-unreadable",
|
|
15528
|
+
message: "Could not read HEAD SHA for GitHub Actions verification.",
|
|
15529
|
+
fix: "Run `git rev-parse HEAD`, then verify GitHub Actions manually before finishing.",
|
|
15530
|
+
impact: 30
|
|
15531
|
+
}];
|
|
15532
|
+
}
|
|
15533
|
+
let runs;
|
|
15534
|
+
try {
|
|
15535
|
+
const raw = await runCommand4("gh", [
|
|
15536
|
+
"run",
|
|
15537
|
+
"list",
|
|
15538
|
+
"--commit",
|
|
15539
|
+
sha,
|
|
15540
|
+
"--limit",
|
|
15541
|
+
"50",
|
|
15542
|
+
"--json",
|
|
15543
|
+
"conclusion,databaseId,name,status,workflowName"
|
|
15544
|
+
], root);
|
|
15545
|
+
runs = JSON.parse(raw);
|
|
15546
|
+
} catch {
|
|
15547
|
+
return [{
|
|
15548
|
+
severity: "error",
|
|
15549
|
+
code: "github-actions-unverified",
|
|
15550
|
+
message: "Could not verify GitHub Actions runs for HEAD.",
|
|
15551
|
+
fix: "Install/authenticate GitHub CLI, then run `gh run list --commit $(git rev-parse HEAD)` and ensure every workflow is successful before finishing.",
|
|
15552
|
+
reason: `Detected GitHub remote ${remote}, but hAIve could not query workflow runs.`,
|
|
15553
|
+
impact: 50
|
|
15554
|
+
}];
|
|
15555
|
+
}
|
|
15556
|
+
if (runs.length === 0) {
|
|
15557
|
+
return [{
|
|
15558
|
+
severity: "error",
|
|
15559
|
+
code: "github-actions-runs-missing",
|
|
15560
|
+
message: "No GitHub Actions runs were found for HEAD.",
|
|
15561
|
+
fix: "Wait for GitHub to create the workflow runs, or verify that the push was not skipped by a `[skip ci]` head commit; rerun `haive enforce finish` after the runs appear.",
|
|
15562
|
+
impact: 50
|
|
15563
|
+
}];
|
|
15564
|
+
}
|
|
15565
|
+
const pending = runs.filter((run) => run.status !== "completed");
|
|
15566
|
+
if (pending.length > 0) {
|
|
15567
|
+
return [{
|
|
15568
|
+
severity: "error",
|
|
15569
|
+
code: "github-actions-pending",
|
|
15570
|
+
message: `${pending.length}/${runs.length} GitHub Actions workflow run(s) for HEAD are still pending: ${formatGithubRunNames(pending)}.`,
|
|
15571
|
+
fix: "Wait for the runs to finish (`gh run watch <run-id> --exit-status`), then rerun `haive enforce finish`.",
|
|
15572
|
+
impact: 50
|
|
15573
|
+
}];
|
|
15574
|
+
}
|
|
15575
|
+
const failed = runs.filter((run) => run.conclusion !== "success");
|
|
15576
|
+
if (failed.length > 0) {
|
|
15577
|
+
return [{
|
|
15578
|
+
severity: "error",
|
|
15579
|
+
code: "github-actions-failed",
|
|
15580
|
+
message: `${failed.length}/${runs.length} GitHub Actions workflow run(s) for HEAD did not pass: ${formatGithubRunNames(failed)}.`,
|
|
15581
|
+
fix: "Inspect the failed run logs with `gh run view <run-id> --log`, fix the issue, push the fix, then rerun `haive enforce finish`.",
|
|
15582
|
+
impact: 80
|
|
15583
|
+
}];
|
|
15584
|
+
}
|
|
15585
|
+
return [{
|
|
15586
|
+
severity: "ok",
|
|
15587
|
+
code: "github-actions-pass",
|
|
15588
|
+
message: `All ${runs.length} GitHub Actions workflow run(s) for HEAD completed successfully.`
|
|
15589
|
+
}];
|
|
15590
|
+
}
|
|
15591
|
+
async function githubRemoteForCurrentBranch(root) {
|
|
15592
|
+
const branch = (await runCommand4("git", ["branch", "--show-current"], root).catch(() => "")).trim();
|
|
15593
|
+
const branchRemote = branch ? (await runCommand4("git", ["config", "--get", `branch.${branch}.remote`], root).catch(() => "")).trim() : "";
|
|
15594
|
+
const remoteName = branchRemote || "origin";
|
|
15595
|
+
const remoteUrl = (await runCommand4("git", ["config", "--get", `remote.${remoteName}.url`], root).catch(() => "")).trim();
|
|
15596
|
+
if (!isGithubRemoteUrl(remoteUrl)) return null;
|
|
15597
|
+
return remoteUrl;
|
|
15598
|
+
}
|
|
15599
|
+
function isGithubRemoteUrl(url) {
|
|
15600
|
+
return /(^git@github\.com:|github\.com[/:])/.test(url);
|
|
15601
|
+
}
|
|
15602
|
+
function formatGithubRunNames(runs) {
|
|
15603
|
+
return runs.slice(0, 6).map((run) => {
|
|
15604
|
+
const label = run.workflowName ?? run.name ?? "workflow";
|
|
15605
|
+
return run.databaseId ? `${label}#${run.databaseId}` : label;
|
|
15606
|
+
}).join(", ");
|
|
15607
|
+
}
|
|
15155
15608
|
function buildScore(findings, threshold = 80) {
|
|
15156
15609
|
const checks = {
|
|
15157
15610
|
total: findings.length,
|
|
@@ -15172,11 +15625,11 @@ function buildScore(findings, threshold = 80) {
|
|
|
15172
15625
|
}
|
|
15173
15626
|
async function installGitEnforcement(root) {
|
|
15174
15627
|
const hooksDir = path51.join(root, ".git", "hooks");
|
|
15175
|
-
if (!
|
|
15628
|
+
if (!existsSync75(path51.join(root, ".git"))) {
|
|
15176
15629
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
15177
15630
|
return;
|
|
15178
15631
|
}
|
|
15179
|
-
await
|
|
15632
|
+
await mkdir20(hooksDir, { recursive: true });
|
|
15180
15633
|
const hooks = [
|
|
15181
15634
|
{
|
|
15182
15635
|
name: "pre-commit",
|
|
@@ -15195,17 +15648,17 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
15195
15648
|
];
|
|
15196
15649
|
for (const hook of hooks) {
|
|
15197
15650
|
const file = path51.join(hooksDir, hook.name);
|
|
15198
|
-
if (
|
|
15651
|
+
if (existsSync75(file)) {
|
|
15199
15652
|
const current = await readFile23(file, "utf8").catch(() => "");
|
|
15200
15653
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
15201
|
-
await
|
|
15654
|
+
await writeFile35(file, hook.body, "utf8");
|
|
15202
15655
|
} else {
|
|
15203
|
-
await
|
|
15656
|
+
await writeFile35(file, `${current.trimEnd()}
|
|
15204
15657
|
|
|
15205
15658
|
${hook.body}`, "utf8");
|
|
15206
15659
|
}
|
|
15207
15660
|
} else {
|
|
15208
|
-
await
|
|
15661
|
+
await writeFile35(file, hook.body, "utf8");
|
|
15209
15662
|
}
|
|
15210
15663
|
await chmod2(file, 493);
|
|
15211
15664
|
}
|
|
@@ -15213,12 +15666,12 @@ ${hook.body}`, "utf8");
|
|
|
15213
15666
|
}
|
|
15214
15667
|
async function installCiEnforcement(root) {
|
|
15215
15668
|
const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
15216
|
-
await
|
|
15217
|
-
if (
|
|
15669
|
+
await mkdir20(path51.dirname(workflowPath), { recursive: true });
|
|
15670
|
+
if (existsSync75(workflowPath)) {
|
|
15218
15671
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
15219
15672
|
return;
|
|
15220
15673
|
}
|
|
15221
|
-
await
|
|
15674
|
+
await writeFile35(workflowPath, `name: haive-enforcement
|
|
15222
15675
|
|
|
15223
15676
|
on:
|
|
15224
15677
|
pull_request:
|
|
@@ -15346,11 +15799,11 @@ function normalizeToolPath(file, root) {
|
|
|
15346
15799
|
return path51.relative(root, normalized).replace(/\\/g, "/");
|
|
15347
15800
|
}
|
|
15348
15801
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
15349
|
-
if (!
|
|
15802
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15350
15803
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
15351
15804
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
15352
15805
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
15353
|
-
const all = await
|
|
15806
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
15354
15807
|
return all.filter(({ memory: memory2 }) => {
|
|
15355
15808
|
const fm = memory2.frontmatter;
|
|
15356
15809
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -15420,19 +15873,19 @@ function registerRun(program2) {
|
|
|
15420
15873
|
|
|
15421
15874
|
// src/commands/sensors.ts
|
|
15422
15875
|
import { execFile as execFile2 } from "child_process";
|
|
15423
|
-
import { existsSync as
|
|
15424
|
-
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";
|
|
15425
15878
|
import path53 from "path";
|
|
15426
15879
|
import { promisify as promisify2 } from "util";
|
|
15427
15880
|
import "commander";
|
|
15428
15881
|
import {
|
|
15429
15882
|
findProjectRoot as findProjectRoot53,
|
|
15430
15883
|
isRetiredMemory as isRetiredMemory3,
|
|
15431
|
-
loadMemoriesFromDir as
|
|
15884
|
+
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
15432
15885
|
resolveHaivePaths as resolveHaivePaths49,
|
|
15433
15886
|
runSensors as runSensors2,
|
|
15434
15887
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15435
|
-
serializeMemory as
|
|
15888
|
+
serializeMemory as serializeMemory27
|
|
15436
15889
|
} from "@hiveai/core";
|
|
15437
15890
|
var exec2 = promisify2(execFile2);
|
|
15438
15891
|
function registerSensors(program2) {
|
|
@@ -15503,7 +15956,7 @@ function registerSensors(program2) {
|
|
|
15503
15956
|
}
|
|
15504
15957
|
const root = findProjectRoot53(opts.dir);
|
|
15505
15958
|
const paths = resolveHaivePaths49(root);
|
|
15506
|
-
const loaded =
|
|
15959
|
+
const loaded = existsSync76(paths.memoriesDir) ? await loadMemoriesFromDir39(paths.memoriesDir) : [];
|
|
15507
15960
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
15508
15961
|
if (!found) {
|
|
15509
15962
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -15523,7 +15976,7 @@ function registerSensors(program2) {
|
|
|
15523
15976
|
},
|
|
15524
15977
|
body: found.memory.body
|
|
15525
15978
|
};
|
|
15526
|
-
await
|
|
15979
|
+
await writeFile36(found.filePath, serializeMemory27(next), "utf8");
|
|
15527
15980
|
ui.success(`Updated ${id}: sensor severity=${severity}`);
|
|
15528
15981
|
if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
|
|
15529
15982
|
ui.info(`message=${sensor.message}`);
|
|
@@ -15539,10 +15992,10 @@ function registerSensors(program2) {
|
|
|
15539
15992
|
const paths = resolveHaivePaths49(root);
|
|
15540
15993
|
const rows = await sensorRows(paths);
|
|
15541
15994
|
const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
|
|
15542
|
-
await
|
|
15995
|
+
await mkdir21(outDir, { recursive: true });
|
|
15543
15996
|
const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
15544
15997
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
15545
|
-
await
|
|
15998
|
+
await writeFile36(outPath, content, "utf8");
|
|
15546
15999
|
if (format === "grep") await chmod3(outPath, 493);
|
|
15547
16000
|
ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
|
|
15548
16001
|
});
|
|
@@ -15565,8 +16018,8 @@ async function sensorRows(paths) {
|
|
|
15565
16018
|
});
|
|
15566
16019
|
}
|
|
15567
16020
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
15568
|
-
if (!
|
|
15569
|
-
const loaded = await
|
|
16021
|
+
if (!existsSync76(paths.memoriesDir)) return [];
|
|
16022
|
+
const loaded = await loadMemoriesFromDir39(paths.memoriesDir);
|
|
15570
16023
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
15571
16024
|
const sensor = memory2.frontmatter.sensor;
|
|
15572
16025
|
if (!sensor) return false;
|
|
@@ -15606,9 +16059,253 @@ function shellQuote(value) {
|
|
|
15606
16059
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
15607
16060
|
}
|
|
15608
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
|
+
|
|
15609
16306
|
// src/index.ts
|
|
15610
|
-
var program = new
|
|
15611
|
-
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");
|
|
15612
16309
|
registerInit(program);
|
|
15613
16310
|
registerWelcome(program);
|
|
15614
16311
|
registerResolveProject(program);
|
|
@@ -15617,6 +16314,8 @@ registerEnforce(program);
|
|
|
15617
16314
|
registerRun(program);
|
|
15618
16315
|
registerAgent(program);
|
|
15619
16316
|
registerSensors(program);
|
|
16317
|
+
registerIngest(program);
|
|
16318
|
+
registerDashboard(program);
|
|
15620
16319
|
registerMcp(program);
|
|
15621
16320
|
registerBriefing(program);
|
|
15622
16321
|
registerTui(program);
|