@hiveai/cli 0.10.3 → 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 +235 -40
- 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";
|
|
@@ -3811,8 +3813,8 @@ import {
|
|
|
3811
3813
|
loadUsageIndex as loadUsageIndex9,
|
|
3812
3814
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
3813
3815
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
+
runSensors,
|
|
3817
|
+
sensorTargetsFromDiff,
|
|
3816
3818
|
tokenizeQuery as tokenizeQuery3
|
|
3817
3819
|
} from "@hiveai/core";
|
|
3818
3820
|
import { z as z24 } from "zod";
|
|
@@ -4071,6 +4073,8 @@ async function memSave(input, ctx) {
|
|
|
4071
4073
|
symbols: input.symbols.length ? input.symbols : fm.anchor.symbols
|
|
4072
4074
|
}
|
|
4073
4075
|
};
|
|
4076
|
+
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForSavedMemory(input.type, input.body, newFrontmatter.anchor.paths) : null;
|
|
4077
|
+
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
4074
4078
|
await writeFile22(
|
|
4075
4079
|
topicMatch.filePath,
|
|
4076
4080
|
serializeMemory3({ frontmatter: newFrontmatter, body: input.body }),
|
|
@@ -4089,7 +4093,8 @@ async function memSave(input, ctx) {
|
|
|
4089
4093
|
revision_count: newFrontmatter.revision_count,
|
|
4090
4094
|
...mergedTw ? { warning: mergedTw } : {},
|
|
4091
4095
|
...bs ? { body_similar: bs } : {},
|
|
4092
|
-
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
|
|
4096
|
+
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {},
|
|
4097
|
+
...suggestedSensor ? { suggested_sensor: true } : {}
|
|
4093
4098
|
};
|
|
4094
4099
|
}
|
|
4095
4100
|
}
|
|
@@ -4105,7 +4110,8 @@ async function memSave(input, ctx) {
|
|
|
4105
4110
|
symbols: input.symbols,
|
|
4106
4111
|
commit: input.commit,
|
|
4107
4112
|
topic: input.topic,
|
|
4108
|
-
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
|
|
4109
4115
|
});
|
|
4110
4116
|
const file = memoryFilePath2(
|
|
4111
4117
|
ctx.paths,
|
|
@@ -4146,9 +4152,14 @@ async function memSave(input, ctx) {
|
|
|
4146
4152
|
...finalWarning ? { warning: finalWarning } : {},
|
|
4147
4153
|
...similar_found ? { similar_found } : {},
|
|
4148
4154
|
...bsNew ? { body_similar: bsNew } : {},
|
|
4149
|
-
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
|
|
4155
|
+
...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {},
|
|
4156
|
+
...frontmatter.sensor?.autogen ? { suggested_sensor: true } : {}
|
|
4150
4157
|
};
|
|
4151
4158
|
}
|
|
4159
|
+
function suggestSensorForSavedMemory(type, body, paths) {
|
|
4160
|
+
if (type !== "gotcha" && type !== "attempt") return null;
|
|
4161
|
+
return suggestSensorFromMemory(body, paths);
|
|
4162
|
+
}
|
|
4152
4163
|
function criticalAnchorWarning(type, status, paths, symbols) {
|
|
4153
4164
|
if (!["decision", "gotcha", "architecture"].includes(type)) return null;
|
|
4154
4165
|
if (status !== "validated") return null;
|
|
@@ -4791,6 +4802,10 @@ async function memTried(input, ctx) {
|
|
|
4791
4802
|
lines.push("", `**Instead, use:** ${input.instead}`);
|
|
4792
4803
|
}
|
|
4793
4804
|
const body = lines.join("\n") + "\n";
|
|
4805
|
+
const sensor = suggestSensorFromMemory2(body, input.paths);
|
|
4806
|
+
if (sensor) {
|
|
4807
|
+
frontmatter.sensor = sensor;
|
|
4808
|
+
}
|
|
4794
4809
|
const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
4795
4810
|
await mkdir32(path52.dirname(file), { recursive: true });
|
|
4796
4811
|
if (existsSync142(file)) {
|
|
@@ -6197,24 +6212,18 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6197
6212
|
}
|
|
6198
6213
|
if (input.diff) {
|
|
6199
6214
|
const added = addedLinesFromDiff(input.diff);
|
|
6200
|
-
const
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
const
|
|
6206
|
-
if (!
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
upsert(memory2.frontmatter, memory2.body, "sensor");
|
|
6213
|
-
const w = seen.get(memory2.frontmatter.id);
|
|
6214
|
-
if (w) {
|
|
6215
|
-
w.sensor_message = hit.message;
|
|
6216
|
-
w.sensor_severity = hit.severity;
|
|
6217
|
-
}
|
|
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;
|
|
6218
6227
|
}
|
|
6219
6228
|
}
|
|
6220
6229
|
}
|
|
@@ -6744,6 +6753,24 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
|
6744
6753
|
repair_command: repairCommand
|
|
6745
6754
|
};
|
|
6746
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
|
+
}
|
|
6747
6774
|
if (isBlockingWarning(warning)) {
|
|
6748
6775
|
return {
|
|
6749
6776
|
...warning,
|
|
@@ -7415,7 +7442,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7415
7442
|
};
|
|
7416
7443
|
}
|
|
7417
7444
|
var SERVER_NAME = "haive";
|
|
7418
|
-
var SERVER_VERSION = "0.10.
|
|
7445
|
+
var SERVER_VERSION = "0.10.4";
|
|
7419
7446
|
function jsonResult(data) {
|
|
7420
7447
|
return {
|
|
7421
7448
|
content: [
|
|
@@ -8934,7 +8961,8 @@ import {
|
|
|
8934
8961
|
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
8935
8962
|
memoryFilePath as memoryFilePath6,
|
|
8936
8963
|
resolveHaivePaths as resolveHaivePaths10,
|
|
8937
|
-
serializeMemory as serializeMemory12
|
|
8964
|
+
serializeMemory as serializeMemory12,
|
|
8965
|
+
suggestSensorFromMemory as suggestSensorFromMemory3
|
|
8938
8966
|
} from "@hiveai/core";
|
|
8939
8967
|
function registerMemoryAdd(memory2) {
|
|
8940
8968
|
memory2.command("add").description(
|
|
@@ -9036,9 +9064,12 @@ TODO \u2014 write the memory body.
|
|
|
9036
9064
|
symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
|
|
9037
9065
|
}
|
|
9038
9066
|
};
|
|
9067
|
+
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
9068
|
+
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
9039
9069
|
await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
|
|
9040
9070
|
ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
|
|
9041
9071
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
9072
|
+
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
9042
9073
|
await runPostMemoryAutopilot(root, paths, config);
|
|
9043
9074
|
return;
|
|
9044
9075
|
}
|
|
@@ -9055,7 +9086,8 @@ TODO \u2014 write the memory body.
|
|
|
9055
9086
|
symbols: parseCsv2(opts.symbols),
|
|
9056
9087
|
commit: opts.commit,
|
|
9057
9088
|
topic: opts.topic,
|
|
9058
|
-
status: config.defaultStatus === "validated" ? "validated" : void 0
|
|
9089
|
+
status: config.defaultStatus === "validated" ? "validated" : void 0,
|
|
9090
|
+
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0
|
|
9059
9091
|
});
|
|
9060
9092
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9061
9093
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
@@ -9079,6 +9111,9 @@ TODO \u2014 write the memory body.
|
|
|
9079
9111
|
await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
|
|
9080
9112
|
ui.success(`Created ${path16.relative(root, file)}`);
|
|
9081
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
|
+
}
|
|
9082
9117
|
await runPostMemoryAutopilot(root, paths, config);
|
|
9083
9118
|
if (inferredTags.length > 0) {
|
|
9084
9119
|
ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
|
|
@@ -9122,6 +9157,10 @@ function parseCsv2(value) {
|
|
|
9122
9157
|
if (!value) return [];
|
|
9123
9158
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
9124
9159
|
}
|
|
9160
|
+
function suggestSensorForCliMemory(type, body, anchorPaths) {
|
|
9161
|
+
if (type !== "gotcha" && type !== "attempt") return null;
|
|
9162
|
+
return suggestSensorFromMemory3(body, anchorPaths);
|
|
9163
|
+
}
|
|
9125
9164
|
function normalizeBody(rawBody, title, titleExplicit) {
|
|
9126
9165
|
const trimmed = rawBody.trim();
|
|
9127
9166
|
if (/^#{1,3}\s+\S/m.test(trimmed)) return `${trimmed}
|
|
@@ -9777,7 +9816,8 @@ import {
|
|
|
9777
9816
|
findProjectRoot as findProjectRoot22,
|
|
9778
9817
|
memoryFilePath as memoryFilePath8,
|
|
9779
9818
|
resolveHaivePaths as resolveHaivePaths19,
|
|
9780
|
-
serializeMemory as serializeMemory17
|
|
9819
|
+
serializeMemory as serializeMemory17,
|
|
9820
|
+
suggestSensorFromMemory as suggestSensorFromMemory4
|
|
9781
9821
|
} from "@hiveai/core";
|
|
9782
9822
|
function registerMemoryTried(memory2) {
|
|
9783
9823
|
memory2.command("tried").description(
|
|
@@ -9820,6 +9860,10 @@ function registerMemoryTried(memory2) {
|
|
|
9820
9860
|
lines.push("", `**Instead, use:** ${opts.instead}`);
|
|
9821
9861
|
}
|
|
9822
9862
|
const body = lines.join("\n") + "\n";
|
|
9863
|
+
const sensor = suggestSensorFromMemory4(body, frontmatter.anchor.paths);
|
|
9864
|
+
if (sensor) {
|
|
9865
|
+
frontmatter.sensor = sensor;
|
|
9866
|
+
}
|
|
9823
9867
|
const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9824
9868
|
await mkdir13(path26.dirname(file), { recursive: true });
|
|
9825
9869
|
if (existsSync40(file)) {
|
|
@@ -9830,6 +9874,7 @@ function registerMemoryTried(memory2) {
|
|
|
9830
9874
|
await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
|
|
9831
9875
|
ui.success(`Recorded: ${path26.relative(root, file)}`);
|
|
9832
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)}`);
|
|
9833
9878
|
});
|
|
9834
9879
|
}
|
|
9835
9880
|
function parseCsv4(value) {
|
|
@@ -12303,8 +12348,8 @@ function registerDoctor(program2) {
|
|
|
12303
12348
|
fix: "haive init"
|
|
12304
12349
|
});
|
|
12305
12350
|
} else {
|
|
12306
|
-
const { readFile:
|
|
12307
|
-
const content = await
|
|
12351
|
+
const { readFile: readFile24 } = await import("fs/promises");
|
|
12352
|
+
const content = await readFile24(paths.projectContext, "utf8");
|
|
12308
12353
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
12309
12354
|
if (isTemplate) {
|
|
12310
12355
|
findings.push({
|
|
@@ -12469,8 +12514,8 @@ function registerDoctor(program2) {
|
|
|
12469
12514
|
let hasClaudeEnforcement = false;
|
|
12470
12515
|
if (existsSync61(claudeSettings)) {
|
|
12471
12516
|
try {
|
|
12472
|
-
const { readFile:
|
|
12473
|
-
const raw = await
|
|
12517
|
+
const { readFile: readFile24 } = await import("fs/promises");
|
|
12518
|
+
const raw = await readFile24(claudeSettings, "utf8");
|
|
12474
12519
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
12475
12520
|
} catch {
|
|
12476
12521
|
hasClaudeEnforcement = false;
|
|
@@ -12493,14 +12538,14 @@ function registerDoctor(program2) {
|
|
|
12493
12538
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
12494
12539
|
});
|
|
12495
12540
|
}
|
|
12496
|
-
findings.push(...await collectInstallFindings(root, "0.10.
|
|
12541
|
+
findings.push(...await collectInstallFindings(root, "0.10.4"));
|
|
12497
12542
|
try {
|
|
12498
12543
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
12499
12544
|
encoding: "utf8",
|
|
12500
12545
|
timeout: 3e3,
|
|
12501
12546
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12502
12547
|
}).trim();
|
|
12503
|
-
const cliVersion = "0.10.
|
|
12548
|
+
const cliVersion = "0.10.4";
|
|
12504
12549
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
12505
12550
|
findings.push({
|
|
12506
12551
|
severity: "warn",
|
|
@@ -13798,7 +13843,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
13798
13843
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
13799
13844
|
});
|
|
13800
13845
|
}
|
|
13801
|
-
findings.push(...await inspectIntegrationVersions(root, "0.10.
|
|
13846
|
+
findings.push(...await inspectIntegrationVersions(root, "0.10.4"));
|
|
13802
13847
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
13803
13848
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
13804
13849
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14408,9 +14453,157 @@ function registerRun(program2) {
|
|
|
14408
14453
|
});
|
|
14409
14454
|
}
|
|
14410
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
|
+
|
|
14411
14604
|
// src/index.ts
|
|
14412
|
-
var program = new
|
|
14413
|
-
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");
|
|
14414
14607
|
registerInit(program);
|
|
14415
14608
|
registerWelcome(program);
|
|
14416
14609
|
registerResolveProject(program);
|
|
@@ -14418,6 +14611,7 @@ registerRuntime(program);
|
|
|
14418
14611
|
registerEnforce(program);
|
|
14419
14612
|
registerRun(program);
|
|
14420
14613
|
registerAgent(program);
|
|
14614
|
+
registerSensors(program);
|
|
14421
14615
|
registerMcp(program);
|
|
14422
14616
|
registerBriefing(program);
|
|
14423
14617
|
registerTui(program);
|
|
@@ -14473,6 +14667,7 @@ var CORE_ROOT_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
14473
14667
|
"briefing",
|
|
14474
14668
|
"enforce",
|
|
14475
14669
|
"run",
|
|
14670
|
+
"sensors",
|
|
14476
14671
|
"sync",
|
|
14477
14672
|
"mcp",
|
|
14478
14673
|
"memory",
|