@hiveai/cli 0.6.0 → 0.7.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 +1179 -341
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -408,9 +408,9 @@ function registerIndexCode(program2) {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
// src/commands/init.ts
|
|
411
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
412
|
-
import { existsSync as
|
|
413
|
-
import
|
|
411
|
+
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
412
|
+
import { existsSync as existsSync6 } from "fs";
|
|
413
|
+
import path7 from "path";
|
|
414
414
|
import { spawnSync } from "child_process";
|
|
415
415
|
import "commander";
|
|
416
416
|
import {
|
|
@@ -420,9 +420,719 @@ import {
|
|
|
420
420
|
saveCodeMap as saveCodeMap2,
|
|
421
421
|
saveConfig
|
|
422
422
|
} from "@hiveai/core";
|
|
423
|
+
|
|
424
|
+
// src/commands/init-bootstrap.ts
|
|
425
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
426
|
+
import { existsSync as existsSync3 } from "fs";
|
|
427
|
+
import path4 from "path";
|
|
428
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
429
|
+
"node_modules",
|
|
430
|
+
"dist",
|
|
431
|
+
"build",
|
|
432
|
+
".next",
|
|
433
|
+
".nuxt",
|
|
434
|
+
".svelte-kit",
|
|
435
|
+
".git",
|
|
436
|
+
"coverage",
|
|
437
|
+
".turbo",
|
|
438
|
+
"out",
|
|
439
|
+
".cache",
|
|
440
|
+
"tmp",
|
|
441
|
+
"temp",
|
|
442
|
+
"__pycache__",
|
|
443
|
+
".venv",
|
|
444
|
+
"venv",
|
|
445
|
+
"target",
|
|
446
|
+
".gradle"
|
|
447
|
+
]);
|
|
448
|
+
var FRAMEWORK_SIGNALS = {
|
|
449
|
+
"NestJS": ["@nestjs/core", "@nestjs/common"],
|
|
450
|
+
"Next.js": ["next"],
|
|
451
|
+
"Remix": ["@remix-run/react", "@remix-run/node"],
|
|
452
|
+
"React": ["react", "react-dom"],
|
|
453
|
+
"Vue": ["vue"],
|
|
454
|
+
"Svelte": ["svelte"],
|
|
455
|
+
"SvelteKit": ["@sveltejs/kit"],
|
|
456
|
+
"Astro": ["astro"],
|
|
457
|
+
"Express": ["express"],
|
|
458
|
+
"Fastify": ["fastify"],
|
|
459
|
+
"Hono": ["hono"],
|
|
460
|
+
"tRPC": ["@trpc/server"],
|
|
461
|
+
"Prisma": ["@prisma/client"],
|
|
462
|
+
"Drizzle": ["drizzle-orm"],
|
|
463
|
+
"Vite": ["vite"],
|
|
464
|
+
"Vitest": ["vitest"],
|
|
465
|
+
"Jest": ["jest"]
|
|
466
|
+
};
|
|
467
|
+
var KEY_DEPS = [
|
|
468
|
+
"@nestjs/jwt",
|
|
469
|
+
"@nestjs/passport",
|
|
470
|
+
"passport-jwt",
|
|
471
|
+
"jsonwebtoken",
|
|
472
|
+
"bcrypt",
|
|
473
|
+
"bcryptjs",
|
|
474
|
+
"stripe",
|
|
475
|
+
"axios",
|
|
476
|
+
"socket.io",
|
|
477
|
+
"ws",
|
|
478
|
+
"redis",
|
|
479
|
+
"ioredis",
|
|
480
|
+
"pg",
|
|
481
|
+
"mysql2",
|
|
482
|
+
"mongodb",
|
|
483
|
+
"mongoose",
|
|
484
|
+
"zod",
|
|
485
|
+
"yup",
|
|
486
|
+
"class-validator",
|
|
487
|
+
"tailwindcss",
|
|
488
|
+
"shadcn",
|
|
489
|
+
"@radix-ui",
|
|
490
|
+
"@vercel/ai",
|
|
491
|
+
"ai",
|
|
492
|
+
"openai",
|
|
493
|
+
"@anthropic-ai/sdk",
|
|
494
|
+
"typescript"
|
|
495
|
+
];
|
|
496
|
+
function detectFrameworks(allDeps) {
|
|
497
|
+
const found = [];
|
|
498
|
+
for (const [fw, signals] of Object.entries(FRAMEWORK_SIGNALS)) {
|
|
499
|
+
if (signals.some((s) => allDeps[s] !== void 0)) found.push(fw);
|
|
500
|
+
}
|
|
501
|
+
return found;
|
|
502
|
+
}
|
|
503
|
+
function detectKeyDeps(allDeps) {
|
|
504
|
+
return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
|
|
505
|
+
}
|
|
506
|
+
function detectLanguage(root) {
|
|
507
|
+
if (existsSync3(path4.join(root, "tsconfig.json"))) return "TypeScript";
|
|
508
|
+
if (existsSync3(path4.join(root, "pyproject.toml")) || existsSync3(path4.join(root, "setup.py"))) return "Python";
|
|
509
|
+
if (existsSync3(path4.join(root, "go.mod"))) return "Go";
|
|
510
|
+
if (existsSync3(path4.join(root, "pom.xml")) || existsSync3(path4.join(root, "build.gradle"))) return "Java/Kotlin";
|
|
511
|
+
if (existsSync3(path4.join(root, "Cargo.toml"))) return "Rust";
|
|
512
|
+
if (existsSync3(path4.join(root, "package.json"))) return "JavaScript";
|
|
513
|
+
return "Unknown";
|
|
514
|
+
}
|
|
515
|
+
function detectProjectType(frameworks, scripts) {
|
|
516
|
+
if (frameworks.includes("NestJS")) return "Backend API (NestJS)";
|
|
517
|
+
if (frameworks.includes("Next.js")) return "Full-stack web app (Next.js)";
|
|
518
|
+
if (frameworks.includes("Remix")) return "Full-stack web app (Remix)";
|
|
519
|
+
if (frameworks.includes("Express") || frameworks.includes("Fastify") || frameworks.includes("Hono")) return "Backend API";
|
|
520
|
+
if (frameworks.includes("React") || frameworks.includes("Vue") || frameworks.includes("Svelte")) return "Frontend SPA";
|
|
521
|
+
if (scripts["build"] && !scripts["dev"]) return "CLI tool / library";
|
|
522
|
+
if (existsSync3("pom.xml")) return "Java backend";
|
|
523
|
+
return "Application";
|
|
524
|
+
}
|
|
525
|
+
async function scanDirs(root, maxDepth = 2) {
|
|
526
|
+
const results = [];
|
|
527
|
+
async function walk(dir, depth) {
|
|
528
|
+
if (depth > maxDepth) return;
|
|
529
|
+
let entries;
|
|
530
|
+
try {
|
|
531
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
532
|
+
} catch {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
for (const entry of entries) {
|
|
536
|
+
if (!entry.isDirectory()) continue;
|
|
537
|
+
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
538
|
+
const rel = path4.relative(root, path4.join(dir, entry.name));
|
|
539
|
+
results.push(rel);
|
|
540
|
+
await walk(path4.join(dir, entry.name), depth + 1);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
await walk(root, 0);
|
|
544
|
+
return results;
|
|
545
|
+
}
|
|
546
|
+
function inferModuleDescriptions(dirs) {
|
|
547
|
+
const known = {
|
|
548
|
+
"src": "main source directory",
|
|
549
|
+
"app": "application entrypoint / routes (Next.js App Router or similar)",
|
|
550
|
+
"pages": "file-based routing pages",
|
|
551
|
+
"components": "reusable UI components",
|
|
552
|
+
"lib": "shared utilities and helpers",
|
|
553
|
+
"utils": "utility functions",
|
|
554
|
+
"hooks": "React hooks",
|
|
555
|
+
"services": "business logic services",
|
|
556
|
+
"controllers": "HTTP controllers / route handlers",
|
|
557
|
+
"modules": "feature modules",
|
|
558
|
+
"middleware": "HTTP or business middleware",
|
|
559
|
+
"guards": "auth / access guards",
|
|
560
|
+
"prisma": "Prisma schema and migrations",
|
|
561
|
+
"migrations": "database migrations",
|
|
562
|
+
"config": "configuration files",
|
|
563
|
+
"types": "TypeScript type definitions",
|
|
564
|
+
"schemas": "validation schemas (Zod / class-validator)",
|
|
565
|
+
"test": "tests",
|
|
566
|
+
"tests": "tests",
|
|
567
|
+
"__tests__": "tests",
|
|
568
|
+
"e2e": "end-to-end tests",
|
|
569
|
+
"public": "static public assets",
|
|
570
|
+
"assets": "static assets",
|
|
571
|
+
"styles": "global CSS / style files",
|
|
572
|
+
"scripts": "build or utility scripts",
|
|
573
|
+
"docs": "documentation",
|
|
574
|
+
"docker": "Docker configuration",
|
|
575
|
+
"infra": "infrastructure / IaC",
|
|
576
|
+
"packages": "monorepo sub-packages",
|
|
577
|
+
"functions": "serverless / edge functions",
|
|
578
|
+
"api": "API routes or client",
|
|
579
|
+
"store": "state management (Redux / Zustand / Pinia)",
|
|
580
|
+
"context": "React contexts",
|
|
581
|
+
"server": "server-side code",
|
|
582
|
+
"client": "client-side code",
|
|
583
|
+
"features": "feature-based modules",
|
|
584
|
+
"routes": "route definitions",
|
|
585
|
+
"workers": "background workers / queues"
|
|
586
|
+
};
|
|
587
|
+
const top = dirs.filter((d) => !d.includes("/")).slice(0, 12);
|
|
588
|
+
return top.map((d) => {
|
|
589
|
+
const desc = known[d.toLowerCase()] ?? "module";
|
|
590
|
+
return `- \`${d}/\` \u2014 ${desc}`;
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
function readmeExcerpt(readme) {
|
|
594
|
+
const lines = readme.split("\n");
|
|
595
|
+
let inContent = false;
|
|
596
|
+
const kept = [];
|
|
597
|
+
for (const line of lines) {
|
|
598
|
+
if (!inContent && line.trim().startsWith("#")) {
|
|
599
|
+
inContent = true;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (!inContent) continue;
|
|
603
|
+
if (kept.length >= 6) break;
|
|
604
|
+
if (line.trim()) kept.push(line.trim());
|
|
605
|
+
}
|
|
606
|
+
return kept.join(" ").slice(0, 400);
|
|
607
|
+
}
|
|
608
|
+
async function generateBootstrapContext(root) {
|
|
609
|
+
let pkg = {};
|
|
610
|
+
const pkgPath = path4.join(root, "package.json");
|
|
611
|
+
if (existsSync3(pkgPath)) {
|
|
612
|
+
try {
|
|
613
|
+
pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
614
|
+
} catch {
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
618
|
+
const frameworks = detectFrameworks(allDeps);
|
|
619
|
+
const keyDeps = detectKeyDeps(allDeps);
|
|
620
|
+
const language = detectLanguage(root);
|
|
621
|
+
const projectType = detectProjectType(frameworks, pkg.scripts ?? {});
|
|
622
|
+
const projectName = pkg.name ?? path4.basename(root);
|
|
623
|
+
const projectDesc = pkg.description ?? "";
|
|
624
|
+
let readmeSummary = "";
|
|
625
|
+
for (const name of ["README.md", "readme.md", "README"]) {
|
|
626
|
+
const p = path4.join(root, name);
|
|
627
|
+
if (existsSync3(p)) {
|
|
628
|
+
try {
|
|
629
|
+
const content = await readFile2(p, "utf8");
|
|
630
|
+
readmeSummary = readmeExcerpt(content);
|
|
631
|
+
break;
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const dirs = await scanDirs(root, 2);
|
|
637
|
+
const moduleLines = inferModuleDescriptions(dirs);
|
|
638
|
+
const scripts = pkg.scripts ?? {};
|
|
639
|
+
const scriptLines = Object.entries(scripts).filter(([k]) => ["build", "dev", "start", "test", "lint", "deploy"].includes(k)).map(([k, v]) => `- \`${k}\`: ${v}`).slice(0, 6);
|
|
640
|
+
const stackParts = [language];
|
|
641
|
+
if (frameworks.length) stackParts.push(...frameworks);
|
|
642
|
+
const techStack = stackParts.join(", ");
|
|
643
|
+
const notableDeps = Object.keys(allDeps).filter((d) => !d.startsWith("@types/") && !["typescript", "eslint", "prettier", "jest"].includes(d)).filter((d) => !["react", "react-dom", "next", "vue", "express"].includes(d)).slice(0, 10).map((d) => `\`${d}\``);
|
|
644
|
+
const lines = [
|
|
645
|
+
`# Project context \u2014 ${projectName}`,
|
|
646
|
+
"",
|
|
647
|
+
`> Auto-generated by \`haive init --bootstrap\`. Review and refine \u2014 especially the Architecture and Gotchas sections.`,
|
|
648
|
+
"",
|
|
649
|
+
`## Overview`,
|
|
650
|
+
`**Type:** ${projectType}`,
|
|
651
|
+
`**Tech stack:** ${techStack}`,
|
|
652
|
+
...projectDesc ? [`**Description:** ${projectDesc}`] : [],
|
|
653
|
+
...readmeSummary ? [`**From README:** ${readmeSummary}`] : [],
|
|
654
|
+
"",
|
|
655
|
+
`## Architecture`,
|
|
656
|
+
`TODO \u2014 fill in the high-level architecture (inferred structure below, verify manually):`,
|
|
657
|
+
"",
|
|
658
|
+
...moduleLines.length ? moduleLines : ["TODO \u2014 no clear structure detected."],
|
|
659
|
+
"",
|
|
660
|
+
`## Key modules`,
|
|
661
|
+
`TODO \u2014 describe the purpose of the main modules. The directory scan found:`,
|
|
662
|
+
...dirs.filter((d) => !d.includes("/")).slice(0, 8).map((d) => `- \`${d}/\``),
|
|
663
|
+
"",
|
|
664
|
+
`## Conventions`,
|
|
665
|
+
`TODO \u2014 fill in coding conventions (naming, patterns, file layout).`,
|
|
666
|
+
"",
|
|
667
|
+
...scriptLines.length ? [
|
|
668
|
+
`**Available scripts:**`,
|
|
669
|
+
...scriptLines,
|
|
670
|
+
""
|
|
671
|
+
] : [],
|
|
672
|
+
...keyDeps.length ? [
|
|
673
|
+
`**Key dependencies in use:** ${keyDeps.map((d) => `\`${d}\``).join(", ")}`,
|
|
674
|
+
""
|
|
675
|
+
] : [],
|
|
676
|
+
...notableDeps.length ? [
|
|
677
|
+
`**Other notable packages:** ${notableDeps.join(", ")}`,
|
|
678
|
+
""
|
|
679
|
+
] : [],
|
|
680
|
+
`## Glossary`,
|
|
681
|
+
`TODO \u2014 domain terms and what they mean here.`,
|
|
682
|
+
"",
|
|
683
|
+
`## Gotchas`,
|
|
684
|
+
`TODO \u2014 known traps, surprising behavior, things newcomers stub their toes on.`,
|
|
685
|
+
`(Run \`haive memory import-changelog\` or \`haive memory import README.md\` to seed these automatically.)`,
|
|
686
|
+
""
|
|
687
|
+
];
|
|
688
|
+
return lines.join("\n");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/commands/init-mcp-setup.ts
|
|
692
|
+
import { readFile as readFile3, writeFile, mkdir } from "fs/promises";
|
|
693
|
+
import { existsSync as existsSync4 } from "fs";
|
|
694
|
+
import path5 from "path";
|
|
695
|
+
import os from "os";
|
|
696
|
+
var HOME = os.homedir();
|
|
697
|
+
var HAIVE_MCP_ENTRY = {
|
|
698
|
+
command: "haive-mcp",
|
|
699
|
+
args: []
|
|
700
|
+
};
|
|
701
|
+
function cursorMcpPath() {
|
|
702
|
+
return path5.join(HOME, ".cursor", "mcp.json");
|
|
703
|
+
}
|
|
704
|
+
async function configureCursor() {
|
|
705
|
+
const mcpPath = cursorMcpPath();
|
|
706
|
+
const cursorDir = path5.join(HOME, ".cursor");
|
|
707
|
+
if (!existsSync4(cursorDir)) return { client: "Cursor", status: "not_installed" };
|
|
708
|
+
let config = {};
|
|
709
|
+
if (existsSync4(mcpPath)) {
|
|
710
|
+
try {
|
|
711
|
+
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
712
|
+
} catch {
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
config.mcpServers ??= {};
|
|
716
|
+
if (config.mcpServers["haive"]) return { client: "Cursor", status: "already_configured" };
|
|
717
|
+
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
718
|
+
await mkdir(cursorDir, { recursive: true });
|
|
719
|
+
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
720
|
+
return { client: "Cursor", status: "configured", path: mcpPath };
|
|
721
|
+
}
|
|
722
|
+
function vscodeMcpPath() {
|
|
723
|
+
const candidates = [
|
|
724
|
+
path5.join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
725
|
+
// Linux
|
|
726
|
+
path5.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
|
|
727
|
+
// macOS
|
|
728
|
+
path5.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
|
|
729
|
+
// Windows
|
|
730
|
+
path5.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
|
|
731
|
+
];
|
|
732
|
+
for (const c of candidates) {
|
|
733
|
+
if (existsSync4(path5.dirname(c))) return c;
|
|
734
|
+
}
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
async function configureVSCode() {
|
|
738
|
+
const mcpPath = vscodeMcpPath();
|
|
739
|
+
if (!mcpPath) return { client: "VS Code", status: "not_installed" };
|
|
740
|
+
let config = {};
|
|
741
|
+
if (existsSync4(mcpPath)) {
|
|
742
|
+
try {
|
|
743
|
+
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
config.servers ??= {};
|
|
748
|
+
if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
|
|
749
|
+
config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
750
|
+
await mkdir(path5.dirname(mcpPath), { recursive: true });
|
|
751
|
+
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
752
|
+
return { client: "VS Code", status: "configured", path: mcpPath };
|
|
753
|
+
}
|
|
754
|
+
function claudeConfigPath() {
|
|
755
|
+
const p = path5.join(HOME, ".claude.json");
|
|
756
|
+
if (existsSync4(p)) return p;
|
|
757
|
+
const p2 = path5.join(HOME, ".config", "claude", "claude.json");
|
|
758
|
+
if (existsSync4(path5.dirname(p2))) return p2;
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
async function configureClaude() {
|
|
762
|
+
const cfgPath = claudeConfigPath() ?? path5.join(HOME, ".claude.json");
|
|
763
|
+
if (!existsSync4(cfgPath) && !existsSync4(path5.join(HOME, ".claude"))) {
|
|
764
|
+
return { client: "Claude Code", status: "not_installed" };
|
|
765
|
+
}
|
|
766
|
+
let config = {};
|
|
767
|
+
if (existsSync4(cfgPath)) {
|
|
768
|
+
try {
|
|
769
|
+
config = JSON.parse(await readFile3(cfgPath, "utf8"));
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
config.mcpServers ??= {};
|
|
774
|
+
if (config.mcpServers["haive"]) return { client: "Claude Code", status: "already_configured" };
|
|
775
|
+
config.mcpServers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
776
|
+
await writeFile(cfgPath, JSON.stringify(config, null, 2), "utf8");
|
|
777
|
+
return { client: "Claude Code", status: "configured", path: cfgPath };
|
|
778
|
+
}
|
|
779
|
+
function windsurfMcpPath() {
|
|
780
|
+
const candidates = [
|
|
781
|
+
path5.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
782
|
+
path5.join(HOME, ".windsurf", "mcp.json")
|
|
783
|
+
];
|
|
784
|
+
for (const c of candidates) {
|
|
785
|
+
if (existsSync4(path5.dirname(c))) return c;
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
async function configureWindsurf() {
|
|
790
|
+
const mcpPath = windsurfMcpPath();
|
|
791
|
+
if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
|
|
792
|
+
let config = {};
|
|
793
|
+
if (existsSync4(mcpPath)) {
|
|
794
|
+
try {
|
|
795
|
+
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
796
|
+
} catch {
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
config.mcpServers ??= {};
|
|
800
|
+
if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
|
|
801
|
+
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
802
|
+
await mkdir(path5.dirname(mcpPath), { recursive: true });
|
|
803
|
+
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
804
|
+
return { client: "Windsurf", status: "configured", path: mcpPath };
|
|
805
|
+
}
|
|
806
|
+
async function autoConfigureMcpClients() {
|
|
807
|
+
const results = [];
|
|
808
|
+
const configurators = [configureCursor, configureVSCode, configureClaude, configureWindsurf];
|
|
809
|
+
for (const fn of configurators) {
|
|
810
|
+
try {
|
|
811
|
+
results.push(await fn());
|
|
812
|
+
} catch (err) {
|
|
813
|
+
const name = fn.name.replace("configure", "");
|
|
814
|
+
results.push({ client: name, status: "error", error: String(err) });
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return results;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/commands/init-stack-packs.ts
|
|
821
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
822
|
+
import { existsSync as existsSync5 } from "fs";
|
|
823
|
+
import path6 from "path";
|
|
824
|
+
import {
|
|
825
|
+
buildFrontmatter,
|
|
826
|
+
memoryFilePath,
|
|
827
|
+
serializeMemory
|
|
828
|
+
} from "@hiveai/core";
|
|
829
|
+
var PACKS = {
|
|
830
|
+
nestjs: [
|
|
831
|
+
{
|
|
832
|
+
slug: "jwtmodule-requires-secret",
|
|
833
|
+
type: "gotcha",
|
|
834
|
+
tags: ["auth", "jwt", "nestjs"],
|
|
835
|
+
body: `JwtModule must be registered with an explicit secret \u2014 there is no default.
|
|
836
|
+
|
|
837
|
+
\`\`\`ts
|
|
838
|
+
JwtModule.register({ secret: process.env.JWT_SECRET, signOptions: { expiresIn: '7d' } })
|
|
839
|
+
\`\`\`
|
|
840
|
+
|
|
841
|
+
Without a secret, tokens are signed with an empty string and any client can forge them.
|
|
842
|
+
Always load the secret from env and validate it is defined at startup.`
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
slug: "global-validation-pipe",
|
|
846
|
+
type: "convention",
|
|
847
|
+
tags: ["validation", "nestjs", "security"],
|
|
848
|
+
body: `Register ValidationPipe globally in main.ts, not per-controller.
|
|
849
|
+
|
|
850
|
+
\`\`\`ts
|
|
851
|
+
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
|
|
852
|
+
\`\`\`
|
|
853
|
+
|
|
854
|
+
- \`whitelist: true\` strips unknown properties silently
|
|
855
|
+
- \`forbidNonWhitelisted: true\` throws 400 on unknown fields (safer)
|
|
856
|
+
- Without this, NestJS passes unvalidated payloads to handlers.`
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
slug: "nestjs-no-direct-orm-in-controller",
|
|
860
|
+
type: "convention",
|
|
861
|
+
tags: ["architecture", "nestjs"],
|
|
862
|
+
body: `Controllers must never import Prisma/TypeORM directly \u2014 that belongs in Services.
|
|
863
|
+
|
|
864
|
+
Controller \u2192 Service \u2192 Repository (or direct ORM) is the required layering.
|
|
865
|
+
Direct ORM usage in controllers makes testing impossible and couples transport to persistence.`
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
slug: "nestjs-exception-filter-for-prisma",
|
|
869
|
+
type: "gotcha",
|
|
870
|
+
tags: ["error-handling", "nestjs", "prisma"],
|
|
871
|
+
body: `Prisma errors bubble up as unhandled 500s without a custom exception filter.
|
|
872
|
+
|
|
873
|
+
Create an \`AllExceptionsFilter\` or a specific \`PrismaClientExceptionFilter\` that maps:
|
|
874
|
+
- P2002 (unique constraint) \u2192 409 Conflict
|
|
875
|
+
- P2025 (record not found) \u2192 404 Not Found
|
|
876
|
+
- P2003 (foreign key) \u2192 422 Unprocessable
|
|
877
|
+
|
|
878
|
+
Without this, clients receive raw Prisma error messages which may leak schema info.`
|
|
879
|
+
}
|
|
880
|
+
],
|
|
881
|
+
nextjs: [
|
|
882
|
+
{
|
|
883
|
+
slug: "server-components-no-client-hooks",
|
|
884
|
+
type: "gotcha",
|
|
885
|
+
tags: ["nextjs", "react", "server-components"],
|
|
886
|
+
body: `Server Components cannot use useState, useEffect, or any browser APIs.
|
|
887
|
+
|
|
888
|
+
Add \`"use client"\` at the top of any component that needs hooks or event handlers.
|
|
889
|
+
The boundary propagates down \u2014 children of a client component don't need the directive.
|
|
890
|
+
|
|
891
|
+
Common mistake: importing a client-only library (e.g. framer-motion) in a server component
|
|
892
|
+
causes a cryptic runtime error. Check for browser globals (window, document, localStorage).`
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
slug: "nextjs-env-client-exposure",
|
|
896
|
+
type: "gotcha",
|
|
897
|
+
tags: ["security", "nextjs", "env"],
|
|
898
|
+
body: `Only environment variables prefixed with NEXT_PUBLIC_ are exposed to the browser.
|
|
899
|
+
|
|
900
|
+
Never put secrets in NEXT_PUBLIC_* variables \u2014 they are bundled into the client JS.
|
|
901
|
+
Variables without the prefix are server-only and safe for API keys, database URLs, etc.`
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
slug: "nextjs-fetch-cache-defaults",
|
|
905
|
+
type: "gotcha",
|
|
906
|
+
tags: ["nextjs", "caching", "fetch"],
|
|
907
|
+
body: `In Next.js App Router, \`fetch()\` is cached indefinitely by default in Server Components.
|
|
908
|
+
|
|
909
|
+
Add \`{ cache: 'no-store' }\` for dynamic data, or \`{ next: { revalidate: 60 } }\` for ISR.
|
|
910
|
+
Forgetting this means stale data is returned after a deploy until the cache expires.`
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
slug: "nextjs-metadata-api",
|
|
914
|
+
type: "convention",
|
|
915
|
+
tags: ["nextjs", "seo"],
|
|
916
|
+
body: `Use the Metadata API (export const metadata / generateMetadata) instead of <Head>.
|
|
917
|
+
|
|
918
|
+
\`<Head>\` from next/head still works in pages/ but is not supported in the App Router.
|
|
919
|
+
Use \`generateMetadata\` for dynamic titles/descriptions based on route params.`
|
|
920
|
+
}
|
|
921
|
+
],
|
|
922
|
+
remix: [
|
|
923
|
+
{
|
|
924
|
+
slug: "remix-loader-vs-action",
|
|
925
|
+
type: "convention",
|
|
926
|
+
tags: ["remix", "architecture"],
|
|
927
|
+
body: `loader = GET data for rendering. action = handle form submissions / mutations.
|
|
928
|
+
|
|
929
|
+
- \`loader\` runs on every GET request (server-side, returns data for the component)
|
|
930
|
+
- \`action\` runs on POST/PUT/DELETE (mutations \u2014 redirect after success)
|
|
931
|
+
- Never fetch inside the component itself for route data \u2014 use the loader instead.`
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
slug: "remix-error-boundaries",
|
|
935
|
+
type: "gotcha",
|
|
936
|
+
tags: ["remix", "error-handling"],
|
|
937
|
+
body: `Each route should export an ErrorBoundary to catch loader/action errors gracefully.
|
|
938
|
+
|
|
939
|
+
Without it, errors bubble to the root boundary and replace the entire page.
|
|
940
|
+
Export \`export function ErrorBoundary() { ... }\` to scope errors to the route.`
|
|
941
|
+
}
|
|
942
|
+
],
|
|
943
|
+
react: [
|
|
944
|
+
{
|
|
945
|
+
slug: "useeffect-cleanup",
|
|
946
|
+
type: "gotcha",
|
|
947
|
+
tags: ["react", "memory-leak"],
|
|
948
|
+
body: `useEffect subscriptions, timers, and async operations need cleanup to avoid memory leaks.
|
|
949
|
+
|
|
950
|
+
\`\`\`ts
|
|
951
|
+
useEffect(() => {
|
|
952
|
+
const controller = new AbortController();
|
|
953
|
+
fetchData({ signal: controller.signal });
|
|
954
|
+
return () => controller.abort(); // cleanup
|
|
955
|
+
}, [dep]);
|
|
956
|
+
\`\`\`
|
|
957
|
+
|
|
958
|
+
Missing cleanup causes: state updates on unmounted components, duplicate subscriptions,
|
|
959
|
+
and event listeners that accumulate across re-renders.`
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
slug: "react-key-prop-in-lists",
|
|
963
|
+
type: "gotcha",
|
|
964
|
+
tags: ["react", "performance"],
|
|
965
|
+
body: `Keys must be stable, unique IDs \u2014 never use array index as key.
|
|
966
|
+
|
|
967
|
+
Using index as key causes React to re-render wrong items on reorder/filter,
|
|
968
|
+
corrupts form state, and triggers avoidable DOM mutations.
|
|
969
|
+
Use item.id or a stable hash \u2014 never Math.random().`
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
slug: "react-avoid-use-effect-for-derived-state",
|
|
973
|
+
type: "convention",
|
|
974
|
+
tags: ["react", "state"],
|
|
975
|
+
body: `Don't use useEffect to sync state from props \u2014 compute it during render instead.
|
|
976
|
+
|
|
977
|
+
\`\`\`ts
|
|
978
|
+
// \u274C Bad
|
|
979
|
+
const [fullName, setFullName] = useState('');
|
|
980
|
+
useEffect(() => { setFullName(first + ' ' + last); }, [first, last]);
|
|
981
|
+
|
|
982
|
+
// \u2705 Good
|
|
983
|
+
const fullName = first + ' ' + last; // derived during render
|
|
984
|
+
\`\`\``
|
|
985
|
+
}
|
|
986
|
+
],
|
|
987
|
+
express: [
|
|
988
|
+
{
|
|
989
|
+
slug: "express-missing-validation",
|
|
990
|
+
type: "gotcha",
|
|
991
|
+
tags: ["security", "express", "validation"],
|
|
992
|
+
body: `Express does not validate request bodies by default \u2014 always validate with zod, joi, or express-validator.
|
|
993
|
+
|
|
994
|
+
Without validation:
|
|
995
|
+
- req.body fields are \`any\` and may be missing, wrong type, or injected
|
|
996
|
+
- Downstream code crashes or processes malicious data
|
|
997
|
+
Add a validation middleware for every route that accepts user input.`
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
slug: "express-async-error-propagation",
|
|
1001
|
+
type: "gotcha",
|
|
1002
|
+
tags: ["express", "error-handling"],
|
|
1003
|
+
body: `Async route handlers don't propagate errors to error middleware without explicit next(err).
|
|
1004
|
+
|
|
1005
|
+
\`\`\`ts
|
|
1006
|
+
// \u274C Unhandled \u2014 Express never sees the rejection
|
|
1007
|
+
app.get('/', async (req, res) => { throw new Error('oops'); });
|
|
1008
|
+
|
|
1009
|
+
// \u2705 Correct
|
|
1010
|
+
app.get('/', async (req, res, next) => {
|
|
1011
|
+
try { await doWork(); }
|
|
1012
|
+
catch (err) { next(err); }
|
|
1013
|
+
});
|
|
1014
|
+
\`\`\`
|
|
1015
|
+
Or use express-async-errors / wrap helper.`
|
|
1016
|
+
}
|
|
1017
|
+
],
|
|
1018
|
+
fastify: [
|
|
1019
|
+
{
|
|
1020
|
+
slug: "fastify-schema-validation-required",
|
|
1021
|
+
type: "convention",
|
|
1022
|
+
tags: ["fastify", "validation", "security"],
|
|
1023
|
+
body: `Always define a JSON schema on routes \u2014 Fastify validates and coerces automatically.
|
|
1024
|
+
|
|
1025
|
+
\`\`\`ts
|
|
1026
|
+
fastify.post('/users', {
|
|
1027
|
+
schema: { body: { type: 'object', required: ['email'], properties: { email: { type: 'string', format: 'email' } } } }
|
|
1028
|
+
}, handler)
|
|
1029
|
+
\`\`\`
|
|
1030
|
+
Routes without schema accept any body and bypass Fastify's fast-json-stringify serialization.`
|
|
1031
|
+
}
|
|
1032
|
+
],
|
|
1033
|
+
prisma: [
|
|
1034
|
+
{
|
|
1035
|
+
slug: "prisma-no-disconnect-in-lambda",
|
|
1036
|
+
type: "gotcha",
|
|
1037
|
+
tags: ["prisma", "serverless"],
|
|
1038
|
+
body: `Do NOT call prisma.$disconnect() inside Lambda/Edge function handlers.
|
|
1039
|
+
|
|
1040
|
+
Calling $disconnect() after each request wastes the warm connection pool.
|
|
1041
|
+
Create one PrismaClient per process (module-level singleton), not per request.
|
|
1042
|
+
Disconnecting is only needed when the process is shutting down.`
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
slug: "prisma-migrations-never-modify",
|
|
1046
|
+
type: "convention",
|
|
1047
|
+
tags: ["prisma", "database", "migrations"],
|
|
1048
|
+
body: `Never modify an existing migration file \u2014 create a new one instead.
|
|
1049
|
+
|
|
1050
|
+
Prisma tracks migration history by file hash. Editing a deployed migration
|
|
1051
|
+
causes \`migrate deploy\` to fail with a checksum mismatch in production.
|
|
1052
|
+
Always use \`npx prisma migrate dev --name <description>\` to create incremental migrations.`
|
|
1053
|
+
}
|
|
1054
|
+
],
|
|
1055
|
+
drizzle: [
|
|
1056
|
+
{
|
|
1057
|
+
slug: "drizzle-always-await-queries",
|
|
1058
|
+
type: "gotcha",
|
|
1059
|
+
tags: ["drizzle", "async"],
|
|
1060
|
+
body: `Drizzle queries are thenable but not auto-executed \u2014 always await them.
|
|
1061
|
+
|
|
1062
|
+
\`\`\`ts
|
|
1063
|
+
// \u274C Silently returns a query builder, never executes
|
|
1064
|
+
const rows = db.select().from(users).where(eq(users.id, id));
|
|
1065
|
+
|
|
1066
|
+
// \u2705 Correct
|
|
1067
|
+
const rows = await db.select().from(users).where(eq(users.id, id));
|
|
1068
|
+
\`\`\``
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
slug: "drizzle-schema-must-match-db",
|
|
1072
|
+
type: "gotcha",
|
|
1073
|
+
tags: ["drizzle", "migrations"],
|
|
1074
|
+
body: `Drizzle does NOT auto-sync the schema to the database \u2014 you must run migrations explicitly.
|
|
1075
|
+
|
|
1076
|
+
After changing schema.ts:
|
|
1077
|
+
1. \`npx drizzle-kit generate\` \u2014 creates migration SQL
|
|
1078
|
+
2. \`npx drizzle-kit migrate\` (or push in dev) \u2014 applies it
|
|
1079
|
+
|
|
1080
|
+
Without this, queries silently operate on stale column definitions and may return wrong data.`
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
};
|
|
1084
|
+
var SUPPORTED_STACKS = Object.keys(PACKS);
|
|
1085
|
+
function isValidStack(name) {
|
|
1086
|
+
return name in PACKS;
|
|
1087
|
+
}
|
|
1088
|
+
function autoDetectStacks(deps) {
|
|
1089
|
+
const detected = [];
|
|
1090
|
+
const stackDetectors = [
|
|
1091
|
+
["nestjs", ["@nestjs/core"]],
|
|
1092
|
+
["nextjs", ["next"]],
|
|
1093
|
+
["remix", ["@remix-run/react", "@remix-run/node"]],
|
|
1094
|
+
["react", ["react"]],
|
|
1095
|
+
["express", ["express"]],
|
|
1096
|
+
["fastify", ["fastify"]],
|
|
1097
|
+
["prisma", ["@prisma/client", "prisma"]],
|
|
1098
|
+
["drizzle", ["drizzle-orm"]]
|
|
1099
|
+
];
|
|
1100
|
+
for (const [stack, signals] of stackDetectors) {
|
|
1101
|
+
if (signals.some((s) => s in deps)) detected.push(stack);
|
|
1102
|
+
}
|
|
1103
|
+
if (detected.includes("nextjs") || detected.includes("remix")) {
|
|
1104
|
+
return detected.filter((s) => s !== "react");
|
|
1105
|
+
}
|
|
1106
|
+
return detected;
|
|
1107
|
+
}
|
|
1108
|
+
async function seedStackPack(haivePaths, stack) {
|
|
1109
|
+
const memories = PACKS[stack];
|
|
1110
|
+
if (!memories) return 0;
|
|
1111
|
+
await mkdir2(haivePaths.teamDir, { recursive: true });
|
|
1112
|
+
let count = 0;
|
|
1113
|
+
for (const mem of memories) {
|
|
1114
|
+
const fm = buildFrontmatter({
|
|
1115
|
+
type: mem.type,
|
|
1116
|
+
slug: `${stack}-${mem.slug}`,
|
|
1117
|
+
scope: "team",
|
|
1118
|
+
status: "validated",
|
|
1119
|
+
tags: mem.tags
|
|
1120
|
+
});
|
|
1121
|
+
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
1122
|
+
if (existsSync5(filePath)) continue;
|
|
1123
|
+
const content = serializeMemory({ frontmatter: fm, body: mem.body });
|
|
1124
|
+
await mkdir2(path6.dirname(filePath), { recursive: true });
|
|
1125
|
+
await writeFile2(filePath, content, "utf8");
|
|
1126
|
+
count++;
|
|
1127
|
+
}
|
|
1128
|
+
return count;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/commands/init.ts
|
|
423
1132
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
424
1133
|
|
|
425
|
-
> Generated by \`haive init\`.
|
|
1134
|
+
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
1135
|
+
> or invoke the MCP prompt \`bootstrap_project\` in your AI client for a richer AI-generated version.
|
|
426
1136
|
|
|
427
1137
|
## Architecture
|
|
428
1138
|
TODO \u2014 high-level overview of the codebase.
|
|
@@ -580,23 +1290,46 @@ function registerInit(program2) {
|
|
|
580
1290
|
).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(
|
|
581
1291
|
"--manual",
|
|
582
1292
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
1293
|
+
).option(
|
|
1294
|
+
"--bootstrap",
|
|
1295
|
+
"auto-generate .ai/project-context.md from package.json, README, and directory structure (no AI needed)"
|
|
1296
|
+
).option(
|
|
1297
|
+
"--stack <stacks>",
|
|
1298
|
+
`pre-seed validated memory packs for the given stacks (comma-separated).
|
|
1299
|
+
Supported: ${SUPPORTED_STACKS.join(", ")}.
|
|
1300
|
+
Use 'auto' to detect from package.json automatically.`
|
|
1301
|
+
).option(
|
|
1302
|
+
"--no-mcp-setup",
|
|
1303
|
+
"skip auto-configuring haive-mcp in Cursor / VS Code / Claude Code"
|
|
583
1304
|
).action(async (opts) => {
|
|
584
|
-
const root =
|
|
1305
|
+
const root = path7.resolve(opts.dir);
|
|
585
1306
|
const paths = resolveHaivePaths4(root);
|
|
586
1307
|
const autopilot = opts.manual !== true;
|
|
587
|
-
if (
|
|
1308
|
+
if (existsSync6(paths.haiveDir)) {
|
|
588
1309
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
589
1310
|
}
|
|
590
|
-
await
|
|
591
|
-
await
|
|
592
|
-
await
|
|
593
|
-
await
|
|
594
|
-
if (!
|
|
595
|
-
|
|
596
|
-
|
|
1311
|
+
await mkdir3(paths.personalDir, { recursive: true });
|
|
1312
|
+
await mkdir3(paths.teamDir, { recursive: true });
|
|
1313
|
+
await mkdir3(paths.moduleDir, { recursive: true });
|
|
1314
|
+
await mkdir3(paths.modulesContextDir, { recursive: true });
|
|
1315
|
+
if (!existsSync6(paths.projectContext)) {
|
|
1316
|
+
if (opts.bootstrap) {
|
|
1317
|
+
ui.info("Bootstrapping project context from local files\u2026");
|
|
1318
|
+
try {
|
|
1319
|
+
const context = await generateBootstrapContext(root);
|
|
1320
|
+
await writeFile3(paths.projectContext, context, "utf8");
|
|
1321
|
+
ui.success("Created .ai/project-context.md (auto-bootstrapped from local files)");
|
|
1322
|
+
} catch (err) {
|
|
1323
|
+
ui.warn(`Bootstrap failed (${String(err)}) \u2014 writing default template instead`);
|
|
1324
|
+
await writeFile3(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
1325
|
+
}
|
|
1326
|
+
} else {
|
|
1327
|
+
await writeFile3(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
1328
|
+
ui.success(`Created ${path7.relative(root, paths.projectContext)}`);
|
|
1329
|
+
}
|
|
597
1330
|
}
|
|
598
|
-
const configExists =
|
|
599
|
-
|
|
1331
|
+
const configExists = existsSync6(
|
|
1332
|
+
path7.join(paths.haiveDir, "haive.config.json")
|
|
600
1333
|
);
|
|
601
1334
|
if (!configExists) {
|
|
602
1335
|
await saveConfig(paths, autopilot ? AUTOPILOT_DEFAULTS : { autopilot: false });
|
|
@@ -607,17 +1340,35 @@ function registerInit(program2) {
|
|
|
607
1340
|
if (opts.bridges) {
|
|
608
1341
|
await writeBridge(root, "CLAUDE.md");
|
|
609
1342
|
await writeBridge(root, ".cursorrules");
|
|
610
|
-
await writeBridge(root,
|
|
1343
|
+
await writeBridge(root, path7.join(".github", "copilot-instructions.md"));
|
|
1344
|
+
}
|
|
1345
|
+
const stacksToSeed = await resolveStacksToSeed(root, opts.stack);
|
|
1346
|
+
if (stacksToSeed.length > 0) {
|
|
1347
|
+
let totalSeeded = 0;
|
|
1348
|
+
for (const stack of stacksToSeed) {
|
|
1349
|
+
const count = await seedStackPack(paths, stack);
|
|
1350
|
+
if (count > 0) {
|
|
1351
|
+
ui.success(`Seeded ${count} memories for stack: ${stack}`);
|
|
1352
|
+
totalSeeded += count;
|
|
1353
|
+
} else {
|
|
1354
|
+
ui.info(`Stack pack '${stack}': all memories already exist \u2014 skipped`);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (totalSeeded > 0) {
|
|
1358
|
+
ui.success(
|
|
1359
|
+
`${totalSeeded} validated team memories pre-seeded \u2014 haive is useful from J+0`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
611
1362
|
}
|
|
612
1363
|
const wantCi = opts.withCi || autopilot;
|
|
613
1364
|
if (wantCi) {
|
|
614
|
-
const ciPath =
|
|
615
|
-
if (
|
|
1365
|
+
const ciPath = path7.join(root, ".github", "workflows", "haive-sync.yml");
|
|
1366
|
+
if (existsSync6(ciPath)) {
|
|
616
1367
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
617
1368
|
} else {
|
|
618
|
-
await
|
|
619
|
-
await
|
|
620
|
-
ui.success(`Created ${
|
|
1369
|
+
await mkdir3(path7.dirname(ciPath), { recursive: true });
|
|
1370
|
+
await writeFile3(ciPath, CI_WORKFLOW, "utf8");
|
|
1371
|
+
ui.success(`Created ${path7.relative(root, ciPath)}`);
|
|
621
1372
|
}
|
|
622
1373
|
}
|
|
623
1374
|
if (autopilot) {
|
|
@@ -641,6 +1392,27 @@ function registerInit(program2) {
|
|
|
641
1392
|
ui.warn("Code-map build failed \u2014 run `haive index code` manually");
|
|
642
1393
|
}
|
|
643
1394
|
}
|
|
1395
|
+
if (opts.mcpSetup !== false) {
|
|
1396
|
+
const mcpResults = await autoConfigureMcpClients();
|
|
1397
|
+
const configured = mcpResults.filter((r) => r.status === "configured");
|
|
1398
|
+
const alreadyOk = mcpResults.filter((r) => r.status === "already_configured");
|
|
1399
|
+
for (const r of configured) {
|
|
1400
|
+
ui.success(`haive-mcp configured in ${r.client} (${r.path})`);
|
|
1401
|
+
}
|
|
1402
|
+
for (const r of alreadyOk) {
|
|
1403
|
+
ui.info(`haive-mcp already configured in ${r.client} \u2014 skipped`);
|
|
1404
|
+
}
|
|
1405
|
+
if (configured.length === 0 && alreadyOk.length === 0) {
|
|
1406
|
+
ui.warn(
|
|
1407
|
+
"No supported AI client detected (Cursor, VS Code, Claude Code, Windsurf).\n Configure manually: add haive-mcp to your client's MCP config.\n See: https://github.com/Doucs91/hAIve#mcp-setup"
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
if (configured.length > 0) {
|
|
1411
|
+
ui.info(
|
|
1412
|
+
ui.dim(" \u2192 Restart your AI client for MCP changes to take effect.")
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
644
1416
|
ui.success(`hAIve initialized at ${root}${autopilot ? " (autopilot mode)" : ""}`);
|
|
645
1417
|
console.log();
|
|
646
1418
|
if (autopilot) {
|
|
@@ -651,45 +1423,74 @@ function registerInit(program2) {
|
|
|
651
1423
|
console.log(ui.dim(" \u2713 Code-map refreshes automatically after every pull"));
|
|
652
1424
|
console.log(ui.dim(" \u2713 Git hooks installed (auto-sync after pull/merge)"));
|
|
653
1425
|
console.log(ui.dim(" \u2713 CI workflow created (pr-stale-check + sync-on-merge)"));
|
|
1426
|
+
if (stacksToSeed.length > 0) {
|
|
1427
|
+
console.log(ui.dim(` \u2713 Stack memory packs pre-seeded (${stacksToSeed.join(", ")})`));
|
|
1428
|
+
}
|
|
1429
|
+
console.log();
|
|
1430
|
+
if (!opts.bootstrap) {
|
|
1431
|
+
console.log(ui.bold("One remaining step (optional but recommended):"));
|
|
1432
|
+
console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 fill project-context.md without AI"));
|
|
1433
|
+
console.log(" " + ui.dim("Or in your AI client: invoke the MCP prompt ") + ui.bold("bootstrap_project"));
|
|
1434
|
+
} else {
|
|
1435
|
+
console.log(ui.bold("Project context bootstrapped from local files."));
|
|
1436
|
+
console.log(ui.dim(" Review .ai/project-context.md and fill in the TODO sections."));
|
|
1437
|
+
console.log(ui.dim(" Or invoke the MCP prompt `bootstrap_project` for a richer AI-generated version."));
|
|
1438
|
+
}
|
|
654
1439
|
console.log();
|
|
655
|
-
console.log(ui.
|
|
656
|
-
console.log("
|
|
657
|
-
console.log(ui.dim("
|
|
1440
|
+
console.log(ui.dim(" Seed more memories instantly:"));
|
|
1441
|
+
console.log(ui.dim(" haive memory import-changelog \u2014 from CHANGELOG.md"));
|
|
1442
|
+
console.log(ui.dim(" haive memory import README.md \u2014 from README / docs"));
|
|
658
1443
|
} else {
|
|
659
1444
|
console.log(ui.bold("Next steps:"));
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1445
|
+
if (!opts.bootstrap) {
|
|
1446
|
+
console.log(ui.dim(" 1. Fill project context (pick one):"));
|
|
1447
|
+
console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 instant, no AI needed"));
|
|
1448
|
+
console.log(" or invoke the MCP prompt " + ui.bold("bootstrap_project") + ui.dim(" in your AI client"));
|
|
1449
|
+
} else {
|
|
1450
|
+
console.log(ui.dim(" 1. Review .ai/project-context.md and fill in the TODO sections."));
|
|
1451
|
+
}
|
|
666
1452
|
console.log();
|
|
667
|
-
console.log(ui.dim("
|
|
1453
|
+
console.log(ui.dim(" 2. Start every AI session with:"));
|
|
668
1454
|
console.log(" " + ui.bold("get_briefing({ task: '\u2026what you are about to do\u2026' })"));
|
|
669
1455
|
console.log();
|
|
670
1456
|
console.log(ui.dim(" Tip: run `haive init` (without --manual) for zero-friction autopilot mode."));
|
|
671
1457
|
}
|
|
672
1458
|
});
|
|
673
1459
|
}
|
|
1460
|
+
async function resolveStacksToSeed(root, stackOpt) {
|
|
1461
|
+
if (!stackOpt) return [];
|
|
1462
|
+
if (stackOpt === "auto") {
|
|
1463
|
+
const pkgPath = path7.join(root, "package.json");
|
|
1464
|
+
if (!existsSync6(pkgPath)) return [];
|
|
1465
|
+
try {
|
|
1466
|
+
const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
|
|
1467
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1468
|
+
return autoDetectStacks(allDeps);
|
|
1469
|
+
} catch {
|
|
1470
|
+
return [];
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return stackOpt.split(",").map((s) => s.trim().toLowerCase()).filter(isValidStack);
|
|
1474
|
+
}
|
|
674
1475
|
async function writeBridge(root, relPath) {
|
|
675
|
-
const target =
|
|
676
|
-
if (
|
|
1476
|
+
const target = path7.join(root, relPath);
|
|
1477
|
+
if (existsSync6(target)) {
|
|
677
1478
|
ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
|
|
678
1479
|
return;
|
|
679
1480
|
}
|
|
680
|
-
await
|
|
681
|
-
await
|
|
1481
|
+
await mkdir3(path7.dirname(target), { recursive: true });
|
|
1482
|
+
await writeFile3(target, BRIDGE_BODY, "utf8");
|
|
682
1483
|
ui.success(`Created bridge ${relPath}`);
|
|
683
1484
|
}
|
|
684
1485
|
|
|
685
1486
|
// src/commands/install-hooks.ts
|
|
686
|
-
import { mkdir as
|
|
687
|
-
import { existsSync as
|
|
688
|
-
import
|
|
1487
|
+
import { mkdir as mkdir4, writeFile as writeFile4, chmod, readFile as readFile5 } from "fs/promises";
|
|
1488
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1489
|
+
import path8 from "path";
|
|
689
1490
|
import "commander";
|
|
690
1491
|
import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
|
|
691
1492
|
var HOOK_MARKER = "# hAIve auto-generated";
|
|
692
|
-
var
|
|
1493
|
+
var POST_MERGE_BODY = `#!/bin/sh
|
|
693
1494
|
${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
|
|
694
1495
|
|
|
695
1496
|
# After a merge or pull, refresh memory anchors and auto-promote eligible
|
|
@@ -700,46 +1501,83 @@ elif [ -x ./node_modules/.bin/haive ]; then
|
|
|
700
1501
|
./node_modules/.bin/haive sync --quiet --since ORIG_HEAD || true
|
|
701
1502
|
fi
|
|
702
1503
|
`;
|
|
703
|
-
var
|
|
1504
|
+
var PRE_PUSH_BODY = `#!/bin/sh
|
|
1505
|
+
${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
|
|
1506
|
+
|
|
1507
|
+
# Before pushing, run haive precommit to surface known anti-patterns and stale memories.
|
|
1508
|
+
# Exit 0 always \u2014 this is advisory only (set HAIVE_BLOCK=1 to make it blocking).
|
|
1509
|
+
HAIVE_BLOCK=\${HAIVE_BLOCK:-0}
|
|
1510
|
+
|
|
1511
|
+
_haive() {
|
|
1512
|
+
if command -v haive >/dev/null 2>&1; then haive "$@"
|
|
1513
|
+
elif [ -x ./node_modules/.bin/haive ]; then ./node_modules/.bin/haive "$@"
|
|
1514
|
+
else return 0
|
|
1515
|
+
fi
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
# Run pre-commit check on diff between local and remote
|
|
1519
|
+
LOCAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
1520
|
+
REMOTE_SHA=$(git rev-parse --verify "@{u}" 2>/dev/null || echo "")
|
|
1521
|
+
if [ -n "$REMOTE_SHA" ]; then
|
|
1522
|
+
DIFF=$(git diff "$REMOTE_SHA"..HEAD 2>/dev/null || "")
|
|
1523
|
+
if [ -n "$DIFF" ]; then
|
|
1524
|
+
_haive precommit --quiet 2>/dev/null || true
|
|
1525
|
+
fi
|
|
1526
|
+
fi
|
|
1527
|
+
|
|
1528
|
+
# Remind agent to save session recap if env var is set
|
|
1529
|
+
if [ "$HAIVE_SESSION_REMINDER" = "1" ]; then
|
|
1530
|
+
echo "haive: session active \u2014 remember to call mem_session_end before closing." >&2
|
|
1531
|
+
fi
|
|
1532
|
+
|
|
1533
|
+
exit 0
|
|
1534
|
+
`;
|
|
1535
|
+
var HOOKS = [
|
|
1536
|
+
{ name: "post-merge", body: POST_MERGE_BODY },
|
|
1537
|
+
{ name: "post-rewrite", body: POST_MERGE_BODY },
|
|
1538
|
+
{ name: "pre-push", body: PRE_PUSH_BODY }
|
|
1539
|
+
];
|
|
704
1540
|
function registerInstallHooks(program2) {
|
|
705
1541
|
program2.command("install-hooks").description(
|
|
706
|
-
"Install git hooks so haive sync runs automatically after every pull or merge.\n\n Installs
|
|
1542
|
+
"Install git hooks so haive sync runs automatically after every pull or merge.\n\n Installs:\n post-merge / post-rewrite \u2014 runs haive sync after every pull/merge\n pre-push \u2014 runs haive precommit before every push (advisory)\n\n Installed automatically by haive init (autopilot mode).\n Use --force to overwrite existing hooks.\n"
|
|
707
1543
|
).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks").action(async (opts) => {
|
|
708
1544
|
const root = findProjectRoot6(opts.dir);
|
|
709
|
-
const gitDir =
|
|
710
|
-
if (!
|
|
1545
|
+
const gitDir = path8.join(root, ".git");
|
|
1546
|
+
if (!existsSync7(gitDir)) {
|
|
711
1547
|
ui.error(`No .git directory at ${root}.`);
|
|
712
1548
|
process.exitCode = 1;
|
|
713
1549
|
return;
|
|
714
1550
|
}
|
|
715
|
-
const hooksDir =
|
|
716
|
-
await
|
|
1551
|
+
const hooksDir = path8.join(gitDir, "hooks");
|
|
1552
|
+
await mkdir4(hooksDir, { recursive: true });
|
|
717
1553
|
let installed = 0;
|
|
718
1554
|
let skipped = 0;
|
|
719
|
-
for (const name of HOOKS) {
|
|
720
|
-
const file =
|
|
721
|
-
if (
|
|
722
|
-
const existing = await
|
|
1555
|
+
for (const { name, body } of HOOKS) {
|
|
1556
|
+
const file = path8.join(hooksDir, name);
|
|
1557
|
+
if (existsSync7(file) && !opts.force) {
|
|
1558
|
+
const existing = await readFile5(file, "utf8");
|
|
723
1559
|
if (!existing.includes(HOOK_MARKER)) {
|
|
724
1560
|
ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
|
|
725
1561
|
skipped++;
|
|
726
1562
|
continue;
|
|
727
1563
|
}
|
|
728
1564
|
}
|
|
729
|
-
await
|
|
1565
|
+
await writeFile4(file, body, "utf8");
|
|
730
1566
|
await chmod(file, 493);
|
|
731
1567
|
installed++;
|
|
732
1568
|
}
|
|
733
1569
|
ui.success(`Installed ${installed} hook(s) in .git/hooks/${skipped ? `, skipped ${skipped}` : ""}`);
|
|
734
|
-
ui.info("
|
|
1570
|
+
ui.info("post-merge: haive sync runs after every pull/merge.");
|
|
1571
|
+
ui.info("pre-push: haive precommit runs before every push (advisory, never blocks).");
|
|
1572
|
+
ui.info(" Set HAIVE_BLOCK=1 in your shell to make pre-push blocking.");
|
|
735
1573
|
});
|
|
736
1574
|
}
|
|
737
1575
|
|
|
738
1576
|
// src/commands/mcp.ts
|
|
739
1577
|
import { spawn } from "child_process";
|
|
740
|
-
import { existsSync as
|
|
1578
|
+
import { existsSync as existsSync8 } from "fs";
|
|
741
1579
|
import { createRequire } from "module";
|
|
742
|
-
import
|
|
1580
|
+
import path9 from "path";
|
|
743
1581
|
import { fileURLToPath } from "url";
|
|
744
1582
|
import "commander";
|
|
745
1583
|
import { findProjectRoot as findProjectRoot7 } from "@hiveai/core";
|
|
@@ -777,26 +1615,26 @@ function registerMcp(program2) {
|
|
|
777
1615
|
function locateMcpBin() {
|
|
778
1616
|
try {
|
|
779
1617
|
const pkgPath = require2.resolve("@hiveai/mcp/package.json");
|
|
780
|
-
const pkgDir =
|
|
781
|
-
const candidate =
|
|
782
|
-
if (
|
|
1618
|
+
const pkgDir = path9.dirname(pkgPath);
|
|
1619
|
+
const candidate = path9.join(pkgDir, "dist", "index.js");
|
|
1620
|
+
if (existsSync8(candidate)) return candidate;
|
|
783
1621
|
} catch {
|
|
784
1622
|
}
|
|
785
|
-
const here =
|
|
786
|
-
const sibling =
|
|
787
|
-
if (
|
|
1623
|
+
const here = path9.dirname(fileURLToPath(import.meta.url));
|
|
1624
|
+
const sibling = path9.resolve(here, "..", "..", "..", "mcp", "dist", "index.js");
|
|
1625
|
+
if (existsSync8(sibling)) return sibling;
|
|
788
1626
|
return null;
|
|
789
1627
|
}
|
|
790
1628
|
|
|
791
1629
|
// src/commands/sync.ts
|
|
792
1630
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
793
|
-
import { readFile as
|
|
794
|
-
import { existsSync as
|
|
795
|
-
import
|
|
1631
|
+
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
1632
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1633
|
+
import path10 from "path";
|
|
796
1634
|
import "commander";
|
|
797
1635
|
import {
|
|
798
1636
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
799
|
-
buildFrontmatter,
|
|
1637
|
+
buildFrontmatter as buildFrontmatter2,
|
|
800
1638
|
findProjectRoot as findProjectRoot8,
|
|
801
1639
|
getUsage,
|
|
802
1640
|
isAutoPromoteEligible,
|
|
@@ -808,7 +1646,7 @@ import {
|
|
|
808
1646
|
pullCrossRepoSources,
|
|
809
1647
|
resolveHaivePaths as resolveHaivePaths5,
|
|
810
1648
|
resolveManifestFiles,
|
|
811
|
-
serializeMemory,
|
|
1649
|
+
serializeMemory as serializeMemory2,
|
|
812
1650
|
trackDependencies,
|
|
813
1651
|
verifyAnchor,
|
|
814
1652
|
watchContracts
|
|
@@ -827,7 +1665,7 @@ function registerSync(program2) {
|
|
|
827
1665
|
).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) => {
|
|
828
1666
|
const root = findProjectRoot8(opts.dir);
|
|
829
1667
|
const paths = resolveHaivePaths5(root);
|
|
830
|
-
if (!
|
|
1668
|
+
if (!existsSync9(paths.memoriesDir)) {
|
|
831
1669
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
832
1670
|
process.exitCode = 1;
|
|
833
1671
|
return;
|
|
@@ -847,9 +1685,9 @@ function registerSync(program2) {
|
|
|
847
1685
|
for (const { memory: memory2, filePath } of memories) {
|
|
848
1686
|
if (memory2.frontmatter.type === "session_recap") {
|
|
849
1687
|
if (memory2.frontmatter.status === "stale") {
|
|
850
|
-
await
|
|
1688
|
+
await writeFile5(
|
|
851
1689
|
filePath,
|
|
852
|
-
|
|
1690
|
+
serializeMemory2({
|
|
853
1691
|
frontmatter: {
|
|
854
1692
|
...memory2.frontmatter,
|
|
855
1693
|
status: "validated",
|
|
@@ -870,9 +1708,9 @@ function registerSync(program2) {
|
|
|
870
1708
|
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
871
1709
|
if (result.stale) {
|
|
872
1710
|
if (memory2.frontmatter.status !== "stale") {
|
|
873
|
-
await
|
|
1711
|
+
await writeFile5(
|
|
874
1712
|
filePath,
|
|
875
|
-
|
|
1713
|
+
serializeMemory2({
|
|
876
1714
|
frontmatter: {
|
|
877
1715
|
...memory2.frontmatter,
|
|
878
1716
|
status: "stale",
|
|
@@ -886,9 +1724,9 @@ function registerSync(program2) {
|
|
|
886
1724
|
staleMarked++;
|
|
887
1725
|
}
|
|
888
1726
|
} else if (memory2.frontmatter.status === "stale") {
|
|
889
|
-
await
|
|
1727
|
+
await writeFile5(
|
|
890
1728
|
filePath,
|
|
891
|
-
|
|
1729
|
+
serializeMemory2({
|
|
892
1730
|
frontmatter: {
|
|
893
1731
|
...memory2.frontmatter,
|
|
894
1732
|
status: "validated",
|
|
@@ -914,9 +1752,9 @@ function registerSync(program2) {
|
|
|
914
1752
|
minReads: autoPromoteMinReads,
|
|
915
1753
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
|
|
916
1754
|
})) {
|
|
917
|
-
await
|
|
1755
|
+
await writeFile5(
|
|
918
1756
|
filePath,
|
|
919
|
-
|
|
1757
|
+
serializeMemory2({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
920
1758
|
"utf8"
|
|
921
1759
|
);
|
|
922
1760
|
promoted++;
|
|
@@ -925,9 +1763,9 @@ function registerSync(program2) {
|
|
|
925
1763
|
if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
|
|
926
1764
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
927
1765
|
if (ageHours >= autoApproveDelayHours) {
|
|
928
|
-
await
|
|
1766
|
+
await writeFile5(
|
|
929
1767
|
filePath,
|
|
930
|
-
|
|
1768
|
+
serializeMemory2({
|
|
931
1769
|
frontmatter: {
|
|
932
1770
|
...fm,
|
|
933
1771
|
status: "validated",
|
|
@@ -959,7 +1797,7 @@ function registerSync(program2) {
|
|
|
959
1797
|
);
|
|
960
1798
|
}
|
|
961
1799
|
if (opts.injectBridge) {
|
|
962
|
-
const bridgeFile = opts.bridgeFile ?
|
|
1800
|
+
const bridgeFile = opts.bridgeFile ? path10.resolve(opts.bridgeFile) : path10.join(root, "CLAUDE.md");
|
|
963
1801
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
964
1802
|
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
965
1803
|
}
|
|
@@ -1056,7 +1894,7 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
1056
1894
|
**Prochaines \xE9tapes (si confirm\xE9) :**
|
|
1057
1895
|
- Consulter le CHANGELOG : \`haive memory import-changelog --from node_modules/<pkg>/CHANGELOG.md\`
|
|
1058
1896
|
- V\xE9rifier les m\xE9moires ancr\xE9es : \`haive memory verify\``;
|
|
1059
|
-
const fm =
|
|
1897
|
+
const fm = buildFrontmatter2({
|
|
1060
1898
|
type: "gotcha",
|
|
1061
1899
|
slug,
|
|
1062
1900
|
scope: "team",
|
|
@@ -1065,11 +1903,11 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
1065
1903
|
paths: [result.file],
|
|
1066
1904
|
topic: `dep-bump-${slugParts}`
|
|
1067
1905
|
});
|
|
1068
|
-
const teamDir =
|
|
1069
|
-
await
|
|
1070
|
-
await
|
|
1071
|
-
|
|
1072
|
-
|
|
1906
|
+
const teamDir = path10.join(paths.memoriesDir, "team");
|
|
1907
|
+
await mkdir5(teamDir, { recursive: true });
|
|
1908
|
+
await writeFile5(
|
|
1909
|
+
path10.join(teamDir, `${fm.id}.md`),
|
|
1910
|
+
serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
1073
1911
|
"utf8"
|
|
1074
1912
|
);
|
|
1075
1913
|
log(ui.yellow(` \u2192 memory created: ${fm.id}`));
|
|
@@ -1123,7 +1961,7 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
1123
1961
|
**Prochaines \xE9tapes (si confirm\xE9) :**
|
|
1124
1962
|
- Rechercher les usages : \`haive memory for-files <fichiers concern\xE9s>\`
|
|
1125
1963
|
- V\xE9rifier les m\xE9moires li\xE9es : \`haive memory query ${diff.contract}\``;
|
|
1126
|
-
const fm =
|
|
1964
|
+
const fm = buildFrontmatter2({
|
|
1127
1965
|
type: "gotcha",
|
|
1128
1966
|
slug,
|
|
1129
1967
|
scope: "team",
|
|
@@ -1132,11 +1970,11 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
1132
1970
|
paths: [diff.file],
|
|
1133
1971
|
topic: `contract-breaking-${diff.contract}`
|
|
1134
1972
|
});
|
|
1135
|
-
const teamDir =
|
|
1136
|
-
await
|
|
1137
|
-
await
|
|
1138
|
-
|
|
1139
|
-
|
|
1973
|
+
const teamDir = path10.join(paths.memoriesDir, "team");
|
|
1974
|
+
await mkdir5(teamDir, { recursive: true });
|
|
1975
|
+
await writeFile5(
|
|
1976
|
+
path10.join(teamDir, `${fm.id}.md`),
|
|
1977
|
+
serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
1140
1978
|
"utf8"
|
|
1141
1979
|
);
|
|
1142
1980
|
log(ui.yellow(` \u2192 memory created: ${fm.id}`));
|
|
@@ -1196,7 +2034,7 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
1196
2034
|
});
|
|
1197
2035
|
}
|
|
1198
2036
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
1199
|
-
if (!
|
|
2037
|
+
if (!existsSync9(memoriesDir)) return;
|
|
1200
2038
|
const all = await loadMemoriesFromDir2(memoriesDir);
|
|
1201
2039
|
const top = all.filter(({ memory: memory2 }) => {
|
|
1202
2040
|
const s = memory2.frontmatter.status;
|
|
@@ -1221,17 +2059,17 @@ ${m.memory.body.trim()}`;
|
|
|
1221
2059
|
` + block + `
|
|
1222
2060
|
|
|
1223
2061
|
${BRIDGE_END}`;
|
|
1224
|
-
const fileExists =
|
|
1225
|
-
let existing = fileExists ? await
|
|
2062
|
+
const fileExists = existsSync9(bridgeFile);
|
|
2063
|
+
let existing = fileExists ? await readFile6(bridgeFile, "utf8") : "";
|
|
1226
2064
|
existing = existing.replace(/\r\n/g, "\n");
|
|
1227
2065
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
1228
2066
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
1229
2067
|
if (startIdx !== -1 && endIdx === -1) {
|
|
1230
|
-
ui.warn(`${
|
|
2068
|
+
ui.warn(`${path10.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
1231
2069
|
return;
|
|
1232
2070
|
}
|
|
1233
2071
|
if (startIdx === -1 && endIdx !== -1) {
|
|
1234
|
-
ui.warn(`${
|
|
2072
|
+
ui.warn(`${path10.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
1235
2073
|
return;
|
|
1236
2074
|
}
|
|
1237
2075
|
let updated;
|
|
@@ -1239,14 +2077,14 @@ ${BRIDGE_END}`;
|
|
|
1239
2077
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
1240
2078
|
} else {
|
|
1241
2079
|
if (!fileExists && !quiet) {
|
|
1242
|
-
ui.info(`Creating ${
|
|
2080
|
+
ui.info(`Creating ${path10.relative(root, bridgeFile)} with haive memory block.`);
|
|
1243
2081
|
}
|
|
1244
2082
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
1245
2083
|
}
|
|
1246
|
-
await
|
|
2084
|
+
await writeFile5(bridgeFile, updated, "utf8");
|
|
1247
2085
|
if (!quiet) {
|
|
1248
2086
|
console.log(
|
|
1249
|
-
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${
|
|
2087
|
+
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path10.relative(root, bridgeFile)}`)
|
|
1250
2088
|
);
|
|
1251
2089
|
}
|
|
1252
2090
|
}
|
|
@@ -1271,18 +2109,18 @@ function collectSinceChanges(root, ref) {
|
|
|
1271
2109
|
|
|
1272
2110
|
// src/commands/memory-add.ts
|
|
1273
2111
|
import { createHash } from "crypto";
|
|
1274
|
-
import { mkdir as
|
|
1275
|
-
import { existsSync as
|
|
1276
|
-
import
|
|
2112
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2113
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2114
|
+
import path11 from "path";
|
|
1277
2115
|
import "commander";
|
|
1278
2116
|
import {
|
|
1279
|
-
buildFrontmatter as
|
|
2117
|
+
buildFrontmatter as buildFrontmatter3,
|
|
1280
2118
|
findProjectRoot as findProjectRoot9,
|
|
1281
2119
|
inferModulesFromPaths,
|
|
1282
2120
|
loadMemoriesFromDir as loadMemoriesFromDir3,
|
|
1283
|
-
memoryFilePath,
|
|
2121
|
+
memoryFilePath as memoryFilePath2,
|
|
1284
2122
|
resolveHaivePaths as resolveHaivePaths6,
|
|
1285
|
-
serializeMemory as
|
|
2123
|
+
serializeMemory as serializeMemory3
|
|
1286
2124
|
} from "@hiveai/core";
|
|
1287
2125
|
function registerMemoryAdd(memory2) {
|
|
1288
2126
|
memory2.command("add").description(
|
|
@@ -1311,7 +2149,7 @@ function registerMemoryAdd(memory2) {
|
|
|
1311
2149
|
).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) => {
|
|
1312
2150
|
const root = findProjectRoot9(opts.dir);
|
|
1313
2151
|
const paths = resolveHaivePaths6(root);
|
|
1314
|
-
if (!
|
|
2152
|
+
if (!existsSync10(paths.haiveDir)) {
|
|
1315
2153
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
1316
2154
|
process.exitCode = 1;
|
|
1317
2155
|
return;
|
|
@@ -1322,7 +2160,7 @@ function registerMemoryAdd(memory2) {
|
|
|
1322
2160
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
|
|
1323
2161
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
1324
2162
|
if (anchorPaths.length > 0) {
|
|
1325
|
-
const missing = anchorPaths.filter((p) => !
|
|
2163
|
+
const missing = anchorPaths.filter((p) => !existsSync10(path11.resolve(root, p)));
|
|
1326
2164
|
if (missing.length > 0) {
|
|
1327
2165
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
1328
2166
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -1334,12 +2172,12 @@ function registerMemoryAdd(memory2) {
|
|
|
1334
2172
|
const title = opts.title ?? opts.slug;
|
|
1335
2173
|
let body;
|
|
1336
2174
|
if (opts.bodyFile !== void 0) {
|
|
1337
|
-
if (!
|
|
2175
|
+
if (!existsSync10(opts.bodyFile)) {
|
|
1338
2176
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
1339
2177
|
process.exitCode = 1;
|
|
1340
2178
|
return;
|
|
1341
2179
|
}
|
|
1342
|
-
const fileContent = await
|
|
2180
|
+
const fileContent = await readFile7(opts.bodyFile, "utf8");
|
|
1343
2181
|
body = opts.title ? `# ${opts.title}
|
|
1344
2182
|
|
|
1345
2183
|
${fileContent.trim()}
|
|
@@ -1355,7 +2193,7 @@ TODO \u2014 write the memory body.
|
|
|
1355
2193
|
`;
|
|
1356
2194
|
}
|
|
1357
2195
|
const scope = opts.scope ?? "personal";
|
|
1358
|
-
if (
|
|
2196
|
+
if (existsSync10(paths.memoriesDir)) {
|
|
1359
2197
|
const incomingHash = createHash("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
1360
2198
|
const allForHash = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1361
2199
|
const hashDup = allForHash.find(
|
|
@@ -1368,7 +2206,7 @@ TODO \u2014 write the memory body.
|
|
|
1368
2206
|
return;
|
|
1369
2207
|
}
|
|
1370
2208
|
}
|
|
1371
|
-
if (opts.topic &&
|
|
2209
|
+
if (opts.topic && existsSync10(paths.memoriesDir)) {
|
|
1372
2210
|
const existing = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1373
2211
|
const topicMatch = existing.find(
|
|
1374
2212
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
@@ -1386,13 +2224,13 @@ TODO \u2014 write the memory body.
|
|
|
1386
2224
|
symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
|
|
1387
2225
|
}
|
|
1388
2226
|
};
|
|
1389
|
-
await
|
|
1390
|
-
ui.success(`Updated (topic upsert) ${
|
|
2227
|
+
await writeFile6(topicMatch.filePath, serializeMemory3({ frontmatter: newFrontmatter, body }), "utf8");
|
|
2228
|
+
ui.success(`Updated (topic upsert) ${path11.relative(root, topicMatch.filePath)}`);
|
|
1391
2229
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
1392
2230
|
return;
|
|
1393
2231
|
}
|
|
1394
2232
|
}
|
|
1395
|
-
const frontmatter =
|
|
2233
|
+
const frontmatter = buildFrontmatter3({
|
|
1396
2234
|
type: opts.type,
|
|
1397
2235
|
slug: opts.slug,
|
|
1398
2236
|
scope,
|
|
@@ -1405,14 +2243,14 @@ TODO \u2014 write the memory body.
|
|
|
1405
2243
|
commit: opts.commit,
|
|
1406
2244
|
topic: opts.topic
|
|
1407
2245
|
});
|
|
1408
|
-
const file =
|
|
1409
|
-
await
|
|
1410
|
-
if (
|
|
2246
|
+
const file = memoryFilePath2(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
2247
|
+
await mkdir6(path11.dirname(file), { recursive: true });
|
|
2248
|
+
if (existsSync10(file)) {
|
|
1411
2249
|
ui.error(`Memory already exists at ${file}`);
|
|
1412
2250
|
process.exitCode = 1;
|
|
1413
2251
|
return;
|
|
1414
2252
|
}
|
|
1415
|
-
if (
|
|
2253
|
+
if (existsSync10(paths.memoriesDir)) {
|
|
1416
2254
|
const existing = await loadMemoriesFromDir3(paths.memoriesDir);
|
|
1417
2255
|
const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
1418
2256
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
@@ -1424,8 +2262,8 @@ TODO \u2014 write the memory body.
|
|
|
1424
2262
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
1425
2263
|
}
|
|
1426
2264
|
}
|
|
1427
|
-
await
|
|
1428
|
-
ui.success(`Created ${
|
|
2265
|
+
await writeFile6(file, serializeMemory3({ frontmatter, body }), "utf8");
|
|
2266
|
+
ui.success(`Created ${path11.relative(root, file)}`);
|
|
1429
2267
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
1430
2268
|
if (inferredTags.length > 0) {
|
|
1431
2269
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
@@ -1455,8 +2293,8 @@ function parseCsv2(value) {
|
|
|
1455
2293
|
}
|
|
1456
2294
|
|
|
1457
2295
|
// src/commands/memory-list.ts
|
|
1458
|
-
import { existsSync as
|
|
1459
|
-
import
|
|
2296
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2297
|
+
import path12 from "path";
|
|
1460
2298
|
import "commander";
|
|
1461
2299
|
import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
|
|
1462
2300
|
|
|
@@ -1472,7 +2310,7 @@ function registerMemoryList(memory2) {
|
|
|
1472
2310
|
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) => {
|
|
1473
2311
|
const root = findProjectRoot10(opts.dir);
|
|
1474
2312
|
const paths = resolveHaivePaths7(root);
|
|
1475
|
-
if (!
|
|
2313
|
+
if (!existsSync11(paths.memoriesDir)) {
|
|
1476
2314
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
1477
2315
|
process.exitCode = 1;
|
|
1478
2316
|
return;
|
|
@@ -1504,7 +2342,7 @@ function registerMemoryList(memory2) {
|
|
|
1504
2342
|
console.log(
|
|
1505
2343
|
`${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
|
|
1506
2344
|
);
|
|
1507
|
-
console.log(` ${ui.dim(
|
|
2345
|
+
console.log(` ${ui.dim(path12.relative(root, filePath))}`);
|
|
1508
2346
|
}
|
|
1509
2347
|
console.log(ui.dim(`
|
|
1510
2348
|
${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
|
|
@@ -1539,21 +2377,21 @@ function matchesFilters(loaded, opts) {
|
|
|
1539
2377
|
}
|
|
1540
2378
|
|
|
1541
2379
|
// src/commands/memory-promote.ts
|
|
1542
|
-
import { mkdir as
|
|
1543
|
-
import { existsSync as
|
|
1544
|
-
import
|
|
2380
|
+
import { mkdir as mkdir7, unlink, writeFile as writeFile7 } from "fs/promises";
|
|
2381
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2382
|
+
import path13 from "path";
|
|
1545
2383
|
import "commander";
|
|
1546
2384
|
import {
|
|
1547
2385
|
findProjectRoot as findProjectRoot11,
|
|
1548
|
-
memoryFilePath as
|
|
2386
|
+
memoryFilePath as memoryFilePath3,
|
|
1549
2387
|
resolveHaivePaths as resolveHaivePaths8,
|
|
1550
|
-
serializeMemory as
|
|
2388
|
+
serializeMemory as serializeMemory4
|
|
1551
2389
|
} from "@hiveai/core";
|
|
1552
2390
|
function registerMemoryPromote(memory2) {
|
|
1553
2391
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
1554
2392
|
const root = findProjectRoot11(opts.dir);
|
|
1555
2393
|
const paths = resolveHaivePaths8(root);
|
|
1556
|
-
if (!
|
|
2394
|
+
if (!existsSync12(paths.memoriesDir)) {
|
|
1557
2395
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
1558
2396
|
process.exitCode = 1;
|
|
1559
2397
|
return;
|
|
@@ -1587,31 +2425,31 @@ function registerMemoryPromote(memory2) {
|
|
|
1587
2425
|
},
|
|
1588
2426
|
body: found.memory.body
|
|
1589
2427
|
};
|
|
1590
|
-
const newPath =
|
|
1591
|
-
await
|
|
1592
|
-
await
|
|
2428
|
+
const newPath = memoryFilePath3(paths, "team", updated.frontmatter.id);
|
|
2429
|
+
await mkdir7(path13.dirname(newPath), { recursive: true });
|
|
2430
|
+
await writeFile7(newPath, serializeMemory4(updated), "utf8");
|
|
1593
2431
|
await unlink(found.filePath);
|
|
1594
2432
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
1595
|
-
ui.info(`Now at ${
|
|
2433
|
+
ui.info(`Now at ${path13.relative(root, newPath)}`);
|
|
1596
2434
|
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
1597
2435
|
});
|
|
1598
2436
|
}
|
|
1599
2437
|
|
|
1600
2438
|
// src/commands/memory-approve.ts
|
|
1601
|
-
import { existsSync as
|
|
1602
|
-
import { writeFile as
|
|
1603
|
-
import
|
|
2439
|
+
import { existsSync as existsSync13 } from "fs";
|
|
2440
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
2441
|
+
import path14 from "path";
|
|
1604
2442
|
import "commander";
|
|
1605
2443
|
import {
|
|
1606
2444
|
findProjectRoot as findProjectRoot12,
|
|
1607
2445
|
resolveHaivePaths as resolveHaivePaths9,
|
|
1608
|
-
serializeMemory as
|
|
2446
|
+
serializeMemory as serializeMemory5
|
|
1609
2447
|
} from "@hiveai/core";
|
|
1610
2448
|
function registerMemoryApprove(memory2) {
|
|
1611
2449
|
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) => {
|
|
1612
2450
|
const root = findProjectRoot12(opts.dir);
|
|
1613
2451
|
const paths = resolveHaivePaths9(root);
|
|
1614
|
-
if (!
|
|
2452
|
+
if (!existsSync13(paths.memoriesDir)) {
|
|
1615
2453
|
ui.error(`No .ai/memories at ${root}.`);
|
|
1616
2454
|
process.exitCode = 1;
|
|
1617
2455
|
return;
|
|
@@ -1633,7 +2471,7 @@ function registerMemoryApprove(memory2) {
|
|
|
1633
2471
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
1634
2472
|
body: found2.memory.body
|
|
1635
2473
|
};
|
|
1636
|
-
await
|
|
2474
|
+
await writeFile8(found2.filePath, serializeMemory5(next2), "utf8");
|
|
1637
2475
|
count++;
|
|
1638
2476
|
}
|
|
1639
2477
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -1662,27 +2500,27 @@ function registerMemoryApprove(memory2) {
|
|
|
1662
2500
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
1663
2501
|
body: found.memory.body
|
|
1664
2502
|
};
|
|
1665
|
-
await
|
|
2503
|
+
await writeFile8(found.filePath, serializeMemory5(next), "utf8");
|
|
1666
2504
|
ui.success(`Approved ${id} (status=validated)`);
|
|
1667
|
-
ui.info(
|
|
2505
|
+
ui.info(path14.relative(root, found.filePath));
|
|
1668
2506
|
});
|
|
1669
2507
|
}
|
|
1670
2508
|
|
|
1671
2509
|
// src/commands/memory-update.ts
|
|
1672
|
-
import { writeFile as
|
|
1673
|
-
import { existsSync as
|
|
1674
|
-
import
|
|
2510
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
2511
|
+
import { existsSync as existsSync14 } from "fs";
|
|
2512
|
+
import path15 from "path";
|
|
1675
2513
|
import "commander";
|
|
1676
2514
|
import {
|
|
1677
2515
|
findProjectRoot as findProjectRoot13,
|
|
1678
2516
|
resolveHaivePaths as resolveHaivePaths10,
|
|
1679
|
-
serializeMemory as
|
|
2517
|
+
serializeMemory as serializeMemory6
|
|
1680
2518
|
} from "@hiveai/core";
|
|
1681
2519
|
function registerMemoryUpdate(memory2) {
|
|
1682
2520
|
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) => {
|
|
1683
2521
|
const root = findProjectRoot13(opts.dir);
|
|
1684
2522
|
const paths = resolveHaivePaths10(root);
|
|
1685
|
-
if (!
|
|
2523
|
+
if (!existsSync14(paths.memoriesDir)) {
|
|
1686
2524
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
1687
2525
|
process.exitCode = 1;
|
|
1688
2526
|
return;
|
|
@@ -1729,12 +2567,12 @@ function registerMemoryUpdate(memory2) {
|
|
|
1729
2567
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
1730
2568
|
return;
|
|
1731
2569
|
}
|
|
1732
|
-
await
|
|
2570
|
+
await writeFile9(
|
|
1733
2571
|
loaded.filePath,
|
|
1734
|
-
|
|
2572
|
+
serializeMemory6({ frontmatter: newFrontmatter, body: newBody }),
|
|
1735
2573
|
"utf8"
|
|
1736
2574
|
);
|
|
1737
|
-
ui.success(`Updated ${
|
|
2575
|
+
ui.success(`Updated ${path15.relative(root, loaded.filePath)}`);
|
|
1738
2576
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
1739
2577
|
});
|
|
1740
2578
|
}
|
|
@@ -1753,9 +2591,9 @@ function parseCsv3(value) {
|
|
|
1753
2591
|
}
|
|
1754
2592
|
|
|
1755
2593
|
// src/commands/memory-auto-promote.ts
|
|
1756
|
-
import { writeFile as
|
|
1757
|
-
import { existsSync as
|
|
1758
|
-
import
|
|
2594
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
2595
|
+
import { existsSync as existsSync15 } from "fs";
|
|
2596
|
+
import path16 from "path";
|
|
1759
2597
|
import "commander";
|
|
1760
2598
|
import {
|
|
1761
2599
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
@@ -1764,7 +2602,7 @@ import {
|
|
|
1764
2602
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
1765
2603
|
loadUsageIndex as loadUsageIndex2,
|
|
1766
2604
|
resolveHaivePaths as resolveHaivePaths11,
|
|
1767
|
-
serializeMemory as
|
|
2605
|
+
serializeMemory as serializeMemory7
|
|
1768
2606
|
} from "@hiveai/core";
|
|
1769
2607
|
function registerMemoryAutoPromote(memory2) {
|
|
1770
2608
|
memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE2.minReads)).option(
|
|
@@ -1774,7 +2612,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
1774
2612
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
1775
2613
|
const root = findProjectRoot14(opts.dir);
|
|
1776
2614
|
const paths = resolveHaivePaths11(root);
|
|
1777
|
-
if (!
|
|
2615
|
+
if (!existsSync15(paths.memoriesDir)) {
|
|
1778
2616
|
ui.error(`No .ai/memories at ${root}.`);
|
|
1779
2617
|
process.exitCode = 1;
|
|
1780
2618
|
return;
|
|
@@ -1800,13 +2638,13 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
1800
2638
|
console.log(
|
|
1801
2639
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
1802
2640
|
);
|
|
1803
|
-
console.log(` ${ui.dim(
|
|
2641
|
+
console.log(` ${ui.dim(path16.relative(root, filePath))}`);
|
|
1804
2642
|
if (opts.apply) {
|
|
1805
2643
|
const next = {
|
|
1806
2644
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
1807
2645
|
body: mem.body
|
|
1808
2646
|
};
|
|
1809
|
-
await
|
|
2647
|
+
await writeFile10(filePath, serializeMemory7(next), "utf8");
|
|
1810
2648
|
written++;
|
|
1811
2649
|
}
|
|
1812
2650
|
}
|
|
@@ -1817,9 +2655,9 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
1817
2655
|
|
|
1818
2656
|
// src/commands/memory-edit.ts
|
|
1819
2657
|
import { spawn as spawn2 } from "child_process";
|
|
1820
|
-
import { existsSync as
|
|
1821
|
-
import { readFile as
|
|
1822
|
-
import
|
|
2658
|
+
import { existsSync as existsSync16 } from "fs";
|
|
2659
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2660
|
+
import path17 from "path";
|
|
1823
2661
|
import "commander";
|
|
1824
2662
|
import {
|
|
1825
2663
|
findProjectRoot as findProjectRoot15,
|
|
@@ -1830,7 +2668,7 @@ function registerMemoryEdit(memory2) {
|
|
|
1830
2668
|
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) => {
|
|
1831
2669
|
const root = findProjectRoot15(opts.dir);
|
|
1832
2670
|
const paths = resolveHaivePaths12(root);
|
|
1833
|
-
if (!
|
|
2671
|
+
if (!existsSync16(paths.memoriesDir)) {
|
|
1834
2672
|
ui.error(`No .ai/memories at ${root}.`);
|
|
1835
2673
|
process.exitCode = 1;
|
|
1836
2674
|
return;
|
|
@@ -1843,13 +2681,13 @@ function registerMemoryEdit(memory2) {
|
|
|
1843
2681
|
return;
|
|
1844
2682
|
}
|
|
1845
2683
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
1846
|
-
ui.info(`Opening ${
|
|
2684
|
+
ui.info(`Opening ${path17.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
1847
2685
|
const code = await runEditor(editor, found.filePath);
|
|
1848
2686
|
if (code !== 0) {
|
|
1849
2687
|
ui.warn(`Editor exited with status ${code}.`);
|
|
1850
2688
|
}
|
|
1851
2689
|
try {
|
|
1852
|
-
const fresh = await
|
|
2690
|
+
const fresh = await readFile8(found.filePath, "utf8");
|
|
1853
2691
|
parseMemory(fresh);
|
|
1854
2692
|
ui.success("Memory still parses cleanly.");
|
|
1855
2693
|
} catch (err) {
|
|
@@ -1870,8 +2708,8 @@ function runEditor(editor, file) {
|
|
|
1870
2708
|
}
|
|
1871
2709
|
|
|
1872
2710
|
// src/commands/memory-for-files.ts
|
|
1873
|
-
import { existsSync as
|
|
1874
|
-
import
|
|
2711
|
+
import { existsSync as existsSync17 } from "fs";
|
|
2712
|
+
import path18 from "path";
|
|
1875
2713
|
import "commander";
|
|
1876
2714
|
import {
|
|
1877
2715
|
deriveConfidence,
|
|
@@ -1886,7 +2724,7 @@ function registerMemoryForFiles(memory2) {
|
|
|
1886
2724
|
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) => {
|
|
1887
2725
|
const root = findProjectRoot16(opts.dir);
|
|
1888
2726
|
const paths = resolveHaivePaths13(root);
|
|
1889
|
-
if (!
|
|
2727
|
+
if (!existsSync17(paths.memoriesDir)) {
|
|
1890
2728
|
ui.error(`No .ai/memories at ${root}.`);
|
|
1891
2729
|
process.exitCode = 1;
|
|
1892
2730
|
return;
|
|
@@ -1993,13 +2831,13 @@ function printGroup(root, label, loaded, usage) {
|
|
|
1993
2831
|
const u = getUsage3(usage, fm.id);
|
|
1994
2832
|
const conf = deriveConfidence(fm, u);
|
|
1995
2833
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
1996
|
-
console.log(` ${ui.dim(
|
|
2834
|
+
console.log(` ${ui.dim(path18.relative(root, filePath))}`);
|
|
1997
2835
|
}
|
|
1998
2836
|
}
|
|
1999
2837
|
|
|
2000
2838
|
// src/commands/memory-hot.ts
|
|
2001
|
-
import { existsSync as
|
|
2002
|
-
import
|
|
2839
|
+
import { existsSync as existsSync18 } from "fs";
|
|
2840
|
+
import path19 from "path";
|
|
2003
2841
|
import "commander";
|
|
2004
2842
|
import {
|
|
2005
2843
|
findProjectRoot as findProjectRoot17,
|
|
@@ -2011,7 +2849,7 @@ function registerMemoryHot(memory2) {
|
|
|
2011
2849
|
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) => {
|
|
2012
2850
|
const root = findProjectRoot17(opts.dir);
|
|
2013
2851
|
const paths = resolveHaivePaths14(root);
|
|
2014
|
-
if (!
|
|
2852
|
+
if (!existsSync18(paths.memoriesDir)) {
|
|
2015
2853
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2016
2854
|
process.exitCode = 1;
|
|
2017
2855
|
return;
|
|
@@ -2039,7 +2877,7 @@ function registerMemoryHot(memory2) {
|
|
|
2039
2877
|
console.log(
|
|
2040
2878
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
2041
2879
|
);
|
|
2042
|
-
console.log(` ${ui.dim(
|
|
2880
|
+
console.log(` ${ui.dim(path19.relative(root, filePath))}`);
|
|
2043
2881
|
}
|
|
2044
2882
|
ui.info(
|
|
2045
2883
|
`${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
|
|
@@ -2048,16 +2886,16 @@ function registerMemoryHot(memory2) {
|
|
|
2048
2886
|
}
|
|
2049
2887
|
|
|
2050
2888
|
// src/commands/memory-tried.ts
|
|
2051
|
-
import { mkdir as
|
|
2052
|
-
import { existsSync as
|
|
2053
|
-
import
|
|
2889
|
+
import { mkdir as mkdir8, writeFile as writeFile11 } from "fs/promises";
|
|
2890
|
+
import { existsSync as existsSync19 } from "fs";
|
|
2891
|
+
import path20 from "path";
|
|
2054
2892
|
import "commander";
|
|
2055
2893
|
import {
|
|
2056
|
-
buildFrontmatter as
|
|
2894
|
+
buildFrontmatter as buildFrontmatter4,
|
|
2057
2895
|
findProjectRoot as findProjectRoot18,
|
|
2058
|
-
memoryFilePath as
|
|
2896
|
+
memoryFilePath as memoryFilePath4,
|
|
2059
2897
|
resolveHaivePaths as resolveHaivePaths15,
|
|
2060
|
-
serializeMemory as
|
|
2898
|
+
serializeMemory as serializeMemory8
|
|
2061
2899
|
} from "@hiveai/core";
|
|
2062
2900
|
function registerMemoryTried(memory2) {
|
|
2063
2901
|
memory2.command("tried").description(
|
|
@@ -2078,13 +2916,13 @@ function registerMemoryTried(memory2) {
|
|
|
2078
2916
|
).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) => {
|
|
2079
2917
|
const root = findProjectRoot18(opts.dir);
|
|
2080
2918
|
const paths = resolveHaivePaths15(root);
|
|
2081
|
-
if (!
|
|
2919
|
+
if (!existsSync19(paths.haiveDir)) {
|
|
2082
2920
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
2083
2921
|
process.exitCode = 1;
|
|
2084
2922
|
return;
|
|
2085
2923
|
}
|
|
2086
2924
|
const slug = opts.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
2087
|
-
const baseFm =
|
|
2925
|
+
const baseFm = buildFrontmatter4({
|
|
2088
2926
|
type: "attempt",
|
|
2089
2927
|
slug,
|
|
2090
2928
|
scope: opts.scope,
|
|
@@ -2100,15 +2938,15 @@ function registerMemoryTried(memory2) {
|
|
|
2100
2938
|
lines.push("", `**Instead, use:** ${opts.instead}`);
|
|
2101
2939
|
}
|
|
2102
2940
|
const body = lines.join("\n") + "\n";
|
|
2103
|
-
const file =
|
|
2104
|
-
await
|
|
2105
|
-
if (
|
|
2941
|
+
const file = memoryFilePath4(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
2942
|
+
await mkdir8(path20.dirname(file), { recursive: true });
|
|
2943
|
+
if (existsSync19(file)) {
|
|
2106
2944
|
ui.error(`Memory already exists at ${file}`);
|
|
2107
2945
|
process.exitCode = 1;
|
|
2108
2946
|
return;
|
|
2109
2947
|
}
|
|
2110
|
-
await
|
|
2111
|
-
ui.success(`Recorded: ${
|
|
2948
|
+
await writeFile11(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
2949
|
+
ui.success(`Recorded: ${path20.relative(root, file)}`);
|
|
2112
2950
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
2113
2951
|
});
|
|
2114
2952
|
}
|
|
@@ -2118,8 +2956,8 @@ function parseCsv4(value) {
|
|
|
2118
2956
|
}
|
|
2119
2957
|
|
|
2120
2958
|
// src/commands/memory-pending.ts
|
|
2121
|
-
import { existsSync as
|
|
2122
|
-
import
|
|
2959
|
+
import { existsSync as existsSync20 } from "fs";
|
|
2960
|
+
import path21 from "path";
|
|
2123
2961
|
import "commander";
|
|
2124
2962
|
import {
|
|
2125
2963
|
findProjectRoot as findProjectRoot19,
|
|
@@ -2131,7 +2969,7 @@ function registerMemoryPending(memory2) {
|
|
|
2131
2969
|
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) => {
|
|
2132
2970
|
const root = findProjectRoot19(opts.dir);
|
|
2133
2971
|
const paths = resolveHaivePaths16(root);
|
|
2134
|
-
if (!
|
|
2972
|
+
if (!existsSync20(paths.memoriesDir)) {
|
|
2135
2973
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2136
2974
|
process.exitCode = 1;
|
|
2137
2975
|
return;
|
|
@@ -2159,15 +2997,15 @@ function registerMemoryPending(memory2) {
|
|
|
2159
2997
|
console.log(
|
|
2160
2998
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
2161
2999
|
);
|
|
2162
|
-
console.log(` ${ui.dim(
|
|
3000
|
+
console.log(` ${ui.dim(path21.relative(root, filePath))}`);
|
|
2163
3001
|
}
|
|
2164
3002
|
ui.info(`${proposed.length} pending`);
|
|
2165
3003
|
});
|
|
2166
3004
|
}
|
|
2167
3005
|
|
|
2168
3006
|
// src/commands/memory-query.ts
|
|
2169
|
-
import { existsSync as
|
|
2170
|
-
import
|
|
3007
|
+
import { existsSync as existsSync21 } from "fs";
|
|
3008
|
+
import path22 from "path";
|
|
2171
3009
|
import "commander";
|
|
2172
3010
|
import {
|
|
2173
3011
|
extractSnippet,
|
|
@@ -2183,7 +3021,7 @@ function registerMemoryQuery(memory2) {
|
|
|
2183
3021
|
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) => {
|
|
2184
3022
|
const root = findProjectRoot20(opts.dir);
|
|
2185
3023
|
const paths = resolveHaivePaths17(root);
|
|
2186
|
-
if (!
|
|
3024
|
+
if (!existsSync21(paths.memoriesDir)) {
|
|
2187
3025
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
2188
3026
|
process.exitCode = 1;
|
|
2189
3027
|
return;
|
|
@@ -2224,7 +3062,7 @@ function registerMemoryQuery(memory2) {
|
|
|
2224
3062
|
const fm = mem.frontmatter;
|
|
2225
3063
|
const statusBadge = ui.statusBadge(fm.status);
|
|
2226
3064
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
2227
|
-
console.log(` ${ui.dim(
|
|
3065
|
+
console.log(` ${ui.dim(path22.relative(root, filePath))}`);
|
|
2228
3066
|
const snippet = extractSnippet(mem.body, snippetNeedle);
|
|
2229
3067
|
if (snippet) console.log(` ${snippet}`);
|
|
2230
3068
|
}
|
|
@@ -2241,8 +3079,8 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
2241
3079
|
}
|
|
2242
3080
|
|
|
2243
3081
|
// src/commands/memory-reject.ts
|
|
2244
|
-
import { writeFile as
|
|
2245
|
-
import { existsSync as
|
|
3082
|
+
import { writeFile as writeFile12 } from "fs/promises";
|
|
3083
|
+
import { existsSync as existsSync22 } from "fs";
|
|
2246
3084
|
import "commander";
|
|
2247
3085
|
import {
|
|
2248
3086
|
findProjectRoot as findProjectRoot21,
|
|
@@ -2250,13 +3088,13 @@ import {
|
|
|
2250
3088
|
recordRejection,
|
|
2251
3089
|
resolveHaivePaths as resolveHaivePaths18,
|
|
2252
3090
|
saveUsageIndex,
|
|
2253
|
-
serializeMemory as
|
|
3091
|
+
serializeMemory as serializeMemory9
|
|
2254
3092
|
} from "@hiveai/core";
|
|
2255
3093
|
function registerMemoryReject(memory2) {
|
|
2256
3094
|
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) => {
|
|
2257
3095
|
const root = findProjectRoot21(opts.dir);
|
|
2258
3096
|
const paths = resolveHaivePaths18(root);
|
|
2259
|
-
if (!
|
|
3097
|
+
if (!existsSync22(paths.memoriesDir)) {
|
|
2260
3098
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2261
3099
|
process.exitCode = 1;
|
|
2262
3100
|
return;
|
|
@@ -2268,9 +3106,9 @@ function registerMemoryReject(memory2) {
|
|
|
2268
3106
|
process.exitCode = 1;
|
|
2269
3107
|
return;
|
|
2270
3108
|
}
|
|
2271
|
-
await
|
|
3109
|
+
await writeFile12(
|
|
2272
3110
|
loaded.filePath,
|
|
2273
|
-
|
|
3111
|
+
serializeMemory9({
|
|
2274
3112
|
frontmatter: {
|
|
2275
3113
|
...loaded.memory.frontmatter,
|
|
2276
3114
|
status: "rejected",
|
|
@@ -2292,9 +3130,9 @@ function registerMemoryReject(memory2) {
|
|
|
2292
3130
|
}
|
|
2293
3131
|
|
|
2294
3132
|
// src/commands/memory-rm.ts
|
|
2295
|
-
import { existsSync as
|
|
3133
|
+
import { existsSync as existsSync23 } from "fs";
|
|
2296
3134
|
import { unlink as unlink2 } from "fs/promises";
|
|
2297
|
-
import
|
|
3135
|
+
import path23 from "path";
|
|
2298
3136
|
import { createInterface } from "readline/promises";
|
|
2299
3137
|
import "commander";
|
|
2300
3138
|
import {
|
|
@@ -2307,7 +3145,7 @@ function registerMemoryRm(memory2) {
|
|
|
2307
3145
|
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) => {
|
|
2308
3146
|
const root = findProjectRoot22(opts.dir);
|
|
2309
3147
|
const paths = resolveHaivePaths19(root);
|
|
2310
|
-
if (!
|
|
3148
|
+
if (!existsSync23(paths.memoriesDir)) {
|
|
2311
3149
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2312
3150
|
process.exitCode = 1;
|
|
2313
3151
|
return;
|
|
@@ -2319,7 +3157,7 @@ function registerMemoryRm(memory2) {
|
|
|
2319
3157
|
process.exitCode = 1;
|
|
2320
3158
|
return;
|
|
2321
3159
|
}
|
|
2322
|
-
const rel =
|
|
3160
|
+
const rel = path23.relative(root, found.filePath);
|
|
2323
3161
|
if (!opts.yes) {
|
|
2324
3162
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2325
3163
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -2343,9 +3181,9 @@ function registerMemoryRm(memory2) {
|
|
|
2343
3181
|
}
|
|
2344
3182
|
|
|
2345
3183
|
// src/commands/memory-show.ts
|
|
2346
|
-
import { existsSync as
|
|
2347
|
-
import { readFile as
|
|
2348
|
-
import
|
|
3184
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3185
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
3186
|
+
import path24 from "path";
|
|
2349
3187
|
import "commander";
|
|
2350
3188
|
import {
|
|
2351
3189
|
deriveConfidence as deriveConfidence2,
|
|
@@ -2358,7 +3196,7 @@ function registerMemoryShow(memory2) {
|
|
|
2358
3196
|
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) => {
|
|
2359
3197
|
const root = findProjectRoot23(opts.dir);
|
|
2360
3198
|
const paths = resolveHaivePaths20(root);
|
|
2361
|
-
if (!
|
|
3199
|
+
if (!existsSync24(paths.memoriesDir)) {
|
|
2362
3200
|
ui.error(`No .ai/memories at ${root}.`);
|
|
2363
3201
|
process.exitCode = 1;
|
|
2364
3202
|
return;
|
|
@@ -2371,7 +3209,7 @@ function registerMemoryShow(memory2) {
|
|
|
2371
3209
|
return;
|
|
2372
3210
|
}
|
|
2373
3211
|
if (opts.raw) {
|
|
2374
|
-
console.log(await
|
|
3212
|
+
console.log(await readFile9(found.filePath, "utf8"));
|
|
2375
3213
|
return;
|
|
2376
3214
|
}
|
|
2377
3215
|
const fm = found.memory.frontmatter;
|
|
@@ -2387,7 +3225,7 @@ function registerMemoryShow(memory2) {
|
|
|
2387
3225
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
2388
3226
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
2389
3227
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
2390
|
-
console.log(`${ui.dim("file:")} ${
|
|
3228
|
+
console.log(`${ui.dim("file:")} ${path24.relative(root, found.filePath)}`);
|
|
2391
3229
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
2392
3230
|
console.log(ui.dim("anchor:"));
|
|
2393
3231
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -2402,8 +3240,8 @@ function registerMemoryShow(memory2) {
|
|
|
2402
3240
|
}
|
|
2403
3241
|
|
|
2404
3242
|
// src/commands/memory-stats.ts
|
|
2405
|
-
import { existsSync as
|
|
2406
|
-
import
|
|
3243
|
+
import { existsSync as existsSync25 } from "fs";
|
|
3244
|
+
import path25 from "path";
|
|
2407
3245
|
import "commander";
|
|
2408
3246
|
import {
|
|
2409
3247
|
deriveConfidence as deriveConfidence3,
|
|
@@ -2416,7 +3254,7 @@ function registerMemoryStats(memory2) {
|
|
|
2416
3254
|
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) => {
|
|
2417
3255
|
const root = findProjectRoot24(opts.dir);
|
|
2418
3256
|
const paths = resolveHaivePaths21(root);
|
|
2419
|
-
if (!
|
|
3257
|
+
if (!existsSync25(paths.memoriesDir)) {
|
|
2420
3258
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
2421
3259
|
process.exitCode = 1;
|
|
2422
3260
|
return;
|
|
@@ -2441,20 +3279,20 @@ function registerMemoryStats(memory2) {
|
|
|
2441
3279
|
console.log(
|
|
2442
3280
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
2443
3281
|
);
|
|
2444
|
-
console.log(` ${ui.dim(
|
|
3282
|
+
console.log(` ${ui.dim(path25.relative(root, filePath))}`);
|
|
2445
3283
|
}
|
|
2446
3284
|
});
|
|
2447
3285
|
}
|
|
2448
3286
|
|
|
2449
3287
|
// src/commands/memory-verify.ts
|
|
2450
|
-
import { writeFile as
|
|
2451
|
-
import { existsSync as
|
|
2452
|
-
import
|
|
3288
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
3289
|
+
import { existsSync as existsSync26 } from "fs";
|
|
3290
|
+
import path26 from "path";
|
|
2453
3291
|
import "commander";
|
|
2454
3292
|
import {
|
|
2455
3293
|
findProjectRoot as findProjectRoot25,
|
|
2456
3294
|
resolveHaivePaths as resolveHaivePaths22,
|
|
2457
|
-
serializeMemory as
|
|
3295
|
+
serializeMemory as serializeMemory10,
|
|
2458
3296
|
verifyAnchor as verifyAnchor2
|
|
2459
3297
|
} from "@hiveai/core";
|
|
2460
3298
|
function registerMemoryVerify(memory2) {
|
|
@@ -2463,7 +3301,7 @@ function registerMemoryVerify(memory2) {
|
|
|
2463
3301
|
).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) => {
|
|
2464
3302
|
const root = findProjectRoot25(opts.dir);
|
|
2465
3303
|
const paths = resolveHaivePaths22(root);
|
|
2466
|
-
if (!
|
|
3304
|
+
if (!existsSync26(paths.memoriesDir)) {
|
|
2467
3305
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
2468
3306
|
process.exitCode = 1;
|
|
2469
3307
|
return;
|
|
@@ -2486,7 +3324,7 @@ function registerMemoryVerify(memory2) {
|
|
|
2486
3324
|
anchorlessIds.push(mem.frontmatter.id);
|
|
2487
3325
|
continue;
|
|
2488
3326
|
}
|
|
2489
|
-
const rel =
|
|
3327
|
+
const rel = path26.relative(root, filePath);
|
|
2490
3328
|
if (result.stale) {
|
|
2491
3329
|
staleCount++;
|
|
2492
3330
|
console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
|
|
@@ -2501,7 +3339,7 @@ function registerMemoryVerify(memory2) {
|
|
|
2501
3339
|
}
|
|
2502
3340
|
if (opts.update) {
|
|
2503
3341
|
const next = applyVerification(mem, result);
|
|
2504
|
-
await
|
|
3342
|
+
await writeFile13(filePath, serializeMemory10(next), "utf8");
|
|
2505
3343
|
updated++;
|
|
2506
3344
|
}
|
|
2507
3345
|
}
|
|
@@ -2549,8 +3387,8 @@ function applyVerification(mem, result) {
|
|
|
2549
3387
|
}
|
|
2550
3388
|
|
|
2551
3389
|
// src/commands/memory-import.ts
|
|
2552
|
-
import { readFile as
|
|
2553
|
-
import { existsSync as
|
|
3390
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
3391
|
+
import { existsSync as existsSync27 } from "fs";
|
|
2554
3392
|
import "commander";
|
|
2555
3393
|
import {
|
|
2556
3394
|
findProjectRoot as findProjectRoot26,
|
|
@@ -2562,17 +3400,17 @@ function registerMemoryImport(memory2) {
|
|
|
2562
3400
|
).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) => {
|
|
2563
3401
|
const root = findProjectRoot26(opts.dir);
|
|
2564
3402
|
const paths = resolveHaivePaths23(root);
|
|
2565
|
-
if (!
|
|
3403
|
+
if (!existsSync27(paths.haiveDir)) {
|
|
2566
3404
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
2567
3405
|
process.exitCode = 1;
|
|
2568
3406
|
return;
|
|
2569
3407
|
}
|
|
2570
|
-
if (!
|
|
3408
|
+
if (!existsSync27(opts.from)) {
|
|
2571
3409
|
ui.error(`File not found: ${opts.from}`);
|
|
2572
3410
|
process.exitCode = 1;
|
|
2573
3411
|
return;
|
|
2574
3412
|
}
|
|
2575
|
-
const content = await
|
|
3413
|
+
const content = await readFile10(opts.from, "utf8");
|
|
2576
3414
|
const scope = opts.scope ?? "team";
|
|
2577
3415
|
ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
|
|
2578
3416
|
ui.info(`Content length: ${content.length} chars`);
|
|
@@ -2600,15 +3438,15 @@ function registerMemoryImport(memory2) {
|
|
|
2600
3438
|
}
|
|
2601
3439
|
|
|
2602
3440
|
// src/commands/memory-import-changelog.ts
|
|
2603
|
-
import { existsSync as
|
|
2604
|
-
import { readFile as
|
|
2605
|
-
import
|
|
3441
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3442
|
+
import { readFile as readFile11, mkdir as mkdir9, writeFile as writeFile14 } from "fs/promises";
|
|
3443
|
+
import path27 from "path";
|
|
2606
3444
|
import "commander";
|
|
2607
3445
|
import {
|
|
2608
|
-
buildFrontmatter as
|
|
3446
|
+
buildFrontmatter as buildFrontmatter5,
|
|
2609
3447
|
findProjectRoot as findProjectRoot27,
|
|
2610
3448
|
resolveHaivePaths as resolveHaivePaths24,
|
|
2611
|
-
serializeMemory as
|
|
3449
|
+
serializeMemory as serializeMemory11
|
|
2612
3450
|
} from "@hiveai/core";
|
|
2613
3451
|
function parseChangelog(content) {
|
|
2614
3452
|
const entries = [];
|
|
@@ -2673,13 +3511,13 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
2673
3511
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2674
3512
|
const root = findProjectRoot27(opts.dir);
|
|
2675
3513
|
const paths = resolveHaivePaths24(root);
|
|
2676
|
-
const changelogPath =
|
|
2677
|
-
if (!
|
|
3514
|
+
const changelogPath = path27.resolve(root, opts.fromChangelog);
|
|
3515
|
+
if (!existsSync28(changelogPath)) {
|
|
2678
3516
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
2679
3517
|
process.exitCode = 1;
|
|
2680
3518
|
return;
|
|
2681
3519
|
}
|
|
2682
|
-
const content = await
|
|
3520
|
+
const content = await readFile11(changelogPath, "utf8");
|
|
2683
3521
|
let entries = parseChangelog(content);
|
|
2684
3522
|
if (entries.length === 0) {
|
|
2685
3523
|
ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
|
|
@@ -2693,10 +3531,10 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
2693
3531
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
2694
3532
|
}
|
|
2695
3533
|
}
|
|
2696
|
-
const pkgName = opts.package ??
|
|
3534
|
+
const pkgName = opts.package ?? path27.basename(path27.dirname(changelogPath));
|
|
2697
3535
|
const scope = opts.scope ?? "team";
|
|
2698
|
-
const teamDir =
|
|
2699
|
-
await
|
|
3536
|
+
const teamDir = path27.join(paths.memoriesDir, scope);
|
|
3537
|
+
await mkdir9(teamDir, { recursive: true });
|
|
2700
3538
|
let saved = 0;
|
|
2701
3539
|
for (const entry of entries) {
|
|
2702
3540
|
const lines = [];
|
|
@@ -2718,11 +3556,11 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
2718
3556
|
lines.push("");
|
|
2719
3557
|
}
|
|
2720
3558
|
lines.push(
|
|
2721
|
-
`**Source:** \`${
|
|
3559
|
+
`**Source:** \`${path27.relative(root, changelogPath)}\`
|
|
2722
3560
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
2723
3561
|
);
|
|
2724
3562
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
2725
|
-
const fm =
|
|
3563
|
+
const fm = buildFrontmatter5({
|
|
2726
3564
|
type: "gotcha",
|
|
2727
3565
|
slug,
|
|
2728
3566
|
scope,
|
|
@@ -2733,12 +3571,12 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
2733
3571
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
2734
3572
|
`v${entry.version}`
|
|
2735
3573
|
],
|
|
2736
|
-
paths: [
|
|
3574
|
+
paths: [path27.relative(root, changelogPath)],
|
|
2737
3575
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
2738
3576
|
});
|
|
2739
|
-
await
|
|
2740
|
-
|
|
2741
|
-
|
|
3577
|
+
await writeFile14(
|
|
3578
|
+
path27.join(teamDir, `${fm.id}.md`),
|
|
3579
|
+
serializeMemory11({ frontmatter: fm, body: lines.join("\n") }),
|
|
2742
3580
|
"utf8"
|
|
2743
3581
|
);
|
|
2744
3582
|
console.log(ui.green(` \u2713 ${fm.id}`));
|
|
@@ -2760,9 +3598,9 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
2760
3598
|
}
|
|
2761
3599
|
|
|
2762
3600
|
// src/commands/memory-digest.ts
|
|
2763
|
-
import { existsSync as
|
|
2764
|
-
import { writeFile as
|
|
2765
|
-
import
|
|
3601
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3602
|
+
import { writeFile as writeFile15 } from "fs/promises";
|
|
3603
|
+
import path28 from "path";
|
|
2766
3604
|
import "commander";
|
|
2767
3605
|
import {
|
|
2768
3606
|
deriveConfidence as deriveConfidence4,
|
|
@@ -2785,7 +3623,7 @@ function registerMemoryDigest(program2) {
|
|
|
2785
3623
|
).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) => {
|
|
2786
3624
|
const root = findProjectRoot28(opts.dir);
|
|
2787
3625
|
const paths = resolveHaivePaths25(root);
|
|
2788
|
-
if (!
|
|
3626
|
+
if (!existsSync29(paths.memoriesDir)) {
|
|
2789
3627
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
2790
3628
|
process.exitCode = 1;
|
|
2791
3629
|
return;
|
|
@@ -2857,8 +3695,8 @@ function registerMemoryDigest(program2) {
|
|
|
2857
3695
|
);
|
|
2858
3696
|
const digest = lines.join("\n");
|
|
2859
3697
|
if (opts.out) {
|
|
2860
|
-
const outPath =
|
|
2861
|
-
await
|
|
3698
|
+
const outPath = path28.resolve(process.cwd(), opts.out);
|
|
3699
|
+
await writeFile15(outPath, digest, "utf8");
|
|
2862
3700
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
2863
3701
|
} else {
|
|
2864
3702
|
console.log(digest);
|
|
@@ -2867,17 +3705,17 @@ function registerMemoryDigest(program2) {
|
|
|
2867
3705
|
}
|
|
2868
3706
|
|
|
2869
3707
|
// src/commands/session-end.ts
|
|
2870
|
-
import { writeFile as
|
|
2871
|
-
import { existsSync as
|
|
2872
|
-
import
|
|
3708
|
+
import { writeFile as writeFile16, mkdir as mkdir10 } from "fs/promises";
|
|
3709
|
+
import { existsSync as existsSync30 } from "fs";
|
|
3710
|
+
import path29 from "path";
|
|
2873
3711
|
import "commander";
|
|
2874
3712
|
import {
|
|
2875
|
-
buildFrontmatter as
|
|
3713
|
+
buildFrontmatter as buildFrontmatter6,
|
|
2876
3714
|
findProjectRoot as findProjectRoot29,
|
|
2877
3715
|
loadMemoriesFromDir as loadMemoriesFromDir6,
|
|
2878
|
-
memoryFilePath as
|
|
3716
|
+
memoryFilePath as memoryFilePath5,
|
|
2879
3717
|
resolveHaivePaths as resolveHaivePaths26,
|
|
2880
|
-
serializeMemory as
|
|
3718
|
+
serializeMemory as serializeMemory12
|
|
2881
3719
|
} from "@hiveai/core";
|
|
2882
3720
|
function buildRecapBody(opts) {
|
|
2883
3721
|
const lines = [];
|
|
@@ -2928,7 +3766,7 @@ function registerSessionEnd(session2) {
|
|
|
2928
3766
|
).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 (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("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2929
3767
|
const root = findProjectRoot29(opts.dir);
|
|
2930
3768
|
const paths = resolveHaivePaths26(root);
|
|
2931
|
-
if (!
|
|
3769
|
+
if (!existsSync30(paths.haiveDir)) {
|
|
2932
3770
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
2933
3771
|
process.exitCode = 1;
|
|
2934
3772
|
return;
|
|
@@ -2937,12 +3775,12 @@ function registerSessionEnd(session2) {
|
|
|
2937
3775
|
const body = buildRecapBody(opts);
|
|
2938
3776
|
const topic = recapTopic(scope, opts.module);
|
|
2939
3777
|
const filesTouched = parseCsv5(opts.files);
|
|
2940
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
3778
|
+
const missingPaths = filesTouched.filter((p) => !existsSync30(path29.resolve(root, p)));
|
|
2941
3779
|
if (missingPaths.length > 0) {
|
|
2942
3780
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
2943
3781
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
2944
3782
|
}
|
|
2945
|
-
if (
|
|
3783
|
+
if (existsSync30(paths.memoriesDir)) {
|
|
2946
3784
|
const existing = await loadMemoriesFromDir6(paths.memoriesDir);
|
|
2947
3785
|
const topicMatch = existing.find(
|
|
2948
3786
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
@@ -2958,13 +3796,13 @@ function registerSessionEnd(session2) {
|
|
|
2958
3796
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
2959
3797
|
}
|
|
2960
3798
|
};
|
|
2961
|
-
await
|
|
3799
|
+
await writeFile16(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
|
|
2962
3800
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
2963
|
-
ui.info(`id=${fm.id} file=${
|
|
3801
|
+
ui.info(`id=${fm.id} file=${path29.relative(root, topicMatch.filePath)}`);
|
|
2964
3802
|
return;
|
|
2965
3803
|
}
|
|
2966
3804
|
}
|
|
2967
|
-
const frontmatter =
|
|
3805
|
+
const frontmatter = buildFrontmatter6({
|
|
2968
3806
|
type: "session_recap",
|
|
2969
3807
|
slug: "recap",
|
|
2970
3808
|
scope,
|
|
@@ -2974,11 +3812,11 @@ function registerSessionEnd(session2) {
|
|
|
2974
3812
|
topic,
|
|
2975
3813
|
status: "validated"
|
|
2976
3814
|
});
|
|
2977
|
-
const file =
|
|
2978
|
-
await
|
|
2979
|
-
await
|
|
3815
|
+
const file = memoryFilePath5(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
3816
|
+
await mkdir10(path29.dirname(file), { recursive: true });
|
|
3817
|
+
await writeFile16(file, serializeMemory12({ frontmatter, body }), "utf8");
|
|
2980
3818
|
ui.success(`Session recap created`);
|
|
2981
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
3819
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path29.relative(root, file)}`);
|
|
2982
3820
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
2983
3821
|
});
|
|
2984
3822
|
}
|
|
@@ -2988,9 +3826,9 @@ function parseCsv5(value) {
|
|
|
2988
3826
|
}
|
|
2989
3827
|
|
|
2990
3828
|
// src/commands/snapshot.ts
|
|
2991
|
-
import { existsSync as
|
|
2992
|
-
import { readdir } from "fs/promises";
|
|
2993
|
-
import
|
|
3829
|
+
import { existsSync as existsSync31 } from "fs";
|
|
3830
|
+
import { readdir as readdir2 } from "fs/promises";
|
|
3831
|
+
import path30 from "path";
|
|
2994
3832
|
import "commander";
|
|
2995
3833
|
import {
|
|
2996
3834
|
diffContract,
|
|
@@ -3023,18 +3861,18 @@ function registerSnapshot(program2) {
|
|
|
3023
3861
|
).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) => {
|
|
3024
3862
|
const root = findProjectRoot30(opts.dir);
|
|
3025
3863
|
const paths = resolveHaivePaths27(root);
|
|
3026
|
-
if (!
|
|
3864
|
+
if (!existsSync31(paths.haiveDir)) {
|
|
3027
3865
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
3028
3866
|
process.exitCode = 1;
|
|
3029
3867
|
return;
|
|
3030
3868
|
}
|
|
3031
3869
|
if (opts.list) {
|
|
3032
|
-
const contractsDir =
|
|
3033
|
-
if (!
|
|
3870
|
+
const contractsDir = path30.join(paths.haiveDir, "contracts");
|
|
3871
|
+
if (!existsSync31(contractsDir)) {
|
|
3034
3872
|
console.log(ui.dim("No contract snapshots found."));
|
|
3035
3873
|
return;
|
|
3036
3874
|
}
|
|
3037
|
-
const files = (await
|
|
3875
|
+
const files = (await readdir2(contractsDir)).filter(
|
|
3038
3876
|
(f) => f.endsWith(".lock") && !f.startsWith("deps-")
|
|
3039
3877
|
);
|
|
3040
3878
|
if (files.length === 0) {
|
|
@@ -3085,7 +3923,7 @@ function registerSnapshot(program2) {
|
|
|
3085
3923
|
return;
|
|
3086
3924
|
}
|
|
3087
3925
|
const contractPath = opts.contract;
|
|
3088
|
-
const name = opts.name ??
|
|
3926
|
+
const name = opts.name ?? path30.basename(contractPath, path30.extname(contractPath));
|
|
3089
3927
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
3090
3928
|
const contract = { name, path: contractPath, format };
|
|
3091
3929
|
try {
|
|
@@ -3140,8 +3978,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
3140
3978
|
}
|
|
3141
3979
|
}
|
|
3142
3980
|
function detectFormat(filePath) {
|
|
3143
|
-
const ext =
|
|
3144
|
-
const base =
|
|
3981
|
+
const ext = path30.extname(filePath).toLowerCase();
|
|
3982
|
+
const base = path30.basename(filePath).toLowerCase();
|
|
3145
3983
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
3146
3984
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
3147
3985
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -3154,9 +3992,9 @@ function detectFormat(filePath) {
|
|
|
3154
3992
|
}
|
|
3155
3993
|
|
|
3156
3994
|
// src/commands/hub.ts
|
|
3157
|
-
import { existsSync as
|
|
3158
|
-
import { mkdir as
|
|
3159
|
-
import
|
|
3995
|
+
import { existsSync as existsSync32 } from "fs";
|
|
3996
|
+
import { mkdir as mkdir11, readFile as readFile12, writeFile as writeFile17, copyFile } from "fs/promises";
|
|
3997
|
+
import path31 from "path";
|
|
3160
3998
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3161
3999
|
import "commander";
|
|
3162
4000
|
import {
|
|
@@ -3165,7 +4003,7 @@ import {
|
|
|
3165
4003
|
loadMemoriesFromDir as loadMemoriesFromDir7,
|
|
3166
4004
|
resolveHaivePaths as resolveHaivePaths28,
|
|
3167
4005
|
saveConfig as saveConfig2,
|
|
3168
|
-
serializeMemory as
|
|
4006
|
+
serializeMemory as serializeMemory13
|
|
3169
4007
|
} from "@hiveai/core";
|
|
3170
4008
|
function registerHub(program2) {
|
|
3171
4009
|
const hub = program2.command("hub").description(
|
|
@@ -3175,8 +4013,8 @@ function registerHub(program2) {
|
|
|
3175
4013
|
hub.command("init <hubPath>").description(
|
|
3176
4014
|
"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"
|
|
3177
4015
|
).action(async (hubPath) => {
|
|
3178
|
-
const absPath =
|
|
3179
|
-
await
|
|
4016
|
+
const absPath = path31.resolve(hubPath);
|
|
4017
|
+
await mkdir11(absPath, { recursive: true });
|
|
3180
4018
|
const gitCheck = spawnSync3("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
3181
4019
|
if (gitCheck.status !== 0) {
|
|
3182
4020
|
const init = spawnSync3("git", ["init"], { cwd: absPath, encoding: "utf8" });
|
|
@@ -3186,10 +4024,10 @@ function registerHub(program2) {
|
|
|
3186
4024
|
return;
|
|
3187
4025
|
}
|
|
3188
4026
|
}
|
|
3189
|
-
const sharedDir =
|
|
3190
|
-
await
|
|
3191
|
-
await
|
|
3192
|
-
|
|
4027
|
+
const sharedDir = path31.join(absPath, ".ai", "memories", "shared");
|
|
4028
|
+
await mkdir11(sharedDir, { recursive: true });
|
|
4029
|
+
await writeFile17(
|
|
4030
|
+
path31.join(absPath, ".ai", "README.md"),
|
|
3193
4031
|
`# hAIve Team Knowledge Hub
|
|
3194
4032
|
|
|
3195
4033
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -3210,8 +4048,8 @@ haive hub pull # import into a project
|
|
|
3210
4048
|
`,
|
|
3211
4049
|
"utf8"
|
|
3212
4050
|
);
|
|
3213
|
-
await
|
|
3214
|
-
|
|
4051
|
+
await writeFile17(
|
|
4052
|
+
path31.join(absPath, ".gitignore"),
|
|
3215
4053
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
3216
4054
|
"utf8"
|
|
3217
4055
|
);
|
|
@@ -3226,7 +4064,7 @@ haive hub pull # import into a project
|
|
|
3226
4064
|
`
|
|
3227
4065
|
Next steps:
|
|
3228
4066
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
3229
|
-
{ "hubPath": "${
|
|
4067
|
+
{ "hubPath": "${path31.relative(process.cwd(), absPath)}" }
|
|
3230
4068
|
2. Run \`haive hub push\` to publish your shared memories
|
|
3231
4069
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
3232
4070
|
`
|
|
@@ -3255,15 +4093,15 @@ Next steps:
|
|
|
3255
4093
|
process.exitCode = 1;
|
|
3256
4094
|
return;
|
|
3257
4095
|
}
|
|
3258
|
-
const hubRoot =
|
|
3259
|
-
if (!
|
|
4096
|
+
const hubRoot = path31.resolve(root, config.hubPath);
|
|
4097
|
+
if (!existsSync32(hubRoot)) {
|
|
3260
4098
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
3261
4099
|
process.exitCode = 1;
|
|
3262
4100
|
return;
|
|
3263
4101
|
}
|
|
3264
|
-
const projectName =
|
|
3265
|
-
const destDir =
|
|
3266
|
-
await
|
|
4102
|
+
const projectName = path31.basename(root);
|
|
4103
|
+
const destDir = path31.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
4104
|
+
await mkdir11(destDir, { recursive: true });
|
|
3267
4105
|
const all = await loadMemoriesFromDir7(paths.memoriesDir);
|
|
3268
4106
|
const shared = all.filter(
|
|
3269
4107
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
@@ -3281,15 +4119,15 @@ Next steps:
|
|
|
3281
4119
|
for (const { memory: memory2 } of shared) {
|
|
3282
4120
|
const fm = memory2.frontmatter;
|
|
3283
4121
|
const fileName = `${fm.id}.md`;
|
|
3284
|
-
const destPath =
|
|
3285
|
-
await
|
|
4122
|
+
const destPath = path31.join(destDir, fileName);
|
|
4123
|
+
await writeFile17(destPath, serializeMemory13(memory2), "utf8");
|
|
3286
4124
|
pushed++;
|
|
3287
4125
|
}
|
|
3288
4126
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
3289
4127
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
3290
4128
|
if (opts.commit) {
|
|
3291
4129
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
3292
|
-
spawnSync3("git", ["add",
|
|
4130
|
+
spawnSync3("git", ["add", path31.join(".ai", "memories", "shared", projectName)], {
|
|
3293
4131
|
cwd: hubRoot
|
|
3294
4132
|
});
|
|
3295
4133
|
const commit = spawnSync3("git", ["commit", "-m", message], {
|
|
@@ -3324,15 +4162,15 @@ Next steps:
|
|
|
3324
4162
|
process.exitCode = 1;
|
|
3325
4163
|
return;
|
|
3326
4164
|
}
|
|
3327
|
-
const hubRoot =
|
|
3328
|
-
const hubSharedDir =
|
|
3329
|
-
if (!
|
|
4165
|
+
const hubRoot = path31.resolve(root, config.hubPath);
|
|
4166
|
+
const hubSharedDir = path31.join(hubRoot, ".ai", "memories", "shared");
|
|
4167
|
+
if (!existsSync32(hubSharedDir)) {
|
|
3330
4168
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
3331
4169
|
return;
|
|
3332
4170
|
}
|
|
3333
|
-
const projectName =
|
|
3334
|
-
const { readdir:
|
|
3335
|
-
const projectDirs = (await
|
|
4171
|
+
const projectName = path31.basename(root);
|
|
4172
|
+
const { readdir: readdir3 } = await import("fs/promises");
|
|
4173
|
+
const projectDirs = (await readdir3(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
3336
4174
|
if (projectDirs.length === 0) {
|
|
3337
4175
|
console.log(ui.dim("No other projects have pushed to the hub yet."));
|
|
3338
4176
|
return;
|
|
@@ -3340,17 +4178,17 @@ Next steps:
|
|
|
3340
4178
|
let totalImported = 0;
|
|
3341
4179
|
let totalUpdated = 0;
|
|
3342
4180
|
for (const sourceName of projectDirs) {
|
|
3343
|
-
const sourceDir =
|
|
3344
|
-
const destDir =
|
|
3345
|
-
await
|
|
3346
|
-
const sourceFiles = (await
|
|
4181
|
+
const sourceDir = path31.join(hubSharedDir, sourceName);
|
|
4182
|
+
const destDir = path31.join(paths.memoriesDir, "shared", sourceName);
|
|
4183
|
+
await mkdir11(destDir, { recursive: true });
|
|
4184
|
+
const sourceFiles = (await readdir3(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
3347
4185
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
3348
4186
|
const existingInDest = await loadDir(destDir);
|
|
3349
4187
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
3350
4188
|
for (const file of sourceFiles) {
|
|
3351
|
-
const srcPath =
|
|
3352
|
-
const destPath =
|
|
3353
|
-
const fileContent = await
|
|
4189
|
+
const srcPath = path31.join(sourceDir, file);
|
|
4190
|
+
const destPath = path31.join(destDir, file);
|
|
4191
|
+
const fileContent = await readFile12(srcPath, "utf8");
|
|
3354
4192
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
3355
4193
|
if (!alreadyTagged) {
|
|
3356
4194
|
await copyFile(srcPath, destPath);
|
|
@@ -3380,14 +4218,14 @@ Next steps:
|
|
|
3380
4218
|
console.log(
|
|
3381
4219
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
3382
4220
|
);
|
|
3383
|
-
const sharedDir =
|
|
3384
|
-
if (
|
|
3385
|
-
const { readdir:
|
|
3386
|
-
const sources = (await
|
|
4221
|
+
const sharedDir = path31.join(paths.memoriesDir, "shared");
|
|
4222
|
+
if (existsSync32(sharedDir)) {
|
|
4223
|
+
const { readdir: readdir3 } = await import("fs/promises");
|
|
4224
|
+
const sources = (await readdir3(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
3387
4225
|
console.log(`
|
|
3388
4226
|
Imported from ${sources.length} source(s):`);
|
|
3389
4227
|
for (const src of sources) {
|
|
3390
|
-
const files = (await
|
|
4228
|
+
const files = (await readdir3(path31.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
3391
4229
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
3392
4230
|
}
|
|
3393
4231
|
} else {
|
|
@@ -3402,8 +4240,8 @@ Next steps:
|
|
|
3402
4240
|
if (outgoing.length > 0) {
|
|
3403
4241
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
3404
4242
|
}
|
|
3405
|
-
void
|
|
3406
|
-
void
|
|
4243
|
+
void readFile12;
|
|
4244
|
+
void writeFile17;
|
|
3407
4245
|
void saveConfig2;
|
|
3408
4246
|
});
|
|
3409
4247
|
}
|
|
@@ -3605,20 +4443,20 @@ function summarize(name, t0, payload, notes) {
|
|
|
3605
4443
|
}
|
|
3606
4444
|
|
|
3607
4445
|
// src/commands/memory-suggest.ts
|
|
3608
|
-
import { mkdir as
|
|
3609
|
-
import { existsSync as
|
|
3610
|
-
import
|
|
4446
|
+
import { mkdir as mkdir12, writeFile as writeFile18 } from "fs/promises";
|
|
4447
|
+
import { existsSync as existsSync33 } from "fs";
|
|
4448
|
+
import path32 from "path";
|
|
3611
4449
|
import "commander";
|
|
3612
4450
|
import {
|
|
3613
4451
|
aggregateUsage as aggregateUsage2,
|
|
3614
|
-
buildFrontmatter as
|
|
4452
|
+
buildFrontmatter as buildFrontmatter7,
|
|
3615
4453
|
findProjectRoot as findProjectRoot34,
|
|
3616
4454
|
loadMemoriesFromDir as loadMemoriesFromDir8,
|
|
3617
|
-
memoryFilePath as
|
|
4455
|
+
memoryFilePath as memoryFilePath6,
|
|
3618
4456
|
parseSince as parseSince2,
|
|
3619
4457
|
readUsageEvents as readUsageEvents2,
|
|
3620
4458
|
resolveHaivePaths as resolveHaivePaths31,
|
|
3621
|
-
serializeMemory as
|
|
4459
|
+
serializeMemory as serializeMemory14
|
|
3622
4460
|
} from "@hiveai/core";
|
|
3623
4461
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
3624
4462
|
"mem_search",
|
|
@@ -3677,7 +4515,7 @@ function registerMemorySuggest(memory2) {
|
|
|
3677
4515
|
}
|
|
3678
4516
|
const created = [];
|
|
3679
4517
|
const skipped = [];
|
|
3680
|
-
const existing =
|
|
4518
|
+
const existing = existsSync33(paths.memoriesDir) ? await loadMemoriesFromDir8(paths.memoriesDir) : [];
|
|
3681
4519
|
for (const s of top) {
|
|
3682
4520
|
const slug = slugify(s.query);
|
|
3683
4521
|
if (!slug) {
|
|
@@ -3689,7 +4527,7 @@ function registerMemorySuggest(memory2) {
|
|
|
3689
4527
|
skipped.push({ query: s.query, reason: `similar memory already exists (${dup.memory.frontmatter.id})` });
|
|
3690
4528
|
continue;
|
|
3691
4529
|
}
|
|
3692
|
-
const fm =
|
|
4530
|
+
const fm = buildFrontmatter7({
|
|
3693
4531
|
type: s.inferred_type,
|
|
3694
4532
|
slug,
|
|
3695
4533
|
scope,
|
|
@@ -3699,14 +4537,14 @@ function registerMemorySuggest(memory2) {
|
|
|
3699
4537
|
});
|
|
3700
4538
|
fm.status = "draft";
|
|
3701
4539
|
const body = renderTemplate(s);
|
|
3702
|
-
const file =
|
|
3703
|
-
await
|
|
3704
|
-
if (
|
|
3705
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
4540
|
+
const file = memoryFilePath6(paths, fm.scope, fm.id, fm.module);
|
|
4541
|
+
await mkdir12(path32.dirname(file), { recursive: true });
|
|
4542
|
+
if (existsSync33(file)) {
|
|
4543
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path32.relative(root, file)}` });
|
|
3706
4544
|
continue;
|
|
3707
4545
|
}
|
|
3708
|
-
await
|
|
3709
|
-
created.push({ id: fm.id, file:
|
|
4546
|
+
await writeFile18(file, serializeMemory14({ frontmatter: fm, body }), "utf8");
|
|
4547
|
+
created.push({ id: fm.id, file: path32.relative(root, file), query: s.query });
|
|
3710
4548
|
}
|
|
3711
4549
|
if (opts.json) {
|
|
3712
4550
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -3799,9 +4637,9 @@ function truncate(text, max) {
|
|
|
3799
4637
|
}
|
|
3800
4638
|
|
|
3801
4639
|
// src/commands/memory-archive.ts
|
|
3802
|
-
import { existsSync as
|
|
3803
|
-
import { writeFile as
|
|
3804
|
-
import
|
|
4640
|
+
import { existsSync as existsSync34 } from "fs";
|
|
4641
|
+
import { writeFile as writeFile19 } from "fs/promises";
|
|
4642
|
+
import path33 from "path";
|
|
3805
4643
|
import "commander";
|
|
3806
4644
|
import {
|
|
3807
4645
|
findProjectRoot as findProjectRoot35,
|
|
@@ -3809,7 +4647,7 @@ import {
|
|
|
3809
4647
|
loadMemoriesFromDir as loadMemoriesFromDir9,
|
|
3810
4648
|
loadUsageIndex as loadUsageIndex11,
|
|
3811
4649
|
resolveHaivePaths as resolveHaivePaths32,
|
|
3812
|
-
serializeMemory as
|
|
4650
|
+
serializeMemory as serializeMemory15
|
|
3813
4651
|
} from "@hiveai/core";
|
|
3814
4652
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
3815
4653
|
function registerMemoryArchive(memory2) {
|
|
@@ -3818,7 +4656,7 @@ function registerMemoryArchive(memory2) {
|
|
|
3818
4656
|
).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) => {
|
|
3819
4657
|
const root = findProjectRoot35(opts.dir);
|
|
3820
4658
|
const paths = resolveHaivePaths32(root);
|
|
3821
|
-
if (!
|
|
4659
|
+
if (!existsSync34(paths.memoriesDir)) {
|
|
3822
4660
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
3823
4661
|
process.exitCode = 1;
|
|
3824
4662
|
return;
|
|
@@ -3839,7 +4677,7 @@ function registerMemoryArchive(memory2) {
|
|
|
3839
4677
|
if (typeFilter && fm.type !== typeFilter) continue;
|
|
3840
4678
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
3841
4679
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
3842
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
4680
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync34(path33.join(paths.root, p)));
|
|
3843
4681
|
const isAnchorless = !hasAnyAnchor;
|
|
3844
4682
|
if (!isAnchorless && !allPathsGone) continue;
|
|
3845
4683
|
const u = getUsage9(usage, fm.id);
|
|
@@ -3887,7 +4725,7 @@ function registerMemoryArchive(memory2) {
|
|
|
3887
4725
|
if (!found) continue;
|
|
3888
4726
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
3889
4727
|
try {
|
|
3890
|
-
await
|
|
4728
|
+
await writeFile19(c.filePath, serializeMemory15({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
3891
4729
|
archived++;
|
|
3892
4730
|
} catch (err) {
|
|
3893
4731
|
if (!opts.json) {
|
|
@@ -3913,7 +4751,7 @@ function parseDays(input) {
|
|
|
3913
4751
|
}
|
|
3914
4752
|
|
|
3915
4753
|
// src/commands/doctor.ts
|
|
3916
|
-
import { existsSync as
|
|
4754
|
+
import { existsSync as existsSync35 } from "fs";
|
|
3917
4755
|
import { stat } from "fs/promises";
|
|
3918
4756
|
import "path";
|
|
3919
4757
|
import "commander";
|
|
@@ -3936,7 +4774,7 @@ function registerDoctor(program2) {
|
|
|
3936
4774
|
const root = findProjectRoot36(opts.dir);
|
|
3937
4775
|
const paths = resolveHaivePaths33(root);
|
|
3938
4776
|
const findings = [];
|
|
3939
|
-
if (!
|
|
4777
|
+
if (!existsSync35(paths.haiveDir)) {
|
|
3940
4778
|
findings.push({
|
|
3941
4779
|
severity: "error",
|
|
3942
4780
|
code: "not-initialized",
|
|
@@ -3945,7 +4783,7 @@ function registerDoctor(program2) {
|
|
|
3945
4783
|
});
|
|
3946
4784
|
return emit(findings, opts);
|
|
3947
4785
|
}
|
|
3948
|
-
if (!
|
|
4786
|
+
if (!existsSync35(paths.projectContext)) {
|
|
3949
4787
|
findings.push({
|
|
3950
4788
|
severity: "warn",
|
|
3951
4789
|
code: "no-project-context",
|
|
@@ -3953,8 +4791,8 @@ function registerDoctor(program2) {
|
|
|
3953
4791
|
fix: "haive init"
|
|
3954
4792
|
});
|
|
3955
4793
|
} else {
|
|
3956
|
-
const { readFile:
|
|
3957
|
-
const content = await
|
|
4794
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
4795
|
+
const content = await readFile13(paths.projectContext, "utf8");
|
|
3958
4796
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
3959
4797
|
if (isTemplate) {
|
|
3960
4798
|
findings.push({
|
|
@@ -3965,7 +4803,7 @@ function registerDoctor(program2) {
|
|
|
3965
4803
|
});
|
|
3966
4804
|
}
|
|
3967
4805
|
}
|
|
3968
|
-
const memories =
|
|
4806
|
+
const memories = existsSync35(paths.memoriesDir) ? await loadMemoriesFromDir10(paths.memoriesDir) : [];
|
|
3969
4807
|
const now = Date.now();
|
|
3970
4808
|
if (memories.length === 0) {
|
|
3971
4809
|
findings.push({
|
|
@@ -4119,7 +4957,7 @@ function isSearchTool(name) {
|
|
|
4119
4957
|
}
|
|
4120
4958
|
|
|
4121
4959
|
// src/commands/playback.ts
|
|
4122
|
-
import { existsSync as
|
|
4960
|
+
import { existsSync as existsSync36 } from "fs";
|
|
4123
4961
|
import "commander";
|
|
4124
4962
|
import {
|
|
4125
4963
|
findProjectRoot as findProjectRoot37,
|
|
@@ -4149,7 +4987,7 @@ function registerPlayback(program2) {
|
|
|
4149
4987
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
4150
4988
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
4151
4989
|
const sessions = bucketSessions(filtered, gapMs);
|
|
4152
|
-
const all =
|
|
4990
|
+
const all = existsSync36(paths.memoriesDir) ? await loadMemoriesFromDir11(paths.memoriesDir) : [];
|
|
4153
4991
|
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);
|
|
4154
4992
|
const enriched = sessions.map((s, i) => {
|
|
4155
4993
|
const startMs = Date.parse(s.start);
|
|
@@ -4345,7 +5183,7 @@ function runCommand(cmd, args, cwd) {
|
|
|
4345
5183
|
|
|
4346
5184
|
// src/index.ts
|
|
4347
5185
|
var program = new Command39();
|
|
4348
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.
|
|
5186
|
+
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.7.0");
|
|
4349
5187
|
registerInit(program);
|
|
4350
5188
|
registerMcp(program);
|
|
4351
5189
|
registerBriefing(program);
|