@hivelore/mcp 0.42.1 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +132 -42
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +26 -7
- package/dist/server.js +134 -43
- package/dist/server.js.map +1 -1
- package/package.json +21 -4
package/README.md
CHANGED
|
@@ -420,7 +420,7 @@ Post-task reflection checklist. Guides the AI through capturing failed approache
|
|
|
420
420
|
```
|
|
421
421
|
Use the post_task prompt with:
|
|
422
422
|
task_summary: "Added Stripe payment integration"
|
|
423
|
-
files_touched: ["src/payments/StripeService.ts", "src/payments/PaymentController.ts"]
|
|
423
|
+
files_touched: '["src/payments/StripeService.ts", "src/payments/PaymentController.ts"]'
|
|
424
424
|
```
|
|
425
425
|
|
|
426
426
|
### `bootstrap_project`
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { mkdir as mkdir8, writeFile as writeFile15 } from "fs/promises";
|
|
7
|
+
import path15 from "path";
|
|
6
8
|
|
|
7
9
|
// src/context.ts
|
|
8
10
|
import { findProjectRoot, resolveHaivePaths } from "@hivelore/core";
|
|
@@ -1134,7 +1136,7 @@ import {
|
|
|
1134
1136
|
import { z as z16 } from "zod";
|
|
1135
1137
|
|
|
1136
1138
|
// src/tools/propose-sensor.ts
|
|
1137
|
-
import {
|
|
1139
|
+
import { execFileSync } from "child_process";
|
|
1138
1140
|
import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
|
|
1139
1141
|
import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
|
|
1140
1142
|
import os from "os";
|
|
@@ -1154,10 +1156,32 @@ import { z as z15 } from "zod";
|
|
|
1154
1156
|
// src/ast-sensors.ts
|
|
1155
1157
|
import path5 from "path";
|
|
1156
1158
|
var cachedEngine;
|
|
1159
|
+
var registeredDynamicLanguages = /* @__PURE__ */ new Set();
|
|
1160
|
+
async function loadDynamicLanguages(engine) {
|
|
1161
|
+
const registrations = {};
|
|
1162
|
+
const candidates = [
|
|
1163
|
+
["python", () => import("@ast-grep/lang-python")],
|
|
1164
|
+
["go", () => import("@ast-grep/lang-go")],
|
|
1165
|
+
["rust", () => import("@ast-grep/lang-rust")],
|
|
1166
|
+
["java", () => import("@ast-grep/lang-java")]
|
|
1167
|
+
];
|
|
1168
|
+
for (const [name, load] of candidates) {
|
|
1169
|
+
try {
|
|
1170
|
+
const mod = await load();
|
|
1171
|
+
registrations[name] = mod.default;
|
|
1172
|
+
registeredDynamicLanguages.add(name);
|
|
1173
|
+
} catch {
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (Object.keys(registrations).length > 0) {
|
|
1177
|
+
engine.registerDynamicLanguage(registrations);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1157
1180
|
async function loadAstEngine() {
|
|
1158
1181
|
if (cachedEngine !== void 0) return cachedEngine;
|
|
1159
1182
|
try {
|
|
1160
1183
|
cachedEngine = await import("@ast-grep/napi");
|
|
1184
|
+
await loadDynamicLanguages(cachedEngine);
|
|
1161
1185
|
} catch {
|
|
1162
1186
|
cachedEngine = null;
|
|
1163
1187
|
}
|
|
@@ -1166,11 +1190,22 @@ async function loadAstEngine() {
|
|
|
1166
1190
|
async function astEngineAvailable() {
|
|
1167
1191
|
return await loadAstEngine() !== null;
|
|
1168
1192
|
}
|
|
1169
|
-
function astLangForPath(filePath) {
|
|
1193
|
+
function astLangForPath(filePath, explicitLanguage) {
|
|
1194
|
+
if (explicitLanguage) {
|
|
1195
|
+
const normalized = explicitLanguage.toLowerCase();
|
|
1196
|
+
if (["typescript", "tsx", "javascript", "html", "css"].includes(normalized)) {
|
|
1197
|
+
return normalized === "typescript" ? "TypeScript" : normalized === "tsx" ? "Tsx" : normalized === "javascript" ? "JavaScript" : normalized[0].toUpperCase() + normalized.slice(1);
|
|
1198
|
+
}
|
|
1199
|
+
return registeredDynamicLanguages.has(normalized) ? normalized : null;
|
|
1200
|
+
}
|
|
1170
1201
|
const ext = path5.extname(filePath).toLowerCase();
|
|
1171
1202
|
if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
|
|
1172
1203
|
if (ext === ".tsx") return "Tsx";
|
|
1173
1204
|
if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "JavaScript";
|
|
1205
|
+
if (ext === ".py" && registeredDynamicLanguages.has("python")) return "python";
|
|
1206
|
+
if (ext === ".go" && registeredDynamicLanguages.has("go")) return "go";
|
|
1207
|
+
if (ext === ".rs" && registeredDynamicLanguages.has("rust")) return "rust";
|
|
1208
|
+
if (ext === ".java" && registeredDynamicLanguages.has("java")) return "java";
|
|
1174
1209
|
return null;
|
|
1175
1210
|
}
|
|
1176
1211
|
function absentPresentInNode(node, absent) {
|
|
@@ -1185,12 +1220,12 @@ function absentPresentInNode(node, absent) {
|
|
|
1185
1220
|
return text.includes(absent);
|
|
1186
1221
|
}
|
|
1187
1222
|
}
|
|
1188
|
-
async function runAstPattern(content, filePath, pattern, absent) {
|
|
1223
|
+
async function runAstPattern(content, filePath, pattern, absent, rule, language) {
|
|
1189
1224
|
const engine = await loadAstEngine();
|
|
1190
1225
|
if (!engine) return { status: "engine-missing", matches: [] };
|
|
1191
|
-
const langName = astLangForPath(filePath);
|
|
1226
|
+
const langName = astLangForPath(filePath, language);
|
|
1192
1227
|
if (!langName) return { status: "unsupported-language", matches: [] };
|
|
1193
|
-
const lang = engine.Lang[langName];
|
|
1228
|
+
const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
|
|
1194
1229
|
let root;
|
|
1195
1230
|
try {
|
|
1196
1231
|
root = engine.parse(lang, content).root();
|
|
@@ -1199,7 +1234,9 @@ async function runAstPattern(content, filePath, pattern, absent) {
|
|
|
1199
1234
|
}
|
|
1200
1235
|
let nodes;
|
|
1201
1236
|
try {
|
|
1202
|
-
|
|
1237
|
+
const matcher = rule ? { rule: pattern ? { all: [{ pattern }, rule] } : rule } : pattern;
|
|
1238
|
+
if (!matcher) return { status: "invalid-pattern", matches: [], detail: "missing pattern/rule" };
|
|
1239
|
+
nodes = root.findAll(matcher);
|
|
1203
1240
|
} catch (err) {
|
|
1204
1241
|
return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
|
|
1205
1242
|
}
|
|
@@ -1216,7 +1253,7 @@ async function runAstPattern(content, filePath, pattern, absent) {
|
|
|
1216
1253
|
return { status: "ok", matches };
|
|
1217
1254
|
}
|
|
1218
1255
|
async function runAstSensorOnContent(input) {
|
|
1219
|
-
const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent);
|
|
1256
|
+
const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
|
|
1220
1257
|
if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
|
|
1221
1258
|
const added = input.addedLines;
|
|
1222
1259
|
return {
|
|
@@ -1236,7 +1273,9 @@ var ProposeSensorInputSchema = {
|
|
|
1236
1273
|
kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
|
|
1237
1274
|
"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."
|
|
1238
1275
|
),
|
|
1239
|
-
pattern: z15.string().optional().describe("kind=regex: regex matching the
|
|
1276
|
+
pattern: z15.string().optional().describe("kind=regex: regex matching the faulty usage; kind=ast: optional structural pattern (may be combined with `rule`)."),
|
|
1277
|
+
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."),
|
|
1278
|
+
language: z15.string().optional().describe("kind=ast: explicit built-in/dynamic language name for non-standard file extensions."),
|
|
1240
1279
|
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."),
|
|
1241
1280
|
timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
|
|
1242
1281
|
absent: z15.string().optional().describe(
|
|
@@ -1267,7 +1306,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1267
1306
|
const targets = [];
|
|
1268
1307
|
for (const rel of relPaths) {
|
|
1269
1308
|
try {
|
|
1270
|
-
const content =
|
|
1309
|
+
const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
|
|
1271
1310
|
cwd: root,
|
|
1272
1311
|
encoding: "utf8",
|
|
1273
1312
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -1288,7 +1327,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1288
1327
|
}
|
|
1289
1328
|
function runCommandForValidation(command, root, timeoutMs = 12e4) {
|
|
1290
1329
|
try {
|
|
1291
|
-
|
|
1330
|
+
execFileSync("bash", ["-c", command], {
|
|
1292
1331
|
cwd: root,
|
|
1293
1332
|
timeout: timeoutMs,
|
|
1294
1333
|
maxBuffer: 8 * 1024 * 1024,
|
|
@@ -1313,7 +1352,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
|
|
|
1313
1352
|
let added = false;
|
|
1314
1353
|
try {
|
|
1315
1354
|
try {
|
|
1316
|
-
|
|
1355
|
+
execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
|
|
1317
1356
|
cwd: root,
|
|
1318
1357
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1319
1358
|
timeout: 6e4
|
|
@@ -1340,7 +1379,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
|
|
|
1340
1379
|
} finally {
|
|
1341
1380
|
if (added) {
|
|
1342
1381
|
try {
|
|
1343
|
-
|
|
1382
|
+
execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
|
|
1344
1383
|
} catch {
|
|
1345
1384
|
try {
|
|
1346
1385
|
rmSync(worktree, { recursive: true, force: true });
|
|
@@ -1380,13 +1419,13 @@ async function proposeSensor(input, ctx) {
|
|
|
1380
1419
|
};
|
|
1381
1420
|
}
|
|
1382
1421
|
} else if (kind === "ast") {
|
|
1383
|
-
if (!input.pattern?.trim()) {
|
|
1422
|
+
if (!input.pattern?.trim() && !input.rule) {
|
|
1384
1423
|
return {
|
|
1385
1424
|
accepted: false,
|
|
1386
1425
|
memory_id: input.memory_id,
|
|
1387
1426
|
severity: input.severity,
|
|
1388
1427
|
reason: "invalid-pattern",
|
|
1389
|
-
guidance: "kind=ast requires a `pattern`
|
|
1428
|
+
guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
|
|
1390
1429
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1391
1430
|
};
|
|
1392
1431
|
}
|
|
@@ -1407,7 +1446,7 @@ async function proposeSensor(input, ctx) {
|
|
|
1407
1446
|
}
|
|
1408
1447
|
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}.` : "";
|
|
1409
1448
|
if (kind === "ast") {
|
|
1410
|
-
const pattern = input.pattern
|
|
1449
|
+
const pattern = input.pattern?.trim();
|
|
1411
1450
|
if (!await astEngineAvailable() && input.severity === "block") {
|
|
1412
1451
|
return {
|
|
1413
1452
|
accepted: false,
|
|
@@ -1418,7 +1457,7 @@ async function proposeSensor(input, ctx) {
|
|
|
1418
1457
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1419
1458
|
};
|
|
1420
1459
|
}
|
|
1421
|
-
const brittleAst = sensorPatternBrittleness(pattern);
|
|
1460
|
+
const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
|
|
1422
1461
|
if (brittleAst && input.severity === "block") {
|
|
1423
1462
|
return {
|
|
1424
1463
|
accepted: false,
|
|
@@ -1433,7 +1472,7 @@ async function proposeSensor(input, ctx) {
|
|
|
1433
1472
|
const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
|
|
1434
1473
|
const firedOnAst = [];
|
|
1435
1474
|
for (const target of currentTargetsAst) {
|
|
1436
|
-
const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: target.content, filePath: target.path });
|
|
1475
|
+
const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
|
|
1437
1476
|
if (scan.status === "invalid-pattern") {
|
|
1438
1477
|
return {
|
|
1439
1478
|
accepted: false,
|
|
@@ -1462,10 +1501,10 @@ async function proposeSensor(input, ctx) {
|
|
|
1462
1501
|
];
|
|
1463
1502
|
let firesOnBadAst = null;
|
|
1464
1503
|
if (badExamplesAst.length > 0 && await astEngineAvailable()) {
|
|
1465
|
-
const exampleLang = anchorPathsAst.find((p) => astLangForPath(p) !== null) ?? "example.tsx";
|
|
1504
|
+
const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
|
|
1466
1505
|
firesOnBadAst = false;
|
|
1467
1506
|
for (const example of badExamplesAst) {
|
|
1468
|
-
const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: example, filePath: exampleLang });
|
|
1507
|
+
const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
|
|
1469
1508
|
if (scan.status === "ok" && scan.matches.length > 0) {
|
|
1470
1509
|
firesOnBadAst = true;
|
|
1471
1510
|
break;
|
|
@@ -1484,10 +1523,12 @@ async function proposeSensor(input, ctx) {
|
|
|
1484
1523
|
}
|
|
1485
1524
|
const sensorAst = {
|
|
1486
1525
|
kind: "ast",
|
|
1487
|
-
pattern,
|
|
1526
|
+
...pattern ? { pattern } : {},
|
|
1527
|
+
...input.rule ? { rule: input.rule } : {},
|
|
1528
|
+
...input.language ? { language: input.language } : {},
|
|
1488
1529
|
...input.absent ? { absent: input.absent } : {},
|
|
1489
1530
|
paths: anchorPathsAst,
|
|
1490
|
-
message: input.message?.trim() || deriveMessage(found.memory.body, pattern, input.absent),
|
|
1531
|
+
message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
|
|
1491
1532
|
...input.incident?.trim() ? { incident: input.incident.trim() } : {},
|
|
1492
1533
|
severity: input.severity,
|
|
1493
1534
|
autogen: false,
|
|
@@ -1550,6 +1591,16 @@ ${verdictCmd.detail}`,
|
|
|
1550
1591
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
|
|
1551
1592
|
};
|
|
1552
1593
|
}
|
|
1594
|
+
if (input.severity === "block" && !input.red_ref?.trim()) {
|
|
1595
|
+
return {
|
|
1596
|
+
accepted: false,
|
|
1597
|
+
memory_id: input.memory_id,
|
|
1598
|
+
severity: input.severity,
|
|
1599
|
+
reason: "red-required",
|
|
1600
|
+
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.",
|
|
1601
|
+
self_check: { silent_on_current: true, fires_on_bad: null, fired_on: [] }
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1553
1604
|
const sensorCmd = {
|
|
1554
1605
|
kind,
|
|
1555
1606
|
command: input.command.trim(),
|
|
@@ -1571,7 +1622,7 @@ ${verdictCmd.detail}`,
|
|
|
1571
1622
|
accepted: true,
|
|
1572
1623
|
memory_id: input.memory_id,
|
|
1573
1624
|
severity: input.severity,
|
|
1574
|
-
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." : "
|
|
1625
|
+
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,
|
|
1575
1626
|
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
|
|
1576
1627
|
file_path: found.filePath
|
|
1577
1628
|
};
|
|
@@ -1640,8 +1691,10 @@ var MemTriedInputSchema = {
|
|
|
1640
1691
|
paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
|
|
1641
1692
|
author: z16.string().optional().describe("Author handle or email"),
|
|
1642
1693
|
sensor: z16.object({
|
|
1643
|
-
kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test
|
|
1644
|
-
pattern: z16.string().optional().describe("kind=regex:
|
|
1694
|
+
kind: z16.enum(["regex", "ast", "shell", "test"]).default("regex").describe("regex/AST pattern, or a shell/test command"),
|
|
1695
|
+
pattern: z16.string().optional().describe("kind=regex|ast: pattern matching the faulty usage"),
|
|
1696
|
+
rule: z16.record(z16.unknown()).optional().describe("kind=ast: full ast-grep Rule object"),
|
|
1697
|
+
language: z16.string().optional().describe("kind=ast: explicit built-in/dynamic language"),
|
|
1645
1698
|
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)"),
|
|
1646
1699
|
timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
|
|
1647
1700
|
absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
|
|
@@ -1691,6 +1744,8 @@ async function memTried(input, ctx) {
|
|
|
1691
1744
|
memory_id: frontmatter.id,
|
|
1692
1745
|
kind: input.sensor.kind ?? "regex",
|
|
1693
1746
|
pattern: input.sensor.pattern,
|
|
1747
|
+
rule: input.sensor.rule,
|
|
1748
|
+
language: input.sensor.language,
|
|
1694
1749
|
command: input.sensor.command,
|
|
1695
1750
|
timeout_ms: input.sensor.timeout_ms,
|
|
1696
1751
|
absent: input.sensor.absent,
|
|
@@ -1985,7 +2040,7 @@ import {
|
|
|
1985
2040
|
import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
|
|
1986
2041
|
import { existsSync as existsSync19 } from "fs";
|
|
1987
2042
|
import path10 from "path";
|
|
1988
|
-
import { execSync
|
|
2043
|
+
import { execSync } from "child_process";
|
|
1989
2044
|
function pendingDistillPath(ctx) {
|
|
1990
2045
|
return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
1991
2046
|
}
|
|
@@ -2028,7 +2083,7 @@ var SessionTracker = class {
|
|
|
2028
2083
|
}
|
|
2029
2084
|
let gitDiff;
|
|
2030
2085
|
try {
|
|
2031
|
-
const raw =
|
|
2086
|
+
const raw = execSync("git diff HEAD", {
|
|
2032
2087
|
cwd: this.ctx.paths.root,
|
|
2033
2088
|
timeout: 5e3,
|
|
2034
2089
|
encoding: "utf8",
|
|
@@ -2060,7 +2115,7 @@ var SessionTracker = class {
|
|
|
2060
2115
|
const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
|
|
2061
2116
|
let diffStat;
|
|
2062
2117
|
try {
|
|
2063
|
-
diffStat =
|
|
2118
|
+
diffStat = execSync("git diff --stat HEAD", {
|
|
2064
2119
|
cwd: this.ctx.paths.root,
|
|
2065
2120
|
timeout: 5e3,
|
|
2066
2121
|
encoding: "utf8",
|
|
@@ -2472,6 +2527,8 @@ var GetBriefingInputSchema = {
|
|
|
2472
2527
|
),
|
|
2473
2528
|
include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
2474
2529
|
track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
2530
|
+
memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
|
|
2531
|
+
deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
|
|
2475
2532
|
format: z20.enum(["full", "compact", "actions"]).default("full").describe(
|
|
2476
2533
|
"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."
|
|
2477
2534
|
),
|
|
@@ -2500,9 +2557,11 @@ async function getBriefing(input, ctx) {
|
|
|
2500
2557
|
let searchMode = "literal";
|
|
2501
2558
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
2502
2559
|
let byId = /* @__PURE__ */ new Map();
|
|
2560
|
+
const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
|
|
2561
|
+
const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
|
|
2503
2562
|
let lastSession;
|
|
2504
2563
|
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2505
|
-
const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2564
|
+
const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
|
|
2506
2565
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
2507
2566
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2508
2567
|
);
|
|
@@ -2535,10 +2594,11 @@ async function getBriefing(input, ctx) {
|
|
|
2535
2594
|
if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
|
|
2536
2595
|
return true;
|
|
2537
2596
|
});
|
|
2538
|
-
usage = await loadUsageIndex8(ctx.paths);
|
|
2597
|
+
usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
|
|
2539
2598
|
byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
|
|
2540
|
-
const
|
|
2541
|
-
|
|
2599
|
+
const semanticEnabled = input.semantic && input.deterministic !== true;
|
|
2600
|
+
const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
|
|
2601
|
+
if (input.task && semanticEnabled) {
|
|
2542
2602
|
searchMode = semanticHits ? "semantic" : "literal_fallback";
|
|
2543
2603
|
}
|
|
2544
2604
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -2874,7 +2934,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2874
2934
|
}
|
|
2875
2935
|
}
|
|
2876
2936
|
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2877
|
-
const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2937
|
+
const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
|
|
2878
2938
|
for (const { memory } of allMems) {
|
|
2879
2939
|
const fm = memory.frontmatter;
|
|
2880
2940
|
if (!fm.requires_human_approval) continue;
|
|
@@ -2931,7 +2991,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2931
2991
|
pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
|
|
2932
2992
|
} catch {
|
|
2933
2993
|
}
|
|
2934
|
-
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
|
|
2994
|
+
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
|
|
2935
2995
|
const cmForBootstrap = await loadCodeMap(ctx.paths);
|
|
2936
2996
|
let existingModules = [];
|
|
2937
2997
|
try {
|
|
@@ -2942,7 +3002,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2942
3002
|
const bootstrap = assessBootstrapState({
|
|
2943
3003
|
projectContextRaw: pcRaw,
|
|
2944
3004
|
memories: allForBootstrap,
|
|
2945
|
-
codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
|
|
3005
|
+
codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
|
|
2946
3006
|
existingModules
|
|
2947
3007
|
});
|
|
2948
3008
|
if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
|
|
@@ -3133,7 +3193,7 @@ function oneLine(value) {
|
|
|
3133
3193
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
3134
3194
|
}
|
|
3135
3195
|
function serverVersion() {
|
|
3136
|
-
return true ? "0.
|
|
3196
|
+
return true ? "0.44.0" : "dev";
|
|
3137
3197
|
}
|
|
3138
3198
|
|
|
3139
3199
|
// src/tools/code-map.ts
|
|
@@ -3419,7 +3479,8 @@ var AntiPatternsCheckInputSchema = {
|
|
|
3419
3479
|
),
|
|
3420
3480
|
min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
|
|
3421
3481
|
"Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
|
|
3422
|
-
)
|
|
3482
|
+
),
|
|
3483
|
+
track: z26.boolean().default(true).describe("Record real prevention outcomes. Set false for eval/selftest probes so synthetic cases never inflate ROI.")
|
|
3423
3484
|
};
|
|
3424
3485
|
function tokenizeDiffForLiteral(diff) {
|
|
3425
3486
|
const lines = diff.split("\n");
|
|
@@ -3612,7 +3673,9 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3612
3673
|
}).slice(0, input.limit);
|
|
3613
3674
|
const isHardBlockCatch = (w) => w.reasons.includes("sensor");
|
|
3614
3675
|
const strongCatches = warnings.filter(isHardBlockCatch);
|
|
3615
|
-
|
|
3676
|
+
if (input.track !== false) {
|
|
3677
|
+
await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
|
|
3678
|
+
}
|
|
3616
3679
|
return {
|
|
3617
3680
|
scanned: negative.length,
|
|
3618
3681
|
warnings
|
|
@@ -4306,6 +4369,7 @@ ${template}\`\`\`
|
|
|
4306
4369
|
// src/prompts/bootstrap-repo.ts
|
|
4307
4370
|
import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
|
|
4308
4371
|
import { existsSync as existsSync29 } from "fs";
|
|
4372
|
+
import path14 from "path";
|
|
4309
4373
|
import {
|
|
4310
4374
|
assessBootstrapState as assessBootstrapState2,
|
|
4311
4375
|
loadCodeMap as loadCodeMap4,
|
|
@@ -4333,7 +4397,7 @@ async function currentAssessment(ctx) {
|
|
|
4333
4397
|
return assessBootstrapState2({
|
|
4334
4398
|
projectContextRaw,
|
|
4335
4399
|
memories,
|
|
4336
|
-
codeFiles: codeMap ? Object.keys(codeMap.files) : [],
|
|
4400
|
+
codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
|
|
4337
4401
|
existingModules
|
|
4338
4402
|
});
|
|
4339
4403
|
}
|
|
@@ -4393,13 +4457,14 @@ Main code areas detected: ${areas}
|
|
|
4393
4457
|
import { z as z35 } from "zod";
|
|
4394
4458
|
var PostTaskArgsSchema = {
|
|
4395
4459
|
task_summary: z35.string().optional().describe("One sentence describing what you just did"),
|
|
4396
|
-
files_touched: z35.
|
|
4460
|
+
files_touched: z35.string().optional().describe("Files you created or modified during the task, as CSV or a JSON array string")
|
|
4397
4461
|
};
|
|
4398
4462
|
function postTaskPrompt(args, ctx) {
|
|
4399
4463
|
const taskLine = args.task_summary ? `
|
|
4400
4464
|
Task just completed: **${args.task_summary}**` : "";
|
|
4401
|
-
const
|
|
4402
|
-
|
|
4465
|
+
const filesTouched = parsePromptFilesTouched(args.files_touched);
|
|
4466
|
+
const filesLine = filesTouched.length > 0 ? `
|
|
4467
|
+
Files touched: ${filesTouched.map((f) => `\`${f}\``).join(", ")}` : "";
|
|
4403
4468
|
const text = `You have just finished a task. Before closing this session, take 60 seconds to capture what you learned.
|
|
4404
4469
|
${taskLine}${filesLine}
|
|
4405
4470
|
|
|
@@ -4488,6 +4553,20 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
4488
4553
|
]
|
|
4489
4554
|
};
|
|
4490
4555
|
}
|
|
4556
|
+
function parsePromptFilesTouched(input) {
|
|
4557
|
+
const raw = input?.trim();
|
|
4558
|
+
if (!raw) return [];
|
|
4559
|
+
if (raw.startsWith("[")) {
|
|
4560
|
+
try {
|
|
4561
|
+
const parsed2 = JSON.parse(raw);
|
|
4562
|
+
if (Array.isArray(parsed2)) {
|
|
4563
|
+
return parsed2.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean);
|
|
4564
|
+
}
|
|
4565
|
+
} catch {
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
return raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
4569
|
+
}
|
|
4491
4570
|
|
|
4492
4571
|
// src/prompts/import-docs.ts
|
|
4493
4572
|
import { z as z36 } from "zod";
|
|
@@ -4561,7 +4640,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4561
4640
|
// src/server.ts
|
|
4562
4641
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
|
|
4563
4642
|
var SERVER_NAME = "hivelore";
|
|
4564
|
-
var SERVER_VERSION = "0.
|
|
4643
|
+
var SERVER_VERSION = "0.44.0";
|
|
4565
4644
|
function jsonResult(data) {
|
|
4566
4645
|
return {
|
|
4567
4646
|
content: [
|
|
@@ -5500,11 +5579,22 @@ function printHaiveMcpVersion() {
|
|
|
5500
5579
|
}
|
|
5501
5580
|
async function runHaiveMcpStdio(options) {
|
|
5502
5581
|
const { server, context } = createHaiveServer({ root: options.root, env: process.env });
|
|
5582
|
+
await writeMcpRuntimeMarker(context).catch(() => {
|
|
5583
|
+
});
|
|
5503
5584
|
console.error(
|
|
5504
5585
|
`[haive-mcp] starting server v${SERVER_VERSION} (project root: ${context.paths.root})`
|
|
5505
5586
|
);
|
|
5506
5587
|
await server.connect(new StdioServerTransport());
|
|
5507
5588
|
}
|
|
5589
|
+
async function writeMcpRuntimeMarker(context) {
|
|
5590
|
+
await mkdir8(context.paths.runtimeDir, { recursive: true });
|
|
5591
|
+
await writeFile15(path15.join(context.paths.runtimeDir, "mcp-server.json"), JSON.stringify({
|
|
5592
|
+
version: SERVER_VERSION,
|
|
5593
|
+
pid: process.pid,
|
|
5594
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5595
|
+
command: "hivelore mcp --stdio"
|
|
5596
|
+
}, null, 2), "utf8");
|
|
5597
|
+
}
|
|
5508
5598
|
|
|
5509
5599
|
// src/index.ts
|
|
5510
5600
|
var parsed = parseMcpCliArgs(process.argv);
|