@hiveai/cli 0.9.11 → 0.9.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/index.js +849 -662
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command51 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync } from "fs";
|
|
@@ -198,7 +198,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
198
198
|
if (!f) continue;
|
|
199
199
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
200
200
|
}
|
|
201
|
-
let entries = [...counts.entries()].map(([
|
|
201
|
+
let entries = [...counts.entries()].map(([path48, changes]) => ({ path: path48, changes }));
|
|
202
202
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
203
203
|
if (lowerPaths.length > 0) {
|
|
204
204
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -730,23 +730,404 @@ function registerIndexCode(program2) {
|
|
|
730
730
|
}
|
|
731
731
|
|
|
732
732
|
// src/commands/init.ts
|
|
733
|
-
import { mkdir as
|
|
734
|
-
import { existsSync as
|
|
735
|
-
import
|
|
736
|
-
import { spawnSync } from "child_process";
|
|
733
|
+
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
734
|
+
import { existsSync as existsSync7 } from "fs";
|
|
735
|
+
import path8 from "path";
|
|
736
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
737
737
|
import "commander";
|
|
738
738
|
import {
|
|
739
739
|
AUTOPILOT_DEFAULTS,
|
|
740
740
|
buildCodeMap as buildCodeMap2,
|
|
741
|
-
resolveHaivePaths as
|
|
741
|
+
resolveHaivePaths as resolveHaivePaths5,
|
|
742
742
|
saveCodeMap as saveCodeMap2,
|
|
743
743
|
saveConfig
|
|
744
744
|
} from "@hiveai/core";
|
|
745
745
|
|
|
746
|
-
// src/commands/
|
|
747
|
-
import {
|
|
746
|
+
// src/commands/agent.ts
|
|
747
|
+
import { spawnSync } from "child_process";
|
|
748
|
+
import { existsSync as existsSync4 } from "fs";
|
|
749
|
+
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
750
|
+
import os2 from "os";
|
|
751
|
+
import path5 from "path";
|
|
752
|
+
import { createInterface } from "readline/promises";
|
|
753
|
+
import "commander";
|
|
754
|
+
import { findProjectRoot as findProjectRoot5, resolveHaivePaths as resolveHaivePaths4 } from "@hiveai/core";
|
|
755
|
+
|
|
756
|
+
// src/commands/init-mcp-setup.ts
|
|
757
|
+
import { readFile as readFile2, writeFile, mkdir as mkdir2 } from "fs/promises";
|
|
748
758
|
import { existsSync as existsSync3 } from "fs";
|
|
749
759
|
import path4 from "path";
|
|
760
|
+
import os from "os";
|
|
761
|
+
var HOME = os.homedir();
|
|
762
|
+
var HAIVE_MCP_ENTRY = {
|
|
763
|
+
command: "haive",
|
|
764
|
+
args: ["mcp", "--stdio"]
|
|
765
|
+
};
|
|
766
|
+
function projectMcpEntry(root) {
|
|
767
|
+
return {
|
|
768
|
+
command: "haive",
|
|
769
|
+
args: ["mcp", "--stdio"],
|
|
770
|
+
env: { HAIVE_PROJECT_ROOT: root }
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function cursorMcpPath() {
|
|
774
|
+
return path4.join(HOME, ".cursor", "mcp.json");
|
|
775
|
+
}
|
|
776
|
+
async function configureCursor() {
|
|
777
|
+
const mcpPath = cursorMcpPath();
|
|
778
|
+
const cursorDir = path4.join(HOME, ".cursor");
|
|
779
|
+
if (!existsSync3(cursorDir)) return { client: "Cursor", status: "not_installed" };
|
|
780
|
+
let config = {};
|
|
781
|
+
if (existsSync3(mcpPath)) {
|
|
782
|
+
try {
|
|
783
|
+
config = JSON.parse(await readFile2(mcpPath, "utf8"));
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
config.mcpServers ??= {};
|
|
788
|
+
if (config.mcpServers["haive"]) return { client: "Cursor", status: "already_configured" };
|
|
789
|
+
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
790
|
+
await mkdir2(cursorDir, { recursive: true });
|
|
791
|
+
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
792
|
+
return { client: "Cursor", status: "configured", path: mcpPath };
|
|
793
|
+
}
|
|
794
|
+
function vscodeMcpPath() {
|
|
795
|
+
const candidates = [
|
|
796
|
+
path4.join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
797
|
+
// Linux
|
|
798
|
+
path4.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
|
|
799
|
+
// macOS
|
|
800
|
+
path4.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
|
|
801
|
+
// Windows
|
|
802
|
+
path4.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
|
|
803
|
+
];
|
|
804
|
+
for (const c of candidates) {
|
|
805
|
+
if (existsSync3(path4.dirname(c))) return c;
|
|
806
|
+
}
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
async function configureVSCode() {
|
|
810
|
+
const mcpPath = vscodeMcpPath();
|
|
811
|
+
if (!mcpPath) return { client: "VS Code", status: "not_installed" };
|
|
812
|
+
let config = {};
|
|
813
|
+
if (existsSync3(mcpPath)) {
|
|
814
|
+
try {
|
|
815
|
+
config = JSON.parse(await readFile2(mcpPath, "utf8"));
|
|
816
|
+
} catch {
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
config.servers ??= {};
|
|
820
|
+
if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
|
|
821
|
+
config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
822
|
+
await mkdir2(path4.dirname(mcpPath), { recursive: true });
|
|
823
|
+
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
824
|
+
return { client: "VS Code", status: "configured", path: mcpPath };
|
|
825
|
+
}
|
|
826
|
+
function claudeConfigPath() {
|
|
827
|
+
const p = path4.join(HOME, ".claude.json");
|
|
828
|
+
if (existsSync3(p)) return p;
|
|
829
|
+
const p2 = path4.join(HOME, ".config", "claude", "claude.json");
|
|
830
|
+
if (existsSync3(path4.dirname(p2))) return p2;
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
async function configureClaude() {
|
|
834
|
+
const cfgPath = claudeConfigPath() ?? path4.join(HOME, ".claude.json");
|
|
835
|
+
if (!existsSync3(cfgPath) && !existsSync3(path4.join(HOME, ".claude"))) {
|
|
836
|
+
return { client: "Claude Code", status: "not_installed" };
|
|
837
|
+
}
|
|
838
|
+
let config = {};
|
|
839
|
+
if (existsSync3(cfgPath)) {
|
|
840
|
+
try {
|
|
841
|
+
config = JSON.parse(await readFile2(cfgPath, "utf8"));
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
config.mcpServers ??= {};
|
|
846
|
+
if (config.mcpServers["haive"]) return { client: "Claude Code", status: "already_configured" };
|
|
847
|
+
config.mcpServers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
848
|
+
await writeFile(cfgPath, JSON.stringify(config, null, 2), "utf8");
|
|
849
|
+
return { client: "Claude Code", status: "configured", path: cfgPath };
|
|
850
|
+
}
|
|
851
|
+
function windsurfMcpPath() {
|
|
852
|
+
const candidates = [
|
|
853
|
+
path4.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
854
|
+
path4.join(HOME, ".windsurf", "mcp.json")
|
|
855
|
+
];
|
|
856
|
+
for (const c of candidates) {
|
|
857
|
+
if (existsSync3(path4.dirname(c))) return c;
|
|
858
|
+
}
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
async function configureWindsurf() {
|
|
862
|
+
const mcpPath = windsurfMcpPath();
|
|
863
|
+
if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
|
|
864
|
+
let config = {};
|
|
865
|
+
if (existsSync3(mcpPath)) {
|
|
866
|
+
try {
|
|
867
|
+
config = JSON.parse(await readFile2(mcpPath, "utf8"));
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
config.mcpServers ??= {};
|
|
872
|
+
if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
|
|
873
|
+
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
874
|
+
await mkdir2(path4.dirname(mcpPath), { recursive: true });
|
|
875
|
+
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
876
|
+
return { client: "Windsurf", status: "configured", path: mcpPath };
|
|
877
|
+
}
|
|
878
|
+
async function autoConfigureMcpClients() {
|
|
879
|
+
const results = [];
|
|
880
|
+
const configurators = [configureCursor, configureVSCode, configureClaude, configureWindsurf];
|
|
881
|
+
for (const fn of configurators) {
|
|
882
|
+
try {
|
|
883
|
+
results.push(await fn());
|
|
884
|
+
} catch (err) {
|
|
885
|
+
const name = fn.name.replace("configure", "");
|
|
886
|
+
results.push({ client: name, status: "error", error: String(err) });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return results;
|
|
890
|
+
}
|
|
891
|
+
async function configureProjectMcpClients(root) {
|
|
892
|
+
const entry = projectMcpEntry(root);
|
|
893
|
+
const results = [];
|
|
894
|
+
try {
|
|
895
|
+
const cursorPath = path4.join(root, ".cursor", "mcp.json");
|
|
896
|
+
let config = {};
|
|
897
|
+
if (existsSync3(cursorPath)) {
|
|
898
|
+
try {
|
|
899
|
+
config = JSON.parse(await readFile2(cursorPath, "utf8"));
|
|
900
|
+
} catch {
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
config.mcpServers ??= {};
|
|
904
|
+
config.mcpServers["haive"] = entry;
|
|
905
|
+
await mkdir2(path4.dirname(cursorPath), { recursive: true });
|
|
906
|
+
await writeFile(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
907
|
+
results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
|
|
908
|
+
} catch (err) {
|
|
909
|
+
results.push({ client: "Cursor (project)", status: "error", error: String(err) });
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const vscodePath = path4.join(root, ".vscode", "mcp.json");
|
|
913
|
+
let config = {};
|
|
914
|
+
if (existsSync3(vscodePath)) {
|
|
915
|
+
try {
|
|
916
|
+
config = JSON.parse(await readFile2(vscodePath, "utf8"));
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
config.servers ??= {};
|
|
921
|
+
config.servers["haive"] = { ...entry, type: "stdio" };
|
|
922
|
+
await mkdir2(path4.dirname(vscodePath), { recursive: true });
|
|
923
|
+
await writeFile(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
924
|
+
results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
|
|
925
|
+
} catch (err) {
|
|
926
|
+
results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
|
|
927
|
+
}
|
|
928
|
+
try {
|
|
929
|
+
const mcpPath = path4.join(root, ".mcp.json");
|
|
930
|
+
let config = {};
|
|
931
|
+
if (existsSync3(mcpPath)) {
|
|
932
|
+
try {
|
|
933
|
+
config = JSON.parse(await readFile2(mcpPath, "utf8"));
|
|
934
|
+
} catch {
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
config.mcpServers ??= {};
|
|
938
|
+
config.mcpServers["haive"] = { ...entry, type: "stdio" };
|
|
939
|
+
await writeFile(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
940
|
+
results.push({ client: "Claude Code (project)", status: "configured", path: mcpPath });
|
|
941
|
+
} catch (err) {
|
|
942
|
+
results.push({ client: "Claude Code (project)", status: "error", error: String(err) });
|
|
943
|
+
}
|
|
944
|
+
return results;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/commands/agent.ts
|
|
948
|
+
function registerAgent(program2) {
|
|
949
|
+
const agent = program2.command("agent").description("Detect, configure, and report the best hAIve mode for AI coding agents.");
|
|
950
|
+
agent.command("detect").description("Detect available AI agents and hAIve MCP/wrapper readiness.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
|
|
951
|
+
const detection = await detectAgentMode(opts.dir);
|
|
952
|
+
printDetection(detection, Boolean(opts.json));
|
|
953
|
+
});
|
|
954
|
+
agent.command("status").description("Alias for agent detect.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
|
|
955
|
+
const detection = await detectAgentMode(opts.dir);
|
|
956
|
+
printDetection(detection, Boolean(opts.json));
|
|
957
|
+
});
|
|
958
|
+
agent.command("setup").description("Configure hAIve project MCP, optional global MCP clients, and wrapper fallback metadata.").option("-d, --dir <dir>", "project root").option("-y, --yes", "approve user-level/global MCP configuration without prompting", false).option("--no-global", "skip user-level/global MCP configuration").option("--json", "emit JSON", false).action(async (opts) => {
|
|
959
|
+
const result = await setupAgentMode(opts.dir, {
|
|
960
|
+
yes: Boolean(opts.yes),
|
|
961
|
+
global: opts.global !== false && opts.noGlobal !== true,
|
|
962
|
+
interactive: process.stdin.isTTY
|
|
963
|
+
});
|
|
964
|
+
if (opts.json) {
|
|
965
|
+
console.log(JSON.stringify(result, null, 2));
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
printSetupResult(result);
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
async function setupAgentMode(dir, opts = {}) {
|
|
972
|
+
const root = findProjectRoot5(dir);
|
|
973
|
+
const paths = resolveHaivePaths4(root);
|
|
974
|
+
const projectResults = await configureProjectMcpClients(root);
|
|
975
|
+
const detectionBeforeGlobal = await detectAgentMode(root);
|
|
976
|
+
let globalResults = [];
|
|
977
|
+
let globalSkippedReason;
|
|
978
|
+
const shouldConsiderGlobal = opts.global !== false;
|
|
979
|
+
if (shouldConsiderGlobal) {
|
|
980
|
+
const approved = opts.yes === true || (opts.interactive ? await confirmGlobalSetup() : false);
|
|
981
|
+
if (approved) {
|
|
982
|
+
globalResults = await autoConfigureMcpClients();
|
|
983
|
+
const codex = await configureCodexIfAvailable(root);
|
|
984
|
+
if (codex) globalResults.push(codex);
|
|
985
|
+
} else {
|
|
986
|
+
globalSkippedReason = opts.interactive ? "User declined user-level/global MCP configuration." : "Non-interactive shell; skipped user-level/global MCP configuration. Re-run `haive agent setup --yes` to apply it.";
|
|
987
|
+
}
|
|
988
|
+
} else {
|
|
989
|
+
globalSkippedReason = "User-level/global MCP configuration disabled.";
|
|
990
|
+
}
|
|
991
|
+
const detection = await detectAgentMode(root);
|
|
992
|
+
const modeFile = await writeAgentModeRecord(paths, detection, globalSkippedReason);
|
|
993
|
+
return {
|
|
994
|
+
detection,
|
|
995
|
+
project_results: projectResults,
|
|
996
|
+
global_results: globalResults,
|
|
997
|
+
mode_file: modeFile,
|
|
998
|
+
...globalSkippedReason ? { global_skipped_reason: globalSkippedReason } : {}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
async function detectAgentMode(dir) {
|
|
1002
|
+
const root = findProjectRoot5(dir);
|
|
1003
|
+
const paths = resolveHaivePaths4(root);
|
|
1004
|
+
const projectMcp = [
|
|
1005
|
+
{ client: "Claude Code", path: path5.join(root, ".mcp.json"), present: existsSync4(path5.join(root, ".mcp.json")) },
|
|
1006
|
+
{ client: "Cursor", path: path5.join(root, ".cursor", "mcp.json"), present: existsSync4(path5.join(root, ".cursor", "mcp.json")) },
|
|
1007
|
+
{ client: "VS Code", path: path5.join(root, ".vscode", "mcp.json"), present: existsSync4(path5.join(root, ".vscode", "mcp.json")) }
|
|
1008
|
+
];
|
|
1009
|
+
const installedAgents = [
|
|
1010
|
+
{ agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
|
|
1011
|
+
{ agent: "Claude", command: "claude", installed: commandExists("claude") },
|
|
1012
|
+
{ agent: "Aider", command: "aider", installed: commandExists("aider") },
|
|
1013
|
+
{ agent: "Cursor", command: "cursor", installed: commandExists("cursor") }
|
|
1014
|
+
];
|
|
1015
|
+
const hasProjectMcp = projectMcp.some((item) => item.present);
|
|
1016
|
+
const hasNativeMcp = hasProjectMcp || installedAgents.some((a) => a.mcp_configured);
|
|
1017
|
+
const wrapperAgent = installedAgents.find((a) => a.installed && ["codex", "claude", "aider"].includes(a.command));
|
|
1018
|
+
const recommendedMode = hasNativeMcp ? "mcp" : wrapperAgent ? "wrapped" : "fallback";
|
|
1019
|
+
const recommendedCommand = recommendedMode === "mcp" ? "Restart your AI client, then call get_briefing before editing." : recommendedMode === "wrapped" && wrapperAgent ? `haive run -- ${wrapperAgent.command}` : 'haive briefing --task "..." --files "..."';
|
|
1020
|
+
return {
|
|
1021
|
+
root,
|
|
1022
|
+
initialized: existsSync4(paths.haiveDir),
|
|
1023
|
+
project_mcp: projectMcp,
|
|
1024
|
+
installed_agents: installedAgents,
|
|
1025
|
+
recommended_mode: recommendedMode,
|
|
1026
|
+
recommended_command: recommendedCommand
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
async function writeAgentModeRecord(paths, detection, skippedReason) {
|
|
1030
|
+
const dir = path5.join(paths.runtimeDir, "enforcement");
|
|
1031
|
+
await mkdir3(dir, { recursive: true });
|
|
1032
|
+
const file = path5.join(dir, "agent-mode.json");
|
|
1033
|
+
const record = {
|
|
1034
|
+
selected_mode: detection.recommended_mode,
|
|
1035
|
+
recommended_command: detection.recommended_command,
|
|
1036
|
+
configured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1037
|
+
project_root: detection.root,
|
|
1038
|
+
notes: [
|
|
1039
|
+
"mcp = native hAIve MCP tools are available or project MCP config exists.",
|
|
1040
|
+
"wrapped = use haive run when native MCP is unavailable.",
|
|
1041
|
+
"fallback = use haive briefing/enforce manually.",
|
|
1042
|
+
...skippedReason ? [skippedReason] : []
|
|
1043
|
+
]
|
|
1044
|
+
};
|
|
1045
|
+
await writeFile2(file, JSON.stringify(record, null, 2) + "\n", "utf8");
|
|
1046
|
+
return file;
|
|
1047
|
+
}
|
|
1048
|
+
async function confirmGlobalSetup() {
|
|
1049
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1050
|
+
try {
|
|
1051
|
+
const answer = await rl.question(
|
|
1052
|
+
"Configure hAIve in user-level AI client configs (Cursor/VS Code/Claude/Codex when detected)? [y/N] "
|
|
1053
|
+
);
|
|
1054
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1055
|
+
} finally {
|
|
1056
|
+
rl.close();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
async function configureCodexIfAvailable(root) {
|
|
1060
|
+
if (!commandExists("codex")) return { client: "Codex", status: "not_installed" };
|
|
1061
|
+
if (codexMcpConfigured()) return { client: "Codex", status: "already_configured" };
|
|
1062
|
+
const result = spawnSync("codex", [
|
|
1063
|
+
"mcp",
|
|
1064
|
+
"add",
|
|
1065
|
+
"haive",
|
|
1066
|
+
"--env",
|
|
1067
|
+
`HAIVE_PROJECT_ROOT=${root}`,
|
|
1068
|
+
"--",
|
|
1069
|
+
"haive",
|
|
1070
|
+
"mcp",
|
|
1071
|
+
"--stdio"
|
|
1072
|
+
], { encoding: "utf8" });
|
|
1073
|
+
if (result.status === 0) return { client: "Codex", status: "configured", path: path5.join(os2.homedir(), ".codex", "config.toml") };
|
|
1074
|
+
return { client: "Codex", status: "error", error: result.stderr || result.stdout || "codex mcp add failed" };
|
|
1075
|
+
}
|
|
1076
|
+
function commandExists(command) {
|
|
1077
|
+
const result = spawnSync(process.platform === "win32" ? "where" : "which", [command], {
|
|
1078
|
+
encoding: "utf8",
|
|
1079
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1080
|
+
});
|
|
1081
|
+
return result.status === 0;
|
|
1082
|
+
}
|
|
1083
|
+
function codexMcpConfigured() {
|
|
1084
|
+
if (!commandExists("codex")) return false;
|
|
1085
|
+
const result = spawnSync("codex", ["mcp", "get", "haive"], {
|
|
1086
|
+
encoding: "utf8",
|
|
1087
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1088
|
+
});
|
|
1089
|
+
return result.status === 0;
|
|
1090
|
+
}
|
|
1091
|
+
function printDetection(detection, json) {
|
|
1092
|
+
if (json) {
|
|
1093
|
+
console.log(JSON.stringify(detection, null, 2));
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
console.log(ui.bold("hAIve agent status"));
|
|
1097
|
+
console.log(ui.dim(` root: ${detection.root}`));
|
|
1098
|
+
console.log(`${detection.initialized ? ui.green("\u2713") : ui.red("\u2717")} project initialized`);
|
|
1099
|
+
for (const cfg of detection.project_mcp) {
|
|
1100
|
+
console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(path5.relative(detection.root, cfg.path))}`);
|
|
1101
|
+
}
|
|
1102
|
+
for (const agent of detection.installed_agents) {
|
|
1103
|
+
const marker = agent.installed ? ui.green("\u2713") : ui.dim("\u2022");
|
|
1104
|
+
const mcp = agent.mcp_configured === true ? " + hAIve MCP" : "";
|
|
1105
|
+
console.log(`${marker} ${agent.agent} (${agent.command})${mcp}`);
|
|
1106
|
+
}
|
|
1107
|
+
console.log(ui.bold(`Recommended mode: ${detection.recommended_mode}`));
|
|
1108
|
+
console.log(` ${detection.recommended_command}`);
|
|
1109
|
+
}
|
|
1110
|
+
function printSetupResult(result) {
|
|
1111
|
+
for (const item of result.project_results) {
|
|
1112
|
+
if (item.status === "configured") ui.success(`${item.client} project MCP config written (${item.path})`);
|
|
1113
|
+
else if (item.status === "already_configured") ui.info(`${item.client} already configured`);
|
|
1114
|
+
else if (item.status === "error") ui.warn(`${item.client}: ${item.error}`);
|
|
1115
|
+
}
|
|
1116
|
+
for (const item of result.global_results) {
|
|
1117
|
+
if (item.status === "configured") ui.success(`${item.client} user-level MCP configured${item.path ? ` (${item.path})` : ""}`);
|
|
1118
|
+
else if (item.status === "already_configured") ui.info(`${item.client} user-level MCP already configured`);
|
|
1119
|
+
else if (item.status === "not_installed") ui.info(`${item.client} not detected`);
|
|
1120
|
+
else if (item.status === "error") ui.warn(`${item.client}: ${item.error}`);
|
|
1121
|
+
}
|
|
1122
|
+
if (result.global_skipped_reason) ui.warn(result.global_skipped_reason);
|
|
1123
|
+
ui.success(`Agent mode recorded at ${result.mode_file}`);
|
|
1124
|
+
printDetection(result.detection, false);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// src/commands/init-bootstrap.ts
|
|
1128
|
+
import { readdir, readFile as readFile3 } from "fs/promises";
|
|
1129
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1130
|
+
import path6 from "path";
|
|
750
1131
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
751
1132
|
"node_modules",
|
|
752
1133
|
"dist",
|
|
@@ -832,12 +1213,12 @@ function detectKeyDeps(allDeps) {
|
|
|
832
1213
|
return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
|
|
833
1214
|
}
|
|
834
1215
|
function detectLanguage(root) {
|
|
835
|
-
if (
|
|
836
|
-
if (
|
|
837
|
-
if (
|
|
838
|
-
if (
|
|
839
|
-
if (
|
|
840
|
-
if (
|
|
1216
|
+
if (existsSync5(path6.join(root, "tsconfig.json"))) return "TypeScript";
|
|
1217
|
+
if (existsSync5(path6.join(root, "pyproject.toml")) || existsSync5(path6.join(root, "setup.py"))) return "Python";
|
|
1218
|
+
if (existsSync5(path6.join(root, "go.mod"))) return "Go";
|
|
1219
|
+
if (existsSync5(path6.join(root, "pom.xml")) || existsSync5(path6.join(root, "build.gradle"))) return "Java/Kotlin";
|
|
1220
|
+
if (existsSync5(path6.join(root, "Cargo.toml"))) return "Rust";
|
|
1221
|
+
if (existsSync5(path6.join(root, "package.json"))) return "JavaScript";
|
|
841
1222
|
return "Unknown";
|
|
842
1223
|
}
|
|
843
1224
|
function detectProjectType(frameworks, scripts, isMonorepo) {
|
|
@@ -854,7 +1235,7 @@ function detectProjectType(frameworks, scripts, isMonorepo) {
|
|
|
854
1235
|
if (frameworks.includes("Express") || frameworks.includes("Fastify") || frameworks.includes("Hono")) return "Backend API";
|
|
855
1236
|
if (frameworks.includes("React") || frameworks.includes("Vue") || frameworks.includes("Svelte")) return "Frontend SPA";
|
|
856
1237
|
if (scripts["build"] && !scripts["dev"]) return "CLI tool / library";
|
|
857
|
-
if (
|
|
1238
|
+
if (existsSync5("pom.xml")) return "Java backend";
|
|
858
1239
|
return "Application";
|
|
859
1240
|
}
|
|
860
1241
|
async function scanDirs(root, maxDepth = 2) {
|
|
@@ -870,9 +1251,9 @@ async function scanDirs(root, maxDepth = 2) {
|
|
|
870
1251
|
for (const entry of entries) {
|
|
871
1252
|
if (!entry.isDirectory()) continue;
|
|
872
1253
|
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
873
|
-
const rel =
|
|
1254
|
+
const rel = path6.relative(root, path6.join(dir, entry.name));
|
|
874
1255
|
results.push(rel);
|
|
875
|
-
await walk(
|
|
1256
|
+
await walk(path6.join(dir, entry.name), depth + 1);
|
|
876
1257
|
}
|
|
877
1258
|
}
|
|
878
1259
|
await walk(root, 0);
|
|
@@ -989,10 +1370,10 @@ function readmeExcerpt(readme) {
|
|
|
989
1370
|
}
|
|
990
1371
|
async function generateBootstrapContext(root) {
|
|
991
1372
|
let pkg = {};
|
|
992
|
-
const pkgPath =
|
|
993
|
-
if (
|
|
1373
|
+
const pkgPath = path6.join(root, "package.json");
|
|
1374
|
+
if (existsSync5(pkgPath)) {
|
|
994
1375
|
try {
|
|
995
|
-
pkg = JSON.parse(await
|
|
1376
|
+
pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
|
|
996
1377
|
} catch {
|
|
997
1378
|
}
|
|
998
1379
|
}
|
|
@@ -1002,14 +1383,14 @@ async function generateBootstrapContext(root) {
|
|
|
1002
1383
|
const language = detectLanguage(root);
|
|
1003
1384
|
const isMonorepo = pkg.workspaces !== void 0 && (Array.isArray(pkg.workspaces) ? pkg.workspaces.length > 0 : true);
|
|
1004
1385
|
const projectType = detectProjectType(frameworks, pkg.scripts ?? {}, isMonorepo);
|
|
1005
|
-
const projectName = pkg.name ??
|
|
1386
|
+
const projectName = pkg.name ?? path6.basename(root);
|
|
1006
1387
|
const projectDesc = pkg.description ?? "";
|
|
1007
1388
|
let readmeSummary = "";
|
|
1008
1389
|
for (const name of ["README.md", "readme.md", "README"]) {
|
|
1009
|
-
const p =
|
|
1010
|
-
if (
|
|
1390
|
+
const p = path6.join(root, name);
|
|
1391
|
+
if (existsSync5(p)) {
|
|
1011
1392
|
try {
|
|
1012
|
-
const content = await
|
|
1393
|
+
const content = await readFile3(p, "utf8");
|
|
1013
1394
|
readmeSummary = readmeExcerpt(content);
|
|
1014
1395
|
break;
|
|
1015
1396
|
} catch {
|
|
@@ -1071,201 +1452,10 @@ async function generateBootstrapContext(root) {
|
|
|
1071
1452
|
return lines.join("\n");
|
|
1072
1453
|
}
|
|
1073
1454
|
|
|
1074
|
-
// src/commands/init-mcp-setup.ts
|
|
1075
|
-
import { readFile as readFile3, writeFile, mkdir as mkdir2 } from "fs/promises";
|
|
1076
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1077
|
-
import path5 from "path";
|
|
1078
|
-
import os from "os";
|
|
1079
|
-
var HOME = os.homedir();
|
|
1080
|
-
var HAIVE_MCP_ENTRY = {
|
|
1081
|
-
command: "haive",
|
|
1082
|
-
args: ["mcp", "--stdio"]
|
|
1083
|
-
};
|
|
1084
|
-
function projectMcpEntry(root) {
|
|
1085
|
-
return {
|
|
1086
|
-
command: "haive",
|
|
1087
|
-
args: ["mcp", "--stdio"],
|
|
1088
|
-
env: { HAIVE_PROJECT_ROOT: root }
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
function cursorMcpPath() {
|
|
1092
|
-
return path5.join(HOME, ".cursor", "mcp.json");
|
|
1093
|
-
}
|
|
1094
|
-
async function configureCursor() {
|
|
1095
|
-
const mcpPath = cursorMcpPath();
|
|
1096
|
-
const cursorDir = path5.join(HOME, ".cursor");
|
|
1097
|
-
if (!existsSync4(cursorDir)) return { client: "Cursor", status: "not_installed" };
|
|
1098
|
-
let config = {};
|
|
1099
|
-
if (existsSync4(mcpPath)) {
|
|
1100
|
-
try {
|
|
1101
|
-
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1102
|
-
} catch {
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
config.mcpServers ??= {};
|
|
1106
|
-
if (config.mcpServers["haive"]) return { client: "Cursor", status: "already_configured" };
|
|
1107
|
-
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
1108
|
-
await mkdir2(cursorDir, { recursive: true });
|
|
1109
|
-
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1110
|
-
return { client: "Cursor", status: "configured", path: mcpPath };
|
|
1111
|
-
}
|
|
1112
|
-
function vscodeMcpPath() {
|
|
1113
|
-
const candidates = [
|
|
1114
|
-
path5.join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
1115
|
-
// Linux
|
|
1116
|
-
path5.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
|
|
1117
|
-
// macOS
|
|
1118
|
-
path5.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
|
|
1119
|
-
// Windows
|
|
1120
|
-
path5.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
|
|
1121
|
-
];
|
|
1122
|
-
for (const c of candidates) {
|
|
1123
|
-
if (existsSync4(path5.dirname(c))) return c;
|
|
1124
|
-
}
|
|
1125
|
-
return null;
|
|
1126
|
-
}
|
|
1127
|
-
async function configureVSCode() {
|
|
1128
|
-
const mcpPath = vscodeMcpPath();
|
|
1129
|
-
if (!mcpPath) return { client: "VS Code", status: "not_installed" };
|
|
1130
|
-
let config = {};
|
|
1131
|
-
if (existsSync4(mcpPath)) {
|
|
1132
|
-
try {
|
|
1133
|
-
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1134
|
-
} catch {
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
config.servers ??= {};
|
|
1138
|
-
if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
|
|
1139
|
-
config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
1140
|
-
await mkdir2(path5.dirname(mcpPath), { recursive: true });
|
|
1141
|
-
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1142
|
-
return { client: "VS Code", status: "configured", path: mcpPath };
|
|
1143
|
-
}
|
|
1144
|
-
function claudeConfigPath() {
|
|
1145
|
-
const p = path5.join(HOME, ".claude.json");
|
|
1146
|
-
if (existsSync4(p)) return p;
|
|
1147
|
-
const p2 = path5.join(HOME, ".config", "claude", "claude.json");
|
|
1148
|
-
if (existsSync4(path5.dirname(p2))) return p2;
|
|
1149
|
-
return null;
|
|
1150
|
-
}
|
|
1151
|
-
async function configureClaude() {
|
|
1152
|
-
const cfgPath = claudeConfigPath() ?? path5.join(HOME, ".claude.json");
|
|
1153
|
-
if (!existsSync4(cfgPath) && !existsSync4(path5.join(HOME, ".claude"))) {
|
|
1154
|
-
return { client: "Claude Code", status: "not_installed" };
|
|
1155
|
-
}
|
|
1156
|
-
let config = {};
|
|
1157
|
-
if (existsSync4(cfgPath)) {
|
|
1158
|
-
try {
|
|
1159
|
-
config = JSON.parse(await readFile3(cfgPath, "utf8"));
|
|
1160
|
-
} catch {
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
config.mcpServers ??= {};
|
|
1164
|
-
if (config.mcpServers["haive"]) return { client: "Claude Code", status: "already_configured" };
|
|
1165
|
-
config.mcpServers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
1166
|
-
await writeFile(cfgPath, JSON.stringify(config, null, 2), "utf8");
|
|
1167
|
-
return { client: "Claude Code", status: "configured", path: cfgPath };
|
|
1168
|
-
}
|
|
1169
|
-
function windsurfMcpPath() {
|
|
1170
|
-
const candidates = [
|
|
1171
|
-
path5.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
1172
|
-
path5.join(HOME, ".windsurf", "mcp.json")
|
|
1173
|
-
];
|
|
1174
|
-
for (const c of candidates) {
|
|
1175
|
-
if (existsSync4(path5.dirname(c))) return c;
|
|
1176
|
-
}
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
|
-
async function configureWindsurf() {
|
|
1180
|
-
const mcpPath = windsurfMcpPath();
|
|
1181
|
-
if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
|
|
1182
|
-
let config = {};
|
|
1183
|
-
if (existsSync4(mcpPath)) {
|
|
1184
|
-
try {
|
|
1185
|
-
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1186
|
-
} catch {
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
config.mcpServers ??= {};
|
|
1190
|
-
if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
|
|
1191
|
-
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
1192
|
-
await mkdir2(path5.dirname(mcpPath), { recursive: true });
|
|
1193
|
-
await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1194
|
-
return { client: "Windsurf", status: "configured", path: mcpPath };
|
|
1195
|
-
}
|
|
1196
|
-
async function autoConfigureMcpClients() {
|
|
1197
|
-
const results = [];
|
|
1198
|
-
const configurators = [configureCursor, configureVSCode, configureClaude, configureWindsurf];
|
|
1199
|
-
for (const fn of configurators) {
|
|
1200
|
-
try {
|
|
1201
|
-
results.push(await fn());
|
|
1202
|
-
} catch (err) {
|
|
1203
|
-
const name = fn.name.replace("configure", "");
|
|
1204
|
-
results.push({ client: name, status: "error", error: String(err) });
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
return results;
|
|
1208
|
-
}
|
|
1209
|
-
async function configureProjectMcpClients(root) {
|
|
1210
|
-
const entry = projectMcpEntry(root);
|
|
1211
|
-
const results = [];
|
|
1212
|
-
try {
|
|
1213
|
-
const cursorPath = path5.join(root, ".cursor", "mcp.json");
|
|
1214
|
-
let config = {};
|
|
1215
|
-
if (existsSync4(cursorPath)) {
|
|
1216
|
-
try {
|
|
1217
|
-
config = JSON.parse(await readFile3(cursorPath, "utf8"));
|
|
1218
|
-
} catch {
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
config.mcpServers ??= {};
|
|
1222
|
-
config.mcpServers["haive"] = entry;
|
|
1223
|
-
await mkdir2(path5.dirname(cursorPath), { recursive: true });
|
|
1224
|
-
await writeFile(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1225
|
-
results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
|
|
1226
|
-
} catch (err) {
|
|
1227
|
-
results.push({ client: "Cursor (project)", status: "error", error: String(err) });
|
|
1228
|
-
}
|
|
1229
|
-
try {
|
|
1230
|
-
const vscodePath = path5.join(root, ".vscode", "mcp.json");
|
|
1231
|
-
let config = {};
|
|
1232
|
-
if (existsSync4(vscodePath)) {
|
|
1233
|
-
try {
|
|
1234
|
-
config = JSON.parse(await readFile3(vscodePath, "utf8"));
|
|
1235
|
-
} catch {
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
config.servers ??= {};
|
|
1239
|
-
config.servers["haive"] = { ...entry, type: "stdio" };
|
|
1240
|
-
await mkdir2(path5.dirname(vscodePath), { recursive: true });
|
|
1241
|
-
await writeFile(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1242
|
-
results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
|
|
1243
|
-
} catch (err) {
|
|
1244
|
-
results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
|
|
1245
|
-
}
|
|
1246
|
-
try {
|
|
1247
|
-
const mcpPath = path5.join(root, ".mcp.json");
|
|
1248
|
-
let config = {};
|
|
1249
|
-
if (existsSync4(mcpPath)) {
|
|
1250
|
-
try {
|
|
1251
|
-
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1252
|
-
} catch {
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
config.mcpServers ??= {};
|
|
1256
|
-
config.mcpServers["haive"] = { ...entry, type: "stdio" };
|
|
1257
|
-
await writeFile(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1258
|
-
results.push({ client: "Claude Code (project)", status: "configured", path: mcpPath });
|
|
1259
|
-
} catch (err) {
|
|
1260
|
-
results.push({ client: "Claude Code (project)", status: "error", error: String(err) });
|
|
1261
|
-
}
|
|
1262
|
-
return results;
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
1455
|
// src/commands/init-stack-packs.ts
|
|
1266
|
-
import { mkdir as
|
|
1267
|
-
import { existsSync as
|
|
1268
|
-
import
|
|
1456
|
+
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
1457
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1458
|
+
import path7 from "path";
|
|
1269
1459
|
import {
|
|
1270
1460
|
buildFrontmatter,
|
|
1271
1461
|
memoryFilePath,
|
|
@@ -1877,7 +2067,7 @@ function autoDetectStacks(deps) {
|
|
|
1877
2067
|
async function seedStackPack(haivePaths, stack) {
|
|
1878
2068
|
const memories = PACKS[stack];
|
|
1879
2069
|
if (!memories) return 0;
|
|
1880
|
-
await
|
|
2070
|
+
await mkdir4(haivePaths.teamDir, { recursive: true });
|
|
1881
2071
|
let count = 0;
|
|
1882
2072
|
for (const mem of memories) {
|
|
1883
2073
|
const fm = buildFrontmatter({
|
|
@@ -1888,10 +2078,10 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
1888
2078
|
tags: mem.tags
|
|
1889
2079
|
});
|
|
1890
2080
|
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
1891
|
-
if (
|
|
2081
|
+
if (existsSync6(filePath)) continue;
|
|
1892
2082
|
const content = serializeMemory({ frontmatter: fm, body: mem.body });
|
|
1893
|
-
await
|
|
1894
|
-
await
|
|
2083
|
+
await mkdir4(path7.dirname(filePath), { recursive: true });
|
|
2084
|
+
await writeFile3(filePath, content, "utf8");
|
|
1895
2085
|
count++;
|
|
1896
2086
|
}
|
|
1897
2087
|
return count;
|
|
@@ -2105,38 +2295,42 @@ function registerInit(program2) {
|
|
|
2105
2295
|
).option(
|
|
2106
2296
|
"--no-mcp-setup",
|
|
2107
2297
|
"skip auto-configuring haive MCP (haive mcp --stdio) in Cursor / VS Code / Claude Code"
|
|
2298
|
+
).option(
|
|
2299
|
+
"-y, --yes",
|
|
2300
|
+
"approve user-level AI client configuration prompts during agent setup",
|
|
2301
|
+
false
|
|
2108
2302
|
).action(async (opts) => {
|
|
2109
|
-
const root =
|
|
2110
|
-
const paths =
|
|
2303
|
+
const root = path8.resolve(opts.dir);
|
|
2304
|
+
const paths = resolveHaivePaths5(root);
|
|
2111
2305
|
const autopilot = opts.manual !== true;
|
|
2112
2306
|
const wantBootstrap = opts.bootstrap === void 0 ? autopilot : opts.bootstrap;
|
|
2113
2307
|
const wantStack = opts.stack === void 0 ? autopilot ? "auto" : void 0 : opts.stack === "none" ? void 0 : opts.stack;
|
|
2114
|
-
if (
|
|
2308
|
+
if (existsSync7(paths.haiveDir)) {
|
|
2115
2309
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
2116
2310
|
}
|
|
2117
|
-
await
|
|
2118
|
-
await
|
|
2119
|
-
await
|
|
2120
|
-
await
|
|
2311
|
+
await mkdir5(paths.personalDir, { recursive: true });
|
|
2312
|
+
await mkdir5(paths.teamDir, { recursive: true });
|
|
2313
|
+
await mkdir5(paths.moduleDir, { recursive: true });
|
|
2314
|
+
await mkdir5(paths.modulesContextDir, { recursive: true });
|
|
2121
2315
|
await ensureAiRuntimeLayout(paths.runtimeDir);
|
|
2122
|
-
if (!
|
|
2316
|
+
if (!existsSync7(paths.projectContext)) {
|
|
2123
2317
|
if (wantBootstrap) {
|
|
2124
2318
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
2125
2319
|
try {
|
|
2126
2320
|
const context = await generateBootstrapContext(root);
|
|
2127
|
-
await
|
|
2321
|
+
await writeFile4(paths.projectContext, context, "utf8");
|
|
2128
2322
|
ui.success("Created .ai/project-context.md (auto-bootstrapped from local files)");
|
|
2129
2323
|
} catch (err) {
|
|
2130
2324
|
ui.warn(`Bootstrap failed (${String(err)}) \u2014 writing default template instead`);
|
|
2131
|
-
await
|
|
2325
|
+
await writeFile4(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
2132
2326
|
}
|
|
2133
2327
|
} else {
|
|
2134
|
-
await
|
|
2135
|
-
ui.success(`Created ${
|
|
2328
|
+
await writeFile4(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
2329
|
+
ui.success(`Created ${path8.relative(root, paths.projectContext)}`);
|
|
2136
2330
|
}
|
|
2137
2331
|
}
|
|
2138
|
-
const configExists =
|
|
2139
|
-
|
|
2332
|
+
const configExists = existsSync7(
|
|
2333
|
+
path8.join(paths.haiveDir, "haive.config.json")
|
|
2140
2334
|
);
|
|
2141
2335
|
if (!configExists) {
|
|
2142
2336
|
await saveConfig(paths, autopilot ? AUTOPILOT_DEFAULTS : { autopilot: false });
|
|
@@ -2147,7 +2341,7 @@ function registerInit(program2) {
|
|
|
2147
2341
|
if (opts.bridges) {
|
|
2148
2342
|
await writeBridge(root, "CLAUDE.md");
|
|
2149
2343
|
await writeBridge(root, ".cursorrules");
|
|
2150
|
-
await writeBridge(root,
|
|
2344
|
+
await writeBridge(root, path8.join(".github", "copilot-instructions.md"));
|
|
2151
2345
|
await writeCursorHaiveRule(root);
|
|
2152
2346
|
}
|
|
2153
2347
|
const stacksToSeed = await resolveStacksToSeed(root, wantStack);
|
|
@@ -2170,18 +2364,18 @@ function registerInit(program2) {
|
|
|
2170
2364
|
}
|
|
2171
2365
|
const wantCi = opts.withCi || autopilot;
|
|
2172
2366
|
if (wantCi) {
|
|
2173
|
-
const ciPath =
|
|
2174
|
-
if (
|
|
2367
|
+
const ciPath = path8.join(root, ".github", "workflows", "haive-sync.yml");
|
|
2368
|
+
if (existsSync7(ciPath)) {
|
|
2175
2369
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
2176
2370
|
} else {
|
|
2177
|
-
await
|
|
2178
|
-
await
|
|
2179
|
-
ui.success(`Created ${
|
|
2371
|
+
await mkdir5(path8.dirname(ciPath), { recursive: true });
|
|
2372
|
+
await writeFile4(ciPath, CI_WORKFLOW, "utf8");
|
|
2373
|
+
ui.success(`Created ${path8.relative(root, ciPath)}`);
|
|
2180
2374
|
}
|
|
2181
2375
|
}
|
|
2182
2376
|
if (autopilot) {
|
|
2183
2377
|
const haiveBin = process.argv[1];
|
|
2184
|
-
const enforcementResult =
|
|
2378
|
+
const enforcementResult = spawnSync2(
|
|
2185
2379
|
process.execPath,
|
|
2186
2380
|
[haiveBin, "enforce", "install", "--dir", root],
|
|
2187
2381
|
{ encoding: "utf8" }
|
|
@@ -2201,36 +2395,28 @@ function registerInit(program2) {
|
|
|
2201
2395
|
}
|
|
2202
2396
|
}
|
|
2203
2397
|
if (opts.mcpSetup !== false) {
|
|
2204
|
-
const
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
}
|
|
2213
|
-
if (configured.length === 0 && alreadyOk.length === 0) {
|
|
2214
|
-
ui.warn(
|
|
2215
|
-
'No supported AI client detected (Cursor, VS Code, Claude Code, Windsurf).\n Configure manually: command "haive", args ["mcp", "--stdio"].\n See: https://github.com/Doucs91/hAIve#mcp-setup'
|
|
2216
|
-
);
|
|
2398
|
+
const agentSetup = await setupAgentMode(root, {
|
|
2399
|
+
yes: opts.yes === true,
|
|
2400
|
+
global: true,
|
|
2401
|
+
interactive: process.stdin.isTTY
|
|
2402
|
+
});
|
|
2403
|
+
for (const r of agentSetup.project_results) {
|
|
2404
|
+
if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${path8.relative(root, r.path)})`);
|
|
2405
|
+
else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
|
|
2217
2406
|
}
|
|
2218
|
-
const
|
|
2219
|
-
|
|
2220
|
-
if (r.status === "
|
|
2221
|
-
ui.success(`haive MCP project config written (${path7.relative(root, r.path)})`);
|
|
2222
|
-
}
|
|
2407
|
+
for (const r of agentSetup.global_results) {
|
|
2408
|
+
if (r.status === "configured") ui.success(`haive MCP configured in ${r.client}${r.path ? ` (${r.path})` : ""}`);
|
|
2409
|
+
else if (r.status === "already_configured") ui.info(`haive MCP already configured in ${r.client} \u2014 skipped`);
|
|
2223
2410
|
}
|
|
2411
|
+
if (agentSetup.global_skipped_reason) ui.warn(agentSetup.global_skipped_reason);
|
|
2412
|
+
ui.info(`Recommended agent mode: ${agentSetup.detection.recommended_mode}`);
|
|
2413
|
+
ui.info(agentSetup.detection.recommended_command);
|
|
2224
2414
|
await ensureGitignoreEntries(root, [
|
|
2225
2415
|
".cursor/mcp.json",
|
|
2226
2416
|
".vscode/mcp.json",
|
|
2227
2417
|
".mcp.json"
|
|
2228
2418
|
]);
|
|
2229
|
-
|
|
2230
|
-
ui.info(
|
|
2231
|
-
ui.dim(" \u2192 Restart your AI client for MCP changes to take effect.")
|
|
2232
|
-
);
|
|
2233
|
-
}
|
|
2419
|
+
ui.info(ui.dim(" \u2192 Restart your AI client for MCP changes to take effect."));
|
|
2234
2420
|
}
|
|
2235
2421
|
ui.success(`hAIve initialized at ${root}${autopilot ? " (autopilot mode)" : ""}`);
|
|
2236
2422
|
console.log();
|
|
@@ -2279,8 +2465,8 @@ function registerInit(program2) {
|
|
|
2279
2465
|
async function resolveStacksToSeed(root, stackOpt) {
|
|
2280
2466
|
if (!stackOpt) return [];
|
|
2281
2467
|
if (stackOpt === "auto") {
|
|
2282
|
-
const pkgPath =
|
|
2283
|
-
if (!
|
|
2468
|
+
const pkgPath = path8.join(root, "package.json");
|
|
2469
|
+
if (!existsSync7(pkgPath)) return [];
|
|
2284
2470
|
try {
|
|
2285
2471
|
const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
|
|
2286
2472
|
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -2293,23 +2479,23 @@ async function resolveStacksToSeed(root, stackOpt) {
|
|
|
2293
2479
|
}
|
|
2294
2480
|
async function writeCursorHaiveRule(root) {
|
|
2295
2481
|
const relPath = ".cursor/rules/haive-mcp-required.mdc";
|
|
2296
|
-
const target =
|
|
2297
|
-
if (
|
|
2482
|
+
const target = path8.join(root, relPath);
|
|
2483
|
+
if (existsSync7(target)) {
|
|
2298
2484
|
ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
|
|
2299
2485
|
return;
|
|
2300
2486
|
}
|
|
2301
|
-
await
|
|
2302
|
-
await
|
|
2487
|
+
await mkdir5(path8.dirname(target), { recursive: true });
|
|
2488
|
+
await writeFile4(target, CURSOR_HAIVE_RULE_MDC, "utf8");
|
|
2303
2489
|
ui.success(`Created Cursor rule ${relPath}`);
|
|
2304
2490
|
}
|
|
2305
2491
|
async function writeBridge(root, relPath) {
|
|
2306
|
-
const target =
|
|
2307
|
-
if (
|
|
2492
|
+
const target = path8.join(root, relPath);
|
|
2493
|
+
if (existsSync7(target)) {
|
|
2308
2494
|
ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
|
|
2309
2495
|
return;
|
|
2310
2496
|
}
|
|
2311
|
-
await
|
|
2312
|
-
await
|
|
2497
|
+
await mkdir5(path8.dirname(target), { recursive: true });
|
|
2498
|
+
await writeFile4(target, BRIDGE_BODY, "utf8");
|
|
2313
2499
|
ui.success(`Created bridge ${relPath}`);
|
|
2314
2500
|
}
|
|
2315
2501
|
var RUNTIME_README_BODY = `# .ai/.runtime \u2014 disposable local layer
|
|
@@ -2325,43 +2511,43 @@ var RUNTIME_GITIGNORE_BODY = `*
|
|
|
2325
2511
|
!README.md
|
|
2326
2512
|
`;
|
|
2327
2513
|
async function ensureAiRuntimeLayout(runtimeDir) {
|
|
2328
|
-
await
|
|
2329
|
-
const gi =
|
|
2330
|
-
if (!
|
|
2331
|
-
await
|
|
2514
|
+
await mkdir5(runtimeDir, { recursive: true });
|
|
2515
|
+
const gi = path8.join(runtimeDir, ".gitignore");
|
|
2516
|
+
if (!existsSync7(gi)) {
|
|
2517
|
+
await writeFile4(gi, RUNTIME_GITIGNORE_BODY, "utf8");
|
|
2332
2518
|
}
|
|
2333
|
-
const readme =
|
|
2334
|
-
if (!
|
|
2335
|
-
await
|
|
2519
|
+
const readme = path8.join(runtimeDir, "README.md");
|
|
2520
|
+
if (!existsSync7(readme)) {
|
|
2521
|
+
await writeFile4(readme, RUNTIME_README_BODY, "utf8");
|
|
2336
2522
|
}
|
|
2337
2523
|
}
|
|
2338
2524
|
async function ensureGitignoreEntries(root, patterns) {
|
|
2339
2525
|
try {
|
|
2340
|
-
const gitignorePath =
|
|
2526
|
+
const gitignorePath = path8.join(root, ".gitignore");
|
|
2341
2527
|
let existing = "";
|
|
2342
|
-
if (
|
|
2528
|
+
if (existsSync7(gitignorePath)) {
|
|
2343
2529
|
existing = await readFile4(gitignorePath, "utf8");
|
|
2344
2530
|
}
|
|
2345
2531
|
const lines = existing.split("\n");
|
|
2346
2532
|
const missing = patterns.filter((p) => !lines.some((l) => l.trim() === p));
|
|
2347
2533
|
if (missing.length === 0) return;
|
|
2348
2534
|
const toAppend = (existing.endsWith("\n") || existing === "" ? "" : "\n") + "# hAIve project-level MCP configs (machine-specific absolute paths)\n" + missing.join("\n") + "\n";
|
|
2349
|
-
await
|
|
2535
|
+
await writeFile4(gitignorePath, existing + toAppend, "utf8");
|
|
2350
2536
|
} catch {
|
|
2351
2537
|
}
|
|
2352
2538
|
}
|
|
2353
2539
|
|
|
2354
2540
|
// src/commands/install-hooks.ts
|
|
2355
|
-
import { mkdir as
|
|
2356
|
-
import { existsSync as
|
|
2357
|
-
import
|
|
2541
|
+
import { mkdir as mkdir7, writeFile as writeFile6, chmod, readFile as readFile6 } from "fs/promises";
|
|
2542
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2543
|
+
import path10 from "path";
|
|
2358
2544
|
import "commander";
|
|
2359
|
-
import { findProjectRoot as
|
|
2545
|
+
import { findProjectRoot as findProjectRoot7 } from "@hiveai/core";
|
|
2360
2546
|
|
|
2361
2547
|
// src/utils/claude-hooks.ts
|
|
2362
|
-
import { existsSync as
|
|
2363
|
-
import { mkdir as
|
|
2364
|
-
import
|
|
2548
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2549
|
+
import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
2550
|
+
import path9 from "path";
|
|
2365
2551
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
2366
2552
|
var POST_TOOL_USE_GROUP = {
|
|
2367
2553
|
matcher: "Edit|Write|Bash",
|
|
@@ -2447,7 +2633,7 @@ function unpatchClaudeSettings(input) {
|
|
|
2447
2633
|
async function installClaudeHooksAtPath(settingsPath) {
|
|
2448
2634
|
let raw = null;
|
|
2449
2635
|
let created = false;
|
|
2450
|
-
if (
|
|
2636
|
+
if (existsSync8(settingsPath)) {
|
|
2451
2637
|
try {
|
|
2452
2638
|
raw = JSON.parse(await readFile5(settingsPath, "utf8"));
|
|
2453
2639
|
} catch {
|
|
@@ -2457,25 +2643,25 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
2457
2643
|
created = true;
|
|
2458
2644
|
}
|
|
2459
2645
|
const patched = patchClaudeSettings(raw);
|
|
2460
|
-
await
|
|
2461
|
-
await
|
|
2646
|
+
await mkdir6(path9.dirname(settingsPath), { recursive: true });
|
|
2647
|
+
await writeFile5(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
2462
2648
|
return { settingsPath, created };
|
|
2463
2649
|
}
|
|
2464
2650
|
async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
2465
|
-
if (!
|
|
2651
|
+
if (!existsSync8(settingsPath)) {
|
|
2466
2652
|
return { settingsPath, created: false };
|
|
2467
2653
|
}
|
|
2468
2654
|
const raw = JSON.parse(await readFile5(settingsPath, "utf8"));
|
|
2469
2655
|
const cleaned = unpatchClaudeSettings(raw);
|
|
2470
|
-
await
|
|
2656
|
+
await writeFile5(settingsPath, JSON.stringify(cleaned, null, 2) + "\n", "utf8");
|
|
2471
2657
|
return { settingsPath, created: false };
|
|
2472
2658
|
}
|
|
2473
2659
|
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
2474
2660
|
if (scope === "user") {
|
|
2475
2661
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
2476
|
-
return
|
|
2662
|
+
return path9.join(home, ".claude", "settings.json");
|
|
2477
2663
|
}
|
|
2478
|
-
return
|
|
2664
|
+
return path9.join(projectRoot, ".claude", "settings.local.json");
|
|
2479
2665
|
}
|
|
2480
2666
|
|
|
2481
2667
|
// src/commands/install-hooks.ts
|
|
@@ -2531,20 +2717,20 @@ fi
|
|
|
2531
2717
|
}
|
|
2532
2718
|
];
|
|
2533
2719
|
async function installGitHooks(opts) {
|
|
2534
|
-
const root =
|
|
2535
|
-
const gitDir =
|
|
2536
|
-
if (!
|
|
2720
|
+
const root = findProjectRoot7(opts.dir);
|
|
2721
|
+
const gitDir = path10.join(root, ".git");
|
|
2722
|
+
if (!existsSync9(gitDir)) {
|
|
2537
2723
|
ui.error(`No .git directory at ${root}.`);
|
|
2538
2724
|
process.exitCode = 1;
|
|
2539
2725
|
return;
|
|
2540
2726
|
}
|
|
2541
|
-
const hooksDir =
|
|
2542
|
-
await
|
|
2727
|
+
const hooksDir = path10.join(gitDir, "hooks");
|
|
2728
|
+
await mkdir7(hooksDir, { recursive: true });
|
|
2543
2729
|
let installed = 0;
|
|
2544
2730
|
let skipped = 0;
|
|
2545
2731
|
for (const { name, body } of HOOKS) {
|
|
2546
|
-
const file =
|
|
2547
|
-
if (
|
|
2732
|
+
const file = path10.join(hooksDir, name);
|
|
2733
|
+
if (existsSync9(file) && !opts.force) {
|
|
2548
2734
|
const existing = await readFile6(file, "utf8");
|
|
2549
2735
|
if (!existing.includes(HOOK_MARKER)) {
|
|
2550
2736
|
ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
|
|
@@ -2552,7 +2738,7 @@ async function installGitHooks(opts) {
|
|
|
2552
2738
|
continue;
|
|
2553
2739
|
}
|
|
2554
2740
|
}
|
|
2555
|
-
await
|
|
2741
|
+
await writeFile6(file, body, "utf8");
|
|
2556
2742
|
await chmod(file, 493);
|
|
2557
2743
|
installed++;
|
|
2558
2744
|
}
|
|
@@ -2562,7 +2748,7 @@ async function installGitHooks(opts) {
|
|
|
2562
2748
|
ui.info("pre-push: haive enforce check blocks pushes that bypass briefing/session recap policy.");
|
|
2563
2749
|
}
|
|
2564
2750
|
async function installClaudeHooks(opts) {
|
|
2565
|
-
const root =
|
|
2751
|
+
const root = findProjectRoot7(opts.dir);
|
|
2566
2752
|
const scope = opts.scope ?? "user";
|
|
2567
2753
|
const settingsPath = opts.settings ?? defaultClaudeSettingsPath(scope, root);
|
|
2568
2754
|
if (opts.uninstall) {
|
|
@@ -2607,11 +2793,11 @@ function registerInstallHooks(program2) {
|
|
|
2607
2793
|
}
|
|
2608
2794
|
|
|
2609
2795
|
// src/commands/observe.ts
|
|
2610
|
-
import { appendFile, mkdir as
|
|
2611
|
-
import { existsSync as
|
|
2612
|
-
import
|
|
2796
|
+
import { appendFile, mkdir as mkdir8 } from "fs/promises";
|
|
2797
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2798
|
+
import path11 from "path";
|
|
2613
2799
|
import "commander";
|
|
2614
|
-
import { findProjectRoot as
|
|
2800
|
+
import { findProjectRoot as findProjectRoot8, resolveHaivePaths as resolveHaivePaths6 } from "@hiveai/core";
|
|
2615
2801
|
var MAX_STDIN_BYTES = 256 * 1024;
|
|
2616
2802
|
var TRUNCATE_FIELD = 800;
|
|
2617
2803
|
function truncate(s, max = TRUNCATE_FIELD) {
|
|
@@ -2681,14 +2867,14 @@ function registerObserve(program2) {
|
|
|
2681
2867
|
}
|
|
2682
2868
|
const root = (() => {
|
|
2683
2869
|
try {
|
|
2684
|
-
return
|
|
2870
|
+
return findProjectRoot8(opts.dir ?? payload.cwd);
|
|
2685
2871
|
} catch {
|
|
2686
2872
|
return null;
|
|
2687
2873
|
}
|
|
2688
2874
|
})();
|
|
2689
2875
|
if (!root) return;
|
|
2690
|
-
const paths =
|
|
2691
|
-
if (!
|
|
2876
|
+
const paths = resolveHaivePaths6(root);
|
|
2877
|
+
if (!existsSync10(paths.haiveDir)) return;
|
|
2692
2878
|
const observation = {
|
|
2693
2879
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2694
2880
|
session_id: payload.session_id,
|
|
@@ -2697,10 +2883,10 @@ function registerObserve(program2) {
|
|
|
2697
2883
|
summary: buildSummary(payload),
|
|
2698
2884
|
files: extractFiles(payload)
|
|
2699
2885
|
};
|
|
2700
|
-
const cacheDir =
|
|
2701
|
-
await
|
|
2886
|
+
const cacheDir = path11.join(paths.haiveDir, ".cache");
|
|
2887
|
+
await mkdir8(cacheDir, { recursive: true });
|
|
2702
2888
|
await appendFile(
|
|
2703
|
-
|
|
2889
|
+
path11.join(cacheDir, "observations.jsonl"),
|
|
2704
2890
|
JSON.stringify(observation) + "\n",
|
|
2705
2891
|
"utf8"
|
|
2706
2892
|
);
|
|
@@ -2711,15 +2897,15 @@ function registerObserve(program2) {
|
|
|
2711
2897
|
|
|
2712
2898
|
// src/commands/mcp.ts
|
|
2713
2899
|
import "commander";
|
|
2714
|
-
import { findProjectRoot as
|
|
2900
|
+
import { findProjectRoot as findProjectRoot10 } from "@hiveai/core";
|
|
2715
2901
|
|
|
2716
2902
|
// ../mcp/dist/server.js
|
|
2717
2903
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2718
2904
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2719
|
-
import { findProjectRoot as
|
|
2720
|
-
import { mkdir as
|
|
2721
|
-
import { existsSync as
|
|
2722
|
-
import
|
|
2905
|
+
import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
|
|
2906
|
+
import { mkdir as mkdir9, writeFile as writeFile7 } from "fs/promises";
|
|
2907
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2908
|
+
import path12 from "path";
|
|
2723
2909
|
import { z } from "zod";
|
|
2724
2910
|
import { readFile as readFile7, readdir as readdir2 } from "fs/promises";
|
|
2725
2911
|
import { existsSync as existsSync22 } from "fs";
|
|
@@ -2803,7 +2989,7 @@ import {
|
|
|
2803
2989
|
} from "@hiveai/core";
|
|
2804
2990
|
import { z as z10 } from "zod";
|
|
2805
2991
|
import { writeFile as writeFile52 } from "fs/promises";
|
|
2806
|
-
import { existsSync as
|
|
2992
|
+
import { existsSync as existsSync112 } from "fs";
|
|
2807
2993
|
import { loadMemoriesFromDir as loadMemoriesFromDir9, serializeMemory as serializeMemory4 } from "@hiveai/core";
|
|
2808
2994
|
import { z as z11 } from "zod";
|
|
2809
2995
|
import { existsSync as existsSync12 } from "fs";
|
|
@@ -2820,7 +3006,7 @@ import {
|
|
|
2820
3006
|
serializeMemory as serializeMemory5
|
|
2821
3007
|
} from "@hiveai/core";
|
|
2822
3008
|
import { z as z13 } from "zod";
|
|
2823
|
-
import { mkdir as mkdir32, writeFile as
|
|
3009
|
+
import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
|
|
2824
3010
|
import { existsSync as existsSync14 } from "fs";
|
|
2825
3011
|
import path52 from "path";
|
|
2826
3012
|
import {
|
|
@@ -2981,8 +3167,8 @@ import { loadConfigSync } from "@hiveai/core";
|
|
|
2981
3167
|
function createContext(options = {}) {
|
|
2982
3168
|
const env = options.env ?? process.env;
|
|
2983
3169
|
const cwd = options.cwd ?? process.cwd();
|
|
2984
|
-
const root = options.root ?? env.HAIVE_PROJECT_ROOT ??
|
|
2985
|
-
return { paths:
|
|
3170
|
+
const root = options.root ?? env.HAIVE_PROJECT_ROOT ?? findProjectRoot9(cwd);
|
|
3171
|
+
return { paths: resolveHaivePaths7(root) };
|
|
2986
3172
|
}
|
|
2987
3173
|
var BootstrapProjectSaveInputSchema = {
|
|
2988
3174
|
content: z.string().min(1).describe("Full Markdown content for the project (or module) context file"),
|
|
@@ -2992,15 +3178,15 @@ var BootstrapProjectSaveInputSchema = {
|
|
|
2992
3178
|
overwrite: z.boolean().default(false).describe("Overwrite an existing file instead of failing")
|
|
2993
3179
|
};
|
|
2994
3180
|
async function bootstrapProjectSave(input, ctx) {
|
|
2995
|
-
const target = input.module ?
|
|
2996
|
-
const exists =
|
|
3181
|
+
const target = input.module ? path12.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
|
|
3182
|
+
const exists = existsSync11(target);
|
|
2997
3183
|
if (exists && !input.overwrite) {
|
|
2998
3184
|
throw new Error(
|
|
2999
3185
|
`${target} already exists. Pass overwrite=true to replace it.`
|
|
3000
3186
|
);
|
|
3001
3187
|
}
|
|
3002
|
-
await
|
|
3003
|
-
await
|
|
3188
|
+
await mkdir9(path12.dirname(target), { recursive: true });
|
|
3189
|
+
await writeFile7(target, input.content, "utf8");
|
|
3004
3190
|
return {
|
|
3005
3191
|
file_path: target,
|
|
3006
3192
|
action: exists ? "overwritten" : "created"
|
|
@@ -3754,7 +3940,7 @@ var MemUpdateInputSchema = {
|
|
|
3754
3940
|
author: z11.string().optional().describe("New author handle or email")
|
|
3755
3941
|
};
|
|
3756
3942
|
async function memUpdate(input, ctx) {
|
|
3757
|
-
if (!
|
|
3943
|
+
if (!existsSync112(ctx.paths.memoriesDir)) {
|
|
3758
3944
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
3759
3945
|
}
|
|
3760
3946
|
const memories = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
|
|
@@ -3890,7 +4076,7 @@ async function memTried(input, ctx) {
|
|
|
3890
4076
|
if (existsSync14(file)) {
|
|
3891
4077
|
throw new Error(`Memory already exists at ${file}`);
|
|
3892
4078
|
}
|
|
3893
|
-
await
|
|
4079
|
+
await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
|
|
3894
4080
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
3895
4081
|
}
|
|
3896
4082
|
var MemObserveInputSchema = {
|
|
@@ -6082,7 +6268,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
6082
6268
|
};
|
|
6083
6269
|
}
|
|
6084
6270
|
var SERVER_NAME = "haive";
|
|
6085
|
-
var SERVER_VERSION = "0.9.
|
|
6271
|
+
var SERVER_VERSION = "0.9.12";
|
|
6086
6272
|
function jsonResult(data) {
|
|
6087
6273
|
return {
|
|
6088
6274
|
content: [
|
|
@@ -6991,7 +7177,7 @@ function registerMcp(program2) {
|
|
|
6991
7177
|
).option("-d, --dir <dir>", "project root (walks up from here for .ai/ / .git/)").option("-r, --root <dir>", "same as --dir (parity with legacy haive-mcp --root)").option("--stdio", "optional marker for client configs \u2014 transport is always stdio", false).action(async (opts) => {
|
|
6992
7178
|
void opts.stdio;
|
|
6993
7179
|
const raw = opts.root ?? opts.dir;
|
|
6994
|
-
const root = raw ?
|
|
7180
|
+
const root = raw ? findProjectRoot10(raw) : findProjectRoot10();
|
|
6995
7181
|
try {
|
|
6996
7182
|
await runHaiveMcpStdio({ root });
|
|
6997
7183
|
} catch (err) {
|
|
@@ -7002,15 +7188,15 @@ function registerMcp(program2) {
|
|
|
7002
7188
|
}
|
|
7003
7189
|
|
|
7004
7190
|
// src/commands/sync.ts
|
|
7005
|
-
import { spawnSync as
|
|
7006
|
-
import { readFile as readFile8, writeFile as writeFile13, mkdir as
|
|
7191
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7192
|
+
import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
|
|
7007
7193
|
import { existsSync as existsSync29 } from "fs";
|
|
7008
|
-
import
|
|
7194
|
+
import path13 from "path";
|
|
7009
7195
|
import "commander";
|
|
7010
7196
|
import {
|
|
7011
7197
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
7012
7198
|
buildFrontmatter as buildFrontmatter6,
|
|
7013
|
-
findProjectRoot as
|
|
7199
|
+
findProjectRoot as findProjectRoot11,
|
|
7014
7200
|
getUsage as getUsage10,
|
|
7015
7201
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
7016
7202
|
isDecaying as isDecaying2,
|
|
@@ -7019,7 +7205,7 @@ import {
|
|
|
7019
7205
|
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
7020
7206
|
loadUsageIndex as loadUsageIndex12,
|
|
7021
7207
|
pullCrossRepoSources,
|
|
7022
|
-
resolveHaivePaths as
|
|
7208
|
+
resolveHaivePaths as resolveHaivePaths8,
|
|
7023
7209
|
resolveManifestFiles,
|
|
7024
7210
|
serializeMemory as serializeMemory11,
|
|
7025
7211
|
trackDependencies,
|
|
@@ -7038,8 +7224,8 @@ function registerSync(program2) {
|
|
|
7038
7224
|
"--inject-bridge",
|
|
7039
7225
|
"inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
|
|
7040
7226
|
).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) => {
|
|
7041
|
-
const root =
|
|
7042
|
-
const paths =
|
|
7227
|
+
const root = findProjectRoot11(opts.dir);
|
|
7228
|
+
const paths = resolveHaivePaths8(root);
|
|
7043
7229
|
if (!existsSync29(paths.memoriesDir)) {
|
|
7044
7230
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
7045
7231
|
process.exitCode = 1;
|
|
@@ -7172,7 +7358,7 @@ function registerSync(program2) {
|
|
|
7172
7358
|
);
|
|
7173
7359
|
}
|
|
7174
7360
|
if (opts.injectBridge) {
|
|
7175
|
-
const bridgeFile = opts.bridgeFile ?
|
|
7361
|
+
const bridgeFile = opts.bridgeFile ? path13.resolve(opts.bridgeFile) : path13.join(root, "CLAUDE.md");
|
|
7176
7362
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
7177
7363
|
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
7178
7364
|
}
|
|
@@ -7278,10 +7464,10 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7278
7464
|
paths: [result.file],
|
|
7279
7465
|
topic: `dep-bump-${slugParts}`
|
|
7280
7466
|
});
|
|
7281
|
-
const teamDir =
|
|
7282
|
-
await
|
|
7467
|
+
const teamDir = path13.join(paths.memoriesDir, "team");
|
|
7468
|
+
await mkdir10(teamDir, { recursive: true });
|
|
7283
7469
|
await writeFile13(
|
|
7284
|
-
|
|
7470
|
+
path13.join(teamDir, `${fm.id}.md`),
|
|
7285
7471
|
serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
7286
7472
|
"utf8"
|
|
7287
7473
|
);
|
|
@@ -7345,10 +7531,10 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7345
7531
|
paths: [diff.file],
|
|
7346
7532
|
topic: `contract-breaking-${diff.contract}`
|
|
7347
7533
|
});
|
|
7348
|
-
const teamDir =
|
|
7349
|
-
await
|
|
7534
|
+
const teamDir = path13.join(paths.memoriesDir, "team");
|
|
7535
|
+
await mkdir10(teamDir, { recursive: true });
|
|
7350
7536
|
await writeFile13(
|
|
7351
|
-
|
|
7537
|
+
path13.join(teamDir, `${fm.id}.md`),
|
|
7352
7538
|
serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
7353
7539
|
"utf8"
|
|
7354
7540
|
);
|
|
@@ -7362,7 +7548,7 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
7362
7548
|
const existingMap = await loadCodeMap4(paths);
|
|
7363
7549
|
if (existingMap) {
|
|
7364
7550
|
const mapAge = new Date(existingMap.generated_at).getTime();
|
|
7365
|
-
const gitResult =
|
|
7551
|
+
const gitResult = spawnSync3(
|
|
7366
7552
|
"git",
|
|
7367
7553
|
[
|
|
7368
7554
|
"diff",
|
|
@@ -7441,11 +7627,11 @@ ${BRIDGE_END}`;
|
|
|
7441
7627
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
7442
7628
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
7443
7629
|
if (startIdx !== -1 && endIdx === -1) {
|
|
7444
|
-
ui.warn(`${
|
|
7630
|
+
ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
7445
7631
|
return;
|
|
7446
7632
|
}
|
|
7447
7633
|
if (startIdx === -1 && endIdx !== -1) {
|
|
7448
|
-
ui.warn(`${
|
|
7634
|
+
ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
7449
7635
|
return;
|
|
7450
7636
|
}
|
|
7451
7637
|
let updated;
|
|
@@ -7453,19 +7639,19 @@ ${BRIDGE_END}`;
|
|
|
7453
7639
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
7454
7640
|
} else {
|
|
7455
7641
|
if (!fileExists && !quiet) {
|
|
7456
|
-
ui.info(`Creating ${
|
|
7642
|
+
ui.info(`Creating ${path13.relative(root, bridgeFile)} with haive memory block.`);
|
|
7457
7643
|
}
|
|
7458
7644
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
7459
7645
|
}
|
|
7460
7646
|
await writeFile13(bridgeFile, updated, "utf8");
|
|
7461
7647
|
if (!quiet) {
|
|
7462
7648
|
console.log(
|
|
7463
|
-
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${
|
|
7649
|
+
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path13.relative(root, bridgeFile)}`)
|
|
7464
7650
|
);
|
|
7465
7651
|
}
|
|
7466
7652
|
}
|
|
7467
7653
|
function collectSinceChanges(root, ref) {
|
|
7468
|
-
const result =
|
|
7654
|
+
const result = spawnSync3(
|
|
7469
7655
|
"git",
|
|
7470
7656
|
["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
|
|
7471
7657
|
{ encoding: "utf8" }
|
|
@@ -7485,17 +7671,17 @@ function collectSinceChanges(root, ref) {
|
|
|
7485
7671
|
|
|
7486
7672
|
// src/commands/memory-add.ts
|
|
7487
7673
|
import { createHash as createHash2 } from "crypto";
|
|
7488
|
-
import { mkdir as
|
|
7674
|
+
import { mkdir as mkdir11, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
|
|
7489
7675
|
import { existsSync as existsSync30 } from "fs";
|
|
7490
|
-
import
|
|
7676
|
+
import path14 from "path";
|
|
7491
7677
|
import "commander";
|
|
7492
7678
|
import {
|
|
7493
7679
|
buildFrontmatter as buildFrontmatter7,
|
|
7494
|
-
findProjectRoot as
|
|
7680
|
+
findProjectRoot as findProjectRoot12,
|
|
7495
7681
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
7496
7682
|
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
7497
7683
|
memoryFilePath as memoryFilePath6,
|
|
7498
|
-
resolveHaivePaths as
|
|
7684
|
+
resolveHaivePaths as resolveHaivePaths9,
|
|
7499
7685
|
serializeMemory as serializeMemory12
|
|
7500
7686
|
} from "@hiveai/core";
|
|
7501
7687
|
function registerMemoryAdd(memory2) {
|
|
@@ -7523,8 +7709,8 @@ function registerMemoryAdd(memory2) {
|
|
|
7523
7709
|
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
7524
7710
|
`
|
|
7525
7711
|
).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) => {
|
|
7526
|
-
const root =
|
|
7527
|
-
const paths =
|
|
7712
|
+
const root = findProjectRoot12(opts.dir);
|
|
7713
|
+
const paths = resolveHaivePaths9(root);
|
|
7528
7714
|
if (!existsSync30(paths.haiveDir)) {
|
|
7529
7715
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
7530
7716
|
process.exitCode = 1;
|
|
@@ -7536,7 +7722,7 @@ function registerMemoryAdd(memory2) {
|
|
|
7536
7722
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
7537
7723
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
7538
7724
|
if (anchorPaths.length > 0) {
|
|
7539
|
-
const missing = anchorPaths.filter((p) => !existsSync30(
|
|
7725
|
+
const missing = anchorPaths.filter((p) => !existsSync30(path14.resolve(root, p)));
|
|
7540
7726
|
if (missing.length > 0) {
|
|
7541
7727
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
7542
7728
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -7601,7 +7787,7 @@ TODO \u2014 write the memory body.
|
|
|
7601
7787
|
}
|
|
7602
7788
|
};
|
|
7603
7789
|
await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
|
|
7604
|
-
ui.success(`Updated (topic upsert) ${
|
|
7790
|
+
ui.success(`Updated (topic upsert) ${path14.relative(root, topicMatch.filePath)}`);
|
|
7605
7791
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
7606
7792
|
return;
|
|
7607
7793
|
}
|
|
@@ -7620,7 +7806,7 @@ TODO \u2014 write the memory body.
|
|
|
7620
7806
|
topic: opts.topic
|
|
7621
7807
|
});
|
|
7622
7808
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
7623
|
-
await
|
|
7809
|
+
await mkdir11(path14.dirname(file), { recursive: true });
|
|
7624
7810
|
if (existsSync30(file)) {
|
|
7625
7811
|
ui.error(`Memory already exists at ${file}`);
|
|
7626
7812
|
process.exitCode = 1;
|
|
@@ -7639,7 +7825,7 @@ TODO \u2014 write the memory body.
|
|
|
7639
7825
|
}
|
|
7640
7826
|
}
|
|
7641
7827
|
await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
|
|
7642
|
-
ui.success(`Created ${
|
|
7828
|
+
ui.success(`Created ${path14.relative(root, file)}`);
|
|
7643
7829
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
7644
7830
|
if (inferredTags.length > 0) {
|
|
7645
7831
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
@@ -7670,9 +7856,9 @@ function parseCsv2(value) {
|
|
|
7670
7856
|
|
|
7671
7857
|
// src/commands/memory-list.ts
|
|
7672
7858
|
import { existsSync as existsSync31 } from "fs";
|
|
7673
|
-
import
|
|
7859
|
+
import path15 from "path";
|
|
7674
7860
|
import "commander";
|
|
7675
|
-
import { findProjectRoot as
|
|
7861
|
+
import { findProjectRoot as findProjectRoot13, resolveHaivePaths as resolveHaivePaths10 } from "@hiveai/core";
|
|
7676
7862
|
|
|
7677
7863
|
// src/utils/fs.ts
|
|
7678
7864
|
import {
|
|
@@ -7684,8 +7870,8 @@ import {
|
|
|
7684
7870
|
// src/commands/memory-list.ts
|
|
7685
7871
|
function registerMemoryList(memory2) {
|
|
7686
7872
|
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) => {
|
|
7687
|
-
const root =
|
|
7688
|
-
const paths =
|
|
7873
|
+
const root = findProjectRoot13(opts.dir);
|
|
7874
|
+
const paths = resolveHaivePaths10(root);
|
|
7689
7875
|
if (!existsSync31(paths.memoriesDir)) {
|
|
7690
7876
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
7691
7877
|
process.exitCode = 1;
|
|
@@ -7718,7 +7904,7 @@ function registerMemoryList(memory2) {
|
|
|
7718
7904
|
console.log(
|
|
7719
7905
|
`${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
|
|
7720
7906
|
);
|
|
7721
|
-
console.log(` ${ui.dim(
|
|
7907
|
+
console.log(` ${ui.dim(path15.relative(root, filePath))}`);
|
|
7722
7908
|
}
|
|
7723
7909
|
console.log(ui.dim(`
|
|
7724
7910
|
${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
|
|
@@ -7753,20 +7939,20 @@ function matchesFilters(loaded, opts) {
|
|
|
7753
7939
|
}
|
|
7754
7940
|
|
|
7755
7941
|
// src/commands/memory-promote.ts
|
|
7756
|
-
import { mkdir as
|
|
7942
|
+
import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
|
|
7757
7943
|
import { existsSync as existsSync33 } from "fs";
|
|
7758
|
-
import
|
|
7944
|
+
import path16 from "path";
|
|
7759
7945
|
import "commander";
|
|
7760
7946
|
import {
|
|
7761
|
-
findProjectRoot as
|
|
7947
|
+
findProjectRoot as findProjectRoot14,
|
|
7762
7948
|
memoryFilePath as memoryFilePath7,
|
|
7763
|
-
resolveHaivePaths as
|
|
7949
|
+
resolveHaivePaths as resolveHaivePaths11,
|
|
7764
7950
|
serializeMemory as serializeMemory13
|
|
7765
7951
|
} from "@hiveai/core";
|
|
7766
7952
|
function registerMemoryPromote(memory2) {
|
|
7767
7953
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
7768
|
-
const root =
|
|
7769
|
-
const paths =
|
|
7954
|
+
const root = findProjectRoot14(opts.dir);
|
|
7955
|
+
const paths = resolveHaivePaths11(root);
|
|
7770
7956
|
if (!existsSync33(paths.memoriesDir)) {
|
|
7771
7957
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
7772
7958
|
process.exitCode = 1;
|
|
@@ -7802,11 +7988,11 @@ function registerMemoryPromote(memory2) {
|
|
|
7802
7988
|
body: found.memory.body
|
|
7803
7989
|
};
|
|
7804
7990
|
const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
|
|
7805
|
-
await
|
|
7991
|
+
await mkdir12(path16.dirname(newPath), { recursive: true });
|
|
7806
7992
|
await writeFile15(newPath, serializeMemory13(updated), "utf8");
|
|
7807
7993
|
await unlink2(found.filePath);
|
|
7808
7994
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
7809
|
-
ui.info(`Now at ${
|
|
7995
|
+
ui.info(`Now at ${path16.relative(root, newPath)}`);
|
|
7810
7996
|
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
7811
7997
|
});
|
|
7812
7998
|
}
|
|
@@ -7814,17 +8000,17 @@ function registerMemoryPromote(memory2) {
|
|
|
7814
8000
|
// src/commands/memory-approve.ts
|
|
7815
8001
|
import { existsSync as existsSync34 } from "fs";
|
|
7816
8002
|
import { writeFile as writeFile16 } from "fs/promises";
|
|
7817
|
-
import
|
|
8003
|
+
import path17 from "path";
|
|
7818
8004
|
import "commander";
|
|
7819
8005
|
import {
|
|
7820
|
-
findProjectRoot as
|
|
7821
|
-
resolveHaivePaths as
|
|
8006
|
+
findProjectRoot as findProjectRoot15,
|
|
8007
|
+
resolveHaivePaths as resolveHaivePaths12,
|
|
7822
8008
|
serializeMemory as serializeMemory14
|
|
7823
8009
|
} from "@hiveai/core";
|
|
7824
8010
|
function registerMemoryApprove(memory2) {
|
|
7825
8011
|
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) => {
|
|
7826
|
-
const root =
|
|
7827
|
-
const paths =
|
|
8012
|
+
const root = findProjectRoot15(opts.dir);
|
|
8013
|
+
const paths = resolveHaivePaths12(root);
|
|
7828
8014
|
if (!existsSync34(paths.memoriesDir)) {
|
|
7829
8015
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7830
8016
|
process.exitCode = 1;
|
|
@@ -7878,24 +8064,24 @@ function registerMemoryApprove(memory2) {
|
|
|
7878
8064
|
};
|
|
7879
8065
|
await writeFile16(found.filePath, serializeMemory14(next), "utf8");
|
|
7880
8066
|
ui.success(`Approved ${id} (status=validated)`);
|
|
7881
|
-
ui.info(
|
|
8067
|
+
ui.info(path17.relative(root, found.filePath));
|
|
7882
8068
|
});
|
|
7883
8069
|
}
|
|
7884
8070
|
|
|
7885
8071
|
// src/commands/memory-update.ts
|
|
7886
8072
|
import { writeFile as writeFile17 } from "fs/promises";
|
|
7887
8073
|
import { existsSync as existsSync35 } from "fs";
|
|
7888
|
-
import
|
|
8074
|
+
import path18 from "path";
|
|
7889
8075
|
import "commander";
|
|
7890
8076
|
import {
|
|
7891
|
-
findProjectRoot as
|
|
7892
|
-
resolveHaivePaths as
|
|
8077
|
+
findProjectRoot as findProjectRoot16,
|
|
8078
|
+
resolveHaivePaths as resolveHaivePaths13,
|
|
7893
8079
|
serializeMemory as serializeMemory15
|
|
7894
8080
|
} from "@hiveai/core";
|
|
7895
8081
|
function registerMemoryUpdate(memory2) {
|
|
7896
8082
|
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) => {
|
|
7897
|
-
const root =
|
|
7898
|
-
const paths =
|
|
8083
|
+
const root = findProjectRoot16(opts.dir);
|
|
8084
|
+
const paths = resolveHaivePaths13(root);
|
|
7899
8085
|
if (!existsSync35(paths.memoriesDir)) {
|
|
7900
8086
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
7901
8087
|
process.exitCode = 1;
|
|
@@ -7948,7 +8134,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
7948
8134
|
serializeMemory15({ frontmatter: newFrontmatter, body: newBody }),
|
|
7949
8135
|
"utf8"
|
|
7950
8136
|
);
|
|
7951
|
-
ui.success(`Updated ${
|
|
8137
|
+
ui.success(`Updated ${path18.relative(root, loaded.filePath)}`);
|
|
7952
8138
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
7953
8139
|
});
|
|
7954
8140
|
}
|
|
@@ -7969,15 +8155,15 @@ function parseCsv3(value) {
|
|
|
7969
8155
|
// src/commands/memory-auto-promote.ts
|
|
7970
8156
|
import { writeFile as writeFile18 } from "fs/promises";
|
|
7971
8157
|
import { existsSync as existsSync36 } from "fs";
|
|
7972
|
-
import
|
|
8158
|
+
import path19 from "path";
|
|
7973
8159
|
import "commander";
|
|
7974
8160
|
import {
|
|
7975
8161
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
|
|
7976
|
-
findProjectRoot as
|
|
8162
|
+
findProjectRoot as findProjectRoot17,
|
|
7977
8163
|
getUsage as getUsage11,
|
|
7978
8164
|
isAutoPromoteEligible as isAutoPromoteEligible3,
|
|
7979
8165
|
loadUsageIndex as loadUsageIndex13,
|
|
7980
|
-
resolveHaivePaths as
|
|
8166
|
+
resolveHaivePaths as resolveHaivePaths14,
|
|
7981
8167
|
serializeMemory as serializeMemory16
|
|
7982
8168
|
} from "@hiveai/core";
|
|
7983
8169
|
function registerMemoryAutoPromote(memory2) {
|
|
@@ -7986,8 +8172,8 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
7986
8172
|
"memories with more rejections than this are skipped",
|
|
7987
8173
|
String(DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
7988
8174
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7989
|
-
const root =
|
|
7990
|
-
const paths =
|
|
8175
|
+
const root = findProjectRoot17(opts.dir);
|
|
8176
|
+
const paths = resolveHaivePaths14(root);
|
|
7991
8177
|
if (!existsSync36(paths.memoriesDir)) {
|
|
7992
8178
|
ui.error(`No .ai/memories at ${root}.`);
|
|
7993
8179
|
process.exitCode = 1;
|
|
@@ -8014,7 +8200,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
8014
8200
|
console.log(
|
|
8015
8201
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
8016
8202
|
);
|
|
8017
|
-
console.log(` ${ui.dim(
|
|
8203
|
+
console.log(` ${ui.dim(path19.relative(root, filePath))}`);
|
|
8018
8204
|
if (opts.apply) {
|
|
8019
8205
|
const next = {
|
|
8020
8206
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
@@ -8033,17 +8219,17 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
8033
8219
|
import { spawn as spawn3 } from "child_process";
|
|
8034
8220
|
import { existsSync as existsSync37 } from "fs";
|
|
8035
8221
|
import { readFile as readFile10 } from "fs/promises";
|
|
8036
|
-
import
|
|
8222
|
+
import path20 from "path";
|
|
8037
8223
|
import "commander";
|
|
8038
8224
|
import {
|
|
8039
|
-
findProjectRoot as
|
|
8225
|
+
findProjectRoot as findProjectRoot18,
|
|
8040
8226
|
parseMemory,
|
|
8041
|
-
resolveHaivePaths as
|
|
8227
|
+
resolveHaivePaths as resolveHaivePaths15
|
|
8042
8228
|
} from "@hiveai/core";
|
|
8043
8229
|
function registerMemoryEdit(memory2) {
|
|
8044
8230
|
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) => {
|
|
8045
|
-
const root =
|
|
8046
|
-
const paths =
|
|
8231
|
+
const root = findProjectRoot18(opts.dir);
|
|
8232
|
+
const paths = resolveHaivePaths15(root);
|
|
8047
8233
|
if (!existsSync37(paths.memoriesDir)) {
|
|
8048
8234
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8049
8235
|
process.exitCode = 1;
|
|
@@ -8057,7 +8243,7 @@ function registerMemoryEdit(memory2) {
|
|
|
8057
8243
|
return;
|
|
8058
8244
|
}
|
|
8059
8245
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
8060
|
-
ui.info(`Opening ${
|
|
8246
|
+
ui.info(`Opening ${path20.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
8061
8247
|
const code = await runEditor(editor, found.filePath);
|
|
8062
8248
|
if (code !== 0) {
|
|
8063
8249
|
ui.warn(`Editor exited with status ${code}.`);
|
|
@@ -8085,21 +8271,21 @@ function runEditor(editor, file) {
|
|
|
8085
8271
|
|
|
8086
8272
|
// src/commands/memory-for-files.ts
|
|
8087
8273
|
import { existsSync as existsSync38 } from "fs";
|
|
8088
|
-
import
|
|
8274
|
+
import path21 from "path";
|
|
8089
8275
|
import "commander";
|
|
8090
8276
|
import {
|
|
8091
8277
|
deriveConfidence as deriveConfidence9,
|
|
8092
|
-
findProjectRoot as
|
|
8278
|
+
findProjectRoot as findProjectRoot19,
|
|
8093
8279
|
getUsage as getUsage12,
|
|
8094
8280
|
inferModulesFromPaths as inferModulesFromPaths4,
|
|
8095
8281
|
loadUsageIndex as loadUsageIndex14,
|
|
8096
8282
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
|
|
8097
|
-
resolveHaivePaths as
|
|
8283
|
+
resolveHaivePaths as resolveHaivePaths16
|
|
8098
8284
|
} from "@hiveai/core";
|
|
8099
8285
|
function registerMemoryForFiles(memory2) {
|
|
8100
8286
|
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) => {
|
|
8101
|
-
const root =
|
|
8102
|
-
const paths =
|
|
8287
|
+
const root = findProjectRoot19(opts.dir);
|
|
8288
|
+
const paths = resolveHaivePaths16(root);
|
|
8103
8289
|
if (!existsSync38(paths.memoriesDir)) {
|
|
8104
8290
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8105
8291
|
process.exitCode = 1;
|
|
@@ -8207,24 +8393,24 @@ function printGroup(root, label, loaded, usage) {
|
|
|
8207
8393
|
const u = getUsage12(usage, fm.id);
|
|
8208
8394
|
const conf = deriveConfidence9(fm, u);
|
|
8209
8395
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
8210
|
-
console.log(` ${ui.dim(
|
|
8396
|
+
console.log(` ${ui.dim(path21.relative(root, filePath))}`);
|
|
8211
8397
|
}
|
|
8212
8398
|
}
|
|
8213
8399
|
|
|
8214
8400
|
// src/commands/memory-hot.ts
|
|
8215
8401
|
import { existsSync as existsSync39 } from "fs";
|
|
8216
|
-
import
|
|
8402
|
+
import path23 from "path";
|
|
8217
8403
|
import "commander";
|
|
8218
8404
|
import {
|
|
8219
|
-
findProjectRoot as
|
|
8405
|
+
findProjectRoot as findProjectRoot20,
|
|
8220
8406
|
getUsage as getUsage13,
|
|
8221
8407
|
loadUsageIndex as loadUsageIndex15,
|
|
8222
|
-
resolveHaivePaths as
|
|
8408
|
+
resolveHaivePaths as resolveHaivePaths17
|
|
8223
8409
|
} from "@hiveai/core";
|
|
8224
8410
|
function registerMemoryHot(memory2) {
|
|
8225
8411
|
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) => {
|
|
8226
|
-
const root =
|
|
8227
|
-
const paths =
|
|
8412
|
+
const root = findProjectRoot20(opts.dir);
|
|
8413
|
+
const paths = resolveHaivePaths17(root);
|
|
8228
8414
|
if (!existsSync39(paths.memoriesDir)) {
|
|
8229
8415
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8230
8416
|
process.exitCode = 1;
|
|
@@ -8253,7 +8439,7 @@ function registerMemoryHot(memory2) {
|
|
|
8253
8439
|
console.log(
|
|
8254
8440
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
8255
8441
|
);
|
|
8256
|
-
console.log(` ${ui.dim(
|
|
8442
|
+
console.log(` ${ui.dim(path23.relative(root, filePath))}`);
|
|
8257
8443
|
}
|
|
8258
8444
|
ui.info(
|
|
8259
8445
|
`${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
|
|
@@ -8262,15 +8448,15 @@ function registerMemoryHot(memory2) {
|
|
|
8262
8448
|
}
|
|
8263
8449
|
|
|
8264
8450
|
// src/commands/memory-tried.ts
|
|
8265
|
-
import { mkdir as
|
|
8451
|
+
import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
|
|
8266
8452
|
import { existsSync as existsSync40 } from "fs";
|
|
8267
|
-
import
|
|
8453
|
+
import path24 from "path";
|
|
8268
8454
|
import "commander";
|
|
8269
8455
|
import {
|
|
8270
8456
|
buildFrontmatter as buildFrontmatter8,
|
|
8271
|
-
findProjectRoot as
|
|
8457
|
+
findProjectRoot as findProjectRoot21,
|
|
8272
8458
|
memoryFilePath as memoryFilePath8,
|
|
8273
|
-
resolveHaivePaths as
|
|
8459
|
+
resolveHaivePaths as resolveHaivePaths18,
|
|
8274
8460
|
serializeMemory as serializeMemory17
|
|
8275
8461
|
} from "@hiveai/core";
|
|
8276
8462
|
function registerMemoryTried(memory2) {
|
|
@@ -8290,8 +8476,8 @@ function registerMemoryTried(memory2) {
|
|
|
8290
8476
|
--paths packages/cli/src/index.ts
|
|
8291
8477
|
`
|
|
8292
8478
|
).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) => {
|
|
8293
|
-
const root =
|
|
8294
|
-
const paths =
|
|
8479
|
+
const root = findProjectRoot21(opts.dir);
|
|
8480
|
+
const paths = resolveHaivePaths18(root);
|
|
8295
8481
|
if (!existsSync40(paths.haiveDir)) {
|
|
8296
8482
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
8297
8483
|
process.exitCode = 1;
|
|
@@ -8315,14 +8501,14 @@ function registerMemoryTried(memory2) {
|
|
|
8315
8501
|
}
|
|
8316
8502
|
const body = lines.join("\n") + "\n";
|
|
8317
8503
|
const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
8318
|
-
await
|
|
8504
|
+
await mkdir13(path24.dirname(file), { recursive: true });
|
|
8319
8505
|
if (existsSync40(file)) {
|
|
8320
8506
|
ui.error(`Memory already exists at ${file}`);
|
|
8321
8507
|
process.exitCode = 1;
|
|
8322
8508
|
return;
|
|
8323
8509
|
}
|
|
8324
8510
|
await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
|
|
8325
|
-
ui.success(`Recorded: ${
|
|
8511
|
+
ui.success(`Recorded: ${path24.relative(root, file)}`);
|
|
8326
8512
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
8327
8513
|
});
|
|
8328
8514
|
}
|
|
@@ -8333,18 +8519,18 @@ function parseCsv4(value) {
|
|
|
8333
8519
|
|
|
8334
8520
|
// src/commands/memory-pending.ts
|
|
8335
8521
|
import { existsSync as existsSync41 } from "fs";
|
|
8336
|
-
import
|
|
8522
|
+
import path25 from "path";
|
|
8337
8523
|
import "commander";
|
|
8338
8524
|
import {
|
|
8339
|
-
findProjectRoot as
|
|
8525
|
+
findProjectRoot as findProjectRoot22,
|
|
8340
8526
|
getUsage as getUsage14,
|
|
8341
8527
|
loadUsageIndex as loadUsageIndex16,
|
|
8342
|
-
resolveHaivePaths as
|
|
8528
|
+
resolveHaivePaths as resolveHaivePaths19
|
|
8343
8529
|
} from "@hiveai/core";
|
|
8344
8530
|
function registerMemoryPending(memory2) {
|
|
8345
8531
|
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) => {
|
|
8346
|
-
const root =
|
|
8347
|
-
const paths =
|
|
8532
|
+
const root = findProjectRoot22(opts.dir);
|
|
8533
|
+
const paths = resolveHaivePaths19(root);
|
|
8348
8534
|
if (!existsSync41(paths.memoriesDir)) {
|
|
8349
8535
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8350
8536
|
process.exitCode = 1;
|
|
@@ -8373,7 +8559,7 @@ function registerMemoryPending(memory2) {
|
|
|
8373
8559
|
console.log(
|
|
8374
8560
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
8375
8561
|
);
|
|
8376
|
-
console.log(` ${ui.dim(
|
|
8562
|
+
console.log(` ${ui.dim(path25.relative(root, filePath))}`);
|
|
8377
8563
|
}
|
|
8378
8564
|
ui.info(`${proposed.length} pending`);
|
|
8379
8565
|
});
|
|
@@ -8381,22 +8567,22 @@ function registerMemoryPending(memory2) {
|
|
|
8381
8567
|
|
|
8382
8568
|
// src/commands/memory-query.ts
|
|
8383
8569
|
import { existsSync as existsSync43 } from "fs";
|
|
8384
|
-
import
|
|
8570
|
+
import path26 from "path";
|
|
8385
8571
|
import "commander";
|
|
8386
8572
|
import {
|
|
8387
8573
|
extractSnippet as extractSnippet2,
|
|
8388
|
-
findProjectRoot as
|
|
8574
|
+
findProjectRoot as findProjectRoot23,
|
|
8389
8575
|
literalMatchesAllTokens as literalMatchesAllTokens3,
|
|
8390
8576
|
literalMatchesAnyToken as literalMatchesAnyToken4,
|
|
8391
8577
|
pickSnippetNeedle as pickSnippetNeedle2,
|
|
8392
|
-
resolveHaivePaths as
|
|
8578
|
+
resolveHaivePaths as resolveHaivePaths20,
|
|
8393
8579
|
tokenizeQuery as tokenizeQuery6,
|
|
8394
8580
|
trackReads as trackReads4
|
|
8395
8581
|
} from "@hiveai/core";
|
|
8396
8582
|
function registerMemoryQuery(memory2) {
|
|
8397
8583
|
memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
|
|
8398
|
-
const root =
|
|
8399
|
-
const paths =
|
|
8584
|
+
const root = findProjectRoot23(opts.dir);
|
|
8585
|
+
const paths = resolveHaivePaths20(root);
|
|
8400
8586
|
if (!existsSync43(paths.memoriesDir)) {
|
|
8401
8587
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
8402
8588
|
process.exitCode = 1;
|
|
@@ -8438,7 +8624,7 @@ function registerMemoryQuery(memory2) {
|
|
|
8438
8624
|
const fm = mem.frontmatter;
|
|
8439
8625
|
const statusBadge = ui.statusBadge(fm.status);
|
|
8440
8626
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
8441
|
-
console.log(` ${ui.dim(
|
|
8627
|
+
console.log(` ${ui.dim(path26.relative(root, filePath))}`);
|
|
8442
8628
|
const snippet = extractSnippet2(mem.body, snippetNeedle);
|
|
8443
8629
|
if (snippet) console.log(` ${snippet}`);
|
|
8444
8630
|
}
|
|
@@ -8459,17 +8645,17 @@ import { writeFile as writeFile20 } from "fs/promises";
|
|
|
8459
8645
|
import { existsSync as existsSync44 } from "fs";
|
|
8460
8646
|
import "commander";
|
|
8461
8647
|
import {
|
|
8462
|
-
findProjectRoot as
|
|
8648
|
+
findProjectRoot as findProjectRoot24,
|
|
8463
8649
|
loadUsageIndex as loadUsageIndex17,
|
|
8464
8650
|
recordRejection as recordRejection2,
|
|
8465
|
-
resolveHaivePaths as
|
|
8651
|
+
resolveHaivePaths as resolveHaivePaths21,
|
|
8466
8652
|
saveUsageIndex as saveUsageIndex3,
|
|
8467
8653
|
serializeMemory as serializeMemory18
|
|
8468
8654
|
} from "@hiveai/core";
|
|
8469
8655
|
function registerMemoryReject(memory2) {
|
|
8470
8656
|
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) => {
|
|
8471
|
-
const root =
|
|
8472
|
-
const paths =
|
|
8657
|
+
const root = findProjectRoot24(opts.dir);
|
|
8658
|
+
const paths = resolveHaivePaths21(root);
|
|
8473
8659
|
if (!existsSync44(paths.memoriesDir)) {
|
|
8474
8660
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8475
8661
|
process.exitCode = 1;
|
|
@@ -8508,19 +8694,19 @@ function registerMemoryReject(memory2) {
|
|
|
8508
8694
|
// src/commands/memory-rm.ts
|
|
8509
8695
|
import { existsSync as existsSync45 } from "fs";
|
|
8510
8696
|
import { unlink as unlink3 } from "fs/promises";
|
|
8511
|
-
import
|
|
8512
|
-
import { createInterface } from "readline/promises";
|
|
8697
|
+
import path27 from "path";
|
|
8698
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
8513
8699
|
import "commander";
|
|
8514
8700
|
import {
|
|
8515
|
-
findProjectRoot as
|
|
8701
|
+
findProjectRoot as findProjectRoot25,
|
|
8516
8702
|
loadUsageIndex as loadUsageIndex18,
|
|
8517
|
-
resolveHaivePaths as
|
|
8703
|
+
resolveHaivePaths as resolveHaivePaths22,
|
|
8518
8704
|
saveUsageIndex as saveUsageIndex4
|
|
8519
8705
|
} from "@hiveai/core";
|
|
8520
8706
|
function registerMemoryRm(memory2) {
|
|
8521
8707
|
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) => {
|
|
8522
|
-
const root =
|
|
8523
|
-
const paths =
|
|
8708
|
+
const root = findProjectRoot25(opts.dir);
|
|
8709
|
+
const paths = resolveHaivePaths22(root);
|
|
8524
8710
|
if (!existsSync45(paths.memoriesDir)) {
|
|
8525
8711
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8526
8712
|
process.exitCode = 1;
|
|
@@ -8533,9 +8719,9 @@ function registerMemoryRm(memory2) {
|
|
|
8533
8719
|
process.exitCode = 1;
|
|
8534
8720
|
return;
|
|
8535
8721
|
}
|
|
8536
|
-
const rel =
|
|
8722
|
+
const rel = path27.relative(root, found.filePath);
|
|
8537
8723
|
if (!opts.yes) {
|
|
8538
|
-
const rl =
|
|
8724
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8539
8725
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
8540
8726
|
rl.close();
|
|
8541
8727
|
if (answer !== "y" && answer !== "yes") {
|
|
@@ -8559,19 +8745,19 @@ function registerMemoryRm(memory2) {
|
|
|
8559
8745
|
// src/commands/memory-show.ts
|
|
8560
8746
|
import { existsSync as existsSync46 } from "fs";
|
|
8561
8747
|
import { readFile as readFile11 } from "fs/promises";
|
|
8562
|
-
import
|
|
8748
|
+
import path28 from "path";
|
|
8563
8749
|
import "commander";
|
|
8564
8750
|
import {
|
|
8565
8751
|
deriveConfidence as deriveConfidence10,
|
|
8566
|
-
findProjectRoot as
|
|
8752
|
+
findProjectRoot as findProjectRoot26,
|
|
8567
8753
|
getUsage as getUsage15,
|
|
8568
8754
|
loadUsageIndex as loadUsageIndex19,
|
|
8569
|
-
resolveHaivePaths as
|
|
8755
|
+
resolveHaivePaths as resolveHaivePaths23
|
|
8570
8756
|
} from "@hiveai/core";
|
|
8571
8757
|
function registerMemoryShow(memory2) {
|
|
8572
8758
|
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) => {
|
|
8573
|
-
const root =
|
|
8574
|
-
const paths =
|
|
8759
|
+
const root = findProjectRoot26(opts.dir);
|
|
8760
|
+
const paths = resolveHaivePaths23(root);
|
|
8575
8761
|
if (!existsSync46(paths.memoriesDir)) {
|
|
8576
8762
|
ui.error(`No .ai/memories at ${root}.`);
|
|
8577
8763
|
process.exitCode = 1;
|
|
@@ -8601,7 +8787,7 @@ function registerMemoryShow(memory2) {
|
|
|
8601
8787
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
8602
8788
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
8603
8789
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
8604
|
-
console.log(`${ui.dim("file:")} ${
|
|
8790
|
+
console.log(`${ui.dim("file:")} ${path28.relative(root, found.filePath)}`);
|
|
8605
8791
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
8606
8792
|
console.log(ui.dim("anchor:"));
|
|
8607
8793
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -8617,19 +8803,19 @@ function registerMemoryShow(memory2) {
|
|
|
8617
8803
|
|
|
8618
8804
|
// src/commands/memory-stats.ts
|
|
8619
8805
|
import { existsSync as existsSync47 } from "fs";
|
|
8620
|
-
import
|
|
8806
|
+
import path29 from "path";
|
|
8621
8807
|
import "commander";
|
|
8622
8808
|
import {
|
|
8623
8809
|
deriveConfidence as deriveConfidence11,
|
|
8624
|
-
findProjectRoot as
|
|
8810
|
+
findProjectRoot as findProjectRoot27,
|
|
8625
8811
|
getUsage as getUsage16,
|
|
8626
8812
|
loadUsageIndex as loadUsageIndex20,
|
|
8627
|
-
resolveHaivePaths as
|
|
8813
|
+
resolveHaivePaths as resolveHaivePaths24
|
|
8628
8814
|
} from "@hiveai/core";
|
|
8629
8815
|
function registerMemoryStats(memory2) {
|
|
8630
8816
|
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) => {
|
|
8631
|
-
const root =
|
|
8632
|
-
const paths =
|
|
8817
|
+
const root = findProjectRoot27(opts.dir);
|
|
8818
|
+
const paths = resolveHaivePaths24(root);
|
|
8633
8819
|
if (!existsSync47(paths.memoriesDir)) {
|
|
8634
8820
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8635
8821
|
process.exitCode = 1;
|
|
@@ -8655,7 +8841,7 @@ function registerMemoryStats(memory2) {
|
|
|
8655
8841
|
console.log(
|
|
8656
8842
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
8657
8843
|
);
|
|
8658
|
-
console.log(` ${ui.dim(
|
|
8844
|
+
console.log(` ${ui.dim(path29.relative(root, filePath))}`);
|
|
8659
8845
|
}
|
|
8660
8846
|
});
|
|
8661
8847
|
}
|
|
@@ -8663,11 +8849,11 @@ function registerMemoryStats(memory2) {
|
|
|
8663
8849
|
// src/commands/memory-verify.ts
|
|
8664
8850
|
import { writeFile as writeFile21 } from "fs/promises";
|
|
8665
8851
|
import { existsSync as existsSync48 } from "fs";
|
|
8666
|
-
import
|
|
8852
|
+
import path30 from "path";
|
|
8667
8853
|
import "commander";
|
|
8668
8854
|
import {
|
|
8669
|
-
findProjectRoot as
|
|
8670
|
-
resolveHaivePaths as
|
|
8855
|
+
findProjectRoot as findProjectRoot28,
|
|
8856
|
+
resolveHaivePaths as resolveHaivePaths25,
|
|
8671
8857
|
serializeMemory as serializeMemory19,
|
|
8672
8858
|
verifyAnchor as verifyAnchor3
|
|
8673
8859
|
} from "@hiveai/core";
|
|
@@ -8675,8 +8861,8 @@ function registerMemoryVerify(memory2) {
|
|
|
8675
8861
|
memory2.command("verify").description(
|
|
8676
8862
|
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
|
|
8677
8863
|
).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) => {
|
|
8678
|
-
const root =
|
|
8679
|
-
const paths =
|
|
8864
|
+
const root = findProjectRoot28(opts.dir);
|
|
8865
|
+
const paths = resolveHaivePaths25(root);
|
|
8680
8866
|
if (!existsSync48(paths.memoriesDir)) {
|
|
8681
8867
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
8682
8868
|
process.exitCode = 1;
|
|
@@ -8700,7 +8886,7 @@ function registerMemoryVerify(memory2) {
|
|
|
8700
8886
|
anchorlessIds.push(mem.frontmatter.id);
|
|
8701
8887
|
continue;
|
|
8702
8888
|
}
|
|
8703
|
-
const rel =
|
|
8889
|
+
const rel = path30.relative(root, filePath);
|
|
8704
8890
|
if (result.stale) {
|
|
8705
8891
|
staleCount++;
|
|
8706
8892
|
console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
|
|
@@ -8767,15 +8953,15 @@ import { readFile as readFile12 } from "fs/promises";
|
|
|
8767
8953
|
import { existsSync as existsSync49 } from "fs";
|
|
8768
8954
|
import "commander";
|
|
8769
8955
|
import {
|
|
8770
|
-
findProjectRoot as
|
|
8771
|
-
resolveHaivePaths as
|
|
8956
|
+
findProjectRoot as findProjectRoot29,
|
|
8957
|
+
resolveHaivePaths as resolveHaivePaths26
|
|
8772
8958
|
} from "@hiveai/core";
|
|
8773
8959
|
function registerMemoryImport(memory2) {
|
|
8774
8960
|
memory2.command("import").description(
|
|
8775
8961
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
8776
8962
|
).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) => {
|
|
8777
|
-
const root =
|
|
8778
|
-
const paths =
|
|
8963
|
+
const root = findProjectRoot29(opts.dir);
|
|
8964
|
+
const paths = resolveHaivePaths26(root);
|
|
8779
8965
|
if (!existsSync49(paths.haiveDir)) {
|
|
8780
8966
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
8781
8967
|
process.exitCode = 1;
|
|
@@ -8815,13 +9001,13 @@ function registerMemoryImport(memory2) {
|
|
|
8815
9001
|
|
|
8816
9002
|
// src/commands/memory-import-changelog.ts
|
|
8817
9003
|
import { existsSync as existsSync50 } from "fs";
|
|
8818
|
-
import { readFile as readFile13, mkdir as
|
|
8819
|
-
import
|
|
9004
|
+
import { readFile as readFile13, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
|
|
9005
|
+
import path31 from "path";
|
|
8820
9006
|
import "commander";
|
|
8821
9007
|
import {
|
|
8822
9008
|
buildFrontmatter as buildFrontmatter9,
|
|
8823
|
-
findProjectRoot as
|
|
8824
|
-
resolveHaivePaths as
|
|
9009
|
+
findProjectRoot as findProjectRoot30,
|
|
9010
|
+
resolveHaivePaths as resolveHaivePaths27,
|
|
8825
9011
|
serializeMemory as serializeMemory20
|
|
8826
9012
|
} from "@hiveai/core";
|
|
8827
9013
|
function parseChangelog(content) {
|
|
@@ -8886,9 +9072,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
8886
9072
|
"--versions <csv>",
|
|
8887
9073
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
8888
9074
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8889
|
-
const root =
|
|
8890
|
-
const paths =
|
|
8891
|
-
const changelogPath =
|
|
9075
|
+
const root = findProjectRoot30(opts.dir);
|
|
9076
|
+
const paths = resolveHaivePaths27(root);
|
|
9077
|
+
const changelogPath = path31.resolve(root, opts.fromChangelog);
|
|
8892
9078
|
if (!existsSync50(changelogPath)) {
|
|
8893
9079
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
8894
9080
|
process.exitCode = 1;
|
|
@@ -8909,10 +9095,10 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
8909
9095
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
8910
9096
|
}
|
|
8911
9097
|
}
|
|
8912
|
-
const pkgName = opts.package ??
|
|
9098
|
+
const pkgName = opts.package ?? path31.basename(path31.dirname(changelogPath));
|
|
8913
9099
|
const scope = opts.scope ?? "team";
|
|
8914
|
-
const teamDir =
|
|
8915
|
-
await
|
|
9100
|
+
const teamDir = path31.join(paths.memoriesDir, scope);
|
|
9101
|
+
await mkdir14(teamDir, { recursive: true });
|
|
8916
9102
|
let saved = 0;
|
|
8917
9103
|
for (const entry of entries) {
|
|
8918
9104
|
const lines = [];
|
|
@@ -8934,7 +9120,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
8934
9120
|
lines.push("");
|
|
8935
9121
|
}
|
|
8936
9122
|
lines.push(
|
|
8937
|
-
`**Source:** \`${
|
|
9123
|
+
`**Source:** \`${path31.relative(root, changelogPath)}\`
|
|
8938
9124
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
8939
9125
|
);
|
|
8940
9126
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
@@ -8949,11 +9135,11 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
8949
9135
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
8950
9136
|
`v${entry.version}`
|
|
8951
9137
|
],
|
|
8952
|
-
paths: [
|
|
9138
|
+
paths: [path31.relative(root, changelogPath)],
|
|
8953
9139
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
8954
9140
|
});
|
|
8955
9141
|
await writeFile23(
|
|
8956
|
-
|
|
9142
|
+
path31.join(teamDir, `${fm.id}.md`),
|
|
8957
9143
|
serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
|
|
8958
9144
|
"utf8"
|
|
8959
9145
|
);
|
|
@@ -8978,15 +9164,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
8978
9164
|
// src/commands/memory-digest.ts
|
|
8979
9165
|
import { existsSync as existsSync51 } from "fs";
|
|
8980
9166
|
import { writeFile as writeFile24 } from "fs/promises";
|
|
8981
|
-
import
|
|
9167
|
+
import path33 from "path";
|
|
8982
9168
|
import "commander";
|
|
8983
9169
|
import {
|
|
8984
9170
|
deriveConfidence as deriveConfidence12,
|
|
8985
|
-
findProjectRoot as
|
|
9171
|
+
findProjectRoot as findProjectRoot31,
|
|
8986
9172
|
getUsage as getUsage17,
|
|
8987
9173
|
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
8988
9174
|
loadUsageIndex as loadUsageIndex21,
|
|
8989
|
-
resolveHaivePaths as
|
|
9175
|
+
resolveHaivePaths as resolveHaivePaths28
|
|
8990
9176
|
} from "@hiveai/core";
|
|
8991
9177
|
var CONFIDENCE_EMOJI = {
|
|
8992
9178
|
unverified: "\u2B1C",
|
|
@@ -8999,8 +9185,8 @@ function registerMemoryDigest(program2) {
|
|
|
8999
9185
|
program2.command("digest").description(
|
|
9000
9186
|
"Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
|
|
9001
9187
|
).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) => {
|
|
9002
|
-
const root =
|
|
9003
|
-
const paths =
|
|
9188
|
+
const root = findProjectRoot31(opts.dir);
|
|
9189
|
+
const paths = resolveHaivePaths28(root);
|
|
9004
9190
|
if (!existsSync51(paths.memoriesDir)) {
|
|
9005
9191
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
9006
9192
|
process.exitCode = 1;
|
|
@@ -9073,7 +9259,7 @@ function registerMemoryDigest(program2) {
|
|
|
9073
9259
|
);
|
|
9074
9260
|
const digest = lines.join("\n");
|
|
9075
9261
|
if (opts.out) {
|
|
9076
|
-
const outPath =
|
|
9262
|
+
const outPath = path33.resolve(process.cwd(), opts.out);
|
|
9077
9263
|
await writeFile24(outPath, digest, "utf8");
|
|
9078
9264
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
9079
9265
|
} else {
|
|
@@ -9083,20 +9269,20 @@ function registerMemoryDigest(program2) {
|
|
|
9083
9269
|
}
|
|
9084
9270
|
|
|
9085
9271
|
// src/commands/session-end.ts
|
|
9086
|
-
import { writeFile as writeFile25, mkdir as
|
|
9272
|
+
import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile14, rm as rm2 } from "fs/promises";
|
|
9087
9273
|
import { existsSync as existsSync53 } from "fs";
|
|
9088
|
-
import
|
|
9274
|
+
import path34 from "path";
|
|
9089
9275
|
import "commander";
|
|
9090
9276
|
import {
|
|
9091
9277
|
buildFrontmatter as buildFrontmatter10,
|
|
9092
|
-
findProjectRoot as
|
|
9278
|
+
findProjectRoot as findProjectRoot32,
|
|
9093
9279
|
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
9094
9280
|
memoryFilePath as memoryFilePath9,
|
|
9095
|
-
resolveHaivePaths as
|
|
9281
|
+
resolveHaivePaths as resolveHaivePaths29,
|
|
9096
9282
|
serializeMemory as serializeMemory21
|
|
9097
9283
|
} from "@hiveai/core";
|
|
9098
9284
|
async function buildAutoRecap(paths) {
|
|
9099
|
-
const obsFile =
|
|
9285
|
+
const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
9100
9286
|
if (!existsSync53(obsFile)) return null;
|
|
9101
9287
|
const raw = await readFile14(obsFile, "utf8").catch(() => "");
|
|
9102
9288
|
if (!raw.trim()) return null;
|
|
@@ -9176,8 +9362,8 @@ function registerSessionEnd(session2) {
|
|
|
9176
9362
|
--next "Add integration tests for webhook signature validation"
|
|
9177
9363
|
`
|
|
9178
9364
|
).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9179
|
-
const root =
|
|
9180
|
-
const paths =
|
|
9365
|
+
const root = findProjectRoot32(opts.dir);
|
|
9366
|
+
const paths = resolveHaivePaths29(root);
|
|
9181
9367
|
if (!existsSync53(paths.haiveDir)) {
|
|
9182
9368
|
if (opts.auto || opts.quiet) return;
|
|
9183
9369
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
@@ -9210,14 +9396,14 @@ function registerSessionEnd(session2) {
|
|
|
9210
9396
|
});
|
|
9211
9397
|
const topic = recapTopic2(scope, opts.module);
|
|
9212
9398
|
const filesTouched = parseCsv5(resolvedFiles);
|
|
9213
|
-
const missingPaths = filesTouched.filter((p) => !existsSync53(
|
|
9399
|
+
const missingPaths = filesTouched.filter((p) => !existsSync53(path34.resolve(root, p)));
|
|
9214
9400
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
9215
9401
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
9216
9402
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
9217
9403
|
}
|
|
9218
9404
|
const cleanupObservations = async () => {
|
|
9219
9405
|
if (!opts.auto) return;
|
|
9220
|
-
const obsFile =
|
|
9406
|
+
const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
9221
9407
|
if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
|
|
9222
9408
|
});
|
|
9223
9409
|
};
|
|
@@ -9241,7 +9427,7 @@ function registerSessionEnd(session2) {
|
|
|
9241
9427
|
await cleanupObservations();
|
|
9242
9428
|
if (!opts.quiet) {
|
|
9243
9429
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
9244
|
-
ui.info(`id=${fm.id} file=${
|
|
9430
|
+
ui.info(`id=${fm.id} file=${path34.relative(root, topicMatch.filePath)}`);
|
|
9245
9431
|
ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
|
|
9246
9432
|
}
|
|
9247
9433
|
return;
|
|
@@ -9258,12 +9444,12 @@ function registerSessionEnd(session2) {
|
|
|
9258
9444
|
status: "validated"
|
|
9259
9445
|
});
|
|
9260
9446
|
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9261
|
-
await
|
|
9447
|
+
await mkdir15(path34.dirname(file), { recursive: true });
|
|
9262
9448
|
await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
|
|
9263
9449
|
await cleanupObservations();
|
|
9264
9450
|
if (!opts.quiet) {
|
|
9265
9451
|
ui.success(`Session recap created`);
|
|
9266
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
9452
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path34.relative(root, file)}`);
|
|
9267
9453
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
9268
9454
|
ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
|
|
9269
9455
|
}
|
|
@@ -9277,13 +9463,13 @@ function parseCsv5(value) {
|
|
|
9277
9463
|
// src/commands/snapshot.ts
|
|
9278
9464
|
import { existsSync as existsSync54 } from "fs";
|
|
9279
9465
|
import { readdir as readdir4 } from "fs/promises";
|
|
9280
|
-
import
|
|
9466
|
+
import path35 from "path";
|
|
9281
9467
|
import "commander";
|
|
9282
9468
|
import {
|
|
9283
9469
|
diffContract,
|
|
9284
|
-
findProjectRoot as
|
|
9470
|
+
findProjectRoot as findProjectRoot33,
|
|
9285
9471
|
loadConfig as loadConfig5,
|
|
9286
|
-
resolveHaivePaths as
|
|
9472
|
+
resolveHaivePaths as resolveHaivePaths30,
|
|
9287
9473
|
snapshotContract
|
|
9288
9474
|
} from "@hiveai/core";
|
|
9289
9475
|
function registerSnapshot(program2) {
|
|
@@ -9308,15 +9494,15 @@ function registerSnapshot(program2) {
|
|
|
9308
9494
|
"--format <format>",
|
|
9309
9495
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
9310
9496
|
).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) => {
|
|
9311
|
-
const root =
|
|
9312
|
-
const paths =
|
|
9497
|
+
const root = findProjectRoot33(opts.dir);
|
|
9498
|
+
const paths = resolveHaivePaths30(root);
|
|
9313
9499
|
if (!existsSync54(paths.haiveDir)) {
|
|
9314
9500
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
9315
9501
|
process.exitCode = 1;
|
|
9316
9502
|
return;
|
|
9317
9503
|
}
|
|
9318
9504
|
if (opts.list) {
|
|
9319
|
-
const contractsDir =
|
|
9505
|
+
const contractsDir = path35.join(paths.haiveDir, "contracts");
|
|
9320
9506
|
if (!existsSync54(contractsDir)) {
|
|
9321
9507
|
console.log(ui.dim("No contract snapshots found."));
|
|
9322
9508
|
return;
|
|
@@ -9372,7 +9558,7 @@ function registerSnapshot(program2) {
|
|
|
9372
9558
|
return;
|
|
9373
9559
|
}
|
|
9374
9560
|
const contractPath = opts.contract;
|
|
9375
|
-
const name = opts.name ??
|
|
9561
|
+
const name = opts.name ?? path35.basename(contractPath, path35.extname(contractPath));
|
|
9376
9562
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
9377
9563
|
const contract = { name, path: contractPath, format };
|
|
9378
9564
|
try {
|
|
@@ -9427,8 +9613,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
9427
9613
|
}
|
|
9428
9614
|
}
|
|
9429
9615
|
function detectFormat(filePath) {
|
|
9430
|
-
const ext =
|
|
9431
|
-
const base =
|
|
9616
|
+
const ext = path35.extname(filePath).toLowerCase();
|
|
9617
|
+
const base = path35.basename(filePath).toLowerCase();
|
|
9432
9618
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
9433
9619
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
9434
9620
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -9442,15 +9628,15 @@ function detectFormat(filePath) {
|
|
|
9442
9628
|
|
|
9443
9629
|
// src/commands/hub.ts
|
|
9444
9630
|
import { existsSync as existsSync55 } from "fs";
|
|
9445
|
-
import { mkdir as
|
|
9446
|
-
import
|
|
9447
|
-
import { spawnSync as
|
|
9631
|
+
import { mkdir as mkdir16, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
|
|
9632
|
+
import path36 from "path";
|
|
9633
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
9448
9634
|
import "commander";
|
|
9449
9635
|
import {
|
|
9450
|
-
findProjectRoot as
|
|
9636
|
+
findProjectRoot as findProjectRoot34,
|
|
9451
9637
|
loadConfig as loadConfig6,
|
|
9452
9638
|
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
9453
|
-
resolveHaivePaths as
|
|
9639
|
+
resolveHaivePaths as resolveHaivePaths31,
|
|
9454
9640
|
saveConfig as saveConfig2,
|
|
9455
9641
|
serializeMemory as serializeMemory23
|
|
9456
9642
|
} from "@hiveai/core";
|
|
@@ -9462,21 +9648,21 @@ function registerHub(program2) {
|
|
|
9462
9648
|
hub.command("init <hubPath>").description(
|
|
9463
9649
|
"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"
|
|
9464
9650
|
).action(async (hubPath) => {
|
|
9465
|
-
const absPath =
|
|
9466
|
-
await
|
|
9467
|
-
const gitCheck =
|
|
9651
|
+
const absPath = path36.resolve(hubPath);
|
|
9652
|
+
await mkdir16(absPath, { recursive: true });
|
|
9653
|
+
const gitCheck = spawnSync4("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
9468
9654
|
if (gitCheck.status !== 0) {
|
|
9469
|
-
const init =
|
|
9655
|
+
const init = spawnSync4("git", ["init"], { cwd: absPath, encoding: "utf8" });
|
|
9470
9656
|
if (init.status !== 0) {
|
|
9471
9657
|
ui.error(`git init failed: ${init.stderr}`);
|
|
9472
9658
|
process.exitCode = 1;
|
|
9473
9659
|
return;
|
|
9474
9660
|
}
|
|
9475
9661
|
}
|
|
9476
|
-
const sharedDir =
|
|
9477
|
-
await
|
|
9662
|
+
const sharedDir = path36.join(absPath, ".ai", "memories", "shared");
|
|
9663
|
+
await mkdir16(sharedDir, { recursive: true });
|
|
9478
9664
|
await writeFile26(
|
|
9479
|
-
|
|
9665
|
+
path36.join(absPath, ".ai", "README.md"),
|
|
9480
9666
|
`# hAIve Team Knowledge Hub
|
|
9481
9667
|
|
|
9482
9668
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -9498,12 +9684,12 @@ haive hub pull # import into a project
|
|
|
9498
9684
|
"utf8"
|
|
9499
9685
|
);
|
|
9500
9686
|
await writeFile26(
|
|
9501
|
-
|
|
9687
|
+
path36.join(absPath, ".gitignore"),
|
|
9502
9688
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
9503
9689
|
"utf8"
|
|
9504
9690
|
);
|
|
9505
|
-
|
|
9506
|
-
|
|
9691
|
+
spawnSync4("git", ["add", "."], { cwd: absPath });
|
|
9692
|
+
spawnSync4("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
|
|
9507
9693
|
cwd: absPath,
|
|
9508
9694
|
encoding: "utf8"
|
|
9509
9695
|
});
|
|
@@ -9513,7 +9699,7 @@ haive hub pull # import into a project
|
|
|
9513
9699
|
`
|
|
9514
9700
|
Next steps:
|
|
9515
9701
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
9516
|
-
{ "hubPath": "${
|
|
9702
|
+
{ "hubPath": "${path36.relative(process.cwd(), absPath)}" }
|
|
9517
9703
|
2. Run \`haive hub push\` to publish your shared memories
|
|
9518
9704
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
9519
9705
|
`
|
|
@@ -9532,8 +9718,8 @@ Next steps:
|
|
|
9532
9718
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
9533
9719
|
`
|
|
9534
9720
|
).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
|
|
9535
|
-
const root =
|
|
9536
|
-
const paths =
|
|
9721
|
+
const root = findProjectRoot34(opts.dir);
|
|
9722
|
+
const paths = resolveHaivePaths31(root);
|
|
9537
9723
|
const config = await loadConfig6(paths);
|
|
9538
9724
|
if (!config.hubPath) {
|
|
9539
9725
|
ui.error(
|
|
@@ -9542,15 +9728,15 @@ Next steps:
|
|
|
9542
9728
|
process.exitCode = 1;
|
|
9543
9729
|
return;
|
|
9544
9730
|
}
|
|
9545
|
-
const hubRoot =
|
|
9731
|
+
const hubRoot = path36.resolve(root, config.hubPath);
|
|
9546
9732
|
if (!existsSync55(hubRoot)) {
|
|
9547
9733
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
9548
9734
|
process.exitCode = 1;
|
|
9549
9735
|
return;
|
|
9550
9736
|
}
|
|
9551
|
-
const projectName =
|
|
9552
|
-
const destDir =
|
|
9553
|
-
await
|
|
9737
|
+
const projectName = path36.basename(root);
|
|
9738
|
+
const destDir = path36.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
9739
|
+
await mkdir16(destDir, { recursive: true });
|
|
9554
9740
|
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
9555
9741
|
const shared = all.filter(
|
|
9556
9742
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
@@ -9568,7 +9754,7 @@ Next steps:
|
|
|
9568
9754
|
for (const { memory: memory2 } of shared) {
|
|
9569
9755
|
const fm = memory2.frontmatter;
|
|
9570
9756
|
const fileName = `${fm.id}.md`;
|
|
9571
|
-
const destPath =
|
|
9757
|
+
const destPath = path36.join(destDir, fileName);
|
|
9572
9758
|
await writeFile26(destPath, serializeMemory23(memory2), "utf8");
|
|
9573
9759
|
pushed++;
|
|
9574
9760
|
}
|
|
@@ -9576,10 +9762,10 @@ Next steps:
|
|
|
9576
9762
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
9577
9763
|
if (opts.commit) {
|
|
9578
9764
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
9579
|
-
|
|
9765
|
+
spawnSync4("git", ["add", path36.join(".ai", "memories", "shared", projectName)], {
|
|
9580
9766
|
cwd: hubRoot
|
|
9581
9767
|
});
|
|
9582
|
-
const commit =
|
|
9768
|
+
const commit = spawnSync4("git", ["commit", "-m", message], {
|
|
9583
9769
|
cwd: hubRoot,
|
|
9584
9770
|
encoding: "utf8"
|
|
9585
9771
|
});
|
|
@@ -9601,8 +9787,8 @@ Next steps:
|
|
|
9601
9787
|
hub.command("pull").description(
|
|
9602
9788
|
"Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
|
|
9603
9789
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9604
|
-
const root =
|
|
9605
|
-
const paths =
|
|
9790
|
+
const root = findProjectRoot34(opts.dir);
|
|
9791
|
+
const paths = resolveHaivePaths31(root);
|
|
9606
9792
|
const config = await loadConfig6(paths);
|
|
9607
9793
|
if (!config.hubPath) {
|
|
9608
9794
|
ui.error(
|
|
@@ -9611,13 +9797,13 @@ Next steps:
|
|
|
9611
9797
|
process.exitCode = 1;
|
|
9612
9798
|
return;
|
|
9613
9799
|
}
|
|
9614
|
-
const hubRoot =
|
|
9615
|
-
const hubSharedDir =
|
|
9800
|
+
const hubRoot = path36.resolve(root, config.hubPath);
|
|
9801
|
+
const hubSharedDir = path36.join(hubRoot, ".ai", "memories", "shared");
|
|
9616
9802
|
if (!existsSync55(hubSharedDir)) {
|
|
9617
9803
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
9618
9804
|
return;
|
|
9619
9805
|
}
|
|
9620
|
-
const projectName =
|
|
9806
|
+
const projectName = path36.basename(root);
|
|
9621
9807
|
const { readdir: readdir6 } = await import("fs/promises");
|
|
9622
9808
|
const projectDirs = (await readdir6(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
9623
9809
|
if (projectDirs.length === 0) {
|
|
@@ -9627,16 +9813,16 @@ Next steps:
|
|
|
9627
9813
|
let totalImported = 0;
|
|
9628
9814
|
let totalUpdated = 0;
|
|
9629
9815
|
for (const sourceName of projectDirs) {
|
|
9630
|
-
const sourceDir =
|
|
9631
|
-
const destDir =
|
|
9632
|
-
await
|
|
9816
|
+
const sourceDir = path36.join(hubSharedDir, sourceName);
|
|
9817
|
+
const destDir = path36.join(paths.memoriesDir, "shared", sourceName);
|
|
9818
|
+
await mkdir16(destDir, { recursive: true });
|
|
9633
9819
|
const sourceFiles = (await readdir6(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
9634
9820
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
9635
9821
|
const existingInDest = await loadDir(destDir);
|
|
9636
9822
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
9637
9823
|
for (const file of sourceFiles) {
|
|
9638
|
-
const srcPath =
|
|
9639
|
-
const destPath =
|
|
9824
|
+
const srcPath = path36.join(sourceDir, file);
|
|
9825
|
+
const destPath = path36.join(destDir, file);
|
|
9640
9826
|
const fileContent = await readFile15(srcPath, "utf8");
|
|
9641
9827
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
9642
9828
|
if (!alreadyTagged) {
|
|
@@ -9660,21 +9846,21 @@ Next steps:
|
|
|
9660
9846
|
);
|
|
9661
9847
|
});
|
|
9662
9848
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9663
|
-
const root =
|
|
9664
|
-
const paths =
|
|
9849
|
+
const root = findProjectRoot34(opts.dir);
|
|
9850
|
+
const paths = resolveHaivePaths31(root);
|
|
9665
9851
|
const config = await loadConfig6(paths);
|
|
9666
9852
|
console.log(ui.bold("Hub status"));
|
|
9667
9853
|
console.log(
|
|
9668
9854
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
9669
9855
|
);
|
|
9670
|
-
const sharedDir =
|
|
9856
|
+
const sharedDir = path36.join(paths.memoriesDir, "shared");
|
|
9671
9857
|
if (existsSync55(sharedDir)) {
|
|
9672
9858
|
const { readdir: readdir6 } = await import("fs/promises");
|
|
9673
9859
|
const sources = (await readdir6(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
9674
9860
|
console.log(`
|
|
9675
9861
|
Imported from ${sources.length} source(s):`);
|
|
9676
9862
|
for (const src of sources) {
|
|
9677
|
-
const files = (await readdir6(
|
|
9863
|
+
const files = (await readdir6(path36.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
9678
9864
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
9679
9865
|
}
|
|
9680
9866
|
} else {
|
|
@@ -9698,16 +9884,16 @@ Next steps:
|
|
|
9698
9884
|
// src/commands/stats.ts
|
|
9699
9885
|
import "commander";
|
|
9700
9886
|
import { existsSync as existsSync56 } from "fs";
|
|
9701
|
-
import { mkdir as
|
|
9702
|
-
import
|
|
9887
|
+
import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
|
|
9888
|
+
import path37 from "path";
|
|
9703
9889
|
import {
|
|
9704
9890
|
aggregateUsage,
|
|
9705
|
-
findProjectRoot as
|
|
9891
|
+
findProjectRoot as findProjectRoot35,
|
|
9706
9892
|
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
9707
9893
|
loadUsageIndex as loadUsageIndex23,
|
|
9708
9894
|
parseSince,
|
|
9709
9895
|
readUsageEvents as readUsageEvents2,
|
|
9710
|
-
resolveHaivePaths as
|
|
9896
|
+
resolveHaivePaths as resolveHaivePaths32,
|
|
9711
9897
|
usageLogSize
|
|
9712
9898
|
} from "@hiveai/core";
|
|
9713
9899
|
function registerStats(program2) {
|
|
@@ -9716,8 +9902,8 @@ function registerStats(program2) {
|
|
|
9716
9902
|
"write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
|
|
9717
9903
|
void 0
|
|
9718
9904
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9719
|
-
const root =
|
|
9720
|
-
const paths =
|
|
9905
|
+
const root = findProjectRoot35(opts.dir);
|
|
9906
|
+
const paths = resolveHaivePaths32(root);
|
|
9721
9907
|
if (opts.exportReport) {
|
|
9722
9908
|
await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
|
|
9723
9909
|
return;
|
|
@@ -9771,7 +9957,7 @@ function registerStats(program2) {
|
|
|
9771
9957
|
});
|
|
9772
9958
|
}
|
|
9773
9959
|
async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
9774
|
-
const outAbs =
|
|
9960
|
+
const outAbs = path37.isAbsolute(outRelative) ? path37.resolve(outRelative) : path37.resolve(root, outRelative);
|
|
9775
9961
|
const size = await usageLogSize(paths);
|
|
9776
9962
|
let events = await readUsageEvents2(paths);
|
|
9777
9963
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
@@ -9806,7 +9992,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
9806
9992
|
ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
|
|
9807
9993
|
events = [];
|
|
9808
9994
|
}
|
|
9809
|
-
await
|
|
9995
|
+
await mkdir17(path37.dirname(outAbs), { recursive: true });
|
|
9810
9996
|
const payload = {
|
|
9811
9997
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9812
9998
|
project_root: root,
|
|
@@ -9870,13 +10056,13 @@ import { performance } from "perf_hooks";
|
|
|
9870
10056
|
import "commander";
|
|
9871
10057
|
import {
|
|
9872
10058
|
estimateTokens as estimateTokens3,
|
|
9873
|
-
findProjectRoot as
|
|
9874
|
-
resolveHaivePaths as
|
|
10059
|
+
findProjectRoot as findProjectRoot36,
|
|
10060
|
+
resolveHaivePaths as resolveHaivePaths33
|
|
9875
10061
|
} from "@hiveai/core";
|
|
9876
10062
|
function registerBench(program2) {
|
|
9877
10063
|
program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9878
|
-
const root =
|
|
9879
|
-
const paths =
|
|
10064
|
+
const root = findProjectRoot36(opts.dir);
|
|
10065
|
+
const paths = resolveHaivePaths33(root);
|
|
9880
10066
|
const ctx = { paths };
|
|
9881
10067
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
9882
10068
|
const scenarios = [
|
|
@@ -9997,9 +10183,9 @@ function summarize(name, t0, payload, notes) {
|
|
|
9997
10183
|
// src/commands/benchmark.ts
|
|
9998
10184
|
import { existsSync as existsSync57 } from "fs";
|
|
9999
10185
|
import { readdir as readdir5, readFile as readFile16, writeFile as writeFile28 } from "fs/promises";
|
|
10000
|
-
import
|
|
10186
|
+
import path38 from "path";
|
|
10001
10187
|
import "commander";
|
|
10002
|
-
import { estimateTokens as estimateTokens4, findProjectRoot as
|
|
10188
|
+
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot37 } from "@hiveai/core";
|
|
10003
10189
|
function registerBenchmark(program2) {
|
|
10004
10190
|
const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
|
|
10005
10191
|
benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
|
|
@@ -10012,9 +10198,9 @@ function registerBenchmark(program2) {
|
|
|
10012
10198
|
}
|
|
10013
10199
|
const markdown = renderMarkdown(root, summary, rows);
|
|
10014
10200
|
if (opts.out) {
|
|
10015
|
-
const outFile =
|
|
10201
|
+
const outFile = path38.isAbsolute(opts.out) ? opts.out : path38.join(root, opts.out);
|
|
10016
10202
|
await writeFile28(outFile, markdown, "utf8");
|
|
10017
|
-
ui.success(`wrote ${
|
|
10203
|
+
ui.success(`wrote ${path38.relative(process.cwd(), outFile)}`);
|
|
10018
10204
|
return;
|
|
10019
10205
|
}
|
|
10020
10206
|
console.log(markdown);
|
|
@@ -10038,9 +10224,9 @@ function registerBenchmark(program2) {
|
|
|
10038
10224
|
}
|
|
10039
10225
|
function resolveBenchmarkRoot(dir) {
|
|
10040
10226
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
10041
|
-
if (
|
|
10042
|
-
const projectRoot =
|
|
10043
|
-
return
|
|
10227
|
+
if (path38.isAbsolute(candidate)) return candidate;
|
|
10228
|
+
const projectRoot = findProjectRoot37(process.cwd());
|
|
10229
|
+
return path38.join(projectRoot, candidate);
|
|
10044
10230
|
}
|
|
10045
10231
|
async function collectRows(root) {
|
|
10046
10232
|
if (!existsSync57(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
@@ -10048,8 +10234,8 @@ async function collectRows(root) {
|
|
|
10048
10234
|
const rows = [];
|
|
10049
10235
|
for (const entry of entries) {
|
|
10050
10236
|
if (!entry.isDirectory()) continue;
|
|
10051
|
-
const fixtureDir =
|
|
10052
|
-
const reportFile =
|
|
10237
|
+
const fixtureDir = path38.join(root, entry.name);
|
|
10238
|
+
const reportFile = path38.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
10053
10239
|
if (!existsSync57(reportFile)) continue;
|
|
10054
10240
|
const report = await readFile16(reportFile, "utf8");
|
|
10055
10241
|
rows.push(parseAgentReport(entry.name, report));
|
|
@@ -10140,19 +10326,19 @@ function escapeRegExp(value) {
|
|
|
10140
10326
|
}
|
|
10141
10327
|
|
|
10142
10328
|
// src/commands/memory-suggest.ts
|
|
10143
|
-
import { mkdir as
|
|
10329
|
+
import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
|
|
10144
10330
|
import { existsSync as existsSync58 } from "fs";
|
|
10145
|
-
import
|
|
10331
|
+
import path39 from "path";
|
|
10146
10332
|
import "commander";
|
|
10147
10333
|
import {
|
|
10148
10334
|
aggregateUsage as aggregateUsage2,
|
|
10149
10335
|
buildFrontmatter as buildFrontmatter11,
|
|
10150
|
-
findProjectRoot as
|
|
10336
|
+
findProjectRoot as findProjectRoot38,
|
|
10151
10337
|
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
10152
10338
|
memoryFilePath as memoryFilePath10,
|
|
10153
10339
|
parseSince as parseSince2,
|
|
10154
10340
|
readUsageEvents as readUsageEvents3,
|
|
10155
|
-
resolveHaivePaths as
|
|
10341
|
+
resolveHaivePaths as resolveHaivePaths34,
|
|
10156
10342
|
serializeMemory as serializeMemory24
|
|
10157
10343
|
} from "@hiveai/core";
|
|
10158
10344
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -10165,8 +10351,8 @@ function registerMemorySuggest(memory2) {
|
|
|
10165
10351
|
memory2.command("suggest").description(
|
|
10166
10352
|
"Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to draft the top-N suggestions as draft memories. They land\n in personal scope by default with status=draft, ready for you to edit and promote."
|
|
10167
10353
|
).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of drafted memories (personal | team)", "personal").option("--auto-save", "draft top-N suggestions as draft memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10168
|
-
const root =
|
|
10169
|
-
const paths =
|
|
10354
|
+
const root = findProjectRoot38(opts.dir);
|
|
10355
|
+
const paths = resolveHaivePaths34(root);
|
|
10170
10356
|
const events = await readUsageEvents3(paths);
|
|
10171
10357
|
if (events.length === 0) {
|
|
10172
10358
|
if (opts.json) {
|
|
@@ -10235,13 +10421,13 @@ function registerMemorySuggest(memory2) {
|
|
|
10235
10421
|
fm.status = "draft";
|
|
10236
10422
|
const body = renderTemplate(s);
|
|
10237
10423
|
const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
|
|
10238
|
-
await
|
|
10424
|
+
await mkdir18(path39.dirname(file), { recursive: true });
|
|
10239
10425
|
if (existsSync58(file)) {
|
|
10240
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
10426
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path39.relative(root, file)}` });
|
|
10241
10427
|
continue;
|
|
10242
10428
|
}
|
|
10243
10429
|
await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
|
|
10244
|
-
created.push({ id: fm.id, file:
|
|
10430
|
+
created.push({ id: fm.id, file: path39.relative(root, file), query: s.query });
|
|
10245
10431
|
}
|
|
10246
10432
|
if (opts.json) {
|
|
10247
10433
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -10336,14 +10522,14 @@ function truncate2(text, max) {
|
|
|
10336
10522
|
// src/commands/memory-archive.ts
|
|
10337
10523
|
import { existsSync as existsSync59 } from "fs";
|
|
10338
10524
|
import { writeFile as writeFile30 } from "fs/promises";
|
|
10339
|
-
import
|
|
10525
|
+
import path40 from "path";
|
|
10340
10526
|
import "commander";
|
|
10341
10527
|
import {
|
|
10342
|
-
findProjectRoot as
|
|
10528
|
+
findProjectRoot as findProjectRoot39,
|
|
10343
10529
|
getUsage as getUsage18,
|
|
10344
10530
|
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
10345
10531
|
loadUsageIndex as loadUsageIndex24,
|
|
10346
|
-
resolveHaivePaths as
|
|
10532
|
+
resolveHaivePaths as resolveHaivePaths35,
|
|
10347
10533
|
serializeMemory as serializeMemory25
|
|
10348
10534
|
} from "@hiveai/core";
|
|
10349
10535
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
@@ -10351,8 +10537,8 @@ function registerMemoryArchive(memory2) {
|
|
|
10351
10537
|
memory2.command("archive").description(
|
|
10352
10538
|
"Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
|
|
10353
10539
|
).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) => {
|
|
10354
|
-
const root =
|
|
10355
|
-
const paths =
|
|
10540
|
+
const root = findProjectRoot39(opts.dir);
|
|
10541
|
+
const paths = resolveHaivePaths35(root);
|
|
10356
10542
|
if (!existsSync59(paths.memoriesDir)) {
|
|
10357
10543
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10358
10544
|
process.exitCode = 1;
|
|
@@ -10374,7 +10560,7 @@ function registerMemoryArchive(memory2) {
|
|
|
10374
10560
|
if (typeFilter && fm.type !== typeFilter) continue;
|
|
10375
10561
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
10376
10562
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
10377
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync59(
|
|
10563
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync59(path40.join(paths.root, p)));
|
|
10378
10564
|
const isAnchorless = !hasAnyAnchor;
|
|
10379
10565
|
if (!isAnchorless && !allPathsGone) continue;
|
|
10380
10566
|
const u = getUsage18(usage, fm.id);
|
|
@@ -10450,27 +10636,27 @@ function parseDays(input) {
|
|
|
10450
10636
|
// src/commands/doctor.ts
|
|
10451
10637
|
import { existsSync as existsSync60 } from "fs";
|
|
10452
10638
|
import { stat } from "fs/promises";
|
|
10453
|
-
import
|
|
10639
|
+
import path41 from "path";
|
|
10454
10640
|
import { execSync as execSync3 } from "child_process";
|
|
10455
10641
|
import "commander";
|
|
10456
10642
|
import {
|
|
10457
10643
|
codeMapPath as codeMapPath2,
|
|
10458
|
-
findProjectRoot as
|
|
10644
|
+
findProjectRoot as findProjectRoot40,
|
|
10459
10645
|
getUsage as getUsage19,
|
|
10460
10646
|
loadCodeMap as loadCodeMap5,
|
|
10461
10647
|
loadConfig as loadConfig7,
|
|
10462
10648
|
loadMemoriesFromDir as loadMemoriesFromDir32,
|
|
10463
10649
|
loadUsageIndex as loadUsageIndex25,
|
|
10464
10650
|
readUsageEvents as readUsageEvents4,
|
|
10465
|
-
resolveHaivePaths as
|
|
10651
|
+
resolveHaivePaths as resolveHaivePaths36
|
|
10466
10652
|
} from "@hiveai/core";
|
|
10467
10653
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
|
|
10468
10654
|
function registerDoctor(program2) {
|
|
10469
10655
|
program2.command("doctor").description(
|
|
10470
10656
|
"Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to suggest commands you can copy-paste."
|
|
10471
10657
|
).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10472
|
-
const root =
|
|
10473
|
-
const paths =
|
|
10658
|
+
const root = findProjectRoot40(opts.dir);
|
|
10659
|
+
const paths = resolveHaivePaths36(root);
|
|
10474
10660
|
const findings = [];
|
|
10475
10661
|
if (!existsSync60(paths.haiveDir)) {
|
|
10476
10662
|
findings.push({
|
|
@@ -10612,7 +10798,7 @@ function registerDoctor(program2) {
|
|
|
10612
10798
|
}
|
|
10613
10799
|
const config = await loadConfig7(paths);
|
|
10614
10800
|
if (config.enforcement?.requireBriefingFirst) {
|
|
10615
|
-
const claudeSettings =
|
|
10801
|
+
const claudeSettings = path41.join(root, ".claude", "settings.local.json");
|
|
10616
10802
|
let hasClaudeEnforcement = false;
|
|
10617
10803
|
if (existsSync60(claudeSettings)) {
|
|
10618
10804
|
try {
|
|
@@ -10646,7 +10832,7 @@ function registerDoctor(program2) {
|
|
|
10646
10832
|
timeout: 3e3,
|
|
10647
10833
|
stdio: ["ignore", "pipe", "ignore"]
|
|
10648
10834
|
}).trim();
|
|
10649
|
-
const cliVersion = "0.9.
|
|
10835
|
+
const cliVersion = "0.9.12";
|
|
10650
10836
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
10651
10837
|
findings.push({
|
|
10652
10838
|
severity: "warn",
|
|
@@ -10698,19 +10884,19 @@ function isSearchTool(name) {
|
|
|
10698
10884
|
import { existsSync as existsSync61 } from "fs";
|
|
10699
10885
|
import "commander";
|
|
10700
10886
|
import {
|
|
10701
|
-
findProjectRoot as
|
|
10887
|
+
findProjectRoot as findProjectRoot41,
|
|
10702
10888
|
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
10703
10889
|
parseSince as parseSince3,
|
|
10704
10890
|
readUsageEvents as readUsageEvents5,
|
|
10705
|
-
resolveHaivePaths as
|
|
10891
|
+
resolveHaivePaths as resolveHaivePaths37
|
|
10706
10892
|
} from "@hiveai/core";
|
|
10707
10893
|
var MS_PER_MINUTE = 6e4;
|
|
10708
10894
|
function registerPlayback(program2) {
|
|
10709
10895
|
program2.command("playback").description(
|
|
10710
10896
|
"Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
|
|
10711
10897
|
).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10712
|
-
const root =
|
|
10713
|
-
const paths =
|
|
10898
|
+
const root = findProjectRoot41(opts.dir);
|
|
10899
|
+
const paths = resolveHaivePaths37(root);
|
|
10714
10900
|
const events = await readUsageEvents5(paths);
|
|
10715
10901
|
if (events.length === 0) {
|
|
10716
10902
|
if (opts.json) {
|
|
@@ -10815,8 +11001,8 @@ function truncate3(text, max) {
|
|
|
10815
11001
|
import { spawn as spawn4 } from "child_process";
|
|
10816
11002
|
import "commander";
|
|
10817
11003
|
import {
|
|
10818
|
-
findProjectRoot as
|
|
10819
|
-
resolveHaivePaths as
|
|
11004
|
+
findProjectRoot as findProjectRoot42,
|
|
11005
|
+
resolveHaivePaths as resolveHaivePaths38
|
|
10820
11006
|
} from "@hiveai/core";
|
|
10821
11007
|
function registerPrecommit(program2) {
|
|
10822
11008
|
program2.command("precommit").description(
|
|
@@ -10826,8 +11012,8 @@ function registerPrecommit(program2) {
|
|
|
10826
11012
|
"'any' | 'high-confidence' (default) | 'never' (report only)",
|
|
10827
11013
|
"high-confidence"
|
|
10828
11014
|
).option("--no-semantic", "disable semantic search in anti-patterns matching").option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10829
|
-
const root =
|
|
10830
|
-
const paths =
|
|
11015
|
+
const root = findProjectRoot42(opts.dir);
|
|
11016
|
+
const paths = resolveHaivePaths38(root);
|
|
10831
11017
|
const ctx = { paths };
|
|
10832
11018
|
let diff = "";
|
|
10833
11019
|
let touchedPaths = opts.paths ?? [];
|
|
@@ -10922,9 +11108,9 @@ function runCommand3(cmd, args, cwd) {
|
|
|
10922
11108
|
import { existsSync as existsSync63 } from "fs";
|
|
10923
11109
|
import "commander";
|
|
10924
11110
|
import {
|
|
10925
|
-
findProjectRoot as
|
|
11111
|
+
findProjectRoot as findProjectRoot43,
|
|
10926
11112
|
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
10927
|
-
resolveHaivePaths as
|
|
11113
|
+
resolveHaivePaths as resolveHaivePaths39
|
|
10928
11114
|
} from "@hiveai/core";
|
|
10929
11115
|
var TYPE_RANK = {
|
|
10930
11116
|
decision: 0,
|
|
@@ -10938,8 +11124,8 @@ function registerWelcome(program2) {
|
|
|
10938
11124
|
program2.command("welcome").description(
|
|
10939
11125
|
"Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
|
|
10940
11126
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10941
|
-
const root =
|
|
10942
|
-
const paths =
|
|
11127
|
+
const root = findProjectRoot43(opts.dir);
|
|
11128
|
+
const paths = resolveHaivePaths39(root);
|
|
10943
11129
|
if (!existsSync63(paths.memoriesDir)) {
|
|
10944
11130
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
10945
11131
|
process.exitCode = 1;
|
|
@@ -10984,12 +11170,12 @@ function registerWelcome(program2) {
|
|
|
10984
11170
|
import { existsSync as existsSync64 } from "fs";
|
|
10985
11171
|
import "commander";
|
|
10986
11172
|
import {
|
|
10987
|
-
findProjectRoot as
|
|
11173
|
+
findProjectRoot as findProjectRoot44,
|
|
10988
11174
|
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
10989
|
-
resolveHaivePaths as
|
|
11175
|
+
resolveHaivePaths as resolveHaivePaths40
|
|
10990
11176
|
} from "@hiveai/core";
|
|
10991
11177
|
async function lintMemoriesAsync(root) {
|
|
10992
|
-
const paths =
|
|
11178
|
+
const paths = resolveHaivePaths40(root);
|
|
10993
11179
|
const out = [];
|
|
10994
11180
|
if (!existsSync64(paths.memoriesDir)) return out;
|
|
10995
11181
|
const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
|
|
@@ -11052,7 +11238,7 @@ function registerMemoryLint(parent) {
|
|
|
11052
11238
|
parent.command("lint").description(
|
|
11053
11239
|
"Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
|
|
11054
11240
|
).option("--json", "emit findings as JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11055
|
-
const root =
|
|
11241
|
+
const root = findProjectRoot44(opts.dir);
|
|
11056
11242
|
const findings = await lintMemoriesAsync(root);
|
|
11057
11243
|
if (opts.json) {
|
|
11058
11244
|
console.log(JSON.stringify({ findings_count: findings.length, findings }, null, 2));
|
|
@@ -11098,27 +11284,27 @@ function registerMemorySuggestTopic(memory2) {
|
|
|
11098
11284
|
}
|
|
11099
11285
|
|
|
11100
11286
|
// src/commands/resolve-project.ts
|
|
11101
|
-
import
|
|
11287
|
+
import path43 from "path";
|
|
11102
11288
|
import "commander";
|
|
11103
11289
|
import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
|
|
11104
11290
|
function registerResolveProject(program2) {
|
|
11105
11291
|
program2.command("resolve-project").description(
|
|
11106
11292
|
"Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
11107
11293
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
11108
|
-
const info = resolveProjectInfo2({ cwd:
|
|
11294
|
+
const info = resolveProjectInfo2({ cwd: path43.resolve(opts.dir) });
|
|
11109
11295
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
11110
11296
|
});
|
|
11111
11297
|
}
|
|
11112
11298
|
|
|
11113
11299
|
// src/commands/runtime-journal.ts
|
|
11114
11300
|
import { existsSync as existsSync65 } from "fs";
|
|
11115
|
-
import
|
|
11301
|
+
import path44 from "path";
|
|
11116
11302
|
import "commander";
|
|
11117
11303
|
import {
|
|
11118
11304
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
11119
|
-
findProjectRoot as
|
|
11305
|
+
findProjectRoot as findProjectRoot45,
|
|
11120
11306
|
readRuntimeJournalTail as readRuntimeJournalTail2,
|
|
11121
|
-
resolveHaivePaths as
|
|
11307
|
+
resolveHaivePaths as resolveHaivePaths41
|
|
11122
11308
|
} from "@hiveai/core";
|
|
11123
11309
|
function registerRuntime(program2) {
|
|
11124
11310
|
const runtime = program2.command("runtime").description(
|
|
@@ -11126,16 +11312,16 @@ function registerRuntime(program2) {
|
|
|
11126
11312
|
);
|
|
11127
11313
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
11128
11314
|
journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
|
|
11129
|
-
const root =
|
|
11130
|
-
const paths =
|
|
11315
|
+
const root = path44.resolve(opts.dir ?? process.cwd());
|
|
11316
|
+
const paths = resolveHaivePaths41(findProjectRoot45(root));
|
|
11131
11317
|
const raw = opts.kind ?? "note";
|
|
11132
11318
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
11133
11319
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
11134
|
-
ui.success(`Appended to ${
|
|
11320
|
+
ui.success(`Appended to ${path44.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
|
|
11135
11321
|
});
|
|
11136
11322
|
journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
|
|
11137
|
-
const root =
|
|
11138
|
-
const paths =
|
|
11323
|
+
const root = path44.resolve(opts.dir ?? process.cwd());
|
|
11324
|
+
const paths = resolveHaivePaths41(findProjectRoot45(root));
|
|
11139
11325
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
11140
11326
|
if (!existsSync65(paths.haiveDir)) {
|
|
11141
11327
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
@@ -11153,12 +11339,12 @@ function registerRuntime(program2) {
|
|
|
11153
11339
|
|
|
11154
11340
|
// src/commands/memory-timeline.ts
|
|
11155
11341
|
import { existsSync as existsSync66 } from "fs";
|
|
11156
|
-
import
|
|
11342
|
+
import path45 from "path";
|
|
11157
11343
|
import "commander";
|
|
11158
11344
|
import {
|
|
11159
11345
|
collectTimelineEntries as collectTimelineEntries2,
|
|
11160
|
-
findProjectRoot as
|
|
11161
|
-
resolveHaivePaths as
|
|
11346
|
+
findProjectRoot as findProjectRoot46,
|
|
11347
|
+
resolveHaivePaths as resolveHaivePaths42
|
|
11162
11348
|
} from "@hiveai/core";
|
|
11163
11349
|
function registerMemoryTimeline(memory2) {
|
|
11164
11350
|
memory2.command("timeline").description(
|
|
@@ -11169,8 +11355,8 @@ function registerMemoryTimeline(memory2) {
|
|
|
11169
11355
|
process.exitCode = 1;
|
|
11170
11356
|
return;
|
|
11171
11357
|
}
|
|
11172
|
-
const root =
|
|
11173
|
-
const paths =
|
|
11358
|
+
const root = path45.resolve(opts.dir ?? process.cwd());
|
|
11359
|
+
const paths = resolveHaivePaths42(findProjectRoot46(root));
|
|
11174
11360
|
if (!existsSync66(paths.memoriesDir)) {
|
|
11175
11361
|
ui.error("No memories \u2014 run `haive init`.");
|
|
11176
11362
|
process.exitCode = 1;
|
|
@@ -11190,13 +11376,13 @@ function registerMemoryTimeline(memory2) {
|
|
|
11190
11376
|
|
|
11191
11377
|
// src/commands/memory-conflict-candidates.ts
|
|
11192
11378
|
import { existsSync as existsSync67 } from "fs";
|
|
11193
|
-
import
|
|
11379
|
+
import path46 from "path";
|
|
11194
11380
|
import "commander";
|
|
11195
11381
|
import {
|
|
11196
11382
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
11197
11383
|
findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
|
|
11198
|
-
findProjectRoot as
|
|
11199
|
-
resolveHaivePaths as
|
|
11384
|
+
findProjectRoot as findProjectRoot47,
|
|
11385
|
+
resolveHaivePaths as resolveHaivePaths43
|
|
11200
11386
|
} from "@hiveai/core";
|
|
11201
11387
|
function parseTypes(csv) {
|
|
11202
11388
|
const allowed = ["decision", "architecture", "convention", "gotcha"];
|
|
@@ -11212,8 +11398,8 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
11212
11398
|
"decision,architecture,convention,gotcha (lexical scan)",
|
|
11213
11399
|
"decision,architecture"
|
|
11214
11400
|
).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
|
|
11215
|
-
const root =
|
|
11216
|
-
const paths =
|
|
11401
|
+
const root = path46.resolve(opts.dir ?? process.cwd());
|
|
11402
|
+
const paths = resolveHaivePaths43(findProjectRoot47(root));
|
|
11217
11403
|
if (!existsSync67(paths.memoriesDir)) {
|
|
11218
11404
|
ui.error("No memories \u2014 run `haive init`.");
|
|
11219
11405
|
process.exitCode = 1;
|
|
@@ -11251,11 +11437,11 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
11251
11437
|
// src/commands/enforce.ts
|
|
11252
11438
|
import { spawn as spawn5 } from "child_process";
|
|
11253
11439
|
import { existsSync as existsSync68 } from "fs";
|
|
11254
|
-
import { chmod as chmod2, mkdir as
|
|
11255
|
-
import
|
|
11440
|
+
import { chmod as chmod2, mkdir as mkdir19, readFile as readFile17, rm as rm3, writeFile as writeFile31 } from "fs/promises";
|
|
11441
|
+
import path47 from "path";
|
|
11256
11442
|
import "commander";
|
|
11257
11443
|
import {
|
|
11258
|
-
findProjectRoot as
|
|
11444
|
+
findProjectRoot as findProjectRoot48,
|
|
11259
11445
|
hasRecentBriefingMarker,
|
|
11260
11446
|
isFreshIsoDate,
|
|
11261
11447
|
loadConfig as loadConfig8,
|
|
@@ -11263,7 +11449,7 @@ import {
|
|
|
11263
11449
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
11264
11450
|
readRecentBriefingMarker,
|
|
11265
11451
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
11266
|
-
resolveHaivePaths as
|
|
11452
|
+
resolveHaivePaths as resolveHaivePaths44,
|
|
11267
11453
|
saveConfig as saveConfig3,
|
|
11268
11454
|
SESSION_RECAP_TTL_MS,
|
|
11269
11455
|
verifyAnchor as verifyAnchor4,
|
|
@@ -11276,9 +11462,9 @@ function registerEnforce(program2) {
|
|
|
11276
11462
|
"Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
|
|
11277
11463
|
);
|
|
11278
11464
|
enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
|
|
11279
|
-
const root =
|
|
11280
|
-
const paths =
|
|
11281
|
-
await
|
|
11465
|
+
const root = findProjectRoot48(opts.dir);
|
|
11466
|
+
const paths = resolveHaivePaths44(root);
|
|
11467
|
+
await mkdir19(paths.haiveDir, { recursive: true });
|
|
11282
11468
|
const current = await loadConfig8(paths);
|
|
11283
11469
|
await saveConfig3(paths, {
|
|
11284
11470
|
...current,
|
|
@@ -11302,7 +11488,7 @@ function registerEnforce(program2) {
|
|
|
11302
11488
|
if (opts.claude !== false) {
|
|
11303
11489
|
try {
|
|
11304
11490
|
const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
|
|
11305
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
11491
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path47.relative(root, result.settingsPath)})`);
|
|
11306
11492
|
} catch (err) {
|
|
11307
11493
|
ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11308
11494
|
}
|
|
@@ -11321,15 +11507,15 @@ function registerEnforce(program2) {
|
|
|
11321
11507
|
if (report.should_block) process.exit(2);
|
|
11322
11508
|
});
|
|
11323
11509
|
enforce.command("cleanup").description("Remove generated hAIve runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
|
|
11324
|
-
const root =
|
|
11325
|
-
const paths =
|
|
11510
|
+
const root = findProjectRoot48(opts.dir);
|
|
11511
|
+
const paths = resolveHaivePaths44(root);
|
|
11326
11512
|
const targets = [
|
|
11327
|
-
|
|
11328
|
-
|
|
11513
|
+
path47.join(paths.haiveDir, ".cache"),
|
|
11514
|
+
path47.join(paths.haiveDir, ".runtime")
|
|
11329
11515
|
];
|
|
11330
11516
|
for (const target of targets) {
|
|
11331
11517
|
if (!existsSync68(target)) continue;
|
|
11332
|
-
const rel =
|
|
11518
|
+
const rel = path47.relative(root, target);
|
|
11333
11519
|
if (opts.dryRun) ui.info(`would remove ${rel}`);
|
|
11334
11520
|
else {
|
|
11335
11521
|
await rm3(target, { recursive: true, force: true });
|
|
@@ -11346,9 +11532,9 @@ function registerEnforce(program2) {
|
|
|
11346
11532
|
const payload = await readHookPayload();
|
|
11347
11533
|
const root = resolveRoot(opts.dir, payload);
|
|
11348
11534
|
if (!root) return;
|
|
11349
|
-
const paths =
|
|
11535
|
+
const paths = resolveHaivePaths44(root);
|
|
11350
11536
|
if (!existsSync68(paths.haiveDir)) return;
|
|
11351
|
-
await
|
|
11537
|
+
await mkdir19(paths.runtimeDir, { recursive: true });
|
|
11352
11538
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
11353
11539
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
11354
11540
|
const budget = resolveBriefingBudget3("quick", {
|
|
@@ -11408,7 +11594,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
11408
11594
|
const payload = await readHookPayload();
|
|
11409
11595
|
const root = resolveRoot(opts.dir, payload);
|
|
11410
11596
|
if (!root) return;
|
|
11411
|
-
const paths =
|
|
11597
|
+
const paths = resolveHaivePaths44(root);
|
|
11412
11598
|
if (!existsSync68(paths.haiveDir)) return;
|
|
11413
11599
|
if (!isWriteLikeTool(payload)) return;
|
|
11414
11600
|
const ok = await hasRecentBriefingMarker(paths, payload.session_id);
|
|
@@ -11431,8 +11617,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
11431
11617
|
});
|
|
11432
11618
|
}
|
|
11433
11619
|
async function runWithEnforcement(command, args, opts) {
|
|
11434
|
-
const root =
|
|
11435
|
-
const paths =
|
|
11620
|
+
const root = findProjectRoot48(opts.dir);
|
|
11621
|
+
const paths = resolveHaivePaths44(root);
|
|
11436
11622
|
if (!existsSync68(paths.haiveDir)) {
|
|
11437
11623
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11438
11624
|
process.exit(1);
|
|
@@ -11452,7 +11638,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
11452
11638
|
process.exit(2);
|
|
11453
11639
|
}
|
|
11454
11640
|
ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
|
|
11455
|
-
ui.info(`Briefing written to ${
|
|
11641
|
+
ui.info(`Briefing written to ${path47.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
11456
11642
|
const child = spawn5(command, args, {
|
|
11457
11643
|
cwd: root,
|
|
11458
11644
|
stdio: "inherit",
|
|
@@ -11501,9 +11687,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
11501
11687
|
source: "haive-run",
|
|
11502
11688
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
11503
11689
|
});
|
|
11504
|
-
const dir =
|
|
11505
|
-
await
|
|
11506
|
-
const file =
|
|
11690
|
+
const dir = path47.join(paths.runtimeDir, "enforcement", "briefings");
|
|
11691
|
+
await mkdir19(dir, { recursive: true });
|
|
11692
|
+
const file = path47.join(dir, `${sessionId}.md`);
|
|
11507
11693
|
const parts = [
|
|
11508
11694
|
"# hAIve Briefing",
|
|
11509
11695
|
"",
|
|
@@ -11525,8 +11711,8 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
11525
11711
|
return file;
|
|
11526
11712
|
}
|
|
11527
11713
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
11528
|
-
const root =
|
|
11529
|
-
const paths =
|
|
11714
|
+
const root = findProjectRoot48(dir);
|
|
11715
|
+
const paths = resolveHaivePaths44(root);
|
|
11530
11716
|
const initialized = existsSync68(paths.haiveDir);
|
|
11531
11717
|
const config = initialized ? await loadConfig8(paths) : {};
|
|
11532
11718
|
const mode = config.enforcement?.mode ?? "strict";
|
|
@@ -11777,12 +11963,12 @@ function buildScore(findings, threshold = 80) {
|
|
|
11777
11963
|
};
|
|
11778
11964
|
}
|
|
11779
11965
|
async function installGitEnforcement(root) {
|
|
11780
|
-
const hooksDir =
|
|
11781
|
-
if (!existsSync68(
|
|
11966
|
+
const hooksDir = path47.join(root, ".git", "hooks");
|
|
11967
|
+
if (!existsSync68(path47.join(root, ".git"))) {
|
|
11782
11968
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
11783
11969
|
return;
|
|
11784
11970
|
}
|
|
11785
|
-
await
|
|
11971
|
+
await mkdir19(hooksDir, { recursive: true });
|
|
11786
11972
|
const hooks = [
|
|
11787
11973
|
{
|
|
11788
11974
|
name: "pre-commit",
|
|
@@ -11800,7 +11986,7 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
11800
11986
|
}
|
|
11801
11987
|
];
|
|
11802
11988
|
for (const hook of hooks) {
|
|
11803
|
-
const file =
|
|
11989
|
+
const file = path47.join(hooksDir, hook.name);
|
|
11804
11990
|
if (existsSync68(file)) {
|
|
11805
11991
|
const current = await readFile17(file, "utf8").catch(() => "");
|
|
11806
11992
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
@@ -11818,8 +12004,8 @@ ${hook.body}`, "utf8");
|
|
|
11818
12004
|
ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
|
|
11819
12005
|
}
|
|
11820
12006
|
async function installCiEnforcement(root) {
|
|
11821
|
-
const workflowPath =
|
|
11822
|
-
await
|
|
12007
|
+
const workflowPath = path47.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
12008
|
+
await mkdir19(path47.dirname(workflowPath), { recursive: true });
|
|
11823
12009
|
if (existsSync68(workflowPath)) {
|
|
11824
12010
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
11825
12011
|
return;
|
|
@@ -11848,7 +12034,7 @@ jobs:
|
|
|
11848
12034
|
- name: Enforce hAIve policy
|
|
11849
12035
|
run: haive enforce ci
|
|
11850
12036
|
`, "utf8");
|
|
11851
|
-
ui.success(`Created ${
|
|
12037
|
+
ui.success(`Created ${path47.relative(root, workflowPath)}`);
|
|
11852
12038
|
}
|
|
11853
12039
|
function printReport(report, json) {
|
|
11854
12040
|
if (json) {
|
|
@@ -11877,7 +12063,7 @@ async function readHookPayload() {
|
|
|
11877
12063
|
}
|
|
11878
12064
|
function resolveRoot(dir, payload) {
|
|
11879
12065
|
try {
|
|
11880
|
-
return
|
|
12066
|
+
return findProjectRoot48(dir ?? payload.cwd);
|
|
11881
12067
|
} catch {
|
|
11882
12068
|
return null;
|
|
11883
12069
|
}
|
|
@@ -11946,14 +12132,15 @@ function registerRun(program2) {
|
|
|
11946
12132
|
}
|
|
11947
12133
|
|
|
11948
12134
|
// src/index.ts
|
|
11949
|
-
var program = new
|
|
11950
|
-
program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.
|
|
12135
|
+
var program = new Command51();
|
|
12136
|
+
program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.12");
|
|
11951
12137
|
registerInit(program);
|
|
11952
12138
|
registerWelcome(program);
|
|
11953
12139
|
registerResolveProject(program);
|
|
11954
12140
|
registerRuntime(program);
|
|
11955
12141
|
registerEnforce(program);
|
|
11956
12142
|
registerRun(program);
|
|
12143
|
+
registerAgent(program);
|
|
11957
12144
|
registerMcp(program);
|
|
11958
12145
|
registerBriefing(program);
|
|
11959
12146
|
registerTui(program);
|