@openclawbrain/cli 0.4.35 → 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.
@@ -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
- * Verify the activation root looks valid (has activation-pointers.json).
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((e) => e.replace(/^\.\//, ""));
33
- return entries.some((e) => e === "activation-pointers.json" || e.endsWith("/activation-pointers.json"));
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
- const entries = readdirSync(activationRoot);
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
- const stats = statSync(resolvedOutput);
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
- //# sourceMappingURL=import-export.js.map
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
+ }