@hiveai/core 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/schema.ts
2
2
  import { z } from "zod";
3
- var MemoryScopeSchema = z.enum(["personal", "team", "module"]);
3
+ var MemoryScopeSchema = z.enum(["personal", "team", "module", "shared"]);
4
4
  var MemoryStatusSchema = z.enum([
5
5
  "draft",
6
6
  "proposed",
@@ -50,6 +50,16 @@ var MemoryFrontmatterSchema = z.object({
50
50
  (data) => data.scope !== "module" || !!data.module,
51
51
  { message: "module name is required when scope is 'module'", path: ["module"] }
52
52
  );
53
+ var CrossRepoProvenanceSchema = z.object({
54
+ source_name: z.string(),
55
+ // the crossRepoSources name from haive.config.json
56
+ source_path: z.string(),
57
+ // original file path in the source repo
58
+ source_id: z.string(),
59
+ // original memory id
60
+ imported_at: z.string()
61
+ // ISO timestamp of import
62
+ }).optional();
53
63
 
54
64
  // src/parser.ts
55
65
  import matter from "gray-matter";
@@ -108,7 +118,8 @@ function buildFrontmatter(input) {
108
118
  created_at: now.toISOString(),
109
119
  expires_when: null,
110
120
  topic: input.topic,
111
- revision_count: 0
121
+ revision_count: 0,
122
+ related_ids: input.relatedIds ?? []
112
123
  });
113
124
  }
114
125
 
@@ -947,12 +958,585 @@ async function loadConfig(paths) {
947
958
  async function saveConfig(paths, config) {
948
959
  await writeFile3(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
949
960
  }
961
+
962
+ // src/cross-repo.ts
963
+ import { existsSync as existsSync6 } from "fs";
964
+ import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
965
+ import path8 from "path";
966
+ import { spawnSync } from "child_process";
967
+ async function loadImportMap(cacheDir) {
968
+ const mapPath = path8.join(cacheDir, "import-map.json");
969
+ if (!existsSync6(mapPath)) return {};
970
+ try {
971
+ return JSON.parse(await readFile6(mapPath, "utf8"));
972
+ } catch {
973
+ return {};
974
+ }
975
+ }
976
+ async function saveImportMap(cacheDir, map) {
977
+ await writeFile4(path8.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
978
+ }
979
+ async function pullCrossRepoSources(paths, config, projectRoot) {
980
+ const sources = config.crossRepoSources ?? [];
981
+ if (sources.length === 0) return [];
982
+ const reports = [];
983
+ for (const source of sources) {
984
+ reports.push(await pullFromSource(paths, source, projectRoot));
985
+ }
986
+ return reports;
987
+ }
988
+ async function pullFromSource(paths, source, projectRoot) {
989
+ const report = {
990
+ source: source.name,
991
+ imported: [],
992
+ updated: [],
993
+ skipped: [],
994
+ errors: []
995
+ };
996
+ let sourceRoot = null;
997
+ if (source.path) {
998
+ const resolved = path8.resolve(projectRoot, source.path);
999
+ if (!existsSync6(resolved)) {
1000
+ report.errors.push(`Path not found: ${resolved}`);
1001
+ return report;
1002
+ }
1003
+ sourceRoot = resolved;
1004
+ } else if (source.git) {
1005
+ sourceRoot = await cloneOrFetchGitSource(source, paths, report);
1006
+ if (!sourceRoot) return report;
1007
+ } else {
1008
+ report.errors.push(`Source "${source.name}" has neither path nor git \u2014 skipping.`);
1009
+ return report;
1010
+ }
1011
+ const sourcePaths = resolveHaivePaths(sourceRoot);
1012
+ if (!existsSync6(sourcePaths.memoriesDir)) {
1013
+ report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
1014
+ return report;
1015
+ }
1016
+ const sourceMemories = (await loadMemoriesFromDir(sourcePaths.memoriesDir)).filter(
1017
+ ({ memory }) => {
1018
+ const fm = memory.frontmatter;
1019
+ if (fm.scope !== "shared") return false;
1020
+ if (fm.status === "rejected" || fm.status === "deprecated") return false;
1021
+ if (source.filter?.tags && source.filter.tags.length > 0) {
1022
+ const hasTag = source.filter.tags.some((t) => fm.tags.includes(t));
1023
+ if (!hasTag) return false;
1024
+ }
1025
+ if (source.filter?.types && source.filter.types.length > 0) {
1026
+ if (!source.filter.types.includes(fm.type)) return false;
1027
+ }
1028
+ return true;
1029
+ }
1030
+ );
1031
+ if (sourceMemories.length === 0) {
1032
+ report.skipped.push("no shared memories found in source");
1033
+ return report;
1034
+ }
1035
+ const destDir = path8.join(paths.memoriesDir, "shared", source.name);
1036
+ await mkdir3(destDir, { recursive: true });
1037
+ const cacheDir = path8.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1038
+ await mkdir3(cacheDir, { recursive: true });
1039
+ const importMap = await loadImportMap(cacheDir);
1040
+ const mapDirty = false;
1041
+ let dirty = mapDirty;
1042
+ for (const { memory } of sourceMemories) {
1043
+ const fm = memory.frontmatter;
1044
+ const sourceId = fm.id;
1045
+ const importTag = `cross-repo:${source.name}`;
1046
+ const tags = [.../* @__PURE__ */ new Set([...fm.tags, importTag])];
1047
+ const importedBodyPrefix = `> **Imported from \`${source.name}\`** (original id: \`${sourceId}\`)
1048
+ > Imported at: ${(/* @__PURE__ */ new Date()).toISOString()}
1049
+
1050
+ `;
1051
+ const existingLocalPath = importMap[sourceId];
1052
+ if (existingLocalPath && existsSync6(existingLocalPath)) {
1053
+ const existingFiles = await loadMemoriesFromDir(destDir);
1054
+ const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
1055
+ const sourceBodyStripped = memory.body.trim();
1056
+ const existingBodyStripped = (existingEntry?.memory.body ?? "").replace(/^>.*\n>.*\n\n/m, "").trim();
1057
+ if (existingBodyStripped === sourceBodyStripped) {
1058
+ report.skipped.push(sourceId);
1059
+ continue;
1060
+ }
1061
+ const updatedBody = importedBodyPrefix + memory.body;
1062
+ if (existingEntry) {
1063
+ await writeFile4(
1064
+ existingLocalPath,
1065
+ serializeMemory({ frontmatter: existingEntry.memory.frontmatter, body: updatedBody }),
1066
+ "utf8"
1067
+ );
1068
+ }
1069
+ report.updated.push(sourceId);
1070
+ } else {
1071
+ const slug = `${source.name}-${fm.id.slice(0, 40)}`;
1072
+ const newFm = buildFrontmatter({
1073
+ type: fm.type,
1074
+ slug,
1075
+ scope: "team",
1076
+ module: void 0,
1077
+ status: "validated",
1078
+ tags,
1079
+ domain: fm.domain,
1080
+ author: fm.author,
1081
+ paths: fm.anchor.paths,
1082
+ symbols: fm.anchor.symbols,
1083
+ commit: fm.anchor.commit,
1084
+ topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
1085
+ });
1086
+ const body = importedBodyPrefix + memory.body;
1087
+ const destPath = path8.join(destDir, `${newFm.id}.md`);
1088
+ await writeFile4(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
1089
+ importMap[sourceId] = destPath;
1090
+ dirty = true;
1091
+ report.imported.push(sourceId);
1092
+ }
1093
+ }
1094
+ if (dirty) await saveImportMap(cacheDir, importMap);
1095
+ return report;
1096
+ }
1097
+ async function cloneOrFetchGitSource(source, paths, report) {
1098
+ const cacheDir = path8.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1099
+ await mkdir3(cacheDir, { recursive: true });
1100
+ if (existsSync6(path8.join(cacheDir, ".git"))) {
1101
+ const result = spawnSync("git", ["fetch", "--depth=1", "origin"], {
1102
+ cwd: cacheDir,
1103
+ encoding: "utf8"
1104
+ });
1105
+ if (result.status !== 0) {
1106
+ report.errors.push(`git fetch failed for ${source.name}: ${result.stderr}`);
1107
+ return null;
1108
+ }
1109
+ spawnSync("git", ["reset", "--hard", "FETCH_HEAD"], { cwd: cacheDir });
1110
+ } else {
1111
+ const result = spawnSync(
1112
+ "git",
1113
+ ["clone", "--depth=1", source.git, "."],
1114
+ { cwd: cacheDir, encoding: "utf8" }
1115
+ );
1116
+ if (result.status !== 0) {
1117
+ report.errors.push(`git clone failed for ${source.name}: ${result.stderr}`);
1118
+ return null;
1119
+ }
1120
+ }
1121
+ return cacheDir;
1122
+ }
1123
+
1124
+ // src/dep-tracker.ts
1125
+ import { existsSync as existsSync7 } from "fs";
1126
+ import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
1127
+ import path9 from "path";
1128
+ function parsePackageJson(content) {
1129
+ try {
1130
+ const pkg = JSON.parse(content);
1131
+ return {
1132
+ ...pkg.dependencies,
1133
+ ...pkg.devDependencies,
1134
+ ...pkg.peerDependencies
1135
+ };
1136
+ } catch {
1137
+ return {};
1138
+ }
1139
+ }
1140
+ function parseGoMod(content) {
1141
+ const result = {};
1142
+ for (const line of content.split("\n")) {
1143
+ const m = line.trim().match(/^(\S+)\s+(v[\d.]+)/);
1144
+ if (m?.[1] && m[2]) result[m[1]] = m[2];
1145
+ }
1146
+ return result;
1147
+ }
1148
+ function parseRequirementsTxt(content) {
1149
+ const result = {};
1150
+ for (const line of content.split("\n")) {
1151
+ const clean = (line.split("#")[0] ?? "").trim();
1152
+ const m = clean.match(/^([A-Za-z0-9_.-]+)[=><~!]+(.+)$/);
1153
+ if (m?.[1] && m[2]) result[m[1].toLowerCase()] = m[2].trim();
1154
+ }
1155
+ return result;
1156
+ }
1157
+ function parseCargotoml(content) {
1158
+ const result = {};
1159
+ let inDeps = false;
1160
+ for (const line of content.split("\n")) {
1161
+ if (/^\[(dependencies|dev-dependencies|build-dependencies)\]/.test(line.trim())) {
1162
+ inDeps = true;
1163
+ continue;
1164
+ }
1165
+ if (line.startsWith("[") && !line.includes("dependencies")) {
1166
+ inDeps = false;
1167
+ continue;
1168
+ }
1169
+ if (!inDeps) continue;
1170
+ const simple = line.match(/^(\w[\w-]*)\s*=\s*"([^"]+)"/);
1171
+ if (simple?.[1] && simple[2]) {
1172
+ result[simple[1]] = simple[2];
1173
+ continue;
1174
+ }
1175
+ const table = line.match(/^(\w[\w-]*)\s*=\s*\{[^}]*version\s*=\s*"([^"]+)"/);
1176
+ if (table?.[1] && table[2]) result[table[1]] = table[2];
1177
+ }
1178
+ return result;
1179
+ }
1180
+ function parsePomXml(content) {
1181
+ const result = {};
1182
+ const depRe = /<dependency>[\s\S]*?<groupId>([^<]+)<\/groupId>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?<version>([^<]+)<\/version>[\s\S]*?<\/dependency>/g;
1183
+ let m;
1184
+ while ((m = depRe.exec(content)) !== null) {
1185
+ if (m[1] && m[2] && m[3]) {
1186
+ result[`${m[1].trim()}:${m[2].trim()}`] = m[3].trim();
1187
+ }
1188
+ }
1189
+ return result;
1190
+ }
1191
+ var KNOWN_MANIFESTS = [
1192
+ { name: "package.json", parser: parsePackageJson },
1193
+ { name: "go.mod", parser: parseGoMod },
1194
+ { name: "requirements.txt", parser: parseRequirementsTxt },
1195
+ { name: "Cargo.toml", parser: parseCargotoml },
1196
+ { name: "pom.xml", parser: parsePomXml }
1197
+ ];
1198
+ function getParser(file) {
1199
+ const base = path9.basename(file);
1200
+ return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
1201
+ }
1202
+ function extractMajor(version) {
1203
+ const clean = version.replace(/^[^0-9]*/, "");
1204
+ const firstPart = clean.split(".")[0];
1205
+ if (!firstPart) return null;
1206
+ const n = parseInt(firstPart, 10);
1207
+ return isNaN(n) ? null : n;
1208
+ }
1209
+ function isMajorBump(from, to) {
1210
+ const fromMajor = extractMajor(from);
1211
+ const toMajor = extractMajor(to);
1212
+ if (fromMajor === null || toMajor === null) return false;
1213
+ return toMajor > fromMajor;
1214
+ }
1215
+ function resolveManifestFiles(projectRoot, configuredFiles) {
1216
+ if (configuredFiles !== void 0) {
1217
+ return configuredFiles.map((f) => path9.resolve(projectRoot, f)).filter(existsSync7);
1218
+ }
1219
+ return KNOWN_MANIFESTS.map(({ name }) => path9.join(projectRoot, name)).filter(existsSync7);
1220
+ }
1221
+ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
1222
+ const contractsDir = path9.join(haiveDir, "contracts");
1223
+ await mkdir4(contractsDir, { recursive: true });
1224
+ const results = [];
1225
+ for (const manifestPath of manifestFiles) {
1226
+ const parser = getParser(manifestPath);
1227
+ if (!parser) continue;
1228
+ const content = await readFile7(manifestPath, "utf8");
1229
+ const currentDeps = parser(content);
1230
+ const lockName = `deps-${path9.basename(manifestPath)}.lock`;
1231
+ const lockPath = path9.join(contractsDir, lockName);
1232
+ if (!existsSync7(lockPath)) {
1233
+ const snapshot2 = {
1234
+ file: path9.relative(projectRoot, manifestPath),
1235
+ format: path9.basename(manifestPath),
1236
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1237
+ deps: currentDeps
1238
+ };
1239
+ await writeFile5(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
1240
+ continue;
1241
+ }
1242
+ const snapshot = JSON.parse(await readFile7(lockPath, "utf8"));
1243
+ const changes = [];
1244
+ for (const [name, currentVer] of Object.entries(currentDeps)) {
1245
+ const prevVer = snapshot.deps[name];
1246
+ if (prevVer && prevVer !== currentVer) {
1247
+ changes.push({
1248
+ name,
1249
+ from: prevVer,
1250
+ to: currentVer,
1251
+ isMajorBump: isMajorBump(prevVer, currentVer)
1252
+ });
1253
+ }
1254
+ }
1255
+ if (changes.length > 0) {
1256
+ results.push({ file: path9.relative(projectRoot, manifestPath), changes });
1257
+ const updated = {
1258
+ ...snapshot,
1259
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1260
+ deps: currentDeps
1261
+ };
1262
+ await writeFile5(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
1263
+ }
1264
+ }
1265
+ return results;
1266
+ }
1267
+
1268
+ // src/contract-watcher.ts
1269
+ import { existsSync as existsSync8 } from "fs";
1270
+ import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
1271
+ import path10 from "path";
1272
+ import crypto from "crypto";
1273
+ function sha256(content) {
1274
+ return crypto.createHash("sha256").update(content).digest("hex");
1275
+ }
1276
+ function parseOpenApi(content, file) {
1277
+ try {
1278
+ let doc;
1279
+ if (file.endsWith(".yaml") || file.endsWith(".yml")) {
1280
+ doc = parseYamlPaths(content);
1281
+ } else {
1282
+ doc = JSON.parse(content);
1283
+ }
1284
+ const paths = doc.paths ?? {};
1285
+ const endpoints = [];
1286
+ for (const [routePath, methods] of Object.entries(paths)) {
1287
+ for (const method of Object.keys(methods)) {
1288
+ if (["get", "post", "put", "patch", "delete", "head", "options"].includes(method)) {
1289
+ endpoints.push(`${method.toUpperCase()} ${routePath}`);
1290
+ }
1291
+ }
1292
+ }
1293
+ const schemas = doc.components?.schemas ?? doc.definitions ?? {};
1294
+ const types = Object.keys(schemas);
1295
+ const fields = {};
1296
+ for (const [typeName, schema] of Object.entries(schemas)) {
1297
+ const props = schema.properties ?? {};
1298
+ fields[typeName] = Object.keys(props);
1299
+ }
1300
+ return { endpoints, types, fields };
1301
+ } catch {
1302
+ return {};
1303
+ }
1304
+ }
1305
+ function parseYamlPaths(content) {
1306
+ const result = {};
1307
+ let currentPath = "";
1308
+ for (const line of content.split("\n")) {
1309
+ const pathMatch = line.match(/^ (\/[^\s:]+):/);
1310
+ if (pathMatch?.[1]) {
1311
+ currentPath = pathMatch[1];
1312
+ result[currentPath] = {};
1313
+ continue;
1314
+ }
1315
+ if (currentPath) {
1316
+ const methodMatch = line.match(/^ (get|post|put|patch|delete|head|options):/);
1317
+ if (methodMatch?.[1]) (result[currentPath] ??= {})[methodMatch[1]] = true;
1318
+ }
1319
+ }
1320
+ return { paths: result };
1321
+ }
1322
+ function parseGraphQL(content) {
1323
+ const types = [];
1324
+ const fields = {};
1325
+ let currentType = "";
1326
+ for (const line of content.split("\n")) {
1327
+ const typeMatch = line.match(/^(?:type|interface|union|enum|input)\s+(\w+)/);
1328
+ if (typeMatch?.[1]) {
1329
+ currentType = typeMatch[1];
1330
+ types.push(currentType);
1331
+ fields[currentType] = [];
1332
+ continue;
1333
+ }
1334
+ if (currentType && line.trim().startsWith("}")) {
1335
+ currentType = "";
1336
+ continue;
1337
+ }
1338
+ if (currentType) {
1339
+ const fieldMatch = line.match(/^\s+(\w+)\s*[:(]/);
1340
+ if (fieldMatch?.[1]) (fields[currentType] ??= []).push(fieldMatch[1]);
1341
+ }
1342
+ }
1343
+ return { types, fields };
1344
+ }
1345
+ function parseProto(content) {
1346
+ const types = [];
1347
+ const fields = {};
1348
+ let currentMsg = "";
1349
+ for (const line of content.split("\n")) {
1350
+ const msgMatch = line.match(/^(?:message|service|enum)\s+(\w+)/);
1351
+ if (msgMatch?.[1]) {
1352
+ currentMsg = msgMatch[1];
1353
+ types.push(currentMsg);
1354
+ fields[currentMsg] = [];
1355
+ continue;
1356
+ }
1357
+ if (currentMsg && line.trim() === "}") {
1358
+ currentMsg = "";
1359
+ continue;
1360
+ }
1361
+ if (currentMsg) {
1362
+ const fieldMatch = line.match(/^\s+(?:optional|required|repeated)?\s*\w+\s+(\w+)\s*=/);
1363
+ if (fieldMatch?.[1]) (fields[currentMsg] ??= []).push(fieldMatch[1]);
1364
+ const rpcMatch = line.match(/^\s+rpc\s+(\w+)/);
1365
+ if (rpcMatch?.[1]) (fields[currentMsg] ??= []).push(`rpc:${rpcMatch[1]}`);
1366
+ }
1367
+ }
1368
+ return { types, fields };
1369
+ }
1370
+ function parseTypescript(content) {
1371
+ const types = [];
1372
+ const fields = {};
1373
+ let currentType = "";
1374
+ let braceDepth = 0;
1375
+ for (const line of content.split("\n")) {
1376
+ const exportMatch = line.match(/^export\s+(?:declare\s+)?(?:interface|class|type|enum)\s+(\w+)/);
1377
+ if (exportMatch?.[1]) {
1378
+ currentType = exportMatch[1];
1379
+ types.push(currentType);
1380
+ fields[currentType] = [];
1381
+ }
1382
+ if (currentType) {
1383
+ braceDepth += (line.match(/{/g) ?? []).length;
1384
+ braceDepth -= (line.match(/}/g) ?? []).length;
1385
+ if (braceDepth <= 0 && line.includes("}")) {
1386
+ currentType = "";
1387
+ braceDepth = 0;
1388
+ continue;
1389
+ }
1390
+ const memberMatch = line.match(/^\s+(?:readonly\s+)?(\w+)\s*[?:(!]/);
1391
+ if (memberMatch?.[1] && currentType) (fields[currentType] ??= []).push(memberMatch[1]);
1392
+ }
1393
+ }
1394
+ return { types, fields };
1395
+ }
1396
+ function parseByFormat(content, format, filePath) {
1397
+ switch (format) {
1398
+ case "openapi":
1399
+ return parseOpenApi(content, filePath);
1400
+ case "graphql":
1401
+ return parseGraphQL(content);
1402
+ case "proto":
1403
+ return parseProto(content);
1404
+ case "typescript":
1405
+ return parseTypescript(content);
1406
+ case "json-schema": {
1407
+ try {
1408
+ const schema = JSON.parse(content);
1409
+ const types = Object.keys(schema.definitions ?? schema.properties ?? {});
1410
+ return { types };
1411
+ } catch {
1412
+ return {};
1413
+ }
1414
+ }
1415
+ default:
1416
+ return {};
1417
+ }
1418
+ }
1419
+ function diffLists(before, after, kind) {
1420
+ const changes = [];
1421
+ const beforeSet = new Set(before);
1422
+ const afterSet = new Set(after);
1423
+ for (const item of beforeSet) {
1424
+ if (!afterSet.has(item)) {
1425
+ const isBreaking = kind === "endpoint" || kind === "field" || kind === "type";
1426
+ changes.push({
1427
+ kind: `${kind}_removed`,
1428
+ description: `${kind} removed: ${item}`,
1429
+ severity: isBreaking ? "breaking" : "unknown"
1430
+ });
1431
+ }
1432
+ }
1433
+ for (const item of afterSet) {
1434
+ if (!beforeSet.has(item)) {
1435
+ changes.push({
1436
+ kind: `${kind}_added`,
1437
+ description: `${kind} added: ${item}`,
1438
+ severity: "additive"
1439
+ });
1440
+ }
1441
+ }
1442
+ return changes;
1443
+ }
1444
+ function diffSnapshots(before, after) {
1445
+ if (before.hash === after.hash) return [];
1446
+ const changes = [];
1447
+ if (before.endpoints && after.endpoints) {
1448
+ changes.push(...diffLists(before.endpoints, after.endpoints, "endpoint"));
1449
+ }
1450
+ if (before.types && after.types) {
1451
+ changes.push(...diffLists(before.types, after.types, "type"));
1452
+ }
1453
+ if (before.fields && after.fields) {
1454
+ const allTypes = /* @__PURE__ */ new Set([...Object.keys(before.fields), ...Object.keys(after.fields)]);
1455
+ for (const typeName of allTypes) {
1456
+ const beforeFields = before.fields[typeName] ?? [];
1457
+ const afterFields = after.fields[typeName] ?? [];
1458
+ const fieldChanges = diffLists(beforeFields, afterFields, "field");
1459
+ for (const fc of fieldChanges) {
1460
+ changes.push({ ...fc, description: `${typeName}.${fc.description}` });
1461
+ }
1462
+ }
1463
+ }
1464
+ if (changes.length === 0) {
1465
+ changes.push({
1466
+ kind: "content_changed",
1467
+ description: "Contract file content changed (no structured diff available)",
1468
+ severity: "unknown"
1469
+ });
1470
+ }
1471
+ return changes;
1472
+ }
1473
+ function contractLockPath(haiveDir, name) {
1474
+ return path10.join(haiveDir, "contracts", `${name}.lock`);
1475
+ }
1476
+ async function snapshotContract(projectRoot, haiveDir, contract) {
1477
+ const filePath = path10.resolve(projectRoot, contract.path);
1478
+ if (!existsSync8(filePath)) {
1479
+ throw new Error(`Contract file not found: ${filePath}`);
1480
+ }
1481
+ const content = await readFile8(filePath, "utf8");
1482
+ const parsed = parseByFormat(content, contract.format, filePath);
1483
+ const snapshot = {
1484
+ name: contract.name,
1485
+ path: contract.path,
1486
+ format: contract.format,
1487
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1488
+ hash: sha256(content),
1489
+ ...parsed
1490
+ };
1491
+ const contractsDir = path10.join(haiveDir, "contracts");
1492
+ await mkdir5(contractsDir, { recursive: true });
1493
+ await writeFile6(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
1494
+ return snapshot;
1495
+ }
1496
+ async function diffContract(projectRoot, haiveDir, contract) {
1497
+ const filePath = path10.resolve(projectRoot, contract.path);
1498
+ if (!existsSync8(filePath)) {
1499
+ return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
1500
+ }
1501
+ const lockPath = contractLockPath(haiveDir, contract.name);
1502
+ if (!existsSync8(lockPath)) {
1503
+ await snapshotContract(projectRoot, haiveDir, contract);
1504
+ return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
1505
+ }
1506
+ const content = await readFile8(filePath, "utf8");
1507
+ const beforeSnapshot = JSON.parse(await readFile8(lockPath, "utf8"));
1508
+ const afterParsed = parseByFormat(content, contract.format, filePath);
1509
+ const afterSnapshot = {
1510
+ ...beforeSnapshot,
1511
+ hash: sha256(content),
1512
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1513
+ ...afterParsed
1514
+ };
1515
+ const changes = diffSnapshots(beforeSnapshot, afterSnapshot);
1516
+ if (changes.length > 0) {
1517
+ await writeFile6(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
1518
+ }
1519
+ return {
1520
+ contract: contract.name,
1521
+ file: contract.path,
1522
+ changes,
1523
+ unchanged: changes.length === 0
1524
+ };
1525
+ }
1526
+ async function watchContracts(projectRoot, haiveDir, contractFiles) {
1527
+ const results = [];
1528
+ for (const contract of contractFiles) {
1529
+ results.push(await diffContract(projectRoot, haiveDir, contract));
1530
+ }
1531
+ return results.filter((r) => !r.unchanged);
1532
+ }
950
1533
  export {
951
1534
  AUTOPILOT_DEFAULTS,
952
1535
  AnchorSchema,
953
1536
  CHARS_PER_TOKEN,
954
1537
  CODE_MAP_FILE,
955
1538
  CONFIG_FILE,
1539
+ CrossRepoProvenanceSchema,
956
1540
  DECAY_DAYS,
957
1541
  DEFAULT_AUTO_PROMOTE_RULE,
958
1542
  DEFAULT_CONFIDENCE_THRESHOLDS,
@@ -971,7 +1555,9 @@ export {
971
1555
  bumpRead,
972
1556
  codeMapPath,
973
1557
  configPath,
1558
+ contractLockPath,
974
1559
  deriveConfidence,
1560
+ diffContract,
975
1561
  emptyUsage,
976
1562
  emptyUsageIndex,
977
1563
  estimateTokens,
@@ -995,19 +1581,24 @@ export {
995
1581
  parseMemory,
996
1582
  pathsOverlap,
997
1583
  pickSnippetNeedle,
1584
+ pullCrossRepoSources,
998
1585
  queryCodeMap,
999
1586
  recordRejection,
1000
1587
  relPathFrom,
1001
1588
  resolveHaivePaths,
1589
+ resolveManifestFiles,
1002
1590
  saveCodeMap,
1003
1591
  saveConfig,
1004
1592
  saveUsageIndex,
1005
1593
  serializeMemory,
1594
+ snapshotContract,
1006
1595
  stripPrivate,
1007
1596
  tokenizeQuery,
1597
+ trackDependencies,
1008
1598
  trackReads,
1009
1599
  truncateToTokens,
1010
1600
  usagePath,
1011
- verifyAnchor
1601
+ verifyAnchor,
1602
+ watchContracts
1012
1603
  };
1013
1604
  //# sourceMappingURL=index.js.map