@hiveai/cli 0.10.2 → 0.10.4
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 +15 -0
- package/dist/index.js +242 -21
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -169,6 +169,21 @@ Strict mode checks for:
|
|
|
169
169
|
|
|
170
170
|
`haive enforce check` prints an enforcement score and fails strict gates when the score drops below the configured threshold.
|
|
171
171
|
|
|
172
|
+
### `haive sensors`
|
|
173
|
+
|
|
174
|
+
Operate executable regex sensors stored on `gotcha`/`attempt` memories.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
haive sensors list
|
|
178
|
+
haive sensors check # scans git diff --cached
|
|
179
|
+
haive sensors check --diff-file diff.patch --json
|
|
180
|
+
haive sensors export --format grep
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Autogenerated sensors are conservative: they start as `warn` and `autogen: true`. A human can promote
|
|
184
|
+
high-confidence sensors to `severity: block`, which makes a deterministic pre-commit blocker when the
|
|
185
|
+
sensor matches added diff lines.
|
|
186
|
+
|
|
172
187
|
### `haive benchmark`
|
|
173
188
|
|
|
174
189
|
Turn hAIve-vs-plain agent trials into a repeatable demo/report.
|
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 Command53 } 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(([path53, changes]) => ({ path: path53, 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)));
|
|
@@ -3623,7 +3623,8 @@ import {
|
|
|
3623
3623
|
loadConfig as loadConfig2,
|
|
3624
3624
|
loadMemoriesFromDir as loadMemoriesFromDir22,
|
|
3625
3625
|
memoryFilePath as memoryFilePath2,
|
|
3626
|
-
serializeMemory as serializeMemory3
|
|
3626
|
+
serializeMemory as serializeMemory3,
|
|
3627
|
+
suggestSensorFromMemory
|
|
3627
3628
|
} from "@hiveai/core";
|
|
3628
3629
|
import { z as z4 } from "zod";
|
|
3629
3630
|
import { existsSync as existsSync52 } from "fs";
|
|
@@ -3712,7 +3713,8 @@ import path52 from "path";
|
|
|
3712
3713
|
import {
|
|
3713
3714
|
buildFrontmatter as buildFrontmatter22,
|
|
3714
3715
|
memoryFilePath as memoryFilePath22,
|
|
3715
|
-
serializeMemory as serializeMemory6
|
|
3716
|
+
serializeMemory as serializeMemory6,
|
|
3717
|
+
suggestSensorFromMemory as suggestSensorFromMemory2
|
|
3716
3718
|
} from "@hiveai/core";
|
|
3717
3719
|
import { z as z14 } from "zod";
|
|
3718
3720
|
import { mkdir as mkdir42, writeFile as writeFile82 } from "fs/promises";
|
|
@@ -3803,6 +3805,7 @@ import {
|
|
|
3803
3805
|
import { z as z23 } from "zod";
|
|
3804
3806
|
import { existsSync as existsSync222 } from "fs";
|
|
3805
3807
|
import {
|
|
3808
|
+
addedLinesFromDiff,
|
|
3806
3809
|
deriveConfidence as deriveConfidence6,
|
|
3807
3810
|
getUsage as getUsage7,
|
|
3808
3811
|
isRetiredMemory as isRetiredMemory2,
|
|
@@ -3810,6 +3813,8 @@ import {
|
|
|
3810
3813
|
loadUsageIndex as loadUsageIndex9,
|
|
3811
3814
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
3812
3815
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
3816
|
+
runSensors,
|
|
3817
|
+
sensorTargetsFromDiff,
|
|
3813
3818
|
tokenizeQuery as tokenizeQuery3
|
|
3814
3819
|
} from "@hiveai/core";
|
|
3815
3820
|
import { z as z24 } from "zod";
|
|
@@ -4068,6 +4073,8 @@ async function memSave(input, ctx) {
|
|
|
4068
4073
|
symbols: input.symbols.length ? input.symbols : fm.anchor.symbols
|
|
4069
4074
|
}
|
|
4070
4075
|
};
|
|
4076
|
+
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForSavedMemory(input.type, input.body, newFrontmatter.anchor.paths) : null;
|
|
4077
|
+
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
4071
4078
|
await writeFile22(
|
|
4072
4079
|
topicMatch.filePath,
|
|
4073
4080
|
serializeMemory3({ frontmatter: newFrontmatter, body: input.body }),
|
|
@@ -4086,7 +4093,8 @@ async function memSave(input, ctx) {
|
|
|
4086
4093
|
revision_count: newFrontmatter.revision_count,
|
|
4087
4094
|
...mergedTw ? { warning: mergedTw } : {},
|
|
4088
4095
|
...bs ? { body_similar: bs } : {},
|
|
4089
|
-
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
|
|
4096
|
+
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {},
|
|
4097
|
+
...suggestedSensor ? { suggested_sensor: true } : {}
|
|
4090
4098
|
};
|
|
4091
4099
|
}
|
|
4092
4100
|
}
|
|
@@ -4102,7 +4110,8 @@ async function memSave(input, ctx) {
|
|
|
4102
4110
|
symbols: input.symbols,
|
|
4103
4111
|
commit: input.commit,
|
|
4104
4112
|
topic: input.topic,
|
|
4105
|
-
status: haiveConfig.defaultStatus === "validated" ? "validated" : void 0
|
|
4113
|
+
status: haiveConfig.defaultStatus === "validated" ? "validated" : void 0,
|
|
4114
|
+
sensor: suggestSensorForSavedMemory(input.type, input.body, input.paths) ?? void 0
|
|
4106
4115
|
});
|
|
4107
4116
|
const file = memoryFilePath2(
|
|
4108
4117
|
ctx.paths,
|
|
@@ -4143,9 +4152,14 @@ async function memSave(input, ctx) {
|
|
|
4143
4152
|
...finalWarning ? { warning: finalWarning } : {},
|
|
4144
4153
|
...similar_found ? { similar_found } : {},
|
|
4145
4154
|
...bsNew ? { body_similar: bsNew } : {},
|
|
4146
|
-
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
|
|
4155
|
+
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {},
|
|
4156
|
+
...frontmatter.sensor?.autogen ? { suggested_sensor: true } : {}
|
|
4147
4157
|
};
|
|
4148
4158
|
}
|
|
4159
|
+
function suggestSensorForSavedMemory(type, body, paths) {
|
|
4160
|
+
if (type !== "gotcha" && type !== "attempt") return null;
|
|
4161
|
+
return suggestSensorFromMemory(body, paths);
|
|
4162
|
+
}
|
|
4149
4163
|
function criticalAnchorWarning(type, status, paths, symbols) {
|
|
4150
4164
|
if (!["decision", "gotcha", "architecture"].includes(type)) return null;
|
|
4151
4165
|
if (status !== "validated") return null;
|
|
@@ -4788,6 +4802,10 @@ async function memTried(input, ctx) {
|
|
|
4788
4802
|
lines.push("", `**Instead, use:** ${input.instead}`);
|
|
4789
4803
|
}
|
|
4790
4804
|
const body = lines.join("\n") + "\n";
|
|
4805
|
+
const sensor = suggestSensorFromMemory2(body, input.paths);
|
|
4806
|
+
if (sensor) {
|
|
4807
|
+
frontmatter.sensor = sensor;
|
|
4808
|
+
}
|
|
4791
4809
|
const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
4792
4810
|
await mkdir32(path52.dirname(file), { recursive: true });
|
|
4793
4811
|
if (existsSync142(file)) {
|
|
@@ -6192,6 +6210,23 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6192
6210
|
}
|
|
6193
6211
|
}
|
|
6194
6212
|
}
|
|
6213
|
+
if (input.diff) {
|
|
6214
|
+
const added = addedLinesFromDiff(input.diff);
|
|
6215
|
+
const diffTargets = sensorTargetsFromDiff(input.diff);
|
|
6216
|
+
const hasFileTargets = diffTargets.some((target) => target.path.length > 0);
|
|
6217
|
+
const targets = diffTargets.length > 0 && hasFileTargets ? diffTargets : input.paths.length > 0 ? input.paths.map((p) => ({ path: p, content: added.trim().length > 0 ? added : input.diff })) : [{ path: "", content: added.trim().length > 0 ? added : input.diff }];
|
|
6218
|
+
const hits = runSensors(negative.map(({ memory: memory2 }) => memory2), targets);
|
|
6219
|
+
for (const hit of hits) {
|
|
6220
|
+
const found = negative.find(({ memory: memory2 }) => memory2.frontmatter.id === hit.memory_id);
|
|
6221
|
+
if (!found) continue;
|
|
6222
|
+
upsert(found.memory.frontmatter, found.memory.body, "sensor");
|
|
6223
|
+
const w = seen.get(found.memory.frontmatter.id);
|
|
6224
|
+
if (w) {
|
|
6225
|
+
w.sensor_message = hit.message;
|
|
6226
|
+
w.sensor_severity = hit.severity;
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6195
6230
|
if (input.semantic && input.diff) {
|
|
6196
6231
|
try {
|
|
6197
6232
|
const mod = await import("@hiveai/embeddings");
|
|
@@ -6210,7 +6245,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6210
6245
|
}
|
|
6211
6246
|
const warnings = [...seen.values()].sort((a, b) => {
|
|
6212
6247
|
const score = (w) => {
|
|
6213
|
-
const reasonW = (w.reasons.includes("anchor") ? 4 : 0) + (w.reasons.includes("literal") ? 2 : 0) + (w.reasons.includes("semantic") ? 1 : 0);
|
|
6248
|
+
const reasonW = (w.reasons.includes("sensor") ? 8 : 0) + (w.reasons.includes("anchor") ? 4 : 0) + (w.reasons.includes("literal") ? 2 : 0) + (w.reasons.includes("semantic") ? 1 : 0);
|
|
6214
6249
|
const confW = w.confidence === "authoritative" ? 3 : w.confidence === "trusted" ? 2 : w.confidence === "low" ? 1 : 0;
|
|
6215
6250
|
return reasonW + confW + (w.semantic_score ?? 0);
|
|
6216
6251
|
};
|
|
@@ -6718,6 +6753,24 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
|
6718
6753
|
repair_command: repairCommand
|
|
6719
6754
|
};
|
|
6720
6755
|
}
|
|
6756
|
+
if (warning.reasons.includes("sensor")) {
|
|
6757
|
+
if (warning.sensor_severity === "block") {
|
|
6758
|
+
return {
|
|
6759
|
+
...warning,
|
|
6760
|
+
level: "blocking",
|
|
6761
|
+
rationale: "deterministic hAIve sensor with block severity matched the added diff",
|
|
6762
|
+
affected_files: affectedFiles,
|
|
6763
|
+
repair_command: repairCommand
|
|
6764
|
+
};
|
|
6765
|
+
}
|
|
6766
|
+
return {
|
|
6767
|
+
...warning,
|
|
6768
|
+
level: "review",
|
|
6769
|
+
rationale: "deterministic hAIve sensor with warn severity matched the added diff",
|
|
6770
|
+
affected_files: affectedFiles,
|
|
6771
|
+
repair_command: repairCommand
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6721
6774
|
if (isBlockingWarning(warning)) {
|
|
6722
6775
|
return {
|
|
6723
6776
|
...warning,
|
|
@@ -7389,7 +7442,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7389
7442
|
};
|
|
7390
7443
|
}
|
|
7391
7444
|
var SERVER_NAME = "haive";
|
|
7392
|
-
var SERVER_VERSION = "0.10.
|
|
7445
|
+
var SERVER_VERSION = "0.10.4";
|
|
7393
7446
|
function jsonResult(data) {
|
|
7394
7447
|
return {
|
|
7395
7448
|
content: [
|
|
@@ -8908,7 +8961,8 @@ import {
|
|
|
8908
8961
|
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
8909
8962
|
memoryFilePath as memoryFilePath6,
|
|
8910
8963
|
resolveHaivePaths as resolveHaivePaths10,
|
|
8911
|
-
serializeMemory as serializeMemory12
|
|
8964
|
+
serializeMemory as serializeMemory12,
|
|
8965
|
+
suggestSensorFromMemory as suggestSensorFromMemory3
|
|
8912
8966
|
} from "@hiveai/core";
|
|
8913
8967
|
function registerMemoryAdd(memory2) {
|
|
8914
8968
|
memory2.command("add").description(
|
|
@@ -9010,9 +9064,12 @@ TODO \u2014 write the memory body.
|
|
|
9010
9064
|
symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
|
|
9011
9065
|
}
|
|
9012
9066
|
};
|
|
9067
|
+
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
9068
|
+
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
9013
9069
|
await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
|
|
9014
9070
|
ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
|
|
9015
9071
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
9072
|
+
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
9016
9073
|
await runPostMemoryAutopilot(root, paths, config);
|
|
9017
9074
|
return;
|
|
9018
9075
|
}
|
|
@@ -9029,7 +9086,8 @@ TODO \u2014 write the memory body.
|
|
|
9029
9086
|
symbols: parseCsv2(opts.symbols),
|
|
9030
9087
|
commit: opts.commit,
|
|
9031
9088
|
topic: opts.topic,
|
|
9032
|
-
status: config.defaultStatus === "validated" ? "validated" : void 0
|
|
9089
|
+
status: config.defaultStatus === "validated" ? "validated" : void 0,
|
|
9090
|
+
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0
|
|
9033
9091
|
});
|
|
9034
9092
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9035
9093
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
@@ -9053,6 +9111,9 @@ TODO \u2014 write the memory body.
|
|
|
9053
9111
|
await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
|
|
9054
9112
|
ui.success(`Created ${path16.relative(root, file)}`);
|
|
9055
9113
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
9114
|
+
if (frontmatter.sensor?.autogen) {
|
|
9115
|
+
ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(frontmatter.sensor.pattern)}`);
|
|
9116
|
+
}
|
|
9056
9117
|
await runPostMemoryAutopilot(root, paths, config);
|
|
9057
9118
|
if (inferredTags.length > 0) {
|
|
9058
9119
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
@@ -9096,6 +9157,10 @@ function parseCsv2(value) {
|
|
|
9096
9157
|
if (!value) return [];
|
|
9097
9158
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
9098
9159
|
}
|
|
9160
|
+
function suggestSensorForCliMemory(type, body, anchorPaths) {
|
|
9161
|
+
if (type !== "gotcha" && type !== "attempt") return null;
|
|
9162
|
+
return suggestSensorFromMemory3(body, anchorPaths);
|
|
9163
|
+
}
|
|
9099
9164
|
function normalizeBody(rawBody, title, titleExplicit) {
|
|
9100
9165
|
const trimmed = rawBody.trim();
|
|
9101
9166
|
if (/^#{1,3}\s+\S/m.test(trimmed)) return `${trimmed}
|
|
@@ -9751,7 +9816,8 @@ import {
|
|
|
9751
9816
|
findProjectRoot as findProjectRoot22,
|
|
9752
9817
|
memoryFilePath as memoryFilePath8,
|
|
9753
9818
|
resolveHaivePaths as resolveHaivePaths19,
|
|
9754
|
-
serializeMemory as serializeMemory17
|
|
9819
|
+
serializeMemory as serializeMemory17,
|
|
9820
|
+
suggestSensorFromMemory as suggestSensorFromMemory4
|
|
9755
9821
|
} from "@hiveai/core";
|
|
9756
9822
|
function registerMemoryTried(memory2) {
|
|
9757
9823
|
memory2.command("tried").description(
|
|
@@ -9794,6 +9860,10 @@ function registerMemoryTried(memory2) {
|
|
|
9794
9860
|
lines.push("", `**Instead, use:** ${opts.instead}`);
|
|
9795
9861
|
}
|
|
9796
9862
|
const body = lines.join("\n") + "\n";
|
|
9863
|
+
const sensor = suggestSensorFromMemory4(body, frontmatter.anchor.paths);
|
|
9864
|
+
if (sensor) {
|
|
9865
|
+
frontmatter.sensor = sensor;
|
|
9866
|
+
}
|
|
9797
9867
|
const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9798
9868
|
await mkdir13(path26.dirname(file), { recursive: true });
|
|
9799
9869
|
if (existsSync40(file)) {
|
|
@@ -9804,6 +9874,7 @@ function registerMemoryTried(memory2) {
|
|
|
9804
9874
|
await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
|
|
9805
9875
|
ui.success(`Recorded: ${path26.relative(root, file)}`);
|
|
9806
9876
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
9877
|
+
if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
9807
9878
|
});
|
|
9808
9879
|
}
|
|
9809
9880
|
function parseCsv4(value) {
|
|
@@ -12277,8 +12348,8 @@ function registerDoctor(program2) {
|
|
|
12277
12348
|
fix: "haive init"
|
|
12278
12349
|
});
|
|
12279
12350
|
} else {
|
|
12280
|
-
const { readFile:
|
|
12281
|
-
const content = await
|
|
12351
|
+
const { readFile: readFile24 } = await import("fs/promises");
|
|
12352
|
+
const content = await readFile24(paths.projectContext, "utf8");
|
|
12282
12353
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
12283
12354
|
if (isTemplate) {
|
|
12284
12355
|
findings.push({
|
|
@@ -12443,8 +12514,8 @@ function registerDoctor(program2) {
|
|
|
12443
12514
|
let hasClaudeEnforcement = false;
|
|
12444
12515
|
if (existsSync61(claudeSettings)) {
|
|
12445
12516
|
try {
|
|
12446
|
-
const { readFile:
|
|
12447
|
-
const raw = await
|
|
12517
|
+
const { readFile: readFile24 } = await import("fs/promises");
|
|
12518
|
+
const raw = await readFile24(claudeSettings, "utf8");
|
|
12448
12519
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
12449
12520
|
} catch {
|
|
12450
12521
|
hasClaudeEnforcement = false;
|
|
@@ -12467,14 +12538,14 @@ function registerDoctor(program2) {
|
|
|
12467
12538
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
12468
12539
|
});
|
|
12469
12540
|
}
|
|
12470
|
-
findings.push(...await collectInstallFindings(root, "0.10.
|
|
12541
|
+
findings.push(...await collectInstallFindings(root, "0.10.4"));
|
|
12471
12542
|
try {
|
|
12472
12543
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
12473
12544
|
encoding: "utf8",
|
|
12474
12545
|
timeout: 3e3,
|
|
12475
12546
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12476
12547
|
}).trim();
|
|
12477
|
-
const cliVersion = "0.10.
|
|
12548
|
+
const cliVersion = "0.10.4";
|
|
12478
12549
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
12479
12550
|
findings.push({
|
|
12480
12551
|
severity: "warn",
|
|
@@ -13772,7 +13843,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
13772
13843
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
13773
13844
|
});
|
|
13774
13845
|
}
|
|
13775
|
-
findings.push(...await inspectIntegrationVersions(root, "0.10.
|
|
13846
|
+
findings.push(...await inspectIntegrationVersions(root, "0.10.4"));
|
|
13776
13847
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
13777
13848
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
13778
13849
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14382,9 +14453,157 @@ function registerRun(program2) {
|
|
|
14382
14453
|
});
|
|
14383
14454
|
}
|
|
14384
14455
|
|
|
14456
|
+
// src/commands/sensors.ts
|
|
14457
|
+
import { execFile as execFile2 } from "child_process";
|
|
14458
|
+
import { existsSync as existsSync69 } from "fs";
|
|
14459
|
+
import { chmod as chmod3, mkdir as mkdir20, readFile as readFile23, writeFile as writeFile34 } from "fs/promises";
|
|
14460
|
+
import path51 from "path";
|
|
14461
|
+
import { promisify as promisify2 } from "util";
|
|
14462
|
+
import "commander";
|
|
14463
|
+
import {
|
|
14464
|
+
findProjectRoot as findProjectRoot50,
|
|
14465
|
+
isRetiredMemory as isRetiredMemory3,
|
|
14466
|
+
loadMemoriesFromDir as loadMemoriesFromDir37,
|
|
14467
|
+
resolveHaivePaths as resolveHaivePaths46,
|
|
14468
|
+
runSensors as runSensors2,
|
|
14469
|
+
sensorTargetsFromDiff as sensorTargetsFromDiff2
|
|
14470
|
+
} from "@hiveai/core";
|
|
14471
|
+
var exec2 = promisify2(execFile2);
|
|
14472
|
+
function registerSensors(program2) {
|
|
14473
|
+
const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
|
|
14474
|
+
sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
14475
|
+
const root = findProjectRoot50(opts.dir);
|
|
14476
|
+
const paths = resolveHaivePaths46(root);
|
|
14477
|
+
const rows = await sensorRows(paths);
|
|
14478
|
+
if (opts.json) {
|
|
14479
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
14480
|
+
return;
|
|
14481
|
+
}
|
|
14482
|
+
if (rows.length === 0) {
|
|
14483
|
+
ui.warn("No sensors found.");
|
|
14484
|
+
return;
|
|
14485
|
+
}
|
|
14486
|
+
console.log(ui.bold(`hAIve sensors \u2014 ${rows.length}`));
|
|
14487
|
+
for (const row of rows) {
|
|
14488
|
+
console.log(
|
|
14489
|
+
` \u2022 ${row.id} ${ui.dim(`(${row.kind}, ${row.severity})`)} ${row.pattern ?? row.command ?? ""}`
|
|
14490
|
+
);
|
|
14491
|
+
if (row.paths.length > 0) console.log(` ${ui.dim("paths:")} ${row.paths.join(", ")}`);
|
|
14492
|
+
if (row.last_fired) console.log(` ${ui.dim("last fired:")} ${row.last_fired}`);
|
|
14493
|
+
}
|
|
14494
|
+
});
|
|
14495
|
+
sensors.command("check").description("Run regex sensors against a diff; defaults to `git diff --cached`").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) => {
|
|
14496
|
+
const root = findProjectRoot50(opts.dir);
|
|
14497
|
+
const paths = resolveHaivePaths46(root);
|
|
14498
|
+
const memories = await runnableSensorMemories(paths);
|
|
14499
|
+
const diff = opts.diffFile ? await readFile23(path51.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
14500
|
+
const targets = sensorTargetsFromDiff2(diff);
|
|
14501
|
+
const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
|
|
14502
|
+
const output = {
|
|
14503
|
+
scanned: memories.length,
|
|
14504
|
+
hits: hits.map((hit) => ({
|
|
14505
|
+
memory_id: hit.memory_id,
|
|
14506
|
+
file: hit.file,
|
|
14507
|
+
severity: hit.severity,
|
|
14508
|
+
message: hit.message,
|
|
14509
|
+
matched_line: hit.matched_line
|
|
14510
|
+
}))
|
|
14511
|
+
};
|
|
14512
|
+
if (opts.json) {
|
|
14513
|
+
console.log(JSON.stringify(output, null, 2));
|
|
14514
|
+
} else {
|
|
14515
|
+
console.log(ui.bold(`hAIve sensors check \u2014 ${hits.length} hit(s), ${memories.length} sensor(s)`));
|
|
14516
|
+
for (const hit of hits) {
|
|
14517
|
+
const marker = hit.severity === "block" ? ui.red("\u2717") : ui.yellow("\u26A0");
|
|
14518
|
+
console.log(` ${marker} ${hit.memory_id} ${ui.dim(`(${hit.severity})`)}`);
|
|
14519
|
+
if (hit.file) console.log(` ${ui.dim("file:")} ${hit.file}`);
|
|
14520
|
+
console.log(` ${hit.message}`);
|
|
14521
|
+
if (hit.matched_line) console.log(` ${ui.dim(hit.matched_line)}`);
|
|
14522
|
+
}
|
|
14523
|
+
}
|
|
14524
|
+
if (hits.some((hit) => hit.severity === "block")) process.exitCode = 1;
|
|
14525
|
+
});
|
|
14526
|
+
sensors.command("export").description("Export regex sensors into .ai/generated for external toolchains").option("--format <format>", "grep | eslint", "grep").option("--out-dir <dir>", "output directory", ".ai/generated").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
14527
|
+
const format = opts.format ?? "grep";
|
|
14528
|
+
if (format !== "grep" && format !== "eslint") {
|
|
14529
|
+
ui.error("--format must be grep or eslint");
|
|
14530
|
+
process.exitCode = 1;
|
|
14531
|
+
return;
|
|
14532
|
+
}
|
|
14533
|
+
const root = findProjectRoot50(opts.dir);
|
|
14534
|
+
const paths = resolveHaivePaths46(root);
|
|
14535
|
+
const rows = await sensorRows(paths);
|
|
14536
|
+
const outDir = path51.resolve(root, opts.outDir ?? ".ai/generated");
|
|
14537
|
+
await mkdir20(outDir, { recursive: true });
|
|
14538
|
+
const outPath = path51.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
14539
|
+
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
14540
|
+
await writeFile34(outPath, content, "utf8");
|
|
14541
|
+
if (format === "grep") await chmod3(outPath, 493);
|
|
14542
|
+
ui.success(`Exported ${rows.length} sensor(s): ${path51.relative(root, outPath)}`);
|
|
14543
|
+
});
|
|
14544
|
+
}
|
|
14545
|
+
async function sensorRows(paths) {
|
|
14546
|
+
const memories = await runnableSensorMemories(paths, false);
|
|
14547
|
+
return memories.map((memory2) => {
|
|
14548
|
+
const sensor = memory2.frontmatter.sensor;
|
|
14549
|
+
return {
|
|
14550
|
+
id: memory2.frontmatter.id,
|
|
14551
|
+
kind: sensor.kind,
|
|
14552
|
+
severity: sensor.severity,
|
|
14553
|
+
pattern: sensor.pattern,
|
|
14554
|
+
command: sensor.command,
|
|
14555
|
+
paths: sensor.paths.length > 0 ? sensor.paths : memory2.frontmatter.anchor.paths,
|
|
14556
|
+
message: sensor.message,
|
|
14557
|
+
autogen: sensor.autogen,
|
|
14558
|
+
last_fired: sensor.last_fired
|
|
14559
|
+
};
|
|
14560
|
+
});
|
|
14561
|
+
}
|
|
14562
|
+
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
14563
|
+
if (!existsSync69(paths.memoriesDir)) return [];
|
|
14564
|
+
const loaded = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14565
|
+
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
14566
|
+
const sensor = memory2.frontmatter.sensor;
|
|
14567
|
+
if (!sensor) return false;
|
|
14568
|
+
if (regexOnly && sensor.kind !== "regex") return false;
|
|
14569
|
+
return !isRetiredMemory3(memory2.frontmatter, memory2.body);
|
|
14570
|
+
});
|
|
14571
|
+
}
|
|
14572
|
+
async function stagedDiff(root) {
|
|
14573
|
+
try {
|
|
14574
|
+
const { stdout } = await exec2("git", ["diff", "--cached"], { cwd: root });
|
|
14575
|
+
return stdout;
|
|
14576
|
+
} catch (err) {
|
|
14577
|
+
throw new Error(`git diff --cached failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
14578
|
+
}
|
|
14579
|
+
}
|
|
14580
|
+
function renderGrepScript(rows) {
|
|
14581
|
+
const lines = [
|
|
14582
|
+
"#!/usr/bin/env bash",
|
|
14583
|
+
"set -euo pipefail",
|
|
14584
|
+
"status=0",
|
|
14585
|
+
""
|
|
14586
|
+
];
|
|
14587
|
+
for (const row of rows.filter((item) => item.kind === "regex" && item.pattern)) {
|
|
14588
|
+
const paths = row.paths.length > 0 ? row.paths : ["."];
|
|
14589
|
+
for (const p of paths) {
|
|
14590
|
+
lines.push(`if grep -RInE -- ${shellQuote(row.pattern)} ${shellQuote(p)}; then`);
|
|
14591
|
+
lines.push(` echo ${shellQuote(`hAIve sensor ${row.id}: ${row.message}`)}`);
|
|
14592
|
+
if (row.severity === "block") lines.push(" status=1");
|
|
14593
|
+
lines.push("fi");
|
|
14594
|
+
lines.push("");
|
|
14595
|
+
}
|
|
14596
|
+
}
|
|
14597
|
+
lines.push("exit $status", "");
|
|
14598
|
+
return lines.join("\n");
|
|
14599
|
+
}
|
|
14600
|
+
function shellQuote(value) {
|
|
14601
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
14602
|
+
}
|
|
14603
|
+
|
|
14385
14604
|
// src/index.ts
|
|
14386
|
-
var program = new
|
|
14387
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.10.
|
|
14605
|
+
var program = new Command53();
|
|
14606
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.10.4").option("--advanced", "show maintenance and experimental commands in help");
|
|
14388
14607
|
registerInit(program);
|
|
14389
14608
|
registerWelcome(program);
|
|
14390
14609
|
registerResolveProject(program);
|
|
@@ -14392,6 +14611,7 @@ registerRuntime(program);
|
|
|
14392
14611
|
registerEnforce(program);
|
|
14393
14612
|
registerRun(program);
|
|
14394
14613
|
registerAgent(program);
|
|
14614
|
+
registerSensors(program);
|
|
14395
14615
|
registerMcp(program);
|
|
14396
14616
|
registerBriefing(program);
|
|
14397
14617
|
registerTui(program);
|
|
@@ -14447,6 +14667,7 @@ var CORE_ROOT_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
14447
14667
|
"briefing",
|
|
14448
14668
|
"enforce",
|
|
14449
14669
|
"run",
|
|
14670
|
+
"sensors",
|
|
14450
14671
|
"sync",
|
|
14451
14672
|
"mcp",
|
|
14452
14673
|
"memory",
|