@hiveai/cli 0.13.8 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +218 -66
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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 Command59 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -199,7 +199,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
199
199
|
if (!f) continue;
|
|
200
200
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
201
201
|
}
|
|
202
|
-
let entries = [...counts.entries()].map(([
|
|
202
|
+
let entries = [...counts.entries()].map(([path56, changes]) => ({ path: path56, changes }));
|
|
203
203
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
204
204
|
if (lowerPaths.length > 0) {
|
|
205
205
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -3019,7 +3019,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3019
3019
|
}
|
|
3020
3020
|
|
|
3021
3021
|
// src/commands/init.ts
|
|
3022
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3022
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.14.0"}`;
|
|
3023
3023
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3024
3024
|
|
|
3025
3025
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -4089,10 +4089,13 @@ import {
|
|
|
4089
4089
|
literalMatchesAnyToken as literalMatchesAnyToken22,
|
|
4090
4090
|
loadCodeMap as loadCodeMap5,
|
|
4091
4091
|
loadConfig as loadConfig3,
|
|
4092
|
+
hashProjectContext,
|
|
4092
4093
|
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
4093
4094
|
loadUsageIndex as loadUsageIndex8,
|
|
4094
4095
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
|
|
4096
|
+
projectContextRecentlyEmitted,
|
|
4095
4097
|
rankMemoriesLexical as rankMemoriesLexical2,
|
|
4098
|
+
recordProjectContextEmission,
|
|
4096
4099
|
queryCodeMap as queryCodeMap2,
|
|
4097
4100
|
resolveBriefingBudget as resolveBriefingBudget2,
|
|
4098
4101
|
serializeMemory as serializeMemory10,
|
|
@@ -4133,6 +4136,7 @@ import { z as z25 } from "zod";
|
|
|
4133
4136
|
import { existsSync as existsSync25 } from "fs";
|
|
4134
4137
|
import {
|
|
4135
4138
|
addedLinesFromDiff,
|
|
4139
|
+
appendPreventionEvent,
|
|
4136
4140
|
buildDocFrequency,
|
|
4137
4141
|
CODE_STOPWORDS,
|
|
4138
4142
|
deriveConfidence as deriveConfidence6,
|
|
@@ -4143,7 +4147,9 @@ import {
|
|
|
4143
4147
|
loadUsageIndex as loadUsageIndex10,
|
|
4144
4148
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
4145
4149
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
4150
|
+
recordPrevention,
|
|
4146
4151
|
runSensors,
|
|
4152
|
+
saveUsageIndex as saveUsageIndex4,
|
|
4147
4153
|
sensorTargetsFromDiff,
|
|
4148
4154
|
tokenizeQuery as tokenizeQuery3
|
|
4149
4155
|
} from "@hiveai/core";
|
|
@@ -5708,6 +5714,9 @@ var GetBriefingInputSchema = {
|
|
|
5708
5714
|
),
|
|
5709
5715
|
max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
5710
5716
|
include_project_context: z19.boolean().default(true),
|
|
5717
|
+
dedupe_project_context: z19.boolean().optional().describe(
|
|
5718
|
+
"Token saver (default ON): skip re-emitting the project-context body if an identical copy was already sent within the last few minutes this session (the agent still has it). Set false to always include it."
|
|
5719
|
+
),
|
|
5711
5720
|
include_module_contexts: z19.boolean().default(true),
|
|
5712
5721
|
semantic: z19.boolean().default(true).describe(
|
|
5713
5722
|
"Use semantic ranking when a task is provided (requires `haive embeddings index`)."
|
|
@@ -5915,7 +5924,17 @@ async function getBriefing(input, ctx) {
|
|
|
5915
5924
|
}
|
|
5916
5925
|
}
|
|
5917
5926
|
}
|
|
5918
|
-
|
|
5927
|
+
let projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile52(ctx.paths.projectContext, "utf8") : "";
|
|
5928
|
+
let contextOmittedRecent = false;
|
|
5929
|
+
if (projectContextRaw && input.dedupe_project_context !== false) {
|
|
5930
|
+
const ctxHash = hashProjectContext(projectContextRaw);
|
|
5931
|
+
if (await projectContextRecentlyEmitted(ctx.paths, ctxHash)) {
|
|
5932
|
+
contextOmittedRecent = true;
|
|
5933
|
+
projectContextRaw = "";
|
|
5934
|
+
} else {
|
|
5935
|
+
await recordProjectContextEmission(ctx.paths, ctxHash);
|
|
5936
|
+
}
|
|
5937
|
+
}
|
|
5919
5938
|
const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
|
|
5920
5939
|
const setupWarnings = [];
|
|
5921
5940
|
let autoContextGenerated = false;
|
|
@@ -6183,7 +6202,11 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
6183
6202
|
search_mode: searchMode,
|
|
6184
6203
|
inferred_modules: inferred,
|
|
6185
6204
|
...lastSession ? { last_session: lastSession } : {},
|
|
6186
|
-
project_context:
|
|
6205
|
+
project_context: contextOmittedRecent ? {
|
|
6206
|
+
content: "(project context unchanged \u2014 omitted to save tokens; it was provided earlier this session. Pass dedupe_project_context:false to force a full copy.)",
|
|
6207
|
+
truncated: false,
|
|
6208
|
+
omitted_recent: true
|
|
6209
|
+
} : adaptiveTrim ? {
|
|
6187
6210
|
content: "(adaptive briefing: auto-generated context omitted \u2014 no team-specific policy matched, so a capable model needs nothing extra here)",
|
|
6188
6211
|
truncated: false,
|
|
6189
6212
|
...isTemplateContext && !autoContextGenerated ? { is_template: true } : {},
|
|
@@ -6669,6 +6692,22 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6669
6692
|
};
|
|
6670
6693
|
return score(b) - score(a);
|
|
6671
6694
|
}).slice(0, input.limit);
|
|
6695
|
+
const strongCatches = warnings.filter(
|
|
6696
|
+
(w) => w.reasons.includes("sensor") || w.distinctive_literal === true || w.reasons.includes("anchor") && w.reasons.includes("literal")
|
|
6697
|
+
);
|
|
6698
|
+
if (strongCatches.length > 0) {
|
|
6699
|
+
const recordedIds = [];
|
|
6700
|
+
for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
|
|
6701
|
+
if (recordedIds.length > 0) {
|
|
6702
|
+
await saveUsageIndex4(ctx.paths, usage).catch(() => {
|
|
6703
|
+
});
|
|
6704
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
6705
|
+
for (const id of recordedIds) {
|
|
6706
|
+
await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
|
|
6707
|
+
});
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6672
6711
|
return {
|
|
6673
6712
|
scanned: negative.length,
|
|
6674
6713
|
warnings
|
|
@@ -7485,7 +7524,7 @@ async function patternDetect(input, ctx) {
|
|
|
7485
7524
|
for (const [p, { count, tools }] of pathCounts) {
|
|
7486
7525
|
if (count < HOT_FILE_MIN) continue;
|
|
7487
7526
|
if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
|
|
7488
|
-
if (CONFIG_PATTERNS.some((
|
|
7527
|
+
if (CONFIG_PATTERNS.some((cp2) => path122.basename(p).includes(cp2))) continue;
|
|
7489
7528
|
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
7490
7529
|
matches.push({
|
|
7491
7530
|
kind: "hot_file",
|
|
@@ -7919,7 +7958,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7919
7958
|
};
|
|
7920
7959
|
}
|
|
7921
7960
|
var SERVER_NAME = "haive";
|
|
7922
|
-
var SERVER_VERSION = "0.
|
|
7961
|
+
var SERVER_VERSION = "0.14.0";
|
|
7923
7962
|
function jsonResult(data) {
|
|
7924
7963
|
return {
|
|
7925
7964
|
content: [
|
|
@@ -8668,6 +8707,7 @@ function createHaiveServer(options = {}) {
|
|
|
8668
8707
|
"anti_patterns_check",
|
|
8669
8708
|
[
|
|
8670
8709
|
"Scan a diff (or set of paths) against documented attempt/gotcha memories.",
|
|
8710
|
+
"[Diff-scan layer: the MEMORY-MATCH component. `pre_commit_check` combines this with sensors + stale checks; `haive enforce check` is the gate.]",
|
|
8671
8711
|
"Surfaces 'you are about to repeat a known mistake' warnings BEFORE you commit.",
|
|
8672
8712
|
"",
|
|
8673
8713
|
"USE BEFORE finalizing a non-trivial change. Cheap and high-signal: the only",
|
|
@@ -8811,6 +8851,7 @@ function createHaiveServer(options = {}) {
|
|
|
8811
8851
|
"pre_commit_check",
|
|
8812
8852
|
[
|
|
8813
8853
|
"One-shot 'should I block this commit?' check. Combines three signals:",
|
|
8854
|
+
"[Diff-scan layer: the COMBINED check (sensors + anti-patterns + stale). `haive enforce check` is the gate that runs this at commit time.]",
|
|
8814
8855
|
"",
|
|
8815
8856
|
" 1. anti_patterns_check \u2014 known gotchas/attempts that match the diff",
|
|
8816
8857
|
" 2. mem_for_files \u2014 conventions/decisions anchored to touched files",
|
|
@@ -10688,7 +10729,7 @@ import {
|
|
|
10688
10729
|
loadUsageIndex as loadUsageIndex18,
|
|
10689
10730
|
recordRejection as recordRejection3,
|
|
10690
10731
|
resolveHaivePaths as resolveHaivePaths23,
|
|
10691
|
-
saveUsageIndex as
|
|
10732
|
+
saveUsageIndex as saveUsageIndex5,
|
|
10692
10733
|
serializeMemory as serializeMemory19
|
|
10693
10734
|
} from "@hiveai/core";
|
|
10694
10735
|
function registerMemoryReject(memory2) {
|
|
@@ -10721,7 +10762,7 @@ function registerMemoryReject(memory2) {
|
|
|
10721
10762
|
);
|
|
10722
10763
|
const idx = await loadUsageIndex18(paths);
|
|
10723
10764
|
recordRejection3(idx, id, opts.reason ?? null);
|
|
10724
|
-
await
|
|
10765
|
+
await saveUsageIndex5(paths, idx);
|
|
10725
10766
|
const u = idx.by_id[id];
|
|
10726
10767
|
ui.success(
|
|
10727
10768
|
`Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
|
|
@@ -10740,7 +10781,7 @@ import {
|
|
|
10740
10781
|
findProjectRoot as findProjectRoot27,
|
|
10741
10782
|
loadUsageIndex as loadUsageIndex19,
|
|
10742
10783
|
resolveHaivePaths as resolveHaivePaths24,
|
|
10743
|
-
saveUsageIndex as
|
|
10784
|
+
saveUsageIndex as saveUsageIndex6
|
|
10744
10785
|
} from "@hiveai/core";
|
|
10745
10786
|
function registerMemoryRm(memory2) {
|
|
10746
10787
|
memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").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) => {
|
|
@@ -10774,7 +10815,7 @@ function registerMemoryRm(memory2) {
|
|
|
10774
10815
|
const idx = await loadUsageIndex19(paths);
|
|
10775
10816
|
if (idx.by_id[id]) {
|
|
10776
10817
|
delete idx.by_id[id];
|
|
10777
|
-
await
|
|
10818
|
+
await saveUsageIndex6(paths, idx);
|
|
10778
10819
|
ui.info("Removed usage entry");
|
|
10779
10820
|
}
|
|
10780
10821
|
}
|
|
@@ -10984,7 +11025,7 @@ import {
|
|
|
10984
11025
|
recordApplied as recordApplied2,
|
|
10985
11026
|
recordRejection as recordRejection4,
|
|
10986
11027
|
resolveHaivePaths as resolveHaivePaths28,
|
|
10987
|
-
saveUsageIndex as
|
|
11028
|
+
saveUsageIndex as saveUsageIndex7
|
|
10988
11029
|
} from "@hiveai/core";
|
|
10989
11030
|
function registerMemoryFeedback(memory2) {
|
|
10990
11031
|
memory2.command("feedback <id>").description(
|
|
@@ -11013,7 +11054,7 @@ function registerMemoryFeedback(memory2) {
|
|
|
11013
11054
|
const outcome = opts.applied ? "applied" : "rejected";
|
|
11014
11055
|
if (opts.applied) recordApplied2(index, id);
|
|
11015
11056
|
else recordRejection4(index, id, opts.reason ?? null);
|
|
11016
|
-
await
|
|
11057
|
+
await saveUsageIndex7(paths, index);
|
|
11017
11058
|
const usage = getUsage19(index, id);
|
|
11018
11059
|
const impact = computeImpact4(target.memory.frontmatter, usage);
|
|
11019
11060
|
if (opts.json) {
|
|
@@ -13289,8 +13330,8 @@ function registerDoctor(program2) {
|
|
|
13289
13330
|
fix: "haive init"
|
|
13290
13331
|
});
|
|
13291
13332
|
} else {
|
|
13292
|
-
const { readFile:
|
|
13293
|
-
const content = await
|
|
13333
|
+
const { readFile: readFile27 } = await import("fs/promises");
|
|
13334
|
+
const content = await readFile27(paths.projectContext, "utf8");
|
|
13294
13335
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
13295
13336
|
if (isTemplate) {
|
|
13296
13337
|
findings.push({
|
|
@@ -13464,8 +13505,8 @@ function registerDoctor(program2) {
|
|
|
13464
13505
|
let hasClaudeEnforcement = false;
|
|
13465
13506
|
if (existsSync68(claudeSettings)) {
|
|
13466
13507
|
try {
|
|
13467
|
-
const { readFile:
|
|
13468
|
-
const raw = await
|
|
13508
|
+
const { readFile: readFile27 } = await import("fs/promises");
|
|
13509
|
+
const raw = await readFile27(claudeSettings, "utf8");
|
|
13469
13510
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
13470
13511
|
} catch {
|
|
13471
13512
|
hasClaudeEnforcement = false;
|
|
@@ -13488,7 +13529,7 @@ function registerDoctor(program2) {
|
|
|
13488
13529
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
13489
13530
|
});
|
|
13490
13531
|
}
|
|
13491
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
13532
|
+
findings.push(...await collectInstallFindings(root, "0.14.0"));
|
|
13492
13533
|
findings.push(...await collectToolchainFindings(root));
|
|
13493
13534
|
try {
|
|
13494
13535
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -13496,7 +13537,7 @@ function registerDoctor(program2) {
|
|
|
13496
13537
|
timeout: 3e3,
|
|
13497
13538
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13498
13539
|
}).trim();
|
|
13499
|
-
const cliVersion = "0.
|
|
13540
|
+
const cliVersion = "0.14.0";
|
|
13500
13541
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
13501
13542
|
findings.push({
|
|
13502
13543
|
severity: "warn",
|
|
@@ -14729,52 +14770,73 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14729
14770
|
[setup warning] ${warning}`);
|
|
14730
14771
|
}
|
|
14731
14772
|
});
|
|
14732
|
-
enforce.command("pre-tool-use").description("Claude Code PreToolUse hook:
|
|
14773
|
+
enforce.command("pre-tool-use").description("Claude Code PreToolUse hook: surface the relevant team policy for the edited file (advise; configurable to block).").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
14733
14774
|
const payload = await readHookPayload();
|
|
14734
14775
|
const root = resolveRoot(opts.dir, payload);
|
|
14735
14776
|
if (!root) return;
|
|
14736
14777
|
const paths = resolveHaivePaths48(root);
|
|
14737
14778
|
if (!existsSync75(paths.haiveDir)) return;
|
|
14738
14779
|
if (!isWriteLikeTool(payload)) return;
|
|
14739
|
-
const
|
|
14740
|
-
if (
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14780
|
+
const config = await loadConfig13(paths);
|
|
14781
|
+
if (config.enforcement?.requireBriefingFirst === false) return;
|
|
14782
|
+
const gate = config.enforcement?.preEditGate ?? "advise";
|
|
14783
|
+
const targetFiles = extractToolPaths(payload, root);
|
|
14784
|
+
const hasMarker = await hasRecentBriefingMarker2(paths, payload.session_id);
|
|
14785
|
+
const missing = targetFiles.length > 0 ? await missingRequiredMemoriesForFiles(paths, targetFiles, payload.session_id) : [];
|
|
14786
|
+
if (hasMarker && missing.length === 0) return;
|
|
14787
|
+
if (targetFiles.length > 0) {
|
|
14788
|
+
await recordFilesIntoBriefingMarker(paths, targetFiles, missing, payload.session_id).catch(() => {
|
|
14789
|
+
});
|
|
14790
|
+
}
|
|
14791
|
+
const contextText = buildPreEditContext(payload.tool_name ?? "write tool", targetFiles, missing, hasMarker);
|
|
14792
|
+
if (gate === "block") {
|
|
14746
14793
|
console.error(
|
|
14747
|
-
|
|
14748
|
-
"hAIve enforcement blocked this action.",
|
|
14749
|
-
`Tool: ${payload.tool_name ?? "write tool"}`,
|
|
14750
|
-
`Files: ${targetFiles.slice(0, 6).join(", ")}`,
|
|
14751
|
-
"",
|
|
14752
|
-
"These files have required hAIve context that was not in the current briefing:",
|
|
14753
|
-
...ids.map((id) => ` - ${id}`),
|
|
14754
|
-
"",
|
|
14755
|
-
"Load the targeted briefing before editing:",
|
|
14756
|
-
` ${briefingCommandForFiles(targetFiles)}`
|
|
14757
|
-
].join("\n")
|
|
14794
|
+
contextText + '\n\nThe relevant context is now recorded \u2014 re-issue the same edit to proceed (no `haive briefing` command needed). To make this advisory instead of blocking, set `{ "enforcement": { "preEditGate": "advise" } }` in .ai/haive.config.json.'
|
|
14758
14795
|
);
|
|
14759
14796
|
process.exit(2);
|
|
14760
14797
|
}
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
);
|
|
14775
|
-
process.exit(2);
|
|
14798
|
+
emitPreToolUseContext(contextText);
|
|
14799
|
+
});
|
|
14800
|
+
}
|
|
14801
|
+
async function recordFilesIntoBriefingMarker(paths, files, missing, sessionId) {
|
|
14802
|
+
const existing = await readRecentBriefingMarker(paths, sessionId);
|
|
14803
|
+
const ids = new Set(existing?.memory_ids ?? []);
|
|
14804
|
+
for (const { memory: memory2 } of missing) ids.add(memory2.frontmatter.id);
|
|
14805
|
+
await writeBriefingMarker3(paths, {
|
|
14806
|
+
sessionId,
|
|
14807
|
+
task: existing?.task ?? "pre-edit auto-briefing",
|
|
14808
|
+
source: "haive-pre-edit",
|
|
14809
|
+
files,
|
|
14810
|
+
memoryIds: [...ids]
|
|
14776
14811
|
});
|
|
14777
14812
|
}
|
|
14813
|
+
function buildPreEditContext(tool, files, missing, hasMarker) {
|
|
14814
|
+
const lines = ["hAIve \u2014 relevant team policy for this edit", `Tool: ${tool}`];
|
|
14815
|
+
if (files.length > 0) lines.push(`Files: ${files.slice(0, 6).join(", ")}`);
|
|
14816
|
+
if (missing.length > 0) {
|
|
14817
|
+
lines.push("", "Consult these before editing (anchored to the files you are touching):");
|
|
14818
|
+
for (const { memory: memory2 } of missing.slice(0, 5)) {
|
|
14819
|
+
const fm = memory2.frontmatter;
|
|
14820
|
+
lines.push("", `### ${fm.id} (${fm.scope}/${fm.type})`, memory2.body.trim().slice(0, 900));
|
|
14821
|
+
}
|
|
14822
|
+
} else if (!hasMarker) {
|
|
14823
|
+
lines.push(
|
|
14824
|
+
"",
|
|
14825
|
+
"No team briefing was loaded yet this session. Proceeding \u2014 but for substantive work call get_briefing / mem_relevant_to for richer context."
|
|
14826
|
+
);
|
|
14827
|
+
}
|
|
14828
|
+
return lines.join("\n");
|
|
14829
|
+
}
|
|
14830
|
+
function emitPreToolUseContext(text) {
|
|
14831
|
+
console.log(
|
|
14832
|
+
JSON.stringify({
|
|
14833
|
+
hookSpecificOutput: {
|
|
14834
|
+
hookEventName: "PreToolUse",
|
|
14835
|
+
additionalContext: text
|
|
14836
|
+
}
|
|
14837
|
+
})
|
|
14838
|
+
);
|
|
14839
|
+
}
|
|
14778
14840
|
async function buildFinishReport(dir) {
|
|
14779
14841
|
const root = findProjectRoot52(dir);
|
|
14780
14842
|
const paths = resolveHaivePaths48(root);
|
|
@@ -15105,7 +15167,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
15105
15167
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
15106
15168
|
});
|
|
15107
15169
|
}
|
|
15108
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
15170
|
+
findings.push(...await inspectIntegrationVersions(root, "0.14.0"));
|
|
15109
15171
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
15110
15172
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
15111
15173
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -15223,7 +15285,7 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
15223
15285
|
}
|
|
15224
15286
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
15225
15287
|
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15226
|
-
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
15288
|
+
const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
|
|
15227
15289
|
if (changedFiles.length === 0) {
|
|
15228
15290
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
15229
15291
|
}
|
|
@@ -16020,8 +16082,10 @@ async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
|
16020
16082
|
return memoryMatchesAnchorPaths6(memory2, files);
|
|
16021
16083
|
}).map(({ memory: memory2, filePath }) => ({ memory: memory2, filePath }));
|
|
16022
16084
|
}
|
|
16023
|
-
function
|
|
16024
|
-
|
|
16085
|
+
function isGeneratedArtifact(file) {
|
|
16086
|
+
if (file === ".ai/project-context.md" || file === ".ai/code-map.json") return true;
|
|
16087
|
+
if (file.startsWith(".ai/.cache/") || file.startsWith(".ai/.runtime/") || file.startsWith(".ai/.usage/")) return true;
|
|
16088
|
+
return false;
|
|
16025
16089
|
}
|
|
16026
16090
|
async function readStdin2(maxBytes) {
|
|
16027
16091
|
if (process.stdin.isTTY) return "";
|
|
@@ -16096,14 +16160,15 @@ import path53 from "path";
|
|
|
16096
16160
|
import { promisify as promisify2 } from "util";
|
|
16097
16161
|
import "commander";
|
|
16098
16162
|
import {
|
|
16163
|
+
appendPreventionEvent as appendPreventionEvent2,
|
|
16099
16164
|
findProjectRoot as findProjectRoot53,
|
|
16100
16165
|
isRetiredMemory as isRetiredMemory3,
|
|
16101
16166
|
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
16102
16167
|
loadUsageIndex as loadUsageIndex29,
|
|
16103
|
-
recordPrevention,
|
|
16168
|
+
recordPrevention as recordPrevention2,
|
|
16104
16169
|
resolveHaivePaths as resolveHaivePaths49,
|
|
16105
16170
|
runSensors as runSensors2,
|
|
16106
|
-
saveUsageIndex as
|
|
16171
|
+
saveUsageIndex as saveUsageIndex8,
|
|
16107
16172
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
16108
16173
|
serializeMemory as serializeMemory27
|
|
16109
16174
|
} from "@hiveai/core";
|
|
@@ -16131,7 +16196,9 @@ function registerSensors(program2) {
|
|
|
16131
16196
|
if (row.last_fired) console.log(` ${ui.dim("last fired:")} ${row.last_fired}`);
|
|
16132
16197
|
}
|
|
16133
16198
|
});
|
|
16134
|
-
sensors.command("check").description(
|
|
16199
|
+
sensors.command("check").description(
|
|
16200
|
+
"Run regex sensors against a diff (the deterministic/computational layer); defaults to `git diff --cached`.\n Diff-scan layers: `sensors check` (regex) and `anti_patterns_check` (memory match) are components;\n `pre_commit_check` combines them; `haive enforce check` is THE gate that runs at commit."
|
|
16201
|
+
).option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
16135
16202
|
const root = findProjectRoot53(opts.dir);
|
|
16136
16203
|
const paths = resolveHaivePaths49(root);
|
|
16137
16204
|
const memories = await runnableSensorMemories(paths);
|
|
@@ -16141,10 +16208,17 @@ function registerSensors(program2) {
|
|
|
16141
16208
|
const firedIds = [...new Set(hits.map((hit) => hit.memory_id))];
|
|
16142
16209
|
if (firedIds.length > 0) {
|
|
16143
16210
|
const usage = await loadUsageIndex29(paths);
|
|
16144
|
-
|
|
16145
|
-
for (const id of firedIds) if (
|
|
16146
|
-
if (
|
|
16147
|
-
|
|
16211
|
+
const recordedIds = [];
|
|
16212
|
+
for (const id of firedIds) if (recordPrevention2(usage, id)) recordedIds.push(id);
|
|
16213
|
+
if (recordedIds.length > 0) {
|
|
16214
|
+
await saveUsageIndex8(paths, usage).catch(() => {
|
|
16215
|
+
});
|
|
16216
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
16217
|
+
for (const id of recordedIds) {
|
|
16218
|
+
await appendPreventionEvent2(paths, { at, id, source: "sensor" }).catch(() => {
|
|
16219
|
+
});
|
|
16220
|
+
}
|
|
16221
|
+
}
|
|
16148
16222
|
}
|
|
16149
16223
|
const output = {
|
|
16150
16224
|
scanned: memories.length,
|
|
@@ -16489,6 +16563,7 @@ import {
|
|
|
16489
16563
|
buildDashboard,
|
|
16490
16564
|
findProjectRoot as findProjectRoot55,
|
|
16491
16565
|
loadMemoriesFromDir as loadMemoriesFromDir41,
|
|
16566
|
+
loadPreventionEvents,
|
|
16492
16567
|
loadUsageIndex as loadUsageIndex30,
|
|
16493
16568
|
resolveHaivePaths as resolveHaivePaths51
|
|
16494
16569
|
} from "@hiveai/core";
|
|
@@ -16505,10 +16580,12 @@ function registerDashboard(program2) {
|
|
|
16505
16580
|
}
|
|
16506
16581
|
const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
|
|
16507
16582
|
const usage = await loadUsageIndex30(paths);
|
|
16583
|
+
const preventionEvents = await loadPreventionEvents(paths);
|
|
16508
16584
|
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
16509
16585
|
const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
|
|
16510
16586
|
const report = buildDashboard(memories, usage, {
|
|
16511
16587
|
top,
|
|
16588
|
+
preventionEvents,
|
|
16512
16589
|
...dormantDays !== void 0 && Number.isFinite(dormantDays) ? { dormantDays } : {}
|
|
16513
16590
|
});
|
|
16514
16591
|
if (opts.json) {
|
|
@@ -16553,11 +16630,22 @@ function renderDashboard(r) {
|
|
|
16553
16630
|
console.log(
|
|
16554
16631
|
` ${prevention.total_events > 0 ? ui.green(`${prevention.total_events} catch event(s)`) : "0 catch events"} \xB7 ${prevention.memories_with_catches} memor${prevention.memories_with_catches === 1 ? "y" : "ies"} with catches`
|
|
16555
16632
|
);
|
|
16633
|
+
console.log(
|
|
16634
|
+
` ${ui.dim("trend:")} ${prevention.trend.last_7d} in 7d \xB7 ${prevention.trend.last_30d} in 30d ${ui.dim("weekly")} [${prevention.trend.weekly.join(" ")}]`
|
|
16635
|
+
);
|
|
16556
16636
|
for (const p of prevention.top.slice(0, 5)) {
|
|
16557
16637
|
console.log(
|
|
16558
16638
|
` ${ui.green("\u2713")} ${p.prevented_count}\xD7 ${p.id}` + (p.last_prevented_at ? ui.dim(` last ${p.last_prevented_at.slice(0, 10)}`) : "")
|
|
16559
16639
|
);
|
|
16560
16640
|
}
|
|
16641
|
+
if (prevention.recurrence.recurring_count > 0) {
|
|
16642
|
+
console.log(
|
|
16643
|
+
` ${ui.yellow("recurrence:")} ${prevention.recurrence.recurring_count} lesson(s) re-introduced after capture ` + ui.dim("(caught on \u22652 distinct days)")
|
|
16644
|
+
);
|
|
16645
|
+
for (const r2 of prevention.recurrence.top.slice(0, 5)) {
|
|
16646
|
+
console.log(` ${ui.yellow("\u21BB")} ${r2.distinct_days} days \xB7 ${r2.catches}\xD7 ${r2.id}`);
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
16561
16649
|
console.log();
|
|
16562
16650
|
console.log(ui.bold("Health"));
|
|
16563
16651
|
console.log(
|
|
@@ -16593,9 +16681,72 @@ function warnNum(n) {
|
|
|
16593
16681
|
return n > 0 ? ui.yellow(String(n)) : String(n);
|
|
16594
16682
|
}
|
|
16595
16683
|
|
|
16684
|
+
// src/commands/dev-link.ts
|
|
16685
|
+
import { execFile as execFile3 } from "child_process";
|
|
16686
|
+
import { cp, readFile as readFile26 } from "fs/promises";
|
|
16687
|
+
import { existsSync as existsSync79 } from "fs";
|
|
16688
|
+
import path55 from "path";
|
|
16689
|
+
import { promisify as promisify3 } from "util";
|
|
16690
|
+
import "commander";
|
|
16691
|
+
import { findProjectRoot as findProjectRoot56 } from "@hiveai/core";
|
|
16692
|
+
var exec3 = promisify3(execFile3);
|
|
16693
|
+
function registerDevLink(program2) {
|
|
16694
|
+
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
|
|
16695
|
+
dev.command("link").description("Hot-swap this repo's built dist into the global @hiveai install so `haive` runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
|
|
16696
|
+
const root = findProjectRoot56(opts.dir);
|
|
16697
|
+
if (!existsSync79(path55.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
16698
|
+
ui.error(`Not the hAIve monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
16699
|
+
process.exitCode = 1;
|
|
16700
|
+
return;
|
|
16701
|
+
}
|
|
16702
|
+
let globalModules;
|
|
16703
|
+
try {
|
|
16704
|
+
globalModules = (await exec3("npm", ["root", "-g"])).stdout.trim();
|
|
16705
|
+
} catch {
|
|
16706
|
+
globalModules = path55.join(path55.dirname(path55.dirname(process.execPath)), "lib", "node_modules");
|
|
16707
|
+
}
|
|
16708
|
+
const globalHive = path55.join(globalModules, "@hiveai");
|
|
16709
|
+
if (!existsSync79(globalHive)) {
|
|
16710
|
+
ui.error(`No global @hiveai install at ${globalHive}. Install once with \`npm i -g @hiveai/cli\`, then re-run.`);
|
|
16711
|
+
process.exitCode = 1;
|
|
16712
|
+
return;
|
|
16713
|
+
}
|
|
16714
|
+
const linked = [];
|
|
16715
|
+
const copyDist = async (fromPkg, toDistDir) => {
|
|
16716
|
+
const from = path55.join(root, "packages", fromPkg, "dist");
|
|
16717
|
+
if (!existsSync79(from) || !existsSync79(path55.dirname(toDistDir))) return;
|
|
16718
|
+
await cp(from, toDistDir, { recursive: true });
|
|
16719
|
+
linked.push(path55.relative(globalModules, toDistDir));
|
|
16720
|
+
};
|
|
16721
|
+
for (const pkg of ["cli", "mcp"]) {
|
|
16722
|
+
await copyDist(pkg, path55.join(globalHive, pkg, "dist"));
|
|
16723
|
+
for (const nested of ["core", "embeddings"]) {
|
|
16724
|
+
await copyDist(nested, path55.join(globalHive, pkg, "node_modules", "@hiveai", nested, "dist"));
|
|
16725
|
+
}
|
|
16726
|
+
}
|
|
16727
|
+
await copyDist("core", path55.join(globalHive, "core", "dist"));
|
|
16728
|
+
let version = "unknown";
|
|
16729
|
+
try {
|
|
16730
|
+
version = JSON.parse(await readFile26(path55.join(root, "package.json"), "utf8")).version ?? "unknown";
|
|
16731
|
+
} catch {
|
|
16732
|
+
}
|
|
16733
|
+
if (opts.json) {
|
|
16734
|
+
console.log(JSON.stringify({ ok: linked.length > 0, version, global_root: globalHive, linked }, null, 2));
|
|
16735
|
+
return;
|
|
16736
|
+
}
|
|
16737
|
+
if (linked.length === 0) {
|
|
16738
|
+
ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
|
|
16739
|
+
return;
|
|
16740
|
+
}
|
|
16741
|
+
ui.success(`Linked local dist (v${version}) into the global @hiveai install:`);
|
|
16742
|
+
for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
|
|
16743
|
+
console.log(ui.dim("The global `haive` now runs your local build (git hooks + MCP included)."));
|
|
16744
|
+
});
|
|
16745
|
+
}
|
|
16746
|
+
|
|
16596
16747
|
// src/index.ts
|
|
16597
|
-
var program = new
|
|
16598
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
16748
|
+
var program = new Command59();
|
|
16749
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.14.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
16599
16750
|
registerInit(program);
|
|
16600
16751
|
registerWelcome(program);
|
|
16601
16752
|
registerResolveProject(program);
|
|
@@ -16606,6 +16757,7 @@ registerAgent(program);
|
|
|
16606
16757
|
registerSensors(program);
|
|
16607
16758
|
registerIngest(program);
|
|
16608
16759
|
registerDashboard(program);
|
|
16760
|
+
registerDevLink(program);
|
|
16609
16761
|
registerMcp(program);
|
|
16610
16762
|
registerBriefing(program);
|
|
16611
16763
|
registerTui(program);
|