@hiveai/cli 0.12.4 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.js +1298 -542
- 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,256 @@ 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
|
+
flask: [
|
|
2796
|
+
{
|
|
2797
|
+
slug: "flask-no-debug-in-prod",
|
|
2798
|
+
type: "gotcha",
|
|
2799
|
+
tags: ["flask", "python", "security", "deployment"],
|
|
2800
|
+
body: `\`app.run(debug=True)\` enables the Werkzeug debugger \u2014 remote code execution if exposed.
|
|
2801
|
+
|
|
2802
|
+
Never ship debug mode. Run behind a real WSGI server (gunicorn/uwsgi) in production and
|
|
2803
|
+
drive debug from the environment for local dev only.`,
|
|
2804
|
+
sensor: {
|
|
2805
|
+
pattern: "app\\.run\\([^)]*debug\\s*=\\s*True",
|
|
2806
|
+
message: "Flask debug=True exposes the Werkzeug console (RCE) \u2014 never run it in production."
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
slug: "flask-secret-key-from-env",
|
|
2811
|
+
type: "convention",
|
|
2812
|
+
tags: ["flask", "python", "security"],
|
|
2813
|
+
body: `Load \`SECRET_KEY\` from the environment \u2014 never commit a literal.
|
|
2814
|
+
|
|
2815
|
+
\`\`\`py
|
|
2816
|
+
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
|
|
2817
|
+
\`\`\`
|
|
2818
|
+
A committed key lets anyone forge sessions and CSRF tokens.`
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
slug: "flask-no-sql-string-interpolation",
|
|
2822
|
+
type: "gotcha",
|
|
2823
|
+
tags: ["flask", "python", "security", "sql-injection"],
|
|
2824
|
+
body: `Never build SQL with f-strings/%-formatting \u2014 use parameterized queries.
|
|
2825
|
+
|
|
2826
|
+
\`\`\`py
|
|
2827
|
+
# \u274C SQL injection
|
|
2828
|
+
db.execute(f"SELECT * FROM users WHERE id = {uid}")
|
|
2829
|
+
# \u2705
|
|
2830
|
+
db.execute("SELECT * FROM users WHERE id = %s", (uid,))
|
|
2831
|
+
\`\`\``
|
|
2832
|
+
}
|
|
2833
|
+
],
|
|
2834
|
+
vue: [
|
|
2835
|
+
{
|
|
2836
|
+
slug: "vue-v-html-xss",
|
|
2837
|
+
type: "gotcha",
|
|
2838
|
+
tags: ["vue", "security", "xss"],
|
|
2839
|
+
body: `\`v-html\` renders raw HTML and bypasses Vue's escaping \u2014 an XSS sink for user content.
|
|
2840
|
+
|
|
2841
|
+
Only use it on trusted/sanitized content. Prefer text interpolation ({{ }}) or sanitize
|
|
2842
|
+
with DOMPurify before binding.`,
|
|
2843
|
+
sensor: {
|
|
2844
|
+
pattern: "v-html",
|
|
2845
|
+
message: "v-html renders unescaped HTML (XSS risk) \u2014 sanitize the value or use text interpolation."
|
|
2846
|
+
}
|
|
2847
|
+
},
|
|
2848
|
+
{
|
|
2849
|
+
slug: "vue-key-in-v-for",
|
|
2850
|
+
type: "convention",
|
|
2851
|
+
tags: ["vue", "performance"],
|
|
2852
|
+
body: `Always bind a stable \`:key\` on \`v-for\` \u2014 and never the loop index.
|
|
2853
|
+
|
|
2854
|
+
Index keys corrupt component state on reorder/insert, exactly like React. Use a stable id.`
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
slug: "vue-props-are-readonly",
|
|
2858
|
+
type: "gotcha",
|
|
2859
|
+
tags: ["vue", "reactivity"],
|
|
2860
|
+
body: `Never mutate a prop inside a child component \u2014 props are one-way (parent \u2192 child).
|
|
2861
|
+
|
|
2862
|
+
Mutating a prop breaks the data flow and warns in dev. Emit an event (\`update:modelValue\`)
|
|
2863
|
+
or copy the prop into local state, depending on intent.`
|
|
2864
|
+
}
|
|
2865
|
+
],
|
|
2866
|
+
spring: [
|
|
2867
|
+
{
|
|
2868
|
+
slug: "spring-constructor-injection",
|
|
2869
|
+
type: "convention",
|
|
2870
|
+
tags: ["spring", "java", "di", "testing"],
|
|
2871
|
+
body: `Prefer constructor injection over \`@Autowired\` field injection.
|
|
2872
|
+
|
|
2873
|
+
Constructor injection makes dependencies explicit, allows \`final\` fields, and lets you
|
|
2874
|
+
instantiate the class in tests without a Spring context. Field injection hides dependencies
|
|
2875
|
+
and forces reflection-based test setup.`
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
slug: "spring-no-cors-wildcard",
|
|
2879
|
+
type: "gotcha",
|
|
2880
|
+
tags: ["spring", "java", "security", "cors"],
|
|
2881
|
+
body: `\`@CrossOrigin(origins = "*")\` (or wildcard CORS config) allows any site to call your API.
|
|
2882
|
+
|
|
2883
|
+
Combined with credentials it leaks authenticated data cross-origin. Whitelist explicit origins.`,
|
|
2884
|
+
sensor: {
|
|
2885
|
+
pattern: "@CrossOrigin\\([^)]*\\*",
|
|
2886
|
+
message: 'Wildcard CORS (@CrossOrigin origins="*") lets any site call your API \u2014 whitelist explicit origins.'
|
|
2887
|
+
}
|
|
2888
|
+
},
|
|
2889
|
+
{
|
|
2890
|
+
slug: "spring-no-field-secrets",
|
|
2891
|
+
type: "convention",
|
|
2892
|
+
tags: ["spring", "java", "security", "config"],
|
|
2893
|
+
body: `Keep secrets in externalized config (env / vault / application.yml placeholders), not in source.
|
|
2894
|
+
|
|
2895
|
+
\`\`\`java
|
|
2896
|
+
@Value("\${app.api-key}") private String apiKey; // resolved from env, not hardcoded
|
|
2897
|
+
\`\`\``
|
|
2898
|
+
}
|
|
2899
|
+
],
|
|
2900
|
+
go: [
|
|
2901
|
+
{
|
|
2902
|
+
slug: "go-check-every-error",
|
|
2903
|
+
type: "convention",
|
|
2904
|
+
tags: ["go", "error-handling"],
|
|
2905
|
+
body: `Check every returned error \u2014 never discard it with \`_\`.
|
|
2906
|
+
|
|
2907
|
+
\`\`\`go
|
|
2908
|
+
// \u274C silently ignores failure
|
|
2909
|
+
val, _ := doThing()
|
|
2910
|
+
|
|
2911
|
+
// \u2705
|
|
2912
|
+
val, err := doThing()
|
|
2913
|
+
if err != nil {
|
|
2914
|
+
return fmt.Errorf("doThing: %w", err)
|
|
2915
|
+
}
|
|
2916
|
+
\`\`\`
|
|
2917
|
+
Wrap with \`%w\` to preserve the chain for errors.Is/As.`
|
|
2918
|
+
},
|
|
2919
|
+
{
|
|
2920
|
+
slug: "go-defer-close-after-error-check",
|
|
2921
|
+
type: "gotcha",
|
|
2922
|
+
tags: ["go", "resources"],
|
|
2923
|
+
body: `Place \`defer rows.Close()\` (or file/body Close) AFTER checking the open error, not before.
|
|
2924
|
+
|
|
2925
|
+
\`\`\`go
|
|
2926
|
+
rows, err := db.Query(q)
|
|
2927
|
+
if err != nil { return err }
|
|
2928
|
+
defer rows.Close() // only reached when rows is non-nil
|
|
2929
|
+
\`\`\`
|
|
2930
|
+
Deferring before the error check can call Close on a nil resource and panic.`
|
|
2931
|
+
},
|
|
2932
|
+
{
|
|
2933
|
+
slug: "go-context-first-param",
|
|
2934
|
+
type: "convention",
|
|
2935
|
+
tags: ["go", "context", "api-design"],
|
|
2936
|
+
body: `context.Context is always the FIRST parameter and is never stored in a struct.
|
|
2937
|
+
|
|
2938
|
+
\`\`\`go
|
|
2939
|
+
func Fetch(ctx context.Context, id string) (*User, error)
|
|
2940
|
+
\`\`\`
|
|
2941
|
+
Pass it explicitly down the call chain so cancellation and deadlines propagate.`
|
|
2942
|
+
}
|
|
2685
2943
|
]
|
|
2686
2944
|
};
|
|
2687
2945
|
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 +2979,16 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
2721
2979
|
await mkdir4(haivePaths.teamDir, { recursive: true });
|
|
2722
2980
|
let count = 0;
|
|
2723
2981
|
for (const mem of memories) {
|
|
2982
|
+
const sensor = mem.sensor ? {
|
|
2983
|
+
kind: "regex",
|
|
2984
|
+
pattern: mem.sensor.pattern,
|
|
2985
|
+
...mem.sensor.flags ? { flags: mem.sensor.flags } : {},
|
|
2986
|
+
paths: mem.sensor.paths ?? [],
|
|
2987
|
+
message: mem.sensor.message,
|
|
2988
|
+
severity: "warn",
|
|
2989
|
+
autogen: false,
|
|
2990
|
+
last_fired: null
|
|
2991
|
+
} : void 0;
|
|
2724
2992
|
const fm = buildFrontmatter({
|
|
2725
2993
|
type: mem.type,
|
|
2726
2994
|
slug: `${stack}-${mem.slug}`,
|
|
@@ -2728,7 +2996,8 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
2728
2996
|
status: "validated",
|
|
2729
2997
|
// STACK_PACK_TAG marks this as generic seed knowledge so briefing ranking
|
|
2730
2998
|
// keeps it at `background` priority until it earns a repo-specific anchor.
|
|
2731
|
-
tags: [...mem.tags, STACK_PACK_TAG]
|
|
2999
|
+
tags: [...mem.tags, STACK_PACK_TAG],
|
|
3000
|
+
...sensor ? { sensor } : {}
|
|
2732
3001
|
});
|
|
2733
3002
|
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
2734
3003
|
if (existsSync9(filePath)) continue;
|
|
@@ -2743,7 +3012,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
2743
3012
|
}
|
|
2744
3013
|
|
|
2745
3014
|
// src/commands/init.ts
|
|
2746
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3015
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.13.0"}`;
|
|
2747
3016
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
2748
3017
|
|
|
2749
3018
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -2931,7 +3200,7 @@ jobs:
|
|
|
2931
3200
|
function registerInit(program2) {
|
|
2932
3201
|
program2.command("init").description(
|
|
2933
3202
|
"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(
|
|
3203
|
+
).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
3204
|
"--manual",
|
|
2936
3205
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
2937
3206
|
).option(
|
|
@@ -2994,6 +3263,7 @@ function registerInit(program2) {
|
|
|
2994
3263
|
}
|
|
2995
3264
|
if (opts.bridges) {
|
|
2996
3265
|
await writeBridge(root, "CLAUDE.md");
|
|
3266
|
+
await writeBridge(root, "AGENTS.md");
|
|
2997
3267
|
await writeBridge(root, ".cursorrules");
|
|
2998
3268
|
await writeBridge(root, path10.join(".github", "copilot-instructions.md"));
|
|
2999
3269
|
await writeCursorHaiveRule(root);
|
|
@@ -3731,37 +4001,49 @@ import {
|
|
|
3731
4001
|
suggestSensorFromMemory as suggestSensorFromMemory2
|
|
3732
4002
|
} from "@hiveai/core";
|
|
3733
4003
|
import { z as z15 } from "zod";
|
|
3734
|
-
import { mkdir as mkdir42, writeFile as writeFile82 } from "fs/promises";
|
|
3735
4004
|
import { existsSync as existsSync16 } from "fs";
|
|
4005
|
+
import { mkdir as mkdir42, readFile as readFile32, writeFile as writeFile82 } from "fs/promises";
|
|
3736
4006
|
import path62 from "path";
|
|
3737
4007
|
import {
|
|
3738
|
-
|
|
3739
|
-
|
|
4008
|
+
draftsFromFindings,
|
|
4009
|
+
filterNewDrafts,
|
|
4010
|
+
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
3740
4011
|
memoryFilePath as memoryFilePath3,
|
|
4012
|
+
parseFindings,
|
|
3741
4013
|
serializeMemory as serializeMemory7
|
|
3742
4014
|
} from "@hiveai/core";
|
|
3743
4015
|
import { z as z16 } from "zod";
|
|
3744
|
-
import {
|
|
3745
|
-
import { existsSync as
|
|
3746
|
-
import
|
|
4016
|
+
import { mkdir as mkdir52, writeFile as writeFile92 } from "fs/promises";
|
|
4017
|
+
import { existsSync as existsSync17 } from "fs";
|
|
4018
|
+
import path72 from "path";
|
|
3747
4019
|
import {
|
|
3748
|
-
buildFrontmatter as
|
|
3749
|
-
|
|
4020
|
+
buildFrontmatter as buildFrontmatter3,
|
|
4021
|
+
isLikelyGuessable,
|
|
3750
4022
|
memoryFilePath as memoryFilePath4,
|
|
3751
4023
|
serializeMemory as serializeMemory8
|
|
3752
4024
|
} from "@hiveai/core";
|
|
3753
4025
|
import { z as z17 } from "zod";
|
|
4026
|
+
import { writeFile as writeFile11, mkdir as mkdir72 } from "fs/promises";
|
|
4027
|
+
import { existsSync as existsSync19 } from "fs";
|
|
4028
|
+
import path92 from "path";
|
|
4029
|
+
import {
|
|
4030
|
+
buildFrontmatter as buildFrontmatter4,
|
|
4031
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
4032
|
+
memoryFilePath as memoryFilePath5,
|
|
4033
|
+
serializeMemory as serializeMemory9
|
|
4034
|
+
} from "@hiveai/core";
|
|
4035
|
+
import { z as z18 } from "zod";
|
|
3754
4036
|
import {
|
|
3755
4037
|
appendUsageEvent,
|
|
3756
4038
|
appendRuntimeJournalEntry,
|
|
3757
4039
|
loadConfig as loadConfig22
|
|
3758
4040
|
} from "@hiveai/core";
|
|
3759
|
-
import { mkdir as
|
|
3760
|
-
import { existsSync as
|
|
3761
|
-
import
|
|
4041
|
+
import { mkdir as mkdir62, writeFile as writeFile10, rm } from "fs/promises";
|
|
4042
|
+
import { existsSync as existsSync18 } from "fs";
|
|
4043
|
+
import path82 from "path";
|
|
3762
4044
|
import { execSync } from "child_process";
|
|
3763
|
-
import { readFile as
|
|
3764
|
-
import { existsSync as
|
|
4045
|
+
import { readFile as readFile52, writeFile as writeFile12 } from "fs/promises";
|
|
4046
|
+
import { existsSync as existsSync21 } from "fs";
|
|
3765
4047
|
import {
|
|
3766
4048
|
allocateBudget,
|
|
3767
4049
|
computeImpact as computeImpact2,
|
|
@@ -3779,13 +4061,13 @@ import {
|
|
|
3779
4061
|
literalMatchesAnyToken as literalMatchesAnyToken22,
|
|
3780
4062
|
loadCodeMap as loadCodeMap5,
|
|
3781
4063
|
loadConfig as loadConfig3,
|
|
3782
|
-
loadMemoriesFromDir as
|
|
4064
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
3783
4065
|
loadUsageIndex as loadUsageIndex8,
|
|
3784
4066
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
|
|
3785
4067
|
rankMemoriesLexical as rankMemoriesLexical2,
|
|
3786
4068
|
queryCodeMap as queryCodeMap2,
|
|
3787
4069
|
resolveBriefingBudget as resolveBriefingBudget2,
|
|
3788
|
-
serializeMemory as
|
|
4070
|
+
serializeMemory as serializeMemory10,
|
|
3789
4071
|
specificityScore as specificityScore2,
|
|
3790
4072
|
GUESSABLE_THRESHOLD,
|
|
3791
4073
|
tokenizeQuery as tokenizeQuery22,
|
|
@@ -3793,34 +4075,34 @@ import {
|
|
|
3793
4075
|
truncateToTokens,
|
|
3794
4076
|
writeBriefingMarker as writeBriefingMarker2
|
|
3795
4077
|
} from "@hiveai/core";
|
|
3796
|
-
import { z as
|
|
3797
|
-
import { readdir as readdir3, readFile as
|
|
3798
|
-
import { existsSync as
|
|
3799
|
-
import
|
|
4078
|
+
import { z as z19 } from "zod";
|
|
4079
|
+
import { readdir as readdir3, readFile as readFile42 } from "fs/promises";
|
|
4080
|
+
import { existsSync as existsSync20 } from "fs";
|
|
4081
|
+
import path102 from "path";
|
|
3800
4082
|
import { isGlobPath, isStackPackSeed as isStackPackSeed2, pathsOverlap } from "@hiveai/core";
|
|
3801
4083
|
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
4084
|
import { z as z20 } from "zod";
|
|
3806
4085
|
import { existsSync as existsSync222 } from "fs";
|
|
3807
4086
|
import { loadMemoriesFromDir as loadMemoriesFromDir16 } from "@hiveai/core";
|
|
3808
4087
|
import { z as z21 } from "zod";
|
|
4088
|
+
import { existsSync as existsSync23 } from "fs";
|
|
4089
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hiveai/core";
|
|
3809
4090
|
import { z as z22 } from "zod";
|
|
3810
4091
|
import { z as z23 } from "zod";
|
|
3811
|
-
import {
|
|
4092
|
+
import { z as z24 } from "zod";
|
|
4093
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3812
4094
|
import { spawn } from "child_process";
|
|
3813
|
-
import
|
|
4095
|
+
import path112 from "path";
|
|
3814
4096
|
import {
|
|
3815
4097
|
deriveConfidence as deriveConfidence5,
|
|
3816
4098
|
getUsage as getUsage7,
|
|
3817
4099
|
loadCodeMap as loadCodeMap32,
|
|
3818
|
-
loadMemoriesFromDir as
|
|
4100
|
+
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
3819
4101
|
loadUsageIndex as loadUsageIndex9,
|
|
3820
4102
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
|
|
3821
4103
|
} from "@hiveai/core";
|
|
3822
|
-
import { z as
|
|
3823
|
-
import { existsSync as
|
|
4104
|
+
import { z as z25 } from "zod";
|
|
4105
|
+
import { existsSync as existsSync25 } from "fs";
|
|
3824
4106
|
import {
|
|
3825
4107
|
addedLinesFromDiff,
|
|
3826
4108
|
buildDocFrequency,
|
|
@@ -3829,7 +4111,7 @@ import {
|
|
|
3829
4111
|
diffHasDistinctiveOverlap,
|
|
3830
4112
|
getUsage as getUsage8,
|
|
3831
4113
|
isRetiredMemory as isRetiredMemory2,
|
|
3832
|
-
loadMemoriesFromDir as
|
|
4114
|
+
loadMemoriesFromDir as loadMemoriesFromDir19,
|
|
3833
4115
|
loadUsageIndex as loadUsageIndex10,
|
|
3834
4116
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
3835
4117
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
@@ -3837,66 +4119,66 @@ import {
|
|
|
3837
4119
|
sensorTargetsFromDiff,
|
|
3838
4120
|
tokenizeQuery as tokenizeQuery3
|
|
3839
4121
|
} from "@hiveai/core";
|
|
3840
|
-
import { z as
|
|
3841
|
-
import { existsSync as
|
|
4122
|
+
import { z as z26 } from "zod";
|
|
4123
|
+
import { existsSync as existsSync26 } from "fs";
|
|
3842
4124
|
import {
|
|
3843
|
-
loadMemoriesFromDir as
|
|
4125
|
+
loadMemoriesFromDir as loadMemoriesFromDir20,
|
|
3844
4126
|
tokenizeQuery as tokenizeQuery4
|
|
3845
4127
|
} from "@hiveai/core";
|
|
3846
|
-
import { z as
|
|
3847
|
-
import { existsSync as
|
|
4128
|
+
import { z as z27 } from "zod";
|
|
4129
|
+
import { existsSync as existsSync27 } from "fs";
|
|
3848
4130
|
import { spawn as spawn2 } from "child_process";
|
|
3849
4131
|
import {
|
|
3850
4132
|
deriveConfidence as deriveConfidence7,
|
|
3851
4133
|
getUsage as getUsage9,
|
|
3852
|
-
loadMemoriesFromDir as
|
|
4134
|
+
loadMemoriesFromDir as loadMemoriesFromDir21,
|
|
3853
4135
|
loadUsageIndex as loadUsageIndex11,
|
|
3854
4136
|
pathsOverlap as singlePathsOverlap
|
|
3855
4137
|
} from "@hiveai/core";
|
|
3856
|
-
import { z as
|
|
3857
|
-
import { existsSync as
|
|
4138
|
+
import { z as z28 } from "zod";
|
|
4139
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3858
4140
|
import {
|
|
3859
4141
|
deriveConfidence as deriveConfidence8,
|
|
3860
4142
|
getUsage as getUsage10,
|
|
3861
|
-
loadMemoriesFromDir as
|
|
4143
|
+
loadMemoriesFromDir as loadMemoriesFromDir222,
|
|
3862
4144
|
loadUsageIndex as loadUsageIndex12,
|
|
3863
4145
|
pathsOverlap as pathsOverlap2,
|
|
3864
4146
|
tokenizeQuery as tokenizeQuery5
|
|
3865
4147
|
} from "@hiveai/core";
|
|
3866
|
-
import { z as z28 } from "zod";
|
|
3867
4148
|
import { z as z29 } from "zod";
|
|
3868
|
-
import {
|
|
3869
|
-
import {
|
|
3870
|
-
import
|
|
4149
|
+
import { z as z30 } from "zod";
|
|
4150
|
+
import { mkdir as mkdir82, writeFile as writeFile13 } from "fs/promises";
|
|
4151
|
+
import { existsSync as existsSync29 } from "fs";
|
|
4152
|
+
import path122 from "path";
|
|
3871
4153
|
import { execSync as execSync2 } from "child_process";
|
|
3872
4154
|
import {
|
|
3873
4155
|
buildFrontmatter as buildFrontmatter5,
|
|
3874
|
-
memoryFilePath as
|
|
4156
|
+
memoryFilePath as memoryFilePath6,
|
|
3875
4157
|
readUsageEvents,
|
|
3876
|
-
serializeMemory as
|
|
4158
|
+
serializeMemory as serializeMemory11
|
|
3877
4159
|
} from "@hiveai/core";
|
|
3878
|
-
import { z as
|
|
3879
|
-
import { existsSync as
|
|
4160
|
+
import { z as z31 } from "zod";
|
|
4161
|
+
import { existsSync as existsSync30 } from "fs";
|
|
3880
4162
|
import {
|
|
3881
4163
|
findLexicalConflictPairs,
|
|
3882
4164
|
findTopicStatusConflictPairs,
|
|
3883
|
-
loadMemoriesFromDir as
|
|
4165
|
+
loadMemoriesFromDir as loadMemoriesFromDir23
|
|
3884
4166
|
} from "@hiveai/core";
|
|
3885
|
-
import { z as z31 } from "zod";
|
|
3886
|
-
import { resolveProjectInfo } from "@hiveai/core";
|
|
3887
4167
|
import { z as z32 } from "zod";
|
|
3888
|
-
import {
|
|
4168
|
+
import { resolveProjectInfo } from "@hiveai/core";
|
|
3889
4169
|
import { z as z33 } from "zod";
|
|
3890
|
-
import {
|
|
3891
|
-
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hiveai/core";
|
|
4170
|
+
import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
|
|
3892
4171
|
import { z as z34 } from "zod";
|
|
3893
|
-
import {
|
|
4172
|
+
import { existsSync as existsSync31 } from "fs";
|
|
4173
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir24 } from "@hiveai/core";
|
|
3894
4174
|
import { z as z35 } from "zod";
|
|
3895
|
-
import {
|
|
4175
|
+
import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
|
|
3896
4176
|
import { z as z36 } from "zod";
|
|
4177
|
+
import { readRuntimeJournalTail } from "@hiveai/core";
|
|
3897
4178
|
import { z as z37 } from "zod";
|
|
3898
4179
|
import { z as z38 } from "zod";
|
|
3899
4180
|
import { z as z39 } from "zod";
|
|
4181
|
+
import { z as z40 } from "zod";
|
|
3900
4182
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
|
|
3901
4183
|
function createContext(options = {}) {
|
|
3902
4184
|
const env = options.env ?? process.env;
|
|
@@ -4879,21 +5161,98 @@ async function memTried(input, ctx) {
|
|
|
4879
5161
|
await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
|
|
4880
5162
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
4881
5163
|
}
|
|
4882
|
-
var
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope
|
|
5164
|
+
var IngestFindingsInputSchema = {
|
|
5165
|
+
format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
|
|
5166
|
+
report_path: z16.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
|
|
5167
|
+
report: z16.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
|
|
5168
|
+
type: z16.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
|
|
5169
|
+
scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
|
|
4888
5170
|
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
4889
|
-
|
|
5171
|
+
min_severity: z16.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
|
|
5172
|
+
limit: z16.number().int().positive().optional().describe("Cap the number of memories created"),
|
|
4890
5173
|
author: z16.string().optional().describe("Author handle or email"),
|
|
4891
|
-
|
|
5174
|
+
dry_run: z16.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
|
|
5175
|
+
};
|
|
5176
|
+
async function ingestFindings(input, ctx) {
|
|
5177
|
+
if (!existsSync16(ctx.paths.haiveDir)) {
|
|
5178
|
+
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
5179
|
+
}
|
|
5180
|
+
let raw;
|
|
5181
|
+
if (input.report && input.report.trim()) {
|
|
5182
|
+
raw = input.report;
|
|
5183
|
+
} else if (input.report_path) {
|
|
5184
|
+
const file = path62.resolve(ctx.paths.root, input.report_path);
|
|
5185
|
+
if (!existsSync16(file)) throw new Error(`Report file not found: ${file}`);
|
|
5186
|
+
raw = await readFile32(file, "utf8");
|
|
5187
|
+
} else {
|
|
5188
|
+
throw new Error("Provide either `report_path` or `report`.");
|
|
5189
|
+
}
|
|
5190
|
+
const findings = parseFindings(input.format, raw);
|
|
5191
|
+
const drafts = draftsFromFindings(findings, {
|
|
5192
|
+
type: input.type,
|
|
5193
|
+
scope: input.scope,
|
|
5194
|
+
module: input.module,
|
|
5195
|
+
author: input.author,
|
|
5196
|
+
...input.min_severity ? { minSeverity: input.min_severity } : {},
|
|
5197
|
+
...input.limit ? { limit: input.limit } : {}
|
|
5198
|
+
});
|
|
5199
|
+
const existing = existsSync16(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
|
|
5200
|
+
const existingTopics = new Set(
|
|
5201
|
+
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
5202
|
+
);
|
|
5203
|
+
const fresh = filterNewDrafts(drafts, existingTopics);
|
|
5204
|
+
const skipped = drafts.length - fresh.length;
|
|
5205
|
+
const created = [];
|
|
5206
|
+
for (const draft of fresh) {
|
|
5207
|
+
let filePath;
|
|
5208
|
+
if (!input.dry_run) filePath = await writeDraft(ctx, draft);
|
|
5209
|
+
created.push({
|
|
5210
|
+
id: draft.frontmatter.id,
|
|
5211
|
+
topic: draft.topic,
|
|
5212
|
+
path: draft.finding.path,
|
|
5213
|
+
rule: draft.finding.ruleId,
|
|
5214
|
+
severity: draft.finding.severity,
|
|
5215
|
+
has_sensor: draft.has_sensor,
|
|
5216
|
+
...filePath ? { file_path: filePath } : {}
|
|
5217
|
+
});
|
|
5218
|
+
}
|
|
5219
|
+
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'.`;
|
|
5220
|
+
return {
|
|
5221
|
+
format: input.format,
|
|
5222
|
+
parsed: drafts.length,
|
|
5223
|
+
new: fresh.length,
|
|
5224
|
+
skipped_existing: skipped,
|
|
5225
|
+
dry_run: input.dry_run,
|
|
5226
|
+
created,
|
|
5227
|
+
notice
|
|
5228
|
+
};
|
|
5229
|
+
}
|
|
5230
|
+
async function writeDraft(ctx, draft) {
|
|
5231
|
+
const file = memoryFilePath3(
|
|
5232
|
+
ctx.paths,
|
|
5233
|
+
draft.frontmatter.scope,
|
|
5234
|
+
draft.frontmatter.id,
|
|
5235
|
+
draft.frontmatter.module
|
|
5236
|
+
);
|
|
5237
|
+
await mkdir42(path62.dirname(file), { recursive: true });
|
|
5238
|
+
await writeFile82(file, serializeMemory7({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
5239
|
+
return file;
|
|
5240
|
+
}
|
|
5241
|
+
var MemObserveInputSchema = {
|
|
5242
|
+
what: z17.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
|
|
5243
|
+
where: z17.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
|
|
5244
|
+
impact: z17.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
|
|
5245
|
+
fix: z17.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
|
|
5246
|
+
scope: z17.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
|
|
5247
|
+
module: z17.string().optional().describe("Module name (required when scope=module)"),
|
|
5248
|
+
tags: z17.array(z17.string()).default([]).describe("Tags for filtering"),
|
|
5249
|
+
author: z17.string().optional().describe("Author handle or email"),
|
|
5250
|
+
force: z17.boolean().default(false).describe(
|
|
4892
5251
|
"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
5252
|
)
|
|
4894
5253
|
};
|
|
4895
5254
|
async function memObserve(input, ctx) {
|
|
4896
|
-
if (!
|
|
5255
|
+
if (!existsSync17(ctx.paths.haiveDir)) {
|
|
4897
5256
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
4898
5257
|
}
|
|
4899
5258
|
const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
|
|
@@ -4925,16 +5284,16 @@ async function memObserve(input, ctx) {
|
|
|
4925
5284
|
lines.push("", `**Fix/workaround:** ${input.fix}`);
|
|
4926
5285
|
}
|
|
4927
5286
|
const body = lines.join("\n") + "\n";
|
|
4928
|
-
const file =
|
|
4929
|
-
await
|
|
4930
|
-
if (
|
|
5287
|
+
const file = memoryFilePath4(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
5288
|
+
await mkdir52(path72.dirname(file), { recursive: true });
|
|
5289
|
+
if (existsSync17(file)) {
|
|
4931
5290
|
throw new Error(`Memory already exists at ${file}`);
|
|
4932
5291
|
}
|
|
4933
|
-
await
|
|
5292
|
+
await writeFile92(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
4934
5293
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
4935
5294
|
}
|
|
4936
5295
|
function pendingDistillPath(ctx) {
|
|
4937
|
-
return
|
|
5296
|
+
return path82.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
4938
5297
|
}
|
|
4939
5298
|
var SessionTracker = class {
|
|
4940
5299
|
events = [];
|
|
@@ -5013,7 +5372,7 @@ var SessionTracker = class {
|
|
|
5013
5372
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
5014
5373
|
);
|
|
5015
5374
|
const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
|
|
5016
|
-
if (!ranPostTask && isSubstantialSession &&
|
|
5375
|
+
if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
|
|
5017
5376
|
try {
|
|
5018
5377
|
const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
|
|
5019
5378
|
const payload = {
|
|
@@ -5026,9 +5385,9 @@ var SessionTracker = class {
|
|
|
5026
5385
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
5027
5386
|
...recapId ? { recap_id: recapId } : {}
|
|
5028
5387
|
};
|
|
5029
|
-
const cacheDir =
|
|
5030
|
-
await
|
|
5031
|
-
await
|
|
5388
|
+
const cacheDir = path82.join(this.ctx.paths.haiveDir, ".cache");
|
|
5389
|
+
await mkdir62(cacheDir, { recursive: true });
|
|
5390
|
+
await writeFile10(
|
|
5032
5391
|
pendingDistillPath(this.ctx),
|
|
5033
5392
|
JSON.stringify(payload, null, 2) + "\n",
|
|
5034
5393
|
"utf8"
|
|
@@ -5047,7 +5406,7 @@ var SessionTracker = class {
|
|
|
5047
5406
|
};
|
|
5048
5407
|
async function clearPendingDistill(ctx) {
|
|
5049
5408
|
const p = pendingDistillPath(ctx);
|
|
5050
|
-
if (
|
|
5409
|
+
if (existsSync18(p)) {
|
|
5051
5410
|
try {
|
|
5052
5411
|
await rm(p);
|
|
5053
5412
|
} catch {
|
|
@@ -5062,15 +5421,15 @@ function summarizeTools(events) {
|
|
|
5062
5421
|
return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
|
|
5063
5422
|
}
|
|
5064
5423
|
var MemSessionEndInputSchema = {
|
|
5065
|
-
goal:
|
|
5066
|
-
accomplished:
|
|
5067
|
-
discoveries:
|
|
5424
|
+
goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
|
|
5425
|
+
accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
|
|
5426
|
+
discoveries: z18.string().default("").describe(
|
|
5068
5427
|
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
5069
5428
|
),
|
|
5070
|
-
files_touched:
|
|
5071
|
-
next_steps:
|
|
5072
|
-
scope:
|
|
5073
|
-
module:
|
|
5429
|
+
files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
|
|
5430
|
+
next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
|
|
5431
|
+
scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
|
|
5432
|
+
module: z18.string().optional().describe("Module name (required when scope=module)")
|
|
5074
5433
|
};
|
|
5075
5434
|
function recapTopic(scope, module) {
|
|
5076
5435
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
@@ -5100,23 +5459,23 @@ ${input.next_steps}`);
|
|
|
5100
5459
|
return lines.join("\n");
|
|
5101
5460
|
}
|
|
5102
5461
|
async function memSessionEnd(input, ctx) {
|
|
5103
|
-
if (!
|
|
5462
|
+
if (!existsSync19(ctx.paths.haiveDir)) {
|
|
5104
5463
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
5105
5464
|
}
|
|
5106
5465
|
const body = buildBody(input);
|
|
5107
5466
|
const topic = recapTopic(input.scope, input.module);
|
|
5108
5467
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
5109
|
-
if (!p || !
|
|
5110
|
-
const rel =
|
|
5468
|
+
if (!p || !path92.isAbsolute(p)) return p;
|
|
5469
|
+
const rel = path92.relative(ctx.paths.root, p);
|
|
5111
5470
|
return rel.startsWith("..") ? p : rel;
|
|
5112
5471
|
});
|
|
5113
5472
|
const invalidPaths = normalizedFiles.filter(
|
|
5114
|
-
(p) => !
|
|
5473
|
+
(p) => !existsSync19(path92.resolve(ctx.paths.root, p))
|
|
5115
5474
|
);
|
|
5116
5475
|
if (invalidPaths.length > 0) {
|
|
5117
5476
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
5118
5477
|
}
|
|
5119
|
-
const existing =
|
|
5478
|
+
const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
|
|
5120
5479
|
const topicMatch = existing.find(
|
|
5121
5480
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === input.scope && (!input.module || memory2.frontmatter.module === input.module)
|
|
5122
5481
|
);
|
|
@@ -5132,9 +5491,9 @@ async function memSessionEnd(input, ctx) {
|
|
|
5132
5491
|
paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
|
|
5133
5492
|
}
|
|
5134
5493
|
};
|
|
5135
|
-
await
|
|
5494
|
+
await writeFile11(
|
|
5136
5495
|
topicMatch.filePath,
|
|
5137
|
-
|
|
5496
|
+
serializeMemory9({ frontmatter: newFrontmatter, body }),
|
|
5138
5497
|
"utf8"
|
|
5139
5498
|
);
|
|
5140
5499
|
await clearPendingDistill(ctx);
|
|
@@ -5156,14 +5515,14 @@ async function memSessionEnd(input, ctx) {
|
|
|
5156
5515
|
topic,
|
|
5157
5516
|
status: "validated"
|
|
5158
5517
|
});
|
|
5159
|
-
const file =
|
|
5518
|
+
const file = memoryFilePath5(
|
|
5160
5519
|
ctx.paths,
|
|
5161
5520
|
frontmatter.scope,
|
|
5162
5521
|
frontmatter.id,
|
|
5163
5522
|
frontmatter.module
|
|
5164
5523
|
);
|
|
5165
|
-
await
|
|
5166
|
-
await
|
|
5524
|
+
await mkdir72(path92.dirname(file), { recursive: true });
|
|
5525
|
+
await writeFile11(file, serializeMemory9({ frontmatter, body }), "utf8");
|
|
5167
5526
|
await clearPendingDistill(ctx);
|
|
5168
5527
|
return {
|
|
5169
5528
|
id: frontmatter.id,
|
|
@@ -5297,50 +5656,50 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
5297
5656
|
}
|
|
5298
5657
|
async function loadModuleContexts2(ctx, modules) {
|
|
5299
5658
|
if (modules.length === 0) return [];
|
|
5300
|
-
if (!
|
|
5659
|
+
if (!existsSync20(ctx.paths.modulesContextDir)) return [];
|
|
5301
5660
|
const available = new Set(
|
|
5302
5661
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
5303
5662
|
);
|
|
5304
5663
|
const out = [];
|
|
5305
5664
|
for (const m of modules) {
|
|
5306
5665
|
if (!available.has(m)) continue;
|
|
5307
|
-
const file =
|
|
5308
|
-
if (
|
|
5309
|
-
out.push({ name: m, content: await
|
|
5666
|
+
const file = path102.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
5667
|
+
if (existsSync20(file)) {
|
|
5668
|
+
out.push({ name: m, content: await readFile42(file, "utf8") });
|
|
5310
5669
|
}
|
|
5311
5670
|
}
|
|
5312
5671
|
return out;
|
|
5313
5672
|
}
|
|
5314
5673
|
var GetBriefingInputSchema = {
|
|
5315
|
-
task:
|
|
5674
|
+
task: z19.string().optional().describe(
|
|
5316
5675
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
5317
5676
|
),
|
|
5318
|
-
files:
|
|
5319
|
-
max_tokens:
|
|
5677
|
+
files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
|
|
5678
|
+
max_tokens: z19.number().int().positive().default(8e3).describe(
|
|
5320
5679
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
5321
5680
|
),
|
|
5322
|
-
max_memories:
|
|
5323
|
-
include_project_context:
|
|
5324
|
-
include_module_contexts:
|
|
5325
|
-
semantic:
|
|
5681
|
+
max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
5682
|
+
include_project_context: z19.boolean().default(true),
|
|
5683
|
+
include_module_contexts: z19.boolean().default(true),
|
|
5684
|
+
semantic: z19.boolean().default(true).describe(
|
|
5326
5685
|
"Use semantic ranking when a task is provided (requires `haive embeddings index`)."
|
|
5327
5686
|
),
|
|
5328
|
-
include_stale:
|
|
5329
|
-
track:
|
|
5330
|
-
format:
|
|
5687
|
+
include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
5688
|
+
track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
5689
|
+
format: z19.enum(["full", "compact", "actions"]).default("full").describe(
|
|
5331
5690
|
"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
5691
|
),
|
|
5333
|
-
symbols:
|
|
5692
|
+
symbols: z19.array(z19.string()).default([]).describe(
|
|
5334
5693
|
"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
5694
|
),
|
|
5336
|
-
min_semantic_score:
|
|
5695
|
+
min_semantic_score: z19.number().min(0).max(1).default(0).describe(
|
|
5337
5696
|
"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
5697
|
),
|
|
5339
|
-
budget_preset:
|
|
5698
|
+
budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
|
|
5340
5699
|
"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
5700
|
)
|
|
5342
5701
|
};
|
|
5343
|
-
var GetBriefingZod =
|
|
5702
|
+
var GetBriefingZod = z19.object(GetBriefingInputSchema);
|
|
5344
5703
|
async function getBriefing(input, ctx) {
|
|
5345
5704
|
const resolvedBudget = resolveBriefingBudget2(input.budget_preset, {
|
|
5346
5705
|
max_tokens: input.max_tokens,
|
|
@@ -5356,8 +5715,8 @@ async function getBriefing(input, ctx) {
|
|
|
5356
5715
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
5357
5716
|
let byId = /* @__PURE__ */ new Map();
|
|
5358
5717
|
let lastSession;
|
|
5359
|
-
if (
|
|
5360
|
-
const allLoaded = await
|
|
5718
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
5719
|
+
const allLoaded = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
5361
5720
|
const recaps = allLoaded.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").sort(
|
|
5362
5721
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
5363
5722
|
);
|
|
@@ -5520,7 +5879,7 @@ async function getBriefing(input, ctx) {
|
|
|
5520
5879
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
5521
5880
|
const newFm = { ...loaded.memory.frontmatter, status: "validated" };
|
|
5522
5881
|
try {
|
|
5523
|
-
await
|
|
5882
|
+
await writeFile12(loaded.filePath, serializeMemory10({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
|
|
5524
5883
|
m.status = "validated";
|
|
5525
5884
|
m.confidence = "trusted";
|
|
5526
5885
|
} catch {
|
|
@@ -5528,12 +5887,12 @@ async function getBriefing(input, ctx) {
|
|
|
5528
5887
|
}
|
|
5529
5888
|
}
|
|
5530
5889
|
}
|
|
5531
|
-
const projectContextRaw = input.include_project_context &&
|
|
5890
|
+
const projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile52(ctx.paths.projectContext, "utf8") : "";
|
|
5532
5891
|
const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
|
|
5533
5892
|
const setupWarnings = [];
|
|
5534
5893
|
let autoContextGenerated = false;
|
|
5535
5894
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
5536
|
-
if ((isTemplateContext || !
|
|
5895
|
+
if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
|
|
5537
5896
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
5538
5897
|
if (haiveConfig.autoContext) {
|
|
5539
5898
|
const codeMap = await loadCodeMap5(ctx.paths);
|
|
@@ -5694,8 +6053,8 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5694
6053
|
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
5695
6054
|
}
|
|
5696
6055
|
}
|
|
5697
|
-
if (
|
|
5698
|
-
const allMems = await
|
|
6056
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
6057
|
+
const allMems = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
5699
6058
|
for (const { memory: memory2 } of allMems) {
|
|
5700
6059
|
const fm = memory2.frontmatter;
|
|
5701
6060
|
if (!fm.requires_human_approval) continue;
|
|
@@ -5705,9 +6064,9 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5705
6064
|
}
|
|
5706
6065
|
}
|
|
5707
6066
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
5708
|
-
if (
|
|
6067
|
+
if (existsSync21(pendingDistillFile)) {
|
|
5709
6068
|
try {
|
|
5710
|
-
const raw = await
|
|
6069
|
+
const raw = await readFile52(pendingDistillFile, "utf8");
|
|
5711
6070
|
const pd = JSON.parse(raw);
|
|
5712
6071
|
const ageMs = Date.now() - new Date(pd.session_end).getTime();
|
|
5713
6072
|
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -5734,7 +6093,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5734
6093
|
}
|
|
5735
6094
|
}
|
|
5736
6095
|
const memoriesEmpty = outputMemories.length === 0;
|
|
5737
|
-
const hasMemoriesDir =
|
|
6096
|
+
const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
|
|
5738
6097
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
5739
6098
|
const hasUnguessableSignal = outputMemories.some(
|
|
5740
6099
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore2(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -5781,7 +6140,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5781
6140
|
"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
6141
|
);
|
|
5783
6142
|
}
|
|
5784
|
-
if (
|
|
6143
|
+
if (existsSync21(ctx.paths.haiveDir)) {
|
|
5785
6144
|
await writeBriefingMarker2(ctx.paths, {
|
|
5786
6145
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
5787
6146
|
...input.task ? { task: input.task } : {},
|
|
@@ -5830,17 +6189,17 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
5830
6189
|
};
|
|
5831
6190
|
}
|
|
5832
6191
|
var CodeMapInputSchema = {
|
|
5833
|
-
file:
|
|
5834
|
-
symbol:
|
|
5835
|
-
paths:
|
|
6192
|
+
file: z20.string().optional().describe("Filter to files whose path contains this substring"),
|
|
6193
|
+
symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
6194
|
+
paths: z20.array(z20.string()).default([]).describe(
|
|
5836
6195
|
"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
6196
|
),
|
|
5838
|
-
max_files:
|
|
5839
|
-
max_tokens:
|
|
6197
|
+
max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
|
|
6198
|
+
max_tokens: z20.number().int().positive().optional().describe(
|
|
5840
6199
|
"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
6200
|
)
|
|
5842
6201
|
};
|
|
5843
|
-
var CodeMapInputZod =
|
|
6202
|
+
var CodeMapInputZod = z20.object(CodeMapInputSchema);
|
|
5844
6203
|
async function codeMapTool(input, ctx) {
|
|
5845
6204
|
const map = await loadCodeMap22(ctx.paths);
|
|
5846
6205
|
if (!map) {
|
|
@@ -5908,14 +6267,14 @@ function estimateFileEntryTokens(f) {
|
|
|
5908
6267
|
return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
|
|
5909
6268
|
}
|
|
5910
6269
|
var MemDiffInputSchema = {
|
|
5911
|
-
id_a:
|
|
5912
|
-
id_b:
|
|
6270
|
+
id_a: z21.string().min(1).describe("First memory id"),
|
|
6271
|
+
id_b: z21.string().min(1).describe("Second memory id")
|
|
5913
6272
|
};
|
|
5914
6273
|
async function memDiff(input, ctx) {
|
|
5915
|
-
if (!
|
|
6274
|
+
if (!existsSync222(ctx.paths.memoriesDir)) {
|
|
5916
6275
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
5917
6276
|
}
|
|
5918
|
-
const all = await
|
|
6277
|
+
const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
|
|
5919
6278
|
const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
|
|
5920
6279
|
const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
|
|
5921
6280
|
if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
|
|
@@ -5948,15 +6307,15 @@ async function memDiff(input, ctx) {
|
|
|
5948
6307
|
};
|
|
5949
6308
|
}
|
|
5950
6309
|
var GetRecapInputSchema = {
|
|
5951
|
-
scope:
|
|
6310
|
+
scope: z22.enum(["personal", "team", "any"]).default("any").describe(
|
|
5952
6311
|
"Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
|
|
5953
6312
|
)
|
|
5954
6313
|
};
|
|
5955
6314
|
async function getRecap(input, ctx) {
|
|
5956
|
-
if (!
|
|
6315
|
+
if (!existsSync23(ctx.paths.memoriesDir)) {
|
|
5957
6316
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
5958
6317
|
}
|
|
5959
|
-
const all = await
|
|
6318
|
+
const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
5960
6319
|
const recaps = all.filter(({ memory: memory2 }) => memory2.frontmatter.type === "session_recap").filter(({ memory: memory2 }) => input.scope === "any" || memory2.frontmatter.scope === input.scope).sort(
|
|
5961
6320
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
5962
6321
|
);
|
|
@@ -5979,11 +6338,11 @@ async function getRecap(input, ctx) {
|
|
|
5979
6338
|
};
|
|
5980
6339
|
}
|
|
5981
6340
|
var MemRelevantToInputSchema = {
|
|
5982
|
-
task:
|
|
5983
|
-
files:
|
|
5984
|
-
limit:
|
|
5985
|
-
min_semantic_score:
|
|
5986
|
-
format:
|
|
6341
|
+
task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
|
|
6342
|
+
files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
|
|
6343
|
+
limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
|
|
6344
|
+
min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
|
|
6345
|
+
format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
|
|
5987
6346
|
};
|
|
5988
6347
|
async function memRelevantTo(input, ctx) {
|
|
5989
6348
|
const briefingInput = {
|
|
@@ -6012,11 +6371,11 @@ async function memRelevantTo(input, ctx) {
|
|
|
6012
6371
|
return out;
|
|
6013
6372
|
}
|
|
6014
6373
|
var CodeSearchInputSchema = {
|
|
6015
|
-
query:
|
|
6374
|
+
query: z24.string().min(1).describe(
|
|
6016
6375
|
"Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
|
|
6017
6376
|
),
|
|
6018
|
-
k:
|
|
6019
|
-
min_score:
|
|
6377
|
+
k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
|
|
6378
|
+
min_score: z24.number().min(0).max(1).default(0.2).describe(
|
|
6020
6379
|
"Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
|
|
6021
6380
|
)
|
|
6022
6381
|
};
|
|
@@ -6045,14 +6404,14 @@ async function codeSearch(input, ctx) {
|
|
|
6045
6404
|
return { available: true, hits: result.hits };
|
|
6046
6405
|
}
|
|
6047
6406
|
var WhyThisFileInputSchema = {
|
|
6048
|
-
path:
|
|
6407
|
+
path: z25.string().min(1).describe(
|
|
6049
6408
|
"Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
|
|
6050
6409
|
),
|
|
6051
|
-
git_log_limit:
|
|
6052
|
-
memory_limit:
|
|
6410
|
+
git_log_limit: z25.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
|
|
6411
|
+
memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
|
|
6053
6412
|
};
|
|
6054
6413
|
async function whyThisFile(input, ctx) {
|
|
6055
|
-
const fileExists =
|
|
6414
|
+
const fileExists = existsSync24(path112.join(ctx.paths.root, input.path));
|
|
6056
6415
|
const [commits, memories, codeMap] = await Promise.all([
|
|
6057
6416
|
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
6058
6417
|
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
@@ -6093,8 +6452,8 @@ async function whyThisFile(input, ctx) {
|
|
|
6093
6452
|
};
|
|
6094
6453
|
}
|
|
6095
6454
|
async function collectAnchoredMemories(ctx, filePath, limit) {
|
|
6096
|
-
if (!
|
|
6097
|
-
const all = await
|
|
6455
|
+
if (!existsSync24(ctx.paths.memoriesDir)) return [];
|
|
6456
|
+
const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
|
|
6098
6457
|
const usage = await loadUsageIndex9(ctx.paths);
|
|
6099
6458
|
const out = [];
|
|
6100
6459
|
for (const { memory: memory2 } of all) {
|
|
@@ -6147,17 +6506,17 @@ function runCommand(cmd, args, cwd) {
|
|
|
6147
6506
|
});
|
|
6148
6507
|
}
|
|
6149
6508
|
var AntiPatternsCheckInputSchema = {
|
|
6150
|
-
diff:
|
|
6509
|
+
diff: z26.string().optional().describe(
|
|
6151
6510
|
"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
6511
|
),
|
|
6153
|
-
paths:
|
|
6512
|
+
paths: z26.array(z26.string()).default([]).describe(
|
|
6154
6513
|
"File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
|
|
6155
6514
|
),
|
|
6156
|
-
limit:
|
|
6157
|
-
semantic:
|
|
6515
|
+
limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
|
|
6516
|
+
semantic: z26.boolean().default(true).describe(
|
|
6158
6517
|
"When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
|
|
6159
6518
|
),
|
|
6160
|
-
min_semantic_score:
|
|
6519
|
+
min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
|
|
6161
6520
|
"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
6521
|
)
|
|
6163
6522
|
};
|
|
@@ -6178,10 +6537,10 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6178
6537
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
6179
6538
|
};
|
|
6180
6539
|
}
|
|
6181
|
-
if (!
|
|
6540
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
6182
6541
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
6183
6542
|
}
|
|
6184
|
-
const all = await
|
|
6543
|
+
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
6185
6544
|
const minSemanticScore = input.min_semantic_score ?? 0.45;
|
|
6186
6545
|
const negative = all.filter(({ memory: memory2 }) => {
|
|
6187
6546
|
const t = memory2.frontmatter.type;
|
|
@@ -6288,12 +6647,12 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6288
6647
|
};
|
|
6289
6648
|
}
|
|
6290
6649
|
var MemDistillInputSchema = {
|
|
6291
|
-
since_days:
|
|
6292
|
-
min_cluster:
|
|
6293
|
-
type_filter:
|
|
6650
|
+
since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
|
|
6651
|
+
min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
|
|
6652
|
+
type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
|
|
6294
6653
|
"Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
|
|
6295
6654
|
),
|
|
6296
|
-
scope:
|
|
6655
|
+
scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
|
|
6297
6656
|
};
|
|
6298
6657
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
6299
6658
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -6333,11 +6692,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
6333
6692
|
"error"
|
|
6334
6693
|
]);
|
|
6335
6694
|
async function memDistill(input, ctx) {
|
|
6336
|
-
if (!
|
|
6695
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
6337
6696
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
6338
6697
|
}
|
|
6339
6698
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
6340
|
-
const all = await
|
|
6699
|
+
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
6341
6700
|
const candidates = all.filter(({ memory: memory2 }) => {
|
|
6342
6701
|
const fm = memory2.frontmatter;
|
|
6343
6702
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -6440,11 +6799,11 @@ function firstHeading(body) {
|
|
|
6440
6799
|
return void 0;
|
|
6441
6800
|
}
|
|
6442
6801
|
var WhyThisDecisionInputSchema = {
|
|
6443
|
-
id:
|
|
6444
|
-
git_log_limit:
|
|
6802
|
+
id: z28.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
|
|
6803
|
+
git_log_limit: z28.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
|
|
6445
6804
|
};
|
|
6446
6805
|
async function whyThisDecision(input, ctx) {
|
|
6447
|
-
if (!
|
|
6806
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
6448
6807
|
return {
|
|
6449
6808
|
found: false,
|
|
6450
6809
|
related: [],
|
|
@@ -6453,7 +6812,7 @@ async function whyThisDecision(input, ctx) {
|
|
|
6453
6812
|
notice: "No .ai/memories directory."
|
|
6454
6813
|
};
|
|
6455
6814
|
}
|
|
6456
|
-
const all = await
|
|
6815
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
6457
6816
|
const usage = await loadUsageIndex11(ctx.paths);
|
|
6458
6817
|
const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
|
|
6459
6818
|
if (!target) {
|
|
@@ -6575,17 +6934,17 @@ function runCommand2(cmd, args, cwd) {
|
|
|
6575
6934
|
});
|
|
6576
6935
|
}
|
|
6577
6936
|
var MemConflictsInputSchema = {
|
|
6578
|
-
id:
|
|
6579
|
-
min_score:
|
|
6580
|
-
semantic:
|
|
6937
|
+
id: z29.string().min(1).describe("Memory id to check for conflicts."),
|
|
6938
|
+
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)."),
|
|
6939
|
+
semantic: z29.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
|
|
6581
6940
|
};
|
|
6582
6941
|
var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
|
|
6583
6942
|
var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
|
|
6584
6943
|
async function memConflicts(input, ctx) {
|
|
6585
|
-
if (!
|
|
6944
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
6586
6945
|
return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
|
|
6587
6946
|
}
|
|
6588
|
-
const all = await
|
|
6947
|
+
const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
|
|
6589
6948
|
const target = all.find(({ memory: memory2 }) => memory2.frontmatter.id === input.id);
|
|
6590
6949
|
if (!target) {
|
|
6591
6950
|
return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
|
|
@@ -6696,15 +7055,15 @@ async function trySemanticSimilarities(ctx, target, others) {
|
|
|
6696
7055
|
return map;
|
|
6697
7056
|
}
|
|
6698
7057
|
var PreCommitCheckInputSchema = {
|
|
6699
|
-
diff:
|
|
7058
|
+
diff: z30.string().optional().describe(
|
|
6700
7059
|
"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
7060
|
),
|
|
6702
|
-
paths:
|
|
6703
|
-
block_on:
|
|
7061
|
+
paths: z30.array(z30.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
|
|
7062
|
+
block_on: z30.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
|
|
6704
7063
|
"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
7064
|
),
|
|
6706
|
-
semantic:
|
|
6707
|
-
anchored_blocks:
|
|
7065
|
+
semantic: z30.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
|
|
7066
|
+
anchored_blocks: z30.boolean().default(false).describe(
|
|
6708
7067
|
"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
7068
|
)
|
|
6710
7069
|
};
|
|
@@ -7010,12 +7369,12 @@ var CONFIG_PATTERNS = [
|
|
|
7010
7369
|
var MAX_DIFF_BYTES = 4096;
|
|
7011
7370
|
var HOT_FILE_MIN = 3;
|
|
7012
7371
|
var PatternDetectInputSchema = {
|
|
7013
|
-
since_days:
|
|
7014
|
-
dry_run:
|
|
7015
|
-
scope:
|
|
7372
|
+
since_days: z31.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
|
|
7373
|
+
dry_run: z31.boolean().default(false).describe("When true, report matches without writing any memory files."),
|
|
7374
|
+
scope: z31.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
|
|
7016
7375
|
};
|
|
7017
7376
|
async function patternDetect(input, ctx) {
|
|
7018
|
-
if (!
|
|
7377
|
+
if (!existsSync29(ctx.paths.haiveDir)) {
|
|
7019
7378
|
return {
|
|
7020
7379
|
scanned_events: 0,
|
|
7021
7380
|
matches: [],
|
|
@@ -7028,13 +7387,13 @@ async function patternDetect(input, ctx) {
|
|
|
7028
7387
|
try {
|
|
7029
7388
|
const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
|
|
7030
7389
|
const configFiles = changedFiles.filter(
|
|
7031
|
-
(f) => CONFIG_PATTERNS.some((p) =>
|
|
7390
|
+
(f) => CONFIG_PATTERNS.some((p) => path122.basename(f.toLowerCase()).includes(p))
|
|
7032
7391
|
);
|
|
7033
7392
|
for (const file of configFiles.slice(0, 5)) {
|
|
7034
7393
|
const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
|
|
7035
7394
|
if (!diff) continue;
|
|
7036
|
-
const parentDir =
|
|
7037
|
-
const baseName =
|
|
7395
|
+
const parentDir = path122.basename(path122.dirname(file));
|
|
7396
|
+
const baseName = path122.basename(file).replace(/\.[^.]+$/, "");
|
|
7038
7397
|
const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
|
|
7039
7398
|
matches.push({
|
|
7040
7399
|
kind: "config_change",
|
|
@@ -7098,7 +7457,7 @@ async function patternDetect(input, ctx) {
|
|
|
7098
7457
|
for (const [p, { count, tools }] of pathCounts) {
|
|
7099
7458
|
if (count < HOT_FILE_MIN) continue;
|
|
7100
7459
|
if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
|
|
7101
|
-
if (CONFIG_PATTERNS.some((cp) =>
|
|
7460
|
+
if (CONFIG_PATTERNS.some((cp) => path122.basename(p).includes(cp))) continue;
|
|
7102
7461
|
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
7103
7462
|
matches.push({
|
|
7104
7463
|
kind: "hot_file",
|
|
@@ -7138,17 +7497,17 @@ async function patternDetect(input, ctx) {
|
|
|
7138
7497
|
paths: match.anchor_paths,
|
|
7139
7498
|
status: "proposed"
|
|
7140
7499
|
});
|
|
7141
|
-
const file =
|
|
7500
|
+
const file = memoryFilePath6(
|
|
7142
7501
|
ctx.paths,
|
|
7143
7502
|
fm.scope === "shared" ? "team" : fm.scope,
|
|
7144
7503
|
fm.id,
|
|
7145
7504
|
void 0
|
|
7146
7505
|
);
|
|
7147
|
-
if (
|
|
7148
|
-
await
|
|
7149
|
-
await
|
|
7506
|
+
if (existsSync29(file)) continue;
|
|
7507
|
+
await mkdir82(path122.dirname(file), { recursive: true });
|
|
7508
|
+
await writeFile13(
|
|
7150
7509
|
file,
|
|
7151
|
-
|
|
7510
|
+
serializeMemory11({ frontmatter: fm, body: match.proposed_body }),
|
|
7152
7511
|
"utf8"
|
|
7153
7512
|
);
|
|
7154
7513
|
savedIds.push(fm.id);
|
|
@@ -7189,17 +7548,17 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
7189
7548
|
}
|
|
7190
7549
|
}
|
|
7191
7550
|
var MemConflictCandidatesInputSchema = {
|
|
7192
|
-
since_days:
|
|
7193
|
-
types:
|
|
7194
|
-
min_jaccard:
|
|
7195
|
-
max_pairs:
|
|
7196
|
-
max_scan:
|
|
7197
|
-
max_topic_pairs:
|
|
7551
|
+
since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
7552
|
+
types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
7553
|
+
min_jaccard: z32.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
7554
|
+
max_pairs: z32.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
7555
|
+
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."),
|
|
7556
|
+
max_topic_pairs: z32.number().int().positive().max(100).default(20).describe(
|
|
7198
7557
|
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
7199
7558
|
)
|
|
7200
7559
|
};
|
|
7201
7560
|
async function memConflictCandidates(input, ctx) {
|
|
7202
|
-
if (!
|
|
7561
|
+
if (!existsSync30(ctx.paths.memoriesDir)) {
|
|
7203
7562
|
return {
|
|
7204
7563
|
pairs: [],
|
|
7205
7564
|
topic_status_pairs: [],
|
|
@@ -7208,7 +7567,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
7208
7567
|
notice: "No .ai/memories directory."
|
|
7209
7568
|
};
|
|
7210
7569
|
}
|
|
7211
|
-
const all = await
|
|
7570
|
+
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
7212
7571
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
7213
7572
|
sinceDays: input.since_days,
|
|
7214
7573
|
types: input.types,
|
|
@@ -7221,7 +7580,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
7221
7580
|
return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
|
|
7222
7581
|
}
|
|
7223
7582
|
var MemResolveProjectInputSchema = {
|
|
7224
|
-
cwd:
|
|
7583
|
+
cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
7225
7584
|
};
|
|
7226
7585
|
async function memResolveProject(input, _ctx) {
|
|
7227
7586
|
void _ctx;
|
|
@@ -7234,7 +7593,7 @@ async function memResolveProject(input, _ctx) {
|
|
|
7234
7593
|
}
|
|
7235
7594
|
var MemSuggestTopicInputSchema = {
|
|
7236
7595
|
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
7237
|
-
title:
|
|
7596
|
+
title: z34.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
7238
7597
|
};
|
|
7239
7598
|
async function memSuggestTopic(input, _ctx) {
|
|
7240
7599
|
void _ctx;
|
|
@@ -7242,15 +7601,15 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
7242
7601
|
return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
|
|
7243
7602
|
}
|
|
7244
7603
|
var MemTimelineInputSchema = {
|
|
7245
|
-
memory_id:
|
|
7246
|
-
topic:
|
|
7247
|
-
limit:
|
|
7604
|
+
memory_id: z35.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
7605
|
+
topic: z35.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
7606
|
+
limit: z35.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
7248
7607
|
};
|
|
7249
7608
|
async function memTimeline(input, ctx) {
|
|
7250
|
-
if (!
|
|
7609
|
+
if (!existsSync31(ctx.paths.memoriesDir)) {
|
|
7251
7610
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
7252
7611
|
}
|
|
7253
|
-
const all = await
|
|
7612
|
+
const all = await loadMemoriesFromDir24(ctx.paths.memoriesDir);
|
|
7254
7613
|
const { entries, notice } = collectTimelineEntries(all, {
|
|
7255
7614
|
memoryId: input.memory_id,
|
|
7256
7615
|
topic: input.topic,
|
|
@@ -7259,9 +7618,9 @@ async function memTimeline(input, ctx) {
|
|
|
7259
7618
|
return { entries, total: entries.length, notice };
|
|
7260
7619
|
}
|
|
7261
7620
|
var RuntimeJournalAppendInputSchema = {
|
|
7262
|
-
message:
|
|
7263
|
-
kind:
|
|
7264
|
-
tool:
|
|
7621
|
+
message: z36.string().min(1).describe("Short line to append to the runtime session journal"),
|
|
7622
|
+
kind: z36.enum(["note", "session_end", "mcp"]).default("note"),
|
|
7623
|
+
tool: z36.string().optional().describe("When kind=mcp, which tool name (optional)")
|
|
7265
7624
|
};
|
|
7266
7625
|
async function runtimeJournalAppend(input, ctx) {
|
|
7267
7626
|
await appendRuntimeJournalEntry2(ctx.paths, {
|
|
@@ -7275,7 +7634,7 @@ async function runtimeJournalAppend(input, ctx) {
|
|
|
7275
7634
|
};
|
|
7276
7635
|
}
|
|
7277
7636
|
var RuntimeJournalTailInputSchema = {
|
|
7278
|
-
limit:
|
|
7637
|
+
limit: z37.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
|
|
7279
7638
|
};
|
|
7280
7639
|
async function runtimeJournalTail(input, ctx) {
|
|
7281
7640
|
const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
|
|
@@ -7285,10 +7644,10 @@ async function runtimeJournalTail(input, ctx) {
|
|
|
7285
7644
|
return { entries };
|
|
7286
7645
|
}
|
|
7287
7646
|
var BootstrapProjectArgsSchema = {
|
|
7288
|
-
module:
|
|
7647
|
+
module: z38.string().optional().describe(
|
|
7289
7648
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
7290
7649
|
),
|
|
7291
|
-
focus:
|
|
7650
|
+
focus: z38.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
7292
7651
|
};
|
|
7293
7652
|
var ROOT_TEMPLATE = `# Project context
|
|
7294
7653
|
|
|
@@ -7369,8 +7728,8 @@ ${template}\`\`\`
|
|
|
7369
7728
|
};
|
|
7370
7729
|
}
|
|
7371
7730
|
var PostTaskArgsSchema = {
|
|
7372
|
-
task_summary:
|
|
7373
|
-
files_touched:
|
|
7731
|
+
task_summary: z39.string().optional().describe("One sentence describing what you just did"),
|
|
7732
|
+
files_touched: z39.array(z39.string()).optional().describe("Files you created or modified during the task")
|
|
7374
7733
|
};
|
|
7375
7734
|
function postTaskPrompt(args, ctx) {
|
|
7376
7735
|
const taskLine = args.task_summary ? `
|
|
@@ -7466,10 +7825,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
7466
7825
|
};
|
|
7467
7826
|
}
|
|
7468
7827
|
var ImportDocsArgsSchema = {
|
|
7469
|
-
content:
|
|
7470
|
-
source:
|
|
7471
|
-
scope:
|
|
7472
|
-
dry_run:
|
|
7828
|
+
content: z40.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
7829
|
+
source: z40.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
7830
|
+
scope: z40.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
7831
|
+
dry_run: z40.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
7473
7832
|
};
|
|
7474
7833
|
function importDocsPrompt(args, ctx) {
|
|
7475
7834
|
const sourceLine = args.source ? `
|
|
@@ -7532,7 +7891,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7532
7891
|
};
|
|
7533
7892
|
}
|
|
7534
7893
|
var SERVER_NAME = "haive";
|
|
7535
|
-
var SERVER_VERSION = "0.
|
|
7894
|
+
var SERVER_VERSION = "0.13.0";
|
|
7536
7895
|
function jsonResult(data) {
|
|
7537
7896
|
return {
|
|
7538
7897
|
content: [
|
|
@@ -7575,7 +7934,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
|
|
|
7575
7934
|
"mem_distill",
|
|
7576
7935
|
"mem_timeline",
|
|
7577
7936
|
"mem_conflict_candidates",
|
|
7578
|
-
"mem_feedback"
|
|
7937
|
+
"mem_feedback",
|
|
7938
|
+
"ingest_findings"
|
|
7579
7939
|
];
|
|
7580
7940
|
var EXPERIMENTAL_PROFILE_TOOLS = [
|
|
7581
7941
|
...MAINTENANCE_PROFILE_TOOLS,
|
|
@@ -7609,7 +7969,8 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7609
7969
|
"mem_delete",
|
|
7610
7970
|
"mem_feedback",
|
|
7611
7971
|
"runtime_journal_append",
|
|
7612
|
-
"pattern_detect"
|
|
7972
|
+
"pattern_detect",
|
|
7973
|
+
"ingest_findings"
|
|
7613
7974
|
]);
|
|
7614
7975
|
function createHaiveServer(options = {}) {
|
|
7615
7976
|
const context = createContext(options);
|
|
@@ -7734,6 +8095,36 @@ function createHaiveServer(options = {}) {
|
|
|
7734
8095
|
return jsonResult(await memTried(input, context));
|
|
7735
8096
|
}
|
|
7736
8097
|
);
|
|
8098
|
+
registerTool(
|
|
8099
|
+
"ingest_findings",
|
|
8100
|
+
[
|
|
8101
|
+
"Turn scanner findings (SonarQube / SARIF) into proposed, anchored memories with sensors.",
|
|
8102
|
+
"",
|
|
8103
|
+
"USE THIS to seed hAIve from your existing quality tooling: each real defect a scanner",
|
|
8104
|
+
"found becomes a `gotcha`/`convention` memory anchored to the file, pre-filled with a",
|
|
8105
|
+
"conservative `warn` sensor \u2014 so the next agent is steered away from it before re-writing it.",
|
|
8106
|
+
"This closes the review\u2194memory loop and kills the cold-start problem.",
|
|
8107
|
+
"",
|
|
8108
|
+
"SAFETY: drafts are status=proposed and sensors are warn-only + autogen. This tool NEVER",
|
|
8109
|
+
"auto-validates and NEVER auto-blocks. A human reviews (mem_pending) and promotes the sensor.",
|
|
8110
|
+
"",
|
|
8111
|
+
"PARAMETERS:",
|
|
8112
|
+
" format \u2014 'sarif' (ESLint/Semgrep/CodeQL) | 'sonar' (SonarQube issues JSON)",
|
|
8113
|
+
" report_path \u2014 project-relative path to the report file (OR pass `report` inline)",
|
|
8114
|
+
" report \u2014 inline JSON content (OR pass `report_path`)",
|
|
8115
|
+
" type \u2014 gotcha (default) | convention",
|
|
8116
|
+
" scope \u2014 team (default) | personal | module",
|
|
8117
|
+
" min_severity \u2014 drop findings below this severity",
|
|
8118
|
+
" dry_run \u2014 preview what would be created without writing",
|
|
8119
|
+
"",
|
|
8120
|
+
"RETURNS: { format, parsed, new, skipped_existing, created[], notice }"
|
|
8121
|
+
].join("\n"),
|
|
8122
|
+
IngestFindingsInputSchema,
|
|
8123
|
+
async (input) => {
|
|
8124
|
+
tracker.record("ingest_findings", `${input.format}:${input.report_path ?? "inline"}`);
|
|
8125
|
+
return jsonResult(await ingestFindings(input, context));
|
|
8126
|
+
}
|
|
8127
|
+
);
|
|
7737
8128
|
registerTool(
|
|
7738
8129
|
"mem_observe",
|
|
7739
8130
|
[
|
|
@@ -8530,8 +8921,8 @@ function registerMcp(program2) {
|
|
|
8530
8921
|
|
|
8531
8922
|
// src/commands/sync.ts
|
|
8532
8923
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
8533
|
-
import { readFile as readFile9, writeFile as
|
|
8534
|
-
import { existsSync as
|
|
8924
|
+
import { readFile as readFile9, writeFile as writeFile14, mkdir as mkdir10 } from "fs/promises";
|
|
8925
|
+
import { existsSync as existsSync33 } from "fs";
|
|
8535
8926
|
import path15 from "path";
|
|
8536
8927
|
import "commander";
|
|
8537
8928
|
import {
|
|
@@ -8544,12 +8935,12 @@ import {
|
|
|
8544
8935
|
isStackPackSeed as isStackPackSeed3,
|
|
8545
8936
|
loadCodeMap as loadCodeMap6,
|
|
8546
8937
|
loadConfig as loadConfig4,
|
|
8547
|
-
loadMemoriesFromDir as
|
|
8938
|
+
loadMemoriesFromDir as loadMemoriesFromDir25,
|
|
8548
8939
|
loadUsageIndex as loadUsageIndex13,
|
|
8549
8940
|
pullCrossRepoSources,
|
|
8550
8941
|
resolveHaivePaths as resolveHaivePaths9,
|
|
8551
8942
|
resolveManifestFiles,
|
|
8552
|
-
serializeMemory as
|
|
8943
|
+
serializeMemory as serializeMemory12,
|
|
8553
8944
|
trackDependencies,
|
|
8554
8945
|
verifyAnchor as verifyAnchor2,
|
|
8555
8946
|
watchContracts
|
|
@@ -8564,11 +8955,11 @@ function registerSync(program2) {
|
|
|
8564
8955
|
"git ref/commit to compare against; report memories added/modified/removed since"
|
|
8565
8956
|
).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
|
|
8566
8957
|
"--inject-bridge",
|
|
8567
|
-
"inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
8958
|
+
"inject top validated memories into CLAUDE.md + AGENTS.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
8568
8959
|
).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").option("--dry-run", "report what would change without writing any files").action(async (opts) => {
|
|
8569
8960
|
const root = findProjectRoot12(opts.dir);
|
|
8570
8961
|
const paths = resolveHaivePaths9(root);
|
|
8571
|
-
if (!
|
|
8962
|
+
if (!existsSync33(paths.memoriesDir)) {
|
|
8572
8963
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8573
8964
|
process.exitCode = 1;
|
|
8574
8965
|
return;
|
|
@@ -8587,14 +8978,14 @@ function registerSync(program2) {
|
|
|
8587
8978
|
let promoted = 0;
|
|
8588
8979
|
let autoApproved = 0;
|
|
8589
8980
|
if (opts.verify !== false) {
|
|
8590
|
-
const memories = await
|
|
8981
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8591
8982
|
for (const { memory: memory2, filePath } of memories) {
|
|
8592
8983
|
if (memory2.frontmatter.type === "session_recap") {
|
|
8593
8984
|
if (memory2.frontmatter.status === "stale") {
|
|
8594
8985
|
if (!dryRun) {
|
|
8595
|
-
await
|
|
8986
|
+
await writeFile14(
|
|
8596
8987
|
filePath,
|
|
8597
|
-
|
|
8988
|
+
serializeMemory12({
|
|
8598
8989
|
frontmatter: {
|
|
8599
8990
|
...memory2.frontmatter,
|
|
8600
8991
|
status: "validated",
|
|
@@ -8617,9 +9008,9 @@ function registerSync(program2) {
|
|
|
8617
9008
|
if (result.stale) {
|
|
8618
9009
|
if (memory2.frontmatter.status !== "stale") {
|
|
8619
9010
|
if (!dryRun) {
|
|
8620
|
-
await
|
|
9011
|
+
await writeFile14(
|
|
8621
9012
|
filePath,
|
|
8622
|
-
|
|
9013
|
+
serializeMemory12({
|
|
8623
9014
|
frontmatter: {
|
|
8624
9015
|
...memory2.frontmatter,
|
|
8625
9016
|
status: "stale",
|
|
@@ -8635,9 +9026,9 @@ function registerSync(program2) {
|
|
|
8635
9026
|
}
|
|
8636
9027
|
} else if (memory2.frontmatter.status === "stale") {
|
|
8637
9028
|
if (!dryRun) {
|
|
8638
|
-
await
|
|
9029
|
+
await writeFile14(
|
|
8639
9030
|
filePath,
|
|
8640
|
-
|
|
9031
|
+
serializeMemory12({
|
|
8641
9032
|
frontmatter: {
|
|
8642
9033
|
...memory2.frontmatter,
|
|
8643
9034
|
status: "validated",
|
|
@@ -8654,7 +9045,7 @@ function registerSync(program2) {
|
|
|
8654
9045
|
}
|
|
8655
9046
|
}
|
|
8656
9047
|
if (opts.promote !== false) {
|
|
8657
|
-
const memories = await
|
|
9048
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8658
9049
|
const usage = await loadUsageIndex13(paths);
|
|
8659
9050
|
const nowMs = Date.now();
|
|
8660
9051
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -8665,9 +9056,9 @@ function registerSync(program2) {
|
|
|
8665
9056
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
|
|
8666
9057
|
})) {
|
|
8667
9058
|
if (!dryRun) {
|
|
8668
|
-
await
|
|
9059
|
+
await writeFile14(
|
|
8669
9060
|
filePath,
|
|
8670
|
-
|
|
9061
|
+
serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
8671
9062
|
"utf8"
|
|
8672
9063
|
);
|
|
8673
9064
|
}
|
|
@@ -8678,9 +9069,9 @@ function registerSync(program2) {
|
|
|
8678
9069
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
8679
9070
|
if (ageHours >= autoApproveDelayHours) {
|
|
8680
9071
|
if (!dryRun) {
|
|
8681
|
-
await
|
|
9072
|
+
await writeFile14(
|
|
8682
9073
|
filePath,
|
|
8683
|
-
|
|
9074
|
+
serializeMemory12({
|
|
8684
9075
|
frontmatter: {
|
|
8685
9076
|
...fm,
|
|
8686
9077
|
status: "validated",
|
|
@@ -8706,7 +9097,7 @@ function registerSync(program2) {
|
|
|
8706
9097
|
for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
|
|
8707
9098
|
}
|
|
8708
9099
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
8709
|
-
const draftMemories = (await
|
|
9100
|
+
const draftMemories = (await loadMemoriesFromDir25(paths.memoriesDir)).filter(
|
|
8710
9101
|
(m) => m.memory.frontmatter.status === "draft"
|
|
8711
9102
|
);
|
|
8712
9103
|
const draftCount = draftMemories.length;
|
|
@@ -8722,9 +9113,18 @@ function registerSync(program2) {
|
|
|
8722
9113
|
);
|
|
8723
9114
|
}
|
|
8724
9115
|
if (opts.injectBridge) {
|
|
8725
|
-
const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
|
|
8726
9116
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
8727
|
-
|
|
9117
|
+
let bridgeTargets;
|
|
9118
|
+
if (opts.bridgeFile) {
|
|
9119
|
+
bridgeTargets = [path15.resolve(opts.bridgeFile)];
|
|
9120
|
+
} else {
|
|
9121
|
+
const agentsMd = path15.join(root, "AGENTS.md");
|
|
9122
|
+
bridgeTargets = [path15.join(root, "CLAUDE.md")];
|
|
9123
|
+
if (existsSync33(agentsMd)) bridgeTargets.push(agentsMd);
|
|
9124
|
+
}
|
|
9125
|
+
for (const bridgeFile of bridgeTargets) {
|
|
9126
|
+
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
9127
|
+
}
|
|
8728
9128
|
}
|
|
8729
9129
|
if (sinceReport && !opts.quiet) {
|
|
8730
9130
|
if (sinceReport.added.length > 0) {
|
|
@@ -8741,7 +9141,7 @@ function registerSync(program2) {
|
|
|
8741
9141
|
}
|
|
8742
9142
|
}
|
|
8743
9143
|
if (!opts.quiet) {
|
|
8744
|
-
const allForDecay = await
|
|
9144
|
+
const allForDecay = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8745
9145
|
const usageForDecay = await loadUsageIndex13(paths);
|
|
8746
9146
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
8747
9147
|
const fm = memory2.frontmatter;
|
|
@@ -8831,9 +9231,9 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
8831
9231
|
if (!dryRun) {
|
|
8832
9232
|
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
8833
9233
|
await mkdir10(teamDir, { recursive: true });
|
|
8834
|
-
await
|
|
9234
|
+
await writeFile14(
|
|
8835
9235
|
path15.join(teamDir, `${fm.id}.md`),
|
|
8836
|
-
|
|
9236
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
8837
9237
|
"utf8"
|
|
8838
9238
|
);
|
|
8839
9239
|
}
|
|
@@ -8900,9 +9300,9 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
8900
9300
|
if (!dryRun) {
|
|
8901
9301
|
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
8902
9302
|
await mkdir10(teamDir, { recursive: true });
|
|
8903
|
-
await
|
|
9303
|
+
await writeFile14(
|
|
8904
9304
|
path15.join(teamDir, `${fm.id}.md`),
|
|
8905
|
-
|
|
9305
|
+
serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
8906
9306
|
"utf8"
|
|
8907
9307
|
);
|
|
8908
9308
|
}
|
|
@@ -8992,8 +9392,8 @@ function bridgeSummaryLine(body) {
|
|
|
8992
9392
|
return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
|
|
8993
9393
|
}
|
|
8994
9394
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
8995
|
-
if (!
|
|
8996
|
-
const all = await
|
|
9395
|
+
if (!existsSync33(memoriesDir)) return;
|
|
9396
|
+
const all = await loadMemoriesFromDir25(memoriesDir);
|
|
8997
9397
|
const top = all.filter(({ memory: memory2 }) => {
|
|
8998
9398
|
const s = memory2.frontmatter.status;
|
|
8999
9399
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
@@ -9018,7 +9418,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
|
9018
9418
|
` + block + `
|
|
9019
9419
|
|
|
9020
9420
|
${BRIDGE_END}`;
|
|
9021
|
-
const fileExists =
|
|
9421
|
+
const fileExists = existsSync33(bridgeFile);
|
|
9022
9422
|
let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
|
|
9023
9423
|
existing = existing.replace(/\r\n/g, "\n");
|
|
9024
9424
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
@@ -9040,7 +9440,7 @@ ${BRIDGE_END}`;
|
|
|
9040
9440
|
}
|
|
9041
9441
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
9042
9442
|
}
|
|
9043
|
-
await
|
|
9443
|
+
await writeFile14(bridgeFile, updated, "utf8");
|
|
9044
9444
|
if (!quiet) {
|
|
9045
9445
|
console.log(
|
|
9046
9446
|
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
|
|
@@ -9068,8 +9468,8 @@ function collectSinceChanges(root, ref) {
|
|
|
9068
9468
|
|
|
9069
9469
|
// src/commands/memory-add.ts
|
|
9070
9470
|
import { createHash as createHash2 } from "crypto";
|
|
9071
|
-
import { mkdir as mkdir11, readFile as readFile10, writeFile as
|
|
9072
|
-
import { existsSync as
|
|
9471
|
+
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile15 } from "fs/promises";
|
|
9472
|
+
import { existsSync as existsSync34 } from "fs";
|
|
9073
9473
|
import path16 from "path";
|
|
9074
9474
|
import "commander";
|
|
9075
9475
|
import {
|
|
@@ -9077,10 +9477,10 @@ import {
|
|
|
9077
9477
|
findProjectRoot as findProjectRoot13,
|
|
9078
9478
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
9079
9479
|
loadConfig as loadConfig5,
|
|
9080
|
-
loadMemoriesFromDir as
|
|
9081
|
-
memoryFilePath as
|
|
9480
|
+
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
9481
|
+
memoryFilePath as memoryFilePath7,
|
|
9082
9482
|
resolveHaivePaths as resolveHaivePaths10,
|
|
9083
|
-
serializeMemory as
|
|
9483
|
+
serializeMemory as serializeMemory13,
|
|
9084
9484
|
suggestSensorFromMemory as suggestSensorFromMemory3
|
|
9085
9485
|
} from "@hiveai/core";
|
|
9086
9486
|
function registerMemoryAdd(memory2) {
|
|
@@ -9111,7 +9511,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9111
9511
|
).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("--activation-keyword <csv>", "skill only: comma-separated keywords that trigger progressive disclosure of this skill").option("--activation-glob <csv>", "skill only: comma-separated path globs that trigger this skill").option("--activation-always", "skill only: always surface this skill (no triggers needed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9112
9512
|
const root = findProjectRoot13(opts.dir);
|
|
9113
9513
|
const paths = resolveHaivePaths10(root);
|
|
9114
|
-
if (!
|
|
9514
|
+
if (!existsSync34(paths.haiveDir)) {
|
|
9115
9515
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9116
9516
|
process.exitCode = 1;
|
|
9117
9517
|
return;
|
|
@@ -9128,7 +9528,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9128
9528
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
9129
9529
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
9130
9530
|
if (anchorPaths.length > 0) {
|
|
9131
|
-
const missing = anchorPaths.filter((p) => !
|
|
9531
|
+
const missing = anchorPaths.filter((p) => !existsSync34(path16.resolve(root, p)));
|
|
9132
9532
|
if (missing.length > 0) {
|
|
9133
9533
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
9134
9534
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -9141,7 +9541,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9141
9541
|
const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
|
|
9142
9542
|
let body;
|
|
9143
9543
|
if (opts.bodyFile !== void 0) {
|
|
9144
|
-
if (!
|
|
9544
|
+
if (!existsSync34(opts.bodyFile)) {
|
|
9145
9545
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9146
9546
|
process.exitCode = 1;
|
|
9147
9547
|
return;
|
|
@@ -9157,9 +9557,9 @@ TODO \u2014 write the memory body.
|
|
|
9157
9557
|
`;
|
|
9158
9558
|
}
|
|
9159
9559
|
const scope = opts.scope ?? config.defaultScope ?? "personal";
|
|
9160
|
-
if (
|
|
9560
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
9161
9561
|
const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
9162
|
-
const allForHash = await
|
|
9562
|
+
const allForHash = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9163
9563
|
const hashDup = allForHash.find(
|
|
9164
9564
|
({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
|
|
9165
9565
|
);
|
|
@@ -9170,8 +9570,8 @@ TODO \u2014 write the memory body.
|
|
|
9170
9570
|
return;
|
|
9171
9571
|
}
|
|
9172
9572
|
}
|
|
9173
|
-
if (opts.topic &&
|
|
9174
|
-
const existing = await
|
|
9573
|
+
if (opts.topic && existsSync34(paths.memoriesDir)) {
|
|
9574
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9175
9575
|
const topicMatch = existing.find(
|
|
9176
9576
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
9177
9577
|
);
|
|
@@ -9191,7 +9591,7 @@ TODO \u2014 write the memory body.
|
|
|
9191
9591
|
};
|
|
9192
9592
|
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
9193
9593
|
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
9194
|
-
await
|
|
9594
|
+
await writeFile15(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
|
|
9195
9595
|
ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
|
|
9196
9596
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
9197
9597
|
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
@@ -9215,15 +9615,15 @@ TODO \u2014 write the memory body.
|
|
|
9215
9615
|
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
|
|
9216
9616
|
activation
|
|
9217
9617
|
});
|
|
9218
|
-
const file =
|
|
9618
|
+
const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9219
9619
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
9220
|
-
if (
|
|
9620
|
+
if (existsSync34(file)) {
|
|
9221
9621
|
ui.error(`Memory already exists at ${file}`);
|
|
9222
9622
|
process.exitCode = 1;
|
|
9223
9623
|
return;
|
|
9224
9624
|
}
|
|
9225
|
-
if (
|
|
9226
|
-
const existing = await
|
|
9625
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
9626
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
9227
9627
|
const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
9228
9628
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
9229
9629
|
const id = memory3.frontmatter.id.toLowerCase();
|
|
@@ -9234,7 +9634,7 @@ TODO \u2014 write the memory body.
|
|
|
9234
9634
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
9235
9635
|
}
|
|
9236
9636
|
}
|
|
9237
|
-
await
|
|
9637
|
+
await writeFile15(file, serializeMemory13({ frontmatter, body }), "utf8");
|
|
9238
9638
|
ui.success(`Created ${path16.relative(root, file)}`);
|
|
9239
9639
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
9240
9640
|
if (frontmatter.sensor?.autogen) {
|
|
@@ -9314,14 +9714,14 @@ function slugify(value) {
|
|
|
9314
9714
|
}
|
|
9315
9715
|
|
|
9316
9716
|
// src/commands/memory-list.ts
|
|
9317
|
-
import { existsSync as
|
|
9717
|
+
import { existsSync as existsSync35 } from "fs";
|
|
9318
9718
|
import path17 from "path";
|
|
9319
9719
|
import "commander";
|
|
9320
9720
|
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
9321
9721
|
|
|
9322
9722
|
// src/utils/fs.ts
|
|
9323
9723
|
import {
|
|
9324
|
-
loadMemoriesFromDir as
|
|
9724
|
+
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
9325
9725
|
loadMemory,
|
|
9326
9726
|
listMarkdownFilesRecursive
|
|
9327
9727
|
} from "@hiveai/core";
|
|
@@ -9331,12 +9731,12 @@ function registerMemoryList(memory2) {
|
|
|
9331
9731
|
memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("--limit <n>", "max memories to display").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9332
9732
|
const root = findProjectRoot14(opts.dir);
|
|
9333
9733
|
const paths = resolveHaivePaths11(root);
|
|
9334
|
-
if (!
|
|
9734
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
9335
9735
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9336
9736
|
process.exitCode = 1;
|
|
9337
9737
|
return;
|
|
9338
9738
|
}
|
|
9339
|
-
const all = await
|
|
9739
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9340
9740
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
9341
9741
|
const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
|
|
9342
9742
|
const filtered = all.filter((m) => {
|
|
@@ -9405,26 +9805,26 @@ function matchesFilters(loaded, opts) {
|
|
|
9405
9805
|
}
|
|
9406
9806
|
|
|
9407
9807
|
// src/commands/memory-promote.ts
|
|
9408
|
-
import { mkdir as mkdir12, unlink as unlink2, writeFile as
|
|
9409
|
-
import { existsSync as
|
|
9808
|
+
import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile16 } from "fs/promises";
|
|
9809
|
+
import { existsSync as existsSync36 } from "fs";
|
|
9410
9810
|
import path18 from "path";
|
|
9411
9811
|
import "commander";
|
|
9412
9812
|
import {
|
|
9413
9813
|
findProjectRoot as findProjectRoot15,
|
|
9414
|
-
memoryFilePath as
|
|
9814
|
+
memoryFilePath as memoryFilePath8,
|
|
9415
9815
|
resolveHaivePaths as resolveHaivePaths12,
|
|
9416
|
-
serializeMemory as
|
|
9816
|
+
serializeMemory as serializeMemory14
|
|
9417
9817
|
} from "@hiveai/core";
|
|
9418
9818
|
function registerMemoryPromote(memory2) {
|
|
9419
9819
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9420
9820
|
const root = findProjectRoot15(opts.dir);
|
|
9421
9821
|
const paths = resolveHaivePaths12(root);
|
|
9422
|
-
if (!
|
|
9822
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
9423
9823
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9424
9824
|
process.exitCode = 1;
|
|
9425
9825
|
return;
|
|
9426
9826
|
}
|
|
9427
|
-
const teamAndModule = await
|
|
9827
|
+
const teamAndModule = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9428
9828
|
const alreadyShared = teamAndModule.find(
|
|
9429
9829
|
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
9430
9830
|
);
|
|
@@ -9438,7 +9838,7 @@ function registerMemoryPromote(memory2) {
|
|
|
9438
9838
|
}
|
|
9439
9839
|
return;
|
|
9440
9840
|
}
|
|
9441
|
-
const all = await
|
|
9841
|
+
const all = await loadMemoriesFromDir27(paths.personalDir);
|
|
9442
9842
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9443
9843
|
if (!found) {
|
|
9444
9844
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -9453,9 +9853,9 @@ function registerMemoryPromote(memory2) {
|
|
|
9453
9853
|
},
|
|
9454
9854
|
body: found.memory.body
|
|
9455
9855
|
};
|
|
9456
|
-
const newPath =
|
|
9856
|
+
const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
|
|
9457
9857
|
await mkdir12(path18.dirname(newPath), { recursive: true });
|
|
9458
|
-
await
|
|
9858
|
+
await writeFile16(newPath, serializeMemory14(updated), "utf8");
|
|
9459
9859
|
await unlink2(found.filePath);
|
|
9460
9860
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
9461
9861
|
ui.info(`Now at ${path18.relative(root, newPath)}`);
|
|
@@ -9464,25 +9864,25 @@ function registerMemoryPromote(memory2) {
|
|
|
9464
9864
|
}
|
|
9465
9865
|
|
|
9466
9866
|
// src/commands/memory-approve.ts
|
|
9467
|
-
import { existsSync as
|
|
9468
|
-
import { writeFile as
|
|
9867
|
+
import { existsSync as existsSync37 } from "fs";
|
|
9868
|
+
import { writeFile as writeFile17 } from "fs/promises";
|
|
9469
9869
|
import path19 from "path";
|
|
9470
9870
|
import "commander";
|
|
9471
9871
|
import {
|
|
9472
9872
|
findProjectRoot as findProjectRoot16,
|
|
9473
9873
|
resolveHaivePaths as resolveHaivePaths13,
|
|
9474
|
-
serializeMemory as
|
|
9874
|
+
serializeMemory as serializeMemory15
|
|
9475
9875
|
} from "@hiveai/core";
|
|
9476
9876
|
function registerMemoryApprove(memory2) {
|
|
9477
9877
|
memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9478
9878
|
const root = findProjectRoot16(opts.dir);
|
|
9479
9879
|
const paths = resolveHaivePaths13(root);
|
|
9480
|
-
if (!
|
|
9880
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
9481
9881
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9482
9882
|
process.exitCode = 1;
|
|
9483
9883
|
return;
|
|
9484
9884
|
}
|
|
9485
|
-
const all = await
|
|
9885
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9486
9886
|
if (opts.all || opts.pending) {
|
|
9487
9887
|
const candidates = all.filter((m) => {
|
|
9488
9888
|
const s = m.memory.frontmatter.status;
|
|
@@ -9499,7 +9899,7 @@ function registerMemoryApprove(memory2) {
|
|
|
9499
9899
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
9500
9900
|
body: found2.memory.body
|
|
9501
9901
|
};
|
|
9502
|
-
await
|
|
9902
|
+
await writeFile17(found2.filePath, serializeMemory15(next2), "utf8");
|
|
9503
9903
|
count++;
|
|
9504
9904
|
}
|
|
9505
9905
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -9528,32 +9928,32 @@ function registerMemoryApprove(memory2) {
|
|
|
9528
9928
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
9529
9929
|
body: found.memory.body
|
|
9530
9930
|
};
|
|
9531
|
-
await
|
|
9931
|
+
await writeFile17(found.filePath, serializeMemory15(next), "utf8");
|
|
9532
9932
|
ui.success(`Approved ${id} (status=validated)`);
|
|
9533
9933
|
ui.info(path19.relative(root, found.filePath));
|
|
9534
9934
|
});
|
|
9535
9935
|
}
|
|
9536
9936
|
|
|
9537
9937
|
// src/commands/memory-update.ts
|
|
9538
|
-
import { readFile as readFile11, writeFile as
|
|
9539
|
-
import { existsSync as
|
|
9938
|
+
import { readFile as readFile11, writeFile as writeFile18 } from "fs/promises";
|
|
9939
|
+
import { existsSync as existsSync38 } from "fs";
|
|
9540
9940
|
import path20 from "path";
|
|
9541
9941
|
import "commander";
|
|
9542
9942
|
import {
|
|
9543
9943
|
findProjectRoot as findProjectRoot17,
|
|
9544
9944
|
resolveHaivePaths as resolveHaivePaths14,
|
|
9545
|
-
serializeMemory as
|
|
9945
|
+
serializeMemory as serializeMemory16
|
|
9546
9946
|
} from "@hiveai/core";
|
|
9547
9947
|
function registerMemoryUpdate(memory2) {
|
|
9548
9948
|
memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--type <type>", "change the memory type (convention | decision | gotcha | architecture | glossary | skill | attempt)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--body-file <path>", "read new body from a Markdown file \u2014 for long content").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9549
9949
|
const root = findProjectRoot17(opts.dir);
|
|
9550
9950
|
const paths = resolveHaivePaths14(root);
|
|
9551
|
-
if (!
|
|
9951
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
9552
9952
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9553
9953
|
process.exitCode = 1;
|
|
9554
9954
|
return;
|
|
9555
9955
|
}
|
|
9556
|
-
const memories = await
|
|
9956
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9557
9957
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
9558
9958
|
if (!loaded) {
|
|
9559
9959
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9590,7 +9990,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
9590
9990
|
if (opts.author !== void 0) updated.push("author");
|
|
9591
9991
|
let newBody;
|
|
9592
9992
|
if (opts.bodyFile !== void 0) {
|
|
9593
|
-
if (!
|
|
9993
|
+
if (!existsSync38(opts.bodyFile)) {
|
|
9594
9994
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
9595
9995
|
process.exitCode = 1;
|
|
9596
9996
|
return;
|
|
@@ -9611,9 +10011,9 @@ function registerMemoryUpdate(memory2) {
|
|
|
9611
10011
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
9612
10012
|
return;
|
|
9613
10013
|
}
|
|
9614
|
-
await
|
|
10014
|
+
await writeFile18(
|
|
9615
10015
|
loaded.filePath,
|
|
9616
|
-
|
|
10016
|
+
serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
|
|
9617
10017
|
"utf8"
|
|
9618
10018
|
);
|
|
9619
10019
|
ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
|
|
@@ -9635,8 +10035,8 @@ function parseCsv3(value) {
|
|
|
9635
10035
|
}
|
|
9636
10036
|
|
|
9637
10037
|
// src/commands/memory-auto-promote.ts
|
|
9638
|
-
import { writeFile as
|
|
9639
|
-
import { existsSync as
|
|
10038
|
+
import { writeFile as writeFile19 } from "fs/promises";
|
|
10039
|
+
import { existsSync as existsSync39 } from "fs";
|
|
9640
10040
|
import path21 from "path";
|
|
9641
10041
|
import "commander";
|
|
9642
10042
|
import {
|
|
@@ -9646,7 +10046,7 @@ import {
|
|
|
9646
10046
|
isAutoPromoteEligible as isAutoPromoteEligible3,
|
|
9647
10047
|
loadUsageIndex as loadUsageIndex14,
|
|
9648
10048
|
resolveHaivePaths as resolveHaivePaths15,
|
|
9649
|
-
serializeMemory as
|
|
10049
|
+
serializeMemory as serializeMemory17
|
|
9650
10050
|
} from "@hiveai/core";
|
|
9651
10051
|
function registerMemoryAutoPromote(memory2) {
|
|
9652
10052
|
memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE3.minReads)).option(
|
|
@@ -9656,7 +10056,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9656
10056
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9657
10057
|
const root = findProjectRoot18(opts.dir);
|
|
9658
10058
|
const paths = resolveHaivePaths15(root);
|
|
9659
|
-
if (!
|
|
10059
|
+
if (!existsSync39(paths.memoriesDir)) {
|
|
9660
10060
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9661
10061
|
process.exitCode = 1;
|
|
9662
10062
|
return;
|
|
@@ -9665,7 +10065,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9665
10065
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
|
|
9666
10066
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
9667
10067
|
};
|
|
9668
|
-
const memories = await
|
|
10068
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9669
10069
|
const usage = await loadUsageIndex14(paths);
|
|
9670
10070
|
const eligible = memories.filter(
|
|
9671
10071
|
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
|
|
@@ -9688,7 +10088,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9688
10088
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
9689
10089
|
body: mem.body
|
|
9690
10090
|
};
|
|
9691
|
-
await
|
|
10091
|
+
await writeFile19(filePath, serializeMemory17(next), "utf8");
|
|
9692
10092
|
written++;
|
|
9693
10093
|
}
|
|
9694
10094
|
}
|
|
@@ -9699,7 +10099,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
9699
10099
|
|
|
9700
10100
|
// src/commands/memory-edit.ts
|
|
9701
10101
|
import { spawn as spawn3 } from "child_process";
|
|
9702
|
-
import { existsSync as
|
|
10102
|
+
import { existsSync as existsSync40 } from "fs";
|
|
9703
10103
|
import { readFile as readFile12 } from "fs/promises";
|
|
9704
10104
|
import path23 from "path";
|
|
9705
10105
|
import "commander";
|
|
@@ -9712,12 +10112,12 @@ function registerMemoryEdit(memory2) {
|
|
|
9712
10112
|
memory2.command("edit <id>").description("Open a memory in $EDITOR and re-validate when you save").option("-e, --editor <cmd>", "editor command (defaults to $EDITOR or 'vi')").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9713
10113
|
const root = findProjectRoot19(opts.dir);
|
|
9714
10114
|
const paths = resolveHaivePaths16(root);
|
|
9715
|
-
if (!
|
|
10115
|
+
if (!existsSync40(paths.memoriesDir)) {
|
|
9716
10116
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9717
10117
|
process.exitCode = 1;
|
|
9718
10118
|
return;
|
|
9719
10119
|
}
|
|
9720
|
-
const all = await
|
|
10120
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9721
10121
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
9722
10122
|
if (!found) {
|
|
9723
10123
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -9752,7 +10152,7 @@ function runEditor(editor, file) {
|
|
|
9752
10152
|
}
|
|
9753
10153
|
|
|
9754
10154
|
// src/commands/memory-for-files.ts
|
|
9755
|
-
import { existsSync as
|
|
10155
|
+
import { existsSync as existsSync41 } from "fs";
|
|
9756
10156
|
import path24 from "path";
|
|
9757
10157
|
import "commander";
|
|
9758
10158
|
import {
|
|
@@ -9768,12 +10168,12 @@ function registerMemoryForFiles(memory2) {
|
|
|
9768
10168
|
memory2.command("for-files <files...>").description("Show memories relevant to the given files (anchor overlap, module, domain)").option("-d, --dir <dir>", "project root").action(async (files, opts) => {
|
|
9769
10169
|
const root = findProjectRoot20(opts.dir);
|
|
9770
10170
|
const paths = resolveHaivePaths17(root);
|
|
9771
|
-
if (!
|
|
10171
|
+
if (!existsSync41(paths.memoriesDir)) {
|
|
9772
10172
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9773
10173
|
process.exitCode = 1;
|
|
9774
10174
|
return;
|
|
9775
10175
|
}
|
|
9776
|
-
const all = await
|
|
10176
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9777
10177
|
const usage = await loadUsageIndex15(paths);
|
|
9778
10178
|
const inferred = inferModulesFromPaths4(files);
|
|
9779
10179
|
const byAnchor = [];
|
|
@@ -9880,7 +10280,7 @@ function printGroup(root, label, loaded, usage) {
|
|
|
9880
10280
|
}
|
|
9881
10281
|
|
|
9882
10282
|
// src/commands/memory-hot.ts
|
|
9883
|
-
import { existsSync as
|
|
10283
|
+
import { existsSync as existsSync43 } from "fs";
|
|
9884
10284
|
import path25 from "path";
|
|
9885
10285
|
import "commander";
|
|
9886
10286
|
import {
|
|
@@ -9895,13 +10295,13 @@ function registerMemoryHot(memory2) {
|
|
|
9895
10295
|
).option("--threshold <n>", "minimum read_count to qualify (default: 3)", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9896
10296
|
const root = findProjectRoot21(opts.dir);
|
|
9897
10297
|
const paths = resolveHaivePaths18(root);
|
|
9898
|
-
if (!
|
|
10298
|
+
if (!existsSync43(paths.memoriesDir)) {
|
|
9899
10299
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9900
10300
|
process.exitCode = 1;
|
|
9901
10301
|
return;
|
|
9902
10302
|
}
|
|
9903
10303
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
9904
|
-
const all = await
|
|
10304
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
9905
10305
|
const usage = await loadUsageIndex16(paths);
|
|
9906
10306
|
const candidates = all.filter(({ memory: mem }) => {
|
|
9907
10307
|
const fm = mem.frontmatter;
|
|
@@ -9933,16 +10333,16 @@ function registerMemoryHot(memory2) {
|
|
|
9933
10333
|
}
|
|
9934
10334
|
|
|
9935
10335
|
// src/commands/memory-tried.ts
|
|
9936
|
-
import { mkdir as mkdir13, writeFile as
|
|
9937
|
-
import { existsSync as
|
|
10336
|
+
import { mkdir as mkdir13, writeFile as writeFile20 } from "fs/promises";
|
|
10337
|
+
import { existsSync as existsSync44 } from "fs";
|
|
9938
10338
|
import path26 from "path";
|
|
9939
10339
|
import "commander";
|
|
9940
10340
|
import {
|
|
9941
10341
|
buildFrontmatter as buildFrontmatter8,
|
|
9942
10342
|
findProjectRoot as findProjectRoot22,
|
|
9943
|
-
memoryFilePath as
|
|
10343
|
+
memoryFilePath as memoryFilePath9,
|
|
9944
10344
|
resolveHaivePaths as resolveHaivePaths19,
|
|
9945
|
-
serializeMemory as
|
|
10345
|
+
serializeMemory as serializeMemory18,
|
|
9946
10346
|
suggestSensorFromMemory as suggestSensorFromMemory4
|
|
9947
10347
|
} from "@hiveai/core";
|
|
9948
10348
|
function registerMemoryTried(memory2) {
|
|
@@ -9964,7 +10364,7 @@ function registerMemoryTried(memory2) {
|
|
|
9964
10364
|
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9965
10365
|
const root = findProjectRoot22(opts.dir);
|
|
9966
10366
|
const paths = resolveHaivePaths19(root);
|
|
9967
|
-
if (!
|
|
10367
|
+
if (!existsSync44(paths.haiveDir)) {
|
|
9968
10368
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9969
10369
|
process.exitCode = 1;
|
|
9970
10370
|
return;
|
|
@@ -9990,14 +10390,14 @@ function registerMemoryTried(memory2) {
|
|
|
9990
10390
|
if (sensor) {
|
|
9991
10391
|
frontmatter.sensor = sensor;
|
|
9992
10392
|
}
|
|
9993
|
-
const file =
|
|
10393
|
+
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9994
10394
|
await mkdir13(path26.dirname(file), { recursive: true });
|
|
9995
|
-
if (
|
|
10395
|
+
if (existsSync44(file)) {
|
|
9996
10396
|
ui.error(`Memory already exists at ${file}`);
|
|
9997
10397
|
process.exitCode = 1;
|
|
9998
10398
|
return;
|
|
9999
10399
|
}
|
|
10000
|
-
await
|
|
10400
|
+
await writeFile20(file, serializeMemory18({ frontmatter, body }), "utf8");
|
|
10001
10401
|
ui.success(`Recorded: ${path26.relative(root, file)}`);
|
|
10002
10402
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
10003
10403
|
if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
@@ -10010,7 +10410,7 @@ function parseCsv4(value) {
|
|
|
10010
10410
|
|
|
10011
10411
|
// src/commands/memory-seed.ts
|
|
10012
10412
|
import { readFile as readFile13 } from "fs/promises";
|
|
10013
|
-
import { existsSync as
|
|
10413
|
+
import { existsSync as existsSync45 } from "fs";
|
|
10014
10414
|
import path27 from "path";
|
|
10015
10415
|
import "commander";
|
|
10016
10416
|
import {
|
|
@@ -10050,7 +10450,7 @@ function registerMemorySeed(memory2) {
|
|
|
10050
10450
|
}
|
|
10051
10451
|
return;
|
|
10052
10452
|
}
|
|
10053
|
-
if (!
|
|
10453
|
+
if (!existsSync45(paths.haiveDir)) {
|
|
10054
10454
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10055
10455
|
process.exitCode = 1;
|
|
10056
10456
|
return;
|
|
@@ -10106,7 +10506,7 @@ function registerMemorySeed(memory2) {
|
|
|
10106
10506
|
}
|
|
10107
10507
|
|
|
10108
10508
|
// src/commands/memory-pending.ts
|
|
10109
|
-
import { existsSync as
|
|
10509
|
+
import { existsSync as existsSync46 } from "fs";
|
|
10110
10510
|
import path28 from "path";
|
|
10111
10511
|
import "commander";
|
|
10112
10512
|
import {
|
|
@@ -10119,12 +10519,12 @@ function registerMemoryPending(memory2) {
|
|
|
10119
10519
|
memory2.command("pending").description("List draft and proposed memories awaiting review (sorted by reads desc).\n\n draft = created but not yet activated \xB7 proposed = promoted, awaiting team validation").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10120
10520
|
const root = findProjectRoot24(opts.dir);
|
|
10121
10521
|
const paths = resolveHaivePaths21(root);
|
|
10122
|
-
if (!
|
|
10522
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
10123
10523
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10124
10524
|
process.exitCode = 1;
|
|
10125
10525
|
return;
|
|
10126
10526
|
}
|
|
10127
|
-
const all = await
|
|
10527
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10128
10528
|
const usage = await loadUsageIndex17(paths);
|
|
10129
10529
|
const filterFn = ({ memory: mem }) => {
|
|
10130
10530
|
if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
|
|
@@ -10177,7 +10577,7 @@ function registerMemoryPending(memory2) {
|
|
|
10177
10577
|
}
|
|
10178
10578
|
|
|
10179
10579
|
// src/commands/memory-query.ts
|
|
10180
|
-
import { existsSync as
|
|
10580
|
+
import { existsSync as existsSync47 } from "fs";
|
|
10181
10581
|
import path29 from "path";
|
|
10182
10582
|
import "commander";
|
|
10183
10583
|
import {
|
|
@@ -10194,7 +10594,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10194
10594
|
memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
|
|
10195
10595
|
const root = findProjectRoot25(opts.dir);
|
|
10196
10596
|
const paths = resolveHaivePaths22(root);
|
|
10197
|
-
if (!
|
|
10597
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
10198
10598
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
10199
10599
|
process.exitCode = 1;
|
|
10200
10600
|
return;
|
|
@@ -10205,7 +10605,7 @@ function registerMemoryQuery(memory2) {
|
|
|
10205
10605
|
return;
|
|
10206
10606
|
}
|
|
10207
10607
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
10208
|
-
const all = await
|
|
10608
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10209
10609
|
const passesFilters2 = (mem) => {
|
|
10210
10610
|
const fm = mem.frontmatter;
|
|
10211
10611
|
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
@@ -10252,8 +10652,8 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
10252
10652
|
}
|
|
10253
10653
|
|
|
10254
10654
|
// src/commands/memory-reject.ts
|
|
10255
|
-
import { writeFile as
|
|
10256
|
-
import { existsSync as
|
|
10655
|
+
import { writeFile as writeFile21 } from "fs/promises";
|
|
10656
|
+
import { existsSync as existsSync48 } from "fs";
|
|
10257
10657
|
import "commander";
|
|
10258
10658
|
import {
|
|
10259
10659
|
findProjectRoot as findProjectRoot26,
|
|
@@ -10261,27 +10661,27 @@ import {
|
|
|
10261
10661
|
recordRejection as recordRejection3,
|
|
10262
10662
|
resolveHaivePaths as resolveHaivePaths23,
|
|
10263
10663
|
saveUsageIndex as saveUsageIndex4,
|
|
10264
|
-
serializeMemory as
|
|
10664
|
+
serializeMemory as serializeMemory19
|
|
10265
10665
|
} from "@hiveai/core";
|
|
10266
10666
|
function registerMemoryReject(memory2) {
|
|
10267
10667
|
memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
10268
10668
|
const root = findProjectRoot26(opts.dir);
|
|
10269
10669
|
const paths = resolveHaivePaths23(root);
|
|
10270
|
-
if (!
|
|
10670
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
10271
10671
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10272
10672
|
process.exitCode = 1;
|
|
10273
10673
|
return;
|
|
10274
10674
|
}
|
|
10275
|
-
const memories = await
|
|
10675
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10276
10676
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
10277
10677
|
if (!loaded) {
|
|
10278
10678
|
ui.error(`No memory with id "${id}".`);
|
|
10279
10679
|
process.exitCode = 1;
|
|
10280
10680
|
return;
|
|
10281
10681
|
}
|
|
10282
|
-
await
|
|
10682
|
+
await writeFile21(
|
|
10283
10683
|
loaded.filePath,
|
|
10284
|
-
|
|
10684
|
+
serializeMemory19({
|
|
10285
10685
|
frontmatter: {
|
|
10286
10686
|
...loaded.memory.frontmatter,
|
|
10287
10687
|
status: "rejected",
|
|
@@ -10303,7 +10703,7 @@ function registerMemoryReject(memory2) {
|
|
|
10303
10703
|
}
|
|
10304
10704
|
|
|
10305
10705
|
// src/commands/memory-rm.ts
|
|
10306
|
-
import { existsSync as
|
|
10706
|
+
import { existsSync as existsSync49 } from "fs";
|
|
10307
10707
|
import { unlink as unlink3 } from "fs/promises";
|
|
10308
10708
|
import path30 from "path";
|
|
10309
10709
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
@@ -10318,12 +10718,12 @@ function registerMemoryRm(memory2) {
|
|
|
10318
10718
|
memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
10319
10719
|
const root = findProjectRoot27(opts.dir);
|
|
10320
10720
|
const paths = resolveHaivePaths24(root);
|
|
10321
|
-
if (!
|
|
10721
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
10322
10722
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10323
10723
|
process.exitCode = 1;
|
|
10324
10724
|
return;
|
|
10325
10725
|
}
|
|
10326
|
-
const all = await
|
|
10726
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10327
10727
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10328
10728
|
if (!found) {
|
|
10329
10729
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10354,7 +10754,7 @@ function registerMemoryRm(memory2) {
|
|
|
10354
10754
|
}
|
|
10355
10755
|
|
|
10356
10756
|
// src/commands/memory-show.ts
|
|
10357
|
-
import { existsSync as
|
|
10757
|
+
import { existsSync as existsSync50 } from "fs";
|
|
10358
10758
|
import { readFile as readFile14 } from "fs/promises";
|
|
10359
10759
|
import path31 from "path";
|
|
10360
10760
|
import "commander";
|
|
@@ -10369,12 +10769,12 @@ function registerMemoryShow(memory2) {
|
|
|
10369
10769
|
memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
10370
10770
|
const root = findProjectRoot28(opts.dir);
|
|
10371
10771
|
const paths = resolveHaivePaths25(root);
|
|
10372
|
-
if (!
|
|
10772
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
10373
10773
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10374
10774
|
process.exitCode = 1;
|
|
10375
10775
|
return;
|
|
10376
10776
|
}
|
|
10377
|
-
const all = await
|
|
10777
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10378
10778
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10379
10779
|
if (!found) {
|
|
10380
10780
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10413,7 +10813,7 @@ function registerMemoryShow(memory2) {
|
|
|
10413
10813
|
}
|
|
10414
10814
|
|
|
10415
10815
|
// src/commands/memory-stats.ts
|
|
10416
|
-
import { existsSync as
|
|
10816
|
+
import { existsSync as existsSync51 } from "fs";
|
|
10417
10817
|
import path33 from "path";
|
|
10418
10818
|
import "commander";
|
|
10419
10819
|
import {
|
|
@@ -10427,12 +10827,12 @@ function registerMemoryStats(memory2) {
|
|
|
10427
10827
|
memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10428
10828
|
const root = findProjectRoot29(opts.dir);
|
|
10429
10829
|
const paths = resolveHaivePaths26(root);
|
|
10430
|
-
if (!
|
|
10830
|
+
if (!existsSync51(paths.memoriesDir)) {
|
|
10431
10831
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10432
10832
|
process.exitCode = 1;
|
|
10433
10833
|
return;
|
|
10434
10834
|
}
|
|
10435
|
-
const all = await
|
|
10835
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10436
10836
|
const usage = await loadUsageIndex21(paths);
|
|
10437
10837
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10438
10838
|
if (target.length === 0) {
|
|
@@ -10458,7 +10858,7 @@ function registerMemoryStats(memory2) {
|
|
|
10458
10858
|
}
|
|
10459
10859
|
|
|
10460
10860
|
// src/commands/memory-impact.ts
|
|
10461
|
-
import { existsSync as
|
|
10861
|
+
import { existsSync as existsSync53 } from "fs";
|
|
10462
10862
|
import "commander";
|
|
10463
10863
|
import {
|
|
10464
10864
|
compareImpact,
|
|
@@ -10475,12 +10875,12 @@ function registerMemoryImpact(memory2) {
|
|
|
10475
10875
|
).option("--id <id>", "show impact for a single memory id").option("--prune", "list only prune candidates (dead weight worth reviewing)", false).option("--tier <tier>", "filter to a tier: high | medium | low | dormant").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10476
10876
|
const root = findProjectRoot30(opts.dir);
|
|
10477
10877
|
const paths = resolveHaivePaths27(root);
|
|
10478
|
-
if (!
|
|
10878
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
10479
10879
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10480
10880
|
process.exitCode = 1;
|
|
10481
10881
|
return;
|
|
10482
10882
|
}
|
|
10483
|
-
const all = await
|
|
10883
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10484
10884
|
const usageIndex = await loadUsageIndex23(paths);
|
|
10485
10885
|
let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
|
|
10486
10886
|
const fm = mem.frontmatter;
|
|
@@ -10546,7 +10946,7 @@ function pad(value, width) {
|
|
|
10546
10946
|
}
|
|
10547
10947
|
|
|
10548
10948
|
// src/commands/memory-feedback.ts
|
|
10549
|
-
import { existsSync as
|
|
10949
|
+
import { existsSync as existsSync54 } from "fs";
|
|
10550
10950
|
import "commander";
|
|
10551
10951
|
import {
|
|
10552
10952
|
computeImpact as computeImpact4,
|
|
@@ -10569,12 +10969,12 @@ function registerMemoryFeedback(memory2) {
|
|
|
10569
10969
|
}
|
|
10570
10970
|
const root = findProjectRoot31(opts.dir);
|
|
10571
10971
|
const paths = resolveHaivePaths28(root);
|
|
10572
|
-
if (!
|
|
10972
|
+
if (!existsSync54(paths.memoriesDir)) {
|
|
10573
10973
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10574
10974
|
process.exitCode = 1;
|
|
10575
10975
|
return;
|
|
10576
10976
|
}
|
|
10577
|
-
const all = await
|
|
10977
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10578
10978
|
const target = all.find((m) => m.memory.frontmatter.id === id);
|
|
10579
10979
|
if (!target) {
|
|
10580
10980
|
ui.error(`No memory with id '${id}'.`);
|
|
@@ -10600,14 +11000,14 @@ function registerMemoryFeedback(memory2) {
|
|
|
10600
11000
|
}
|
|
10601
11001
|
|
|
10602
11002
|
// src/commands/memory-verify.ts
|
|
10603
|
-
import { writeFile as
|
|
10604
|
-
import { existsSync as
|
|
11003
|
+
import { writeFile as writeFile23 } from "fs/promises";
|
|
11004
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10605
11005
|
import path34 from "path";
|
|
10606
11006
|
import "commander";
|
|
10607
11007
|
import {
|
|
10608
11008
|
findProjectRoot as findProjectRoot32,
|
|
10609
11009
|
resolveHaivePaths as resolveHaivePaths29,
|
|
10610
|
-
serializeMemory as
|
|
11010
|
+
serializeMemory as serializeMemory20,
|
|
10611
11011
|
verifyAnchor as verifyAnchor3
|
|
10612
11012
|
} from "@hiveai/core";
|
|
10613
11013
|
function registerMemoryVerify(memory2) {
|
|
@@ -10616,7 +11016,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10616
11016
|
).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10617
11017
|
const root = findProjectRoot32(opts.dir);
|
|
10618
11018
|
const paths = resolveHaivePaths29(root);
|
|
10619
|
-
if (!
|
|
11019
|
+
if (!existsSync55(paths.memoriesDir)) {
|
|
10620
11020
|
if (opts.json) {
|
|
10621
11021
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
10622
11022
|
} else {
|
|
@@ -10625,7 +11025,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10625
11025
|
process.exitCode = 1;
|
|
10626
11026
|
return;
|
|
10627
11027
|
}
|
|
10628
|
-
const all = await
|
|
11028
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10629
11029
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
10630
11030
|
if (opts.id && targets.length === 0) {
|
|
10631
11031
|
if (opts.json) {
|
|
@@ -10674,7 +11074,7 @@ function registerMemoryVerify(memory2) {
|
|
|
10674
11074
|
}
|
|
10675
11075
|
if (opts.update) {
|
|
10676
11076
|
const next = applyVerification2(mem, result);
|
|
10677
|
-
await
|
|
11077
|
+
await writeFile23(filePath, serializeMemory20(next), "utf8");
|
|
10678
11078
|
updated++;
|
|
10679
11079
|
}
|
|
10680
11080
|
}
|
|
@@ -10737,7 +11137,7 @@ function applyVerification2(mem, result) {
|
|
|
10737
11137
|
|
|
10738
11138
|
// src/commands/memory-import.ts
|
|
10739
11139
|
import { readFile as readFile15 } from "fs/promises";
|
|
10740
|
-
import { existsSync as
|
|
11140
|
+
import { existsSync as existsSync56 } from "fs";
|
|
10741
11141
|
import "commander";
|
|
10742
11142
|
import {
|
|
10743
11143
|
findProjectRoot as findProjectRoot33,
|
|
@@ -10749,12 +11149,12 @@ function registerMemoryImport(memory2) {
|
|
|
10749
11149
|
).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10750
11150
|
const root = findProjectRoot33(opts.dir);
|
|
10751
11151
|
const paths = resolveHaivePaths30(root);
|
|
10752
|
-
if (!
|
|
11152
|
+
if (!existsSync56(paths.haiveDir)) {
|
|
10753
11153
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10754
11154
|
process.exitCode = 1;
|
|
10755
11155
|
return;
|
|
10756
11156
|
}
|
|
10757
|
-
if (!
|
|
11157
|
+
if (!existsSync56(opts.from)) {
|
|
10758
11158
|
ui.error(`File not found: ${opts.from}`);
|
|
10759
11159
|
process.exitCode = 1;
|
|
10760
11160
|
return;
|
|
@@ -10787,15 +11187,15 @@ function registerMemoryImport(memory2) {
|
|
|
10787
11187
|
}
|
|
10788
11188
|
|
|
10789
11189
|
// src/commands/memory-import-changelog.ts
|
|
10790
|
-
import { existsSync as
|
|
10791
|
-
import { readFile as readFile16, mkdir as mkdir14, writeFile as
|
|
11190
|
+
import { existsSync as existsSync57 } from "fs";
|
|
11191
|
+
import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile24 } from "fs/promises";
|
|
10792
11192
|
import path35 from "path";
|
|
10793
11193
|
import "commander";
|
|
10794
11194
|
import {
|
|
10795
11195
|
buildFrontmatter as buildFrontmatter9,
|
|
10796
11196
|
findProjectRoot as findProjectRoot34,
|
|
10797
11197
|
resolveHaivePaths as resolveHaivePaths31,
|
|
10798
|
-
serializeMemory as
|
|
11198
|
+
serializeMemory as serializeMemory21
|
|
10799
11199
|
} from "@hiveai/core";
|
|
10800
11200
|
function parseChangelog(content) {
|
|
10801
11201
|
const entries = [];
|
|
@@ -10862,7 +11262,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10862
11262
|
const root = findProjectRoot34(opts.dir);
|
|
10863
11263
|
const paths = resolveHaivePaths31(root);
|
|
10864
11264
|
const changelogPath = path35.resolve(root, opts.fromChangelog);
|
|
10865
|
-
if (!
|
|
11265
|
+
if (!existsSync57(changelogPath)) {
|
|
10866
11266
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
10867
11267
|
process.exitCode = 1;
|
|
10868
11268
|
return;
|
|
@@ -10925,9 +11325,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10925
11325
|
paths: [path35.relative(root, changelogPath)],
|
|
10926
11326
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
10927
11327
|
});
|
|
10928
|
-
await
|
|
11328
|
+
await writeFile24(
|
|
10929
11329
|
path35.join(teamDir, `${fm.id}.md`),
|
|
10930
|
-
|
|
11330
|
+
serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
|
|
10931
11331
|
"utf8"
|
|
10932
11332
|
);
|
|
10933
11333
|
console.log(ui.green(` \u2713 ${fm.id}`));
|
|
@@ -10949,15 +11349,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
10949
11349
|
}
|
|
10950
11350
|
|
|
10951
11351
|
// src/commands/memory-digest.ts
|
|
10952
|
-
import { existsSync as
|
|
10953
|
-
import { writeFile as
|
|
11352
|
+
import { existsSync as existsSync58 } from "fs";
|
|
11353
|
+
import { writeFile as writeFile25 } from "fs/promises";
|
|
10954
11354
|
import path36 from "path";
|
|
10955
11355
|
import "commander";
|
|
10956
11356
|
import {
|
|
10957
11357
|
deriveConfidence as deriveConfidence12,
|
|
10958
11358
|
findProjectRoot as findProjectRoot35,
|
|
10959
11359
|
getUsage as getUsage20,
|
|
10960
|
-
loadMemoriesFromDir as
|
|
11360
|
+
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
10961
11361
|
loadUsageIndex as loadUsageIndex25,
|
|
10962
11362
|
resolveHaivePaths as resolveHaivePaths32
|
|
10963
11363
|
} from "@hiveai/core";
|
|
@@ -10974,7 +11374,7 @@ function registerMemoryDigest(program2) {
|
|
|
10974
11374
|
).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10975
11375
|
const root = findProjectRoot35(opts.dir);
|
|
10976
11376
|
const paths = resolveHaivePaths32(root);
|
|
10977
|
-
if (!
|
|
11377
|
+
if (!existsSync58(paths.memoriesDir)) {
|
|
10978
11378
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
10979
11379
|
process.exitCode = 1;
|
|
10980
11380
|
return;
|
|
@@ -10982,7 +11382,7 @@ function registerMemoryDigest(program2) {
|
|
|
10982
11382
|
const days = Math.max(1, Number(opts.days ?? 7));
|
|
10983
11383
|
const scopeFilter = opts.scope ?? "team";
|
|
10984
11384
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
10985
|
-
const all = await
|
|
11385
|
+
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
10986
11386
|
const usage = await loadUsageIndex25(paths);
|
|
10987
11387
|
const recent = all.filter(({ memory: mem }) => {
|
|
10988
11388
|
const fm = mem.frontmatter;
|
|
@@ -11047,7 +11447,7 @@ function registerMemoryDigest(program2) {
|
|
|
11047
11447
|
const digest = lines.join("\n");
|
|
11048
11448
|
if (opts.out) {
|
|
11049
11449
|
const outPath = path36.resolve(process.cwd(), opts.out);
|
|
11050
|
-
await
|
|
11450
|
+
await writeFile25(outPath, digest, "utf8");
|
|
11051
11451
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
11052
11452
|
} else {
|
|
11053
11453
|
console.log(digest);
|
|
@@ -11056,22 +11456,22 @@ function registerMemoryDigest(program2) {
|
|
|
11056
11456
|
}
|
|
11057
11457
|
|
|
11058
11458
|
// src/commands/session-end.ts
|
|
11059
|
-
import { writeFile as
|
|
11060
|
-
import { existsSync as
|
|
11459
|
+
import { writeFile as writeFile26, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
|
|
11460
|
+
import { existsSync as existsSync59 } from "fs";
|
|
11061
11461
|
import { spawn as spawn4 } from "child_process";
|
|
11062
11462
|
import path37 from "path";
|
|
11063
11463
|
import "commander";
|
|
11064
11464
|
import {
|
|
11065
11465
|
buildFrontmatter as buildFrontmatter10,
|
|
11066
11466
|
findProjectRoot as findProjectRoot36,
|
|
11067
|
-
loadMemoriesFromDir as
|
|
11068
|
-
memoryFilePath as
|
|
11467
|
+
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
11468
|
+
memoryFilePath as memoryFilePath10,
|
|
11069
11469
|
resolveHaivePaths as resolveHaivePaths33,
|
|
11070
|
-
serializeMemory as
|
|
11470
|
+
serializeMemory as serializeMemory23
|
|
11071
11471
|
} from "@hiveai/core";
|
|
11072
11472
|
async function buildAutoRecap(paths) {
|
|
11073
11473
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11074
|
-
if (!
|
|
11474
|
+
if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
|
|
11075
11475
|
const raw = await readFile17(obsFile, "utf8").catch(() => "");
|
|
11076
11476
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
11077
11477
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -11262,7 +11662,7 @@ function registerSessionEnd(session2) {
|
|
|
11262
11662
|
).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11263
11663
|
const root = findProjectRoot36(opts.dir);
|
|
11264
11664
|
const paths = resolveHaivePaths33(root);
|
|
11265
|
-
if (!
|
|
11665
|
+
if (!existsSync59(paths.haiveDir)) {
|
|
11266
11666
|
if (opts.auto || opts.quiet) return;
|
|
11267
11667
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11268
11668
|
process.exitCode = 1;
|
|
@@ -11295,7 +11695,7 @@ function registerSessionEnd(session2) {
|
|
|
11295
11695
|
});
|
|
11296
11696
|
const topic = recapTopic2(scope, opts.module);
|
|
11297
11697
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
11298
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
11698
|
+
const missingPaths = filesTouched.filter((p) => !existsSync59(path37.resolve(root, p)));
|
|
11299
11699
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
11300
11700
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
11301
11701
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -11303,11 +11703,11 @@ function registerSessionEnd(session2) {
|
|
|
11303
11703
|
const cleanupObservations = async () => {
|
|
11304
11704
|
if (!opts.auto) return;
|
|
11305
11705
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11306
|
-
if (
|
|
11706
|
+
if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
|
|
11307
11707
|
});
|
|
11308
11708
|
};
|
|
11309
|
-
if (
|
|
11310
|
-
const existing = await
|
|
11709
|
+
if (existsSync59(paths.memoriesDir)) {
|
|
11710
|
+
const existing = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
11311
11711
|
const topicMatch = existing.find(
|
|
11312
11712
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
11313
11713
|
);
|
|
@@ -11323,7 +11723,7 @@ function registerSessionEnd(session2) {
|
|
|
11323
11723
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
11324
11724
|
}
|
|
11325
11725
|
};
|
|
11326
|
-
await
|
|
11726
|
+
await writeFile26(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
|
|
11327
11727
|
await cleanupObservations();
|
|
11328
11728
|
if (!opts.quiet) {
|
|
11329
11729
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
@@ -11343,9 +11743,9 @@ function registerSessionEnd(session2) {
|
|
|
11343
11743
|
topic,
|
|
11344
11744
|
status: "validated"
|
|
11345
11745
|
});
|
|
11346
|
-
const file =
|
|
11746
|
+
const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
11347
11747
|
await mkdir15(path37.dirname(file), { recursive: true });
|
|
11348
|
-
await
|
|
11748
|
+
await writeFile26(file, serializeMemory23({ frontmatter, body }), "utf8");
|
|
11349
11749
|
await cleanupObservations();
|
|
11350
11750
|
if (!opts.quiet) {
|
|
11351
11751
|
ui.success(`Session recap created`);
|
|
@@ -11368,7 +11768,7 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
11368
11768
|
}
|
|
11369
11769
|
|
|
11370
11770
|
// src/commands/snapshot.ts
|
|
11371
|
-
import { existsSync as
|
|
11771
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11372
11772
|
import { readdir as readdir4 } from "fs/promises";
|
|
11373
11773
|
import path38 from "path";
|
|
11374
11774
|
import "commander";
|
|
@@ -11403,14 +11803,14 @@ function registerSnapshot(program2) {
|
|
|
11403
11803
|
).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11404
11804
|
const root = findProjectRoot37(opts.dir);
|
|
11405
11805
|
const paths = resolveHaivePaths34(root);
|
|
11406
|
-
if (!
|
|
11806
|
+
if (!existsSync60(paths.haiveDir)) {
|
|
11407
11807
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
11408
11808
|
process.exitCode = 1;
|
|
11409
11809
|
return;
|
|
11410
11810
|
}
|
|
11411
11811
|
if (opts.list) {
|
|
11412
11812
|
const contractsDir = path38.join(paths.haiveDir, "contracts");
|
|
11413
|
-
if (!
|
|
11813
|
+
if (!existsSync60(contractsDir)) {
|
|
11414
11814
|
console.log(ui.dim("No contract snapshots found."));
|
|
11415
11815
|
return;
|
|
11416
11816
|
}
|
|
@@ -11534,18 +11934,18 @@ function detectFormat(filePath) {
|
|
|
11534
11934
|
}
|
|
11535
11935
|
|
|
11536
11936
|
// src/commands/hub.ts
|
|
11537
|
-
import { existsSync as
|
|
11538
|
-
import { mkdir as mkdir16, readFile as readFile18, writeFile as
|
|
11937
|
+
import { existsSync as existsSync61 } from "fs";
|
|
11938
|
+
import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile27, copyFile } from "fs/promises";
|
|
11539
11939
|
import path39 from "path";
|
|
11540
11940
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
11541
11941
|
import "commander";
|
|
11542
11942
|
import {
|
|
11543
11943
|
findProjectRoot as findProjectRoot38,
|
|
11544
11944
|
loadConfig as loadConfig8,
|
|
11545
|
-
loadMemoriesFromDir as
|
|
11945
|
+
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
11546
11946
|
resolveHaivePaths as resolveHaivePaths35,
|
|
11547
11947
|
saveConfig as saveConfig3,
|
|
11548
|
-
serializeMemory as
|
|
11948
|
+
serializeMemory as serializeMemory24
|
|
11549
11949
|
} from "@hiveai/core";
|
|
11550
11950
|
function registerHub(program2) {
|
|
11551
11951
|
const hub = program2.command("hub").description(
|
|
@@ -11568,7 +11968,7 @@ function registerHub(program2) {
|
|
|
11568
11968
|
}
|
|
11569
11969
|
const sharedDir = path39.join(absPath, ".ai", "memories", "shared");
|
|
11570
11970
|
await mkdir16(sharedDir, { recursive: true });
|
|
11571
|
-
await
|
|
11971
|
+
await writeFile27(
|
|
11572
11972
|
path39.join(absPath, ".ai", "README.md"),
|
|
11573
11973
|
`# hAIve Team Knowledge Hub
|
|
11574
11974
|
|
|
@@ -11590,7 +11990,7 @@ haive hub pull # import into a project
|
|
|
11590
11990
|
`,
|
|
11591
11991
|
"utf8"
|
|
11592
11992
|
);
|
|
11593
|
-
await
|
|
11993
|
+
await writeFile27(
|
|
11594
11994
|
path39.join(absPath, ".gitignore"),
|
|
11595
11995
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
11596
11996
|
"utf8"
|
|
@@ -11636,7 +12036,7 @@ Next steps:
|
|
|
11636
12036
|
return;
|
|
11637
12037
|
}
|
|
11638
12038
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11639
|
-
if (!
|
|
12039
|
+
if (!existsSync61(hubRoot)) {
|
|
11640
12040
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
11641
12041
|
process.exitCode = 1;
|
|
11642
12042
|
return;
|
|
@@ -11644,7 +12044,7 @@ Next steps:
|
|
|
11644
12044
|
const projectName = path39.basename(root);
|
|
11645
12045
|
const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
11646
12046
|
await mkdir16(destDir, { recursive: true });
|
|
11647
|
-
const all = await
|
|
12047
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11648
12048
|
const shared = all.filter(
|
|
11649
12049
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
11650
12050
|
!memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
@@ -11662,7 +12062,7 @@ Next steps:
|
|
|
11662
12062
|
const fm = memory2.frontmatter;
|
|
11663
12063
|
const fileName = `${fm.id}.md`;
|
|
11664
12064
|
const destPath = path39.join(destDir, fileName);
|
|
11665
|
-
await
|
|
12065
|
+
await writeFile27(destPath, serializeMemory24(memory2), "utf8");
|
|
11666
12066
|
pushed++;
|
|
11667
12067
|
}
|
|
11668
12068
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
@@ -11706,7 +12106,7 @@ Next steps:
|
|
|
11706
12106
|
}
|
|
11707
12107
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11708
12108
|
const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
|
|
11709
|
-
if (!
|
|
12109
|
+
if (!existsSync61(hubSharedDir)) {
|
|
11710
12110
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
11711
12111
|
return;
|
|
11712
12112
|
}
|
|
@@ -11761,7 +12161,7 @@ Next steps:
|
|
|
11761
12161
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
11762
12162
|
);
|
|
11763
12163
|
const sharedDir = path39.join(paths.memoriesDir, "shared");
|
|
11764
|
-
if (
|
|
12164
|
+
if (existsSync61(sharedDir)) {
|
|
11765
12165
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11766
12166
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
11767
12167
|
console.log(`
|
|
@@ -11773,7 +12173,7 @@ Next steps:
|
|
|
11773
12173
|
} else {
|
|
11774
12174
|
console.log(ui.dim(" No imported shared memories yet."));
|
|
11775
12175
|
}
|
|
11776
|
-
const all = await
|
|
12176
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11777
12177
|
const outgoing = all.filter(
|
|
11778
12178
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
11779
12179
|
);
|
|
@@ -11783,20 +12183,20 @@ Next steps:
|
|
|
11783
12183
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
11784
12184
|
}
|
|
11785
12185
|
void readFile18;
|
|
11786
|
-
void
|
|
12186
|
+
void writeFile27;
|
|
11787
12187
|
void saveConfig3;
|
|
11788
12188
|
});
|
|
11789
12189
|
}
|
|
11790
12190
|
|
|
11791
12191
|
// src/commands/stats.ts
|
|
11792
12192
|
import "commander";
|
|
11793
|
-
import { existsSync as
|
|
11794
|
-
import { mkdir as mkdir17, writeFile as
|
|
12193
|
+
import { existsSync as existsSync63 } from "fs";
|
|
12194
|
+
import { mkdir as mkdir17, writeFile as writeFile28 } from "fs/promises";
|
|
11795
12195
|
import path40 from "path";
|
|
11796
12196
|
import {
|
|
11797
12197
|
aggregateUsage,
|
|
11798
12198
|
findProjectRoot as findProjectRoot39,
|
|
11799
|
-
loadMemoriesFromDir as
|
|
12199
|
+
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
11800
12200
|
loadUsageIndex as loadUsageIndex26,
|
|
11801
12201
|
parseSince,
|
|
11802
12202
|
readUsageEvents as readUsageEvents2,
|
|
@@ -11868,8 +12268,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11868
12268
|
const size = await usageLogSize(paths);
|
|
11869
12269
|
let events = await readUsageEvents2(paths);
|
|
11870
12270
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
11871
|
-
if (
|
|
11872
|
-
const mems = await
|
|
12271
|
+
if (existsSync63(paths.memoriesDir)) {
|
|
12272
|
+
const mems = await loadMemoriesFromDir31(paths.memoriesDir);
|
|
11873
12273
|
for (const { memory: memory2 } of mems) {
|
|
11874
12274
|
const fm = memory2.frontmatter;
|
|
11875
12275
|
if (fm.type === "session_recap") memoryCount.total_skipped_session++;
|
|
@@ -11911,7 +12311,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11911
12311
|
top_memory_reads: memoryHitsLeader,
|
|
11912
12312
|
roi_hints: roiHints
|
|
11913
12313
|
};
|
|
11914
|
-
await
|
|
12314
|
+
await writeFile28(outAbs, JSON.stringify(payload, null, 2), "utf8");
|
|
11915
12315
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
11916
12316
|
}
|
|
11917
12317
|
async function renderMemoryHits(paths, opts) {
|
|
@@ -12088,8 +12488,8 @@ function summarize(name, t0, payload, notes) {
|
|
|
12088
12488
|
}
|
|
12089
12489
|
|
|
12090
12490
|
// src/commands/benchmark.ts
|
|
12091
|
-
import { existsSync as
|
|
12092
|
-
import { readdir as readdir5, readFile as readFile19, writeFile as
|
|
12491
|
+
import { existsSync as existsSync64 } from "fs";
|
|
12492
|
+
import { readdir as readdir5, readFile as readFile19, writeFile as writeFile29 } from "fs/promises";
|
|
12093
12493
|
import path41 from "path";
|
|
12094
12494
|
import "commander";
|
|
12095
12495
|
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
@@ -12106,7 +12506,7 @@ function registerBenchmark(program2) {
|
|
|
12106
12506
|
const markdown = renderMarkdown(root, summary, rows);
|
|
12107
12507
|
if (opts.out) {
|
|
12108
12508
|
const outFile = path41.isAbsolute(opts.out) ? opts.out : path41.join(root, opts.out);
|
|
12109
|
-
await
|
|
12509
|
+
await writeFile29(outFile, markdown, "utf8");
|
|
12110
12510
|
ui.success(`wrote ${path41.relative(process.cwd(), outFile)}`);
|
|
12111
12511
|
return;
|
|
12112
12512
|
}
|
|
@@ -12136,14 +12536,14 @@ function resolveBenchmarkRoot(dir) {
|
|
|
12136
12536
|
return path41.join(projectRoot, candidate);
|
|
12137
12537
|
}
|
|
12138
12538
|
async function collectRows(root) {
|
|
12139
|
-
if (!
|
|
12539
|
+
if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
12140
12540
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
12141
12541
|
const rows = [];
|
|
12142
12542
|
for (const entry of entries) {
|
|
12143
12543
|
if (!entry.isDirectory()) continue;
|
|
12144
12544
|
const fixtureDir = path41.join(root, entry.name);
|
|
12145
12545
|
const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
12146
|
-
if (!
|
|
12546
|
+
if (!existsSync64(reportFile)) continue;
|
|
12147
12547
|
const report = await readFile19(reportFile, "utf8");
|
|
12148
12548
|
rows.push(parseAgentReport(entry.name, report));
|
|
12149
12549
|
}
|
|
@@ -12233,14 +12633,15 @@ function escapeRegExp(value) {
|
|
|
12233
12633
|
}
|
|
12234
12634
|
|
|
12235
12635
|
// src/commands/eval.ts
|
|
12236
|
-
import { readFile as readFile20, writeFile as
|
|
12237
|
-
import { existsSync as
|
|
12636
|
+
import { mkdir as mkdir18, readFile as readFile20, writeFile as writeFile30 } from "fs/promises";
|
|
12637
|
+
import { existsSync as existsSync65 } from "fs";
|
|
12238
12638
|
import path43 from "path";
|
|
12239
12639
|
import "commander";
|
|
12240
12640
|
import {
|
|
12241
12641
|
aggregateRetrieval,
|
|
12242
12642
|
aggregateSensors,
|
|
12243
12643
|
buildReport,
|
|
12644
|
+
compareEvalReports,
|
|
12244
12645
|
findProjectRoot as findProjectRoot42,
|
|
12245
12646
|
resolveHaivePaths as resolveHaivePaths38,
|
|
12246
12647
|
scoreRetrievalCase,
|
|
@@ -12250,10 +12651,10 @@ import {
|
|
|
12250
12651
|
function registerEval(program2) {
|
|
12251
12652
|
program2.command("eval").description(
|
|
12252
12653
|
"Rigorous, repeatable quality eval: do the right memories surface (retrieval) and do the right sensors fire (catch-rate)? Emits a chiffr\xE9 0\u2013100 score. Uses .ai/eval cases via --spec, or auto-synthesizes cases from anchored memories."
|
|
12253
|
-
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12654
|
+
).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("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12254
12655
|
const root = findProjectRoot42(opts.dir);
|
|
12255
12656
|
const paths = resolveHaivePaths38(root);
|
|
12256
|
-
if (!
|
|
12657
|
+
if (!existsSync65(paths.memoriesDir)) {
|
|
12257
12658
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12258
12659
|
process.exitCode = 1;
|
|
12259
12660
|
return;
|
|
@@ -12285,30 +12686,87 @@ function registerEval(program2) {
|
|
|
12285
12686
|
sensorAgg = aggregateSensors(results);
|
|
12286
12687
|
}
|
|
12287
12688
|
const report = buildReport(retrievalAgg, sensorAgg);
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12689
|
+
const baselineFile = opts.baselineFile ? path43.isAbsolute(opts.baselineFile) ? opts.baselineFile : path43.join(root, opts.baselineFile) : path43.join(root, ".ai", "eval", "baseline.json");
|
|
12690
|
+
if (opts.baseline) {
|
|
12691
|
+
const snapshot = {
|
|
12692
|
+
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12693
|
+
k,
|
|
12694
|
+
spec_source: resolvedSpec.source,
|
|
12695
|
+
report
|
|
12696
|
+
};
|
|
12697
|
+
await mkdir18(path43.dirname(baselineFile), { recursive: true });
|
|
12698
|
+
await writeFile30(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
|
|
12699
|
+
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path43.relative(root, baselineFile)}`);
|
|
12700
|
+
}
|
|
12701
|
+
let delta = null;
|
|
12702
|
+
if (opts.compare || opts.regressionGate) {
|
|
12703
|
+
if (!existsSync65(baselineFile)) {
|
|
12704
|
+
if (opts.regressionGate) {
|
|
12705
|
+
if (!opts.json) ui.info(`No baseline at ${path43.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`haive eval --baseline\` to enable it.`);
|
|
12706
|
+
} else {
|
|
12707
|
+
ui.error(`No baseline at ${path43.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
|
|
12708
|
+
process.exitCode = 1;
|
|
12709
|
+
return;
|
|
12710
|
+
}
|
|
12296
12711
|
} else {
|
|
12297
|
-
|
|
12712
|
+
const snapshot = JSON.parse(await readFile20(baselineFile, "utf8"));
|
|
12713
|
+
delta = compareEvalReports(snapshot.report, report);
|
|
12298
12714
|
}
|
|
12299
12715
|
}
|
|
12300
|
-
if (opts.
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
process.exitCode = 1;
|
|
12305
|
-
} else if (report.score < threshold) {
|
|
12306
|
-
ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
|
|
12307
|
-
process.exitCode = 1;
|
|
12308
|
-
}
|
|
12716
|
+
if (opts.json) {
|
|
12717
|
+
console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report, ...delta ? { delta } : {} }, null, 2));
|
|
12718
|
+
applyExitGates(opts, report, delta);
|
|
12719
|
+
return;
|
|
12309
12720
|
}
|
|
12721
|
+
if (delta) {
|
|
12722
|
+
console.log(renderDelta(delta));
|
|
12723
|
+
}
|
|
12724
|
+
const md = renderMarkdown2(root, k, resolvedSpec.source, report);
|
|
12725
|
+
if (opts.out) {
|
|
12726
|
+
const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
|
|
12727
|
+
await writeFile30(outFile, md, "utf8");
|
|
12728
|
+
ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
|
|
12729
|
+
} else {
|
|
12730
|
+
console.log(md);
|
|
12731
|
+
}
|
|
12732
|
+
applyExitGates(opts, report, delta);
|
|
12310
12733
|
});
|
|
12311
12734
|
}
|
|
12735
|
+
function applyExitGates(opts, report, delta) {
|
|
12736
|
+
if (opts.failUnder !== void 0) {
|
|
12737
|
+
const threshold = Number(opts.failUnder);
|
|
12738
|
+
if (Number.isNaN(threshold)) {
|
|
12739
|
+
ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
|
|
12740
|
+
process.exitCode = 1;
|
|
12741
|
+
} else if (report.score < threshold) {
|
|
12742
|
+
ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
|
|
12743
|
+
process.exitCode = 1;
|
|
12744
|
+
}
|
|
12745
|
+
}
|
|
12746
|
+
if ((opts.failOnRegression || opts.regressionGate) && delta?.regressed) {
|
|
12747
|
+
ui.error(`eval score regressed ${delta.score.baseline} \u2192 ${delta.score.current} (\u0394 ${delta.score.delta}) vs baseline`);
|
|
12748
|
+
process.exitCode = 1;
|
|
12749
|
+
}
|
|
12750
|
+
}
|
|
12751
|
+
function fmtDelta(label, m) {
|
|
12752
|
+
if (!m) return null;
|
|
12753
|
+
const sign = m.delta > 0 ? "+" : "";
|
|
12754
|
+
const arrow = m.delta > 0 ? ui.green("\u25B2") : m.delta < 0 ? ui.red("\u25BC") : ui.dim("=");
|
|
12755
|
+
return ` ${arrow} ${label.padEnd(12)} ${m.baseline} \u2192 ${m.current} ${ui.dim(`(${sign}${m.delta})`)}`;
|
|
12756
|
+
}
|
|
12757
|
+
function renderDelta(delta) {
|
|
12758
|
+
const verdict = delta.regressed ? ui.red("REGRESSED") : delta.improved ? ui.green("IMPROVED") : ui.dim("UNCHANGED");
|
|
12759
|
+
const lines = [ui.bold(`Eval vs baseline \u2014 ${verdict}`)];
|
|
12760
|
+
for (const line of [
|
|
12761
|
+
fmtDelta("score", delta.score),
|
|
12762
|
+
fmtDelta("mean recall", delta.mean_recall),
|
|
12763
|
+
fmtDelta("mrr", delta.mrr),
|
|
12764
|
+
fmtDelta("catch-rate", delta.catch_rate)
|
|
12765
|
+
]) {
|
|
12766
|
+
if (line) lines.push(line);
|
|
12767
|
+
}
|
|
12768
|
+
return lines.join("\n");
|
|
12769
|
+
}
|
|
12312
12770
|
async function resolveSpec(opts, root, memoriesDir) {
|
|
12313
12771
|
if (opts.spec) {
|
|
12314
12772
|
const file = path43.resolve(opts.spec);
|
|
@@ -12316,10 +12774,10 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
12316
12774
|
return { spec: JSON.parse(raw), source: file };
|
|
12317
12775
|
}
|
|
12318
12776
|
const defaultSpec = path43.join(root, ".ai", "eval", "spec.json");
|
|
12319
|
-
if (
|
|
12777
|
+
if (existsSync65(defaultSpec)) {
|
|
12320
12778
|
const raw = await readFile20(defaultSpec, "utf8");
|
|
12321
12779
|
const explicit = JSON.parse(raw);
|
|
12322
|
-
const memories2 = await
|
|
12780
|
+
const memories2 = await loadMemoriesFromDir27(memoriesDir);
|
|
12323
12781
|
const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
|
|
12324
12782
|
return {
|
|
12325
12783
|
spec: {
|
|
@@ -12329,7 +12787,7 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
12329
12787
|
source: ".ai/eval/spec.json + synthesized anchored retrieval"
|
|
12330
12788
|
};
|
|
12331
12789
|
}
|
|
12332
|
-
const memories = await
|
|
12790
|
+
const memories = await loadMemoriesFromDir27(memoriesDir);
|
|
12333
12791
|
return {
|
|
12334
12792
|
spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
|
|
12335
12793
|
source: "synthesized anchored retrieval"
|
|
@@ -12419,8 +12877,8 @@ function renderMarkdown2(root, k, source, report) {
|
|
|
12419
12877
|
}
|
|
12420
12878
|
|
|
12421
12879
|
// src/commands/memory-suggest.ts
|
|
12422
|
-
import { mkdir as
|
|
12423
|
-
import { existsSync as
|
|
12880
|
+
import { mkdir as mkdir19, writeFile as writeFile31 } from "fs/promises";
|
|
12881
|
+
import { existsSync as existsSync66 } from "fs";
|
|
12424
12882
|
import path44 from "path";
|
|
12425
12883
|
import "commander";
|
|
12426
12884
|
import {
|
|
@@ -12428,12 +12886,12 @@ import {
|
|
|
12428
12886
|
buildFrontmatter as buildFrontmatter11,
|
|
12429
12887
|
findProjectRoot as findProjectRoot43,
|
|
12430
12888
|
loadConfig as loadConfig9,
|
|
12431
|
-
loadMemoriesFromDir as
|
|
12432
|
-
memoryFilePath as
|
|
12889
|
+
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
12890
|
+
memoryFilePath as memoryFilePath11,
|
|
12433
12891
|
parseSince as parseSince2,
|
|
12434
12892
|
readUsageEvents as readUsageEvents3,
|
|
12435
12893
|
resolveHaivePaths as resolveHaivePaths39,
|
|
12436
|
-
serializeMemory as
|
|
12894
|
+
serializeMemory as serializeMemory25
|
|
12437
12895
|
} from "@hiveai/core";
|
|
12438
12896
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
12439
12897
|
"mem_search",
|
|
@@ -12499,7 +12957,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12499
12957
|
}
|
|
12500
12958
|
const created = [];
|
|
12501
12959
|
const skipped = [];
|
|
12502
|
-
const existing =
|
|
12960
|
+
const existing = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
|
|
12503
12961
|
for (const s of top) {
|
|
12504
12962
|
const slug = slugify2(s.query);
|
|
12505
12963
|
if (!slug) {
|
|
@@ -12521,13 +12979,13 @@ function registerMemorySuggest(memory2) {
|
|
|
12521
12979
|
status
|
|
12522
12980
|
});
|
|
12523
12981
|
const body = renderTemplate(s, fm.id, status);
|
|
12524
|
-
const file =
|
|
12525
|
-
await
|
|
12526
|
-
if (
|
|
12982
|
+
const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
|
|
12983
|
+
await mkdir19(path44.dirname(file), { recursive: true });
|
|
12984
|
+
if (existsSync66(file)) {
|
|
12527
12985
|
skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
|
|
12528
12986
|
continue;
|
|
12529
12987
|
}
|
|
12530
|
-
await
|
|
12988
|
+
await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
|
|
12531
12989
|
created.push({ id: fm.id, file: path44.relative(root, file), query: s.query });
|
|
12532
12990
|
}
|
|
12533
12991
|
if (opts.json) {
|
|
@@ -12626,8 +13084,8 @@ function truncate2(text, max) {
|
|
|
12626
13084
|
}
|
|
12627
13085
|
|
|
12628
13086
|
// src/commands/memory-archive.ts
|
|
12629
|
-
import { existsSync as
|
|
12630
|
-
import { writeFile as
|
|
13087
|
+
import { existsSync as existsSync67 } from "fs";
|
|
13088
|
+
import { writeFile as writeFile33 } from "fs/promises";
|
|
12631
13089
|
import path45 from "path";
|
|
12632
13090
|
import "commander";
|
|
12633
13091
|
import {
|
|
@@ -12635,10 +13093,10 @@ import {
|
|
|
12635
13093
|
getUsage as getUsage21,
|
|
12636
13094
|
retirementSignal as retirementSignal2,
|
|
12637
13095
|
loadConfig as loadConfig10,
|
|
12638
|
-
loadMemoriesFromDir as
|
|
13096
|
+
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
12639
13097
|
loadUsageIndex as loadUsageIndex27,
|
|
12640
13098
|
resolveHaivePaths as resolveHaivePaths40,
|
|
12641
|
-
serializeMemory as
|
|
13099
|
+
serializeMemory as serializeMemory26
|
|
12642
13100
|
} from "@hiveai/core";
|
|
12643
13101
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
12644
13102
|
function registerMemoryArchive(memory2) {
|
|
@@ -12647,7 +13105,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12647
13105
|
).option("--since <window>", "minimum age since last read (e.g. '180d', '6m'). Default: enforcement.decayAfterDays or 180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--unread", "decay by unread-age ALONE (ignore anchor status) \u2014 more aggressive corpus hygiene", false).option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12648
13106
|
const root = findProjectRoot44(opts.dir);
|
|
12649
13107
|
const paths = resolveHaivePaths40(root);
|
|
12650
|
-
if (!
|
|
13108
|
+
if (!existsSync67(paths.memoriesDir)) {
|
|
12651
13109
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12652
13110
|
process.exitCode = 1;
|
|
12653
13111
|
return;
|
|
@@ -12661,7 +13119,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12661
13119
|
return;
|
|
12662
13120
|
}
|
|
12663
13121
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
12664
|
-
const all = await
|
|
13122
|
+
const all = await loadMemoriesFromDir34(paths.memoriesDir);
|
|
12665
13123
|
const usage = await loadUsageIndex27(paths);
|
|
12666
13124
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
12667
13125
|
const candidates = [];
|
|
@@ -12672,7 +13130,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12672
13130
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
12673
13131
|
const retired = retirementSignal2(fm, mem.body);
|
|
12674
13132
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
12675
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
13133
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path45.join(paths.root, p)));
|
|
12676
13134
|
const isAnchorless = !hasAnyAnchor;
|
|
12677
13135
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
12678
13136
|
const u = getUsage21(usage, fm.id);
|
|
@@ -12721,7 +13179,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12721
13179
|
if (!found) continue;
|
|
12722
13180
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
12723
13181
|
try {
|
|
12724
|
-
await
|
|
13182
|
+
await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
12725
13183
|
archived++;
|
|
12726
13184
|
} catch (err) {
|
|
12727
13185
|
if (!opts.json) {
|
|
@@ -12747,8 +13205,8 @@ function parseDays(input) {
|
|
|
12747
13205
|
}
|
|
12748
13206
|
|
|
12749
13207
|
// src/commands/doctor.ts
|
|
12750
|
-
import { existsSync as
|
|
12751
|
-
import { readFile as readFile21, stat, writeFile as
|
|
13208
|
+
import { existsSync as existsSync68, statSync as statSync2 } from "fs";
|
|
13209
|
+
import { readFile as readFile21, stat, writeFile as writeFile34 } from "fs/promises";
|
|
12752
13210
|
import path46 from "path";
|
|
12753
13211
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
12754
13212
|
import "commander";
|
|
@@ -12759,7 +13217,7 @@ import {
|
|
|
12759
13217
|
isStackPackSeed as isStackPackSeed4,
|
|
12760
13218
|
loadCodeMap as loadCodeMap7,
|
|
12761
13219
|
loadConfig as loadConfig11,
|
|
12762
|
-
loadMemoriesFromDir as
|
|
13220
|
+
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
12763
13221
|
loadUsageIndex as loadUsageIndex28,
|
|
12764
13222
|
readUsageEvents as readUsageEvents4,
|
|
12765
13223
|
resolveHaivePaths as resolveHaivePaths41
|
|
@@ -12774,7 +13232,7 @@ function registerDoctor(program2) {
|
|
|
12774
13232
|
const findings = [];
|
|
12775
13233
|
const repairs = [];
|
|
12776
13234
|
const config = await loadConfig11(paths);
|
|
12777
|
-
if (!
|
|
13235
|
+
if (!existsSync68(paths.haiveDir)) {
|
|
12778
13236
|
findings.push({
|
|
12779
13237
|
severity: "error",
|
|
12780
13238
|
code: "not-initialized",
|
|
@@ -12795,7 +13253,7 @@ function registerDoctor(program2) {
|
|
|
12795
13253
|
})
|
|
12796
13254
|
);
|
|
12797
13255
|
}
|
|
12798
|
-
if (!
|
|
13256
|
+
if (!existsSync68(paths.projectContext)) {
|
|
12799
13257
|
findings.push({
|
|
12800
13258
|
severity: "warn",
|
|
12801
13259
|
code: "no-project-context",
|
|
@@ -12803,8 +13261,8 @@ function registerDoctor(program2) {
|
|
|
12803
13261
|
fix: "haive init"
|
|
12804
13262
|
});
|
|
12805
13263
|
} else {
|
|
12806
|
-
const { readFile:
|
|
12807
|
-
const content = await
|
|
13264
|
+
const { readFile: readFile26 } = await import("fs/promises");
|
|
13265
|
+
const content = await readFile26(paths.projectContext, "utf8");
|
|
12808
13266
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
12809
13267
|
if (isTemplate) {
|
|
12810
13268
|
findings.push({
|
|
@@ -12824,7 +13282,7 @@ function registerDoctor(program2) {
|
|
|
12824
13282
|
});
|
|
12825
13283
|
}
|
|
12826
13284
|
}
|
|
12827
|
-
const memories =
|
|
13285
|
+
const memories = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
|
|
12828
13286
|
const now = Date.now();
|
|
12829
13287
|
if (memories.length === 0) {
|
|
12830
13288
|
findings.push({
|
|
@@ -12976,10 +13434,10 @@ function registerDoctor(program2) {
|
|
|
12976
13434
|
if (config.enforcement?.requireBriefingFirst) {
|
|
12977
13435
|
const claudeSettings = path46.join(root, ".claude", "settings.local.json");
|
|
12978
13436
|
let hasClaudeEnforcement = false;
|
|
12979
|
-
if (
|
|
13437
|
+
if (existsSync68(claudeSettings)) {
|
|
12980
13438
|
try {
|
|
12981
|
-
const { readFile:
|
|
12982
|
-
const raw = await
|
|
13439
|
+
const { readFile: readFile26 } = await import("fs/promises");
|
|
13440
|
+
const raw = await readFile26(claudeSettings, "utf8");
|
|
12983
13441
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
12984
13442
|
} catch {
|
|
12985
13443
|
hasClaudeEnforcement = false;
|
|
@@ -13002,7 +13460,7 @@ function registerDoctor(program2) {
|
|
|
13002
13460
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
13003
13461
|
});
|
|
13004
13462
|
}
|
|
13005
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
13463
|
+
findings.push(...await collectInstallFindings(root, "0.13.0"));
|
|
13006
13464
|
findings.push(...await collectToolchainFindings(root));
|
|
13007
13465
|
try {
|
|
13008
13466
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -13010,7 +13468,7 @@ function registerDoctor(program2) {
|
|
|
13010
13468
|
timeout: 3e3,
|
|
13011
13469
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13012
13470
|
}).trim();
|
|
13013
|
-
const cliVersion = "0.
|
|
13471
|
+
const cliVersion = "0.13.0";
|
|
13014
13472
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
13015
13473
|
findings.push({
|
|
13016
13474
|
severity: "warn",
|
|
@@ -13032,14 +13490,14 @@ npm uninstall -g @hiveai/mcp`
|
|
|
13032
13490
|
];
|
|
13033
13491
|
const staleConfigs = [];
|
|
13034
13492
|
for (const cfgPath of configPaths) {
|
|
13035
|
-
if (!
|
|
13493
|
+
if (!existsSync68(cfgPath)) continue;
|
|
13036
13494
|
try {
|
|
13037
13495
|
const raw = await readFile21(cfgPath, "utf8");
|
|
13038
13496
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
13039
13497
|
staleConfigs.push(path46.relative(root, cfgPath));
|
|
13040
13498
|
if (opts.fix && !opts.dryRun) {
|
|
13041
13499
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
13042
|
-
await
|
|
13500
|
+
await writeFile34(cfgPath, updated, "utf8");
|
|
13043
13501
|
}
|
|
13044
13502
|
}
|
|
13045
13503
|
} catch {
|
|
@@ -13328,7 +13786,7 @@ which -a haive`
|
|
|
13328
13786
|
];
|
|
13329
13787
|
for (const rel of integrationFiles) {
|
|
13330
13788
|
const file = path46.join(root, rel);
|
|
13331
|
-
if (!
|
|
13789
|
+
if (!existsSync68(file)) continue;
|
|
13332
13790
|
const text = await readFile21(file, "utf8").catch(() => "");
|
|
13333
13791
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
13334
13792
|
const version = versionForBinary(bin);
|
|
@@ -13376,7 +13834,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
13376
13834
|
const isHaiveWorkspace = (await readJson(path46.join(root, "package.json")))?.name === "haive-monorepo";
|
|
13377
13835
|
if (!isHaiveWorkspace) return findings;
|
|
13378
13836
|
const cliDist = path46.join(root, "packages/cli/dist/index.js");
|
|
13379
|
-
if (!
|
|
13837
|
+
if (!existsSync68(cliDist)) {
|
|
13380
13838
|
findings.push({
|
|
13381
13839
|
severity: "warn",
|
|
13382
13840
|
code: "workspace-dist-missing",
|
|
@@ -13400,7 +13858,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
13400
13858
|
"packages/core/src/index.ts",
|
|
13401
13859
|
"packages/mcp/src/server.ts",
|
|
13402
13860
|
"packages/cli/src/index.ts"
|
|
13403
|
-
].map((rel) => path46.join(root, rel)).filter(
|
|
13861
|
+
].map((rel) => path46.join(root, rel)).filter(existsSync68);
|
|
13404
13862
|
if (sourceFiles.length > 0) {
|
|
13405
13863
|
const distMtime = statSync2(cliDist).mtimeMs;
|
|
13406
13864
|
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
@@ -13489,7 +13947,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
13489
13947
|
}
|
|
13490
13948
|
}
|
|
13491
13949
|
async function readJson(file) {
|
|
13492
|
-
if (!
|
|
13950
|
+
if (!existsSync68(file)) return null;
|
|
13493
13951
|
try {
|
|
13494
13952
|
return JSON.parse(await readFile21(file, "utf8"));
|
|
13495
13953
|
} catch {
|
|
@@ -13560,11 +14018,11 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
13560
14018
|
}
|
|
13561
14019
|
|
|
13562
14020
|
// src/commands/playback.ts
|
|
13563
|
-
import { existsSync as
|
|
14021
|
+
import { existsSync as existsSync69 } from "fs";
|
|
13564
14022
|
import "commander";
|
|
13565
14023
|
import {
|
|
13566
14024
|
findProjectRoot as findProjectRoot46,
|
|
13567
|
-
loadMemoriesFromDir as
|
|
14025
|
+
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
13568
14026
|
parseSince as parseSince3,
|
|
13569
14027
|
readUsageEvents as readUsageEvents5,
|
|
13570
14028
|
resolveHaivePaths as resolveHaivePaths42
|
|
@@ -13590,7 +14048,7 @@ function registerPlayback(program2) {
|
|
|
13590
14048
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
13591
14049
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
13592
14050
|
const sessions = bucketSessions(filtered, gapMs);
|
|
13593
|
-
const all =
|
|
14051
|
+
const all = existsSync69(paths.memoriesDir) ? await loadMemoriesFromDir36(paths.memoriesDir) : [];
|
|
13594
14052
|
const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
|
|
13595
14053
|
const enriched = sessions.map((s, i) => {
|
|
13596
14054
|
const startMs = Date.parse(s.start);
|
|
@@ -13832,11 +14290,11 @@ function runCommand3(cmd, args, cwd) {
|
|
|
13832
14290
|
}
|
|
13833
14291
|
|
|
13834
14292
|
// src/commands/welcome.ts
|
|
13835
|
-
import { existsSync as
|
|
14293
|
+
import { existsSync as existsSync70 } from "fs";
|
|
13836
14294
|
import "commander";
|
|
13837
14295
|
import {
|
|
13838
14296
|
findProjectRoot as findProjectRoot48,
|
|
13839
|
-
loadMemoriesFromDir as
|
|
14297
|
+
loadMemoriesFromDir as loadMemoriesFromDir37,
|
|
13840
14298
|
resolveHaivePaths as resolveHaivePaths44
|
|
13841
14299
|
} from "@hiveai/core";
|
|
13842
14300
|
var TYPE_RANK = {
|
|
@@ -13854,12 +14312,12 @@ function registerWelcome(program2) {
|
|
|
13854
14312
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13855
14313
|
const root = findProjectRoot48(opts.dir);
|
|
13856
14314
|
const paths = resolveHaivePaths44(root);
|
|
13857
|
-
if (!
|
|
14315
|
+
if (!existsSync70(paths.memoriesDir)) {
|
|
13858
14316
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
13859
14317
|
process.exitCode = 1;
|
|
13860
14318
|
return;
|
|
13861
14319
|
}
|
|
13862
|
-
const all = await
|
|
14320
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
13863
14321
|
const team = all.filter(
|
|
13864
14322
|
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
13865
14323
|
);
|
|
@@ -13926,7 +14384,7 @@ function registerResolveProject(program2) {
|
|
|
13926
14384
|
}
|
|
13927
14385
|
|
|
13928
14386
|
// src/commands/runtime-journal.ts
|
|
13929
|
-
import { existsSync as
|
|
14387
|
+
import { existsSync as existsSync71 } from "fs";
|
|
13930
14388
|
import path48 from "path";
|
|
13931
14389
|
import "commander";
|
|
13932
14390
|
import {
|
|
@@ -13952,7 +14410,7 @@ function registerRuntime(program2) {
|
|
|
13952
14410
|
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13953
14411
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13954
14412
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13955
|
-
if (!
|
|
14413
|
+
if (!existsSync71(paths.haiveDir)) {
|
|
13956
14414
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
13957
14415
|
process.exitCode = 1;
|
|
13958
14416
|
return;
|
|
@@ -13967,7 +14425,7 @@ function registerRuntime(program2) {
|
|
|
13967
14425
|
}
|
|
13968
14426
|
|
|
13969
14427
|
// src/commands/memory-timeline.ts
|
|
13970
|
-
import { existsSync as
|
|
14428
|
+
import { existsSync as existsSync73 } from "fs";
|
|
13971
14429
|
import path49 from "path";
|
|
13972
14430
|
import "commander";
|
|
13973
14431
|
import {
|
|
@@ -13986,13 +14444,13 @@ function registerMemoryTimeline(memory2) {
|
|
|
13986
14444
|
}
|
|
13987
14445
|
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
13988
14446
|
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
13989
|
-
if (!
|
|
14447
|
+
if (!existsSync73(paths.memoriesDir)) {
|
|
13990
14448
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13991
14449
|
process.exitCode = 1;
|
|
13992
14450
|
return;
|
|
13993
14451
|
}
|
|
13994
14452
|
const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13995
|
-
const all = await
|
|
14453
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
13996
14454
|
const { entries, notice } = collectTimelineEntries2(all, {
|
|
13997
14455
|
memoryId: opts.id,
|
|
13998
14456
|
topic: opts.topic,
|
|
@@ -14004,7 +14462,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
14004
14462
|
}
|
|
14005
14463
|
|
|
14006
14464
|
// src/commands/memory-conflict-candidates.ts
|
|
14007
|
-
import { existsSync as
|
|
14465
|
+
import { existsSync as existsSync74 } from "fs";
|
|
14008
14466
|
import path50 from "path";
|
|
14009
14467
|
import "commander";
|
|
14010
14468
|
import {
|
|
@@ -14029,7 +14487,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14029
14487
|
).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
|
|
14030
14488
|
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
14031
14489
|
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
14032
|
-
if (!
|
|
14490
|
+
if (!existsSync74(paths.memoriesDir)) {
|
|
14033
14491
|
ui.error("No memories \u2014 run `haive init`.");
|
|
14034
14492
|
process.exitCode = 1;
|
|
14035
14493
|
return;
|
|
@@ -14039,7 +14497,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14039
14497
|
const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
|
|
14040
14498
|
const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
|
|
14041
14499
|
const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
|
|
14042
|
-
const all = await
|
|
14500
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
14043
14501
|
const lexical = findLexicalConflictPairs2(all, {
|
|
14044
14502
|
sinceDays,
|
|
14045
14503
|
types: parseTypes(opts.types),
|
|
@@ -14065,8 +14523,8 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
14065
14523
|
|
|
14066
14524
|
// src/commands/enforce.ts
|
|
14067
14525
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
14068
|
-
import { existsSync as
|
|
14069
|
-
import { chmod as chmod2, mkdir as
|
|
14526
|
+
import { existsSync as existsSync75, statSync as statSync3 } from "fs";
|
|
14527
|
+
import { chmod as chmod2, mkdir as mkdir20, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile35 } from "fs/promises";
|
|
14070
14528
|
import path51 from "path";
|
|
14071
14529
|
import "commander";
|
|
14072
14530
|
import {
|
|
@@ -14075,7 +14533,7 @@ import {
|
|
|
14075
14533
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
14076
14534
|
isFreshIsoDate,
|
|
14077
14535
|
loadConfig as loadConfig13,
|
|
14078
|
-
loadMemoriesFromDir as
|
|
14536
|
+
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
14079
14537
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
14080
14538
|
readRecentBriefingMarker,
|
|
14081
14539
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
@@ -14094,7 +14552,7 @@ function registerEnforce(program2) {
|
|
|
14094
14552
|
enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
|
|
14095
14553
|
const root = findProjectRoot52(opts.dir);
|
|
14096
14554
|
const paths = resolveHaivePaths48(root);
|
|
14097
|
-
await
|
|
14555
|
+
await mkdir20(paths.haiveDir, { recursive: true });
|
|
14098
14556
|
const current = await loadConfig13(paths);
|
|
14099
14557
|
await saveConfig4(paths, {
|
|
14100
14558
|
...current,
|
|
@@ -14140,14 +14598,14 @@ function registerEnforce(program2) {
|
|
|
14140
14598
|
const root = findProjectRoot52(opts.dir);
|
|
14141
14599
|
const paths = resolveHaivePaths48(root);
|
|
14142
14600
|
const cacheDir = path51.join(paths.haiveDir, ".cache");
|
|
14143
|
-
if (
|
|
14601
|
+
if (existsSync75(cacheDir)) {
|
|
14144
14602
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
14145
14603
|
else {
|
|
14146
14604
|
const removed = await cleanupCacheDir(cacheDir);
|
|
14147
14605
|
ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
14148
14606
|
}
|
|
14149
14607
|
}
|
|
14150
|
-
if (
|
|
14608
|
+
if (existsSync75(paths.runtimeDir)) {
|
|
14151
14609
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
14152
14610
|
else {
|
|
14153
14611
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
@@ -14172,8 +14630,8 @@ function registerEnforce(program2) {
|
|
|
14172
14630
|
const root = resolveRoot(opts.dir, payload);
|
|
14173
14631
|
if (!root) return;
|
|
14174
14632
|
const paths = resolveHaivePaths48(root);
|
|
14175
|
-
if (!
|
|
14176
|
-
await
|
|
14633
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
14634
|
+
await mkdir20(paths.runtimeDir, { recursive: true });
|
|
14177
14635
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
14178
14636
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
14179
14637
|
await applyLightweightRepairs(root, paths);
|
|
@@ -14235,7 +14693,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14235
14693
|
const root = resolveRoot(opts.dir, payload);
|
|
14236
14694
|
if (!root) return;
|
|
14237
14695
|
const paths = resolveHaivePaths48(root);
|
|
14238
|
-
if (!
|
|
14696
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
14239
14697
|
if (!isWriteLikeTool(payload)) return;
|
|
14240
14698
|
const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
|
|
14241
14699
|
if (ok) {
|
|
@@ -14279,7 +14737,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14279
14737
|
async function buildFinishReport(dir) {
|
|
14280
14738
|
const root = findProjectRoot52(dir);
|
|
14281
14739
|
const paths = resolveHaivePaths48(root);
|
|
14282
|
-
const initialized =
|
|
14740
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
14283
14741
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14284
14742
|
const mode = config.enforcement?.mode ?? "strict";
|
|
14285
14743
|
const findings = [];
|
|
@@ -14477,7 +14935,7 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
14477
14935
|
async function runWithEnforcement(command, args, opts) {
|
|
14478
14936
|
const root = findProjectRoot52(opts.dir);
|
|
14479
14937
|
const paths = resolveHaivePaths48(root);
|
|
14480
|
-
if (!
|
|
14938
|
+
if (!existsSync75(paths.haiveDir)) {
|
|
14481
14939
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
14482
14940
|
process.exit(1);
|
|
14483
14941
|
}
|
|
@@ -14547,7 +15005,7 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14547
15005
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
14548
15006
|
});
|
|
14549
15007
|
const dir = path51.join(paths.runtimeDir, "enforcement", "briefings");
|
|
14550
|
-
await
|
|
15008
|
+
await mkdir20(dir, { recursive: true });
|
|
14551
15009
|
const file = path51.join(dir, `${sessionId}.md`);
|
|
14552
15010
|
const parts = [
|
|
14553
15011
|
"# hAIve Briefing",
|
|
@@ -14566,13 +15024,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14566
15024
|
if (briefing.setup_warnings.length > 0) {
|
|
14567
15025
|
parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
|
|
14568
15026
|
}
|
|
14569
|
-
await
|
|
15027
|
+
await writeFile35(file, parts.join("\n") + "\n", "utf8");
|
|
14570
15028
|
return file;
|
|
14571
15029
|
}
|
|
14572
15030
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
14573
15031
|
const root = findProjectRoot52(dir);
|
|
14574
15032
|
const paths = resolveHaivePaths48(root);
|
|
14575
|
-
const initialized =
|
|
15033
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
14576
15034
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14577
15035
|
if (initialized) await applyLightweightRepairs(root, paths);
|
|
14578
15036
|
const mode = config.enforcement?.mode ?? "strict";
|
|
@@ -14603,7 +15061,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14603
15061
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14604
15062
|
});
|
|
14605
15063
|
}
|
|
14606
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
15064
|
+
findings.push(...await inspectIntegrationVersions(root, "0.13.0"));
|
|
14607
15065
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14608
15066
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14609
15067
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14673,8 +15131,8 @@ function withCategories(report) {
|
|
|
14673
15131
|
};
|
|
14674
15132
|
}
|
|
14675
15133
|
async function hasRecentSessionRecap(paths) {
|
|
14676
|
-
if (!
|
|
14677
|
-
const all = await
|
|
15134
|
+
if (!existsSync75(paths.memoriesDir)) return false;
|
|
15135
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14678
15136
|
return all.some(({ memory: memory2 }) => {
|
|
14679
15137
|
const fm = memory2.frontmatter;
|
|
14680
15138
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -14682,8 +15140,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
14682
15140
|
});
|
|
14683
15141
|
}
|
|
14684
15142
|
async function verifyMemoryPolicy(paths, config) {
|
|
14685
|
-
if (!
|
|
14686
|
-
const all = await
|
|
15143
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15144
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14687
15145
|
const findings = [];
|
|
14688
15146
|
const staleImportant = [];
|
|
14689
15147
|
let verified = 0;
|
|
@@ -14720,12 +15178,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
14720
15178
|
return findings;
|
|
14721
15179
|
}
|
|
14722
15180
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
14723
|
-
if (!
|
|
15181
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
14724
15182
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
14725
15183
|
if (changedFiles.length === 0) {
|
|
14726
15184
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
14727
15185
|
}
|
|
14728
|
-
const all = await
|
|
15186
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
14729
15187
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
14730
15188
|
const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
14731
15189
|
const fm = memory2.frontmatter;
|
|
@@ -14829,7 +15287,7 @@ function isGeneratedArtifactStatusLine(line) {
|
|
|
14829
15287
|
}
|
|
14830
15288
|
async function cleanupRuntimeDir(runtimeDir) {
|
|
14831
15289
|
let removed = 0;
|
|
14832
|
-
await
|
|
15290
|
+
await mkdir20(runtimeDir, { recursive: true });
|
|
14833
15291
|
const entries = await readdir6(runtimeDir, { withFileTypes: true }).catch(() => []);
|
|
14834
15292
|
for (const entry of entries) {
|
|
14835
15293
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
@@ -14840,9 +15298,9 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14840
15298
|
await rm3(path51.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
14841
15299
|
removed++;
|
|
14842
15300
|
}
|
|
14843
|
-
await
|
|
14844
|
-
if (!
|
|
14845
|
-
await
|
|
15301
|
+
await writeFile35(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
15302
|
+
if (!existsSync75(path51.join(runtimeDir, "README.md"))) {
|
|
15303
|
+
await writeFile35(
|
|
14846
15304
|
path51.join(runtimeDir, "README.md"),
|
|
14847
15305
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
14848
15306
|
"utf8"
|
|
@@ -14852,14 +15310,14 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14852
15310
|
}
|
|
14853
15311
|
async function cleanupCacheDir(cacheDir) {
|
|
14854
15312
|
let removed = 0;
|
|
14855
|
-
await
|
|
15313
|
+
await mkdir20(cacheDir, { recursive: true });
|
|
14856
15314
|
const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
14857
15315
|
for (const entry of entries) {
|
|
14858
15316
|
if (entry.name === ".gitignore") continue;
|
|
14859
15317
|
await rm3(path51.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
14860
15318
|
removed++;
|
|
14861
15319
|
}
|
|
14862
|
-
await
|
|
15320
|
+
await writeFile35(path51.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
14863
15321
|
return removed;
|
|
14864
15322
|
}
|
|
14865
15323
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -14884,7 +15342,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
14884
15342
|
const findings = [];
|
|
14885
15343
|
for (const rel of files) {
|
|
14886
15344
|
const file = path51.join(root, rel);
|
|
14887
|
-
if (!
|
|
15345
|
+
if (!existsSync75(file)) continue;
|
|
14888
15346
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14889
15347
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
14890
15348
|
const version = versionForBinary2(bin);
|
|
@@ -14994,7 +15452,7 @@ async function resolveCiDiffRange(root) {
|
|
|
14994
15452
|
}
|
|
14995
15453
|
async function resolveGithubEventRange(root) {
|
|
14996
15454
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
14997
|
-
if (!eventPath || !
|
|
15455
|
+
if (!eventPath || !existsSync75(eventPath)) return null;
|
|
14998
15456
|
try {
|
|
14999
15457
|
const event = JSON.parse(await readFile23(eventPath, "utf8"));
|
|
15000
15458
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -15277,11 +15735,11 @@ function buildScore(findings, threshold = 80) {
|
|
|
15277
15735
|
}
|
|
15278
15736
|
async function installGitEnforcement(root) {
|
|
15279
15737
|
const hooksDir = path51.join(root, ".git", "hooks");
|
|
15280
|
-
if (!
|
|
15738
|
+
if (!existsSync75(path51.join(root, ".git"))) {
|
|
15281
15739
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
15282
15740
|
return;
|
|
15283
15741
|
}
|
|
15284
|
-
await
|
|
15742
|
+
await mkdir20(hooksDir, { recursive: true });
|
|
15285
15743
|
const hooks = [
|
|
15286
15744
|
{
|
|
15287
15745
|
name: "pre-commit",
|
|
@@ -15300,17 +15758,17 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
15300
15758
|
];
|
|
15301
15759
|
for (const hook of hooks) {
|
|
15302
15760
|
const file = path51.join(hooksDir, hook.name);
|
|
15303
|
-
if (
|
|
15761
|
+
if (existsSync75(file)) {
|
|
15304
15762
|
const current = await readFile23(file, "utf8").catch(() => "");
|
|
15305
15763
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
15306
|
-
await
|
|
15764
|
+
await writeFile35(file, hook.body, "utf8");
|
|
15307
15765
|
} else {
|
|
15308
|
-
await
|
|
15766
|
+
await writeFile35(file, `${current.trimEnd()}
|
|
15309
15767
|
|
|
15310
15768
|
${hook.body}`, "utf8");
|
|
15311
15769
|
}
|
|
15312
15770
|
} else {
|
|
15313
|
-
await
|
|
15771
|
+
await writeFile35(file, hook.body, "utf8");
|
|
15314
15772
|
}
|
|
15315
15773
|
await chmod2(file, 493);
|
|
15316
15774
|
}
|
|
@@ -15318,12 +15776,12 @@ ${hook.body}`, "utf8");
|
|
|
15318
15776
|
}
|
|
15319
15777
|
async function installCiEnforcement(root) {
|
|
15320
15778
|
const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
15321
|
-
await
|
|
15322
|
-
if (
|
|
15779
|
+
await mkdir20(path51.dirname(workflowPath), { recursive: true });
|
|
15780
|
+
if (existsSync75(workflowPath)) {
|
|
15323
15781
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
15324
15782
|
return;
|
|
15325
15783
|
}
|
|
15326
|
-
await
|
|
15784
|
+
await writeFile35(workflowPath, `name: haive-enforcement
|
|
15327
15785
|
|
|
15328
15786
|
on:
|
|
15329
15787
|
pull_request:
|
|
@@ -15451,11 +15909,11 @@ function normalizeToolPath(file, root) {
|
|
|
15451
15909
|
return path51.relative(root, normalized).replace(/\\/g, "/");
|
|
15452
15910
|
}
|
|
15453
15911
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
15454
|
-
if (!
|
|
15912
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15455
15913
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
15456
15914
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
15457
15915
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
15458
|
-
const all = await
|
|
15916
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
15459
15917
|
return all.filter(({ memory: memory2 }) => {
|
|
15460
15918
|
const fm = memory2.frontmatter;
|
|
15461
15919
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -15525,19 +15983,19 @@ function registerRun(program2) {
|
|
|
15525
15983
|
|
|
15526
15984
|
// src/commands/sensors.ts
|
|
15527
15985
|
import { execFile as execFile2 } from "child_process";
|
|
15528
|
-
import { existsSync as
|
|
15529
|
-
import { chmod as chmod3, mkdir as
|
|
15986
|
+
import { existsSync as existsSync76 } from "fs";
|
|
15987
|
+
import { chmod as chmod3, mkdir as mkdir21, readFile as readFile24, writeFile as writeFile36 } from "fs/promises";
|
|
15530
15988
|
import path53 from "path";
|
|
15531
15989
|
import { promisify as promisify2 } from "util";
|
|
15532
15990
|
import "commander";
|
|
15533
15991
|
import {
|
|
15534
15992
|
findProjectRoot as findProjectRoot53,
|
|
15535
15993
|
isRetiredMemory as isRetiredMemory3,
|
|
15536
|
-
loadMemoriesFromDir as
|
|
15994
|
+
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
15537
15995
|
resolveHaivePaths as resolveHaivePaths49,
|
|
15538
15996
|
runSensors as runSensors2,
|
|
15539
15997
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15540
|
-
serializeMemory as
|
|
15998
|
+
serializeMemory as serializeMemory27
|
|
15541
15999
|
} from "@hiveai/core";
|
|
15542
16000
|
var exec2 = promisify2(execFile2);
|
|
15543
16001
|
function registerSensors(program2) {
|
|
@@ -15608,7 +16066,7 @@ function registerSensors(program2) {
|
|
|
15608
16066
|
}
|
|
15609
16067
|
const root = findProjectRoot53(opts.dir);
|
|
15610
16068
|
const paths = resolveHaivePaths49(root);
|
|
15611
|
-
const loaded =
|
|
16069
|
+
const loaded = existsSync76(paths.memoriesDir) ? await loadMemoriesFromDir39(paths.memoriesDir) : [];
|
|
15612
16070
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
15613
16071
|
if (!found) {
|
|
15614
16072
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -15628,7 +16086,7 @@ function registerSensors(program2) {
|
|
|
15628
16086
|
},
|
|
15629
16087
|
body: found.memory.body
|
|
15630
16088
|
};
|
|
15631
|
-
await
|
|
16089
|
+
await writeFile36(found.filePath, serializeMemory27(next), "utf8");
|
|
15632
16090
|
ui.success(`Updated ${id}: sensor severity=${severity}`);
|
|
15633
16091
|
if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
|
|
15634
16092
|
ui.info(`message=${sensor.message}`);
|
|
@@ -15644,10 +16102,10 @@ function registerSensors(program2) {
|
|
|
15644
16102
|
const paths = resolveHaivePaths49(root);
|
|
15645
16103
|
const rows = await sensorRows(paths);
|
|
15646
16104
|
const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
|
|
15647
|
-
await
|
|
16105
|
+
await mkdir21(outDir, { recursive: true });
|
|
15648
16106
|
const outPath = path53.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
15649
16107
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
15650
|
-
await
|
|
16108
|
+
await writeFile36(outPath, content, "utf8");
|
|
15651
16109
|
if (format === "grep") await chmod3(outPath, 493);
|
|
15652
16110
|
ui.success(`Exported ${rows.length} sensor(s): ${path53.relative(root, outPath)}`);
|
|
15653
16111
|
});
|
|
@@ -15670,8 +16128,8 @@ async function sensorRows(paths) {
|
|
|
15670
16128
|
});
|
|
15671
16129
|
}
|
|
15672
16130
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
15673
|
-
if (!
|
|
15674
|
-
const loaded = await
|
|
16131
|
+
if (!existsSync76(paths.memoriesDir)) return [];
|
|
16132
|
+
const loaded = await loadMemoriesFromDir39(paths.memoriesDir);
|
|
15675
16133
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
15676
16134
|
const sensor = memory2.frontmatter.sensor;
|
|
15677
16135
|
if (!sensor) return false;
|
|
@@ -15711,9 +16169,305 @@ function shellQuote(value) {
|
|
|
15711
16169
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
15712
16170
|
}
|
|
15713
16171
|
|
|
16172
|
+
// src/commands/ingest.ts
|
|
16173
|
+
import { existsSync as existsSync77 } from "fs";
|
|
16174
|
+
import { mkdir as mkdir23, readFile as readFile25, writeFile as writeFile37 } from "fs/promises";
|
|
16175
|
+
import path54 from "path";
|
|
16176
|
+
import "commander";
|
|
16177
|
+
import {
|
|
16178
|
+
draftsFromFindings as draftsFromFindings2,
|
|
16179
|
+
filterNewDrafts as filterNewDrafts2,
|
|
16180
|
+
findProjectRoot as findProjectRoot54,
|
|
16181
|
+
loadMemoriesFromDir as loadMemoriesFromDir40,
|
|
16182
|
+
memoryFilePath as memoryFilePath12,
|
|
16183
|
+
parseFindings as parseFindings2,
|
|
16184
|
+
resolveHaivePaths as resolveHaivePaths50,
|
|
16185
|
+
serializeMemory as serializeMemory28
|
|
16186
|
+
} from "@hiveai/core";
|
|
16187
|
+
var SEVERITIES = ["info", "minor", "major", "critical", "blocker"];
|
|
16188
|
+
function registerIngest(program2) {
|
|
16189
|
+
program2.command("ingest").description(
|
|
16190
|
+
"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 `sonar-api` fetches issues live over plain HTTPS from any SonarQube/SonarCloud instance \u2014\n no MCP or special setup required, just a URL + token you provide (or SONAR_HOST_URL /\n SONAR_TOKEN env). If you don't use it, file-based ingest works exactly the same.\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 haive ingest --from sonar-api --sonar-component my_project --min-severity major\n"
|
|
16191
|
+
).argument("[file]", "path to the findings report JSON (required for --from sarif|sonar)").requiredOption("--from <format>", "report format: sarif | sonar | sonar-api").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("--sonar-url <url>", "SonarQube base URL for --from sonar-api (or env SONAR_HOST_URL)").option("--sonar-token <token>", "SonarQube token for --from sonar-api (or env SONAR_TOKEN)").option("--sonar-component <key>", "SonarQube project/component key for --from sonar-api").option("--sonar-branch <branch>", "optional SonarQube branch for --from sonar-api").option("-d, --dir <dir>", "project root").action(async (file, opts) => {
|
|
16192
|
+
const format = opts.from;
|
|
16193
|
+
if (format !== "sarif" && format !== "sonar" && format !== "sonar-api") {
|
|
16194
|
+
ui.error("--from must be sarif, sonar, or sonar-api");
|
|
16195
|
+
process.exitCode = 1;
|
|
16196
|
+
return;
|
|
16197
|
+
}
|
|
16198
|
+
if (opts.type && opts.type !== "gotcha" && opts.type !== "convention") {
|
|
16199
|
+
ui.error("--type must be gotcha or convention");
|
|
16200
|
+
process.exitCode = 1;
|
|
16201
|
+
return;
|
|
16202
|
+
}
|
|
16203
|
+
if (opts.minSeverity && !SEVERITIES.includes(opts.minSeverity)) {
|
|
16204
|
+
ui.error(`--min-severity must be one of: ${SEVERITIES.join(", ")}`);
|
|
16205
|
+
process.exitCode = 1;
|
|
16206
|
+
return;
|
|
16207
|
+
}
|
|
16208
|
+
const root = findProjectRoot54(opts.dir);
|
|
16209
|
+
const paths = resolveHaivePaths50(root);
|
|
16210
|
+
if (!existsSync77(paths.haiveDir)) {
|
|
16211
|
+
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
16212
|
+
process.exitCode = 1;
|
|
16213
|
+
return;
|
|
16214
|
+
}
|
|
16215
|
+
const parseFormat = format === "sarif" ? "sarif" : "sonar";
|
|
16216
|
+
let raw;
|
|
16217
|
+
if (format === "sonar-api") {
|
|
16218
|
+
const fetched = await fetchSonarIssues(opts);
|
|
16219
|
+
if (!fetched.ok) {
|
|
16220
|
+
ui.error(fetched.error);
|
|
16221
|
+
process.exitCode = 1;
|
|
16222
|
+
return;
|
|
16223
|
+
}
|
|
16224
|
+
raw = fetched.json;
|
|
16225
|
+
} else {
|
|
16226
|
+
if (!file) {
|
|
16227
|
+
ui.error(`--from ${format} needs a report file argument, e.g. \`haive ingest --from ${format} report.json\`.`);
|
|
16228
|
+
process.exitCode = 1;
|
|
16229
|
+
return;
|
|
16230
|
+
}
|
|
16231
|
+
const reportPath = path54.resolve(root, file);
|
|
16232
|
+
if (!existsSync77(reportPath)) {
|
|
16233
|
+
ui.error(`Report file not found: ${reportPath}`);
|
|
16234
|
+
process.exitCode = 1;
|
|
16235
|
+
return;
|
|
16236
|
+
}
|
|
16237
|
+
try {
|
|
16238
|
+
raw = await readFile25(reportPath, "utf8");
|
|
16239
|
+
} catch (err) {
|
|
16240
|
+
ui.error(`Could not read ${reportPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16241
|
+
process.exitCode = 1;
|
|
16242
|
+
return;
|
|
16243
|
+
}
|
|
16244
|
+
}
|
|
16245
|
+
let drafts;
|
|
16246
|
+
try {
|
|
16247
|
+
const findings = parseFindings2(parseFormat, raw);
|
|
16248
|
+
drafts = draftsFromFindings2(findings, {
|
|
16249
|
+
type: opts.type ?? "gotcha",
|
|
16250
|
+
scope: opts.scope ?? "team",
|
|
16251
|
+
module: opts.module,
|
|
16252
|
+
author: opts.author,
|
|
16253
|
+
...opts.minSeverity ? { minSeverity: opts.minSeverity } : {},
|
|
16254
|
+
...opts.limit ? { limit: Math.max(0, Number.parseInt(opts.limit, 10) || 0) } : {}
|
|
16255
|
+
});
|
|
16256
|
+
} catch (err) {
|
|
16257
|
+
ui.error(`Failed to parse ${format} report: ${err instanceof Error ? err.message : String(err)}`);
|
|
16258
|
+
process.exitCode = 1;
|
|
16259
|
+
return;
|
|
16260
|
+
}
|
|
16261
|
+
const existing = existsSync77(paths.memoriesDir) ? await loadMemoriesFromDir40(paths.memoriesDir) : [];
|
|
16262
|
+
const existingTopics = new Set(
|
|
16263
|
+
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
16264
|
+
);
|
|
16265
|
+
const fresh = filterNewDrafts2(drafts, existingTopics);
|
|
16266
|
+
const skipped = drafts.length - fresh.length;
|
|
16267
|
+
if (opts.json) {
|
|
16268
|
+
const created2 = [];
|
|
16269
|
+
if (!opts.dryRun) {
|
|
16270
|
+
for (const draft of fresh) created2.push(await writeDraft2(paths, draft));
|
|
16271
|
+
}
|
|
16272
|
+
console.log(
|
|
16273
|
+
JSON.stringify(
|
|
16274
|
+
{
|
|
16275
|
+
format,
|
|
16276
|
+
parsed: drafts.length,
|
|
16277
|
+
new: fresh.length,
|
|
16278
|
+
skipped_existing: skipped,
|
|
16279
|
+
dry_run: Boolean(opts.dryRun),
|
|
16280
|
+
drafts: fresh.map((d) => ({
|
|
16281
|
+
id: d.frontmatter.id,
|
|
16282
|
+
topic: d.topic,
|
|
16283
|
+
path: d.finding.path,
|
|
16284
|
+
rule: d.finding.ruleId,
|
|
16285
|
+
severity: d.finding.severity,
|
|
16286
|
+
has_sensor: d.has_sensor
|
|
16287
|
+
}))
|
|
16288
|
+
},
|
|
16289
|
+
null,
|
|
16290
|
+
2
|
|
16291
|
+
)
|
|
16292
|
+
);
|
|
16293
|
+
return;
|
|
16294
|
+
}
|
|
16295
|
+
console.log(
|
|
16296
|
+
ui.bold(
|
|
16297
|
+
`hAIve ingest (${format}) \u2014 ${drafts.length} finding(s), ${fresh.length} new` + (skipped > 0 ? `, ${skipped} already ingested` : "")
|
|
16298
|
+
)
|
|
16299
|
+
);
|
|
16300
|
+
if (fresh.length === 0) {
|
|
16301
|
+
ui.info("Nothing to ingest.");
|
|
16302
|
+
return;
|
|
16303
|
+
}
|
|
16304
|
+
for (const draft of fresh) {
|
|
16305
|
+
const sensorTag = draft.has_sensor ? ui.dim(" +sensor") : "";
|
|
16306
|
+
console.log(
|
|
16307
|
+
` \u2022 ${draft.finding.ruleId} ${ui.dim(`(${draft.finding.severity})`)} \u2192 ${draft.finding.path}${sensorTag}`
|
|
16308
|
+
);
|
|
16309
|
+
if (opts.dryRun) console.log(` ${ui.dim("would create:")} ${draft.frontmatter.id}`);
|
|
16310
|
+
}
|
|
16311
|
+
if (opts.dryRun) {
|
|
16312
|
+
ui.info(`Dry run \u2014 nothing written. Re-run without --dry-run to create ${fresh.length} proposed memory(ies).`);
|
|
16313
|
+
return;
|
|
16314
|
+
}
|
|
16315
|
+
let created = 0;
|
|
16316
|
+
for (const draft of fresh) {
|
|
16317
|
+
await writeDraft2(paths, draft);
|
|
16318
|
+
created++;
|
|
16319
|
+
}
|
|
16320
|
+
ui.success(`Created ${created} proposed memory(ies) under ${path54.relative(root, paths.memoriesDir)}/`);
|
|
16321
|
+
ui.info("Review with `haive memory pending`; promote sensors with `haive sensors promote <id> --yes`.");
|
|
16322
|
+
});
|
|
16323
|
+
}
|
|
16324
|
+
async function writeDraft2(paths, draft) {
|
|
16325
|
+
const file = memoryFilePath12(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
16326
|
+
await mkdir23(path54.dirname(file), { recursive: true });
|
|
16327
|
+
await writeFile37(file, serializeMemory28({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
16328
|
+
return file;
|
|
16329
|
+
}
|
|
16330
|
+
async function fetchSonarIssues(opts) {
|
|
16331
|
+
const baseUrl = (opts.sonarUrl ?? process.env.SONAR_HOST_URL ?? "").trim().replace(/\/+$/, "");
|
|
16332
|
+
const token = (opts.sonarToken ?? process.env.SONAR_TOKEN ?? "").trim();
|
|
16333
|
+
const component = (opts.sonarComponent ?? "").trim();
|
|
16334
|
+
if (!baseUrl) {
|
|
16335
|
+
return { ok: false, error: "--from sonar-api needs --sonar-url (or env SONAR_HOST_URL)." };
|
|
16336
|
+
}
|
|
16337
|
+
if (!token) {
|
|
16338
|
+
return { ok: false, error: "--from sonar-api needs --sonar-token (or env SONAR_TOKEN)." };
|
|
16339
|
+
}
|
|
16340
|
+
if (!component) {
|
|
16341
|
+
return { ok: false, error: "--from sonar-api needs --sonar-component <projectKey>." };
|
|
16342
|
+
}
|
|
16343
|
+
if (typeof fetch !== "function") {
|
|
16344
|
+
return { ok: false, error: "global fetch is unavailable \u2014 Node 18+ is required for --from sonar-api." };
|
|
16345
|
+
}
|
|
16346
|
+
const params = new URLSearchParams({ componentKeys: component, resolved: "false", ps: "500" });
|
|
16347
|
+
if (opts.sonarBranch) params.set("branch", opts.sonarBranch);
|
|
16348
|
+
const url = `${baseUrl}/api/issues/search?${params.toString()}`;
|
|
16349
|
+
try {
|
|
16350
|
+
const res = await fetch(url, {
|
|
16351
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }
|
|
16352
|
+
});
|
|
16353
|
+
if (!res.ok) {
|
|
16354
|
+
const hint = res.status === 401 || res.status === 403 ? " (check the token and its permissions)" : "";
|
|
16355
|
+
return { ok: false, error: `SonarQube API returned ${res.status} ${res.statusText}${hint}.` };
|
|
16356
|
+
}
|
|
16357
|
+
const json = await res.text();
|
|
16358
|
+
return { ok: true, json };
|
|
16359
|
+
} catch (err) {
|
|
16360
|
+
return {
|
|
16361
|
+
ok: false,
|
|
16362
|
+
error: `Could not reach SonarQube at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. File-based ingest (--from sonar) still works.`
|
|
16363
|
+
};
|
|
16364
|
+
}
|
|
16365
|
+
}
|
|
16366
|
+
|
|
16367
|
+
// src/commands/dashboard.ts
|
|
16368
|
+
import { existsSync as existsSync78 } from "fs";
|
|
16369
|
+
import "commander";
|
|
16370
|
+
import {
|
|
16371
|
+
buildDashboard,
|
|
16372
|
+
findProjectRoot as findProjectRoot55,
|
|
16373
|
+
loadMemoriesFromDir as loadMemoriesFromDir41,
|
|
16374
|
+
loadUsageIndex as loadUsageIndex29,
|
|
16375
|
+
resolveHaivePaths as resolveHaivePaths51
|
|
16376
|
+
} from "@hiveai/core";
|
|
16377
|
+
function registerDashboard(program2) {
|
|
16378
|
+
program2.command("dashboard").description(
|
|
16379
|
+
"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."
|
|
16380
|
+
).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) => {
|
|
16381
|
+
const root = findProjectRoot55(opts.dir);
|
|
16382
|
+
const paths = resolveHaivePaths51(root);
|
|
16383
|
+
if (!existsSync78(paths.haiveDir)) {
|
|
16384
|
+
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
16385
|
+
process.exitCode = 1;
|
|
16386
|
+
return;
|
|
16387
|
+
}
|
|
16388
|
+
const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
|
|
16389
|
+
const usage = await loadUsageIndex29(paths);
|
|
16390
|
+
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
16391
|
+
const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
|
|
16392
|
+
const report = buildDashboard(memories, usage, {
|
|
16393
|
+
top,
|
|
16394
|
+
...dormantDays !== void 0 && Number.isFinite(dormantDays) ? { dormantDays } : {}
|
|
16395
|
+
});
|
|
16396
|
+
if (opts.json) {
|
|
16397
|
+
console.log(JSON.stringify(report, null, 2));
|
|
16398
|
+
return;
|
|
16399
|
+
}
|
|
16400
|
+
renderDashboard(report);
|
|
16401
|
+
});
|
|
16402
|
+
}
|
|
16403
|
+
function renderDashboard(r) {
|
|
16404
|
+
const { inventory: inv, impact, sensors, health, decay, corpus } = r;
|
|
16405
|
+
console.log(ui.bold("hAIve dashboard"));
|
|
16406
|
+
console.log(
|
|
16407
|
+
` ${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`
|
|
16408
|
+
);
|
|
16409
|
+
console.log(` ${ui.dim("scopes:")} ${formatCounts(inv.by_scope)}`);
|
|
16410
|
+
console.log(` ${ui.dim("types: ")} ${formatCounts(inv.by_type)}`);
|
|
16411
|
+
console.log();
|
|
16412
|
+
console.log(ui.bold("Impact"));
|
|
16413
|
+
console.log(
|
|
16414
|
+
` ${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"}`
|
|
16415
|
+
);
|
|
16416
|
+
if (impact.top.length > 0) {
|
|
16417
|
+
console.log(ui.dim(" top by demonstrated utility:"));
|
|
16418
|
+
for (const row of impact.top.filter((x) => x.score > 0).slice(0, 8)) {
|
|
16419
|
+
console.log(
|
|
16420
|
+
` ${tierMark(row.tier)} ${row.score.toFixed(2)} ${row.id}` + (row.signals.length ? ui.dim(` [${row.signals.join(", ")}]`) : "")
|
|
16421
|
+
);
|
|
16422
|
+
}
|
|
16423
|
+
}
|
|
16424
|
+
console.log();
|
|
16425
|
+
console.log(ui.bold("Sensors"));
|
|
16426
|
+
console.log(
|
|
16427
|
+
` ${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"}`
|
|
16428
|
+
);
|
|
16429
|
+
for (const s of sensors.recently_fired.slice(0, 5)) {
|
|
16430
|
+
const marker = s.severity === "block" ? ui.red("\u2717") : ui.yellow("\u26A0");
|
|
16431
|
+
console.log(` ${marker} ${s.id} ${ui.dim(`last fired ${s.last_fired.slice(0, 10)}`)}`);
|
|
16432
|
+
}
|
|
16433
|
+
console.log();
|
|
16434
|
+
console.log(ui.bold("Health"));
|
|
16435
|
+
console.log(
|
|
16436
|
+
` stale ${warnNum(health.stale)} \xB7 anchorless ${warnNum(health.anchorless)} \xB7 pending ${health.pending} \xB7 prune candidates ${warnNum(health.prune_candidates)}`
|
|
16437
|
+
);
|
|
16438
|
+
if (health.anchorless > 0) {
|
|
16439
|
+
ui.info("Anchorless validated decisions/gotchas can't detect drift \u2014 add `paths`/`symbols`.");
|
|
16440
|
+
}
|
|
16441
|
+
console.log();
|
|
16442
|
+
console.log(ui.bold(`Decay (>${decay.decay_days}d)`));
|
|
16443
|
+
console.log(` ${decay.decaying} decaying memor${decay.decaying === 1 ? "y" : "ies"}`);
|
|
16444
|
+
for (const d of decay.top_dormant.slice(0, 5)) {
|
|
16445
|
+
const last = d.last_read_at ? d.last_read_at.slice(0, 10) : "never read";
|
|
16446
|
+
console.log(` ${ui.dim(String(d.age_days).padStart(4) + "d")} ${d.id} ${ui.dim(`(${last})`)}`);
|
|
16447
|
+
}
|
|
16448
|
+
if (health.prune_candidates > 0 || decay.decaying > 0) {
|
|
16449
|
+
console.log();
|
|
16450
|
+
ui.info("Review low-value memories with `haive memory impact` and `haive memory lint`.");
|
|
16451
|
+
}
|
|
16452
|
+
}
|
|
16453
|
+
function formatCounts(map) {
|
|
16454
|
+
const entries = Object.entries(map).sort((a, b) => b[1] - a[1]);
|
|
16455
|
+
if (entries.length === 0) return "none";
|
|
16456
|
+
return entries.map(([k, v]) => `${k} ${v}`).join(", ");
|
|
16457
|
+
}
|
|
16458
|
+
function tierMark(tier) {
|
|
16459
|
+
if (tier === "high") return ui.green("\u25CF");
|
|
16460
|
+
if (tier === "medium") return ui.yellow("\u25CF");
|
|
16461
|
+
if (tier === "dormant") return ui.dim("\u25CB");
|
|
16462
|
+
return "\xB7";
|
|
16463
|
+
}
|
|
16464
|
+
function warnNum(n) {
|
|
16465
|
+
return n > 0 ? ui.yellow(String(n)) : String(n);
|
|
16466
|
+
}
|
|
16467
|
+
|
|
15714
16468
|
// src/index.ts
|
|
15715
|
-
var program = new
|
|
15716
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
16469
|
+
var program = new Command58();
|
|
16470
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.13.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
15717
16471
|
registerInit(program);
|
|
15718
16472
|
registerWelcome(program);
|
|
15719
16473
|
registerResolveProject(program);
|
|
@@ -15722,6 +16476,8 @@ registerEnforce(program);
|
|
|
15722
16476
|
registerRun(program);
|
|
15723
16477
|
registerAgent(program);
|
|
15724
16478
|
registerSensors(program);
|
|
16479
|
+
registerIngest(program);
|
|
16480
|
+
registerDashboard(program);
|
|
15725
16481
|
registerMcp(program);
|
|
15726
16482
|
registerBriefing(program);
|
|
15727
16483
|
registerTui(program);
|