@hivelore/mcp 0.38.0 → 0.39.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/dist/index.js +104 -64
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +27 -8
- package/dist/server.js +112 -63
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1261,6 +1261,7 @@ async function proposeSensor(input, ctx) {
|
|
|
1261
1261
|
if (!found) {
|
|
1262
1262
|
throw new Error(`No memory found with id ${input.memory_id}`);
|
|
1263
1263
|
}
|
|
1264
|
+
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}.` : "";
|
|
1264
1265
|
if (kind !== "regex") {
|
|
1265
1266
|
const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
|
|
1266
1267
|
const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
@@ -1295,7 +1296,7 @@ ${verdictCmd.detail}`,
|
|
|
1295
1296
|
accepted: true,
|
|
1296
1297
|
memory_id: input.memory_id,
|
|
1297
1298
|
severity: input.severity,
|
|
1298
|
-
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})
|
|
1299
|
+
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}).`) + personalScopeNudge,
|
|
1299
1300
|
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
|
|
1300
1301
|
};
|
|
1301
1302
|
}
|
|
@@ -1344,6 +1345,7 @@ ${verdictCmd.detail}`,
|
|
|
1344
1345
|
accepted: true,
|
|
1345
1346
|
memory_id: input.memory_id,
|
|
1346
1347
|
severity: input.severity,
|
|
1348
|
+
...personalScopeNudge ? { guidance: personalScopeNudge.trim() } : {},
|
|
1347
1349
|
self_check,
|
|
1348
1350
|
file_path: found.filePath
|
|
1349
1351
|
};
|
|
@@ -1354,7 +1356,9 @@ var MemTriedInputSchema = {
|
|
|
1354
1356
|
what: z16.string().min(1).describe("Brief description of the approach that was tried"),
|
|
1355
1357
|
why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
|
|
1356
1358
|
instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
|
|
1357
|
-
scope: z16.enum(["personal", "team", "module"]).
|
|
1359
|
+
scope: z16.enum(["personal", "team", "module"]).optional().describe(
|
|
1360
|
+
"Visibility scope. Defaults to personal \u2014 EXCEPT when a one-shot `sensor` is attached: an enforced lesson is team truth (the sensor must travel to every machine and CI), so it defaults to team. Pass scope explicitly to override."
|
|
1361
|
+
),
|
|
1358
1362
|
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
1359
1363
|
tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
|
|
1360
1364
|
paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
|
|
@@ -1378,10 +1382,11 @@ async function memTried(input, ctx) {
|
|
|
1378
1382
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1379
1383
|
}
|
|
1380
1384
|
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
1385
|
+
const scope = input.scope ?? (input.sensor ? "team" : "personal");
|
|
1381
1386
|
const baseFm = buildFrontmatter2({
|
|
1382
1387
|
type: "attempt",
|
|
1383
1388
|
slug,
|
|
1384
|
-
scope
|
|
1389
|
+
scope,
|
|
1385
1390
|
module: input.module,
|
|
1386
1391
|
tags: input.tags,
|
|
1387
1392
|
paths: input.paths,
|
|
@@ -1429,7 +1434,7 @@ async function memTried(input, ctx) {
|
|
|
1429
1434
|
...verdict.reason ? { reason: verdict.reason } : {},
|
|
1430
1435
|
...verdict.guidance ? { guidance: verdict.guidance } : {}
|
|
1431
1436
|
},
|
|
1432
|
-
hint: verdict.accepted ? "Loop closed: the attempt is saved AND enforced \u2014 the gate now refuses a repeat deterministically." : `Attempt saved, but the sensor was rejected (${verdict.reason}). Revise per the guidance and re-propose with propose_sensor.`
|
|
1437
|
+
hint: (verdict.accepted ? "Loop closed: the attempt is saved AND enforced \u2014 the gate now refuses a repeat deterministically." : `Attempt saved, but the sensor was rejected (${verdict.reason}). Revise per the guidance and re-propose with propose_sensor.`) + (input.scope === void 0 ? " Saved team-scoped (an enforced lesson must travel with the repo) \u2014 pass scope:'personal' to keep it private." : "")
|
|
1433
1438
|
};
|
|
1434
1439
|
}
|
|
1435
1440
|
const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
|
|
@@ -1456,6 +1461,7 @@ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from
|
|
|
1456
1461
|
import path7 from "path";
|
|
1457
1462
|
import { z as z17 } from "zod";
|
|
1458
1463
|
import {
|
|
1464
|
+
buildProposeCommand,
|
|
1459
1465
|
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
1460
1466
|
normalizeFramework,
|
|
1461
1467
|
parseLessonFields,
|
|
@@ -1463,38 +1469,46 @@ import {
|
|
|
1463
1469
|
scaffoldPostIncidentTest
|
|
1464
1470
|
} from "@hivelore/core";
|
|
1465
1471
|
var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
|
|
1466
|
-
async function
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
if (hasPkg
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
} catch {
|
|
1486
|
-
pkg = null;
|
|
1487
|
-
}
|
|
1472
|
+
async function detectForAnchor(root, rel) {
|
|
1473
|
+
let dir = path7.resolve(root, rel);
|
|
1474
|
+
try {
|
|
1475
|
+
if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
|
|
1476
|
+
} catch {
|
|
1477
|
+
if (path7.extname(dir)) dir = path7.dirname(dir);
|
|
1478
|
+
}
|
|
1479
|
+
while (dir.startsWith(root)) {
|
|
1480
|
+
const pkgJson = path7.join(dir, "package.json");
|
|
1481
|
+
const hasPkg = existsSync17(pkgJson);
|
|
1482
|
+
const goMod = existsSync17(path7.join(dir, "go.mod"));
|
|
1483
|
+
const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
|
|
1484
|
+
if (hasPkg || goMod || pySignal) {
|
|
1485
|
+
let pkg = null;
|
|
1486
|
+
if (hasPkg) {
|
|
1487
|
+
try {
|
|
1488
|
+
pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
|
|
1489
|
+
} catch {
|
|
1490
|
+
pkg = null;
|
|
1488
1491
|
}
|
|
1489
|
-
const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
|
|
1490
|
-
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1491
1492
|
}
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
dir = parent;
|
|
1493
|
+
const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
|
|
1494
|
+
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1495
1495
|
}
|
|
1496
|
+
const parent = path7.dirname(dir);
|
|
1497
|
+
if (parent === dir || dir === root) break;
|
|
1498
|
+
dir = parent;
|
|
1496
1499
|
}
|
|
1497
|
-
return
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
async function detectTestFrameworksForAnchors(root, anchorPaths) {
|
|
1503
|
+
const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
|
|
1504
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1505
|
+
for (const rel of starts) {
|
|
1506
|
+
const found = await detectForAnchor(root, rel) ?? { framework: "vitest", baseDir: "" };
|
|
1507
|
+
const existing = groups.get(found.baseDir);
|
|
1508
|
+
if (existing) existing.anchors.push(rel);
|
|
1509
|
+
else groups.set(found.baseDir, { ...found, anchors: [rel] });
|
|
1510
|
+
}
|
|
1511
|
+
return [...groups.values()];
|
|
1498
1512
|
}
|
|
1499
1513
|
var ScaffoldTestInputSchema = {
|
|
1500
1514
|
memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
|
|
@@ -1509,43 +1523,69 @@ async function scaffoldTest(input, ctx) {
|
|
|
1509
1523
|
return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
|
|
1510
1524
|
}
|
|
1511
1525
|
const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
|
|
1512
|
-
const
|
|
1513
|
-
const
|
|
1526
|
+
const allGroups = await detectTestFrameworksForAnchors(ctx.paths.root, anchorPaths);
|
|
1527
|
+
const groups = input.out_path ? allGroups.slice(0, 1) : allGroups;
|
|
1528
|
+
const frameworkFor = (detected) => input.framework ? normalizeFramework(input.framework) ?? detected : detected;
|
|
1514
1529
|
const fields = parseLessonFields(found.memory.body);
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
{ framework, outPath: input.out_path, baseDir:
|
|
1530
|
+
const lesson = {
|
|
1531
|
+
memoryId: input.memory_id,
|
|
1532
|
+
title: fields.title || input.memory_id,
|
|
1533
|
+
whyFailed: fields.whyFailed,
|
|
1534
|
+
instead: fields.instead,
|
|
1535
|
+
incident: found.memory.frontmatter.sensor?.incident,
|
|
1536
|
+
paths: anchorPaths
|
|
1537
|
+
};
|
|
1538
|
+
let scaffolds = groups.map(
|
|
1539
|
+
(g) => scaffoldPostIncidentTest(lesson, { framework: frameworkFor(g.framework), outPath: input.out_path, baseDir: g.baseDir })
|
|
1525
1540
|
);
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1541
|
+
let proposeCommand = scaffolds[0].proposeCommand;
|
|
1542
|
+
if (scaffolds.length > 1) {
|
|
1543
|
+
proposeCommand = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
|
|
1544
|
+
scaffolds = groups.map(
|
|
1545
|
+
(g) => scaffoldPostIncidentTest(lesson, {
|
|
1546
|
+
framework: frameworkFor(g.framework),
|
|
1547
|
+
baseDir: g.baseDir,
|
|
1548
|
+
proposeCommandOverride: proposeCommand
|
|
1549
|
+
})
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
const results = [];
|
|
1553
|
+
for (const scaffold of scaffolds) {
|
|
1554
|
+
const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
|
|
1555
|
+
let written = false;
|
|
1556
|
+
let alreadyExists = false;
|
|
1557
|
+
if (input.write) {
|
|
1558
|
+
if (existsSync17(abs)) {
|
|
1559
|
+
alreadyExists = true;
|
|
1560
|
+
} else {
|
|
1561
|
+
await mkdir4(path7.dirname(abs), { recursive: true });
|
|
1562
|
+
await writeFile10(abs, scaffold.content, "utf8");
|
|
1563
|
+
written = true;
|
|
1564
|
+
}
|
|
1536
1565
|
}
|
|
1566
|
+
results.push({
|
|
1567
|
+
framework: scaffold.framework,
|
|
1568
|
+
path: scaffold.relPath,
|
|
1569
|
+
run_command: scaffold.runCommand,
|
|
1570
|
+
content: scaffold.content,
|
|
1571
|
+
written,
|
|
1572
|
+
already_exists: alreadyExists
|
|
1573
|
+
});
|
|
1537
1574
|
}
|
|
1575
|
+
const first = results[0];
|
|
1576
|
+
const anyExisting = results.some((r) => r.already_exists);
|
|
1538
1577
|
return {
|
|
1539
1578
|
ok: true,
|
|
1540
1579
|
memory_id: input.memory_id,
|
|
1541
|
-
framework,
|
|
1542
|
-
path:
|
|
1543
|
-
run_command:
|
|
1544
|
-
propose_command:
|
|
1545
|
-
content:
|
|
1546
|
-
written,
|
|
1547
|
-
already_exists:
|
|
1548
|
-
|
|
1580
|
+
framework: first.framework,
|
|
1581
|
+
path: first.path,
|
|
1582
|
+
run_command: first.run_command,
|
|
1583
|
+
propose_command: proposeCommand,
|
|
1584
|
+
content: first.content,
|
|
1585
|
+
written: first.written,
|
|
1586
|
+
already_exists: first.already_exists,
|
|
1587
|
+
...results.length > 1 ? { scaffolds: results } : {},
|
|
1588
|
+
notice: (results.length > 1 ? `Lesson spans ${results.length} packages \u2014 one pending test per owning package; ONE propose_command arms them all (chained oracle). ` : "") + (anyExisting ? "Some file(s) already exist \u2014 not overwritten. Delete them or pass out_path to write elsewhere." : "PENDING test scaffolded. Fill in the assertion (RED on the incident, GREEN once fixed), run it, then arm it with propose_command \u2014 propose_sensor stays the sole validated writer.")
|
|
1549
1589
|
};
|
|
1550
1590
|
}
|
|
1551
1591
|
|
|
@@ -2812,7 +2852,7 @@ function oneLine(value) {
|
|
|
2812
2852
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
2813
2853
|
}
|
|
2814
2854
|
function serverVersion() {
|
|
2815
|
-
return true ? "0.
|
|
2855
|
+
return true ? "0.39.0" : "dev";
|
|
2816
2856
|
}
|
|
2817
2857
|
|
|
2818
2858
|
// src/tools/code-map.ts
|
|
@@ -4240,7 +4280,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4240
4280
|
// src/server.ts
|
|
4241
4281
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
|
|
4242
4282
|
var SERVER_NAME = "hivelore";
|
|
4243
|
-
var SERVER_VERSION = "0.
|
|
4283
|
+
var SERVER_VERSION = "0.39.0";
|
|
4244
4284
|
function jsonResult(data) {
|
|
4245
4285
|
return {
|
|
4246
4286
|
content: [
|