@hiveai/cli 0.4.3 → 0.5.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 Command32 } from "commander";
4
+ import { Command as Command35 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -341,6 +341,33 @@ function registerIndexCode(program2) {
341
341
  `Indexed ${fileCount} file(s) with ${exportCount} export(s) \u2192 ${path2.relative(root, codeMapPath(paths))}`
342
342
  );
343
343
  });
344
+ idx.command("code-search").description(
345
+ "Build the semantic-search embeddings index for code (powers the code_search MCP tool).\n\n Reads .ai/code-map.json (run `haive index code` first) and embeds each exported\n symbol's metadata (filename + name + kind + description).\n\n Re-runs are incremental: unchanged entries keep their cached vectors, only the\n diff is re-embedded. First run downloads the bge-small-en-v1.5 model (~110MB).\n"
346
+ ).option("-d, --dir <dir>", "project root").action(async (opts) => {
347
+ const root = findProjectRoot4(opts.dir);
348
+ const paths = resolveHaivePaths3(root);
349
+ let mod;
350
+ try {
351
+ mod = await import("@hiveai/embeddings");
352
+ } catch {
353
+ ui.error(
354
+ "@hiveai/embeddings is not installed. Install it (`pnpm add @hiveai/embeddings`) or run `haive embeddings install`."
355
+ );
356
+ process.exit(1);
357
+ }
358
+ ui.info("Loading embedder (first run downloads ~110MB)\u2026");
359
+ const embedder = await mod.Embedder.create();
360
+ ui.info(`Embedding code-map symbols\u2026`);
361
+ try {
362
+ const { report } = await mod.rebuildCodeIndex(paths, embedder);
363
+ ui.success(
364
+ `Code-search index ready: ${report.total} symbols (+${report.added} new, ~${report.updated} updated, =${report.unchanged} cached, -${report.removed} removed)`
365
+ );
366
+ } catch (err) {
367
+ ui.error(err instanceof Error ? err.message : String(err));
368
+ process.exit(1);
369
+ }
370
+ });
344
371
  }
345
372
 
346
373
  // src/commands/init.ts
@@ -470,6 +497,25 @@ jobs:
470
497
  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.\`
471
498
  });
472
499
 
500
+ # \u2500\u2500 hAIve PR Memory Check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
501
+ # Posts a comment on every PR surfacing memories relevant to the changed files.
502
+ # Reviewers and AI agents see gotchas, conventions, and action_required items
503
+ # before they start coding.
504
+ pr-memory-check:
505
+ if: github.event_name == 'pull_request'
506
+ runs-on: ubuntu-latest
507
+ permissions:
508
+ pull-requests: write
509
+ contents: read
510
+ steps:
511
+ - uses: actions/checkout@v4
512
+
513
+ - uses: Doucs91/hAIve/packages/github-action@main
514
+ with:
515
+ github-token: \${{ secrets.GITHUB_TOKEN }}
516
+ # post-if-empty: 'true' # uncomment to always post (even when no memories found)
517
+ # max-memories: '5' # limit memories per file in the comment
518
+
473
519
  # On push to main: push shared memories to the hub (if hubPath is configured)
474
520
  # Uncomment and configure hubPath in .ai/haive.config.json to enable.
475
521
  # hub-push:
@@ -490,7 +536,6 @@ jobs:
490
536
  # - name: push shared memories to hub
491
537
  # run: haive hub push --commit
492
538
  # # Requires hubPath in .ai/haive.config.json pointing to a cloned hub repo.
493
- # # The hub repo must be available at that path in the CI workspace.
494
539
  `;
495
540
  function registerInit(program2) {
496
541
  program2.command("init").description(
@@ -3326,9 +3371,294 @@ Next steps:
3326
3371
  });
3327
3372
  }
3328
3373
 
3374
+ // src/commands/stats.ts
3375
+ import "commander";
3376
+ import {
3377
+ aggregateUsage,
3378
+ findProjectRoot as findProjectRoot32,
3379
+ parseSince,
3380
+ readUsageEvents,
3381
+ resolveHaivePaths as resolveHaivePaths29,
3382
+ usageLogSize
3383
+ } from "@hiveai/core";
3384
+ function registerStats(program2) {
3385
+ 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) => {
3386
+ const root = findProjectRoot32(opts.dir);
3387
+ const paths = resolveHaivePaths29(root);
3388
+ const size = await usageLogSize(paths);
3389
+ if (!size.exists) {
3390
+ if (opts.json) {
3391
+ console.log(JSON.stringify({ error: "no_usage_log" }));
3392
+ return;
3393
+ }
3394
+ ui.warn(
3395
+ `No usage log found at ${root}. Stats are populated as the MCP server logs each tool call. Run a session first, then re-check.`
3396
+ );
3397
+ return;
3398
+ }
3399
+ const events = await readUsageEvents(paths);
3400
+ const since = parseSince(opts.since);
3401
+ const aggregate = aggregateUsage(events, since ?? void 0);
3402
+ if (opts.json) {
3403
+ console.log(JSON.stringify(aggregate, null, 2));
3404
+ return;
3405
+ }
3406
+ const window = opts.since ?? "all time";
3407
+ console.log(ui.bold(`hAIve usage stats (${window})`));
3408
+ console.log(
3409
+ ` ${ui.dim("total calls:")} ${aggregate.total} ${ui.dim("unique tools:")} ${aggregate.by_tool.length} ${ui.dim("log lines:")} ${size.lines}`
3410
+ );
3411
+ if (aggregate.window_start) {
3412
+ console.log(
3413
+ ` ${ui.dim("window:")} ${aggregate.window_start.slice(0, 19)} \u2192 ${aggregate.window_end?.slice(0, 19)}`
3414
+ );
3415
+ }
3416
+ if (aggregate.by_tool.length === 0) {
3417
+ ui.info(`No events in window. Try a wider --since (current: ${window}).`);
3418
+ return;
3419
+ }
3420
+ console.log();
3421
+ console.log(ui.bold("Top tools:"));
3422
+ const maxCount = aggregate.by_tool[0]?.count ?? 1;
3423
+ for (const t of aggregate.by_tool.slice(0, 20)) {
3424
+ const bar = "\u2588".repeat(Math.max(1, Math.round(t.count / maxCount * 30)));
3425
+ const pct = (t.count / aggregate.total * 100).toFixed(1);
3426
+ console.log(
3427
+ ` ${t.tool.padEnd(28)} ${ui.green(bar)} ${ui.bold(String(t.count))} ${ui.dim(`(${pct}%, last ${t.last_used.slice(0, 19)})`)}`
3428
+ );
3429
+ }
3430
+ });
3431
+ }
3432
+
3433
+ // src/commands/bench.ts
3434
+ import { performance } from "perf_hooks";
3435
+ import "commander";
3436
+ import {
3437
+ estimateTokens,
3438
+ findProjectRoot as findProjectRoot33,
3439
+ resolveHaivePaths as resolveHaivePaths30
3440
+ } from "@hiveai/core";
3441
+ import {
3442
+ antiPatternsCheck,
3443
+ codeMapTool,
3444
+ codeSearch,
3445
+ getBriefing,
3446
+ getRecap,
3447
+ memRelevantTo
3448
+ } from "@hiveai/mcp";
3449
+ function registerBench(program2) {
3450
+ 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) => {
3451
+ const root = findProjectRoot33(opts.dir);
3452
+ const paths = resolveHaivePaths30(root);
3453
+ const ctx = { paths };
3454
+ const task = opts.task ?? "audit dependencies for security risks";
3455
+ const scenarios = [
3456
+ async () => {
3457
+ const t0 = performance.now();
3458
+ const out = await getBriefing(
3459
+ {
3460
+ task,
3461
+ files: [],
3462
+ max_tokens: 4e3,
3463
+ max_memories: 8,
3464
+ include_project_context: true,
3465
+ include_module_contexts: true,
3466
+ semantic: true,
3467
+ include_stale: false,
3468
+ track: false,
3469
+ format: "compact",
3470
+ symbols: [],
3471
+ min_semantic_score: 0
3472
+ },
3473
+ ctx
3474
+ );
3475
+ return summarize("get_briefing(compact)", t0, out, [
3476
+ out.low_value ? "low_value (cold-start)" : `${out.memories.length} memories`,
3477
+ `search=${out.search_mode}`
3478
+ ]);
3479
+ },
3480
+ async () => {
3481
+ const t0 = performance.now();
3482
+ const out = await codeMapTool({ paths: [], max_files: 40, max_tokens: 2e3 }, ctx);
3483
+ return summarize("code_map(budget=2k)", t0, out, [
3484
+ out.available ? `${out.files.length}/${out.total_files} files` : "unavailable",
3485
+ out.budget_clipped ? "clipped" : "fits"
3486
+ ]);
3487
+ },
3488
+ async () => {
3489
+ const t0 = performance.now();
3490
+ const out = await getRecap({ scope: "any" }, ctx);
3491
+ return summarize("get_recap", t0, out, [
3492
+ out.recap ? `${out.recap.id.slice(0, 30)}\u2026` : "no recap"
3493
+ ]);
3494
+ },
3495
+ async () => {
3496
+ const t0 = performance.now();
3497
+ const out = await memRelevantTo(
3498
+ { task, files: [], limit: 8, min_semantic_score: 0.25, format: "compact" },
3499
+ ctx
3500
+ );
3501
+ return summarize("mem_relevant_to", t0, out, [
3502
+ `${out.memories.length} memories`,
3503
+ `search=${out.search_mode}`
3504
+ ]);
3505
+ },
3506
+ async () => {
3507
+ const t0 = performance.now();
3508
+ const out = await codeSearch({ query: task, k: 5, min_score: 0.2 }, ctx);
3509
+ return summarize("code_search", t0, out, [
3510
+ out.available ? `${out.hits.length} hits` : "needs index (haive index code-search)"
3511
+ ]);
3512
+ },
3513
+ async () => {
3514
+ const t0 = performance.now();
3515
+ const out = await antiPatternsCheck({ diff: task, paths: [], limit: 5, semantic: true }, ctx);
3516
+ return summarize("anti_patterns_check", t0, out, [
3517
+ `${out.warnings.length}/${out.scanned} warn`
3518
+ ]);
3519
+ }
3520
+ ];
3521
+ const results = [];
3522
+ for (const run of scenarios) {
3523
+ try {
3524
+ results.push(await run());
3525
+ } catch (err) {
3526
+ results.push({
3527
+ name: "(error)",
3528
+ ok: false,
3529
+ latency_ms: 0,
3530
+ payload_tokens: 0,
3531
+ notes: [err instanceof Error ? err.message : String(err)]
3532
+ });
3533
+ }
3534
+ }
3535
+ if (opts.json) {
3536
+ console.log(JSON.stringify({ root, task, scenarios: results }, null, 2));
3537
+ return;
3538
+ }
3539
+ console.log(ui.bold(`hAIve bench \u2014 ${root}`));
3540
+ console.log(ui.dim(`task: ${task}`));
3541
+ console.log();
3542
+ console.log(
3543
+ `${"scenario".padEnd(28)} ${"latency".padStart(8)} ${"tokens".padStart(7)} notes`
3544
+ );
3545
+ console.log("\u2500".repeat(88));
3546
+ for (const r of results) {
3547
+ const status = r.ok ? ui.green("\u2713") : ui.red("\u2717");
3548
+ console.log(
3549
+ `${status} ${r.name.padEnd(26)} ${`${r.latency_ms.toFixed(0)} ms`.padStart(8)} ${String(r.payload_tokens).padStart(7)} ${r.notes.join("; ")}`
3550
+ );
3551
+ }
3552
+ const totalTokens = results.reduce((s, r) => s + r.payload_tokens, 0);
3553
+ const totalMs = results.reduce((s, r) => s + r.latency_ms, 0);
3554
+ console.log("\u2500".repeat(88));
3555
+ console.log(
3556
+ `${ui.dim("totals:")} ${`${totalMs.toFixed(0)} ms`.padStart(8)} ${String(totalTokens).padStart(7)}`
3557
+ );
3558
+ });
3559
+ }
3560
+ function summarize(name, t0, payload, notes) {
3561
+ return {
3562
+ name,
3563
+ ok: true,
3564
+ latency_ms: performance.now() - t0,
3565
+ payload_tokens: estimateTokens(JSON.stringify(payload)),
3566
+ notes
3567
+ };
3568
+ }
3569
+
3570
+ // src/commands/memory-suggest.ts
3571
+ import "commander";
3572
+ import {
3573
+ aggregateUsage as aggregateUsage2,
3574
+ findProjectRoot as findProjectRoot34,
3575
+ parseSince as parseSince2,
3576
+ readUsageEvents as readUsageEvents2,
3577
+ resolveHaivePaths as resolveHaivePaths31
3578
+ } from "@hiveai/core";
3579
+ var SEARCH_TOOLS = /* @__PURE__ */ new Set([
3580
+ "mem_search",
3581
+ "code_search",
3582
+ "mem_relevant_to",
3583
+ "get_briefing"
3584
+ ]);
3585
+ function registerMemorySuggest(memory2) {
3586
+ memory2.command("suggest").description("Suggest memories to create based on recurring search queries in the usage log.").option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
3587
+ const root = findProjectRoot34(opts.dir);
3588
+ const paths = resolveHaivePaths31(root);
3589
+ const events = await readUsageEvents2(paths);
3590
+ if (events.length === 0) {
3591
+ if (opts.json) {
3592
+ console.log(JSON.stringify({ suggestions: [] }));
3593
+ return;
3594
+ }
3595
+ ui.warn("No usage log entries yet. Suggestions appear after the MCP server records some calls.");
3596
+ return;
3597
+ }
3598
+ const since = parseSince2(opts.since);
3599
+ const minCount = Math.max(1, parseInt(opts.min ?? "2", 10));
3600
+ const cutoff = since ? since.getTime() : 0;
3601
+ const queries = /* @__PURE__ */ new Map();
3602
+ for (const e of events) {
3603
+ if (cutoff && Date.parse(e.at) < cutoff) continue;
3604
+ if (!SEARCH_TOOLS.has(e.tool)) continue;
3605
+ const key = (e.summary ?? "").toLowerCase().trim();
3606
+ if (!key) continue;
3607
+ const prior = queries.get(key);
3608
+ if (prior) {
3609
+ prior.count++;
3610
+ prior.tools.add(e.tool);
3611
+ if (e.at > prior.last) prior.last = e.at;
3612
+ } else {
3613
+ queries.set(key, { count: 1, tools: /* @__PURE__ */ new Set([e.tool]), last: e.at });
3614
+ }
3615
+ }
3616
+ const suggestions = [...queries.entries()].filter(([, v]) => v.count >= minCount).map(([query, v]) => ({
3617
+ query,
3618
+ count: v.count,
3619
+ tools: [...v.tools].sort(),
3620
+ last_used: v.last,
3621
+ reason: chooseReason(v.tools, v.count)
3622
+ })).sort((a, b) => b.count - a.count);
3623
+ if (opts.json) {
3624
+ console.log(JSON.stringify({ window: opts.since, suggestions }, null, 2));
3625
+ return;
3626
+ }
3627
+ const totals = aggregateUsage2(events, since ?? void 0);
3628
+ console.log(ui.bold(`hAIve memory suggestions (${opts.since ?? "all time"})`));
3629
+ console.log(
3630
+ ui.dim(`scanned ${totals.total} events, ${suggestions.length} repeated queries (\u2265${minCount})`)
3631
+ );
3632
+ console.log();
3633
+ if (suggestions.length === 0) {
3634
+ ui.info("No recurring searches yet \u2014 nothing to suggest.");
3635
+ return;
3636
+ }
3637
+ for (const s of suggestions.slice(0, 30)) {
3638
+ console.log(
3639
+ ` ${ui.bold(`\xD7${s.count}`)} ${ui.dim(`[${s.tools.join(",")}]`)} ${truncate(s.query, 70)}`
3640
+ );
3641
+ console.log(` ${ui.dim("\u2192")} ${s.reason}`);
3642
+ }
3643
+ });
3644
+ }
3645
+ function chooseReason(tools, count) {
3646
+ if (tools.has("code_search")) {
3647
+ return `${count} agents searched the code for this \u2014 consider mem_save (architecture/decision) capturing where it lives.`;
3648
+ }
3649
+ if (tools.has("mem_search") || tools.has("mem_relevant_to")) {
3650
+ return `${count} agents asked but the memory layer had no clear answer \u2014 consider mem_save (convention/decision/gotcha).`;
3651
+ }
3652
+ return `${count} agents asked the briefing for this \u2014 consider promoting the answer to a team memory.`;
3653
+ }
3654
+ function truncate(text, max) {
3655
+ if (text.length <= max) return text;
3656
+ return text.slice(0, max - 1) + "\u2026";
3657
+ }
3658
+
3329
3659
  // src/index.ts
3330
- var program = new Command32();
3331
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.4.3");
3660
+ var program = new Command35();
3661
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.5.0");
3332
3662
  registerInit(program);
3333
3663
  registerMcp(program);
3334
3664
  registerBriefing(program);
@@ -3358,10 +3688,13 @@ registerMemoryTried(memory);
3358
3688
  registerMemoryImport(memory);
3359
3689
  registerMemoryImportChangelog(memory);
3360
3690
  registerMemoryDigest(memory);
3691
+ registerMemorySuggest(memory);
3361
3692
  var session = program.command("session").description("Manage session lifecycle");
3362
3693
  registerSessionEnd(session);
3363
3694
  registerSnapshot(program);
3364
3695
  registerHub(program);
3696
+ registerStats(program);
3697
+ registerBench(program);
3365
3698
  program.parseAsync(process.argv).catch((err) => {
3366
3699
  if (isZodError(err)) {
3367
3700
  for (const issue of err.issues) {