@hiveai/core 0.3.2 → 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.d.ts +157 -7
- package/dist/index.js +594 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|