@hivelore/cli 0.38.0 → 0.39.1
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-UOMGIXZN.js → chunk-YMIQAOFL.js} +137 -65
- package/dist/chunk-YMIQAOFL.js.map +1 -0
- package/dist/index.js +369 -210
- package/dist/index.js.map +1 -1
- package/dist/{server-HG2K3WOQ.js → server-X6S6KTYJ.js} +4 -2
- package/package.json +4 -4
- package/dist/chunk-UOMGIXZN.js.map +0 -1
- /package/dist/{server-HG2K3WOQ.js.map → server-X6S6KTYJ.js.map} +0 -0
|
@@ -139,6 +139,8 @@ import { existsSync as existsSync15 } from "fs";
|
|
|
139
139
|
import path5 from "path";
|
|
140
140
|
import {
|
|
141
141
|
extractSensorExamples,
|
|
142
|
+
extractTestFilePathsFromCommand,
|
|
143
|
+
hasPendingTestMarker,
|
|
142
144
|
judgeProposedSensor,
|
|
143
145
|
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
144
146
|
serializeMemory as serializeMemory7
|
|
@@ -149,6 +151,7 @@ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from
|
|
|
149
151
|
import path7 from "path";
|
|
150
152
|
import { z as z17 } from "zod";
|
|
151
153
|
import {
|
|
154
|
+
buildProposeCommand,
|
|
152
155
|
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
153
156
|
normalizeFramework,
|
|
154
157
|
parseLessonFields,
|
|
@@ -1391,7 +1394,26 @@ async function proposeSensor(input, ctx) {
|
|
|
1391
1394
|
if (!found) {
|
|
1392
1395
|
throw new Error(`No memory found with id ${input.memory_id}`);
|
|
1393
1396
|
}
|
|
1397
|
+
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}.` : "";
|
|
1394
1398
|
if (kind !== "regex") {
|
|
1399
|
+
const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path5.resolve(ctx.paths.root, rel)));
|
|
1400
|
+
const pendingTests = [];
|
|
1401
|
+
for (const rel of referencedTests) {
|
|
1402
|
+
try {
|
|
1403
|
+
if (hasPendingTestMarker(await readFile3(path5.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (pendingTests.length > 0 && input.severity === "block") {
|
|
1408
|
+
return {
|
|
1409
|
+
accepted: false,
|
|
1410
|
+
memory_id: input.memory_id,
|
|
1411
|
+
severity: input.severity,
|
|
1412
|
+
reason: "oracle-pending",
|
|
1413
|
+
guidance: `The routed test is still a PENDING stub (${pendingTests.join(", ")}) \u2014 it passes on anything, so the sensor would enforce nothing while reporting protection. Write the assertion (RED on the incident, GREEN once fixed), run it, then re-propose.`,
|
|
1414
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: pendingTests }
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1395
1417
|
const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
|
|
1396
1418
|
const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
1397
1419
|
if (verdictCmd.status !== "passed" && input.severity === "block") {
|
|
@@ -1425,7 +1447,7 @@ ${verdictCmd.detail}`,
|
|
|
1425
1447
|
accepted: true,
|
|
1426
1448
|
memory_id: input.memory_id,
|
|
1427
1449
|
severity: input.severity,
|
|
1428
|
-
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})
|
|
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,
|
|
1429
1451
|
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
|
|
1430
1452
|
};
|
|
1431
1453
|
}
|
|
@@ -1474,6 +1496,7 @@ ${verdictCmd.detail}`,
|
|
|
1474
1496
|
accepted: true,
|
|
1475
1497
|
memory_id: input.memory_id,
|
|
1476
1498
|
severity: input.severity,
|
|
1499
|
+
...personalScopeNudge ? { guidance: personalScopeNudge.trim() } : {},
|
|
1477
1500
|
self_check,
|
|
1478
1501
|
file_path: found.filePath
|
|
1479
1502
|
};
|
|
@@ -1482,7 +1505,9 @@ var MemTriedInputSchema = {
|
|
|
1482
1505
|
what: z16.string().min(1).describe("Brief description of the approach that was tried"),
|
|
1483
1506
|
why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
|
|
1484
1507
|
instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
|
|
1485
|
-
scope: z16.enum(["personal", "team", "module"]).
|
|
1508
|
+
scope: z16.enum(["personal", "team", "module"]).optional().describe(
|
|
1509
|
+
"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."
|
|
1510
|
+
),
|
|
1486
1511
|
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
1487
1512
|
tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
|
|
1488
1513
|
paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
|
|
@@ -1505,11 +1530,15 @@ async function memTried(input, ctx) {
|
|
|
1505
1530
|
if (!existsSync16(ctx.paths.haiveDir)) {
|
|
1506
1531
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1507
1532
|
}
|
|
1508
|
-
const
|
|
1533
|
+
const SLUG_STOPWORDS = /* @__PURE__ */ new Set(["and", "or", "the", "a", "an", "of", "to", "in", "for", "with", "on", "at", "by"]);
|
|
1534
|
+
const words = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5);
|
|
1535
|
+
while (words.length > 2 && SLUG_STOPWORDS.has(words[words.length - 1])) words.pop();
|
|
1536
|
+
const slug = words.join("-");
|
|
1537
|
+
const scope = input.scope ?? (input.sensor ? "team" : "personal");
|
|
1509
1538
|
const baseFm = buildFrontmatter2({
|
|
1510
1539
|
type: "attempt",
|
|
1511
1540
|
slug,
|
|
1512
|
-
scope
|
|
1541
|
+
scope,
|
|
1513
1542
|
module: input.module,
|
|
1514
1543
|
tags: input.tags,
|
|
1515
1544
|
paths: input.paths,
|
|
@@ -1557,7 +1586,7 @@ async function memTried(input, ctx) {
|
|
|
1557
1586
|
...verdict.reason ? { reason: verdict.reason } : {},
|
|
1558
1587
|
...verdict.guidance ? { guidance: verdict.guidance } : {}
|
|
1559
1588
|
},
|
|
1560
|
-
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.`
|
|
1589
|
+
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." : "")
|
|
1561
1590
|
};
|
|
1562
1591
|
}
|
|
1563
1592
|
const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
|
|
@@ -1578,39 +1607,55 @@ async function memTried(input, ctx) {
|
|
|
1578
1607
|
};
|
|
1579
1608
|
}
|
|
1580
1609
|
var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
|
|
1581
|
-
async function
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
if (hasPkg
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
} catch {
|
|
1601
|
-
pkg = null;
|
|
1602
|
-
}
|
|
1610
|
+
async function detectForAnchor(root, rel) {
|
|
1611
|
+
let dir = path7.resolve(root, rel);
|
|
1612
|
+
try {
|
|
1613
|
+
if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
|
|
1614
|
+
} catch {
|
|
1615
|
+
if (path7.extname(dir)) dir = path7.dirname(dir);
|
|
1616
|
+
}
|
|
1617
|
+
while (dir.startsWith(root)) {
|
|
1618
|
+
const pkgJson = path7.join(dir, "package.json");
|
|
1619
|
+
const hasPkg = existsSync17(pkgJson);
|
|
1620
|
+
const goMod = existsSync17(path7.join(dir, "go.mod"));
|
|
1621
|
+
const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
|
|
1622
|
+
if (hasPkg || goMod || pySignal) {
|
|
1623
|
+
let pkg = null;
|
|
1624
|
+
if (hasPkg) {
|
|
1625
|
+
try {
|
|
1626
|
+
pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
|
|
1627
|
+
} catch {
|
|
1628
|
+
pkg = null;
|
|
1603
1629
|
}
|
|
1604
|
-
const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
|
|
1605
|
-
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1606
1630
|
}
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
dir = parent;
|
|
1631
|
+
const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
|
|
1632
|
+
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1610
1633
|
}
|
|
1634
|
+
const parent = path7.dirname(dir);
|
|
1635
|
+
if (parent === dir || dir === root) break;
|
|
1636
|
+
dir = parent;
|
|
1637
|
+
}
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
async function detectTestFrameworkForPaths(root, anchorPaths) {
|
|
1641
|
+
const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
|
|
1642
|
+
for (const rel of starts) {
|
|
1643
|
+
const found = await detectForAnchor(root, rel);
|
|
1644
|
+
if (found) return found;
|
|
1611
1645
|
}
|
|
1612
1646
|
return { framework: "vitest", baseDir: "" };
|
|
1613
1647
|
}
|
|
1648
|
+
async function detectTestFrameworksForAnchors(root, anchorPaths) {
|
|
1649
|
+
const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
|
|
1650
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1651
|
+
for (const rel of starts) {
|
|
1652
|
+
const found = await detectForAnchor(root, rel) ?? { framework: "vitest", baseDir: "" };
|
|
1653
|
+
const existing = groups.get(found.baseDir);
|
|
1654
|
+
if (existing) existing.anchors.push(rel);
|
|
1655
|
+
else groups.set(found.baseDir, { ...found, anchors: [rel] });
|
|
1656
|
+
}
|
|
1657
|
+
return [...groups.values()];
|
|
1658
|
+
}
|
|
1614
1659
|
var ScaffoldTestInputSchema = {
|
|
1615
1660
|
memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
|
|
1616
1661
|
framework: z17.enum(["vitest", "jest", "pytest", "gotest"]).optional().describe("Test framework. Auto-detected from the package that owns the lesson's anchor paths when omitted."),
|
|
@@ -1624,43 +1669,69 @@ async function scaffoldTest(input, ctx) {
|
|
|
1624
1669
|
return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
|
|
1625
1670
|
}
|
|
1626
1671
|
const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
|
|
1627
|
-
const
|
|
1628
|
-
const
|
|
1672
|
+
const allGroups = await detectTestFrameworksForAnchors(ctx.paths.root, anchorPaths);
|
|
1673
|
+
const groups = input.out_path ? allGroups.slice(0, 1) : allGroups;
|
|
1674
|
+
const frameworkFor = (detected) => input.framework ? normalizeFramework(input.framework) ?? detected : detected;
|
|
1629
1675
|
const fields = parseLessonFields(found.memory.body);
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
{ framework, outPath: input.out_path, baseDir:
|
|
1676
|
+
const lesson = {
|
|
1677
|
+
memoryId: input.memory_id,
|
|
1678
|
+
title: fields.title || input.memory_id,
|
|
1679
|
+
whyFailed: fields.whyFailed,
|
|
1680
|
+
instead: fields.instead,
|
|
1681
|
+
incident: found.memory.frontmatter.sensor?.incident,
|
|
1682
|
+
paths: anchorPaths
|
|
1683
|
+
};
|
|
1684
|
+
let scaffolds = groups.map(
|
|
1685
|
+
(g) => scaffoldPostIncidentTest(lesson, { framework: frameworkFor(g.framework), outPath: input.out_path, baseDir: g.baseDir })
|
|
1640
1686
|
);
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1687
|
+
let proposeCommand = scaffolds[0].proposeCommand;
|
|
1688
|
+
if (scaffolds.length > 1) {
|
|
1689
|
+
proposeCommand = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
|
|
1690
|
+
scaffolds = groups.map(
|
|
1691
|
+
(g) => scaffoldPostIncidentTest(lesson, {
|
|
1692
|
+
framework: frameworkFor(g.framework),
|
|
1693
|
+
baseDir: g.baseDir,
|
|
1694
|
+
proposeCommandOverride: proposeCommand
|
|
1695
|
+
})
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
const results = [];
|
|
1699
|
+
for (const scaffold of scaffolds) {
|
|
1700
|
+
const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
|
|
1701
|
+
let written = false;
|
|
1702
|
+
let alreadyExists = false;
|
|
1703
|
+
if (input.write) {
|
|
1704
|
+
if (existsSync17(abs)) {
|
|
1705
|
+
alreadyExists = true;
|
|
1706
|
+
} else {
|
|
1707
|
+
await mkdir4(path7.dirname(abs), { recursive: true });
|
|
1708
|
+
await writeFile10(abs, scaffold.content, "utf8");
|
|
1709
|
+
written = true;
|
|
1710
|
+
}
|
|
1651
1711
|
}
|
|
1712
|
+
results.push({
|
|
1713
|
+
framework: scaffold.framework,
|
|
1714
|
+
path: scaffold.relPath,
|
|
1715
|
+
run_command: scaffold.runCommand,
|
|
1716
|
+
content: scaffold.content,
|
|
1717
|
+
written,
|
|
1718
|
+
already_exists: alreadyExists
|
|
1719
|
+
});
|
|
1652
1720
|
}
|
|
1721
|
+
const first = results[0];
|
|
1722
|
+
const anyExisting = results.some((r) => r.already_exists);
|
|
1653
1723
|
return {
|
|
1654
1724
|
ok: true,
|
|
1655
1725
|
memory_id: input.memory_id,
|
|
1656
|
-
framework,
|
|
1657
|
-
path:
|
|
1658
|
-
run_command:
|
|
1659
|
-
propose_command:
|
|
1660
|
-
content:
|
|
1661
|
-
written,
|
|
1662
|
-
already_exists:
|
|
1663
|
-
|
|
1726
|
+
framework: first.framework,
|
|
1727
|
+
path: first.path,
|
|
1728
|
+
run_command: first.run_command,
|
|
1729
|
+
propose_command: proposeCommand,
|
|
1730
|
+
content: first.content,
|
|
1731
|
+
written: first.written,
|
|
1732
|
+
already_exists: first.already_exists,
|
|
1733
|
+
...results.length > 1 ? { scaffolds: results } : {},
|
|
1734
|
+
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.")
|
|
1664
1735
|
};
|
|
1665
1736
|
}
|
|
1666
1737
|
var IngestFindingsInputSchema = {
|
|
@@ -2827,7 +2898,7 @@ function oneLine(value) {
|
|
|
2827
2898
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
2828
2899
|
}
|
|
2829
2900
|
function serverVersion() {
|
|
2830
|
-
return true ? "0.
|
|
2901
|
+
return true ? "0.39.1" : "dev";
|
|
2831
2902
|
}
|
|
2832
2903
|
var CodeMapInputSchema = {
|
|
2833
2904
|
file: z21.string().optional().describe("Filter to files whose path contains this substring"),
|
|
@@ -4154,7 +4225,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4154
4225
|
};
|
|
4155
4226
|
}
|
|
4156
4227
|
var SERVER_NAME = "hivelore";
|
|
4157
|
-
var SERVER_VERSION = "0.
|
|
4228
|
+
var SERVER_VERSION = "0.39.1";
|
|
4158
4229
|
function jsonResult(data) {
|
|
4159
4230
|
return {
|
|
4160
4231
|
content: [
|
|
@@ -5104,6 +5175,7 @@ export {
|
|
|
5104
5175
|
proposeSensor,
|
|
5105
5176
|
memTried,
|
|
5106
5177
|
detectTestFrameworkForPaths,
|
|
5178
|
+
detectTestFrameworksForAnchors,
|
|
5107
5179
|
scaffoldTest,
|
|
5108
5180
|
getBriefing,
|
|
5109
5181
|
codeMapTool,
|
|
@@ -5129,4 +5201,4 @@ export {
|
|
|
5129
5201
|
printHaiveMcpVersion,
|
|
5130
5202
|
runHaiveMcpStdio
|
|
5131
5203
|
};
|
|
5132
|
-
//# sourceMappingURL=chunk-
|
|
5204
|
+
//# sourceMappingURL=chunk-YMIQAOFL.js.map
|