@openclawbrain/cli 0.4.34 → 0.4.36
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/src/cli.d.ts +69 -1
- package/dist/src/cli.js +817 -4
- package/dist/src/graphify-compiled-artifacts.d.ts +127 -0
- package/dist/src/graphify-compiled-artifacts.js +1185 -0
- package/dist/src/graphify-import-slice.js +1091 -0
- package/dist/src/graphify-lints.js +977 -0
- package/dist/src/graphify-maintenance-diff.d.ts +167 -0
- package/dist/src/graphify-maintenance-diff.js +1288 -0
- package/dist/src/graphify-runner.js +745 -0
- package/dist/src/import-export.d.ts +127 -0
- package/dist/src/import-export.js +938 -26
- package/dist/src/index.js +4 -2
- package/dist/src/session-store.js +37 -0
- package/dist/src/session-tail.js +111 -2
- package/package.json +9 -9
|
@@ -4,12 +4,35 @@
|
|
|
4
4
|
* export: tar + gzip the entire activation root → output.tar.gz
|
|
5
5
|
* import: extract tar.gz → activation root, with safety checks
|
|
6
6
|
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
7
8
|
import { execSync } from "node:child_process";
|
|
8
|
-
import { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
9
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
9
10
|
import path from "node:path";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import { canonicalJson } from "@openclawbrain/contracts";
|
|
12
|
+
import { buildOpenClawSessionCorpusSnapshot } from "./session-tail.js";
|
|
13
|
+
import { discoverOpenClawHomes, inspectOpenClawHome } from "./openclaw-home-layout.js";
|
|
14
|
+
import { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
15
|
+
import { inspectOpenClawBrainHookStatus } from "./openclaw-hook-truth.js";
|
|
16
|
+
import { listOpenClawProfileRuntimeLoadProofs, resolveAttachmentRuntimeLoadProofsPath } from "./attachment-truth.js";
|
|
17
|
+
import { buildGraphifyCompiledArtifactPack, writeGraphifyCompiledArtifactPack } from "./graphify-compiled-artifacts.js";
|
|
18
|
+
import { buildGraphifyImportSlice, resolveGraphifyImportSliceOutputDir, writeGraphifyImportSliceBundle } from "./graphify-import-slice.js";
|
|
19
|
+
import { buildGraphifyMaintenanceDiffBundle, writeGraphifyMaintenanceDiffBundle } from "./graphify-maintenance-diff.js";
|
|
20
|
+
|
|
21
|
+
function hashCanonicalJson(value) {
|
|
22
|
+
return `sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
|
|
23
|
+
}
|
|
24
|
+
function writeCanonicalJsonFile(filePath, value) {
|
|
25
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
26
|
+
const text = canonicalJson(value);
|
|
27
|
+
writeFileSync(filePath, `${text}\n`, "utf8");
|
|
28
|
+
return {
|
|
29
|
+
path: filePath,
|
|
30
|
+
digest: `sha256:${createHash("sha256").update(text).digest("hex")}`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function normalizeGraphifyOutputDir(outputDir) {
|
|
34
|
+
return path.resolve(outputDir);
|
|
35
|
+
}
|
|
13
36
|
function validateActivationRoot(activationRoot) {
|
|
14
37
|
if (!existsSync(activationRoot)) {
|
|
15
38
|
throw new Error(`Activation root does not exist: ${activationRoot}`);
|
|
@@ -20,36 +43,383 @@ function validateActivationRoot(activationRoot) {
|
|
|
20
43
|
`This doesn't look like a valid activation root.`);
|
|
21
44
|
}
|
|
22
45
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Check if a tar.gz archive contains activation-pointers.json at the top level.
|
|
25
|
-
*/
|
|
26
46
|
function archiveContainsPointers(archivePath) {
|
|
27
47
|
try {
|
|
28
48
|
const listing = execSync(`tar tzf ${JSON.stringify(archivePath)}`, {
|
|
29
49
|
encoding: "utf8",
|
|
30
50
|
maxBuffer: 10 * 1024 * 1024,
|
|
31
51
|
});
|
|
32
|
-
const entries = listing.split("\n").map((
|
|
33
|
-
return entries.some((
|
|
52
|
+
const entries = listing.split("\n").map((entry) => entry.replace(/^\.\//, ""));
|
|
53
|
+
return entries.some((entry) => entry === "activation-pointers.json" || entry.endsWith("/activation-pointers.json"));
|
|
34
54
|
}
|
|
35
55
|
catch {
|
|
36
56
|
return false;
|
|
37
57
|
}
|
|
38
58
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Check if activation root already has meaningful data.
|
|
41
|
-
*/
|
|
42
59
|
function activationRootHasData(activationRoot) {
|
|
43
|
-
if (!existsSync(activationRoot))
|
|
60
|
+
if (!existsSync(activationRoot)) {
|
|
44
61
|
return false;
|
|
62
|
+
}
|
|
45
63
|
try {
|
|
46
|
-
|
|
47
|
-
return entries.length > 0;
|
|
64
|
+
return readdirSync(activationRoot).length > 0;
|
|
48
65
|
}
|
|
49
66
|
catch {
|
|
50
67
|
return false;
|
|
51
68
|
}
|
|
52
69
|
}
|
|
70
|
+
function normalizeOptionalString(value) {
|
|
71
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
72
|
+
}
|
|
73
|
+
function timestampToken(value = new Date().toISOString()) {
|
|
74
|
+
return String(value).replace(/[:]/g, "-");
|
|
75
|
+
}
|
|
76
|
+
function sha256Text(text) {
|
|
77
|
+
return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
|
|
78
|
+
}
|
|
79
|
+
function stableJson(value) {
|
|
80
|
+
return canonicalJson(value);
|
|
81
|
+
}
|
|
82
|
+
function writeTextFile(filePath, text) {
|
|
83
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
84
|
+
writeFileSync(filePath, text, "utf8");
|
|
85
|
+
return filePath;
|
|
86
|
+
}
|
|
87
|
+
function readTextIfExists(filePath) {
|
|
88
|
+
return existsSync(filePath) ? readFileSync(filePath, "utf8") : null;
|
|
89
|
+
}
|
|
90
|
+
function normalizeStableRelativePath(rootPath, candidatePath) {
|
|
91
|
+
const relativePath = path.relative(rootPath, candidatePath);
|
|
92
|
+
if (relativePath.length === 0) {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
return relativePath.split(path.sep).join(path.posix.sep);
|
|
96
|
+
}
|
|
97
|
+
function hashStablePathTreeEntry(digest, entry) {
|
|
98
|
+
digest.update(`${entry.kind}\u0000${entry.path}\u0000`);
|
|
99
|
+
if (entry.kind === "file") {
|
|
100
|
+
digest.update(`${entry.size}\u0000${entry.hash}\n`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (entry.kind === "symlink") {
|
|
104
|
+
digest.update(`${entry.target}\n`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
digest.update("\n");
|
|
108
|
+
}
|
|
109
|
+
function walkStablePathTree(inputPath, rootPath, entries, digest, totals) {
|
|
110
|
+
const dirents = readdirSync(inputPath, { withFileTypes: true })
|
|
111
|
+
.slice()
|
|
112
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
113
|
+
for (const dirent of dirents) {
|
|
114
|
+
const absolutePath = path.join(inputPath, dirent.name);
|
|
115
|
+
const relativePath = normalizeStableRelativePath(rootPath, absolutePath);
|
|
116
|
+
const fileStat = lstatSync(absolutePath);
|
|
117
|
+
if (fileStat.isSymbolicLink()) {
|
|
118
|
+
const target = readlinkSync(absolutePath);
|
|
119
|
+
const entry = { kind: "symlink", path: relativePath, target };
|
|
120
|
+
entries.push(entry);
|
|
121
|
+
totals.symlinkCount += 1;
|
|
122
|
+
hashStablePathTreeEntry(digest, entry);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (fileStat.isDirectory()) {
|
|
126
|
+
const entry = { kind: "directory", path: relativePath };
|
|
127
|
+
entries.push(entry);
|
|
128
|
+
totals.directoryCount += 1;
|
|
129
|
+
hashStablePathTreeEntry(digest, entry);
|
|
130
|
+
walkStablePathTree(absolutePath, rootPath, entries, digest, totals);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (fileStat.isFile()) {
|
|
134
|
+
const fileBuffer = readFileSync(absolutePath);
|
|
135
|
+
const fileHash = createHash("sha256").update(fileBuffer).digest("hex");
|
|
136
|
+
const entry = { kind: "file", path: relativePath, size: fileStat.size, hash: fileHash };
|
|
137
|
+
entries.push(entry);
|
|
138
|
+
totals.fileCount += 1;
|
|
139
|
+
totals.totalBytes += fileStat.size;
|
|
140
|
+
hashStablePathTreeEntry(digest, entry);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Describe a file tree using stable relative paths and a deterministic SHA-256 hash.
|
|
146
|
+
*
|
|
147
|
+
* The hash only depends on relative paths, file contents, and symlink targets.
|
|
148
|
+
*/
|
|
149
|
+
export function describeStablePathTree(inputPath) {
|
|
150
|
+
const resolvedPath = path.resolve(inputPath);
|
|
151
|
+
if (!existsSync(resolvedPath)) {
|
|
152
|
+
throw new Error(`Path does not exist: ${resolvedPath}`);
|
|
153
|
+
}
|
|
154
|
+
const digest = createHash("sha256");
|
|
155
|
+
const entries = [];
|
|
156
|
+
const totals = {
|
|
157
|
+
fileCount: 0,
|
|
158
|
+
directoryCount: 0,
|
|
159
|
+
symlinkCount: 0,
|
|
160
|
+
totalBytes: 0,
|
|
161
|
+
};
|
|
162
|
+
const stats = lstatSync(resolvedPath);
|
|
163
|
+
if (stats.isSymbolicLink()) {
|
|
164
|
+
const target = readlinkSync(resolvedPath);
|
|
165
|
+
const entry = { kind: "symlink", path: path.basename(resolvedPath), target };
|
|
166
|
+
entries.push(entry);
|
|
167
|
+
totals.symlinkCount += 1;
|
|
168
|
+
hashStablePathTreeEntry(digest, entry);
|
|
169
|
+
}
|
|
170
|
+
else if (stats.isFile()) {
|
|
171
|
+
const fileBuffer = readFileSync(resolvedPath);
|
|
172
|
+
const fileHash = createHash("sha256").update(fileBuffer).digest("hex");
|
|
173
|
+
const entry = { kind: "file", path: path.basename(resolvedPath), size: stats.size, hash: fileHash };
|
|
174
|
+
entries.push(entry);
|
|
175
|
+
totals.fileCount += 1;
|
|
176
|
+
totals.totalBytes += stats.size;
|
|
177
|
+
hashStablePathTreeEntry(digest, entry);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const entry = { kind: "directory", path: "." };
|
|
181
|
+
entries.push(entry);
|
|
182
|
+
totals.directoryCount += 1;
|
|
183
|
+
hashStablePathTreeEntry(digest, entry);
|
|
184
|
+
walkStablePathTree(resolvedPath, resolvedPath, entries, digest, totals);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
path: resolvedPath,
|
|
188
|
+
kind: stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "symlink",
|
|
189
|
+
hash: digest.digest("hex"),
|
|
190
|
+
entries,
|
|
191
|
+
...totals,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function ensureDir(dirPath) {
|
|
195
|
+
mkdirSync(dirPath, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
function tryMirrorTree(sourceRoot, destinationRoot) {
|
|
198
|
+
rmSync(destinationRoot, { recursive: true, force: true });
|
|
199
|
+
try {
|
|
200
|
+
symlinkSync(sourceRoot, destinationRoot, "dir");
|
|
201
|
+
return { path: destinationRoot, mode: "symlink" };
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
cpSync(sourceRoot, destinationRoot, { recursive: true });
|
|
205
|
+
return { path: destinationRoot, mode: "copy" };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function buildProjectionMarkdown(options) {
|
|
209
|
+
const extraDetails = Array.isArray(options.extraDetails) ? options.extraDetails.filter((line) => typeof line === "string" && line.trim().length > 0) : [];
|
|
210
|
+
const body = [
|
|
211
|
+
`# ${options.title}`,
|
|
212
|
+
"",
|
|
213
|
+
"Projection-only surface; non-authoritative by design.",
|
|
214
|
+
"",
|
|
215
|
+
`- kind: ${options.kind}`,
|
|
216
|
+
`- bundle root: \`${path.resolve(options.bundleRoot)}\``,
|
|
217
|
+
`- source bundle hash: \`${options.sourceBundleHash}\``,
|
|
218
|
+
`- canonical archive: \`${path.relative(options.bundleRoot, options.canonicalArchivePath)}\``,
|
|
219
|
+
`- generated at: \`${options.generatedAt}\``,
|
|
220
|
+
`- source path: \`${options.sourcePath}\``,
|
|
221
|
+
...extraDetails.map((line) => `- ${line}`),
|
|
222
|
+
"",
|
|
223
|
+
"## Source projection",
|
|
224
|
+
"",
|
|
225
|
+
options.sourceText === null ? "_Source unavailable._" : options.sourceText.replace(/\n?$/u, ""),
|
|
226
|
+
""
|
|
227
|
+
];
|
|
228
|
+
return body.join("\n");
|
|
229
|
+
}
|
|
230
|
+
function writeProjectionSurface(filePath, text) {
|
|
231
|
+
return writeTextFile(filePath, text);
|
|
232
|
+
}
|
|
233
|
+
function resolveGraphifySourceBundleOpenClawHome(options = {}) {
|
|
234
|
+
const explicitOpenClawHome = typeof options.openclawHome === "string" && options.openclawHome.trim().length > 0
|
|
235
|
+
? path.resolve(options.openclawHome)
|
|
236
|
+
: null;
|
|
237
|
+
if (explicitOpenClawHome !== null) {
|
|
238
|
+
return explicitOpenClawHome;
|
|
239
|
+
}
|
|
240
|
+
const explicitProfileRoots = Array.isArray(options.profileRoots)
|
|
241
|
+
? options.profileRoots.filter((root) => typeof root === "string" && root.trim().length > 0).map((root) => path.resolve(root))
|
|
242
|
+
: [];
|
|
243
|
+
if (explicitProfileRoots.length === 1) {
|
|
244
|
+
return explicitProfileRoots[0];
|
|
245
|
+
}
|
|
246
|
+
if (explicitProfileRoots.length > 1) {
|
|
247
|
+
throw new Error("graphify source bundle export expects one OpenClaw home; pass --openclaw-home to disambiguate multiple profile roots");
|
|
248
|
+
}
|
|
249
|
+
const homeDir = path.resolve(options.homeDir ?? process.env.HOME ?? process.env.USERPROFILE ?? path.resolve("."));
|
|
250
|
+
const discoveredHomes = discoverOpenClawHomes(homeDir);
|
|
251
|
+
if (discoveredHomes.length === 1) {
|
|
252
|
+
return discoveredHomes[0].openclawHome;
|
|
253
|
+
}
|
|
254
|
+
if (discoveredHomes.length === 0) {
|
|
255
|
+
throw new Error(`No OpenClaw home found beneath ${homeDir}. Pass --openclaw-home <path> to export a canonical source bundle.`);
|
|
256
|
+
}
|
|
257
|
+
throw new Error(`Multiple OpenClaw homes were discovered beneath ${homeDir}. Pass --openclaw-home <path> to select one.`);
|
|
258
|
+
}
|
|
259
|
+
function resolveGraphifySourceBundleActivationRoot(openclawHome, explicitActivationRoot) {
|
|
260
|
+
const normalizedExplicitActivationRoot = typeof explicitActivationRoot === "string" && explicitActivationRoot.trim().length > 0
|
|
261
|
+
? path.resolve(explicitActivationRoot)
|
|
262
|
+
: null;
|
|
263
|
+
if (normalizedExplicitActivationRoot !== null) {
|
|
264
|
+
return normalizedExplicitActivationRoot;
|
|
265
|
+
}
|
|
266
|
+
const resolved = resolveActivationRoot({ openclawHome, quiet: true });
|
|
267
|
+
if (typeof resolved === "string" && resolved.trim().length > 0) {
|
|
268
|
+
return path.resolve(resolved);
|
|
269
|
+
}
|
|
270
|
+
return path.resolve(path.dirname(openclawHome), ".openclawbrain", "activation");
|
|
271
|
+
}
|
|
272
|
+
function summarizeSourceBundleFiles(fileDigests) {
|
|
273
|
+
return Object.fromEntries(Object.entries(fileDigests).sort((left, right) => left[0].localeCompare(right[0])));
|
|
274
|
+
}
|
|
275
|
+
function readRuntimeLoadProofSnapshot(activationRoot) {
|
|
276
|
+
const snapshot = listOpenClawProfileRuntimeLoadProofs(activationRoot);
|
|
277
|
+
return {
|
|
278
|
+
contract: "graphify_source_runtime_load_proofs.v1",
|
|
279
|
+
runtimeOwner: "openclaw",
|
|
280
|
+
activationRoot: path.resolve(activationRoot),
|
|
281
|
+
path: snapshot.path,
|
|
282
|
+
proofs: snapshot.proofs,
|
|
283
|
+
error: snapshot.error
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function buildGraphifySourceBundleStatus(input) {
|
|
287
|
+
const sourceBundleRoot = path.resolve(input.outputDir);
|
|
288
|
+
const runtimeLoadProofPath = resolveAttachmentRuntimeLoadProofsPath(input.activationRoot);
|
|
289
|
+
return {
|
|
290
|
+
contract: "graphify_source_runtime_status.v1",
|
|
291
|
+
runtimeOwner: "openclaw",
|
|
292
|
+
bundleId: input.bundleId,
|
|
293
|
+
corpusId: input.corpusId,
|
|
294
|
+
corpusDigest: input.corpusDigest,
|
|
295
|
+
createdAt: input.createdAt,
|
|
296
|
+
observedAt: input.observedAt,
|
|
297
|
+
openclawHome: input.openclawHome,
|
|
298
|
+
activationRoot: input.activationRoot,
|
|
299
|
+
sourceBundleRoot,
|
|
300
|
+
openclawHomeInspection: input.openclawHomeInspection,
|
|
301
|
+
hookStatus: input.hookStatus,
|
|
302
|
+
runtimeLoadProof: input.runtimeLoadProof,
|
|
303
|
+
runtimeLoadProofPath,
|
|
304
|
+
sessionTail: {
|
|
305
|
+
lane: input.sessionTail.lane,
|
|
306
|
+
observedAt: input.sessionTail.observedAt,
|
|
307
|
+
noopReason: input.sessionTail.poll.noopReason,
|
|
308
|
+
warnings: [...input.sessionTail.poll.warnings],
|
|
309
|
+
sourceCount: input.sessionTail.poll.sources.length,
|
|
310
|
+
changeCount: input.sessionTail.poll.changes.length,
|
|
311
|
+
emittedEventCount: input.sessionTail.interactionEvents.length + input.sessionTail.feedbackEvents.length,
|
|
312
|
+
cursorCount: input.sessionTail.poll.cursor.length
|
|
313
|
+
},
|
|
314
|
+
normalizedEventExport: input.normalizedEventExport === null ? null : {
|
|
315
|
+
exportDigest: input.normalizedEventExport.provenance.exportDigest,
|
|
316
|
+
range: input.normalizedEventExport.range,
|
|
317
|
+
interactionCount: input.normalizedEventExport.provenance.interactionCount,
|
|
318
|
+
feedbackCount: input.normalizedEventExport.provenance.feedbackCount,
|
|
319
|
+
sourceStreams: [...input.normalizedEventExport.provenance.sourceStreams],
|
|
320
|
+
contracts: [...input.normalizedEventExport.provenance.contracts],
|
|
321
|
+
semanticSurface: input.normalizedEventExport.provenance.semanticSurface ?? null
|
|
322
|
+
},
|
|
323
|
+
proofFiles: input.proofFiles,
|
|
324
|
+
sourceSummaries: input.sourceSummaries,
|
|
325
|
+
provenance: {
|
|
326
|
+
authority: "canonical_machine_export",
|
|
327
|
+
sourceAuthority: "session_tail",
|
|
328
|
+
lane: "local_session_tail"
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function buildGraphifyWorkspaceMetadata(input) {
|
|
333
|
+
return {
|
|
334
|
+
contract: "graphify_source_workspace_metadata.v1",
|
|
335
|
+
runtimeOwner: "openclaw",
|
|
336
|
+
bundleId: input.bundleId,
|
|
337
|
+
corpusId: input.corpusId,
|
|
338
|
+
corpusDigest: input.corpusDigest,
|
|
339
|
+
createdAt: input.createdAt,
|
|
340
|
+
openclawHome: input.openclawHome,
|
|
341
|
+
activationRoot: input.activationRoot,
|
|
342
|
+
sourceBundleRoot: path.resolve(input.outputDir),
|
|
343
|
+
sourceBundleRunId: path.basename(path.resolve(input.outputDir)),
|
|
344
|
+
profileInspection: input.openclawHomeInspection,
|
|
345
|
+
hookStatus: input.hookStatus,
|
|
346
|
+
sourceRoots: [...new Set(input.sourceSummaries.map((source) => source.profileRoot))].sort((left, right) => left.localeCompare(right)),
|
|
347
|
+
sourceCount: input.sourceSummaries.length,
|
|
348
|
+
sessionCount: input.sessionTail.poll.sources.length,
|
|
349
|
+
proofPaths: {
|
|
350
|
+
runtimeLoadProof: input.runtimeLoadProof.path,
|
|
351
|
+
openclawHomeInspection: input.proofFiles["openclaw-home-inspection.json"].path,
|
|
352
|
+
hookStatus: input.proofFiles["hook-status.json"].path,
|
|
353
|
+
sessionTail: input.proofFiles["session-tail.json"].path,
|
|
354
|
+
corpusSources: input.proofFiles["corpus-sources.json"].path
|
|
355
|
+
},
|
|
356
|
+
sourceSummaries: input.sourceSummaries.map((source) => ({
|
|
357
|
+
sourceId: source.sourceId,
|
|
358
|
+
profileRoot: source.profileRoot,
|
|
359
|
+
agentId: source.agentId,
|
|
360
|
+
sourceIndexPath: source.sourceIndexPath,
|
|
361
|
+
sessionKey: source.sessionKey,
|
|
362
|
+
sessionId: source.sessionId,
|
|
363
|
+
sessionFile: source.sessionFile,
|
|
364
|
+
sessionIndexDigest: source.sessionIndexDigest,
|
|
365
|
+
sessionFileDigest: source.sessionFileDigest,
|
|
366
|
+
sourceManifestDigest: source.sourceManifestDigest,
|
|
367
|
+
changeKind: source.changeKind,
|
|
368
|
+
eventCounts: source.eventCounts
|
|
369
|
+
})),
|
|
370
|
+
provenance: {
|
|
371
|
+
authority: "canonical_machine_export",
|
|
372
|
+
sourceAuthority: "session_tail",
|
|
373
|
+
lane: "local_session_tail"
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function buildCorpusManifest(input) {
|
|
378
|
+
return {
|
|
379
|
+
contract: "graphify_source_bundle_manifest.v1",
|
|
380
|
+
runtimeOwner: "openclaw",
|
|
381
|
+
bundleId: input.bundleId,
|
|
382
|
+
corpusId: input.corpusId,
|
|
383
|
+
corpusDigest: input.corpusDigest,
|
|
384
|
+
createdAt: input.createdAt,
|
|
385
|
+
observedAt: input.observedAt,
|
|
386
|
+
openclawHome: input.openclawHome,
|
|
387
|
+
activationRoot: input.activationRoot,
|
|
388
|
+
sourceBundleRoot: path.resolve(input.outputDir),
|
|
389
|
+
openclawHomeInspection: input.openclawHomeInspection,
|
|
390
|
+
hookStatus: input.hookStatus,
|
|
391
|
+
sourceAuthority: "session_tail",
|
|
392
|
+
sessionTail: {
|
|
393
|
+
lane: input.sessionTail.lane,
|
|
394
|
+
observedAt: input.sessionTail.observedAt,
|
|
395
|
+
noopReason: input.sessionTail.poll.noopReason,
|
|
396
|
+
warnings: [...input.sessionTail.poll.warnings],
|
|
397
|
+
sourceCount: input.sessionTail.poll.sources.length,
|
|
398
|
+
changeCount: input.sessionTail.poll.changes.length,
|
|
399
|
+
emittedEventCount: input.sessionTail.interactionEvents.length + input.sessionTail.feedbackEvents.length,
|
|
400
|
+
cursorCount: input.sessionTail.poll.cursor.length
|
|
401
|
+
},
|
|
402
|
+
normalizedEventExport: input.normalizedEventExport === null ? null : {
|
|
403
|
+
exportDigest: input.normalizedEventExport.provenance.exportDigest,
|
|
404
|
+
range: input.normalizedEventExport.range,
|
|
405
|
+
interactionCount: input.normalizedEventExport.provenance.interactionCount,
|
|
406
|
+
feedbackCount: input.normalizedEventExport.provenance.feedbackCount,
|
|
407
|
+
sourceStreams: [...input.normalizedEventExport.provenance.sourceStreams],
|
|
408
|
+
contracts: [...input.normalizedEventExport.provenance.contracts],
|
|
409
|
+
semanticSurface: input.normalizedEventExport.provenance.semanticSurface ?? null
|
|
410
|
+
},
|
|
411
|
+
sourceSummaries: input.sourceSummaries,
|
|
412
|
+
fileDigests: input.fileDigests,
|
|
413
|
+
proofFiles: summarizeSourceBundleFiles(input.fileDigests.proofFiles),
|
|
414
|
+
provenance: {
|
|
415
|
+
authority: "canonical_machine_export",
|
|
416
|
+
sourceAuthority: "session_tail",
|
|
417
|
+
lane: "local_session_tail",
|
|
418
|
+
exportMode: "graphify_source_bundle"
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
53
423
|
/**
|
|
54
424
|
* Export (backup) the activation root to a tar.gz archive.
|
|
55
425
|
*/
|
|
@@ -59,14 +429,11 @@ export function exportBrain(options) {
|
|
|
59
429
|
const resolvedOutput = path.resolve(outputPath);
|
|
60
430
|
try {
|
|
61
431
|
validateActivationRoot(resolvedRoot);
|
|
62
|
-
// Ensure output directory exists
|
|
63
432
|
const outputDir = path.dirname(resolvedOutput);
|
|
64
433
|
if (!existsSync(outputDir)) {
|
|
65
434
|
mkdirSync(outputDir, { recursive: true });
|
|
66
435
|
}
|
|
67
|
-
// Create tar.gz from the activation root contents
|
|
68
436
|
execSync(`tar czf ${JSON.stringify(resolvedOutput)} -C ${JSON.stringify(resolvedRoot)} .`, { stdio: "pipe" });
|
|
69
|
-
// Verify the archive was created
|
|
70
437
|
if (!existsSync(resolvedOutput)) {
|
|
71
438
|
return {
|
|
72
439
|
ok: false,
|
|
@@ -75,7 +442,7 @@ export function exportBrain(options) {
|
|
|
75
442
|
error: "Archive was not created (tar returned success but file missing)",
|
|
76
443
|
};
|
|
77
444
|
}
|
|
78
|
-
|
|
445
|
+
statSync(resolvedOutput);
|
|
79
446
|
return {
|
|
80
447
|
ok: true,
|
|
81
448
|
outputPath: resolvedOutput,
|
|
@@ -91,6 +458,437 @@ export function exportBrain(options) {
|
|
|
91
458
|
};
|
|
92
459
|
}
|
|
93
460
|
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Export a canonical Graphify source bundle from the current OpenClaw corpus.
|
|
464
|
+
*/
|
|
465
|
+
export function exportGraphifySourceBundle(options) {
|
|
466
|
+
const outputDir = options.outputDir === undefined || options.outputDir === null || String(options.outputDir).trim().length === 0
|
|
467
|
+
? null
|
|
468
|
+
: normalizeGraphifyOutputDir(options.outputDir);
|
|
469
|
+
if (outputDir === null) {
|
|
470
|
+
return {
|
|
471
|
+
ok: false,
|
|
472
|
+
error: "graphify source bundle export requires --output-dir <path>",
|
|
473
|
+
bundleDir: null
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
const openclawHome = resolveGraphifySourceBundleOpenClawHome(options);
|
|
478
|
+
const activationRoot = resolveGraphifySourceBundleActivationRoot(openclawHome, options.activationRoot);
|
|
479
|
+
const sessionTail = buildOpenClawSessionCorpusSnapshot({
|
|
480
|
+
profileRoots: options.profileRoots ?? [openclawHome],
|
|
481
|
+
...(options.homeDir === undefined ? {} : { homeDir: options.homeDir }),
|
|
482
|
+
...(options.cursor === undefined ? {} : { cursor: options.cursor }),
|
|
483
|
+
...(options.observedAt === undefined ? {} : { observedAt: options.observedAt }),
|
|
484
|
+
emitExistingOnFirstPoll: true
|
|
485
|
+
});
|
|
486
|
+
if (sessionTail.normalizedEventExport === null) {
|
|
487
|
+
return {
|
|
488
|
+
ok: false,
|
|
489
|
+
bundleDir: outputDir,
|
|
490
|
+
error: "graphify source bundle export found no bridgeable session events"
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const createdAt = typeof options.createdAt === "string" && options.createdAt.trim().length > 0
|
|
494
|
+
? options.createdAt
|
|
495
|
+
: new Date().toISOString();
|
|
496
|
+
const observedAt = sessionTail.observedAt;
|
|
497
|
+
const bundleId = sessionTail.corpusId;
|
|
498
|
+
const corpusId = sessionTail.corpusId;
|
|
499
|
+
const openclawHomeInspection = inspectOpenClawHome(openclawHome);
|
|
500
|
+
const hookStatus = inspectOpenClawBrainHookStatus(openclawHome);
|
|
501
|
+
const runtimeLoadProof = readRuntimeLoadProofSnapshot(activationRoot);
|
|
502
|
+
const proofDir = path.join(outputDir, "proof");
|
|
503
|
+
mkdirSync(proofDir, { recursive: true });
|
|
504
|
+
const proofFiles = {
|
|
505
|
+
"openclaw-home-inspection.json": writeCanonicalJsonFile(path.join(proofDir, "openclaw-home-inspection.json"), {
|
|
506
|
+
contract: "graphify_source_openclaw_home_inspection.v1",
|
|
507
|
+
runtimeOwner: "openclaw",
|
|
508
|
+
openclawHomeInspection,
|
|
509
|
+
bundleId,
|
|
510
|
+
corpusId,
|
|
511
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
512
|
+
createdAt,
|
|
513
|
+
observedAt
|
|
514
|
+
}),
|
|
515
|
+
"hook-status.json": writeCanonicalJsonFile(path.join(proofDir, "hook-status.json"), {
|
|
516
|
+
contract: "graphify_source_hook_status.v1",
|
|
517
|
+
runtimeOwner: "openclaw",
|
|
518
|
+
hookStatus,
|
|
519
|
+
bundleId,
|
|
520
|
+
corpusId,
|
|
521
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
522
|
+
createdAt,
|
|
523
|
+
observedAt
|
|
524
|
+
}),
|
|
525
|
+
"runtime-load-proofs.json": writeCanonicalJsonFile(path.join(proofDir, "runtime-load-proofs.json"), runtimeLoadProof),
|
|
526
|
+
"session-tail.json": writeCanonicalJsonFile(path.join(proofDir, "session-tail.json"), {
|
|
527
|
+
contract: "graphify_source_session_tail_snapshot.v1",
|
|
528
|
+
runtimeOwner: "openclaw",
|
|
529
|
+
bundleId,
|
|
530
|
+
corpusId,
|
|
531
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
532
|
+
createdAt,
|
|
533
|
+
observedAt,
|
|
534
|
+
sessionTail: sessionTail.poll
|
|
535
|
+
}),
|
|
536
|
+
"corpus-sources.json": writeCanonicalJsonFile(path.join(proofDir, "corpus-sources.json"), {
|
|
537
|
+
contract: "graphify_source_corpus_sources.v1",
|
|
538
|
+
runtimeOwner: "openclaw",
|
|
539
|
+
bundleId,
|
|
540
|
+
corpusId,
|
|
541
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
542
|
+
createdAt,
|
|
543
|
+
observedAt,
|
|
544
|
+
sourceSummaries: sessionTail.sourceSummaries
|
|
545
|
+
})
|
|
546
|
+
};
|
|
547
|
+
const normalizedEventExportResult = writeCanonicalJsonFile(path.join(outputDir, "normalized-event-export.json"), sessionTail.normalizedEventExport);
|
|
548
|
+
const runtimeStatus = buildGraphifySourceBundleStatus({
|
|
549
|
+
bundleId,
|
|
550
|
+
corpusId,
|
|
551
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
552
|
+
createdAt,
|
|
553
|
+
observedAt,
|
|
554
|
+
openclawHome,
|
|
555
|
+
activationRoot,
|
|
556
|
+
outputDir,
|
|
557
|
+
openclawHomeInspection,
|
|
558
|
+
hookStatus,
|
|
559
|
+
runtimeLoadProof,
|
|
560
|
+
sessionTail,
|
|
561
|
+
normalizedEventExport: sessionTail.normalizedEventExport,
|
|
562
|
+
sourceSummaries: sessionTail.sourceSummaries,
|
|
563
|
+
proofFiles
|
|
564
|
+
});
|
|
565
|
+
const runtimeStatusResult = writeCanonicalJsonFile(path.join(outputDir, "runtime-status.json"), runtimeStatus);
|
|
566
|
+
const workspaceMetadata = buildGraphifyWorkspaceMetadata({
|
|
567
|
+
bundleId,
|
|
568
|
+
corpusId,
|
|
569
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
570
|
+
createdAt,
|
|
571
|
+
openclawHome,
|
|
572
|
+
activationRoot,
|
|
573
|
+
outputDir,
|
|
574
|
+
openclawHomeInspection,
|
|
575
|
+
hookStatus,
|
|
576
|
+
sessionTail,
|
|
577
|
+
sourceSummaries: sessionTail.sourceSummaries,
|
|
578
|
+
runtimeLoadProof,
|
|
579
|
+
proofFiles
|
|
580
|
+
});
|
|
581
|
+
const workspaceMetadataResult = writeCanonicalJsonFile(path.join(outputDir, "workspace-metadata.json"), workspaceMetadata);
|
|
582
|
+
const fileDigests = {
|
|
583
|
+
normalizedEventExport: normalizedEventExportResult.digest,
|
|
584
|
+
runtimeStatus: runtimeStatusResult.digest,
|
|
585
|
+
workspaceMetadata: workspaceMetadataResult.digest,
|
|
586
|
+
proofFiles: summarizeSourceBundleFiles(Object.fromEntries(Object.entries(proofFiles).map(([name, result]) => [name, result.digest])))
|
|
587
|
+
};
|
|
588
|
+
const corpusManifest = buildCorpusManifest({
|
|
589
|
+
bundleId,
|
|
590
|
+
corpusId,
|
|
591
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
592
|
+
createdAt,
|
|
593
|
+
observedAt,
|
|
594
|
+
openclawHome,
|
|
595
|
+
activationRoot,
|
|
596
|
+
outputDir,
|
|
597
|
+
openclawHomeInspection,
|
|
598
|
+
hookStatus,
|
|
599
|
+
sessionTail,
|
|
600
|
+
normalizedEventExport: sessionTail.normalizedEventExport,
|
|
601
|
+
sourceSummaries: sessionTail.sourceSummaries,
|
|
602
|
+
fileDigests
|
|
603
|
+
});
|
|
604
|
+
const corpusManifestResult = writeCanonicalJsonFile(path.join(outputDir, "corpus-manifest.json"), corpusManifest);
|
|
605
|
+
return {
|
|
606
|
+
ok: true,
|
|
607
|
+
bundleDir: outputDir,
|
|
608
|
+
bundleId,
|
|
609
|
+
corpusId,
|
|
610
|
+
corpusDigest: sessionTail.corpusDigest,
|
|
611
|
+
outputPaths: {
|
|
612
|
+
corpusManifest: corpusManifestResult.path,
|
|
613
|
+
normalizedEventExport: normalizedEventExportResult.path,
|
|
614
|
+
runtimeStatus: runtimeStatusResult.path,
|
|
615
|
+
workspaceMetadata: workspaceMetadataResult.path,
|
|
616
|
+
proofDir,
|
|
617
|
+
proofFiles: Object.fromEntries(Object.entries(proofFiles).map(([name, result]) => [name, result.path]))
|
|
618
|
+
},
|
|
619
|
+
corpusManifest,
|
|
620
|
+
normalizedEventExport: sessionTail.normalizedEventExport,
|
|
621
|
+
runtimeStatus,
|
|
622
|
+
workspaceMetadata,
|
|
623
|
+
sourceSummaries: sessionTail.sourceSummaries,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
return {
|
|
628
|
+
ok: false,
|
|
629
|
+
bundleDir: outputDir,
|
|
630
|
+
error: err instanceof Error ? err.message : String(err)
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Project the canonical machine export into Graphify-friendly markdown and
|
|
637
|
+
* filesystem surfaces while keeping the projection explicitly non-authoritative.
|
|
638
|
+
*/
|
|
639
|
+
export function exportGraphifyProjection(options) {
|
|
640
|
+
const resolvedActivationRoot = path.resolve(options.activationRoot);
|
|
641
|
+
const resolvedRepoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
642
|
+
const resolvedWorkspaceRoot = path.resolve(options.workspaceRoot ?? resolvedRepoRoot);
|
|
643
|
+
const resolvedOutputRoot = path.resolve(options.outputRoot ?? path.join(process.cwd(), "artifacts", "graphify-source-bundles"));
|
|
644
|
+
const runId = normalizeOptionalString(options.runId) ?? timestampToken(new Date().toISOString());
|
|
645
|
+
const bundleRoot = path.join(resolvedOutputRoot, runId);
|
|
646
|
+
const generatedAt = normalizeOptionalString(options.generatedAt) ?? new Date().toISOString();
|
|
647
|
+
const sessionTimestamp = normalizeOptionalString(options.sessionTimestamp) ?? generatedAt;
|
|
648
|
+
const sessionKey = normalizeOptionalString(options.sessionKey) ?? "current-session";
|
|
649
|
+
const resolvedSessionSourcePath = normalizeOptionalString(options.sessionSourcePath);
|
|
650
|
+
const resolvedProofSummarySourcePath = normalizeOptionalString(options.proofSummarySourcePath);
|
|
651
|
+
const resolvedDocsRoot = path.resolve(options.docsRoot ?? path.join(resolvedRepoRoot, "docs"));
|
|
652
|
+
const resolvedCodeRoot = path.resolve(options.codeRoot ?? path.join(resolvedRepoRoot, "packages", "cli", "dist", "src"));
|
|
653
|
+
rmSync(bundleRoot, { recursive: true, force: true });
|
|
654
|
+
ensureDir(path.join(bundleRoot, "canonical"));
|
|
655
|
+
ensureDir(path.join(bundleRoot, "workspace"));
|
|
656
|
+
ensureDir(path.join(bundleRoot, "proof"));
|
|
657
|
+
ensureDir(path.join(bundleRoot, "sessions", sessionKey));
|
|
658
|
+
const canonicalArchivePath = path.join(bundleRoot, "canonical", "machine-export.tar.gz");
|
|
659
|
+
const exportResult = exportBrain({
|
|
660
|
+
activationRoot: resolvedActivationRoot,
|
|
661
|
+
outputPath: canonicalArchivePath,
|
|
662
|
+
});
|
|
663
|
+
if (!exportResult.ok) {
|
|
664
|
+
return {
|
|
665
|
+
ok: false,
|
|
666
|
+
runId,
|
|
667
|
+
bundleRoot,
|
|
668
|
+
sourceBundleHash: null,
|
|
669
|
+
canonicalArchivePath,
|
|
670
|
+
canonicalArchiveSha256: null,
|
|
671
|
+
manifestPath: null,
|
|
672
|
+
sessionProjectionPath: null,
|
|
673
|
+
workspaceMemoryPath: null,
|
|
674
|
+
workspaceTasksPath: null,
|
|
675
|
+
proofSummaryPath: null,
|
|
676
|
+
docsMirrorRoot: null,
|
|
677
|
+
codeMirrorRoot: null,
|
|
678
|
+
error: exportResult.error ?? "canonical machine export failed",
|
|
679
|
+
warnings: [],
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const canonicalArchiveText = readFileSync(canonicalArchivePath);
|
|
683
|
+
const canonicalArchiveSha256 = createHash("sha256").update(canonicalArchiveText).digest("hex");
|
|
684
|
+
const canonicalArchiveResultPath = path.join(bundleRoot, "canonical", "machine-export.json");
|
|
685
|
+
writeTextFile(canonicalArchiveResultPath, JSON.stringify({
|
|
686
|
+
ok: exportResult.ok,
|
|
687
|
+
activationRoot: exportResult.activationRoot,
|
|
688
|
+
outputPath: exportResult.outputPath,
|
|
689
|
+
archiveSha256: canonicalArchiveSha256,
|
|
690
|
+
archiveBytes: statSync(canonicalArchivePath).size,
|
|
691
|
+
}, null, 2));
|
|
692
|
+
const memorySourcePath = path.join(resolvedWorkspaceRoot, "MEMORY.md");
|
|
693
|
+
const tasksSourcePath = path.join(resolvedWorkspaceRoot, "TASKS.md");
|
|
694
|
+
const memoryText = readTextIfExists(memorySourcePath);
|
|
695
|
+
const tasksText = readTextIfExists(tasksSourcePath);
|
|
696
|
+
const sessionSourceText = resolvedSessionSourcePath === null ? null : readTextIfExists(resolvedSessionSourcePath);
|
|
697
|
+
const proofSummarySourceText = resolvedProofSummarySourcePath === null ? null : readTextIfExists(resolvedProofSummarySourcePath);
|
|
698
|
+
const docsMirror = existsSync(resolvedDocsRoot) ? tryMirrorTree(resolvedDocsRoot, path.join(bundleRoot, "docs")) : { path: path.join(bundleRoot, "docs"), mode: "copy" };
|
|
699
|
+
const codeMirror = existsSync(resolvedCodeRoot) ? tryMirrorTree(resolvedCodeRoot, path.join(bundleRoot, "code")) : { path: path.join(bundleRoot, "code"), mode: "copy" };
|
|
700
|
+
if (!existsSync(docsMirror.path)) {
|
|
701
|
+
ensureDir(docsMirror.path);
|
|
702
|
+
}
|
|
703
|
+
if (!existsSync(codeMirror.path)) {
|
|
704
|
+
ensureDir(codeMirror.path);
|
|
705
|
+
}
|
|
706
|
+
const sourceBundleHash = createHash("sha256").update(stableJson({
|
|
707
|
+
contract: "graphify_source_bundle.v1",
|
|
708
|
+
runId,
|
|
709
|
+
activationRoot: resolvedActivationRoot,
|
|
710
|
+
repoRoot: resolvedRepoRoot,
|
|
711
|
+
workspaceRoot: resolvedWorkspaceRoot,
|
|
712
|
+
generatedAt,
|
|
713
|
+
sessionKey,
|
|
714
|
+
sessionTimestamp,
|
|
715
|
+
sessionSourcePath: resolvedSessionSourcePath,
|
|
716
|
+
proofSummarySourcePath: resolvedProofSummarySourcePath,
|
|
717
|
+
canonicalArchiveSha256,
|
|
718
|
+
memorySha256: memoryText === null ? null : sha256Text(memoryText),
|
|
719
|
+
tasksSha256: tasksText === null ? null : sha256Text(tasksText),
|
|
720
|
+
sessionSourceSha256: sessionSourceText === null ? null : sha256Text(sessionSourceText),
|
|
721
|
+
proofSummarySourceSha256: proofSummarySourceText === null ? null : sha256Text(proofSummarySourceText),
|
|
722
|
+
docsMirrorMode: docsMirror.mode,
|
|
723
|
+
codeMirrorMode: codeMirror.mode,
|
|
724
|
+
})).digest("hex");
|
|
725
|
+
const sessionProjectionPath = path.join(bundleRoot, "sessions", sessionKey, `${timestampToken(sessionTimestamp)}.md`);
|
|
726
|
+
const workspaceMemoryPath = path.join(bundleRoot, "workspace", "MEMORY.md");
|
|
727
|
+
const workspaceTasksPath = path.join(bundleRoot, "workspace", "TASKS.md");
|
|
728
|
+
const proofSummaryPath = path.join(bundleRoot, "proof", "summary.md");
|
|
729
|
+
const manifestPath = path.join(bundleRoot, "corpus-manifest.json");
|
|
730
|
+
const readmePath = path.join(bundleRoot, "README.md");
|
|
731
|
+
const sessionMarkdown = buildProjectionMarkdown({
|
|
732
|
+
title: "Graphify session projection",
|
|
733
|
+
kind: "session_projection",
|
|
734
|
+
bundleRoot,
|
|
735
|
+
sourceBundleHash,
|
|
736
|
+
canonicalArchivePath,
|
|
737
|
+
sourcePath: resolvedSessionSourcePath ?? path.join(resolvedWorkspaceRoot, "agents", "main", "sessions"),
|
|
738
|
+
generatedAt,
|
|
739
|
+
sourceText: sessionSourceText,
|
|
740
|
+
extraDetails: [
|
|
741
|
+
`session key: \`${sessionKey}\``,
|
|
742
|
+
`session timestamp: \`${sessionTimestamp}\``,
|
|
743
|
+
`source bundle linkage: \`${path.relative(bundleRoot, canonicalArchivePath)}\``,
|
|
744
|
+
],
|
|
745
|
+
});
|
|
746
|
+
const memoryMarkdown = buildProjectionMarkdown({
|
|
747
|
+
title: "Graphify workspace MEMORY projection",
|
|
748
|
+
kind: "workspace_memory_projection",
|
|
749
|
+
bundleRoot,
|
|
750
|
+
sourceBundleHash,
|
|
751
|
+
canonicalArchivePath,
|
|
752
|
+
sourcePath: memorySourcePath,
|
|
753
|
+
generatedAt,
|
|
754
|
+
sourceText: memoryText,
|
|
755
|
+
extraDetails: [
|
|
756
|
+
`workspace root: \`${resolvedWorkspaceRoot}\``,
|
|
757
|
+
`source bundle linkage: \`${path.relative(bundleRoot, canonicalArchivePath)}\``,
|
|
758
|
+
],
|
|
759
|
+
});
|
|
760
|
+
const tasksMarkdown = buildProjectionMarkdown({
|
|
761
|
+
title: "Graphify workspace TASKS projection",
|
|
762
|
+
kind: "workspace_tasks_projection",
|
|
763
|
+
bundleRoot,
|
|
764
|
+
sourceBundleHash,
|
|
765
|
+
canonicalArchivePath,
|
|
766
|
+
sourcePath: tasksSourcePath,
|
|
767
|
+
generatedAt,
|
|
768
|
+
sourceText: tasksText,
|
|
769
|
+
extraDetails: [
|
|
770
|
+
`workspace root: \`${resolvedWorkspaceRoot}\``,
|
|
771
|
+
`source bundle linkage: \`${path.relative(bundleRoot, canonicalArchivePath)}\``,
|
|
772
|
+
],
|
|
773
|
+
});
|
|
774
|
+
const proofMarkdown = buildProjectionMarkdown({
|
|
775
|
+
title: "Graphify proof summary projection",
|
|
776
|
+
kind: "proof_summary_projection",
|
|
777
|
+
bundleRoot,
|
|
778
|
+
sourceBundleHash,
|
|
779
|
+
canonicalArchivePath,
|
|
780
|
+
sourcePath: resolvedProofSummarySourcePath ?? path.join(bundleRoot, "proof", "summary.source.md"),
|
|
781
|
+
generatedAt,
|
|
782
|
+
sourceText: proofSummarySourceText,
|
|
783
|
+
extraDetails: [
|
|
784
|
+
`source bundle linkage: \`${path.relative(bundleRoot, canonicalArchivePath)}\``,
|
|
785
|
+
`workspace root: \`${resolvedWorkspaceRoot}\``,
|
|
786
|
+
],
|
|
787
|
+
});
|
|
788
|
+
writeTextFile(readmePath, [
|
|
789
|
+
"# Graphify projection bundle",
|
|
790
|
+
"",
|
|
791
|
+
"Projection-only surface; non-authoritative by design.",
|
|
792
|
+
"",
|
|
793
|
+
`- bundle root: \`${bundleRoot}\``,
|
|
794
|
+
`- run id: \`${runId}\``,
|
|
795
|
+
`- source bundle hash: \`${sourceBundleHash}\``,
|
|
796
|
+
`- canonical archive: \`${path.relative(bundleRoot, canonicalArchivePath)}\``,
|
|
797
|
+
"",
|
|
798
|
+
"This bundle mirrors canonical machine export data into Graphify-friendly surfaces for review only.",
|
|
799
|
+
""
|
|
800
|
+
].join("\n"));
|
|
801
|
+
writeProjectionSurface(sessionProjectionPath, sessionMarkdown);
|
|
802
|
+
writeProjectionSurface(workspaceMemoryPath, memoryMarkdown);
|
|
803
|
+
writeProjectionSurface(workspaceTasksPath, tasksMarkdown);
|
|
804
|
+
writeProjectionSurface(proofSummaryPath, proofMarkdown);
|
|
805
|
+
const manifest = {
|
|
806
|
+
contract: "graphify_source_bundle.v1",
|
|
807
|
+
sourceKind: "canonical_ocb_source_bundle",
|
|
808
|
+
generatedAt,
|
|
809
|
+
runId,
|
|
810
|
+
authoritative: false,
|
|
811
|
+
projectionTruth: "projection_only",
|
|
812
|
+
sourceBundleHash,
|
|
813
|
+
canonicalMachineExport: {
|
|
814
|
+
path: path.relative(bundleRoot, canonicalArchivePath),
|
|
815
|
+
sha256: canonicalArchiveSha256,
|
|
816
|
+
bytes: statSync(canonicalArchivePath).size,
|
|
817
|
+
activationRoot: resolvedActivationRoot,
|
|
818
|
+
},
|
|
819
|
+
provenance: {
|
|
820
|
+
repoRoot: resolvedRepoRoot,
|
|
821
|
+
workspaceRoot: resolvedWorkspaceRoot,
|
|
822
|
+
sessionKey,
|
|
823
|
+
sessionTimestamp,
|
|
824
|
+
sessionSourcePath: resolvedSessionSourcePath,
|
|
825
|
+
proofSummarySourcePath: resolvedProofSummarySourcePath,
|
|
826
|
+
docsMirrorRoot: resolvedDocsRoot,
|
|
827
|
+
codeMirrorRoot: resolvedCodeRoot,
|
|
828
|
+
},
|
|
829
|
+
outputs: {
|
|
830
|
+
bundleRoot: ".",
|
|
831
|
+
canonicalArchive: path.relative(bundleRoot, canonicalArchivePath),
|
|
832
|
+
canonicalArchiveResult: path.relative(bundleRoot, canonicalArchiveResultPath),
|
|
833
|
+
corpusManifest: path.relative(bundleRoot, manifestPath),
|
|
834
|
+
readme: path.relative(bundleRoot, readmePath),
|
|
835
|
+
sessionProjection: path.relative(bundleRoot, sessionProjectionPath),
|
|
836
|
+
workspaceMemory: path.relative(bundleRoot, workspaceMemoryPath),
|
|
837
|
+
workspaceTasks: path.relative(bundleRoot, workspaceTasksPath),
|
|
838
|
+
proofSummary: path.relative(bundleRoot, proofSummaryPath),
|
|
839
|
+
docsMirror: path.relative(bundleRoot, path.join(bundleRoot, "docs")),
|
|
840
|
+
codeMirror: path.relative(bundleRoot, path.join(bundleRoot, "code")),
|
|
841
|
+
},
|
|
842
|
+
mirrorModes: {
|
|
843
|
+
docs: docsMirror.mode,
|
|
844
|
+
code: codeMirror.mode,
|
|
845
|
+
},
|
|
846
|
+
sourceHashes: {
|
|
847
|
+
canonicalArchiveSha256,
|
|
848
|
+
memorySha256: memoryText === null ? null : sha256Text(memoryText),
|
|
849
|
+
tasksSha256: tasksText === null ? null : sha256Text(tasksText),
|
|
850
|
+
sessionSourceSha256: sessionSourceText === null ? null : sha256Text(sessionSourceText),
|
|
851
|
+
proofSummarySourceSha256: proofSummarySourceText === null ? null : sha256Text(proofSummarySourceText),
|
|
852
|
+
},
|
|
853
|
+
notes: [
|
|
854
|
+
"This bundle is projection-only and non-authoritative.",
|
|
855
|
+
"Canonical machine export linkage remains explicit.",
|
|
856
|
+
"Docs/code mirrors are curated Graphify-friendly surfaces only.",
|
|
857
|
+
],
|
|
858
|
+
};
|
|
859
|
+
writeTextFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
860
|
+
return {
|
|
861
|
+
ok: true,
|
|
862
|
+
runId,
|
|
863
|
+
bundleRoot,
|
|
864
|
+
sourceBundleHash,
|
|
865
|
+
canonicalArchivePath,
|
|
866
|
+
canonicalArchiveSha256,
|
|
867
|
+
manifestPath,
|
|
868
|
+
sessionProjectionPath,
|
|
869
|
+
workspaceMemoryPath,
|
|
870
|
+
workspaceTasksPath,
|
|
871
|
+
proofSummaryPath,
|
|
872
|
+
docsMirrorRoot: docsMirror.path,
|
|
873
|
+
codeMirrorRoot: codeMirror.path,
|
|
874
|
+
warnings: docsMirror.mode === "copy" || codeMirror.mode === "copy"
|
|
875
|
+
? [docsMirror.mode === "copy" ? "docs mirror was copied because symlink creation failed" : null, codeMirror.mode === "copy" ? "code mirror was copied because symlink creation failed" : null].filter((warning) => warning !== null)
|
|
876
|
+
: [],
|
|
877
|
+
outputPaths: {
|
|
878
|
+
canonicalArchive: canonicalArchivePath,
|
|
879
|
+
canonicalArchiveResult: canonicalArchiveResultPath,
|
|
880
|
+
corpusManifest: manifestPath,
|
|
881
|
+
readme: readmePath,
|
|
882
|
+
sessionProjection: sessionProjectionPath,
|
|
883
|
+
workspaceMemory: workspaceMemoryPath,
|
|
884
|
+
workspaceTasks: workspaceTasksPath,
|
|
885
|
+
proofSummary: proofSummaryPath,
|
|
886
|
+
docsMirror: docsMirror.path,
|
|
887
|
+
codeMirror: codeMirror.path,
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
94
892
|
/**
|
|
95
893
|
* Import (restore) a tar.gz archive into the activation root.
|
|
96
894
|
*/
|
|
@@ -99,7 +897,6 @@ export function importBrain(options) {
|
|
|
99
897
|
const resolvedArchive = path.resolve(archivePath);
|
|
100
898
|
const resolvedRoot = path.resolve(activationRoot);
|
|
101
899
|
try {
|
|
102
|
-
// Verify archive exists
|
|
103
900
|
if (!existsSync(resolvedArchive)) {
|
|
104
901
|
return {
|
|
105
902
|
ok: false,
|
|
@@ -108,7 +905,6 @@ export function importBrain(options) {
|
|
|
108
905
|
error: `Archive not found: ${resolvedArchive}`,
|
|
109
906
|
};
|
|
110
907
|
}
|
|
111
|
-
// Verify archive contains activation-pointers.json
|
|
112
908
|
if (!archiveContainsPointers(resolvedArchive)) {
|
|
113
909
|
return {
|
|
114
910
|
ok: false,
|
|
@@ -118,7 +914,6 @@ export function importBrain(options) {
|
|
|
118
914
|
"This doesn't look like a valid brain backup.",
|
|
119
915
|
};
|
|
120
916
|
}
|
|
121
|
-
// Check if activation root already has data
|
|
122
917
|
let warning;
|
|
123
918
|
if (activationRootHasData(resolvedRoot)) {
|
|
124
919
|
if (!force) {
|
|
@@ -132,13 +927,10 @@ export function importBrain(options) {
|
|
|
132
927
|
}
|
|
133
928
|
warning = `Overwrote existing data in ${resolvedRoot}`;
|
|
134
929
|
}
|
|
135
|
-
// Create activation root if needed
|
|
136
930
|
if (!existsSync(resolvedRoot)) {
|
|
137
931
|
mkdirSync(resolvedRoot, { recursive: true });
|
|
138
932
|
}
|
|
139
|
-
// Extract archive
|
|
140
933
|
execSync(`tar xzf ${JSON.stringify(resolvedArchive)} -C ${JSON.stringify(resolvedRoot)}`, { stdio: "pipe" });
|
|
141
|
-
// Verify extraction produced activation-pointers.json
|
|
142
934
|
const pointersPath = path.join(resolvedRoot, "activation-pointers.json");
|
|
143
935
|
if (!existsSync(pointersPath)) {
|
|
144
936
|
return {
|
|
@@ -168,4 +960,124 @@ export function importBrain(options) {
|
|
|
168
960
|
};
|
|
169
961
|
}
|
|
170
962
|
}
|
|
171
|
-
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Build and write a Graphify-derived compiled artifact pack.
|
|
966
|
+
*/
|
|
967
|
+
export function exportGraphifyCompiledArtifactsPack(options = {}) {
|
|
968
|
+
try {
|
|
969
|
+
const bundle = buildGraphifyCompiledArtifactPack(options);
|
|
970
|
+
const writeResult = writeGraphifyCompiledArtifactPack(bundle.outputDir, bundle);
|
|
971
|
+
return {
|
|
972
|
+
ok: true,
|
|
973
|
+
bundleId: bundle.bundleId,
|
|
974
|
+
packId: bundle.packId,
|
|
975
|
+
proposalId: bundle.proposalId,
|
|
976
|
+
outputDir: bundle.outputDir,
|
|
977
|
+
manifestPath: bundle.bundlePaths.manifest,
|
|
978
|
+
compilerProposalPath: bundle.bundlePaths.compilerProposal,
|
|
979
|
+
surfaceMapPath: bundle.bundlePaths.surfaceMap,
|
|
980
|
+
proposalReportPath: bundle.bundlePaths.proposalReport,
|
|
981
|
+
verdictPath: bundle.bundlePaths.verdict,
|
|
982
|
+
artifactCount: bundle.artifactEntries.length,
|
|
983
|
+
validation: bundle.validation,
|
|
984
|
+
digest: bundle.digest,
|
|
985
|
+
writtenFiles: writeResult.writtenFiles,
|
|
986
|
+
fileCount: writeResult.fileCount,
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
catch (error) {
|
|
990
|
+
return {
|
|
991
|
+
ok: false,
|
|
992
|
+
outputDir: path.resolve(options.outputDir ?? "."),
|
|
993
|
+
candidatePackInput: null,
|
|
994
|
+
error: error instanceof Error ? error.message : String(error),
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Build and write a conservative EXTRACTED-only Graphify import slice.
|
|
1001
|
+
*/
|
|
1002
|
+
export function exportGraphifyImportSlice(options = {}) {
|
|
1003
|
+
try {
|
|
1004
|
+
const bundle = buildGraphifyImportSlice(options);
|
|
1005
|
+
const writeResult = writeGraphifyImportSliceBundle(bundle.outputDir, bundle);
|
|
1006
|
+
return {
|
|
1007
|
+
ok: true,
|
|
1008
|
+
runId: bundle.runId,
|
|
1009
|
+
sliceId: bundle.sliceId,
|
|
1010
|
+
proposalId: bundle.proposalId,
|
|
1011
|
+
rollbackKey: bundle.rollbackKey,
|
|
1012
|
+
outputRoot: bundle.outputRoot,
|
|
1013
|
+
outputDir: bundle.outputDir,
|
|
1014
|
+
bundleRoot: bundle.bundleRoot,
|
|
1015
|
+
repoRoot: bundle.repoRoot,
|
|
1016
|
+
workspaceRoot: bundle.workspaceRoot,
|
|
1017
|
+
sourceBundleId: bundle.sourceBundleId,
|
|
1018
|
+
sourceBundleHash: bundle.sourceBundleHash,
|
|
1019
|
+
sourceBundleKind: bundle.sourceBundleKind,
|
|
1020
|
+
graphifyRunId: bundle.graphifyRunId,
|
|
1021
|
+
graphifyVersion: bundle.graphifyVersion,
|
|
1022
|
+
graphifyCommand: bundle.graphifyCommand,
|
|
1023
|
+
counts: bundle.counts,
|
|
1024
|
+
truthBoundary: bundle.truthBoundary,
|
|
1025
|
+
candidatePackInput: bundle.candidatePackInput,
|
|
1026
|
+
importSlice: bundle.importSlice,
|
|
1027
|
+
proposalEnvelope: bundle.proposalEnvelope,
|
|
1028
|
+
replayGate: bundle.replayGate,
|
|
1029
|
+
report: bundle.reportMarkdown,
|
|
1030
|
+
paths: bundle.paths,
|
|
1031
|
+
digest: bundle.digest,
|
|
1032
|
+
writtenFiles: writeResult.writtenFiles,
|
|
1033
|
+
fileCount: writeResult.fileCount,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
return {
|
|
1038
|
+
ok: false,
|
|
1039
|
+
outputRoot: path.resolve(options.outputRoot ?? path.join(process.cwd(), "artifacts", "graphify-imports")),
|
|
1040
|
+
outputDir: resolveGraphifyImportSliceOutputDir(options),
|
|
1041
|
+
bundleRoot: path.resolve(options.bundleRoot ?? options.bundleDir ?? options.bundlePath ?? "."),
|
|
1042
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Build and write a Graphify maintenance diff bundle.
|
|
1049
|
+
*/
|
|
1050
|
+
export function exportGraphifyMaintenanceDiff(options = {}) {
|
|
1051
|
+
try {
|
|
1052
|
+
const bundle = buildGraphifyMaintenanceDiffBundle(options);
|
|
1053
|
+
const writeResult = writeGraphifyMaintenanceDiffBundle(bundle.outputDir, bundle);
|
|
1054
|
+
return {
|
|
1055
|
+
ok: true,
|
|
1056
|
+
runId: bundle.runId,
|
|
1057
|
+
diffId: bundle.diffId,
|
|
1058
|
+
proposalId: bundle.proposalId,
|
|
1059
|
+
rollbackKey: bundle.rollbackKey,
|
|
1060
|
+
repoRoot: bundle.repoRoot,
|
|
1061
|
+
workspaceRoot: bundle.workspaceRoot,
|
|
1062
|
+
graphifyRoot: bundle.graphifyRoot,
|
|
1063
|
+
ocbRoot: bundle.ocbRoot,
|
|
1064
|
+
outputRoot: bundle.outputRoot,
|
|
1065
|
+
outputDir: bundle.outputDir,
|
|
1066
|
+
report: bundle.report,
|
|
1067
|
+
proposalSuggestion: bundle.proposalSuggestion,
|
|
1068
|
+
verdict: bundle.verdict,
|
|
1069
|
+
paths: bundle.paths,
|
|
1070
|
+
digest: bundle.digest,
|
|
1071
|
+
writtenFiles: writeResult.writtenFiles,
|
|
1072
|
+
fileCount: writeResult.fileCount,
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
catch (error) {
|
|
1076
|
+
return {
|
|
1077
|
+
ok: false,
|
|
1078
|
+
outputRoot: path.resolve(options.outputRoot ?? path.join(process.cwd(), "artifacts", "graphify-maintenance-diff")),
|
|
1079
|
+
outputDir: path.join(path.resolve(options.outputRoot ?? path.join(process.cwd(), "artifacts", "graphify-maintenance-diff")), options.runId ?? `graphify-maintenance-diff-${Date.now()}`),
|
|
1080
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
}
|