@hiveai/cli 0.2.15 → 0.3.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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command28 } from "commander";
4
+ import { Command as Command29 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -11,8 +11,10 @@ import {
11
11
  findProjectRoot,
12
12
  literalMatchesAllTokens,
13
13
  literalMatchesAnyToken,
14
+ loadCodeMap,
14
15
  loadMemoriesFromDir,
15
16
  memoryMatchesAnchorPaths,
17
+ queryCodeMap,
16
18
  resolveHaivePaths,
17
19
  tokenizeQuery,
18
20
  trackReads
@@ -52,7 +54,7 @@ var ui = {
52
54
  function registerBriefing(program2) {
53
55
  program2.command("briefing").description(
54
56
  "Print project context + relevant memories in one shot \u2014 ideal for agent onboarding"
55
- ).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (anchors memories)").option("--max-memories <n>", "cap on memories surfaced", "10").option(
57
+ ).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (anchors memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter)").option("--max-memories <n>", "cap on memories surfaced", "10").option(
56
58
  "--scope <scope>",
57
59
  "personal | team | module | all (default: team)",
58
60
  "team"
@@ -89,13 +91,24 @@ function registerBriefing(program2) {
89
91
  }
90
92
  if (existsSync(paths.projectContext)) {
91
93
  const ctx = await readFile(paths.projectContext, "utf8");
92
- console.log(`${ui.bold("=== Project Context ===")}
94
+ const isTemplate = ctx.includes("TODO \u2014 high-level overview") || ctx.includes("Generated by `haive init`");
95
+ if (isTemplate) {
96
+ ui.warn(
97
+ "project-context.md still contains the default template \u2014 get_briefing will return little value."
98
+ );
99
+ ui.warn(
100
+ "Fix: in your AI client, invoke the MCP prompt bootstrap_project to auto-fill it from your codebase."
101
+ );
102
+ console.log();
103
+ } else {
104
+ console.log(`${ui.bold("=== Project Context ===")}
93
105
  `);
94
- console.log(ctx.trim());
95
- console.log();
106
+ console.log(ctx.trim());
107
+ console.log();
108
+ }
96
109
  } else {
97
110
  ui.warn(
98
- "No project-context.md found. Run `haive init` and the `bootstrap_project` MCP prompt to set it up."
111
+ "No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
99
112
  );
100
113
  }
101
114
  const candidates = all.filter(({ memory: mem }) => {
@@ -152,6 +165,34 @@ function registerBriefing(program2) {
152
165
  await trackReads(paths, ids).catch(() => {
153
166
  });
154
167
  }
168
+ const requestedSymbols = (opts.symbols ?? "").split(",").map((s) => s.trim()).filter(Boolean);
169
+ if (requestedSymbols.length > 0) {
170
+ const codeMap = await loadCodeMap(paths);
171
+ if (!codeMap) {
172
+ ui.warn("No code-map found. Run `haive index code` first to enable symbol lookup.");
173
+ } else {
174
+ console.log(`
175
+ ${ui.bold("=== Symbol Locations ===")}
176
+ `);
177
+ for (const sym of requestedSymbols) {
178
+ const { files } = queryCodeMap(codeMap, { symbol: sym });
179
+ if (files.length === 0) {
180
+ console.log(`${ui.dim(sym)} (not found in code-map)`);
181
+ } else {
182
+ for (const f of files) {
183
+ const exports = f.entry.exports.filter(
184
+ (e) => e.name.toLowerCase().includes(sym.toLowerCase())
185
+ );
186
+ for (const e of exports) {
187
+ const desc = e.description ? ` \u2014 ${e.description}` : "";
188
+ console.log(`${ui.bold(e.name)} ${ui.dim(f.path + ":" + e.line)} [${e.kind}]${desc}`);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ console.log();
194
+ }
195
+ }
155
196
  });
156
197
  }
157
198
  function parseCsv(value) {
@@ -172,7 +213,7 @@ function registerTui(program2) {
172
213
  const root = findProjectRoot2(opts.dir);
173
214
  const { render } = await import("ink");
174
215
  const { createElement } = await import("react");
175
- const { Dashboard } = await import("./Dashboard-SRPCHP7Z.js");
216
+ const { Dashboard } = await import("./Dashboard-HVELRRC7.js");
176
217
  const { waitUntilExit } = render(createElement(Dashboard, { root }));
177
218
  await waitUntilExit();
178
219
  });
@@ -338,12 +379,15 @@ on:
338
379
  push:
339
380
  branches: [main, master]
340
381
  pull_request:
341
- paths:
342
- - '.ai/**'
382
+ branches: [main, master]
343
383
 
344
384
  jobs:
345
- sync:
385
+ # On push to main/master: sync anchors + auto-promote + commit changes
386
+ sync-on-merge:
387
+ if: github.event_name == 'push'
346
388
  runs-on: ubuntu-latest
389
+ permissions:
390
+ contents: write
347
391
  steps:
348
392
  - uses: actions/checkout@v4
349
393
  with:
@@ -353,19 +397,58 @@ jobs:
353
397
  with:
354
398
  node-version: '20'
355
399
 
356
- - name: Install hAIve CLI
357
- run: npm install -g @haive/cli
400
+ - name: install haive
401
+ run: npm install -g @hiveai/cli
358
402
 
359
- - name: Sync memories (verify anchors + auto-promote)
360
- run: haive sync --quiet
403
+ - name: refresh memory anchors + auto-promote
404
+ run: haive sync --since HEAD~1 || true
361
405
 
362
- - name: Commit updated memories (if any)
406
+ - name: commit updated memories (if any)
363
407
  run: |
364
408
  git config user.name "github-actions[bot]"
365
409
  git config user.email "github-actions[bot]@users.noreply.github.com"
366
410
  git add .ai/
367
411
  git diff --cached --quiet || git commit -m "chore: haive sync [skip ci]"
368
412
  git push
413
+
414
+ # On pull request: warn if PR touches files that would invalidate memories
415
+ pr-stale-check:
416
+ if: github.event_name == 'pull_request'
417
+ runs-on: ubuntu-latest
418
+ permissions:
419
+ pull-requests: write
420
+ steps:
421
+ - uses: actions/checkout@v4
422
+ with:
423
+ fetch-depth: 0
424
+
425
+ - uses: actions/setup-node@v4
426
+ with:
427
+ node-version: '20'
428
+
429
+ - name: install haive
430
+ run: npm install -g @hiveai/cli
431
+
432
+ - name: verify memory anchors touched by this PR
433
+ id: verify
434
+ run: |
435
+ haive memory verify 2>&1 | tee /tmp/haive-verify.txt || true
436
+ STALE=$(grep -c 'stale' /tmp/haive-verify.txt || echo 0)
437
+ echo "stale_count=$STALE" >> "$GITHUB_OUTPUT"
438
+
439
+ - name: comment on PR if stale memories detected
440
+ if: steps.verify.outputs.stale_count != '0'
441
+ uses: actions/github-script@v7
442
+ with:
443
+ script: |
444
+ const fs = require('fs');
445
+ const report = fs.readFileSync('/tmp/haive-verify.txt', 'utf8').trim();
446
+ await github.rest.issues.createComment({
447
+ owner: context.repo.owner,
448
+ repo: context.repo.repo,
449
+ issue_number: context.issue.number,
450
+ body: \`### haive \u2014 Stale memories detected\\n\\nSome memories anchored to code modified in this PR may be outdated:\\n\\n\\\`\\\`\\\`\\n\${report}\\n\\\`\\\`\\\`\\n\\nRun \\\`haive memory verify --update\\\` locally to refresh them before merging.\`
451
+ });
369
452
  `;
370
453
  function registerInit(program2) {
371
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)").action(async (opts) => {
@@ -813,6 +896,16 @@ function registerMemoryAdd(memory2) {
813
896
  const autoTagsEnabled = opts.autoTag !== false;
814
897
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
815
898
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
899
+ if (anchorPaths.length > 0) {
900
+ const missing = anchorPaths.filter((p) => !existsSync7(path7.resolve(root, p)));
901
+ if (missing.length > 0) {
902
+ ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
903
+ for (const p of missing) ui.warn(` \u2717 ${p}`);
904
+ ui.warn(
905
+ "Memories anchored to non-existent paths will be immediately marked stale by `haive sync`.\n Verify the paths are relative to the project root and the files/directories exist."
906
+ );
907
+ }
908
+ }
816
909
  const title = opts.title ?? opts.slug;
817
910
  let body;
818
911
  if (opts.bodyFile !== void 0) {
@@ -2066,17 +2159,124 @@ function registerMemoryImport(memory2) {
2066
2159
  });
2067
2160
  }
2068
2161
 
2069
- // src/commands/session-end.ts
2070
- import { writeFile as writeFile12, mkdir as mkdir6 } from "fs/promises";
2162
+ // src/commands/memory-digest.ts
2071
2163
  import { existsSync as existsSync25 } from "fs";
2164
+ import { writeFile as writeFile12 } from "fs/promises";
2072
2165
  import path23 from "path";
2073
2166
  import "commander";
2074
2167
  import {
2075
- buildFrontmatter as buildFrontmatter3,
2168
+ deriveConfidence as deriveConfidence4,
2076
2169
  findProjectRoot as findProjectRoot27,
2170
+ getUsage as getUsage8,
2077
2171
  loadMemoriesFromDir as loadMemoriesFromDir5,
2172
+ loadUsageIndex as loadUsageIndex10,
2173
+ resolveHaivePaths as resolveHaivePaths24
2174
+ } from "@hiveai/core";
2175
+ var CONFIDENCE_EMOJI = {
2176
+ unverified: "\u2B1C",
2177
+ low: "\u{1F7E1}",
2178
+ trusted: "\u{1F7E2}",
2179
+ authoritative: "\u2B50",
2180
+ stale: "\u{1F534}"
2181
+ };
2182
+ function registerMemoryDigest(program2) {
2183
+ program2.command("digest").description(
2184
+ "Generate a Markdown review digest of recently added/updated memories (default: last 7 days)"
2185
+ ).option("--days <n>", "look-back window in days", "7").option("--scope <scope>", "personal | team | module | all", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
2186
+ const root = findProjectRoot27(opts.dir);
2187
+ const paths = resolveHaivePaths24(root);
2188
+ if (!existsSync25(paths.memoriesDir)) {
2189
+ ui.error("No .ai/memories found. Run `haive init` first.");
2190
+ process.exitCode = 1;
2191
+ return;
2192
+ }
2193
+ const days = Math.max(1, Number(opts.days ?? 7));
2194
+ const scopeFilter = opts.scope ?? "team";
2195
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
2196
+ const all = await loadMemoriesFromDir5(paths.memoriesDir);
2197
+ const usage = await loadUsageIndex10(paths);
2198
+ const recent = all.filter(({ memory: mem }) => {
2199
+ const fm = mem.frontmatter;
2200
+ if (fm.type === "session_recap") return false;
2201
+ if (fm.status === "rejected" || fm.status === "deprecated") return false;
2202
+ if (scopeFilter !== "all" && fm.scope !== scopeFilter) return false;
2203
+ return new Date(fm.created_at) >= cutoff;
2204
+ });
2205
+ const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2206
+ const lines = [
2207
+ `# hAIve Memory Digest \u2014 ${now}`,
2208
+ ``,
2209
+ `> **Period:** last ${days} day${days > 1 ? "s" : ""} | **Scope:** ${scopeFilter} | **Total:** ${recent.length} memor${recent.length === 1 ? "y" : "ies"}`,
2210
+ ``,
2211
+ `---`,
2212
+ ``
2213
+ ];
2214
+ if (recent.length === 0) {
2215
+ lines.push(`_No new memories in the last ${days} days._`);
2216
+ } else {
2217
+ const byType = /* @__PURE__ */ new Map();
2218
+ for (const m of recent) {
2219
+ const t = m.memory.frontmatter.type;
2220
+ if (!byType.has(t)) byType.set(t, []);
2221
+ byType.get(t).push(m);
2222
+ }
2223
+ for (const [type, mems] of byType) {
2224
+ lines.push(`## ${type.charAt(0).toUpperCase() + type.slice(1)} (${mems.length})`);
2225
+ lines.push(``);
2226
+ for (const { memory: mem } of mems) {
2227
+ const fm = mem.frontmatter;
2228
+ const u = getUsage8(usage, fm.id);
2229
+ const confidence = deriveConfidence4(fm, u);
2230
+ const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
2231
+ const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
2232
+ lines.push(`### ${emoji} \`${fm.id}\``);
2233
+ lines.push(``);
2234
+ lines.push(`| Field | Value |`);
2235
+ lines.push(`|---|---|`);
2236
+ lines.push(`| **Status** | \`${fm.status}\` |`);
2237
+ lines.push(`| **Confidence** | ${confidence} |`);
2238
+ lines.push(`| **Scope** | ${fm.scope}${fm.module ? `/${fm.module}` : ""} |`);
2239
+ lines.push(`| **Tags** | ${fm.tags.length > 0 ? fm.tags.map((t) => `\`${t}\``).join(", ") : "_none_"} |`);
2240
+ lines.push(`| **Anchor** | ${anchor} |`);
2241
+ lines.push(`| **Reads** | ${u.read_count} |`);
2242
+ lines.push(`| **Created** | ${fm.created_at.slice(0, 10)} |`);
2243
+ lines.push(``);
2244
+ const bodyPreview = mem.body.split("\n").slice(0, 6).join("\n").trim();
2245
+ lines.push(bodyPreview);
2246
+ lines.push(``);
2247
+ lines.push(`**Action:** [ ] approve &nbsp;&nbsp; [ ] reject &nbsp;&nbsp; [ ] keep as-is`);
2248
+ lines.push(``);
2249
+ lines.push(`---`);
2250
+ lines.push(``);
2251
+ }
2252
+ }
2253
+ }
2254
+ lines.push(``);
2255
+ lines.push(
2256
+ `> _To take action: \`haive memory approve <id>\`, \`haive memory reject <id>\`, or open \`haive tui\` for interactive review._`
2257
+ );
2258
+ const digest = lines.join("\n");
2259
+ if (opts.out) {
2260
+ const outPath = path23.resolve(process.cwd(), opts.out);
2261
+ await writeFile12(outPath, digest, "utf8");
2262
+ ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
2263
+ } else {
2264
+ console.log(digest);
2265
+ }
2266
+ });
2267
+ }
2268
+
2269
+ // src/commands/session-end.ts
2270
+ import { writeFile as writeFile13, mkdir as mkdir6 } from "fs/promises";
2271
+ import { existsSync as existsSync26 } from "fs";
2272
+ import path24 from "path";
2273
+ import "commander";
2274
+ import {
2275
+ buildFrontmatter as buildFrontmatter3,
2276
+ findProjectRoot as findProjectRoot28,
2277
+ loadMemoriesFromDir as loadMemoriesFromDir6,
2078
2278
  memoryFilePath as memoryFilePath4,
2079
- resolveHaivePaths as resolveHaivePaths24,
2279
+ resolveHaivePaths as resolveHaivePaths25,
2080
2280
  serializeMemory as serializeMemory10
2081
2281
  } from "@hiveai/core";
2082
2282
  function buildRecapBody(opts) {
@@ -2109,9 +2309,9 @@ function recapTopic(scope, module) {
2109
2309
  }
2110
2310
  function registerSessionEnd(session2) {
2111
2311
  session2.command("end").description("Save a structured end-of-session recap (goal / accomplished / discoveries / next steps)").requiredOption("--goal <text>", "What you were trying to accomplish (1\u20132 sentences)").requiredOption("--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").option("--next <text>", "What should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
2112
- const root = findProjectRoot27(opts.dir);
2113
- const paths = resolveHaivePaths24(root);
2114
- if (!existsSync25(paths.haiveDir)) {
2312
+ const root = findProjectRoot28(opts.dir);
2313
+ const paths = resolveHaivePaths25(root);
2314
+ if (!existsSync26(paths.haiveDir)) {
2115
2315
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
2116
2316
  process.exitCode = 1;
2117
2317
  return;
@@ -2120,8 +2320,13 @@ function registerSessionEnd(session2) {
2120
2320
  const body = buildRecapBody(opts);
2121
2321
  const topic = recapTopic(scope, opts.module);
2122
2322
  const filesTouched = parseCsv5(opts.files);
2123
- if (existsSync25(paths.memoriesDir)) {
2124
- const existing = await loadMemoriesFromDir5(paths.memoriesDir);
2323
+ const missingPaths = filesTouched.filter((p) => !existsSync26(path24.resolve(root, p)));
2324
+ if (missingPaths.length > 0) {
2325
+ ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
2326
+ for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
2327
+ }
2328
+ if (existsSync26(paths.memoriesDir)) {
2329
+ const existing = await loadMemoriesFromDir6(paths.memoriesDir);
2125
2330
  const topicMatch = existing.find(
2126
2331
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
2127
2332
  );
@@ -2136,9 +2341,9 @@ function registerSessionEnd(session2) {
2136
2341
  paths: filesTouched.length ? filesTouched : fm.anchor.paths
2137
2342
  }
2138
2343
  };
2139
- await writeFile12(topicMatch.filePath, serializeMemory10({ frontmatter: newFrontmatter, body }), "utf8");
2344
+ await writeFile13(topicMatch.filePath, serializeMemory10({ frontmatter: newFrontmatter, body }), "utf8");
2140
2345
  ui.success(`Session recap updated (revision #${revisionCount})`);
2141
- ui.info(`id=${fm.id} file=${path23.relative(root, topicMatch.filePath)}`);
2346
+ ui.info(`id=${fm.id} file=${path24.relative(root, topicMatch.filePath)}`);
2142
2347
  return;
2143
2348
  }
2144
2349
  }
@@ -2153,10 +2358,10 @@ function registerSessionEnd(session2) {
2153
2358
  status: "validated"
2154
2359
  });
2155
2360
  const file = memoryFilePath4(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
2156
- await mkdir6(path23.dirname(file), { recursive: true });
2157
- await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
2361
+ await mkdir6(path24.dirname(file), { recursive: true });
2362
+ await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
2158
2363
  ui.success(`Session recap created`);
2159
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path23.relative(root, file)}`);
2364
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path24.relative(root, file)}`);
2160
2365
  ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
2161
2366
  });
2162
2367
  }
@@ -2166,8 +2371,8 @@ function parseCsv5(value) {
2166
2371
  }
2167
2372
 
2168
2373
  // src/index.ts
2169
- var program = new Command28();
2170
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.15");
2374
+ var program = new Command29();
2375
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.3.0");
2171
2376
  registerInit(program);
2172
2377
  registerMcp(program);
2173
2378
  registerBriefing(program);
@@ -2195,6 +2400,7 @@ registerMemoryUpdate(memory);
2195
2400
  registerMemoryHot(memory);
2196
2401
  registerMemoryTried(memory);
2197
2402
  registerMemoryImport(memory);
2403
+ registerMemoryDigest(memory);
2198
2404
  var session = program.command("session").description("Manage session lifecycle");
2199
2405
  registerSessionEnd(session);
2200
2406
  program.parseAsync(process.argv).catch((err) => {