@hivelore/mcp 0.39.2 → 0.43.2
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 +375 -58
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +60 -5
- package/dist/server.js +379 -58
- package/dist/server.js.map +1 -1
- package/package.json +10 -3
package/dist/server.js
CHANGED
|
@@ -1122,7 +1122,7 @@ async function memApprove(input, ctx) {
|
|
|
1122
1122
|
// src/tools/mem-tried.ts
|
|
1123
1123
|
import { mkdir as mkdir3, writeFile as writeFile9 } from "fs/promises";
|
|
1124
1124
|
import { existsSync as existsSync16 } from "fs";
|
|
1125
|
-
import
|
|
1125
|
+
import path7 from "path";
|
|
1126
1126
|
import {
|
|
1127
1127
|
buildFrontmatter as buildFrontmatter2,
|
|
1128
1128
|
memoryFilePath as memoryFilePath2,
|
|
@@ -1132,25 +1132,146 @@ import {
|
|
|
1132
1132
|
import { z as z16 } from "zod";
|
|
1133
1133
|
|
|
1134
1134
|
// src/tools/propose-sensor.ts
|
|
1135
|
-
import {
|
|
1135
|
+
import { execFileSync } from "child_process";
|
|
1136
1136
|
import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
|
|
1137
|
-
import { existsSync as existsSync15 } from "fs";
|
|
1138
|
-
import
|
|
1137
|
+
import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
|
|
1138
|
+
import os from "os";
|
|
1139
|
+
import path6 from "path";
|
|
1139
1140
|
import {
|
|
1140
1141
|
extractSensorExamples,
|
|
1141
1142
|
extractTestFilePathsFromCommand,
|
|
1142
1143
|
hasPendingTestMarker,
|
|
1143
1144
|
judgeProposedSensor,
|
|
1144
1145
|
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
1146
|
+
scrubbedCommandEnv,
|
|
1147
|
+
sensorPatternBrittleness,
|
|
1145
1148
|
serializeMemory as serializeMemory7
|
|
1146
1149
|
} from "@hivelore/core";
|
|
1147
1150
|
import { z as z15 } from "zod";
|
|
1151
|
+
|
|
1152
|
+
// src/ast-sensors.ts
|
|
1153
|
+
import path5 from "path";
|
|
1154
|
+
var cachedEngine;
|
|
1155
|
+
var registeredDynamicLanguages = /* @__PURE__ */ new Set();
|
|
1156
|
+
async function loadDynamicLanguages(engine) {
|
|
1157
|
+
const registrations = {};
|
|
1158
|
+
const candidates = [
|
|
1159
|
+
["python", () => import("@ast-grep/lang-python")],
|
|
1160
|
+
["go", () => import("@ast-grep/lang-go")],
|
|
1161
|
+
["rust", () => import("@ast-grep/lang-rust")],
|
|
1162
|
+
["java", () => import("@ast-grep/lang-java")]
|
|
1163
|
+
];
|
|
1164
|
+
for (const [name, load] of candidates) {
|
|
1165
|
+
try {
|
|
1166
|
+
const mod = await load();
|
|
1167
|
+
registrations[name] = mod.default;
|
|
1168
|
+
registeredDynamicLanguages.add(name);
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (Object.keys(registrations).length > 0) {
|
|
1173
|
+
engine.registerDynamicLanguage(registrations);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
async function loadAstEngine() {
|
|
1177
|
+
if (cachedEngine !== void 0) return cachedEngine;
|
|
1178
|
+
try {
|
|
1179
|
+
cachedEngine = await import("@ast-grep/napi");
|
|
1180
|
+
await loadDynamicLanguages(cachedEngine);
|
|
1181
|
+
} catch {
|
|
1182
|
+
cachedEngine = null;
|
|
1183
|
+
}
|
|
1184
|
+
return cachedEngine;
|
|
1185
|
+
}
|
|
1186
|
+
async function astEngineAvailable() {
|
|
1187
|
+
return await loadAstEngine() !== null;
|
|
1188
|
+
}
|
|
1189
|
+
function astLangForPath(filePath, explicitLanguage) {
|
|
1190
|
+
if (explicitLanguage) {
|
|
1191
|
+
const normalized = explicitLanguage.toLowerCase();
|
|
1192
|
+
if (["typescript", "tsx", "javascript", "html", "css"].includes(normalized)) {
|
|
1193
|
+
return normalized === "typescript" ? "TypeScript" : normalized === "tsx" ? "Tsx" : normalized === "javascript" ? "JavaScript" : normalized[0].toUpperCase() + normalized.slice(1);
|
|
1194
|
+
}
|
|
1195
|
+
return registeredDynamicLanguages.has(normalized) ? normalized : null;
|
|
1196
|
+
}
|
|
1197
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
1198
|
+
if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
|
|
1199
|
+
if (ext === ".tsx") return "Tsx";
|
|
1200
|
+
if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "JavaScript";
|
|
1201
|
+
if (ext === ".py" && registeredDynamicLanguages.has("python")) return "python";
|
|
1202
|
+
if (ext === ".go" && registeredDynamicLanguages.has("go")) return "go";
|
|
1203
|
+
if (ext === ".rs" && registeredDynamicLanguages.has("rust")) return "rust";
|
|
1204
|
+
if (ext === ".java" && registeredDynamicLanguages.has("java")) return "java";
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
function absentPresentInNode(node, absent) {
|
|
1208
|
+
try {
|
|
1209
|
+
if (node.find(absent)) return true;
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
const text = node.text();
|
|
1213
|
+
try {
|
|
1214
|
+
return new RegExp(absent).test(text);
|
|
1215
|
+
} catch {
|
|
1216
|
+
return text.includes(absent);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
async function runAstPattern(content, filePath, pattern, absent, rule, language) {
|
|
1220
|
+
const engine = await loadAstEngine();
|
|
1221
|
+
if (!engine) return { status: "engine-missing", matches: [] };
|
|
1222
|
+
const langName = astLangForPath(filePath, language);
|
|
1223
|
+
if (!langName) return { status: "unsupported-language", matches: [] };
|
|
1224
|
+
const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
|
|
1225
|
+
let root;
|
|
1226
|
+
try {
|
|
1227
|
+
root = engine.parse(lang, content).root();
|
|
1228
|
+
} catch (err) {
|
|
1229
|
+
return { status: "parse-error", matches: [], detail: String(err).slice(0, 200) };
|
|
1230
|
+
}
|
|
1231
|
+
let nodes;
|
|
1232
|
+
try {
|
|
1233
|
+
const matcher = rule ? { rule: pattern ? { all: [{ pattern }, rule] } : rule } : pattern;
|
|
1234
|
+
if (!matcher) return { status: "invalid-pattern", matches: [], detail: "missing pattern/rule" };
|
|
1235
|
+
nodes = root.findAll(matcher);
|
|
1236
|
+
} catch (err) {
|
|
1237
|
+
return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
|
|
1238
|
+
}
|
|
1239
|
+
const matches = [];
|
|
1240
|
+
for (const node of nodes) {
|
|
1241
|
+
if (absent && absentPresentInNode(node, absent)) continue;
|
|
1242
|
+
const range = node.range();
|
|
1243
|
+
matches.push({
|
|
1244
|
+
startLine: range.start.line + 1,
|
|
1245
|
+
endLine: range.end.line + 1,
|
|
1246
|
+
text: node.text().trim().slice(0, 200)
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
return { status: "ok", matches };
|
|
1250
|
+
}
|
|
1251
|
+
async function runAstSensorOnContent(input) {
|
|
1252
|
+
const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
|
|
1253
|
+
if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
|
|
1254
|
+
const added = input.addedLines;
|
|
1255
|
+
return {
|
|
1256
|
+
status: "ok",
|
|
1257
|
+
matches: scan.matches.filter((m) => {
|
|
1258
|
+
for (let line = m.startLine; line <= m.endLine; line++) {
|
|
1259
|
+
if (added.has(line)) return true;
|
|
1260
|
+
}
|
|
1261
|
+
return false;
|
|
1262
|
+
})
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// src/tools/propose-sensor.ts
|
|
1148
1267
|
var ProposeSensorInputSchema = {
|
|
1149
1268
|
memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
|
|
1150
|
-
kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
|
|
1151
|
-
"regex = pattern matched on added diff lines (default). shell|test = a COMMAND the gate runs when the diff touches the sensor's paths \u2014 routes the team's own oracle (an existing test, an invariant script) to this lesson. Command sensors only execute where enforcement.runCommandSensors=true."
|
|
1269
|
+
kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
|
|
1270
|
+
"regex = pattern matched on added diff lines (default). ast = an ast-grep STRUCTURAL pattern (e.g. 'stripe.paymentIntents.create($$$)') matched on the AST of changed files \u2014 comments and strings can never false-positive; `absent` is a sub-pattern that must be missing INSIDE the match (requires the optional @ast-grep/napi engine). shell|test = a COMMAND the gate runs when the diff touches the sensor's paths \u2014 routes the team's own oracle (an existing test, an invariant script) to this lesson. Command sensors only execute where enforcement.runCommandSensors=true."
|
|
1152
1271
|
),
|
|
1153
|
-
pattern: z15.string().optional().describe("kind=regex: regex matching the
|
|
1272
|
+
pattern: z15.string().optional().describe("kind=regex: regex matching the faulty usage; kind=ast: optional structural pattern (may be combined with `rule`)."),
|
|
1273
|
+
rule: z15.record(z15.unknown()).optional().describe("kind=ast: full ast-grep Rule object (kind/inside/has/not/all/any/etc.). May be used alone or combined with pattern."),
|
|
1274
|
+
language: z15.string().optional().describe("kind=ast: explicit built-in/dynamic language name for non-standard file extensions."),
|
|
1154
1275
|
command: z15.string().optional().describe("kind=shell|test: command to execute (e.g. 'npx vitest run tests/payments/refund.spec.ts'). Non-zero exit = the lesson fires."),
|
|
1155
1276
|
timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
|
|
1156
1277
|
absent: z15.string().optional().describe(
|
|
@@ -1162,6 +1283,9 @@ var ProposeSensorInputSchema = {
|
|
|
1162
1283
|
incident: z15.string().optional().describe(
|
|
1163
1284
|
"Provenance: the real incident this sensor guards \u2014 a ticket/prod ref ('prod #442', 'INC-1029', '2026-06 refund overcharge'). Turns 'a test failed' into 'this reproduces the incident the test exists to prevent'. Surfaced in the block message and the prevention receipt. Strongly recommended for command/test sensors routed from a post-incident test."
|
|
1164
1285
|
),
|
|
1286
|
+
red_ref: z15.string().optional().describe(
|
|
1287
|
+
"kind=shell|test: prove the oracle actually catches the incident. A git ref (commit/branch) of the PRE-FIX state; validation replays it in a scratch worktree and requires the command to FAIL there (RED) in addition to passing on the current tree (GREEN). On success the sensor records red_proven: true \u2014 'the test demonstrably catches the incident', shown in the prevention receipt."
|
|
1288
|
+
),
|
|
1165
1289
|
flags: z15.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
|
|
1166
1290
|
paths: z15.array(z15.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
|
|
1167
1291
|
};
|
|
@@ -1178,7 +1302,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1178
1302
|
const targets = [];
|
|
1179
1303
|
for (const rel of relPaths) {
|
|
1180
1304
|
try {
|
|
1181
|
-
const content =
|
|
1305
|
+
const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
|
|
1182
1306
|
cwd: root,
|
|
1183
1307
|
encoding: "utf8",
|
|
1184
1308
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -1188,7 +1312,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1188
1312
|
continue;
|
|
1189
1313
|
} catch {
|
|
1190
1314
|
}
|
|
1191
|
-
const abs =
|
|
1315
|
+
const abs = path6.resolve(root, rel);
|
|
1192
1316
|
if (!existsSync15(abs)) continue;
|
|
1193
1317
|
try {
|
|
1194
1318
|
targets.push({ path: rel, content: await readFile3(abs, "utf8") });
|
|
@@ -1199,11 +1323,13 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1199
1323
|
}
|
|
1200
1324
|
function runCommandForValidation(command, root, timeoutMs = 12e4) {
|
|
1201
1325
|
try {
|
|
1202
|
-
|
|
1326
|
+
execFileSync("bash", ["-c", command], {
|
|
1203
1327
|
cwd: root,
|
|
1204
1328
|
timeout: timeoutMs,
|
|
1205
1329
|
maxBuffer: 8 * 1024 * 1024,
|
|
1206
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1330
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1331
|
+
// Same containment as the gate executor: an oracle gets a test-runner env, not credentials.
|
|
1332
|
+
env: { ...scrubbedCommandEnv(process.env), HIVELORE_SENSOR: "validation" }
|
|
1207
1333
|
});
|
|
1208
1334
|
return { status: "passed", detail: "exit 0" };
|
|
1209
1335
|
} catch (err) {
|
|
@@ -1217,6 +1343,48 @@ ${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
|
|
|
1217
1343
|
return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
|
|
1218
1344
|
}
|
|
1219
1345
|
}
|
|
1346
|
+
function proveRedOnIncident(command, root, redRef, timeoutMs) {
|
|
1347
|
+
const worktree = path6.join(os.tmpdir(), `hivelore-red-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1348
|
+
let added = false;
|
|
1349
|
+
try {
|
|
1350
|
+
try {
|
|
1351
|
+
execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
|
|
1352
|
+
cwd: root,
|
|
1353
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1354
|
+
timeout: 6e4
|
|
1355
|
+
});
|
|
1356
|
+
added = true;
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
const e = err;
|
|
1359
|
+
return { proven: false, reason: "red-ref-invalid", detail: (e.stderr?.toString() ?? String(err)).slice(0, 300) };
|
|
1360
|
+
}
|
|
1361
|
+
const mainModules = path6.join(root, "node_modules");
|
|
1362
|
+
const wtModules = path6.join(worktree, "node_modules");
|
|
1363
|
+
if (existsSync15(mainModules) && !existsSync15(wtModules)) {
|
|
1364
|
+
try {
|
|
1365
|
+
symlinkSync(mainModules, wtModules, "dir");
|
|
1366
|
+
} catch {
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const run = runCommandForValidation(command, worktree, timeoutMs);
|
|
1370
|
+
if (run.status === "failed") return { proven: true, detail: run.detail };
|
|
1371
|
+
if (run.status === "passed") {
|
|
1372
|
+
return { proven: false, reason: "red-not-proven", detail: "oracle PASSED on the incident state \u2014 it does not catch the incident" };
|
|
1373
|
+
}
|
|
1374
|
+
return { proven: false, reason: "red-unrunnable", detail: run.detail };
|
|
1375
|
+
} finally {
|
|
1376
|
+
if (added) {
|
|
1377
|
+
try {
|
|
1378
|
+
execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
|
|
1379
|
+
} catch {
|
|
1380
|
+
try {
|
|
1381
|
+
rmSync(worktree, { recursive: true, force: true });
|
|
1382
|
+
} catch {
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1220
1388
|
async function proposeSensor(input, ctx) {
|
|
1221
1389
|
if (!existsSync15(ctx.paths.memoriesDir)) {
|
|
1222
1390
|
throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
@@ -1246,6 +1414,17 @@ async function proposeSensor(input, ctx) {
|
|
|
1246
1414
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1247
1415
|
};
|
|
1248
1416
|
}
|
|
1417
|
+
} else if (kind === "ast") {
|
|
1418
|
+
if (!input.pattern?.trim() && !input.rule) {
|
|
1419
|
+
return {
|
|
1420
|
+
accepted: false,
|
|
1421
|
+
memory_id: input.memory_id,
|
|
1422
|
+
severity: input.severity,
|
|
1423
|
+
reason: "invalid-pattern",
|
|
1424
|
+
guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
|
|
1425
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1249
1428
|
} else if (!input.command?.trim()) {
|
|
1250
1429
|
return {
|
|
1251
1430
|
accepted: false,
|
|
@@ -1262,12 +1441,111 @@ async function proposeSensor(input, ctx) {
|
|
|
1262
1441
|
throw new Error(`No memory found with id ${input.memory_id}`);
|
|
1263
1442
|
}
|
|
1264
1443
|
const personalScopeNudge = found.memory.frontmatter.scope === "personal" ? ` Note: this lesson is personal-scoped, so the sensor guards only YOUR machine (personal memories are gitignored). Promote it so the gate travels with the repo: hivelore memory promote ${input.memory_id}.` : "";
|
|
1265
|
-
if (kind
|
|
1266
|
-
const
|
|
1444
|
+
if (kind === "ast") {
|
|
1445
|
+
const pattern = input.pattern?.trim();
|
|
1446
|
+
if (!await astEngineAvailable() && input.severity === "block") {
|
|
1447
|
+
return {
|
|
1448
|
+
accepted: false,
|
|
1449
|
+
memory_id: input.memory_id,
|
|
1450
|
+
severity: input.severity,
|
|
1451
|
+
reason: "ast-engine-missing",
|
|
1452
|
+
guidance: "The optional AST engine is not installed, so this proposal cannot be validated \u2014 a block sensor is only trusted after proof. Install it (`npm i -g @ast-grep/napi`, or add it to the repo) and re-propose.",
|
|
1453
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
|
|
1457
|
+
if (brittleAst && input.severity === "block") {
|
|
1458
|
+
return {
|
|
1459
|
+
accepted: false,
|
|
1460
|
+
memory_id: input.memory_id,
|
|
1461
|
+
severity: input.severity,
|
|
1462
|
+
reason: "brittle",
|
|
1463
|
+
guidance: `The pattern is brittle (${brittleAst}). Use a durable structural pattern, then re-propose.`,
|
|
1464
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
const anchorPathsAst = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
1468
|
+
const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
|
|
1469
|
+
const firedOnAst = [];
|
|
1470
|
+
for (const target of currentTargetsAst) {
|
|
1471
|
+
const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
|
|
1472
|
+
if (scan.status === "invalid-pattern") {
|
|
1473
|
+
return {
|
|
1474
|
+
accepted: false,
|
|
1475
|
+
memory_id: input.memory_id,
|
|
1476
|
+
severity: input.severity,
|
|
1477
|
+
reason: "invalid-pattern",
|
|
1478
|
+
guidance: `The ast-grep pattern is invalid: ${scan.detail ?? "unparseable"}. Fix it and re-propose.`,
|
|
1479
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
if (scan.status === "ok" && scan.matches.length > 0) firedOnAst.push(target.path);
|
|
1483
|
+
}
|
|
1484
|
+
if (firedOnAst.length > 0 && input.severity === "block") {
|
|
1485
|
+
return {
|
|
1486
|
+
accepted: false,
|
|
1487
|
+
memory_id: input.memory_id,
|
|
1488
|
+
severity: input.severity,
|
|
1489
|
+
reason: "fires-on-current",
|
|
1490
|
+
guidance: `The pattern matches the CURRENT (correct) code in ${firedOnAst.join(", ")}. Add/tighten the 'absent' companion sub-pattern so correct usage is excluded, then re-propose.`,
|
|
1491
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: firedOnAst }
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
const badExamplesAst = [
|
|
1495
|
+
...input.bad_example ? [input.bad_example] : [],
|
|
1496
|
+
...extractSensorExamples(found.memory.body)
|
|
1497
|
+
];
|
|
1498
|
+
let firesOnBadAst = null;
|
|
1499
|
+
if (badExamplesAst.length > 0 && await astEngineAvailable()) {
|
|
1500
|
+
const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
|
|
1501
|
+
firesOnBadAst = false;
|
|
1502
|
+
for (const example of badExamplesAst) {
|
|
1503
|
+
const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
|
|
1504
|
+
if (scan.status === "ok" && scan.matches.length > 0) {
|
|
1505
|
+
firesOnBadAst = true;
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
if (firesOnBadAst === false && input.severity === "block") {
|
|
1511
|
+
return {
|
|
1512
|
+
accepted: false,
|
|
1513
|
+
memory_id: input.memory_id,
|
|
1514
|
+
severity: input.severity,
|
|
1515
|
+
reason: "missed-bad-example",
|
|
1516
|
+
guidance: "The pattern did not match the bad example structurally, so it won't catch the mistake. Adjust it, then re-propose.",
|
|
1517
|
+
self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: false, fired_on: [] }
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
const sensorAst = {
|
|
1521
|
+
kind: "ast",
|
|
1522
|
+
...pattern ? { pattern } : {},
|
|
1523
|
+
...input.rule ? { rule: input.rule } : {},
|
|
1524
|
+
...input.language ? { language: input.language } : {},
|
|
1525
|
+
...input.absent ? { absent: input.absent } : {},
|
|
1526
|
+
paths: anchorPathsAst,
|
|
1527
|
+
message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
|
|
1528
|
+
...input.incident?.trim() ? { incident: input.incident.trim() } : {},
|
|
1529
|
+
severity: input.severity,
|
|
1530
|
+
autogen: false,
|
|
1531
|
+
last_fired: null
|
|
1532
|
+
};
|
|
1533
|
+
await writeFile8(found.filePath, serializeMemory7({ frontmatter: { ...found.memory.frontmatter, sensor: sensorAst }, body: found.memory.body }), "utf8");
|
|
1534
|
+
return {
|
|
1535
|
+
accepted: true,
|
|
1536
|
+
memory_id: input.memory_id,
|
|
1537
|
+
severity: input.severity,
|
|
1538
|
+
guidance: "Structural sensor accepted \u2014 it matches the AST, so comments/strings can never false-positive." + (await astEngineAvailable() ? "" : " Note: the AST engine is not installed here; the sensor is UNRUNNABLE (warn-only) until @ast-grep/napi is available.") + personalScopeNudge,
|
|
1539
|
+
self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: firesOnBadAst, fired_on: firedOnAst },
|
|
1540
|
+
file_path: found.filePath
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
if (kind === "shell" || kind === "test") {
|
|
1544
|
+
const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path6.resolve(ctx.paths.root, rel)));
|
|
1267
1545
|
const pendingTests = [];
|
|
1268
1546
|
for (const rel of referencedTests) {
|
|
1269
1547
|
try {
|
|
1270
|
-
if (hasPendingTestMarker(await readFile3(
|
|
1548
|
+
if (hasPendingTestMarker(await readFile3(path6.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
|
|
1271
1549
|
} catch {
|
|
1272
1550
|
}
|
|
1273
1551
|
}
|
|
@@ -1283,6 +1561,21 @@ async function proposeSensor(input, ctx) {
|
|
|
1283
1561
|
}
|
|
1284
1562
|
const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
|
|
1285
1563
|
const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
1564
|
+
let redProven = false;
|
|
1565
|
+
if (input.red_ref?.trim()) {
|
|
1566
|
+
const red = proveRedOnIncident(input.command.trim(), ctx.paths.root, input.red_ref.trim(), input.timeout_ms);
|
|
1567
|
+
if (!red.proven && input.severity === "block") {
|
|
1568
|
+
return {
|
|
1569
|
+
accepted: false,
|
|
1570
|
+
memory_id: input.memory_id,
|
|
1571
|
+
severity: input.severity,
|
|
1572
|
+
reason: red.reason ?? "red-not-proven",
|
|
1573
|
+
guidance: red.reason === "red-ref-invalid" ? `red_ref could not be checked out (${red.detail}). Pass a valid commit/ref of the pre-fix state.` : red.reason === "red-unrunnable" ? `The oracle could not RUN on the incident state (${red.detail}) \u2014 it proves nothing there. Fix the command or drop red_ref.` : "The oracle PASSED on the incident state, so it does not catch the incident it claims to guard. Strengthen the assertion until it goes RED on red_ref, then re-propose. Output: " + red.detail.slice(0, 300),
|
|
1574
|
+
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: false, fired_on: [] }
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
redProven = red.proven;
|
|
1578
|
+
}
|
|
1286
1579
|
if (verdictCmd.status !== "passed" && input.severity === "block") {
|
|
1287
1580
|
return {
|
|
1288
1581
|
accepted: false,
|
|
@@ -1294,6 +1587,16 @@ ${verdictCmd.detail}`,
|
|
|
1294
1587
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
|
|
1295
1588
|
};
|
|
1296
1589
|
}
|
|
1590
|
+
if (input.severity === "block" && !input.red_ref?.trim()) {
|
|
1591
|
+
return {
|
|
1592
|
+
accepted: false,
|
|
1593
|
+
memory_id: input.memory_id,
|
|
1594
|
+
severity: input.severity,
|
|
1595
|
+
reason: "red-required",
|
|
1596
|
+
guidance: "A blocking shell/test sensor requires `red_ref`: the oracle must PASS on the current tree and FAIL on the pre-fix incident state. Pass the incident commit/ref, or propose the sensor at warn severity until RED can be proven.",
|
|
1597
|
+
self_check: { silent_on_current: true, fires_on_bad: null, fired_on: [] }
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1297
1600
|
const sensorCmd = {
|
|
1298
1601
|
kind,
|
|
1299
1602
|
command: input.command.trim(),
|
|
@@ -1301,6 +1604,7 @@ ${verdictCmd.detail}`,
|
|
|
1301
1604
|
paths: anchorPathsCmd,
|
|
1302
1605
|
message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
|
|
1303
1606
|
...input.incident?.trim() ? { incident: input.incident.trim() } : {},
|
|
1607
|
+
...redProven ? { red_proven: true } : {},
|
|
1304
1608
|
severity: input.severity,
|
|
1305
1609
|
autogen: false,
|
|
1306
1610
|
last_fired: null
|
|
@@ -1314,8 +1618,9 @@ ${verdictCmd.detail}`,
|
|
|
1314
1618
|
accepted: true,
|
|
1315
1619
|
memory_id: input.memory_id,
|
|
1316
1620
|
severity: input.severity,
|
|
1317
|
-
guidance: (verdictCmd.status === "passed" ? "Command oracle passes on the current tree; the gate now runs it when the diff touches the sensor's paths (requires enforcement.runCommandSensors=true)." : `Accepted at warn severity, but note: ${verdictCmd.status} on the current tree (${verdictCmd.detail}).`) + (pendingTests.length > 0 ? ` Note: the routed test is still a PENDING stub (${pendingTests.join(", ")}) \u2014 it passes on anything; write the assertion to make this oracle real.` : "") + personalScopeNudge,
|
|
1318
|
-
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
|
|
1621
|
+
guidance: (verdictCmd.status === "passed" ? "Command oracle passes on the current tree; the gate now runs it when the diff touches the sensor's paths (requires enforcement.runCommandSensors=true)." : `Accepted at warn severity, but note: ${verdictCmd.status} on the current tree (${verdictCmd.detail}).`) + (redProven ? " RED proven: the oracle demonstrably FAILS on the incident state (red_ref) \u2014 recorded as red_proven." : " This warn-only oracle is not RED-proven; pass red_ref before promoting it to block.") + (pendingTests.length > 0 ? ` Note: the routed test is still a PENDING stub (${pendingTests.join(", ")}) \u2014 it passes on anything; write the assertion to make this oracle real.` : "") + personalScopeNudge,
|
|
1622
|
+
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
|
|
1623
|
+
file_path: found.filePath
|
|
1319
1624
|
};
|
|
1320
1625
|
}
|
|
1321
1626
|
const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
@@ -1382,14 +1687,17 @@ var MemTriedInputSchema = {
|
|
|
1382
1687
|
paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
|
|
1383
1688
|
author: z16.string().optional().describe("Author handle or email"),
|
|
1384
1689
|
sensor: z16.object({
|
|
1385
|
-
kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test
|
|
1386
|
-
pattern: z16.string().optional().describe("kind=regex:
|
|
1690
|
+
kind: z16.enum(["regex", "ast", "shell", "test"]).default("regex").describe("regex/AST pattern, or a shell/test command"),
|
|
1691
|
+
pattern: z16.string().optional().describe("kind=regex|ast: pattern matching the faulty usage"),
|
|
1692
|
+
rule: z16.record(z16.unknown()).optional().describe("kind=ast: full ast-grep Rule object"),
|
|
1693
|
+
language: z16.string().optional().describe("kind=ast: explicit built-in/dynamic language"),
|
|
1387
1694
|
command: z16.string().optional().describe("kind=shell|test: command the gate runs when the diff touches the sensor's paths (non-zero exit = lesson fires)"),
|
|
1388
1695
|
timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
|
|
1389
1696
|
absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
|
|
1390
1697
|
severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
|
|
1391
1698
|
message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
|
|
1392
1699
|
incident: z16.string().optional().describe("Provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 surfaced when it fires and in the receipt"),
|
|
1700
|
+
red_ref: z16.string().optional().describe("kind=shell|test: pre-fix commit/ref \u2014 the oracle must FAIL on it (proves the test catches the incident; records red_proven)"),
|
|
1393
1701
|
bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
|
|
1394
1702
|
}).optional().describe(
|
|
1395
1703
|
"ONE-SHOT loop close: validate and attach a sensor in the same call (equivalent to a follow-up propose_sensor). Validated against HEAD \u2014 silent on current code, fires on the bad example. If rejected, the attempt is still saved and the verdict tells you how to revise."
|
|
@@ -1421,7 +1729,7 @@ async function memTried(input, ctx) {
|
|
|
1421
1729
|
}
|
|
1422
1730
|
const body = lines.join("\n") + "\n";
|
|
1423
1731
|
const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
1424
|
-
await mkdir3(
|
|
1732
|
+
await mkdir3(path7.dirname(file), { recursive: true });
|
|
1425
1733
|
if (existsSync16(file)) {
|
|
1426
1734
|
throw new Error(`Memory already exists at ${file}`);
|
|
1427
1735
|
}
|
|
@@ -1432,12 +1740,15 @@ async function memTried(input, ctx) {
|
|
|
1432
1740
|
memory_id: frontmatter.id,
|
|
1433
1741
|
kind: input.sensor.kind ?? "regex",
|
|
1434
1742
|
pattern: input.sensor.pattern,
|
|
1743
|
+
rule: input.sensor.rule,
|
|
1744
|
+
language: input.sensor.language,
|
|
1435
1745
|
command: input.sensor.command,
|
|
1436
1746
|
timeout_ms: input.sensor.timeout_ms,
|
|
1437
1747
|
absent: input.sensor.absent,
|
|
1438
1748
|
severity: input.sensor.severity ?? "block",
|
|
1439
1749
|
message: input.sensor.message,
|
|
1440
1750
|
incident: input.sensor.incident,
|
|
1751
|
+
red_ref: input.sensor.red_ref,
|
|
1441
1752
|
bad_example: input.sensor.bad_example,
|
|
1442
1753
|
flags: void 0,
|
|
1443
1754
|
paths: []
|
|
@@ -1479,7 +1790,7 @@ async function memTried(input, ctx) {
|
|
|
1479
1790
|
// src/tools/scaffold-test.ts
|
|
1480
1791
|
import { existsSync as existsSync17, statSync } from "fs";
|
|
1481
1792
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
|
|
1482
|
-
import
|
|
1793
|
+
import path8 from "path";
|
|
1483
1794
|
import { z as z17 } from "zod";
|
|
1484
1795
|
import {
|
|
1485
1796
|
buildProposeCommand,
|
|
@@ -1491,17 +1802,17 @@ import {
|
|
|
1491
1802
|
} from "@hivelore/core";
|
|
1492
1803
|
var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
|
|
1493
1804
|
async function detectForAnchor(root, rel) {
|
|
1494
|
-
let dir =
|
|
1805
|
+
let dir = path8.resolve(root, rel);
|
|
1495
1806
|
try {
|
|
1496
|
-
if (!statSync(dir).isDirectory()) dir =
|
|
1807
|
+
if (!statSync(dir).isDirectory()) dir = path8.dirname(dir);
|
|
1497
1808
|
} catch {
|
|
1498
|
-
if (
|
|
1809
|
+
if (path8.extname(dir)) dir = path8.dirname(dir);
|
|
1499
1810
|
}
|
|
1500
1811
|
while (dir.startsWith(root)) {
|
|
1501
|
-
const pkgJson =
|
|
1812
|
+
const pkgJson = path8.join(dir, "package.json");
|
|
1502
1813
|
const hasPkg = existsSync17(pkgJson);
|
|
1503
|
-
const goMod = existsSync17(
|
|
1504
|
-
const pySignal = PY_SIGNALS.some((s) => existsSync17(
|
|
1814
|
+
const goMod = existsSync17(path8.join(dir, "go.mod"));
|
|
1815
|
+
const pySignal = PY_SIGNALS.some((s) => existsSync17(path8.join(dir, s)));
|
|
1505
1816
|
if (hasPkg || goMod || pySignal) {
|
|
1506
1817
|
let pkg = null;
|
|
1507
1818
|
if (hasPkg) {
|
|
@@ -1511,10 +1822,10 @@ async function detectForAnchor(root, rel) {
|
|
|
1511
1822
|
pkg = null;
|
|
1512
1823
|
}
|
|
1513
1824
|
}
|
|
1514
|
-
const baseDir =
|
|
1825
|
+
const baseDir = path8.relative(root, dir).split(path8.sep).join("/");
|
|
1515
1826
|
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1516
1827
|
}
|
|
1517
|
-
const parent =
|
|
1828
|
+
const parent = path8.dirname(dir);
|
|
1518
1829
|
if (parent === dir || dir === root) break;
|
|
1519
1830
|
dir = parent;
|
|
1520
1831
|
}
|
|
@@ -1580,14 +1891,14 @@ async function scaffoldTest(input, ctx) {
|
|
|
1580
1891
|
}
|
|
1581
1892
|
const results = [];
|
|
1582
1893
|
for (const scaffold of scaffolds) {
|
|
1583
|
-
const abs =
|
|
1894
|
+
const abs = path8.isAbsolute(scaffold.relPath) ? scaffold.relPath : path8.resolve(ctx.paths.root, scaffold.relPath);
|
|
1584
1895
|
let written = false;
|
|
1585
1896
|
let alreadyExists = false;
|
|
1586
1897
|
if (input.write) {
|
|
1587
1898
|
if (existsSync17(abs)) {
|
|
1588
1899
|
alreadyExists = true;
|
|
1589
1900
|
} else {
|
|
1590
|
-
await mkdir4(
|
|
1901
|
+
await mkdir4(path8.dirname(abs), { recursive: true });
|
|
1591
1902
|
await writeFile10(abs, scaffold.content, "utf8");
|
|
1592
1903
|
written = true;
|
|
1593
1904
|
}
|
|
@@ -1621,7 +1932,7 @@ async function scaffoldTest(input, ctx) {
|
|
|
1621
1932
|
// src/tools/ingest-findings.ts
|
|
1622
1933
|
import { existsSync as existsSync18 } from "fs";
|
|
1623
1934
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
|
|
1624
|
-
import
|
|
1935
|
+
import path9 from "path";
|
|
1625
1936
|
import {
|
|
1626
1937
|
draftsFromFindings,
|
|
1627
1938
|
filterNewDrafts,
|
|
@@ -1652,7 +1963,7 @@ async function ingestFindings(input, ctx) {
|
|
|
1652
1963
|
if (input.report && input.report.trim()) {
|
|
1653
1964
|
raw = input.report;
|
|
1654
1965
|
} else if (input.report_path) {
|
|
1655
|
-
const file =
|
|
1966
|
+
const file = path9.resolve(ctx.paths.root, input.report_path);
|
|
1656
1967
|
if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
|
|
1657
1968
|
raw = await readFile5(file, "utf8");
|
|
1658
1969
|
} else {
|
|
@@ -1706,7 +2017,7 @@ async function writeDraft(ctx, draft) {
|
|
|
1706
2017
|
draft.frontmatter.id,
|
|
1707
2018
|
draft.frontmatter.module
|
|
1708
2019
|
);
|
|
1709
|
-
await mkdir5(
|
|
2020
|
+
await mkdir5(path9.dirname(file), { recursive: true });
|
|
1710
2021
|
await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
1711
2022
|
return file;
|
|
1712
2023
|
}
|
|
@@ -1714,7 +2025,7 @@ async function writeDraft(ctx, draft) {
|
|
|
1714
2025
|
// src/tools/mem-session-end.ts
|
|
1715
2026
|
import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
|
|
1716
2027
|
import { existsSync as existsSync20 } from "fs";
|
|
1717
|
-
import
|
|
2028
|
+
import path11 from "path";
|
|
1718
2029
|
import {
|
|
1719
2030
|
buildFrontmatter as buildFrontmatter3,
|
|
1720
2031
|
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
@@ -1732,10 +2043,10 @@ import {
|
|
|
1732
2043
|
} from "@hivelore/core";
|
|
1733
2044
|
import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
|
|
1734
2045
|
import { existsSync as existsSync19 } from "fs";
|
|
1735
|
-
import
|
|
1736
|
-
import { execSync
|
|
2046
|
+
import path10 from "path";
|
|
2047
|
+
import { execSync } from "child_process";
|
|
1737
2048
|
function pendingDistillPath(ctx) {
|
|
1738
|
-
return
|
|
2049
|
+
return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
1739
2050
|
}
|
|
1740
2051
|
var SessionTracker = class {
|
|
1741
2052
|
events = [];
|
|
@@ -1776,7 +2087,7 @@ var SessionTracker = class {
|
|
|
1776
2087
|
}
|
|
1777
2088
|
let gitDiff;
|
|
1778
2089
|
try {
|
|
1779
|
-
const raw =
|
|
2090
|
+
const raw = execSync("git diff HEAD", {
|
|
1780
2091
|
cwd: this.ctx.paths.root,
|
|
1781
2092
|
timeout: 5e3,
|
|
1782
2093
|
encoding: "utf8",
|
|
@@ -1808,7 +2119,7 @@ var SessionTracker = class {
|
|
|
1808
2119
|
const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
|
|
1809
2120
|
let diffStat;
|
|
1810
2121
|
try {
|
|
1811
|
-
diffStat =
|
|
2122
|
+
diffStat = execSync("git diff --stat HEAD", {
|
|
1812
2123
|
cwd: this.ctx.paths.root,
|
|
1813
2124
|
timeout: 5e3,
|
|
1814
2125
|
encoding: "utf8",
|
|
@@ -1852,7 +2163,7 @@ var SessionTracker = class {
|
|
|
1852
2163
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
1853
2164
|
...recapId ? { recap_id: recapId } : {}
|
|
1854
2165
|
};
|
|
1855
|
-
const cacheDir =
|
|
2166
|
+
const cacheDir = path10.join(this.ctx.paths.haiveDir, ".cache");
|
|
1856
2167
|
await mkdir6(cacheDir, { recursive: true });
|
|
1857
2168
|
await writeFile12(
|
|
1858
2169
|
pendingDistillPath(this.ctx),
|
|
@@ -1934,12 +2245,12 @@ async function memSessionEnd(input, ctx) {
|
|
|
1934
2245
|
const body = buildBody(input);
|
|
1935
2246
|
const topic = recapTopic(input.scope, input.module);
|
|
1936
2247
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
1937
|
-
if (!p || !
|
|
1938
|
-
const rel =
|
|
2248
|
+
if (!p || !path11.isAbsolute(p)) return p;
|
|
2249
|
+
const rel = path11.relative(ctx.paths.root, p);
|
|
1939
2250
|
return rel.startsWith("..") ? p : rel;
|
|
1940
2251
|
});
|
|
1941
2252
|
const invalidPaths = normalizedFiles.filter(
|
|
1942
|
-
(p) => !existsSync20(
|
|
2253
|
+
(p) => !existsSync20(path11.resolve(ctx.paths.root, p))
|
|
1943
2254
|
);
|
|
1944
2255
|
if (invalidPaths.length > 0) {
|
|
1945
2256
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
@@ -1990,7 +2301,7 @@ async function memSessionEnd(input, ctx) {
|
|
|
1990
2301
|
frontmatter.id,
|
|
1991
2302
|
frontmatter.module
|
|
1992
2303
|
);
|
|
1993
|
-
await mkdir7(
|
|
2304
|
+
await mkdir7(path11.dirname(file), { recursive: true });
|
|
1994
2305
|
await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
|
|
1995
2306
|
await clearPendingDistill(ctx);
|
|
1996
2307
|
return {
|
|
@@ -2005,7 +2316,7 @@ async function memSessionEnd(input, ctx) {
|
|
|
2005
2316
|
// src/tools/get-briefing.ts
|
|
2006
2317
|
import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
|
|
2007
2318
|
import { existsSync as existsSync22 } from "fs";
|
|
2008
|
-
import
|
|
2319
|
+
import path13 from "path";
|
|
2009
2320
|
import {
|
|
2010
2321
|
allocateBudget,
|
|
2011
2322
|
assessBootstrapState,
|
|
@@ -2052,7 +2363,7 @@ import { z as z20 } from "zod";
|
|
|
2052
2363
|
// src/tools/briefing-helpers.ts
|
|
2053
2364
|
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
2054
2365
|
import { existsSync as existsSync21 } from "fs";
|
|
2055
|
-
import
|
|
2366
|
+
import path12 from "path";
|
|
2056
2367
|
import {
|
|
2057
2368
|
classifyMemoryPriority as coreClassifyPriority,
|
|
2058
2369
|
isGlobPath,
|
|
@@ -2192,7 +2503,7 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
2192
2503
|
const out = [];
|
|
2193
2504
|
for (const m of modules) {
|
|
2194
2505
|
if (!available.has(m)) continue;
|
|
2195
|
-
const file =
|
|
2506
|
+
const file = path12.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
2196
2507
|
if (existsSync21(file)) {
|
|
2197
2508
|
out.push({ name: m, content: await readFile6(file, "utf8") });
|
|
2198
2509
|
}
|
|
@@ -2220,6 +2531,8 @@ var GetBriefingInputSchema = {
|
|
|
2220
2531
|
),
|
|
2221
2532
|
include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
2222
2533
|
track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
2534
|
+
memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
|
|
2535
|
+
deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
|
|
2223
2536
|
format: z20.enum(["full", "compact", "actions"]).default("full").describe(
|
|
2224
2537
|
"Output format: 'full' returns memory bodies (honors token budget via truncation); 'compact' returns a 1-line summary per memory (call mem_get for detail); 'actions' squeezes bodies to actionable bullet lines \u2014 fewer tokens vs full."
|
|
2225
2538
|
),
|
|
@@ -2248,9 +2561,11 @@ async function getBriefing(input, ctx) {
|
|
|
2248
2561
|
let searchMode = "literal";
|
|
2249
2562
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
2250
2563
|
let byId = /* @__PURE__ */ new Map();
|
|
2564
|
+
const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
|
|
2565
|
+
const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
|
|
2251
2566
|
let lastSession;
|
|
2252
2567
|
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2253
|
-
const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2568
|
+
const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
|
|
2254
2569
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
2255
2570
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2256
2571
|
);
|
|
@@ -2283,10 +2598,11 @@ async function getBriefing(input, ctx) {
|
|
|
2283
2598
|
if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
|
|
2284
2599
|
return true;
|
|
2285
2600
|
});
|
|
2286
|
-
usage = await loadUsageIndex8(ctx.paths);
|
|
2601
|
+
usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
|
|
2287
2602
|
byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
|
|
2288
|
-
const
|
|
2289
|
-
|
|
2603
|
+
const semanticEnabled = input.semantic && input.deterministic !== true;
|
|
2604
|
+
const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
|
|
2605
|
+
if (input.task && semanticEnabled) {
|
|
2290
2606
|
searchMode = semanticHits ? "semantic" : "literal_fallback";
|
|
2291
2607
|
}
|
|
2292
2608
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -2622,7 +2938,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2622
2938
|
}
|
|
2623
2939
|
}
|
|
2624
2940
|
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2625
|
-
const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2941
|
+
const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
|
|
2626
2942
|
for (const { memory } of allMems) {
|
|
2627
2943
|
const fm = memory.frontmatter;
|
|
2628
2944
|
if (!fm.requires_human_approval) continue;
|
|
@@ -2679,7 +2995,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2679
2995
|
pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
|
|
2680
2996
|
} catch {
|
|
2681
2997
|
}
|
|
2682
|
-
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
|
|
2998
|
+
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
|
|
2683
2999
|
const cmForBootstrap = await loadCodeMap(ctx.paths);
|
|
2684
3000
|
let existingModules = [];
|
|
2685
3001
|
try {
|
|
@@ -2690,7 +3006,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2690
3006
|
const bootstrap = assessBootstrapState({
|
|
2691
3007
|
projectContextRaw: pcRaw,
|
|
2692
3008
|
memories: allForBootstrap,
|
|
2693
|
-
codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
|
|
3009
|
+
codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
|
|
2694
3010
|
existingModules
|
|
2695
3011
|
});
|
|
2696
3012
|
if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
|
|
@@ -2813,7 +3129,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2813
3129
|
};
|
|
2814
3130
|
}
|
|
2815
3131
|
async function detectRunCommands(root) {
|
|
2816
|
-
const pkgPath =
|
|
3132
|
+
const pkgPath = path13.join(root, "package.json");
|
|
2817
3133
|
if (!existsSync22(pkgPath)) return null;
|
|
2818
3134
|
try {
|
|
2819
3135
|
const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
|
|
@@ -2881,7 +3197,7 @@ function oneLine(value) {
|
|
|
2881
3197
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
2882
3198
|
}
|
|
2883
3199
|
function serverVersion() {
|
|
2884
|
-
return true ? "0.
|
|
3200
|
+
return true ? "0.43.2" : "dev";
|
|
2885
3201
|
}
|
|
2886
3202
|
|
|
2887
3203
|
// src/tools/code-map.ts
|
|
@@ -4054,6 +4370,7 @@ ${template}\`\`\`
|
|
|
4054
4370
|
// src/prompts/bootstrap-repo.ts
|
|
4055
4371
|
import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
|
|
4056
4372
|
import { existsSync as existsSync29 } from "fs";
|
|
4373
|
+
import path14 from "path";
|
|
4057
4374
|
import {
|
|
4058
4375
|
assessBootstrapState as assessBootstrapState2,
|
|
4059
4376
|
loadCodeMap as loadCodeMap4,
|
|
@@ -4081,7 +4398,7 @@ async function currentAssessment(ctx) {
|
|
|
4081
4398
|
return assessBootstrapState2({
|
|
4082
4399
|
projectContextRaw,
|
|
4083
4400
|
memories,
|
|
4084
|
-
codeFiles: codeMap ? Object.keys(codeMap.files) : [],
|
|
4401
|
+
codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
|
|
4085
4402
|
existingModules
|
|
4086
4403
|
});
|
|
4087
4404
|
}
|
|
@@ -4309,7 +4626,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4309
4626
|
// src/server.ts
|
|
4310
4627
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
|
|
4311
4628
|
var SERVER_NAME = "hivelore";
|
|
4312
|
-
var SERVER_VERSION = "0.
|
|
4629
|
+
var SERVER_VERSION = "0.43.2";
|
|
4313
4630
|
function jsonResult(data) {
|
|
4314
4631
|
return {
|
|
4315
4632
|
content: [
|
|
@@ -5261,6 +5578,8 @@ export {
|
|
|
5261
5578
|
SERVER_VERSION,
|
|
5262
5579
|
TOOL_PROFILES,
|
|
5263
5580
|
antiPatternsCheck,
|
|
5581
|
+
astEngineAvailable,
|
|
5582
|
+
astLangForPath,
|
|
5264
5583
|
codeMapTool,
|
|
5265
5584
|
codeSearch,
|
|
5266
5585
|
createHaiveServer,
|
|
@@ -5281,6 +5600,8 @@ export {
|
|
|
5281
5600
|
printHaiveMcpVersion,
|
|
5282
5601
|
proposeSensor,
|
|
5283
5602
|
readPresumedCorrectTargets,
|
|
5603
|
+
runAstPattern,
|
|
5604
|
+
runAstSensorOnContent,
|
|
5284
5605
|
runHaiveMcpStdio,
|
|
5285
5606
|
scaffoldTest
|
|
5286
5607
|
};
|