@hiveai/cli 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Dashboard-Y2AIWFZK.js +0 -0
- package/dist/index.js +1107 -471
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
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 Command40 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync } from "fs";
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
literalMatchesAnyToken,
|
|
15
15
|
loadCodeMap,
|
|
16
16
|
loadMemoriesFromDir,
|
|
17
|
+
loadUsageIndex,
|
|
17
18
|
memoryMatchesAnchorPaths,
|
|
18
19
|
queryCodeMap,
|
|
19
20
|
resolveHaivePaths,
|
|
@@ -51,11 +52,240 @@ var ui = {
|
|
|
51
52
|
}
|
|
52
53
|
};
|
|
53
54
|
|
|
55
|
+
// src/utils/briefing-radar.ts
|
|
56
|
+
import { execFile } from "child_process";
|
|
57
|
+
import { promisify } from "util";
|
|
58
|
+
var exec = promisify(execFile);
|
|
59
|
+
var DEFAULT_DAYS_BACK = 14;
|
|
60
|
+
var DEFAULT_MAX_COMMITS = 5;
|
|
61
|
+
var DEFAULT_MAX_TODOS = 8;
|
|
62
|
+
var DEFAULT_MAX_HOT_FILES = 5;
|
|
63
|
+
var TODO_RE = /\b(?:TODO|FIXME|HACK|XXX)\b[: ]?(.{0,120})/i;
|
|
64
|
+
var SOURCE_GLOBS = [
|
|
65
|
+
"*.ts",
|
|
66
|
+
"*.tsx",
|
|
67
|
+
"*.js",
|
|
68
|
+
"*.jsx",
|
|
69
|
+
"*.py",
|
|
70
|
+
"*.go",
|
|
71
|
+
"*.rs",
|
|
72
|
+
"*.java",
|
|
73
|
+
"*.kt",
|
|
74
|
+
"*.swift",
|
|
75
|
+
"*.rb",
|
|
76
|
+
"*.php",
|
|
77
|
+
"*.cs",
|
|
78
|
+
"*.cpp",
|
|
79
|
+
"*.c",
|
|
80
|
+
"*.h"
|
|
81
|
+
];
|
|
82
|
+
async function isGitRepo(root) {
|
|
83
|
+
try {
|
|
84
|
+
await exec("git", ["rev-parse", "--is-inside-work-tree"], { cwd: root });
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getRecentCommits(root, daysBack, maxCommits, taskTokens, filePaths) {
|
|
91
|
+
try {
|
|
92
|
+
const { stdout } = await exec(
|
|
93
|
+
"git",
|
|
94
|
+
[
|
|
95
|
+
"log",
|
|
96
|
+
`--since=${daysBack}.days.ago`,
|
|
97
|
+
"--name-only",
|
|
98
|
+
"--pretty=format:%x1f%h%x1f%ad%x1f%s",
|
|
99
|
+
"--date=short",
|
|
100
|
+
"-n",
|
|
101
|
+
"60"
|
|
102
|
+
],
|
|
103
|
+
{ cwd: root, maxBuffer: 4 * 1024 * 1024 }
|
|
104
|
+
);
|
|
105
|
+
const blocks = stdout.split("").filter((b) => b.trim().length > 0);
|
|
106
|
+
const commits = [];
|
|
107
|
+
for (let i = 0; i + 2 < blocks.length; i += 3) {
|
|
108
|
+
const sha = blocks[i].trim();
|
|
109
|
+
const date = blocks[i + 1].trim();
|
|
110
|
+
const tail = blocks[i + 2];
|
|
111
|
+
const lines = tail.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
112
|
+
const subject = lines.shift() ?? "";
|
|
113
|
+
const files = lines;
|
|
114
|
+
commits.push({ sha, date, subject, files });
|
|
115
|
+
}
|
|
116
|
+
const lowerTokens = taskTokens?.map((t) => t.toLowerCase()) ?? [];
|
|
117
|
+
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
118
|
+
const scored = commits.map((c) => {
|
|
119
|
+
let score = 0;
|
|
120
|
+
const haystack = (c.subject + " " + c.files.join(" ")).toLowerCase();
|
|
121
|
+
for (const t of lowerTokens) if (haystack.includes(t)) score += 2;
|
|
122
|
+
for (const p of lowerPaths) if (c.files.some((f) => f.toLowerCase().includes(p))) score += 3;
|
|
123
|
+
return { c, score };
|
|
124
|
+
});
|
|
125
|
+
if (lowerTokens.length === 0 && lowerPaths.length === 0) {
|
|
126
|
+
return commits.slice(0, maxCommits);
|
|
127
|
+
}
|
|
128
|
+
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, maxCommits).map((s) => s.c);
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function getOpenTodos(root, maxTodos, taskTokens, filePaths) {
|
|
134
|
+
try {
|
|
135
|
+
const includeArgs = SOURCE_GLOBS.flatMap((g) => ["--include", g]);
|
|
136
|
+
const { stdout } = await exec(
|
|
137
|
+
"grep",
|
|
138
|
+
[
|
|
139
|
+
"-rnE",
|
|
140
|
+
"--exclude-dir=node_modules",
|
|
141
|
+
"--exclude-dir=.git",
|
|
142
|
+
"--exclude-dir=dist",
|
|
143
|
+
"--exclude-dir=build",
|
|
144
|
+
"--exclude-dir=.next",
|
|
145
|
+
"--exclude-dir=coverage",
|
|
146
|
+
...includeArgs,
|
|
147
|
+
"\\b(TODO|FIXME|HACK|XXX)\\b",
|
|
148
|
+
"."
|
|
149
|
+
],
|
|
150
|
+
{ cwd: root, maxBuffer: 4 * 1024 * 1024 }
|
|
151
|
+
).catch((err) => ({ stdout: err.stdout ?? "" }));
|
|
152
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
153
|
+
const parsed = [];
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
const m = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
156
|
+
if (!m) continue;
|
|
157
|
+
const [, file, lineNoStr, rest] = m;
|
|
158
|
+
const todoMatch = rest.match(TODO_RE);
|
|
159
|
+
if (!todoMatch) continue;
|
|
160
|
+
const text = (todoMatch[1] ?? "").trim() || rest.trim().slice(0, 120);
|
|
161
|
+
parsed.push({ file: file.replace(/^\.\//, ""), line: Number(lineNoStr), text });
|
|
162
|
+
}
|
|
163
|
+
const lowerTokens = taskTokens?.map((t) => t.toLowerCase()) ?? [];
|
|
164
|
+
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
165
|
+
if (lowerTokens.length === 0 && lowerPaths.length === 0) {
|
|
166
|
+
return parsed.slice(0, maxTodos);
|
|
167
|
+
}
|
|
168
|
+
const scored = parsed.map((t) => {
|
|
169
|
+
let score = 0;
|
|
170
|
+
const hay = (t.file + " " + t.text).toLowerCase();
|
|
171
|
+
for (const tok of lowerTokens) if (hay.includes(tok)) score += 1;
|
|
172
|
+
for (const p of lowerPaths) if (t.file.toLowerCase().includes(p)) score += 2;
|
|
173
|
+
return { t, score };
|
|
174
|
+
});
|
|
175
|
+
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, maxTodos).map((s) => s.t);
|
|
176
|
+
} catch {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
181
|
+
try {
|
|
182
|
+
const { stdout } = await exec(
|
|
183
|
+
"git",
|
|
184
|
+
[
|
|
185
|
+
"log",
|
|
186
|
+
`--since=${daysBack * 6}.days.ago`,
|
|
187
|
+
"--name-only",
|
|
188
|
+
"--pretty=format:"
|
|
189
|
+
],
|
|
190
|
+
{ cwd: root, maxBuffer: 4 * 1024 * 1024 }
|
|
191
|
+
);
|
|
192
|
+
const counts = /* @__PURE__ */ new Map();
|
|
193
|
+
for (const raw of stdout.split("\n")) {
|
|
194
|
+
const f = raw.trim();
|
|
195
|
+
if (!f) continue;
|
|
196
|
+
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
197
|
+
}
|
|
198
|
+
let entries = [...counts.entries()].map(([path37, changes]) => ({ path: path37, changes }));
|
|
199
|
+
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
200
|
+
if (lowerPaths.length > 0) {
|
|
201
|
+
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
202
|
+
}
|
|
203
|
+
return entries.sort((a, b) => b.changes - a.changes).slice(0, maxHotFiles);
|
|
204
|
+
} catch {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function buildRadar(opts) {
|
|
209
|
+
const inside = await isGitRepo(opts.root);
|
|
210
|
+
if (!inside) {
|
|
211
|
+
return { recentCommits: [], openTodos: [], hotFiles: [], insideGitRepo: false };
|
|
212
|
+
}
|
|
213
|
+
const daysBack = opts.daysBack ?? DEFAULT_DAYS_BACK;
|
|
214
|
+
const [recentCommits, openTodos, hotFiles] = await Promise.all([
|
|
215
|
+
getRecentCommits(opts.root, daysBack, opts.maxCommits ?? DEFAULT_MAX_COMMITS, opts.taskTokens, opts.filePaths),
|
|
216
|
+
getOpenTodos(opts.root, opts.maxTodos ?? DEFAULT_MAX_TODOS, opts.taskTokens, opts.filePaths),
|
|
217
|
+
getHotFiles(opts.root, daysBack, opts.maxHotFiles ?? DEFAULT_MAX_HOT_FILES, opts.filePaths)
|
|
218
|
+
]);
|
|
219
|
+
return { recentCommits, openTodos, hotFiles, insideGitRepo: true };
|
|
220
|
+
}
|
|
221
|
+
function radarHasContent(r) {
|
|
222
|
+
return r.recentCommits.length > 0 || r.openTodos.length > 0 || r.hotFiles.length > 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
54
225
|
// src/commands/briefing.ts
|
|
226
|
+
var RADAR_AUTO_THRESHOLD = 3;
|
|
227
|
+
var CHARS_PER_TOKEN = 4;
|
|
228
|
+
function printRadar(radar, out, reason) {
|
|
229
|
+
if (!radar.insideGitRepo) return;
|
|
230
|
+
if (!radarHasContent(radar)) return;
|
|
231
|
+
const header = reason === "low-memory-signal" ? "=== Project Radar (few relevant memories \u2014 surfacing live signals) ===" : "=== Project Radar ===";
|
|
232
|
+
out(`${ui.bold(header)}
|
|
233
|
+
`);
|
|
234
|
+
if (radar.recentCommits.length > 0) {
|
|
235
|
+
out(ui.bold("Recent commits:"));
|
|
236
|
+
for (const c of radar.recentCommits) {
|
|
237
|
+
const filesBlurb = c.files.slice(0, 3).join(", ");
|
|
238
|
+
const more = c.files.length > 3 ? ` (+${c.files.length - 3})` : "";
|
|
239
|
+
out(` ${ui.dim(c.date)} ${c.sha} ${c.subject}`);
|
|
240
|
+
if (filesBlurb) out(ui.dim(` ${filesBlurb}${more}`));
|
|
241
|
+
}
|
|
242
|
+
out("");
|
|
243
|
+
}
|
|
244
|
+
if (radar.openTodos.length > 0) {
|
|
245
|
+
out(ui.bold("Open TODOs/FIXMEs:"));
|
|
246
|
+
for (const t of radar.openTodos) {
|
|
247
|
+
out(` ${ui.dim(t.file + ":" + t.line)} ${t.text}`);
|
|
248
|
+
}
|
|
249
|
+
out("");
|
|
250
|
+
}
|
|
251
|
+
if (radar.hotFiles.length > 0) {
|
|
252
|
+
out(ui.bold("Hot files (most modified recently):"));
|
|
253
|
+
for (const f of radar.hotFiles) {
|
|
254
|
+
out(` ${f.changes}\xD7 ${ui.dim(f.path)}`);
|
|
255
|
+
}
|
|
256
|
+
out("");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
var TokenBudgetWriter = class {
|
|
260
|
+
constructor(budgetChars) {
|
|
261
|
+
this.budgetChars = budgetChars;
|
|
262
|
+
}
|
|
263
|
+
budgetChars;
|
|
264
|
+
used = 0;
|
|
265
|
+
truncated = false;
|
|
266
|
+
write(text) {
|
|
267
|
+
if (this.truncated) return false;
|
|
268
|
+
const next = this.used + text.length + 1;
|
|
269
|
+
if (next > this.budgetChars) {
|
|
270
|
+
console.log(ui.dim(`... [briefing truncated to fit --max-tokens budget \xB7 ${Math.round(this.used / CHARS_PER_TOKEN)} tokens used]`));
|
|
271
|
+
this.truncated = true;
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
console.log(text);
|
|
275
|
+
this.used = next;
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
isTruncated() {
|
|
279
|
+
return this.truncated;
|
|
280
|
+
}
|
|
281
|
+
remainingChars() {
|
|
282
|
+
return Math.max(0, this.budgetChars - this.used);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
55
285
|
function registerBriefing(program2) {
|
|
56
286
|
program2.command("briefing").description(
|
|
57
287
|
'Print the full project briefing: last session recap + project context + relevant memories.\n Equivalent to calling get_briefing via MCP. Run before starting any task.\n\n Examples:\n haive briefing\n haive briefing --task "add Stripe payment" --files src/payments/PaymentService.ts\n haive briefing --symbols PaymentService,TenantFilter # look up where symbols live\n'
|
|
58
|
-
).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "10").option(
|
|
288
|
+
).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "10").option("--max-tokens <n>", "approximate token budget for the entire briefing (truncates if exceeded)").option("--explain-source", "annotate each memory with [source: <relative-path> \xB7 anchors: <files>] for traceable citations").option("--radar", "force project radar (recent commits, open TODOs, hot files) even when memories are plentiful").option("--no-radar", "disable the project radar even when memories are scarce").option(
|
|
59
289
|
"--scope <scope>",
|
|
60
290
|
"personal | team | shared | all (default: all \u2014 includes team + shared cross-repo memories)",
|
|
61
291
|
"all"
|
|
@@ -67,14 +297,29 @@ function registerBriefing(program2) {
|
|
|
67
297
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
68
298
|
const root = findProjectRoot(opts.dir);
|
|
69
299
|
const paths = resolveHaivePaths(root);
|
|
300
|
+
const budgetTokens = opts.maxTokens ? Math.max(100, Number(opts.maxTokens)) : null;
|
|
301
|
+
const writer = budgetTokens ? new TokenBudgetWriter(budgetTokens * CHARS_PER_TOKEN) : null;
|
|
302
|
+
const out = (text) => {
|
|
303
|
+
if (writer) return writer.write(text);
|
|
304
|
+
console.log(text);
|
|
305
|
+
return true;
|
|
306
|
+
};
|
|
307
|
+
const stopped = () => writer?.isTruncated() ?? false;
|
|
70
308
|
if (!existsSync(paths.memoriesDir)) {
|
|
71
309
|
if (existsSync(paths.projectContext)) {
|
|
72
|
-
|
|
310
|
+
out(`${ui.bold("=== Project Context ===")}
|
|
73
311
|
`);
|
|
74
|
-
|
|
312
|
+
out((await readFile(paths.projectContext, "utf8")).trim());
|
|
313
|
+
out("");
|
|
75
314
|
} else {
|
|
76
315
|
ui.warn("No project-context.md found. Run `haive init` and the `bootstrap_project` MCP prompt to set it up.");
|
|
77
316
|
}
|
|
317
|
+
if (opts.radar !== false && !stopped()) {
|
|
318
|
+
const filePathsEarly = parseCsv(opts.files);
|
|
319
|
+
const tokensEarly = opts.task ? tokenizeQuery(opts.task) : null;
|
|
320
|
+
const radar = await buildRadar({ root, taskTokens: tokensEarly, filePaths: filePathsEarly });
|
|
321
|
+
printRadar(radar, out, "low-memory-signal");
|
|
322
|
+
}
|
|
78
323
|
return;
|
|
79
324
|
}
|
|
80
325
|
const ownMemories = await loadMemoriesFromDir(paths.memoriesDir);
|
|
@@ -112,17 +357,17 @@ function registerBriefing(program2) {
|
|
|
112
357
|
const recaps = all.filter(({ memory: mem }) => mem.frontmatter.type === "session_recap").sort(
|
|
113
358
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
114
359
|
);
|
|
115
|
-
if (recaps.length > 0) {
|
|
360
|
+
if (recaps.length > 0 && !stopped()) {
|
|
116
361
|
const recap = recaps[0];
|
|
117
362
|
const fm = recap.memory.frontmatter;
|
|
118
363
|
const rev = fm.revision_count ? ` \xB7 revision #${fm.revision_count}` : "";
|
|
119
|
-
|
|
364
|
+
out(`${ui.bold("=== Last Session Recap ===")}
|
|
120
365
|
`);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
366
|
+
out(ui.dim(`${fm.id} (${fm.scope}${rev})`));
|
|
367
|
+
out(recap.memory.body.trim());
|
|
368
|
+
out("");
|
|
124
369
|
}
|
|
125
|
-
if (existsSync(paths.projectContext)) {
|
|
370
|
+
if (existsSync(paths.projectContext) && !stopped()) {
|
|
126
371
|
const ctx = await readFile(paths.projectContext, "utf8");
|
|
127
372
|
const isTemplate = ctx.includes("TODO \u2014 high-level overview") || ctx.includes("Generated by `haive init`");
|
|
128
373
|
if (isTemplate) {
|
|
@@ -132,14 +377,14 @@ function registerBriefing(program2) {
|
|
|
132
377
|
ui.warn(
|
|
133
378
|
"Fix: in your AI client, invoke the MCP prompt bootstrap_project to auto-fill it from your codebase."
|
|
134
379
|
);
|
|
135
|
-
|
|
380
|
+
out("");
|
|
136
381
|
} else {
|
|
137
|
-
|
|
382
|
+
out(`${ui.bold("=== Project Context ===")}
|
|
138
383
|
`);
|
|
139
|
-
|
|
140
|
-
|
|
384
|
+
out(ctx.trim());
|
|
385
|
+
out("");
|
|
141
386
|
}
|
|
142
|
-
} else {
|
|
387
|
+
} else if (!existsSync(paths.projectContext)) {
|
|
143
388
|
ui.warn(
|
|
144
389
|
"No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
|
|
145
390
|
);
|
|
@@ -177,54 +422,85 @@ function registerBriefing(program2) {
|
|
|
177
422
|
if (draftCount > 0) {
|
|
178
423
|
ui.info(`(${draftCount} draft memories excluded \u2014 use --include-draft to show)`);
|
|
179
424
|
}
|
|
425
|
+
if (opts.radar !== false && !stopped()) {
|
|
426
|
+
const radar = await buildRadar({ root, taskTokens: tokens, filePaths });
|
|
427
|
+
out("");
|
|
428
|
+
printRadar(radar, out, "low-memory-signal");
|
|
429
|
+
}
|
|
180
430
|
return;
|
|
181
431
|
}
|
|
182
|
-
|
|
432
|
+
if (stopped()) return;
|
|
433
|
+
const usageIndex = await loadUsageIndex(paths).catch(() => null);
|
|
434
|
+
out(`${ui.bold("=== Relevant Memories ===")}
|
|
183
435
|
`);
|
|
184
436
|
for (const item of top) {
|
|
437
|
+
if (stopped()) break;
|
|
185
438
|
const fm = item.memory.frontmatter;
|
|
186
439
|
const badge = ui.statusBadge(fm.status);
|
|
187
440
|
const draftMarker = fm.status === "draft" ? ui.yellow(" [DRAFT]") : "";
|
|
188
441
|
const unverifiedMarker = fm.status === "proposed" ? ui.yellow(" [UNVERIFIED]") : "";
|
|
189
442
|
const originMarker = item.origin ? ` ${ui.yellow("[from " + item.origin + "]")}` : "";
|
|
190
|
-
|
|
191
|
-
|
|
443
|
+
const reads = usageIndex?.by_id[fm.id]?.read_count ?? 0;
|
|
444
|
+
const hitMarker = reads > 0 ? ` ${ui.dim("\xB7 " + reads + "\xD7 read")}` : "";
|
|
445
|
+
out(
|
|
446
|
+
`${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}${originMarker}${hitMarker}`
|
|
192
447
|
);
|
|
193
|
-
|
|
194
|
-
|
|
448
|
+
if (opts.explainSource) {
|
|
449
|
+
const relPath = path.relative(root, item.filePath);
|
|
450
|
+
const anchorPaths = fm.anchor?.paths ?? [];
|
|
451
|
+
const anchorSymbols = fm.anchor?.symbols ?? [];
|
|
452
|
+
const parts = [`source: ${relPath}`];
|
|
453
|
+
if (anchorPaths.length > 0) parts.push(`paths: ${anchorPaths.join(", ")}`);
|
|
454
|
+
if (anchorSymbols.length > 0) parts.push(`symbols: ${anchorSymbols.join(", ")}`);
|
|
455
|
+
out(ui.dim(` [${parts.join(" \xB7 ")}]`));
|
|
456
|
+
}
|
|
457
|
+
out(item.memory.body.trim());
|
|
458
|
+
out("");
|
|
195
459
|
}
|
|
196
|
-
|
|
460
|
+
if (!stopped()) out(ui.dim(`${top.length} memor${top.length === 1 ? "y" : "ies"} surfaced`));
|
|
197
461
|
const ids = top.map(({ memory: mem }) => mem.frontmatter.id);
|
|
198
462
|
if (ids.length > 0) {
|
|
199
463
|
await trackReads(paths, ids).catch(() => {
|
|
200
464
|
});
|
|
201
465
|
}
|
|
466
|
+
const radarForced = opts.radar === true;
|
|
467
|
+
const radarAuto = opts.radar !== false && top.length < RADAR_AUTO_THRESHOLD;
|
|
468
|
+
if ((radarForced || radarAuto) && !stopped()) {
|
|
469
|
+
const radar = await buildRadar({ root, taskTokens: tokens, filePaths });
|
|
470
|
+
if (radarHasContent(radar)) {
|
|
471
|
+
out("");
|
|
472
|
+
printRadar(radar, out, radarForced ? "forced" : "low-memory-signal");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
202
475
|
const requestedSymbols = (opts.symbols ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
203
|
-
if (requestedSymbols.length > 0) {
|
|
476
|
+
if (requestedSymbols.length > 0 && !stopped()) {
|
|
204
477
|
const codeMap = await loadCodeMap(paths);
|
|
205
478
|
if (!codeMap) {
|
|
206
479
|
ui.warn("No code-map found. Run `haive index code` first to enable symbol lookup.");
|
|
207
480
|
} else {
|
|
208
|
-
|
|
481
|
+
out(`
|
|
209
482
|
${ui.bold("=== Symbol Locations ===")}
|
|
210
483
|
`);
|
|
211
484
|
for (const sym of requestedSymbols) {
|
|
485
|
+
if (stopped()) break;
|
|
212
486
|
const { files } = queryCodeMap(codeMap, { symbol: sym });
|
|
213
487
|
if (files.length === 0) {
|
|
214
|
-
|
|
488
|
+
out(`${ui.dim(sym)} (not found in code-map)`);
|
|
215
489
|
} else {
|
|
216
490
|
for (const f of files) {
|
|
491
|
+
if (stopped()) break;
|
|
217
492
|
const exports = f.entry.exports.filter(
|
|
218
493
|
(e) => e.name.toLowerCase().includes(sym.toLowerCase())
|
|
219
494
|
);
|
|
220
495
|
for (const e of exports) {
|
|
496
|
+
if (stopped()) break;
|
|
221
497
|
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
222
|
-
|
|
498
|
+
out(`${ui.bold(e.name)} ${ui.dim(f.path + ":" + e.line)} [${e.kind}]${desc}`);
|
|
223
499
|
}
|
|
224
500
|
}
|
|
225
501
|
}
|
|
226
502
|
}
|
|
227
|
-
|
|
503
|
+
out("");
|
|
228
504
|
}
|
|
229
505
|
}
|
|
230
506
|
});
|
|
@@ -1671,18 +1947,21 @@ jobs:
|
|
|
1671
1947
|
`;
|
|
1672
1948
|
function registerInit(program2) {
|
|
1673
1949
|
program2.command("init").description(
|
|
1674
|
-
"Initialize a hAIve project \u2014 autopilot mode ON by default (zero human intervention).\n Add --manual
|
|
1950
|
+
"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."
|
|
1675
1951
|
).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(
|
|
1676
1952
|
"--manual",
|
|
1677
1953
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
1678
1954
|
).option(
|
|
1679
1955
|
"--bootstrap",
|
|
1680
|
-
"auto-generate .ai/project-context.md from package.json, README, and directory structure (
|
|
1956
|
+
"auto-generate .ai/project-context.md from package.json, README, and directory structure (ON by default in autopilot)"
|
|
1957
|
+
).option(
|
|
1958
|
+
"--no-bootstrap",
|
|
1959
|
+
"skip the project-context auto-generation (only the default template is written)"
|
|
1681
1960
|
).option(
|
|
1682
1961
|
"--stack <stacks>",
|
|
1683
1962
|
`pre-seed validated memory packs for the given stacks (comma-separated).
|
|
1684
1963
|
Supported: ${SUPPORTED_STACKS.join(", ")}.
|
|
1685
|
-
|
|
1964
|
+
Defaults to 'auto' in autopilot mode (detects from package.json). Pass 'none' to disable.`
|
|
1686
1965
|
).option(
|
|
1687
1966
|
"--no-mcp-setup",
|
|
1688
1967
|
"skip auto-configuring haive-mcp in Cursor / VS Code / Claude Code"
|
|
@@ -1690,6 +1969,8 @@ function registerInit(program2) {
|
|
|
1690
1969
|
const root = path7.resolve(opts.dir);
|
|
1691
1970
|
const paths = resolveHaivePaths4(root);
|
|
1692
1971
|
const autopilot = opts.manual !== true;
|
|
1972
|
+
const wantBootstrap = opts.bootstrap === void 0 ? autopilot : opts.bootstrap;
|
|
1973
|
+
const wantStack = opts.stack === void 0 ? autopilot ? "auto" : void 0 : opts.stack === "none" ? void 0 : opts.stack;
|
|
1693
1974
|
if (existsSync6(paths.haiveDir)) {
|
|
1694
1975
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
1695
1976
|
}
|
|
@@ -1698,7 +1979,7 @@ function registerInit(program2) {
|
|
|
1698
1979
|
await mkdir3(paths.moduleDir, { recursive: true });
|
|
1699
1980
|
await mkdir3(paths.modulesContextDir, { recursive: true });
|
|
1700
1981
|
if (!existsSync6(paths.projectContext)) {
|
|
1701
|
-
if (
|
|
1982
|
+
if (wantBootstrap) {
|
|
1702
1983
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
1703
1984
|
try {
|
|
1704
1985
|
const context = await generateBootstrapContext(root);
|
|
@@ -1727,7 +2008,7 @@ function registerInit(program2) {
|
|
|
1727
2008
|
await writeBridge(root, ".cursorrules");
|
|
1728
2009
|
await writeBridge(root, path7.join(".github", "copilot-instructions.md"));
|
|
1729
2010
|
}
|
|
1730
|
-
const stacksToSeed = await resolveStacksToSeed(root,
|
|
2011
|
+
const stacksToSeed = await resolveStacksToSeed(root, wantStack);
|
|
1731
2012
|
if (stacksToSeed.length > 0) {
|
|
1732
2013
|
let totalSeeded = 0;
|
|
1733
2014
|
for (const stack of stacksToSeed) {
|
|
@@ -1869,11 +2150,106 @@ async function writeBridge(root, relPath) {
|
|
|
1869
2150
|
}
|
|
1870
2151
|
|
|
1871
2152
|
// src/commands/install-hooks.ts
|
|
1872
|
-
import { mkdir as
|
|
1873
|
-
import { existsSync as
|
|
1874
|
-
import
|
|
2153
|
+
import { mkdir as mkdir5, writeFile as writeFile5, chmod, readFile as readFile6 } from "fs/promises";
|
|
2154
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2155
|
+
import path9 from "path";
|
|
1875
2156
|
import "commander";
|
|
1876
2157
|
import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
|
|
2158
|
+
|
|
2159
|
+
// src/utils/claude-hooks.ts
|
|
2160
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2161
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
2162
|
+
import path8 from "path";
|
|
2163
|
+
var HAIVE_HOOK_TAG = "haive-passive-capture";
|
|
2164
|
+
var POST_TOOL_USE_GROUP = {
|
|
2165
|
+
matcher: "Edit|Write|Bash",
|
|
2166
|
+
hooks: [
|
|
2167
|
+
{
|
|
2168
|
+
type: "command",
|
|
2169
|
+
command: "haive observe",
|
|
2170
|
+
haive_tag: HAIVE_HOOK_TAG
|
|
2171
|
+
}
|
|
2172
|
+
]
|
|
2173
|
+
};
|
|
2174
|
+
var SESSION_END_GROUP = {
|
|
2175
|
+
hooks: [
|
|
2176
|
+
{
|
|
2177
|
+
type: "command",
|
|
2178
|
+
command: "haive session end --quiet --auto",
|
|
2179
|
+
haive_tag: HAIVE_HOOK_TAG
|
|
2180
|
+
}
|
|
2181
|
+
]
|
|
2182
|
+
};
|
|
2183
|
+
function dropHaiveGroups(groups) {
|
|
2184
|
+
return groups.filter(
|
|
2185
|
+
(g) => !g.hooks.some((h) => h.haive_tag === HAIVE_HOOK_TAG)
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
function patchClaudeSettings(input) {
|
|
2189
|
+
const settings = input ? { ...input } : {};
|
|
2190
|
+
const hooks = settings.hooks ? { ...settings.hooks } : {};
|
|
2191
|
+
hooks.PostToolUse = [
|
|
2192
|
+
...dropHaiveGroups(hooks.PostToolUse ?? []),
|
|
2193
|
+
POST_TOOL_USE_GROUP
|
|
2194
|
+
];
|
|
2195
|
+
hooks.SessionEnd = [
|
|
2196
|
+
...dropHaiveGroups(hooks.SessionEnd ?? []),
|
|
2197
|
+
SESSION_END_GROUP
|
|
2198
|
+
];
|
|
2199
|
+
settings.hooks = hooks;
|
|
2200
|
+
return settings;
|
|
2201
|
+
}
|
|
2202
|
+
function unpatchClaudeSettings(input) {
|
|
2203
|
+
const settings = input ? { ...input } : {};
|
|
2204
|
+
if (!settings.hooks) return settings;
|
|
2205
|
+
const hooks = { ...settings.hooks };
|
|
2206
|
+
for (const [event, groups] of Object.entries(hooks)) {
|
|
2207
|
+
const cleaned = dropHaiveGroups(groups);
|
|
2208
|
+
if (cleaned.length === 0) {
|
|
2209
|
+
delete hooks[event];
|
|
2210
|
+
} else {
|
|
2211
|
+
hooks[event] = cleaned;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
settings.hooks = hooks;
|
|
2215
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
2216
|
+
return settings;
|
|
2217
|
+
}
|
|
2218
|
+
async function installClaudeHooksAtPath(settingsPath) {
|
|
2219
|
+
let raw = null;
|
|
2220
|
+
let created = false;
|
|
2221
|
+
if (existsSync7(settingsPath)) {
|
|
2222
|
+
try {
|
|
2223
|
+
raw = JSON.parse(await readFile5(settingsPath, "utf8"));
|
|
2224
|
+
} catch {
|
|
2225
|
+
throw new Error(`${settingsPath} exists but is not valid JSON. Fix it manually first.`);
|
|
2226
|
+
}
|
|
2227
|
+
} else {
|
|
2228
|
+
created = true;
|
|
2229
|
+
}
|
|
2230
|
+
const patched = patchClaudeSettings(raw);
|
|
2231
|
+
await mkdir4(path8.dirname(settingsPath), { recursive: true });
|
|
2232
|
+
await writeFile4(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
2233
|
+
return { settingsPath, created };
|
|
2234
|
+
}
|
|
2235
|
+
async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
2236
|
+
if (!existsSync7(settingsPath)) {
|
|
2237
|
+
return { settingsPath, created: false };
|
|
2238
|
+
}
|
|
2239
|
+
const raw = JSON.parse(await readFile5(settingsPath, "utf8"));
|
|
2240
|
+
const cleaned = unpatchClaudeSettings(raw);
|
|
2241
|
+
await writeFile4(settingsPath, JSON.stringify(cleaned, null, 2) + "\n", "utf8");
|
|
2242
|
+
return { settingsPath, created: false };
|
|
2243
|
+
}
|
|
2244
|
+
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
2245
|
+
if (scope === "user") {
|
|
2246
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
2247
|
+
return path8.join(home, ".claude", "settings.json");
|
|
2248
|
+
}
|
|
2249
|
+
return path8.join(projectRoot, ".claude", "settings.local.json");
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// src/commands/install-hooks.ts
|
|
1877
2253
|
var HOOK_MARKER = "# hAIve auto-generated";
|
|
1878
2254
|
var POST_MERGE_BODY = `#!/bin/sh
|
|
1879
2255
|
${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
|
|
@@ -1922,50 +2298,192 @@ var HOOKS = [
|
|
|
1922
2298
|
{ name: "post-rewrite", body: POST_MERGE_BODY },
|
|
1923
2299
|
{ name: "pre-push", body: PRE_PUSH_BODY }
|
|
1924
2300
|
];
|
|
2301
|
+
async function installGitHooks(opts) {
|
|
2302
|
+
const root = findProjectRoot6(opts.dir);
|
|
2303
|
+
const gitDir = path9.join(root, ".git");
|
|
2304
|
+
if (!existsSync8(gitDir)) {
|
|
2305
|
+
ui.error(`No .git directory at ${root}.`);
|
|
2306
|
+
process.exitCode = 1;
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
const hooksDir = path9.join(gitDir, "hooks");
|
|
2310
|
+
await mkdir5(hooksDir, { recursive: true });
|
|
2311
|
+
let installed = 0;
|
|
2312
|
+
let skipped = 0;
|
|
2313
|
+
for (const { name, body } of HOOKS) {
|
|
2314
|
+
const file = path9.join(hooksDir, name);
|
|
2315
|
+
if (existsSync8(file) && !opts.force) {
|
|
2316
|
+
const existing = await readFile6(file, "utf8");
|
|
2317
|
+
if (!existing.includes(HOOK_MARKER)) {
|
|
2318
|
+
ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
|
|
2319
|
+
skipped++;
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
await writeFile5(file, body, "utf8");
|
|
2324
|
+
await chmod(file, 493);
|
|
2325
|
+
installed++;
|
|
2326
|
+
}
|
|
2327
|
+
ui.success(`Installed ${installed} git hook(s) in .git/hooks/${skipped ? `, skipped ${skipped}` : ""}`);
|
|
2328
|
+
ui.info("post-merge: haive sync runs after every pull/merge.");
|
|
2329
|
+
ui.info("pre-push: haive precommit runs before every push (advisory, never blocks).");
|
|
2330
|
+
ui.info(" Set HAIVE_BLOCK=1 in your shell to make pre-push blocking.");
|
|
2331
|
+
}
|
|
2332
|
+
async function installClaudeHooks(opts) {
|
|
2333
|
+
const root = findProjectRoot6(opts.dir);
|
|
2334
|
+
const scope = opts.scope ?? "user";
|
|
2335
|
+
const settingsPath = opts.settings ?? defaultClaudeSettingsPath(scope, root);
|
|
2336
|
+
if (opts.uninstall) {
|
|
2337
|
+
const result = await uninstallClaudeHooksAtPath(settingsPath);
|
|
2338
|
+
ui.success(`Removed hAIve hooks from ${result.settingsPath}`);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
try {
|
|
2342
|
+
const result = await installClaudeHooksAtPath(settingsPath);
|
|
2343
|
+
if (result.created) {
|
|
2344
|
+
ui.success(`Created ${result.settingsPath} with hAIve passive-capture hooks`);
|
|
2345
|
+
} else {
|
|
2346
|
+
ui.success(`Patched ${result.settingsPath} (existing user hooks preserved)`);
|
|
2347
|
+
}
|
|
2348
|
+
} catch (err) {
|
|
2349
|
+
ui.error(err instanceof Error ? err.message : String(err));
|
|
2350
|
+
process.exitCode = 1;
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
ui.info("PostToolUse hook: `haive observe` runs after every Edit/Write/Bash");
|
|
2354
|
+
ui.info(" (appends a JSON line to .ai/.cache/observations.jsonl)");
|
|
2355
|
+
ui.info("SessionEnd hook: `haive session end --auto --quiet` distills observations");
|
|
2356
|
+
ui.info(" into a session_recap memory at session close");
|
|
2357
|
+
ui.info("Restart Claude Code (or open a new conversation) for the hooks to take effect.");
|
|
2358
|
+
ui.info(`Run \`haive install-hooks claude --uninstall\` to remove.`);
|
|
2359
|
+
}
|
|
1925
2360
|
function registerInstallHooks(program2) {
|
|
1926
|
-
program2.command("install-hooks").description(
|
|
1927
|
-
"Install
|
|
1928
|
-
).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks").action(async (opts) => {
|
|
1929
|
-
const
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2361
|
+
program2.command("install-hooks [target]").description(
|
|
2362
|
+
"Install hAIve hooks. Targets:\n\n git (default) post-merge / post-rewrite / pre-push for haive sync + precommit\n claude PostToolUse + SessionEnd hooks in ~/.claude/settings.json\n for passive observation capture (Claude Code only)\n\n Examples:\n haive install-hooks # git hooks (legacy default)\n haive install-hooks git\n haive install-hooks claude\n haive install-hooks claude --scope project\n haive install-hooks claude --uninstall\n"
|
|
2363
|
+
).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks (git target only)").option("--scope <scope>", "claude target: 'user' (~/.claude) or 'project' (.claude/)", "user").option("--uninstall", "remove previously installed hAIve hooks (claude target only)").option("--settings <path>", "explicit path to settings.json (claude target only)").action(async (target, opts) => {
|
|
2364
|
+
const t = (target ?? "git").toLowerCase();
|
|
2365
|
+
if (t === "git") {
|
|
2366
|
+
await installGitHooks(opts);
|
|
2367
|
+
} else if (t === "claude") {
|
|
2368
|
+
await installClaudeHooks(opts);
|
|
2369
|
+
} else {
|
|
2370
|
+
ui.error(`Unknown target: ${target}. Available: git, claude`);
|
|
1933
2371
|
process.exitCode = 1;
|
|
1934
|
-
return;
|
|
1935
2372
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/commands/observe.ts
|
|
2377
|
+
import { appendFile, mkdir as mkdir6 } from "fs/promises";
|
|
2378
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2379
|
+
import path10 from "path";
|
|
2380
|
+
import "commander";
|
|
2381
|
+
import { findProjectRoot as findProjectRoot7, resolveHaivePaths as resolveHaivePaths5 } from "@hiveai/core";
|
|
2382
|
+
var MAX_STDIN_BYTES = 256 * 1024;
|
|
2383
|
+
var TRUNCATE_FIELD = 800;
|
|
2384
|
+
function truncate(s, max = TRUNCATE_FIELD) {
|
|
2385
|
+
if (s == null) return "";
|
|
2386
|
+
const str = typeof s === "string" ? s : JSON.stringify(s);
|
|
2387
|
+
return str.length > max ? str.slice(0, max) + "\u2026" : str;
|
|
2388
|
+
}
|
|
2389
|
+
function extractFiles(payload) {
|
|
2390
|
+
const files = /* @__PURE__ */ new Set();
|
|
2391
|
+
const input = payload.tool_input ?? {};
|
|
2392
|
+
for (const k of ["file_path", "path", "notebook_path"]) {
|
|
2393
|
+
const v = input[k];
|
|
2394
|
+
if (typeof v === "string") files.add(v);
|
|
2395
|
+
}
|
|
2396
|
+
const cmd = input["command"];
|
|
2397
|
+
if (typeof cmd === "string") {
|
|
2398
|
+
const matches = cmd.match(/[\w./-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|kt|swift|rb|php|cs|cpp|c|h|md|json|yml|yaml)\b/g);
|
|
2399
|
+
if (matches) for (const m of matches) files.add(m);
|
|
2400
|
+
}
|
|
2401
|
+
return [...files].slice(0, 8);
|
|
2402
|
+
}
|
|
2403
|
+
function buildSummary(payload) {
|
|
2404
|
+
const tool = payload.tool_name ?? "?";
|
|
2405
|
+
const input = payload.tool_input ?? {};
|
|
2406
|
+
if (tool === "Bash") return `Bash: ${truncate(input["command"], 200)}`;
|
|
2407
|
+
if (tool === "Edit") return `Edit ${truncate(input["file_path"], 200)}`;
|
|
2408
|
+
if (tool === "Write") return `Write ${truncate(input["file_path"], 200)}`;
|
|
2409
|
+
return `${tool}: ${truncate(input, 200)}`;
|
|
2410
|
+
}
|
|
2411
|
+
async function readStdin(maxBytes) {
|
|
2412
|
+
if (process.stdin.isTTY) return "";
|
|
2413
|
+
return await new Promise((resolve) => {
|
|
2414
|
+
const chunks = [];
|
|
2415
|
+
let total = 0;
|
|
2416
|
+
let done = false;
|
|
2417
|
+
const finish = () => {
|
|
2418
|
+
if (done) return;
|
|
2419
|
+
done = true;
|
|
2420
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
2421
|
+
};
|
|
2422
|
+
process.stdin.on("data", (c) => {
|
|
2423
|
+
total += c.length;
|
|
2424
|
+
if (total > maxBytes) {
|
|
2425
|
+
process.stdin.destroy();
|
|
2426
|
+
finish();
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
chunks.push(c);
|
|
2430
|
+
});
|
|
2431
|
+
process.stdin.on("end", finish);
|
|
2432
|
+
process.stdin.on("error", finish);
|
|
2433
|
+
setTimeout(finish, 2e3);
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
function registerObserve(program2) {
|
|
2437
|
+
program2.command("observe").description(
|
|
2438
|
+
"Passive-capture endpoint for Claude Code PostToolUse hooks.\n\n Reads a JSON payload on stdin and appends an observation record to\n .ai/.cache/observations.jsonl. Always exits 0; never blocks the agent.\n Wired up automatically by `haive install-hooks claude`."
|
|
2439
|
+
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2440
|
+
try {
|
|
2441
|
+
const raw = await readStdin(MAX_STDIN_BYTES);
|
|
2442
|
+
if (!raw.trim()) return;
|
|
2443
|
+
let payload;
|
|
2444
|
+
try {
|
|
2445
|
+
payload = JSON.parse(raw);
|
|
2446
|
+
} catch {
|
|
2447
|
+
return;
|
|
1949
2448
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2449
|
+
const root = (() => {
|
|
2450
|
+
try {
|
|
2451
|
+
return findProjectRoot7(opts.dir ?? payload.cwd);
|
|
2452
|
+
} catch {
|
|
2453
|
+
return null;
|
|
2454
|
+
}
|
|
2455
|
+
})();
|
|
2456
|
+
if (!root) return;
|
|
2457
|
+
const paths = resolveHaivePaths5(root);
|
|
2458
|
+
if (!existsSync9(paths.haiveDir)) return;
|
|
2459
|
+
const observation = {
|
|
2460
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2461
|
+
session_id: payload.session_id,
|
|
2462
|
+
cwd: payload.cwd,
|
|
2463
|
+
tool: payload.tool_name ?? "?",
|
|
2464
|
+
summary: buildSummary(payload),
|
|
2465
|
+
files: extractFiles(payload)
|
|
2466
|
+
};
|
|
2467
|
+
const cacheDir = path10.join(paths.haiveDir, ".cache");
|
|
2468
|
+
await mkdir6(cacheDir, { recursive: true });
|
|
2469
|
+
await appendFile(
|
|
2470
|
+
path10.join(cacheDir, "observations.jsonl"),
|
|
2471
|
+
JSON.stringify(observation) + "\n",
|
|
2472
|
+
"utf8"
|
|
2473
|
+
);
|
|
2474
|
+
} catch {
|
|
2475
|
+
}
|
|
1958
2476
|
});
|
|
1959
2477
|
}
|
|
1960
2478
|
|
|
1961
2479
|
// src/commands/mcp.ts
|
|
1962
2480
|
import { spawn } from "child_process";
|
|
1963
|
-
import { existsSync as
|
|
2481
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1964
2482
|
import { createRequire } from "module";
|
|
1965
|
-
import
|
|
2483
|
+
import path11 from "path";
|
|
1966
2484
|
import { fileURLToPath } from "url";
|
|
1967
2485
|
import "commander";
|
|
1968
|
-
import { findProjectRoot as
|
|
2486
|
+
import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
|
|
1969
2487
|
var require2 = createRequire(import.meta.url);
|
|
1970
2488
|
function registerMcp(program2) {
|
|
1971
2489
|
program2.command("mcp").description(
|
|
@@ -1982,7 +2500,7 @@ function registerMcp(program2) {
|
|
|
1982
2500
|
VS Code: code --add-mcp '{"name":"haive","command":"haive-mcp",...}'
|
|
1983
2501
|
`
|
|
1984
2502
|
).option("-d, --dir <dir>", "project root (defaults to nearest .ai/ or .git/)").action((opts) => {
|
|
1985
|
-
const root =
|
|
2503
|
+
const root = findProjectRoot8(opts.dir);
|
|
1986
2504
|
const bin = locateMcpBin();
|
|
1987
2505
|
if (!bin) {
|
|
1988
2506
|
ui.error(
|
|
@@ -2000,36 +2518,36 @@ function registerMcp(program2) {
|
|
|
2000
2518
|
function locateMcpBin() {
|
|
2001
2519
|
try {
|
|
2002
2520
|
const pkgPath = require2.resolve("@hiveai/mcp/package.json");
|
|
2003
|
-
const pkgDir =
|
|
2004
|
-
const candidate =
|
|
2005
|
-
if (
|
|
2521
|
+
const pkgDir = path11.dirname(pkgPath);
|
|
2522
|
+
const candidate = path11.join(pkgDir, "dist", "index.js");
|
|
2523
|
+
if (existsSync10(candidate)) return candidate;
|
|
2006
2524
|
} catch {
|
|
2007
2525
|
}
|
|
2008
|
-
const here =
|
|
2009
|
-
const sibling =
|
|
2010
|
-
if (
|
|
2526
|
+
const here = path11.dirname(fileURLToPath(import.meta.url));
|
|
2527
|
+
const sibling = path11.resolve(here, "..", "..", "..", "mcp", "dist", "index.js");
|
|
2528
|
+
if (existsSync10(sibling)) return sibling;
|
|
2011
2529
|
return null;
|
|
2012
2530
|
}
|
|
2013
2531
|
|
|
2014
2532
|
// src/commands/sync.ts
|
|
2015
2533
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2016
|
-
import { readFile as
|
|
2017
|
-
import { existsSync as
|
|
2018
|
-
import
|
|
2534
|
+
import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir7 } from "fs/promises";
|
|
2535
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2536
|
+
import path12 from "path";
|
|
2019
2537
|
import "commander";
|
|
2020
2538
|
import {
|
|
2021
2539
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
2022
2540
|
buildFrontmatter as buildFrontmatter2,
|
|
2023
|
-
findProjectRoot as
|
|
2541
|
+
findProjectRoot as findProjectRoot9,
|
|
2024
2542
|
getUsage,
|
|
2025
2543
|
isAutoPromoteEligible,
|
|
2026
2544
|
isDecaying,
|
|
2027
2545
|
loadCodeMap as loadCodeMap2,
|
|
2028
2546
|
loadConfig,
|
|
2029
2547
|
loadMemoriesFromDir as loadMemoriesFromDir2,
|
|
2030
|
-
loadUsageIndex,
|
|
2548
|
+
loadUsageIndex as loadUsageIndex2,
|
|
2031
2549
|
pullCrossRepoSources,
|
|
2032
|
-
resolveHaivePaths as
|
|
2550
|
+
resolveHaivePaths as resolveHaivePaths6,
|
|
2033
2551
|
resolveManifestFiles,
|
|
2034
2552
|
serializeMemory as serializeMemory2,
|
|
2035
2553
|
trackDependencies,
|
|
@@ -2048,9 +2566,9 @@ function registerSync(program2) {
|
|
|
2048
2566
|
"--inject-bridge",
|
|
2049
2567
|
"inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
2050
2568
|
).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) => {
|
|
2051
|
-
const root =
|
|
2052
|
-
const paths =
|
|
2053
|
-
if (!
|
|
2569
|
+
const root = findProjectRoot9(opts.dir);
|
|
2570
|
+
const paths = resolveHaivePaths6(root);
|
|
2571
|
+
if (!existsSync11(paths.memoriesDir)) {
|
|
2054
2572
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
2055
2573
|
process.exitCode = 1;
|
|
2056
2574
|
return;
|
|
@@ -2070,7 +2588,7 @@ function registerSync(program2) {
|
|
|
2070
2588
|
for (const { memory: memory2, filePath } of memories) {
|
|
2071
2589
|
if (memory2.frontmatter.type === "session_recap") {
|
|
2072
2590
|
if (memory2.frontmatter.status === "stale") {
|
|
2073
|
-
await
|
|
2591
|
+
await writeFile6(
|
|
2074
2592
|
filePath,
|
|
2075
2593
|
serializeMemory2({
|
|
2076
2594
|
frontmatter: {
|
|
@@ -2093,7 +2611,7 @@ function registerSync(program2) {
|
|
|
2093
2611
|
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2094
2612
|
if (result.stale) {
|
|
2095
2613
|
if (memory2.frontmatter.status !== "stale") {
|
|
2096
|
-
await
|
|
2614
|
+
await writeFile6(
|
|
2097
2615
|
filePath,
|
|
2098
2616
|
serializeMemory2({
|
|
2099
2617
|
frontmatter: {
|
|
@@ -2109,7 +2627,7 @@ function registerSync(program2) {
|
|
|
2109
2627
|
staleMarked++;
|
|
2110
2628
|
}
|
|
2111
2629
|
} else if (memory2.frontmatter.status === "stale") {
|
|
2112
|
-
await
|
|
2630
|
+
await writeFile6(
|
|
2113
2631
|
filePath,
|
|
2114
2632
|
serializeMemory2({
|
|
2115
2633
|
frontmatter: {
|
|
@@ -2128,7 +2646,7 @@ function registerSync(program2) {
|
|
|
2128
2646
|
}
|
|
2129
2647
|
if (opts.promote !== false) {
|
|
2130
2648
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
2131
|
-
const usage = await
|
|
2649
|
+
const usage = await loadUsageIndex2(paths);
|
|
2132
2650
|
const nowMs = Date.now();
|
|
2133
2651
|
for (const { memory: memory2, filePath } of memories) {
|
|
2134
2652
|
const fm = memory2.frontmatter;
|
|
@@ -2137,7 +2655,7 @@ function registerSync(program2) {
|
|
|
2137
2655
|
minReads: autoPromoteMinReads,
|
|
2138
2656
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
|
|
2139
2657
|
})) {
|
|
2140
|
-
await
|
|
2658
|
+
await writeFile6(
|
|
2141
2659
|
filePath,
|
|
2142
2660
|
serializeMemory2({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
2143
2661
|
"utf8"
|
|
@@ -2148,7 +2666,7 @@ function registerSync(program2) {
|
|
|
2148
2666
|
if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
|
|
2149
2667
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
2150
2668
|
if (ageHours >= autoApproveDelayHours) {
|
|
2151
|
-
await
|
|
2669
|
+
await writeFile6(
|
|
2152
2670
|
filePath,
|
|
2153
2671
|
serializeMemory2({
|
|
2154
2672
|
frontmatter: {
|
|
@@ -2182,7 +2700,7 @@ function registerSync(program2) {
|
|
|
2182
2700
|
);
|
|
2183
2701
|
}
|
|
2184
2702
|
if (opts.injectBridge) {
|
|
2185
|
-
const bridgeFile = opts.bridgeFile ?
|
|
2703
|
+
const bridgeFile = opts.bridgeFile ? path12.resolve(opts.bridgeFile) : path12.join(root, "CLAUDE.md");
|
|
2186
2704
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
2187
2705
|
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
2188
2706
|
}
|
|
@@ -2202,7 +2720,7 @@ function registerSync(program2) {
|
|
|
2202
2720
|
}
|
|
2203
2721
|
if (!opts.quiet) {
|
|
2204
2722
|
const allForDecay = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
2205
|
-
const usageForDecay = await
|
|
2723
|
+
const usageForDecay = await loadUsageIndex2(paths);
|
|
2206
2724
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
2207
2725
|
const fm = memory2.frontmatter;
|
|
2208
2726
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -2288,10 +2806,10 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
2288
2806
|
paths: [result.file],
|
|
2289
2807
|
topic: `dep-bump-${slugParts}`
|
|
2290
2808
|
});
|
|
2291
|
-
const teamDir =
|
|
2292
|
-
await
|
|
2293
|
-
await
|
|
2294
|
-
|
|
2809
|
+
const teamDir = path12.join(paths.memoriesDir, "team");
|
|
2810
|
+
await mkdir7(teamDir, { recursive: true });
|
|
2811
|
+
await writeFile6(
|
|
2812
|
+
path12.join(teamDir, `${fm.id}.md`),
|
|
2295
2813
|
serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
2296
2814
|
"utf8"
|
|
2297
2815
|
);
|
|
@@ -2355,10 +2873,10 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
2355
2873
|
paths: [diff.file],
|
|
2356
2874
|
topic: `contract-breaking-${diff.contract}`
|
|
2357
2875
|
});
|
|
2358
|
-
const teamDir =
|
|
2359
|
-
await
|
|
2360
|
-
await
|
|
2361
|
-
|
|
2876
|
+
const teamDir = path12.join(paths.memoriesDir, "team");
|
|
2877
|
+
await mkdir7(teamDir, { recursive: true });
|
|
2878
|
+
await writeFile6(
|
|
2879
|
+
path12.join(teamDir, `${fm.id}.md`),
|
|
2362
2880
|
serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
2363
2881
|
"utf8"
|
|
2364
2882
|
);
|
|
@@ -2419,7 +2937,7 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
2419
2937
|
});
|
|
2420
2938
|
}
|
|
2421
2939
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
2422
|
-
if (!
|
|
2940
|
+
if (!existsSync11(memoriesDir)) return;
|
|
2423
2941
|
const all = await loadMemoriesFromDir2(memoriesDir);
|
|
2424
2942
|
const top = all.filter(({ memory: memory2 }) => {
|
|
2425
2943
|
const s = memory2.frontmatter.status;
|
|
@@ -2444,17 +2962,17 @@ ${m.memory.body.trim()}`;
|
|
|
2444
2962
|
` + block + `
|
|
2445
2963
|
|
|
2446
2964
|
${BRIDGE_END}`;
|
|
2447
|
-
const fileExists =
|
|
2448
|
-
let existing = fileExists ? await
|
|
2965
|
+
const fileExists = existsSync11(bridgeFile);
|
|
2966
|
+
let existing = fileExists ? await readFile7(bridgeFile, "utf8") : "";
|
|
2449
2967
|
existing = existing.replace(/\r\n/g, "\n");
|
|
2450
2968
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
2451
2969
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
2452
2970
|
if (startIdx !== -1 && endIdx === -1) {
|
|
2453
|
-
ui.warn(`${
|
|
2971
|
+
ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
2454
2972
|
return;
|
|
2455
2973
|
}
|
|
2456
2974
|
if (startIdx === -1 && endIdx !== -1) {
|
|
2457
|
-
ui.warn(`${
|
|
2975
|
+
ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
2458
2976
|
return;
|
|
2459
2977
|
}
|
|
2460
2978
|
let updated;
|
|
@@ -2462,14 +2980,14 @@ ${BRIDGE_END}`;
|
|
|
2462
2980
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
2463
2981
|
} else {
|
|
2464
2982
|
if (!fileExists && !quiet) {
|
|
2465
|
-
ui.info(`Creating ${
|
|
2983
|
+
ui.info(`Creating ${path12.relative(root, bridgeFile)} with haive memory block.`);
|
|
2466
2984
|
}
|
|
2467
2985
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
2468
2986
|
}
|
|
2469
|
-
await
|
|
2987
|
+
await writeFile6(bridgeFile, updated, "utf8");
|
|
2470
2988
|
if (!quiet) {
|
|
2471
2989
|
console.log(
|
|
2472
|
-
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${
|
|
2990
|
+
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path12.relative(root, bridgeFile)}`)
|
|
2473
2991
|
);
|
|
2474
2992
|
}
|
|
2475
2993
|
}
|
|
@@ -2494,17 +3012,17 @@ function collectSinceChanges(root, ref) {
|
|
|
2494
3012
|
|
|
2495
3013
|
// src/commands/memory-add.ts
|
|
2496
3014
|
import { createHash } from "crypto";
|
|
2497
|
-
import { mkdir as
|
|
2498
|
-
import { existsSync as
|
|
2499
|
-
import
|
|
3015
|
+
import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
|
|
3016
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3017
|
+
import path13 from "path";
|
|
2500
3018
|
import "commander";
|
|
2501
3019
|
import {
|
|
2502
3020
|
buildFrontmatter as buildFrontmatter3,
|
|
2503
|
-
findProjectRoot as
|
|
3021
|
+
findProjectRoot as findProjectRoot10,
|
|
2504
3022
|
inferModulesFromPaths,
|
|
2505
3023
|
loadMemoriesFromDir as loadMemoriesFromDir3,
|
|
2506
3024
|
memoryFilePath as memoryFilePath2,
|
|
2507
|
-
resolveHaivePaths as
|
|
3025
|
+
resolveHaivePaths as resolveHaivePaths7,
|
|
2508
3026
|
serializeMemory as serializeMemory3
|
|
2509
3027
|
} from "@hiveai/core";
|
|
2510
3028
|
function registerMemoryAdd(memory2) {
|
|
@@ -2532,9 +3050,9 @@ function registerMemoryAdd(memory2) {
|
|
|
2532
3050
|
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
2533
3051
|
`
|
|
2534
3052
|
).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) => {
|
|
2535
|
-
const root =
|
|
2536
|
-
const paths =
|
|
2537
|
-
if (!
|
|
3053
|
+
const root = findProjectRoot10(opts.dir);
|
|
3054
|
+
const paths = resolveHaivePaths7(root);
|
|
3055
|
+
if (!existsSync12(paths.haiveDir)) {
|
|
2538
3056
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
2539
3057
|
process.exitCode = 1;
|
|
2540
3058
|
return;
|
|
@@ -2545,7 +3063,7 @@ function registerMemoryAdd(memory2) {
|
|
|
2545
3063
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
|
|
2546
3064
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
2547
3065
|
if (anchorPaths.length > 0) {
|
|
2548
|
-
const missing = anchorPaths.filter((p) => !
|
|
3066
|
+
const missing = anchorPaths.filter((p) => !existsSync12(path13.resolve(root, p)));
|
|
2549
3067
|
if (missing.length > 0) {
|
|
2550
3068
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
2551
3069
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -2557,12 +3075,12 @@ function registerMemoryAdd(memory2) {
|
|
|
2557
3075
|
const title = opts.title ?? opts.slug;
|
|
2558
3076
|
let body;
|
|
2559
3077
|
if (opts.bodyFile !== void 0) {
|
|
2560
|
-
if (!
|
|
3078
|
+
if (!existsSync12(opts.bodyFile)) {
|
|
2561
3079
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
2562
3080
|
process.exitCode = 1;
|
|
2563
3081
|
return;
|
|
2564
3082
|
}
|
|
2565
|
-
const fileContent = await
|
|
3083
|
+
const fileContent = await readFile8(opts.bodyFile, "utf8");
|
|
2566
3084
|
body = opts.title ? `# ${opts.title}
|
|
2567
3085
|
|
|
2568
3086
|
${fileContent.trim()}
|
|
@@ -2578,7 +3096,7 @@ TODO \u2014 write the memory body.
|
|
|
2578
3096
|
`;
|
|
2579
3097
|
}
|
|
2580
3098
|
const scope = opts.scope ?? "personal";
|
|
2581
|
-
if (
|
|
3099
|
+
if (existsSync12(paths.memoriesDir)) {
|
|
2582
3100
|
const incomingHash = createHash("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
2583
3101
|
const allForHash = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
2584
3102
|
const hashDup = allForHash.find(
|
|
@@ -2591,7 +3109,7 @@ TODO \u2014 write the memory body.
|
|
|
2591
3109
|
return;
|
|
2592
3110
|
}
|
|
2593
3111
|
}
|
|
2594
|
-
if (opts.topic &&
|
|
3112
|
+
if (opts.topic && existsSync12(paths.memoriesDir)) {
|
|
2595
3113
|
const existing = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
2596
3114
|
const topicMatch = existing.find(
|
|
2597
3115
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
@@ -2609,8 +3127,8 @@ TODO \u2014 write the memory body.
|
|
|
2609
3127
|
symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
|
|
2610
3128
|
}
|
|
2611
3129
|
};
|
|
2612
|
-
await
|
|
2613
|
-
ui.success(`Updated (topic upsert) ${
|
|
3130
|
+
await writeFile7(topicMatch.filePath, serializeMemory3({ frontmatter: newFrontmatter, body }), "utf8");
|
|
3131
|
+
ui.success(`Updated (topic upsert) ${path13.relative(root, topicMatch.filePath)}`);
|
|
2614
3132
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
2615
3133
|
return;
|
|
2616
3134
|
}
|
|
@@ -2629,13 +3147,13 @@ TODO \u2014 write the memory body.
|
|
|
2629
3147
|
topic: opts.topic
|
|
2630
3148
|
});
|
|
2631
3149
|
const file = memoryFilePath2(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
2632
|
-
await
|
|
2633
|
-
if (
|
|
3150
|
+
await mkdir8(path13.dirname(file), { recursive: true });
|
|
3151
|
+
if (existsSync12(file)) {
|
|
2634
3152
|
ui.error(`Memory already exists at ${file}`);
|
|
2635
3153
|
process.exitCode = 1;
|
|
2636
3154
|
return;
|
|
2637
3155
|
}
|
|
2638
|
-
if (
|
|
3156
|
+
if (existsSync12(paths.memoriesDir)) {
|
|
2639
3157
|
const existing = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
2640
3158
|
const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
2641
3159
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
@@ -2647,8 +3165,8 @@ TODO \u2014 write the memory body.
|
|
|
2647
3165
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
2648
3166
|
}
|
|
2649
3167
|
}
|
|
2650
|
-
await
|
|
2651
|
-
ui.success(`Created ${
|
|
3168
|
+
await writeFile7(file, serializeMemory3({ frontmatter, body }), "utf8");
|
|
3169
|
+
ui.success(`Created ${path13.relative(root, file)}`);
|
|
2652
3170
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
2653
3171
|
if (inferredTags.length > 0) {
|
|
2654
3172
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
@@ -2678,10 +3196,10 @@ function parseCsv2(value) {
|
|
|
2678
3196
|
}
|
|
2679
3197
|
|
|
2680
3198
|
// src/commands/memory-list.ts
|
|
2681
|
-
import { existsSync as
|
|
2682
|
-
import
|
|
3199
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3200
|
+
import path14 from "path";
|
|
2683
3201
|
import "commander";
|
|
2684
|
-
import { findProjectRoot as
|
|
3202
|
+
import { findProjectRoot as findProjectRoot11, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
|
|
2685
3203
|
|
|
2686
3204
|
// src/utils/fs.ts
|
|
2687
3205
|
import {
|
|
@@ -2693,9 +3211,9 @@ import {
|
|
|
2693
3211
|
// src/commands/memory-list.ts
|
|
2694
3212
|
function registerMemoryList(memory2) {
|
|
2695
3213
|
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) => {
|
|
2696
|
-
const root =
|
|
2697
|
-
const paths =
|
|
2698
|
-
if (!
|
|
3214
|
+
const root = findProjectRoot11(opts.dir);
|
|
3215
|
+
const paths = resolveHaivePaths8(root);
|
|
3216
|
+
if (!existsSync13(paths.memoriesDir)) {
|
|
2699
3217
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
2700
3218
|
process.exitCode = 1;
|
|
2701
3219
|
return;
|
|
@@ -2727,7 +3245,7 @@ function registerMemoryList(memory2) {
|
|
|
2727
3245
|
console.log(
|
|
2728
3246
|
`${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
|
|
2729
3247
|
);
|
|
2730
|
-
console.log(` ${ui.dim(
|
|
3248
|
+
console.log(` ${ui.dim(path14.relative(root, filePath))}`);
|
|
2731
3249
|
}
|
|
2732
3250
|
console.log(ui.dim(`
|
|
2733
3251
|
${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
|
|
@@ -2762,21 +3280,21 @@ function matchesFilters(loaded, opts) {
|
|
|
2762
3280
|
}
|
|
2763
3281
|
|
|
2764
3282
|
// src/commands/memory-promote.ts
|
|
2765
|
-
import { mkdir as
|
|
2766
|
-
import { existsSync as
|
|
2767
|
-
import
|
|
3283
|
+
import { mkdir as mkdir9, unlink, writeFile as writeFile8 } from "fs/promises";
|
|
3284
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3285
|
+
import path15 from "path";
|
|
2768
3286
|
import "commander";
|
|
2769
3287
|
import {
|
|
2770
|
-
findProjectRoot as
|
|
3288
|
+
findProjectRoot as findProjectRoot12,
|
|
2771
3289
|
memoryFilePath as memoryFilePath3,
|
|
2772
|
-
resolveHaivePaths as
|
|
3290
|
+
resolveHaivePaths as resolveHaivePaths9,
|
|
2773
3291
|
serializeMemory as serializeMemory4
|
|
2774
3292
|
} from "@hiveai/core";
|
|
2775
3293
|
function registerMemoryPromote(memory2) {
|
|
2776
3294
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
2777
|
-
const root =
|
|
2778
|
-
const paths =
|
|
2779
|
-
if (!
|
|
3295
|
+
const root = findProjectRoot12(opts.dir);
|
|
3296
|
+
const paths = resolveHaivePaths9(root);
|
|
3297
|
+
if (!existsSync14(paths.memoriesDir)) {
|
|
2780
3298
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
2781
3299
|
process.exitCode = 1;
|
|
2782
3300
|
return;
|
|
@@ -2811,30 +3329,30 @@ function registerMemoryPromote(memory2) {
|
|
|
2811
3329
|
body: found.memory.body
|
|
2812
3330
|
};
|
|
2813
3331
|
const newPath = memoryFilePath3(paths, "team", updated.frontmatter.id);
|
|
2814
|
-
await
|
|
2815
|
-
await
|
|
3332
|
+
await mkdir9(path15.dirname(newPath), { recursive: true });
|
|
3333
|
+
await writeFile8(newPath, serializeMemory4(updated), "utf8");
|
|
2816
3334
|
await unlink(found.filePath);
|
|
2817
3335
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
2818
|
-
ui.info(`Now at ${
|
|
3336
|
+
ui.info(`Now at ${path15.relative(root, newPath)}`);
|
|
2819
3337
|
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
2820
3338
|
});
|
|
2821
3339
|
}
|
|
2822
3340
|
|
|
2823
3341
|
// src/commands/memory-approve.ts
|
|
2824
|
-
import { existsSync as
|
|
2825
|
-
import { writeFile as
|
|
2826
|
-
import
|
|
3342
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3343
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
3344
|
+
import path16 from "path";
|
|
2827
3345
|
import "commander";
|
|
2828
3346
|
import {
|
|
2829
|
-
findProjectRoot as
|
|
2830
|
-
resolveHaivePaths as
|
|
3347
|
+
findProjectRoot as findProjectRoot13,
|
|
3348
|
+
resolveHaivePaths as resolveHaivePaths10,
|
|
2831
3349
|
serializeMemory as serializeMemory5
|
|
2832
3350
|
} from "@hiveai/core";
|
|
2833
3351
|
function registerMemoryApprove(memory2) {
|
|
2834
3352
|
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) => {
|
|
2835
|
-
const root =
|
|
2836
|
-
const paths =
|
|
2837
|
-
if (!
|
|
3353
|
+
const root = findProjectRoot13(opts.dir);
|
|
3354
|
+
const paths = resolveHaivePaths10(root);
|
|
3355
|
+
if (!existsSync15(paths.memoriesDir)) {
|
|
2838
3356
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2839
3357
|
process.exitCode = 1;
|
|
2840
3358
|
return;
|
|
@@ -2856,7 +3374,7 @@ function registerMemoryApprove(memory2) {
|
|
|
2856
3374
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
2857
3375
|
body: found2.memory.body
|
|
2858
3376
|
};
|
|
2859
|
-
await
|
|
3377
|
+
await writeFile9(found2.filePath, serializeMemory5(next2), "utf8");
|
|
2860
3378
|
count++;
|
|
2861
3379
|
}
|
|
2862
3380
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -2885,27 +3403,27 @@ function registerMemoryApprove(memory2) {
|
|
|
2885
3403
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
2886
3404
|
body: found.memory.body
|
|
2887
3405
|
};
|
|
2888
|
-
await
|
|
3406
|
+
await writeFile9(found.filePath, serializeMemory5(next), "utf8");
|
|
2889
3407
|
ui.success(`Approved ${id} (status=validated)`);
|
|
2890
|
-
ui.info(
|
|
3408
|
+
ui.info(path16.relative(root, found.filePath));
|
|
2891
3409
|
});
|
|
2892
3410
|
}
|
|
2893
3411
|
|
|
2894
3412
|
// src/commands/memory-update.ts
|
|
2895
|
-
import { writeFile as
|
|
2896
|
-
import { existsSync as
|
|
2897
|
-
import
|
|
3413
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
3414
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3415
|
+
import path17 from "path";
|
|
2898
3416
|
import "commander";
|
|
2899
3417
|
import {
|
|
2900
|
-
findProjectRoot as
|
|
2901
|
-
resolveHaivePaths as
|
|
3418
|
+
findProjectRoot as findProjectRoot14,
|
|
3419
|
+
resolveHaivePaths as resolveHaivePaths11,
|
|
2902
3420
|
serializeMemory as serializeMemory6
|
|
2903
3421
|
} from "@hiveai/core";
|
|
2904
3422
|
function registerMemoryUpdate(memory2) {
|
|
2905
3423
|
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) => {
|
|
2906
|
-
const root =
|
|
2907
|
-
const paths =
|
|
2908
|
-
if (!
|
|
3424
|
+
const root = findProjectRoot14(opts.dir);
|
|
3425
|
+
const paths = resolveHaivePaths11(root);
|
|
3426
|
+
if (!existsSync16(paths.memoriesDir)) {
|
|
2909
3427
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
2910
3428
|
process.exitCode = 1;
|
|
2911
3429
|
return;
|
|
@@ -2952,12 +3470,12 @@ function registerMemoryUpdate(memory2) {
|
|
|
2952
3470
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
2953
3471
|
return;
|
|
2954
3472
|
}
|
|
2955
|
-
await
|
|
3473
|
+
await writeFile10(
|
|
2956
3474
|
loaded.filePath,
|
|
2957
3475
|
serializeMemory6({ frontmatter: newFrontmatter, body: newBody }),
|
|
2958
3476
|
"utf8"
|
|
2959
3477
|
);
|
|
2960
|
-
ui.success(`Updated ${
|
|
3478
|
+
ui.success(`Updated ${path17.relative(root, loaded.filePath)}`);
|
|
2961
3479
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
2962
3480
|
});
|
|
2963
3481
|
}
|
|
@@ -2976,17 +3494,17 @@ function parseCsv3(value) {
|
|
|
2976
3494
|
}
|
|
2977
3495
|
|
|
2978
3496
|
// src/commands/memory-auto-promote.ts
|
|
2979
|
-
import { writeFile as
|
|
2980
|
-
import { existsSync as
|
|
2981
|
-
import
|
|
3497
|
+
import { writeFile as writeFile11 } from "fs/promises";
|
|
3498
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3499
|
+
import path18 from "path";
|
|
2982
3500
|
import "commander";
|
|
2983
3501
|
import {
|
|
2984
3502
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
2985
|
-
findProjectRoot as
|
|
3503
|
+
findProjectRoot as findProjectRoot15,
|
|
2986
3504
|
getUsage as getUsage2,
|
|
2987
3505
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
2988
|
-
loadUsageIndex as
|
|
2989
|
-
resolveHaivePaths as
|
|
3506
|
+
loadUsageIndex as loadUsageIndex3,
|
|
3507
|
+
resolveHaivePaths as resolveHaivePaths12,
|
|
2990
3508
|
serializeMemory as serializeMemory7
|
|
2991
3509
|
} from "@hiveai/core";
|
|
2992
3510
|
function registerMemoryAutoPromote(memory2) {
|
|
@@ -2995,9 +3513,9 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
2995
3513
|
"memories with more rejections than this are skipped",
|
|
2996
3514
|
String(DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
|
|
2997
3515
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2998
|
-
const root =
|
|
2999
|
-
const paths =
|
|
3000
|
-
if (!
|
|
3516
|
+
const root = findProjectRoot15(opts.dir);
|
|
3517
|
+
const paths = resolveHaivePaths12(root);
|
|
3518
|
+
if (!existsSync17(paths.memoriesDir)) {
|
|
3001
3519
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3002
3520
|
process.exitCode = 1;
|
|
3003
3521
|
return;
|
|
@@ -3007,7 +3525,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
3007
3525
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
|
|
3008
3526
|
};
|
|
3009
3527
|
const memories = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3010
|
-
const usage = await
|
|
3528
|
+
const usage = await loadUsageIndex3(paths);
|
|
3011
3529
|
const eligible = memories.filter(
|
|
3012
3530
|
({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
|
|
3013
3531
|
);
|
|
@@ -3023,13 +3541,13 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
3023
3541
|
console.log(
|
|
3024
3542
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
3025
3543
|
);
|
|
3026
|
-
console.log(` ${ui.dim(
|
|
3544
|
+
console.log(` ${ui.dim(path18.relative(root, filePath))}`);
|
|
3027
3545
|
if (opts.apply) {
|
|
3028
3546
|
const next = {
|
|
3029
3547
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
3030
3548
|
body: mem.body
|
|
3031
3549
|
};
|
|
3032
|
-
await
|
|
3550
|
+
await writeFile11(filePath, serializeMemory7(next), "utf8");
|
|
3033
3551
|
written++;
|
|
3034
3552
|
}
|
|
3035
3553
|
}
|
|
@@ -3040,20 +3558,20 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
3040
3558
|
|
|
3041
3559
|
// src/commands/memory-edit.ts
|
|
3042
3560
|
import { spawn as spawn2 } from "child_process";
|
|
3043
|
-
import { existsSync as
|
|
3044
|
-
import { readFile as
|
|
3045
|
-
import
|
|
3561
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3562
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3563
|
+
import path19 from "path";
|
|
3046
3564
|
import "commander";
|
|
3047
3565
|
import {
|
|
3048
|
-
findProjectRoot as
|
|
3566
|
+
findProjectRoot as findProjectRoot16,
|
|
3049
3567
|
parseMemory,
|
|
3050
|
-
resolveHaivePaths as
|
|
3568
|
+
resolveHaivePaths as resolveHaivePaths13
|
|
3051
3569
|
} from "@hiveai/core";
|
|
3052
3570
|
function registerMemoryEdit(memory2) {
|
|
3053
3571
|
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) => {
|
|
3054
|
-
const root =
|
|
3055
|
-
const paths =
|
|
3056
|
-
if (!
|
|
3572
|
+
const root = findProjectRoot16(opts.dir);
|
|
3573
|
+
const paths = resolveHaivePaths13(root);
|
|
3574
|
+
if (!existsSync18(paths.memoriesDir)) {
|
|
3057
3575
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3058
3576
|
process.exitCode = 1;
|
|
3059
3577
|
return;
|
|
@@ -3066,13 +3584,13 @@ function registerMemoryEdit(memory2) {
|
|
|
3066
3584
|
return;
|
|
3067
3585
|
}
|
|
3068
3586
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
3069
|
-
ui.info(`Opening ${
|
|
3587
|
+
ui.info(`Opening ${path19.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
3070
3588
|
const code = await runEditor(editor, found.filePath);
|
|
3071
3589
|
if (code !== 0) {
|
|
3072
3590
|
ui.warn(`Editor exited with status ${code}.`);
|
|
3073
3591
|
}
|
|
3074
3592
|
try {
|
|
3075
|
-
const fresh = await
|
|
3593
|
+
const fresh = await readFile9(found.filePath, "utf8");
|
|
3076
3594
|
parseMemory(fresh);
|
|
3077
3595
|
ui.success("Memory still parses cleanly.");
|
|
3078
3596
|
} catch (err) {
|
|
@@ -3093,29 +3611,29 @@ function runEditor(editor, file) {
|
|
|
3093
3611
|
}
|
|
3094
3612
|
|
|
3095
3613
|
// src/commands/memory-for-files.ts
|
|
3096
|
-
import { existsSync as
|
|
3097
|
-
import
|
|
3098
|
-
import "commander";
|
|
3614
|
+
import { existsSync as existsSync19 } from "fs";
|
|
3615
|
+
import path20 from "path";
|
|
3616
|
+
import "commander";
|
|
3099
3617
|
import {
|
|
3100
3618
|
deriveConfidence,
|
|
3101
|
-
findProjectRoot as
|
|
3619
|
+
findProjectRoot as findProjectRoot17,
|
|
3102
3620
|
getUsage as getUsage3,
|
|
3103
3621
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
3104
|
-
loadUsageIndex as
|
|
3622
|
+
loadUsageIndex as loadUsageIndex4,
|
|
3105
3623
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
3106
|
-
resolveHaivePaths as
|
|
3624
|
+
resolveHaivePaths as resolveHaivePaths14
|
|
3107
3625
|
} from "@hiveai/core";
|
|
3108
3626
|
function registerMemoryForFiles(memory2) {
|
|
3109
3627
|
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) => {
|
|
3110
|
-
const root =
|
|
3111
|
-
const paths =
|
|
3112
|
-
if (!
|
|
3628
|
+
const root = findProjectRoot17(opts.dir);
|
|
3629
|
+
const paths = resolveHaivePaths14(root);
|
|
3630
|
+
if (!existsSync19(paths.memoriesDir)) {
|
|
3113
3631
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3114
3632
|
process.exitCode = 1;
|
|
3115
3633
|
return;
|
|
3116
3634
|
}
|
|
3117
3635
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3118
|
-
const usage = await
|
|
3636
|
+
const usage = await loadUsageIndex4(paths);
|
|
3119
3637
|
const inferred = inferModulesFromPaths2(files);
|
|
3120
3638
|
const byAnchor = [];
|
|
3121
3639
|
const byModule = [];
|
|
@@ -3216,32 +3734,32 @@ function printGroup(root, label, loaded, usage) {
|
|
|
3216
3734
|
const u = getUsage3(usage, fm.id);
|
|
3217
3735
|
const conf = deriveConfidence(fm, u);
|
|
3218
3736
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
3219
|
-
console.log(` ${ui.dim(
|
|
3737
|
+
console.log(` ${ui.dim(path20.relative(root, filePath))}`);
|
|
3220
3738
|
}
|
|
3221
3739
|
}
|
|
3222
3740
|
|
|
3223
3741
|
// src/commands/memory-hot.ts
|
|
3224
|
-
import { existsSync as
|
|
3225
|
-
import
|
|
3742
|
+
import { existsSync as existsSync20 } from "fs";
|
|
3743
|
+
import path21 from "path";
|
|
3226
3744
|
import "commander";
|
|
3227
3745
|
import {
|
|
3228
|
-
findProjectRoot as
|
|
3746
|
+
findProjectRoot as findProjectRoot18,
|
|
3229
3747
|
getUsage as getUsage4,
|
|
3230
|
-
loadUsageIndex as
|
|
3231
|
-
resolveHaivePaths as
|
|
3748
|
+
loadUsageIndex as loadUsageIndex5,
|
|
3749
|
+
resolveHaivePaths as resolveHaivePaths15
|
|
3232
3750
|
} from "@hiveai/core";
|
|
3233
3751
|
function registerMemoryHot(memory2) {
|
|
3234
3752
|
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) => {
|
|
3235
|
-
const root =
|
|
3236
|
-
const paths =
|
|
3237
|
-
if (!
|
|
3753
|
+
const root = findProjectRoot18(opts.dir);
|
|
3754
|
+
const paths = resolveHaivePaths15(root);
|
|
3755
|
+
if (!existsSync20(paths.memoriesDir)) {
|
|
3238
3756
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3239
3757
|
process.exitCode = 1;
|
|
3240
3758
|
return;
|
|
3241
3759
|
}
|
|
3242
3760
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
3243
3761
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3244
|
-
const usage = await
|
|
3762
|
+
const usage = await loadUsageIndex5(paths);
|
|
3245
3763
|
const candidates = all.filter(({ memory: mem }) => {
|
|
3246
3764
|
const fm = mem.frontmatter;
|
|
3247
3765
|
if (opts.status && fm.status !== opts.status) return false;
|
|
@@ -3262,7 +3780,7 @@ function registerMemoryHot(memory2) {
|
|
|
3262
3780
|
console.log(
|
|
3263
3781
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
3264
3782
|
);
|
|
3265
|
-
console.log(` ${ui.dim(
|
|
3783
|
+
console.log(` ${ui.dim(path21.relative(root, filePath))}`);
|
|
3266
3784
|
}
|
|
3267
3785
|
ui.info(
|
|
3268
3786
|
`${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
|
|
@@ -3271,15 +3789,15 @@ function registerMemoryHot(memory2) {
|
|
|
3271
3789
|
}
|
|
3272
3790
|
|
|
3273
3791
|
// src/commands/memory-tried.ts
|
|
3274
|
-
import { mkdir as
|
|
3275
|
-
import { existsSync as
|
|
3276
|
-
import
|
|
3792
|
+
import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
|
|
3793
|
+
import { existsSync as existsSync21 } from "fs";
|
|
3794
|
+
import path22 from "path";
|
|
3277
3795
|
import "commander";
|
|
3278
3796
|
import {
|
|
3279
3797
|
buildFrontmatter as buildFrontmatter4,
|
|
3280
|
-
findProjectRoot as
|
|
3798
|
+
findProjectRoot as findProjectRoot19,
|
|
3281
3799
|
memoryFilePath as memoryFilePath4,
|
|
3282
|
-
resolveHaivePaths as
|
|
3800
|
+
resolveHaivePaths as resolveHaivePaths16,
|
|
3283
3801
|
serializeMemory as serializeMemory8
|
|
3284
3802
|
} from "@hiveai/core";
|
|
3285
3803
|
function registerMemoryTried(memory2) {
|
|
@@ -3299,9 +3817,9 @@ function registerMemoryTried(memory2) {
|
|
|
3299
3817
|
--paths packages/cli/src/index.ts
|
|
3300
3818
|
`
|
|
3301
3819
|
).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) => {
|
|
3302
|
-
const root =
|
|
3303
|
-
const paths =
|
|
3304
|
-
if (!
|
|
3820
|
+
const root = findProjectRoot19(opts.dir);
|
|
3821
|
+
const paths = resolveHaivePaths16(root);
|
|
3822
|
+
if (!existsSync21(paths.haiveDir)) {
|
|
3305
3823
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
3306
3824
|
process.exitCode = 1;
|
|
3307
3825
|
return;
|
|
@@ -3324,14 +3842,14 @@ function registerMemoryTried(memory2) {
|
|
|
3324
3842
|
}
|
|
3325
3843
|
const body = lines.join("\n") + "\n";
|
|
3326
3844
|
const file = memoryFilePath4(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
3327
|
-
await
|
|
3328
|
-
if (
|
|
3845
|
+
await mkdir10(path22.dirname(file), { recursive: true });
|
|
3846
|
+
if (existsSync21(file)) {
|
|
3329
3847
|
ui.error(`Memory already exists at ${file}`);
|
|
3330
3848
|
process.exitCode = 1;
|
|
3331
3849
|
return;
|
|
3332
3850
|
}
|
|
3333
|
-
await
|
|
3334
|
-
ui.success(`Recorded: ${
|
|
3851
|
+
await writeFile12(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
3852
|
+
ui.success(`Recorded: ${path22.relative(root, file)}`);
|
|
3335
3853
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
3336
3854
|
});
|
|
3337
3855
|
}
|
|
@@ -3341,26 +3859,26 @@ function parseCsv4(value) {
|
|
|
3341
3859
|
}
|
|
3342
3860
|
|
|
3343
3861
|
// src/commands/memory-pending.ts
|
|
3344
|
-
import { existsSync as
|
|
3345
|
-
import
|
|
3862
|
+
import { existsSync as existsSync22 } from "fs";
|
|
3863
|
+
import path23 from "path";
|
|
3346
3864
|
import "commander";
|
|
3347
3865
|
import {
|
|
3348
|
-
findProjectRoot as
|
|
3866
|
+
findProjectRoot as findProjectRoot20,
|
|
3349
3867
|
getUsage as getUsage5,
|
|
3350
|
-
loadUsageIndex as
|
|
3351
|
-
resolveHaivePaths as
|
|
3868
|
+
loadUsageIndex as loadUsageIndex6,
|
|
3869
|
+
resolveHaivePaths as resolveHaivePaths17
|
|
3352
3870
|
} from "@hiveai/core";
|
|
3353
3871
|
function registerMemoryPending(memory2) {
|
|
3354
3872
|
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) => {
|
|
3355
|
-
const root =
|
|
3356
|
-
const paths =
|
|
3357
|
-
if (!
|
|
3873
|
+
const root = findProjectRoot20(opts.dir);
|
|
3874
|
+
const paths = resolveHaivePaths17(root);
|
|
3875
|
+
if (!existsSync22(paths.memoriesDir)) {
|
|
3358
3876
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3359
3877
|
process.exitCode = 1;
|
|
3360
3878
|
return;
|
|
3361
3879
|
}
|
|
3362
3880
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3363
|
-
const usage = await
|
|
3881
|
+
const usage = await loadUsageIndex6(paths);
|
|
3364
3882
|
const proposed = all.filter(({ memory: mem }) => {
|
|
3365
3883
|
if (mem.frontmatter.status !== "proposed") return false;
|
|
3366
3884
|
if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
|
|
@@ -3382,31 +3900,31 @@ function registerMemoryPending(memory2) {
|
|
|
3382
3900
|
console.log(
|
|
3383
3901
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
3384
3902
|
);
|
|
3385
|
-
console.log(` ${ui.dim(
|
|
3903
|
+
console.log(` ${ui.dim(path23.relative(root, filePath))}`);
|
|
3386
3904
|
}
|
|
3387
3905
|
ui.info(`${proposed.length} pending`);
|
|
3388
3906
|
});
|
|
3389
3907
|
}
|
|
3390
3908
|
|
|
3391
3909
|
// src/commands/memory-query.ts
|
|
3392
|
-
import { existsSync as
|
|
3393
|
-
import
|
|
3910
|
+
import { existsSync as existsSync23 } from "fs";
|
|
3911
|
+
import path24 from "path";
|
|
3394
3912
|
import "commander";
|
|
3395
3913
|
import {
|
|
3396
3914
|
extractSnippet,
|
|
3397
|
-
findProjectRoot as
|
|
3915
|
+
findProjectRoot as findProjectRoot21,
|
|
3398
3916
|
literalMatchesAllTokens as literalMatchesAllTokens2,
|
|
3399
3917
|
literalMatchesAnyToken as literalMatchesAnyToken2,
|
|
3400
3918
|
pickSnippetNeedle,
|
|
3401
|
-
resolveHaivePaths as
|
|
3919
|
+
resolveHaivePaths as resolveHaivePaths18,
|
|
3402
3920
|
tokenizeQuery as tokenizeQuery2,
|
|
3403
3921
|
trackReads as trackReads2
|
|
3404
3922
|
} from "@hiveai/core";
|
|
3405
3923
|
function registerMemoryQuery(memory2) {
|
|
3406
|
-
memory2.command("query <text>").description("Search memories by id, tag, or substring (AND, OR fallback)").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) => {
|
|
3407
|
-
const root =
|
|
3408
|
-
const paths =
|
|
3409
|
-
if (!
|
|
3924
|
+
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) => {
|
|
3925
|
+
const root = findProjectRoot21(opts.dir);
|
|
3926
|
+
const paths = resolveHaivePaths18(root);
|
|
3927
|
+
if (!existsSync23(paths.memoriesDir)) {
|
|
3410
3928
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
3411
3929
|
process.exitCode = 1;
|
|
3412
3930
|
return;
|
|
@@ -3447,7 +3965,7 @@ function registerMemoryQuery(memory2) {
|
|
|
3447
3965
|
const fm = mem.frontmatter;
|
|
3448
3966
|
const statusBadge = ui.statusBadge(fm.status);
|
|
3449
3967
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
3450
|
-
console.log(` ${ui.dim(
|
|
3968
|
+
console.log(` ${ui.dim(path24.relative(root, filePath))}`);
|
|
3451
3969
|
const snippet = extractSnippet(mem.body, snippetNeedle);
|
|
3452
3970
|
if (snippet) console.log(` ${snippet}`);
|
|
3453
3971
|
}
|
|
@@ -3464,22 +3982,22 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
3464
3982
|
}
|
|
3465
3983
|
|
|
3466
3984
|
// src/commands/memory-reject.ts
|
|
3467
|
-
import { writeFile as
|
|
3468
|
-
import { existsSync as
|
|
3985
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
3986
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3469
3987
|
import "commander";
|
|
3470
3988
|
import {
|
|
3471
|
-
findProjectRoot as
|
|
3472
|
-
loadUsageIndex as
|
|
3989
|
+
findProjectRoot as findProjectRoot22,
|
|
3990
|
+
loadUsageIndex as loadUsageIndex7,
|
|
3473
3991
|
recordRejection,
|
|
3474
|
-
resolveHaivePaths as
|
|
3992
|
+
resolveHaivePaths as resolveHaivePaths19,
|
|
3475
3993
|
saveUsageIndex,
|
|
3476
3994
|
serializeMemory as serializeMemory9
|
|
3477
3995
|
} from "@hiveai/core";
|
|
3478
3996
|
function registerMemoryReject(memory2) {
|
|
3479
3997
|
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) => {
|
|
3480
|
-
const root =
|
|
3481
|
-
const paths =
|
|
3482
|
-
if (!
|
|
3998
|
+
const root = findProjectRoot22(opts.dir);
|
|
3999
|
+
const paths = resolveHaivePaths19(root);
|
|
4000
|
+
if (!existsSync24(paths.memoriesDir)) {
|
|
3483
4001
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3484
4002
|
process.exitCode = 1;
|
|
3485
4003
|
return;
|
|
@@ -3491,7 +4009,7 @@ function registerMemoryReject(memory2) {
|
|
|
3491
4009
|
process.exitCode = 1;
|
|
3492
4010
|
return;
|
|
3493
4011
|
}
|
|
3494
|
-
await
|
|
4012
|
+
await writeFile13(
|
|
3495
4013
|
loaded.filePath,
|
|
3496
4014
|
serializeMemory9({
|
|
3497
4015
|
frontmatter: {
|
|
@@ -3503,7 +4021,7 @@ function registerMemoryReject(memory2) {
|
|
|
3503
4021
|
}),
|
|
3504
4022
|
"utf8"
|
|
3505
4023
|
);
|
|
3506
|
-
const idx = await
|
|
4024
|
+
const idx = await loadUsageIndex7(paths);
|
|
3507
4025
|
recordRejection(idx, id, opts.reason ?? null);
|
|
3508
4026
|
await saveUsageIndex(paths, idx);
|
|
3509
4027
|
const u = idx.by_id[id];
|
|
@@ -3515,22 +4033,22 @@ function registerMemoryReject(memory2) {
|
|
|
3515
4033
|
}
|
|
3516
4034
|
|
|
3517
4035
|
// src/commands/memory-rm.ts
|
|
3518
|
-
import { existsSync as
|
|
4036
|
+
import { existsSync as existsSync25 } from "fs";
|
|
3519
4037
|
import { unlink as unlink2 } from "fs/promises";
|
|
3520
|
-
import
|
|
4038
|
+
import path25 from "path";
|
|
3521
4039
|
import { createInterface } from "readline/promises";
|
|
3522
4040
|
import "commander";
|
|
3523
4041
|
import {
|
|
3524
|
-
findProjectRoot as
|
|
3525
|
-
loadUsageIndex as
|
|
3526
|
-
resolveHaivePaths as
|
|
4042
|
+
findProjectRoot as findProjectRoot23,
|
|
4043
|
+
loadUsageIndex as loadUsageIndex8,
|
|
4044
|
+
resolveHaivePaths as resolveHaivePaths20,
|
|
3527
4045
|
saveUsageIndex as saveUsageIndex2
|
|
3528
4046
|
} from "@hiveai/core";
|
|
3529
4047
|
function registerMemoryRm(memory2) {
|
|
3530
4048
|
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) => {
|
|
3531
|
-
const root =
|
|
3532
|
-
const paths =
|
|
3533
|
-
if (!
|
|
4049
|
+
const root = findProjectRoot23(opts.dir);
|
|
4050
|
+
const paths = resolveHaivePaths20(root);
|
|
4051
|
+
if (!existsSync25(paths.memoriesDir)) {
|
|
3534
4052
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3535
4053
|
process.exitCode = 1;
|
|
3536
4054
|
return;
|
|
@@ -3542,7 +4060,7 @@ function registerMemoryRm(memory2) {
|
|
|
3542
4060
|
process.exitCode = 1;
|
|
3543
4061
|
return;
|
|
3544
4062
|
}
|
|
3545
|
-
const rel =
|
|
4063
|
+
const rel = path25.relative(root, found.filePath);
|
|
3546
4064
|
if (!opts.yes) {
|
|
3547
4065
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3548
4066
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -3555,7 +4073,7 @@ function registerMemoryRm(memory2) {
|
|
|
3555
4073
|
await unlink2(found.filePath);
|
|
3556
4074
|
ui.success(`Deleted ${rel}`);
|
|
3557
4075
|
if (!opts.keepUsage) {
|
|
3558
|
-
const idx = await
|
|
4076
|
+
const idx = await loadUsageIndex8(paths);
|
|
3559
4077
|
if (idx.by_id[id]) {
|
|
3560
4078
|
delete idx.by_id[id];
|
|
3561
4079
|
await saveUsageIndex2(paths, idx);
|
|
@@ -3566,22 +4084,22 @@ function registerMemoryRm(memory2) {
|
|
|
3566
4084
|
}
|
|
3567
4085
|
|
|
3568
4086
|
// src/commands/memory-show.ts
|
|
3569
|
-
import { existsSync as
|
|
3570
|
-
import { readFile as
|
|
3571
|
-
import
|
|
4087
|
+
import { existsSync as existsSync26 } from "fs";
|
|
4088
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
4089
|
+
import path26 from "path";
|
|
3572
4090
|
import "commander";
|
|
3573
4091
|
import {
|
|
3574
4092
|
deriveConfidence as deriveConfidence2,
|
|
3575
|
-
findProjectRoot as
|
|
4093
|
+
findProjectRoot as findProjectRoot24,
|
|
3576
4094
|
getUsage as getUsage6,
|
|
3577
|
-
loadUsageIndex as
|
|
3578
|
-
resolveHaivePaths as
|
|
4095
|
+
loadUsageIndex as loadUsageIndex9,
|
|
4096
|
+
resolveHaivePaths as resolveHaivePaths21
|
|
3579
4097
|
} from "@hiveai/core";
|
|
3580
4098
|
function registerMemoryShow(memory2) {
|
|
3581
4099
|
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) => {
|
|
3582
|
-
const root =
|
|
3583
|
-
const paths =
|
|
3584
|
-
if (!
|
|
4100
|
+
const root = findProjectRoot24(opts.dir);
|
|
4101
|
+
const paths = resolveHaivePaths21(root);
|
|
4102
|
+
if (!existsSync26(paths.memoriesDir)) {
|
|
3585
4103
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3586
4104
|
process.exitCode = 1;
|
|
3587
4105
|
return;
|
|
@@ -3594,11 +4112,11 @@ function registerMemoryShow(memory2) {
|
|
|
3594
4112
|
return;
|
|
3595
4113
|
}
|
|
3596
4114
|
if (opts.raw) {
|
|
3597
|
-
console.log(await
|
|
4115
|
+
console.log(await readFile10(found.filePath, "utf8"));
|
|
3598
4116
|
return;
|
|
3599
4117
|
}
|
|
3600
4118
|
const fm = found.memory.frontmatter;
|
|
3601
|
-
const usage = await
|
|
4119
|
+
const usage = await loadUsageIndex9(paths);
|
|
3602
4120
|
const u = getUsage6(usage, fm.id);
|
|
3603
4121
|
const conf = deriveConfidence2(fm, u);
|
|
3604
4122
|
console.log(ui.bold(fm.id));
|
|
@@ -3610,7 +4128,7 @@ function registerMemoryShow(memory2) {
|
|
|
3610
4128
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
3611
4129
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
3612
4130
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
3613
|
-
console.log(`${ui.dim("file:")} ${
|
|
4131
|
+
console.log(`${ui.dim("file:")} ${path26.relative(root, found.filePath)}`);
|
|
3614
4132
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
3615
4133
|
console.log(ui.dim("anchor:"));
|
|
3616
4134
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -3625,27 +4143,27 @@ function registerMemoryShow(memory2) {
|
|
|
3625
4143
|
}
|
|
3626
4144
|
|
|
3627
4145
|
// src/commands/memory-stats.ts
|
|
3628
|
-
import { existsSync as
|
|
3629
|
-
import
|
|
4146
|
+
import { existsSync as existsSync27 } from "fs";
|
|
4147
|
+
import path27 from "path";
|
|
3630
4148
|
import "commander";
|
|
3631
4149
|
import {
|
|
3632
4150
|
deriveConfidence as deriveConfidence3,
|
|
3633
|
-
findProjectRoot as
|
|
4151
|
+
findProjectRoot as findProjectRoot25,
|
|
3634
4152
|
getUsage as getUsage7,
|
|
3635
|
-
loadUsageIndex as
|
|
3636
|
-
resolveHaivePaths as
|
|
4153
|
+
loadUsageIndex as loadUsageIndex10,
|
|
4154
|
+
resolveHaivePaths as resolveHaivePaths22
|
|
3637
4155
|
} from "@hiveai/core";
|
|
3638
4156
|
function registerMemoryStats(memory2) {
|
|
3639
4157
|
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) => {
|
|
3640
|
-
const root =
|
|
3641
|
-
const paths =
|
|
3642
|
-
if (!
|
|
4158
|
+
const root = findProjectRoot25(opts.dir);
|
|
4159
|
+
const paths = resolveHaivePaths22(root);
|
|
4160
|
+
if (!existsSync27(paths.memoriesDir)) {
|
|
3643
4161
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
3644
4162
|
process.exitCode = 1;
|
|
3645
4163
|
return;
|
|
3646
4164
|
}
|
|
3647
4165
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3648
|
-
const usage = await
|
|
4166
|
+
const usage = await loadUsageIndex10(paths);
|
|
3649
4167
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
3650
4168
|
if (target.length === 0) {
|
|
3651
4169
|
ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
|
|
@@ -3664,19 +4182,19 @@ function registerMemoryStats(memory2) {
|
|
|
3664
4182
|
console.log(
|
|
3665
4183
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
3666
4184
|
);
|
|
3667
|
-
console.log(` ${ui.dim(
|
|
4185
|
+
console.log(` ${ui.dim(path27.relative(root, filePath))}`);
|
|
3668
4186
|
}
|
|
3669
4187
|
});
|
|
3670
4188
|
}
|
|
3671
4189
|
|
|
3672
4190
|
// src/commands/memory-verify.ts
|
|
3673
|
-
import { writeFile as
|
|
3674
|
-
import { existsSync as
|
|
3675
|
-
import
|
|
4191
|
+
import { writeFile as writeFile14 } from "fs/promises";
|
|
4192
|
+
import { existsSync as existsSync28 } from "fs";
|
|
4193
|
+
import path28 from "path";
|
|
3676
4194
|
import "commander";
|
|
3677
4195
|
import {
|
|
3678
|
-
findProjectRoot as
|
|
3679
|
-
resolveHaivePaths as
|
|
4196
|
+
findProjectRoot as findProjectRoot26,
|
|
4197
|
+
resolveHaivePaths as resolveHaivePaths23,
|
|
3680
4198
|
serializeMemory as serializeMemory10,
|
|
3681
4199
|
verifyAnchor as verifyAnchor2
|
|
3682
4200
|
} from "@hiveai/core";
|
|
@@ -3684,9 +4202,9 @@ function registerMemoryVerify(memory2) {
|
|
|
3684
4202
|
memory2.command("verify").description(
|
|
3685
4203
|
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
|
|
3686
4204
|
).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) => {
|
|
3687
|
-
const root =
|
|
3688
|
-
const paths =
|
|
3689
|
-
if (!
|
|
4205
|
+
const root = findProjectRoot26(opts.dir);
|
|
4206
|
+
const paths = resolveHaivePaths23(root);
|
|
4207
|
+
if (!existsSync28(paths.memoriesDir)) {
|
|
3690
4208
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
3691
4209
|
process.exitCode = 1;
|
|
3692
4210
|
return;
|
|
@@ -3709,7 +4227,7 @@ function registerMemoryVerify(memory2) {
|
|
|
3709
4227
|
anchorlessIds.push(mem.frontmatter.id);
|
|
3710
4228
|
continue;
|
|
3711
4229
|
}
|
|
3712
|
-
const rel =
|
|
4230
|
+
const rel = path28.relative(root, filePath);
|
|
3713
4231
|
if (result.stale) {
|
|
3714
4232
|
staleCount++;
|
|
3715
4233
|
console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
|
|
@@ -3724,7 +4242,7 @@ function registerMemoryVerify(memory2) {
|
|
|
3724
4242
|
}
|
|
3725
4243
|
if (opts.update) {
|
|
3726
4244
|
const next = applyVerification(mem, result);
|
|
3727
|
-
await
|
|
4245
|
+
await writeFile14(filePath, serializeMemory10(next), "utf8");
|
|
3728
4246
|
updated++;
|
|
3729
4247
|
}
|
|
3730
4248
|
}
|
|
@@ -3772,30 +4290,30 @@ function applyVerification(mem, result) {
|
|
|
3772
4290
|
}
|
|
3773
4291
|
|
|
3774
4292
|
// src/commands/memory-import.ts
|
|
3775
|
-
import { readFile as
|
|
3776
|
-
import { existsSync as
|
|
4293
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
4294
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3777
4295
|
import "commander";
|
|
3778
4296
|
import {
|
|
3779
|
-
findProjectRoot as
|
|
3780
|
-
resolveHaivePaths as
|
|
4297
|
+
findProjectRoot as findProjectRoot27,
|
|
4298
|
+
resolveHaivePaths as resolveHaivePaths24
|
|
3781
4299
|
} from "@hiveai/core";
|
|
3782
4300
|
function registerMemoryImport(memory2) {
|
|
3783
4301
|
memory2.command("import").description(
|
|
3784
4302
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
3785
4303
|
).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) => {
|
|
3786
|
-
const root =
|
|
3787
|
-
const paths =
|
|
3788
|
-
if (!
|
|
4304
|
+
const root = findProjectRoot27(opts.dir);
|
|
4305
|
+
const paths = resolveHaivePaths24(root);
|
|
4306
|
+
if (!existsSync29(paths.haiveDir)) {
|
|
3789
4307
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
3790
4308
|
process.exitCode = 1;
|
|
3791
4309
|
return;
|
|
3792
4310
|
}
|
|
3793
|
-
if (!
|
|
4311
|
+
if (!existsSync29(opts.from)) {
|
|
3794
4312
|
ui.error(`File not found: ${opts.from}`);
|
|
3795
4313
|
process.exitCode = 1;
|
|
3796
4314
|
return;
|
|
3797
4315
|
}
|
|
3798
|
-
const content = await
|
|
4316
|
+
const content = await readFile11(opts.from, "utf8");
|
|
3799
4317
|
const scope = opts.scope ?? "team";
|
|
3800
4318
|
ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
|
|
3801
4319
|
ui.info(`Content length: ${content.length} chars`);
|
|
@@ -3823,14 +4341,14 @@ function registerMemoryImport(memory2) {
|
|
|
3823
4341
|
}
|
|
3824
4342
|
|
|
3825
4343
|
// src/commands/memory-import-changelog.ts
|
|
3826
|
-
import { existsSync as
|
|
3827
|
-
import { readFile as
|
|
3828
|
-
import
|
|
4344
|
+
import { existsSync as existsSync30 } from "fs";
|
|
4345
|
+
import { readFile as readFile12, mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
|
|
4346
|
+
import path29 from "path";
|
|
3829
4347
|
import "commander";
|
|
3830
4348
|
import {
|
|
3831
4349
|
buildFrontmatter as buildFrontmatter5,
|
|
3832
|
-
findProjectRoot as
|
|
3833
|
-
resolveHaivePaths as
|
|
4350
|
+
findProjectRoot as findProjectRoot28,
|
|
4351
|
+
resolveHaivePaths as resolveHaivePaths25,
|
|
3834
4352
|
serializeMemory as serializeMemory11
|
|
3835
4353
|
} from "@hiveai/core";
|
|
3836
4354
|
function parseChangelog(content) {
|
|
@@ -3894,15 +4412,15 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3894
4412
|
"--versions <csv>",
|
|
3895
4413
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
3896
4414
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
3897
|
-
const root =
|
|
3898
|
-
const paths =
|
|
3899
|
-
const changelogPath =
|
|
3900
|
-
if (!
|
|
4415
|
+
const root = findProjectRoot28(opts.dir);
|
|
4416
|
+
const paths = resolveHaivePaths25(root);
|
|
4417
|
+
const changelogPath = path29.resolve(root, opts.fromChangelog);
|
|
4418
|
+
if (!existsSync30(changelogPath)) {
|
|
3901
4419
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
3902
4420
|
process.exitCode = 1;
|
|
3903
4421
|
return;
|
|
3904
4422
|
}
|
|
3905
|
-
const content = await
|
|
4423
|
+
const content = await readFile12(changelogPath, "utf8");
|
|
3906
4424
|
let entries = parseChangelog(content);
|
|
3907
4425
|
if (entries.length === 0) {
|
|
3908
4426
|
ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
|
|
@@ -3916,10 +4434,10 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3916
4434
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
3917
4435
|
}
|
|
3918
4436
|
}
|
|
3919
|
-
const pkgName = opts.package ??
|
|
4437
|
+
const pkgName = opts.package ?? path29.basename(path29.dirname(changelogPath));
|
|
3920
4438
|
const scope = opts.scope ?? "team";
|
|
3921
|
-
const teamDir =
|
|
3922
|
-
await
|
|
4439
|
+
const teamDir = path29.join(paths.memoriesDir, scope);
|
|
4440
|
+
await mkdir11(teamDir, { recursive: true });
|
|
3923
4441
|
let saved = 0;
|
|
3924
4442
|
for (const entry of entries) {
|
|
3925
4443
|
const lines = [];
|
|
@@ -3941,7 +4459,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3941
4459
|
lines.push("");
|
|
3942
4460
|
}
|
|
3943
4461
|
lines.push(
|
|
3944
|
-
`**Source:** \`${
|
|
4462
|
+
`**Source:** \`${path29.relative(root, changelogPath)}\`
|
|
3945
4463
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
3946
4464
|
);
|
|
3947
4465
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
@@ -3956,11 +4474,11 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3956
4474
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
3957
4475
|
`v${entry.version}`
|
|
3958
4476
|
],
|
|
3959
|
-
paths: [
|
|
4477
|
+
paths: [path29.relative(root, changelogPath)],
|
|
3960
4478
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
3961
4479
|
});
|
|
3962
|
-
await
|
|
3963
|
-
|
|
4480
|
+
await writeFile15(
|
|
4481
|
+
path29.join(teamDir, `${fm.id}.md`),
|
|
3964
4482
|
serializeMemory11({ frontmatter: fm, body: lines.join("\n") }),
|
|
3965
4483
|
"utf8"
|
|
3966
4484
|
);
|
|
@@ -3983,17 +4501,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
3983
4501
|
}
|
|
3984
4502
|
|
|
3985
4503
|
// src/commands/memory-digest.ts
|
|
3986
|
-
import { existsSync as
|
|
3987
|
-
import { writeFile as
|
|
3988
|
-
import
|
|
4504
|
+
import { existsSync as existsSync31 } from "fs";
|
|
4505
|
+
import { writeFile as writeFile16 } from "fs/promises";
|
|
4506
|
+
import path30 from "path";
|
|
3989
4507
|
import "commander";
|
|
3990
4508
|
import {
|
|
3991
4509
|
deriveConfidence as deriveConfidence4,
|
|
3992
|
-
findProjectRoot as
|
|
4510
|
+
findProjectRoot as findProjectRoot29,
|
|
3993
4511
|
getUsage as getUsage8,
|
|
3994
4512
|
loadMemoriesFromDir as loadMemoriesFromDir5,
|
|
3995
|
-
loadUsageIndex as
|
|
3996
|
-
resolveHaivePaths as
|
|
4513
|
+
loadUsageIndex as loadUsageIndex11,
|
|
4514
|
+
resolveHaivePaths as resolveHaivePaths26
|
|
3997
4515
|
} from "@hiveai/core";
|
|
3998
4516
|
var CONFIDENCE_EMOJI = {
|
|
3999
4517
|
unverified: "\u2B1C",
|
|
@@ -4006,9 +4524,9 @@ function registerMemoryDigest(program2) {
|
|
|
4006
4524
|
program2.command("digest").description(
|
|
4007
4525
|
"Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
|
|
4008
4526
|
).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) => {
|
|
4009
|
-
const root =
|
|
4010
|
-
const paths =
|
|
4011
|
-
if (!
|
|
4527
|
+
const root = findProjectRoot29(opts.dir);
|
|
4528
|
+
const paths = resolveHaivePaths26(root);
|
|
4529
|
+
if (!existsSync31(paths.memoriesDir)) {
|
|
4012
4530
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
4013
4531
|
process.exitCode = 1;
|
|
4014
4532
|
return;
|
|
@@ -4017,7 +4535,7 @@ function registerMemoryDigest(program2) {
|
|
|
4017
4535
|
const scopeFilter = opts.scope ?? "team";
|
|
4018
4536
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
4019
4537
|
const all = await loadMemoriesFromDir5(paths.memoriesDir);
|
|
4020
|
-
const usage = await
|
|
4538
|
+
const usage = await loadUsageIndex11(paths);
|
|
4021
4539
|
const recent = all.filter(({ memory: mem }) => {
|
|
4022
4540
|
const fm = mem.frontmatter;
|
|
4023
4541
|
if (fm.type === "session_recap") return false;
|
|
@@ -4080,8 +4598,8 @@ function registerMemoryDigest(program2) {
|
|
|
4080
4598
|
);
|
|
4081
4599
|
const digest = lines.join("\n");
|
|
4082
4600
|
if (opts.out) {
|
|
4083
|
-
const outPath =
|
|
4084
|
-
await
|
|
4601
|
+
const outPath = path30.resolve(process.cwd(), opts.out);
|
|
4602
|
+
await writeFile16(outPath, digest, "utf8");
|
|
4085
4603
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
4086
4604
|
} else {
|
|
4087
4605
|
console.log(digest);
|
|
@@ -4090,18 +4608,52 @@ function registerMemoryDigest(program2) {
|
|
|
4090
4608
|
}
|
|
4091
4609
|
|
|
4092
4610
|
// src/commands/session-end.ts
|
|
4093
|
-
import { writeFile as
|
|
4094
|
-
import { existsSync as
|
|
4095
|
-
import
|
|
4611
|
+
import { writeFile as writeFile17, mkdir as mkdir12, readFile as readFile13, rm } from "fs/promises";
|
|
4612
|
+
import { existsSync as existsSync32 } from "fs";
|
|
4613
|
+
import path31 from "path";
|
|
4096
4614
|
import "commander";
|
|
4097
4615
|
import {
|
|
4098
4616
|
buildFrontmatter as buildFrontmatter6,
|
|
4099
|
-
findProjectRoot as
|
|
4617
|
+
findProjectRoot as findProjectRoot30,
|
|
4100
4618
|
loadMemoriesFromDir as loadMemoriesFromDir6,
|
|
4101
4619
|
memoryFilePath as memoryFilePath5,
|
|
4102
|
-
resolveHaivePaths as
|
|
4620
|
+
resolveHaivePaths as resolveHaivePaths27,
|
|
4103
4621
|
serializeMemory as serializeMemory12
|
|
4104
4622
|
} from "@hiveai/core";
|
|
4623
|
+
async function buildAutoRecap(paths) {
|
|
4624
|
+
const obsFile = path31.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
4625
|
+
if (!existsSync32(obsFile)) return null;
|
|
4626
|
+
const raw = await readFile13(obsFile, "utf8").catch(() => "");
|
|
4627
|
+
if (!raw.trim()) return null;
|
|
4628
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
4629
|
+
const obs = [];
|
|
4630
|
+
for (const line of lines) {
|
|
4631
|
+
try {
|
|
4632
|
+
obs.push(JSON.parse(line));
|
|
4633
|
+
} catch {
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
if (obs.length === 0) return null;
|
|
4637
|
+
const toolCounts = /* @__PURE__ */ new Map();
|
|
4638
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
4639
|
+
const summaries = [];
|
|
4640
|
+
for (const o of obs) {
|
|
4641
|
+
toolCounts.set(o.tool, (toolCounts.get(o.tool) ?? 0) + 1);
|
|
4642
|
+
for (const f of o.files ?? []) fileCounts.set(f, (fileCounts.get(f) ?? 0) + 1);
|
|
4643
|
+
if (summaries.length < 10) summaries.push(`- ${o.summary}`);
|
|
4644
|
+
}
|
|
4645
|
+
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t} \xD7${c}`).join(", ");
|
|
4646
|
+
const topFiles = [...fileCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
|
|
4647
|
+
const goal = `Auto-captured session \u2014 ${obs.length} tool calls (${topTools})`;
|
|
4648
|
+
const accomplished = summaries.length ? `Recent activity:
|
|
4649
|
+
${summaries.join("\n")}` : `Activity captured but no parseable summaries.`;
|
|
4650
|
+
return {
|
|
4651
|
+
goal,
|
|
4652
|
+
accomplished,
|
|
4653
|
+
files: topFiles.map(([f]) => f),
|
|
4654
|
+
rawCount: obs.length
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4105
4657
|
function buildRecapBody(opts) {
|
|
4106
4658
|
const lines = [];
|
|
4107
4659
|
lines.push(`## Goal
|
|
@@ -4148,24 +4700,53 @@ function registerSessionEnd(session2) {
|
|
|
4148
4700
|
--files src/payments/WebhookController.ts,src/payments/WebhookService.ts \\\\
|
|
4149
4701
|
--next "Add integration tests for webhook signature validation"
|
|
4150
4702
|
`
|
|
4151
|
-
).
|
|
4152
|
-
const root =
|
|
4153
|
-
const paths =
|
|
4154
|
-
if (!
|
|
4703
|
+
).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) => {
|
|
4704
|
+
const root = findProjectRoot30(opts.dir);
|
|
4705
|
+
const paths = resolveHaivePaths27(root);
|
|
4706
|
+
if (!existsSync32(paths.haiveDir)) {
|
|
4707
|
+
if (opts.auto || opts.quiet) return;
|
|
4155
4708
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
4156
4709
|
process.exitCode = 1;
|
|
4157
4710
|
return;
|
|
4158
4711
|
}
|
|
4712
|
+
let resolvedFiles = opts.files;
|
|
4713
|
+
let goal = opts.goal;
|
|
4714
|
+
let accomplished = opts.accomplished;
|
|
4715
|
+
if (opts.auto) {
|
|
4716
|
+
const synth = await buildAutoRecap(paths);
|
|
4717
|
+
if (!synth) return;
|
|
4718
|
+
goal = goal ?? synth.goal;
|
|
4719
|
+
accomplished = accomplished ?? synth.accomplished;
|
|
4720
|
+
if (!resolvedFiles && synth.files.length) resolvedFiles = synth.files.join(",");
|
|
4721
|
+
}
|
|
4722
|
+
if (!goal || !accomplished) {
|
|
4723
|
+
if (opts.quiet) return;
|
|
4724
|
+
ui.error("session-end requires --goal and --accomplished (or pass --auto with captured observations).");
|
|
4725
|
+
process.exitCode = 1;
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4159
4728
|
const scope = opts.scope ?? "personal";
|
|
4160
|
-
const body = buildRecapBody(
|
|
4729
|
+
const body = buildRecapBody({
|
|
4730
|
+
goal,
|
|
4731
|
+
accomplished,
|
|
4732
|
+
discoveries: opts.discoveries,
|
|
4733
|
+
files: resolvedFiles,
|
|
4734
|
+
next: opts.next
|
|
4735
|
+
});
|
|
4161
4736
|
const topic = recapTopic(scope, opts.module);
|
|
4162
|
-
const filesTouched = parseCsv5(
|
|
4163
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
4164
|
-
if (missingPaths.length > 0) {
|
|
4737
|
+
const filesTouched = parseCsv5(resolvedFiles);
|
|
4738
|
+
const missingPaths = filesTouched.filter((p) => !existsSync32(path31.resolve(root, p)));
|
|
4739
|
+
if (missingPaths.length > 0 && !opts.quiet) {
|
|
4165
4740
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
4166
4741
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
4167
4742
|
}
|
|
4168
|
-
|
|
4743
|
+
const cleanupObservations = async () => {
|
|
4744
|
+
if (!opts.auto) return;
|
|
4745
|
+
const obsFile = path31.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
4746
|
+
if (existsSync32(obsFile)) await rm(obsFile).catch(() => {
|
|
4747
|
+
});
|
|
4748
|
+
};
|
|
4749
|
+
if (existsSync32(paths.memoriesDir)) {
|
|
4169
4750
|
const existing = await loadMemoriesFromDir6(paths.memoriesDir);
|
|
4170
4751
|
const topicMatch = existing.find(
|
|
4171
4752
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
@@ -4181,9 +4762,12 @@ function registerSessionEnd(session2) {
|
|
|
4181
4762
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
4182
4763
|
}
|
|
4183
4764
|
};
|
|
4184
|
-
await
|
|
4185
|
-
|
|
4186
|
-
|
|
4765
|
+
await writeFile17(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
|
|
4766
|
+
await cleanupObservations();
|
|
4767
|
+
if (!opts.quiet) {
|
|
4768
|
+
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
4769
|
+
ui.info(`id=${fm.id} file=${path31.relative(root, topicMatch.filePath)}`);
|
|
4770
|
+
}
|
|
4187
4771
|
return;
|
|
4188
4772
|
}
|
|
4189
4773
|
}
|
|
@@ -4198,11 +4782,14 @@ function registerSessionEnd(session2) {
|
|
|
4198
4782
|
status: "validated"
|
|
4199
4783
|
});
|
|
4200
4784
|
const file = memoryFilePath5(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
4201
|
-
await
|
|
4202
|
-
await
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4785
|
+
await mkdir12(path31.dirname(file), { recursive: true });
|
|
4786
|
+
await writeFile17(file, serializeMemory12({ frontmatter, body }), "utf8");
|
|
4787
|
+
await cleanupObservations();
|
|
4788
|
+
if (!opts.quiet) {
|
|
4789
|
+
ui.success(`Session recap created`);
|
|
4790
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path31.relative(root, file)}`);
|
|
4791
|
+
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
4792
|
+
}
|
|
4206
4793
|
});
|
|
4207
4794
|
}
|
|
4208
4795
|
function parseCsv5(value) {
|
|
@@ -4211,15 +4798,15 @@ function parseCsv5(value) {
|
|
|
4211
4798
|
}
|
|
4212
4799
|
|
|
4213
4800
|
// src/commands/snapshot.ts
|
|
4214
|
-
import { existsSync as
|
|
4801
|
+
import { existsSync as existsSync33 } from "fs";
|
|
4215
4802
|
import { readdir as readdir2 } from "fs/promises";
|
|
4216
|
-
import
|
|
4803
|
+
import path32 from "path";
|
|
4217
4804
|
import "commander";
|
|
4218
4805
|
import {
|
|
4219
4806
|
diffContract,
|
|
4220
|
-
findProjectRoot as
|
|
4807
|
+
findProjectRoot as findProjectRoot31,
|
|
4221
4808
|
loadConfig as loadConfig2,
|
|
4222
|
-
resolveHaivePaths as
|
|
4809
|
+
resolveHaivePaths as resolveHaivePaths28,
|
|
4223
4810
|
snapshotContract
|
|
4224
4811
|
} from "@hiveai/core";
|
|
4225
4812
|
function registerSnapshot(program2) {
|
|
@@ -4244,16 +4831,16 @@ function registerSnapshot(program2) {
|
|
|
4244
4831
|
"--format <format>",
|
|
4245
4832
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
4246
4833
|
).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) => {
|
|
4247
|
-
const root =
|
|
4248
|
-
const paths =
|
|
4249
|
-
if (!
|
|
4834
|
+
const root = findProjectRoot31(opts.dir);
|
|
4835
|
+
const paths = resolveHaivePaths28(root);
|
|
4836
|
+
if (!existsSync33(paths.haiveDir)) {
|
|
4250
4837
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
4251
4838
|
process.exitCode = 1;
|
|
4252
4839
|
return;
|
|
4253
4840
|
}
|
|
4254
4841
|
if (opts.list) {
|
|
4255
|
-
const contractsDir =
|
|
4256
|
-
if (!
|
|
4842
|
+
const contractsDir = path32.join(paths.haiveDir, "contracts");
|
|
4843
|
+
if (!existsSync33(contractsDir)) {
|
|
4257
4844
|
console.log(ui.dim("No contract snapshots found."));
|
|
4258
4845
|
return;
|
|
4259
4846
|
}
|
|
@@ -4308,7 +4895,7 @@ function registerSnapshot(program2) {
|
|
|
4308
4895
|
return;
|
|
4309
4896
|
}
|
|
4310
4897
|
const contractPath = opts.contract;
|
|
4311
|
-
const name = opts.name ??
|
|
4898
|
+
const name = opts.name ?? path32.basename(contractPath, path32.extname(contractPath));
|
|
4312
4899
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
4313
4900
|
const contract = { name, path: contractPath, format };
|
|
4314
4901
|
try {
|
|
@@ -4363,8 +4950,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
4363
4950
|
}
|
|
4364
4951
|
}
|
|
4365
4952
|
function detectFormat(filePath) {
|
|
4366
|
-
const ext =
|
|
4367
|
-
const base =
|
|
4953
|
+
const ext = path32.extname(filePath).toLowerCase();
|
|
4954
|
+
const base = path32.basename(filePath).toLowerCase();
|
|
4368
4955
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
4369
4956
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
4370
4957
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -4377,16 +4964,16 @@ function detectFormat(filePath) {
|
|
|
4377
4964
|
}
|
|
4378
4965
|
|
|
4379
4966
|
// src/commands/hub.ts
|
|
4380
|
-
import { existsSync as
|
|
4381
|
-
import { mkdir as
|
|
4382
|
-
import
|
|
4967
|
+
import { existsSync as existsSync34 } from "fs";
|
|
4968
|
+
import { mkdir as mkdir13, readFile as readFile14, writeFile as writeFile18, copyFile } from "fs/promises";
|
|
4969
|
+
import path33 from "path";
|
|
4383
4970
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4384
4971
|
import "commander";
|
|
4385
4972
|
import {
|
|
4386
|
-
findProjectRoot as
|
|
4973
|
+
findProjectRoot as findProjectRoot32,
|
|
4387
4974
|
loadConfig as loadConfig3,
|
|
4388
4975
|
loadMemoriesFromDir as loadMemoriesFromDir7,
|
|
4389
|
-
resolveHaivePaths as
|
|
4976
|
+
resolveHaivePaths as resolveHaivePaths29,
|
|
4390
4977
|
saveConfig as saveConfig2,
|
|
4391
4978
|
serializeMemory as serializeMemory13
|
|
4392
4979
|
} from "@hiveai/core";
|
|
@@ -4398,8 +4985,8 @@ function registerHub(program2) {
|
|
|
4398
4985
|
hub.command("init <hubPath>").description(
|
|
4399
4986
|
"Initialize a new team-knowledge hub repo at <hubPath>.\n\n Creates a git repo with a .ai/ directory structure ready for shared memories.\n\n Example:\n haive hub init ../team-hub\n haive hub init /srv/git/team-knowledge\n"
|
|
4400
4987
|
).action(async (hubPath) => {
|
|
4401
|
-
const absPath =
|
|
4402
|
-
await
|
|
4988
|
+
const absPath = path33.resolve(hubPath);
|
|
4989
|
+
await mkdir13(absPath, { recursive: true });
|
|
4403
4990
|
const gitCheck = spawnSync3("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
4404
4991
|
if (gitCheck.status !== 0) {
|
|
4405
4992
|
const init = spawnSync3("git", ["init"], { cwd: absPath, encoding: "utf8" });
|
|
@@ -4409,10 +4996,10 @@ function registerHub(program2) {
|
|
|
4409
4996
|
return;
|
|
4410
4997
|
}
|
|
4411
4998
|
}
|
|
4412
|
-
const sharedDir =
|
|
4413
|
-
await
|
|
4414
|
-
await
|
|
4415
|
-
|
|
4999
|
+
const sharedDir = path33.join(absPath, ".ai", "memories", "shared");
|
|
5000
|
+
await mkdir13(sharedDir, { recursive: true });
|
|
5001
|
+
await writeFile18(
|
|
5002
|
+
path33.join(absPath, ".ai", "README.md"),
|
|
4416
5003
|
`# hAIve Team Knowledge Hub
|
|
4417
5004
|
|
|
4418
5005
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -4433,8 +5020,8 @@ haive hub pull # import into a project
|
|
|
4433
5020
|
`,
|
|
4434
5021
|
"utf8"
|
|
4435
5022
|
);
|
|
4436
|
-
await
|
|
4437
|
-
|
|
5023
|
+
await writeFile18(
|
|
5024
|
+
path33.join(absPath, ".gitignore"),
|
|
4438
5025
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
4439
5026
|
"utf8"
|
|
4440
5027
|
);
|
|
@@ -4449,7 +5036,7 @@ haive hub pull # import into a project
|
|
|
4449
5036
|
`
|
|
4450
5037
|
Next steps:
|
|
4451
5038
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
4452
|
-
{ "hubPath": "${
|
|
5039
|
+
{ "hubPath": "${path33.relative(process.cwd(), absPath)}" }
|
|
4453
5040
|
2. Run \`haive hub push\` to publish your shared memories
|
|
4454
5041
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
4455
5042
|
`
|
|
@@ -4468,8 +5055,8 @@ Next steps:
|
|
|
4468
5055
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
4469
5056
|
`
|
|
4470
5057
|
).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
|
|
4471
|
-
const root =
|
|
4472
|
-
const paths =
|
|
5058
|
+
const root = findProjectRoot32(opts.dir);
|
|
5059
|
+
const paths = resolveHaivePaths29(root);
|
|
4473
5060
|
const config = await loadConfig3(paths);
|
|
4474
5061
|
if (!config.hubPath) {
|
|
4475
5062
|
ui.error(
|
|
@@ -4478,15 +5065,15 @@ Next steps:
|
|
|
4478
5065
|
process.exitCode = 1;
|
|
4479
5066
|
return;
|
|
4480
5067
|
}
|
|
4481
|
-
const hubRoot =
|
|
4482
|
-
if (!
|
|
5068
|
+
const hubRoot = path33.resolve(root, config.hubPath);
|
|
5069
|
+
if (!existsSync34(hubRoot)) {
|
|
4483
5070
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
4484
5071
|
process.exitCode = 1;
|
|
4485
5072
|
return;
|
|
4486
5073
|
}
|
|
4487
|
-
const projectName =
|
|
4488
|
-
const destDir =
|
|
4489
|
-
await
|
|
5074
|
+
const projectName = path33.basename(root);
|
|
5075
|
+
const destDir = path33.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
5076
|
+
await mkdir13(destDir, { recursive: true });
|
|
4490
5077
|
const all = await loadMemoriesFromDir7(paths.memoriesDir);
|
|
4491
5078
|
const shared = all.filter(
|
|
4492
5079
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
@@ -4504,15 +5091,15 @@ Next steps:
|
|
|
4504
5091
|
for (const { memory: memory2 } of shared) {
|
|
4505
5092
|
const fm = memory2.frontmatter;
|
|
4506
5093
|
const fileName = `${fm.id}.md`;
|
|
4507
|
-
const destPath =
|
|
4508
|
-
await
|
|
5094
|
+
const destPath = path33.join(destDir, fileName);
|
|
5095
|
+
await writeFile18(destPath, serializeMemory13(memory2), "utf8");
|
|
4509
5096
|
pushed++;
|
|
4510
5097
|
}
|
|
4511
5098
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
4512
5099
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
4513
5100
|
if (opts.commit) {
|
|
4514
5101
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
4515
|
-
spawnSync3("git", ["add",
|
|
5102
|
+
spawnSync3("git", ["add", path33.join(".ai", "memories", "shared", projectName)], {
|
|
4516
5103
|
cwd: hubRoot
|
|
4517
5104
|
});
|
|
4518
5105
|
const commit = spawnSync3("git", ["commit", "-m", message], {
|
|
@@ -4537,8 +5124,8 @@ Next steps:
|
|
|
4537
5124
|
hub.command("pull").description(
|
|
4538
5125
|
"Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
|
|
4539
5126
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
4540
|
-
const root =
|
|
4541
|
-
const paths =
|
|
5127
|
+
const root = findProjectRoot32(opts.dir);
|
|
5128
|
+
const paths = resolveHaivePaths29(root);
|
|
4542
5129
|
const config = await loadConfig3(paths);
|
|
4543
5130
|
if (!config.hubPath) {
|
|
4544
5131
|
ui.error(
|
|
@@ -4547,13 +5134,13 @@ Next steps:
|
|
|
4547
5134
|
process.exitCode = 1;
|
|
4548
5135
|
return;
|
|
4549
5136
|
}
|
|
4550
|
-
const hubRoot =
|
|
4551
|
-
const hubSharedDir =
|
|
4552
|
-
if (!
|
|
5137
|
+
const hubRoot = path33.resolve(root, config.hubPath);
|
|
5138
|
+
const hubSharedDir = path33.join(hubRoot, ".ai", "memories", "shared");
|
|
5139
|
+
if (!existsSync34(hubSharedDir)) {
|
|
4553
5140
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
4554
5141
|
return;
|
|
4555
5142
|
}
|
|
4556
|
-
const projectName =
|
|
5143
|
+
const projectName = path33.basename(root);
|
|
4557
5144
|
const { readdir: readdir3 } = await import("fs/promises");
|
|
4558
5145
|
const projectDirs = (await readdir3(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
4559
5146
|
if (projectDirs.length === 0) {
|
|
@@ -4563,17 +5150,17 @@ Next steps:
|
|
|
4563
5150
|
let totalImported = 0;
|
|
4564
5151
|
let totalUpdated = 0;
|
|
4565
5152
|
for (const sourceName of projectDirs) {
|
|
4566
|
-
const sourceDir =
|
|
4567
|
-
const destDir =
|
|
4568
|
-
await
|
|
5153
|
+
const sourceDir = path33.join(hubSharedDir, sourceName);
|
|
5154
|
+
const destDir = path33.join(paths.memoriesDir, "shared", sourceName);
|
|
5155
|
+
await mkdir13(destDir, { recursive: true });
|
|
4569
5156
|
const sourceFiles = (await readdir3(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
4570
5157
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
4571
5158
|
const existingInDest = await loadDir(destDir);
|
|
4572
5159
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
4573
5160
|
for (const file of sourceFiles) {
|
|
4574
|
-
const srcPath =
|
|
4575
|
-
const destPath =
|
|
4576
|
-
const fileContent = await
|
|
5161
|
+
const srcPath = path33.join(sourceDir, file);
|
|
5162
|
+
const destPath = path33.join(destDir, file);
|
|
5163
|
+
const fileContent = await readFile14(srcPath, "utf8");
|
|
4577
5164
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
4578
5165
|
if (!alreadyTagged) {
|
|
4579
5166
|
await copyFile(srcPath, destPath);
|
|
@@ -4596,21 +5183,21 @@ Next steps:
|
|
|
4596
5183
|
);
|
|
4597
5184
|
});
|
|
4598
5185
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
4599
|
-
const root =
|
|
4600
|
-
const paths =
|
|
5186
|
+
const root = findProjectRoot32(opts.dir);
|
|
5187
|
+
const paths = resolveHaivePaths29(root);
|
|
4601
5188
|
const config = await loadConfig3(paths);
|
|
4602
5189
|
console.log(ui.bold("Hub status"));
|
|
4603
5190
|
console.log(
|
|
4604
5191
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
4605
5192
|
);
|
|
4606
|
-
const sharedDir =
|
|
4607
|
-
if (
|
|
5193
|
+
const sharedDir = path33.join(paths.memoriesDir, "shared");
|
|
5194
|
+
if (existsSync34(sharedDir)) {
|
|
4608
5195
|
const { readdir: readdir3 } = await import("fs/promises");
|
|
4609
5196
|
const sources = (await readdir3(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
4610
5197
|
console.log(`
|
|
4611
5198
|
Imported from ${sources.length} source(s):`);
|
|
4612
5199
|
for (const src of sources) {
|
|
4613
|
-
const files = (await readdir3(
|
|
5200
|
+
const files = (await readdir3(path33.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
4614
5201
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
4615
5202
|
}
|
|
4616
5203
|
} else {
|
|
@@ -4625,8 +5212,8 @@ Next steps:
|
|
|
4625
5212
|
if (outgoing.length > 0) {
|
|
4626
5213
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
4627
5214
|
}
|
|
4628
|
-
void
|
|
4629
|
-
void
|
|
5215
|
+
void readFile14;
|
|
5216
|
+
void writeFile18;
|
|
4630
5217
|
void saveConfig2;
|
|
4631
5218
|
});
|
|
4632
5219
|
}
|
|
@@ -4635,16 +5222,21 @@ Next steps:
|
|
|
4635
5222
|
import "commander";
|
|
4636
5223
|
import {
|
|
4637
5224
|
aggregateUsage,
|
|
4638
|
-
findProjectRoot as
|
|
5225
|
+
findProjectRoot as findProjectRoot33,
|
|
5226
|
+
loadUsageIndex as loadUsageIndex12,
|
|
4639
5227
|
parseSince,
|
|
4640
5228
|
readUsageEvents,
|
|
4641
|
-
resolveHaivePaths as
|
|
5229
|
+
resolveHaivePaths as resolveHaivePaths30,
|
|
4642
5230
|
usageLogSize
|
|
4643
5231
|
} from "@hiveai/core";
|
|
4644
5232
|
function registerStats(program2) {
|
|
4645
|
-
program2.command("stats").description("Show MCP tool-usage stats over a window (e.g. --since 7d).").option("--since <window>", "ISO date or relative (e.g. '7d', '24h', '30m')", "30d").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
4646
|
-
const root =
|
|
4647
|
-
const paths =
|
|
5233
|
+
program2.command("stats").description("Show MCP tool-usage stats over a window (e.g. --since 7d).").option("--since <window>", "ISO date or relative (e.g. '7d', '24h', '30m')", "30d").option("--json", "emit JSON instead of human-readable output", false).option("--memory-hits", "show top-read memories (which mems are actually being used)", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
5234
|
+
const root = findProjectRoot33(opts.dir);
|
|
5235
|
+
const paths = resolveHaivePaths30(root);
|
|
5236
|
+
if (opts.memoryHits) {
|
|
5237
|
+
await renderMemoryHits(paths, opts);
|
|
5238
|
+
return;
|
|
5239
|
+
}
|
|
4648
5240
|
const size = await usageLogSize(paths);
|
|
4649
5241
|
if (!size.exists) {
|
|
4650
5242
|
if (opts.json) {
|
|
@@ -4689,14 +5281,57 @@ function registerStats(program2) {
|
|
|
4689
5281
|
}
|
|
4690
5282
|
});
|
|
4691
5283
|
}
|
|
5284
|
+
async function renderMemoryHits(paths, opts) {
|
|
5285
|
+
const index = await loadUsageIndex12(paths);
|
|
5286
|
+
const since = parseSince(opts.since ?? "30d");
|
|
5287
|
+
const sinceMs = since ? new Date(since).getTime() : null;
|
|
5288
|
+
const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
|
|
5289
|
+
if (!sinceMs || !e.last_read_at) return !sinceMs;
|
|
5290
|
+
return new Date(e.last_read_at).getTime() >= sinceMs;
|
|
5291
|
+
}).sort((a, b) => b.read_count - a.read_count);
|
|
5292
|
+
if (opts.json) {
|
|
5293
|
+
console.log(JSON.stringify({
|
|
5294
|
+
window: opts.since ?? "30d",
|
|
5295
|
+
total_mems_with_hits: entries.length,
|
|
5296
|
+
top: entries.slice(0, 50)
|
|
5297
|
+
}, null, 2));
|
|
5298
|
+
return;
|
|
5299
|
+
}
|
|
5300
|
+
const window = opts.since ?? "30d";
|
|
5301
|
+
console.log(ui.bold(`Memory hits (${window})`));
|
|
5302
|
+
if (entries.length === 0) {
|
|
5303
|
+
ui.info(
|
|
5304
|
+
`No memory reads recorded in window. Reads are logged when \`haive briefing\` or \`haive memory query\` surface a memory.`
|
|
5305
|
+
);
|
|
5306
|
+
return;
|
|
5307
|
+
}
|
|
5308
|
+
console.log(
|
|
5309
|
+
` ${ui.dim("memories with hits:")} ${entries.length} ${ui.dim("total reads:")} ${entries.reduce((a, e) => a + e.read_count, 0)}`
|
|
5310
|
+
);
|
|
5311
|
+
console.log();
|
|
5312
|
+
console.log(ui.bold("Top read memories:"));
|
|
5313
|
+
const maxCount = entries[0].read_count;
|
|
5314
|
+
for (const e of entries.slice(0, 25)) {
|
|
5315
|
+
const bar = "\u2588".repeat(Math.max(1, Math.round(e.read_count / maxCount * 20)));
|
|
5316
|
+
const lastRead = e.last_read_at?.slice(0, 10) ?? "?";
|
|
5317
|
+
console.log(
|
|
5318
|
+
` ${ui.bold(String(e.read_count).padStart(4))} ${ui.green(bar.padEnd(20))} ${e.id} ${ui.dim(`last: ${lastRead}`)}`
|
|
5319
|
+
);
|
|
5320
|
+
}
|
|
5321
|
+
const dead = Object.keys(index.by_id).length - entries.length;
|
|
5322
|
+
if (dead > 0) {
|
|
5323
|
+
console.log();
|
|
5324
|
+
ui.info(`${dead} memor${dead === 1 ? "y" : "ies"} never read in window \u2014 candidates for cleanup (run \`haive doctor\`).`);
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
4692
5327
|
|
|
4693
5328
|
// src/commands/bench.ts
|
|
4694
5329
|
import { performance } from "perf_hooks";
|
|
4695
5330
|
import "commander";
|
|
4696
5331
|
import {
|
|
4697
5332
|
estimateTokens,
|
|
4698
|
-
findProjectRoot as
|
|
4699
|
-
resolveHaivePaths as
|
|
5333
|
+
findProjectRoot as findProjectRoot34,
|
|
5334
|
+
resolveHaivePaths as resolveHaivePaths31
|
|
4700
5335
|
} from "@hiveai/core";
|
|
4701
5336
|
import {
|
|
4702
5337
|
antiPatternsCheck,
|
|
@@ -4708,8 +5343,8 @@ import {
|
|
|
4708
5343
|
} from "@hiveai/mcp";
|
|
4709
5344
|
function registerBench(program2) {
|
|
4710
5345
|
program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
4711
|
-
const root =
|
|
4712
|
-
const paths =
|
|
5346
|
+
const root = findProjectRoot34(opts.dir);
|
|
5347
|
+
const paths = resolveHaivePaths31(root);
|
|
4713
5348
|
const ctx = { paths };
|
|
4714
5349
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
4715
5350
|
const scenarios = [
|
|
@@ -4828,19 +5463,19 @@ function summarize(name, t0, payload, notes) {
|
|
|
4828
5463
|
}
|
|
4829
5464
|
|
|
4830
5465
|
// src/commands/memory-suggest.ts
|
|
4831
|
-
import { mkdir as
|
|
4832
|
-
import { existsSync as
|
|
4833
|
-
import
|
|
5466
|
+
import { mkdir as mkdir14, writeFile as writeFile19 } from "fs/promises";
|
|
5467
|
+
import { existsSync as existsSync35 } from "fs";
|
|
5468
|
+
import path34 from "path";
|
|
4834
5469
|
import "commander";
|
|
4835
5470
|
import {
|
|
4836
5471
|
aggregateUsage as aggregateUsage2,
|
|
4837
5472
|
buildFrontmatter as buildFrontmatter7,
|
|
4838
|
-
findProjectRoot as
|
|
5473
|
+
findProjectRoot as findProjectRoot35,
|
|
4839
5474
|
loadMemoriesFromDir as loadMemoriesFromDir8,
|
|
4840
5475
|
memoryFilePath as memoryFilePath6,
|
|
4841
5476
|
parseSince as parseSince2,
|
|
4842
5477
|
readUsageEvents as readUsageEvents2,
|
|
4843
|
-
resolveHaivePaths as
|
|
5478
|
+
resolveHaivePaths as resolveHaivePaths32,
|
|
4844
5479
|
serializeMemory as serializeMemory14
|
|
4845
5480
|
} from "@hiveai/core";
|
|
4846
5481
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -4853,8 +5488,8 @@ function registerMemorySuggest(memory2) {
|
|
|
4853
5488
|
memory2.command("suggest").description(
|
|
4854
5489
|
"Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to draft the top-N suggestions as draft memories. They land\n in personal scope by default with status=draft, ready for you to edit and promote."
|
|
4855
5490
|
).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of drafted memories (personal | team)", "personal").option("--auto-save", "draft top-N suggestions as draft memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
4856
|
-
const root =
|
|
4857
|
-
const paths =
|
|
5491
|
+
const root = findProjectRoot35(opts.dir);
|
|
5492
|
+
const paths = resolveHaivePaths32(root);
|
|
4858
5493
|
const events = await readUsageEvents2(paths);
|
|
4859
5494
|
if (events.length === 0) {
|
|
4860
5495
|
if (opts.json) {
|
|
@@ -4900,7 +5535,7 @@ function registerMemorySuggest(memory2) {
|
|
|
4900
5535
|
}
|
|
4901
5536
|
const created = [];
|
|
4902
5537
|
const skipped = [];
|
|
4903
|
-
const existing =
|
|
5538
|
+
const existing = existsSync35(paths.memoriesDir) ? await loadMemoriesFromDir8(paths.memoriesDir) : [];
|
|
4904
5539
|
for (const s of top) {
|
|
4905
5540
|
const slug = slugify(s.query);
|
|
4906
5541
|
if (!slug) {
|
|
@@ -4923,13 +5558,13 @@ function registerMemorySuggest(memory2) {
|
|
|
4923
5558
|
fm.status = "draft";
|
|
4924
5559
|
const body = renderTemplate(s);
|
|
4925
5560
|
const file = memoryFilePath6(paths, fm.scope, fm.id, fm.module);
|
|
4926
|
-
await
|
|
4927
|
-
if (
|
|
4928
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
5561
|
+
await mkdir14(path34.dirname(file), { recursive: true });
|
|
5562
|
+
if (existsSync35(file)) {
|
|
5563
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path34.relative(root, file)}` });
|
|
4929
5564
|
continue;
|
|
4930
5565
|
}
|
|
4931
|
-
await
|
|
4932
|
-
created.push({ id: fm.id, file:
|
|
5566
|
+
await writeFile19(file, serializeMemory14({ frontmatter: fm, body }), "utf8");
|
|
5567
|
+
created.push({ id: fm.id, file: path34.relative(root, file), query: s.query });
|
|
4933
5568
|
}
|
|
4934
5569
|
if (opts.json) {
|
|
4935
5570
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -4937,10 +5572,10 @@ function registerMemorySuggest(memory2) {
|
|
|
4937
5572
|
}
|
|
4938
5573
|
for (const c of created) {
|
|
4939
5574
|
ui.success(`Drafted ${c.id} \u2192 ${c.file}`);
|
|
4940
|
-
console.log(` ${ui.dim("from query:")} ${
|
|
5575
|
+
console.log(` ${ui.dim("from query:")} ${truncate2(c.query, 60)}`);
|
|
4941
5576
|
}
|
|
4942
5577
|
for (const s of skipped) {
|
|
4943
|
-
ui.warn(`Skipped: ${
|
|
5578
|
+
ui.warn(`Skipped: ${truncate2(s.query, 50)} \u2014 ${s.reason}`);
|
|
4944
5579
|
}
|
|
4945
5580
|
if (created.length > 0) {
|
|
4946
5581
|
console.log();
|
|
@@ -4964,7 +5599,7 @@ function registerMemorySuggest(memory2) {
|
|
|
4964
5599
|
}
|
|
4965
5600
|
for (const s of suggestions.slice(0, 30)) {
|
|
4966
5601
|
console.log(
|
|
4967
|
-
` ${ui.bold(`\xD7${s.count}`)} ${ui.dim(`[${s.tools.join(",")}]`)} ${
|
|
5602
|
+
` ${ui.bold(`\xD7${s.count}`)} ${ui.dim(`[${s.tools.join(",")}]`)} ${truncate2(s.query, 70)}`
|
|
4968
5603
|
);
|
|
4969
5604
|
console.log(` ${ui.dim("\u2192")} ${s.reason}`);
|
|
4970
5605
|
}
|
|
@@ -5010,28 +5645,28 @@ function renderTemplate(s) {
|
|
|
5010
5645
|
`- **Why** \u2014 the rationale or root cause`,
|
|
5011
5646
|
`- **How to apply** \u2014 what an agent should do when this comes up again`,
|
|
5012
5647
|
``,
|
|
5013
|
-
`Then run \`haive memory promote ${
|
|
5648
|
+
`Then run \`haive memory promote ${truncate2(s.query, 30)}\` to mark it validated.`
|
|
5014
5649
|
].join("\n");
|
|
5015
5650
|
}
|
|
5016
5651
|
function slugify(s) {
|
|
5017
5652
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
5018
5653
|
}
|
|
5019
|
-
function
|
|
5654
|
+
function truncate2(text, max) {
|
|
5020
5655
|
if (text.length <= max) return text;
|
|
5021
5656
|
return text.slice(0, max - 1) + "\u2026";
|
|
5022
5657
|
}
|
|
5023
5658
|
|
|
5024
5659
|
// src/commands/memory-archive.ts
|
|
5025
|
-
import { existsSync as
|
|
5026
|
-
import { writeFile as
|
|
5027
|
-
import
|
|
5660
|
+
import { existsSync as existsSync36 } from "fs";
|
|
5661
|
+
import { writeFile as writeFile20 } from "fs/promises";
|
|
5662
|
+
import path35 from "path";
|
|
5028
5663
|
import "commander";
|
|
5029
5664
|
import {
|
|
5030
|
-
findProjectRoot as
|
|
5665
|
+
findProjectRoot as findProjectRoot36,
|
|
5031
5666
|
getUsage as getUsage9,
|
|
5032
5667
|
loadMemoriesFromDir as loadMemoriesFromDir9,
|
|
5033
|
-
loadUsageIndex as
|
|
5034
|
-
resolveHaivePaths as
|
|
5668
|
+
loadUsageIndex as loadUsageIndex13,
|
|
5669
|
+
resolveHaivePaths as resolveHaivePaths33,
|
|
5035
5670
|
serializeMemory as serializeMemory15
|
|
5036
5671
|
} from "@hiveai/core";
|
|
5037
5672
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -5039,9 +5674,9 @@ function registerMemoryArchive(memory2) {
|
|
|
5039
5674
|
memory2.command("archive").description(
|
|
5040
5675
|
"Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
|
|
5041
5676
|
).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) => {
|
|
5042
|
-
const root =
|
|
5043
|
-
const paths =
|
|
5044
|
-
if (!
|
|
5677
|
+
const root = findProjectRoot36(opts.dir);
|
|
5678
|
+
const paths = resolveHaivePaths33(root);
|
|
5679
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
5045
5680
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
5046
5681
|
process.exitCode = 1;
|
|
5047
5682
|
return;
|
|
@@ -5054,7 +5689,7 @@ function registerMemoryArchive(memory2) {
|
|
|
5054
5689
|
}
|
|
5055
5690
|
const cutoff = Date.now() - minDays * MS_PER_DAY;
|
|
5056
5691
|
const all = await loadMemoriesFromDir9(paths.memoriesDir);
|
|
5057
|
-
const usage = await
|
|
5692
|
+
const usage = await loadUsageIndex13(paths);
|
|
5058
5693
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
5059
5694
|
const candidates = [];
|
|
5060
5695
|
for (const { memory: mem, filePath } of all) {
|
|
@@ -5062,7 +5697,7 @@ function registerMemoryArchive(memory2) {
|
|
|
5062
5697
|
if (typeFilter && fm.type !== typeFilter) continue;
|
|
5063
5698
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
5064
5699
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
5065
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
5700
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync36(path35.join(paths.root, p)));
|
|
5066
5701
|
const isAnchorless = !hasAnyAnchor;
|
|
5067
5702
|
if (!isAnchorless && !allPathsGone) continue;
|
|
5068
5703
|
const u = getUsage9(usage, fm.id);
|
|
@@ -5110,7 +5745,7 @@ function registerMemoryArchive(memory2) {
|
|
|
5110
5745
|
if (!found) continue;
|
|
5111
5746
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
5112
5747
|
try {
|
|
5113
|
-
await
|
|
5748
|
+
await writeFile20(c.filePath, serializeMemory15({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
5114
5749
|
archived++;
|
|
5115
5750
|
} catch (err) {
|
|
5116
5751
|
if (!opts.json) {
|
|
@@ -5136,30 +5771,30 @@ function parseDays(input) {
|
|
|
5136
5771
|
}
|
|
5137
5772
|
|
|
5138
5773
|
// src/commands/doctor.ts
|
|
5139
|
-
import { existsSync as
|
|
5774
|
+
import { existsSync as existsSync37 } from "fs";
|
|
5140
5775
|
import { stat } from "fs/promises";
|
|
5141
5776
|
import "path";
|
|
5142
5777
|
import "commander";
|
|
5143
5778
|
import {
|
|
5144
5779
|
codeMapPath as codeMapPath2,
|
|
5145
|
-
findProjectRoot as
|
|
5780
|
+
findProjectRoot as findProjectRoot37,
|
|
5146
5781
|
getUsage as getUsage10,
|
|
5147
5782
|
loadCodeMap as loadCodeMap3,
|
|
5148
5783
|
loadConfig as loadConfig4,
|
|
5149
5784
|
loadMemoriesFromDir as loadMemoriesFromDir10,
|
|
5150
|
-
loadUsageIndex as
|
|
5785
|
+
loadUsageIndex as loadUsageIndex14,
|
|
5151
5786
|
readUsageEvents as readUsageEvents3,
|
|
5152
|
-
resolveHaivePaths as
|
|
5787
|
+
resolveHaivePaths as resolveHaivePaths34
|
|
5153
5788
|
} from "@hiveai/core";
|
|
5154
5789
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
5155
5790
|
function registerDoctor(program2) {
|
|
5156
5791
|
program2.command("doctor").description(
|
|
5157
5792
|
"Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to suggest commands you can copy-paste."
|
|
5158
5793
|
).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
5159
|
-
const root =
|
|
5160
|
-
const paths =
|
|
5794
|
+
const root = findProjectRoot37(opts.dir);
|
|
5795
|
+
const paths = resolveHaivePaths34(root);
|
|
5161
5796
|
const findings = [];
|
|
5162
|
-
if (!
|
|
5797
|
+
if (!existsSync37(paths.haiveDir)) {
|
|
5163
5798
|
findings.push({
|
|
5164
5799
|
severity: "error",
|
|
5165
5800
|
code: "not-initialized",
|
|
@@ -5168,7 +5803,7 @@ function registerDoctor(program2) {
|
|
|
5168
5803
|
});
|
|
5169
5804
|
return emit(findings, opts);
|
|
5170
5805
|
}
|
|
5171
|
-
if (!
|
|
5806
|
+
if (!existsSync37(paths.projectContext)) {
|
|
5172
5807
|
findings.push({
|
|
5173
5808
|
severity: "warn",
|
|
5174
5809
|
code: "no-project-context",
|
|
@@ -5176,8 +5811,8 @@ function registerDoctor(program2) {
|
|
|
5176
5811
|
fix: "haive init"
|
|
5177
5812
|
});
|
|
5178
5813
|
} else {
|
|
5179
|
-
const { readFile:
|
|
5180
|
-
const content = await
|
|
5814
|
+
const { readFile: readFile15 } = await import("fs/promises");
|
|
5815
|
+
const content = await readFile15(paths.projectContext, "utf8");
|
|
5181
5816
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
5182
5817
|
if (isTemplate) {
|
|
5183
5818
|
findings.push({
|
|
@@ -5188,7 +5823,7 @@ function registerDoctor(program2) {
|
|
|
5188
5823
|
});
|
|
5189
5824
|
}
|
|
5190
5825
|
}
|
|
5191
|
-
const memories =
|
|
5826
|
+
const memories = existsSync37(paths.memoriesDir) ? await loadMemoriesFromDir10(paths.memoriesDir) : [];
|
|
5192
5827
|
const now = Date.now();
|
|
5193
5828
|
if (memories.length === 0) {
|
|
5194
5829
|
findings.push({
|
|
@@ -5197,7 +5832,7 @@ function registerDoctor(program2) {
|
|
|
5197
5832
|
message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
|
|
5198
5833
|
});
|
|
5199
5834
|
} else {
|
|
5200
|
-
const usage = await
|
|
5835
|
+
const usage = await loadUsageIndex14(paths);
|
|
5201
5836
|
const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
|
|
5202
5837
|
if (stale.length > 0) {
|
|
5203
5838
|
findings.push({
|
|
@@ -5342,22 +5977,22 @@ function isSearchTool(name) {
|
|
|
5342
5977
|
}
|
|
5343
5978
|
|
|
5344
5979
|
// src/commands/playback.ts
|
|
5345
|
-
import { existsSync as
|
|
5980
|
+
import { existsSync as existsSync38 } from "fs";
|
|
5346
5981
|
import "commander";
|
|
5347
5982
|
import {
|
|
5348
|
-
findProjectRoot as
|
|
5983
|
+
findProjectRoot as findProjectRoot38,
|
|
5349
5984
|
loadMemoriesFromDir as loadMemoriesFromDir11,
|
|
5350
5985
|
parseSince as parseSince3,
|
|
5351
5986
|
readUsageEvents as readUsageEvents4,
|
|
5352
|
-
resolveHaivePaths as
|
|
5987
|
+
resolveHaivePaths as resolveHaivePaths35
|
|
5353
5988
|
} from "@hiveai/core";
|
|
5354
5989
|
var MS_PER_MINUTE = 6e4;
|
|
5355
5990
|
function registerPlayback(program2) {
|
|
5356
5991
|
program2.command("playback").description(
|
|
5357
5992
|
"Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
|
|
5358
5993
|
).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
5359
|
-
const root =
|
|
5360
|
-
const paths =
|
|
5994
|
+
const root = findProjectRoot38(opts.dir);
|
|
5995
|
+
const paths = resolveHaivePaths35(root);
|
|
5361
5996
|
const events = await readUsageEvents4(paths);
|
|
5362
5997
|
if (events.length === 0) {
|
|
5363
5998
|
if (opts.json) {
|
|
@@ -5372,7 +6007,7 @@ function registerPlayback(program2) {
|
|
|
5372
6007
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
5373
6008
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
5374
6009
|
const sessions = bucketSessions(filtered, gapMs);
|
|
5375
|
-
const all =
|
|
6010
|
+
const all = existsSync38(paths.memoriesDir) ? await loadMemoriesFromDir11(paths.memoriesDir) : [];
|
|
5376
6011
|
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);
|
|
5377
6012
|
const enriched = sessions.map((s, i) => {
|
|
5378
6013
|
const startMs = Date.parse(s.start);
|
|
@@ -5412,7 +6047,7 @@ function registerPlayback(program2) {
|
|
|
5412
6047
|
if (s.briefing_tasks.length > 0) {
|
|
5413
6048
|
console.log(` ${ui.dim("briefings asked:")}`);
|
|
5414
6049
|
for (const t of s.briefing_tasks) {
|
|
5415
|
-
console.log(` \u2022 ${
|
|
6050
|
+
console.log(` \u2022 ${truncate3(t, 80)}`);
|
|
5416
6051
|
}
|
|
5417
6052
|
}
|
|
5418
6053
|
if (s.memories_created_since > 0) {
|
|
@@ -5453,7 +6088,7 @@ function countTools(events) {
|
|
|
5453
6088
|
for (const e of events) out[e.tool] = (out[e.tool] ?? 0) + 1;
|
|
5454
6089
|
return out;
|
|
5455
6090
|
}
|
|
5456
|
-
function
|
|
6091
|
+
function truncate3(text, max) {
|
|
5457
6092
|
if (text.length <= max) return text;
|
|
5458
6093
|
return text.slice(0, max - 1) + "\u2026";
|
|
5459
6094
|
}
|
|
@@ -5462,8 +6097,8 @@ function truncate2(text, max) {
|
|
|
5462
6097
|
import { spawn as spawn3 } from "child_process";
|
|
5463
6098
|
import "commander";
|
|
5464
6099
|
import {
|
|
5465
|
-
findProjectRoot as
|
|
5466
|
-
resolveHaivePaths as
|
|
6100
|
+
findProjectRoot as findProjectRoot39,
|
|
6101
|
+
resolveHaivePaths as resolveHaivePaths36
|
|
5467
6102
|
} from "@hiveai/core";
|
|
5468
6103
|
import { preCommitCheck } from "@hiveai/mcp";
|
|
5469
6104
|
function registerPrecommit(program2) {
|
|
@@ -5474,8 +6109,8 @@ function registerPrecommit(program2) {
|
|
|
5474
6109
|
"'any' | 'high-confidence' (default) | 'never' (report only)",
|
|
5475
6110
|
"high-confidence"
|
|
5476
6111
|
).option("--no-semantic", "disable semantic search in anti-patterns matching").option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
5477
|
-
const root =
|
|
5478
|
-
const paths =
|
|
6112
|
+
const root = findProjectRoot39(opts.dir);
|
|
6113
|
+
const paths = resolveHaivePaths36(root);
|
|
5479
6114
|
const ctx = { paths };
|
|
5480
6115
|
let diff = "";
|
|
5481
6116
|
let touchedPaths = opts.paths ?? [];
|
|
@@ -5567,8 +6202,8 @@ function runCommand(cmd, args, cwd) {
|
|
|
5567
6202
|
}
|
|
5568
6203
|
|
|
5569
6204
|
// src/index.ts
|
|
5570
|
-
var program = new
|
|
5571
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.
|
|
6205
|
+
var program = new Command40();
|
|
6206
|
+
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.8.0");
|
|
5572
6207
|
registerInit(program);
|
|
5573
6208
|
registerMcp(program);
|
|
5574
6209
|
registerBriefing(program);
|
|
@@ -5576,6 +6211,7 @@ registerTui(program);
|
|
|
5576
6211
|
registerEmbeddings(program);
|
|
5577
6212
|
registerSync(program);
|
|
5578
6213
|
registerInstallHooks(program);
|
|
6214
|
+
registerObserve(program);
|
|
5579
6215
|
registerIndexCode(program);
|
|
5580
6216
|
var memory = program.command("memory").description("Manage memory entries");
|
|
5581
6217
|
registerMemoryAdd(memory);
|