@hiveai/cli 0.9.3 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +651 -150
- 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 Command47 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync } from "fs";
|
|
@@ -197,7 +197,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
197
197
|
if (!f) continue;
|
|
198
198
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
199
199
|
}
|
|
200
|
-
let entries = [...counts.entries()].map(([
|
|
200
|
+
let entries = [...counts.entries()].map(([path45, changes]) => ({ path: path45, changes }));
|
|
201
201
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
202
202
|
if (lowerPaths.length > 0) {
|
|
203
203
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -1882,6 +1882,7 @@ var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
|
1882
1882
|
|
|
1883
1883
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
1884
1884
|
> or invoke the MCP prompt \`bootstrap_project\` in your AI client for a richer AI-generated version.
|
|
1885
|
+
> **AI agents**: use the **haive** MCP (\`get_briefing\` / \`mem_relevant_to\`) before substantive edits \u2014 see bridge files and \`.cursor/rules/haive-mcp-required.mdc\`.
|
|
1885
1886
|
|
|
1886
1887
|
## Architecture
|
|
1887
1888
|
TODO \u2014 high-level overview of the codebase.
|
|
@@ -1906,16 +1907,47 @@ Memories live under \`.ai/memories/\` (personal/team/module).
|
|
|
1906
1907
|
|
|
1907
1908
|
## hAIve \u2014 mandatory rules for AI agents
|
|
1908
1909
|
|
|
1910
|
+
Installing or initializing hAIve in this repository means **the team expects AI agents to drive work through hAIve**, not bypass it.
|
|
1911
|
+
|
|
1912
|
+
### MCP first (default \u2014 never optional unless the developer says so)
|
|
1913
|
+
The **haive** MCP server must be enabled in your client. Until it is unavailable (misconfiguration report to the developer), you **must** use it for substantive work:
|
|
1914
|
+
|
|
1915
|
+
- **Before** you edit project source files, configs (\`package.json\`, tooling), or workflows for the current goal, invoke \`get_briefing\` with a short **task** string (and **files** / **symbols** when you know them). If project context was already refreshed in this session and you only need ranked memories, you may call \`mem_relevant_to\` instead\u2014but **still** consult hAIve before trusting ungrounded guesses.
|
|
1916
|
+
- **Before** declaring a coding task finished, invoke the MCP prompt \`post_task\` when the session produced learnings or substantive changes worth capturing.
|
|
1917
|
+
|
|
1918
|
+
Do **not** treat these as nice-to-have: they are **default-required** for coding agents here.
|
|
1919
|
+
|
|
1909
1920
|
### Session rules
|
|
1910
|
-
- **Before** marking a task as done ("Done", "Finished", "C'est fait"), call the MCP prompt \`post_task\`.
|
|
1911
1921
|
- **Immediately** when an approach fails (wrong API, wrong pattern, wrong assumption), call \`mem_tried\` \u2014 do not wait until the end of the session.
|
|
1912
|
-
- When starting a new task, call \`get_briefing\` with the task description to load relevant memories and avoid repeating past mistakes.
|
|
1913
1922
|
|
|
1914
1923
|
### Safety rules \u2014 NEVER violate these
|
|
1915
1924
|
- If \`get_briefing\` returns an \`action_required\` list, **stop and show each item to the developer** before doing anything. Use the exact \`developer_message\` provided. Wait for explicit confirmation.
|
|
1916
1925
|
- **Never modify code autonomously** because of a breaking change detected in another project (dependency version bump, API contract change, removed field). Always ask first.
|
|
1917
1926
|
- When in doubt about a cross-repo change: ask, don't act.
|
|
1918
1927
|
`;
|
|
1928
|
+
var CURSOR_HAIVE_RULE_MDC = `---
|
|
1929
|
+
description: Require hAIve MCP (get_briefing / mem_relevant_to) before substantive repo edits
|
|
1930
|
+
alwaysApply: true
|
|
1931
|
+
---
|
|
1932
|
+
|
|
1933
|
+
# hAIve \u2014 required for agents
|
|
1934
|
+
|
|
1935
|
+
This repository uses **hAIve**. Running \`haive init\` means the team expects agents to **use the haive MCP server by default**, not skip it.
|
|
1936
|
+
|
|
1937
|
+
## Before you change the repo
|
|
1938
|
+
|
|
1939
|
+
1. Call **\`get_briefing\`** with \`task\` (and \`files\` / \`symbols\` when known), **or** \`mem_relevant_to\` if full project context is already loaded and you only need ranked memories.
|
|
1940
|
+
2. Then read/search the codebase as needed. Do not invert this order for non-trivial work.
|
|
1941
|
+
|
|
1942
|
+
## During / after the task
|
|
1943
|
+
|
|
1944
|
+
- On failure: **\`mem_tried\`** immediately.
|
|
1945
|
+
- Before closing a substantive session: MCP prompt **\`post_task\`** when there is something worth capturing.
|
|
1946
|
+
|
|
1947
|
+
## If haive MCP is missing
|
|
1948
|
+
|
|
1949
|
+
Tell the developer to enable the **haive** server (e.g. \`haive mcp --stdio\` in client config) and restart the client. Do not silently ignore hAIve.
|
|
1950
|
+
`;
|
|
1919
1951
|
var CI_WORKFLOW = `name: haive-sync
|
|
1920
1952
|
|
|
1921
1953
|
on:
|
|
@@ -2036,7 +2068,7 @@ jobs:
|
|
|
2036
2068
|
function registerInit(program2) {
|
|
2037
2069
|
program2.command("init").description(
|
|
2038
2070
|
"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."
|
|
2039
|
-
).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
|
|
2071
|
+
).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(
|
|
2040
2072
|
"--manual",
|
|
2041
2073
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
2042
2074
|
).option(
|
|
@@ -2066,6 +2098,7 @@ function registerInit(program2) {
|
|
|
2066
2098
|
await mkdir3(paths.teamDir, { recursive: true });
|
|
2067
2099
|
await mkdir3(paths.moduleDir, { recursive: true });
|
|
2068
2100
|
await mkdir3(paths.modulesContextDir, { recursive: true });
|
|
2101
|
+
await ensureAiRuntimeLayout(paths.runtimeDir);
|
|
2069
2102
|
if (!existsSync6(paths.projectContext)) {
|
|
2070
2103
|
if (wantBootstrap) {
|
|
2071
2104
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
@@ -2095,6 +2128,7 @@ function registerInit(program2) {
|
|
|
2095
2128
|
await writeBridge(root, "CLAUDE.md");
|
|
2096
2129
|
await writeBridge(root, ".cursorrules");
|
|
2097
2130
|
await writeBridge(root, path7.join(".github", "copilot-instructions.md"));
|
|
2131
|
+
await writeCursorHaiveRule(root);
|
|
2098
2132
|
}
|
|
2099
2133
|
const stacksToSeed = await resolveStacksToSeed(root, wantStack);
|
|
2100
2134
|
if (stacksToSeed.length > 0) {
|
|
@@ -2237,6 +2271,17 @@ async function resolveStacksToSeed(root, stackOpt) {
|
|
|
2237
2271
|
}
|
|
2238
2272
|
return stackOpt.split(",").map((s) => s.trim().toLowerCase()).filter(isValidStack);
|
|
2239
2273
|
}
|
|
2274
|
+
async function writeCursorHaiveRule(root) {
|
|
2275
|
+
const relPath = ".cursor/rules/haive-mcp-required.mdc";
|
|
2276
|
+
const target = path7.join(root, relPath);
|
|
2277
|
+
if (existsSync6(target)) {
|
|
2278
|
+
ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
await mkdir3(path7.dirname(target), { recursive: true });
|
|
2282
|
+
await writeFile3(target, CURSOR_HAIVE_RULE_MDC, "utf8");
|
|
2283
|
+
ui.success(`Created Cursor rule ${relPath}`);
|
|
2284
|
+
}
|
|
2240
2285
|
async function writeBridge(root, relPath) {
|
|
2241
2286
|
const target = path7.join(root, relPath);
|
|
2242
2287
|
if (existsSync6(target)) {
|
|
@@ -2247,6 +2292,29 @@ async function writeBridge(root, relPath) {
|
|
|
2247
2292
|
await writeFile3(target, BRIDGE_BODY, "utf8");
|
|
2248
2293
|
ui.success(`Created bridge ${relPath}`);
|
|
2249
2294
|
}
|
|
2295
|
+
var RUNTIME_README_BODY = `# .ai/.runtime \u2014 disposable local layer
|
|
2296
|
+
|
|
2297
|
+
Not team truth. Use for machine-local session notes or tooling scratch files.
|
|
2298
|
+
Official memories belong in .ai/memories/ (versioned in Git).
|
|
2299
|
+
Only .gitignore and this README are meant to commit; everything else stays untracked.
|
|
2300
|
+
|
|
2301
|
+
Session continuity (local): agents may append \`session-journal.ndjson\` via MCP \`runtime_journal_append\` or \`haive runtime journal append\`.
|
|
2302
|
+
`;
|
|
2303
|
+
var RUNTIME_GITIGNORE_BODY = `*
|
|
2304
|
+
!.gitignore
|
|
2305
|
+
!README.md
|
|
2306
|
+
`;
|
|
2307
|
+
async function ensureAiRuntimeLayout(runtimeDir) {
|
|
2308
|
+
await mkdir3(runtimeDir, { recursive: true });
|
|
2309
|
+
const gi = path7.join(runtimeDir, ".gitignore");
|
|
2310
|
+
if (!existsSync6(gi)) {
|
|
2311
|
+
await writeFile3(gi, RUNTIME_GITIGNORE_BODY, "utf8");
|
|
2312
|
+
}
|
|
2313
|
+
const readme = path7.join(runtimeDir, "README.md");
|
|
2314
|
+
if (!existsSync6(readme)) {
|
|
2315
|
+
await writeFile3(readme, RUNTIME_README_BODY, "utf8");
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2250
2318
|
async function ensureGitignoreEntries(root, patterns) {
|
|
2251
2319
|
try {
|
|
2252
2320
|
const gitignorePath = path7.join(root, ".gitignore");
|
|
@@ -2631,6 +2699,7 @@ import {
|
|
|
2631
2699
|
loadMemoriesFromDir as loadMemoriesFromDir3,
|
|
2632
2700
|
loadUsageIndex as loadUsageIndex2,
|
|
2633
2701
|
pickSnippetNeedle,
|
|
2702
|
+
rankMemoriesLexical,
|
|
2634
2703
|
tokenizeQuery as tokenizeQuery2,
|
|
2635
2704
|
trackReads as trackReads2
|
|
2636
2705
|
} from "@hiveai/core";
|
|
@@ -2728,7 +2797,11 @@ import {
|
|
|
2728
2797
|
serializeMemory as serializeMemory8
|
|
2729
2798
|
} from "@hiveai/core";
|
|
2730
2799
|
import { z as z16 } from "zod";
|
|
2731
|
-
import {
|
|
2800
|
+
import {
|
|
2801
|
+
appendUsageEvent,
|
|
2802
|
+
appendRuntimeJournalEntry,
|
|
2803
|
+
loadConfig as loadConfig2
|
|
2804
|
+
} from "@hiveai/core";
|
|
2732
2805
|
import { mkdir as mkdir52, writeFile as writeFile9, rm } from "fs/promises";
|
|
2733
2806
|
import { existsSync as existsSync16 } from "fs";
|
|
2734
2807
|
import path72 from "path";
|
|
@@ -2832,9 +2905,27 @@ import {
|
|
|
2832
2905
|
serializeMemory as serializeMemory10
|
|
2833
2906
|
} from "@hiveai/core";
|
|
2834
2907
|
import { z as z29 } from "zod";
|
|
2908
|
+
import { existsSync as existsSync27 } from "fs";
|
|
2909
|
+
import {
|
|
2910
|
+
findLexicalConflictPairs,
|
|
2911
|
+
findTopicStatusConflictPairs,
|
|
2912
|
+
loadMemoriesFromDir as loadMemoriesFromDir21
|
|
2913
|
+
} from "@hiveai/core";
|
|
2835
2914
|
import { z as z30 } from "zod";
|
|
2915
|
+
import { resolveProjectInfo } from "@hiveai/core";
|
|
2836
2916
|
import { z as z31 } from "zod";
|
|
2917
|
+
import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
|
|
2837
2918
|
import { z as z32 } from "zod";
|
|
2919
|
+
import { existsSync as existsSync28 } from "fs";
|
|
2920
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir222 } from "@hiveai/core";
|
|
2921
|
+
import { z as z33 } from "zod";
|
|
2922
|
+
import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
|
|
2923
|
+
import { z as z34 } from "zod";
|
|
2924
|
+
import { readRuntimeJournalTail } from "@hiveai/core";
|
|
2925
|
+
import { z as z35 } from "zod";
|
|
2926
|
+
import { z as z36 } from "zod";
|
|
2927
|
+
import { z as z37 } from "zod";
|
|
2928
|
+
import { z as z38 } from "zod";
|
|
2838
2929
|
function createContext(options = {}) {
|
|
2839
2930
|
const env = options.env ?? process.env;
|
|
2840
2931
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -3119,6 +3210,9 @@ var MemSearchInputSchema = {
|
|
|
3119
3210
|
semantic: z5.boolean().default(false).describe(
|
|
3120
3211
|
"Use semantic similarity from the embeddings index (requires `haive embeddings index`)."
|
|
3121
3212
|
),
|
|
3213
|
+
lexical_rank: z5.boolean().default(false).describe(
|
|
3214
|
+
"When true (and semantic is false), rank the filtered corpus with Okapi-BM25-style lexical scoring instead of literal AND/OR. Helps phrase-like queries without embeddings."
|
|
3215
|
+
),
|
|
3122
3216
|
min_score: z5.number().min(0).max(1).default(0).describe("Minimum cosine similarity (semantic mode only)"),
|
|
3123
3217
|
track: z5.boolean().default(true).describe("Increment read_count on returned memories (used for passive validation)")
|
|
3124
3218
|
};
|
|
@@ -3144,6 +3238,27 @@ async function memSearch(input, ctx) {
|
|
|
3144
3238
|
notice: "Semantic search unavailable (embeddings index missing or @hiveai/embeddings not installed). Falling back to literal search."
|
|
3145
3239
|
};
|
|
3146
3240
|
}
|
|
3241
|
+
} else if (input.lexical_rank && input.query.trim()) {
|
|
3242
|
+
const { ranked, scores } = rankMemoriesLexical(
|
|
3243
|
+
filtered,
|
|
3244
|
+
input.query,
|
|
3245
|
+
input.limit
|
|
3246
|
+
);
|
|
3247
|
+
if (ranked.length > 0) {
|
|
3248
|
+
const snippetNeedle = pickSnippetNeedle(input.query);
|
|
3249
|
+
result = {
|
|
3250
|
+
matches: ranked.map(
|
|
3251
|
+
(loaded, i) => lexicalHit(loaded, snippetNeedle, usage, scores[i])
|
|
3252
|
+
),
|
|
3253
|
+
total: ranked.length,
|
|
3254
|
+
mode: "lexical_ranked"
|
|
3255
|
+
};
|
|
3256
|
+
} else {
|
|
3257
|
+
result = {
|
|
3258
|
+
...buildLiteralResult(input, filtered, usage),
|
|
3259
|
+
notice: "Lexical ranking found no BM25-positive hits \u2014 showing literal matches instead."
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3147
3262
|
} else {
|
|
3148
3263
|
result = buildLiteralResult(input, filtered, usage);
|
|
3149
3264
|
}
|
|
@@ -3241,6 +3356,9 @@ function toHit(loaded, needle, usage) {
|
|
|
3241
3356
|
file_path: loaded.filePath
|
|
3242
3357
|
};
|
|
3243
3358
|
}
|
|
3359
|
+
function lexicalHit(loaded, needle, usage, lexicalScore) {
|
|
3360
|
+
return { ...toHit(loaded, needle, usage), score: lexicalScore };
|
|
3361
|
+
}
|
|
3244
3362
|
var MemVerifyInputSchema = {
|
|
3245
3363
|
id: z6.string().optional().describe("If set, verify only this memory id"),
|
|
3246
3364
|
update: z6.boolean().default(false).describe("Write the resulting status back to disk (status=stale or validated)")
|
|
@@ -3832,6 +3950,14 @@ var SessionTracker = class {
|
|
|
3832
3950
|
recapId = result.id;
|
|
3833
3951
|
} catch {
|
|
3834
3952
|
}
|
|
3953
|
+
void appendRuntimeJournalEntry(this.ctx.paths, {
|
|
3954
|
+
kind: "session_end",
|
|
3955
|
+
message: recapId ? `auto session close | ${toolSummary} | recap:${recapId}` : `auto session close | ${toolSummary}`,
|
|
3956
|
+
meta: {
|
|
3957
|
+
recap_id: recapId ?? null,
|
|
3958
|
+
total_tool_calls: totalCalls
|
|
3959
|
+
}
|
|
3960
|
+
});
|
|
3835
3961
|
const ranPostTask = this.events.some(
|
|
3836
3962
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
3837
3963
|
);
|
|
@@ -5572,11 +5698,107 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
5572
5698
|
return null;
|
|
5573
5699
|
}
|
|
5574
5700
|
}
|
|
5701
|
+
var MemConflictCandidatesInputSchema = {
|
|
5702
|
+
since_days: z30.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
5703
|
+
types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
5704
|
+
min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
5705
|
+
max_pairs: z30.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
5706
|
+
max_scan: z30.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
|
|
5707
|
+
max_topic_pairs: z30.number().int().positive().max(100).default(20).describe(
|
|
5708
|
+
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
5709
|
+
)
|
|
5710
|
+
};
|
|
5711
|
+
async function memConflictCandidates(input, ctx) {
|
|
5712
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
5713
|
+
return {
|
|
5714
|
+
pairs: [],
|
|
5715
|
+
topic_status_pairs: [],
|
|
5716
|
+
scanned: 0,
|
|
5717
|
+
truncated: false,
|
|
5718
|
+
notice: "No .ai/memories directory."
|
|
5719
|
+
};
|
|
5720
|
+
}
|
|
5721
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
5722
|
+
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
5723
|
+
sinceDays: input.since_days,
|
|
5724
|
+
types: input.types,
|
|
5725
|
+
minJaccard: input.min_jaccard,
|
|
5726
|
+
maxPairs: input.max_pairs,
|
|
5727
|
+
maxScan: input.max_scan
|
|
5728
|
+
});
|
|
5729
|
+
const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
|
|
5730
|
+
const notice = pairs.length === 0 && topicStatusPairs.length === 0 ? "No lexical or topic-status candidates \u2014 widen since_days/types or lower min_jaccard." : void 0;
|
|
5731
|
+
return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
|
|
5732
|
+
}
|
|
5733
|
+
var MemResolveProjectInputSchema = {
|
|
5734
|
+
cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
5735
|
+
};
|
|
5736
|
+
async function memResolveProject(input, _ctx) {
|
|
5737
|
+
void _ctx;
|
|
5738
|
+
return {
|
|
5739
|
+
ok: true,
|
|
5740
|
+
info: resolveProjectInfo({
|
|
5741
|
+
cwd: input.cwd
|
|
5742
|
+
})
|
|
5743
|
+
};
|
|
5744
|
+
}
|
|
5745
|
+
var MemSuggestTopicInputSchema = {
|
|
5746
|
+
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
5747
|
+
title: z32.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
5748
|
+
};
|
|
5749
|
+
async function memSuggestTopic(input, _ctx) {
|
|
5750
|
+
void _ctx;
|
|
5751
|
+
const suggestion = suggestTopicKey(input.type, input.title);
|
|
5752
|
+
return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
|
|
5753
|
+
}
|
|
5754
|
+
var MemTimelineInputSchema = {
|
|
5755
|
+
memory_id: z33.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
5756
|
+
topic: z33.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
5757
|
+
limit: z33.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
5758
|
+
};
|
|
5759
|
+
async function memTimeline(input, ctx) {
|
|
5760
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
5761
|
+
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
5762
|
+
}
|
|
5763
|
+
const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
|
|
5764
|
+
const { entries, notice } = collectTimelineEntries(all, {
|
|
5765
|
+
memoryId: input.memory_id,
|
|
5766
|
+
topic: input.topic,
|
|
5767
|
+
limit: input.limit
|
|
5768
|
+
});
|
|
5769
|
+
return { entries, total: entries.length, notice };
|
|
5770
|
+
}
|
|
5771
|
+
var RuntimeJournalAppendInputSchema = {
|
|
5772
|
+
message: z34.string().min(1).describe("Short line to append to the runtime session journal"),
|
|
5773
|
+
kind: z34.enum(["note", "session_end", "mcp"]).default("note"),
|
|
5774
|
+
tool: z34.string().optional().describe("When kind=mcp, which tool name (optional)")
|
|
5775
|
+
};
|
|
5776
|
+
async function runtimeJournalAppend(input, ctx) {
|
|
5777
|
+
await appendRuntimeJournalEntry2(ctx.paths, {
|
|
5778
|
+
kind: input.kind,
|
|
5779
|
+
message: input.message,
|
|
5780
|
+
...input.tool ? { tool: input.tool } : {}
|
|
5781
|
+
});
|
|
5782
|
+
return {
|
|
5783
|
+
ok: true,
|
|
5784
|
+
path_hint: `${ctx.paths.runtimeDir}/session-journal.ndjson`
|
|
5785
|
+
};
|
|
5786
|
+
}
|
|
5787
|
+
var RuntimeJournalTailInputSchema = {
|
|
5788
|
+
limit: z35.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
|
|
5789
|
+
};
|
|
5790
|
+
async function runtimeJournalTail(input, ctx) {
|
|
5791
|
+
const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
|
|
5792
|
+
if (entries.length === 0) {
|
|
5793
|
+
return { entries: [], empty: true };
|
|
5794
|
+
}
|
|
5795
|
+
return { entries };
|
|
5796
|
+
}
|
|
5575
5797
|
var BootstrapProjectArgsSchema = {
|
|
5576
|
-
module:
|
|
5798
|
+
module: z36.string().optional().describe(
|
|
5577
5799
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
5578
5800
|
),
|
|
5579
|
-
focus:
|
|
5801
|
+
focus: z36.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
5580
5802
|
};
|
|
5581
5803
|
var ROOT_TEMPLATE = `# Project context
|
|
5582
5804
|
|
|
@@ -5657,8 +5879,8 @@ ${template}\`\`\`
|
|
|
5657
5879
|
};
|
|
5658
5880
|
}
|
|
5659
5881
|
var PostTaskArgsSchema = {
|
|
5660
|
-
task_summary:
|
|
5661
|
-
files_touched:
|
|
5882
|
+
task_summary: z37.string().optional().describe("One sentence describing what you just did"),
|
|
5883
|
+
files_touched: z37.array(z37.string()).optional().describe("Files you created or modified during the task")
|
|
5662
5884
|
};
|
|
5663
5885
|
function postTaskPrompt(args, ctx) {
|
|
5664
5886
|
const taskLine = args.task_summary ? `
|
|
@@ -5741,10 +5963,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
5741
5963
|
};
|
|
5742
5964
|
}
|
|
5743
5965
|
var ImportDocsArgsSchema = {
|
|
5744
|
-
content:
|
|
5745
|
-
source:
|
|
5746
|
-
scope:
|
|
5747
|
-
dry_run:
|
|
5966
|
+
content: z38.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
5967
|
+
source: z38.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
5968
|
+
scope: z38.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
5969
|
+
dry_run: z38.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
5748
5970
|
};
|
|
5749
5971
|
function importDocsPrompt(args, ctx) {
|
|
5750
5972
|
const sourceLine = args.source ? `
|
|
@@ -5807,7 +6029,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
5807
6029
|
};
|
|
5808
6030
|
}
|
|
5809
6031
|
var SERVER_NAME = "haive";
|
|
5810
|
-
var SERVER_VERSION = "0.9.
|
|
6032
|
+
var SERVER_VERSION = "0.9.6";
|
|
5811
6033
|
function jsonResult(data) {
|
|
5812
6034
|
return {
|
|
5813
6035
|
content: [
|
|
@@ -5858,6 +6080,23 @@ function createHaiveServer(options = {}) {
|
|
|
5858
6080
|
return jsonResult(await memSave(input, context));
|
|
5859
6081
|
}
|
|
5860
6082
|
);
|
|
6083
|
+
server.tool(
|
|
6084
|
+
"mem_suggest_topic",
|
|
6085
|
+
[
|
|
6086
|
+
"Propose a stable `topic` key (topic-upsert) from type + short title.",
|
|
6087
|
+
"",
|
|
6088
|
+
"USE BEFORE mem_save when you want deterministic updates to the same memory over time;",
|
|
6089
|
+
"families mimic Engram-style grouping (architecture/*, bug/*, decision/*, \u2026).",
|
|
6090
|
+
"",
|
|
6091
|
+
"PARAMETERS:",
|
|
6092
|
+
" type \u2014 convention | decision | gotcha | architecture | glossary | attempt | session_recap",
|
|
6093
|
+
" title \u2014 phrase to slugify under the suggested family prefix",
|
|
6094
|
+
"",
|
|
6095
|
+
"RETURNS: { topic_key, family, type }"
|
|
6096
|
+
].join("\n"),
|
|
6097
|
+
MemSuggestTopicInputSchema,
|
|
6098
|
+
async (input) => jsonResult(await memSuggestTopic(input, context))
|
|
6099
|
+
);
|
|
5861
6100
|
server.tool(
|
|
5862
6101
|
"mem_tried",
|
|
5863
6102
|
[
|
|
@@ -5949,8 +6188,12 @@ function createHaiveServer(options = {}) {
|
|
|
5949
6188
|
server.tool(
|
|
5950
6189
|
"get_briefing",
|
|
5951
6190
|
[
|
|
5952
|
-
"\u2B50
|
|
5953
|
-
"
|
|
6191
|
+
"\u2B50 DEFAULT-FIRST for coding agents on any repo where `haive init` ran: call this BEFORE",
|
|
6192
|
+
"changing source or project config for the current goal (unless the developer explicitly opts out).",
|
|
6193
|
+
"One-shot onboarding: everything relevant in a single call under a token budget.",
|
|
6194
|
+
"",
|
|
6195
|
+
"PROGRESSIVE DISCLOSURE \u2014 after this, drill down only if needed:",
|
|
6196
|
+
" mem_relevant_to / mem_search (compact lists) \u2192 mem_get (full body + anchors).",
|
|
5954
6197
|
"",
|
|
5955
6198
|
"RETURNS (in order of priority):",
|
|
5956
6199
|
" 0. action_required \u2014 \u26A0\uFE0F HANDLE THIS FIRST if non-empty (see protocol below)",
|
|
@@ -5985,7 +6228,7 @@ function createHaiveServer(options = {}) {
|
|
|
5985
6228
|
" low \u2014 proposed, few reads (take with caution)",
|
|
5986
6229
|
" unverified \u2014 draft (unverified: true flag set)",
|
|
5987
6230
|
"",
|
|
5988
|
-
"Replaces 4\u20135 separate tool calls.
|
|
6231
|
+
"Replaces 4\u20135 separate tool calls. Prefer this first; use mem_search / mem_get only for follow-up."
|
|
5989
6232
|
].join("\n"),
|
|
5990
6233
|
GetBriefingInputSchema,
|
|
5991
6234
|
async (input) => {
|
|
@@ -6004,6 +6247,8 @@ function createHaiveServer(options = {}) {
|
|
|
6004
6247
|
"SEARCH MODES:",
|
|
6005
6248
|
" Literal (default): AND search across id, tags, and body \u2014 all tokens must match.",
|
|
6006
6249
|
" Falls back to OR automatically if no AND results (partial match).",
|
|
6250
|
+
" Lexical rank (lexical_rank: true, semantic: false): Okapi-BM25-style scoring on the",
|
|
6251
|
+
" filtered corpus \u2014 good for phrase-like queries without embeddings.",
|
|
6007
6252
|
" Semantic (semantic: true): embedding-based similarity \u2014 finds related memories",
|
|
6008
6253
|
" even with different wording. Requires haive embeddings index to be built.",
|
|
6009
6254
|
"",
|
|
@@ -6012,6 +6257,7 @@ function createHaiveServer(options = {}) {
|
|
|
6012
6257
|
" scope \u2014 filter by personal | team | module",
|
|
6013
6258
|
" type \u2014 filter by convention | decision | gotcha | architecture | glossary",
|
|
6014
6259
|
" semantic \u2014 true for embedding-based search (requires @hiveai/embeddings)",
|
|
6260
|
+
" lexical_rank \u2014 BM25-style ranking (ignored when semantic is true)",
|
|
6015
6261
|
" limit \u2014 max results (default 10)",
|
|
6016
6262
|
"",
|
|
6017
6263
|
"RETURNS: array of { id, type, scope, status, confidence, body, match_quality }"
|
|
@@ -6022,6 +6268,22 @@ function createHaiveServer(options = {}) {
|
|
|
6022
6268
|
return jsonResult(await memSearch(input, context));
|
|
6023
6269
|
}
|
|
6024
6270
|
);
|
|
6271
|
+
server.tool(
|
|
6272
|
+
"mem_timeline",
|
|
6273
|
+
[
|
|
6274
|
+
"Chronological view of related memories: by shared frontmatter.topic OR expanded from a seed id",
|
|
6275
|
+
"(related_ids, same topic, overlapping anchor paths \u2014 one extra hop on related_ids).",
|
|
6276
|
+
"",
|
|
6277
|
+
"PARAMETERS:",
|
|
6278
|
+
" memory_id \u2014 optional seed memory id",
|
|
6279
|
+
" topic \u2014 optional topic key (required if memory_id omitted)",
|
|
6280
|
+
" limit \u2014 max entries (default 30)",
|
|
6281
|
+
"",
|
|
6282
|
+
"RETURNS: { entries: [{ id, type, scope, created_at, one_line, topic? }], total, notice? }"
|
|
6283
|
+
].join("\n"),
|
|
6284
|
+
MemTimelineInputSchema,
|
|
6285
|
+
async (input) => jsonResult(await memTimeline(input, context))
|
|
6286
|
+
);
|
|
6025
6287
|
server.tool(
|
|
6026
6288
|
"mem_for_files",
|
|
6027
6289
|
[
|
|
@@ -6051,7 +6313,7 @@ function createHaiveServer(options = {}) {
|
|
|
6051
6313
|
[
|
|
6052
6314
|
"Fetch a single memory by its full id with all details.",
|
|
6053
6315
|
"",
|
|
6054
|
-
"USE WHEN get_briefing returned a
|
|
6316
|
+
"USE WHEN get_briefing / mem_relevant_to / mem_search returned a compact hit and you need",
|
|
6055
6317
|
"the full body, or when you know the exact id of a memory.",
|
|
6056
6318
|
"",
|
|
6057
6319
|
"PARAMETERS:",
|
|
@@ -6139,6 +6401,22 @@ function createHaiveServer(options = {}) {
|
|
|
6139
6401
|
CodeMapInputSchema,
|
|
6140
6402
|
async (input) => jsonResult(await codeMapTool(input, context))
|
|
6141
6403
|
);
|
|
6404
|
+
server.tool(
|
|
6405
|
+
"mem_resolve_project",
|
|
6406
|
+
[
|
|
6407
|
+
"Diagnostics: resolve which project root hAIve is using (never throws).",
|
|
6408
|
+
"",
|
|
6409
|
+
"USE IN multi-root workspaces or when the agent CWD may not be the repo root \u2014",
|
|
6410
|
+
"mirrors HAIVE_PROJECT_ROOT, findProjectRoot markers, and presence of .ai/memories.",
|
|
6411
|
+
"",
|
|
6412
|
+
"PARAMETERS:",
|
|
6413
|
+
" cwd \u2014 optional directory used for discovery when HAIVE_PROJECT_ROOT is unset",
|
|
6414
|
+
"",
|
|
6415
|
+
"RETURNS: { ok: true, info: { cwd, resolved_root, haive_project_root_env, \u2026 } }"
|
|
6416
|
+
].join("\n"),
|
|
6417
|
+
MemResolveProjectInputSchema,
|
|
6418
|
+
async (input) => jsonResult(await memResolveProject(input, context))
|
|
6419
|
+
);
|
|
6142
6420
|
server.tool(
|
|
6143
6421
|
"mem_update",
|
|
6144
6422
|
[
|
|
@@ -6272,6 +6550,8 @@ function createHaiveServer(options = {}) {
|
|
|
6272
6550
|
"One-shot ranked memories for a task \u2014 use instead of get_briefing when",
|
|
6273
6551
|
"project context is already loaded and you only want the relevant memory layer.",
|
|
6274
6552
|
"",
|
|
6553
|
+
"Second step in progressive disclosure (after get_briefing): narrow here, then mem_get for full text.",
|
|
6554
|
+
"",
|
|
6275
6555
|
"Reuses the same ranking pipeline (anchor / module / literal / semantic) but",
|
|
6276
6556
|
"skips project_context, modules, action_required, etc.",
|
|
6277
6557
|
"",
|
|
@@ -6429,6 +6709,53 @@ function createHaiveServer(options = {}) {
|
|
|
6429
6709
|
return jsonResult(await memConflicts(input, context));
|
|
6430
6710
|
}
|
|
6431
6711
|
);
|
|
6712
|
+
server.tool(
|
|
6713
|
+
"mem_conflict_candidates",
|
|
6714
|
+
[
|
|
6715
|
+
"Bulk scan for conflict CANDIDATES (not proof):",
|
|
6716
|
+
"",
|
|
6717
|
+
" 1. Lexical similarity (Jaccard) on decision/architecture-like pairs",
|
|
6718
|
+
" 2. Same frontmatter.topic with validated vs rejected \u2014 quick human-review signal",
|
|
6719
|
+
"",
|
|
6720
|
+
"Advisory only \u2014 follow with mem_conflicts_with on specific ids.",
|
|
6721
|
+
"",
|
|
6722
|
+
"PARAMETERS:",
|
|
6723
|
+
" since_days, types, min_jaccard, max_pairs, max_scan, max_topic_pairs",
|
|
6724
|
+
"",
|
|
6725
|
+
"RETURNS: { pairs, topic_status_pairs, scanned, truncated, notice? }"
|
|
6726
|
+
].join("\n"),
|
|
6727
|
+
MemConflictCandidatesInputSchema,
|
|
6728
|
+
async (input) => {
|
|
6729
|
+
tracker.record("mem_conflict_candidates", `${input.since_days}d`);
|
|
6730
|
+
return jsonResult(await memConflictCandidates(input, context));
|
|
6731
|
+
}
|
|
6732
|
+
);
|
|
6733
|
+
server.tool(
|
|
6734
|
+
"runtime_journal_append",
|
|
6735
|
+
[
|
|
6736
|
+
"Append one line to `.ai/.runtime/session-journal.ndjson` \u2014 machine-local session continuity.",
|
|
6737
|
+
"",
|
|
6738
|
+
"Does NOT replace team memories; complements mem_session_end recaps for local traces.",
|
|
6739
|
+
"",
|
|
6740
|
+
"PARAMETERS: message, kind (note|session_end|mcp), optional tool",
|
|
6741
|
+
"",
|
|
6742
|
+
"RETURNS: { ok, path_hint }"
|
|
6743
|
+
].join("\n"),
|
|
6744
|
+
RuntimeJournalAppendInputSchema,
|
|
6745
|
+
async (input) => jsonResult(await runtimeJournalAppend(input, context))
|
|
6746
|
+
);
|
|
6747
|
+
server.tool(
|
|
6748
|
+
"runtime_journal_tail",
|
|
6749
|
+
[
|
|
6750
|
+
"Read the last N entries from the runtime session journal (parsed JSON lines).",
|
|
6751
|
+
"",
|
|
6752
|
+
"PARAMETERS: limit (default 30, max 500)",
|
|
6753
|
+
"",
|
|
6754
|
+
"RETURNS: { entries: [...], empty?: true }"
|
|
6755
|
+
].join("\n"),
|
|
6756
|
+
RuntimeJournalTailInputSchema,
|
|
6757
|
+
async (input) => jsonResult(await runtimeJournalTail(input, context))
|
|
6758
|
+
);
|
|
6432
6759
|
server.tool(
|
|
6433
6760
|
"pre_commit_check",
|
|
6434
6761
|
[
|
|
@@ -6566,7 +6893,7 @@ function registerMcp(program2) {
|
|
|
6566
6893
|
// src/commands/sync.ts
|
|
6567
6894
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6568
6895
|
import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir8 } from "fs/promises";
|
|
6569
|
-
import { existsSync as
|
|
6896
|
+
import { existsSync as existsSync29 } from "fs";
|
|
6570
6897
|
import path12 from "path";
|
|
6571
6898
|
import "commander";
|
|
6572
6899
|
import {
|
|
@@ -6578,7 +6905,7 @@ import {
|
|
|
6578
6905
|
isDecaying as isDecaying2,
|
|
6579
6906
|
loadCodeMap as loadCodeMap4,
|
|
6580
6907
|
loadConfig as loadConfig4,
|
|
6581
|
-
loadMemoriesFromDir as
|
|
6908
|
+
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
6582
6909
|
loadUsageIndex as loadUsageIndex12,
|
|
6583
6910
|
pullCrossRepoSources,
|
|
6584
6911
|
resolveHaivePaths as resolveHaivePaths7,
|
|
@@ -6602,7 +6929,7 @@ function registerSync(program2) {
|
|
|
6602
6929
|
).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").action(async (opts) => {
|
|
6603
6930
|
const root = findProjectRoot10(opts.dir);
|
|
6604
6931
|
const paths = resolveHaivePaths7(root);
|
|
6605
|
-
if (!
|
|
6932
|
+
if (!existsSync29(paths.memoriesDir)) {
|
|
6606
6933
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
6607
6934
|
process.exitCode = 1;
|
|
6608
6935
|
return;
|
|
@@ -6618,7 +6945,7 @@ function registerSync(program2) {
|
|
|
6618
6945
|
let promoted = 0;
|
|
6619
6946
|
let autoApproved = 0;
|
|
6620
6947
|
if (opts.verify !== false) {
|
|
6621
|
-
const memories = await
|
|
6948
|
+
const memories = await loadMemoriesFromDir23(paths.memoriesDir);
|
|
6622
6949
|
for (const { memory: memory2, filePath } of memories) {
|
|
6623
6950
|
if (memory2.frontmatter.type === "session_recap") {
|
|
6624
6951
|
if (memory2.frontmatter.status === "stale") {
|
|
@@ -6679,7 +7006,7 @@ function registerSync(program2) {
|
|
|
6679
7006
|
}
|
|
6680
7007
|
}
|
|
6681
7008
|
if (opts.promote !== false) {
|
|
6682
|
-
const memories = await
|
|
7009
|
+
const memories = await loadMemoriesFromDir23(paths.memoriesDir);
|
|
6683
7010
|
const usage = await loadUsageIndex12(paths);
|
|
6684
7011
|
const nowMs = Date.now();
|
|
6685
7012
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -6718,7 +7045,7 @@ function registerSync(program2) {
|
|
|
6718
7045
|
}
|
|
6719
7046
|
}
|
|
6720
7047
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
6721
|
-
const draftMemories = (await
|
|
7048
|
+
const draftMemories = (await loadMemoriesFromDir23(paths.memoriesDir)).filter(
|
|
6722
7049
|
(m) => m.memory.frontmatter.status === "draft"
|
|
6723
7050
|
);
|
|
6724
7051
|
const draftCount = draftMemories.length;
|
|
@@ -6753,7 +7080,7 @@ function registerSync(program2) {
|
|
|
6753
7080
|
}
|
|
6754
7081
|
}
|
|
6755
7082
|
if (!opts.quiet) {
|
|
6756
|
-
const allForDecay = await
|
|
7083
|
+
const allForDecay = await loadMemoriesFromDir23(paths.memoriesDir);
|
|
6757
7084
|
const usageForDecay = await loadUsageIndex12(paths);
|
|
6758
7085
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
6759
7086
|
const fm = memory2.frontmatter;
|
|
@@ -6971,8 +7298,8 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
6971
7298
|
});
|
|
6972
7299
|
}
|
|
6973
7300
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
6974
|
-
if (!
|
|
6975
|
-
const all = await
|
|
7301
|
+
if (!existsSync29(memoriesDir)) return;
|
|
7302
|
+
const all = await loadMemoriesFromDir23(memoriesDir);
|
|
6976
7303
|
const top = all.filter(({ memory: memory2 }) => {
|
|
6977
7304
|
const s = memory2.frontmatter.status;
|
|
6978
7305
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
@@ -6996,7 +7323,7 @@ ${m.memory.body.trim()}`;
|
|
|
6996
7323
|
` + block + `
|
|
6997
7324
|
|
|
6998
7325
|
${BRIDGE_END}`;
|
|
6999
|
-
const fileExists =
|
|
7326
|
+
const fileExists = existsSync29(bridgeFile);
|
|
7000
7327
|
let existing = fileExists ? await readFile8(bridgeFile, "utf8") : "";
|
|
7001
7328
|
existing = existing.replace(/\r\n/g, "\n");
|
|
7002
7329
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
@@ -7047,14 +7374,14 @@ function collectSinceChanges(root, ref) {
|
|
|
7047
7374
|
// src/commands/memory-add.ts
|
|
7048
7375
|
import { createHash as createHash2 } from "crypto";
|
|
7049
7376
|
import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
|
|
7050
|
-
import { existsSync as
|
|
7377
|
+
import { existsSync as existsSync30 } from "fs";
|
|
7051
7378
|
import path13 from "path";
|
|
7052
7379
|
import "commander";
|
|
7053
7380
|
import {
|
|
7054
7381
|
buildFrontmatter as buildFrontmatter7,
|
|
7055
7382
|
findProjectRoot as findProjectRoot11,
|
|
7056
7383
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
7057
|
-
loadMemoriesFromDir as
|
|
7384
|
+
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
7058
7385
|
memoryFilePath as memoryFilePath6,
|
|
7059
7386
|
resolveHaivePaths as resolveHaivePaths8,
|
|
7060
7387
|
serializeMemory as serializeMemory12
|
|
@@ -7086,7 +7413,7 @@ function registerMemoryAdd(memory2) {
|
|
|
7086
7413
|
).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: personal, or team in autopilot)", "personal").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("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7087
7414
|
const root = findProjectRoot11(opts.dir);
|
|
7088
7415
|
const paths = resolveHaivePaths8(root);
|
|
7089
|
-
if (!
|
|
7416
|
+
if (!existsSync30(paths.haiveDir)) {
|
|
7090
7417
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
7091
7418
|
process.exitCode = 1;
|
|
7092
7419
|
return;
|
|
@@ -7097,7 +7424,7 @@ function registerMemoryAdd(memory2) {
|
|
|
7097
7424
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
7098
7425
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
7099
7426
|
if (anchorPaths.length > 0) {
|
|
7100
|
-
const missing = anchorPaths.filter((p) => !
|
|
7427
|
+
const missing = anchorPaths.filter((p) => !existsSync30(path13.resolve(root, p)));
|
|
7101
7428
|
if (missing.length > 0) {
|
|
7102
7429
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
7103
7430
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -7109,7 +7436,7 @@ function registerMemoryAdd(memory2) {
|
|
|
7109
7436
|
const title = opts.title ?? opts.slug;
|
|
7110
7437
|
let body;
|
|
7111
7438
|
if (opts.bodyFile !== void 0) {
|
|
7112
|
-
if (!
|
|
7439
|
+
if (!existsSync30(opts.bodyFile)) {
|
|
7113
7440
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
7114
7441
|
process.exitCode = 1;
|
|
7115
7442
|
return;
|
|
@@ -7130,9 +7457,9 @@ TODO \u2014 write the memory body.
|
|
|
7130
7457
|
`;
|
|
7131
7458
|
}
|
|
7132
7459
|
const scope = opts.scope ?? "personal";
|
|
7133
|
-
if (
|
|
7460
|
+
if (existsSync30(paths.memoriesDir)) {
|
|
7134
7461
|
const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
7135
|
-
const allForHash = await
|
|
7462
|
+
const allForHash = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
7136
7463
|
const hashDup = allForHash.find(
|
|
7137
7464
|
({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
|
|
7138
7465
|
);
|
|
@@ -7143,8 +7470,8 @@ TODO \u2014 write the memory body.
|
|
|
7143
7470
|
return;
|
|
7144
7471
|
}
|
|
7145
7472
|
}
|
|
7146
|
-
if (opts.topic &&
|
|
7147
|
-
const existing = await
|
|
7473
|
+
if (opts.topic && existsSync30(paths.memoriesDir)) {
|
|
7474
|
+
const existing = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
7148
7475
|
const topicMatch = existing.find(
|
|
7149
7476
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
7150
7477
|
);
|
|
@@ -7182,13 +7509,13 @@ TODO \u2014 write the memory body.
|
|
|
7182
7509
|
});
|
|
7183
7510
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
7184
7511
|
await mkdir9(path13.dirname(file), { recursive: true });
|
|
7185
|
-
if (
|
|
7512
|
+
if (existsSync30(file)) {
|
|
7186
7513
|
ui.error(`Memory already exists at ${file}`);
|
|
7187
7514
|
process.exitCode = 1;
|
|
7188
7515
|
return;
|
|
7189
7516
|
}
|
|
7190
|
-
if (
|
|
7191
|
-
const existing = await
|
|
7517
|
+
if (existsSync30(paths.memoriesDir)) {
|
|
7518
|
+
const existing = await loadMemoriesFromDir24(paths.memoriesDir);
|
|
7192
7519
|
const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
7193
7520
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
7194
7521
|
const id = memory3.frontmatter.id.toLowerCase();
|
|
@@ -7230,14 +7557,14 @@ function parseCsv2(value) {
|
|
|
7230
7557
|
}
|
|
7231
7558
|
|
|
7232
7559
|
// src/commands/memory-list.ts
|
|
7233
|
-
import { existsSync as
|
|
7560
|
+
import { existsSync as existsSync31 } from "fs";
|
|
7234
7561
|
import path14 from "path";
|
|
7235
7562
|
import "commander";
|
|
7236
7563
|
import { findProjectRoot as findProjectRoot12, resolveHaivePaths as resolveHaivePaths9 } from "@hiveai/core";
|
|
7237
7564
|
|
|
7238
7565
|
// src/utils/fs.ts
|
|
7239
7566
|
import {
|
|
7240
|
-
loadMemoriesFromDir as
|
|
7567
|
+
loadMemoriesFromDir as loadMemoriesFromDir25,
|
|
7241
7568
|
loadMemory,
|
|
7242
7569
|
listMarkdownFilesRecursive
|
|
7243
7570
|
} from "@hiveai/core";
|
|
@@ -7247,12 +7574,12 @@ function registerMemoryList(memory2) {
|
|
|
7247
7574
|
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("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7248
7575
|
const root = findProjectRoot12(opts.dir);
|
|
7249
7576
|
const paths = resolveHaivePaths9(root);
|
|
7250
|
-
if (!
|
|
7577
|
+
if (!existsSync31(paths.memoriesDir)) {
|
|
7251
7578
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
7252
7579
|
process.exitCode = 1;
|
|
7253
7580
|
return;
|
|
7254
7581
|
}
|
|
7255
|
-
const all = await
|
|
7582
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7256
7583
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
7257
7584
|
const filtered = all.filter((m) => {
|
|
7258
7585
|
if (!matchesFilters(m, opts)) return false;
|
|
@@ -7315,7 +7642,7 @@ function matchesFilters(loaded, opts) {
|
|
|
7315
7642
|
|
|
7316
7643
|
// src/commands/memory-promote.ts
|
|
7317
7644
|
import { mkdir as mkdir10, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
|
|
7318
|
-
import { existsSync as
|
|
7645
|
+
import { existsSync as existsSync33 } from "fs";
|
|
7319
7646
|
import path15 from "path";
|
|
7320
7647
|
import "commander";
|
|
7321
7648
|
import {
|
|
@@ -7328,12 +7655,12 @@ function registerMemoryPromote(memory2) {
|
|
|
7328
7655
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
7329
7656
|
const root = findProjectRoot13(opts.dir);
|
|
7330
7657
|
const paths = resolveHaivePaths10(root);
|
|
7331
|
-
if (!
|
|
7658
|
+
if (!existsSync33(paths.memoriesDir)) {
|
|
7332
7659
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
7333
7660
|
process.exitCode = 1;
|
|
7334
7661
|
return;
|
|
7335
7662
|
}
|
|
7336
|
-
const teamAndModule = await
|
|
7663
|
+
const teamAndModule = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7337
7664
|
const alreadyShared = teamAndModule.find(
|
|
7338
7665
|
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
7339
7666
|
);
|
|
@@ -7347,7 +7674,7 @@ function registerMemoryPromote(memory2) {
|
|
|
7347
7674
|
}
|
|
7348
7675
|
return;
|
|
7349
7676
|
}
|
|
7350
|
-
const all = await
|
|
7677
|
+
const all = await loadMemoriesFromDir25(paths.personalDir);
|
|
7351
7678
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
7352
7679
|
if (!found) {
|
|
7353
7680
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -7373,7 +7700,7 @@ function registerMemoryPromote(memory2) {
|
|
|
7373
7700
|
}
|
|
7374
7701
|
|
|
7375
7702
|
// src/commands/memory-approve.ts
|
|
7376
|
-
import { existsSync as
|
|
7703
|
+
import { existsSync as existsSync34 } from "fs";
|
|
7377
7704
|
import { writeFile as writeFile16 } from "fs/promises";
|
|
7378
7705
|
import path16 from "path";
|
|
7379
7706
|
import "commander";
|
|
@@ -7386,12 +7713,12 @@ function registerMemoryApprove(memory2) {
|
|
|
7386
7713
|
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) => {
|
|
7387
7714
|
const root = findProjectRoot14(opts.dir);
|
|
7388
7715
|
const paths = resolveHaivePaths11(root);
|
|
7389
|
-
if (!
|
|
7716
|
+
if (!existsSync34(paths.memoriesDir)) {
|
|
7390
7717
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7391
7718
|
process.exitCode = 1;
|
|
7392
7719
|
return;
|
|
7393
7720
|
}
|
|
7394
|
-
const all = await
|
|
7721
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7395
7722
|
if (opts.all || opts.pending) {
|
|
7396
7723
|
const candidates = all.filter((m) => {
|
|
7397
7724
|
const s = m.memory.frontmatter.status;
|
|
@@ -7445,7 +7772,7 @@ function registerMemoryApprove(memory2) {
|
|
|
7445
7772
|
|
|
7446
7773
|
// src/commands/memory-update.ts
|
|
7447
7774
|
import { writeFile as writeFile17 } from "fs/promises";
|
|
7448
|
-
import { existsSync as
|
|
7775
|
+
import { existsSync as existsSync35 } from "fs";
|
|
7449
7776
|
import path17 from "path";
|
|
7450
7777
|
import "commander";
|
|
7451
7778
|
import {
|
|
@@ -7457,12 +7784,12 @@ function registerMemoryUpdate(memory2) {
|
|
|
7457
7784
|
memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").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("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").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) => {
|
|
7458
7785
|
const root = findProjectRoot15(opts.dir);
|
|
7459
7786
|
const paths = resolveHaivePaths12(root);
|
|
7460
|
-
if (!
|
|
7787
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
7461
7788
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
7462
7789
|
process.exitCode = 1;
|
|
7463
7790
|
return;
|
|
7464
7791
|
}
|
|
7465
|
-
const memories = await
|
|
7792
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7466
7793
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
7467
7794
|
if (!loaded) {
|
|
7468
7795
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -7529,7 +7856,7 @@ function parseCsv3(value) {
|
|
|
7529
7856
|
|
|
7530
7857
|
// src/commands/memory-auto-promote.ts
|
|
7531
7858
|
import { writeFile as writeFile18 } from "fs/promises";
|
|
7532
|
-
import { existsSync as
|
|
7859
|
+
import { existsSync as existsSync36 } from "fs";
|
|
7533
7860
|
import path18 from "path";
|
|
7534
7861
|
import "commander";
|
|
7535
7862
|
import {
|
|
@@ -7549,7 +7876,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
7549
7876
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7550
7877
|
const root = findProjectRoot16(opts.dir);
|
|
7551
7878
|
const paths = resolveHaivePaths13(root);
|
|
7552
|
-
if (!
|
|
7879
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
7553
7880
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7554
7881
|
process.exitCode = 1;
|
|
7555
7882
|
return;
|
|
@@ -7558,7 +7885,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
7558
7885
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
|
|
7559
7886
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
7560
7887
|
};
|
|
7561
|
-
const memories = await
|
|
7888
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7562
7889
|
const usage = await loadUsageIndex13(paths);
|
|
7563
7890
|
const eligible = memories.filter(
|
|
7564
7891
|
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage11(usage, memory3.frontmatter.id), rule)
|
|
@@ -7592,7 +7919,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
7592
7919
|
|
|
7593
7920
|
// src/commands/memory-edit.ts
|
|
7594
7921
|
import { spawn as spawn3 } from "child_process";
|
|
7595
|
-
import { existsSync as
|
|
7922
|
+
import { existsSync as existsSync37 } from "fs";
|
|
7596
7923
|
import { readFile as readFile10 } from "fs/promises";
|
|
7597
7924
|
import path19 from "path";
|
|
7598
7925
|
import "commander";
|
|
@@ -7605,12 +7932,12 @@ function registerMemoryEdit(memory2) {
|
|
|
7605
7932
|
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) => {
|
|
7606
7933
|
const root = findProjectRoot17(opts.dir);
|
|
7607
7934
|
const paths = resolveHaivePaths14(root);
|
|
7608
|
-
if (!
|
|
7935
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
7609
7936
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7610
7937
|
process.exitCode = 1;
|
|
7611
7938
|
return;
|
|
7612
7939
|
}
|
|
7613
|
-
const all = await
|
|
7940
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7614
7941
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
7615
7942
|
if (!found) {
|
|
7616
7943
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -7645,7 +7972,7 @@ function runEditor(editor, file) {
|
|
|
7645
7972
|
}
|
|
7646
7973
|
|
|
7647
7974
|
// src/commands/memory-for-files.ts
|
|
7648
|
-
import { existsSync as
|
|
7975
|
+
import { existsSync as existsSync38 } from "fs";
|
|
7649
7976
|
import path20 from "path";
|
|
7650
7977
|
import "commander";
|
|
7651
7978
|
import {
|
|
@@ -7661,12 +7988,12 @@ function registerMemoryForFiles(memory2) {
|
|
|
7661
7988
|
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) => {
|
|
7662
7989
|
const root = findProjectRoot18(opts.dir);
|
|
7663
7990
|
const paths = resolveHaivePaths15(root);
|
|
7664
|
-
if (!
|
|
7991
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
7665
7992
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7666
7993
|
process.exitCode = 1;
|
|
7667
7994
|
return;
|
|
7668
7995
|
}
|
|
7669
|
-
const all = await
|
|
7996
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7670
7997
|
const usage = await loadUsageIndex14(paths);
|
|
7671
7998
|
const inferred = inferModulesFromPaths4(files);
|
|
7672
7999
|
const byAnchor = [];
|
|
@@ -7773,7 +8100,7 @@ function printGroup(root, label, loaded, usage) {
|
|
|
7773
8100
|
}
|
|
7774
8101
|
|
|
7775
8102
|
// src/commands/memory-hot.ts
|
|
7776
|
-
import { existsSync as
|
|
8103
|
+
import { existsSync as existsSync39 } from "fs";
|
|
7777
8104
|
import path21 from "path";
|
|
7778
8105
|
import "commander";
|
|
7779
8106
|
import {
|
|
@@ -7786,13 +8113,13 @@ function registerMemoryHot(memory2) {
|
|
|
7786
8113
|
memory2.command("hot").description("List memories actively used but not yet validated (good promotion candidates)").option("--threshold <n>", "minimum read_count to qualify", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7787
8114
|
const root = findProjectRoot19(opts.dir);
|
|
7788
8115
|
const paths = resolveHaivePaths16(root);
|
|
7789
|
-
if (!
|
|
8116
|
+
if (!existsSync39(paths.memoriesDir)) {
|
|
7790
8117
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7791
8118
|
process.exitCode = 1;
|
|
7792
8119
|
return;
|
|
7793
8120
|
}
|
|
7794
8121
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
7795
|
-
const all = await
|
|
8122
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7796
8123
|
const usage = await loadUsageIndex15(paths);
|
|
7797
8124
|
const candidates = all.filter(({ memory: mem }) => {
|
|
7798
8125
|
const fm = mem.frontmatter;
|
|
@@ -7824,7 +8151,7 @@ function registerMemoryHot(memory2) {
|
|
|
7824
8151
|
|
|
7825
8152
|
// src/commands/memory-tried.ts
|
|
7826
8153
|
import { mkdir as mkdir11, writeFile as writeFile19 } from "fs/promises";
|
|
7827
|
-
import { existsSync as
|
|
8154
|
+
import { existsSync as existsSync40 } from "fs";
|
|
7828
8155
|
import path23 from "path";
|
|
7829
8156
|
import "commander";
|
|
7830
8157
|
import {
|
|
@@ -7853,7 +8180,7 @@ function registerMemoryTried(memory2) {
|
|
|
7853
8180
|
).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 (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7854
8181
|
const root = findProjectRoot20(opts.dir);
|
|
7855
8182
|
const paths = resolveHaivePaths17(root);
|
|
7856
|
-
if (!
|
|
8183
|
+
if (!existsSync40(paths.haiveDir)) {
|
|
7857
8184
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
7858
8185
|
process.exitCode = 1;
|
|
7859
8186
|
return;
|
|
@@ -7877,7 +8204,7 @@ function registerMemoryTried(memory2) {
|
|
|
7877
8204
|
const body = lines.join("\n") + "\n";
|
|
7878
8205
|
const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
7879
8206
|
await mkdir11(path23.dirname(file), { recursive: true });
|
|
7880
|
-
if (
|
|
8207
|
+
if (existsSync40(file)) {
|
|
7881
8208
|
ui.error(`Memory already exists at ${file}`);
|
|
7882
8209
|
process.exitCode = 1;
|
|
7883
8210
|
return;
|
|
@@ -7893,7 +8220,7 @@ function parseCsv4(value) {
|
|
|
7893
8220
|
}
|
|
7894
8221
|
|
|
7895
8222
|
// src/commands/memory-pending.ts
|
|
7896
|
-
import { existsSync as
|
|
8223
|
+
import { existsSync as existsSync41 } from "fs";
|
|
7897
8224
|
import path24 from "path";
|
|
7898
8225
|
import "commander";
|
|
7899
8226
|
import {
|
|
@@ -7906,12 +8233,12 @@ function registerMemoryPending(memory2) {
|
|
|
7906
8233
|
memory2.command("pending").description("List 'proposed' memories awaiting review (sorted by reads desc)").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7907
8234
|
const root = findProjectRoot21(opts.dir);
|
|
7908
8235
|
const paths = resolveHaivePaths18(root);
|
|
7909
|
-
if (!
|
|
8236
|
+
if (!existsSync41(paths.memoriesDir)) {
|
|
7910
8237
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7911
8238
|
process.exitCode = 1;
|
|
7912
8239
|
return;
|
|
7913
8240
|
}
|
|
7914
|
-
const all = await
|
|
8241
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7915
8242
|
const usage = await loadUsageIndex16(paths);
|
|
7916
8243
|
const proposed = all.filter(({ memory: mem }) => {
|
|
7917
8244
|
if (mem.frontmatter.status !== "proposed") return false;
|
|
@@ -7941,7 +8268,7 @@ function registerMemoryPending(memory2) {
|
|
|
7941
8268
|
}
|
|
7942
8269
|
|
|
7943
8270
|
// src/commands/memory-query.ts
|
|
7944
|
-
import { existsSync as
|
|
8271
|
+
import { existsSync as existsSync43 } from "fs";
|
|
7945
8272
|
import path25 from "path";
|
|
7946
8273
|
import "commander";
|
|
7947
8274
|
import {
|
|
@@ -7958,7 +8285,7 @@ function registerMemoryQuery(memory2) {
|
|
|
7958
8285
|
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) => {
|
|
7959
8286
|
const root = findProjectRoot22(opts.dir);
|
|
7960
8287
|
const paths = resolveHaivePaths19(root);
|
|
7961
|
-
if (!
|
|
8288
|
+
if (!existsSync43(paths.memoriesDir)) {
|
|
7962
8289
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
7963
8290
|
process.exitCode = 1;
|
|
7964
8291
|
return;
|
|
@@ -7969,7 +8296,7 @@ function registerMemoryQuery(memory2) {
|
|
|
7969
8296
|
return;
|
|
7970
8297
|
}
|
|
7971
8298
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
7972
|
-
const all = await
|
|
8299
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
7973
8300
|
const passesFilters2 = (mem) => {
|
|
7974
8301
|
const fm = mem.frontmatter;
|
|
7975
8302
|
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
@@ -8017,7 +8344,7 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
8017
8344
|
|
|
8018
8345
|
// src/commands/memory-reject.ts
|
|
8019
8346
|
import { writeFile as writeFile20 } from "fs/promises";
|
|
8020
|
-
import { existsSync as
|
|
8347
|
+
import { existsSync as existsSync44 } from "fs";
|
|
8021
8348
|
import "commander";
|
|
8022
8349
|
import {
|
|
8023
8350
|
findProjectRoot as findProjectRoot23,
|
|
@@ -8031,12 +8358,12 @@ function registerMemoryReject(memory2) {
|
|
|
8031
8358
|
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) => {
|
|
8032
8359
|
const root = findProjectRoot23(opts.dir);
|
|
8033
8360
|
const paths = resolveHaivePaths20(root);
|
|
8034
|
-
if (!
|
|
8361
|
+
if (!existsSync44(paths.memoriesDir)) {
|
|
8035
8362
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8036
8363
|
process.exitCode = 1;
|
|
8037
8364
|
return;
|
|
8038
8365
|
}
|
|
8039
|
-
const memories = await
|
|
8366
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8040
8367
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
8041
8368
|
if (!loaded) {
|
|
8042
8369
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -8067,7 +8394,7 @@ function registerMemoryReject(memory2) {
|
|
|
8067
8394
|
}
|
|
8068
8395
|
|
|
8069
8396
|
// src/commands/memory-rm.ts
|
|
8070
|
-
import { existsSync as
|
|
8397
|
+
import { existsSync as existsSync45 } from "fs";
|
|
8071
8398
|
import { unlink as unlink3 } from "fs/promises";
|
|
8072
8399
|
import path26 from "path";
|
|
8073
8400
|
import { createInterface } from "readline/promises";
|
|
@@ -8082,12 +8409,12 @@ function registerMemoryRm(memory2) {
|
|
|
8082
8409
|
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) => {
|
|
8083
8410
|
const root = findProjectRoot24(opts.dir);
|
|
8084
8411
|
const paths = resolveHaivePaths21(root);
|
|
8085
|
-
if (!
|
|
8412
|
+
if (!existsSync45(paths.memoriesDir)) {
|
|
8086
8413
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8087
8414
|
process.exitCode = 1;
|
|
8088
8415
|
return;
|
|
8089
8416
|
}
|
|
8090
|
-
const all = await
|
|
8417
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8091
8418
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
8092
8419
|
if (!found) {
|
|
8093
8420
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -8118,7 +8445,7 @@ function registerMemoryRm(memory2) {
|
|
|
8118
8445
|
}
|
|
8119
8446
|
|
|
8120
8447
|
// src/commands/memory-show.ts
|
|
8121
|
-
import { existsSync as
|
|
8448
|
+
import { existsSync as existsSync46 } from "fs";
|
|
8122
8449
|
import { readFile as readFile11 } from "fs/promises";
|
|
8123
8450
|
import path27 from "path";
|
|
8124
8451
|
import "commander";
|
|
@@ -8133,12 +8460,12 @@ function registerMemoryShow(memory2) {
|
|
|
8133
8460
|
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) => {
|
|
8134
8461
|
const root = findProjectRoot25(opts.dir);
|
|
8135
8462
|
const paths = resolveHaivePaths22(root);
|
|
8136
|
-
if (!
|
|
8463
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
8137
8464
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8138
8465
|
process.exitCode = 1;
|
|
8139
8466
|
return;
|
|
8140
8467
|
}
|
|
8141
|
-
const all = await
|
|
8468
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8142
8469
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
8143
8470
|
if (!found) {
|
|
8144
8471
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -8177,7 +8504,7 @@ function registerMemoryShow(memory2) {
|
|
|
8177
8504
|
}
|
|
8178
8505
|
|
|
8179
8506
|
// src/commands/memory-stats.ts
|
|
8180
|
-
import { existsSync as
|
|
8507
|
+
import { existsSync as existsSync47 } from "fs";
|
|
8181
8508
|
import path28 from "path";
|
|
8182
8509
|
import "commander";
|
|
8183
8510
|
import {
|
|
@@ -8191,12 +8518,12 @@ function registerMemoryStats(memory2) {
|
|
|
8191
8518
|
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) => {
|
|
8192
8519
|
const root = findProjectRoot26(opts.dir);
|
|
8193
8520
|
const paths = resolveHaivePaths23(root);
|
|
8194
|
-
if (!
|
|
8521
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
8195
8522
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8196
8523
|
process.exitCode = 1;
|
|
8197
8524
|
return;
|
|
8198
8525
|
}
|
|
8199
|
-
const all = await
|
|
8526
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8200
8527
|
const usage = await loadUsageIndex20(paths);
|
|
8201
8528
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
8202
8529
|
if (target.length === 0) {
|
|
@@ -8223,7 +8550,7 @@ function registerMemoryStats(memory2) {
|
|
|
8223
8550
|
|
|
8224
8551
|
// src/commands/memory-verify.ts
|
|
8225
8552
|
import { writeFile as writeFile21 } from "fs/promises";
|
|
8226
|
-
import { existsSync as
|
|
8553
|
+
import { existsSync as existsSync48 } from "fs";
|
|
8227
8554
|
import path29 from "path";
|
|
8228
8555
|
import "commander";
|
|
8229
8556
|
import {
|
|
@@ -8238,12 +8565,12 @@ function registerMemoryVerify(memory2) {
|
|
|
8238
8565
|
).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("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8239
8566
|
const root = findProjectRoot27(opts.dir);
|
|
8240
8567
|
const paths = resolveHaivePaths24(root);
|
|
8241
|
-
if (!
|
|
8568
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
8242
8569
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8243
8570
|
process.exitCode = 1;
|
|
8244
8571
|
return;
|
|
8245
8572
|
}
|
|
8246
|
-
const all = await
|
|
8573
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
8247
8574
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
8248
8575
|
if (opts.id && targets.length === 0) {
|
|
8249
8576
|
ui.error(`No memory with id "${opts.id}".`);
|
|
@@ -8325,7 +8652,7 @@ function applyVerification2(mem, result) {
|
|
|
8325
8652
|
|
|
8326
8653
|
// src/commands/memory-import.ts
|
|
8327
8654
|
import { readFile as readFile12 } from "fs/promises";
|
|
8328
|
-
import { existsSync as
|
|
8655
|
+
import { existsSync as existsSync49 } from "fs";
|
|
8329
8656
|
import "commander";
|
|
8330
8657
|
import {
|
|
8331
8658
|
findProjectRoot as findProjectRoot28,
|
|
@@ -8337,12 +8664,12 @@ function registerMemoryImport(memory2) {
|
|
|
8337
8664
|
).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) => {
|
|
8338
8665
|
const root = findProjectRoot28(opts.dir);
|
|
8339
8666
|
const paths = resolveHaivePaths25(root);
|
|
8340
|
-
if (!
|
|
8667
|
+
if (!existsSync49(paths.haiveDir)) {
|
|
8341
8668
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
8342
8669
|
process.exitCode = 1;
|
|
8343
8670
|
return;
|
|
8344
8671
|
}
|
|
8345
|
-
if (!
|
|
8672
|
+
if (!existsSync49(opts.from)) {
|
|
8346
8673
|
ui.error(`File not found: ${opts.from}`);
|
|
8347
8674
|
process.exitCode = 1;
|
|
8348
8675
|
return;
|
|
@@ -8375,7 +8702,7 @@ function registerMemoryImport(memory2) {
|
|
|
8375
8702
|
}
|
|
8376
8703
|
|
|
8377
8704
|
// src/commands/memory-import-changelog.ts
|
|
8378
|
-
import { existsSync as
|
|
8705
|
+
import { existsSync as existsSync50 } from "fs";
|
|
8379
8706
|
import { readFile as readFile13, mkdir as mkdir12, writeFile as writeFile23 } from "fs/promises";
|
|
8380
8707
|
import path30 from "path";
|
|
8381
8708
|
import "commander";
|
|
@@ -8449,7 +8776,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
8449
8776
|
const root = findProjectRoot29(opts.dir);
|
|
8450
8777
|
const paths = resolveHaivePaths26(root);
|
|
8451
8778
|
const changelogPath = path30.resolve(root, opts.fromChangelog);
|
|
8452
|
-
if (!
|
|
8779
|
+
if (!existsSync50(changelogPath)) {
|
|
8453
8780
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
8454
8781
|
process.exitCode = 1;
|
|
8455
8782
|
return;
|
|
@@ -8535,7 +8862,7 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
8535
8862
|
}
|
|
8536
8863
|
|
|
8537
8864
|
// src/commands/memory-digest.ts
|
|
8538
|
-
import { existsSync as
|
|
8865
|
+
import { existsSync as existsSync51 } from "fs";
|
|
8539
8866
|
import { writeFile as writeFile24 } from "fs/promises";
|
|
8540
8867
|
import path31 from "path";
|
|
8541
8868
|
import "commander";
|
|
@@ -8543,7 +8870,7 @@ import {
|
|
|
8543
8870
|
deriveConfidence as deriveConfidence12,
|
|
8544
8871
|
findProjectRoot as findProjectRoot30,
|
|
8545
8872
|
getUsage as getUsage17,
|
|
8546
|
-
loadMemoriesFromDir as
|
|
8873
|
+
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
8547
8874
|
loadUsageIndex as loadUsageIndex21,
|
|
8548
8875
|
resolveHaivePaths as resolveHaivePaths27
|
|
8549
8876
|
} from "@hiveai/core";
|
|
@@ -8560,7 +8887,7 @@ function registerMemoryDigest(program2) {
|
|
|
8560
8887
|
).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) => {
|
|
8561
8888
|
const root = findProjectRoot30(opts.dir);
|
|
8562
8889
|
const paths = resolveHaivePaths27(root);
|
|
8563
|
-
if (!
|
|
8890
|
+
if (!existsSync51(paths.memoriesDir)) {
|
|
8564
8891
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
8565
8892
|
process.exitCode = 1;
|
|
8566
8893
|
return;
|
|
@@ -8568,7 +8895,7 @@ function registerMemoryDigest(program2) {
|
|
|
8568
8895
|
const days = Math.max(1, Number(opts.days ?? 7));
|
|
8569
8896
|
const scopeFilter = opts.scope ?? "team";
|
|
8570
8897
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
8571
|
-
const all = await
|
|
8898
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
8572
8899
|
const usage = await loadUsageIndex21(paths);
|
|
8573
8900
|
const recent = all.filter(({ memory: mem }) => {
|
|
8574
8901
|
const fm = mem.frontmatter;
|
|
@@ -8643,20 +8970,20 @@ function registerMemoryDigest(program2) {
|
|
|
8643
8970
|
|
|
8644
8971
|
// src/commands/session-end.ts
|
|
8645
8972
|
import { writeFile as writeFile25, mkdir as mkdir13, readFile as readFile14, rm as rm2 } from "fs/promises";
|
|
8646
|
-
import { existsSync as
|
|
8973
|
+
import { existsSync as existsSync53 } from "fs";
|
|
8647
8974
|
import path33 from "path";
|
|
8648
8975
|
import "commander";
|
|
8649
8976
|
import {
|
|
8650
8977
|
buildFrontmatter as buildFrontmatter10,
|
|
8651
8978
|
findProjectRoot as findProjectRoot31,
|
|
8652
|
-
loadMemoriesFromDir as
|
|
8979
|
+
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
8653
8980
|
memoryFilePath as memoryFilePath9,
|
|
8654
8981
|
resolveHaivePaths as resolveHaivePaths28,
|
|
8655
8982
|
serializeMemory as serializeMemory21
|
|
8656
8983
|
} from "@hiveai/core";
|
|
8657
8984
|
async function buildAutoRecap(paths) {
|
|
8658
8985
|
const obsFile = path33.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
8659
|
-
if (!
|
|
8986
|
+
if (!existsSync53(obsFile)) return null;
|
|
8660
8987
|
const raw = await readFile14(obsFile, "utf8").catch(() => "");
|
|
8661
8988
|
if (!raw.trim()) return null;
|
|
8662
8989
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -8737,7 +9064,7 @@ function registerSessionEnd(session2) {
|
|
|
8737
9064
|
).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) => {
|
|
8738
9065
|
const root = findProjectRoot31(opts.dir);
|
|
8739
9066
|
const paths = resolveHaivePaths28(root);
|
|
8740
|
-
if (!
|
|
9067
|
+
if (!existsSync53(paths.haiveDir)) {
|
|
8741
9068
|
if (opts.auto || opts.quiet) return;
|
|
8742
9069
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
8743
9070
|
process.exitCode = 1;
|
|
@@ -8769,7 +9096,7 @@ function registerSessionEnd(session2) {
|
|
|
8769
9096
|
});
|
|
8770
9097
|
const topic = recapTopic2(scope, opts.module);
|
|
8771
9098
|
const filesTouched = parseCsv5(resolvedFiles);
|
|
8772
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
9099
|
+
const missingPaths = filesTouched.filter((p) => !existsSync53(path33.resolve(root, p)));
|
|
8773
9100
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
8774
9101
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
8775
9102
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -8777,11 +9104,11 @@ function registerSessionEnd(session2) {
|
|
|
8777
9104
|
const cleanupObservations = async () => {
|
|
8778
9105
|
if (!opts.auto) return;
|
|
8779
9106
|
const obsFile = path33.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
8780
|
-
if (
|
|
9107
|
+
if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
|
|
8781
9108
|
});
|
|
8782
9109
|
};
|
|
8783
|
-
if (
|
|
8784
|
-
const existing = await
|
|
9110
|
+
if (existsSync53(paths.memoriesDir)) {
|
|
9111
|
+
const existing = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
8785
9112
|
const topicMatch = existing.find(
|
|
8786
9113
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
8787
9114
|
);
|
|
@@ -8834,7 +9161,7 @@ function parseCsv5(value) {
|
|
|
8834
9161
|
}
|
|
8835
9162
|
|
|
8836
9163
|
// src/commands/snapshot.ts
|
|
8837
|
-
import { existsSync as
|
|
9164
|
+
import { existsSync as existsSync54 } from "fs";
|
|
8838
9165
|
import { readdir as readdir4 } from "fs/promises";
|
|
8839
9166
|
import path34 from "path";
|
|
8840
9167
|
import "commander";
|
|
@@ -8869,14 +9196,14 @@ function registerSnapshot(program2) {
|
|
|
8869
9196
|
).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) => {
|
|
8870
9197
|
const root = findProjectRoot32(opts.dir);
|
|
8871
9198
|
const paths = resolveHaivePaths29(root);
|
|
8872
|
-
if (!
|
|
9199
|
+
if (!existsSync54(paths.haiveDir)) {
|
|
8873
9200
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
8874
9201
|
process.exitCode = 1;
|
|
8875
9202
|
return;
|
|
8876
9203
|
}
|
|
8877
9204
|
if (opts.list) {
|
|
8878
9205
|
const contractsDir = path34.join(paths.haiveDir, "contracts");
|
|
8879
|
-
if (!
|
|
9206
|
+
if (!existsSync54(contractsDir)) {
|
|
8880
9207
|
console.log(ui.dim("No contract snapshots found."));
|
|
8881
9208
|
return;
|
|
8882
9209
|
}
|
|
@@ -9000,7 +9327,7 @@ function detectFormat(filePath) {
|
|
|
9000
9327
|
}
|
|
9001
9328
|
|
|
9002
9329
|
// src/commands/hub.ts
|
|
9003
|
-
import { existsSync as
|
|
9330
|
+
import { existsSync as existsSync55 } from "fs";
|
|
9004
9331
|
import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
|
|
9005
9332
|
import path35 from "path";
|
|
9006
9333
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -9008,7 +9335,7 @@ import "commander";
|
|
|
9008
9335
|
import {
|
|
9009
9336
|
findProjectRoot as findProjectRoot33,
|
|
9010
9337
|
loadConfig as loadConfig6,
|
|
9011
|
-
loadMemoriesFromDir as
|
|
9338
|
+
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
9012
9339
|
resolveHaivePaths as resolveHaivePaths30,
|
|
9013
9340
|
saveConfig as saveConfig2,
|
|
9014
9341
|
serializeMemory as serializeMemory23
|
|
@@ -9102,7 +9429,7 @@ Next steps:
|
|
|
9102
9429
|
return;
|
|
9103
9430
|
}
|
|
9104
9431
|
const hubRoot = path35.resolve(root, config.hubPath);
|
|
9105
|
-
if (!
|
|
9432
|
+
if (!existsSync55(hubRoot)) {
|
|
9106
9433
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
9107
9434
|
process.exitCode = 1;
|
|
9108
9435
|
return;
|
|
@@ -9110,7 +9437,7 @@ Next steps:
|
|
|
9110
9437
|
const projectName = path35.basename(root);
|
|
9111
9438
|
const destDir = path35.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
9112
9439
|
await mkdir14(destDir, { recursive: true });
|
|
9113
|
-
const all = await
|
|
9440
|
+
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
9114
9441
|
const shared = all.filter(
|
|
9115
9442
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
9116
9443
|
!memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
@@ -9172,7 +9499,7 @@ Next steps:
|
|
|
9172
9499
|
}
|
|
9173
9500
|
const hubRoot = path35.resolve(root, config.hubPath);
|
|
9174
9501
|
const hubSharedDir = path35.join(hubRoot, ".ai", "memories", "shared");
|
|
9175
|
-
if (!
|
|
9502
|
+
if (!existsSync55(hubSharedDir)) {
|
|
9176
9503
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
9177
9504
|
return;
|
|
9178
9505
|
}
|
|
@@ -9227,7 +9554,7 @@ Next steps:
|
|
|
9227
9554
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
9228
9555
|
);
|
|
9229
9556
|
const sharedDir = path35.join(paths.memoriesDir, "shared");
|
|
9230
|
-
if (
|
|
9557
|
+
if (existsSync55(sharedDir)) {
|
|
9231
9558
|
const { readdir: readdir5 } = await import("fs/promises");
|
|
9232
9559
|
const sources = (await readdir5(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
9233
9560
|
console.log(`
|
|
@@ -9239,7 +9566,7 @@ Next steps:
|
|
|
9239
9566
|
} else {
|
|
9240
9567
|
console.log(ui.dim(" No imported shared memories yet."));
|
|
9241
9568
|
}
|
|
9242
|
-
const all = await
|
|
9569
|
+
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
9243
9570
|
const outgoing = all.filter(
|
|
9244
9571
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
9245
9572
|
);
|
|
@@ -9256,13 +9583,13 @@ Next steps:
|
|
|
9256
9583
|
|
|
9257
9584
|
// src/commands/stats.ts
|
|
9258
9585
|
import "commander";
|
|
9259
|
-
import { existsSync as
|
|
9586
|
+
import { existsSync as existsSync56 } from "fs";
|
|
9260
9587
|
import { mkdir as mkdir15, writeFile as writeFile27 } from "fs/promises";
|
|
9261
9588
|
import path36 from "path";
|
|
9262
9589
|
import {
|
|
9263
9590
|
aggregateUsage,
|
|
9264
9591
|
findProjectRoot as findProjectRoot34,
|
|
9265
|
-
loadMemoriesFromDir as
|
|
9592
|
+
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
9266
9593
|
loadUsageIndex as loadUsageIndex23,
|
|
9267
9594
|
parseSince,
|
|
9268
9595
|
readUsageEvents as readUsageEvents2,
|
|
@@ -9334,8 +9661,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
9334
9661
|
const size = await usageLogSize(paths);
|
|
9335
9662
|
let events = await readUsageEvents2(paths);
|
|
9336
9663
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
9337
|
-
if (
|
|
9338
|
-
const mems = await
|
|
9664
|
+
if (existsSync56(paths.memoriesDir)) {
|
|
9665
|
+
const mems = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
9339
9666
|
for (const { memory: memory2 } of mems) {
|
|
9340
9667
|
const fm = memory2.frontmatter;
|
|
9341
9668
|
if (fm.type === "session_recap") memoryCount.total_skipped_session++;
|
|
@@ -9555,14 +9882,14 @@ function summarize(name, t0, payload, notes) {
|
|
|
9555
9882
|
|
|
9556
9883
|
// src/commands/memory-suggest.ts
|
|
9557
9884
|
import { mkdir as mkdir16, writeFile as writeFile28 } from "fs/promises";
|
|
9558
|
-
import { existsSync as
|
|
9885
|
+
import { existsSync as existsSync57 } from "fs";
|
|
9559
9886
|
import path37 from "path";
|
|
9560
9887
|
import "commander";
|
|
9561
9888
|
import {
|
|
9562
9889
|
aggregateUsage as aggregateUsage2,
|
|
9563
9890
|
buildFrontmatter as buildFrontmatter11,
|
|
9564
9891
|
findProjectRoot as findProjectRoot36,
|
|
9565
|
-
loadMemoriesFromDir as
|
|
9892
|
+
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
9566
9893
|
memoryFilePath as memoryFilePath10,
|
|
9567
9894
|
parseSince as parseSince2,
|
|
9568
9895
|
readUsageEvents as readUsageEvents3,
|
|
@@ -9626,7 +9953,7 @@ function registerMemorySuggest(memory2) {
|
|
|
9626
9953
|
}
|
|
9627
9954
|
const created = [];
|
|
9628
9955
|
const skipped = [];
|
|
9629
|
-
const existing =
|
|
9956
|
+
const existing = existsSync57(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
|
|
9630
9957
|
for (const s of top) {
|
|
9631
9958
|
const slug = slugify(s.query);
|
|
9632
9959
|
if (!slug) {
|
|
@@ -9650,7 +9977,7 @@ function registerMemorySuggest(memory2) {
|
|
|
9650
9977
|
const body = renderTemplate(s);
|
|
9651
9978
|
const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
|
|
9652
9979
|
await mkdir16(path37.dirname(file), { recursive: true });
|
|
9653
|
-
if (
|
|
9980
|
+
if (existsSync57(file)) {
|
|
9654
9981
|
skipped.push({ query: s.query, reason: `file already exists at ${path37.relative(root, file)}` });
|
|
9655
9982
|
continue;
|
|
9656
9983
|
}
|
|
@@ -9748,14 +10075,14 @@ function truncate2(text, max) {
|
|
|
9748
10075
|
}
|
|
9749
10076
|
|
|
9750
10077
|
// src/commands/memory-archive.ts
|
|
9751
|
-
import { existsSync as
|
|
10078
|
+
import { existsSync as existsSync58 } from "fs";
|
|
9752
10079
|
import { writeFile as writeFile29 } from "fs/promises";
|
|
9753
10080
|
import path38 from "path";
|
|
9754
10081
|
import "commander";
|
|
9755
10082
|
import {
|
|
9756
10083
|
findProjectRoot as findProjectRoot37,
|
|
9757
10084
|
getUsage as getUsage18,
|
|
9758
|
-
loadMemoriesFromDir as
|
|
10085
|
+
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
9759
10086
|
loadUsageIndex as loadUsageIndex24,
|
|
9760
10087
|
resolveHaivePaths as resolveHaivePaths34,
|
|
9761
10088
|
serializeMemory as serializeMemory25
|
|
@@ -9767,7 +10094,7 @@ function registerMemoryArchive(memory2) {
|
|
|
9767
10094
|
).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").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) => {
|
|
9768
10095
|
const root = findProjectRoot37(opts.dir);
|
|
9769
10096
|
const paths = resolveHaivePaths34(root);
|
|
9770
|
-
if (!
|
|
10097
|
+
if (!existsSync58(paths.memoriesDir)) {
|
|
9771
10098
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9772
10099
|
process.exitCode = 1;
|
|
9773
10100
|
return;
|
|
@@ -9779,7 +10106,7 @@ function registerMemoryArchive(memory2) {
|
|
|
9779
10106
|
return;
|
|
9780
10107
|
}
|
|
9781
10108
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
9782
|
-
const all = await
|
|
10109
|
+
const all = await loadMemoriesFromDir31(paths.memoriesDir);
|
|
9783
10110
|
const usage = await loadUsageIndex24(paths);
|
|
9784
10111
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
9785
10112
|
const candidates = [];
|
|
@@ -9788,7 +10115,7 @@ function registerMemoryArchive(memory2) {
|
|
|
9788
10115
|
if (typeFilter && fm.type !== typeFilter) continue;
|
|
9789
10116
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
9790
10117
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
9791
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
10118
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync58(path38.join(paths.root, p)));
|
|
9792
10119
|
const isAnchorless = !hasAnyAnchor;
|
|
9793
10120
|
if (!isAnchorless && !allPathsGone) continue;
|
|
9794
10121
|
const u = getUsage18(usage, fm.id);
|
|
@@ -9862,7 +10189,7 @@ function parseDays(input) {
|
|
|
9862
10189
|
}
|
|
9863
10190
|
|
|
9864
10191
|
// src/commands/doctor.ts
|
|
9865
|
-
import { existsSync as
|
|
10192
|
+
import { existsSync as existsSync59 } from "fs";
|
|
9866
10193
|
import { stat } from "fs/promises";
|
|
9867
10194
|
import "path";
|
|
9868
10195
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -9873,7 +10200,7 @@ import {
|
|
|
9873
10200
|
getUsage as getUsage19,
|
|
9874
10201
|
loadCodeMap as loadCodeMap5,
|
|
9875
10202
|
loadConfig as loadConfig7,
|
|
9876
|
-
loadMemoriesFromDir as
|
|
10203
|
+
loadMemoriesFromDir as loadMemoriesFromDir32,
|
|
9877
10204
|
loadUsageIndex as loadUsageIndex25,
|
|
9878
10205
|
readUsageEvents as readUsageEvents4,
|
|
9879
10206
|
resolveHaivePaths as resolveHaivePaths35
|
|
@@ -9886,7 +10213,7 @@ function registerDoctor(program2) {
|
|
|
9886
10213
|
const root = findProjectRoot38(opts.dir);
|
|
9887
10214
|
const paths = resolveHaivePaths35(root);
|
|
9888
10215
|
const findings = [];
|
|
9889
|
-
if (!
|
|
10216
|
+
if (!existsSync59(paths.haiveDir)) {
|
|
9890
10217
|
findings.push({
|
|
9891
10218
|
severity: "error",
|
|
9892
10219
|
code: "not-initialized",
|
|
@@ -9895,7 +10222,7 @@ function registerDoctor(program2) {
|
|
|
9895
10222
|
});
|
|
9896
10223
|
return emit(findings, opts);
|
|
9897
10224
|
}
|
|
9898
|
-
if (!
|
|
10225
|
+
if (!existsSync59(paths.projectContext)) {
|
|
9899
10226
|
findings.push({
|
|
9900
10227
|
severity: "warn",
|
|
9901
10228
|
code: "no-project-context",
|
|
@@ -9915,7 +10242,7 @@ function registerDoctor(program2) {
|
|
|
9915
10242
|
});
|
|
9916
10243
|
}
|
|
9917
10244
|
}
|
|
9918
|
-
const memories =
|
|
10245
|
+
const memories = existsSync59(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
|
|
9919
10246
|
const now = Date.now();
|
|
9920
10247
|
if (memories.length === 0) {
|
|
9921
10248
|
findings.push({
|
|
@@ -10039,7 +10366,7 @@ function registerDoctor(program2) {
|
|
|
10039
10366
|
timeout: 3e3,
|
|
10040
10367
|
stdio: ["ignore", "pipe", "ignore"]
|
|
10041
10368
|
}).trim();
|
|
10042
|
-
const cliVersion = "0.9.
|
|
10369
|
+
const cliVersion = "0.9.6";
|
|
10043
10370
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
10044
10371
|
findings.push({
|
|
10045
10372
|
severity: "warn",
|
|
@@ -10088,11 +10415,11 @@ function isSearchTool(name) {
|
|
|
10088
10415
|
}
|
|
10089
10416
|
|
|
10090
10417
|
// src/commands/playback.ts
|
|
10091
|
-
import { existsSync as
|
|
10418
|
+
import { existsSync as existsSync60 } from "fs";
|
|
10092
10419
|
import "commander";
|
|
10093
10420
|
import {
|
|
10094
10421
|
findProjectRoot as findProjectRoot39,
|
|
10095
|
-
loadMemoriesFromDir as
|
|
10422
|
+
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
10096
10423
|
parseSince as parseSince3,
|
|
10097
10424
|
readUsageEvents as readUsageEvents5,
|
|
10098
10425
|
resolveHaivePaths as resolveHaivePaths36
|
|
@@ -10118,7 +10445,7 @@ function registerPlayback(program2) {
|
|
|
10118
10445
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
10119
10446
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
10120
10447
|
const sessions = bucketSessions(filtered, gapMs);
|
|
10121
|
-
const all =
|
|
10448
|
+
const all = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
|
|
10122
10449
|
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);
|
|
10123
10450
|
const enriched = sessions.map((s, i) => {
|
|
10124
10451
|
const startMs = Date.parse(s.start);
|
|
@@ -10312,11 +10639,11 @@ function runCommand3(cmd, args, cwd) {
|
|
|
10312
10639
|
}
|
|
10313
10640
|
|
|
10314
10641
|
// src/commands/welcome.ts
|
|
10315
|
-
import { existsSync as
|
|
10642
|
+
import { existsSync as existsSync61 } from "fs";
|
|
10316
10643
|
import "commander";
|
|
10317
10644
|
import {
|
|
10318
10645
|
findProjectRoot as findProjectRoot41,
|
|
10319
|
-
loadMemoriesFromDir as
|
|
10646
|
+
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
10320
10647
|
resolveHaivePaths as resolveHaivePaths38
|
|
10321
10648
|
} from "@hiveai/core";
|
|
10322
10649
|
var TYPE_RANK = {
|
|
@@ -10333,12 +10660,12 @@ function registerWelcome(program2) {
|
|
|
10333
10660
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10334
10661
|
const root = findProjectRoot41(opts.dir);
|
|
10335
10662
|
const paths = resolveHaivePaths38(root);
|
|
10336
|
-
if (!
|
|
10663
|
+
if (!existsSync61(paths.memoriesDir)) {
|
|
10337
10664
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
10338
10665
|
process.exitCode = 1;
|
|
10339
10666
|
return;
|
|
10340
10667
|
}
|
|
10341
|
-
const all = await
|
|
10668
|
+
const all = await loadMemoriesFromDir34(paths.memoriesDir);
|
|
10342
10669
|
const team = all.filter(
|
|
10343
10670
|
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
10344
10671
|
);
|
|
@@ -10374,18 +10701,18 @@ function registerWelcome(program2) {
|
|
|
10374
10701
|
}
|
|
10375
10702
|
|
|
10376
10703
|
// src/commands/memory-lint.ts
|
|
10377
|
-
import { existsSync as
|
|
10704
|
+
import { existsSync as existsSync63 } from "fs";
|
|
10378
10705
|
import "commander";
|
|
10379
10706
|
import {
|
|
10380
10707
|
findProjectRoot as findProjectRoot42,
|
|
10381
|
-
loadMemoriesFromDir as
|
|
10708
|
+
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
10382
10709
|
resolveHaivePaths as resolveHaivePaths39
|
|
10383
10710
|
} from "@hiveai/core";
|
|
10384
10711
|
async function lintMemoriesAsync(root) {
|
|
10385
10712
|
const paths = resolveHaivePaths39(root);
|
|
10386
10713
|
const out = [];
|
|
10387
|
-
if (!
|
|
10388
|
-
const loaded = await
|
|
10714
|
+
if (!existsSync63(paths.memoriesDir)) return out;
|
|
10715
|
+
const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
|
|
10389
10716
|
const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
|
|
10390
10717
|
for (const { filePath, memory: memory2 } of loaded) {
|
|
10391
10718
|
const fm = memory2.frontmatter;
|
|
@@ -10472,11 +10799,182 @@ function registerMemoryLint(parent) {
|
|
|
10472
10799
|
});
|
|
10473
10800
|
}
|
|
10474
10801
|
|
|
10802
|
+
// src/commands/memory-suggest-topic.ts
|
|
10803
|
+
import "commander";
|
|
10804
|
+
import { MemoryTypeSchema as MemoryTypeSchema2, suggestTopicKey as suggestTopicKey2 } from "@hiveai/core";
|
|
10805
|
+
function registerMemorySuggestTopic(memory2) {
|
|
10806
|
+
memory2.command("suggest-topic").description("Suggest a stable topic key (topic-upsert) from type + title phrase").requiredOption(
|
|
10807
|
+
"--type <type>",
|
|
10808
|
+
"convention | decision | gotcha | architecture | glossary | attempt | session_recap"
|
|
10809
|
+
).argument("<title>", "Short title or phrase to slugify").action((title, opts) => {
|
|
10810
|
+
const parsed = MemoryTypeSchema2.safeParse(opts.type);
|
|
10811
|
+
if (!parsed.success) {
|
|
10812
|
+
ui.error(`Invalid type: ${opts.type}`);
|
|
10813
|
+
process.exit(1);
|
|
10814
|
+
}
|
|
10815
|
+
const suggestion = suggestTopicKey2(parsed.data, title);
|
|
10816
|
+
console.log(JSON.stringify({ type: parsed.data, ...suggestion }, null, 2));
|
|
10817
|
+
});
|
|
10818
|
+
}
|
|
10819
|
+
|
|
10820
|
+
// src/commands/resolve-project.ts
|
|
10821
|
+
import path40 from "path";
|
|
10822
|
+
import "commander";
|
|
10823
|
+
import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
|
|
10824
|
+
function registerResolveProject(program2) {
|
|
10825
|
+
program2.command("resolve-project").description(
|
|
10826
|
+
"Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
10827
|
+
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
10828
|
+
const info = resolveProjectInfo2({ cwd: path40.resolve(opts.dir) });
|
|
10829
|
+
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
10830
|
+
});
|
|
10831
|
+
}
|
|
10832
|
+
|
|
10833
|
+
// src/commands/runtime-journal.ts
|
|
10834
|
+
import { existsSync as existsSync64 } from "fs";
|
|
10835
|
+
import path41 from "path";
|
|
10836
|
+
import "commander";
|
|
10837
|
+
import {
|
|
10838
|
+
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
10839
|
+
findProjectRoot as findProjectRoot43,
|
|
10840
|
+
readRuntimeJournalTail as readRuntimeJournalTail2,
|
|
10841
|
+
resolveHaivePaths as resolveHaivePaths40
|
|
10842
|
+
} from "@hiveai/core";
|
|
10843
|
+
function registerRuntime(program2) {
|
|
10844
|
+
const runtime = program2.command("runtime").description(
|
|
10845
|
+
"Local-only .ai/.runtime helpers (not versioned team memory). See session-journal.ndjson."
|
|
10846
|
+
);
|
|
10847
|
+
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
10848
|
+
journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
|
|
10849
|
+
const root = path41.resolve(opts.dir ?? process.cwd());
|
|
10850
|
+
const paths = resolveHaivePaths40(findProjectRoot43(root));
|
|
10851
|
+
const raw = opts.kind ?? "note";
|
|
10852
|
+
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
10853
|
+
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
10854
|
+
ui.success(`Appended to ${path41.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
|
|
10855
|
+
});
|
|
10856
|
+
journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
|
|
10857
|
+
const root = path41.resolve(opts.dir ?? process.cwd());
|
|
10858
|
+
const paths = resolveHaivePaths40(findProjectRoot43(root));
|
|
10859
|
+
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
10860
|
+
if (!existsSync64(paths.haiveDir)) {
|
|
10861
|
+
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
10862
|
+
process.exitCode = 1;
|
|
10863
|
+
return;
|
|
10864
|
+
}
|
|
10865
|
+
const entries = await readRuntimeJournalTail2(paths, limit);
|
|
10866
|
+
if (entries.length === 0) {
|
|
10867
|
+
ui.info("Journal empty or missing.");
|
|
10868
|
+
return;
|
|
10869
|
+
}
|
|
10870
|
+
console.log(JSON.stringify({ entries, count: entries.length }, null, 2));
|
|
10871
|
+
});
|
|
10872
|
+
}
|
|
10873
|
+
|
|
10874
|
+
// src/commands/memory-timeline.ts
|
|
10875
|
+
import { existsSync as existsSync65 } from "fs";
|
|
10876
|
+
import path43 from "path";
|
|
10877
|
+
import "commander";
|
|
10878
|
+
import {
|
|
10879
|
+
collectTimelineEntries as collectTimelineEntries2,
|
|
10880
|
+
findProjectRoot as findProjectRoot44,
|
|
10881
|
+
resolveHaivePaths as resolveHaivePaths41
|
|
10882
|
+
} from "@hiveai/core";
|
|
10883
|
+
function registerMemoryTimeline(memory2) {
|
|
10884
|
+
memory2.command("timeline").description(
|
|
10885
|
+
"List related memories chronologically (topic, related_ids, anchors) \u2014 same logic as MCP mem_timeline."
|
|
10886
|
+
).option("--id <id>", "seed memory id").option("--topic <key>", "filter by frontmatter.topic (use without --id for topic-only)").option("-n, --limit <n>", "max entries", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
|
|
10887
|
+
if (!opts.id && !opts.topic) {
|
|
10888
|
+
ui.error("Provide --id and/or --topic.");
|
|
10889
|
+
process.exitCode = 1;
|
|
10890
|
+
return;
|
|
10891
|
+
}
|
|
10892
|
+
const root = path43.resolve(opts.dir ?? process.cwd());
|
|
10893
|
+
const paths = resolveHaivePaths41(findProjectRoot44(root));
|
|
10894
|
+
if (!existsSync65(paths.memoriesDir)) {
|
|
10895
|
+
ui.error("No memories \u2014 run `haive init`.");
|
|
10896
|
+
process.exitCode = 1;
|
|
10897
|
+
return;
|
|
10898
|
+
}
|
|
10899
|
+
const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
10900
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
10901
|
+
const { entries, notice } = collectTimelineEntries2(all, {
|
|
10902
|
+
memoryId: opts.id,
|
|
10903
|
+
topic: opts.topic,
|
|
10904
|
+
limit
|
|
10905
|
+
});
|
|
10906
|
+
if (notice) ui.warn(notice);
|
|
10907
|
+
console.log(JSON.stringify({ entries, total: entries.length }, null, 2));
|
|
10908
|
+
});
|
|
10909
|
+
}
|
|
10910
|
+
|
|
10911
|
+
// src/commands/memory-conflict-candidates.ts
|
|
10912
|
+
import { existsSync as existsSync66 } from "fs";
|
|
10913
|
+
import path44 from "path";
|
|
10914
|
+
import "commander";
|
|
10915
|
+
import {
|
|
10916
|
+
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
10917
|
+
findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
|
|
10918
|
+
findProjectRoot as findProjectRoot45,
|
|
10919
|
+
resolveHaivePaths as resolveHaivePaths42
|
|
10920
|
+
} from "@hiveai/core";
|
|
10921
|
+
function parseTypes(csv) {
|
|
10922
|
+
const allowed = ["decision", "architecture", "convention", "gotcha"];
|
|
10923
|
+
const parts = csv.split(",").map((s) => s.trim().toLowerCase());
|
|
10924
|
+
const out = parts.filter((p) => allowed.includes(p));
|
|
10925
|
+
return out.length ? out : ["decision", "architecture"];
|
|
10926
|
+
}
|
|
10927
|
+
function registerMemoryConflictCandidates(memory2) {
|
|
10928
|
+
memory2.command("conflict-candidates").description(
|
|
10929
|
+
"Heuristic conflict candidates (lexical Jaccard + same-topic validated/rejected pairs) \u2014 aligns with MCP mem_conflict_candidates."
|
|
10930
|
+
).option("-d, --dir <dir>", "project root", process.cwd()).option("--since-days <n>", "only memories created within N days (lexical scan)", "365").option(
|
|
10931
|
+
"--types <csv>",
|
|
10932
|
+
"decision,architecture,convention,gotcha (lexical scan)",
|
|
10933
|
+
"decision,architecture"
|
|
10934
|
+
).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) => {
|
|
10935
|
+
const root = path44.resolve(opts.dir ?? process.cwd());
|
|
10936
|
+
const paths = resolveHaivePaths42(findProjectRoot45(root));
|
|
10937
|
+
if (!existsSync66(paths.memoriesDir)) {
|
|
10938
|
+
ui.error("No memories \u2014 run `haive init`.");
|
|
10939
|
+
process.exitCode = 1;
|
|
10940
|
+
return;
|
|
10941
|
+
}
|
|
10942
|
+
const sinceDays = Math.max(1, parseInt(opts.sinceDays, 10) || 365);
|
|
10943
|
+
const minJaccard = parseFloat(opts.minJaccard) || 0.45;
|
|
10944
|
+
const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
|
|
10945
|
+
const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
|
|
10946
|
+
const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
|
|
10947
|
+
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
10948
|
+
const lexical = findLexicalConflictPairs2(all, {
|
|
10949
|
+
sinceDays,
|
|
10950
|
+
types: parseTypes(opts.types),
|
|
10951
|
+
minJaccard,
|
|
10952
|
+
maxPairs,
|
|
10953
|
+
maxScan
|
|
10954
|
+
});
|
|
10955
|
+
const topicStatusPairs = findTopicStatusConflictPairs2(all, maxTopicPairs);
|
|
10956
|
+
console.log(
|
|
10957
|
+
JSON.stringify(
|
|
10958
|
+
{
|
|
10959
|
+
pairs: lexical.pairs,
|
|
10960
|
+
topic_status_pairs: topicStatusPairs,
|
|
10961
|
+
scanned: lexical.scanned,
|
|
10962
|
+
truncated: lexical.truncated
|
|
10963
|
+
},
|
|
10964
|
+
null,
|
|
10965
|
+
2
|
|
10966
|
+
)
|
|
10967
|
+
);
|
|
10968
|
+
});
|
|
10969
|
+
}
|
|
10970
|
+
|
|
10475
10971
|
// src/index.ts
|
|
10476
|
-
var program = new
|
|
10477
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.
|
|
10972
|
+
var program = new Command47();
|
|
10973
|
+
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.6");
|
|
10478
10974
|
registerInit(program);
|
|
10479
10975
|
registerWelcome(program);
|
|
10976
|
+
registerResolveProject(program);
|
|
10977
|
+
registerRuntime(program);
|
|
10480
10978
|
registerMcp(program);
|
|
10481
10979
|
registerBriefing(program);
|
|
10482
10980
|
registerTui(program);
|
|
@@ -10507,6 +11005,9 @@ registerMemoryImport(memory);
|
|
|
10507
11005
|
registerMemoryImportChangelog(memory);
|
|
10508
11006
|
registerMemoryDigest(memory);
|
|
10509
11007
|
registerMemorySuggest(memory);
|
|
11008
|
+
registerMemorySuggestTopic(memory);
|
|
11009
|
+
registerMemoryTimeline(memory);
|
|
11010
|
+
registerMemoryConflictCandidates(memory);
|
|
10510
11011
|
registerMemoryArchive(memory);
|
|
10511
11012
|
registerMemoryLint(memory);
|
|
10512
11013
|
var session = program.command("session").description("Manage session lifecycle");
|