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