@hiveai/cli 0.2.1 → 0.2.3
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 +498 -203
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command24 } from "commander";
|
|
5
5
|
|
|
6
|
-
// src/commands/
|
|
6
|
+
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync } from "fs";
|
|
8
|
-
import
|
|
8
|
+
import { readFile } from "fs/promises";
|
|
9
9
|
import "commander";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
findProjectRoot,
|
|
12
|
+
literalMatchesAllTokens,
|
|
13
|
+
loadMemoriesFromDir,
|
|
14
|
+
memoryMatchesAnchorPaths,
|
|
15
|
+
resolveHaivePaths,
|
|
16
|
+
tokenizeQuery
|
|
17
|
+
} from "@hiveai/core";
|
|
11
18
|
|
|
12
19
|
// src/utils/ui.ts
|
|
13
20
|
import pc from "picocolors";
|
|
@@ -17,16 +24,107 @@ var ui = {
|
|
|
17
24
|
warn: (msg) => console.log(pc.yellow("\u26A0"), msg),
|
|
18
25
|
error: (msg) => console.error(pc.red("\u2717"), msg),
|
|
19
26
|
dim: (msg) => pc.dim(msg),
|
|
20
|
-
bold: (msg) => pc.bold(msg)
|
|
27
|
+
bold: (msg) => pc.bold(msg),
|
|
28
|
+
green: (msg) => pc.green(msg),
|
|
29
|
+
yellow: (msg) => pc.yellow(msg),
|
|
30
|
+
red: (msg) => pc.red(msg),
|
|
31
|
+
statusBadge: (status) => {
|
|
32
|
+
switch (status) {
|
|
33
|
+
case "validated":
|
|
34
|
+
return pc.green(status);
|
|
35
|
+
case "proposed":
|
|
36
|
+
return pc.yellow(status);
|
|
37
|
+
case "stale":
|
|
38
|
+
return pc.yellow(status);
|
|
39
|
+
case "rejected":
|
|
40
|
+
return pc.red(status);
|
|
41
|
+
case "deprecated":
|
|
42
|
+
return pc.dim(status);
|
|
43
|
+
default:
|
|
44
|
+
return pc.dim(status);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
21
47
|
};
|
|
22
48
|
|
|
49
|
+
// src/commands/briefing.ts
|
|
50
|
+
function registerBriefing(program2) {
|
|
51
|
+
program2.command("briefing").description(
|
|
52
|
+
"Print project context + relevant memories in one shot \u2014 ideal for agent onboarding"
|
|
53
|
+
).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (anchors memories)").option("--max-memories <n>", "cap on memories surfaced", "10").option(
|
|
54
|
+
"--scope <scope>",
|
|
55
|
+
"personal | team | module (default: team + validated only)",
|
|
56
|
+
"team"
|
|
57
|
+
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
58
|
+
const root = findProjectRoot(opts.dir);
|
|
59
|
+
const paths = resolveHaivePaths(root);
|
|
60
|
+
if (existsSync(paths.projectContext)) {
|
|
61
|
+
const ctx = await readFile(paths.projectContext, "utf8");
|
|
62
|
+
console.log(`${ui.bold("=== Project Context ===")}
|
|
63
|
+
`);
|
|
64
|
+
console.log(ctx.trim());
|
|
65
|
+
console.log();
|
|
66
|
+
} else {
|
|
67
|
+
ui.warn(
|
|
68
|
+
"No project-context.md found. Run `haive init` and the `bootstrap_project` MCP prompt to set it up."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (!existsSync(paths.memoriesDir)) return;
|
|
72
|
+
const all = await loadMemoriesFromDir(paths.memoriesDir);
|
|
73
|
+
const filePaths = parseCsv(opts.files);
|
|
74
|
+
const tokens = opts.task ? tokenizeQuery(opts.task) : null;
|
|
75
|
+
const maxMemories = Math.max(1, Number(opts.maxMemories ?? 10));
|
|
76
|
+
const scopeFilter = opts.scope ?? "team";
|
|
77
|
+
const candidates = all.filter(({ memory: mem }) => {
|
|
78
|
+
const fm = mem.frontmatter;
|
|
79
|
+
if (fm.status === "rejected" || fm.status === "deprecated") return false;
|
|
80
|
+
if (scopeFilter !== "all" && fm.scope !== scopeFilter) return false;
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
const scored = candidates.map(({ memory: mem, filePath }) => {
|
|
84
|
+
const fm = mem.frontmatter;
|
|
85
|
+
let score = 0;
|
|
86
|
+
if (fm.status === "validated") score += 3;
|
|
87
|
+
else if (fm.status === "proposed") score += 1;
|
|
88
|
+
if (filePaths.length > 0 && memoryMatchesAnchorPaths(mem, filePaths)) score += 4;
|
|
89
|
+
if (tokens && literalMatchesAllTokens(mem, tokens)) score += 3;
|
|
90
|
+
return { memory: mem, filePath, score };
|
|
91
|
+
});
|
|
92
|
+
scored.sort((a, b) => b.score - a.score);
|
|
93
|
+
const top = scored.slice(0, maxMemories);
|
|
94
|
+
if (top.length === 0) {
|
|
95
|
+
ui.info("No relevant memories found.");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
console.log(`${ui.bold("=== Relevant Memories ===")}
|
|
99
|
+
`);
|
|
100
|
+
for (const { memory: mem } of top) {
|
|
101
|
+
const fm = mem.frontmatter;
|
|
102
|
+
const badge = ui.statusBadge(fm.status);
|
|
103
|
+
console.log(
|
|
104
|
+
`${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}`
|
|
105
|
+
);
|
|
106
|
+
console.log(mem.body.trim());
|
|
107
|
+
console.log();
|
|
108
|
+
}
|
|
109
|
+
console.log(ui.dim(`${top.length} memor${top.length === 1 ? "y" : "ies"} surfaced`));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function parseCsv(value) {
|
|
113
|
+
if (!value) return [];
|
|
114
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
23
117
|
// src/commands/embeddings.ts
|
|
118
|
+
import { existsSync as existsSync2 } from "fs";
|
|
119
|
+
import path from "path";
|
|
120
|
+
import "commander";
|
|
121
|
+
import { findProjectRoot as findProjectRoot2, resolveHaivePaths as resolveHaivePaths2 } from "@hiveai/core";
|
|
24
122
|
function registerEmbeddings(program2) {
|
|
25
123
|
const embeddings = program2.command("embeddings").description("Manage local embeddings index for semantic search");
|
|
26
124
|
embeddings.command("index").description("Generate or refresh the embeddings index for all memories").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
27
|
-
const root =
|
|
28
|
-
const paths =
|
|
29
|
-
if (!
|
|
125
|
+
const root = findProjectRoot2(opts.dir);
|
|
126
|
+
const paths = resolveHaivePaths2(root);
|
|
127
|
+
if (!existsSync2(paths.memoriesDir)) {
|
|
30
128
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
31
129
|
process.exitCode = 1;
|
|
32
130
|
return;
|
|
@@ -41,8 +139,8 @@ function registerEmbeddings(program2) {
|
|
|
41
139
|
);
|
|
42
140
|
});
|
|
43
141
|
embeddings.command("query <text>").description("Run a semantic search against the local embeddings index").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "10").option("--min-score <n>", "minimum cosine similarity (0-1)", "0").action(async (text, opts) => {
|
|
44
|
-
const root =
|
|
45
|
-
const paths =
|
|
142
|
+
const root = findProjectRoot2(opts.dir);
|
|
143
|
+
const paths = resolveHaivePaths2(root);
|
|
46
144
|
const { semanticSearch } = await loadEmbeddings();
|
|
47
145
|
const result = await semanticSearch(paths, text, {
|
|
48
146
|
limit: Number(opts.limit ?? 10),
|
|
@@ -64,8 +162,8 @@ function registerEmbeddings(program2) {
|
|
|
64
162
|
}
|
|
65
163
|
});
|
|
66
164
|
embeddings.command("status").description("Show the embeddings index status").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
67
|
-
const root =
|
|
68
|
-
const paths =
|
|
165
|
+
const root = findProjectRoot2(opts.dir);
|
|
166
|
+
const paths = resolveHaivePaths2(root);
|
|
69
167
|
const { indexStat } = await loadEmbeddings();
|
|
70
168
|
const stat = await indexStat(paths);
|
|
71
169
|
if (!stat.exists) {
|
|
@@ -81,11 +179,11 @@ function registerEmbeddings(program2) {
|
|
|
81
179
|
async function loadEmbeddings() {
|
|
82
180
|
try {
|
|
83
181
|
return await import("@hiveai/embeddings");
|
|
84
|
-
} catch
|
|
182
|
+
} catch {
|
|
85
183
|
ui.error(
|
|
86
184
|
"Could not load @hiveai/embeddings. Run: npm install -g @hiveai/embeddings (or `pnpm build` in the monorepo)"
|
|
87
185
|
);
|
|
88
|
-
|
|
186
|
+
process.exit(1);
|
|
89
187
|
}
|
|
90
188
|
}
|
|
91
189
|
|
|
@@ -95,8 +193,8 @@ import "commander";
|
|
|
95
193
|
import {
|
|
96
194
|
buildCodeMap,
|
|
97
195
|
codeMapPath,
|
|
98
|
-
findProjectRoot as
|
|
99
|
-
resolveHaivePaths as
|
|
196
|
+
findProjectRoot as findProjectRoot3,
|
|
197
|
+
resolveHaivePaths as resolveHaivePaths3,
|
|
100
198
|
saveCodeMap
|
|
101
199
|
} from "@hiveai/core";
|
|
102
200
|
function registerIndexCode(program2) {
|
|
@@ -107,8 +205,8 @@ function registerIndexCode(program2) {
|
|
|
107
205
|
"extra directory names to skip (comma-separated)",
|
|
108
206
|
""
|
|
109
207
|
).action(async (opts) => {
|
|
110
|
-
const root =
|
|
111
|
-
const paths =
|
|
208
|
+
const root = findProjectRoot3(opts.dir);
|
|
209
|
+
const paths = resolveHaivePaths3(root);
|
|
112
210
|
const extraExcludes = (opts.exclude ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
113
211
|
ui.info(`Indexing source files in ${root}\u2026`);
|
|
114
212
|
const map = await buildCodeMap(root, {
|
|
@@ -136,10 +234,10 @@ function registerIndexCode(program2) {
|
|
|
136
234
|
|
|
137
235
|
// src/commands/init.ts
|
|
138
236
|
import { mkdir, writeFile } from "fs/promises";
|
|
139
|
-
import { existsSync as
|
|
237
|
+
import { existsSync as existsSync3 } from "fs";
|
|
140
238
|
import path3 from "path";
|
|
141
239
|
import "commander";
|
|
142
|
-
import { resolveHaivePaths as
|
|
240
|
+
import { resolveHaivePaths as resolveHaivePaths4 } from "@hiveai/core";
|
|
143
241
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
144
242
|
|
|
145
243
|
> Generated by \`haive init\`. Edit this file (or let your AI agent fill it via the upcoming MCP \`bootstrap_project\` tool).
|
|
@@ -168,15 +266,15 @@ Memories live under \`.ai/memories/\` (personal/team/module).
|
|
|
168
266
|
function registerInit(program2) {
|
|
169
267
|
program2.command("init").description("Initialize a hAIve project (.ai/ structure + bridge files)").option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md").action(async (opts) => {
|
|
170
268
|
const root = path3.resolve(opts.dir);
|
|
171
|
-
const paths =
|
|
172
|
-
if (
|
|
269
|
+
const paths = resolveHaivePaths4(root);
|
|
270
|
+
if (existsSync3(paths.haiveDir)) {
|
|
173
271
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
174
272
|
}
|
|
175
273
|
await mkdir(paths.personalDir, { recursive: true });
|
|
176
274
|
await mkdir(paths.teamDir, { recursive: true });
|
|
177
275
|
await mkdir(paths.moduleDir, { recursive: true });
|
|
178
276
|
await mkdir(paths.modulesContextDir, { recursive: true });
|
|
179
|
-
if (!
|
|
277
|
+
if (!existsSync3(paths.projectContext)) {
|
|
180
278
|
await writeFile(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
181
279
|
ui.success(`Created ${path3.relative(root, paths.projectContext)}`);
|
|
182
280
|
}
|
|
@@ -191,7 +289,7 @@ function registerInit(program2) {
|
|
|
191
289
|
}
|
|
192
290
|
async function writeBridge(root, relPath) {
|
|
193
291
|
const target = path3.join(root, relPath);
|
|
194
|
-
if (
|
|
292
|
+
if (existsSync3(target)) {
|
|
195
293
|
ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
|
|
196
294
|
return;
|
|
197
295
|
}
|
|
@@ -201,11 +299,11 @@ async function writeBridge(root, relPath) {
|
|
|
201
299
|
}
|
|
202
300
|
|
|
203
301
|
// src/commands/install-hooks.ts
|
|
204
|
-
import { mkdir as mkdir2, writeFile as writeFile2, chmod, readFile } from "fs/promises";
|
|
205
|
-
import { existsSync as
|
|
302
|
+
import { mkdir as mkdir2, writeFile as writeFile2, chmod, readFile as readFile2 } from "fs/promises";
|
|
303
|
+
import { existsSync as existsSync4 } from "fs";
|
|
206
304
|
import path4 from "path";
|
|
207
305
|
import "commander";
|
|
208
|
-
import { findProjectRoot as
|
|
306
|
+
import { findProjectRoot as findProjectRoot5 } from "@hiveai/core";
|
|
209
307
|
var HOOK_MARKER = "# hAIve auto-generated";
|
|
210
308
|
var HOOK_BODY = `#!/bin/sh
|
|
211
309
|
${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
|
|
@@ -221,9 +319,9 @@ fi
|
|
|
221
319
|
var HOOKS = ["post-merge", "post-rewrite"];
|
|
222
320
|
function registerInstallHooks(program2) {
|
|
223
321
|
program2.command("install-hooks").description("Install git hooks that run `haive sync` after pull/merge").option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks").action(async (opts) => {
|
|
224
|
-
const root =
|
|
322
|
+
const root = findProjectRoot5(opts.dir);
|
|
225
323
|
const gitDir = path4.join(root, ".git");
|
|
226
|
-
if (!
|
|
324
|
+
if (!existsSync4(gitDir)) {
|
|
227
325
|
ui.error(`No .git directory at ${root}.`);
|
|
228
326
|
process.exitCode = 1;
|
|
229
327
|
return;
|
|
@@ -234,8 +332,8 @@ function registerInstallHooks(program2) {
|
|
|
234
332
|
let skipped = 0;
|
|
235
333
|
for (const name of HOOKS) {
|
|
236
334
|
const file = path4.join(hooksDir, name);
|
|
237
|
-
if (
|
|
238
|
-
const existing = await
|
|
335
|
+
if (existsSync4(file) && !opts.force) {
|
|
336
|
+
const existing = await readFile2(file, "utf8");
|
|
239
337
|
if (!existing.includes(HOOK_MARKER)) {
|
|
240
338
|
ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
|
|
241
339
|
skipped++;
|
|
@@ -253,16 +351,16 @@ function registerInstallHooks(program2) {
|
|
|
253
351
|
|
|
254
352
|
// src/commands/mcp.ts
|
|
255
353
|
import { spawn } from "child_process";
|
|
256
|
-
import { existsSync as
|
|
354
|
+
import { existsSync as existsSync5 } from "fs";
|
|
257
355
|
import { createRequire } from "module";
|
|
258
356
|
import path5 from "path";
|
|
259
357
|
import { fileURLToPath } from "url";
|
|
260
358
|
import "commander";
|
|
261
|
-
import { findProjectRoot as
|
|
359
|
+
import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
|
|
262
360
|
var require2 = createRequire(import.meta.url);
|
|
263
361
|
function registerMcp(program2) {
|
|
264
362
|
program2.command("mcp").description("Start the hAIve MCP server (stdio transport)").option("-d, --dir <dir>", "project root (defaults to nearest .ai/ or .git/)").action((opts) => {
|
|
265
|
-
const root =
|
|
363
|
+
const root = findProjectRoot6(opts.dir);
|
|
266
364
|
const bin = locateMcpBin();
|
|
267
365
|
if (!bin) {
|
|
268
366
|
ui.error(
|
|
@@ -282,29 +380,29 @@ function locateMcpBin() {
|
|
|
282
380
|
const pkgPath = require2.resolve("@hiveai/mcp/package.json");
|
|
283
381
|
const pkgDir = path5.dirname(pkgPath);
|
|
284
382
|
const candidate = path5.join(pkgDir, "dist", "index.js");
|
|
285
|
-
if (
|
|
383
|
+
if (existsSync5(candidate)) return candidate;
|
|
286
384
|
} catch {
|
|
287
385
|
}
|
|
288
386
|
const here = path5.dirname(fileURLToPath(import.meta.url));
|
|
289
387
|
const sibling = path5.resolve(here, "..", "..", "..", "mcp", "dist", "index.js");
|
|
290
|
-
if (
|
|
388
|
+
if (existsSync5(sibling)) return sibling;
|
|
291
389
|
return null;
|
|
292
390
|
}
|
|
293
391
|
|
|
294
392
|
// src/commands/sync.ts
|
|
295
393
|
import { spawnSync } from "child_process";
|
|
296
394
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
297
|
-
import { existsSync as
|
|
395
|
+
import { existsSync as existsSync6 } from "fs";
|
|
298
396
|
import "path";
|
|
299
397
|
import "commander";
|
|
300
398
|
import {
|
|
301
399
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
302
|
-
findProjectRoot as
|
|
400
|
+
findProjectRoot as findProjectRoot7,
|
|
303
401
|
getUsage,
|
|
304
402
|
isAutoPromoteEligible,
|
|
305
|
-
loadMemoriesFromDir,
|
|
403
|
+
loadMemoriesFromDir as loadMemoriesFromDir2,
|
|
306
404
|
loadUsageIndex,
|
|
307
|
-
resolveHaivePaths as
|
|
405
|
+
resolveHaivePaths as resolveHaivePaths5,
|
|
308
406
|
serializeMemory,
|
|
309
407
|
verifyAnchor
|
|
310
408
|
} from "@hiveai/core";
|
|
@@ -313,9 +411,9 @@ function registerSync(program2) {
|
|
|
313
411
|
"--since <ref>",
|
|
314
412
|
"git ref/commit to compare against; report memories added/modified/removed since"
|
|
315
413
|
).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").action(async (opts) => {
|
|
316
|
-
const root =
|
|
317
|
-
const paths =
|
|
318
|
-
if (!
|
|
414
|
+
const root = findProjectRoot7(opts.dir);
|
|
415
|
+
const paths = resolveHaivePaths5(root);
|
|
416
|
+
if (!existsSync6(paths.memoriesDir)) {
|
|
319
417
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
320
418
|
process.exitCode = 1;
|
|
321
419
|
return;
|
|
@@ -327,7 +425,7 @@ function registerSync(program2) {
|
|
|
327
425
|
let revalidated = 0;
|
|
328
426
|
let promoted = 0;
|
|
329
427
|
if (opts.verify !== false) {
|
|
330
|
-
const memories = await
|
|
428
|
+
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
331
429
|
for (const { memory: memory2, filePath } of memories) {
|
|
332
430
|
const isAnchored = memory2.frontmatter.anchor.paths.length > 0 || memory2.frontmatter.anchor.symbols.length > 0;
|
|
333
431
|
if (!isAnchored) continue;
|
|
@@ -369,7 +467,7 @@ function registerSync(program2) {
|
|
|
369
467
|
}
|
|
370
468
|
}
|
|
371
469
|
if (opts.promote !== false) {
|
|
372
|
-
const memories = await
|
|
470
|
+
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
373
471
|
const usage = await loadUsageIndex(paths);
|
|
374
472
|
for (const { memory: memory2, filePath } of memories) {
|
|
375
473
|
if (isAutoPromoteEligible(
|
|
@@ -390,9 +488,20 @@ function registerSync(program2) {
|
|
|
390
488
|
}
|
|
391
489
|
}
|
|
392
490
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
491
|
+
const draftMemories = (await loadMemoriesFromDir2(paths.memoriesDir)).filter(
|
|
492
|
+
(m) => m.memory.frontmatter.status === "draft"
|
|
493
|
+
);
|
|
494
|
+
const draftCount = draftMemories.length;
|
|
393
495
|
log(
|
|
394
496
|
`${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
|
|
395
497
|
);
|
|
498
|
+
if (!opts.quiet && draftCount > 0) {
|
|
499
|
+
log(
|
|
500
|
+
ui.dim(
|
|
501
|
+
`\u2139 ${draftCount} memor${draftCount === 1 ? "y" : "ies"} in draft \u2014 run \`haive memory approve <id>\` to activate or \`haive memory list --status draft\` to review`
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
}
|
|
396
505
|
if (sinceReport && !opts.quiet) {
|
|
397
506
|
if (sinceReport.added.length > 0) {
|
|
398
507
|
log(ui.bold("\nNew memories:"));
|
|
@@ -430,28 +539,28 @@ function collectSinceChanges(root, ref) {
|
|
|
430
539
|
|
|
431
540
|
// src/commands/memory-add.ts
|
|
432
541
|
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
433
|
-
import { existsSync as
|
|
542
|
+
import { existsSync as existsSync7 } from "fs";
|
|
434
543
|
import path7 from "path";
|
|
435
544
|
import "commander";
|
|
436
545
|
import {
|
|
437
546
|
buildFrontmatter,
|
|
438
|
-
findProjectRoot as
|
|
547
|
+
findProjectRoot as findProjectRoot8,
|
|
439
548
|
inferModulesFromPaths,
|
|
440
549
|
memoryFilePath,
|
|
441
|
-
resolveHaivePaths as
|
|
550
|
+
resolveHaivePaths as resolveHaivePaths6,
|
|
442
551
|
serializeMemory as serializeMemory2
|
|
443
552
|
} from "@hiveai/core";
|
|
444
553
|
function registerMemoryAdd(memory2) {
|
|
445
|
-
memory2.command("add").description("Add a new memory (defaults to personal scope
|
|
446
|
-
const root =
|
|
447
|
-
const paths =
|
|
448
|
-
if (!
|
|
554
|
+
memory2.command("add").description("Add a new memory (defaults to personal scope)").requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary").requiredOption("--slug <slug>", "short 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", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor paths, comma-separated").option("--symbols <csv>", "anchor symbols, comma-separated").option("--commit <sha>", "anchor commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
555
|
+
const root = findProjectRoot8(opts.dir);
|
|
556
|
+
const paths = resolveHaivePaths6(root);
|
|
557
|
+
if (!existsSync7(paths.haiveDir)) {
|
|
449
558
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
450
559
|
process.exitCode = 1;
|
|
451
560
|
return;
|
|
452
561
|
}
|
|
453
|
-
const userTags =
|
|
454
|
-
const anchorPaths =
|
|
562
|
+
const userTags = parseCsv2(opts.tags);
|
|
563
|
+
const anchorPaths = parseCsv2(opts.paths);
|
|
455
564
|
const autoTagsEnabled = opts.autoTag !== false;
|
|
456
565
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
|
|
457
566
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
@@ -464,73 +573,112 @@ function registerMemoryAdd(memory2) {
|
|
|
464
573
|
domain: opts.domain,
|
|
465
574
|
author: opts.author,
|
|
466
575
|
paths: anchorPaths,
|
|
467
|
-
symbols:
|
|
576
|
+
symbols: parseCsv2(opts.symbols),
|
|
468
577
|
commit: opts.commit
|
|
469
578
|
});
|
|
470
|
-
const
|
|
579
|
+
const title = opts.title ?? opts.slug;
|
|
580
|
+
let body;
|
|
581
|
+
if (opts.body !== void 0) {
|
|
582
|
+
body = opts.title ? `# ${opts.title}
|
|
583
|
+
|
|
584
|
+
${opts.body}` : opts.body;
|
|
585
|
+
} else {
|
|
586
|
+
body = `# ${title}
|
|
471
587
|
|
|
472
588
|
TODO \u2014 write the memory body.
|
|
473
589
|
`;
|
|
590
|
+
}
|
|
474
591
|
const file = memoryFilePath(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
475
592
|
await mkdir3(path7.dirname(file), { recursive: true });
|
|
476
|
-
if (
|
|
593
|
+
if (existsSync7(file)) {
|
|
477
594
|
ui.error(`Memory already exists at ${file}`);
|
|
478
595
|
process.exitCode = 1;
|
|
479
596
|
return;
|
|
480
597
|
}
|
|
481
598
|
await writeFile4(file, serializeMemory2({ frontmatter, body }), "utf8");
|
|
482
599
|
ui.success(`Created ${path7.relative(root, file)}`);
|
|
483
|
-
ui.info(`
|
|
600
|
+
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
484
601
|
if (inferredTags.length > 0) {
|
|
485
602
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
486
603
|
}
|
|
604
|
+
if (frontmatter.scope === "personal") {
|
|
605
|
+
console.log(
|
|
606
|
+
ui.dim(
|
|
607
|
+
`\u2192 next: haive memory approve ${frontmatter.id} (activate) | haive memory promote ${frontmatter.id} (share with team)`
|
|
608
|
+
)
|
|
609
|
+
);
|
|
610
|
+
} else {
|
|
611
|
+
console.log(
|
|
612
|
+
ui.dim(`\u2192 next: haive memory approve ${frontmatter.id} (mark as validated)`)
|
|
613
|
+
);
|
|
614
|
+
}
|
|
487
615
|
});
|
|
488
616
|
}
|
|
489
|
-
function
|
|
617
|
+
function parseCsv2(value) {
|
|
490
618
|
if (!value) return [];
|
|
491
619
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
492
620
|
}
|
|
493
621
|
|
|
494
622
|
// src/commands/memory-list.ts
|
|
495
|
-
import { existsSync as
|
|
623
|
+
import { existsSync as existsSync8 } from "fs";
|
|
496
624
|
import path8 from "path";
|
|
497
625
|
import "commander";
|
|
498
|
-
import { findProjectRoot as
|
|
626
|
+
import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
|
|
499
627
|
|
|
500
628
|
// src/utils/fs.ts
|
|
501
629
|
import {
|
|
502
|
-
loadMemoriesFromDir as
|
|
630
|
+
loadMemoriesFromDir as loadMemoriesFromDir3,
|
|
503
631
|
loadMemory,
|
|
504
632
|
listMarkdownFilesRecursive
|
|
505
633
|
} from "@hiveai/core";
|
|
506
634
|
|
|
507
635
|
// src/commands/memory-list.ts
|
|
508
636
|
function registerMemoryList(memory2) {
|
|
509
|
-
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("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
510
|
-
const root =
|
|
511
|
-
const paths =
|
|
512
|
-
if (!
|
|
637
|
+
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) => {
|
|
638
|
+
const root = findProjectRoot9(opts.dir);
|
|
639
|
+
const paths = resolveHaivePaths7(root);
|
|
640
|
+
if (!existsSync8(paths.memoriesDir)) {
|
|
513
641
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
514
642
|
process.exitCode = 1;
|
|
515
643
|
return;
|
|
516
644
|
}
|
|
517
|
-
const all = await
|
|
518
|
-
const
|
|
645
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
646
|
+
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
647
|
+
const filtered = all.filter((m) => {
|
|
648
|
+
if (!matchesFilters(m, opts)) return false;
|
|
649
|
+
const status = m.memory.frontmatter.status;
|
|
650
|
+
if (!opts.showRejected && status === "rejected") return false;
|
|
651
|
+
if (statusFilter && !statusFilter.includes(status)) return false;
|
|
652
|
+
return true;
|
|
653
|
+
});
|
|
519
654
|
if (filtered.length === 0) {
|
|
520
655
|
ui.info("No memories match the filters.");
|
|
656
|
+
const rejectedCount = all.filter((m) => m.memory.frontmatter.status === "rejected").length;
|
|
657
|
+
if (rejectedCount > 0 && !opts.showRejected) {
|
|
658
|
+
ui.info(`(${rejectedCount} rejected hidden \u2014 use --show-rejected to include)`);
|
|
659
|
+
}
|
|
521
660
|
return;
|
|
522
661
|
}
|
|
523
662
|
for (const { memory: mem, filePath } of filtered) {
|
|
524
663
|
const fm = mem.frontmatter;
|
|
525
664
|
const tagStr = fm.tags.length ? ui.dim(` [${fm.tags.join(", ")}]`) : "";
|
|
526
665
|
const moduleStr = fm.module ? ui.dim(` (${fm.module})`) : "";
|
|
666
|
+
const statusBadge = ui.statusBadge(fm.status);
|
|
527
667
|
console.log(
|
|
528
|
-
`${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)}${moduleStr}${tagStr}`
|
|
668
|
+
`${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
|
|
529
669
|
);
|
|
530
670
|
console.log(` ${ui.dim(path8.relative(root, filePath))}`);
|
|
531
671
|
}
|
|
532
672
|
console.log(ui.dim(`
|
|
533
673
|
${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
|
|
674
|
+
const draftCount = filtered.filter((m) => m.memory.frontmatter.status === "draft").length;
|
|
675
|
+
if (draftCount > 0) {
|
|
676
|
+
console.log(
|
|
677
|
+
ui.dim(
|
|
678
|
+
`\u2139 ${draftCount} in draft \u2014 use \`haive memory approve <id>\` to activate or \`haive memory promote <id>\` to share with team`
|
|
679
|
+
)
|
|
680
|
+
);
|
|
681
|
+
}
|
|
534
682
|
});
|
|
535
683
|
}
|
|
536
684
|
function matchesFilters(loaded, opts) {
|
|
@@ -544,25 +692,39 @@ function matchesFilters(loaded, opts) {
|
|
|
544
692
|
|
|
545
693
|
// src/commands/memory-promote.ts
|
|
546
694
|
import { mkdir as mkdir4, unlink, writeFile as writeFile5 } from "fs/promises";
|
|
547
|
-
import { existsSync as
|
|
695
|
+
import { existsSync as existsSync9 } from "fs";
|
|
548
696
|
import path9 from "path";
|
|
549
697
|
import "commander";
|
|
550
698
|
import {
|
|
551
|
-
findProjectRoot as
|
|
699
|
+
findProjectRoot as findProjectRoot10,
|
|
552
700
|
memoryFilePath as memoryFilePath2,
|
|
553
|
-
resolveHaivePaths as
|
|
701
|
+
resolveHaivePaths as resolveHaivePaths8,
|
|
554
702
|
serializeMemory as serializeMemory3
|
|
555
703
|
} from "@hiveai/core";
|
|
556
704
|
function registerMemoryPromote(memory2) {
|
|
557
705
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
558
|
-
const root =
|
|
559
|
-
const paths =
|
|
560
|
-
if (!
|
|
706
|
+
const root = findProjectRoot10(opts.dir);
|
|
707
|
+
const paths = resolveHaivePaths8(root);
|
|
708
|
+
if (!existsSync9(paths.memoriesDir)) {
|
|
561
709
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
562
710
|
process.exitCode = 1;
|
|
563
711
|
return;
|
|
564
712
|
}
|
|
565
|
-
const
|
|
713
|
+
const teamAndModule = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
714
|
+
const alreadyShared = teamAndModule.find(
|
|
715
|
+
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
716
|
+
);
|
|
717
|
+
if (alreadyShared) {
|
|
718
|
+
const fm = alreadyShared.memory.frontmatter;
|
|
719
|
+
ui.warn(
|
|
720
|
+
`"${id}" is already in ${fm.scope} scope (status=${fm.status}).`
|
|
721
|
+
);
|
|
722
|
+
if (fm.status !== "validated") {
|
|
723
|
+
ui.info(`\u2192 run \`haive memory approve ${id}\` to validate it`);
|
|
724
|
+
}
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const all = await loadMemoriesFromDir3(paths.personalDir);
|
|
566
728
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
567
729
|
if (!found) {
|
|
568
730
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -583,29 +745,57 @@ function registerMemoryPromote(memory2) {
|
|
|
583
745
|
await unlink(found.filePath);
|
|
584
746
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
585
747
|
ui.info(`Now at ${path9.relative(root, newPath)}`);
|
|
748
|
+
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
586
749
|
});
|
|
587
750
|
}
|
|
588
751
|
|
|
589
752
|
// src/commands/memory-approve.ts
|
|
590
|
-
import { existsSync as
|
|
753
|
+
import { existsSync as existsSync10 } from "fs";
|
|
591
754
|
import { writeFile as writeFile6 } from "fs/promises";
|
|
592
755
|
import path10 from "path";
|
|
593
756
|
import "commander";
|
|
594
757
|
import {
|
|
595
|
-
findProjectRoot as
|
|
596
|
-
resolveHaivePaths as
|
|
758
|
+
findProjectRoot as findProjectRoot11,
|
|
759
|
+
resolveHaivePaths as resolveHaivePaths9,
|
|
597
760
|
serializeMemory as serializeMemory4
|
|
598
761
|
} from "@hiveai/core";
|
|
599
762
|
function registerMemoryApprove(memory2) {
|
|
600
|
-
memory2.command("approve
|
|
601
|
-
const root =
|
|
602
|
-
const paths =
|
|
603
|
-
if (!
|
|
763
|
+
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) => {
|
|
764
|
+
const root = findProjectRoot11(opts.dir);
|
|
765
|
+
const paths = resolveHaivePaths9(root);
|
|
766
|
+
if (!existsSync10(paths.memoriesDir)) {
|
|
604
767
|
ui.error(`No .ai/memories at ${root}.`);
|
|
605
768
|
process.exitCode = 1;
|
|
606
769
|
return;
|
|
607
770
|
}
|
|
608
|
-
const all = await
|
|
771
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
772
|
+
if (opts.all || opts.pending) {
|
|
773
|
+
const candidates = all.filter((m) => {
|
|
774
|
+
const s = m.memory.frontmatter.status;
|
|
775
|
+
if (opts.all) return s === "proposed" || s === "draft";
|
|
776
|
+
return s === "proposed";
|
|
777
|
+
});
|
|
778
|
+
if (candidates.length === 0) {
|
|
779
|
+
ui.info(opts.all ? "No draft or proposed memories to approve." : "No proposed memories to approve.");
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
let count = 0;
|
|
783
|
+
for (const found2 of candidates) {
|
|
784
|
+
const next2 = {
|
|
785
|
+
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
786
|
+
body: found2.memory.body
|
|
787
|
+
};
|
|
788
|
+
await writeFile6(found2.filePath, serializeMemory4(next2), "utf8");
|
|
789
|
+
count++;
|
|
790
|
+
}
|
|
791
|
+
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (!id) {
|
|
795
|
+
ui.error("Provide a memory id or use --all / --pending for bulk approval.");
|
|
796
|
+
process.exitCode = 1;
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
609
799
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
610
800
|
if (!found) {
|
|
611
801
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -630,19 +820,103 @@ function registerMemoryApprove(memory2) {
|
|
|
630
820
|
});
|
|
631
821
|
}
|
|
632
822
|
|
|
633
|
-
// src/commands/memory-
|
|
823
|
+
// src/commands/memory-update.ts
|
|
634
824
|
import { writeFile as writeFile7 } from "fs/promises";
|
|
635
|
-
import { existsSync as
|
|
825
|
+
import { existsSync as existsSync11 } from "fs";
|
|
636
826
|
import path11 from "path";
|
|
637
827
|
import "commander";
|
|
828
|
+
import {
|
|
829
|
+
findProjectRoot as findProjectRoot12,
|
|
830
|
+
resolveHaivePaths as resolveHaivePaths10,
|
|
831
|
+
serializeMemory as serializeMemory5
|
|
832
|
+
} from "@hiveai/core";
|
|
833
|
+
function registerMemoryUpdate(memory2) {
|
|
834
|
+
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) => {
|
|
835
|
+
const root = findProjectRoot12(opts.dir);
|
|
836
|
+
const paths = resolveHaivePaths10(root);
|
|
837
|
+
if (!existsSync11(paths.memoriesDir)) {
|
|
838
|
+
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
839
|
+
process.exitCode = 1;
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const memories = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
843
|
+
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
844
|
+
if (!loaded) {
|
|
845
|
+
ui.error(`No memory with id "${id}".`);
|
|
846
|
+
process.exitCode = 1;
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const updated = [];
|
|
850
|
+
const { frontmatter, body } = loaded.memory;
|
|
851
|
+
const newAnchor = { ...frontmatter.anchor };
|
|
852
|
+
if (opts.paths !== void 0) {
|
|
853
|
+
newAnchor.paths = parseCsv3(opts.paths);
|
|
854
|
+
updated.push("anchor.paths");
|
|
855
|
+
}
|
|
856
|
+
if (opts.symbols !== void 0) {
|
|
857
|
+
newAnchor.symbols = parseCsv3(opts.symbols);
|
|
858
|
+
updated.push("anchor.symbols");
|
|
859
|
+
}
|
|
860
|
+
if (opts.commit !== void 0) {
|
|
861
|
+
newAnchor.commit = opts.commit;
|
|
862
|
+
updated.push("anchor.commit");
|
|
863
|
+
}
|
|
864
|
+
const newFrontmatter = {
|
|
865
|
+
...frontmatter,
|
|
866
|
+
anchor: newAnchor,
|
|
867
|
+
...opts.tags !== void 0 ? { tags: parseCsv3(opts.tags) } : {},
|
|
868
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {},
|
|
869
|
+
...opts.author !== void 0 ? { author: opts.author } : {}
|
|
870
|
+
};
|
|
871
|
+
if (opts.tags !== void 0) updated.push("tags");
|
|
872
|
+
if (opts.domain !== void 0) updated.push("domain");
|
|
873
|
+
if (opts.author !== void 0) updated.push("author");
|
|
874
|
+
let newBody = opts.body !== void 0 ? opts.body : body;
|
|
875
|
+
if (opts.title !== void 0) {
|
|
876
|
+
newBody = replaceFirstHeading(newBody, opts.title);
|
|
877
|
+
updated.push("title");
|
|
878
|
+
}
|
|
879
|
+
if (opts.body !== void 0) updated.push("body");
|
|
880
|
+
if (updated.length === 0) {
|
|
881
|
+
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
await writeFile7(
|
|
885
|
+
loaded.filePath,
|
|
886
|
+
serializeMemory5({ frontmatter: newFrontmatter, body: newBody }),
|
|
887
|
+
"utf8"
|
|
888
|
+
);
|
|
889
|
+
ui.success(`Updated ${path11.relative(root, loaded.filePath)}`);
|
|
890
|
+
ui.info(`fields: ${updated.join(", ")}`);
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
function replaceFirstHeading(body, title) {
|
|
894
|
+
const headingRe = /^#\s+.+$/m;
|
|
895
|
+
const replacement = `# ${title}`;
|
|
896
|
+
if (headingRe.test(body)) {
|
|
897
|
+
return body.replace(headingRe, replacement);
|
|
898
|
+
}
|
|
899
|
+
return `${replacement}
|
|
900
|
+
|
|
901
|
+
${body}`;
|
|
902
|
+
}
|
|
903
|
+
function parseCsv3(value) {
|
|
904
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// src/commands/memory-auto-promote.ts
|
|
908
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
909
|
+
import { existsSync as existsSync12 } from "fs";
|
|
910
|
+
import path12 from "path";
|
|
911
|
+
import "commander";
|
|
638
912
|
import {
|
|
639
913
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
640
|
-
findProjectRoot as
|
|
914
|
+
findProjectRoot as findProjectRoot13,
|
|
641
915
|
getUsage as getUsage2,
|
|
642
916
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
643
917
|
loadUsageIndex as loadUsageIndex2,
|
|
644
|
-
resolveHaivePaths as
|
|
645
|
-
serializeMemory as
|
|
918
|
+
resolveHaivePaths as resolveHaivePaths11,
|
|
919
|
+
serializeMemory as serializeMemory6
|
|
646
920
|
} from "@hiveai/core";
|
|
647
921
|
function registerMemoryAutoPromote(memory2) {
|
|
648
922
|
memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE2.minReads)).option(
|
|
@@ -650,9 +924,9 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
650
924
|
"memories with more rejections than this are skipped",
|
|
651
925
|
String(DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
|
|
652
926
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
653
|
-
const root =
|
|
654
|
-
const paths =
|
|
655
|
-
if (!
|
|
927
|
+
const root = findProjectRoot13(opts.dir);
|
|
928
|
+
const paths = resolveHaivePaths11(root);
|
|
929
|
+
if (!existsSync12(paths.memoriesDir)) {
|
|
656
930
|
ui.error(`No .ai/memories at ${root}.`);
|
|
657
931
|
process.exitCode = 1;
|
|
658
932
|
return;
|
|
@@ -661,7 +935,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
661
935
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads),
|
|
662
936
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
|
|
663
937
|
};
|
|
664
|
-
const memories = await
|
|
938
|
+
const memories = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
665
939
|
const usage = await loadUsageIndex2(paths);
|
|
666
940
|
const eligible = memories.filter(
|
|
667
941
|
({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
|
|
@@ -678,13 +952,13 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
678
952
|
console.log(
|
|
679
953
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
680
954
|
);
|
|
681
|
-
console.log(` ${ui.dim(
|
|
955
|
+
console.log(` ${ui.dim(path12.relative(root, filePath))}`);
|
|
682
956
|
if (opts.apply) {
|
|
683
957
|
const next = {
|
|
684
958
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
685
959
|
body: mem.body
|
|
686
960
|
};
|
|
687
|
-
await
|
|
961
|
+
await writeFile8(filePath, serializeMemory6(next), "utf8");
|
|
688
962
|
written++;
|
|
689
963
|
}
|
|
690
964
|
}
|
|
@@ -695,25 +969,25 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
695
969
|
|
|
696
970
|
// src/commands/memory-edit.ts
|
|
697
971
|
import { spawn as spawn2 } from "child_process";
|
|
698
|
-
import { existsSync as
|
|
699
|
-
import { readFile as
|
|
700
|
-
import
|
|
972
|
+
import { existsSync as existsSync13 } from "fs";
|
|
973
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
974
|
+
import path13 from "path";
|
|
701
975
|
import "commander";
|
|
702
976
|
import {
|
|
703
|
-
findProjectRoot as
|
|
977
|
+
findProjectRoot as findProjectRoot14,
|
|
704
978
|
parseMemory,
|
|
705
|
-
resolveHaivePaths as
|
|
979
|
+
resolveHaivePaths as resolveHaivePaths12
|
|
706
980
|
} from "@hiveai/core";
|
|
707
981
|
function registerMemoryEdit(memory2) {
|
|
708
982
|
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) => {
|
|
709
|
-
const root =
|
|
710
|
-
const paths =
|
|
711
|
-
if (!
|
|
983
|
+
const root = findProjectRoot14(opts.dir);
|
|
984
|
+
const paths = resolveHaivePaths12(root);
|
|
985
|
+
if (!existsSync13(paths.memoriesDir)) {
|
|
712
986
|
ui.error(`No .ai/memories at ${root}.`);
|
|
713
987
|
process.exitCode = 1;
|
|
714
988
|
return;
|
|
715
989
|
}
|
|
716
|
-
const all = await
|
|
990
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
717
991
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
718
992
|
if (!found) {
|
|
719
993
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -721,13 +995,13 @@ function registerMemoryEdit(memory2) {
|
|
|
721
995
|
return;
|
|
722
996
|
}
|
|
723
997
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
724
|
-
ui.info(`Opening ${
|
|
998
|
+
ui.info(`Opening ${path13.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
725
999
|
const code = await runEditor(editor, found.filePath);
|
|
726
1000
|
if (code !== 0) {
|
|
727
1001
|
ui.warn(`Editor exited with status ${code}.`);
|
|
728
1002
|
}
|
|
729
1003
|
try {
|
|
730
|
-
const fresh = await
|
|
1004
|
+
const fresh = await readFile3(found.filePath, "utf8");
|
|
731
1005
|
parseMemory(fresh);
|
|
732
1006
|
ui.success("Memory still parses cleanly.");
|
|
733
1007
|
} catch (err) {
|
|
@@ -748,28 +1022,28 @@ function runEditor(editor, file) {
|
|
|
748
1022
|
}
|
|
749
1023
|
|
|
750
1024
|
// src/commands/memory-for-files.ts
|
|
751
|
-
import { existsSync as
|
|
752
|
-
import
|
|
1025
|
+
import { existsSync as existsSync14 } from "fs";
|
|
1026
|
+
import path14 from "path";
|
|
753
1027
|
import "commander";
|
|
754
1028
|
import {
|
|
755
1029
|
deriveConfidence,
|
|
756
|
-
findProjectRoot as
|
|
1030
|
+
findProjectRoot as findProjectRoot15,
|
|
757
1031
|
getUsage as getUsage3,
|
|
758
1032
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
759
1033
|
loadUsageIndex as loadUsageIndex3,
|
|
760
|
-
memoryMatchesAnchorPaths,
|
|
761
|
-
resolveHaivePaths as
|
|
1034
|
+
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
1035
|
+
resolveHaivePaths as resolveHaivePaths13
|
|
762
1036
|
} from "@hiveai/core";
|
|
763
1037
|
function registerMemoryForFiles(memory2) {
|
|
764
1038
|
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) => {
|
|
765
|
-
const root =
|
|
766
|
-
const paths =
|
|
767
|
-
if (!
|
|
1039
|
+
const root = findProjectRoot15(opts.dir);
|
|
1040
|
+
const paths = resolveHaivePaths13(root);
|
|
1041
|
+
if (!existsSync14(paths.memoriesDir)) {
|
|
768
1042
|
ui.error(`No .ai/memories at ${root}.`);
|
|
769
1043
|
process.exitCode = 1;
|
|
770
1044
|
return;
|
|
771
1045
|
}
|
|
772
|
-
const all = await
|
|
1046
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
773
1047
|
const usage = await loadUsageIndex3(paths);
|
|
774
1048
|
const inferred = inferModulesFromPaths2(files);
|
|
775
1049
|
const byAnchor = [];
|
|
@@ -777,7 +1051,7 @@ function registerMemoryForFiles(memory2) {
|
|
|
777
1051
|
const byDomain = [];
|
|
778
1052
|
const seen = /* @__PURE__ */ new Set();
|
|
779
1053
|
for (const loaded of all) {
|
|
780
|
-
if (
|
|
1054
|
+
if (memoryMatchesAnchorPaths2(loaded.memory, files)) {
|
|
781
1055
|
byAnchor.push(loaded);
|
|
782
1056
|
seen.add(loaded.memory.frontmatter.id);
|
|
783
1057
|
}
|
|
@@ -817,31 +1091,31 @@ function printGroup(root, label, loaded, usage) {
|
|
|
817
1091
|
const u = getUsage3(usage, fm.id);
|
|
818
1092
|
const conf = deriveConfidence(fm, u);
|
|
819
1093
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
820
|
-
console.log(` ${ui.dim(
|
|
1094
|
+
console.log(` ${ui.dim(path14.relative(root, filePath))}`);
|
|
821
1095
|
}
|
|
822
1096
|
}
|
|
823
1097
|
|
|
824
1098
|
// src/commands/memory-hot.ts
|
|
825
|
-
import { existsSync as
|
|
826
|
-
import
|
|
1099
|
+
import { existsSync as existsSync15 } from "fs";
|
|
1100
|
+
import path15 from "path";
|
|
827
1101
|
import "commander";
|
|
828
1102
|
import {
|
|
829
|
-
findProjectRoot as
|
|
1103
|
+
findProjectRoot as findProjectRoot16,
|
|
830
1104
|
getUsage as getUsage4,
|
|
831
1105
|
loadUsageIndex as loadUsageIndex4,
|
|
832
|
-
resolveHaivePaths as
|
|
1106
|
+
resolveHaivePaths as resolveHaivePaths14
|
|
833
1107
|
} from "@hiveai/core";
|
|
834
1108
|
function registerMemoryHot(memory2) {
|
|
835
1109
|
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) => {
|
|
836
|
-
const root =
|
|
837
|
-
const paths =
|
|
838
|
-
if (!
|
|
1110
|
+
const root = findProjectRoot16(opts.dir);
|
|
1111
|
+
const paths = resolveHaivePaths14(root);
|
|
1112
|
+
if (!existsSync15(paths.memoriesDir)) {
|
|
839
1113
|
ui.error(`No .ai/memories at ${root}.`);
|
|
840
1114
|
process.exitCode = 1;
|
|
841
1115
|
return;
|
|
842
1116
|
}
|
|
843
1117
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
844
|
-
const all = await
|
|
1118
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
845
1119
|
const usage = await loadUsageIndex4(paths);
|
|
846
1120
|
const candidates = all.filter(({ memory: mem }) => {
|
|
847
1121
|
const fm = mem.frontmatter;
|
|
@@ -863,7 +1137,7 @@ function registerMemoryHot(memory2) {
|
|
|
863
1137
|
console.log(
|
|
864
1138
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
865
1139
|
);
|
|
866
|
-
console.log(` ${ui.dim(
|
|
1140
|
+
console.log(` ${ui.dim(path15.relative(root, filePath))}`);
|
|
867
1141
|
}
|
|
868
1142
|
ui.info(
|
|
869
1143
|
`${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
|
|
@@ -872,25 +1146,25 @@ function registerMemoryHot(memory2) {
|
|
|
872
1146
|
}
|
|
873
1147
|
|
|
874
1148
|
// src/commands/memory-pending.ts
|
|
875
|
-
import { existsSync as
|
|
876
|
-
import
|
|
1149
|
+
import { existsSync as existsSync16 } from "fs";
|
|
1150
|
+
import path16 from "path";
|
|
877
1151
|
import "commander";
|
|
878
1152
|
import {
|
|
879
|
-
findProjectRoot as
|
|
1153
|
+
findProjectRoot as findProjectRoot17,
|
|
880
1154
|
getUsage as getUsage5,
|
|
881
1155
|
loadUsageIndex as loadUsageIndex5,
|
|
882
|
-
resolveHaivePaths as
|
|
1156
|
+
resolveHaivePaths as resolveHaivePaths15
|
|
883
1157
|
} from "@hiveai/core";
|
|
884
1158
|
function registerMemoryPending(memory2) {
|
|
885
1159
|
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) => {
|
|
886
|
-
const root =
|
|
887
|
-
const paths =
|
|
888
|
-
if (!
|
|
1160
|
+
const root = findProjectRoot17(opts.dir);
|
|
1161
|
+
const paths = resolveHaivePaths15(root);
|
|
1162
|
+
if (!existsSync16(paths.memoriesDir)) {
|
|
889
1163
|
ui.error(`No .ai/memories at ${root}.`);
|
|
890
1164
|
process.exitCode = 1;
|
|
891
1165
|
return;
|
|
892
1166
|
}
|
|
893
|
-
const all = await
|
|
1167
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
894
1168
|
const usage = await loadUsageIndex5(paths);
|
|
895
1169
|
const proposed = all.filter(({ memory: mem }) => {
|
|
896
1170
|
if (mem.frontmatter.status !== "proposed") return false;
|
|
@@ -913,36 +1187,42 @@ function registerMemoryPending(memory2) {
|
|
|
913
1187
|
console.log(
|
|
914
1188
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
915
1189
|
);
|
|
916
|
-
console.log(` ${ui.dim(
|
|
1190
|
+
console.log(` ${ui.dim(path16.relative(root, filePath))}`);
|
|
917
1191
|
}
|
|
918
1192
|
ui.info(`${proposed.length} pending`);
|
|
919
1193
|
});
|
|
920
1194
|
}
|
|
921
1195
|
|
|
922
1196
|
// src/commands/memory-query.ts
|
|
923
|
-
import { existsSync as
|
|
924
|
-
import
|
|
1197
|
+
import { existsSync as existsSync17 } from "fs";
|
|
1198
|
+
import path17 from "path";
|
|
925
1199
|
import "commander";
|
|
926
1200
|
import {
|
|
927
1201
|
extractSnippet,
|
|
928
|
-
findProjectRoot as
|
|
929
|
-
literalMatchesAllTokens,
|
|
1202
|
+
findProjectRoot as findProjectRoot18,
|
|
1203
|
+
literalMatchesAllTokens as literalMatchesAllTokens2,
|
|
930
1204
|
pickSnippetNeedle,
|
|
931
|
-
resolveHaivePaths as
|
|
932
|
-
tokenizeQuery
|
|
1205
|
+
resolveHaivePaths as resolveHaivePaths16,
|
|
1206
|
+
tokenizeQuery as tokenizeQuery2
|
|
933
1207
|
} from "@hiveai/core";
|
|
934
1208
|
function registerMemoryQuery(memory2) {
|
|
935
|
-
memory2.command("query <text>").description("Search memories by id, tag, or substring (multi-word AND)").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").action(async (text, opts) => {
|
|
936
|
-
const root =
|
|
937
|
-
const paths =
|
|
938
|
-
if (!
|
|
1209
|
+
memory2.command("query <text>").description("Search memories by id, tag, or substring (multi-word AND)").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)").action(async (text, opts) => {
|
|
1210
|
+
const root = findProjectRoot18(opts.dir);
|
|
1211
|
+
const paths = resolveHaivePaths16(root);
|
|
1212
|
+
if (!existsSync17(paths.memoriesDir)) {
|
|
939
1213
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
940
1214
|
process.exitCode = 1;
|
|
941
1215
|
return;
|
|
942
1216
|
}
|
|
943
|
-
const tokens =
|
|
944
|
-
const
|
|
945
|
-
const
|
|
1217
|
+
const tokens = tokenizeQuery2(text);
|
|
1218
|
+
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
1219
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1220
|
+
const matches = all.filter(({ memory: mem }) => {
|
|
1221
|
+
const fm = mem.frontmatter;
|
|
1222
|
+
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
1223
|
+
if (statusFilter && !statusFilter.includes(fm.status)) return false;
|
|
1224
|
+
return literalMatchesAllTokens2(mem, tokens);
|
|
1225
|
+
});
|
|
946
1226
|
const limit = Math.max(1, Number(opts.limit ?? 20));
|
|
947
1227
|
const top = matches.slice(0, limit);
|
|
948
1228
|
if (top.length === 0) {
|
|
@@ -951,9 +1231,11 @@ function registerMemoryQuery(memory2) {
|
|
|
951
1231
|
}
|
|
952
1232
|
const snippetNeedle = pickSnippetNeedle(text);
|
|
953
1233
|
for (const { memory: mem, filePath } of top) {
|
|
1234
|
+
const fm = mem.frontmatter;
|
|
1235
|
+
const statusBadge = ui.statusBadge(fm.status);
|
|
1236
|
+
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
1237
|
+
console.log(` ${ui.dim(path17.relative(root, filePath))}`);
|
|
954
1238
|
const snippet = extractSnippet(mem.body, snippetNeedle);
|
|
955
|
-
console.log(`${ui.bold(mem.frontmatter.id)} ${ui.dim(mem.frontmatter.scope)}`);
|
|
956
|
-
console.log(` ${ui.dim(path16.relative(root, filePath))}`);
|
|
957
1239
|
if (snippet) console.log(` ${snippet}`);
|
|
958
1240
|
}
|
|
959
1241
|
console.log(
|
|
@@ -964,70 +1246,81 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
964
1246
|
}
|
|
965
1247
|
|
|
966
1248
|
// src/commands/memory-reject.ts
|
|
967
|
-
import {
|
|
1249
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
1250
|
+
import { existsSync as existsSync18 } from "fs";
|
|
968
1251
|
import "commander";
|
|
969
1252
|
import {
|
|
970
|
-
findProjectRoot as
|
|
1253
|
+
findProjectRoot as findProjectRoot19,
|
|
971
1254
|
loadUsageIndex as loadUsageIndex6,
|
|
972
1255
|
recordRejection,
|
|
973
|
-
resolveHaivePaths as
|
|
974
|
-
saveUsageIndex
|
|
1256
|
+
resolveHaivePaths as resolveHaivePaths17,
|
|
1257
|
+
saveUsageIndex,
|
|
1258
|
+
serializeMemory as serializeMemory7
|
|
975
1259
|
} from "@hiveai/core";
|
|
976
1260
|
function registerMemoryReject(memory2) {
|
|
977
1261
|
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) => {
|
|
978
|
-
const root =
|
|
979
|
-
const paths =
|
|
980
|
-
if (!
|
|
1262
|
+
const root = findProjectRoot19(opts.dir);
|
|
1263
|
+
const paths = resolveHaivePaths17(root);
|
|
1264
|
+
if (!existsSync18(paths.memoriesDir)) {
|
|
981
1265
|
ui.error(`No .ai/memories at ${root}.`);
|
|
982
1266
|
process.exitCode = 1;
|
|
983
1267
|
return;
|
|
984
1268
|
}
|
|
985
|
-
const memories = await
|
|
986
|
-
|
|
1269
|
+
const memories = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1270
|
+
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
1271
|
+
if (!loaded) {
|
|
987
1272
|
ui.error(`No memory with id "${id}".`);
|
|
988
1273
|
process.exitCode = 1;
|
|
989
1274
|
return;
|
|
990
1275
|
}
|
|
1276
|
+
await writeFile9(
|
|
1277
|
+
loaded.filePath,
|
|
1278
|
+
serializeMemory7({
|
|
1279
|
+
frontmatter: { ...loaded.memory.frontmatter, status: "rejected" },
|
|
1280
|
+
body: loaded.memory.body
|
|
1281
|
+
}),
|
|
1282
|
+
"utf8"
|
|
1283
|
+
);
|
|
991
1284
|
const idx = await loadUsageIndex6(paths);
|
|
992
1285
|
recordRejection(idx, id, opts.reason ?? null);
|
|
993
1286
|
await saveUsageIndex(paths, idx);
|
|
994
1287
|
const u = idx.by_id[id];
|
|
995
1288
|
ui.success(
|
|
996
|
-
`
|
|
1289
|
+
`Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
|
|
997
1290
|
);
|
|
998
1291
|
if (opts.reason) ui.info(`reason: ${opts.reason}`);
|
|
999
1292
|
});
|
|
1000
1293
|
}
|
|
1001
1294
|
|
|
1002
1295
|
// src/commands/memory-rm.ts
|
|
1003
|
-
import { existsSync as
|
|
1296
|
+
import { existsSync as existsSync19 } from "fs";
|
|
1004
1297
|
import { unlink as unlink2 } from "fs/promises";
|
|
1005
|
-
import
|
|
1298
|
+
import path18 from "path";
|
|
1006
1299
|
import { createInterface } from "readline/promises";
|
|
1007
1300
|
import "commander";
|
|
1008
1301
|
import {
|
|
1009
|
-
findProjectRoot as
|
|
1302
|
+
findProjectRoot as findProjectRoot20,
|
|
1010
1303
|
loadUsageIndex as loadUsageIndex7,
|
|
1011
|
-
resolveHaivePaths as
|
|
1304
|
+
resolveHaivePaths as resolveHaivePaths18,
|
|
1012
1305
|
saveUsageIndex as saveUsageIndex2
|
|
1013
1306
|
} from "@hiveai/core";
|
|
1014
1307
|
function registerMemoryRm(memory2) {
|
|
1015
1308
|
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) => {
|
|
1016
|
-
const root =
|
|
1017
|
-
const paths =
|
|
1018
|
-
if (!
|
|
1309
|
+
const root = findProjectRoot20(opts.dir);
|
|
1310
|
+
const paths = resolveHaivePaths18(root);
|
|
1311
|
+
if (!existsSync19(paths.memoriesDir)) {
|
|
1019
1312
|
ui.error(`No .ai/memories at ${root}.`);
|
|
1020
1313
|
process.exitCode = 1;
|
|
1021
1314
|
return;
|
|
1022
1315
|
}
|
|
1023
|
-
const all = await
|
|
1316
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1024
1317
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
1025
1318
|
if (!found) {
|
|
1026
1319
|
ui.error(`No memory with id "${id}".`);
|
|
1027
1320
|
process.exitCode = 1;
|
|
1028
1321
|
return;
|
|
1029
1322
|
}
|
|
1030
|
-
const rel =
|
|
1323
|
+
const rel = path18.relative(root, found.filePath);
|
|
1031
1324
|
if (!opts.yes) {
|
|
1032
1325
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1033
1326
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -1051,27 +1344,27 @@ function registerMemoryRm(memory2) {
|
|
|
1051
1344
|
}
|
|
1052
1345
|
|
|
1053
1346
|
// src/commands/memory-show.ts
|
|
1054
|
-
import { existsSync as
|
|
1055
|
-
import { readFile as
|
|
1056
|
-
import
|
|
1347
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1348
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1349
|
+
import path19 from "path";
|
|
1057
1350
|
import "commander";
|
|
1058
1351
|
import {
|
|
1059
1352
|
deriveConfidence as deriveConfidence2,
|
|
1060
|
-
findProjectRoot as
|
|
1353
|
+
findProjectRoot as findProjectRoot21,
|
|
1061
1354
|
getUsage as getUsage6,
|
|
1062
1355
|
loadUsageIndex as loadUsageIndex8,
|
|
1063
|
-
resolveHaivePaths as
|
|
1356
|
+
resolveHaivePaths as resolveHaivePaths19
|
|
1064
1357
|
} from "@hiveai/core";
|
|
1065
1358
|
function registerMemoryShow(memory2) {
|
|
1066
1359
|
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) => {
|
|
1067
|
-
const root =
|
|
1068
|
-
const paths =
|
|
1069
|
-
if (!
|
|
1360
|
+
const root = findProjectRoot21(opts.dir);
|
|
1361
|
+
const paths = resolveHaivePaths19(root);
|
|
1362
|
+
if (!existsSync20(paths.memoriesDir)) {
|
|
1070
1363
|
ui.error(`No .ai/memories at ${root}.`);
|
|
1071
1364
|
process.exitCode = 1;
|
|
1072
1365
|
return;
|
|
1073
1366
|
}
|
|
1074
|
-
const all = await
|
|
1367
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1075
1368
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
1076
1369
|
if (!found) {
|
|
1077
1370
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -1079,7 +1372,7 @@ function registerMemoryShow(memory2) {
|
|
|
1079
1372
|
return;
|
|
1080
1373
|
}
|
|
1081
1374
|
if (opts.raw) {
|
|
1082
|
-
console.log(await
|
|
1375
|
+
console.log(await readFile4(found.filePath, "utf8"));
|
|
1083
1376
|
return;
|
|
1084
1377
|
}
|
|
1085
1378
|
const fm = found.memory.frontmatter;
|
|
@@ -1095,7 +1388,7 @@ function registerMemoryShow(memory2) {
|
|
|
1095
1388
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
1096
1389
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
1097
1390
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
1098
|
-
console.log(`${ui.dim("file:")} ${
|
|
1391
|
+
console.log(`${ui.dim("file:")} ${path19.relative(root, found.filePath)}`);
|
|
1099
1392
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
1100
1393
|
console.log(ui.dim("anchor:"));
|
|
1101
1394
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -1110,26 +1403,26 @@ function registerMemoryShow(memory2) {
|
|
|
1110
1403
|
}
|
|
1111
1404
|
|
|
1112
1405
|
// src/commands/memory-stats.ts
|
|
1113
|
-
import { existsSync as
|
|
1114
|
-
import
|
|
1406
|
+
import { existsSync as existsSync21 } from "fs";
|
|
1407
|
+
import path20 from "path";
|
|
1115
1408
|
import "commander";
|
|
1116
1409
|
import {
|
|
1117
1410
|
deriveConfidence as deriveConfidence3,
|
|
1118
|
-
findProjectRoot as
|
|
1411
|
+
findProjectRoot as findProjectRoot22,
|
|
1119
1412
|
getUsage as getUsage7,
|
|
1120
1413
|
loadUsageIndex as loadUsageIndex9,
|
|
1121
|
-
resolveHaivePaths as
|
|
1414
|
+
resolveHaivePaths as resolveHaivePaths20
|
|
1122
1415
|
} from "@hiveai/core";
|
|
1123
1416
|
function registerMemoryStats(memory2) {
|
|
1124
1417
|
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) => {
|
|
1125
|
-
const root =
|
|
1126
|
-
const paths =
|
|
1127
|
-
if (!
|
|
1418
|
+
const root = findProjectRoot22(opts.dir);
|
|
1419
|
+
const paths = resolveHaivePaths20(root);
|
|
1420
|
+
if (!existsSync21(paths.memoriesDir)) {
|
|
1128
1421
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
1129
1422
|
process.exitCode = 1;
|
|
1130
1423
|
return;
|
|
1131
1424
|
}
|
|
1132
|
-
const all = await
|
|
1425
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1133
1426
|
const usage = await loadUsageIndex9(paths);
|
|
1134
1427
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
1135
1428
|
if (target.length === 0) {
|
|
@@ -1149,32 +1442,32 @@ function registerMemoryStats(memory2) {
|
|
|
1149
1442
|
console.log(
|
|
1150
1443
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
1151
1444
|
);
|
|
1152
|
-
console.log(` ${ui.dim(
|
|
1445
|
+
console.log(` ${ui.dim(path20.relative(root, filePath))}`);
|
|
1153
1446
|
}
|
|
1154
1447
|
});
|
|
1155
1448
|
}
|
|
1156
1449
|
|
|
1157
1450
|
// src/commands/memory-verify.ts
|
|
1158
|
-
import { writeFile as
|
|
1159
|
-
import { existsSync as
|
|
1160
|
-
import
|
|
1451
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
1452
|
+
import { existsSync as existsSync22 } from "fs";
|
|
1453
|
+
import path21 from "path";
|
|
1161
1454
|
import "commander";
|
|
1162
1455
|
import {
|
|
1163
|
-
findProjectRoot as
|
|
1164
|
-
resolveHaivePaths as
|
|
1165
|
-
serializeMemory as
|
|
1456
|
+
findProjectRoot as findProjectRoot23,
|
|
1457
|
+
resolveHaivePaths as resolveHaivePaths21,
|
|
1458
|
+
serializeMemory as serializeMemory8,
|
|
1166
1459
|
verifyAnchor as verifyAnchor2
|
|
1167
1460
|
} from "@hiveai/core";
|
|
1168
1461
|
function registerMemoryVerify(memory2) {
|
|
1169
1462
|
memory2.command("verify").description("Check memory anchors against current code, optionally marking stale ones").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 for re-freshed) back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
1170
|
-
const root =
|
|
1171
|
-
const paths =
|
|
1172
|
-
if (!
|
|
1463
|
+
const root = findProjectRoot23(opts.dir);
|
|
1464
|
+
const paths = resolveHaivePaths21(root);
|
|
1465
|
+
if (!existsSync22(paths.memoriesDir)) {
|
|
1173
1466
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
1174
1467
|
process.exitCode = 1;
|
|
1175
1468
|
return;
|
|
1176
1469
|
}
|
|
1177
|
-
const all = await
|
|
1470
|
+
const all = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1178
1471
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
1179
1472
|
if (opts.id && targets.length === 0) {
|
|
1180
1473
|
ui.error(`No memory with id "${opts.id}".`);
|
|
@@ -1192,7 +1485,7 @@ function registerMemoryVerify(memory2) {
|
|
|
1192
1485
|
anchorless++;
|
|
1193
1486
|
continue;
|
|
1194
1487
|
}
|
|
1195
|
-
const rel =
|
|
1488
|
+
const rel = path21.relative(root, filePath);
|
|
1196
1489
|
if (result.stale) {
|
|
1197
1490
|
staleCount++;
|
|
1198
1491
|
console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
|
|
@@ -1204,7 +1497,7 @@ function registerMemoryVerify(memory2) {
|
|
|
1204
1497
|
}
|
|
1205
1498
|
if (opts.update) {
|
|
1206
1499
|
const next = applyVerification(mem, result);
|
|
1207
|
-
await
|
|
1500
|
+
await writeFile10(filePath, serializeMemory8(next), "utf8");
|
|
1208
1501
|
updated++;
|
|
1209
1502
|
}
|
|
1210
1503
|
}
|
|
@@ -1243,10 +1536,11 @@ function applyVerification(mem, result) {
|
|
|
1243
1536
|
}
|
|
1244
1537
|
|
|
1245
1538
|
// src/index.ts
|
|
1246
|
-
var program = new
|
|
1247
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.
|
|
1539
|
+
var program = new Command24();
|
|
1540
|
+
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.3");
|
|
1248
1541
|
registerInit(program);
|
|
1249
1542
|
registerMcp(program);
|
|
1543
|
+
registerBriefing(program);
|
|
1250
1544
|
registerEmbeddings(program);
|
|
1251
1545
|
registerSync(program);
|
|
1252
1546
|
registerInstallHooks(program);
|
|
@@ -1266,6 +1560,7 @@ registerMemoryEdit(memory);
|
|
|
1266
1560
|
registerMemoryRm(memory);
|
|
1267
1561
|
registerMemoryPending(memory);
|
|
1268
1562
|
registerMemoryApprove(memory);
|
|
1563
|
+
registerMemoryUpdate(memory);
|
|
1269
1564
|
registerMemoryHot(memory);
|
|
1270
1565
|
program.parseAsync(process.argv).catch((err) => {
|
|
1271
1566
|
if (isZodError(err)) {
|