@hiveai/cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -252,14 +252,22 @@ Show memories relevant to specific files you're about to edit.
|
|
|
252
252
|
haive memory for-files src/payments/PaymentService.java src/payments/PaymentController.java
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
#### `haive memory stats` / `hot` / `pending`
|
|
255
|
+
#### `haive memory stats` / `hot` / `pending` / `digest`
|
|
256
256
|
|
|
257
257
|
```bash
|
|
258
258
|
haive memory stats # Usage stats and confidence levels for all memories
|
|
259
259
|
haive memory hot # Most-read unvalidated memories (good promotion candidates)
|
|
260
260
|
haive memory pending # Proposed memories awaiting review
|
|
261
|
+
|
|
262
|
+
# Generate a Markdown review digest for bulk approval/rejection:
|
|
263
|
+
haive memory digest # Last 7 days, team scope (prints to stdout)
|
|
264
|
+
haive memory digest --days 14 # Last 14 days
|
|
265
|
+
haive memory digest --scope all # All scopes
|
|
266
|
+
haive memory digest --out digest.md # Write to file
|
|
261
267
|
```
|
|
262
268
|
|
|
269
|
+
The digest groups memories by type, shows confidence level (⬜ unverified / 🟡 low / 🟢 trusted / ⭐ authoritative), anchor, read count, and action checkboxes for easy bulk review.
|
|
270
|
+
|
|
263
271
|
---
|
|
264
272
|
|
|
265
273
|
### `haive briefing`
|
|
@@ -267,12 +275,20 @@ haive memory pending # Proposed memories awaiting review
|
|
|
267
275
|
Print the full project briefing — project context + relevant memories — in one shot. Use before starting a task.
|
|
268
276
|
|
|
269
277
|
```bash
|
|
270
|
-
haive briefing
|
|
271
|
-
haive briefing --task "add a Stripe payment"
|
|
278
|
+
haive briefing # Full briefing, team scope
|
|
279
|
+
haive briefing --task "add a Stripe payment" # Filter by task relevance
|
|
272
280
|
haive briefing --files src/payments/PaymentService.java # Filter by files
|
|
273
|
-
haive briefing --
|
|
274
|
-
haive briefing --
|
|
275
|
-
haive briefing --
|
|
281
|
+
haive briefing --symbols PaymentService,TenantFilter # Look up symbol locations in code-map
|
|
282
|
+
haive briefing --scope all # Include personal memories
|
|
283
|
+
haive briefing --include-stale # Include stale memories
|
|
284
|
+
haive briefing --max-memories 15 # Show more memories
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**`--symbols` (requires `haive index code`):** look up where specific symbols are defined across your entire codebase — no grep needed. Returns file, line number, kind (class/interface/function/enum), and JSDoc description for each match.
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
PaymentProvider src/payments/PaymentProvider.java:12 [interface] — Abstract payment provider
|
|
291
|
+
PaymentProvider src/frontend/payment.types.ts:4 [enum] — Mobile payment provider enum
|
|
276
292
|
```
|
|
277
293
|
|
|
278
294
|
---
|
|
@@ -337,13 +353,51 @@ The code map lets AI agents find where a function lives without grepping — dra
|
|
|
337
353
|
|
|
338
354
|
### `haive tui`
|
|
339
355
|
|
|
340
|
-
Interactive terminal dashboard
|
|
356
|
+
Interactive terminal dashboard with 3 screens — browse, filter, and manage memories without leaving the terminal.
|
|
341
357
|
|
|
342
358
|
```bash
|
|
343
|
-
haive tui # Open the TUI
|
|
359
|
+
haive tui # Open the TUI
|
|
344
360
|
haive tui --dir /path/to/project
|
|
345
361
|
```
|
|
346
362
|
|
|
363
|
+
**Screens (switch with `1` `2` `3`):**
|
|
364
|
+
|
|
365
|
+
| Screen | Key | What it shows |
|
|
366
|
+
|---|---|---|
|
|
367
|
+
| Memories | `1` | Full list + preview panel, filter by status (Tab), actions |
|
|
368
|
+
| Health | `2` | Stale memories, pending review, anchorless memories |
|
|
369
|
+
| Stats | `3` | Top-read memories, decaying (>90d unused), totals by status |
|
|
370
|
+
|
|
371
|
+
**Actions (in Memories screen):**
|
|
372
|
+
|
|
373
|
+
| Key | Action |
|
|
374
|
+
|---|---|
|
|
375
|
+
| `↑` `↓` | Navigate |
|
|
376
|
+
| `Tab` | Cycle filter (all / draft / proposed / validated / stale / rejected) |
|
|
377
|
+
| `a` | Approve (→ validated) |
|
|
378
|
+
| `r` | Reject |
|
|
379
|
+
| `p` | Propose (→ proposed) |
|
|
380
|
+
| `d` | Delete |
|
|
381
|
+
| `q` | Quit |
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
### `haive session end`
|
|
386
|
+
|
|
387
|
+
Save a structured end-of-session recap. Surfaced automatically at the start of the next session via `get_briefing`.
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
haive session end \
|
|
391
|
+
--goal "Add Stripe payment integration" \
|
|
392
|
+
--accomplished "Implemented PaymentService, added tests, deployed to staging" \
|
|
393
|
+
--discoveries "The webhook signature must use the raw request body, not parsed JSON" \
|
|
394
|
+
--files "src/payments/PaymentService.ts,src/payments/webhook.ts" \
|
|
395
|
+
--next "Add retry logic for failed webhooks" \
|
|
396
|
+
--scope team
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
One recap is kept per scope (topic-upsert: `revision_count` increments on each call).
|
|
400
|
+
|
|
347
401
|
---
|
|
348
402
|
|
|
349
403
|
## Memory lifecycle
|
|
@@ -5,6 +5,7 @@ import { useState, useEffect, useCallback } from "react";
|
|
|
5
5
|
import { Box, Text, useInput, useApp } from "ink";
|
|
6
6
|
import { existsSync } from "fs";
|
|
7
7
|
import { writeFile, unlink } from "fs/promises";
|
|
8
|
+
import path from "path";
|
|
8
9
|
import {
|
|
9
10
|
getUsage,
|
|
10
11
|
isDecaying,
|
|
@@ -83,13 +84,37 @@ function Dashboard({ root }) {
|
|
|
83
84
|
serializeMemory({ frontmatter: { ...fm, status: newStatus }, body: selected.memory.body }),
|
|
84
85
|
"utf8"
|
|
85
86
|
);
|
|
86
|
-
const label = newStatus === "validated" ? "\u2713 Approved" :
|
|
87
|
-
const color = newStatus === "validated" ? "green" :
|
|
87
|
+
const label = newStatus === "validated" ? "\u2713 Approved" : "\u2717 Rejected";
|
|
88
|
+
const color = newStatus === "validated" ? "green" : "red";
|
|
88
89
|
flash_(`${label}: ${fm.id.slice(0, 40)}`, color);
|
|
89
90
|
const prev = cursor;
|
|
90
91
|
await reload();
|
|
91
92
|
setCursor(Math.min(prev, Math.max(0, filtered.length - 2)));
|
|
92
93
|
}, [selected, cursor, filtered.length, reload]);
|
|
94
|
+
const doPromote = useCallback(async () => {
|
|
95
|
+
if (!selected) return;
|
|
96
|
+
const fm = selected.memory.frontmatter;
|
|
97
|
+
if (fm.scope === "team") {
|
|
98
|
+
flash_("Already team scope", "yellow");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const teamDir = path.join(path.dirname(path.dirname(selected.filePath)), "team");
|
|
102
|
+
const newFilePath = path.join(teamDir, path.basename(selected.filePath));
|
|
103
|
+
const { mkdir } = await import("fs/promises");
|
|
104
|
+
await mkdir(teamDir, { recursive: true });
|
|
105
|
+
await writeFile(
|
|
106
|
+
newFilePath,
|
|
107
|
+
serializeMemory({
|
|
108
|
+
frontmatter: { ...fm, scope: "team", status: "proposed" },
|
|
109
|
+
body: selected.memory.body
|
|
110
|
+
}),
|
|
111
|
+
"utf8"
|
|
112
|
+
);
|
|
113
|
+
await unlink(selected.filePath);
|
|
114
|
+
flash_(`\u2191 Promoted to team: ${fm.id.slice(0, 36)}`, "yellow");
|
|
115
|
+
await reload();
|
|
116
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
117
|
+
}, [selected, reload]);
|
|
93
118
|
const doDelete = useCallback(async () => {
|
|
94
119
|
if (!selected) return;
|
|
95
120
|
const fm = selected.memory.frontmatter;
|
|
@@ -125,7 +150,7 @@ function Dashboard({ root }) {
|
|
|
125
150
|
}
|
|
126
151
|
if (input === "a") void doStatusChange("validated");
|
|
127
152
|
if (input === "r") void doStatusChange("rejected");
|
|
128
|
-
if (input === "p") void
|
|
153
|
+
if (input === "p") void doPromote();
|
|
129
154
|
if (input === "d") void doDelete();
|
|
130
155
|
}
|
|
131
156
|
});
|
|
@@ -225,7 +250,7 @@ function Dashboard({ root }) {
|
|
|
225
250
|
] })
|
|
226
251
|
] }),
|
|
227
252
|
/* @__PURE__ */ jsx(FlashBar, {}),
|
|
228
|
-
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate [tab] filter [a] approve [r] reject [p]
|
|
253
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate [tab] filter [a] approve [r] reject [p] promote personal\u2192team [d] delete" }) })
|
|
229
254
|
] });
|
|
230
255
|
}
|
|
231
256
|
if (screen === "health") {
|
|
@@ -333,4 +358,4 @@ function Dashboard({ root }) {
|
|
|
333
358
|
export {
|
|
334
359
|
Dashboard
|
|
335
360
|
};
|
|
336
|
-
//# sourceMappingURL=Dashboard-
|
|
361
|
+
//# sourceMappingURL=Dashboard-Y2AIWFZK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tui/Dashboard.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from \"react\";\nimport { Box, Text, useInput, useApp } from \"ink\";\nimport { existsSync } from \"node:fs\";\nimport { writeFile, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n getUsage,\n isDecaying,\n loadMemoriesFromDir,\n loadUsageIndex,\n resolveHaivePaths,\n serializeMemory,\n type LoadedMemory,\n type UsageIndex,\n} from \"@hiveai/core\";\n\ntype Screen = \"memories\" | \"health\" | \"stats\";\ntype FilterStatus = \"all\" | \"draft\" | \"proposed\" | \"validated\" | \"stale\" | \"rejected\";\nconst FILTERS: FilterStatus[] = [\"all\", \"draft\", \"proposed\", \"validated\", \"stale\", \"rejected\"];\nconst LIST_H = 14;\n\nfunction statusColor(status: string): \"green\" | \"yellow\" | \"red\" | undefined {\n if (status === \"validated\") return \"green\";\n if (status === \"proposed\" || status === \"stale\") return \"yellow\";\n if (status === \"rejected\") return \"red\";\n return undefined;\n}\n\ninterface Props { root: string; }\n\nexport function Dashboard({ root }: Props) {\n const { exit } = useApp();\n const paths = resolveHaivePaths(root);\n\n const [screen, setScreen] = useState<Screen>(\"memories\");\n const [memories, setMemories] = useState<LoadedMemory[]>([]);\n const [usage, setUsage] = useState<UsageIndex>({ version: 1, updated_at: \"\", by_id: {} });\n const [loading, setLoading] = useState(true);\n const [filterIdx, setFilterIdx] = useState(0);\n const [cursor, setCursor] = useState(0);\n const [flash, setFlash] = useState<{ text: string; color: \"green\" | \"red\" | \"yellow\" } | null>(null);\n\n const filter: FilterStatus = FILTERS[filterIdx] ?? \"all\";\n\n const reload = useCallback(async () => {\n setLoading(true);\n const [mems, u] = await Promise.all([\n existsSync(paths.memoriesDir) ? loadMemoriesFromDir(paths.memoriesDir) : Promise.resolve([]),\n loadUsageIndex(paths),\n ]);\n setMemories(mems);\n setUsage(u);\n setLoading(false);\n }, [paths.memoriesDir]);\n\n useEffect(() => { void reload(); }, [reload]);\n\n // ── Derived data ──────────────────────────────────────────────────────\n const nonRecap = memories.filter((m) => m.memory.frontmatter.type !== \"session_recap\");\n\n const filtered = nonRecap.filter((m) => {\n const s = m.memory.frontmatter.status;\n if (filter === \"all\") return s !== \"rejected\";\n return s === filter;\n });\n\n const staleMemories = nonRecap.filter((m) => m.memory.frontmatter.status === \"stale\");\n const anchorless = nonRecap.filter(\n (m) =>\n m.memory.frontmatter.anchor.paths.length === 0 &&\n m.memory.frontmatter.anchor.symbols.length === 0 &&\n m.memory.frontmatter.status !== \"rejected\",\n );\n const pending = nonRecap.filter((m) => m.memory.frontmatter.status === \"proposed\");\n\n // Top-5 by read_count\n const topRead = [...nonRecap]\n .map((m) => ({ m, u: getUsage(usage, m.memory.frontmatter.id) }))\n .filter(({ u }) => u.read_count > 0)\n .sort((a, b) => b.u.read_count - a.u.read_count)\n .slice(0, 5);\n\n // Decaying memories\n const decaying = nonRecap.filter(({ memory: mem }) => {\n const u = getUsage(usage, mem.frontmatter.id);\n return isDecaying(u, mem.frontmatter.created_at);\n });\n\n const selected = filtered[cursor];\n\n const counts = nonRecap.reduce<Record<string, number>>((acc, m) => {\n acc[m.memory.frontmatter.status] = (acc[m.memory.frontmatter.status] ?? 0) + 1;\n return acc;\n }, {});\n\n const flash_ = (text: string, color: \"green\" | \"red\" | \"yellow\" = \"green\") => {\n setFlash({ text, color });\n setTimeout(() => setFlash(null), 2500);\n };\n\n // ── Actions ───────────────────────────────────────────────────────────\n const doStatusChange = useCallback(async (newStatus: \"validated\" | \"rejected\") => {\n if (!selected) return;\n const fm = selected.memory.frontmatter;\n if (fm.status === newStatus) { flash_(`Already ${newStatus}`, \"yellow\"); return; }\n await writeFile(\n selected.filePath,\n serializeMemory({ frontmatter: { ...fm, status: newStatus }, body: selected.memory.body }),\n \"utf8\",\n );\n const label = newStatus === \"validated\" ? \"✓ Approved\" : \"✗ Rejected\";\n const color = newStatus === \"validated\" ? \"green\" : \"red\";\n flash_(`${label}: ${fm.id.slice(0, 40)}`, color);\n const prev = cursor;\n await reload();\n setCursor(Math.min(prev, Math.max(0, filtered.length - 2)));\n }, [selected, cursor, filtered.length, reload]);\n\n // Promote = move personal → team (scope change) + set status proposed\n const doPromote = useCallback(async () => {\n if (!selected) return;\n const fm = selected.memory.frontmatter;\n if (fm.scope === \"team\") { flash_(\"Already team scope\", \"yellow\"); return; }\n const teamDir = path.join(path.dirname(path.dirname(selected.filePath)), \"team\");\n const newFilePath = path.join(teamDir, path.basename(selected.filePath));\n const { mkdir } = await import(\"node:fs/promises\");\n await mkdir(teamDir, { recursive: true });\n await writeFile(\n newFilePath,\n serializeMemory({\n frontmatter: { ...fm, scope: \"team\" as const, status: \"proposed\" as const },\n body: selected.memory.body,\n }),\n \"utf8\",\n );\n await unlink(selected.filePath);\n flash_(`↑ Promoted to team: ${fm.id.slice(0, 36)}`, \"yellow\");\n await reload();\n setCursor((c) => Math.max(0, c - 1));\n }, [selected, reload]);\n\n const doDelete = useCallback(async () => {\n if (!selected) return;\n const fm = selected.memory.frontmatter;\n await unlink(selected.filePath);\n flash_(`🗑 Deleted: ${fm.id.slice(0, 40)}`, \"red\");\n await reload();\n setCursor((c) => Math.max(0, c - 1));\n }, [selected, reload]);\n\n useInput((input, key) => {\n if (input === \"q\") { exit(); return; }\n if (input === \"1\") { setScreen(\"memories\"); setCursor(0); return; }\n if (input === \"2\") { setScreen(\"health\"); return; }\n if (input === \"3\") { setScreen(\"stats\"); return; }\n\n if (screen === \"memories\") {\n if (key.upArrow) setCursor((c) => Math.max(0, c - 1));\n if (key.downArrow) setCursor((c) => Math.min(filtered.length - 1, c + 1));\n if (key.tab) { setFilterIdx((i) => (i + 1) % FILTERS.length); setCursor(0); }\n if (input === \"a\") void doStatusChange(\"validated\");\n if (input === \"r\") void doStatusChange(\"rejected\");\n if (input === \"p\") void doPromote();\n if (input === \"d\") void doDelete();\n }\n });\n\n if (loading) return <Text dimColor>Loading memories…</Text>;\n if (!existsSync(paths.memoriesDir)) {\n return <Text color=\"red\">No .ai/memories found — run `haive init` first.</Text>;\n }\n\n // ── Header (shared) ───────────────────────────────────────────────────\n const v = counts[\"validated\"] ?? 0;\n const p = counts[\"proposed\"] ?? 0;\n const d = counts[\"draft\"] ?? 0;\n const st = counts[\"stale\"] ?? 0;\n const rej = counts[\"rejected\"] ?? 0;\n\n const Header = () => (\n <Box borderStyle=\"round\" paddingX={1} gap={2}>\n <Text bold color=\"cyan\">hAIve</Text>\n <Text dimColor>{root.length > 40 ? \"…\" + root.slice(-38) : root}</Text>\n <Text> </Text>\n <Text color=\"green\">✓ {v}</Text>\n <Text color={p > 0 ? \"yellow\" : undefined}> ~ {p}</Text>\n <Text dimColor> · {d}</Text>\n {st > 0 && <Text color=\"yellow\"> ⚠ {st}</Text>}\n {rej > 0 && <Text color=\"red\"> ✗ {rej}</Text>}\n </Box>\n );\n\n const ScreenTabs = () => (\n <Box paddingX={1} gap={3} marginBottom={0}>\n {([\"memories\", \"health\", \"stats\"] as Screen[]).map((s, i) => (\n <Text key={s} color={screen === s ? \"cyan\" : undefined} bold={screen === s}>\n [{i + 1}] {screen === s ? `[${s}]` : s}\n </Text>\n ))}\n <Text dimColor> [q] quit</Text>\n </Box>\n );\n\n const FlashBar = () => flash\n ? <Box paddingX={1}><Text color={flash.color}>{flash.text}</Text></Box>\n : null;\n\n // ── Screen: Memories ─────────────────────────────────────────────────\n if (screen === \"memories\") {\n const half = Math.floor(LIST_H / 2);\n const start = Math.max(0, Math.min(cursor - half, Math.max(0, filtered.length - LIST_H)));\n const visible = filtered.slice(start, start + LIST_H);\n\n return (\n <Box flexDirection=\"column\">\n <Header />\n <ScreenTabs />\n\n {/* Filter bar */}\n <Box paddingX={1} gap={2} marginBottom={0}>\n {FILTERS.map((f) => (\n <Text key={f} color={filter === f ? \"cyan\" : undefined} bold={filter === f}>\n {filter === f ? `[${f}]` : f}\n </Text>\n ))}\n <Text dimColor> [tab] cycle</Text>\n </Box>\n\n {/* List + Preview */}\n <Box>\n <Box flexDirection=\"column\" width={64} borderStyle=\"single\" paddingX={1}>\n <Text bold dimColor>{`MEMORIES ${filtered.length}/${nonRecap.length}`}</Text>\n {filtered.length === 0 ? (\n <Text dimColor> (no memories in this filter)</Text>\n ) : (\n visible.map((m, vi) => {\n const absIdx = start + vi;\n const fm = m.memory.frontmatter;\n const sel = absIdx === cursor;\n const idShort = fm.id.length > 43 ? fm.id.slice(0, 40) + \"…\" : fm.id;\n return (\n <Box key={fm.id}>\n <Text color={sel ? \"cyan\" : undefined} bold={sel}>\n {sel ? \"▶ \" : \" \"}\n {idShort.padEnd(43)}\n </Text>\n <Text color={statusColor(fm.status)}>{fm.status.slice(0, 9)}</Text>\n </Box>\n );\n })\n )}\n </Box>\n\n {/* Preview */}\n <Box flexDirection=\"column\" width={40} borderStyle=\"single\" paddingX={1}>\n <Text bold dimColor>PREVIEW</Text>\n {selected ? (\n <>\n <Text bold>{selected.memory.frontmatter.id.slice(0, 36)}</Text>\n <Text color=\"cyan\">\n {selected.memory.frontmatter.scope}/{selected.memory.frontmatter.type}\n {selected.memory.frontmatter.module ? ` [${selected.memory.frontmatter.module}]` : \"\"}\n </Text>\n <Text color={statusColor(selected.memory.frontmatter.status)}>\n {selected.memory.frontmatter.status}\n {selected.memory.frontmatter.revision_count ? ` (rev ${selected.memory.frontmatter.revision_count})` : \"\"}\n </Text>\n <Text dimColor>tags: {selected.memory.frontmatter.tags.slice(0, 5).join(\", \") || \"(none)\"}</Text>\n <Text> </Text>\n {selected.memory.body\n .split(\"\\n\")\n .slice(0, LIST_H - 4)\n .map((line, i) => (\n <Text key={i} wrap=\"truncate-end\">{line || \" \"}</Text>\n ))}\n </>\n ) : (\n <Text dimColor>select a memory</Text>\n )}\n </Box>\n </Box>\n\n <FlashBar />\n <Box paddingX={1}>\n <Text dimColor>↑↓ navigate [tab] filter [a] approve [r] reject [p] promote personal→team [d] delete</Text>\n </Box>\n </Box>\n );\n }\n\n // ── Screen: Health ────────────────────────────────────────────────────\n if (screen === \"health\") {\n return (\n <Box flexDirection=\"column\">\n <Header />\n <ScreenTabs />\n <Box gap={2}>\n\n {/* Stale memories */}\n <Box flexDirection=\"column\" width={40} borderStyle=\"single\" paddingX={1}>\n <Text bold color={staleMemories.length > 0 ? \"yellow\" : \"green\"}>\n ⚠ STALE ({staleMemories.length})\n </Text>\n {staleMemories.length === 0\n ? <Text dimColor> All memories are fresh</Text>\n : staleMemories.slice(0, LIST_H).map((m) => (\n <Text key={m.memory.frontmatter.id} wrap=\"truncate-end\" color=\"yellow\">\n {m.memory.frontmatter.id.slice(0, 36)}\n </Text>\n ))\n }\n {staleMemories.length > LIST_H && (\n <Text dimColor> … +{staleMemories.length - LIST_H} more</Text>\n )}\n </Box>\n\n <Box flexDirection=\"column\" gap={1}>\n {/* Pending review */}\n <Box flexDirection=\"column\" width={44} borderStyle=\"single\" paddingX={1}>\n <Text bold color={pending.length > 0 ? \"yellow\" : \"green\"}>\n ~ PENDING REVIEW ({pending.length})\n </Text>\n {pending.length === 0\n ? <Text dimColor> No memories pending review</Text>\n : pending.slice(0, 6).map((m) => (\n <Text key={m.memory.frontmatter.id} wrap=\"truncate-end\">\n {m.memory.frontmatter.id.slice(0, 40)}\n </Text>\n ))\n }\n </Box>\n\n {/* Anchorless */}\n <Box flexDirection=\"column\" width={44} borderStyle=\"single\" paddingX={1}>\n <Text bold dimColor>⊘ ANCHORLESS ({anchorless.length})</Text>\n <Text dimColor> No paths/symbols — staleness undetectable</Text>\n {anchorless.slice(0, 5).map((m) => (\n <Text key={m.memory.frontmatter.id} wrap=\"truncate-end\" dimColor>\n {m.memory.frontmatter.id.slice(0, 40)}\n </Text>\n ))}\n {anchorless.length > 5 && (\n <Text dimColor> … +{anchorless.length - 5} more</Text>\n )}\n </Box>\n </Box>\n </Box>\n\n <Box paddingX={1} marginTop={1}>\n <Text dimColor>\n Run `haive memory verify --update` to recheck anchors | `haive memory update <id> --paths <files>` to add anchors\n </Text>\n </Box>\n <FlashBar />\n </Box>\n );\n }\n\n // ── Screen: Stats ─────────────────────────────────────────────────────\n return (\n <Box flexDirection=\"column\">\n <Header />\n <ScreenTabs />\n <Box gap={2}>\n\n {/* Top read */}\n <Box flexDirection=\"column\" width={44} borderStyle=\"single\" paddingX={1}>\n <Text bold dimColor>📖 TOP READ MEMORIES</Text>\n {topRead.length === 0\n ? <Text dimColor> No read data yet (use `get_briefing`)</Text>\n : topRead.map(({ m, u }) => (\n <Box key={m.memory.frontmatter.id}>\n <Text wrap=\"truncate-end\">\n {m.memory.frontmatter.id.slice(0, 32).padEnd(32)}\n </Text>\n <Text color=\"cyan\"> ×{u.read_count}</Text>\n </Box>\n ))\n }\n </Box>\n\n <Box flexDirection=\"column\" gap={1}>\n {/* Decaying */}\n <Box flexDirection=\"column\" width={44} borderStyle=\"single\" paddingX={1}>\n <Text bold color={decaying.length > 0 ? \"yellow\" : \"green\"}>\n ⏳ DECAYING (not read in 90d) ({decaying.length})\n </Text>\n {decaying.length === 0\n ? <Text dimColor> All memories are actively used</Text>\n : decaying.slice(0, 5).map((m) => (\n <Text key={m.memory.frontmatter.id} wrap=\"truncate-end\" color=\"yellow\">\n {m.memory.frontmatter.id.slice(0, 40)}\n </Text>\n ))\n }\n </Box>\n\n {/* Memory totals */}\n <Box flexDirection=\"column\" width={44} borderStyle=\"single\" paddingX={1}>\n <Text bold dimColor>📊 MEMORY TOTALS</Text>\n <Text> Validated: <Text color=\"green\">{v}</Text></Text>\n <Text> Proposed: <Text color=\"yellow\">{p}</Text></Text>\n <Text> Draft: <Text dimColor>{d}</Text></Text>\n <Text> Stale: <Text color=\"yellow\">{st}</Text></Text>\n <Text> Rejected: <Text color=\"red\">{rej}</Text></Text>\n <Text> Total: <Text bold>{nonRecap.length}</Text></Text>\n </Box>\n </Box>\n </Box>\n <FlashBar />\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -213,7 +213,7 @@ function registerTui(program2) {
|
|
|
213
213
|
const root = findProjectRoot2(opts.dir);
|
|
214
214
|
const { render } = await import("ink");
|
|
215
215
|
const { createElement } = await import("react");
|
|
216
|
-
const { Dashboard } = await import("./Dashboard-
|
|
216
|
+
const { Dashboard } = await import("./Dashboard-Y2AIWFZK.js");
|
|
217
217
|
const { waitUntilExit } = render(createElement(Dashboard, { root }));
|
|
218
218
|
await waitUntilExit();
|
|
219
219
|
});
|
|
@@ -341,8 +341,15 @@ function registerIndexCode(program2) {
|
|
|
341
341
|
import { mkdir, writeFile } from "fs/promises";
|
|
342
342
|
import { existsSync as existsSync3 } from "fs";
|
|
343
343
|
import path3 from "path";
|
|
344
|
+
import { spawnSync } from "child_process";
|
|
344
345
|
import "commander";
|
|
345
|
-
import {
|
|
346
|
+
import {
|
|
347
|
+
AUTOPILOT_DEFAULTS,
|
|
348
|
+
buildCodeMap as buildCodeMap2,
|
|
349
|
+
resolveHaivePaths as resolveHaivePaths4,
|
|
350
|
+
saveCodeMap as saveCodeMap2,
|
|
351
|
+
saveConfig
|
|
352
|
+
} from "@hiveai/core";
|
|
346
353
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
347
354
|
|
|
348
355
|
> Generated by \`haive init\`. Edit this file (or let your AI agent fill it via the upcoming MCP \`bootstrap_project\` tool).
|
|
@@ -451,9 +458,13 @@ jobs:
|
|
|
451
458
|
});
|
|
452
459
|
`;
|
|
453
460
|
function registerInit(program2) {
|
|
454
|
-
program2.command("init").description("Initialize a hAIve project (.ai/ structure + bridge files)").option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml)").
|
|
461
|
+
program2.command("init").description("Initialize a hAIve project (.ai/ structure + bridge files)").option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml)").option(
|
|
462
|
+
"--autopilot",
|
|
463
|
+
"zero-friction mode: memories \u2192 validated, auto-approve, auto-session, auto-context, git hooks + CI included"
|
|
464
|
+
).action(async (opts) => {
|
|
455
465
|
const root = path3.resolve(opts.dir);
|
|
456
466
|
const paths = resolveHaivePaths4(root);
|
|
467
|
+
const autopilot = opts.autopilot === true;
|
|
457
468
|
if (existsSync3(paths.haiveDir)) {
|
|
458
469
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
459
470
|
}
|
|
@@ -465,12 +476,22 @@ function registerInit(program2) {
|
|
|
465
476
|
await writeFile(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
466
477
|
ui.success(`Created ${path3.relative(root, paths.projectContext)}`);
|
|
467
478
|
}
|
|
479
|
+
const configExists = existsSync3(
|
|
480
|
+
path3.join(paths.haiveDir, "haive.config.json")
|
|
481
|
+
);
|
|
482
|
+
if (!configExists) {
|
|
483
|
+
await saveConfig(paths, autopilot ? AUTOPILOT_DEFAULTS : { autopilot: false });
|
|
484
|
+
ui.success(
|
|
485
|
+
`Created .ai/haive.config.json (mode: ${autopilot ? "autopilot" : "standard"})`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
468
488
|
if (opts.bridges) {
|
|
469
489
|
await writeBridge(root, "CLAUDE.md");
|
|
470
490
|
await writeBridge(root, ".cursorrules");
|
|
471
491
|
await writeBridge(root, path3.join(".github", "copilot-instructions.md"));
|
|
472
492
|
}
|
|
473
|
-
|
|
493
|
+
const wantCi = opts.withCi || autopilot;
|
|
494
|
+
if (wantCi) {
|
|
474
495
|
const ciPath = path3.join(root, ".github", "workflows", "haive-sync.yml");
|
|
475
496
|
if (existsSync3(ciPath)) {
|
|
476
497
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
@@ -480,29 +501,55 @@ function registerInit(program2) {
|
|
|
480
501
|
ui.success(`Created ${path3.relative(root, ciPath)}`);
|
|
481
502
|
}
|
|
482
503
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
504
|
+
if (autopilot) {
|
|
505
|
+
const haiveBin = process.argv[1];
|
|
506
|
+
const hookResult = spawnSync(
|
|
507
|
+
process.execPath,
|
|
508
|
+
[haiveBin, "install-hooks", "--dir", root],
|
|
509
|
+
{ encoding: "utf8" }
|
|
510
|
+
);
|
|
511
|
+
if (hookResult.status === 0) {
|
|
512
|
+
ui.success("Git hooks installed (auto-sync after pull/merge)");
|
|
513
|
+
} else {
|
|
514
|
+
ui.warn("Git hooks not installed (not a git repo or no .git/ found) \u2014 run `haive install-hooks` manually");
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
ui.info("Building code-map\u2026");
|
|
518
|
+
const map = await buildCodeMap2(root);
|
|
519
|
+
await saveCodeMap2(paths, map);
|
|
520
|
+
ui.success(`Code-map built (${Object.keys(map.files).length} files)`);
|
|
521
|
+
} catch {
|
|
522
|
+
ui.warn("Code-map build failed \u2014 run `haive index code` manually");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
ui.success(`hAIve initialized at ${root}${autopilot ? " (autopilot mode)" : ""}`);
|
|
503
526
|
console.log();
|
|
504
|
-
|
|
505
|
-
|
|
527
|
+
if (autopilot) {
|
|
528
|
+
console.log(ui.bold("Autopilot mode is ON \u2014 hAIve runs itself:"));
|
|
529
|
+
console.log(ui.dim(" \u2713 Memories go directly to validated (no approval needed)"));
|
|
530
|
+
console.log(ui.dim(" \u2713 Proposed memories auto-approve after 72h without rejection"));
|
|
531
|
+
console.log(ui.dim(" \u2713 Session recap saved automatically when the AI session closes"));
|
|
532
|
+
console.log(ui.dim(" \u2713 Code-map refreshes automatically after every pull"));
|
|
533
|
+
console.log(ui.dim(" \u2713 Git hooks installed (auto-sync after pull/merge)"));
|
|
534
|
+
console.log(ui.dim(" \u2713 CI workflow created (pr-stale-check + sync-on-merge)"));
|
|
535
|
+
console.log();
|
|
536
|
+
console.log(ui.bold("One remaining step:"));
|
|
537
|
+
console.log(" In your AI client, invoke the MCP prompt: " + ui.bold("bootstrap_project"));
|
|
538
|
+
console.log(ui.dim(" This fills .ai/project-context.md \u2014 only needed once."));
|
|
539
|
+
} else {
|
|
540
|
+
console.log(ui.bold("Next steps:"));
|
|
541
|
+
console.log(ui.dim(" 1. Fill project context \u2014 let your AI agent do it:"));
|
|
542
|
+
console.log(" " + ui.bold("In your AI client (Claude, Cursor\u2026), invoke the MCP prompt: bootstrap_project"));
|
|
543
|
+
console.log(ui.dim(" This analyzes your codebase and writes .ai/project-context.md automatically."));
|
|
544
|
+
console.log();
|
|
545
|
+
console.log(ui.dim(" 2. Point your AI client at the MCP server:"));
|
|
546
|
+
console.log(` haive-mcp --root ${root}`);
|
|
547
|
+
console.log();
|
|
548
|
+
console.log(ui.dim(" 3. Start every AI session with:"));
|
|
549
|
+
console.log(" " + ui.bold("get_briefing({ task: '\u2026what you are about to do\u2026' })"));
|
|
550
|
+
console.log();
|
|
551
|
+
console.log(ui.dim(" Tip: run `haive init --autopilot` for zero-friction mode (no manual steps)."));
|
|
552
|
+
}
|
|
506
553
|
});
|
|
507
554
|
}
|
|
508
555
|
async function writeBridge(root, relPath) {
|
|
@@ -608,7 +655,7 @@ function locateMcpBin() {
|
|
|
608
655
|
}
|
|
609
656
|
|
|
610
657
|
// src/commands/sync.ts
|
|
611
|
-
import { spawnSync } from "child_process";
|
|
658
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
612
659
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
613
660
|
import { existsSync as existsSync6 } from "fs";
|
|
614
661
|
import path6 from "path";
|
|
@@ -619,6 +666,8 @@ import {
|
|
|
619
666
|
getUsage,
|
|
620
667
|
isAutoPromoteEligible,
|
|
621
668
|
isDecaying,
|
|
669
|
+
loadCodeMap as loadCodeMap2,
|
|
670
|
+
loadConfig,
|
|
622
671
|
loadMemoriesFromDir as loadMemoriesFromDir2,
|
|
623
672
|
loadUsageIndex,
|
|
624
673
|
resolveHaivePaths as resolveHaivePaths5,
|
|
@@ -645,9 +694,13 @@ function registerSync(program2) {
|
|
|
645
694
|
const log = (msg) => {
|
|
646
695
|
if (!opts.quiet) console.log(msg);
|
|
647
696
|
};
|
|
697
|
+
const config = await loadConfig(paths);
|
|
698
|
+
const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
|
|
699
|
+
const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads;
|
|
648
700
|
let staleMarked = 0;
|
|
649
701
|
let revalidated = 0;
|
|
650
702
|
let promoted = 0;
|
|
703
|
+
let autoApproved = 0;
|
|
651
704
|
if (opts.verify !== false) {
|
|
652
705
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
653
706
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -712,21 +765,39 @@ function registerSync(program2) {
|
|
|
712
765
|
if (opts.promote !== false) {
|
|
713
766
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
714
767
|
const usage = await loadUsageIndex(paths);
|
|
768
|
+
const nowMs = Date.now();
|
|
715
769
|
for (const { memory: memory2, filePath } of memories) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
770
|
+
const fm = memory2.frontmatter;
|
|
771
|
+
if (fm.type === "session_recap") continue;
|
|
772
|
+
if (isAutoPromoteEligible(fm, getUsage(usage, fm.id), {
|
|
773
|
+
minReads: autoPromoteMinReads,
|
|
774
|
+
maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
|
|
775
|
+
})) {
|
|
721
776
|
await writeFile3(
|
|
722
777
|
filePath,
|
|
723
|
-
serializeMemory({
|
|
724
|
-
frontmatter: { ...memory2.frontmatter, status: "validated" },
|
|
725
|
-
body: memory2.body
|
|
726
|
-
}),
|
|
778
|
+
serializeMemory({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
727
779
|
"utf8"
|
|
728
780
|
);
|
|
729
781
|
promoted++;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
|
|
785
|
+
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
786
|
+
if (ageHours >= autoApproveDelayHours) {
|
|
787
|
+
await writeFile3(
|
|
788
|
+
filePath,
|
|
789
|
+
serializeMemory({
|
|
790
|
+
frontmatter: {
|
|
791
|
+
...fm,
|
|
792
|
+
status: "validated",
|
|
793
|
+
verified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
794
|
+
},
|
|
795
|
+
body: memory2.body
|
|
796
|
+
}),
|
|
797
|
+
"utf8"
|
|
798
|
+
);
|
|
799
|
+
autoApproved++;
|
|
800
|
+
}
|
|
730
801
|
}
|
|
731
802
|
}
|
|
732
803
|
}
|
|
@@ -735,8 +806,9 @@ function registerSync(program2) {
|
|
|
735
806
|
(m) => m.memory.frontmatter.status === "draft"
|
|
736
807
|
);
|
|
737
808
|
const draftCount = draftMemories.length;
|
|
809
|
+
const autoApprovedNote = autoApproved > 0 ? ` \xB7 ${autoApproved} auto-approved` : "";
|
|
738
810
|
log(
|
|
739
|
-
`${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
|
|
811
|
+
`${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${autoApprovedNote}${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
|
|
740
812
|
);
|
|
741
813
|
if (!opts.quiet && draftCount > 0) {
|
|
742
814
|
log(
|
|
@@ -781,6 +853,43 @@ function registerSync(program2) {
|
|
|
781
853
|
}
|
|
782
854
|
}
|
|
783
855
|
}
|
|
856
|
+
const existingMap = await loadCodeMap2(paths);
|
|
857
|
+
if (existingMap) {
|
|
858
|
+
const mapAge = new Date(existingMap.generated_at).getTime();
|
|
859
|
+
const gitResult = spawnSync2(
|
|
860
|
+
"git",
|
|
861
|
+
[
|
|
862
|
+
"diff",
|
|
863
|
+
"--name-only",
|
|
864
|
+
"--diff-filter=ACMR",
|
|
865
|
+
`@{${new Date(mapAge).toISOString()}}..HEAD`,
|
|
866
|
+
"--",
|
|
867
|
+
"*.ts",
|
|
868
|
+
"*.tsx",
|
|
869
|
+
"*.js",
|
|
870
|
+
"*.jsx",
|
|
871
|
+
"*.java",
|
|
872
|
+
"*.kt",
|
|
873
|
+
"*.py",
|
|
874
|
+
"*.go",
|
|
875
|
+
"*.rs",
|
|
876
|
+
"*.cs",
|
|
877
|
+
"*.php"
|
|
878
|
+
],
|
|
879
|
+
{ cwd: root, encoding: "utf8" }
|
|
880
|
+
);
|
|
881
|
+
const changedSourceFiles = (gitResult.stdout ?? "").trim();
|
|
882
|
+
if (changedSourceFiles.length > 0) {
|
|
883
|
+
try {
|
|
884
|
+
const { buildCodeMap: buildCodeMap3, saveCodeMap: saveCodeMap3 } = await import("@hiveai/core");
|
|
885
|
+
log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
|
|
886
|
+
const newMap = await buildCodeMap3(root);
|
|
887
|
+
await saveCodeMap3(paths, newMap);
|
|
888
|
+
log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
|
|
889
|
+
} catch {
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
784
893
|
if (opts.embed) {
|
|
785
894
|
try {
|
|
786
895
|
const emb = await import("@hiveai/embeddings");
|
|
@@ -849,7 +958,7 @@ ${BRIDGE_END}`;
|
|
|
849
958
|
}
|
|
850
959
|
}
|
|
851
960
|
function collectSinceChanges(root, ref) {
|
|
852
|
-
const result =
|
|
961
|
+
const result = spawnSync2(
|
|
853
962
|
"git",
|
|
854
963
|
["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
|
|
855
964
|
{ encoding: "utf8" }
|
|
@@ -2372,7 +2481,7 @@ function parseCsv5(value) {
|
|
|
2372
2481
|
|
|
2373
2482
|
// src/index.ts
|
|
2374
2483
|
var program = new Command29();
|
|
2375
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.3.
|
|
2484
|
+
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.3.2");
|
|
2376
2485
|
registerInit(program);
|
|
2377
2486
|
registerMcp(program);
|
|
2378
2487
|
registerBriefing(program);
|