@hiveai/cli 0.7.1 → 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 +1113 -474
- 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
|
});
|
|
@@ -610,12 +886,15 @@ function inferModuleDescriptions(dirs, frameworks = []) {
|
|
|
610
886
|
"shared": "shared code across modules"
|
|
611
887
|
};
|
|
612
888
|
const isNestJS = frameworks.includes("NestJS");
|
|
889
|
+
const isSrcBased = isNestJS || frameworks.some((f) => ["Express", "Fastify", "Hono", "tRPC", "Apollo", "GraphQL"].includes(f));
|
|
890
|
+
const isFrontend = frameworks.some((f) => ["React", "Vue", "Svelte", "SvelteKit", "Astro"].includes(f));
|
|
613
891
|
const srcSubdirs = dirs.filter((d) => d.startsWith("src/") && d.split("/").length === 2);
|
|
614
|
-
if (
|
|
615
|
-
const
|
|
892
|
+
if (isSrcBased && srcSubdirs.length >= 2 || isFrontend && srcSubdirs.length >= 3) {
|
|
893
|
+
const label = isNestJS ? "main source (NestJS feature modules)" : "main source";
|
|
894
|
+
const result = [`- \`src/\` \u2014 ${label}`];
|
|
616
895
|
for (const d of srcSubdirs.slice(0, 12)) {
|
|
617
896
|
const name = d.split("/")[1];
|
|
618
|
-
const desc = known[name.toLowerCase()] ?? "
|
|
897
|
+
const desc = known[name.toLowerCase()] ?? "module";
|
|
619
898
|
result.push(` - \`${name}/\` \u2014 ${desc}`);
|
|
620
899
|
}
|
|
621
900
|
const otherTopLevel = dirs.filter((d) => !d.includes("/") && d !== "src").slice(0, 6);
|
|
@@ -1668,18 +1947,21 @@ jobs:
|
|
|
1668
1947
|
`;
|
|
1669
1948
|
function registerInit(program2) {
|
|
1670
1949
|
program2.command("init").description(
|
|
1671
|
-
"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."
|
|
1672
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(
|
|
1673
1952
|
"--manual",
|
|
1674
1953
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
1675
1954
|
).option(
|
|
1676
1955
|
"--bootstrap",
|
|
1677
|
-
"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)"
|
|
1678
1960
|
).option(
|
|
1679
1961
|
"--stack <stacks>",
|
|
1680
1962
|
`pre-seed validated memory packs for the given stacks (comma-separated).
|
|
1681
1963
|
Supported: ${SUPPORTED_STACKS.join(", ")}.
|
|
1682
|
-
|
|
1964
|
+
Defaults to 'auto' in autopilot mode (detects from package.json). Pass 'none' to disable.`
|
|
1683
1965
|
).option(
|
|
1684
1966
|
"--no-mcp-setup",
|
|
1685
1967
|
"skip auto-configuring haive-mcp in Cursor / VS Code / Claude Code"
|
|
@@ -1687,6 +1969,8 @@ function registerInit(program2) {
|
|
|
1687
1969
|
const root = path7.resolve(opts.dir);
|
|
1688
1970
|
const paths = resolveHaivePaths4(root);
|
|
1689
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;
|
|
1690
1974
|
if (existsSync6(paths.haiveDir)) {
|
|
1691
1975
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
1692
1976
|
}
|
|
@@ -1695,7 +1979,7 @@ function registerInit(program2) {
|
|
|
1695
1979
|
await mkdir3(paths.moduleDir, { recursive: true });
|
|
1696
1980
|
await mkdir3(paths.modulesContextDir, { recursive: true });
|
|
1697
1981
|
if (!existsSync6(paths.projectContext)) {
|
|
1698
|
-
if (
|
|
1982
|
+
if (wantBootstrap) {
|
|
1699
1983
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
1700
1984
|
try {
|
|
1701
1985
|
const context = await generateBootstrapContext(root);
|
|
@@ -1724,7 +2008,7 @@ function registerInit(program2) {
|
|
|
1724
2008
|
await writeBridge(root, ".cursorrules");
|
|
1725
2009
|
await writeBridge(root, path7.join(".github", "copilot-instructions.md"));
|
|
1726
2010
|
}
|
|
1727
|
-
const stacksToSeed = await resolveStacksToSeed(root,
|
|
2011
|
+
const stacksToSeed = await resolveStacksToSeed(root, wantStack);
|
|
1728
2012
|
if (stacksToSeed.length > 0) {
|
|
1729
2013
|
let totalSeeded = 0;
|
|
1730
2014
|
for (const stack of stacksToSeed) {
|
|
@@ -1866,11 +2150,106 @@ async function writeBridge(root, relPath) {
|
|
|
1866
2150
|
}
|
|
1867
2151
|
|
|
1868
2152
|
// src/commands/install-hooks.ts
|
|
1869
|
-
import { mkdir as
|
|
1870
|
-
import { existsSync as
|
|
1871
|
-
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";
|
|
1872
2156
|
import "commander";
|
|
1873
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
|
|
1874
2253
|
var HOOK_MARKER = "# hAIve auto-generated";
|
|
1875
2254
|
var POST_MERGE_BODY = `#!/bin/sh
|
|
1876
2255
|
${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
|
|
@@ -1919,50 +2298,192 @@ var HOOKS = [
|
|
|
1919
2298
|
{ name: "post-rewrite", body: POST_MERGE_BODY },
|
|
1920
2299
|
{ name: "pre-push", body: PRE_PUSH_BODY }
|
|
1921
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
|
+
}
|
|
1922
2360
|
function registerInstallHooks(program2) {
|
|
1923
|
-
program2.command("install-hooks").description(
|
|
1924
|
-
"Install
|
|
1925
|
-
).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks").action(async (opts) => {
|
|
1926
|
-
const
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
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`);
|
|
1930
2371
|
process.exitCode = 1;
|
|
1931
|
-
return;
|
|
1932
2372
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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;
|
|
1946
2448
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
+
}
|
|
1955
2476
|
});
|
|
1956
2477
|
}
|
|
1957
2478
|
|
|
1958
2479
|
// src/commands/mcp.ts
|
|
1959
2480
|
import { spawn } from "child_process";
|
|
1960
|
-
import { existsSync as
|
|
2481
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1961
2482
|
import { createRequire } from "module";
|
|
1962
|
-
import
|
|
2483
|
+
import path11 from "path";
|
|
1963
2484
|
import { fileURLToPath } from "url";
|
|
1964
2485
|
import "commander";
|
|
1965
|
-
import { findProjectRoot as
|
|
2486
|
+
import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
|
|
1966
2487
|
var require2 = createRequire(import.meta.url);
|
|
1967
2488
|
function registerMcp(program2) {
|
|
1968
2489
|
program2.command("mcp").description(
|
|
@@ -1979,7 +2500,7 @@ function registerMcp(program2) {
|
|
|
1979
2500
|
VS Code: code --add-mcp '{"name":"haive","command":"haive-mcp",...}'
|
|
1980
2501
|
`
|
|
1981
2502
|
).option("-d, --dir <dir>", "project root (defaults to nearest .ai/ or .git/)").action((opts) => {
|
|
1982
|
-
const root =
|
|
2503
|
+
const root = findProjectRoot8(opts.dir);
|
|
1983
2504
|
const bin = locateMcpBin();
|
|
1984
2505
|
if (!bin) {
|
|
1985
2506
|
ui.error(
|
|
@@ -1997,36 +2518,36 @@ function registerMcp(program2) {
|
|
|
1997
2518
|
function locateMcpBin() {
|
|
1998
2519
|
try {
|
|
1999
2520
|
const pkgPath = require2.resolve("@hiveai/mcp/package.json");
|
|
2000
|
-
const pkgDir =
|
|
2001
|
-
const candidate =
|
|
2002
|
-
if (
|
|
2521
|
+
const pkgDir = path11.dirname(pkgPath);
|
|
2522
|
+
const candidate = path11.join(pkgDir, "dist", "index.js");
|
|
2523
|
+
if (existsSync10(candidate)) return candidate;
|
|
2003
2524
|
} catch {
|
|
2004
2525
|
}
|
|
2005
|
-
const here =
|
|
2006
|
-
const sibling =
|
|
2007
|
-
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;
|
|
2008
2529
|
return null;
|
|
2009
2530
|
}
|
|
2010
2531
|
|
|
2011
2532
|
// src/commands/sync.ts
|
|
2012
2533
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
2013
|
-
import { readFile as
|
|
2014
|
-
import { existsSync as
|
|
2015
|
-
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";
|
|
2016
2537
|
import "commander";
|
|
2017
2538
|
import {
|
|
2018
2539
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
2019
2540
|
buildFrontmatter as buildFrontmatter2,
|
|
2020
|
-
findProjectRoot as
|
|
2541
|
+
findProjectRoot as findProjectRoot9,
|
|
2021
2542
|
getUsage,
|
|
2022
2543
|
isAutoPromoteEligible,
|
|
2023
2544
|
isDecaying,
|
|
2024
2545
|
loadCodeMap as loadCodeMap2,
|
|
2025
2546
|
loadConfig,
|
|
2026
2547
|
loadMemoriesFromDir as loadMemoriesFromDir2,
|
|
2027
|
-
loadUsageIndex,
|
|
2548
|
+
loadUsageIndex as loadUsageIndex2,
|
|
2028
2549
|
pullCrossRepoSources,
|
|
2029
|
-
resolveHaivePaths as
|
|
2550
|
+
resolveHaivePaths as resolveHaivePaths6,
|
|
2030
2551
|
resolveManifestFiles,
|
|
2031
2552
|
serializeMemory as serializeMemory2,
|
|
2032
2553
|
trackDependencies,
|
|
@@ -2045,9 +2566,9 @@ function registerSync(program2) {
|
|
|
2045
2566
|
"--inject-bridge",
|
|
2046
2567
|
"inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
2047
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) => {
|
|
2048
|
-
const root =
|
|
2049
|
-
const paths =
|
|
2050
|
-
if (!
|
|
2569
|
+
const root = findProjectRoot9(opts.dir);
|
|
2570
|
+
const paths = resolveHaivePaths6(root);
|
|
2571
|
+
if (!existsSync11(paths.memoriesDir)) {
|
|
2051
2572
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
2052
2573
|
process.exitCode = 1;
|
|
2053
2574
|
return;
|
|
@@ -2067,7 +2588,7 @@ function registerSync(program2) {
|
|
|
2067
2588
|
for (const { memory: memory2, filePath } of memories) {
|
|
2068
2589
|
if (memory2.frontmatter.type === "session_recap") {
|
|
2069
2590
|
if (memory2.frontmatter.status === "stale") {
|
|
2070
|
-
await
|
|
2591
|
+
await writeFile6(
|
|
2071
2592
|
filePath,
|
|
2072
2593
|
serializeMemory2({
|
|
2073
2594
|
frontmatter: {
|
|
@@ -2090,7 +2611,7 @@ function registerSync(program2) {
|
|
|
2090
2611
|
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2091
2612
|
if (result.stale) {
|
|
2092
2613
|
if (memory2.frontmatter.status !== "stale") {
|
|
2093
|
-
await
|
|
2614
|
+
await writeFile6(
|
|
2094
2615
|
filePath,
|
|
2095
2616
|
serializeMemory2({
|
|
2096
2617
|
frontmatter: {
|
|
@@ -2106,7 +2627,7 @@ function registerSync(program2) {
|
|
|
2106
2627
|
staleMarked++;
|
|
2107
2628
|
}
|
|
2108
2629
|
} else if (memory2.frontmatter.status === "stale") {
|
|
2109
|
-
await
|
|
2630
|
+
await writeFile6(
|
|
2110
2631
|
filePath,
|
|
2111
2632
|
serializeMemory2({
|
|
2112
2633
|
frontmatter: {
|
|
@@ -2125,7 +2646,7 @@ function registerSync(program2) {
|
|
|
2125
2646
|
}
|
|
2126
2647
|
if (opts.promote !== false) {
|
|
2127
2648
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
2128
|
-
const usage = await
|
|
2649
|
+
const usage = await loadUsageIndex2(paths);
|
|
2129
2650
|
const nowMs = Date.now();
|
|
2130
2651
|
for (const { memory: memory2, filePath } of memories) {
|
|
2131
2652
|
const fm = memory2.frontmatter;
|
|
@@ -2134,7 +2655,7 @@ function registerSync(program2) {
|
|
|
2134
2655
|
minReads: autoPromoteMinReads,
|
|
2135
2656
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
|
|
2136
2657
|
})) {
|
|
2137
|
-
await
|
|
2658
|
+
await writeFile6(
|
|
2138
2659
|
filePath,
|
|
2139
2660
|
serializeMemory2({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
2140
2661
|
"utf8"
|
|
@@ -2145,7 +2666,7 @@ function registerSync(program2) {
|
|
|
2145
2666
|
if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
|
|
2146
2667
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
2147
2668
|
if (ageHours >= autoApproveDelayHours) {
|
|
2148
|
-
await
|
|
2669
|
+
await writeFile6(
|
|
2149
2670
|
filePath,
|
|
2150
2671
|
serializeMemory2({
|
|
2151
2672
|
frontmatter: {
|
|
@@ -2179,7 +2700,7 @@ function registerSync(program2) {
|
|
|
2179
2700
|
);
|
|
2180
2701
|
}
|
|
2181
2702
|
if (opts.injectBridge) {
|
|
2182
|
-
const bridgeFile = opts.bridgeFile ?
|
|
2703
|
+
const bridgeFile = opts.bridgeFile ? path12.resolve(opts.bridgeFile) : path12.join(root, "CLAUDE.md");
|
|
2183
2704
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
2184
2705
|
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
2185
2706
|
}
|
|
@@ -2199,7 +2720,7 @@ function registerSync(program2) {
|
|
|
2199
2720
|
}
|
|
2200
2721
|
if (!opts.quiet) {
|
|
2201
2722
|
const allForDecay = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
2202
|
-
const usageForDecay = await
|
|
2723
|
+
const usageForDecay = await loadUsageIndex2(paths);
|
|
2203
2724
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
2204
2725
|
const fm = memory2.frontmatter;
|
|
2205
2726
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -2285,10 +2806,10 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
2285
2806
|
paths: [result.file],
|
|
2286
2807
|
topic: `dep-bump-${slugParts}`
|
|
2287
2808
|
});
|
|
2288
|
-
const teamDir =
|
|
2289
|
-
await
|
|
2290
|
-
await
|
|
2291
|
-
|
|
2809
|
+
const teamDir = path12.join(paths.memoriesDir, "team");
|
|
2810
|
+
await mkdir7(teamDir, { recursive: true });
|
|
2811
|
+
await writeFile6(
|
|
2812
|
+
path12.join(teamDir, `${fm.id}.md`),
|
|
2292
2813
|
serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
2293
2814
|
"utf8"
|
|
2294
2815
|
);
|
|
@@ -2352,10 +2873,10 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
2352
2873
|
paths: [diff.file],
|
|
2353
2874
|
topic: `contract-breaking-${diff.contract}`
|
|
2354
2875
|
});
|
|
2355
|
-
const teamDir =
|
|
2356
|
-
await
|
|
2357
|
-
await
|
|
2358
|
-
|
|
2876
|
+
const teamDir = path12.join(paths.memoriesDir, "team");
|
|
2877
|
+
await mkdir7(teamDir, { recursive: true });
|
|
2878
|
+
await writeFile6(
|
|
2879
|
+
path12.join(teamDir, `${fm.id}.md`),
|
|
2359
2880
|
serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
2360
2881
|
"utf8"
|
|
2361
2882
|
);
|
|
@@ -2416,7 +2937,7 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
2416
2937
|
});
|
|
2417
2938
|
}
|
|
2418
2939
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
2419
|
-
if (!
|
|
2940
|
+
if (!existsSync11(memoriesDir)) return;
|
|
2420
2941
|
const all = await loadMemoriesFromDir2(memoriesDir);
|
|
2421
2942
|
const top = all.filter(({ memory: memory2 }) => {
|
|
2422
2943
|
const s = memory2.frontmatter.status;
|
|
@@ -2441,17 +2962,17 @@ ${m.memory.body.trim()}`;
|
|
|
2441
2962
|
` + block + `
|
|
2442
2963
|
|
|
2443
2964
|
${BRIDGE_END}`;
|
|
2444
|
-
const fileExists =
|
|
2445
|
-
let existing = fileExists ? await
|
|
2965
|
+
const fileExists = existsSync11(bridgeFile);
|
|
2966
|
+
let existing = fileExists ? await readFile7(bridgeFile, "utf8") : "";
|
|
2446
2967
|
existing = existing.replace(/\r\n/g, "\n");
|
|
2447
2968
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
2448
2969
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
2449
2970
|
if (startIdx !== -1 && endIdx === -1) {
|
|
2450
|
-
ui.warn(`${
|
|
2971
|
+
ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
2451
2972
|
return;
|
|
2452
2973
|
}
|
|
2453
2974
|
if (startIdx === -1 && endIdx !== -1) {
|
|
2454
|
-
ui.warn(`${
|
|
2975
|
+
ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
2455
2976
|
return;
|
|
2456
2977
|
}
|
|
2457
2978
|
let updated;
|
|
@@ -2459,14 +2980,14 @@ ${BRIDGE_END}`;
|
|
|
2459
2980
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
2460
2981
|
} else {
|
|
2461
2982
|
if (!fileExists && !quiet) {
|
|
2462
|
-
ui.info(`Creating ${
|
|
2983
|
+
ui.info(`Creating ${path12.relative(root, bridgeFile)} with haive memory block.`);
|
|
2463
2984
|
}
|
|
2464
2985
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
2465
2986
|
}
|
|
2466
|
-
await
|
|
2987
|
+
await writeFile6(bridgeFile, updated, "utf8");
|
|
2467
2988
|
if (!quiet) {
|
|
2468
2989
|
console.log(
|
|
2469
|
-
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)}`)
|
|
2470
2991
|
);
|
|
2471
2992
|
}
|
|
2472
2993
|
}
|
|
@@ -2491,17 +3012,17 @@ function collectSinceChanges(root, ref) {
|
|
|
2491
3012
|
|
|
2492
3013
|
// src/commands/memory-add.ts
|
|
2493
3014
|
import { createHash } from "crypto";
|
|
2494
|
-
import { mkdir as
|
|
2495
|
-
import { existsSync as
|
|
2496
|
-
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";
|
|
2497
3018
|
import "commander";
|
|
2498
3019
|
import {
|
|
2499
3020
|
buildFrontmatter as buildFrontmatter3,
|
|
2500
|
-
findProjectRoot as
|
|
3021
|
+
findProjectRoot as findProjectRoot10,
|
|
2501
3022
|
inferModulesFromPaths,
|
|
2502
3023
|
loadMemoriesFromDir as loadMemoriesFromDir3,
|
|
2503
3024
|
memoryFilePath as memoryFilePath2,
|
|
2504
|
-
resolveHaivePaths as
|
|
3025
|
+
resolveHaivePaths as resolveHaivePaths7,
|
|
2505
3026
|
serializeMemory as serializeMemory3
|
|
2506
3027
|
} from "@hiveai/core";
|
|
2507
3028
|
function registerMemoryAdd(memory2) {
|
|
@@ -2529,9 +3050,9 @@ function registerMemoryAdd(memory2) {
|
|
|
2529
3050
|
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
2530
3051
|
`
|
|
2531
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) => {
|
|
2532
|
-
const root =
|
|
2533
|
-
const paths =
|
|
2534
|
-
if (!
|
|
3053
|
+
const root = findProjectRoot10(opts.dir);
|
|
3054
|
+
const paths = resolveHaivePaths7(root);
|
|
3055
|
+
if (!existsSync12(paths.haiveDir)) {
|
|
2535
3056
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
2536
3057
|
process.exitCode = 1;
|
|
2537
3058
|
return;
|
|
@@ -2542,7 +3063,7 @@ function registerMemoryAdd(memory2) {
|
|
|
2542
3063
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
|
|
2543
3064
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
2544
3065
|
if (anchorPaths.length > 0) {
|
|
2545
|
-
const missing = anchorPaths.filter((p) => !
|
|
3066
|
+
const missing = anchorPaths.filter((p) => !existsSync12(path13.resolve(root, p)));
|
|
2546
3067
|
if (missing.length > 0) {
|
|
2547
3068
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
2548
3069
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -2554,12 +3075,12 @@ function registerMemoryAdd(memory2) {
|
|
|
2554
3075
|
const title = opts.title ?? opts.slug;
|
|
2555
3076
|
let body;
|
|
2556
3077
|
if (opts.bodyFile !== void 0) {
|
|
2557
|
-
if (!
|
|
3078
|
+
if (!existsSync12(opts.bodyFile)) {
|
|
2558
3079
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
2559
3080
|
process.exitCode = 1;
|
|
2560
3081
|
return;
|
|
2561
3082
|
}
|
|
2562
|
-
const fileContent = await
|
|
3083
|
+
const fileContent = await readFile8(opts.bodyFile, "utf8");
|
|
2563
3084
|
body = opts.title ? `# ${opts.title}
|
|
2564
3085
|
|
|
2565
3086
|
${fileContent.trim()}
|
|
@@ -2575,7 +3096,7 @@ TODO \u2014 write the memory body.
|
|
|
2575
3096
|
`;
|
|
2576
3097
|
}
|
|
2577
3098
|
const scope = opts.scope ?? "personal";
|
|
2578
|
-
if (
|
|
3099
|
+
if (existsSync12(paths.memoriesDir)) {
|
|
2579
3100
|
const incomingHash = createHash("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
2580
3101
|
const allForHash = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
2581
3102
|
const hashDup = allForHash.find(
|
|
@@ -2588,7 +3109,7 @@ TODO \u2014 write the memory body.
|
|
|
2588
3109
|
return;
|
|
2589
3110
|
}
|
|
2590
3111
|
}
|
|
2591
|
-
if (opts.topic &&
|
|
3112
|
+
if (opts.topic && existsSync12(paths.memoriesDir)) {
|
|
2592
3113
|
const existing = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
2593
3114
|
const topicMatch = existing.find(
|
|
2594
3115
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
@@ -2606,8 +3127,8 @@ TODO \u2014 write the memory body.
|
|
|
2606
3127
|
symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
|
|
2607
3128
|
}
|
|
2608
3129
|
};
|
|
2609
|
-
await
|
|
2610
|
-
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)}`);
|
|
2611
3132
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
2612
3133
|
return;
|
|
2613
3134
|
}
|
|
@@ -2626,13 +3147,13 @@ TODO \u2014 write the memory body.
|
|
|
2626
3147
|
topic: opts.topic
|
|
2627
3148
|
});
|
|
2628
3149
|
const file = memoryFilePath2(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
2629
|
-
await
|
|
2630
|
-
if (
|
|
3150
|
+
await mkdir8(path13.dirname(file), { recursive: true });
|
|
3151
|
+
if (existsSync12(file)) {
|
|
2631
3152
|
ui.error(`Memory already exists at ${file}`);
|
|
2632
3153
|
process.exitCode = 1;
|
|
2633
3154
|
return;
|
|
2634
3155
|
}
|
|
2635
|
-
if (
|
|
3156
|
+
if (existsSync12(paths.memoriesDir)) {
|
|
2636
3157
|
const existing = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
2637
3158
|
const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
2638
3159
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
@@ -2644,8 +3165,8 @@ TODO \u2014 write the memory body.
|
|
|
2644
3165
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
2645
3166
|
}
|
|
2646
3167
|
}
|
|
2647
|
-
await
|
|
2648
|
-
ui.success(`Created ${
|
|
3168
|
+
await writeFile7(file, serializeMemory3({ frontmatter, body }), "utf8");
|
|
3169
|
+
ui.success(`Created ${path13.relative(root, file)}`);
|
|
2649
3170
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
2650
3171
|
if (inferredTags.length > 0) {
|
|
2651
3172
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
@@ -2675,10 +3196,10 @@ function parseCsv2(value) {
|
|
|
2675
3196
|
}
|
|
2676
3197
|
|
|
2677
3198
|
// src/commands/memory-list.ts
|
|
2678
|
-
import { existsSync as
|
|
2679
|
-
import
|
|
3199
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3200
|
+
import path14 from "path";
|
|
2680
3201
|
import "commander";
|
|
2681
|
-
import { findProjectRoot as
|
|
3202
|
+
import { findProjectRoot as findProjectRoot11, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
|
|
2682
3203
|
|
|
2683
3204
|
// src/utils/fs.ts
|
|
2684
3205
|
import {
|
|
@@ -2690,9 +3211,9 @@ import {
|
|
|
2690
3211
|
// src/commands/memory-list.ts
|
|
2691
3212
|
function registerMemoryList(memory2) {
|
|
2692
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) => {
|
|
2693
|
-
const root =
|
|
2694
|
-
const paths =
|
|
2695
|
-
if (!
|
|
3214
|
+
const root = findProjectRoot11(opts.dir);
|
|
3215
|
+
const paths = resolveHaivePaths8(root);
|
|
3216
|
+
if (!existsSync13(paths.memoriesDir)) {
|
|
2696
3217
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
2697
3218
|
process.exitCode = 1;
|
|
2698
3219
|
return;
|
|
@@ -2724,7 +3245,7 @@ function registerMemoryList(memory2) {
|
|
|
2724
3245
|
console.log(
|
|
2725
3246
|
`${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
|
|
2726
3247
|
);
|
|
2727
|
-
console.log(` ${ui.dim(
|
|
3248
|
+
console.log(` ${ui.dim(path14.relative(root, filePath))}`);
|
|
2728
3249
|
}
|
|
2729
3250
|
console.log(ui.dim(`
|
|
2730
3251
|
${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
|
|
@@ -2759,21 +3280,21 @@ function matchesFilters(loaded, opts) {
|
|
|
2759
3280
|
}
|
|
2760
3281
|
|
|
2761
3282
|
// src/commands/memory-promote.ts
|
|
2762
|
-
import { mkdir as
|
|
2763
|
-
import { existsSync as
|
|
2764
|
-
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";
|
|
2765
3286
|
import "commander";
|
|
2766
3287
|
import {
|
|
2767
|
-
findProjectRoot as
|
|
3288
|
+
findProjectRoot as findProjectRoot12,
|
|
2768
3289
|
memoryFilePath as memoryFilePath3,
|
|
2769
|
-
resolveHaivePaths as
|
|
3290
|
+
resolveHaivePaths as resolveHaivePaths9,
|
|
2770
3291
|
serializeMemory as serializeMemory4
|
|
2771
3292
|
} from "@hiveai/core";
|
|
2772
3293
|
function registerMemoryPromote(memory2) {
|
|
2773
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) => {
|
|
2774
|
-
const root =
|
|
2775
|
-
const paths =
|
|
2776
|
-
if (!
|
|
3295
|
+
const root = findProjectRoot12(opts.dir);
|
|
3296
|
+
const paths = resolveHaivePaths9(root);
|
|
3297
|
+
if (!existsSync14(paths.memoriesDir)) {
|
|
2777
3298
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
2778
3299
|
process.exitCode = 1;
|
|
2779
3300
|
return;
|
|
@@ -2808,30 +3329,30 @@ function registerMemoryPromote(memory2) {
|
|
|
2808
3329
|
body: found.memory.body
|
|
2809
3330
|
};
|
|
2810
3331
|
const newPath = memoryFilePath3(paths, "team", updated.frontmatter.id);
|
|
2811
|
-
await
|
|
2812
|
-
await
|
|
3332
|
+
await mkdir9(path15.dirname(newPath), { recursive: true });
|
|
3333
|
+
await writeFile8(newPath, serializeMemory4(updated), "utf8");
|
|
2813
3334
|
await unlink(found.filePath);
|
|
2814
3335
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
2815
|
-
ui.info(`Now at ${
|
|
3336
|
+
ui.info(`Now at ${path15.relative(root, newPath)}`);
|
|
2816
3337
|
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
2817
3338
|
});
|
|
2818
3339
|
}
|
|
2819
3340
|
|
|
2820
3341
|
// src/commands/memory-approve.ts
|
|
2821
|
-
import { existsSync as
|
|
2822
|
-
import { writeFile as
|
|
2823
|
-
import
|
|
3342
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3343
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
3344
|
+
import path16 from "path";
|
|
2824
3345
|
import "commander";
|
|
2825
3346
|
import {
|
|
2826
|
-
findProjectRoot as
|
|
2827
|
-
resolveHaivePaths as
|
|
3347
|
+
findProjectRoot as findProjectRoot13,
|
|
3348
|
+
resolveHaivePaths as resolveHaivePaths10,
|
|
2828
3349
|
serializeMemory as serializeMemory5
|
|
2829
3350
|
} from "@hiveai/core";
|
|
2830
3351
|
function registerMemoryApprove(memory2) {
|
|
2831
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) => {
|
|
2832
|
-
const root =
|
|
2833
|
-
const paths =
|
|
2834
|
-
if (!
|
|
3353
|
+
const root = findProjectRoot13(opts.dir);
|
|
3354
|
+
const paths = resolveHaivePaths10(root);
|
|
3355
|
+
if (!existsSync15(paths.memoriesDir)) {
|
|
2835
3356
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2836
3357
|
process.exitCode = 1;
|
|
2837
3358
|
return;
|
|
@@ -2853,7 +3374,7 @@ function registerMemoryApprove(memory2) {
|
|
|
2853
3374
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
2854
3375
|
body: found2.memory.body
|
|
2855
3376
|
};
|
|
2856
|
-
await
|
|
3377
|
+
await writeFile9(found2.filePath, serializeMemory5(next2), "utf8");
|
|
2857
3378
|
count++;
|
|
2858
3379
|
}
|
|
2859
3380
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -2882,27 +3403,27 @@ function registerMemoryApprove(memory2) {
|
|
|
2882
3403
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
2883
3404
|
body: found.memory.body
|
|
2884
3405
|
};
|
|
2885
|
-
await
|
|
3406
|
+
await writeFile9(found.filePath, serializeMemory5(next), "utf8");
|
|
2886
3407
|
ui.success(`Approved ${id} (status=validated)`);
|
|
2887
|
-
ui.info(
|
|
3408
|
+
ui.info(path16.relative(root, found.filePath));
|
|
2888
3409
|
});
|
|
2889
3410
|
}
|
|
2890
3411
|
|
|
2891
3412
|
// src/commands/memory-update.ts
|
|
2892
|
-
import { writeFile as
|
|
2893
|
-
import { existsSync as
|
|
2894
|
-
import
|
|
3413
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
3414
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3415
|
+
import path17 from "path";
|
|
2895
3416
|
import "commander";
|
|
2896
3417
|
import {
|
|
2897
|
-
findProjectRoot as
|
|
2898
|
-
resolveHaivePaths as
|
|
3418
|
+
findProjectRoot as findProjectRoot14,
|
|
3419
|
+
resolveHaivePaths as resolveHaivePaths11,
|
|
2899
3420
|
serializeMemory as serializeMemory6
|
|
2900
3421
|
} from "@hiveai/core";
|
|
2901
3422
|
function registerMemoryUpdate(memory2) {
|
|
2902
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) => {
|
|
2903
|
-
const root =
|
|
2904
|
-
const paths =
|
|
2905
|
-
if (!
|
|
3424
|
+
const root = findProjectRoot14(opts.dir);
|
|
3425
|
+
const paths = resolveHaivePaths11(root);
|
|
3426
|
+
if (!existsSync16(paths.memoriesDir)) {
|
|
2906
3427
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
2907
3428
|
process.exitCode = 1;
|
|
2908
3429
|
return;
|
|
@@ -2949,12 +3470,12 @@ function registerMemoryUpdate(memory2) {
|
|
|
2949
3470
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
2950
3471
|
return;
|
|
2951
3472
|
}
|
|
2952
|
-
await
|
|
3473
|
+
await writeFile10(
|
|
2953
3474
|
loaded.filePath,
|
|
2954
3475
|
serializeMemory6({ frontmatter: newFrontmatter, body: newBody }),
|
|
2955
3476
|
"utf8"
|
|
2956
3477
|
);
|
|
2957
|
-
ui.success(`Updated ${
|
|
3478
|
+
ui.success(`Updated ${path17.relative(root, loaded.filePath)}`);
|
|
2958
3479
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
2959
3480
|
});
|
|
2960
3481
|
}
|
|
@@ -2973,17 +3494,17 @@ function parseCsv3(value) {
|
|
|
2973
3494
|
}
|
|
2974
3495
|
|
|
2975
3496
|
// src/commands/memory-auto-promote.ts
|
|
2976
|
-
import { writeFile as
|
|
2977
|
-
import { existsSync as
|
|
2978
|
-
import
|
|
3497
|
+
import { writeFile as writeFile11 } from "fs/promises";
|
|
3498
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3499
|
+
import path18 from "path";
|
|
2979
3500
|
import "commander";
|
|
2980
3501
|
import {
|
|
2981
3502
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
2982
|
-
findProjectRoot as
|
|
3503
|
+
findProjectRoot as findProjectRoot15,
|
|
2983
3504
|
getUsage as getUsage2,
|
|
2984
3505
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
2985
|
-
loadUsageIndex as
|
|
2986
|
-
resolveHaivePaths as
|
|
3506
|
+
loadUsageIndex as loadUsageIndex3,
|
|
3507
|
+
resolveHaivePaths as resolveHaivePaths12,
|
|
2987
3508
|
serializeMemory as serializeMemory7
|
|
2988
3509
|
} from "@hiveai/core";
|
|
2989
3510
|
function registerMemoryAutoPromote(memory2) {
|
|
@@ -2992,9 +3513,9 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
2992
3513
|
"memories with more rejections than this are skipped",
|
|
2993
3514
|
String(DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
|
|
2994
3515
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2995
|
-
const root =
|
|
2996
|
-
const paths =
|
|
2997
|
-
if (!
|
|
3516
|
+
const root = findProjectRoot15(opts.dir);
|
|
3517
|
+
const paths = resolveHaivePaths12(root);
|
|
3518
|
+
if (!existsSync17(paths.memoriesDir)) {
|
|
2998
3519
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2999
3520
|
process.exitCode = 1;
|
|
3000
3521
|
return;
|
|
@@ -3004,7 +3525,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
3004
3525
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
|
|
3005
3526
|
};
|
|
3006
3527
|
const memories = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3007
|
-
const usage = await
|
|
3528
|
+
const usage = await loadUsageIndex3(paths);
|
|
3008
3529
|
const eligible = memories.filter(
|
|
3009
3530
|
({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
|
|
3010
3531
|
);
|
|
@@ -3020,13 +3541,13 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
3020
3541
|
console.log(
|
|
3021
3542
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
3022
3543
|
);
|
|
3023
|
-
console.log(` ${ui.dim(
|
|
3544
|
+
console.log(` ${ui.dim(path18.relative(root, filePath))}`);
|
|
3024
3545
|
if (opts.apply) {
|
|
3025
3546
|
const next = {
|
|
3026
3547
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
3027
3548
|
body: mem.body
|
|
3028
3549
|
};
|
|
3029
|
-
await
|
|
3550
|
+
await writeFile11(filePath, serializeMemory7(next), "utf8");
|
|
3030
3551
|
written++;
|
|
3031
3552
|
}
|
|
3032
3553
|
}
|
|
@@ -3037,20 +3558,20 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
3037
3558
|
|
|
3038
3559
|
// src/commands/memory-edit.ts
|
|
3039
3560
|
import { spawn as spawn2 } from "child_process";
|
|
3040
|
-
import { existsSync as
|
|
3041
|
-
import { readFile as
|
|
3042
|
-
import
|
|
3561
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3562
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3563
|
+
import path19 from "path";
|
|
3043
3564
|
import "commander";
|
|
3044
3565
|
import {
|
|
3045
|
-
findProjectRoot as
|
|
3566
|
+
findProjectRoot as findProjectRoot16,
|
|
3046
3567
|
parseMemory,
|
|
3047
|
-
resolveHaivePaths as
|
|
3568
|
+
resolveHaivePaths as resolveHaivePaths13
|
|
3048
3569
|
} from "@hiveai/core";
|
|
3049
3570
|
function registerMemoryEdit(memory2) {
|
|
3050
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) => {
|
|
3051
|
-
const root =
|
|
3052
|
-
const paths =
|
|
3053
|
-
if (!
|
|
3572
|
+
const root = findProjectRoot16(opts.dir);
|
|
3573
|
+
const paths = resolveHaivePaths13(root);
|
|
3574
|
+
if (!existsSync18(paths.memoriesDir)) {
|
|
3054
3575
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3055
3576
|
process.exitCode = 1;
|
|
3056
3577
|
return;
|
|
@@ -3063,13 +3584,13 @@ function registerMemoryEdit(memory2) {
|
|
|
3063
3584
|
return;
|
|
3064
3585
|
}
|
|
3065
3586
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
3066
|
-
ui.info(`Opening ${
|
|
3587
|
+
ui.info(`Opening ${path19.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
3067
3588
|
const code = await runEditor(editor, found.filePath);
|
|
3068
3589
|
if (code !== 0) {
|
|
3069
3590
|
ui.warn(`Editor exited with status ${code}.`);
|
|
3070
3591
|
}
|
|
3071
3592
|
try {
|
|
3072
|
-
const fresh = await
|
|
3593
|
+
const fresh = await readFile9(found.filePath, "utf8");
|
|
3073
3594
|
parseMemory(fresh);
|
|
3074
3595
|
ui.success("Memory still parses cleanly.");
|
|
3075
3596
|
} catch (err) {
|
|
@@ -3090,29 +3611,29 @@ function runEditor(editor, file) {
|
|
|
3090
3611
|
}
|
|
3091
3612
|
|
|
3092
3613
|
// src/commands/memory-for-files.ts
|
|
3093
|
-
import { existsSync as
|
|
3094
|
-
import
|
|
3095
|
-
import "commander";
|
|
3614
|
+
import { existsSync as existsSync19 } from "fs";
|
|
3615
|
+
import path20 from "path";
|
|
3616
|
+
import "commander";
|
|
3096
3617
|
import {
|
|
3097
3618
|
deriveConfidence,
|
|
3098
|
-
findProjectRoot as
|
|
3619
|
+
findProjectRoot as findProjectRoot17,
|
|
3099
3620
|
getUsage as getUsage3,
|
|
3100
3621
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
3101
|
-
loadUsageIndex as
|
|
3622
|
+
loadUsageIndex as loadUsageIndex4,
|
|
3102
3623
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
3103
|
-
resolveHaivePaths as
|
|
3624
|
+
resolveHaivePaths as resolveHaivePaths14
|
|
3104
3625
|
} from "@hiveai/core";
|
|
3105
3626
|
function registerMemoryForFiles(memory2) {
|
|
3106
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) => {
|
|
3107
|
-
const root =
|
|
3108
|
-
const paths =
|
|
3109
|
-
if (!
|
|
3628
|
+
const root = findProjectRoot17(opts.dir);
|
|
3629
|
+
const paths = resolveHaivePaths14(root);
|
|
3630
|
+
if (!existsSync19(paths.memoriesDir)) {
|
|
3110
3631
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3111
3632
|
process.exitCode = 1;
|
|
3112
3633
|
return;
|
|
3113
3634
|
}
|
|
3114
3635
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3115
|
-
const usage = await
|
|
3636
|
+
const usage = await loadUsageIndex4(paths);
|
|
3116
3637
|
const inferred = inferModulesFromPaths2(files);
|
|
3117
3638
|
const byAnchor = [];
|
|
3118
3639
|
const byModule = [];
|
|
@@ -3213,32 +3734,32 @@ function printGroup(root, label, loaded, usage) {
|
|
|
3213
3734
|
const u = getUsage3(usage, fm.id);
|
|
3214
3735
|
const conf = deriveConfidence(fm, u);
|
|
3215
3736
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
3216
|
-
console.log(` ${ui.dim(
|
|
3737
|
+
console.log(` ${ui.dim(path20.relative(root, filePath))}`);
|
|
3217
3738
|
}
|
|
3218
3739
|
}
|
|
3219
3740
|
|
|
3220
3741
|
// src/commands/memory-hot.ts
|
|
3221
|
-
import { existsSync as
|
|
3222
|
-
import
|
|
3742
|
+
import { existsSync as existsSync20 } from "fs";
|
|
3743
|
+
import path21 from "path";
|
|
3223
3744
|
import "commander";
|
|
3224
3745
|
import {
|
|
3225
|
-
findProjectRoot as
|
|
3746
|
+
findProjectRoot as findProjectRoot18,
|
|
3226
3747
|
getUsage as getUsage4,
|
|
3227
|
-
loadUsageIndex as
|
|
3228
|
-
resolveHaivePaths as
|
|
3748
|
+
loadUsageIndex as loadUsageIndex5,
|
|
3749
|
+
resolveHaivePaths as resolveHaivePaths15
|
|
3229
3750
|
} from "@hiveai/core";
|
|
3230
3751
|
function registerMemoryHot(memory2) {
|
|
3231
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) => {
|
|
3232
|
-
const root =
|
|
3233
|
-
const paths =
|
|
3234
|
-
if (!
|
|
3753
|
+
const root = findProjectRoot18(opts.dir);
|
|
3754
|
+
const paths = resolveHaivePaths15(root);
|
|
3755
|
+
if (!existsSync20(paths.memoriesDir)) {
|
|
3235
3756
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3236
3757
|
process.exitCode = 1;
|
|
3237
3758
|
return;
|
|
3238
3759
|
}
|
|
3239
3760
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
3240
3761
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3241
|
-
const usage = await
|
|
3762
|
+
const usage = await loadUsageIndex5(paths);
|
|
3242
3763
|
const candidates = all.filter(({ memory: mem }) => {
|
|
3243
3764
|
const fm = mem.frontmatter;
|
|
3244
3765
|
if (opts.status && fm.status !== opts.status) return false;
|
|
@@ -3259,7 +3780,7 @@ function registerMemoryHot(memory2) {
|
|
|
3259
3780
|
console.log(
|
|
3260
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}`)}`
|
|
3261
3782
|
);
|
|
3262
|
-
console.log(` ${ui.dim(
|
|
3783
|
+
console.log(` ${ui.dim(path21.relative(root, filePath))}`);
|
|
3263
3784
|
}
|
|
3264
3785
|
ui.info(
|
|
3265
3786
|
`${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
|
|
@@ -3268,15 +3789,15 @@ function registerMemoryHot(memory2) {
|
|
|
3268
3789
|
}
|
|
3269
3790
|
|
|
3270
3791
|
// src/commands/memory-tried.ts
|
|
3271
|
-
import { mkdir as
|
|
3272
|
-
import { existsSync as
|
|
3273
|
-
import
|
|
3792
|
+
import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
|
|
3793
|
+
import { existsSync as existsSync21 } from "fs";
|
|
3794
|
+
import path22 from "path";
|
|
3274
3795
|
import "commander";
|
|
3275
3796
|
import {
|
|
3276
3797
|
buildFrontmatter as buildFrontmatter4,
|
|
3277
|
-
findProjectRoot as
|
|
3798
|
+
findProjectRoot as findProjectRoot19,
|
|
3278
3799
|
memoryFilePath as memoryFilePath4,
|
|
3279
|
-
resolveHaivePaths as
|
|
3800
|
+
resolveHaivePaths as resolveHaivePaths16,
|
|
3280
3801
|
serializeMemory as serializeMemory8
|
|
3281
3802
|
} from "@hiveai/core";
|
|
3282
3803
|
function registerMemoryTried(memory2) {
|
|
@@ -3296,9 +3817,9 @@ function registerMemoryTried(memory2) {
|
|
|
3296
3817
|
--paths packages/cli/src/index.ts
|
|
3297
3818
|
`
|
|
3298
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) => {
|
|
3299
|
-
const root =
|
|
3300
|
-
const paths =
|
|
3301
|
-
if (!
|
|
3820
|
+
const root = findProjectRoot19(opts.dir);
|
|
3821
|
+
const paths = resolveHaivePaths16(root);
|
|
3822
|
+
if (!existsSync21(paths.haiveDir)) {
|
|
3302
3823
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
3303
3824
|
process.exitCode = 1;
|
|
3304
3825
|
return;
|
|
@@ -3321,14 +3842,14 @@ function registerMemoryTried(memory2) {
|
|
|
3321
3842
|
}
|
|
3322
3843
|
const body = lines.join("\n") + "\n";
|
|
3323
3844
|
const file = memoryFilePath4(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
3324
|
-
await
|
|
3325
|
-
if (
|
|
3845
|
+
await mkdir10(path22.dirname(file), { recursive: true });
|
|
3846
|
+
if (existsSync21(file)) {
|
|
3326
3847
|
ui.error(`Memory already exists at ${file}`);
|
|
3327
3848
|
process.exitCode = 1;
|
|
3328
3849
|
return;
|
|
3329
3850
|
}
|
|
3330
|
-
await
|
|
3331
|
-
ui.success(`Recorded: ${
|
|
3851
|
+
await writeFile12(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
3852
|
+
ui.success(`Recorded: ${path22.relative(root, file)}`);
|
|
3332
3853
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
3333
3854
|
});
|
|
3334
3855
|
}
|
|
@@ -3338,26 +3859,26 @@ function parseCsv4(value) {
|
|
|
3338
3859
|
}
|
|
3339
3860
|
|
|
3340
3861
|
// src/commands/memory-pending.ts
|
|
3341
|
-
import { existsSync as
|
|
3342
|
-
import
|
|
3862
|
+
import { existsSync as existsSync22 } from "fs";
|
|
3863
|
+
import path23 from "path";
|
|
3343
3864
|
import "commander";
|
|
3344
3865
|
import {
|
|
3345
|
-
findProjectRoot as
|
|
3866
|
+
findProjectRoot as findProjectRoot20,
|
|
3346
3867
|
getUsage as getUsage5,
|
|
3347
|
-
loadUsageIndex as
|
|
3348
|
-
resolveHaivePaths as
|
|
3868
|
+
loadUsageIndex as loadUsageIndex6,
|
|
3869
|
+
resolveHaivePaths as resolveHaivePaths17
|
|
3349
3870
|
} from "@hiveai/core";
|
|
3350
3871
|
function registerMemoryPending(memory2) {
|
|
3351
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) => {
|
|
3352
|
-
const root =
|
|
3353
|
-
const paths =
|
|
3354
|
-
if (!
|
|
3873
|
+
const root = findProjectRoot20(opts.dir);
|
|
3874
|
+
const paths = resolveHaivePaths17(root);
|
|
3875
|
+
if (!existsSync22(paths.memoriesDir)) {
|
|
3355
3876
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3356
3877
|
process.exitCode = 1;
|
|
3357
3878
|
return;
|
|
3358
3879
|
}
|
|
3359
3880
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3360
|
-
const usage = await
|
|
3881
|
+
const usage = await loadUsageIndex6(paths);
|
|
3361
3882
|
const proposed = all.filter(({ memory: mem }) => {
|
|
3362
3883
|
if (mem.frontmatter.status !== "proposed") return false;
|
|
3363
3884
|
if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
|
|
@@ -3379,31 +3900,31 @@ function registerMemoryPending(memory2) {
|
|
|
3379
3900
|
console.log(
|
|
3380
3901
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
3381
3902
|
);
|
|
3382
|
-
console.log(` ${ui.dim(
|
|
3903
|
+
console.log(` ${ui.dim(path23.relative(root, filePath))}`);
|
|
3383
3904
|
}
|
|
3384
3905
|
ui.info(`${proposed.length} pending`);
|
|
3385
3906
|
});
|
|
3386
3907
|
}
|
|
3387
3908
|
|
|
3388
3909
|
// src/commands/memory-query.ts
|
|
3389
|
-
import { existsSync as
|
|
3390
|
-
import
|
|
3910
|
+
import { existsSync as existsSync23 } from "fs";
|
|
3911
|
+
import path24 from "path";
|
|
3391
3912
|
import "commander";
|
|
3392
3913
|
import {
|
|
3393
3914
|
extractSnippet,
|
|
3394
|
-
findProjectRoot as
|
|
3915
|
+
findProjectRoot as findProjectRoot21,
|
|
3395
3916
|
literalMatchesAllTokens as literalMatchesAllTokens2,
|
|
3396
3917
|
literalMatchesAnyToken as literalMatchesAnyToken2,
|
|
3397
3918
|
pickSnippetNeedle,
|
|
3398
|
-
resolveHaivePaths as
|
|
3919
|
+
resolveHaivePaths as resolveHaivePaths18,
|
|
3399
3920
|
tokenizeQuery as tokenizeQuery2,
|
|
3400
3921
|
trackReads as trackReads2
|
|
3401
3922
|
} from "@hiveai/core";
|
|
3402
3923
|
function registerMemoryQuery(memory2) {
|
|
3403
|
-
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) => {
|
|
3404
|
-
const root =
|
|
3405
|
-
const paths =
|
|
3406
|
-
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)) {
|
|
3407
3928
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
3408
3929
|
process.exitCode = 1;
|
|
3409
3930
|
return;
|
|
@@ -3444,7 +3965,7 @@ function registerMemoryQuery(memory2) {
|
|
|
3444
3965
|
const fm = mem.frontmatter;
|
|
3445
3966
|
const statusBadge = ui.statusBadge(fm.status);
|
|
3446
3967
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
3447
|
-
console.log(` ${ui.dim(
|
|
3968
|
+
console.log(` ${ui.dim(path24.relative(root, filePath))}`);
|
|
3448
3969
|
const snippet = extractSnippet(mem.body, snippetNeedle);
|
|
3449
3970
|
if (snippet) console.log(` ${snippet}`);
|
|
3450
3971
|
}
|
|
@@ -3461,22 +3982,22 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
3461
3982
|
}
|
|
3462
3983
|
|
|
3463
3984
|
// src/commands/memory-reject.ts
|
|
3464
|
-
import { writeFile as
|
|
3465
|
-
import { existsSync as
|
|
3985
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
3986
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3466
3987
|
import "commander";
|
|
3467
3988
|
import {
|
|
3468
|
-
findProjectRoot as
|
|
3469
|
-
loadUsageIndex as
|
|
3989
|
+
findProjectRoot as findProjectRoot22,
|
|
3990
|
+
loadUsageIndex as loadUsageIndex7,
|
|
3470
3991
|
recordRejection,
|
|
3471
|
-
resolveHaivePaths as
|
|
3992
|
+
resolveHaivePaths as resolveHaivePaths19,
|
|
3472
3993
|
saveUsageIndex,
|
|
3473
3994
|
serializeMemory as serializeMemory9
|
|
3474
3995
|
} from "@hiveai/core";
|
|
3475
3996
|
function registerMemoryReject(memory2) {
|
|
3476
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) => {
|
|
3477
|
-
const root =
|
|
3478
|
-
const paths =
|
|
3479
|
-
if (!
|
|
3998
|
+
const root = findProjectRoot22(opts.dir);
|
|
3999
|
+
const paths = resolveHaivePaths19(root);
|
|
4000
|
+
if (!existsSync24(paths.memoriesDir)) {
|
|
3480
4001
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3481
4002
|
process.exitCode = 1;
|
|
3482
4003
|
return;
|
|
@@ -3488,7 +4009,7 @@ function registerMemoryReject(memory2) {
|
|
|
3488
4009
|
process.exitCode = 1;
|
|
3489
4010
|
return;
|
|
3490
4011
|
}
|
|
3491
|
-
await
|
|
4012
|
+
await writeFile13(
|
|
3492
4013
|
loaded.filePath,
|
|
3493
4014
|
serializeMemory9({
|
|
3494
4015
|
frontmatter: {
|
|
@@ -3500,7 +4021,7 @@ function registerMemoryReject(memory2) {
|
|
|
3500
4021
|
}),
|
|
3501
4022
|
"utf8"
|
|
3502
4023
|
);
|
|
3503
|
-
const idx = await
|
|
4024
|
+
const idx = await loadUsageIndex7(paths);
|
|
3504
4025
|
recordRejection(idx, id, opts.reason ?? null);
|
|
3505
4026
|
await saveUsageIndex(paths, idx);
|
|
3506
4027
|
const u = idx.by_id[id];
|
|
@@ -3512,22 +4033,22 @@ function registerMemoryReject(memory2) {
|
|
|
3512
4033
|
}
|
|
3513
4034
|
|
|
3514
4035
|
// src/commands/memory-rm.ts
|
|
3515
|
-
import { existsSync as
|
|
4036
|
+
import { existsSync as existsSync25 } from "fs";
|
|
3516
4037
|
import { unlink as unlink2 } from "fs/promises";
|
|
3517
|
-
import
|
|
4038
|
+
import path25 from "path";
|
|
3518
4039
|
import { createInterface } from "readline/promises";
|
|
3519
4040
|
import "commander";
|
|
3520
4041
|
import {
|
|
3521
|
-
findProjectRoot as
|
|
3522
|
-
loadUsageIndex as
|
|
3523
|
-
resolveHaivePaths as
|
|
4042
|
+
findProjectRoot as findProjectRoot23,
|
|
4043
|
+
loadUsageIndex as loadUsageIndex8,
|
|
4044
|
+
resolveHaivePaths as resolveHaivePaths20,
|
|
3524
4045
|
saveUsageIndex as saveUsageIndex2
|
|
3525
4046
|
} from "@hiveai/core";
|
|
3526
4047
|
function registerMemoryRm(memory2) {
|
|
3527
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) => {
|
|
3528
|
-
const root =
|
|
3529
|
-
const paths =
|
|
3530
|
-
if (!
|
|
4049
|
+
const root = findProjectRoot23(opts.dir);
|
|
4050
|
+
const paths = resolveHaivePaths20(root);
|
|
4051
|
+
if (!existsSync25(paths.memoriesDir)) {
|
|
3531
4052
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3532
4053
|
process.exitCode = 1;
|
|
3533
4054
|
return;
|
|
@@ -3539,7 +4060,7 @@ function registerMemoryRm(memory2) {
|
|
|
3539
4060
|
process.exitCode = 1;
|
|
3540
4061
|
return;
|
|
3541
4062
|
}
|
|
3542
|
-
const rel =
|
|
4063
|
+
const rel = path25.relative(root, found.filePath);
|
|
3543
4064
|
if (!opts.yes) {
|
|
3544
4065
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3545
4066
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -3552,7 +4073,7 @@ function registerMemoryRm(memory2) {
|
|
|
3552
4073
|
await unlink2(found.filePath);
|
|
3553
4074
|
ui.success(`Deleted ${rel}`);
|
|
3554
4075
|
if (!opts.keepUsage) {
|
|
3555
|
-
const idx = await
|
|
4076
|
+
const idx = await loadUsageIndex8(paths);
|
|
3556
4077
|
if (idx.by_id[id]) {
|
|
3557
4078
|
delete idx.by_id[id];
|
|
3558
4079
|
await saveUsageIndex2(paths, idx);
|
|
@@ -3563,22 +4084,22 @@ function registerMemoryRm(memory2) {
|
|
|
3563
4084
|
}
|
|
3564
4085
|
|
|
3565
4086
|
// src/commands/memory-show.ts
|
|
3566
|
-
import { existsSync as
|
|
3567
|
-
import { readFile as
|
|
3568
|
-
import
|
|
4087
|
+
import { existsSync as existsSync26 } from "fs";
|
|
4088
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
4089
|
+
import path26 from "path";
|
|
3569
4090
|
import "commander";
|
|
3570
4091
|
import {
|
|
3571
4092
|
deriveConfidence as deriveConfidence2,
|
|
3572
|
-
findProjectRoot as
|
|
4093
|
+
findProjectRoot as findProjectRoot24,
|
|
3573
4094
|
getUsage as getUsage6,
|
|
3574
|
-
loadUsageIndex as
|
|
3575
|
-
resolveHaivePaths as
|
|
4095
|
+
loadUsageIndex as loadUsageIndex9,
|
|
4096
|
+
resolveHaivePaths as resolveHaivePaths21
|
|
3576
4097
|
} from "@hiveai/core";
|
|
3577
4098
|
function registerMemoryShow(memory2) {
|
|
3578
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) => {
|
|
3579
|
-
const root =
|
|
3580
|
-
const paths =
|
|
3581
|
-
if (!
|
|
4100
|
+
const root = findProjectRoot24(opts.dir);
|
|
4101
|
+
const paths = resolveHaivePaths21(root);
|
|
4102
|
+
if (!existsSync26(paths.memoriesDir)) {
|
|
3582
4103
|
ui.error(`No .ai/memories at ${root}.`);
|
|
3583
4104
|
process.exitCode = 1;
|
|
3584
4105
|
return;
|
|
@@ -3591,11 +4112,11 @@ function registerMemoryShow(memory2) {
|
|
|
3591
4112
|
return;
|
|
3592
4113
|
}
|
|
3593
4114
|
if (opts.raw) {
|
|
3594
|
-
console.log(await
|
|
4115
|
+
console.log(await readFile10(found.filePath, "utf8"));
|
|
3595
4116
|
return;
|
|
3596
4117
|
}
|
|
3597
4118
|
const fm = found.memory.frontmatter;
|
|
3598
|
-
const usage = await
|
|
4119
|
+
const usage = await loadUsageIndex9(paths);
|
|
3599
4120
|
const u = getUsage6(usage, fm.id);
|
|
3600
4121
|
const conf = deriveConfidence2(fm, u);
|
|
3601
4122
|
console.log(ui.bold(fm.id));
|
|
@@ -3607,7 +4128,7 @@ function registerMemoryShow(memory2) {
|
|
|
3607
4128
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
3608
4129
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
3609
4130
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
3610
|
-
console.log(`${ui.dim("file:")} ${
|
|
4131
|
+
console.log(`${ui.dim("file:")} ${path26.relative(root, found.filePath)}`);
|
|
3611
4132
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
3612
4133
|
console.log(ui.dim("anchor:"));
|
|
3613
4134
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -3622,27 +4143,27 @@ function registerMemoryShow(memory2) {
|
|
|
3622
4143
|
}
|
|
3623
4144
|
|
|
3624
4145
|
// src/commands/memory-stats.ts
|
|
3625
|
-
import { existsSync as
|
|
3626
|
-
import
|
|
4146
|
+
import { existsSync as existsSync27 } from "fs";
|
|
4147
|
+
import path27 from "path";
|
|
3627
4148
|
import "commander";
|
|
3628
4149
|
import {
|
|
3629
4150
|
deriveConfidence as deriveConfidence3,
|
|
3630
|
-
findProjectRoot as
|
|
4151
|
+
findProjectRoot as findProjectRoot25,
|
|
3631
4152
|
getUsage as getUsage7,
|
|
3632
|
-
loadUsageIndex as
|
|
3633
|
-
resolveHaivePaths as
|
|
4153
|
+
loadUsageIndex as loadUsageIndex10,
|
|
4154
|
+
resolveHaivePaths as resolveHaivePaths22
|
|
3634
4155
|
} from "@hiveai/core";
|
|
3635
4156
|
function registerMemoryStats(memory2) {
|
|
3636
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) => {
|
|
3637
|
-
const root =
|
|
3638
|
-
const paths =
|
|
3639
|
-
if (!
|
|
4158
|
+
const root = findProjectRoot25(opts.dir);
|
|
4159
|
+
const paths = resolveHaivePaths22(root);
|
|
4160
|
+
if (!existsSync27(paths.memoriesDir)) {
|
|
3640
4161
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
3641
4162
|
process.exitCode = 1;
|
|
3642
4163
|
return;
|
|
3643
4164
|
}
|
|
3644
4165
|
const all = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
3645
|
-
const usage = await
|
|
4166
|
+
const usage = await loadUsageIndex10(paths);
|
|
3646
4167
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
3647
4168
|
if (target.length === 0) {
|
|
3648
4169
|
ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
|
|
@@ -3661,19 +4182,19 @@ function registerMemoryStats(memory2) {
|
|
|
3661
4182
|
console.log(
|
|
3662
4183
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
3663
4184
|
);
|
|
3664
|
-
console.log(` ${ui.dim(
|
|
4185
|
+
console.log(` ${ui.dim(path27.relative(root, filePath))}`);
|
|
3665
4186
|
}
|
|
3666
4187
|
});
|
|
3667
4188
|
}
|
|
3668
4189
|
|
|
3669
4190
|
// src/commands/memory-verify.ts
|
|
3670
|
-
import { writeFile as
|
|
3671
|
-
import { existsSync as
|
|
3672
|
-
import
|
|
4191
|
+
import { writeFile as writeFile14 } from "fs/promises";
|
|
4192
|
+
import { existsSync as existsSync28 } from "fs";
|
|
4193
|
+
import path28 from "path";
|
|
3673
4194
|
import "commander";
|
|
3674
4195
|
import {
|
|
3675
|
-
findProjectRoot as
|
|
3676
|
-
resolveHaivePaths as
|
|
4196
|
+
findProjectRoot as findProjectRoot26,
|
|
4197
|
+
resolveHaivePaths as resolveHaivePaths23,
|
|
3677
4198
|
serializeMemory as serializeMemory10,
|
|
3678
4199
|
verifyAnchor as verifyAnchor2
|
|
3679
4200
|
} from "@hiveai/core";
|
|
@@ -3681,9 +4202,9 @@ function registerMemoryVerify(memory2) {
|
|
|
3681
4202
|
memory2.command("verify").description(
|
|
3682
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"
|
|
3683
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) => {
|
|
3684
|
-
const root =
|
|
3685
|
-
const paths =
|
|
3686
|
-
if (!
|
|
4205
|
+
const root = findProjectRoot26(opts.dir);
|
|
4206
|
+
const paths = resolveHaivePaths23(root);
|
|
4207
|
+
if (!existsSync28(paths.memoriesDir)) {
|
|
3687
4208
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
3688
4209
|
process.exitCode = 1;
|
|
3689
4210
|
return;
|
|
@@ -3706,7 +4227,7 @@ function registerMemoryVerify(memory2) {
|
|
|
3706
4227
|
anchorlessIds.push(mem.frontmatter.id);
|
|
3707
4228
|
continue;
|
|
3708
4229
|
}
|
|
3709
|
-
const rel =
|
|
4230
|
+
const rel = path28.relative(root, filePath);
|
|
3710
4231
|
if (result.stale) {
|
|
3711
4232
|
staleCount++;
|
|
3712
4233
|
console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
|
|
@@ -3721,7 +4242,7 @@ function registerMemoryVerify(memory2) {
|
|
|
3721
4242
|
}
|
|
3722
4243
|
if (opts.update) {
|
|
3723
4244
|
const next = applyVerification(mem, result);
|
|
3724
|
-
await
|
|
4245
|
+
await writeFile14(filePath, serializeMemory10(next), "utf8");
|
|
3725
4246
|
updated++;
|
|
3726
4247
|
}
|
|
3727
4248
|
}
|
|
@@ -3769,30 +4290,30 @@ function applyVerification(mem, result) {
|
|
|
3769
4290
|
}
|
|
3770
4291
|
|
|
3771
4292
|
// src/commands/memory-import.ts
|
|
3772
|
-
import { readFile as
|
|
3773
|
-
import { existsSync as
|
|
4293
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
4294
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3774
4295
|
import "commander";
|
|
3775
4296
|
import {
|
|
3776
|
-
findProjectRoot as
|
|
3777
|
-
resolveHaivePaths as
|
|
4297
|
+
findProjectRoot as findProjectRoot27,
|
|
4298
|
+
resolveHaivePaths as resolveHaivePaths24
|
|
3778
4299
|
} from "@hiveai/core";
|
|
3779
4300
|
function registerMemoryImport(memory2) {
|
|
3780
4301
|
memory2.command("import").description(
|
|
3781
4302
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
3782
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) => {
|
|
3783
|
-
const root =
|
|
3784
|
-
const paths =
|
|
3785
|
-
if (!
|
|
4304
|
+
const root = findProjectRoot27(opts.dir);
|
|
4305
|
+
const paths = resolveHaivePaths24(root);
|
|
4306
|
+
if (!existsSync29(paths.haiveDir)) {
|
|
3786
4307
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
3787
4308
|
process.exitCode = 1;
|
|
3788
4309
|
return;
|
|
3789
4310
|
}
|
|
3790
|
-
if (!
|
|
4311
|
+
if (!existsSync29(opts.from)) {
|
|
3791
4312
|
ui.error(`File not found: ${opts.from}`);
|
|
3792
4313
|
process.exitCode = 1;
|
|
3793
4314
|
return;
|
|
3794
4315
|
}
|
|
3795
|
-
const content = await
|
|
4316
|
+
const content = await readFile11(opts.from, "utf8");
|
|
3796
4317
|
const scope = opts.scope ?? "team";
|
|
3797
4318
|
ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
|
|
3798
4319
|
ui.info(`Content length: ${content.length} chars`);
|
|
@@ -3820,14 +4341,14 @@ function registerMemoryImport(memory2) {
|
|
|
3820
4341
|
}
|
|
3821
4342
|
|
|
3822
4343
|
// src/commands/memory-import-changelog.ts
|
|
3823
|
-
import { existsSync as
|
|
3824
|
-
import { readFile as
|
|
3825
|
-
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";
|
|
3826
4347
|
import "commander";
|
|
3827
4348
|
import {
|
|
3828
4349
|
buildFrontmatter as buildFrontmatter5,
|
|
3829
|
-
findProjectRoot as
|
|
3830
|
-
resolveHaivePaths as
|
|
4350
|
+
findProjectRoot as findProjectRoot28,
|
|
4351
|
+
resolveHaivePaths as resolveHaivePaths25,
|
|
3831
4352
|
serializeMemory as serializeMemory11
|
|
3832
4353
|
} from "@hiveai/core";
|
|
3833
4354
|
function parseChangelog(content) {
|
|
@@ -3891,15 +4412,15 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3891
4412
|
"--versions <csv>",
|
|
3892
4413
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
3893
4414
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
3894
|
-
const root =
|
|
3895
|
-
const paths =
|
|
3896
|
-
const changelogPath =
|
|
3897
|
-
if (!
|
|
4415
|
+
const root = findProjectRoot28(opts.dir);
|
|
4416
|
+
const paths = resolveHaivePaths25(root);
|
|
4417
|
+
const changelogPath = path29.resolve(root, opts.fromChangelog);
|
|
4418
|
+
if (!existsSync30(changelogPath)) {
|
|
3898
4419
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
3899
4420
|
process.exitCode = 1;
|
|
3900
4421
|
return;
|
|
3901
4422
|
}
|
|
3902
|
-
const content = await
|
|
4423
|
+
const content = await readFile12(changelogPath, "utf8");
|
|
3903
4424
|
let entries = parseChangelog(content);
|
|
3904
4425
|
if (entries.length === 0) {
|
|
3905
4426
|
ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
|
|
@@ -3913,10 +4434,10 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3913
4434
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
3914
4435
|
}
|
|
3915
4436
|
}
|
|
3916
|
-
const pkgName = opts.package ??
|
|
4437
|
+
const pkgName = opts.package ?? path29.basename(path29.dirname(changelogPath));
|
|
3917
4438
|
const scope = opts.scope ?? "team";
|
|
3918
|
-
const teamDir =
|
|
3919
|
-
await
|
|
4439
|
+
const teamDir = path29.join(paths.memoriesDir, scope);
|
|
4440
|
+
await mkdir11(teamDir, { recursive: true });
|
|
3920
4441
|
let saved = 0;
|
|
3921
4442
|
for (const entry of entries) {
|
|
3922
4443
|
const lines = [];
|
|
@@ -3938,7 +4459,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3938
4459
|
lines.push("");
|
|
3939
4460
|
}
|
|
3940
4461
|
lines.push(
|
|
3941
|
-
`**Source:** \`${
|
|
4462
|
+
`**Source:** \`${path29.relative(root, changelogPath)}\`
|
|
3942
4463
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
3943
4464
|
);
|
|
3944
4465
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
@@ -3953,11 +4474,11 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
3953
4474
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
3954
4475
|
`v${entry.version}`
|
|
3955
4476
|
],
|
|
3956
|
-
paths: [
|
|
4477
|
+
paths: [path29.relative(root, changelogPath)],
|
|
3957
4478
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
3958
4479
|
});
|
|
3959
|
-
await
|
|
3960
|
-
|
|
4480
|
+
await writeFile15(
|
|
4481
|
+
path29.join(teamDir, `${fm.id}.md`),
|
|
3961
4482
|
serializeMemory11({ frontmatter: fm, body: lines.join("\n") }),
|
|
3962
4483
|
"utf8"
|
|
3963
4484
|
);
|
|
@@ -3980,17 +4501,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
3980
4501
|
}
|
|
3981
4502
|
|
|
3982
4503
|
// src/commands/memory-digest.ts
|
|
3983
|
-
import { existsSync as
|
|
3984
|
-
import { writeFile as
|
|
3985
|
-
import
|
|
4504
|
+
import { existsSync as existsSync31 } from "fs";
|
|
4505
|
+
import { writeFile as writeFile16 } from "fs/promises";
|
|
4506
|
+
import path30 from "path";
|
|
3986
4507
|
import "commander";
|
|
3987
4508
|
import {
|
|
3988
4509
|
deriveConfidence as deriveConfidence4,
|
|
3989
|
-
findProjectRoot as
|
|
4510
|
+
findProjectRoot as findProjectRoot29,
|
|
3990
4511
|
getUsage as getUsage8,
|
|
3991
4512
|
loadMemoriesFromDir as loadMemoriesFromDir5,
|
|
3992
|
-
loadUsageIndex as
|
|
3993
|
-
resolveHaivePaths as
|
|
4513
|
+
loadUsageIndex as loadUsageIndex11,
|
|
4514
|
+
resolveHaivePaths as resolveHaivePaths26
|
|
3994
4515
|
} from "@hiveai/core";
|
|
3995
4516
|
var CONFIDENCE_EMOJI = {
|
|
3996
4517
|
unverified: "\u2B1C",
|
|
@@ -4003,9 +4524,9 @@ function registerMemoryDigest(program2) {
|
|
|
4003
4524
|
program2.command("digest").description(
|
|
4004
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"
|
|
4005
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) => {
|
|
4006
|
-
const root =
|
|
4007
|
-
const paths =
|
|
4008
|
-
if (!
|
|
4527
|
+
const root = findProjectRoot29(opts.dir);
|
|
4528
|
+
const paths = resolveHaivePaths26(root);
|
|
4529
|
+
if (!existsSync31(paths.memoriesDir)) {
|
|
4009
4530
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
4010
4531
|
process.exitCode = 1;
|
|
4011
4532
|
return;
|
|
@@ -4014,7 +4535,7 @@ function registerMemoryDigest(program2) {
|
|
|
4014
4535
|
const scopeFilter = opts.scope ?? "team";
|
|
4015
4536
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
4016
4537
|
const all = await loadMemoriesFromDir5(paths.memoriesDir);
|
|
4017
|
-
const usage = await
|
|
4538
|
+
const usage = await loadUsageIndex11(paths);
|
|
4018
4539
|
const recent = all.filter(({ memory: mem }) => {
|
|
4019
4540
|
const fm = mem.frontmatter;
|
|
4020
4541
|
if (fm.type === "session_recap") return false;
|
|
@@ -4077,8 +4598,8 @@ function registerMemoryDigest(program2) {
|
|
|
4077
4598
|
);
|
|
4078
4599
|
const digest = lines.join("\n");
|
|
4079
4600
|
if (opts.out) {
|
|
4080
|
-
const outPath =
|
|
4081
|
-
await
|
|
4601
|
+
const outPath = path30.resolve(process.cwd(), opts.out);
|
|
4602
|
+
await writeFile16(outPath, digest, "utf8");
|
|
4082
4603
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
4083
4604
|
} else {
|
|
4084
4605
|
console.log(digest);
|
|
@@ -4087,18 +4608,52 @@ function registerMemoryDigest(program2) {
|
|
|
4087
4608
|
}
|
|
4088
4609
|
|
|
4089
4610
|
// src/commands/session-end.ts
|
|
4090
|
-
import { writeFile as
|
|
4091
|
-
import { existsSync as
|
|
4092
|
-
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";
|
|
4093
4614
|
import "commander";
|
|
4094
4615
|
import {
|
|
4095
4616
|
buildFrontmatter as buildFrontmatter6,
|
|
4096
|
-
findProjectRoot as
|
|
4617
|
+
findProjectRoot as findProjectRoot30,
|
|
4097
4618
|
loadMemoriesFromDir as loadMemoriesFromDir6,
|
|
4098
4619
|
memoryFilePath as memoryFilePath5,
|
|
4099
|
-
resolveHaivePaths as
|
|
4620
|
+
resolveHaivePaths as resolveHaivePaths27,
|
|
4100
4621
|
serializeMemory as serializeMemory12
|
|
4101
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
|
+
}
|
|
4102
4657
|
function buildRecapBody(opts) {
|
|
4103
4658
|
const lines = [];
|
|
4104
4659
|
lines.push(`## Goal
|
|
@@ -4145,24 +4700,53 @@ function registerSessionEnd(session2) {
|
|
|
4145
4700
|
--files src/payments/WebhookController.ts,src/payments/WebhookService.ts \\\\
|
|
4146
4701
|
--next "Add integration tests for webhook signature validation"
|
|
4147
4702
|
`
|
|
4148
|
-
).
|
|
4149
|
-
const root =
|
|
4150
|
-
const paths =
|
|
4151
|
-
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;
|
|
4152
4708
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
4153
4709
|
process.exitCode = 1;
|
|
4154
4710
|
return;
|
|
4155
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
|
+
}
|
|
4156
4728
|
const scope = opts.scope ?? "personal";
|
|
4157
|
-
const body = buildRecapBody(
|
|
4729
|
+
const body = buildRecapBody({
|
|
4730
|
+
goal,
|
|
4731
|
+
accomplished,
|
|
4732
|
+
discoveries: opts.discoveries,
|
|
4733
|
+
files: resolvedFiles,
|
|
4734
|
+
next: opts.next
|
|
4735
|
+
});
|
|
4158
4736
|
const topic = recapTopic(scope, opts.module);
|
|
4159
|
-
const filesTouched = parseCsv5(
|
|
4160
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
4161
|
-
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) {
|
|
4162
4740
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
4163
4741
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
4164
4742
|
}
|
|
4165
|
-
|
|
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)) {
|
|
4166
4750
|
const existing = await loadMemoriesFromDir6(paths.memoriesDir);
|
|
4167
4751
|
const topicMatch = existing.find(
|
|
4168
4752
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
@@ -4178,9 +4762,12 @@ function registerSessionEnd(session2) {
|
|
|
4178
4762
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
4179
4763
|
}
|
|
4180
4764
|
};
|
|
4181
|
-
await
|
|
4182
|
-
|
|
4183
|
-
|
|
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
|
+
}
|
|
4184
4771
|
return;
|
|
4185
4772
|
}
|
|
4186
4773
|
}
|
|
@@ -4195,11 +4782,14 @@ function registerSessionEnd(session2) {
|
|
|
4195
4782
|
status: "validated"
|
|
4196
4783
|
});
|
|
4197
4784
|
const file = memoryFilePath5(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
4198
|
-
await
|
|
4199
|
-
await
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
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
|
+
}
|
|
4203
4793
|
});
|
|
4204
4794
|
}
|
|
4205
4795
|
function parseCsv5(value) {
|
|
@@ -4208,15 +4798,15 @@ function parseCsv5(value) {
|
|
|
4208
4798
|
}
|
|
4209
4799
|
|
|
4210
4800
|
// src/commands/snapshot.ts
|
|
4211
|
-
import { existsSync as
|
|
4801
|
+
import { existsSync as existsSync33 } from "fs";
|
|
4212
4802
|
import { readdir as readdir2 } from "fs/promises";
|
|
4213
|
-
import
|
|
4803
|
+
import path32 from "path";
|
|
4214
4804
|
import "commander";
|
|
4215
4805
|
import {
|
|
4216
4806
|
diffContract,
|
|
4217
|
-
findProjectRoot as
|
|
4807
|
+
findProjectRoot as findProjectRoot31,
|
|
4218
4808
|
loadConfig as loadConfig2,
|
|
4219
|
-
resolveHaivePaths as
|
|
4809
|
+
resolveHaivePaths as resolveHaivePaths28,
|
|
4220
4810
|
snapshotContract
|
|
4221
4811
|
} from "@hiveai/core";
|
|
4222
4812
|
function registerSnapshot(program2) {
|
|
@@ -4241,16 +4831,16 @@ function registerSnapshot(program2) {
|
|
|
4241
4831
|
"--format <format>",
|
|
4242
4832
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
4243
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) => {
|
|
4244
|
-
const root =
|
|
4245
|
-
const paths =
|
|
4246
|
-
if (!
|
|
4834
|
+
const root = findProjectRoot31(opts.dir);
|
|
4835
|
+
const paths = resolveHaivePaths28(root);
|
|
4836
|
+
if (!existsSync33(paths.haiveDir)) {
|
|
4247
4837
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
4248
4838
|
process.exitCode = 1;
|
|
4249
4839
|
return;
|
|
4250
4840
|
}
|
|
4251
4841
|
if (opts.list) {
|
|
4252
|
-
const contractsDir =
|
|
4253
|
-
if (!
|
|
4842
|
+
const contractsDir = path32.join(paths.haiveDir, "contracts");
|
|
4843
|
+
if (!existsSync33(contractsDir)) {
|
|
4254
4844
|
console.log(ui.dim("No contract snapshots found."));
|
|
4255
4845
|
return;
|
|
4256
4846
|
}
|
|
@@ -4305,7 +4895,7 @@ function registerSnapshot(program2) {
|
|
|
4305
4895
|
return;
|
|
4306
4896
|
}
|
|
4307
4897
|
const contractPath = opts.contract;
|
|
4308
|
-
const name = opts.name ??
|
|
4898
|
+
const name = opts.name ?? path32.basename(contractPath, path32.extname(contractPath));
|
|
4309
4899
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
4310
4900
|
const contract = { name, path: contractPath, format };
|
|
4311
4901
|
try {
|
|
@@ -4360,8 +4950,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
4360
4950
|
}
|
|
4361
4951
|
}
|
|
4362
4952
|
function detectFormat(filePath) {
|
|
4363
|
-
const ext =
|
|
4364
|
-
const base =
|
|
4953
|
+
const ext = path32.extname(filePath).toLowerCase();
|
|
4954
|
+
const base = path32.basename(filePath).toLowerCase();
|
|
4365
4955
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
4366
4956
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
4367
4957
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -4374,16 +4964,16 @@ function detectFormat(filePath) {
|
|
|
4374
4964
|
}
|
|
4375
4965
|
|
|
4376
4966
|
// src/commands/hub.ts
|
|
4377
|
-
import { existsSync as
|
|
4378
|
-
import { mkdir as
|
|
4379
|
-
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";
|
|
4380
4970
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4381
4971
|
import "commander";
|
|
4382
4972
|
import {
|
|
4383
|
-
findProjectRoot as
|
|
4973
|
+
findProjectRoot as findProjectRoot32,
|
|
4384
4974
|
loadConfig as loadConfig3,
|
|
4385
4975
|
loadMemoriesFromDir as loadMemoriesFromDir7,
|
|
4386
|
-
resolveHaivePaths as
|
|
4976
|
+
resolveHaivePaths as resolveHaivePaths29,
|
|
4387
4977
|
saveConfig as saveConfig2,
|
|
4388
4978
|
serializeMemory as serializeMemory13
|
|
4389
4979
|
} from "@hiveai/core";
|
|
@@ -4395,8 +4985,8 @@ function registerHub(program2) {
|
|
|
4395
4985
|
hub.command("init <hubPath>").description(
|
|
4396
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"
|
|
4397
4987
|
).action(async (hubPath) => {
|
|
4398
|
-
const absPath =
|
|
4399
|
-
await
|
|
4988
|
+
const absPath = path33.resolve(hubPath);
|
|
4989
|
+
await mkdir13(absPath, { recursive: true });
|
|
4400
4990
|
const gitCheck = spawnSync3("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
4401
4991
|
if (gitCheck.status !== 0) {
|
|
4402
4992
|
const init = spawnSync3("git", ["init"], { cwd: absPath, encoding: "utf8" });
|
|
@@ -4406,10 +4996,10 @@ function registerHub(program2) {
|
|
|
4406
4996
|
return;
|
|
4407
4997
|
}
|
|
4408
4998
|
}
|
|
4409
|
-
const sharedDir =
|
|
4410
|
-
await
|
|
4411
|
-
await
|
|
4412
|
-
|
|
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"),
|
|
4413
5003
|
`# hAIve Team Knowledge Hub
|
|
4414
5004
|
|
|
4415
5005
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -4430,8 +5020,8 @@ haive hub pull # import into a project
|
|
|
4430
5020
|
`,
|
|
4431
5021
|
"utf8"
|
|
4432
5022
|
);
|
|
4433
|
-
await
|
|
4434
|
-
|
|
5023
|
+
await writeFile18(
|
|
5024
|
+
path33.join(absPath, ".gitignore"),
|
|
4435
5025
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
4436
5026
|
"utf8"
|
|
4437
5027
|
);
|
|
@@ -4446,7 +5036,7 @@ haive hub pull # import into a project
|
|
|
4446
5036
|
`
|
|
4447
5037
|
Next steps:
|
|
4448
5038
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
4449
|
-
{ "hubPath": "${
|
|
5039
|
+
{ "hubPath": "${path33.relative(process.cwd(), absPath)}" }
|
|
4450
5040
|
2. Run \`haive hub push\` to publish your shared memories
|
|
4451
5041
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
4452
5042
|
`
|
|
@@ -4465,8 +5055,8 @@ Next steps:
|
|
|
4465
5055
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
4466
5056
|
`
|
|
4467
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) => {
|
|
4468
|
-
const root =
|
|
4469
|
-
const paths =
|
|
5058
|
+
const root = findProjectRoot32(opts.dir);
|
|
5059
|
+
const paths = resolveHaivePaths29(root);
|
|
4470
5060
|
const config = await loadConfig3(paths);
|
|
4471
5061
|
if (!config.hubPath) {
|
|
4472
5062
|
ui.error(
|
|
@@ -4475,15 +5065,15 @@ Next steps:
|
|
|
4475
5065
|
process.exitCode = 1;
|
|
4476
5066
|
return;
|
|
4477
5067
|
}
|
|
4478
|
-
const hubRoot =
|
|
4479
|
-
if (!
|
|
5068
|
+
const hubRoot = path33.resolve(root, config.hubPath);
|
|
5069
|
+
if (!existsSync34(hubRoot)) {
|
|
4480
5070
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
4481
5071
|
process.exitCode = 1;
|
|
4482
5072
|
return;
|
|
4483
5073
|
}
|
|
4484
|
-
const projectName =
|
|
4485
|
-
const destDir =
|
|
4486
|
-
await
|
|
5074
|
+
const projectName = path33.basename(root);
|
|
5075
|
+
const destDir = path33.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
5076
|
+
await mkdir13(destDir, { recursive: true });
|
|
4487
5077
|
const all = await loadMemoriesFromDir7(paths.memoriesDir);
|
|
4488
5078
|
const shared = all.filter(
|
|
4489
5079
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
@@ -4501,15 +5091,15 @@ Next steps:
|
|
|
4501
5091
|
for (const { memory: memory2 } of shared) {
|
|
4502
5092
|
const fm = memory2.frontmatter;
|
|
4503
5093
|
const fileName = `${fm.id}.md`;
|
|
4504
|
-
const destPath =
|
|
4505
|
-
await
|
|
5094
|
+
const destPath = path33.join(destDir, fileName);
|
|
5095
|
+
await writeFile18(destPath, serializeMemory13(memory2), "utf8");
|
|
4506
5096
|
pushed++;
|
|
4507
5097
|
}
|
|
4508
5098
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
4509
5099
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
4510
5100
|
if (opts.commit) {
|
|
4511
5101
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
4512
|
-
spawnSync3("git", ["add",
|
|
5102
|
+
spawnSync3("git", ["add", path33.join(".ai", "memories", "shared", projectName)], {
|
|
4513
5103
|
cwd: hubRoot
|
|
4514
5104
|
});
|
|
4515
5105
|
const commit = spawnSync3("git", ["commit", "-m", message], {
|
|
@@ -4534,8 +5124,8 @@ Next steps:
|
|
|
4534
5124
|
hub.command("pull").description(
|
|
4535
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"
|
|
4536
5126
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
4537
|
-
const root =
|
|
4538
|
-
const paths =
|
|
5127
|
+
const root = findProjectRoot32(opts.dir);
|
|
5128
|
+
const paths = resolveHaivePaths29(root);
|
|
4539
5129
|
const config = await loadConfig3(paths);
|
|
4540
5130
|
if (!config.hubPath) {
|
|
4541
5131
|
ui.error(
|
|
@@ -4544,13 +5134,13 @@ Next steps:
|
|
|
4544
5134
|
process.exitCode = 1;
|
|
4545
5135
|
return;
|
|
4546
5136
|
}
|
|
4547
|
-
const hubRoot =
|
|
4548
|
-
const hubSharedDir =
|
|
4549
|
-
if (!
|
|
5137
|
+
const hubRoot = path33.resolve(root, config.hubPath);
|
|
5138
|
+
const hubSharedDir = path33.join(hubRoot, ".ai", "memories", "shared");
|
|
5139
|
+
if (!existsSync34(hubSharedDir)) {
|
|
4550
5140
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
4551
5141
|
return;
|
|
4552
5142
|
}
|
|
4553
|
-
const projectName =
|
|
5143
|
+
const projectName = path33.basename(root);
|
|
4554
5144
|
const { readdir: readdir3 } = await import("fs/promises");
|
|
4555
5145
|
const projectDirs = (await readdir3(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
4556
5146
|
if (projectDirs.length === 0) {
|
|
@@ -4560,17 +5150,17 @@ Next steps:
|
|
|
4560
5150
|
let totalImported = 0;
|
|
4561
5151
|
let totalUpdated = 0;
|
|
4562
5152
|
for (const sourceName of projectDirs) {
|
|
4563
|
-
const sourceDir =
|
|
4564
|
-
const destDir =
|
|
4565
|
-
await
|
|
5153
|
+
const sourceDir = path33.join(hubSharedDir, sourceName);
|
|
5154
|
+
const destDir = path33.join(paths.memoriesDir, "shared", sourceName);
|
|
5155
|
+
await mkdir13(destDir, { recursive: true });
|
|
4566
5156
|
const sourceFiles = (await readdir3(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
4567
5157
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
4568
5158
|
const existingInDest = await loadDir(destDir);
|
|
4569
5159
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
4570
5160
|
for (const file of sourceFiles) {
|
|
4571
|
-
const srcPath =
|
|
4572
|
-
const destPath =
|
|
4573
|
-
const fileContent = await
|
|
5161
|
+
const srcPath = path33.join(sourceDir, file);
|
|
5162
|
+
const destPath = path33.join(destDir, file);
|
|
5163
|
+
const fileContent = await readFile14(srcPath, "utf8");
|
|
4574
5164
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
4575
5165
|
if (!alreadyTagged) {
|
|
4576
5166
|
await copyFile(srcPath, destPath);
|
|
@@ -4593,21 +5183,21 @@ Next steps:
|
|
|
4593
5183
|
);
|
|
4594
5184
|
});
|
|
4595
5185
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
4596
|
-
const root =
|
|
4597
|
-
const paths =
|
|
5186
|
+
const root = findProjectRoot32(opts.dir);
|
|
5187
|
+
const paths = resolveHaivePaths29(root);
|
|
4598
5188
|
const config = await loadConfig3(paths);
|
|
4599
5189
|
console.log(ui.bold("Hub status"));
|
|
4600
5190
|
console.log(
|
|
4601
5191
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
4602
5192
|
);
|
|
4603
|
-
const sharedDir =
|
|
4604
|
-
if (
|
|
5193
|
+
const sharedDir = path33.join(paths.memoriesDir, "shared");
|
|
5194
|
+
if (existsSync34(sharedDir)) {
|
|
4605
5195
|
const { readdir: readdir3 } = await import("fs/promises");
|
|
4606
5196
|
const sources = (await readdir3(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
4607
5197
|
console.log(`
|
|
4608
5198
|
Imported from ${sources.length} source(s):`);
|
|
4609
5199
|
for (const src of sources) {
|
|
4610
|
-
const files = (await readdir3(
|
|
5200
|
+
const files = (await readdir3(path33.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
4611
5201
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
4612
5202
|
}
|
|
4613
5203
|
} else {
|
|
@@ -4622,8 +5212,8 @@ Next steps:
|
|
|
4622
5212
|
if (outgoing.length > 0) {
|
|
4623
5213
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
4624
5214
|
}
|
|
4625
|
-
void
|
|
4626
|
-
void
|
|
5215
|
+
void readFile14;
|
|
5216
|
+
void writeFile18;
|
|
4627
5217
|
void saveConfig2;
|
|
4628
5218
|
});
|
|
4629
5219
|
}
|
|
@@ -4632,16 +5222,21 @@ Next steps:
|
|
|
4632
5222
|
import "commander";
|
|
4633
5223
|
import {
|
|
4634
5224
|
aggregateUsage,
|
|
4635
|
-
findProjectRoot as
|
|
5225
|
+
findProjectRoot as findProjectRoot33,
|
|
5226
|
+
loadUsageIndex as loadUsageIndex12,
|
|
4636
5227
|
parseSince,
|
|
4637
5228
|
readUsageEvents,
|
|
4638
|
-
resolveHaivePaths as
|
|
5229
|
+
resolveHaivePaths as resolveHaivePaths30,
|
|
4639
5230
|
usageLogSize
|
|
4640
5231
|
} from "@hiveai/core";
|
|
4641
5232
|
function registerStats(program2) {
|
|
4642
|
-
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) => {
|
|
4643
|
-
const root =
|
|
4644
|
-
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
|
+
}
|
|
4645
5240
|
const size = await usageLogSize(paths);
|
|
4646
5241
|
if (!size.exists) {
|
|
4647
5242
|
if (opts.json) {
|
|
@@ -4686,14 +5281,57 @@ function registerStats(program2) {
|
|
|
4686
5281
|
}
|
|
4687
5282
|
});
|
|
4688
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
|
+
}
|
|
4689
5327
|
|
|
4690
5328
|
// src/commands/bench.ts
|
|
4691
5329
|
import { performance } from "perf_hooks";
|
|
4692
5330
|
import "commander";
|
|
4693
5331
|
import {
|
|
4694
5332
|
estimateTokens,
|
|
4695
|
-
findProjectRoot as
|
|
4696
|
-
resolveHaivePaths as
|
|
5333
|
+
findProjectRoot as findProjectRoot34,
|
|
5334
|
+
resolveHaivePaths as resolveHaivePaths31
|
|
4697
5335
|
} from "@hiveai/core";
|
|
4698
5336
|
import {
|
|
4699
5337
|
antiPatternsCheck,
|
|
@@ -4705,8 +5343,8 @@ import {
|
|
|
4705
5343
|
} from "@hiveai/mcp";
|
|
4706
5344
|
function registerBench(program2) {
|
|
4707
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) => {
|
|
4708
|
-
const root =
|
|
4709
|
-
const paths =
|
|
5346
|
+
const root = findProjectRoot34(opts.dir);
|
|
5347
|
+
const paths = resolveHaivePaths31(root);
|
|
4710
5348
|
const ctx = { paths };
|
|
4711
5349
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
4712
5350
|
const scenarios = [
|
|
@@ -4825,19 +5463,19 @@ function summarize(name, t0, payload, notes) {
|
|
|
4825
5463
|
}
|
|
4826
5464
|
|
|
4827
5465
|
// src/commands/memory-suggest.ts
|
|
4828
|
-
import { mkdir as
|
|
4829
|
-
import { existsSync as
|
|
4830
|
-
import
|
|
5466
|
+
import { mkdir as mkdir14, writeFile as writeFile19 } from "fs/promises";
|
|
5467
|
+
import { existsSync as existsSync35 } from "fs";
|
|
5468
|
+
import path34 from "path";
|
|
4831
5469
|
import "commander";
|
|
4832
5470
|
import {
|
|
4833
5471
|
aggregateUsage as aggregateUsage2,
|
|
4834
5472
|
buildFrontmatter as buildFrontmatter7,
|
|
4835
|
-
findProjectRoot as
|
|
5473
|
+
findProjectRoot as findProjectRoot35,
|
|
4836
5474
|
loadMemoriesFromDir as loadMemoriesFromDir8,
|
|
4837
5475
|
memoryFilePath as memoryFilePath6,
|
|
4838
5476
|
parseSince as parseSince2,
|
|
4839
5477
|
readUsageEvents as readUsageEvents2,
|
|
4840
|
-
resolveHaivePaths as
|
|
5478
|
+
resolveHaivePaths as resolveHaivePaths32,
|
|
4841
5479
|
serializeMemory as serializeMemory14
|
|
4842
5480
|
} from "@hiveai/core";
|
|
4843
5481
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -4850,8 +5488,8 @@ function registerMemorySuggest(memory2) {
|
|
|
4850
5488
|
memory2.command("suggest").description(
|
|
4851
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."
|
|
4852
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) => {
|
|
4853
|
-
const root =
|
|
4854
|
-
const paths =
|
|
5491
|
+
const root = findProjectRoot35(opts.dir);
|
|
5492
|
+
const paths = resolveHaivePaths32(root);
|
|
4855
5493
|
const events = await readUsageEvents2(paths);
|
|
4856
5494
|
if (events.length === 0) {
|
|
4857
5495
|
if (opts.json) {
|
|
@@ -4897,7 +5535,7 @@ function registerMemorySuggest(memory2) {
|
|
|
4897
5535
|
}
|
|
4898
5536
|
const created = [];
|
|
4899
5537
|
const skipped = [];
|
|
4900
|
-
const existing =
|
|
5538
|
+
const existing = existsSync35(paths.memoriesDir) ? await loadMemoriesFromDir8(paths.memoriesDir) : [];
|
|
4901
5539
|
for (const s of top) {
|
|
4902
5540
|
const slug = slugify(s.query);
|
|
4903
5541
|
if (!slug) {
|
|
@@ -4920,13 +5558,13 @@ function registerMemorySuggest(memory2) {
|
|
|
4920
5558
|
fm.status = "draft";
|
|
4921
5559
|
const body = renderTemplate(s);
|
|
4922
5560
|
const file = memoryFilePath6(paths, fm.scope, fm.id, fm.module);
|
|
4923
|
-
await
|
|
4924
|
-
if (
|
|
4925
|
-
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)}` });
|
|
4926
5564
|
continue;
|
|
4927
5565
|
}
|
|
4928
|
-
await
|
|
4929
|
-
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 });
|
|
4930
5568
|
}
|
|
4931
5569
|
if (opts.json) {
|
|
4932
5570
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -4934,10 +5572,10 @@ function registerMemorySuggest(memory2) {
|
|
|
4934
5572
|
}
|
|
4935
5573
|
for (const c of created) {
|
|
4936
5574
|
ui.success(`Drafted ${c.id} \u2192 ${c.file}`);
|
|
4937
|
-
console.log(` ${ui.dim("from query:")} ${
|
|
5575
|
+
console.log(` ${ui.dim("from query:")} ${truncate2(c.query, 60)}`);
|
|
4938
5576
|
}
|
|
4939
5577
|
for (const s of skipped) {
|
|
4940
|
-
ui.warn(`Skipped: ${
|
|
5578
|
+
ui.warn(`Skipped: ${truncate2(s.query, 50)} \u2014 ${s.reason}`);
|
|
4941
5579
|
}
|
|
4942
5580
|
if (created.length > 0) {
|
|
4943
5581
|
console.log();
|
|
@@ -4961,7 +5599,7 @@ function registerMemorySuggest(memory2) {
|
|
|
4961
5599
|
}
|
|
4962
5600
|
for (const s of suggestions.slice(0, 30)) {
|
|
4963
5601
|
console.log(
|
|
4964
|
-
` ${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)}`
|
|
4965
5603
|
);
|
|
4966
5604
|
console.log(` ${ui.dim("\u2192")} ${s.reason}`);
|
|
4967
5605
|
}
|
|
@@ -5007,28 +5645,28 @@ function renderTemplate(s) {
|
|
|
5007
5645
|
`- **Why** \u2014 the rationale or root cause`,
|
|
5008
5646
|
`- **How to apply** \u2014 what an agent should do when this comes up again`,
|
|
5009
5647
|
``,
|
|
5010
|
-
`Then run \`haive memory promote ${
|
|
5648
|
+
`Then run \`haive memory promote ${truncate2(s.query, 30)}\` to mark it validated.`
|
|
5011
5649
|
].join("\n");
|
|
5012
5650
|
}
|
|
5013
5651
|
function slugify(s) {
|
|
5014
5652
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
5015
5653
|
}
|
|
5016
|
-
function
|
|
5654
|
+
function truncate2(text, max) {
|
|
5017
5655
|
if (text.length <= max) return text;
|
|
5018
5656
|
return text.slice(0, max - 1) + "\u2026";
|
|
5019
5657
|
}
|
|
5020
5658
|
|
|
5021
5659
|
// src/commands/memory-archive.ts
|
|
5022
|
-
import { existsSync as
|
|
5023
|
-
import { writeFile as
|
|
5024
|
-
import
|
|
5660
|
+
import { existsSync as existsSync36 } from "fs";
|
|
5661
|
+
import { writeFile as writeFile20 } from "fs/promises";
|
|
5662
|
+
import path35 from "path";
|
|
5025
5663
|
import "commander";
|
|
5026
5664
|
import {
|
|
5027
|
-
findProjectRoot as
|
|
5665
|
+
findProjectRoot as findProjectRoot36,
|
|
5028
5666
|
getUsage as getUsage9,
|
|
5029
5667
|
loadMemoriesFromDir as loadMemoriesFromDir9,
|
|
5030
|
-
loadUsageIndex as
|
|
5031
|
-
resolveHaivePaths as
|
|
5668
|
+
loadUsageIndex as loadUsageIndex13,
|
|
5669
|
+
resolveHaivePaths as resolveHaivePaths33,
|
|
5032
5670
|
serializeMemory as serializeMemory15
|
|
5033
5671
|
} from "@hiveai/core";
|
|
5034
5672
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -5036,9 +5674,9 @@ function registerMemoryArchive(memory2) {
|
|
|
5036
5674
|
memory2.command("archive").description(
|
|
5037
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."
|
|
5038
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) => {
|
|
5039
|
-
const root =
|
|
5040
|
-
const paths =
|
|
5041
|
-
if (!
|
|
5677
|
+
const root = findProjectRoot36(opts.dir);
|
|
5678
|
+
const paths = resolveHaivePaths33(root);
|
|
5679
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
5042
5680
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
5043
5681
|
process.exitCode = 1;
|
|
5044
5682
|
return;
|
|
@@ -5051,7 +5689,7 @@ function registerMemoryArchive(memory2) {
|
|
|
5051
5689
|
}
|
|
5052
5690
|
const cutoff = Date.now() - minDays * MS_PER_DAY;
|
|
5053
5691
|
const all = await loadMemoriesFromDir9(paths.memoriesDir);
|
|
5054
|
-
const usage = await
|
|
5692
|
+
const usage = await loadUsageIndex13(paths);
|
|
5055
5693
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
5056
5694
|
const candidates = [];
|
|
5057
5695
|
for (const { memory: mem, filePath } of all) {
|
|
@@ -5059,7 +5697,7 @@ function registerMemoryArchive(memory2) {
|
|
|
5059
5697
|
if (typeFilter && fm.type !== typeFilter) continue;
|
|
5060
5698
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
5061
5699
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
5062
|
-
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)));
|
|
5063
5701
|
const isAnchorless = !hasAnyAnchor;
|
|
5064
5702
|
if (!isAnchorless && !allPathsGone) continue;
|
|
5065
5703
|
const u = getUsage9(usage, fm.id);
|
|
@@ -5107,7 +5745,7 @@ function registerMemoryArchive(memory2) {
|
|
|
5107
5745
|
if (!found) continue;
|
|
5108
5746
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
5109
5747
|
try {
|
|
5110
|
-
await
|
|
5748
|
+
await writeFile20(c.filePath, serializeMemory15({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
5111
5749
|
archived++;
|
|
5112
5750
|
} catch (err) {
|
|
5113
5751
|
if (!opts.json) {
|
|
@@ -5133,30 +5771,30 @@ function parseDays(input) {
|
|
|
5133
5771
|
}
|
|
5134
5772
|
|
|
5135
5773
|
// src/commands/doctor.ts
|
|
5136
|
-
import { existsSync as
|
|
5774
|
+
import { existsSync as existsSync37 } from "fs";
|
|
5137
5775
|
import { stat } from "fs/promises";
|
|
5138
5776
|
import "path";
|
|
5139
5777
|
import "commander";
|
|
5140
5778
|
import {
|
|
5141
5779
|
codeMapPath as codeMapPath2,
|
|
5142
|
-
findProjectRoot as
|
|
5780
|
+
findProjectRoot as findProjectRoot37,
|
|
5143
5781
|
getUsage as getUsage10,
|
|
5144
5782
|
loadCodeMap as loadCodeMap3,
|
|
5145
5783
|
loadConfig as loadConfig4,
|
|
5146
5784
|
loadMemoriesFromDir as loadMemoriesFromDir10,
|
|
5147
|
-
loadUsageIndex as
|
|
5785
|
+
loadUsageIndex as loadUsageIndex14,
|
|
5148
5786
|
readUsageEvents as readUsageEvents3,
|
|
5149
|
-
resolveHaivePaths as
|
|
5787
|
+
resolveHaivePaths as resolveHaivePaths34
|
|
5150
5788
|
} from "@hiveai/core";
|
|
5151
5789
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
5152
5790
|
function registerDoctor(program2) {
|
|
5153
5791
|
program2.command("doctor").description(
|
|
5154
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."
|
|
5155
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) => {
|
|
5156
|
-
const root =
|
|
5157
|
-
const paths =
|
|
5794
|
+
const root = findProjectRoot37(opts.dir);
|
|
5795
|
+
const paths = resolveHaivePaths34(root);
|
|
5158
5796
|
const findings = [];
|
|
5159
|
-
if (!
|
|
5797
|
+
if (!existsSync37(paths.haiveDir)) {
|
|
5160
5798
|
findings.push({
|
|
5161
5799
|
severity: "error",
|
|
5162
5800
|
code: "not-initialized",
|
|
@@ -5165,7 +5803,7 @@ function registerDoctor(program2) {
|
|
|
5165
5803
|
});
|
|
5166
5804
|
return emit(findings, opts);
|
|
5167
5805
|
}
|
|
5168
|
-
if (!
|
|
5806
|
+
if (!existsSync37(paths.projectContext)) {
|
|
5169
5807
|
findings.push({
|
|
5170
5808
|
severity: "warn",
|
|
5171
5809
|
code: "no-project-context",
|
|
@@ -5173,8 +5811,8 @@ function registerDoctor(program2) {
|
|
|
5173
5811
|
fix: "haive init"
|
|
5174
5812
|
});
|
|
5175
5813
|
} else {
|
|
5176
|
-
const { readFile:
|
|
5177
|
-
const content = await
|
|
5814
|
+
const { readFile: readFile15 } = await import("fs/promises");
|
|
5815
|
+
const content = await readFile15(paths.projectContext, "utf8");
|
|
5178
5816
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
5179
5817
|
if (isTemplate) {
|
|
5180
5818
|
findings.push({
|
|
@@ -5185,7 +5823,7 @@ function registerDoctor(program2) {
|
|
|
5185
5823
|
});
|
|
5186
5824
|
}
|
|
5187
5825
|
}
|
|
5188
|
-
const memories =
|
|
5826
|
+
const memories = existsSync37(paths.memoriesDir) ? await loadMemoriesFromDir10(paths.memoriesDir) : [];
|
|
5189
5827
|
const now = Date.now();
|
|
5190
5828
|
if (memories.length === 0) {
|
|
5191
5829
|
findings.push({
|
|
@@ -5194,7 +5832,7 @@ function registerDoctor(program2) {
|
|
|
5194
5832
|
message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
|
|
5195
5833
|
});
|
|
5196
5834
|
} else {
|
|
5197
|
-
const usage = await
|
|
5835
|
+
const usage = await loadUsageIndex14(paths);
|
|
5198
5836
|
const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
|
|
5199
5837
|
if (stale.length > 0) {
|
|
5200
5838
|
findings.push({
|
|
@@ -5339,22 +5977,22 @@ function isSearchTool(name) {
|
|
|
5339
5977
|
}
|
|
5340
5978
|
|
|
5341
5979
|
// src/commands/playback.ts
|
|
5342
|
-
import { existsSync as
|
|
5980
|
+
import { existsSync as existsSync38 } from "fs";
|
|
5343
5981
|
import "commander";
|
|
5344
5982
|
import {
|
|
5345
|
-
findProjectRoot as
|
|
5983
|
+
findProjectRoot as findProjectRoot38,
|
|
5346
5984
|
loadMemoriesFromDir as loadMemoriesFromDir11,
|
|
5347
5985
|
parseSince as parseSince3,
|
|
5348
5986
|
readUsageEvents as readUsageEvents4,
|
|
5349
|
-
resolveHaivePaths as
|
|
5987
|
+
resolveHaivePaths as resolveHaivePaths35
|
|
5350
5988
|
} from "@hiveai/core";
|
|
5351
5989
|
var MS_PER_MINUTE = 6e4;
|
|
5352
5990
|
function registerPlayback(program2) {
|
|
5353
5991
|
program2.command("playback").description(
|
|
5354
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?'"
|
|
5355
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) => {
|
|
5356
|
-
const root =
|
|
5357
|
-
const paths =
|
|
5994
|
+
const root = findProjectRoot38(opts.dir);
|
|
5995
|
+
const paths = resolveHaivePaths35(root);
|
|
5358
5996
|
const events = await readUsageEvents4(paths);
|
|
5359
5997
|
if (events.length === 0) {
|
|
5360
5998
|
if (opts.json) {
|
|
@@ -5369,7 +6007,7 @@ function registerPlayback(program2) {
|
|
|
5369
6007
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
5370
6008
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
5371
6009
|
const sessions = bucketSessions(filtered, gapMs);
|
|
5372
|
-
const all =
|
|
6010
|
+
const all = existsSync38(paths.memoriesDir) ? await loadMemoriesFromDir11(paths.memoriesDir) : [];
|
|
5373
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);
|
|
5374
6012
|
const enriched = sessions.map((s, i) => {
|
|
5375
6013
|
const startMs = Date.parse(s.start);
|
|
@@ -5409,7 +6047,7 @@ function registerPlayback(program2) {
|
|
|
5409
6047
|
if (s.briefing_tasks.length > 0) {
|
|
5410
6048
|
console.log(` ${ui.dim("briefings asked:")}`);
|
|
5411
6049
|
for (const t of s.briefing_tasks) {
|
|
5412
|
-
console.log(` \u2022 ${
|
|
6050
|
+
console.log(` \u2022 ${truncate3(t, 80)}`);
|
|
5413
6051
|
}
|
|
5414
6052
|
}
|
|
5415
6053
|
if (s.memories_created_since > 0) {
|
|
@@ -5450,7 +6088,7 @@ function countTools(events) {
|
|
|
5450
6088
|
for (const e of events) out[e.tool] = (out[e.tool] ?? 0) + 1;
|
|
5451
6089
|
return out;
|
|
5452
6090
|
}
|
|
5453
|
-
function
|
|
6091
|
+
function truncate3(text, max) {
|
|
5454
6092
|
if (text.length <= max) return text;
|
|
5455
6093
|
return text.slice(0, max - 1) + "\u2026";
|
|
5456
6094
|
}
|
|
@@ -5459,8 +6097,8 @@ function truncate2(text, max) {
|
|
|
5459
6097
|
import { spawn as spawn3 } from "child_process";
|
|
5460
6098
|
import "commander";
|
|
5461
6099
|
import {
|
|
5462
|
-
findProjectRoot as
|
|
5463
|
-
resolveHaivePaths as
|
|
6100
|
+
findProjectRoot as findProjectRoot39,
|
|
6101
|
+
resolveHaivePaths as resolveHaivePaths36
|
|
5464
6102
|
} from "@hiveai/core";
|
|
5465
6103
|
import { preCommitCheck } from "@hiveai/mcp";
|
|
5466
6104
|
function registerPrecommit(program2) {
|
|
@@ -5471,8 +6109,8 @@ function registerPrecommit(program2) {
|
|
|
5471
6109
|
"'any' | 'high-confidence' (default) | 'never' (report only)",
|
|
5472
6110
|
"high-confidence"
|
|
5473
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) => {
|
|
5474
|
-
const root =
|
|
5475
|
-
const paths =
|
|
6112
|
+
const root = findProjectRoot39(opts.dir);
|
|
6113
|
+
const paths = resolveHaivePaths36(root);
|
|
5476
6114
|
const ctx = { paths };
|
|
5477
6115
|
let diff = "";
|
|
5478
6116
|
let touchedPaths = opts.paths ?? [];
|
|
@@ -5564,8 +6202,8 @@ function runCommand(cmd, args, cwd) {
|
|
|
5564
6202
|
}
|
|
5565
6203
|
|
|
5566
6204
|
// src/index.ts
|
|
5567
|
-
var program = new
|
|
5568
|
-
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");
|
|
5569
6207
|
registerInit(program);
|
|
5570
6208
|
registerMcp(program);
|
|
5571
6209
|
registerBriefing(program);
|
|
@@ -5573,6 +6211,7 @@ registerTui(program);
|
|
|
5573
6211
|
registerEmbeddings(program);
|
|
5574
6212
|
registerSync(program);
|
|
5575
6213
|
registerInstallHooks(program);
|
|
6214
|
+
registerObserve(program);
|
|
5576
6215
|
registerIndexCode(program);
|
|
5577
6216
|
var memory = program.command("memory").description("Manage memory entries");
|
|
5578
6217
|
registerMemoryAdd(memory);
|