@hiveai/mcp 0.12.4 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.js +352 -229
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.js +352 -229
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/server.js
CHANGED
|
@@ -1141,32 +1141,123 @@ async function memTried(input, ctx) {
|
|
|
1141
1141
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
1142
1142
|
}
|
|
1143
1143
|
|
|
1144
|
-
// src/tools/
|
|
1145
|
-
import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
|
|
1144
|
+
// src/tools/ingest-findings.ts
|
|
1146
1145
|
import { existsSync as existsSync16 } from "fs";
|
|
1146
|
+
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
|
|
1147
1147
|
import path6 from "path";
|
|
1148
1148
|
import {
|
|
1149
|
-
|
|
1150
|
-
|
|
1149
|
+
draftsFromFindings,
|
|
1150
|
+
filterNewDrafts,
|
|
1151
|
+
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
1151
1152
|
memoryFilePath as memoryFilePath3,
|
|
1153
|
+
parseFindings,
|
|
1152
1154
|
serializeMemory as serializeMemory7
|
|
1153
1155
|
} from "@hiveai/core";
|
|
1154
1156
|
import { z as z16 } from "zod";
|
|
1155
|
-
var
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope
|
|
1157
|
+
var IngestFindingsInputSchema = {
|
|
1158
|
+
format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
|
|
1159
|
+
report_path: z16.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
|
|
1160
|
+
report: z16.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
|
|
1161
|
+
type: z16.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
|
|
1162
|
+
scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
|
|
1161
1163
|
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
1162
|
-
|
|
1164
|
+
min_severity: z16.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
|
|
1165
|
+
limit: z16.number().int().positive().optional().describe("Cap the number of memories created"),
|
|
1163
1166
|
author: z16.string().optional().describe("Author handle or email"),
|
|
1164
|
-
|
|
1167
|
+
dry_run: z16.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
|
|
1168
|
+
};
|
|
1169
|
+
async function ingestFindings(input, ctx) {
|
|
1170
|
+
if (!existsSync16(ctx.paths.haiveDir)) {
|
|
1171
|
+
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
1172
|
+
}
|
|
1173
|
+
let raw;
|
|
1174
|
+
if (input.report && input.report.trim()) {
|
|
1175
|
+
raw = input.report;
|
|
1176
|
+
} else if (input.report_path) {
|
|
1177
|
+
const file = path6.resolve(ctx.paths.root, input.report_path);
|
|
1178
|
+
if (!existsSync16(file)) throw new Error(`Report file not found: ${file}`);
|
|
1179
|
+
raw = await readFile3(file, "utf8");
|
|
1180
|
+
} else {
|
|
1181
|
+
throw new Error("Provide either `report_path` or `report`.");
|
|
1182
|
+
}
|
|
1183
|
+
const findings = parseFindings(input.format, raw);
|
|
1184
|
+
const drafts = draftsFromFindings(findings, {
|
|
1185
|
+
type: input.type,
|
|
1186
|
+
scope: input.scope,
|
|
1187
|
+
module: input.module,
|
|
1188
|
+
author: input.author,
|
|
1189
|
+
...input.min_severity ? { minSeverity: input.min_severity } : {},
|
|
1190
|
+
...input.limit ? { limit: input.limit } : {}
|
|
1191
|
+
});
|
|
1192
|
+
const existing = existsSync16(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
|
|
1193
|
+
const existingTopics = new Set(
|
|
1194
|
+
existing.map(({ memory }) => memory.frontmatter.topic).filter((t) => Boolean(t))
|
|
1195
|
+
);
|
|
1196
|
+
const fresh = filterNewDrafts(drafts, existingTopics);
|
|
1197
|
+
const skipped = drafts.length - fresh.length;
|
|
1198
|
+
const created = [];
|
|
1199
|
+
for (const draft of fresh) {
|
|
1200
|
+
let filePath;
|
|
1201
|
+
if (!input.dry_run) filePath = await writeDraft(ctx, draft);
|
|
1202
|
+
created.push({
|
|
1203
|
+
id: draft.frontmatter.id,
|
|
1204
|
+
topic: draft.topic,
|
|
1205
|
+
path: draft.finding.path,
|
|
1206
|
+
rule: draft.finding.ruleId,
|
|
1207
|
+
severity: draft.finding.severity,
|
|
1208
|
+
has_sensor: draft.has_sensor,
|
|
1209
|
+
...filePath ? { file_path: filePath } : {}
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
const notice = input.dry_run ? `Dry run \u2014 ${fresh.length} memory(ies) would be created (status=proposed). Re-run with dry_run=false to write them.` : `Created ${fresh.length} proposed memory(ies). They are NOT validated and their sensors are warn-only \u2014 review with mem_pending and promote with 'haive sensors promote'.`;
|
|
1213
|
+
return {
|
|
1214
|
+
format: input.format,
|
|
1215
|
+
parsed: drafts.length,
|
|
1216
|
+
new: fresh.length,
|
|
1217
|
+
skipped_existing: skipped,
|
|
1218
|
+
dry_run: input.dry_run,
|
|
1219
|
+
created,
|
|
1220
|
+
notice
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
async function writeDraft(ctx, draft) {
|
|
1224
|
+
const file = memoryFilePath3(
|
|
1225
|
+
ctx.paths,
|
|
1226
|
+
draft.frontmatter.scope,
|
|
1227
|
+
draft.frontmatter.id,
|
|
1228
|
+
draft.frontmatter.module
|
|
1229
|
+
);
|
|
1230
|
+
await mkdir4(path6.dirname(file), { recursive: true });
|
|
1231
|
+
await writeFile8(file, serializeMemory7({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
1232
|
+
return file;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/tools/mem-observe.ts
|
|
1236
|
+
import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
|
|
1237
|
+
import { existsSync as existsSync17 } from "fs";
|
|
1238
|
+
import path7 from "path";
|
|
1239
|
+
import {
|
|
1240
|
+
buildFrontmatter as buildFrontmatter3,
|
|
1241
|
+
isLikelyGuessable,
|
|
1242
|
+
memoryFilePath as memoryFilePath4,
|
|
1243
|
+
serializeMemory as serializeMemory8
|
|
1244
|
+
} from "@hiveai/core";
|
|
1245
|
+
import { z as z17 } from "zod";
|
|
1246
|
+
var MemObserveInputSchema = {
|
|
1247
|
+
what: z17.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
|
|
1248
|
+
where: z17.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
|
|
1249
|
+
impact: z17.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
|
|
1250
|
+
fix: z17.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
|
|
1251
|
+
scope: z17.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
|
|
1252
|
+
module: z17.string().optional().describe("Module name (required when scope=module)"),
|
|
1253
|
+
tags: z17.array(z17.string()).default([]).describe("Tags for filtering"),
|
|
1254
|
+
author: z17.string().optional().describe("Author handle or email"),
|
|
1255
|
+
force: z17.boolean().default(false).describe(
|
|
1165
1256
|
"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."
|
|
1166
1257
|
)
|
|
1167
1258
|
};
|
|
1168
1259
|
async function memObserve(input, ctx) {
|
|
1169
|
-
if (!
|
|
1260
|
+
if (!existsSync17(ctx.paths.haiveDir)) {
|
|
1170
1261
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
1171
1262
|
}
|
|
1172
1263
|
const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
|
|
@@ -1198,26 +1289,26 @@ async function memObserve(input, ctx) {
|
|
|
1198
1289
|
lines.push("", `**Fix/workaround:** ${input.fix}`);
|
|
1199
1290
|
}
|
|
1200
1291
|
const body = lines.join("\n") + "\n";
|
|
1201
|
-
const file =
|
|
1202
|
-
await
|
|
1203
|
-
if (
|
|
1292
|
+
const file = memoryFilePath4(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
1293
|
+
await mkdir5(path7.dirname(file), { recursive: true });
|
|
1294
|
+
if (existsSync17(file)) {
|
|
1204
1295
|
throw new Error(`Memory already exists at ${file}`);
|
|
1205
1296
|
}
|
|
1206
|
-
await
|
|
1297
|
+
await writeFile9(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
1207
1298
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
1208
1299
|
}
|
|
1209
1300
|
|
|
1210
1301
|
// src/tools/mem-session-end.ts
|
|
1211
|
-
import { writeFile as
|
|
1212
|
-
import { existsSync as
|
|
1213
|
-
import
|
|
1302
|
+
import { writeFile as writeFile11, mkdir as mkdir7 } from "fs/promises";
|
|
1303
|
+
import { existsSync as existsSync19 } from "fs";
|
|
1304
|
+
import path9 from "path";
|
|
1214
1305
|
import {
|
|
1215
1306
|
buildFrontmatter as buildFrontmatter4,
|
|
1216
|
-
loadMemoriesFromDir as
|
|
1217
|
-
memoryFilePath as
|
|
1218
|
-
serializeMemory as
|
|
1307
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
1308
|
+
memoryFilePath as memoryFilePath5,
|
|
1309
|
+
serializeMemory as serializeMemory9
|
|
1219
1310
|
} from "@hiveai/core";
|
|
1220
|
-
import { z as
|
|
1311
|
+
import { z as z18 } from "zod";
|
|
1221
1312
|
|
|
1222
1313
|
// src/session-tracker.ts
|
|
1223
1314
|
import {
|
|
@@ -1225,12 +1316,12 @@ import {
|
|
|
1225
1316
|
appendRuntimeJournalEntry,
|
|
1226
1317
|
loadConfig as loadConfig2
|
|
1227
1318
|
} from "@hiveai/core";
|
|
1228
|
-
import { mkdir as
|
|
1229
|
-
import { existsSync as
|
|
1230
|
-
import
|
|
1319
|
+
import { mkdir as mkdir6, writeFile as writeFile10, rm } from "fs/promises";
|
|
1320
|
+
import { existsSync as existsSync18 } from "fs";
|
|
1321
|
+
import path8 from "path";
|
|
1231
1322
|
import { execSync } from "child_process";
|
|
1232
1323
|
function pendingDistillPath(ctx) {
|
|
1233
|
-
return
|
|
1324
|
+
return path8.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
1234
1325
|
}
|
|
1235
1326
|
var SessionTracker = class {
|
|
1236
1327
|
events = [];
|
|
@@ -1309,7 +1400,7 @@ var SessionTracker = class {
|
|
|
1309
1400
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
1310
1401
|
);
|
|
1311
1402
|
const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
|
|
1312
|
-
if (!ranPostTask && isSubstantialSession &&
|
|
1403
|
+
if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
|
|
1313
1404
|
try {
|
|
1314
1405
|
const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
|
|
1315
1406
|
const payload = {
|
|
@@ -1322,9 +1413,9 @@ var SessionTracker = class {
|
|
|
1322
1413
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
1323
1414
|
...recapId ? { recap_id: recapId } : {}
|
|
1324
1415
|
};
|
|
1325
|
-
const cacheDir =
|
|
1326
|
-
await
|
|
1327
|
-
await
|
|
1416
|
+
const cacheDir = path8.join(this.ctx.paths.haiveDir, ".cache");
|
|
1417
|
+
await mkdir6(cacheDir, { recursive: true });
|
|
1418
|
+
await writeFile10(
|
|
1328
1419
|
pendingDistillPath(this.ctx),
|
|
1329
1420
|
JSON.stringify(payload, null, 2) + "\n",
|
|
1330
1421
|
"utf8"
|
|
@@ -1343,7 +1434,7 @@ var SessionTracker = class {
|
|
|
1343
1434
|
};
|
|
1344
1435
|
async function clearPendingDistill(ctx) {
|
|
1345
1436
|
const p = pendingDistillPath(ctx);
|
|
1346
|
-
if (
|
|
1437
|
+
if (existsSync18(p)) {
|
|
1347
1438
|
try {
|
|
1348
1439
|
await rm(p);
|
|
1349
1440
|
} catch {
|
|
@@ -1360,15 +1451,15 @@ function summarizeTools(events) {
|
|
|
1360
1451
|
|
|
1361
1452
|
// src/tools/mem-session-end.ts
|
|
1362
1453
|
var MemSessionEndInputSchema = {
|
|
1363
|
-
goal:
|
|
1364
|
-
accomplished:
|
|
1365
|
-
discoveries:
|
|
1454
|
+
goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
|
|
1455
|
+
accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
|
|
1456
|
+
discoveries: z18.string().default("").describe(
|
|
1366
1457
|
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
1367
1458
|
),
|
|
1368
|
-
files_touched:
|
|
1369
|
-
next_steps:
|
|
1370
|
-
scope:
|
|
1371
|
-
module:
|
|
1459
|
+
files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
|
|
1460
|
+
next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
|
|
1461
|
+
scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
|
|
1462
|
+
module: z18.string().optional().describe("Module name (required when scope=module)")
|
|
1372
1463
|
};
|
|
1373
1464
|
function recapTopic(scope, module) {
|
|
1374
1465
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
@@ -1398,23 +1489,23 @@ ${input.next_steps}`);
|
|
|
1398
1489
|
return lines.join("\n");
|
|
1399
1490
|
}
|
|
1400
1491
|
async function memSessionEnd(input, ctx) {
|
|
1401
|
-
if (!
|
|
1492
|
+
if (!existsSync19(ctx.paths.haiveDir)) {
|
|
1402
1493
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
1403
1494
|
}
|
|
1404
1495
|
const body = buildBody(input);
|
|
1405
1496
|
const topic = recapTopic(input.scope, input.module);
|
|
1406
1497
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
1407
|
-
if (!p || !
|
|
1408
|
-
const rel =
|
|
1498
|
+
if (!p || !path9.isAbsolute(p)) return p;
|
|
1499
|
+
const rel = path9.relative(ctx.paths.root, p);
|
|
1409
1500
|
return rel.startsWith("..") ? p : rel;
|
|
1410
1501
|
});
|
|
1411
1502
|
const invalidPaths = normalizedFiles.filter(
|
|
1412
|
-
(p) => !
|
|
1503
|
+
(p) => !existsSync19(path9.resolve(ctx.paths.root, p))
|
|
1413
1504
|
);
|
|
1414
1505
|
if (invalidPaths.length > 0) {
|
|
1415
1506
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
1416
1507
|
}
|
|
1417
|
-
const existing =
|
|
1508
|
+
const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
|
|
1418
1509
|
const topicMatch = existing.find(
|
|
1419
1510
|
({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
|
|
1420
1511
|
);
|
|
@@ -1430,9 +1521,9 @@ async function memSessionEnd(input, ctx) {
|
|
|
1430
1521
|
paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
|
|
1431
1522
|
}
|
|
1432
1523
|
};
|
|
1433
|
-
await
|
|
1524
|
+
await writeFile11(
|
|
1434
1525
|
topicMatch.filePath,
|
|
1435
|
-
|
|
1526
|
+
serializeMemory9({ frontmatter: newFrontmatter, body }),
|
|
1436
1527
|
"utf8"
|
|
1437
1528
|
);
|
|
1438
1529
|
await clearPendingDistill(ctx);
|
|
@@ -1454,14 +1545,14 @@ async function memSessionEnd(input, ctx) {
|
|
|
1454
1545
|
topic,
|
|
1455
1546
|
status: "validated"
|
|
1456
1547
|
});
|
|
1457
|
-
const file =
|
|
1548
|
+
const file = memoryFilePath5(
|
|
1458
1549
|
ctx.paths,
|
|
1459
1550
|
frontmatter.scope,
|
|
1460
1551
|
frontmatter.id,
|
|
1461
1552
|
frontmatter.module
|
|
1462
1553
|
);
|
|
1463
|
-
await
|
|
1464
|
-
await
|
|
1554
|
+
await mkdir7(path9.dirname(file), { recursive: true });
|
|
1555
|
+
await writeFile11(file, serializeMemory9({ frontmatter, body }), "utf8");
|
|
1465
1556
|
await clearPendingDistill(ctx);
|
|
1466
1557
|
return {
|
|
1467
1558
|
id: frontmatter.id,
|
|
@@ -1473,8 +1564,8 @@ async function memSessionEnd(input, ctx) {
|
|
|
1473
1564
|
}
|
|
1474
1565
|
|
|
1475
1566
|
// src/tools/get-briefing.ts
|
|
1476
|
-
import { readFile as
|
|
1477
|
-
import { existsSync as
|
|
1567
|
+
import { readFile as readFile5, writeFile as writeFile12 } from "fs/promises";
|
|
1568
|
+
import { existsSync as existsSync21 } from "fs";
|
|
1478
1569
|
import {
|
|
1479
1570
|
allocateBudget,
|
|
1480
1571
|
computeImpact as computeImpact2,
|
|
@@ -1492,13 +1583,13 @@ import {
|
|
|
1492
1583
|
literalMatchesAnyToken as literalMatchesAnyToken2,
|
|
1493
1584
|
loadCodeMap,
|
|
1494
1585
|
loadConfig as loadConfig3,
|
|
1495
|
-
loadMemoriesFromDir as
|
|
1586
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
1496
1587
|
loadUsageIndex as loadUsageIndex8,
|
|
1497
1588
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
1498
1589
|
rankMemoriesLexical as rankMemoriesLexical2,
|
|
1499
1590
|
queryCodeMap,
|
|
1500
1591
|
resolveBriefingBudget,
|
|
1501
|
-
serializeMemory as
|
|
1592
|
+
serializeMemory as serializeMemory10,
|
|
1502
1593
|
specificityScore,
|
|
1503
1594
|
GUESSABLE_THRESHOLD,
|
|
1504
1595
|
tokenizeQuery as tokenizeQuery2,
|
|
@@ -1506,12 +1597,12 @@ import {
|
|
|
1506
1597
|
truncateToTokens,
|
|
1507
1598
|
writeBriefingMarker
|
|
1508
1599
|
} from "@hiveai/core";
|
|
1509
|
-
import { z as
|
|
1600
|
+
import { z as z19 } from "zod";
|
|
1510
1601
|
|
|
1511
1602
|
// src/tools/briefing-helpers.ts
|
|
1512
|
-
import { readdir as readdir3, readFile as
|
|
1513
|
-
import { existsSync as
|
|
1514
|
-
import
|
|
1603
|
+
import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
|
|
1604
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1605
|
+
import path10 from "path";
|
|
1515
1606
|
import { isGlobPath, isStackPackSeed, pathsOverlap } from "@hiveai/core";
|
|
1516
1607
|
function compactSummary(body) {
|
|
1517
1608
|
for (const line of body.split("\n")) {
|
|
@@ -1637,16 +1728,16 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
1637
1728
|
}
|
|
1638
1729
|
async function loadModuleContexts2(ctx, modules) {
|
|
1639
1730
|
if (modules.length === 0) return [];
|
|
1640
|
-
if (!
|
|
1731
|
+
if (!existsSync20(ctx.paths.modulesContextDir)) return [];
|
|
1641
1732
|
const available = new Set(
|
|
1642
1733
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
1643
1734
|
);
|
|
1644
1735
|
const out = [];
|
|
1645
1736
|
for (const m of modules) {
|
|
1646
1737
|
if (!available.has(m)) continue;
|
|
1647
|
-
const file =
|
|
1648
|
-
if (
|
|
1649
|
-
out.push({ name: m, content: await
|
|
1738
|
+
const file = path10.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
1739
|
+
if (existsSync20(file)) {
|
|
1740
|
+
out.push({ name: m, content: await readFile4(file, "utf8") });
|
|
1650
1741
|
}
|
|
1651
1742
|
}
|
|
1652
1743
|
return out;
|
|
@@ -1654,35 +1745,35 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
1654
1745
|
|
|
1655
1746
|
// src/tools/get-briefing.ts
|
|
1656
1747
|
var GetBriefingInputSchema = {
|
|
1657
|
-
task:
|
|
1748
|
+
task: z19.string().optional().describe(
|
|
1658
1749
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
1659
1750
|
),
|
|
1660
|
-
files:
|
|
1661
|
-
max_tokens:
|
|
1751
|
+
files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
|
|
1752
|
+
max_tokens: z19.number().int().positive().default(8e3).describe(
|
|
1662
1753
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
1663
1754
|
),
|
|
1664
|
-
max_memories:
|
|
1665
|
-
include_project_context:
|
|
1666
|
-
include_module_contexts:
|
|
1667
|
-
semantic:
|
|
1755
|
+
max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
1756
|
+
include_project_context: z19.boolean().default(true),
|
|
1757
|
+
include_module_contexts: z19.boolean().default(true),
|
|
1758
|
+
semantic: z19.boolean().default(true).describe(
|
|
1668
1759
|
"Use semantic ranking when a task is provided (requires `haive embeddings index`)."
|
|
1669
1760
|
),
|
|
1670
|
-
include_stale:
|
|
1671
|
-
track:
|
|
1672
|
-
format:
|
|
1761
|
+
include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
1762
|
+
track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
1763
|
+
format: z19.enum(["full", "compact", "actions"]).default("full").describe(
|
|
1673
1764
|
"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."
|
|
1674
1765
|
),
|
|
1675
|
-
symbols:
|
|
1766
|
+
symbols: z19.array(z19.string()).default([]).describe(
|
|
1676
1767
|
"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 `haive index code` to have been run."
|
|
1677
1768
|
),
|
|
1678
|
-
min_semantic_score:
|
|
1769
|
+
min_semantic_score: z19.number().min(0).max(1).default(0).describe(
|
|
1679
1770
|
"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."
|
|
1680
1771
|
),
|
|
1681
|
-
budget_preset:
|
|
1772
|
+
budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
|
|
1682
1773
|
"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."
|
|
1683
1774
|
)
|
|
1684
1775
|
};
|
|
1685
|
-
var GetBriefingZod =
|
|
1776
|
+
var GetBriefingZod = z19.object(GetBriefingInputSchema);
|
|
1686
1777
|
async function getBriefing(input, ctx) {
|
|
1687
1778
|
const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
|
|
1688
1779
|
max_tokens: input.max_tokens,
|
|
@@ -1698,8 +1789,8 @@ async function getBriefing(input, ctx) {
|
|
|
1698
1789
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
1699
1790
|
let byId = /* @__PURE__ */ new Map();
|
|
1700
1791
|
let lastSession;
|
|
1701
|
-
if (
|
|
1702
|
-
const allLoaded = await
|
|
1792
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
1793
|
+
const allLoaded = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
1703
1794
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
1704
1795
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
1705
1796
|
);
|
|
@@ -1862,7 +1953,7 @@ async function getBriefing(input, ctx) {
|
|
|
1862
1953
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
1863
1954
|
const newFm = { ...loaded.memory.frontmatter, status: "validated" };
|
|
1864
1955
|
try {
|
|
1865
|
-
await
|
|
1956
|
+
await writeFile12(loaded.filePath, serializeMemory10({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
|
|
1866
1957
|
m.status = "validated";
|
|
1867
1958
|
m.confidence = "trusted";
|
|
1868
1959
|
} catch {
|
|
@@ -1870,12 +1961,12 @@ async function getBriefing(input, ctx) {
|
|
|
1870
1961
|
}
|
|
1871
1962
|
}
|
|
1872
1963
|
}
|
|
1873
|
-
const projectContextRaw = input.include_project_context &&
|
|
1964
|
+
const projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile5(ctx.paths.projectContext, "utf8") : "";
|
|
1874
1965
|
const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
|
|
1875
1966
|
const setupWarnings = [];
|
|
1876
1967
|
let autoContextGenerated = false;
|
|
1877
1968
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
1878
|
-
if ((isTemplateContext || !
|
|
1969
|
+
if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
|
|
1879
1970
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
1880
1971
|
if (haiveConfig.autoContext) {
|
|
1881
1972
|
const codeMap = await loadCodeMap(ctx.paths);
|
|
@@ -2036,8 +2127,8 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2036
2127
|
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
2037
2128
|
}
|
|
2038
2129
|
}
|
|
2039
|
-
if (
|
|
2040
|
-
const allMems = await
|
|
2130
|
+
if (existsSync21(ctx.paths.memoriesDir)) {
|
|
2131
|
+
const allMems = await loadMemoriesFromDir15(ctx.paths.memoriesDir);
|
|
2041
2132
|
for (const { memory } of allMems) {
|
|
2042
2133
|
const fm = memory.frontmatter;
|
|
2043
2134
|
if (!fm.requires_human_approval) continue;
|
|
@@ -2047,9 +2138,9 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2047
2138
|
}
|
|
2048
2139
|
}
|
|
2049
2140
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
2050
|
-
if (
|
|
2141
|
+
if (existsSync21(pendingDistillFile)) {
|
|
2051
2142
|
try {
|
|
2052
|
-
const raw = await
|
|
2143
|
+
const raw = await readFile5(pendingDistillFile, "utf8");
|
|
2053
2144
|
const pd = JSON.parse(raw);
|
|
2054
2145
|
const ageMs = Date.now() - new Date(pd.session_end).getTime();
|
|
2055
2146
|
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -2076,7 +2167,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2076
2167
|
}
|
|
2077
2168
|
}
|
|
2078
2169
|
const memoriesEmpty = outputMemories.length === 0;
|
|
2079
|
-
const hasMemoriesDir =
|
|
2170
|
+
const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
|
|
2080
2171
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
2081
2172
|
const hasUnguessableSignal = outputMemories.some(
|
|
2082
2173
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -2123,7 +2214,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2123
2214
|
"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."
|
|
2124
2215
|
);
|
|
2125
2216
|
}
|
|
2126
|
-
if (
|
|
2217
|
+
if (existsSync21(ctx.paths.haiveDir)) {
|
|
2127
2218
|
await writeBriefingMarker(ctx.paths, {
|
|
2128
2219
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
2129
2220
|
...input.task ? { task: input.task } : {},
|
|
@@ -2174,19 +2265,19 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2174
2265
|
|
|
2175
2266
|
// src/tools/code-map.ts
|
|
2176
2267
|
import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hiveai/core";
|
|
2177
|
-
import { z as
|
|
2268
|
+
import { z as z20 } from "zod";
|
|
2178
2269
|
var CodeMapInputSchema = {
|
|
2179
|
-
file:
|
|
2180
|
-
symbol:
|
|
2181
|
-
paths:
|
|
2270
|
+
file: z20.string().optional().describe("Filter to files whose path contains this substring"),
|
|
2271
|
+
symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
2272
|
+
paths: z20.array(z20.string()).default([]).describe(
|
|
2182
2273
|
"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."
|
|
2183
2274
|
),
|
|
2184
|
-
max_files:
|
|
2185
|
-
max_tokens:
|
|
2275
|
+
max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
|
|
2276
|
+
max_tokens: z20.number().int().positive().optional().describe(
|
|
2186
2277
|
"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)."
|
|
2187
2278
|
)
|
|
2188
2279
|
};
|
|
2189
|
-
var CodeMapInputZod =
|
|
2280
|
+
var CodeMapInputZod = z20.object(CodeMapInputSchema);
|
|
2190
2281
|
async function codeMapTool(input, ctx) {
|
|
2191
2282
|
const map = await loadCodeMap2(ctx.paths);
|
|
2192
2283
|
if (!map) {
|
|
@@ -2255,18 +2346,18 @@ function estimateFileEntryTokens(f) {
|
|
|
2255
2346
|
}
|
|
2256
2347
|
|
|
2257
2348
|
// src/tools/mem-diff.ts
|
|
2258
|
-
import { existsSync as
|
|
2259
|
-
import { loadMemoriesFromDir as
|
|
2260
|
-
import { z as
|
|
2349
|
+
import { existsSync as existsSync22 } from "fs";
|
|
2350
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir16 } from "@hiveai/core";
|
|
2351
|
+
import { z as z21 } from "zod";
|
|
2261
2352
|
var MemDiffInputSchema = {
|
|
2262
|
-
id_a:
|
|
2263
|
-
id_b:
|
|
2353
|
+
id_a: z21.string().min(1).describe("First memory id"),
|
|
2354
|
+
id_b: z21.string().min(1).describe("Second memory id")
|
|
2264
2355
|
};
|
|
2265
2356
|
async function memDiff(input, ctx) {
|
|
2266
|
-
if (!
|
|
2357
|
+
if (!existsSync22(ctx.paths.memoriesDir)) {
|
|
2267
2358
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
2268
2359
|
}
|
|
2269
|
-
const all = await
|
|
2360
|
+
const all = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
|
|
2270
2361
|
const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
|
|
2271
2362
|
const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
|
|
2272
2363
|
if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
|
|
@@ -2300,19 +2391,19 @@ async function memDiff(input, ctx) {
|
|
|
2300
2391
|
}
|
|
2301
2392
|
|
|
2302
2393
|
// src/tools/get-recap.ts
|
|
2303
|
-
import { existsSync as
|
|
2304
|
-
import { loadMemoriesFromDir as
|
|
2305
|
-
import { z as
|
|
2394
|
+
import { existsSync as existsSync23 } from "fs";
|
|
2395
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hiveai/core";
|
|
2396
|
+
import { z as z22 } from "zod";
|
|
2306
2397
|
var GetRecapInputSchema = {
|
|
2307
|
-
scope:
|
|
2398
|
+
scope: z22.enum(["personal", "team", "any"]).default("any").describe(
|
|
2308
2399
|
"Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
|
|
2309
2400
|
)
|
|
2310
2401
|
};
|
|
2311
2402
|
async function getRecap(input, ctx) {
|
|
2312
|
-
if (!
|
|
2403
|
+
if (!existsSync23(ctx.paths.memoriesDir)) {
|
|
2313
2404
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
2314
2405
|
}
|
|
2315
|
-
const all = await
|
|
2406
|
+
const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2316
2407
|
const recaps = all.filter(({ memory }) => memory.frontmatter.type === "session_recap").filter(({ memory }) => input.scope === "any" || memory.frontmatter.scope === input.scope).sort(
|
|
2317
2408
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2318
2409
|
);
|
|
@@ -2336,13 +2427,13 @@ async function getRecap(input, ctx) {
|
|
|
2336
2427
|
}
|
|
2337
2428
|
|
|
2338
2429
|
// src/tools/mem-relevant-to.ts
|
|
2339
|
-
import { z as
|
|
2430
|
+
import { z as z23 } from "zod";
|
|
2340
2431
|
var MemRelevantToInputSchema = {
|
|
2341
|
-
task:
|
|
2342
|
-
files:
|
|
2343
|
-
limit:
|
|
2344
|
-
min_semantic_score:
|
|
2345
|
-
format:
|
|
2432
|
+
task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
|
|
2433
|
+
files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
|
|
2434
|
+
limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
|
|
2435
|
+
min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
|
|
2436
|
+
format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
|
|
2346
2437
|
};
|
|
2347
2438
|
async function memRelevantTo(input, ctx) {
|
|
2348
2439
|
const briefingInput = {
|
|
@@ -2372,13 +2463,13 @@ async function memRelevantTo(input, ctx) {
|
|
|
2372
2463
|
}
|
|
2373
2464
|
|
|
2374
2465
|
// src/tools/code-search.ts
|
|
2375
|
-
import { z as
|
|
2466
|
+
import { z as z24 } from "zod";
|
|
2376
2467
|
var CodeSearchInputSchema = {
|
|
2377
|
-
query:
|
|
2468
|
+
query: z24.string().min(1).describe(
|
|
2378
2469
|
"Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
|
|
2379
2470
|
),
|
|
2380
|
-
k:
|
|
2381
|
-
min_score:
|
|
2471
|
+
k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
|
|
2472
|
+
min_score: z24.number().min(0).max(1).default(0.2).describe(
|
|
2382
2473
|
"Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
|
|
2383
2474
|
)
|
|
2384
2475
|
};
|
|
@@ -2408,27 +2499,27 @@ async function codeSearch(input, ctx) {
|
|
|
2408
2499
|
}
|
|
2409
2500
|
|
|
2410
2501
|
// src/tools/why-this-file.ts
|
|
2411
|
-
import { existsSync as
|
|
2502
|
+
import { existsSync as existsSync24 } from "fs";
|
|
2412
2503
|
import { spawn } from "child_process";
|
|
2413
|
-
import
|
|
2504
|
+
import path11 from "path";
|
|
2414
2505
|
import {
|
|
2415
2506
|
deriveConfidence as deriveConfidence5,
|
|
2416
2507
|
getUsage as getUsage7,
|
|
2417
2508
|
loadCodeMap as loadCodeMap3,
|
|
2418
|
-
loadMemoriesFromDir as
|
|
2509
|
+
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
2419
2510
|
loadUsageIndex as loadUsageIndex9,
|
|
2420
2511
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
|
|
2421
2512
|
} from "@hiveai/core";
|
|
2422
|
-
import { z as
|
|
2513
|
+
import { z as z25 } from "zod";
|
|
2423
2514
|
var WhyThisFileInputSchema = {
|
|
2424
|
-
path:
|
|
2515
|
+
path: z25.string().min(1).describe(
|
|
2425
2516
|
"Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
|
|
2426
2517
|
),
|
|
2427
|
-
git_log_limit:
|
|
2428
|
-
memory_limit:
|
|
2518
|
+
git_log_limit: z25.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
|
|
2519
|
+
memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
|
|
2429
2520
|
};
|
|
2430
2521
|
async function whyThisFile(input, ctx) {
|
|
2431
|
-
const fileExists =
|
|
2522
|
+
const fileExists = existsSync24(path11.join(ctx.paths.root, input.path));
|
|
2432
2523
|
const [commits, memories, codeMap] = await Promise.all([
|
|
2433
2524
|
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
2434
2525
|
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
@@ -2469,8 +2560,8 @@ async function whyThisFile(input, ctx) {
|
|
|
2469
2560
|
};
|
|
2470
2561
|
}
|
|
2471
2562
|
async function collectAnchoredMemories(ctx, filePath, limit) {
|
|
2472
|
-
if (!
|
|
2473
|
-
const all = await
|
|
2563
|
+
if (!existsSync24(ctx.paths.memoriesDir)) return [];
|
|
2564
|
+
const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
|
|
2474
2565
|
const usage = await loadUsageIndex9(ctx.paths);
|
|
2475
2566
|
const out = [];
|
|
2476
2567
|
for (const { memory } of all) {
|
|
@@ -2524,7 +2615,7 @@ function runCommand(cmd, args, cwd) {
|
|
|
2524
2615
|
}
|
|
2525
2616
|
|
|
2526
2617
|
// src/tools/anti-patterns-check.ts
|
|
2527
|
-
import { existsSync as
|
|
2618
|
+
import { existsSync as existsSync25 } from "fs";
|
|
2528
2619
|
import {
|
|
2529
2620
|
addedLinesFromDiff,
|
|
2530
2621
|
buildDocFrequency,
|
|
@@ -2533,7 +2624,7 @@ import {
|
|
|
2533
2624
|
diffHasDistinctiveOverlap,
|
|
2534
2625
|
getUsage as getUsage8,
|
|
2535
2626
|
isRetiredMemory as isRetiredMemory2,
|
|
2536
|
-
loadMemoriesFromDir as
|
|
2627
|
+
loadMemoriesFromDir as loadMemoriesFromDir19,
|
|
2537
2628
|
loadUsageIndex as loadUsageIndex10,
|
|
2538
2629
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
2539
2630
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
@@ -2541,19 +2632,19 @@ import {
|
|
|
2541
2632
|
sensorTargetsFromDiff,
|
|
2542
2633
|
tokenizeQuery as tokenizeQuery3
|
|
2543
2634
|
} from "@hiveai/core";
|
|
2544
|
-
import { z as
|
|
2635
|
+
import { z as z26 } from "zod";
|
|
2545
2636
|
var AntiPatternsCheckInputSchema = {
|
|
2546
|
-
diff:
|
|
2637
|
+
diff: z26.string().optional().describe(
|
|
2547
2638
|
"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."
|
|
2548
2639
|
),
|
|
2549
|
-
paths:
|
|
2640
|
+
paths: z26.array(z26.string()).default([]).describe(
|
|
2550
2641
|
"File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
|
|
2551
2642
|
),
|
|
2552
|
-
limit:
|
|
2553
|
-
semantic:
|
|
2643
|
+
limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
|
|
2644
|
+
semantic: z26.boolean().default(true).describe(
|
|
2554
2645
|
"When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
|
|
2555
2646
|
),
|
|
2556
|
-
min_semantic_score:
|
|
2647
|
+
min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
|
|
2557
2648
|
"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."
|
|
2558
2649
|
)
|
|
2559
2650
|
};
|
|
@@ -2574,10 +2665,10 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
2574
2665
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
2575
2666
|
};
|
|
2576
2667
|
}
|
|
2577
|
-
if (!
|
|
2668
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
2578
2669
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
2579
2670
|
}
|
|
2580
|
-
const all = await
|
|
2671
|
+
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
2581
2672
|
const minSemanticScore = input.min_semantic_score ?? 0.45;
|
|
2582
2673
|
const negative = all.filter(({ memory }) => {
|
|
2583
2674
|
const t = memory.frontmatter.type;
|
|
@@ -2685,19 +2776,19 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
2685
2776
|
}
|
|
2686
2777
|
|
|
2687
2778
|
// src/tools/mem-distill.ts
|
|
2688
|
-
import { existsSync as
|
|
2779
|
+
import { existsSync as existsSync26 } from "fs";
|
|
2689
2780
|
import {
|
|
2690
|
-
loadMemoriesFromDir as
|
|
2781
|
+
loadMemoriesFromDir as loadMemoriesFromDir20,
|
|
2691
2782
|
tokenizeQuery as tokenizeQuery4
|
|
2692
2783
|
} from "@hiveai/core";
|
|
2693
|
-
import { z as
|
|
2784
|
+
import { z as z27 } from "zod";
|
|
2694
2785
|
var MemDistillInputSchema = {
|
|
2695
|
-
since_days:
|
|
2696
|
-
min_cluster:
|
|
2697
|
-
type_filter:
|
|
2786
|
+
since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
|
|
2787
|
+
min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
|
|
2788
|
+
type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
|
|
2698
2789
|
"Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
|
|
2699
2790
|
),
|
|
2700
|
-
scope:
|
|
2791
|
+
scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
|
|
2701
2792
|
};
|
|
2702
2793
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
2703
2794
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -2737,11 +2828,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
2737
2828
|
"error"
|
|
2738
2829
|
]);
|
|
2739
2830
|
async function memDistill(input, ctx) {
|
|
2740
|
-
if (!
|
|
2831
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
2741
2832
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
2742
2833
|
}
|
|
2743
2834
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
2744
|
-
const all = await
|
|
2835
|
+
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
2745
2836
|
const candidates = all.filter(({ memory }) => {
|
|
2746
2837
|
const fm = memory.frontmatter;
|
|
2747
2838
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -2845,22 +2936,22 @@ function firstHeading(body) {
|
|
|
2845
2936
|
}
|
|
2846
2937
|
|
|
2847
2938
|
// src/tools/why-this-decision.ts
|
|
2848
|
-
import { existsSync as
|
|
2939
|
+
import { existsSync as existsSync27 } from "fs";
|
|
2849
2940
|
import { spawn as spawn2 } from "child_process";
|
|
2850
2941
|
import {
|
|
2851
2942
|
deriveConfidence as deriveConfidence7,
|
|
2852
2943
|
getUsage as getUsage9,
|
|
2853
|
-
loadMemoriesFromDir as
|
|
2944
|
+
loadMemoriesFromDir as loadMemoriesFromDir21,
|
|
2854
2945
|
loadUsageIndex as loadUsageIndex11,
|
|
2855
2946
|
pathsOverlap as singlePathsOverlap
|
|
2856
2947
|
} from "@hiveai/core";
|
|
2857
|
-
import { z as
|
|
2948
|
+
import { z as z28 } from "zod";
|
|
2858
2949
|
var WhyThisDecisionInputSchema = {
|
|
2859
|
-
id:
|
|
2860
|
-
git_log_limit:
|
|
2950
|
+
id: z28.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
|
|
2951
|
+
git_log_limit: z28.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
|
|
2861
2952
|
};
|
|
2862
2953
|
async function whyThisDecision(input, ctx) {
|
|
2863
|
-
if (!
|
|
2954
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
2864
2955
|
return {
|
|
2865
2956
|
found: false,
|
|
2866
2957
|
related: [],
|
|
@@ -2869,7 +2960,7 @@ async function whyThisDecision(input, ctx) {
|
|
|
2869
2960
|
notice: "No .ai/memories directory."
|
|
2870
2961
|
};
|
|
2871
2962
|
}
|
|
2872
|
-
const all = await
|
|
2963
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
2873
2964
|
const usage = await loadUsageIndex11(ctx.paths);
|
|
2874
2965
|
const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
|
|
2875
2966
|
if (!target) {
|
|
@@ -2992,28 +3083,28 @@ function runCommand2(cmd, args, cwd) {
|
|
|
2992
3083
|
}
|
|
2993
3084
|
|
|
2994
3085
|
// src/tools/mem-conflicts.ts
|
|
2995
|
-
import { existsSync as
|
|
3086
|
+
import { existsSync as existsSync28 } from "fs";
|
|
2996
3087
|
import {
|
|
2997
3088
|
deriveConfidence as deriveConfidence8,
|
|
2998
3089
|
getUsage as getUsage10,
|
|
2999
|
-
loadMemoriesFromDir as
|
|
3090
|
+
loadMemoriesFromDir as loadMemoriesFromDir22,
|
|
3000
3091
|
loadUsageIndex as loadUsageIndex12,
|
|
3001
3092
|
pathsOverlap as pathsOverlap2,
|
|
3002
3093
|
tokenizeQuery as tokenizeQuery5
|
|
3003
3094
|
} from "@hiveai/core";
|
|
3004
|
-
import { z as
|
|
3095
|
+
import { z as z29 } from "zod";
|
|
3005
3096
|
var MemConflictsInputSchema = {
|
|
3006
|
-
id:
|
|
3007
|
-
min_score:
|
|
3008
|
-
semantic:
|
|
3097
|
+
id: z29.string().min(1).describe("Memory id to check for conflicts."),
|
|
3098
|
+
min_score: z29.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
|
|
3099
|
+
semantic: z29.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
|
|
3009
3100
|
};
|
|
3010
3101
|
var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
|
|
3011
3102
|
var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
|
|
3012
3103
|
async function memConflicts(input, ctx) {
|
|
3013
|
-
if (!
|
|
3104
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
3014
3105
|
return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
|
|
3015
3106
|
}
|
|
3016
|
-
const all = await
|
|
3107
|
+
const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
|
|
3017
3108
|
const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
|
|
3018
3109
|
if (!target) {
|
|
3019
3110
|
return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
|
|
@@ -3125,17 +3216,17 @@ async function trySemanticSimilarities(ctx, target, others) {
|
|
|
3125
3216
|
}
|
|
3126
3217
|
|
|
3127
3218
|
// src/tools/precommit-check.ts
|
|
3128
|
-
import { z as
|
|
3219
|
+
import { z as z30 } from "zod";
|
|
3129
3220
|
var PreCommitCheckInputSchema = {
|
|
3130
|
-
diff:
|
|
3221
|
+
diff: z30.string().optional().describe(
|
|
3131
3222
|
"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`."
|
|
3132
3223
|
),
|
|
3133
|
-
paths:
|
|
3134
|
-
block_on:
|
|
3224
|
+
paths: z30.array(z30.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
|
|
3225
|
+
block_on: z30.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
|
|
3135
3226
|
"When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
|
|
3136
3227
|
),
|
|
3137
|
-
semantic:
|
|
3138
|
-
anchored_blocks:
|
|
3228
|
+
semantic: z30.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
|
|
3229
|
+
anchored_blocks: z30.boolean().default(false).describe(
|
|
3139
3230
|
"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."
|
|
3140
3231
|
)
|
|
3141
3232
|
};
|
|
@@ -3421,17 +3512,17 @@ function repairTargetPathForWarning(warning, paths) {
|
|
|
3421
3512
|
}
|
|
3422
3513
|
|
|
3423
3514
|
// src/tools/pattern-detect.ts
|
|
3424
|
-
import { mkdir as
|
|
3425
|
-
import { existsSync as
|
|
3426
|
-
import
|
|
3515
|
+
import { mkdir as mkdir8, writeFile as writeFile13 } from "fs/promises";
|
|
3516
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3517
|
+
import path12 from "path";
|
|
3427
3518
|
import { execSync as execSync2 } from "child_process";
|
|
3428
3519
|
import {
|
|
3429
3520
|
buildFrontmatter as buildFrontmatter5,
|
|
3430
|
-
memoryFilePath as
|
|
3521
|
+
memoryFilePath as memoryFilePath6,
|
|
3431
3522
|
readUsageEvents,
|
|
3432
|
-
serializeMemory as
|
|
3523
|
+
serializeMemory as serializeMemory11
|
|
3433
3524
|
} from "@hiveai/core";
|
|
3434
|
-
import { z as
|
|
3525
|
+
import { z as z31 } from "zod";
|
|
3435
3526
|
var CONFIG_PATTERNS = [
|
|
3436
3527
|
".eslintrc",
|
|
3437
3528
|
"eslint.config",
|
|
@@ -3454,12 +3545,12 @@ var CONFIG_PATTERNS = [
|
|
|
3454
3545
|
var MAX_DIFF_BYTES = 4096;
|
|
3455
3546
|
var HOT_FILE_MIN = 3;
|
|
3456
3547
|
var PatternDetectInputSchema = {
|
|
3457
|
-
since_days:
|
|
3458
|
-
dry_run:
|
|
3459
|
-
scope:
|
|
3548
|
+
since_days: z31.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
|
|
3549
|
+
dry_run: z31.boolean().default(false).describe("When true, report matches without writing any memory files."),
|
|
3550
|
+
scope: z31.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
|
|
3460
3551
|
};
|
|
3461
3552
|
async function patternDetect(input, ctx) {
|
|
3462
|
-
if (!
|
|
3553
|
+
if (!existsSync29(ctx.paths.haiveDir)) {
|
|
3463
3554
|
return {
|
|
3464
3555
|
scanned_events: 0,
|
|
3465
3556
|
matches: [],
|
|
@@ -3472,13 +3563,13 @@ async function patternDetect(input, ctx) {
|
|
|
3472
3563
|
try {
|
|
3473
3564
|
const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
|
|
3474
3565
|
const configFiles = changedFiles.filter(
|
|
3475
|
-
(f) => CONFIG_PATTERNS.some((p) =>
|
|
3566
|
+
(f) => CONFIG_PATTERNS.some((p) => path12.basename(f.toLowerCase()).includes(p))
|
|
3476
3567
|
);
|
|
3477
3568
|
for (const file of configFiles.slice(0, 5)) {
|
|
3478
3569
|
const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
|
|
3479
3570
|
if (!diff) continue;
|
|
3480
|
-
const parentDir =
|
|
3481
|
-
const baseName =
|
|
3571
|
+
const parentDir = path12.basename(path12.dirname(file));
|
|
3572
|
+
const baseName = path12.basename(file).replace(/\.[^.]+$/, "");
|
|
3482
3573
|
const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
|
|
3483
3574
|
matches.push({
|
|
3484
3575
|
kind: "config_change",
|
|
@@ -3542,7 +3633,7 @@ async function patternDetect(input, ctx) {
|
|
|
3542
3633
|
for (const [p, { count, tools }] of pathCounts) {
|
|
3543
3634
|
if (count < HOT_FILE_MIN) continue;
|
|
3544
3635
|
if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
|
|
3545
|
-
if (CONFIG_PATTERNS.some((cp) =>
|
|
3636
|
+
if (CONFIG_PATTERNS.some((cp) => path12.basename(p).includes(cp))) continue;
|
|
3546
3637
|
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
3547
3638
|
matches.push({
|
|
3548
3639
|
kind: "hot_file",
|
|
@@ -3582,17 +3673,17 @@ async function patternDetect(input, ctx) {
|
|
|
3582
3673
|
paths: match.anchor_paths,
|
|
3583
3674
|
status: "proposed"
|
|
3584
3675
|
});
|
|
3585
|
-
const file =
|
|
3676
|
+
const file = memoryFilePath6(
|
|
3586
3677
|
ctx.paths,
|
|
3587
3678
|
fm.scope === "shared" ? "team" : fm.scope,
|
|
3588
3679
|
fm.id,
|
|
3589
3680
|
void 0
|
|
3590
3681
|
);
|
|
3591
|
-
if (
|
|
3592
|
-
await
|
|
3593
|
-
await
|
|
3682
|
+
if (existsSync29(file)) continue;
|
|
3683
|
+
await mkdir8(path12.dirname(file), { recursive: true });
|
|
3684
|
+
await writeFile13(
|
|
3594
3685
|
file,
|
|
3595
|
-
|
|
3686
|
+
serializeMemory11({ frontmatter: fm, body: match.proposed_body }),
|
|
3596
3687
|
"utf8"
|
|
3597
3688
|
);
|
|
3598
3689
|
savedIds.push(fm.id);
|
|
@@ -3634,25 +3725,25 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
3634
3725
|
}
|
|
3635
3726
|
|
|
3636
3727
|
// src/tools/mem-conflict-candidates.ts
|
|
3637
|
-
import { existsSync as
|
|
3728
|
+
import { existsSync as existsSync30 } from "fs";
|
|
3638
3729
|
import {
|
|
3639
3730
|
findLexicalConflictPairs,
|
|
3640
3731
|
findTopicStatusConflictPairs,
|
|
3641
|
-
loadMemoriesFromDir as
|
|
3732
|
+
loadMemoriesFromDir as loadMemoriesFromDir23
|
|
3642
3733
|
} from "@hiveai/core";
|
|
3643
|
-
import { z as
|
|
3734
|
+
import { z as z32 } from "zod";
|
|
3644
3735
|
var MemConflictCandidatesInputSchema = {
|
|
3645
|
-
since_days:
|
|
3646
|
-
types:
|
|
3647
|
-
min_jaccard:
|
|
3648
|
-
max_pairs:
|
|
3649
|
-
max_scan:
|
|
3650
|
-
max_topic_pairs:
|
|
3736
|
+
since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
3737
|
+
types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
3738
|
+
min_jaccard: z32.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
3739
|
+
max_pairs: z32.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
3740
|
+
max_scan: z32.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
|
|
3741
|
+
max_topic_pairs: z32.number().int().positive().max(100).default(20).describe(
|
|
3651
3742
|
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
3652
3743
|
)
|
|
3653
3744
|
};
|
|
3654
3745
|
async function memConflictCandidates(input, ctx) {
|
|
3655
|
-
if (!
|
|
3746
|
+
if (!existsSync30(ctx.paths.memoriesDir)) {
|
|
3656
3747
|
return {
|
|
3657
3748
|
pairs: [],
|
|
3658
3749
|
topic_status_pairs: [],
|
|
@@ -3661,7 +3752,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
3661
3752
|
notice: "No .ai/memories directory."
|
|
3662
3753
|
};
|
|
3663
3754
|
}
|
|
3664
|
-
const all = await
|
|
3755
|
+
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
3665
3756
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
3666
3757
|
sinceDays: input.since_days,
|
|
3667
3758
|
types: input.types,
|
|
@@ -3676,9 +3767,9 @@ async function memConflictCandidates(input, ctx) {
|
|
|
3676
3767
|
|
|
3677
3768
|
// src/tools/mem-resolve-project.ts
|
|
3678
3769
|
import { resolveProjectInfo } from "@hiveai/core";
|
|
3679
|
-
import { z as
|
|
3770
|
+
import { z as z33 } from "zod";
|
|
3680
3771
|
var MemResolveProjectInputSchema = {
|
|
3681
|
-
cwd:
|
|
3772
|
+
cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
3682
3773
|
};
|
|
3683
3774
|
async function memResolveProject(input, _ctx) {
|
|
3684
3775
|
void _ctx;
|
|
@@ -3692,10 +3783,10 @@ async function memResolveProject(input, _ctx) {
|
|
|
3692
3783
|
|
|
3693
3784
|
// src/tools/mem-suggest-topic.ts
|
|
3694
3785
|
import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
|
|
3695
|
-
import { z as
|
|
3786
|
+
import { z as z34 } from "zod";
|
|
3696
3787
|
var MemSuggestTopicInputSchema = {
|
|
3697
3788
|
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
3698
|
-
title:
|
|
3789
|
+
title: z34.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
3699
3790
|
};
|
|
3700
3791
|
async function memSuggestTopic(input, _ctx) {
|
|
3701
3792
|
void _ctx;
|
|
@@ -3704,19 +3795,19 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
3704
3795
|
}
|
|
3705
3796
|
|
|
3706
3797
|
// src/tools/mem-timeline.ts
|
|
3707
|
-
import { existsSync as
|
|
3708
|
-
import { collectTimelineEntries, loadMemoriesFromDir as
|
|
3709
|
-
import { z as
|
|
3798
|
+
import { existsSync as existsSync31 } from "fs";
|
|
3799
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir24 } from "@hiveai/core";
|
|
3800
|
+
import { z as z35 } from "zod";
|
|
3710
3801
|
var MemTimelineInputSchema = {
|
|
3711
|
-
memory_id:
|
|
3712
|
-
topic:
|
|
3713
|
-
limit:
|
|
3802
|
+
memory_id: z35.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
3803
|
+
topic: z35.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
3804
|
+
limit: z35.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
3714
3805
|
};
|
|
3715
3806
|
async function memTimeline(input, ctx) {
|
|
3716
|
-
if (!
|
|
3807
|
+
if (!existsSync31(ctx.paths.memoriesDir)) {
|
|
3717
3808
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
3718
3809
|
}
|
|
3719
|
-
const all = await
|
|
3810
|
+
const all = await loadMemoriesFromDir24(ctx.paths.memoriesDir);
|
|
3720
3811
|
const { entries, notice } = collectTimelineEntries(all, {
|
|
3721
3812
|
memoryId: input.memory_id,
|
|
3722
3813
|
topic: input.topic,
|
|
@@ -3727,11 +3818,11 @@ async function memTimeline(input, ctx) {
|
|
|
3727
3818
|
|
|
3728
3819
|
// src/tools/runtime-journal-append.ts
|
|
3729
3820
|
import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
|
|
3730
|
-
import { z as
|
|
3821
|
+
import { z as z36 } from "zod";
|
|
3731
3822
|
var RuntimeJournalAppendInputSchema = {
|
|
3732
|
-
message:
|
|
3733
|
-
kind:
|
|
3734
|
-
tool:
|
|
3823
|
+
message: z36.string().min(1).describe("Short line to append to the runtime session journal"),
|
|
3824
|
+
kind: z36.enum(["note", "session_end", "mcp"]).default("note"),
|
|
3825
|
+
tool: z36.string().optional().describe("When kind=mcp, which tool name (optional)")
|
|
3735
3826
|
};
|
|
3736
3827
|
async function runtimeJournalAppend(input, ctx) {
|
|
3737
3828
|
await appendRuntimeJournalEntry2(ctx.paths, {
|
|
@@ -3747,9 +3838,9 @@ async function runtimeJournalAppend(input, ctx) {
|
|
|
3747
3838
|
|
|
3748
3839
|
// src/tools/runtime-journal-tail.ts
|
|
3749
3840
|
import { readRuntimeJournalTail } from "@hiveai/core";
|
|
3750
|
-
import { z as
|
|
3841
|
+
import { z as z37 } from "zod";
|
|
3751
3842
|
var RuntimeJournalTailInputSchema = {
|
|
3752
|
-
limit:
|
|
3843
|
+
limit: z37.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
|
|
3753
3844
|
};
|
|
3754
3845
|
async function runtimeJournalTail(input, ctx) {
|
|
3755
3846
|
const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
|
|
@@ -3760,12 +3851,12 @@ async function runtimeJournalTail(input, ctx) {
|
|
|
3760
3851
|
}
|
|
3761
3852
|
|
|
3762
3853
|
// src/prompts/bootstrap-project.ts
|
|
3763
|
-
import { z as
|
|
3854
|
+
import { z as z38 } from "zod";
|
|
3764
3855
|
var BootstrapProjectArgsSchema = {
|
|
3765
|
-
module:
|
|
3856
|
+
module: z38.string().optional().describe(
|
|
3766
3857
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
3767
3858
|
),
|
|
3768
|
-
focus:
|
|
3859
|
+
focus: z38.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
3769
3860
|
};
|
|
3770
3861
|
var ROOT_TEMPLATE = `# Project context
|
|
3771
3862
|
|
|
@@ -3847,10 +3938,10 @@ ${template}\`\`\`
|
|
|
3847
3938
|
}
|
|
3848
3939
|
|
|
3849
3940
|
// src/prompts/post-task.ts
|
|
3850
|
-
import { z as
|
|
3941
|
+
import { z as z39 } from "zod";
|
|
3851
3942
|
var PostTaskArgsSchema = {
|
|
3852
|
-
task_summary:
|
|
3853
|
-
files_touched:
|
|
3943
|
+
task_summary: z39.string().optional().describe("One sentence describing what you just did"),
|
|
3944
|
+
files_touched: z39.array(z39.string()).optional().describe("Files you created or modified during the task")
|
|
3854
3945
|
};
|
|
3855
3946
|
function postTaskPrompt(args, ctx) {
|
|
3856
3947
|
const taskLine = args.task_summary ? `
|
|
@@ -3947,12 +4038,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
3947
4038
|
}
|
|
3948
4039
|
|
|
3949
4040
|
// src/prompts/import-docs.ts
|
|
3950
|
-
import { z as
|
|
4041
|
+
import { z as z40 } from "zod";
|
|
3951
4042
|
var ImportDocsArgsSchema = {
|
|
3952
|
-
content:
|
|
3953
|
-
source:
|
|
3954
|
-
scope:
|
|
3955
|
-
dry_run:
|
|
4043
|
+
content: z40.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
4044
|
+
source: z40.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
4045
|
+
scope: z40.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
4046
|
+
dry_run: z40.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
3956
4047
|
};
|
|
3957
4048
|
function importDocsPrompt(args, ctx) {
|
|
3958
4049
|
const sourceLine = args.source ? `
|
|
@@ -4018,7 +4109,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4018
4109
|
// src/server.ts
|
|
4019
4110
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hiveai/core";
|
|
4020
4111
|
var SERVER_NAME = "haive";
|
|
4021
|
-
var SERVER_VERSION = "0.
|
|
4112
|
+
var SERVER_VERSION = "0.13.0";
|
|
4022
4113
|
function jsonResult(data) {
|
|
4023
4114
|
return {
|
|
4024
4115
|
content: [
|
|
@@ -4061,7 +4152,8 @@ var MAINTENANCE_PROFILE_TOOLS = [
|
|
|
4061
4152
|
"mem_distill",
|
|
4062
4153
|
"mem_timeline",
|
|
4063
4154
|
"mem_conflict_candidates",
|
|
4064
|
-
"mem_feedback"
|
|
4155
|
+
"mem_feedback",
|
|
4156
|
+
"ingest_findings"
|
|
4065
4157
|
];
|
|
4066
4158
|
var EXPERIMENTAL_PROFILE_TOOLS = [
|
|
4067
4159
|
...MAINTENANCE_PROFILE_TOOLS,
|
|
@@ -4095,7 +4187,8 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4095
4187
|
"mem_delete",
|
|
4096
4188
|
"mem_feedback",
|
|
4097
4189
|
"runtime_journal_append",
|
|
4098
|
-
"pattern_detect"
|
|
4190
|
+
"pattern_detect",
|
|
4191
|
+
"ingest_findings"
|
|
4099
4192
|
]);
|
|
4100
4193
|
function createHaiveServer(options = {}) {
|
|
4101
4194
|
const context = createContext(options);
|
|
@@ -4220,6 +4313,36 @@ function createHaiveServer(options = {}) {
|
|
|
4220
4313
|
return jsonResult(await memTried(input, context));
|
|
4221
4314
|
}
|
|
4222
4315
|
);
|
|
4316
|
+
registerTool(
|
|
4317
|
+
"ingest_findings",
|
|
4318
|
+
[
|
|
4319
|
+
"Turn scanner findings (SonarQube / SARIF) into proposed, anchored memories with sensors.",
|
|
4320
|
+
"",
|
|
4321
|
+
"USE THIS to seed hAIve from your existing quality tooling: each real defect a scanner",
|
|
4322
|
+
"found becomes a `gotcha`/`convention` memory anchored to the file, pre-filled with a",
|
|
4323
|
+
"conservative `warn` sensor \u2014 so the next agent is steered away from it before re-writing it.",
|
|
4324
|
+
"This closes the review\u2194memory loop and kills the cold-start problem.",
|
|
4325
|
+
"",
|
|
4326
|
+
"SAFETY: drafts are status=proposed and sensors are warn-only + autogen. This tool NEVER",
|
|
4327
|
+
"auto-validates and NEVER auto-blocks. A human reviews (mem_pending) and promotes the sensor.",
|
|
4328
|
+
"",
|
|
4329
|
+
"PARAMETERS:",
|
|
4330
|
+
" format \u2014 'sarif' (ESLint/Semgrep/CodeQL) | 'sonar' (SonarQube issues JSON)",
|
|
4331
|
+
" report_path \u2014 project-relative path to the report file (OR pass `report` inline)",
|
|
4332
|
+
" report \u2014 inline JSON content (OR pass `report_path`)",
|
|
4333
|
+
" type \u2014 gotcha (default) | convention",
|
|
4334
|
+
" scope \u2014 team (default) | personal | module",
|
|
4335
|
+
" min_severity \u2014 drop findings below this severity",
|
|
4336
|
+
" dry_run \u2014 preview what would be created without writing",
|
|
4337
|
+
"",
|
|
4338
|
+
"RETURNS: { format, parsed, new, skipped_existing, created[], notice }"
|
|
4339
|
+
].join("\n"),
|
|
4340
|
+
IngestFindingsInputSchema,
|
|
4341
|
+
async (input) => {
|
|
4342
|
+
tracker.record("ingest_findings", `${input.format}:${input.report_path ?? "inline"}`);
|
|
4343
|
+
return jsonResult(await ingestFindings(input, context));
|
|
4344
|
+
}
|
|
4345
|
+
);
|
|
4223
4346
|
registerTool(
|
|
4224
4347
|
"mem_observe",
|
|
4225
4348
|
[
|