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