@hivelore/cli 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/{chunk-XA5FXG6E.js → chunk-FXDGOBPT.js} +376 -59
- package/dist/chunk-FXDGOBPT.js.map +1 -0
- package/dist/index.js +550 -49
- package/dist/index.js.map +1 -1
- package/dist/{server-G6N6NJ64.js → server-WW6JHBYY.js} +10 -2
- package/package.json +11 -4
- package/dist/chunk-XA5FXG6E.js.map +0 -1
- /package/dist/{server-G6N6NJ64.js.map → server-WW6JHBYY.js.map} +0 -0
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
import { z as z14 } from "zod";
|
|
126
126
|
import { mkdir as mkdir3, writeFile as writeFile9 } from "fs/promises";
|
|
127
127
|
import { existsSync as existsSync16 } from "fs";
|
|
128
|
-
import
|
|
128
|
+
import path7 from "path";
|
|
129
129
|
import {
|
|
130
130
|
buildFrontmatter as buildFrontmatter2,
|
|
131
131
|
memoryFilePath as memoryFilePath2,
|
|
@@ -133,22 +133,26 @@ import {
|
|
|
133
133
|
suggestSensorSeed as suggestSensorSeed2
|
|
134
134
|
} from "@hivelore/core";
|
|
135
135
|
import { z as z16 } from "zod";
|
|
136
|
-
import {
|
|
136
|
+
import { execFileSync } from "child_process";
|
|
137
137
|
import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
|
|
138
|
-
import { existsSync as existsSync15 } from "fs";
|
|
139
|
-
import
|
|
138
|
+
import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
|
|
139
|
+
import os from "os";
|
|
140
|
+
import path6 from "path";
|
|
140
141
|
import {
|
|
141
142
|
extractSensorExamples,
|
|
142
143
|
extractTestFilePathsFromCommand,
|
|
143
144
|
hasPendingTestMarker,
|
|
144
145
|
judgeProposedSensor,
|
|
145
146
|
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
147
|
+
scrubbedCommandEnv,
|
|
148
|
+
sensorPatternBrittleness,
|
|
146
149
|
serializeMemory as serializeMemory7
|
|
147
150
|
} from "@hivelore/core";
|
|
148
151
|
import { z as z15 } from "zod";
|
|
152
|
+
import path5 from "path";
|
|
149
153
|
import { existsSync as existsSync17, statSync } from "fs";
|
|
150
154
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
|
|
151
|
-
import
|
|
155
|
+
import path8 from "path";
|
|
152
156
|
import { z as z17 } from "zod";
|
|
153
157
|
import {
|
|
154
158
|
buildProposeCommand,
|
|
@@ -160,7 +164,7 @@ import {
|
|
|
160
164
|
} from "@hivelore/core";
|
|
161
165
|
import { existsSync as existsSync18 } from "fs";
|
|
162
166
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
|
|
163
|
-
import
|
|
167
|
+
import path9 from "path";
|
|
164
168
|
import {
|
|
165
169
|
draftsFromFindings,
|
|
166
170
|
filterNewDrafts,
|
|
@@ -172,7 +176,7 @@ import {
|
|
|
172
176
|
import { z as z18 } from "zod";
|
|
173
177
|
import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
|
|
174
178
|
import { existsSync as existsSync20 } from "fs";
|
|
175
|
-
import
|
|
179
|
+
import path11 from "path";
|
|
176
180
|
import {
|
|
177
181
|
buildFrontmatter as buildFrontmatter3,
|
|
178
182
|
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
@@ -188,11 +192,11 @@ import {
|
|
|
188
192
|
} from "@hivelore/core";
|
|
189
193
|
import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
|
|
190
194
|
import { existsSync as existsSync19 } from "fs";
|
|
191
|
-
import
|
|
192
|
-
import { execSync
|
|
195
|
+
import path10 from "path";
|
|
196
|
+
import { execSync } from "child_process";
|
|
193
197
|
import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
|
|
194
198
|
import { existsSync as existsSync22 } from "fs";
|
|
195
|
-
import
|
|
199
|
+
import path13 from "path";
|
|
196
200
|
import {
|
|
197
201
|
allocateBudget,
|
|
198
202
|
assessBootstrapState,
|
|
@@ -237,7 +241,7 @@ import {
|
|
|
237
241
|
import { z as z20 } from "zod";
|
|
238
242
|
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
239
243
|
import { existsSync as existsSync21 } from "fs";
|
|
240
|
-
import
|
|
244
|
+
import path12 from "path";
|
|
241
245
|
import {
|
|
242
246
|
classifyMemoryPriority as coreClassifyPriority,
|
|
243
247
|
isGlobPath,
|
|
@@ -301,6 +305,7 @@ import { z as z32 } from "zod";
|
|
|
301
305
|
import { z as z33 } from "zod";
|
|
302
306
|
import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
|
|
303
307
|
import { existsSync as existsSync29 } from "fs";
|
|
308
|
+
import path14 from "path";
|
|
304
309
|
import {
|
|
305
310
|
assessBootstrapState as assessBootstrapState2,
|
|
306
311
|
loadCodeMap as loadCodeMap4,
|
|
@@ -1278,12 +1283,125 @@ async function memApprove(input, ctx) {
|
|
|
1278
1283
|
file_path: found.filePath
|
|
1279
1284
|
};
|
|
1280
1285
|
}
|
|
1286
|
+
var cachedEngine;
|
|
1287
|
+
var registeredDynamicLanguages = /* @__PURE__ */ new Set();
|
|
1288
|
+
async function loadDynamicLanguages(engine) {
|
|
1289
|
+
const registrations = {};
|
|
1290
|
+
const candidates = [
|
|
1291
|
+
["python", () => import("@ast-grep/lang-python")],
|
|
1292
|
+
["go", () => import("@ast-grep/lang-go")],
|
|
1293
|
+
["rust", () => import("@ast-grep/lang-rust")],
|
|
1294
|
+
["java", () => import("@ast-grep/lang-java")]
|
|
1295
|
+
];
|
|
1296
|
+
for (const [name, load] of candidates) {
|
|
1297
|
+
try {
|
|
1298
|
+
const mod = await load();
|
|
1299
|
+
registrations[name] = mod.default;
|
|
1300
|
+
registeredDynamicLanguages.add(name);
|
|
1301
|
+
} catch {
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (Object.keys(registrations).length > 0) {
|
|
1305
|
+
engine.registerDynamicLanguage(registrations);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
async function loadAstEngine() {
|
|
1309
|
+
if (cachedEngine !== void 0) return cachedEngine;
|
|
1310
|
+
try {
|
|
1311
|
+
cachedEngine = await import("@ast-grep/napi");
|
|
1312
|
+
await loadDynamicLanguages(cachedEngine);
|
|
1313
|
+
} catch {
|
|
1314
|
+
cachedEngine = null;
|
|
1315
|
+
}
|
|
1316
|
+
return cachedEngine;
|
|
1317
|
+
}
|
|
1318
|
+
async function astEngineAvailable() {
|
|
1319
|
+
return await loadAstEngine() !== null;
|
|
1320
|
+
}
|
|
1321
|
+
function astLangForPath(filePath, explicitLanguage) {
|
|
1322
|
+
if (explicitLanguage) {
|
|
1323
|
+
const normalized = explicitLanguage.toLowerCase();
|
|
1324
|
+
if (["typescript", "tsx", "javascript", "html", "css"].includes(normalized)) {
|
|
1325
|
+
return normalized === "typescript" ? "TypeScript" : normalized === "tsx" ? "Tsx" : normalized === "javascript" ? "JavaScript" : normalized[0].toUpperCase() + normalized.slice(1);
|
|
1326
|
+
}
|
|
1327
|
+
return registeredDynamicLanguages.has(normalized) ? normalized : null;
|
|
1328
|
+
}
|
|
1329
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
1330
|
+
if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
|
|
1331
|
+
if (ext === ".tsx") return "Tsx";
|
|
1332
|
+
if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "JavaScript";
|
|
1333
|
+
if (ext === ".py" && registeredDynamicLanguages.has("python")) return "python";
|
|
1334
|
+
if (ext === ".go" && registeredDynamicLanguages.has("go")) return "go";
|
|
1335
|
+
if (ext === ".rs" && registeredDynamicLanguages.has("rust")) return "rust";
|
|
1336
|
+
if (ext === ".java" && registeredDynamicLanguages.has("java")) return "java";
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
function absentPresentInNode(node, absent) {
|
|
1340
|
+
try {
|
|
1341
|
+
if (node.find(absent)) return true;
|
|
1342
|
+
} catch {
|
|
1343
|
+
}
|
|
1344
|
+
const text = node.text();
|
|
1345
|
+
try {
|
|
1346
|
+
return new RegExp(absent).test(text);
|
|
1347
|
+
} catch {
|
|
1348
|
+
return text.includes(absent);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async function runAstPattern(content, filePath, pattern, absent, rule, language) {
|
|
1352
|
+
const engine = await loadAstEngine();
|
|
1353
|
+
if (!engine) return { status: "engine-missing", matches: [] };
|
|
1354
|
+
const langName = astLangForPath(filePath, language);
|
|
1355
|
+
if (!langName) return { status: "unsupported-language", matches: [] };
|
|
1356
|
+
const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
|
|
1357
|
+
let root;
|
|
1358
|
+
try {
|
|
1359
|
+
root = engine.parse(lang, content).root();
|
|
1360
|
+
} catch (err) {
|
|
1361
|
+
return { status: "parse-error", matches: [], detail: String(err).slice(0, 200) };
|
|
1362
|
+
}
|
|
1363
|
+
let nodes;
|
|
1364
|
+
try {
|
|
1365
|
+
const matcher = rule ? { rule: pattern ? { all: [{ pattern }, rule] } : rule } : pattern;
|
|
1366
|
+
if (!matcher) return { status: "invalid-pattern", matches: [], detail: "missing pattern/rule" };
|
|
1367
|
+
nodes = root.findAll(matcher);
|
|
1368
|
+
} catch (err) {
|
|
1369
|
+
return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
|
|
1370
|
+
}
|
|
1371
|
+
const matches = [];
|
|
1372
|
+
for (const node of nodes) {
|
|
1373
|
+
if (absent && absentPresentInNode(node, absent)) continue;
|
|
1374
|
+
const range = node.range();
|
|
1375
|
+
matches.push({
|
|
1376
|
+
startLine: range.start.line + 1,
|
|
1377
|
+
endLine: range.end.line + 1,
|
|
1378
|
+
text: node.text().trim().slice(0, 200)
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
return { status: "ok", matches };
|
|
1382
|
+
}
|
|
1383
|
+
async function runAstSensorOnContent(input) {
|
|
1384
|
+
const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
|
|
1385
|
+
if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
|
|
1386
|
+
const added = input.addedLines;
|
|
1387
|
+
return {
|
|
1388
|
+
status: "ok",
|
|
1389
|
+
matches: scan.matches.filter((m) => {
|
|
1390
|
+
for (let line = m.startLine; line <= m.endLine; line++) {
|
|
1391
|
+
if (added.has(line)) return true;
|
|
1392
|
+
}
|
|
1393
|
+
return false;
|
|
1394
|
+
})
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1281
1397
|
var ProposeSensorInputSchema = {
|
|
1282
1398
|
memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
|
|
1283
|
-
kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
|
|
1284
|
-
"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."
|
|
1399
|
+
kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
|
|
1400
|
+
"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."
|
|
1285
1401
|
),
|
|
1286
|
-
pattern: z15.string().optional().describe("kind=regex: regex matching the
|
|
1402
|
+
pattern: z15.string().optional().describe("kind=regex: regex matching the faulty usage; kind=ast: optional structural pattern (may be combined with `rule`)."),
|
|
1403
|
+
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."),
|
|
1404
|
+
language: z15.string().optional().describe("kind=ast: explicit built-in/dynamic language name for non-standard file extensions."),
|
|
1287
1405
|
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."),
|
|
1288
1406
|
timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
|
|
1289
1407
|
absent: z15.string().optional().describe(
|
|
@@ -1295,6 +1413,9 @@ var ProposeSensorInputSchema = {
|
|
|
1295
1413
|
incident: z15.string().optional().describe(
|
|
1296
1414
|
"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."
|
|
1297
1415
|
),
|
|
1416
|
+
red_ref: z15.string().optional().describe(
|
|
1417
|
+
"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."
|
|
1418
|
+
),
|
|
1298
1419
|
flags: z15.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
|
|
1299
1420
|
paths: z15.array(z15.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
|
|
1300
1421
|
};
|
|
@@ -1311,7 +1432,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1311
1432
|
const targets = [];
|
|
1312
1433
|
for (const rel of relPaths) {
|
|
1313
1434
|
try {
|
|
1314
|
-
const content =
|
|
1435
|
+
const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
|
|
1315
1436
|
cwd: root,
|
|
1316
1437
|
encoding: "utf8",
|
|
1317
1438
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -1321,7 +1442,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1321
1442
|
continue;
|
|
1322
1443
|
} catch {
|
|
1323
1444
|
}
|
|
1324
|
-
const abs =
|
|
1445
|
+
const abs = path6.resolve(root, rel);
|
|
1325
1446
|
if (!existsSync15(abs)) continue;
|
|
1326
1447
|
try {
|
|
1327
1448
|
targets.push({ path: rel, content: await readFile3(abs, "utf8") });
|
|
@@ -1332,11 +1453,13 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1332
1453
|
}
|
|
1333
1454
|
function runCommandForValidation(command, root, timeoutMs = 12e4) {
|
|
1334
1455
|
try {
|
|
1335
|
-
|
|
1456
|
+
execFileSync("bash", ["-c", command], {
|
|
1336
1457
|
cwd: root,
|
|
1337
1458
|
timeout: timeoutMs,
|
|
1338
1459
|
maxBuffer: 8 * 1024 * 1024,
|
|
1339
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1460
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1461
|
+
// Same containment as the gate executor: an oracle gets a test-runner env, not credentials.
|
|
1462
|
+
env: { ...scrubbedCommandEnv(process.env), HIVELORE_SENSOR: "validation" }
|
|
1340
1463
|
});
|
|
1341
1464
|
return { status: "passed", detail: "exit 0" };
|
|
1342
1465
|
} catch (err) {
|
|
@@ -1350,6 +1473,48 @@ ${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
|
|
|
1350
1473
|
return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
|
|
1351
1474
|
}
|
|
1352
1475
|
}
|
|
1476
|
+
function proveRedOnIncident(command, root, redRef, timeoutMs) {
|
|
1477
|
+
const worktree = path6.join(os.tmpdir(), `hivelore-red-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1478
|
+
let added = false;
|
|
1479
|
+
try {
|
|
1480
|
+
try {
|
|
1481
|
+
execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
|
|
1482
|
+
cwd: root,
|
|
1483
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1484
|
+
timeout: 6e4
|
|
1485
|
+
});
|
|
1486
|
+
added = true;
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
const e = err;
|
|
1489
|
+
return { proven: false, reason: "red-ref-invalid", detail: (e.stderr?.toString() ?? String(err)).slice(0, 300) };
|
|
1490
|
+
}
|
|
1491
|
+
const mainModules = path6.join(root, "node_modules");
|
|
1492
|
+
const wtModules = path6.join(worktree, "node_modules");
|
|
1493
|
+
if (existsSync15(mainModules) && !existsSync15(wtModules)) {
|
|
1494
|
+
try {
|
|
1495
|
+
symlinkSync(mainModules, wtModules, "dir");
|
|
1496
|
+
} catch {
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
const run = runCommandForValidation(command, worktree, timeoutMs);
|
|
1500
|
+
if (run.status === "failed") return { proven: true, detail: run.detail };
|
|
1501
|
+
if (run.status === "passed") {
|
|
1502
|
+
return { proven: false, reason: "red-not-proven", detail: "oracle PASSED on the incident state \u2014 it does not catch the incident" };
|
|
1503
|
+
}
|
|
1504
|
+
return { proven: false, reason: "red-unrunnable", detail: run.detail };
|
|
1505
|
+
} finally {
|
|
1506
|
+
if (added) {
|
|
1507
|
+
try {
|
|
1508
|
+
execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
|
|
1509
|
+
} catch {
|
|
1510
|
+
try {
|
|
1511
|
+
rmSync(worktree, { recursive: true, force: true });
|
|
1512
|
+
} catch {
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1353
1518
|
async function proposeSensor(input, ctx) {
|
|
1354
1519
|
if (!existsSync15(ctx.paths.memoriesDir)) {
|
|
1355
1520
|
throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
@@ -1379,6 +1544,17 @@ async function proposeSensor(input, ctx) {
|
|
|
1379
1544
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1380
1545
|
};
|
|
1381
1546
|
}
|
|
1547
|
+
} else if (kind === "ast") {
|
|
1548
|
+
if (!input.pattern?.trim() && !input.rule) {
|
|
1549
|
+
return {
|
|
1550
|
+
accepted: false,
|
|
1551
|
+
memory_id: input.memory_id,
|
|
1552
|
+
severity: input.severity,
|
|
1553
|
+
reason: "invalid-pattern",
|
|
1554
|
+
guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
|
|
1555
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1382
1558
|
} else if (!input.command?.trim()) {
|
|
1383
1559
|
return {
|
|
1384
1560
|
accepted: false,
|
|
@@ -1395,12 +1571,111 @@ async function proposeSensor(input, ctx) {
|
|
|
1395
1571
|
throw new Error(`No memory found with id ${input.memory_id}`);
|
|
1396
1572
|
}
|
|
1397
1573
|
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}.` : "";
|
|
1398
|
-
if (kind
|
|
1399
|
-
const
|
|
1574
|
+
if (kind === "ast") {
|
|
1575
|
+
const pattern = input.pattern?.trim();
|
|
1576
|
+
if (!await astEngineAvailable() && input.severity === "block") {
|
|
1577
|
+
return {
|
|
1578
|
+
accepted: false,
|
|
1579
|
+
memory_id: input.memory_id,
|
|
1580
|
+
severity: input.severity,
|
|
1581
|
+
reason: "ast-engine-missing",
|
|
1582
|
+
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.",
|
|
1583
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
|
|
1587
|
+
if (brittleAst && input.severity === "block") {
|
|
1588
|
+
return {
|
|
1589
|
+
accepted: false,
|
|
1590
|
+
memory_id: input.memory_id,
|
|
1591
|
+
severity: input.severity,
|
|
1592
|
+
reason: "brittle",
|
|
1593
|
+
guidance: `The pattern is brittle (${brittleAst}). Use a durable structural pattern, then re-propose.`,
|
|
1594
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
const anchorPathsAst = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
1598
|
+
const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
|
|
1599
|
+
const firedOnAst = [];
|
|
1600
|
+
for (const target of currentTargetsAst) {
|
|
1601
|
+
const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
|
|
1602
|
+
if (scan.status === "invalid-pattern") {
|
|
1603
|
+
return {
|
|
1604
|
+
accepted: false,
|
|
1605
|
+
memory_id: input.memory_id,
|
|
1606
|
+
severity: input.severity,
|
|
1607
|
+
reason: "invalid-pattern",
|
|
1608
|
+
guidance: `The ast-grep pattern is invalid: ${scan.detail ?? "unparseable"}. Fix it and re-propose.`,
|
|
1609
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
if (scan.status === "ok" && scan.matches.length > 0) firedOnAst.push(target.path);
|
|
1613
|
+
}
|
|
1614
|
+
if (firedOnAst.length > 0 && input.severity === "block") {
|
|
1615
|
+
return {
|
|
1616
|
+
accepted: false,
|
|
1617
|
+
memory_id: input.memory_id,
|
|
1618
|
+
severity: input.severity,
|
|
1619
|
+
reason: "fires-on-current",
|
|
1620
|
+
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.`,
|
|
1621
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: firedOnAst }
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
const badExamplesAst = [
|
|
1625
|
+
...input.bad_example ? [input.bad_example] : [],
|
|
1626
|
+
...extractSensorExamples(found.memory.body)
|
|
1627
|
+
];
|
|
1628
|
+
let firesOnBadAst = null;
|
|
1629
|
+
if (badExamplesAst.length > 0 && await astEngineAvailable()) {
|
|
1630
|
+
const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
|
|
1631
|
+
firesOnBadAst = false;
|
|
1632
|
+
for (const example of badExamplesAst) {
|
|
1633
|
+
const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
|
|
1634
|
+
if (scan.status === "ok" && scan.matches.length > 0) {
|
|
1635
|
+
firesOnBadAst = true;
|
|
1636
|
+
break;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (firesOnBadAst === false && input.severity === "block") {
|
|
1641
|
+
return {
|
|
1642
|
+
accepted: false,
|
|
1643
|
+
memory_id: input.memory_id,
|
|
1644
|
+
severity: input.severity,
|
|
1645
|
+
reason: "missed-bad-example",
|
|
1646
|
+
guidance: "The pattern did not match the bad example structurally, so it won't catch the mistake. Adjust it, then re-propose.",
|
|
1647
|
+
self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: false, fired_on: [] }
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
const sensorAst = {
|
|
1651
|
+
kind: "ast",
|
|
1652
|
+
...pattern ? { pattern } : {},
|
|
1653
|
+
...input.rule ? { rule: input.rule } : {},
|
|
1654
|
+
...input.language ? { language: input.language } : {},
|
|
1655
|
+
...input.absent ? { absent: input.absent } : {},
|
|
1656
|
+
paths: anchorPathsAst,
|
|
1657
|
+
message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
|
|
1658
|
+
...input.incident?.trim() ? { incident: input.incident.trim() } : {},
|
|
1659
|
+
severity: input.severity,
|
|
1660
|
+
autogen: false,
|
|
1661
|
+
last_fired: null
|
|
1662
|
+
};
|
|
1663
|
+
await writeFile8(found.filePath, serializeMemory7({ frontmatter: { ...found.memory.frontmatter, sensor: sensorAst }, body: found.memory.body }), "utf8");
|
|
1664
|
+
return {
|
|
1665
|
+
accepted: true,
|
|
1666
|
+
memory_id: input.memory_id,
|
|
1667
|
+
severity: input.severity,
|
|
1668
|
+
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,
|
|
1669
|
+
self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: firesOnBadAst, fired_on: firedOnAst },
|
|
1670
|
+
file_path: found.filePath
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
if (kind === "shell" || kind === "test") {
|
|
1674
|
+
const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path6.resolve(ctx.paths.root, rel)));
|
|
1400
1675
|
const pendingTests = [];
|
|
1401
1676
|
for (const rel of referencedTests) {
|
|
1402
1677
|
try {
|
|
1403
|
-
if (hasPendingTestMarker(await readFile3(
|
|
1678
|
+
if (hasPendingTestMarker(await readFile3(path6.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
|
|
1404
1679
|
} catch {
|
|
1405
1680
|
}
|
|
1406
1681
|
}
|
|
@@ -1416,6 +1691,21 @@ async function proposeSensor(input, ctx) {
|
|
|
1416
1691
|
}
|
|
1417
1692
|
const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
|
|
1418
1693
|
const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
1694
|
+
let redProven = false;
|
|
1695
|
+
if (input.red_ref?.trim()) {
|
|
1696
|
+
const red = proveRedOnIncident(input.command.trim(), ctx.paths.root, input.red_ref.trim(), input.timeout_ms);
|
|
1697
|
+
if (!red.proven && input.severity === "block") {
|
|
1698
|
+
return {
|
|
1699
|
+
accepted: false,
|
|
1700
|
+
memory_id: input.memory_id,
|
|
1701
|
+
severity: input.severity,
|
|
1702
|
+
reason: red.reason ?? "red-not-proven",
|
|
1703
|
+
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),
|
|
1704
|
+
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: false, fired_on: [] }
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
redProven = red.proven;
|
|
1708
|
+
}
|
|
1419
1709
|
if (verdictCmd.status !== "passed" && input.severity === "block") {
|
|
1420
1710
|
return {
|
|
1421
1711
|
accepted: false,
|
|
@@ -1427,6 +1717,16 @@ ${verdictCmd.detail}`,
|
|
|
1427
1717
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
|
|
1428
1718
|
};
|
|
1429
1719
|
}
|
|
1720
|
+
if (input.severity === "block" && !input.red_ref?.trim()) {
|
|
1721
|
+
return {
|
|
1722
|
+
accepted: false,
|
|
1723
|
+
memory_id: input.memory_id,
|
|
1724
|
+
severity: input.severity,
|
|
1725
|
+
reason: "red-required",
|
|
1726
|
+
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.",
|
|
1727
|
+
self_check: { silent_on_current: true, fires_on_bad: null, fired_on: [] }
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1430
1730
|
const sensorCmd = {
|
|
1431
1731
|
kind,
|
|
1432
1732
|
command: input.command.trim(),
|
|
@@ -1434,6 +1734,7 @@ ${verdictCmd.detail}`,
|
|
|
1434
1734
|
paths: anchorPathsCmd,
|
|
1435
1735
|
message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
|
|
1436
1736
|
...input.incident?.trim() ? { incident: input.incident.trim() } : {},
|
|
1737
|
+
...redProven ? { red_proven: true } : {},
|
|
1437
1738
|
severity: input.severity,
|
|
1438
1739
|
autogen: false,
|
|
1439
1740
|
last_fired: null
|
|
@@ -1447,8 +1748,9 @@ ${verdictCmd.detail}`,
|
|
|
1447
1748
|
accepted: true,
|
|
1448
1749
|
memory_id: input.memory_id,
|
|
1449
1750
|
severity: input.severity,
|
|
1450
|
-
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,
|
|
1451
|
-
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
|
|
1751
|
+
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,
|
|
1752
|
+
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
|
|
1753
|
+
file_path: found.filePath
|
|
1452
1754
|
};
|
|
1453
1755
|
}
|
|
1454
1756
|
const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
@@ -1513,14 +1815,17 @@ var MemTriedInputSchema = {
|
|
|
1513
1815
|
paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
|
|
1514
1816
|
author: z16.string().optional().describe("Author handle or email"),
|
|
1515
1817
|
sensor: z16.object({
|
|
1516
|
-
kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test
|
|
1517
|
-
pattern: z16.string().optional().describe("kind=regex:
|
|
1818
|
+
kind: z16.enum(["regex", "ast", "shell", "test"]).default("regex").describe("regex/AST pattern, or a shell/test command"),
|
|
1819
|
+
pattern: z16.string().optional().describe("kind=regex|ast: pattern matching the faulty usage"),
|
|
1820
|
+
rule: z16.record(z16.unknown()).optional().describe("kind=ast: full ast-grep Rule object"),
|
|
1821
|
+
language: z16.string().optional().describe("kind=ast: explicit built-in/dynamic language"),
|
|
1518
1822
|
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)"),
|
|
1519
1823
|
timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
|
|
1520
1824
|
absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
|
|
1521
1825
|
severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
|
|
1522
1826
|
message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
|
|
1523
1827
|
incident: z16.string().optional().describe("Provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 surfaced when it fires and in the receipt"),
|
|
1828
|
+
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)"),
|
|
1524
1829
|
bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
|
|
1525
1830
|
}).optional().describe(
|
|
1526
1831
|
"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."
|
|
@@ -1552,7 +1857,7 @@ async function memTried(input, ctx) {
|
|
|
1552
1857
|
}
|
|
1553
1858
|
const body = lines.join("\n") + "\n";
|
|
1554
1859
|
const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
1555
|
-
await mkdir3(
|
|
1860
|
+
await mkdir3(path7.dirname(file), { recursive: true });
|
|
1556
1861
|
if (existsSync16(file)) {
|
|
1557
1862
|
throw new Error(`Memory already exists at ${file}`);
|
|
1558
1863
|
}
|
|
@@ -1563,12 +1868,15 @@ async function memTried(input, ctx) {
|
|
|
1563
1868
|
memory_id: frontmatter.id,
|
|
1564
1869
|
kind: input.sensor.kind ?? "regex",
|
|
1565
1870
|
pattern: input.sensor.pattern,
|
|
1871
|
+
rule: input.sensor.rule,
|
|
1872
|
+
language: input.sensor.language,
|
|
1566
1873
|
command: input.sensor.command,
|
|
1567
1874
|
timeout_ms: input.sensor.timeout_ms,
|
|
1568
1875
|
absent: input.sensor.absent,
|
|
1569
1876
|
severity: input.sensor.severity ?? "block",
|
|
1570
1877
|
message: input.sensor.message,
|
|
1571
1878
|
incident: input.sensor.incident,
|
|
1879
|
+
red_ref: input.sensor.red_ref,
|
|
1572
1880
|
bad_example: input.sensor.bad_example,
|
|
1573
1881
|
flags: void 0,
|
|
1574
1882
|
paths: []
|
|
@@ -1608,17 +1916,17 @@ async function memTried(input, ctx) {
|
|
|
1608
1916
|
}
|
|
1609
1917
|
var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
|
|
1610
1918
|
async function detectForAnchor(root, rel) {
|
|
1611
|
-
let dir =
|
|
1919
|
+
let dir = path8.resolve(root, rel);
|
|
1612
1920
|
try {
|
|
1613
|
-
if (!statSync(dir).isDirectory()) dir =
|
|
1921
|
+
if (!statSync(dir).isDirectory()) dir = path8.dirname(dir);
|
|
1614
1922
|
} catch {
|
|
1615
|
-
if (
|
|
1923
|
+
if (path8.extname(dir)) dir = path8.dirname(dir);
|
|
1616
1924
|
}
|
|
1617
1925
|
while (dir.startsWith(root)) {
|
|
1618
|
-
const pkgJson =
|
|
1926
|
+
const pkgJson = path8.join(dir, "package.json");
|
|
1619
1927
|
const hasPkg = existsSync17(pkgJson);
|
|
1620
|
-
const goMod = existsSync17(
|
|
1621
|
-
const pySignal = PY_SIGNALS.some((s) => existsSync17(
|
|
1928
|
+
const goMod = existsSync17(path8.join(dir, "go.mod"));
|
|
1929
|
+
const pySignal = PY_SIGNALS.some((s) => existsSync17(path8.join(dir, s)));
|
|
1622
1930
|
if (hasPkg || goMod || pySignal) {
|
|
1623
1931
|
let pkg = null;
|
|
1624
1932
|
if (hasPkg) {
|
|
@@ -1628,10 +1936,10 @@ async function detectForAnchor(root, rel) {
|
|
|
1628
1936
|
pkg = null;
|
|
1629
1937
|
}
|
|
1630
1938
|
}
|
|
1631
|
-
const baseDir =
|
|
1939
|
+
const baseDir = path8.relative(root, dir).split(path8.sep).join("/");
|
|
1632
1940
|
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1633
1941
|
}
|
|
1634
|
-
const parent =
|
|
1942
|
+
const parent = path8.dirname(dir);
|
|
1635
1943
|
if (parent === dir || dir === root) break;
|
|
1636
1944
|
dir = parent;
|
|
1637
1945
|
}
|
|
@@ -1697,14 +2005,14 @@ async function scaffoldTest(input, ctx) {
|
|
|
1697
2005
|
}
|
|
1698
2006
|
const results = [];
|
|
1699
2007
|
for (const scaffold of scaffolds) {
|
|
1700
|
-
const abs =
|
|
2008
|
+
const abs = path8.isAbsolute(scaffold.relPath) ? scaffold.relPath : path8.resolve(ctx.paths.root, scaffold.relPath);
|
|
1701
2009
|
let written = false;
|
|
1702
2010
|
let alreadyExists = false;
|
|
1703
2011
|
if (input.write) {
|
|
1704
2012
|
if (existsSync17(abs)) {
|
|
1705
2013
|
alreadyExists = true;
|
|
1706
2014
|
} else {
|
|
1707
|
-
await mkdir4(
|
|
2015
|
+
await mkdir4(path8.dirname(abs), { recursive: true });
|
|
1708
2016
|
await writeFile10(abs, scaffold.content, "utf8");
|
|
1709
2017
|
written = true;
|
|
1710
2018
|
}
|
|
@@ -1755,7 +2063,7 @@ async function ingestFindings(input, ctx) {
|
|
|
1755
2063
|
if (input.report && input.report.trim()) {
|
|
1756
2064
|
raw = input.report;
|
|
1757
2065
|
} else if (input.report_path) {
|
|
1758
|
-
const file =
|
|
2066
|
+
const file = path9.resolve(ctx.paths.root, input.report_path);
|
|
1759
2067
|
if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
|
|
1760
2068
|
raw = await readFile5(file, "utf8");
|
|
1761
2069
|
} else {
|
|
@@ -1809,12 +2117,12 @@ async function writeDraft(ctx, draft) {
|
|
|
1809
2117
|
draft.frontmatter.id,
|
|
1810
2118
|
draft.frontmatter.module
|
|
1811
2119
|
);
|
|
1812
|
-
await mkdir5(
|
|
2120
|
+
await mkdir5(path9.dirname(file), { recursive: true });
|
|
1813
2121
|
await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
1814
2122
|
return file;
|
|
1815
2123
|
}
|
|
1816
2124
|
function pendingDistillPath(ctx) {
|
|
1817
|
-
return
|
|
2125
|
+
return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
1818
2126
|
}
|
|
1819
2127
|
var SessionTracker = class {
|
|
1820
2128
|
events = [];
|
|
@@ -1855,7 +2163,7 @@ var SessionTracker = class {
|
|
|
1855
2163
|
}
|
|
1856
2164
|
let gitDiff;
|
|
1857
2165
|
try {
|
|
1858
|
-
const raw =
|
|
2166
|
+
const raw = execSync("git diff HEAD", {
|
|
1859
2167
|
cwd: this.ctx.paths.root,
|
|
1860
2168
|
timeout: 5e3,
|
|
1861
2169
|
encoding: "utf8",
|
|
@@ -1887,7 +2195,7 @@ var SessionTracker = class {
|
|
|
1887
2195
|
const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
|
|
1888
2196
|
let diffStat;
|
|
1889
2197
|
try {
|
|
1890
|
-
diffStat =
|
|
2198
|
+
diffStat = execSync("git diff --stat HEAD", {
|
|
1891
2199
|
cwd: this.ctx.paths.root,
|
|
1892
2200
|
timeout: 5e3,
|
|
1893
2201
|
encoding: "utf8",
|
|
@@ -1931,7 +2239,7 @@ var SessionTracker = class {
|
|
|
1931
2239
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
1932
2240
|
...recapId ? { recap_id: recapId } : {}
|
|
1933
2241
|
};
|
|
1934
|
-
const cacheDir =
|
|
2242
|
+
const cacheDir = path10.join(this.ctx.paths.haiveDir, ".cache");
|
|
1935
2243
|
await mkdir6(cacheDir, { recursive: true });
|
|
1936
2244
|
await writeFile12(
|
|
1937
2245
|
pendingDistillPath(this.ctx),
|
|
@@ -2011,12 +2319,12 @@ async function memSessionEnd(input, ctx) {
|
|
|
2011
2319
|
const body = buildBody(input);
|
|
2012
2320
|
const topic = recapTopic(input.scope, input.module);
|
|
2013
2321
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
2014
|
-
if (!p || !
|
|
2015
|
-
const rel =
|
|
2322
|
+
if (!p || !path11.isAbsolute(p)) return p;
|
|
2323
|
+
const rel = path11.relative(ctx.paths.root, p);
|
|
2016
2324
|
return rel.startsWith("..") ? p : rel;
|
|
2017
2325
|
});
|
|
2018
2326
|
const invalidPaths = normalizedFiles.filter(
|
|
2019
|
-
(p) => !existsSync20(
|
|
2327
|
+
(p) => !existsSync20(path11.resolve(ctx.paths.root, p))
|
|
2020
2328
|
);
|
|
2021
2329
|
if (invalidPaths.length > 0) {
|
|
2022
2330
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
@@ -2067,7 +2375,7 @@ async function memSessionEnd(input, ctx) {
|
|
|
2067
2375
|
frontmatter.id,
|
|
2068
2376
|
frontmatter.module
|
|
2069
2377
|
);
|
|
2070
|
-
await mkdir7(
|
|
2378
|
+
await mkdir7(path11.dirname(file), { recursive: true });
|
|
2071
2379
|
await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
|
|
2072
2380
|
await clearPendingDistill(ctx);
|
|
2073
2381
|
return {
|
|
@@ -2211,7 +2519,7 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
2211
2519
|
const out = [];
|
|
2212
2520
|
for (const m of modules) {
|
|
2213
2521
|
if (!available.has(m)) continue;
|
|
2214
|
-
const file =
|
|
2522
|
+
const file = path12.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
2215
2523
|
if (existsSync21(file)) {
|
|
2216
2524
|
out.push({ name: m, content: await readFile6(file, "utf8") });
|
|
2217
2525
|
}
|
|
@@ -2237,6 +2545,8 @@ var GetBriefingInputSchema = {
|
|
|
2237
2545
|
),
|
|
2238
2546
|
include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
2239
2547
|
track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
2548
|
+
memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
|
|
2549
|
+
deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
|
|
2240
2550
|
format: z20.enum(["full", "compact", "actions"]).default("full").describe(
|
|
2241
2551
|
"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."
|
|
2242
2552
|
),
|
|
@@ -2265,9 +2575,11 @@ async function getBriefing(input, ctx) {
|
|
|
2265
2575
|
let searchMode = "literal";
|
|
2266
2576
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
2267
2577
|
let byId = /* @__PURE__ */ new Map();
|
|
2578
|
+
const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
|
|
2579
|
+
const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
|
|
2268
2580
|
let lastSession;
|
|
2269
2581
|
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2270
|
-
const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2582
|
+
const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
|
|
2271
2583
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
2272
2584
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2273
2585
|
);
|
|
@@ -2300,10 +2612,11 @@ async function getBriefing(input, ctx) {
|
|
|
2300
2612
|
if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
|
|
2301
2613
|
return true;
|
|
2302
2614
|
});
|
|
2303
|
-
usage = await loadUsageIndex8(ctx.paths);
|
|
2615
|
+
usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
|
|
2304
2616
|
byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2617
|
+
const semanticEnabled = input.semantic && input.deterministic !== true;
|
|
2618
|
+
const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
|
|
2619
|
+
if (input.task && semanticEnabled) {
|
|
2307
2620
|
searchMode = semanticHits ? "semantic" : "literal_fallback";
|
|
2308
2621
|
}
|
|
2309
2622
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -2639,7 +2952,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2639
2952
|
}
|
|
2640
2953
|
}
|
|
2641
2954
|
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2642
|
-
const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2955
|
+
const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
|
|
2643
2956
|
for (const { memory } of allMems) {
|
|
2644
2957
|
const fm = memory.frontmatter;
|
|
2645
2958
|
if (!fm.requires_human_approval) continue;
|
|
@@ -2696,7 +3009,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2696
3009
|
pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
|
|
2697
3010
|
} catch {
|
|
2698
3011
|
}
|
|
2699
|
-
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
|
|
3012
|
+
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
|
|
2700
3013
|
const cmForBootstrap = await loadCodeMap(ctx.paths);
|
|
2701
3014
|
let existingModules = [];
|
|
2702
3015
|
try {
|
|
@@ -2707,7 +3020,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2707
3020
|
const bootstrap = assessBootstrapState({
|
|
2708
3021
|
projectContextRaw: pcRaw,
|
|
2709
3022
|
memories: allForBootstrap,
|
|
2710
|
-
codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
|
|
3023
|
+
codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
|
|
2711
3024
|
existingModules
|
|
2712
3025
|
});
|
|
2713
3026
|
if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
|
|
@@ -2830,7 +3143,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2830
3143
|
};
|
|
2831
3144
|
}
|
|
2832
3145
|
async function detectRunCommands(root) {
|
|
2833
|
-
const pkgPath =
|
|
3146
|
+
const pkgPath = path13.join(root, "package.json");
|
|
2834
3147
|
if (!existsSync22(pkgPath)) return null;
|
|
2835
3148
|
try {
|
|
2836
3149
|
const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
|
|
@@ -2898,7 +3211,7 @@ function oneLine(value) {
|
|
|
2898
3211
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
2899
3212
|
}
|
|
2900
3213
|
function serverVersion() {
|
|
2901
|
-
return true ? "0.
|
|
3214
|
+
return true ? "0.43.2" : "dev";
|
|
2902
3215
|
}
|
|
2903
3216
|
var CodeMapInputSchema = {
|
|
2904
3217
|
file: z21.string().optional().describe("Filter to files whose path contains this substring"),
|
|
@@ -4006,7 +4319,7 @@ async function currentAssessment(ctx) {
|
|
|
4006
4319
|
return assessBootstrapState2({
|
|
4007
4320
|
projectContextRaw,
|
|
4008
4321
|
memories,
|
|
4009
|
-
codeFiles: codeMap ? Object.keys(codeMap.files) : [],
|
|
4322
|
+
codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
|
|
4010
4323
|
existingModules
|
|
4011
4324
|
});
|
|
4012
4325
|
}
|
|
@@ -4225,7 +4538,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4225
4538
|
};
|
|
4226
4539
|
}
|
|
4227
4540
|
var SERVER_NAME = "hivelore";
|
|
4228
|
-
var SERVER_VERSION = "0.
|
|
4541
|
+
var SERVER_VERSION = "0.43.2";
|
|
4229
4542
|
function jsonResult(data) {
|
|
4230
4543
|
return {
|
|
4231
4544
|
content: [
|
|
@@ -5171,6 +5484,10 @@ async function runHaiveMcpStdio(options) {
|
|
|
5171
5484
|
}
|
|
5172
5485
|
|
|
5173
5486
|
export {
|
|
5487
|
+
astEngineAvailable,
|
|
5488
|
+
astLangForPath,
|
|
5489
|
+
runAstPattern,
|
|
5490
|
+
runAstSensorOnContent,
|
|
5174
5491
|
readPresumedCorrectTargets,
|
|
5175
5492
|
proposeSensor,
|
|
5176
5493
|
memTried,
|
|
@@ -5201,4 +5518,4 @@ export {
|
|
|
5201
5518
|
printHaiveMcpVersion,
|
|
5202
5519
|
runHaiveMcpStdio
|
|
5203
5520
|
};
|
|
5204
|
-
//# sourceMappingURL=chunk-
|
|
5521
|
+
//# sourceMappingURL=chunk-FXDGOBPT.js.map
|