@hivelore/core 0.53.4 → 0.56.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.d.ts +385 -1
- package/dist/index.js +654 -176
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1042,6 +1042,26 @@ function renderPreventionReceiptShare(receipt) {
|
|
|
1042
1042
|
lines.push("", `<sub>${HIVELORE_ATTRIBUTION}</sub>`);
|
|
1043
1043
|
return lines.join("\n");
|
|
1044
1044
|
}
|
|
1045
|
+
var PREVENTION_RECEIPT_MARKER = "<!-- haive:prevention-receipt -->";
|
|
1046
|
+
function renderPreventionComment(receipt, findings = []) {
|
|
1047
|
+
const fired = findings.filter((f) => f.code === "sensor-block" || f.code === "sensor-warn");
|
|
1048
|
+
const lines = [PREVENTION_RECEIPT_MARKER, "", "## Hivelore prevention receipt", ""];
|
|
1049
|
+
if (fired.length === 0) {
|
|
1050
|
+
lines.push("No documented sensor fired on this PR.", "");
|
|
1051
|
+
} else {
|
|
1052
|
+
lines.push("### Fired on this PR", "");
|
|
1053
|
+
for (const f of fired) {
|
|
1054
|
+
const where = f.file ? ` \u2014 \`${f.file}\`` : "";
|
|
1055
|
+
lines.push(`- **${f.memory_ids?.[0] ?? "sensor"}**${where} \u2014 ${f.message ?? "sensor fired"}`);
|
|
1056
|
+
if (f.matched_line) lines.push(` \`\`\`
|
|
1057
|
+
${f.matched_line}
|
|
1058
|
+
\`\`\``);
|
|
1059
|
+
}
|
|
1060
|
+
lines.push("");
|
|
1061
|
+
}
|
|
1062
|
+
lines.push(renderPreventionReceiptShare(receipt));
|
|
1063
|
+
return lines.join("\n");
|
|
1064
|
+
}
|
|
1045
1065
|
async function loadPreventionEvents(paths) {
|
|
1046
1066
|
const file = preventionLogPath(paths);
|
|
1047
1067
|
if (!existsSync4(file)) return [];
|
|
@@ -1204,6 +1224,178 @@ async function recordProjectContextEmission(paths, hash, now = Date.now()) {
|
|
|
1204
1224
|
});
|
|
1205
1225
|
}
|
|
1206
1226
|
|
|
1227
|
+
// src/gate-reminder.ts
|
|
1228
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
|
|
1229
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1230
|
+
import path8 from "path";
|
|
1231
|
+
var GATE_REMINDER_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
1232
|
+
function reminderMarkerPath(paths) {
|
|
1233
|
+
return path8.join(paths.haiveDir, ".cache", "gate-reminders.json");
|
|
1234
|
+
}
|
|
1235
|
+
async function readMarkers(paths) {
|
|
1236
|
+
const file = reminderMarkerPath(paths);
|
|
1237
|
+
if (!existsSync6(file)) return {};
|
|
1238
|
+
try {
|
|
1239
|
+
const parsed = JSON.parse(await readFile6(file, "utf8"));
|
|
1240
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1241
|
+
} catch {
|
|
1242
|
+
return {};
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
async function shouldExpandGateReminder(paths, key, now = Date.now(), windowMs = GATE_REMINDER_WINDOW_MS) {
|
|
1246
|
+
const last = (await readMarkers(paths))[key];
|
|
1247
|
+
if (!last) return true;
|
|
1248
|
+
const at = Date.parse(last);
|
|
1249
|
+
return !Number.isFinite(at) || now - at >= windowMs;
|
|
1250
|
+
}
|
|
1251
|
+
async function recordGateReminder(paths, key, now = Date.now()) {
|
|
1252
|
+
const file = reminderMarkerPath(paths);
|
|
1253
|
+
const markers = await readMarkers(paths);
|
|
1254
|
+
markers[key] = new Date(now).toISOString();
|
|
1255
|
+
await mkdir4(path8.dirname(file), { recursive: true }).catch(() => {
|
|
1256
|
+
});
|
|
1257
|
+
await writeFile3(file, JSON.stringify(markers, null, 2), "utf8").catch(() => {
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/gate-verdict.ts
|
|
1262
|
+
var PROCESS_GATE_CODES = /* @__PURE__ */ new Set([
|
|
1263
|
+
"briefing-missing",
|
|
1264
|
+
"session-recap-missing",
|
|
1265
|
+
"decision-coverage-missing",
|
|
1266
|
+
"bootstrap-incomplete"
|
|
1267
|
+
]);
|
|
1268
|
+
var CONTENT_CATCH_CODES = /* @__PURE__ */ new Set(["sensor-block", "precommit-policy-block"]);
|
|
1269
|
+
var CONTENT_CODES = /* @__PURE__ */ new Set([...CONTENT_CATCH_CODES, "sensor-warn"]);
|
|
1270
|
+
var SETUP_GATE_CODES = /* @__PURE__ */ new Set([
|
|
1271
|
+
...PROCESS_GATE_CODES,
|
|
1272
|
+
"enforcement-score-below-threshold"
|
|
1273
|
+
]);
|
|
1274
|
+
var DEFAULT_POSTURE = "balanced";
|
|
1275
|
+
var POSTURE_DEFAULTS = {
|
|
1276
|
+
// Report everything, refuse nothing. For adopting Hivelore on a repo mid-flight.
|
|
1277
|
+
advisory: { mode: "advisory", processGate: "warn", humanCommits: "relaxed" },
|
|
1278
|
+
// Refuse on deterministic, code-bound evidence only. The default.
|
|
1279
|
+
balanced: { mode: "strict", processGate: "warn", humanCommits: "relaxed" },
|
|
1280
|
+
// Process gates bind too, at the sharing points. For teams that want the workflow enforced.
|
|
1281
|
+
strict: { mode: "strict", processGate: "block", humanCommits: "strict" }
|
|
1282
|
+
};
|
|
1283
|
+
function resolveGatePolicy(input) {
|
|
1284
|
+
const cfg = input ?? {};
|
|
1285
|
+
const posture = cfg.posture ?? DEFAULT_POSTURE;
|
|
1286
|
+
const base = POSTURE_DEFAULTS[posture] ?? POSTURE_DEFAULTS[DEFAULT_POSTURE];
|
|
1287
|
+
const overrides = [];
|
|
1288
|
+
for (const key of ["mode", "processGate", "humanCommits"]) {
|
|
1289
|
+
if (cfg[key] !== void 0) overrides.push(key);
|
|
1290
|
+
}
|
|
1291
|
+
return {
|
|
1292
|
+
posture,
|
|
1293
|
+
mode: cfg.mode ?? base.mode,
|
|
1294
|
+
processGate: cfg.processGate ?? base.processGate,
|
|
1295
|
+
humanCommits: cfg.humanCommits ?? base.humanCommits,
|
|
1296
|
+
scoreThreshold: cfg.scoreThreshold ?? 80,
|
|
1297
|
+
overrides
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
function describePosture(policy) {
|
|
1301
|
+
const base = policy.posture === "advisory" ? "reports everything, refuses nothing" : policy.posture === "strict" ? "refuses on deterministic findings AND on process gates at pre-push/CI" : "refuses on deterministic findings only (sensors, anti-patterns, stale anchors on touched files)";
|
|
1302
|
+
const pinned = policy.overrides.length > 0 ? ` \xB7 overridden: ${policy.overrides.join(", ")}` : "";
|
|
1303
|
+
return `${policy.posture} \u2014 ${base}${pinned}`;
|
|
1304
|
+
}
|
|
1305
|
+
function processGateDecision(policy, stage, isAgent) {
|
|
1306
|
+
if (policy.processGate !== "block") {
|
|
1307
|
+
return {
|
|
1308
|
+
refuses: false,
|
|
1309
|
+
reason: `advisory: process gates report, they do not refuse \u2014 only sensors and other deterministic findings block. Set enforcement.posture="strict" (or processGate="block") to change that.`
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
if (stage === "pre-commit" || stage === "local") {
|
|
1313
|
+
return {
|
|
1314
|
+
refuses: false,
|
|
1315
|
+
reason: "advisory at commit time: process gates bind the sharing points (pre-push, CI), not local iteration."
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
if (stage !== "ci" && !isAgent && policy.humanCommits === "relaxed") {
|
|
1319
|
+
return {
|
|
1320
|
+
refuses: false,
|
|
1321
|
+
reason: `relaxed to a warning: no agent harness detected, so this human commit is not bound by agent process gates \u2014 set enforcement.humanCommits="strict" to change that.`
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
return { refuses: true, reason: "enforced: process gates bind at this sharing point." };
|
|
1325
|
+
}
|
|
1326
|
+
function penaltyOf(finding) {
|
|
1327
|
+
if (finding.severity === "error") return finding.impact ?? 25;
|
|
1328
|
+
if (finding.severity === "warn") return finding.impact ?? 8;
|
|
1329
|
+
return 0;
|
|
1330
|
+
}
|
|
1331
|
+
function computeBaselineHealth(findings, threshold) {
|
|
1332
|
+
const baseline = findings.filter((f) => !CONTENT_CODES.has(f.code));
|
|
1333
|
+
const penalty = baseline.reduce((sum, f) => sum + penaltyOf(f), 0);
|
|
1334
|
+
return {
|
|
1335
|
+
score: Math.max(0, Math.min(100, 100 - penalty)),
|
|
1336
|
+
threshold,
|
|
1337
|
+
checks: {
|
|
1338
|
+
total: findings.length,
|
|
1339
|
+
ok: findings.filter((f) => f.severity === "ok").length,
|
|
1340
|
+
warn: findings.filter((f) => f.severity === "warn").length,
|
|
1341
|
+
error: findings.filter((f) => f.severity === "error").length
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function dedupeRefusals(findings) {
|
|
1346
|
+
const byMemory = /* @__PURE__ */ new Map();
|
|
1347
|
+
const out = [];
|
|
1348
|
+
for (const finding of findings) {
|
|
1349
|
+
if (!CONTENT_CATCH_CODES.has(finding.code) || finding.severity !== "error") continue;
|
|
1350
|
+
const key = finding.memory_ids?.[0];
|
|
1351
|
+
if (!key) {
|
|
1352
|
+
out.push(finding);
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
const existing = byMemory.get(key);
|
|
1356
|
+
if (!existing || !existing.matched_line && finding.matched_line) byMemory.set(key, finding);
|
|
1357
|
+
}
|
|
1358
|
+
return [...byMemory.values(), ...out];
|
|
1359
|
+
}
|
|
1360
|
+
function decideVerdict(input) {
|
|
1361
|
+
const { policy, stage, isAgent } = input;
|
|
1362
|
+
const process2 = processGateDecision(policy, stage, isAgent);
|
|
1363
|
+
const findings = input.findings.map((finding) => {
|
|
1364
|
+
if (finding.severity !== "error" || !PROCESS_GATE_CODES.has(finding.code)) return finding;
|
|
1365
|
+
if (process2.refuses) return finding;
|
|
1366
|
+
return {
|
|
1367
|
+
...finding,
|
|
1368
|
+
severity: "warn",
|
|
1369
|
+
// Capped so a downgraded gate cannot dominate the health score it no longer refuses on.
|
|
1370
|
+
impact: Math.min(finding.impact ?? 8, 8),
|
|
1371
|
+
reason: process2.reason,
|
|
1372
|
+
message: `${finding.message} (${process2.reason})`
|
|
1373
|
+
};
|
|
1374
|
+
});
|
|
1375
|
+
const baselineHealth = computeBaselineHealth(findings, policy.scoreThreshold);
|
|
1376
|
+
const refusals = dedupeRefusals(findings);
|
|
1377
|
+
const hasErrors = findings.some((f) => f.severity === "error");
|
|
1378
|
+
return {
|
|
1379
|
+
findings,
|
|
1380
|
+
should_block: policy.mode === "strict" && hasErrors,
|
|
1381
|
+
actor: isAgent ? `agent (${(input.agentSignals ?? []).join(", ")})` : process2.refuses ? "human \u2014 strict (enforcement.humanCommits)" : "human \u2014 process gates relaxed",
|
|
1382
|
+
baseline_health: baselineHealth,
|
|
1383
|
+
refusals,
|
|
1384
|
+
process_gate_reason: process2.reason
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function buildBaselineHealthFinding(findings, health, shouldBlock) {
|
|
1388
|
+
if (shouldBlock || health.score >= health.threshold) return null;
|
|
1389
|
+
const topPenalties = findings.filter((f) => !CONTENT_CODES.has(f.code)).map((f) => ({ code: f.code, penalty: penaltyOf(f) })).filter((p) => p.penalty > 0).sort((a, b) => b.penalty - a.penalty).slice(0, 3);
|
|
1390
|
+
return {
|
|
1391
|
+
severity: "warn",
|
|
1392
|
+
code: "enforcement-score-below-threshold",
|
|
1393
|
+
message: `Repo knowledge-layer health ${health.score}% is below the ${health.threshold}% target` + (topPenalties.length > 0 ? ` \u2014 top gaps: ${topPenalties.map((p) => `${p.code} (\u2212${p.penalty})`).join(", ")}` : "") + ". This measures the repo's baseline, not this change; it never blocks.",
|
|
1394
|
+
fix: "Fill the gaps above (bootstrap, briefing, recap), then rerun `hivelore enforce check`.",
|
|
1395
|
+
impact: 0
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1207
1399
|
// src/priority.ts
|
|
1208
1400
|
var DEFAULT_PRIORITY_SIGNALS = {
|
|
1209
1401
|
type: "",
|
|
@@ -1394,10 +1586,10 @@ function sensorPatternBrittleness(pattern) {
|
|
|
1394
1586
|
function normalizeProjectPath(value) {
|
|
1395
1587
|
return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
|
|
1396
1588
|
}
|
|
1397
|
-
function sensorAppliesToPath(sensor, anchorPaths,
|
|
1589
|
+
function sensorAppliesToPath(sensor, anchorPaths, path22) {
|
|
1398
1590
|
const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
|
|
1399
1591
|
if (scopes.length === 0) return true;
|
|
1400
|
-
const target = normalizeProjectPath(
|
|
1592
|
+
const target = normalizeProjectPath(path22);
|
|
1401
1593
|
return scopes.some((rawScope) => {
|
|
1402
1594
|
const scope = normalizeProjectPath(rawScope);
|
|
1403
1595
|
if (!scope) return false;
|
|
@@ -1722,6 +1914,34 @@ function judgeProposedSensor(sensor, input) {
|
|
|
1722
1914
|
}
|
|
1723
1915
|
return { accepted: true, self_check, brittle };
|
|
1724
1916
|
}
|
|
1917
|
+
function explainSensorRejection(verdict, context) {
|
|
1918
|
+
const retry = context.style === "cli" ? "re-run" : "re-propose";
|
|
1919
|
+
switch (verdict.reason) {
|
|
1920
|
+
case "fires-on-current": {
|
|
1921
|
+
const where = verdict.self_check.fired_on.join(", ");
|
|
1922
|
+
const warnCommand = context.style === "cli" ? `hivelore sensors propose ${context.memoryId ?? "<memory-id>"} --pattern '<same>' --severity warn` : `propose_sensor({ memory_id: "${context.memoryId ?? "<memory-id>"}", pattern: "<same>", severity: "warn" })`;
|
|
1923
|
+
return [
|
|
1924
|
+
`A block sensor must be silent on the current code, and this one fires on: ${where}.`,
|
|
1925
|
+
"That means one of two things:",
|
|
1926
|
+
` 1. The faulty pattern is STILL PRESENT \u2014 the usual case when you document a problem before`,
|
|
1927
|
+
` fixing it. A block sensor cannot be armed yet. Arm it as a warning now, fix the code,`,
|
|
1928
|
+
` then promote it:`,
|
|
1929
|
+
` ${warnCommand}`,
|
|
1930
|
+
` \u2026then re-run the same proposal with severity "block" once ${where} is clean.`,
|
|
1931
|
+
` 2. The pattern also matches LEGITIMATE usage. Add or tighten the 'absent' companion so`,
|
|
1932
|
+
` correct usage is excluded, then ${retry}.`
|
|
1933
|
+
].join("\n");
|
|
1934
|
+
}
|
|
1935
|
+
case "fires-on-correct":
|
|
1936
|
+
return `Inverted: the pattern matches the lesson's OWN recommended fix (its \`Instead, use:\` approach) \u2014 it would block correct code and never the mistake. Point the pattern at the FAULTY usage, then ${retry}.`;
|
|
1937
|
+
case "missed-bad-example":
|
|
1938
|
+
return `The sensor did not match the bad example, so it won't catch the mistake. Adjust the pattern, then ${retry}.`;
|
|
1939
|
+
case "brittle":
|
|
1940
|
+
return `The pattern is brittle (${verdict.brittle}). Use a durable pattern (avoid hardcoded line numbers), then ${retry}.`;
|
|
1941
|
+
default:
|
|
1942
|
+
return `Re-propose with a discriminating pattern, then ${retry}.`;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1725
1945
|
function isHarnessErrorOutput(output) {
|
|
1726
1946
|
if (!output) return false;
|
|
1727
1947
|
return HARNESS_ERROR_SIGNATURES.some((re) => re.test(output));
|
|
@@ -1884,7 +2104,33 @@ function suggestSensorSeed(body, anchorPaths, options = {}) {
|
|
|
1884
2104
|
message: companion ? companionMessage(body, companion) : sensorMessageFromBody(body, token)
|
|
1885
2105
|
};
|
|
1886
2106
|
if (companion) seed.absent = escapeRegExp(companion.required);
|
|
1887
|
-
return seed;
|
|
2107
|
+
return seedFiresOnCorrectUsage(seed, body) ? null : seed;
|
|
2108
|
+
}
|
|
2109
|
+
function seedFiresOnCorrectUsage(seed, body) {
|
|
2110
|
+
const correct = correctUsageText(body);
|
|
2111
|
+
if (!correct) return false;
|
|
2112
|
+
let re;
|
|
2113
|
+
try {
|
|
2114
|
+
re = new RegExp(seed.pattern, "m");
|
|
2115
|
+
} catch {
|
|
2116
|
+
return true;
|
|
2117
|
+
}
|
|
2118
|
+
if (!re.test(correct)) return false;
|
|
2119
|
+
if (seed.absent) {
|
|
2120
|
+
try {
|
|
2121
|
+
if (new RegExp(seed.absent, "m").test(correct)) return false;
|
|
2122
|
+
} catch {
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return true;
|
|
2126
|
+
}
|
|
2127
|
+
function correctUsageText(body) {
|
|
2128
|
+
const parts = [...extractCorrectApproachExamples(body)];
|
|
2129
|
+
const headingRe = /^#{1,6}\s+(?:how to apply|instead|correct|the fix|fix|recommended|do this|guidance)\b[^\n]*\n([\s\S]*?)(?=^#{1,6}\s|(?![\s\S]))/gim;
|
|
2130
|
+
for (const match of body.matchAll(headingRe)) {
|
|
2131
|
+
if (match[1]) parts.push(match[1]);
|
|
2132
|
+
}
|
|
2133
|
+
return parts.join("\n").trim();
|
|
1888
2134
|
}
|
|
1889
2135
|
function suggestSensorFromMemory(body, anchorPaths, options = {}) {
|
|
1890
2136
|
const seed = suggestSensorSeed(body, anchorPaths, options);
|
|
@@ -2649,10 +2895,11 @@ function allocateBudget(parts, maxTokens) {
|
|
|
2649
2895
|
}
|
|
2650
2896
|
|
|
2651
2897
|
// src/code-map.ts
|
|
2652
|
-
import { mkdir as
|
|
2653
|
-
import {
|
|
2898
|
+
import { mkdir as mkdir5, readFile as readFile7, readdir as readdir3, stat as stat2, writeFile as writeFile4 } from "fs/promises";
|
|
2899
|
+
import { createHash as createHash2 } from "crypto";
|
|
2900
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2654
2901
|
import { spawnSync } from "child_process";
|
|
2655
|
-
import
|
|
2902
|
+
import path9 from "path";
|
|
2656
2903
|
|
|
2657
2904
|
// src/ast-parser.ts
|
|
2658
2905
|
import { fileURLToPath } from "url";
|
|
@@ -3064,27 +3311,53 @@ var CODE_MAP_DEFAULT_EXCLUDE = [
|
|
|
3064
3311
|
];
|
|
3065
3312
|
var TEST_FILE_RE = /\.(test|spec)\.[a-z]+$/i;
|
|
3066
3313
|
function codeMapPath(paths) {
|
|
3067
|
-
return
|
|
3314
|
+
return path9.join(paths.haiveDir, CODE_MAP_FILE);
|
|
3315
|
+
}
|
|
3316
|
+
function codeMapMetaPath(paths) {
|
|
3317
|
+
return path9.join(paths.runtimeDir, "code-map-meta.json");
|
|
3318
|
+
}
|
|
3319
|
+
function serializeCodeMap(map) {
|
|
3320
|
+
const files = {};
|
|
3321
|
+
for (const key of Object.keys(map.files).sort()) files[key] = map.files[key];
|
|
3322
|
+
return `${JSON.stringify({ version: map.version, files }, null, 2)}
|
|
3323
|
+
`;
|
|
3324
|
+
}
|
|
3325
|
+
function codeMapContentHash(map) {
|
|
3326
|
+
return createHash2("sha256").update(serializeCodeMap(map)).digest("hex").slice(0, 16);
|
|
3068
3327
|
}
|
|
3069
3328
|
async function loadCodeMap(paths) {
|
|
3070
3329
|
const file = codeMapPath(paths);
|
|
3071
|
-
if (!
|
|
3072
|
-
|
|
3330
|
+
if (!existsSync7(file)) return null;
|
|
3331
|
+
const parsed = JSON.parse(await readFile7(file, "utf8"));
|
|
3332
|
+
const generatedAt = parsed.generated_at ?? await readFile7(codeMapMetaPath(paths), "utf8").then((raw) => JSON.parse(raw).generated_at).catch(() => void 0) ?? await stat2(file).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3333
|
+
return { ...parsed, root: parsed.root ?? paths.root, generated_at: generatedAt };
|
|
3073
3334
|
}
|
|
3074
3335
|
async function saveCodeMap(paths, map) {
|
|
3075
3336
|
const file = codeMapPath(paths);
|
|
3076
|
-
|
|
3077
|
-
await
|
|
3337
|
+
const payload = serializeCodeMap(map);
|
|
3338
|
+
const current = existsSync7(file) ? await readFile7(file, "utf8").catch(() => null) : null;
|
|
3339
|
+
if (current === payload) return;
|
|
3340
|
+
await mkdir5(path9.dirname(file), { recursive: true });
|
|
3341
|
+
await writeFile4(file, payload, "utf8");
|
|
3342
|
+
await mkdir5(paths.runtimeDir, { recursive: true }).catch(() => {
|
|
3343
|
+
});
|
|
3344
|
+
await writeFile4(
|
|
3345
|
+
codeMapMetaPath(paths),
|
|
3346
|
+
`${JSON.stringify({ generated_at: (/* @__PURE__ */ new Date()).toISOString(), content_hash: codeMapContentHash(map) }, null, 2)}
|
|
3347
|
+
`,
|
|
3348
|
+
"utf8"
|
|
3349
|
+
).catch(() => {
|
|
3350
|
+
});
|
|
3078
3351
|
}
|
|
3079
3352
|
async function buildCodeMap(root, options = {}) {
|
|
3080
3353
|
const include = new Set(options.includeExtensions ?? CODE_MAP_DEFAULT_INCLUDE);
|
|
3081
3354
|
const exclude = new Set(options.excludeDirs ?? CODE_MAP_DEFAULT_EXCLUDE);
|
|
3082
3355
|
const files = {};
|
|
3083
3356
|
for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
|
|
3084
|
-
const rel =
|
|
3357
|
+
const rel = path9.relative(root, abs).replace(/\\/g, "/");
|
|
3085
3358
|
if (rel.startsWith(".ai/")) continue;
|
|
3086
|
-
const content = await
|
|
3087
|
-
const ext =
|
|
3359
|
+
const content = await readFile7(abs, "utf8");
|
|
3360
|
+
const ext = path9.extname(abs).toLowerCase();
|
|
3088
3361
|
const entry = await parseFileEntry(content, ext);
|
|
3089
3362
|
if (entry.exports.length > 0) files[rel] = entry;
|
|
3090
3363
|
}
|
|
@@ -3105,11 +3378,11 @@ async function countSourceFilesOnDisk(root, options = {}) {
|
|
|
3105
3378
|
async function* collectSourceFiles(root, include, exclude, includeUntracked) {
|
|
3106
3379
|
const gitFiles = gitSourceFiles(root, include, exclude, includeUntracked === true);
|
|
3107
3380
|
if (gitFiles) {
|
|
3108
|
-
for (const rel of gitFiles) yield
|
|
3381
|
+
for (const rel of gitFiles) yield path9.join(root, rel);
|
|
3109
3382
|
for await (const nested of findNestedGitRepos(root, exclude)) {
|
|
3110
3383
|
const nestedFiles = gitSourceFiles(nested, include, exclude, includeUntracked === true);
|
|
3111
3384
|
if (nestedFiles) {
|
|
3112
|
-
for (const rel of nestedFiles) yield
|
|
3385
|
+
for (const rel of nestedFiles) yield path9.join(nested, rel);
|
|
3113
3386
|
}
|
|
3114
3387
|
}
|
|
3115
3388
|
return;
|
|
@@ -3128,8 +3401,8 @@ async function* findNestedGitRepos(root, exclude, depth = 0) {
|
|
|
3128
3401
|
if (!entry.isDirectory()) continue;
|
|
3129
3402
|
if (entry.name.startsWith(".")) continue;
|
|
3130
3403
|
if (exclude.has(entry.name)) continue;
|
|
3131
|
-
const full =
|
|
3132
|
-
if (
|
|
3404
|
+
const full = path9.join(root, entry.name);
|
|
3405
|
+
if (existsSync7(path9.join(full, ".git"))) {
|
|
3133
3406
|
yield full;
|
|
3134
3407
|
} else {
|
|
3135
3408
|
yield* findNestedGitRepos(full, exclude, depth + 1);
|
|
@@ -3158,11 +3431,11 @@ async function* walkSourceFiles(dir, include, exclude) {
|
|
|
3158
3431
|
if (entry.isDirectory()) continue;
|
|
3159
3432
|
}
|
|
3160
3433
|
if (exclude.has(entry.name)) continue;
|
|
3161
|
-
const full =
|
|
3434
|
+
const full = path9.join(dir, entry.name);
|
|
3162
3435
|
if (entry.isDirectory()) {
|
|
3163
3436
|
yield* walkSourceFiles(full, include, exclude);
|
|
3164
3437
|
} else if (entry.isFile()) {
|
|
3165
|
-
const ext =
|
|
3438
|
+
const ext = path9.extname(entry.name).toLowerCase();
|
|
3166
3439
|
if (include.has(ext) && !TEST_FILE_RE.test(entry.name)) yield full;
|
|
3167
3440
|
}
|
|
3168
3441
|
}
|
|
@@ -3173,7 +3446,7 @@ function isIncludedSourcePath(rel, include, exclude) {
|
|
|
3173
3446
|
const parts = normalized.split("/");
|
|
3174
3447
|
if (parts.some((part) => exclude.has(part))) return false;
|
|
3175
3448
|
const base = parts.at(-1) ?? "";
|
|
3176
|
-
const ext =
|
|
3449
|
+
const ext = path9.extname(base).toLowerCase();
|
|
3177
3450
|
return include.has(ext) && !TEST_FILE_RE.test(base);
|
|
3178
3451
|
}
|
|
3179
3452
|
var EXPORT_RE = /(?:^|;)[ \t]*export\s+(?:default\s+)?(async\s+)?(function|class|interface|type|const|let|var|enum)\s+(\*?)\s*([A-Za-z_$][\w$]*)/gm;
|
|
@@ -3435,10 +3708,10 @@ function queryCodeMap(map, options) {
|
|
|
3435
3708
|
}
|
|
3436
3709
|
|
|
3437
3710
|
// src/config.ts
|
|
3438
|
-
import { existsSync as
|
|
3711
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3439
3712
|
import { readFileSync } from "fs";
|
|
3440
|
-
import { readFile as
|
|
3441
|
-
import
|
|
3713
|
+
import { readFile as readFile8, rm, writeFile as writeFile5 } from "fs/promises";
|
|
3714
|
+
import path10 from "path";
|
|
3442
3715
|
var CONFIG_FILE = "hivelore.config.json";
|
|
3443
3716
|
var LEGACY_CONFIG_FILE = "haive.config.json";
|
|
3444
3717
|
var DEFAULT_BRIEFING_EXCLUDE_TAGS = [
|
|
@@ -3534,19 +3807,19 @@ function antiPatternGateParams(gate) {
|
|
|
3534
3807
|
}
|
|
3535
3808
|
}
|
|
3536
3809
|
function configPath(paths) {
|
|
3537
|
-
return
|
|
3810
|
+
return path10.join(paths.haiveDir, CONFIG_FILE);
|
|
3538
3811
|
}
|
|
3539
3812
|
function resolveConfigPath(paths) {
|
|
3540
3813
|
const current = configPath(paths);
|
|
3541
|
-
if (
|
|
3542
|
-
const legacy =
|
|
3543
|
-
return
|
|
3814
|
+
if (existsSync8(current)) return current;
|
|
3815
|
+
const legacy = path10.join(paths.haiveDir, LEGACY_CONFIG_FILE);
|
|
3816
|
+
return existsSync8(legacy) ? legacy : current;
|
|
3544
3817
|
}
|
|
3545
3818
|
async function loadConfig(paths) {
|
|
3546
3819
|
const file = resolveConfigPath(paths);
|
|
3547
|
-
if (!
|
|
3820
|
+
if (!existsSync8(file)) return { ...DEFAULT_CONFIG };
|
|
3548
3821
|
try {
|
|
3549
|
-
const raw = await
|
|
3822
|
+
const raw = await readFile8(file, "utf8");
|
|
3550
3823
|
const parsed = JSON.parse(raw);
|
|
3551
3824
|
const merged = mergeConfig(DEFAULT_CONFIG, parsed);
|
|
3552
3825
|
if (merged.autopilot) {
|
|
@@ -3559,7 +3832,7 @@ async function loadConfig(paths) {
|
|
|
3559
3832
|
}
|
|
3560
3833
|
function loadConfigSync(paths) {
|
|
3561
3834
|
const file = resolveConfigPath(paths);
|
|
3562
|
-
if (!
|
|
3835
|
+
if (!existsSync8(file)) return { ...DEFAULT_CONFIG };
|
|
3563
3836
|
try {
|
|
3564
3837
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
3565
3838
|
const merged = mergeConfig(DEFAULT_CONFIG, parsed);
|
|
@@ -3569,9 +3842,9 @@ function loadConfigSync(paths) {
|
|
|
3569
3842
|
}
|
|
3570
3843
|
}
|
|
3571
3844
|
async function saveConfig(paths, config) {
|
|
3572
|
-
await
|
|
3573
|
-
const legacy =
|
|
3574
|
-
if (
|
|
3845
|
+
await writeFile5(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
3846
|
+
const legacy = path10.join(paths.haiveDir, LEGACY_CONFIG_FILE);
|
|
3847
|
+
if (existsSync8(legacy)) {
|
|
3575
3848
|
try {
|
|
3576
3849
|
await rm(legacy, { force: true });
|
|
3577
3850
|
} catch {
|
|
@@ -3594,21 +3867,21 @@ function mergeConfig(base, override) {
|
|
|
3594
3867
|
}
|
|
3595
3868
|
|
|
3596
3869
|
// src/cross-repo.ts
|
|
3597
|
-
import { existsSync as
|
|
3598
|
-
import { mkdir as
|
|
3599
|
-
import
|
|
3870
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3871
|
+
import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
3872
|
+
import path11 from "path";
|
|
3600
3873
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3601
3874
|
async function loadImportMap(cacheDir) {
|
|
3602
|
-
const mapPath =
|
|
3603
|
-
if (!
|
|
3875
|
+
const mapPath = path11.join(cacheDir, "import-map.json");
|
|
3876
|
+
if (!existsSync9(mapPath)) return {};
|
|
3604
3877
|
try {
|
|
3605
|
-
return JSON.parse(await
|
|
3878
|
+
return JSON.parse(await readFile9(mapPath, "utf8"));
|
|
3606
3879
|
} catch {
|
|
3607
3880
|
return {};
|
|
3608
3881
|
}
|
|
3609
3882
|
}
|
|
3610
3883
|
async function saveImportMap(cacheDir, map) {
|
|
3611
|
-
await
|
|
3884
|
+
await writeFile6(path11.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
|
|
3612
3885
|
}
|
|
3613
3886
|
async function pullCrossRepoSources(paths, config, projectRoot) {
|
|
3614
3887
|
const sources = config.crossRepoSources ?? [];
|
|
@@ -3629,8 +3902,8 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
3629
3902
|
};
|
|
3630
3903
|
let sourceRoot = null;
|
|
3631
3904
|
if (source.path) {
|
|
3632
|
-
const resolved =
|
|
3633
|
-
if (!
|
|
3905
|
+
const resolved = path11.resolve(projectRoot, source.path);
|
|
3906
|
+
if (!existsSync9(resolved)) {
|
|
3634
3907
|
report.errors.push(`Path not found: ${resolved}`);
|
|
3635
3908
|
return report;
|
|
3636
3909
|
}
|
|
@@ -3643,7 +3916,7 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
3643
3916
|
return report;
|
|
3644
3917
|
}
|
|
3645
3918
|
const sourcePaths = resolveHaivePaths(sourceRoot);
|
|
3646
|
-
if (!
|
|
3919
|
+
if (!existsSync9(sourcePaths.memoriesDir)) {
|
|
3647
3920
|
report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
|
|
3648
3921
|
return report;
|
|
3649
3922
|
}
|
|
@@ -3666,10 +3939,10 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
3666
3939
|
report.skipped.push("no shared memories found in source");
|
|
3667
3940
|
return report;
|
|
3668
3941
|
}
|
|
3669
|
-
const destDir =
|
|
3670
|
-
await
|
|
3671
|
-
const cacheDir =
|
|
3672
|
-
await
|
|
3942
|
+
const destDir = path11.join(paths.memoriesDir, "shared", source.name);
|
|
3943
|
+
await mkdir6(destDir, { recursive: true });
|
|
3944
|
+
const cacheDir = path11.join(paths.haiveDir, ".cache", "cross-repo", source.name);
|
|
3945
|
+
await mkdir6(cacheDir, { recursive: true });
|
|
3673
3946
|
const importMap = await loadImportMap(cacheDir);
|
|
3674
3947
|
const mapDirty = false;
|
|
3675
3948
|
let dirty = mapDirty;
|
|
@@ -3683,7 +3956,7 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
3683
3956
|
|
|
3684
3957
|
`;
|
|
3685
3958
|
const existingLocalPath = importMap[sourceId];
|
|
3686
|
-
if (existingLocalPath &&
|
|
3959
|
+
if (existingLocalPath && existsSync9(existingLocalPath)) {
|
|
3687
3960
|
const existingFiles = await loadMemoriesFromDir(destDir);
|
|
3688
3961
|
const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
|
|
3689
3962
|
const sourceBodyStripped = memory.body.trim();
|
|
@@ -3694,7 +3967,7 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
3694
3967
|
}
|
|
3695
3968
|
const updatedBody = importedBodyPrefix + memory.body;
|
|
3696
3969
|
if (existingEntry) {
|
|
3697
|
-
await
|
|
3970
|
+
await writeFile6(
|
|
3698
3971
|
existingLocalPath,
|
|
3699
3972
|
serializeMemory({ frontmatter: existingEntry.memory.frontmatter, body: updatedBody }),
|
|
3700
3973
|
"utf8"
|
|
@@ -3718,8 +3991,8 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
3718
3991
|
topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
|
|
3719
3992
|
});
|
|
3720
3993
|
const body = importedBodyPrefix + memory.body;
|
|
3721
|
-
const destPath =
|
|
3722
|
-
await
|
|
3994
|
+
const destPath = path11.join(destDir, `${newFm.id}.md`);
|
|
3995
|
+
await writeFile6(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
|
|
3723
3996
|
importMap[sourceId] = destPath;
|
|
3724
3997
|
dirty = true;
|
|
3725
3998
|
report.imported.push(sourceId);
|
|
@@ -3729,9 +4002,9 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
3729
4002
|
return report;
|
|
3730
4003
|
}
|
|
3731
4004
|
async function cloneOrFetchGitSource(source, paths, report) {
|
|
3732
|
-
const cacheDir =
|
|
3733
|
-
await
|
|
3734
|
-
if (
|
|
4005
|
+
const cacheDir = path11.join(paths.haiveDir, ".cache", "cross-repo", source.name);
|
|
4006
|
+
await mkdir6(cacheDir, { recursive: true });
|
|
4007
|
+
if (existsSync9(path11.join(cacheDir, ".git"))) {
|
|
3735
4008
|
const result = spawnSync2("git", ["fetch", "--depth=1", "origin"], {
|
|
3736
4009
|
cwd: cacheDir,
|
|
3737
4010
|
encoding: "utf8"
|
|
@@ -3756,9 +4029,9 @@ async function cloneOrFetchGitSource(source, paths, report) {
|
|
|
3756
4029
|
}
|
|
3757
4030
|
|
|
3758
4031
|
// src/dep-tracker.ts
|
|
3759
|
-
import { existsSync as
|
|
3760
|
-
import { readFile as
|
|
3761
|
-
import
|
|
4032
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4033
|
+
import { readFile as readFile10, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
|
|
4034
|
+
import path12 from "path";
|
|
3762
4035
|
function parsePackageJson(content) {
|
|
3763
4036
|
try {
|
|
3764
4037
|
const pkg = JSON.parse(content);
|
|
@@ -3830,7 +4103,7 @@ var KNOWN_MANIFESTS = [
|
|
|
3830
4103
|
{ name: "pom.xml", parser: parsePomXml }
|
|
3831
4104
|
];
|
|
3832
4105
|
function getParser(file) {
|
|
3833
|
-
const base =
|
|
4106
|
+
const base = path12.basename(file);
|
|
3834
4107
|
return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
|
|
3835
4108
|
}
|
|
3836
4109
|
function extractMajor(version) {
|
|
@@ -3848,32 +4121,32 @@ function isMajorBump(from, to) {
|
|
|
3848
4121
|
}
|
|
3849
4122
|
function resolveManifestFiles(projectRoot, configuredFiles) {
|
|
3850
4123
|
if (configuredFiles !== void 0) {
|
|
3851
|
-
return configuredFiles.map((f) =>
|
|
4124
|
+
return configuredFiles.map((f) => path12.resolve(projectRoot, f)).filter(existsSync10);
|
|
3852
4125
|
}
|
|
3853
|
-
return KNOWN_MANIFESTS.map(({ name }) =>
|
|
4126
|
+
return KNOWN_MANIFESTS.map(({ name }) => path12.join(projectRoot, name)).filter(existsSync10);
|
|
3854
4127
|
}
|
|
3855
4128
|
async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
|
|
3856
|
-
const contractsDir =
|
|
3857
|
-
await
|
|
4129
|
+
const contractsDir = path12.join(haiveDir, "contracts");
|
|
4130
|
+
await mkdir7(contractsDir, { recursive: true });
|
|
3858
4131
|
const results = [];
|
|
3859
4132
|
for (const manifestPath of manifestFiles) {
|
|
3860
4133
|
const parser2 = getParser(manifestPath);
|
|
3861
4134
|
if (!parser2) continue;
|
|
3862
|
-
const content = await
|
|
4135
|
+
const content = await readFile10(manifestPath, "utf8");
|
|
3863
4136
|
const currentDeps = parser2(content);
|
|
3864
|
-
const lockName = `deps-${
|
|
3865
|
-
const lockPath =
|
|
3866
|
-
if (!
|
|
4137
|
+
const lockName = `deps-${path12.basename(manifestPath)}.lock`;
|
|
4138
|
+
const lockPath = path12.join(contractsDir, lockName);
|
|
4139
|
+
if (!existsSync10(lockPath)) {
|
|
3867
4140
|
const snapshot2 = {
|
|
3868
|
-
file:
|
|
3869
|
-
format:
|
|
4141
|
+
file: path12.relative(projectRoot, manifestPath),
|
|
4142
|
+
format: path12.basename(manifestPath),
|
|
3870
4143
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3871
4144
|
deps: currentDeps
|
|
3872
4145
|
};
|
|
3873
|
-
await
|
|
4146
|
+
await writeFile7(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
|
|
3874
4147
|
continue;
|
|
3875
4148
|
}
|
|
3876
|
-
const snapshot = JSON.parse(await
|
|
4149
|
+
const snapshot = JSON.parse(await readFile10(lockPath, "utf8"));
|
|
3877
4150
|
const changes = [];
|
|
3878
4151
|
for (const [name, currentVer] of Object.entries(currentDeps)) {
|
|
3879
4152
|
const prevVer = snapshot.deps[name];
|
|
@@ -3887,22 +4160,22 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
|
|
|
3887
4160
|
}
|
|
3888
4161
|
}
|
|
3889
4162
|
if (changes.length > 0) {
|
|
3890
|
-
results.push({ file:
|
|
4163
|
+
results.push({ file: path12.relative(projectRoot, manifestPath), changes });
|
|
3891
4164
|
const updated = {
|
|
3892
4165
|
...snapshot,
|
|
3893
4166
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3894
4167
|
deps: currentDeps
|
|
3895
4168
|
};
|
|
3896
|
-
await
|
|
4169
|
+
await writeFile7(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
3897
4170
|
}
|
|
3898
4171
|
}
|
|
3899
4172
|
return results;
|
|
3900
4173
|
}
|
|
3901
4174
|
|
|
3902
4175
|
// src/contract-watcher.ts
|
|
3903
|
-
import { existsSync as
|
|
3904
|
-
import { readFile as
|
|
3905
|
-
import
|
|
4176
|
+
import { existsSync as existsSync11 } from "fs";
|
|
4177
|
+
import { readFile as readFile11, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
|
|
4178
|
+
import path13 from "path";
|
|
3906
4179
|
import crypto from "crypto";
|
|
3907
4180
|
function sha256(content) {
|
|
3908
4181
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
@@ -4105,14 +4378,14 @@ function diffSnapshots(before, after) {
|
|
|
4105
4378
|
return changes;
|
|
4106
4379
|
}
|
|
4107
4380
|
function contractLockPath(haiveDir, name) {
|
|
4108
|
-
return
|
|
4381
|
+
return path13.join(haiveDir, "contracts", `${name}.lock`);
|
|
4109
4382
|
}
|
|
4110
4383
|
async function snapshotContract(projectRoot, haiveDir, contract) {
|
|
4111
|
-
const filePath =
|
|
4112
|
-
if (!
|
|
4384
|
+
const filePath = path13.resolve(projectRoot, contract.path);
|
|
4385
|
+
if (!existsSync11(filePath)) {
|
|
4113
4386
|
throw new Error(`Contract file not found: ${filePath}`);
|
|
4114
4387
|
}
|
|
4115
|
-
const content = await
|
|
4388
|
+
const content = await readFile11(filePath, "utf8");
|
|
4116
4389
|
const parsed = parseByFormat(content, contract.format, filePath);
|
|
4117
4390
|
const snapshot = {
|
|
4118
4391
|
name: contract.name,
|
|
@@ -4122,23 +4395,23 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
|
|
|
4122
4395
|
hash: sha256(content),
|
|
4123
4396
|
...parsed
|
|
4124
4397
|
};
|
|
4125
|
-
const contractsDir =
|
|
4126
|
-
await
|
|
4127
|
-
await
|
|
4398
|
+
const contractsDir = path13.join(haiveDir, "contracts");
|
|
4399
|
+
await mkdir8(contractsDir, { recursive: true });
|
|
4400
|
+
await writeFile8(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
|
|
4128
4401
|
return snapshot;
|
|
4129
4402
|
}
|
|
4130
4403
|
async function diffContract(projectRoot, haiveDir, contract) {
|
|
4131
|
-
const filePath =
|
|
4132
|
-
if (!
|
|
4404
|
+
const filePath = path13.resolve(projectRoot, contract.path);
|
|
4405
|
+
if (!existsSync11(filePath)) {
|
|
4133
4406
|
return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
|
|
4134
4407
|
}
|
|
4135
4408
|
const lockPath = contractLockPath(haiveDir, contract.name);
|
|
4136
|
-
if (!
|
|
4409
|
+
if (!existsSync11(lockPath)) {
|
|
4137
4410
|
await snapshotContract(projectRoot, haiveDir, contract);
|
|
4138
4411
|
return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
|
|
4139
4412
|
}
|
|
4140
|
-
const content = await
|
|
4141
|
-
const beforeSnapshot = JSON.parse(await
|
|
4413
|
+
const content = await readFile11(filePath, "utf8");
|
|
4414
|
+
const beforeSnapshot = JSON.parse(await readFile11(lockPath, "utf8"));
|
|
4142
4415
|
const afterParsed = parseByFormat(content, contract.format, filePath);
|
|
4143
4416
|
const afterSnapshot = {
|
|
4144
4417
|
...beforeSnapshot,
|
|
@@ -4148,7 +4421,7 @@ async function diffContract(projectRoot, haiveDir, contract) {
|
|
|
4148
4421
|
};
|
|
4149
4422
|
const changes = diffSnapshots(beforeSnapshot, afterSnapshot);
|
|
4150
4423
|
if (changes.length > 0) {
|
|
4151
|
-
await
|
|
4424
|
+
await writeFile8(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
|
|
4152
4425
|
}
|
|
4153
4426
|
return {
|
|
4154
4427
|
contract: contract.name,
|
|
@@ -4166,27 +4439,27 @@ async function watchContracts(projectRoot, haiveDir, contractFiles) {
|
|
|
4166
4439
|
}
|
|
4167
4440
|
|
|
4168
4441
|
// src/usage-log.ts
|
|
4169
|
-
import { appendFile as appendFile2, mkdir as
|
|
4170
|
-
import { existsSync as
|
|
4171
|
-
import
|
|
4442
|
+
import { appendFile as appendFile2, mkdir as mkdir9, readFile as readFile12, stat as stat3 } from "fs/promises";
|
|
4443
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4444
|
+
import path14 from "path";
|
|
4172
4445
|
var USAGE_LOG_FILE = "tool-usage.jsonl";
|
|
4173
4446
|
var USAGE_LOG_DIR = ".usage";
|
|
4174
4447
|
function usageLogPath(paths) {
|
|
4175
|
-
return
|
|
4448
|
+
return path14.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
|
|
4176
4449
|
}
|
|
4177
4450
|
async function appendUsageEvent(paths, event) {
|
|
4178
4451
|
try {
|
|
4179
4452
|
const file = usageLogPath(paths);
|
|
4180
|
-
const dir =
|
|
4181
|
-
if (!
|
|
4453
|
+
const dir = path14.dirname(file);
|
|
4454
|
+
if (!existsSync12(dir)) await mkdir9(dir, { recursive: true });
|
|
4182
4455
|
await appendFile2(file, JSON.stringify(event) + "\n", "utf8");
|
|
4183
4456
|
} catch {
|
|
4184
4457
|
}
|
|
4185
4458
|
}
|
|
4186
4459
|
async function readUsageEvents(paths) {
|
|
4187
4460
|
const file = usageLogPath(paths);
|
|
4188
|
-
if (!
|
|
4189
|
-
const raw = await
|
|
4461
|
+
if (!existsSync12(file)) return [];
|
|
4462
|
+
const raw = await readFile12(file, "utf8");
|
|
4190
4463
|
const out = [];
|
|
4191
4464
|
for (const line of raw.split("\n")) {
|
|
4192
4465
|
if (!line) continue;
|
|
@@ -4235,12 +4508,184 @@ function parseSince(input) {
|
|
|
4235
4508
|
}
|
|
4236
4509
|
async function usageLogSize(paths) {
|
|
4237
4510
|
const file = usageLogPath(paths);
|
|
4238
|
-
if (!
|
|
4239
|
-
const st = await
|
|
4240
|
-
const raw = await
|
|
4511
|
+
if (!existsSync12(file)) return { exists: false, size_bytes: 0, lines: 0 };
|
|
4512
|
+
const st = await stat3(file);
|
|
4513
|
+
const raw = await readFile12(file, "utf8");
|
|
4241
4514
|
return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
|
|
4242
4515
|
}
|
|
4243
4516
|
|
|
4517
|
+
// src/friction.ts
|
|
4518
|
+
import { appendFile as appendFile3, mkdir as mkdir10, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
|
|
4519
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4520
|
+
import { createHash as createHash3 } from "crypto";
|
|
4521
|
+
import path15 from "path";
|
|
4522
|
+
var FRICTION_LOG_FILE = "friction.jsonl";
|
|
4523
|
+
var FRICTION_STATE_FILE = "friction-state.json";
|
|
4524
|
+
var FRICTION_FIELD_MAX = 2e3;
|
|
4525
|
+
function frictionLogPath(paths) {
|
|
4526
|
+
return path15.join(paths.runtimeDir, FRICTION_LOG_FILE);
|
|
4527
|
+
}
|
|
4528
|
+
function frictionStatePath(paths) {
|
|
4529
|
+
return path15.join(paths.runtimeDir, FRICTION_STATE_FILE);
|
|
4530
|
+
}
|
|
4531
|
+
function normalizeFrictionSummary(value) {
|
|
4532
|
+
return value.toLowerCase().replace(/[a-z]?:?[\\/](?:[\w.-]+[\\/])+/g, "/").replace(/\d{3,}/g, "N").replace(/\s+/g, " ").trim();
|
|
4533
|
+
}
|
|
4534
|
+
function frictionFingerprint(input) {
|
|
4535
|
+
const basis = [
|
|
4536
|
+
input.kind,
|
|
4537
|
+
input.surface.trim().toLowerCase(),
|
|
4538
|
+
normalizeFrictionSummary(input.summary)
|
|
4539
|
+
].join("|");
|
|
4540
|
+
return createHash3("sha256").update(basis).digest("hex").slice(0, 16);
|
|
4541
|
+
}
|
|
4542
|
+
function truncate(value) {
|
|
4543
|
+
if (value === void 0) return void 0;
|
|
4544
|
+
const trimmed = value.trim();
|
|
4545
|
+
if (!trimmed) return void 0;
|
|
4546
|
+
return trimmed.length > FRICTION_FIELD_MAX ? `${trimmed.slice(0, FRICTION_FIELD_MAX)}
|
|
4547
|
+
\u2026[truncated]` : trimmed;
|
|
4548
|
+
}
|
|
4549
|
+
function normalizeKind(kind, repro) {
|
|
4550
|
+
if (kind === "bug" && !truncate(repro)) {
|
|
4551
|
+
return { kind: "suggestion", downgraded_from: "bug" };
|
|
4552
|
+
}
|
|
4553
|
+
return { kind };
|
|
4554
|
+
}
|
|
4555
|
+
async function appendFrictionReport(paths, input) {
|
|
4556
|
+
const { kind, downgraded_from } = normalizeKind(input.kind, input.repro);
|
|
4557
|
+
const summary = truncate(input.summary) ?? "(no summary)";
|
|
4558
|
+
const surface = truncate(input.surface) ?? "(unknown)";
|
|
4559
|
+
const fingerprint = frictionFingerprint({ kind, surface, summary });
|
|
4560
|
+
const report = {
|
|
4561
|
+
at: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
4562
|
+
kind,
|
|
4563
|
+
surface,
|
|
4564
|
+
summary,
|
|
4565
|
+
...truncate(input.expected) ? { expected: truncate(input.expected) } : {},
|
|
4566
|
+
...truncate(input.observed) ? { observed: truncate(input.observed) } : {},
|
|
4567
|
+
...truncate(input.repro) ? { repro: truncate(input.repro) } : {},
|
|
4568
|
+
...input.version ? { version: input.version } : {},
|
|
4569
|
+
...downgraded_from ? { downgraded_from } : {},
|
|
4570
|
+
fingerprint
|
|
4571
|
+
};
|
|
4572
|
+
const prior = (await readFrictionReports(paths)).filter((r) => r.fingerprint === fingerprint);
|
|
4573
|
+
try {
|
|
4574
|
+
if (!existsSync13(paths.runtimeDir)) await mkdir10(paths.runtimeDir, { recursive: true });
|
|
4575
|
+
await appendFile3(frictionLogPath(paths), JSON.stringify(report) + "\n", "utf8");
|
|
4576
|
+
} catch {
|
|
4577
|
+
}
|
|
4578
|
+
const state = await loadFrictionState(paths);
|
|
4579
|
+
return {
|
|
4580
|
+
report,
|
|
4581
|
+
occurrences: prior.length + 1,
|
|
4582
|
+
already_reported: prior.length > 0,
|
|
4583
|
+
...state[fingerprint] ? { resolved_as: state[fingerprint] } : {}
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
async function readFrictionReports(paths) {
|
|
4587
|
+
const file = frictionLogPath(paths);
|
|
4588
|
+
if (!existsSync13(file)) return [];
|
|
4589
|
+
let raw;
|
|
4590
|
+
try {
|
|
4591
|
+
raw = await readFile13(file, "utf8");
|
|
4592
|
+
} catch {
|
|
4593
|
+
return [];
|
|
4594
|
+
}
|
|
4595
|
+
const out = [];
|
|
4596
|
+
for (const line of raw.split("\n")) {
|
|
4597
|
+
if (!line.trim()) continue;
|
|
4598
|
+
try {
|
|
4599
|
+
const parsed = JSON.parse(line);
|
|
4600
|
+
if (parsed.fingerprint && parsed.at && parsed.summary) out.push(parsed);
|
|
4601
|
+
} catch {
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
return out;
|
|
4605
|
+
}
|
|
4606
|
+
async function loadFrictionState(paths) {
|
|
4607
|
+
const file = frictionStatePath(paths);
|
|
4608
|
+
if (!existsSync13(file)) return {};
|
|
4609
|
+
try {
|
|
4610
|
+
const parsed = JSON.parse(await readFile13(file, "utf8"));
|
|
4611
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4612
|
+
} catch {
|
|
4613
|
+
return {};
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
async function saveFrictionState(paths, state) {
|
|
4617
|
+
if (!existsSync13(paths.runtimeDir)) await mkdir10(paths.runtimeDir, { recursive: true });
|
|
4618
|
+
await writeFile9(frictionStatePath(paths), JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
4619
|
+
}
|
|
4620
|
+
async function setFrictionStatus(paths, fingerprint, status, url) {
|
|
4621
|
+
const state = await loadFrictionState(paths);
|
|
4622
|
+
const entry = {
|
|
4623
|
+
status,
|
|
4624
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4625
|
+
...url ? { url } : {}
|
|
4626
|
+
};
|
|
4627
|
+
state[fingerprint] = entry;
|
|
4628
|
+
await saveFrictionState(paths, state);
|
|
4629
|
+
return entry;
|
|
4630
|
+
}
|
|
4631
|
+
function groupFriction(reports, state = {}) {
|
|
4632
|
+
const byFingerprint = /* @__PURE__ */ new Map();
|
|
4633
|
+
for (const report of reports) {
|
|
4634
|
+
const bucket = byFingerprint.get(report.fingerprint);
|
|
4635
|
+
if (bucket) bucket.push(report);
|
|
4636
|
+
else byFingerprint.set(report.fingerprint, [report]);
|
|
4637
|
+
}
|
|
4638
|
+
const groups = [];
|
|
4639
|
+
for (const [fingerprint, bucket] of byFingerprint) {
|
|
4640
|
+
const sorted = [...bucket].sort((a, b) => a.at.localeCompare(b.at));
|
|
4641
|
+
const latest = sorted[sorted.length - 1];
|
|
4642
|
+
const resolution = state[fingerprint];
|
|
4643
|
+
groups.push({
|
|
4644
|
+
fingerprint,
|
|
4645
|
+
kind: latest.kind,
|
|
4646
|
+
surface: latest.surface,
|
|
4647
|
+
summary: latest.summary,
|
|
4648
|
+
count: sorted.length,
|
|
4649
|
+
first_seen: sorted[0].at,
|
|
4650
|
+
last_seen: latest.at,
|
|
4651
|
+
latest,
|
|
4652
|
+
status: resolution?.status ?? "open",
|
|
4653
|
+
...resolution?.at ? { submitted_at: resolution.at } : {},
|
|
4654
|
+
...resolution?.url ? { url: resolution.url } : {}
|
|
4655
|
+
});
|
|
4656
|
+
}
|
|
4657
|
+
return groups.sort(
|
|
4658
|
+
(a, b) => b.count - a.count || b.last_seen.localeCompare(a.last_seen)
|
|
4659
|
+
);
|
|
4660
|
+
}
|
|
4661
|
+
function formatFrictionIssue(group) {
|
|
4662
|
+
const title = `[${group.kind}] ${group.surface}: ${group.summary}`.slice(0, 120);
|
|
4663
|
+
const r = group.latest;
|
|
4664
|
+
const lines = [
|
|
4665
|
+
`**Surface:** \`${group.surface}\``,
|
|
4666
|
+
`**Reported:** ${group.count}\xD7 (first ${group.first_seen.slice(0, 10)}, last ${group.last_seen.slice(0, 10)})`,
|
|
4667
|
+
...r.version ? [`**Version:** ${r.version}`] : [],
|
|
4668
|
+
"",
|
|
4669
|
+
"### What happened",
|
|
4670
|
+
group.summary
|
|
4671
|
+
];
|
|
4672
|
+
if (r.expected) lines.push("", "### Expected", r.expected);
|
|
4673
|
+
if (r.observed) lines.push("", "### Observed", r.observed);
|
|
4674
|
+
if (r.repro) lines.push("", "### Reproduction", "```sh", r.repro, "```");
|
|
4675
|
+
if (r.downgraded_from === "bug") {
|
|
4676
|
+
lines.push(
|
|
4677
|
+
"",
|
|
4678
|
+
"> Reported as a bug but filed as a suggestion: no reproduction was supplied."
|
|
4679
|
+
);
|
|
4680
|
+
}
|
|
4681
|
+
lines.push(
|
|
4682
|
+
"",
|
|
4683
|
+
"---",
|
|
4684
|
+
"<sub>Captured by an AI agent session via `report_friction` and reviewed by a human before submission.</sub>"
|
|
4685
|
+
);
|
|
4686
|
+
return { title, body: lines.join("\n") };
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4244
4689
|
// src/briefing-preset.ts
|
|
4245
4690
|
var BRIEFING_PRESET_DEFAULTS = {
|
|
4246
4691
|
/** Fast session start — minimal tokens, skip module CONTEXT.md slices */
|
|
@@ -4307,21 +4752,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
|
|
|
4307
4752
|
}
|
|
4308
4753
|
|
|
4309
4754
|
// src/resolve-project.ts
|
|
4310
|
-
import { existsSync as
|
|
4311
|
-
import
|
|
4755
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4756
|
+
import path16 from "path";
|
|
4312
4757
|
var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
|
|
4313
4758
|
function markersAtRoot(root) {
|
|
4314
4759
|
const found = [];
|
|
4315
4760
|
for (const m of ROOT_MARKERS2) {
|
|
4316
|
-
if (
|
|
4761
|
+
if (existsSync14(path16.join(root, m))) found.push(m);
|
|
4317
4762
|
}
|
|
4318
4763
|
return found;
|
|
4319
4764
|
}
|
|
4320
4765
|
function resolveProjectInfo(opts = {}) {
|
|
4321
4766
|
const env = opts.env ?? process.env;
|
|
4322
|
-
const cwd =
|
|
4767
|
+
const cwd = path16.resolve(opts.cwd ?? process.cwd());
|
|
4323
4768
|
const raw = env.HAIVE_PROJECT_ROOT;
|
|
4324
|
-
const explicit = raw !== void 0 && raw !== "" ?
|
|
4769
|
+
const explicit = raw !== void 0 && raw !== "" ? path16.resolve(raw) : null;
|
|
4325
4770
|
const resolvedRoot = explicit ?? findProjectRoot(cwd);
|
|
4326
4771
|
const paths = resolveHaivePaths(resolvedRoot);
|
|
4327
4772
|
return {
|
|
@@ -4329,8 +4774,8 @@ function resolveProjectInfo(opts = {}) {
|
|
|
4329
4774
|
resolved_root: resolvedRoot,
|
|
4330
4775
|
haive_project_root_env: explicit,
|
|
4331
4776
|
explicit_root: explicit != null,
|
|
4332
|
-
haive_dir_exists:
|
|
4333
|
-
memories_dir_exists:
|
|
4777
|
+
haive_dir_exists: existsSync14(paths.haiveDir),
|
|
4778
|
+
memories_dir_exists: existsSync14(paths.memoriesDir),
|
|
4334
4779
|
runtime_dir: paths.runtimeDir,
|
|
4335
4780
|
markers_found: markersAtRoot(resolvedRoot)
|
|
4336
4781
|
};
|
|
@@ -4585,16 +5030,16 @@ function findLexicalConflictPairs(memories, opts) {
|
|
|
4585
5030
|
}
|
|
4586
5031
|
|
|
4587
5032
|
// src/runtime-journal.ts
|
|
4588
|
-
import { mkdir as
|
|
4589
|
-
import { existsSync as
|
|
4590
|
-
import
|
|
5033
|
+
import { mkdir as mkdir11, readFile as readFile14, appendFile as appendFile4 } from "fs/promises";
|
|
5034
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5035
|
+
import path17 from "path";
|
|
4591
5036
|
var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
|
|
4592
5037
|
function runtimeJournalPath(paths) {
|
|
4593
|
-
return
|
|
5038
|
+
return path17.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
|
|
4594
5039
|
}
|
|
4595
5040
|
async function appendRuntimeJournalEntry(paths, entry) {
|
|
4596
5041
|
try {
|
|
4597
|
-
await
|
|
5042
|
+
await mkdir11(paths.runtimeDir, { recursive: true });
|
|
4598
5043
|
const line = {
|
|
4599
5044
|
ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4600
5045
|
kind: entry.kind,
|
|
@@ -4602,7 +5047,7 @@ async function appendRuntimeJournalEntry(paths, entry) {
|
|
|
4602
5047
|
...entry.tool !== void 0 ? { tool: entry.tool } : {},
|
|
4603
5048
|
...entry.meta !== void 0 ? { meta: entry.meta } : {}
|
|
4604
5049
|
};
|
|
4605
|
-
await
|
|
5050
|
+
await appendFile4(
|
|
4606
5051
|
runtimeJournalPath(paths),
|
|
4607
5052
|
JSON.stringify(line) + "\n",
|
|
4608
5053
|
"utf8"
|
|
@@ -4612,9 +5057,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
|
|
|
4612
5057
|
}
|
|
4613
5058
|
async function readRuntimeJournalTail(paths, limit) {
|
|
4614
5059
|
const file = runtimeJournalPath(paths);
|
|
4615
|
-
if (!
|
|
5060
|
+
if (!existsSync15(file) || limit <= 0) return [];
|
|
4616
5061
|
try {
|
|
4617
|
-
const raw = await
|
|
5062
|
+
const raw = await readFile14(file, "utf8");
|
|
4618
5063
|
const lines = raw.trim().split("\n").filter(Boolean);
|
|
4619
5064
|
const parsed = [];
|
|
4620
5065
|
for (const line of lines.slice(-limit)) {
|
|
@@ -4630,22 +5075,22 @@ async function readRuntimeJournalTail(paths, limit) {
|
|
|
4630
5075
|
}
|
|
4631
5076
|
|
|
4632
5077
|
// src/enforcement.ts
|
|
4633
|
-
import { mkdir as
|
|
4634
|
-
import { existsSync as
|
|
4635
|
-
import
|
|
5078
|
+
import { mkdir as mkdir12, readdir as readdir4, readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
|
|
5079
|
+
import { existsSync as existsSync16 } from "fs";
|
|
5080
|
+
import path18 from "path";
|
|
4636
5081
|
var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
4637
5082
|
var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4638
5083
|
function enforcementDir(paths) {
|
|
4639
|
-
return
|
|
5084
|
+
return path18.join(paths.runtimeDir, "enforcement");
|
|
4640
5085
|
}
|
|
4641
5086
|
function briefingMarkersDir(paths) {
|
|
4642
|
-
return
|
|
5087
|
+
return path18.join(enforcementDir(paths), "briefings");
|
|
4643
5088
|
}
|
|
4644
5089
|
function normalizeSessionId(sessionId) {
|
|
4645
5090
|
return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
|
|
4646
5091
|
}
|
|
4647
5092
|
function briefingMarkerPath(paths, sessionId) {
|
|
4648
|
-
return
|
|
5093
|
+
return path18.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
|
|
4649
5094
|
}
|
|
4650
5095
|
async function writeBriefingMarker(paths, input) {
|
|
4651
5096
|
const sessionId = normalizeSessionId(input.sessionId);
|
|
@@ -4670,8 +5115,8 @@ async function writeBriefingMarker(paths, input) {
|
|
|
4670
5115
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4671
5116
|
root: paths.root
|
|
4672
5117
|
};
|
|
4673
|
-
await
|
|
4674
|
-
await
|
|
5118
|
+
await mkdir12(briefingMarkersDir(paths), { recursive: true });
|
|
5119
|
+
await writeFile10(
|
|
4675
5120
|
briefingMarkerPath(paths, marker.session_id),
|
|
4676
5121
|
JSON.stringify(marker, null, 2) + "\n",
|
|
4677
5122
|
"utf8"
|
|
@@ -4680,9 +5125,9 @@ async function writeBriefingMarker(paths, input) {
|
|
|
4680
5125
|
}
|
|
4681
5126
|
async function readSessionBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER_TTL_MS) {
|
|
4682
5127
|
const file = briefingMarkerPath(paths, sessionId);
|
|
4683
|
-
if (!
|
|
5128
|
+
if (!existsSync16(file)) return null;
|
|
4684
5129
|
try {
|
|
4685
|
-
const marker = JSON.parse(await
|
|
5130
|
+
const marker = JSON.parse(await readFile15(file, "utf8"));
|
|
4686
5131
|
const created = Date.parse(marker.created_at);
|
|
4687
5132
|
if (!Number.isFinite(created) || Date.now() - created > ttlMs) return null;
|
|
4688
5133
|
return marker;
|
|
@@ -4694,18 +5139,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
|
|
|
4694
5139
|
const now = Date.now();
|
|
4695
5140
|
const candidates = [];
|
|
4696
5141
|
const exact = briefingMarkerPath(paths, sessionId);
|
|
4697
|
-
if (
|
|
5142
|
+
if (existsSync16(exact)) candidates.push(exact);
|
|
4698
5143
|
try {
|
|
4699
5144
|
const dir = briefingMarkersDir(paths);
|
|
4700
5145
|
const files = await readdir4(dir);
|
|
4701
5146
|
for (const file of files) {
|
|
4702
|
-
if (file.endsWith(".json")) candidates.push(
|
|
5147
|
+
if (file.endsWith(".json")) candidates.push(path18.join(dir, file));
|
|
4703
5148
|
}
|
|
4704
5149
|
} catch {
|
|
4705
5150
|
}
|
|
4706
5151
|
for (const file of new Set(candidates)) {
|
|
4707
5152
|
try {
|
|
4708
|
-
const marker = JSON.parse(await
|
|
5153
|
+
const marker = JSON.parse(await readFile15(file, "utf8"));
|
|
4709
5154
|
const created = Date.parse(marker.created_at);
|
|
4710
5155
|
if (Number.isFinite(created) && now - created <= ttlMs) return true;
|
|
4711
5156
|
} catch {
|
|
@@ -4717,12 +5162,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
|
|
|
4717
5162
|
const now = Date.now();
|
|
4718
5163
|
const candidates = [];
|
|
4719
5164
|
const exact = briefingMarkerPath(paths, sessionId);
|
|
4720
|
-
if (
|
|
5165
|
+
if (existsSync16(exact)) candidates.push(exact);
|
|
4721
5166
|
try {
|
|
4722
5167
|
const dir = briefingMarkersDir(paths);
|
|
4723
5168
|
const files = await readdir4(dir);
|
|
4724
5169
|
for (const file of files) {
|
|
4725
|
-
if (file.endsWith(".json")) candidates.push(
|
|
5170
|
+
if (file.endsWith(".json")) candidates.push(path18.join(dir, file));
|
|
4726
5171
|
}
|
|
4727
5172
|
} catch {
|
|
4728
5173
|
}
|
|
@@ -4730,7 +5175,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
|
|
|
4730
5175
|
let freshestTs = 0;
|
|
4731
5176
|
for (const file of new Set(candidates)) {
|
|
4732
5177
|
try {
|
|
4733
|
-
const marker = JSON.parse(await
|
|
5178
|
+
const marker = JSON.parse(await readFile15(file, "utf8"));
|
|
4734
5179
|
const created = Date.parse(marker.created_at);
|
|
4735
5180
|
if (!Number.isFinite(created) || now - created > ttlMs) continue;
|
|
4736
5181
|
if (created > freshestTs) {
|
|
@@ -4780,15 +5225,15 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
|
|
|
4780
5225
|
}
|
|
4781
5226
|
|
|
4782
5227
|
// src/sensor-ledger.ts
|
|
4783
|
-
import { createHash as
|
|
4784
|
-
import { existsSync as
|
|
4785
|
-
import { appendFile as
|
|
4786
|
-
import
|
|
5228
|
+
import { createHash as createHash4 } from "crypto";
|
|
5229
|
+
import { existsSync as existsSync17, readFileSync as readFileSync2 } from "fs";
|
|
5230
|
+
import { appendFile as appendFile5, mkdir as mkdir13, readFile as readFile16, rename, writeFile as writeFile11 } from "fs/promises";
|
|
5231
|
+
import path19 from "path";
|
|
4787
5232
|
var MAX_LINES = 1e4;
|
|
4788
5233
|
var RETAINED_LINES = 8e3;
|
|
4789
5234
|
var DAY_MS = 864e5;
|
|
4790
5235
|
function sensorLedgerPath(paths) {
|
|
4791
|
-
return
|
|
5236
|
+
return path19.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
|
|
4792
5237
|
}
|
|
4793
5238
|
function isEvaluation(value) {
|
|
4794
5239
|
if (!value || typeof value !== "object") return false;
|
|
@@ -4799,13 +5244,13 @@ async function appendSensorEvaluations(paths, evaluations) {
|
|
|
4799
5244
|
if (evaluations.length === 0) return;
|
|
4800
5245
|
try {
|
|
4801
5246
|
const file = sensorLedgerPath(paths);
|
|
4802
|
-
await
|
|
4803
|
-
await
|
|
4804
|
-
const raw = await
|
|
5247
|
+
await mkdir13(path19.dirname(file), { recursive: true });
|
|
5248
|
+
await appendFile5(file, evaluations.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
5249
|
+
const raw = await readFile16(file, "utf8");
|
|
4805
5250
|
const lines = raw.split("\n").filter(Boolean);
|
|
4806
5251
|
if (lines.length > MAX_LINES) {
|
|
4807
5252
|
const temp = `${file}.${process.pid}.tmp`;
|
|
4808
|
-
await
|
|
5253
|
+
await writeFile11(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
|
|
4809
5254
|
await rename(temp, file);
|
|
4810
5255
|
}
|
|
4811
5256
|
} catch {
|
|
@@ -4814,9 +5259,9 @@ async function appendSensorEvaluations(paths, evaluations) {
|
|
|
4814
5259
|
async function loadSensorLedger(paths, opts = {}) {
|
|
4815
5260
|
try {
|
|
4816
5261
|
const file = sensorLedgerPath(paths);
|
|
4817
|
-
if (!
|
|
5262
|
+
if (!existsSync17(file)) return [];
|
|
4818
5263
|
const since = opts.since ? Date.parse(opts.since) : Number.NEGATIVE_INFINITY;
|
|
4819
|
-
const raw = await
|
|
5264
|
+
const raw = await readFile16(file, "utf8");
|
|
4820
5265
|
const out = [];
|
|
4821
5266
|
for (const line of raw.split("\n")) {
|
|
4822
5267
|
if (!line.trim()) continue;
|
|
@@ -4838,11 +5283,11 @@ function computeScopeHash(root, scopedFiles) {
|
|
|
4838
5283
|
try {
|
|
4839
5284
|
const files = [...new Set(scopedFiles.map((f) => f.replace(/\\/g, "/")))].sort();
|
|
4840
5285
|
if (files.length === 0) return "";
|
|
4841
|
-
const hash =
|
|
5286
|
+
const hash = createHash4("sha256");
|
|
4842
5287
|
let included = 0;
|
|
4843
5288
|
for (const rel of files) {
|
|
4844
|
-
const abs =
|
|
4845
|
-
if (!
|
|
5289
|
+
const abs = path19.resolve(root, rel);
|
|
5290
|
+
if (!existsSync17(abs)) continue;
|
|
4846
5291
|
try {
|
|
4847
5292
|
hash.update(rel);
|
|
4848
5293
|
hash.update("\0");
|
|
@@ -5576,8 +6021,8 @@ function normalizeFindingSeverity(raw) {
|
|
|
5576
6021
|
return "info";
|
|
5577
6022
|
}
|
|
5578
6023
|
}
|
|
5579
|
-
function findingKey(tool, ruleId,
|
|
5580
|
-
return `${tool}:${ruleId}:${
|
|
6024
|
+
function findingKey(tool, ruleId, path22) {
|
|
6025
|
+
return `${tool}:${ruleId}:${path22}`;
|
|
5581
6026
|
}
|
|
5582
6027
|
function coerceJson(input) {
|
|
5583
6028
|
if (typeof input === "string") {
|
|
@@ -5611,8 +6056,8 @@ function parseSarif(input) {
|
|
|
5611
6056
|
const physical = asRecord(location.physicalLocation);
|
|
5612
6057
|
const artifact = asRecord(physical.artifactLocation);
|
|
5613
6058
|
const region = asRecord(physical.region);
|
|
5614
|
-
const
|
|
5615
|
-
if (!
|
|
6059
|
+
const path22 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
|
|
6060
|
+
if (!path22) continue;
|
|
5616
6061
|
const line = typeof region.startLine === "number" ? region.startLine : void 0;
|
|
5617
6062
|
const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
|
|
5618
6063
|
findings.push({
|
|
@@ -5620,10 +6065,10 @@ function parseSarif(input) {
|
|
|
5620
6065
|
ruleId,
|
|
5621
6066
|
message: message.trim(),
|
|
5622
6067
|
severity,
|
|
5623
|
-
path:
|
|
6068
|
+
path: path22,
|
|
5624
6069
|
...line !== void 0 ? { line } : {},
|
|
5625
6070
|
...snippet ? { snippet } : {},
|
|
5626
|
-
key: findingKey(tool, ruleId,
|
|
6071
|
+
key: findingKey(tool, ruleId, path22)
|
|
5627
6072
|
});
|
|
5628
6073
|
}
|
|
5629
6074
|
}
|
|
@@ -5642,17 +6087,17 @@ function parseSonar(input) {
|
|
|
5642
6087
|
(typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
|
|
5643
6088
|
);
|
|
5644
6089
|
const component = typeof issue.component === "string" ? issue.component : "";
|
|
5645
|
-
const
|
|
5646
|
-
if (!
|
|
6090
|
+
const path22 = componentToPath(component);
|
|
6091
|
+
if (!path22) continue;
|
|
5647
6092
|
const line = typeof issue.line === "number" ? issue.line : void 0;
|
|
5648
6093
|
findings.push({
|
|
5649
6094
|
tool: "sonar",
|
|
5650
6095
|
ruleId,
|
|
5651
6096
|
message,
|
|
5652
6097
|
severity,
|
|
5653
|
-
path:
|
|
6098
|
+
path: path22,
|
|
5654
6099
|
...line !== void 0 ? { line } : {},
|
|
5655
|
-
key: findingKey("sonar", ruleId,
|
|
6100
|
+
key: findingKey("sonar", ruleId, path22)
|
|
5656
6101
|
});
|
|
5657
6102
|
}
|
|
5658
6103
|
return findings;
|
|
@@ -5665,7 +6110,7 @@ function parseEslintJson(input, opts = {}) {
|
|
|
5665
6110
|
const file = asRecord(fileRaw);
|
|
5666
6111
|
const rawPath = typeof file.filePath === "string" ? file.filePath : "";
|
|
5667
6112
|
if (!rawPath) continue;
|
|
5668
|
-
const
|
|
6113
|
+
const path22 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
|
|
5669
6114
|
for (const msgRaw of asArray(file.messages)) {
|
|
5670
6115
|
const msg = asRecord(msgRaw);
|
|
5671
6116
|
const ruleId = typeof msg.ruleId === "string" && msg.ruleId ? msg.ruleId : "parse-error";
|
|
@@ -5677,9 +6122,9 @@ function parseEslintJson(input, opts = {}) {
|
|
|
5677
6122
|
ruleId,
|
|
5678
6123
|
message,
|
|
5679
6124
|
severity,
|
|
5680
|
-
path:
|
|
6125
|
+
path: path22,
|
|
5681
6126
|
...line !== void 0 ? { line } : {},
|
|
5682
|
-
key: findingKey("eslint", ruleId,
|
|
6127
|
+
key: findingKey("eslint", ruleId, path22)
|
|
5683
6128
|
});
|
|
5684
6129
|
}
|
|
5685
6130
|
}
|
|
@@ -6129,7 +6574,7 @@ function tallyHotFiles(paths, source = "agent") {
|
|
|
6129
6574
|
if (!norm) continue;
|
|
6130
6575
|
counts.set(norm, (counts.get(norm) ?? 0) + 1);
|
|
6131
6576
|
}
|
|
6132
|
-
return [...counts.entries()].map(([
|
|
6577
|
+
return [...counts.entries()].map(([path22, changes]) => ({ path: path22, changes, source })).sort((a, b) => b.changes - a.changes);
|
|
6133
6578
|
}
|
|
6134
6579
|
function mergeHotFiles(a, b) {
|
|
6135
6580
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -6149,21 +6594,21 @@ function mergeHotFiles(a, b) {
|
|
|
6149
6594
|
}
|
|
6150
6595
|
|
|
6151
6596
|
// src/eval-history.ts
|
|
6152
|
-
import { appendFile as
|
|
6153
|
-
import { existsSync as
|
|
6154
|
-
import
|
|
6597
|
+
import { appendFile as appendFile6, mkdir as mkdir14, readFile as readFile17 } from "fs/promises";
|
|
6598
|
+
import { existsSync as existsSync18 } from "fs";
|
|
6599
|
+
import path20 from "path";
|
|
6155
6600
|
function evalHistoryPath(paths) {
|
|
6156
|
-
return
|
|
6601
|
+
return path20.join(paths.haiveDir, ".cache", "eval-history.jsonl");
|
|
6157
6602
|
}
|
|
6158
6603
|
async function appendEvalHistory(paths, entry) {
|
|
6159
6604
|
const file = evalHistoryPath(paths);
|
|
6160
|
-
await
|
|
6161
|
-
await
|
|
6605
|
+
await mkdir14(path20.dirname(file), { recursive: true });
|
|
6606
|
+
await appendFile6(file, JSON.stringify(entry) + "\n", "utf8");
|
|
6162
6607
|
}
|
|
6163
6608
|
async function loadEvalHistory(paths) {
|
|
6164
6609
|
const file = evalHistoryPath(paths);
|
|
6165
|
-
if (!
|
|
6166
|
-
const raw = await
|
|
6610
|
+
if (!existsSync18(file)) return [];
|
|
6611
|
+
const raw = await readFile17(file, "utf8").catch(() => "");
|
|
6167
6612
|
const out = [];
|
|
6168
6613
|
for (const line of raw.split("\n")) {
|
|
6169
6614
|
const trimmed = line.trim();
|
|
@@ -6506,12 +6951,12 @@ ${trimmed}`;
|
|
|
6506
6951
|
}
|
|
6507
6952
|
|
|
6508
6953
|
// src/handoff.ts
|
|
6509
|
-
import { writeFile as
|
|
6510
|
-
import { existsSync as
|
|
6511
|
-
import
|
|
6954
|
+
import { writeFile as writeFile12, readFile as readFile18, stat as stat4 } from "fs/promises";
|
|
6955
|
+
import { existsSync as existsSync19 } from "fs";
|
|
6956
|
+
import path21 from "path";
|
|
6512
6957
|
var HANDOFF_FILENAME = "NEXT.md";
|
|
6513
6958
|
function handoffFilePath(root) {
|
|
6514
|
-
return
|
|
6959
|
+
return path21.join(root, HANDOFF_FILENAME);
|
|
6515
6960
|
}
|
|
6516
6961
|
function buildHandoffMarkdown(data) {
|
|
6517
6962
|
const at = (data.at ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -6560,20 +7005,20 @@ function buildHandoffMarkdown(data) {
|
|
|
6560
7005
|
}
|
|
6561
7006
|
async function writeSessionHandoff(root, data) {
|
|
6562
7007
|
const file = handoffFilePath(root);
|
|
6563
|
-
await
|
|
7008
|
+
await writeFile12(file, buildHandoffMarkdown(data), "utf8");
|
|
6564
7009
|
return file;
|
|
6565
7010
|
}
|
|
6566
7011
|
async function readSessionHandoff(root) {
|
|
6567
7012
|
const file = handoffFilePath(root);
|
|
6568
|
-
if (!
|
|
6569
|
-
const raw = await
|
|
7013
|
+
if (!existsSync19(file)) return null;
|
|
7014
|
+
const raw = await readFile18(file, "utf8").catch(() => "");
|
|
6570
7015
|
return raw.trim() ? raw : null;
|
|
6571
7016
|
}
|
|
6572
7017
|
async function handoffAgeMs(root, now = /* @__PURE__ */ new Date()) {
|
|
6573
7018
|
const file = handoffFilePath(root);
|
|
6574
|
-
if (!
|
|
7019
|
+
if (!existsSync19(file)) return null;
|
|
6575
7020
|
try {
|
|
6576
|
-
const s = await
|
|
7021
|
+
const s = await stat4(file);
|
|
6577
7022
|
return Math.max(0, now.getTime() - s.mtimeMs);
|
|
6578
7023
|
} catch {
|
|
6579
7024
|
return null;
|
|
@@ -6698,6 +7143,7 @@ export {
|
|
|
6698
7143
|
CODE_MAP_FILE,
|
|
6699
7144
|
CODE_STOPWORDS,
|
|
6700
7145
|
CONFIG_FILE,
|
|
7146
|
+
CONTENT_CATCH_CODES,
|
|
6701
7147
|
CrossRepoProvenanceSchema,
|
|
6702
7148
|
DECAY_DAYS,
|
|
6703
7149
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
@@ -6705,8 +7151,13 @@ export {
|
|
|
6705
7151
|
DEFAULT_CONFIDENCE_THRESHOLDS,
|
|
6706
7152
|
DEFAULT_CONFIG,
|
|
6707
7153
|
DEFAULT_DORMANT_DAYS,
|
|
7154
|
+
DEFAULT_POSTURE,
|
|
6708
7155
|
DEFAULT_PRIORITY_SIGNALS,
|
|
6709
7156
|
ENV_WORKAROUND_TAGS,
|
|
7157
|
+
FRICTION_FIELD_MAX,
|
|
7158
|
+
FRICTION_LOG_FILE,
|
|
7159
|
+
FRICTION_STATE_FILE,
|
|
7160
|
+
GATE_REMINDER_WINDOW_MS,
|
|
6710
7161
|
GUESSABLE_THRESHOLD,
|
|
6711
7162
|
HAIVE_DIR,
|
|
6712
7163
|
HAIVE_OWNED_FILES,
|
|
@@ -6720,6 +7171,8 @@ export {
|
|
|
6720
7171
|
MemoryStatusSchema,
|
|
6721
7172
|
MemoryTypeSchema,
|
|
6722
7173
|
PREVENTION_DEBOUNCE_MS,
|
|
7174
|
+
PREVENTION_RECEIPT_MARKER,
|
|
7175
|
+
PROCESS_GATE_CODES,
|
|
6723
7176
|
PROJECT_CONTEXT_FILE,
|
|
6724
7177
|
PROJECT_CONTEXT_THROTTLE_MS,
|
|
6725
7178
|
REVIEW_LEARNING_MARKER,
|
|
@@ -6729,6 +7182,7 @@ export {
|
|
|
6729
7182
|
SENSOR_ABSENT_LOOKBACK,
|
|
6730
7183
|
SENSOR_ABSENT_WINDOW,
|
|
6731
7184
|
SESSION_RECAP_TTL_MS,
|
|
7185
|
+
SETUP_GATE_CODES,
|
|
6732
7186
|
STACK_PACK_TAG,
|
|
6733
7187
|
SensorSchema,
|
|
6734
7188
|
TEST_FRAMEWORKS,
|
|
@@ -6744,6 +7198,7 @@ export {
|
|
|
6744
7198
|
anchorMatchesComponent,
|
|
6745
7199
|
antiPatternGateParams,
|
|
6746
7200
|
appendEvalHistory,
|
|
7201
|
+
appendFrictionReport,
|
|
6747
7202
|
appendPreventionEvent,
|
|
6748
7203
|
appendProposedRetrievalCases,
|
|
6749
7204
|
appendRuntimeJournalEntry,
|
|
@@ -6760,6 +7215,7 @@ export {
|
|
|
6760
7215
|
briefingMarkerPath,
|
|
6761
7216
|
briefingMarkersDir,
|
|
6762
7217
|
briefingProofLine,
|
|
7218
|
+
buildBaselineHealthFinding,
|
|
6763
7219
|
buildCodeMap,
|
|
6764
7220
|
buildCoverageIndex,
|
|
6765
7221
|
buildDashboard,
|
|
@@ -6771,6 +7227,7 @@ export {
|
|
|
6771
7227
|
buildReport,
|
|
6772
7228
|
bumpRead,
|
|
6773
7229
|
classifyMemoryPriority,
|
|
7230
|
+
codeMapContentHash,
|
|
6774
7231
|
codeMapPath,
|
|
6775
7232
|
collectTimelineEntries,
|
|
6776
7233
|
compactAutoRecapBody,
|
|
@@ -6779,6 +7236,7 @@ export {
|
|
|
6779
7236
|
compareImpact,
|
|
6780
7237
|
compileRegexSensor,
|
|
6781
7238
|
componentOf,
|
|
7239
|
+
computeBaselineHealth,
|
|
6782
7240
|
computeEvalTrend,
|
|
6783
7241
|
computeGatePrecision,
|
|
6784
7242
|
computeImpact,
|
|
@@ -6788,8 +7246,11 @@ export {
|
|
|
6788
7246
|
configPath,
|
|
6789
7247
|
contractLockPath,
|
|
6790
7248
|
countSourceFilesOnDisk,
|
|
7249
|
+
decideVerdict,
|
|
7250
|
+
dedupeRefusals,
|
|
6791
7251
|
deriveConfidence,
|
|
6792
7252
|
deriveMainAreas,
|
|
7253
|
+
describePosture,
|
|
6793
7254
|
detectAgentContext,
|
|
6794
7255
|
detectSensorWeakening,
|
|
6795
7256
|
detectStacksFromManifests,
|
|
@@ -6805,6 +7266,7 @@ export {
|
|
|
6805
7266
|
evalHistoryPath,
|
|
6806
7267
|
evaluateSkillActivation,
|
|
6807
7268
|
existingGateMissShas,
|
|
7269
|
+
explainSensorRejection,
|
|
6808
7270
|
extractActionsBriefBody,
|
|
6809
7271
|
extractCorrectApproachExamples,
|
|
6810
7272
|
extractReferencedPaths,
|
|
@@ -6821,10 +7283,15 @@ export {
|
|
|
6821
7283
|
findingBody,
|
|
6822
7284
|
findingToDraft,
|
|
6823
7285
|
firstMemoryOneLine,
|
|
7286
|
+
formatFrictionIssue,
|
|
7287
|
+
frictionFingerprint,
|
|
7288
|
+
frictionLogPath,
|
|
7289
|
+
frictionStatePath,
|
|
6824
7290
|
gatePassedShas,
|
|
6825
7291
|
generateBridges,
|
|
6826
7292
|
getUsage,
|
|
6827
7293
|
globToRegExp,
|
|
7294
|
+
groupFriction,
|
|
6828
7295
|
handoffAgeMs,
|
|
6829
7296
|
handoffFilePath,
|
|
6830
7297
|
hasPendingTestMarker,
|
|
@@ -6861,6 +7328,7 @@ export {
|
|
|
6861
7328
|
loadConfig,
|
|
6862
7329
|
loadConfigSync,
|
|
6863
7330
|
loadEvalHistory,
|
|
7331
|
+
loadFrictionState,
|
|
6864
7332
|
loadMemoriesFromDir,
|
|
6865
7333
|
loadMemoriesFromDirDetailed,
|
|
6866
7334
|
loadMemory,
|
|
@@ -6879,6 +7347,8 @@ export {
|
|
|
6879
7347
|
newMemoryId,
|
|
6880
7348
|
normalizeFindingSeverity,
|
|
6881
7349
|
normalizeFramework,
|
|
7350
|
+
normalizeFrictionSummary,
|
|
7351
|
+
normalizeKind,
|
|
6882
7352
|
normalizeScaffoldStyle,
|
|
6883
7353
|
normalizeSessionId,
|
|
6884
7354
|
overallScore,
|
|
@@ -6907,12 +7377,14 @@ export {
|
|
|
6907
7377
|
quarantineNote,
|
|
6908
7378
|
queryCodeMap,
|
|
6909
7379
|
rankMemoriesLexical,
|
|
7380
|
+
readFrictionReports,
|
|
6910
7381
|
readRecentBriefingMarker,
|
|
6911
7382
|
readRuntimeJournalTail,
|
|
6912
7383
|
readSessionHandoff,
|
|
6913
7384
|
readUsageEvents,
|
|
6914
7385
|
recommendFeedbackAdjustment,
|
|
6915
7386
|
recordApplied,
|
|
7387
|
+
recordGateReminder,
|
|
6916
7388
|
recordPrevention,
|
|
6917
7389
|
recordPreventionHits,
|
|
6918
7390
|
recordProjectContextEmission,
|
|
@@ -6921,10 +7393,12 @@ export {
|
|
|
6921
7393
|
renderBehaviourCoverageLine,
|
|
6922
7394
|
renderBootstrapChecklist,
|
|
6923
7395
|
renderCaughtForYou,
|
|
7396
|
+
renderPreventionComment,
|
|
6924
7397
|
renderPreventionReceipt,
|
|
6925
7398
|
renderPreventionReceiptShare,
|
|
6926
7399
|
resolveBriefingBudget,
|
|
6927
7400
|
resolveConfigPath,
|
|
7401
|
+
resolveGatePolicy,
|
|
6928
7402
|
resolveHaivePaths,
|
|
6929
7403
|
resolveManifestFiles,
|
|
6930
7404
|
resolveProjectInfo,
|
|
@@ -6938,6 +7412,7 @@ export {
|
|
|
6938
7412
|
runtimeJournalPath,
|
|
6939
7413
|
saveCodeMap,
|
|
6940
7414
|
saveConfig,
|
|
7415
|
+
saveFrictionState,
|
|
6941
7416
|
saveUsageIndex,
|
|
6942
7417
|
scaffoldPostIncidentTest,
|
|
6943
7418
|
scannableSensorTargets,
|
|
@@ -6951,7 +7426,10 @@ export {
|
|
|
6951
7426
|
sensorPromotedAtMap,
|
|
6952
7427
|
sensorSelfCheck,
|
|
6953
7428
|
sensorTargetsFromDiff,
|
|
7429
|
+
serializeCodeMap,
|
|
6954
7430
|
serializeMemory,
|
|
7431
|
+
setFrictionStatus,
|
|
7432
|
+
shouldExpandGateReminder,
|
|
6955
7433
|
snapshotContract,
|
|
6956
7434
|
specificityScore,
|
|
6957
7435
|
stripPrivate,
|