@hivelore/mcp 0.30.1 → 0.33.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 +398 -1136
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +100 -196
- package/dist/server.js +401 -1143
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/server.js
CHANGED
|
@@ -1120,94 +1120,45 @@ async function memApprove(input, ctx) {
|
|
|
1120
1120
|
}
|
|
1121
1121
|
|
|
1122
1122
|
// src/tools/mem-tried.ts
|
|
1123
|
-
import { mkdir as mkdir3, writeFile as
|
|
1124
|
-
import { existsSync as
|
|
1125
|
-
import
|
|
1123
|
+
import { mkdir as mkdir3, writeFile as writeFile9 } from "fs/promises";
|
|
1124
|
+
import { existsSync as existsSync16 } from "fs";
|
|
1125
|
+
import path6 from "path";
|
|
1126
1126
|
import {
|
|
1127
1127
|
buildFrontmatter as buildFrontmatter2,
|
|
1128
1128
|
memoryFilePath as memoryFilePath2,
|
|
1129
|
-
serializeMemory as
|
|
1129
|
+
serializeMemory as serializeMemory8,
|
|
1130
1130
|
suggestSensorSeed as suggestSensorSeed2
|
|
1131
1131
|
} from "@hivelore/core";
|
|
1132
|
-
import { z as
|
|
1133
|
-
var MemTriedInputSchema = {
|
|
1134
|
-
what: z15.string().min(1).describe("Brief description of the approach that was tried"),
|
|
1135
|
-
why_failed: z15.string().min(1).describe("Why it failed or why it should NOT be used"),
|
|
1136
|
-
instead: z15.string().optional().describe("What to use or do instead (recommended alternative)"),
|
|
1137
|
-
scope: z15.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
|
|
1138
|
-
module: z15.string().optional().describe("Module name (required when scope=module)"),
|
|
1139
|
-
tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
|
|
1140
|
-
paths: z15.array(z15.string()).default([]).describe("Anchor file paths this applies to"),
|
|
1141
|
-
author: z15.string().optional().describe("Author handle or email")
|
|
1142
|
-
};
|
|
1143
|
-
async function memTried(input, ctx) {
|
|
1144
|
-
if (!existsSync15(ctx.paths.haiveDir)) {
|
|
1145
|
-
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1146
|
-
}
|
|
1147
|
-
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
1148
|
-
const baseFm = buildFrontmatter2({
|
|
1149
|
-
type: "attempt",
|
|
1150
|
-
slug,
|
|
1151
|
-
scope: input.scope,
|
|
1152
|
-
module: input.module,
|
|
1153
|
-
tags: input.tags,
|
|
1154
|
-
paths: input.paths,
|
|
1155
|
-
author: input.author
|
|
1156
|
-
});
|
|
1157
|
-
const frontmatter = { ...baseFm, status: "validated" };
|
|
1158
|
-
const lines = [`# ${input.what}`, ""];
|
|
1159
|
-
lines.push(`**Why it failed / do NOT use:** ${input.why_failed}`);
|
|
1160
|
-
if (input.instead) {
|
|
1161
|
-
lines.push("", `**Instead, use:** ${input.instead}`);
|
|
1162
|
-
}
|
|
1163
|
-
const body = lines.join("\n") + "\n";
|
|
1164
|
-
const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
1165
|
-
await mkdir3(path5.dirname(file), { recursive: true });
|
|
1166
|
-
if (existsSync15(file)) {
|
|
1167
|
-
throw new Error(`Memory already exists at ${file}`);
|
|
1168
|
-
}
|
|
1169
|
-
await writeFile8(file, serializeMemory7({ frontmatter, body }), "utf8");
|
|
1170
|
-
const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
|
|
1171
|
-
const hint = input.paths.length === 0 ? "No `paths` given, so this attempt is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives, then call propose_sensor to close the loop." : seed ? "This attempt is NOT yet enforced. Call propose_sensor to turn it into a reliable block \u2014 a candidate is pre-filled in proposed_sensor_seed (refine it: pattern = the faulty usage, absent = the correct-usage marker). Hivelore validates the proposal (silent on current code, fires on the bad example) before trusting it to block." : "This attempt is NOT yet enforced and no candidate pattern could be derived from the wording. Call propose_sensor with a discriminating pattern (pattern = faulty usage, absent = correct-usage marker) to close the loop.";
|
|
1172
|
-
return {
|
|
1173
|
-
id: frontmatter.id,
|
|
1174
|
-
scope: frontmatter.scope,
|
|
1175
|
-
file_path: file,
|
|
1176
|
-
loop_open: true,
|
|
1177
|
-
...seed ? {
|
|
1178
|
-
proposed_sensor_seed: {
|
|
1179
|
-
pattern: seed.pattern,
|
|
1180
|
-
...seed.absent ? { absent: seed.absent } : {},
|
|
1181
|
-
message: seed.message
|
|
1182
|
-
}
|
|
1183
|
-
} : {},
|
|
1184
|
-
hint
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1132
|
+
import { z as z16 } from "zod";
|
|
1187
1133
|
|
|
1188
1134
|
// src/tools/propose-sensor.ts
|
|
1189
1135
|
import { execSync } from "child_process";
|
|
1190
|
-
import { readFile as readFile3, writeFile as
|
|
1191
|
-
import { existsSync as
|
|
1192
|
-
import
|
|
1136
|
+
import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
|
|
1137
|
+
import { existsSync as existsSync15 } from "fs";
|
|
1138
|
+
import path5 from "path";
|
|
1193
1139
|
import {
|
|
1194
1140
|
extractSensorExamples,
|
|
1195
1141
|
judgeProposedSensor,
|
|
1196
1142
|
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
1197
|
-
serializeMemory as
|
|
1143
|
+
serializeMemory as serializeMemory7
|
|
1198
1144
|
} from "@hivelore/core";
|
|
1199
|
-
import { z as
|
|
1145
|
+
import { z as z15 } from "zod";
|
|
1200
1146
|
var ProposeSensorInputSchema = {
|
|
1201
|
-
memory_id:
|
|
1202
|
-
|
|
1203
|
-
|
|
1147
|
+
memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
|
|
1148
|
+
kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
|
|
1149
|
+
"regex = pattern matched on added diff lines (default). shell|test = a COMMAND the gate runs when the diff touches the sensor's paths \u2014 routes the team's own oracle (an existing test, an invariant script) to this lesson. Command sensors only execute where enforcement.runCommandSensors=true."
|
|
1150
|
+
),
|
|
1151
|
+
pattern: z15.string().optional().describe("kind=regex: regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
|
|
1152
|
+
command: z15.string().optional().describe("kind=shell|test: command to execute (e.g. 'npx vitest run tests/payments/refund.spec.ts'). Non-zero exit = the lesson fires."),
|
|
1153
|
+
timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
|
|
1154
|
+
absent: z15.string().optional().describe(
|
|
1204
1155
|
"Regex for the CORRECT-usage marker (e.g. 'idempotencyKey'). When it appears in the window around a match, the catch is suppressed \u2014 this is what makes the sensor discriminate the faulty call from the correct one. STRONGLY recommended for 'X without Y' lessons."
|
|
1205
1156
|
),
|
|
1206
|
-
bad_example:
|
|
1207
|
-
severity:
|
|
1208
|
-
message:
|
|
1209
|
-
flags:
|
|
1210
|
-
paths:
|
|
1157
|
+
bad_example: z15.string().optional().describe("A code snippet that SHOULD match \u2014 proves the sensor catches the mistake. If omitted, examples are read from the lesson body."),
|
|
1158
|
+
severity: z15.enum(["warn", "block"]).default("block").describe("block = hard-fail the gate (accepted ONLY if it passes self-validation). warn = advisory."),
|
|
1159
|
+
message: z15.string().optional().describe("LLM-facing fix message shown when it fires. Defaults to one derived from the lesson."),
|
|
1160
|
+
flags: z15.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
|
|
1161
|
+
paths: z15.array(z15.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
|
|
1211
1162
|
};
|
|
1212
1163
|
function deriveMessage(body, pattern, absent) {
|
|
1213
1164
|
const instead = body.match(/\*\*Instead,\s*use:\*\*\s*([^\n]+)/i)?.[1]?.trim();
|
|
@@ -1232,8 +1183,8 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1232
1183
|
continue;
|
|
1233
1184
|
} catch {
|
|
1234
1185
|
}
|
|
1235
|
-
const abs =
|
|
1236
|
-
if (!
|
|
1186
|
+
const abs = path5.resolve(root, rel);
|
|
1187
|
+
if (!existsSync15(abs)) continue;
|
|
1237
1188
|
try {
|
|
1238
1189
|
targets.push({ path: rel, content: await readFile3(abs, "utf8") });
|
|
1239
1190
|
} catch {
|
|
@@ -1241,20 +1192,62 @@ async function readPresumedCorrectTargets(root, relPaths) {
|
|
|
1241
1192
|
}
|
|
1242
1193
|
return targets;
|
|
1243
1194
|
}
|
|
1195
|
+
function runCommandForValidation(command, root, timeoutMs = 12e4) {
|
|
1196
|
+
try {
|
|
1197
|
+
execSync(`bash -c ${JSON.stringify(command)}`, {
|
|
1198
|
+
cwd: root,
|
|
1199
|
+
timeout: timeoutMs,
|
|
1200
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
1201
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1202
|
+
});
|
|
1203
|
+
return { status: "passed", detail: "exit 0" };
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
const e = err;
|
|
1206
|
+
const out = `${e.stdout?.toString() ?? ""}
|
|
1207
|
+
${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
|
|
1208
|
+
if (e.killed) return { status: "unrunnable", detail: `timed out after ${timeoutMs}ms` };
|
|
1209
|
+
if (e.status === 127 || e.status === 126 || e.code === "ENOENT") {
|
|
1210
|
+
return { status: "unrunnable", detail: e.status === 126 ? "command found but not executable" : "command not found" };
|
|
1211
|
+
}
|
|
1212
|
+
return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1244
1215
|
async function proposeSensor(input, ctx) {
|
|
1245
|
-
if (!
|
|
1216
|
+
if (!existsSync15(ctx.paths.memoriesDir)) {
|
|
1246
1217
|
throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1247
1218
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
if (input.
|
|
1251
|
-
|
|
1219
|
+
const kind = input.kind ?? "regex";
|
|
1220
|
+
if (kind === "regex") {
|
|
1221
|
+
if (!input.pattern) {
|
|
1222
|
+
return {
|
|
1223
|
+
accepted: false,
|
|
1224
|
+
memory_id: input.memory_id,
|
|
1225
|
+
severity: input.severity,
|
|
1226
|
+
reason: "invalid-regex",
|
|
1227
|
+
guidance: "kind=regex requires a `pattern`.",
|
|
1228
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
new RegExp(input.pattern, input.flags ?? "");
|
|
1233
|
+
if (input.absent) new RegExp(input.absent, input.flags ?? "");
|
|
1234
|
+
} catch (err) {
|
|
1235
|
+
return {
|
|
1236
|
+
accepted: false,
|
|
1237
|
+
memory_id: input.memory_id,
|
|
1238
|
+
severity: input.severity,
|
|
1239
|
+
reason: "invalid-regex",
|
|
1240
|
+
guidance: `The pattern or absent regex does not compile: ${String(err)}`,
|
|
1241
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
} else if (!input.command?.trim()) {
|
|
1252
1245
|
return {
|
|
1253
1246
|
accepted: false,
|
|
1254
1247
|
memory_id: input.memory_id,
|
|
1255
1248
|
severity: input.severity,
|
|
1256
|
-
reason: "invalid-
|
|
1257
|
-
guidance:
|
|
1249
|
+
reason: "invalid-command",
|
|
1250
|
+
guidance: "kind=shell|test requires a `command` (the check the gate will execute).",
|
|
1258
1251
|
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
|
|
1259
1252
|
};
|
|
1260
1253
|
}
|
|
@@ -1263,6 +1256,43 @@ async function proposeSensor(input, ctx) {
|
|
|
1263
1256
|
if (!found) {
|
|
1264
1257
|
throw new Error(`No memory found with id ${input.memory_id}`);
|
|
1265
1258
|
}
|
|
1259
|
+
if (kind !== "regex") {
|
|
1260
|
+
const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
|
|
1261
|
+
const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
1262
|
+
if (verdictCmd.status !== "passed" && input.severity === "block") {
|
|
1263
|
+
return {
|
|
1264
|
+
accepted: false,
|
|
1265
|
+
memory_id: input.memory_id,
|
|
1266
|
+
severity: input.severity,
|
|
1267
|
+
reason: verdictCmd.status === "unrunnable" ? "command-unrunnable" : "fails-on-current",
|
|
1268
|
+
guidance: verdictCmd.status === "unrunnable" ? `The command could not run (${verdictCmd.detail}). Fix the command (or timeout_ms), then re-propose.` : `The command FAILS on the current tree \u2014 the presumed-correct state must pass, or the gate would block every commit. Revert the faulty diff (or fix the check), then re-propose. Output tail:
|
|
1269
|
+
${verdictCmd.detail}`,
|
|
1270
|
+
self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
const sensorCmd = {
|
|
1274
|
+
kind,
|
|
1275
|
+
command: input.command.trim(),
|
|
1276
|
+
...input.timeout_ms ? { timeout_ms: input.timeout_ms } : {},
|
|
1277
|
+
paths: anchorPathsCmd,
|
|
1278
|
+
message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
|
|
1279
|
+
severity: input.severity,
|
|
1280
|
+
autogen: false,
|
|
1281
|
+
last_fired: null
|
|
1282
|
+
};
|
|
1283
|
+
const nextCmd = {
|
|
1284
|
+
frontmatter: { ...found.memory.frontmatter, sensor: sensorCmd },
|
|
1285
|
+
body: found.memory.body
|
|
1286
|
+
};
|
|
1287
|
+
await writeFile8(found.filePath, serializeMemory7(nextCmd), "utf8");
|
|
1288
|
+
return {
|
|
1289
|
+
accepted: true,
|
|
1290
|
+
memory_id: input.memory_id,
|
|
1291
|
+
severity: input.severity,
|
|
1292
|
+
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}).`,
|
|
1293
|
+
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1266
1296
|
const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
1267
1297
|
const currentTargets = await readPresumedCorrectTargets(ctx.paths.root, anchorPaths);
|
|
1268
1298
|
const badExamples = [
|
|
@@ -1302,7 +1332,7 @@ async function proposeSensor(input, ctx) {
|
|
|
1302
1332
|
frontmatter: { ...found.memory.frontmatter, sensor },
|
|
1303
1333
|
body: found.memory.body
|
|
1304
1334
|
};
|
|
1305
|
-
await
|
|
1335
|
+
await writeFile8(found.filePath, serializeMemory7(next), "utf8");
|
|
1306
1336
|
return {
|
|
1307
1337
|
accepted: true,
|
|
1308
1338
|
memory_id: input.memory_id,
|
|
@@ -1312,6 +1342,105 @@ async function proposeSensor(input, ctx) {
|
|
|
1312
1342
|
};
|
|
1313
1343
|
}
|
|
1314
1344
|
|
|
1345
|
+
// src/tools/mem-tried.ts
|
|
1346
|
+
var MemTriedInputSchema = {
|
|
1347
|
+
what: z16.string().min(1).describe("Brief description of the approach that was tried"),
|
|
1348
|
+
why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
|
|
1349
|
+
instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
|
|
1350
|
+
scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
|
|
1351
|
+
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
1352
|
+
tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
|
|
1353
|
+
paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
|
|
1354
|
+
author: z16.string().optional().describe("Author handle or email"),
|
|
1355
|
+
sensor: z16.object({
|
|
1356
|
+
kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
|
|
1357
|
+
pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
|
|
1358
|
+
command: z16.string().optional().describe("kind=shell|test: command the gate runs when the diff touches the sensor's paths (non-zero exit = lesson fires)"),
|
|
1359
|
+
timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
|
|
1360
|
+
absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
|
|
1361
|
+
severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
|
|
1362
|
+
message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
|
|
1363
|
+
bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
|
|
1364
|
+
}).optional().describe(
|
|
1365
|
+
"ONE-SHOT loop close: validate and attach a sensor in the same call (equivalent to a follow-up propose_sensor). Validated against HEAD \u2014 silent on current code, fires on the bad example. If rejected, the attempt is still saved and the verdict tells you how to revise."
|
|
1366
|
+
)
|
|
1367
|
+
};
|
|
1368
|
+
async function memTried(input, ctx) {
|
|
1369
|
+
if (!existsSync16(ctx.paths.haiveDir)) {
|
|
1370
|
+
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1371
|
+
}
|
|
1372
|
+
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
1373
|
+
const baseFm = buildFrontmatter2({
|
|
1374
|
+
type: "attempt",
|
|
1375
|
+
slug,
|
|
1376
|
+
scope: input.scope,
|
|
1377
|
+
module: input.module,
|
|
1378
|
+
tags: input.tags,
|
|
1379
|
+
paths: input.paths,
|
|
1380
|
+
author: input.author
|
|
1381
|
+
});
|
|
1382
|
+
const frontmatter = { ...baseFm, status: "validated" };
|
|
1383
|
+
const lines = [`# ${input.what}`, ""];
|
|
1384
|
+
lines.push(`**Why it failed / do NOT use:** ${input.why_failed}`);
|
|
1385
|
+
if (input.instead) {
|
|
1386
|
+
lines.push("", `**Instead, use:** ${input.instead}`);
|
|
1387
|
+
}
|
|
1388
|
+
const body = lines.join("\n") + "\n";
|
|
1389
|
+
const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
1390
|
+
await mkdir3(path6.dirname(file), { recursive: true });
|
|
1391
|
+
if (existsSync16(file)) {
|
|
1392
|
+
throw new Error(`Memory already exists at ${file}`);
|
|
1393
|
+
}
|
|
1394
|
+
await writeFile9(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
1395
|
+
if (input.sensor) {
|
|
1396
|
+
const verdict = await proposeSensor(
|
|
1397
|
+
{
|
|
1398
|
+
memory_id: frontmatter.id,
|
|
1399
|
+
kind: input.sensor.kind ?? "regex",
|
|
1400
|
+
pattern: input.sensor.pattern,
|
|
1401
|
+
command: input.sensor.command,
|
|
1402
|
+
timeout_ms: input.sensor.timeout_ms,
|
|
1403
|
+
absent: input.sensor.absent,
|
|
1404
|
+
severity: input.sensor.severity ?? "block",
|
|
1405
|
+
message: input.sensor.message,
|
|
1406
|
+
bad_example: input.sensor.bad_example,
|
|
1407
|
+
flags: void 0,
|
|
1408
|
+
paths: []
|
|
1409
|
+
},
|
|
1410
|
+
ctx
|
|
1411
|
+
);
|
|
1412
|
+
return {
|
|
1413
|
+
id: frontmatter.id,
|
|
1414
|
+
scope: frontmatter.scope,
|
|
1415
|
+
file_path: file,
|
|
1416
|
+
loop_open: !verdict.accepted,
|
|
1417
|
+
sensor_result: {
|
|
1418
|
+
accepted: verdict.accepted,
|
|
1419
|
+
severity: input.sensor.severity ?? "block",
|
|
1420
|
+
...verdict.reason ? { reason: verdict.reason } : {},
|
|
1421
|
+
...verdict.guidance ? { guidance: verdict.guidance } : {}
|
|
1422
|
+
},
|
|
1423
|
+
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.`
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
|
|
1427
|
+
const hint = input.paths.length === 0 ? "No `paths` given, so this attempt is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives, then call propose_sensor to close the loop." : seed ? "This attempt is NOT yet enforced. Call propose_sensor (or re-run mem_tried with the one-shot `sensor` parameter) to turn it into a reliable block \u2014 a candidate is pre-filled in proposed_sensor_seed (refine it: pattern = the faulty usage, absent = the correct-usage marker). Hivelore validates the proposal (silent on current code, fires on the bad example) before trusting it to block." : "This attempt is NOT yet enforced and no candidate pattern could be derived from the wording. Call propose_sensor with a discriminating pattern (pattern = faulty usage, absent = correct-usage marker) to close the loop.";
|
|
1428
|
+
return {
|
|
1429
|
+
id: frontmatter.id,
|
|
1430
|
+
scope: frontmatter.scope,
|
|
1431
|
+
file_path: file,
|
|
1432
|
+
loop_open: true,
|
|
1433
|
+
...seed ? {
|
|
1434
|
+
proposed_sensor_seed: {
|
|
1435
|
+
pattern: seed.pattern,
|
|
1436
|
+
...seed.absent ? { absent: seed.absent } : {},
|
|
1437
|
+
message: seed.message
|
|
1438
|
+
}
|
|
1439
|
+
} : {},
|
|
1440
|
+
hint
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1315
1444
|
// src/tools/ingest-findings.ts
|
|
1316
1445
|
import { existsSync as existsSync17 } from "fs";
|
|
1317
1446
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
|
|
@@ -1405,83 +1534,17 @@ async function writeDraft(ctx, draft) {
|
|
|
1405
1534
|
return file;
|
|
1406
1535
|
}
|
|
1407
1536
|
|
|
1408
|
-
// src/tools/mem-
|
|
1409
|
-
import {
|
|
1410
|
-
import { existsSync as
|
|
1411
|
-
import
|
|
1537
|
+
// src/tools/mem-session-end.ts
|
|
1538
|
+
import { writeFile as writeFile12, mkdir as mkdir6 } from "fs/promises";
|
|
1539
|
+
import { existsSync as existsSync19 } from "fs";
|
|
1540
|
+
import path9 from "path";
|
|
1412
1541
|
import {
|
|
1413
1542
|
buildFrontmatter as buildFrontmatter3,
|
|
1414
|
-
|
|
1543
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
1415
1544
|
memoryFilePath as memoryFilePath4,
|
|
1416
1545
|
serializeMemory as serializeMemory10
|
|
1417
1546
|
} from "@hivelore/core";
|
|
1418
1547
|
import { z as z18 } from "zod";
|
|
1419
|
-
var MemObserveInputSchema = {
|
|
1420
|
-
what: z18.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
|
|
1421
|
-
where: z18.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
|
|
1422
|
-
impact: z18.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
|
|
1423
|
-
fix: z18.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
|
|
1424
|
-
scope: z18.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
|
|
1425
|
-
module: z18.string().optional().describe("Module name (required when scope=module)"),
|
|
1426
|
-
tags: z18.array(z18.string()).default([]).describe("Tags for filtering"),
|
|
1427
|
-
author: z18.string().optional().describe("Author handle or email"),
|
|
1428
|
-
force: z18.boolean().default(false).describe(
|
|
1429
|
-
"Save even if the observation looks like generic, guessable knowledge. By default, low-specificity observations (things a capable model already knows) are SKIPPED to keep the corpus high-signal \u2014 only unguessable, team-specific discoveries are worth storing."
|
|
1430
|
-
)
|
|
1431
|
-
};
|
|
1432
|
-
async function memObserve(input, ctx) {
|
|
1433
|
-
if (!existsSync18(ctx.paths.haiveDir)) {
|
|
1434
|
-
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1435
|
-
}
|
|
1436
|
-
const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
|
|
1437
|
-
if (!input.force && isLikelyGuessable(signalText)) {
|
|
1438
|
-
return {
|
|
1439
|
-
id: "",
|
|
1440
|
-
scope: input.scope,
|
|
1441
|
-
file_path: "",
|
|
1442
|
-
skipped: true,
|
|
1443
|
-
reason: "Observation looks like generic, guessable knowledge (low specificity) \u2014 not saved. Capture only arbitrary, team-specific facts (exact names, values, formats). Pass force=true to override."
|
|
1444
|
-
};
|
|
1445
|
-
}
|
|
1446
|
-
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-");
|
|
1447
|
-
const anchorPaths = input.where.split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
|
|
1448
|
-
const baseFm = buildFrontmatter3({
|
|
1449
|
-
type: "gotcha",
|
|
1450
|
-
slug,
|
|
1451
|
-
scope: input.scope,
|
|
1452
|
-
module: input.module,
|
|
1453
|
-
tags: input.tags,
|
|
1454
|
-
paths: anchorPaths,
|
|
1455
|
-
author: input.author
|
|
1456
|
-
});
|
|
1457
|
-
const frontmatter = { ...baseFm, status: "validated" };
|
|
1458
|
-
const lines = [`# ${input.what}`, ""];
|
|
1459
|
-
lines.push(`**Where:** \`${input.where}\``);
|
|
1460
|
-
lines.push("", `**Impact:** ${input.impact}`);
|
|
1461
|
-
if (input.fix) {
|
|
1462
|
-
lines.push("", `**Fix/workaround:** ${input.fix}`);
|
|
1463
|
-
}
|
|
1464
|
-
const body = lines.join("\n") + "\n";
|
|
1465
|
-
const file = memoryFilePath4(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
1466
|
-
await mkdir5(path8.dirname(file), { recursive: true });
|
|
1467
|
-
if (existsSync18(file)) {
|
|
1468
|
-
throw new Error(`Memory already exists at ${file}`);
|
|
1469
|
-
}
|
|
1470
|
-
await writeFile11(file, serializeMemory10({ frontmatter, body }), "utf8");
|
|
1471
|
-
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
// src/tools/mem-session-end.ts
|
|
1475
|
-
import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
|
|
1476
|
-
import { existsSync as existsSync20 } from "fs";
|
|
1477
|
-
import path10 from "path";
|
|
1478
|
-
import {
|
|
1479
|
-
buildFrontmatter as buildFrontmatter4,
|
|
1480
|
-
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
1481
|
-
memoryFilePath as memoryFilePath5,
|
|
1482
|
-
serializeMemory as serializeMemory11
|
|
1483
|
-
} from "@hivelore/core";
|
|
1484
|
-
import { z as z19 } from "zod";
|
|
1485
1548
|
|
|
1486
1549
|
// src/session-tracker.ts
|
|
1487
1550
|
import {
|
|
@@ -1490,12 +1553,12 @@ import {
|
|
|
1490
1553
|
loadConfig as loadConfig2,
|
|
1491
1554
|
writeSessionHandoff
|
|
1492
1555
|
} from "@hivelore/core";
|
|
1493
|
-
import { mkdir as
|
|
1494
|
-
import { existsSync as
|
|
1495
|
-
import
|
|
1556
|
+
import { mkdir as mkdir5, writeFile as writeFile11, rm } from "fs/promises";
|
|
1557
|
+
import { existsSync as existsSync18 } from "fs";
|
|
1558
|
+
import path8 from "path";
|
|
1496
1559
|
import { execSync as execSync2 } from "child_process";
|
|
1497
1560
|
function pendingDistillPath(ctx) {
|
|
1498
|
-
return
|
|
1561
|
+
return path8.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
1499
1562
|
}
|
|
1500
1563
|
var SessionTracker = class {
|
|
1501
1564
|
events = [];
|
|
@@ -1599,7 +1662,7 @@ var SessionTracker = class {
|
|
|
1599
1662
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
1600
1663
|
);
|
|
1601
1664
|
const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
|
|
1602
|
-
if (!ranPostTask && isSubstantialSession &&
|
|
1665
|
+
if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
|
|
1603
1666
|
try {
|
|
1604
1667
|
const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
|
|
1605
1668
|
const payload = {
|
|
@@ -1612,9 +1675,9 @@ var SessionTracker = class {
|
|
|
1612
1675
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
1613
1676
|
...recapId ? { recap_id: recapId } : {}
|
|
1614
1677
|
};
|
|
1615
|
-
const cacheDir =
|
|
1616
|
-
await
|
|
1617
|
-
await
|
|
1678
|
+
const cacheDir = path8.join(this.ctx.paths.haiveDir, ".cache");
|
|
1679
|
+
await mkdir5(cacheDir, { recursive: true });
|
|
1680
|
+
await writeFile11(
|
|
1618
1681
|
pendingDistillPath(this.ctx),
|
|
1619
1682
|
JSON.stringify(payload, null, 2) + "\n",
|
|
1620
1683
|
"utf8"
|
|
@@ -1633,7 +1696,7 @@ var SessionTracker = class {
|
|
|
1633
1696
|
};
|
|
1634
1697
|
async function clearPendingDistill(ctx) {
|
|
1635
1698
|
const p = pendingDistillPath(ctx);
|
|
1636
|
-
if (
|
|
1699
|
+
if (existsSync18(p)) {
|
|
1637
1700
|
try {
|
|
1638
1701
|
await rm(p);
|
|
1639
1702
|
} catch {
|
|
@@ -1650,15 +1713,15 @@ function summarizeTools(events) {
|
|
|
1650
1713
|
|
|
1651
1714
|
// src/tools/mem-session-end.ts
|
|
1652
1715
|
var MemSessionEndInputSchema = {
|
|
1653
|
-
goal:
|
|
1654
|
-
accomplished:
|
|
1655
|
-
discoveries:
|
|
1716
|
+
goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
|
|
1717
|
+
accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
|
|
1718
|
+
discoveries: z18.string().default("").describe(
|
|
1656
1719
|
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
1657
1720
|
),
|
|
1658
|
-
files_touched:
|
|
1659
|
-
next_steps:
|
|
1660
|
-
scope:
|
|
1661
|
-
module:
|
|
1721
|
+
files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
|
|
1722
|
+
next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
|
|
1723
|
+
scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
|
|
1724
|
+
module: z18.string().optional().describe("Module name (required when scope=module)")
|
|
1662
1725
|
};
|
|
1663
1726
|
function recapTopic(scope, module) {
|
|
1664
1727
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
@@ -1688,23 +1751,23 @@ ${input.next_steps}`);
|
|
|
1688
1751
|
return lines.join("\n");
|
|
1689
1752
|
}
|
|
1690
1753
|
async function memSessionEnd(input, ctx) {
|
|
1691
|
-
if (!
|
|
1754
|
+
if (!existsSync19(ctx.paths.haiveDir)) {
|
|
1692
1755
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1693
1756
|
}
|
|
1694
1757
|
const body = buildBody(input);
|
|
1695
1758
|
const topic = recapTopic(input.scope, input.module);
|
|
1696
1759
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
1697
|
-
if (!p || !
|
|
1698
|
-
const rel =
|
|
1760
|
+
if (!p || !path9.isAbsolute(p)) return p;
|
|
1761
|
+
const rel = path9.relative(ctx.paths.root, p);
|
|
1699
1762
|
return rel.startsWith("..") ? p : rel;
|
|
1700
1763
|
});
|
|
1701
1764
|
const invalidPaths = normalizedFiles.filter(
|
|
1702
|
-
(p) => !
|
|
1765
|
+
(p) => !existsSync19(path9.resolve(ctx.paths.root, p))
|
|
1703
1766
|
);
|
|
1704
1767
|
if (invalidPaths.length > 0) {
|
|
1705
1768
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
1706
1769
|
}
|
|
1707
|
-
const existing =
|
|
1770
|
+
const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
|
|
1708
1771
|
const topicMatch = existing.find(
|
|
1709
1772
|
({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
|
|
1710
1773
|
);
|
|
@@ -1720,9 +1783,9 @@ async function memSessionEnd(input, ctx) {
|
|
|
1720
1783
|
paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
|
|
1721
1784
|
}
|
|
1722
1785
|
};
|
|
1723
|
-
await
|
|
1786
|
+
await writeFile12(
|
|
1724
1787
|
topicMatch.filePath,
|
|
1725
|
-
|
|
1788
|
+
serializeMemory10({ frontmatter: newFrontmatter, body }),
|
|
1726
1789
|
"utf8"
|
|
1727
1790
|
);
|
|
1728
1791
|
await clearPendingDistill(ctx);
|
|
@@ -1734,7 +1797,7 @@ async function memSessionEnd(input, ctx) {
|
|
|
1734
1797
|
revision_count: revisionCount
|
|
1735
1798
|
};
|
|
1736
1799
|
}
|
|
1737
|
-
const frontmatter =
|
|
1800
|
+
const frontmatter = buildFrontmatter3({
|
|
1738
1801
|
type: "session_recap",
|
|
1739
1802
|
slug: "recap",
|
|
1740
1803
|
scope: input.scope,
|
|
@@ -1744,14 +1807,14 @@ async function memSessionEnd(input, ctx) {
|
|
|
1744
1807
|
topic,
|
|
1745
1808
|
status: "validated"
|
|
1746
1809
|
});
|
|
1747
|
-
const file =
|
|
1810
|
+
const file = memoryFilePath4(
|
|
1748
1811
|
ctx.paths,
|
|
1749
1812
|
frontmatter.scope,
|
|
1750
1813
|
frontmatter.id,
|
|
1751
1814
|
frontmatter.module
|
|
1752
1815
|
);
|
|
1753
|
-
await
|
|
1754
|
-
await
|
|
1816
|
+
await mkdir6(path9.dirname(file), { recursive: true });
|
|
1817
|
+
await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
|
|
1755
1818
|
await clearPendingDistill(ctx);
|
|
1756
1819
|
return {
|
|
1757
1820
|
id: frontmatter.id,
|
|
@@ -1763,9 +1826,9 @@ async function memSessionEnd(input, ctx) {
|
|
|
1763
1826
|
}
|
|
1764
1827
|
|
|
1765
1828
|
// src/tools/get-briefing.ts
|
|
1766
|
-
import { readFile as readFile6, writeFile as
|
|
1767
|
-
import { existsSync as
|
|
1768
|
-
import
|
|
1829
|
+
import { readFile as readFile6, writeFile as writeFile13, readdir as readdir4 } from "fs/promises";
|
|
1830
|
+
import { existsSync as existsSync21 } from "fs";
|
|
1831
|
+
import path11 from "path";
|
|
1769
1832
|
import {
|
|
1770
1833
|
allocateBudget,
|
|
1771
1834
|
assessBootstrapState,
|
|
@@ -1799,7 +1862,7 @@ import {
|
|
|
1799
1862
|
queryCodeMap,
|
|
1800
1863
|
readSessionHandoff,
|
|
1801
1864
|
resolveBriefingBudget,
|
|
1802
|
-
serializeMemory as
|
|
1865
|
+
serializeMemory as serializeMemory11,
|
|
1803
1866
|
specificityScore,
|
|
1804
1867
|
GUESSABLE_THRESHOLD,
|
|
1805
1868
|
tokenizeQuery as tokenizeQuery2,
|
|
@@ -1807,12 +1870,12 @@ import {
|
|
|
1807
1870
|
truncateToTokens,
|
|
1808
1871
|
writeBriefingMarker
|
|
1809
1872
|
} from "@hivelore/core";
|
|
1810
|
-
import { z as
|
|
1873
|
+
import { z as z19 } from "zod";
|
|
1811
1874
|
|
|
1812
1875
|
// src/tools/briefing-helpers.ts
|
|
1813
1876
|
import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
|
|
1814
|
-
import { existsSync as
|
|
1815
|
-
import
|
|
1877
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1878
|
+
import path10 from "path";
|
|
1816
1879
|
import {
|
|
1817
1880
|
classifyMemoryPriority as coreClassifyPriority,
|
|
1818
1881
|
isGlobPath,
|
|
@@ -1945,15 +2008,15 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
1945
2008
|
}
|
|
1946
2009
|
async function loadModuleContexts2(ctx, modules) {
|
|
1947
2010
|
if (modules.length === 0) return [];
|
|
1948
|
-
if (!
|
|
2011
|
+
if (!existsSync20(ctx.paths.modulesContextDir)) return [];
|
|
1949
2012
|
const available = new Set(
|
|
1950
2013
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
1951
2014
|
);
|
|
1952
2015
|
const out = [];
|
|
1953
2016
|
for (const m of modules) {
|
|
1954
2017
|
if (!available.has(m)) continue;
|
|
1955
|
-
const file =
|
|
1956
|
-
if (
|
|
2018
|
+
const file = path10.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
2019
|
+
if (existsSync20(file)) {
|
|
1957
2020
|
out.push({ name: m, content: await readFile5(file, "utf8") });
|
|
1958
2021
|
}
|
|
1959
2022
|
}
|
|
@@ -1962,38 +2025,38 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
1962
2025
|
|
|
1963
2026
|
// src/tools/get-briefing.ts
|
|
1964
2027
|
var GetBriefingInputSchema = {
|
|
1965
|
-
task:
|
|
2028
|
+
task: z19.string().optional().describe(
|
|
1966
2029
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
1967
2030
|
),
|
|
1968
|
-
files:
|
|
1969
|
-
max_tokens:
|
|
2031
|
+
files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
|
|
2032
|
+
max_tokens: z19.number().int().positive().default(8e3).describe(
|
|
1970
2033
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
1971
2034
|
),
|
|
1972
|
-
max_memories:
|
|
1973
|
-
include_project_context:
|
|
1974
|
-
dedupe_project_context:
|
|
2035
|
+
max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
2036
|
+
include_project_context: z19.boolean().default(true),
|
|
2037
|
+
dedupe_project_context: z19.boolean().optional().describe(
|
|
1975
2038
|
"Token saver (default ON): skip re-emitting the project-context body if an identical copy was already sent within the last few minutes this session (the agent still has it). Set false to always include it."
|
|
1976
2039
|
),
|
|
1977
|
-
include_module_contexts:
|
|
1978
|
-
semantic:
|
|
2040
|
+
include_module_contexts: z19.boolean().default(true),
|
|
2041
|
+
semantic: z19.boolean().default(true).describe(
|
|
1979
2042
|
"Use semantic ranking when a task is provided (requires `hivelore embeddings index`)."
|
|
1980
2043
|
),
|
|
1981
|
-
include_stale:
|
|
1982
|
-
track:
|
|
1983
|
-
format:
|
|
2044
|
+
include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
2045
|
+
track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
2046
|
+
format: z19.enum(["full", "compact", "actions"]).default("full").describe(
|
|
1984
2047
|
"Output format: 'full' returns memory bodies (honors token budget via truncation); 'compact' returns a 1-line summary per memory (call mem_get for detail); 'actions' squeezes bodies to actionable bullet lines \u2014 fewer tokens vs full."
|
|
1985
2048
|
),
|
|
1986
|
-
symbols:
|
|
2049
|
+
symbols: z19.array(z19.string()).default([]).describe(
|
|
1987
2050
|
"Symbol names to look up in the code-map (e.g. ['PaymentService', 'TenantFilter']). Returns the file(s) exporting each symbol so agents don't need to grep. Requires `hivelore index code` to have been run."
|
|
1988
2051
|
),
|
|
1989
|
-
min_semantic_score:
|
|
2052
|
+
min_semantic_score: z19.number().min(0).max(1).default(0).describe(
|
|
1990
2053
|
"Drop semantic-only memory hits whose cosine score is below this threshold. Useful to avoid weakly-related noise when the task is short or the corpus is broad. Has no effect on memories matched via anchor/module/literal \u2014 those are always kept. Try 0.25\u20130.4 for stricter matching."
|
|
1991
2054
|
),
|
|
1992
|
-
budget_preset:
|
|
2055
|
+
budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
|
|
1993
2056
|
"Shortcut token budget: 'quick' minimizes tokens/skip module CONTEXT slices; 'balanced' mirrors historical defaults; 'deep' uses a larger briefing. When set, overrides max_tokens, max_memories, and include_module_contexts."
|
|
1994
2057
|
)
|
|
1995
2058
|
};
|
|
1996
|
-
var GetBriefingZod =
|
|
2059
|
+
var GetBriefingZod = z19.object(GetBriefingInputSchema);
|
|
1997
2060
|
async function getBriefing(input, ctx) {
|
|
1998
2061
|
const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
|
|
1999
2062
|
max_tokens: input.max_tokens,
|
|
@@ -2009,7 +2072,7 @@ async function getBriefing(input, ctx) {
|
|
|
2009
2072
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
2010
2073
|
let byId = /* @__PURE__ */ new Map();
|
|
2011
2074
|
let lastSession;
|
|
2012
|
-
if (
|
|
2075
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
2013
2076
|
const allLoaded = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
|
|
2014
2077
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
2015
2078
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
@@ -2184,7 +2247,7 @@ async function getBriefing(input, ctx) {
|
|
|
2184
2247
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
2185
2248
|
const newFm = { ...loaded.memory.frontmatter, status: "validated", validated_by: "auto" };
|
|
2186
2249
|
try {
|
|
2187
|
-
await
|
|
2250
|
+
await writeFile13(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
|
|
2188
2251
|
m.status = "validated";
|
|
2189
2252
|
m.confidence = "trusted";
|
|
2190
2253
|
} catch {
|
|
@@ -2192,7 +2255,7 @@ async function getBriefing(input, ctx) {
|
|
|
2192
2255
|
}
|
|
2193
2256
|
}
|
|
2194
2257
|
}
|
|
2195
|
-
let projectContextRaw = input.include_project_context &&
|
|
2258
|
+
let projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
|
|
2196
2259
|
let contextOmittedRecent = false;
|
|
2197
2260
|
if (projectContextRaw && input.dedupe_project_context !== false) {
|
|
2198
2261
|
const ctxHash = hashProjectContext(projectContextRaw);
|
|
@@ -2207,7 +2270,7 @@ async function getBriefing(input, ctx) {
|
|
|
2207
2270
|
const setupWarnings = [];
|
|
2208
2271
|
let autoContextGenerated = false;
|
|
2209
2272
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
2210
|
-
if ((isTemplateContext || !
|
|
2273
|
+
if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
|
|
2211
2274
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
2212
2275
|
if (haiveConfig.autoContext) {
|
|
2213
2276
|
const codeMap = await loadCodeMap(ctx.paths);
|
|
@@ -2313,11 +2376,20 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2313
2376
|
if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
|
|
2314
2377
|
}
|
|
2315
2378
|
const formattedMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : input.format === "actions" ? trimmedMemories.map((m) => ({ ...m, body: extractActionsBriefBody(m.body) })) : trimmedMemories;
|
|
2316
|
-
|
|
2379
|
+
let outputMemories = formattedMemories.map((m) => ({
|
|
2317
2380
|
...m,
|
|
2318
2381
|
priority: classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols),
|
|
2319
2382
|
why: explainWhySurfaced(m, byId.get(m.id), input.files, inferred)
|
|
2320
2383
|
}));
|
|
2384
|
+
if (input.format === "full") {
|
|
2385
|
+
const hasDirectHits = outputMemories.some((m) => m.priority === "must_read" || m.priority === "useful");
|
|
2386
|
+
if (hasDirectHits) {
|
|
2387
|
+
outputMemories = outputMemories.map(
|
|
2388
|
+
(m) => m.priority === "background" ? { ...m, body: `${compactSummary(m.body)}
|
|
2389
|
+
(background \u2014 full body: mem_get("${m.id}"))` } : m
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2321
2393
|
const briefingQuality = classifyBriefingQuality(outputMemories, {
|
|
2322
2394
|
isTemplateContext,
|
|
2323
2395
|
autoContextGenerated,
|
|
@@ -2372,7 +2444,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2372
2444
|
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
2373
2445
|
}
|
|
2374
2446
|
}
|
|
2375
|
-
if (
|
|
2447
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
2376
2448
|
const allMems = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
|
|
2377
2449
|
for (const { memory } of allMems) {
|
|
2378
2450
|
const fm = memory.frontmatter;
|
|
@@ -2383,7 +2455,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2383
2455
|
}
|
|
2384
2456
|
}
|
|
2385
2457
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
2386
|
-
if (
|
|
2458
|
+
if (existsSync21(pendingDistillFile)) {
|
|
2387
2459
|
try {
|
|
2388
2460
|
const raw = await readFile6(pendingDistillFile, "utf8");
|
|
2389
2461
|
const pd = JSON.parse(raw);
|
|
@@ -2412,7 +2484,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2412
2484
|
}
|
|
2413
2485
|
}
|
|
2414
2486
|
const memoriesEmpty = outputMemories.length === 0;
|
|
2415
|
-
const hasMemoriesDir =
|
|
2487
|
+
const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
|
|
2416
2488
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
2417
2489
|
const hasUnguessableSignal = outputMemories.some(
|
|
2418
2490
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -2430,7 +2502,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2430
2502
|
pcRaw = await readFile6(ctx.paths.projectContext, "utf8");
|
|
2431
2503
|
} catch {
|
|
2432
2504
|
}
|
|
2433
|
-
const allForBootstrap =
|
|
2505
|
+
const allForBootstrap = existsSync21(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
|
|
2434
2506
|
const cmForBootstrap = await loadCodeMap(ctx.paths);
|
|
2435
2507
|
let existingModules = [];
|
|
2436
2508
|
try {
|
|
@@ -2494,7 +2566,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2494
2566
|
"No team-specific policy matched these files/task \u2014 nothing here a capable model can't infer. The auto-generated project context was trimmed to keep this briefing near-zero-cost; proceed with normal Read/Grep."
|
|
2495
2567
|
);
|
|
2496
2568
|
}
|
|
2497
|
-
if (outputMemories.length > 0 &&
|
|
2569
|
+
if (outputMemories.length > 0 && existsSync21(ctx.paths.haiveDir)) {
|
|
2498
2570
|
const proof = briefingProofLine(await loadPreventionEvents(ctx.paths));
|
|
2499
2571
|
if (proof) hints.push(proof);
|
|
2500
2572
|
}
|
|
@@ -2508,7 +2580,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2508
2580
|
adaptiveTrim
|
|
2509
2581
|
});
|
|
2510
2582
|
const breadcrumbTokens = breadcrumbs ? estimateTokens([...breadcrumbs.start_here, ...breadcrumbs.drill_down, breadcrumbs.note ?? ""].join("\n")) : 0;
|
|
2511
|
-
if (
|
|
2583
|
+
if (existsSync21(ctx.paths.haiveDir)) {
|
|
2512
2584
|
await writeBriefingMarker(ctx.paths, {
|
|
2513
2585
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
2514
2586
|
...input.task ? { task: input.task } : {},
|
|
@@ -2564,8 +2636,8 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2564
2636
|
};
|
|
2565
2637
|
}
|
|
2566
2638
|
async function detectRunCommands(root) {
|
|
2567
|
-
const pkgPath =
|
|
2568
|
-
if (!
|
|
2639
|
+
const pkgPath = path11.join(root, "package.json");
|
|
2640
|
+
if (!existsSync21(pkgPath)) return null;
|
|
2569
2641
|
try {
|
|
2570
2642
|
const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
|
|
2571
2643
|
const scripts = pkg.scripts ?? {};
|
|
@@ -2632,24 +2704,24 @@ function oneLine(value) {
|
|
|
2632
2704
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
2633
2705
|
}
|
|
2634
2706
|
function serverVersion() {
|
|
2635
|
-
return true ? "0.
|
|
2707
|
+
return true ? "0.33.0" : "dev";
|
|
2636
2708
|
}
|
|
2637
2709
|
|
|
2638
2710
|
// src/tools/code-map.ts
|
|
2639
2711
|
import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hivelore/core";
|
|
2640
|
-
import { z as
|
|
2712
|
+
import { z as z20 } from "zod";
|
|
2641
2713
|
var CodeMapInputSchema = {
|
|
2642
|
-
file:
|
|
2643
|
-
symbol:
|
|
2644
|
-
paths:
|
|
2714
|
+
file: z20.string().optional().describe("Filter to files whose path contains this substring"),
|
|
2715
|
+
symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
2716
|
+
paths: z20.array(z20.string()).default([]).describe(
|
|
2645
2717
|
"Filter to files under any of these path prefixes (e.g. ['packages/mcp/src/tools/', 'src/auth/']). OR-joined with `file` substring; useful to get a focused view of one module."
|
|
2646
2718
|
),
|
|
2647
|
-
max_files:
|
|
2648
|
-
max_tokens:
|
|
2719
|
+
max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
|
|
2720
|
+
max_tokens: z20.number().int().positive().optional().describe(
|
|
2649
2721
|
"Approximate token budget for the response. When the matching set exceeds it, files are ranked by export density (exports per LOC) and the highest-signal ones are kept first. Omit to disable budgeting (legacy behavior)."
|
|
2650
2722
|
)
|
|
2651
2723
|
};
|
|
2652
|
-
var CodeMapInputZod =
|
|
2724
|
+
var CodeMapInputZod = z20.object(CodeMapInputSchema);
|
|
2653
2725
|
async function codeMapTool(input, ctx) {
|
|
2654
2726
|
const map = await loadCodeMap2(ctx.paths);
|
|
2655
2727
|
if (!map) {
|
|
@@ -2718,15 +2790,15 @@ function estimateFileEntryTokens(f) {
|
|
|
2718
2790
|
}
|
|
2719
2791
|
|
|
2720
2792
|
// src/tools/mem-diff.ts
|
|
2721
|
-
import { existsSync as
|
|
2793
|
+
import { existsSync as existsSync22 } from "fs";
|
|
2722
2794
|
import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hivelore/core";
|
|
2723
|
-
import { z as
|
|
2795
|
+
import { z as z21 } from "zod";
|
|
2724
2796
|
var MemDiffInputSchema = {
|
|
2725
|
-
id_a:
|
|
2726
|
-
id_b:
|
|
2797
|
+
id_a: z21.string().min(1).describe("First memory id"),
|
|
2798
|
+
id_b: z21.string().min(1).describe("Second memory id")
|
|
2727
2799
|
};
|
|
2728
2800
|
async function memDiff(input, ctx) {
|
|
2729
|
-
if (!
|
|
2801
|
+
if (!existsSync22(ctx.paths.memoriesDir)) {
|
|
2730
2802
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
2731
2803
|
}
|
|
2732
2804
|
const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
@@ -2763,16 +2835,16 @@ async function memDiff(input, ctx) {
|
|
|
2763
2835
|
}
|
|
2764
2836
|
|
|
2765
2837
|
// src/tools/get-recap.ts
|
|
2766
|
-
import { existsSync as
|
|
2838
|
+
import { existsSync as existsSync23 } from "fs";
|
|
2767
2839
|
import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
|
|
2768
|
-
import { z as
|
|
2840
|
+
import { z as z22 } from "zod";
|
|
2769
2841
|
var GetRecapInputSchema = {
|
|
2770
|
-
scope:
|
|
2842
|
+
scope: z22.enum(["personal", "team", "any"]).default("any").describe(
|
|
2771
2843
|
"Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
|
|
2772
2844
|
)
|
|
2773
2845
|
};
|
|
2774
2846
|
async function getRecap(input, ctx) {
|
|
2775
|
-
if (!
|
|
2847
|
+
if (!existsSync23(ctx.paths.memoriesDir)) {
|
|
2776
2848
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
2777
2849
|
}
|
|
2778
2850
|
const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
|
|
@@ -2799,13 +2871,13 @@ async function getRecap(input, ctx) {
|
|
|
2799
2871
|
}
|
|
2800
2872
|
|
|
2801
2873
|
// src/tools/mem-relevant-to.ts
|
|
2802
|
-
import { z as
|
|
2874
|
+
import { z as z23 } from "zod";
|
|
2803
2875
|
var MemRelevantToInputSchema = {
|
|
2804
|
-
task:
|
|
2805
|
-
files:
|
|
2806
|
-
limit:
|
|
2807
|
-
min_semantic_score:
|
|
2808
|
-
format:
|
|
2876
|
+
task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
|
|
2877
|
+
files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
|
|
2878
|
+
limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
|
|
2879
|
+
min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
|
|
2880
|
+
format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
|
|
2809
2881
|
};
|
|
2810
2882
|
async function memRelevantTo(input, ctx) {
|
|
2811
2883
|
const briefingInput = {
|
|
@@ -2835,14 +2907,14 @@ async function memRelevantTo(input, ctx) {
|
|
|
2835
2907
|
}
|
|
2836
2908
|
|
|
2837
2909
|
// src/tools/code-search.ts
|
|
2838
|
-
import { z as
|
|
2910
|
+
import { z as z24 } from "zod";
|
|
2839
2911
|
import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
|
|
2840
2912
|
var CodeSearchInputSchema = {
|
|
2841
|
-
query:
|
|
2913
|
+
query: z24.string().min(1).describe(
|
|
2842
2914
|
"Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
|
|
2843
2915
|
),
|
|
2844
|
-
k:
|
|
2845
|
-
min_score:
|
|
2916
|
+
k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
|
|
2917
|
+
min_score: z24.number().min(0).max(1).default(0.2).describe(
|
|
2846
2918
|
"Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
|
|
2847
2919
|
)
|
|
2848
2920
|
};
|
|
@@ -2884,155 +2956,39 @@ async function codeSearch(input, ctx) {
|
|
|
2884
2956
|
};
|
|
2885
2957
|
}
|
|
2886
2958
|
|
|
2887
|
-
// src/tools/why-this-file.ts
|
|
2888
|
-
import { existsSync as existsSync25 } from "fs";
|
|
2889
|
-
import { spawn } from "child_process";
|
|
2890
|
-
import path13 from "path";
|
|
2891
|
-
import {
|
|
2892
|
-
deriveConfidence as deriveConfidence5,
|
|
2893
|
-
getUsage as getUsage7,
|
|
2894
|
-
loadCodeMap as loadCodeMap4,
|
|
2895
|
-
loadMemoriesFromDir as loadMemoriesFromDir19,
|
|
2896
|
-
loadUsageIndex as loadUsageIndex9,
|
|
2897
|
-
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
|
|
2898
|
-
} from "@hivelore/core";
|
|
2899
|
-
import { z as z26 } from "zod";
|
|
2900
|
-
var WhyThisFileInputSchema = {
|
|
2901
|
-
path: z26.string().min(1).describe(
|
|
2902
|
-
"Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
|
|
2903
|
-
),
|
|
2904
|
-
git_log_limit: z26.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
|
|
2905
|
-
memory_limit: z26.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
|
|
2906
|
-
};
|
|
2907
|
-
async function whyThisFile(input, ctx) {
|
|
2908
|
-
const fileExists = existsSync25(path13.join(ctx.paths.root, input.path));
|
|
2909
|
-
const [commits, memories, codeMap] = await Promise.all([
|
|
2910
|
-
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
2911
|
-
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
2912
|
-
loadCodeMap4(ctx.paths)
|
|
2913
|
-
]);
|
|
2914
|
-
const codeMapEntry = codeMap?.files[input.path];
|
|
2915
|
-
const hints = [];
|
|
2916
|
-
if (!fileExists) {
|
|
2917
|
-
hints.push(`File '${input.path}' does not exist on disk \u2014 path may be wrong or file removed.`);
|
|
2918
|
-
}
|
|
2919
|
-
if (commits.length === 0 && fileExists) {
|
|
2920
|
-
hints.push("No git history found \u2014 file may be untracked or git not initialized.");
|
|
2921
|
-
}
|
|
2922
|
-
if (memories.length === 0 && fileExists) {
|
|
2923
|
-
hints.push(
|
|
2924
|
-
"No memories anchored here. If you discover something non-obvious while editing, use mem_observe (with where=" + input.path + ") to capture it."
|
|
2925
|
-
);
|
|
2926
|
-
}
|
|
2927
|
-
if (memories.some((m) => m.type === "attempt" || m.type === "gotcha")) {
|
|
2928
|
-
hints.push("\u26A0\uFE0F attempt/gotcha memories anchored to this file \u2014 read them BEFORE editing.");
|
|
2929
|
-
}
|
|
2930
|
-
return {
|
|
2931
|
-
file: input.path,
|
|
2932
|
-
exists: fileExists,
|
|
2933
|
-
recent_commits: commits,
|
|
2934
|
-
memories,
|
|
2935
|
-
code_map_entry: codeMapEntry ? {
|
|
2936
|
-
...codeMapEntry.summary ? { summary: codeMapEntry.summary } : {},
|
|
2937
|
-
loc: codeMapEntry.loc,
|
|
2938
|
-
exports: codeMapEntry.exports.map((e) => ({
|
|
2939
|
-
name: e.name,
|
|
2940
|
-
kind: e.kind,
|
|
2941
|
-
line: e.line,
|
|
2942
|
-
...e.description ? { description: e.description } : {}
|
|
2943
|
-
}))
|
|
2944
|
-
} : null,
|
|
2945
|
-
...hints.length > 0 ? { hints } : {}
|
|
2946
|
-
};
|
|
2947
|
-
}
|
|
2948
|
-
async function collectAnchoredMemories(ctx, filePath, limit) {
|
|
2949
|
-
if (!existsSync25(ctx.paths.memoriesDir)) return [];
|
|
2950
|
-
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
2951
|
-
const usage = await loadUsageIndex9(ctx.paths);
|
|
2952
|
-
const out = [];
|
|
2953
|
-
for (const { memory } of all) {
|
|
2954
|
-
const fm = memory.frontmatter;
|
|
2955
|
-
if (fm.status === "rejected" || fm.status === "deprecated") continue;
|
|
2956
|
-
if (fm.type === "session_recap") continue;
|
|
2957
|
-
if (!memoryMatchesAnchorPaths3(memory, [filePath])) continue;
|
|
2958
|
-
const u = getUsage7(usage, fm.id);
|
|
2959
|
-
out.push({
|
|
2960
|
-
id: fm.id,
|
|
2961
|
-
type: fm.type,
|
|
2962
|
-
scope: fm.scope,
|
|
2963
|
-
confidence: deriveConfidence5(fm, u),
|
|
2964
|
-
body_preview: memory.body.split("\n").slice(0, 6).join("\n")
|
|
2965
|
-
});
|
|
2966
|
-
if (out.length >= limit) break;
|
|
2967
|
-
}
|
|
2968
|
-
return out;
|
|
2969
|
-
}
|
|
2970
|
-
async function runGitLog(cwd, filePath, limit) {
|
|
2971
|
-
const sep = "<<HV>>";
|
|
2972
|
-
const fmt = `%h${sep}%an${sep}%ar${sep}%s`;
|
|
2973
|
-
const output = await runCommand(
|
|
2974
|
-
"git",
|
|
2975
|
-
["log", `-n`, String(limit), `--pretty=format:${fmt}`, "--", filePath],
|
|
2976
|
-
cwd
|
|
2977
|
-
);
|
|
2978
|
-
if (!output.trim()) return [];
|
|
2979
|
-
return output.split("\n").map((line) => {
|
|
2980
|
-
const [sha = "", author = "", relative_date = "", subject = ""] = line.split(sep);
|
|
2981
|
-
return { sha, author, relative_date, subject };
|
|
2982
|
-
}).filter((c) => c.sha);
|
|
2983
|
-
}
|
|
2984
|
-
function runCommand(cmd, args, cwd) {
|
|
2985
|
-
return new Promise((resolve, reject) => {
|
|
2986
|
-
const proc = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
2987
|
-
let stdout = "";
|
|
2988
|
-
let stderr = "";
|
|
2989
|
-
proc.stdout.on("data", (chunk) => {
|
|
2990
|
-
stdout += chunk.toString();
|
|
2991
|
-
});
|
|
2992
|
-
proc.stderr.on("data", (chunk) => {
|
|
2993
|
-
stderr += chunk.toString();
|
|
2994
|
-
});
|
|
2995
|
-
proc.on("error", reject);
|
|
2996
|
-
proc.on("close", (code) => {
|
|
2997
|
-
if (code === 0) resolve(stdout);
|
|
2998
|
-
else reject(new Error(stderr || `${cmd} exited with code ${code}`));
|
|
2999
|
-
});
|
|
3000
|
-
});
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
2959
|
// src/tools/anti-patterns-check.ts
|
|
3004
|
-
import { existsSync as
|
|
2960
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3005
2961
|
import {
|
|
3006
2962
|
addedLinesFromDiff,
|
|
3007
2963
|
BRIDGE_TARGET_PATH,
|
|
3008
2964
|
buildDocFrequency,
|
|
3009
2965
|
CODE_STOPWORDS,
|
|
3010
|
-
deriveConfidence as
|
|
2966
|
+
deriveConfidence as deriveConfidence5,
|
|
3011
2967
|
diffHasDistinctiveOverlap,
|
|
3012
|
-
getUsage as
|
|
2968
|
+
getUsage as getUsage7,
|
|
3013
2969
|
isRetiredMemory as isRetiredMemory2,
|
|
3014
|
-
loadMemoriesFromDir as
|
|
3015
|
-
loadUsageIndex as
|
|
2970
|
+
loadMemoriesFromDir as loadMemoriesFromDir19,
|
|
2971
|
+
loadUsageIndex as loadUsageIndex9,
|
|
3016
2972
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
3017
|
-
memoryMatchesAnchorPaths as
|
|
2973
|
+
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3,
|
|
3018
2974
|
recordPreventionHits,
|
|
3019
2975
|
runSensors,
|
|
3020
2976
|
sensorTargetsFromDiff,
|
|
3021
2977
|
tokenizeQuery as tokenizeQuery3
|
|
3022
2978
|
} from "@hivelore/core";
|
|
3023
|
-
import { z as
|
|
2979
|
+
import { z as z25 } from "zod";
|
|
3024
2980
|
var AntiPatternsCheckInputSchema = {
|
|
3025
|
-
diff:
|
|
2981
|
+
diff: z25.string().optional().describe(
|
|
3026
2982
|
"Raw unified diff text (or any code/text snippet) to scan for previously documented anti-patterns. Tokens from the diff are used to match memory bodies and the embeddings index."
|
|
3027
2983
|
),
|
|
3028
|
-
paths:
|
|
2984
|
+
paths: z25.array(z25.string()).default([]).describe(
|
|
3029
2985
|
"File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
|
|
3030
2986
|
),
|
|
3031
|
-
limit:
|
|
3032
|
-
semantic:
|
|
2987
|
+
limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
|
|
2988
|
+
semantic: z25.boolean().default(true).describe(
|
|
3033
2989
|
"When true, also use semantic search (requires @hivelore/embeddings + memory index) to find related anti-patterns."
|
|
3034
2990
|
),
|
|
3035
|
-
min_semantic_score:
|
|
2991
|
+
min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
|
|
3036
2992
|
"Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
|
|
3037
2993
|
)
|
|
3038
2994
|
};
|
|
@@ -3116,10 +3072,10 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3116
3072
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
3117
3073
|
};
|
|
3118
3074
|
}
|
|
3119
|
-
if (!
|
|
3075
|
+
if (!existsSync24(ctx.paths.memoriesDir)) {
|
|
3120
3076
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
3121
3077
|
}
|
|
3122
|
-
const all = await
|
|
3078
|
+
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
3123
3079
|
const minSemanticScore = input.min_semantic_score ?? 0.45;
|
|
3124
3080
|
const negative = all.filter(({ memory }) => {
|
|
3125
3081
|
const t = memory.frontmatter.type;
|
|
@@ -3130,7 +3086,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3130
3086
|
if (negative.length === 0) {
|
|
3131
3087
|
return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
|
|
3132
3088
|
}
|
|
3133
|
-
const usage = await
|
|
3089
|
+
const usage = await loadUsageIndex9(ctx.paths);
|
|
3134
3090
|
const docFreq = buildDocFrequency(negative.map(({ memory }) => memory.body));
|
|
3135
3091
|
const seen = /* @__PURE__ */ new Map();
|
|
3136
3092
|
const upsert = (fm, body, reason, score) => {
|
|
@@ -3142,12 +3098,12 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3142
3098
|
}
|
|
3143
3099
|
return;
|
|
3144
3100
|
}
|
|
3145
|
-
const u =
|
|
3101
|
+
const u = getUsage7(usage, fm.id);
|
|
3146
3102
|
seen.set(fm.id, {
|
|
3147
3103
|
id: fm.id,
|
|
3148
3104
|
type: fm.type,
|
|
3149
3105
|
scope: fm.scope,
|
|
3150
|
-
confidence:
|
|
3106
|
+
confidence: deriveConfidence5(fm, u),
|
|
3151
3107
|
body_preview: body.split("\n").slice(0, 5).join("\n").slice(0, 400),
|
|
3152
3108
|
reasons: [reason],
|
|
3153
3109
|
tags: fm.tags ?? [],
|
|
@@ -3158,7 +3114,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3158
3114
|
};
|
|
3159
3115
|
if (input.paths.length > 0) {
|
|
3160
3116
|
for (const { memory } of negative) {
|
|
3161
|
-
if (
|
|
3117
|
+
if (memoryMatchesAnchorPaths3(memory, input.paths)) {
|
|
3162
3118
|
upsert(memory.frontmatter, memory.body, "anchor");
|
|
3163
3119
|
}
|
|
3164
3120
|
}
|
|
@@ -3235,19 +3191,19 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3235
3191
|
}
|
|
3236
3192
|
|
|
3237
3193
|
// src/tools/mem-distill.ts
|
|
3238
|
-
import { existsSync as
|
|
3194
|
+
import { existsSync as existsSync25 } from "fs";
|
|
3239
3195
|
import {
|
|
3240
|
-
loadMemoriesFromDir as
|
|
3196
|
+
loadMemoriesFromDir as loadMemoriesFromDir20,
|
|
3241
3197
|
tokenizeQuery as tokenizeQuery4
|
|
3242
3198
|
} from "@hivelore/core";
|
|
3243
|
-
import { z as
|
|
3199
|
+
import { z as z26 } from "zod";
|
|
3244
3200
|
var MemDistillInputSchema = {
|
|
3245
|
-
since_days:
|
|
3246
|
-
min_cluster:
|
|
3247
|
-
type_filter:
|
|
3201
|
+
since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
|
|
3202
|
+
min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
|
|
3203
|
+
type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
|
|
3248
3204
|
"Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
|
|
3249
3205
|
),
|
|
3250
|
-
scope:
|
|
3206
|
+
scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
|
|
3251
3207
|
};
|
|
3252
3208
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
3253
3209
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -3287,11 +3243,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
3287
3243
|
"error"
|
|
3288
3244
|
]);
|
|
3289
3245
|
async function memDistill(input, ctx) {
|
|
3290
|
-
if (!
|
|
3246
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
3291
3247
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
3292
3248
|
}
|
|
3293
3249
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
3294
|
-
const all = await
|
|
3250
|
+
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
3295
3251
|
const candidates = all.filter(({ memory }) => {
|
|
3296
3252
|
const fm = memory.frontmatter;
|
|
3297
3253
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -3394,299 +3350,19 @@ function firstHeading(body) {
|
|
|
3394
3350
|
return void 0;
|
|
3395
3351
|
}
|
|
3396
3352
|
|
|
3397
|
-
// src/tools/why-this-decision.ts
|
|
3398
|
-
import { existsSync as existsSync28 } from "fs";
|
|
3399
|
-
import { spawn as spawn2 } from "child_process";
|
|
3400
|
-
import {
|
|
3401
|
-
deriveConfidence as deriveConfidence7,
|
|
3402
|
-
getUsage as getUsage9,
|
|
3403
|
-
loadMemoriesFromDir as loadMemoriesFromDir22,
|
|
3404
|
-
loadUsageIndex as loadUsageIndex11,
|
|
3405
|
-
pathsOverlap as singlePathsOverlap
|
|
3406
|
-
} from "@hivelore/core";
|
|
3407
|
-
import { z as z29 } from "zod";
|
|
3408
|
-
var WhyThisDecisionInputSchema = {
|
|
3409
|
-
id: z29.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
|
|
3410
|
-
git_log_limit: z29.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
|
|
3411
|
-
};
|
|
3412
|
-
async function whyThisDecision(input, ctx) {
|
|
3413
|
-
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
3414
|
-
return {
|
|
3415
|
-
found: false,
|
|
3416
|
-
related: [],
|
|
3417
|
-
path_neighbors: [],
|
|
3418
|
-
recent_commits: [],
|
|
3419
|
-
notice: "No .ai/memories directory."
|
|
3420
|
-
};
|
|
3421
|
-
}
|
|
3422
|
-
const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
|
|
3423
|
-
const usage = await loadUsageIndex11(ctx.paths);
|
|
3424
|
-
const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
|
|
3425
|
-
if (!target) {
|
|
3426
|
-
return {
|
|
3427
|
-
found: false,
|
|
3428
|
-
related: [],
|
|
3429
|
-
path_neighbors: [],
|
|
3430
|
-
recent_commits: [],
|
|
3431
|
-
notice: `Memory '${input.id}' not found.`
|
|
3432
|
-
};
|
|
3433
|
-
}
|
|
3434
|
-
const fm = target.memory.frontmatter;
|
|
3435
|
-
const targetUsage = getUsage9(usage, fm.id);
|
|
3436
|
-
const decision = {
|
|
3437
|
-
id: fm.id,
|
|
3438
|
-
type: fm.type,
|
|
3439
|
-
scope: fm.scope,
|
|
3440
|
-
status: fm.status,
|
|
3441
|
-
confidence: deriveConfidence7(fm, targetUsage),
|
|
3442
|
-
body: target.memory.body,
|
|
3443
|
-
created_at: fm.created_at
|
|
3444
|
-
};
|
|
3445
|
-
const relatedSet = new Set(fm.related_ids ?? []);
|
|
3446
|
-
const related = [];
|
|
3447
|
-
for (const { memory } of all) {
|
|
3448
|
-
if (memory.frontmatter.id === fm.id) continue;
|
|
3449
|
-
const isExplicit = relatedSet.has(memory.frontmatter.id);
|
|
3450
|
-
const isBackLink = (memory.frontmatter.related_ids ?? []).includes(fm.id);
|
|
3451
|
-
if (!isExplicit && !isBackLink) continue;
|
|
3452
|
-
const u = getUsage9(usage, memory.frontmatter.id);
|
|
3453
|
-
related.push({
|
|
3454
|
-
id: memory.frontmatter.id,
|
|
3455
|
-
type: memory.frontmatter.type,
|
|
3456
|
-
scope: memory.frontmatter.scope,
|
|
3457
|
-
confidence: deriveConfidence7(memory.frontmatter, u),
|
|
3458
|
-
body_preview: memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
|
|
3459
|
-
relation: isExplicit ? "explicit" : "back-link"
|
|
3460
|
-
});
|
|
3461
|
-
}
|
|
3462
|
-
const targetPaths = fm.anchor.paths;
|
|
3463
|
-
const path_neighbors = [];
|
|
3464
|
-
if (targetPaths.length > 0) {
|
|
3465
|
-
for (const { memory } of all) {
|
|
3466
|
-
if (memory.frontmatter.id === fm.id) continue;
|
|
3467
|
-
if (relatedSet.has(memory.frontmatter.id)) continue;
|
|
3468
|
-
const overlappingPaths = memory.frontmatter.anchor.paths.filter(
|
|
3469
|
-
(p) => targetPaths.some((tp) => singlePathsOverlap(p, tp))
|
|
3470
|
-
);
|
|
3471
|
-
if (overlappingPaths.length === 0) continue;
|
|
3472
|
-
const u = getUsage9(usage, memory.frontmatter.id);
|
|
3473
|
-
path_neighbors.push({
|
|
3474
|
-
id: memory.frontmatter.id,
|
|
3475
|
-
type: memory.frontmatter.type,
|
|
3476
|
-
scope: memory.frontmatter.scope,
|
|
3477
|
-
confidence: deriveConfidence7(memory.frontmatter, u),
|
|
3478
|
-
overlap: overlappingPaths,
|
|
3479
|
-
body_preview: memory.body.split("\n").slice(0, 3).join("\n").slice(0, 200)
|
|
3480
|
-
});
|
|
3481
|
-
if (path_neighbors.length >= 10) break;
|
|
3482
|
-
}
|
|
3483
|
-
}
|
|
3484
|
-
const recent_commits = [];
|
|
3485
|
-
for (const p of targetPaths.slice(0, 5)) {
|
|
3486
|
-
try {
|
|
3487
|
-
const commits = await runGitLog2(ctx.paths.root, p, input.git_log_limit);
|
|
3488
|
-
for (const c of commits) recent_commits.push({ path: p, ...c });
|
|
3489
|
-
} catch {
|
|
3490
|
-
}
|
|
3491
|
-
}
|
|
3492
|
-
const hints = [];
|
|
3493
|
-
if (decision.confidence === "low" || decision.confidence === "stale") {
|
|
3494
|
-
hints.push(`\u26A0\uFE0F Confidence is ${decision.confidence}. Verify this decision still applies before quoting it.`);
|
|
3495
|
-
}
|
|
3496
|
-
if (related.length === 0 && path_neighbors.length === 0 && targetPaths.length === 0) {
|
|
3497
|
-
hints.push("No related memories and no anchored paths \u2014 this decision is isolated; consider adding related_ids or paths.");
|
|
3498
|
-
}
|
|
3499
|
-
if (fm.type !== "decision" && fm.type !== "architecture") {
|
|
3500
|
-
hints.push(`Memory type is '${fm.type}', not 'decision'/'architecture' \u2014 output may be less informative.`);
|
|
3501
|
-
}
|
|
3502
|
-
return {
|
|
3503
|
-
found: true,
|
|
3504
|
-
decision,
|
|
3505
|
-
related,
|
|
3506
|
-
path_neighbors,
|
|
3507
|
-
recent_commits,
|
|
3508
|
-
...hints.length > 0 ? { hints } : {}
|
|
3509
|
-
};
|
|
3510
|
-
}
|
|
3511
|
-
async function runGitLog2(cwd, filePath, limit) {
|
|
3512
|
-
const sep = "<<HV>>";
|
|
3513
|
-
const fmt = `%h${sep}%an${sep}%ar${sep}%s`;
|
|
3514
|
-
const output = await runCommand2(
|
|
3515
|
-
"git",
|
|
3516
|
-
["log", "-n", String(limit), `--pretty=format:${fmt}`, "--", filePath],
|
|
3517
|
-
cwd
|
|
3518
|
-
);
|
|
3519
|
-
if (!output.trim()) return [];
|
|
3520
|
-
return output.split("\n").map((line) => {
|
|
3521
|
-
const [sha = "", author = "", relative_date = "", subject = ""] = line.split(sep);
|
|
3522
|
-
return { sha, author, relative_date, subject };
|
|
3523
|
-
}).filter((c) => c.sha);
|
|
3524
|
-
}
|
|
3525
|
-
function runCommand2(cmd, args, cwd) {
|
|
3526
|
-
return new Promise((resolve, reject) => {
|
|
3527
|
-
const proc = spawn2(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
3528
|
-
let stdout = "";
|
|
3529
|
-
let stderr = "";
|
|
3530
|
-
proc.stdout.on("data", (chunk) => {
|
|
3531
|
-
stdout += chunk.toString();
|
|
3532
|
-
});
|
|
3533
|
-
proc.stderr.on("data", (chunk) => {
|
|
3534
|
-
stderr += chunk.toString();
|
|
3535
|
-
});
|
|
3536
|
-
proc.on("error", reject);
|
|
3537
|
-
proc.on("close", (code) => {
|
|
3538
|
-
if (code === 0) resolve(stdout);
|
|
3539
|
-
else reject(new Error(stderr || `${cmd} exited with code ${code}`));
|
|
3540
|
-
});
|
|
3541
|
-
});
|
|
3542
|
-
}
|
|
3543
|
-
|
|
3544
|
-
// src/tools/mem-conflicts.ts
|
|
3545
|
-
import { existsSync as existsSync29 } from "fs";
|
|
3546
|
-
import {
|
|
3547
|
-
deriveConfidence as deriveConfidence8,
|
|
3548
|
-
getUsage as getUsage10,
|
|
3549
|
-
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
3550
|
-
loadUsageIndex as loadUsageIndex12,
|
|
3551
|
-
pathsOverlap as pathsOverlap2,
|
|
3552
|
-
tokenizeQuery as tokenizeQuery5
|
|
3553
|
-
} from "@hivelore/core";
|
|
3554
|
-
import { z as z30 } from "zod";
|
|
3555
|
-
var MemConflictsInputSchema = {
|
|
3556
|
-
id: z30.string().min(1).describe("Memory id to check for conflicts."),
|
|
3557
|
-
min_score: z30.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
|
|
3558
|
-
semantic: z30.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
|
|
3559
|
-
};
|
|
3560
|
-
var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
|
|
3561
|
-
var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
|
|
3562
|
-
async function memConflicts(input, ctx) {
|
|
3563
|
-
if (!existsSync29(ctx.paths.memoriesDir)) {
|
|
3564
|
-
return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
|
|
3565
|
-
}
|
|
3566
|
-
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
3567
|
-
const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
|
|
3568
|
-
if (!target) {
|
|
3569
|
-
return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
|
|
3570
|
-
}
|
|
3571
|
-
const usage = await loadUsageIndex12(ctx.paths);
|
|
3572
|
-
const others = all.filter(
|
|
3573
|
-
({ memory }) => memory.frontmatter.id !== input.id && memory.frontmatter.type !== "session_recap"
|
|
3574
|
-
);
|
|
3575
|
-
const simScores = input.semantic ? await trySemanticSimilarities(ctx, target, others) : null;
|
|
3576
|
-
const targetText = (target.memory.body + " " + target.memory.frontmatter.tags.join(" ")).toLowerCase();
|
|
3577
|
-
const targetTokens = new Set(tokenizeQuery5(targetText));
|
|
3578
|
-
const targetPolarity = polarity(targetText);
|
|
3579
|
-
const targetPaths = target.memory.frontmatter.anchor.paths;
|
|
3580
|
-
const explicitContradicts = extractContradictsTags(target.memory.body);
|
|
3581
|
-
const conflicts = [];
|
|
3582
|
-
for (const other of others) {
|
|
3583
|
-
const fm = other.memory.frontmatter;
|
|
3584
|
-
const otherText = (other.memory.body + " " + fm.tags.join(" ")).toLowerCase();
|
|
3585
|
-
const reasons = [];
|
|
3586
|
-
const sim = simScores?.get(fm.id) ?? null;
|
|
3587
|
-
const hasPathOverlap = fm.anchor.paths.some((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)));
|
|
3588
|
-
const otherTokens = new Set(tokenizeQuery5(otherText));
|
|
3589
|
-
const tokenOverlap = countIntersection(targetTokens, otherTokens);
|
|
3590
|
-
const isSemanticNeighbor = sim !== null && sim >= input.min_score;
|
|
3591
|
-
if (!hasPathOverlap && tokenOverlap < 4 && !isSemanticNeighbor) continue;
|
|
3592
|
-
const otherContradicts = extractContradictsTags(other.memory.body);
|
|
3593
|
-
if (explicitContradicts.has(fm.id) || otherContradicts.has(input.id)) {
|
|
3594
|
-
reasons.push("explicit-contradiction-tag");
|
|
3595
|
-
}
|
|
3596
|
-
if (target.memory.frontmatter.status === "validated" && fm.status === "rejected" || target.memory.frontmatter.status === "rejected" && fm.status === "validated") {
|
|
3597
|
-
if (tokenOverlap >= 4 || isSemanticNeighbor) reasons.push("opposite-status");
|
|
3598
|
-
}
|
|
3599
|
-
if (hasPathOverlap) {
|
|
3600
|
-
const tType = target.memory.frontmatter.type;
|
|
3601
|
-
const oType = fm.type;
|
|
3602
|
-
const isAttemptVsRule = tType === "attempt" && (oType === "convention" || oType === "decision") || oType === "attempt" && (tType === "convention" || tType === "decision");
|
|
3603
|
-
if (isAttemptVsRule) reasons.push("attempt-vs-convention-same-paths");
|
|
3604
|
-
}
|
|
3605
|
-
if (isSemanticNeighbor) {
|
|
3606
|
-
const otherPolarity = polarity(otherText);
|
|
3607
|
-
if (targetPolarity === "positive" && otherPolarity === "negative" || targetPolarity === "negative" && otherPolarity === "positive") {
|
|
3608
|
-
reasons.push("polarity-keywords");
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
if (reasons.length === 0) continue;
|
|
3612
|
-
const u = getUsage10(usage, fm.id);
|
|
3613
|
-
conflicts.push({
|
|
3614
|
-
id: fm.id,
|
|
3615
|
-
type: fm.type,
|
|
3616
|
-
scope: fm.scope,
|
|
3617
|
-
status: fm.status,
|
|
3618
|
-
confidence: deriveConfidence8(fm, u),
|
|
3619
|
-
body_preview: other.memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
|
|
3620
|
-
similarity: sim,
|
|
3621
|
-
reasons,
|
|
3622
|
-
shared_paths: fm.anchor.paths.filter((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)))
|
|
3623
|
-
});
|
|
3624
|
-
}
|
|
3625
|
-
conflicts.sort((a, b) => {
|
|
3626
|
-
const score = (c) => (c.reasons.includes("explicit-contradiction-tag") ? 100 : 0) + (c.reasons.includes("opposite-status") ? 50 : 0) + (c.reasons.includes("attempt-vs-convention-same-paths") ? 25 : 0) + (c.reasons.includes("polarity-keywords") ? 10 : 0) + (c.similarity ?? 0) * 5;
|
|
3627
|
-
return score(b) - score(a);
|
|
3628
|
-
});
|
|
3629
|
-
return {
|
|
3630
|
-
found: true,
|
|
3631
|
-
target: {
|
|
3632
|
-
id: target.memory.frontmatter.id,
|
|
3633
|
-
type: target.memory.frontmatter.type,
|
|
3634
|
-
status: target.memory.frontmatter.status
|
|
3635
|
-
},
|
|
3636
|
-
scanned: others.length,
|
|
3637
|
-
conflicts: conflicts.slice(0, 10)
|
|
3638
|
-
};
|
|
3639
|
-
}
|
|
3640
|
-
function polarity(text) {
|
|
3641
|
-
const neg = NEGATIVE_PATTERNS.test(text);
|
|
3642
|
-
const pos = POSITIVE_PATTERNS.test(text);
|
|
3643
|
-
if (neg && !pos) return "negative";
|
|
3644
|
-
if (pos && !neg) return "positive";
|
|
3645
|
-
return "neutral";
|
|
3646
|
-
}
|
|
3647
|
-
function extractContradictsTags(body) {
|
|
3648
|
-
const out = /* @__PURE__ */ new Set();
|
|
3649
|
-
for (const m of body.matchAll(/#contradicts:([\w-]+)/g)) {
|
|
3650
|
-
if (m[1]) out.add(m[1]);
|
|
3651
|
-
}
|
|
3652
|
-
return out;
|
|
3653
|
-
}
|
|
3654
|
-
function countIntersection(a, b) {
|
|
3655
|
-
let n = 0;
|
|
3656
|
-
for (const x of a) if (b.has(x)) n++;
|
|
3657
|
-
return n;
|
|
3658
|
-
}
|
|
3659
|
-
async function trySemanticSimilarities(ctx, target, others) {
|
|
3660
|
-
let mod;
|
|
3661
|
-
try {
|
|
3662
|
-
mod = await import("@hivelore/embeddings");
|
|
3663
|
-
} catch {
|
|
3664
|
-
return null;
|
|
3665
|
-
}
|
|
3666
|
-
const result = await mod.semanticSearch(
|
|
3667
|
-
ctx.paths,
|
|
3668
|
-
target.memory.body,
|
|
3669
|
-
{ limit: others.length }
|
|
3670
|
-
);
|
|
3671
|
-
if (!result) return null;
|
|
3672
|
-
const map = /* @__PURE__ */ new Map();
|
|
3673
|
-
for (const hit of result.hits) map.set(hit.id, hit.score);
|
|
3674
|
-
return map;
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
3353
|
// src/tools/precommit-check.ts
|
|
3678
|
-
import { pathsOverlap as
|
|
3679
|
-
import { z as
|
|
3354
|
+
import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
|
|
3355
|
+
import { z as z27 } from "zod";
|
|
3680
3356
|
var PreCommitCheckInputSchema = {
|
|
3681
|
-
diff:
|
|
3357
|
+
diff: z27.string().optional().describe(
|
|
3682
3358
|
"Raw unified diff text to scan. If omitted, only `paths` is used. When called from a pre-commit hook, pipe the output of `git diff --cached`."
|
|
3683
3359
|
),
|
|
3684
|
-
paths:
|
|
3685
|
-
block_on:
|
|
3360
|
+
paths: z27.array(z27.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
|
|
3361
|
+
block_on: z27.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
|
|
3686
3362
|
"When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
|
|
3687
3363
|
),
|
|
3688
|
-
semantic:
|
|
3689
|
-
anchored_blocks:
|
|
3364
|
+
semantic: z27.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
|
|
3365
|
+
anchored_blocks: z27.boolean().default(false).describe(
|
|
3690
3366
|
"When true, ALSO block a high-confidence anti-pattern (attempt/gotcha) that is anchored to a touched file AND corroborated by the diff (literal token overlap, or semantic >= 0.45) \u2014 not just very strong semantic matches. Powers the 'anchored' enforcement gate. Config/docs-only commits are still downgraded. Default false preserves the soft, semantic-only blocking behavior."
|
|
3691
3367
|
)
|
|
3692
3368
|
};
|
|
@@ -3755,7 +3431,7 @@ async function preCommitCheck(input, ctx) {
|
|
|
3755
3431
|
}
|
|
3756
3432
|
function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
3757
3433
|
const codeFiles = paths.filter((p) => !p.startsWith(".ai/") && !isHaiveOwnedPath(p));
|
|
3758
|
-
const anchorHits = (warning.anchor_paths ?? []).length > 0 ? codeFiles.filter((p) => (warning.anchor_paths ?? []).some((ap) =>
|
|
3434
|
+
const anchorHits = (warning.anchor_paths ?? []).length > 0 ? codeFiles.filter((p) => (warning.anchor_paths ?? []).some((ap) => pathsOverlap2(ap, p))) : [];
|
|
3759
3435
|
const affectedFiles = anchorHits.length > 0 ? anchorHits : codeFiles;
|
|
3760
3436
|
const repairCommand = repairCommandForWarning(warning, affectedFiles);
|
|
3761
3437
|
const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
|
|
@@ -3991,228 +3667,15 @@ function repairTargetPathForWarning(warning, paths) {
|
|
|
3991
3667
|
return usablePaths[0];
|
|
3992
3668
|
}
|
|
3993
3669
|
|
|
3994
|
-
// src/tools/pattern-detect.ts
|
|
3995
|
-
import { mkdir as mkdir8, writeFile as writeFile15 } from "fs/promises";
|
|
3996
|
-
import { existsSync as existsSync30 } from "fs";
|
|
3997
|
-
import path14 from "path";
|
|
3998
|
-
import { execSync as execSync3 } from "child_process";
|
|
3999
|
-
import {
|
|
4000
|
-
buildFrontmatter as buildFrontmatter5,
|
|
4001
|
-
memoryFilePath as memoryFilePath6,
|
|
4002
|
-
readUsageEvents,
|
|
4003
|
-
serializeMemory as serializeMemory13
|
|
4004
|
-
} from "@hivelore/core";
|
|
4005
|
-
import { z as z32 } from "zod";
|
|
4006
|
-
var CONFIG_PATTERNS = [
|
|
4007
|
-
".eslintrc",
|
|
4008
|
-
"eslint.config",
|
|
4009
|
-
"prettier.config",
|
|
4010
|
-
".prettierrc",
|
|
4011
|
-
"tsconfig",
|
|
4012
|
-
"jsconfig",
|
|
4013
|
-
"vitest.config",
|
|
4014
|
-
"jest.config",
|
|
4015
|
-
".env.example",
|
|
4016
|
-
".env.defaults",
|
|
4017
|
-
"tailwind.config",
|
|
4018
|
-
"vite.config",
|
|
4019
|
-
"next.config",
|
|
4020
|
-
"babel.config",
|
|
4021
|
-
"postcss.config",
|
|
4022
|
-
"renovate.json",
|
|
4023
|
-
"dependabot.yml"
|
|
4024
|
-
];
|
|
4025
|
-
var MAX_DIFF_BYTES = 4096;
|
|
4026
|
-
var HOT_FILE_MIN = 3;
|
|
4027
|
-
var PatternDetectInputSchema = {
|
|
4028
|
-
since_days: z32.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
|
|
4029
|
-
dry_run: z32.boolean().default(false).describe("When true, report matches without writing any memory files."),
|
|
4030
|
-
scope: z32.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
|
|
4031
|
-
};
|
|
4032
|
-
async function patternDetect(input, ctx) {
|
|
4033
|
-
if (!existsSync30(ctx.paths.haiveDir)) {
|
|
4034
|
-
return {
|
|
4035
|
-
scanned_events: 0,
|
|
4036
|
-
matches: [],
|
|
4037
|
-
saved: 0,
|
|
4038
|
-
saved_ids: [],
|
|
4039
|
-
notice: "No .ai/ directory found. Run 'hivelore init' first."
|
|
4040
|
-
};
|
|
4041
|
-
}
|
|
4042
|
-
const matches = [];
|
|
4043
|
-
try {
|
|
4044
|
-
const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
|
|
4045
|
-
const configFiles = changedFiles.filter(
|
|
4046
|
-
(f) => CONFIG_PATTERNS.some((p) => path14.basename(f.toLowerCase()).includes(p))
|
|
4047
|
-
);
|
|
4048
|
-
for (const file of configFiles.slice(0, 5)) {
|
|
4049
|
-
const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
|
|
4050
|
-
if (!diff) continue;
|
|
4051
|
-
const parentDir = path14.basename(path14.dirname(file));
|
|
4052
|
-
const baseName = path14.basename(file).replace(/\.[^.]+$/, "");
|
|
4053
|
-
const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
|
|
4054
|
-
matches.push({
|
|
4055
|
-
kind: "config_change",
|
|
4056
|
-
signal: `Config file modified: ${file}`,
|
|
4057
|
-
proposed_type: "convention",
|
|
4058
|
-
proposed_slug: `config-change-${slug}`,
|
|
4059
|
-
proposed_body: [
|
|
4060
|
-
`# Config change: \`${file}\``,
|
|
4061
|
-
"",
|
|
4062
|
-
"This configuration file was recently modified. The diff below captures the intent.",
|
|
4063
|
-
"Review and update this memory with the **reason** for the change if known.",
|
|
4064
|
-
"",
|
|
4065
|
-
"```diff",
|
|
4066
|
-
diff.slice(0, MAX_DIFF_BYTES),
|
|
4067
|
-
"```"
|
|
4068
|
-
].join("\n"),
|
|
4069
|
-
anchor_paths: [file]
|
|
4070
|
-
});
|
|
4071
|
-
}
|
|
4072
|
-
} catch {
|
|
4073
|
-
}
|
|
4074
|
-
const events = await readUsageEvents(ctx.paths);
|
|
4075
|
-
const cutoff = Date.now() - input.since_days * 24 * 60 * 60 * 1e3;
|
|
4076
|
-
const recent = events.filter((e) => Date.parse(e.at) >= cutoff);
|
|
4077
|
-
const pathCounts = /* @__PURE__ */ new Map();
|
|
4078
|
-
for (const e of recent) {
|
|
4079
|
-
if (!["mem_tried", "mem_observe", "mem_save"].includes(e.tool)) continue;
|
|
4080
|
-
if (!e.summary) continue;
|
|
4081
|
-
const tokens = e.summary.match(/[^\s"'`,;()[\]{}]+\.[a-zA-Z]{1,6}/g) ?? [];
|
|
4082
|
-
for (const t of tokens) {
|
|
4083
|
-
const key = t.toLowerCase();
|
|
4084
|
-
const existing = pathCounts.get(key);
|
|
4085
|
-
if (existing) {
|
|
4086
|
-
existing.count++;
|
|
4087
|
-
existing.tools.add(e.tool);
|
|
4088
|
-
} else {
|
|
4089
|
-
pathCounts.set(key, { count: 1, tools: /* @__PURE__ */ new Set([e.tool]) });
|
|
4090
|
-
}
|
|
4091
|
-
}
|
|
4092
|
-
}
|
|
4093
|
-
for (const [p, { count, tools }] of pathCounts) {
|
|
4094
|
-
if (count < HOT_FILE_MIN) continue;
|
|
4095
|
-
const isGotchaSignal = tools.has("mem_tried") || tools.has("mem_observe");
|
|
4096
|
-
if (!isGotchaSignal) continue;
|
|
4097
|
-
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
4098
|
-
matches.push({
|
|
4099
|
-
kind: "repeated_path",
|
|
4100
|
-
signal: `Path '${p}' appears ${count}\xD7 in mem_tried/mem_observe events`,
|
|
4101
|
-
proposed_type: "gotcha",
|
|
4102
|
-
proposed_slug: `repeated-issue-${slug}`,
|
|
4103
|
-
proposed_body: [
|
|
4104
|
-
`# Recurring issue near \`${p}\``,
|
|
4105
|
-
"",
|
|
4106
|
-
`This file appeared ${count} times in failed-approach or observation events over the last ${input.since_days} days. Review the related attempt/gotcha memories and consolidate them into a single authoritative gotcha.`,
|
|
4107
|
-
"",
|
|
4108
|
-
`**Source signals:** ${[...tools].join(", ")} (${count} events)`
|
|
4109
|
-
].join("\n"),
|
|
4110
|
-
anchor_paths: [p]
|
|
4111
|
-
});
|
|
4112
|
-
}
|
|
4113
|
-
for (const [p, { count, tools }] of pathCounts) {
|
|
4114
|
-
if (count < HOT_FILE_MIN) continue;
|
|
4115
|
-
if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
|
|
4116
|
-
if (CONFIG_PATTERNS.some((cp) => path14.basename(p).includes(cp))) continue;
|
|
4117
|
-
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
4118
|
-
matches.push({
|
|
4119
|
-
kind: "hot_file",
|
|
4120
|
-
signal: `Path '${p}' referenced ${count}\xD7 across mem_save events`,
|
|
4121
|
-
proposed_type: "convention",
|
|
4122
|
-
proposed_slug: `hot-file-${slug}`,
|
|
4123
|
-
proposed_body: [
|
|
4124
|
-
`# Frequent edits to \`${p}\``,
|
|
4125
|
-
"",
|
|
4126
|
-
`This file was referenced ${count} times in memory-saving events over the last ${input.since_days} days \u2014 a signal that a recurring pattern or convention applies here.`,
|
|
4127
|
-
"",
|
|
4128
|
-
"**Suggested action:** review recent memories anchored to this path and extract the common pattern as a named convention."
|
|
4129
|
-
].join("\n"),
|
|
4130
|
-
anchor_paths: [p]
|
|
4131
|
-
});
|
|
4132
|
-
}
|
|
4133
|
-
if (matches.length === 0) {
|
|
4134
|
-
return {
|
|
4135
|
-
scanned_events: recent.length,
|
|
4136
|
-
matches: [],
|
|
4137
|
-
saved: 0,
|
|
4138
|
-
saved_ids: [],
|
|
4139
|
-
notice: `No patterns detected in the last ${input.since_days} days (${recent.length} events scanned).`
|
|
4140
|
-
};
|
|
4141
|
-
}
|
|
4142
|
-
if (input.dry_run) {
|
|
4143
|
-
return { scanned_events: recent.length, matches, saved: 0, saved_ids: [] };
|
|
4144
|
-
}
|
|
4145
|
-
const savedIds = [];
|
|
4146
|
-
for (const match of matches) {
|
|
4147
|
-
try {
|
|
4148
|
-
const fm = buildFrontmatter5({
|
|
4149
|
-
type: match.proposed_type,
|
|
4150
|
-
slug: match.proposed_slug,
|
|
4151
|
-
scope: input.scope,
|
|
4152
|
-
tags: ["pattern-detect", match.kind],
|
|
4153
|
-
paths: match.anchor_paths,
|
|
4154
|
-
status: "proposed"
|
|
4155
|
-
});
|
|
4156
|
-
const file = memoryFilePath6(
|
|
4157
|
-
ctx.paths,
|
|
4158
|
-
fm.scope === "shared" ? "team" : fm.scope,
|
|
4159
|
-
fm.id,
|
|
4160
|
-
void 0
|
|
4161
|
-
);
|
|
4162
|
-
if (existsSync30(file)) continue;
|
|
4163
|
-
await mkdir8(path14.dirname(file), { recursive: true });
|
|
4164
|
-
await writeFile15(
|
|
4165
|
-
file,
|
|
4166
|
-
serializeMemory13({ frontmatter: fm, body: match.proposed_body }),
|
|
4167
|
-
"utf8"
|
|
4168
|
-
);
|
|
4169
|
-
savedIds.push(fm.id);
|
|
4170
|
-
} catch {
|
|
4171
|
-
}
|
|
4172
|
-
}
|
|
4173
|
-
return {
|
|
4174
|
-
scanned_events: recent.length,
|
|
4175
|
-
matches,
|
|
4176
|
-
saved: savedIds.length,
|
|
4177
|
-
saved_ids: savedIds
|
|
4178
|
-
};
|
|
4179
|
-
}
|
|
4180
|
-
function gitChangedFiles(root, sinceDays) {
|
|
4181
|
-
try {
|
|
4182
|
-
const out = execSync3(
|
|
4183
|
-
`git log --name-only --pretty="" --diff-filter=AM --since="${sinceDays} days ago"`,
|
|
4184
|
-
{ cwd: root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
|
|
4185
|
-
);
|
|
4186
|
-
return [...new Set(out.split("\n").map((l) => l.trim()).filter(Boolean))];
|
|
4187
|
-
} catch {
|
|
4188
|
-
return [];
|
|
4189
|
-
}
|
|
4190
|
-
}
|
|
4191
|
-
function gitFileDiff(root, file, sinceDays) {
|
|
4192
|
-
try {
|
|
4193
|
-
const out = execSync3(
|
|
4194
|
-
`git log -p --follow --since="${sinceDays} days ago" -- "${file}"`,
|
|
4195
|
-
{ cwd: root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
|
|
4196
|
-
);
|
|
4197
|
-
if (!out.trim()) return null;
|
|
4198
|
-
const diffLines = out.split("\n").filter(
|
|
4199
|
-
(l) => l.startsWith("+") || l.startsWith("-") || l.startsWith("@@") || l.startsWith("diff")
|
|
4200
|
-
);
|
|
4201
|
-
return diffLines.join("\n").slice(0, MAX_DIFF_BYTES) || null;
|
|
4202
|
-
} catch {
|
|
4203
|
-
return null;
|
|
4204
|
-
}
|
|
4205
|
-
}
|
|
4206
|
-
|
|
4207
3670
|
// src/tools/mem-conflict-candidates.ts
|
|
4208
|
-
import { existsSync as
|
|
3671
|
+
import { existsSync as existsSync26 } from "fs";
|
|
4209
3672
|
import {
|
|
4210
3673
|
findLexicalConflictPairs,
|
|
4211
3674
|
findTopicStatusConflictPairs,
|
|
4212
|
-
loadMemoriesFromDir as
|
|
3675
|
+
loadMemoriesFromDir as loadMemoriesFromDir21,
|
|
4213
3676
|
planConflictResolution
|
|
4214
3677
|
} from "@hivelore/core";
|
|
4215
|
-
import { z as
|
|
3678
|
+
import { z as z28 } from "zod";
|
|
4216
3679
|
function suggestResolution(byId, idA, idB) {
|
|
4217
3680
|
const a = byId.get(idA);
|
|
4218
3681
|
const b = byId.get(idB);
|
|
@@ -4226,17 +3689,17 @@ function suggestResolution(byId, idA, idB) {
|
|
|
4226
3689
|
};
|
|
4227
3690
|
}
|
|
4228
3691
|
var MemConflictCandidatesInputSchema = {
|
|
4229
|
-
since_days:
|
|
4230
|
-
types:
|
|
4231
|
-
min_jaccard:
|
|
4232
|
-
max_pairs:
|
|
4233
|
-
max_scan:
|
|
4234
|
-
max_topic_pairs:
|
|
3692
|
+
since_days: z28.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
3693
|
+
types: z28.array(z28.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
3694
|
+
min_jaccard: z28.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
3695
|
+
max_pairs: z28.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
3696
|
+
max_scan: z28.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
|
|
3697
|
+
max_topic_pairs: z28.number().int().positive().max(100).default(20).describe(
|
|
4235
3698
|
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
4236
3699
|
)
|
|
4237
3700
|
};
|
|
4238
3701
|
async function memConflictCandidates(input, ctx) {
|
|
4239
|
-
if (!
|
|
3702
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
4240
3703
|
return {
|
|
4241
3704
|
pairs: [],
|
|
4242
3705
|
topic_status_pairs: [],
|
|
@@ -4245,7 +3708,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
4245
3708
|
notice: "No .ai/memories directory."
|
|
4246
3709
|
};
|
|
4247
3710
|
}
|
|
4248
|
-
const all = await
|
|
3711
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
4249
3712
|
const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
|
|
4250
3713
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
4251
3714
|
sinceDays: input.since_days,
|
|
@@ -4275,9 +3738,9 @@ async function memConflictCandidates(input, ctx) {
|
|
|
4275
3738
|
|
|
4276
3739
|
// src/tools/mem-resolve-project.ts
|
|
4277
3740
|
import { resolveProjectInfo } from "@hivelore/core";
|
|
4278
|
-
import { z as
|
|
3741
|
+
import { z as z29 } from "zod";
|
|
4279
3742
|
var MemResolveProjectInputSchema = {
|
|
4280
|
-
cwd:
|
|
3743
|
+
cwd: z29.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
4281
3744
|
};
|
|
4282
3745
|
async function memResolveProject(input, _ctx) {
|
|
4283
3746
|
void _ctx;
|
|
@@ -4291,10 +3754,10 @@ async function memResolveProject(input, _ctx) {
|
|
|
4291
3754
|
|
|
4292
3755
|
// src/tools/mem-suggest-topic.ts
|
|
4293
3756
|
import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
|
|
4294
|
-
import { z as
|
|
3757
|
+
import { z as z30 } from "zod";
|
|
4295
3758
|
var MemSuggestTopicInputSchema = {
|
|
4296
3759
|
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
4297
|
-
title:
|
|
3760
|
+
title: z30.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
4298
3761
|
};
|
|
4299
3762
|
async function memSuggestTopic(input, _ctx) {
|
|
4300
3763
|
void _ctx;
|
|
@@ -4303,19 +3766,19 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
4303
3766
|
}
|
|
4304
3767
|
|
|
4305
3768
|
// src/tools/mem-timeline.ts
|
|
4306
|
-
import { existsSync as
|
|
4307
|
-
import { collectTimelineEntries, loadMemoriesFromDir as
|
|
4308
|
-
import { z as
|
|
3769
|
+
import { existsSync as existsSync27 } from "fs";
|
|
3770
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hivelore/core";
|
|
3771
|
+
import { z as z31 } from "zod";
|
|
4309
3772
|
var MemTimelineInputSchema = {
|
|
4310
|
-
memory_id:
|
|
4311
|
-
topic:
|
|
4312
|
-
limit:
|
|
3773
|
+
memory_id: z31.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
3774
|
+
topic: z31.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
3775
|
+
limit: z31.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
4313
3776
|
};
|
|
4314
3777
|
async function memTimeline(input, ctx) {
|
|
4315
|
-
if (!
|
|
3778
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
4316
3779
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
4317
3780
|
}
|
|
4318
|
-
const all = await
|
|
3781
|
+
const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
|
|
4319
3782
|
const { entries, notice } = collectTimelineEntries(all, {
|
|
4320
3783
|
memoryId: input.memory_id,
|
|
4321
3784
|
topic: input.topic,
|
|
@@ -4324,47 +3787,13 @@ async function memTimeline(input, ctx) {
|
|
|
4324
3787
|
return { entries, total: entries.length, notice };
|
|
4325
3788
|
}
|
|
4326
3789
|
|
|
4327
|
-
// src/tools/runtime-journal-append.ts
|
|
4328
|
-
import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hivelore/core";
|
|
4329
|
-
import { z as z37 } from "zod";
|
|
4330
|
-
var RuntimeJournalAppendInputSchema = {
|
|
4331
|
-
message: z37.string().min(1).describe("Short line to append to the runtime session journal"),
|
|
4332
|
-
kind: z37.enum(["note", "session_end", "mcp"]).default("note"),
|
|
4333
|
-
tool: z37.string().optional().describe("When kind=mcp, which tool name (optional)")
|
|
4334
|
-
};
|
|
4335
|
-
async function runtimeJournalAppend(input, ctx) {
|
|
4336
|
-
await appendRuntimeJournalEntry2(ctx.paths, {
|
|
4337
|
-
kind: input.kind,
|
|
4338
|
-
message: input.message,
|
|
4339
|
-
...input.tool ? { tool: input.tool } : {}
|
|
4340
|
-
});
|
|
4341
|
-
return {
|
|
4342
|
-
ok: true,
|
|
4343
|
-
path_hint: `${ctx.paths.runtimeDir}/session-journal.ndjson`
|
|
4344
|
-
};
|
|
4345
|
-
}
|
|
4346
|
-
|
|
4347
|
-
// src/tools/runtime-journal-tail.ts
|
|
4348
|
-
import { readRuntimeJournalTail } from "@hivelore/core";
|
|
4349
|
-
import { z as z38 } from "zod";
|
|
4350
|
-
var RuntimeJournalTailInputSchema = {
|
|
4351
|
-
limit: z38.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
|
|
4352
|
-
};
|
|
4353
|
-
async function runtimeJournalTail(input, ctx) {
|
|
4354
|
-
const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
|
|
4355
|
-
if (entries.length === 0) {
|
|
4356
|
-
return { entries: [], empty: true };
|
|
4357
|
-
}
|
|
4358
|
-
return { entries };
|
|
4359
|
-
}
|
|
4360
|
-
|
|
4361
3790
|
// src/prompts/bootstrap-project.ts
|
|
4362
|
-
import { z as
|
|
3791
|
+
import { z as z32 } from "zod";
|
|
4363
3792
|
var BootstrapProjectArgsSchema = {
|
|
4364
|
-
module:
|
|
3793
|
+
module: z32.string().optional().describe(
|
|
4365
3794
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
4366
3795
|
),
|
|
4367
|
-
focus:
|
|
3796
|
+
focus: z32.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
4368
3797
|
};
|
|
4369
3798
|
var ROOT_TEMPLATE = `# Project context
|
|
4370
3799
|
|
|
@@ -4447,16 +3876,16 @@ ${template}\`\`\`
|
|
|
4447
3876
|
|
|
4448
3877
|
// src/prompts/bootstrap-repo.ts
|
|
4449
3878
|
import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
|
|
4450
|
-
import { existsSync as
|
|
3879
|
+
import { existsSync as existsSync28 } from "fs";
|
|
4451
3880
|
import {
|
|
4452
3881
|
assessBootstrapState as assessBootstrapState2,
|
|
4453
|
-
loadCodeMap as
|
|
4454
|
-
loadMemoriesFromDir as
|
|
3882
|
+
loadCodeMap as loadCodeMap4,
|
|
3883
|
+
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
4455
3884
|
renderBootstrapChecklist as renderBootstrapChecklist2
|
|
4456
3885
|
} from "@hivelore/core";
|
|
4457
|
-
import { z as
|
|
3886
|
+
import { z as z33 } from "zod";
|
|
4458
3887
|
var BootstrapRepoArgsSchema = {
|
|
4459
|
-
focus:
|
|
3888
|
+
focus: z33.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
|
|
4460
3889
|
};
|
|
4461
3890
|
async function currentAssessment(ctx) {
|
|
4462
3891
|
let projectContextRaw = "";
|
|
@@ -4464,8 +3893,8 @@ async function currentAssessment(ctx) {
|
|
|
4464
3893
|
projectContextRaw = await readFile7(ctx.paths.projectContext, "utf8");
|
|
4465
3894
|
} catch {
|
|
4466
3895
|
}
|
|
4467
|
-
const memories =
|
|
4468
|
-
const codeMap = await
|
|
3896
|
+
const memories = existsSync28(ctx.paths.memoriesDir) ? await loadMemoriesFromDir23(ctx.paths.memoriesDir) : [];
|
|
3897
|
+
const codeMap = await loadCodeMap4(ctx.paths);
|
|
4469
3898
|
let existingModules = [];
|
|
4470
3899
|
try {
|
|
4471
3900
|
const entries = await readdir5(ctx.paths.modulesContextDir, { withFileTypes: true });
|
|
@@ -4532,10 +3961,10 @@ Main code areas detected: ${areas}
|
|
|
4532
3961
|
}
|
|
4533
3962
|
|
|
4534
3963
|
// src/prompts/post-task.ts
|
|
4535
|
-
import { z as
|
|
3964
|
+
import { z as z34 } from "zod";
|
|
4536
3965
|
var PostTaskArgsSchema = {
|
|
4537
|
-
task_summary:
|
|
4538
|
-
files_touched:
|
|
3966
|
+
task_summary: z34.string().optional().describe("One sentence describing what you just did"),
|
|
3967
|
+
files_touched: z34.array(z34.string()).optional().describe("Files you created or modified during the task")
|
|
4539
3968
|
};
|
|
4540
3969
|
function postTaskPrompt(args, ctx) {
|
|
4541
3970
|
const taskLine = args.task_summary ? `
|
|
@@ -4632,12 +4061,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
4632
4061
|
}
|
|
4633
4062
|
|
|
4634
4063
|
// src/prompts/import-docs.ts
|
|
4635
|
-
import { z as
|
|
4064
|
+
import { z as z35 } from "zod";
|
|
4636
4065
|
var ImportDocsArgsSchema = {
|
|
4637
|
-
content:
|
|
4638
|
-
source:
|
|
4639
|
-
scope:
|
|
4640
|
-
dry_run:
|
|
4066
|
+
content: z35.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
4067
|
+
source: z35.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
4068
|
+
scope: z35.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
4069
|
+
dry_run: z35.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
4641
4070
|
};
|
|
4642
4071
|
function importDocsPrompt(args, ctx) {
|
|
4643
4072
|
const sourceLine = args.source ? `
|
|
@@ -4703,7 +4132,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4703
4132
|
// src/server.ts
|
|
4704
4133
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
|
|
4705
4134
|
var SERVER_NAME = "hivelore";
|
|
4706
|
-
var SERVER_VERSION = "0.
|
|
4135
|
+
var SERVER_VERSION = "0.33.0";
|
|
4707
4136
|
function jsonResult(data) {
|
|
4708
4137
|
return {
|
|
4709
4138
|
content: [
|
|
@@ -4751,14 +4180,7 @@ var MAINTENANCE_PROFILE_TOOLS = [
|
|
|
4751
4180
|
"ingest_findings"
|
|
4752
4181
|
];
|
|
4753
4182
|
var EXPERIMENTAL_PROFILE_TOOLS = [
|
|
4754
|
-
...MAINTENANCE_PROFILE_TOOLS
|
|
4755
|
-
"mem_observe",
|
|
4756
|
-
"why_this_file",
|
|
4757
|
-
"why_this_decision",
|
|
4758
|
-
"mem_conflicts_with",
|
|
4759
|
-
"pattern_detect",
|
|
4760
|
-
"runtime_journal_append",
|
|
4761
|
-
"runtime_journal_tail"
|
|
4183
|
+
...MAINTENANCE_PROFILE_TOOLS
|
|
4762
4184
|
];
|
|
4763
4185
|
var TOOL_PROFILES = {
|
|
4764
4186
|
enforcement: new Set(ENFORCEMENT_PROFILE_TOOLS),
|
|
@@ -4773,7 +4195,6 @@ var BRIEFING_TOOLS = /* @__PURE__ */ new Set(["get_briefing", "mem_relevant_to"]
|
|
|
4773
4195
|
var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
4774
4196
|
"mem_save",
|
|
4775
4197
|
"mem_tried",
|
|
4776
|
-
"mem_observe",
|
|
4777
4198
|
"mem_session_end",
|
|
4778
4199
|
"bootstrap_project_save",
|
|
4779
4200
|
"mem_update",
|
|
@@ -4781,8 +4202,6 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4781
4202
|
"mem_reject",
|
|
4782
4203
|
"mem_delete",
|
|
4783
4204
|
"mem_feedback",
|
|
4784
|
-
"runtime_journal_append",
|
|
4785
|
-
"pattern_detect",
|
|
4786
4205
|
"ingest_findings",
|
|
4787
4206
|
"propose_sensor"
|
|
4788
4207
|
]);
|
|
@@ -4845,7 +4264,7 @@ function createHaiveServer(options = {}) {
|
|
|
4845
4264
|
" - A domain term and what it means in this codebase",
|
|
4846
4265
|
"",
|
|
4847
4266
|
"DO NOT USE for failed approaches \u2192 use mem_tried instead (better structure).",
|
|
4848
|
-
"
|
|
4267
|
+
"For reactive code discoveries during exploration, prefer a compact gotcha via mem_save.",
|
|
4849
4268
|
"",
|
|
4850
4269
|
"PARAMETERS:",
|
|
4851
4270
|
" type \u2014 convention | decision | gotcha | architecture | glossary | attempt",
|
|
@@ -4972,38 +4391,6 @@ function createHaiveServer(options = {}) {
|
|
|
4972
4391
|
return jsonResult(await ingestFindings(input, context));
|
|
4973
4392
|
}
|
|
4974
4393
|
);
|
|
4975
|
-
registerTool(
|
|
4976
|
-
"mem_observe",
|
|
4977
|
-
[
|
|
4978
|
-
"Capture a code-level discovery made WHILE READING existing code.",
|
|
4979
|
-
"",
|
|
4980
|
-
"USE THIS when you read a file and spot something the team may not know about:",
|
|
4981
|
-
" - A bug or race condition hiding in the code",
|
|
4982
|
-
" - A security gap or missing validation",
|
|
4983
|
-
" - An inconsistency between two files",
|
|
4984
|
-
" - A missing configuration or environment variable",
|
|
4985
|
-
" - Anything that could silently break in production",
|
|
4986
|
-
"",
|
|
4987
|
-
"DIFFERENCE from mem_save: mem_observe is for REACTIVE discoveries during code",
|
|
4988
|
-
"reading. mem_save is for deliberate knowledge capture (conventions, decisions).",
|
|
4989
|
-
"",
|
|
4990
|
-
"Auto-validated, anchored to file paths for staleness detection.",
|
|
4991
|
-
"",
|
|
4992
|
-
"PARAMETERS:",
|
|
4993
|
-
" what \u2014 one-line title (e.g. 'MobilePaymentController: duplicate @RequestBody')",
|
|
4994
|
-
" where \u2014 file path(s) where the issue lives",
|
|
4995
|
-
" impact \u2014 what breaks or could break because of this",
|
|
4996
|
-
" fix \u2014 suggested fix (optional)",
|
|
4997
|
-
" scope \u2014 team (default, since discoveries benefit everyone)",
|
|
4998
|
-
"",
|
|
4999
|
-
"RETURNS: { id, file_path }"
|
|
5000
|
-
].join("\n"),
|
|
5001
|
-
MemObserveInputSchema,
|
|
5002
|
-
async (input) => {
|
|
5003
|
-
tracker.record("mem_observe", input.where);
|
|
5004
|
-
return jsonResult(await memObserve(input, context));
|
|
5005
|
-
}
|
|
5006
|
-
);
|
|
5007
4394
|
registerTool(
|
|
5008
4395
|
"mem_session_end",
|
|
5009
4396
|
[
|
|
@@ -5463,26 +4850,6 @@ function createHaiveServer(options = {}) {
|
|
|
5463
4850
|
return jsonResult(await codeSearch(input, context));
|
|
5464
4851
|
}
|
|
5465
4852
|
);
|
|
5466
|
-
registerTool(
|
|
5467
|
-
"why_this_file",
|
|
5468
|
-
[
|
|
5469
|
-
"One-shot file-context lookup: combines recent git history, memories anchored",
|
|
5470
|
-
"to the path, and the code-map entry. Answers 'why is this file the way it is?'",
|
|
5471
|
-
"in a single call instead of 3-4 manual ones.",
|
|
5472
|
-
"",
|
|
5473
|
-
"PARAMETERS:",
|
|
5474
|
-
" path \u2014 project-relative path (required)",
|
|
5475
|
-
" git_log_limit \u2014 recent commits to include (default 5)",
|
|
5476
|
-
" memory_limit \u2014 anchored memories cap (default 5)",
|
|
5477
|
-
"",
|
|
5478
|
-
"RETURNS: { file, exists, recent_commits: [...], memories: [...], code_map_entry, hints? }"
|
|
5479
|
-
].join("\n"),
|
|
5480
|
-
WhyThisFileInputSchema,
|
|
5481
|
-
async (input) => {
|
|
5482
|
-
tracker.record("why_this_file", input.path);
|
|
5483
|
-
return jsonResult(await whyThisFile(input, context));
|
|
5484
|
-
}
|
|
5485
|
-
);
|
|
5486
4853
|
registerTool(
|
|
5487
4854
|
"anti_patterns_check",
|
|
5488
4855
|
[
|
|
@@ -5532,54 +4899,6 @@ function createHaiveServer(options = {}) {
|
|
|
5532
4899
|
return jsonResult(await memDistill(input, context));
|
|
5533
4900
|
}
|
|
5534
4901
|
);
|
|
5535
|
-
registerTool(
|
|
5536
|
-
"why_this_decision",
|
|
5537
|
-
[
|
|
5538
|
-
"Trace the genealogy of a memory (especially decision/architecture):",
|
|
5539
|
-
"the memory itself + memories explicitly linked via related_ids + memories",
|
|
5540
|
-
"anchored to overlapping paths + recent commits touching those paths.",
|
|
5541
|
-
"",
|
|
5542
|
-
"USE WHEN you find a memory and need to understand WHY it was made and",
|
|
5543
|
-
"what surrounds it. One call instead of 4-5 manual lookups.",
|
|
5544
|
-
"",
|
|
5545
|
-
"PARAMETERS:",
|
|
5546
|
-
" id \u2014 memory id (required)",
|
|
5547
|
-
" git_log_limit \u2014 how many recent commits per anchor path (default 5)",
|
|
5548
|
-
"",
|
|
5549
|
-
"RETURNS: { decision, related: [...], path_neighbors: [...], recent_commits: [...] }"
|
|
5550
|
-
].join("\n"),
|
|
5551
|
-
WhyThisDecisionInputSchema,
|
|
5552
|
-
async (input) => {
|
|
5553
|
-
tracker.record("why_this_decision", input.id);
|
|
5554
|
-
return jsonResult(await whyThisDecision(input, context));
|
|
5555
|
-
}
|
|
5556
|
-
);
|
|
5557
|
-
registerTool(
|
|
5558
|
-
"mem_conflicts_with",
|
|
5559
|
-
[
|
|
5560
|
-
"Detect memories that potentially CONTRADICT a given memory.",
|
|
5561
|
-
"",
|
|
5562
|
-
"USE BEFORE relying on a memory's advice \u2014 surfaces 'another memory says",
|
|
5563
|
-
"the opposite'. Detection uses several heuristics layered together:",
|
|
5564
|
-
"",
|
|
5565
|
-
" 1. Opposite status \u2014 validated vs rejected on overlapping topic",
|
|
5566
|
-
" 2. attempt-vs-convention on overlapping anchor paths",
|
|
5567
|
-
" 3. Polarity keywords \u2014 'use X' vs 'do not use X' among semantic neighbors",
|
|
5568
|
-
" 4. Explicit #contradicts:<id> tags in either body",
|
|
5569
|
-
"",
|
|
5570
|
-
"PARAMETERS:",
|
|
5571
|
-
" id \u2014 memory id to check (required)",
|
|
5572
|
-
" min_score \u2014 minimum cosine similarity for semantic neighbors (default 0.5)",
|
|
5573
|
-
" semantic \u2014 use embeddings (default true)",
|
|
5574
|
-
"",
|
|
5575
|
-
"RETURNS: { found, target, scanned, conflicts: [{ id, reasons, similarity, ... }] }"
|
|
5576
|
-
].join("\n"),
|
|
5577
|
-
MemConflictsInputSchema,
|
|
5578
|
-
async (input) => {
|
|
5579
|
-
tracker.record("mem_conflicts_with", input.id);
|
|
5580
|
-
return jsonResult(await memConflicts(input, context));
|
|
5581
|
-
}
|
|
5582
|
-
);
|
|
5583
4902
|
registerTool(
|
|
5584
4903
|
"mem_conflict_candidates",
|
|
5585
4904
|
[
|
|
@@ -5588,7 +4907,7 @@ function createHaiveServer(options = {}) {
|
|
|
5588
4907
|
" 1. Lexical similarity (Jaccard) on decision/architecture-like pairs",
|
|
5589
4908
|
" 2. Same frontmatter.topic with validated vs rejected \u2014 quick human-review signal",
|
|
5590
4909
|
"",
|
|
5591
|
-
"Advisory only \u2014
|
|
4910
|
+
"Advisory only \u2014 review the listed candidate ids, then resolve with mem_update/mem_delete.",
|
|
5592
4911
|
"",
|
|
5593
4912
|
"PARAMETERS:",
|
|
5594
4913
|
" since_days, types, min_jaccard, max_pairs, max_scan, max_topic_pairs",
|
|
@@ -5601,32 +4920,6 @@ function createHaiveServer(options = {}) {
|
|
|
5601
4920
|
return jsonResult(await memConflictCandidates(input, context));
|
|
5602
4921
|
}
|
|
5603
4922
|
);
|
|
5604
|
-
registerTool(
|
|
5605
|
-
"runtime_journal_append",
|
|
5606
|
-
[
|
|
5607
|
-
"Append one line to `.ai/.runtime/session-journal.ndjson` \u2014 machine-local session continuity.",
|
|
5608
|
-
"",
|
|
5609
|
-
"Does NOT replace team memories; complements mem_session_end recaps for local traces.",
|
|
5610
|
-
"",
|
|
5611
|
-
"PARAMETERS: message, kind (note|session_end|mcp), optional tool",
|
|
5612
|
-
"",
|
|
5613
|
-
"RETURNS: { ok, path_hint }"
|
|
5614
|
-
].join("\n"),
|
|
5615
|
-
RuntimeJournalAppendInputSchema,
|
|
5616
|
-
async (input) => jsonResult(await runtimeJournalAppend(input, context))
|
|
5617
|
-
);
|
|
5618
|
-
registerTool(
|
|
5619
|
-
"runtime_journal_tail",
|
|
5620
|
-
[
|
|
5621
|
-
"Read the last N entries from the runtime session journal (parsed JSON lines).",
|
|
5622
|
-
"",
|
|
5623
|
-
"PARAMETERS: limit (default 30, max 500)",
|
|
5624
|
-
"",
|
|
5625
|
-
"RETURNS: { entries: [...], empty?: true }"
|
|
5626
|
-
].join("\n"),
|
|
5627
|
-
RuntimeJournalTailInputSchema,
|
|
5628
|
-
async (input) => jsonResult(await runtimeJournalTail(input, context))
|
|
5629
|
-
);
|
|
5630
4923
|
registerTool(
|
|
5631
4924
|
"pre_commit_check",
|
|
5632
4925
|
[
|
|
@@ -5653,37 +4946,6 @@ function createHaiveServer(options = {}) {
|
|
|
5653
4946
|
return jsonResult(await preCommitCheck(input, context));
|
|
5654
4947
|
}
|
|
5655
4948
|
);
|
|
5656
|
-
registerTool(
|
|
5657
|
-
"pattern_detect",
|
|
5658
|
-
[
|
|
5659
|
-
"Heuristic memory detector \u2014 finds knowledge worth saving WITHOUT calling an LLM.",
|
|
5660
|
-
"",
|
|
5661
|
-
"Runs three signals over local git history and the tool-usage log:",
|
|
5662
|
-
" 1. CONFIG_CHANGE \u2014 config files modified recently (tsconfig, eslint, prettier, \u2026)",
|
|
5663
|
-
" \u2192 proposes a convention memory with the git diff as body.",
|
|
5664
|
-
" 2. REPEATED_PATH \u2014 same file appears \u22653\xD7 in mem_tried/mem_observe events",
|
|
5665
|
-
" \u2192 proposes a gotcha memory anchored to that path.",
|
|
5666
|
-
" 3. HOT_FILE \u2014 source file referenced \u22653\xD7 in writing-tool events",
|
|
5667
|
-
" \u2192 proposes a convention memory (frequent edits = pattern emerging).",
|
|
5668
|
-
"",
|
|
5669
|
-
"Saves memories with status='proposed'. They feed into auto-promote (Phase 4)",
|
|
5670
|
-
"or are surfaced in the next post_task distillation for LLM review.",
|
|
5671
|
-
"",
|
|
5672
|
-
"USE periodically (e.g. end of sprint) or trigger from post-commit hook.",
|
|
5673
|
-
"",
|
|
5674
|
-
"PARAMETERS:",
|
|
5675
|
-
" since_days \u2014 look-back window in days (default 7)",
|
|
5676
|
-
" dry_run \u2014 report matches without saving (default false)",
|
|
5677
|
-
" scope \u2014 'team' (default) | 'personal'",
|
|
5678
|
-
"",
|
|
5679
|
-
"RETURNS: { scanned_events, matches: [{kind, signal, proposed_type, \u2026}], saved, saved_ids }"
|
|
5680
|
-
].join("\n"),
|
|
5681
|
-
PatternDetectInputSchema,
|
|
5682
|
-
async (input) => {
|
|
5683
|
-
tracker.record("pattern_detect", `since=${input.since_days}d/dry_run=${input.dry_run}`);
|
|
5684
|
-
return jsonResult(await patternDetect(input, context));
|
|
5685
|
-
}
|
|
5686
|
-
);
|
|
5687
4949
|
registerTool(
|
|
5688
4950
|
"mem_diff",
|
|
5689
4951
|
[
|
|
@@ -5734,7 +4996,7 @@ function createHaiveServer(options = {}) {
|
|
|
5734
4996
|
[
|
|
5735
4997
|
"\u2B50 Post-task reflection \u2014 run at the end of every session to capture what you learned:",
|
|
5736
4998
|
"failed approaches (mem_tried), new conventions/decisions/gotchas (mem_save),",
|
|
5737
|
-
"
|
|
4999
|
+
"failed approaches (mem_tried), and an end-of-session recap (mem_session_end).",
|
|
5738
5000
|
"In autopilot mode a minimal recap saves automatically; calling this produces a richer one."
|
|
5739
5001
|
].join(" "),
|
|
5740
5002
|
PostTaskArgsSchema,
|
|
@@ -5799,21 +5061,17 @@ export {
|
|
|
5799
5061
|
getBriefing,
|
|
5800
5062
|
getRecap,
|
|
5801
5063
|
memConflictCandidates,
|
|
5802
|
-
memConflicts,
|
|
5803
5064
|
memDistill,
|
|
5804
5065
|
memRelevantTo,
|
|
5805
5066
|
memResolveProject,
|
|
5806
5067
|
memSuggestTopic,
|
|
5807
5068
|
memTimeline,
|
|
5069
|
+
memTried,
|
|
5808
5070
|
parseMcpCliArgs,
|
|
5809
|
-
patternDetect,
|
|
5810
5071
|
preCommitCheck,
|
|
5811
5072
|
printHaiveMcpVersion,
|
|
5073
|
+
proposeSensor,
|
|
5812
5074
|
readPresumedCorrectTargets,
|
|
5813
|
-
runHaiveMcpStdio
|
|
5814
|
-
runtimeJournalAppend,
|
|
5815
|
-
runtimeJournalTail,
|
|
5816
|
-
whyThisDecision,
|
|
5817
|
-
whyThisFile
|
|
5075
|
+
runHaiveMcpStdio
|
|
5818
5076
|
};
|
|
5819
5077
|
//# sourceMappingURL=server.js.map
|