mimetic-cli 0.1.2 → 0.1.3

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.
Files changed (49) hide show
  1. package/AGENTS.md +66 -0
  2. package/CONTRIBUTING.md +39 -0
  3. package/README.md +4 -1
  4. package/SECURITY.md +34 -0
  5. package/dist/core/git-state.d.ts +31 -0
  6. package/dist/core/git-state.js +142 -0
  7. package/dist/core/git-state.js.map +1 -0
  8. package/dist/core/index.d.ts +4 -0
  9. package/dist/core/index.js +3 -0
  10. package/dist/core/index.js.map +1 -0
  11. package/dist/core/run-primitives.d.ts +66 -0
  12. package/dist/core/run-primitives.js +120 -0
  13. package/dist/core/run-primitives.js.map +1 -0
  14. package/dist/observer-assets.js +1663 -2180
  15. package/dist/observer-assets.js.map +1 -1
  16. package/dist/observer-data.d.ts +1 -1
  17. package/dist/observer-data.js +5 -1
  18. package/dist/observer-data.js.map +1 -1
  19. package/dist/observer.js +8 -61
  20. package/dist/observer.js.map +1 -1
  21. package/dist/oss-meta-lab.d.ts +50 -0
  22. package/dist/oss-meta-lab.js +454 -27
  23. package/dist/oss-meta-lab.js.map +1 -1
  24. package/dist/program.d.ts +6 -0
  25. package/dist/program.js +75 -8
  26. package/dist/program.js.map +1 -1
  27. package/dist/run.d.ts +19 -6
  28. package/dist/run.js +1263 -9
  29. package/dist/run.js.map +1 -1
  30. package/docs/architecture/github-feedback-loop.md +189 -0
  31. package/docs/architecture/local-codex-tui-actor.md +210 -0
  32. package/docs/architecture/observer.md +109 -0
  33. package/docs/architecture/oss-lab-poc.md +170 -0
  34. package/docs/architecture/project-layout.md +132 -0
  35. package/docs/contracts/adapter-fixtures.md +80 -0
  36. package/docs/contracts/core.md +71 -0
  37. package/docs/contracts/feedback.md +131 -0
  38. package/docs/contracts/policy.md +273 -0
  39. package/docs/contracts/run-bundle.md +110 -0
  40. package/docs/contracts/schemas.md +511 -0
  41. package/docs/goals/current.md +163 -0
  42. package/docs/principles/self-driving-harness.md +129 -0
  43. package/docs/product/open-source-install-experience.md +138 -0
  44. package/docs/ramp/README.md +167 -0
  45. package/docs/release/open-source-readiness.md +171 -0
  46. package/docs/release/public-readiness-standard.md +205 -0
  47. package/docs/roadmap/world-class-open-source-v0.md +286 -0
  48. package/package.json +13 -2
  49. package/skills/mimetic-cli/SKILL.md +1 -1
package/dist/run.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
+ import { spawn } from "node:child_process";
2
3
  import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
3
5
  import path from "node:path";
6
+ import { buildObserverData } from "./observer-data.js";
4
7
  export const RUN_BUNDLE_SCHEMA = "mimetic.run-bundle.v1";
5
8
  export const REVIEW_SCHEMA = "mimetic.review.v1";
6
9
  export const VERIFY_SCHEMA = "mimetic.verify-result.v1";
@@ -11,6 +14,9 @@ const sensitivePatterns = [
11
14
  /gho_[a-z0-9_]{12,}/i,
12
15
  /BEGIN (RSA|OPENSSH|PRIVATE) KEY/i
13
16
  ];
17
+ const LOCAL_CODEX_TUI_DEFAULT_TIMEOUT_MS = 240_000;
18
+ const LOCAL_CODEX_TUI_MAX_TIMEOUT_MS = 600_000;
19
+ const LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS = 80_000;
14
20
  const builtinPersona = {
15
21
  id: "builtin-synthetic-new-user",
16
22
  name: "Built-in Synthetic New User",
@@ -37,15 +43,15 @@ export async function runDryRun(options) {
37
43
  error: cwdError
38
44
  };
39
45
  }
40
- if (!options.dryRun) {
46
+ if (options.actor !== undefined && !isLocalCodexActor(options.actor)) {
41
47
  return {
42
48
  schema: "mimetic.run-result.v1",
43
49
  ok: false,
44
50
  cwd,
45
51
  warnings,
46
52
  error: {
47
- code: "MIMETIC_LIVE_RUN_UNIMPLEMENTED",
48
- message: "Only run --dry-run is implemented in this open-source-safe slice."
53
+ code: "MIMETIC_UNSUPPORTED_ACTOR",
54
+ message: `Unsupported actor: ${options.actor}`
49
55
  }
50
56
  };
51
57
  }
@@ -62,6 +68,25 @@ export async function runDryRun(options) {
62
68
  }
63
69
  };
64
70
  }
71
+ if (!options.dryRun) {
72
+ const actor = resolveRequestedLocalCodexActor(options.actor);
73
+ if (actor === "codex-tui") {
74
+ return runLocalCodexTui({ ...options, actor, cwd, simCount });
75
+ }
76
+ if (actor === "codex-exec") {
77
+ return runLocalCodexExec({ ...options, actor, cwd, simCount });
78
+ }
79
+ return {
80
+ schema: "mimetic.run-result.v1",
81
+ ok: false,
82
+ cwd,
83
+ warnings,
84
+ error: {
85
+ code: "MIMETIC_LIVE_RUN_UNIMPLEMENTED",
86
+ message: "Only run --dry-run is implemented unless --actor codex-tui, --actor codex-exec, or the matching MIMETIC_ENABLE_LOCAL_CODEX_* env var is set."
87
+ }
88
+ };
89
+ }
65
90
  const now = new Date();
66
91
  const createdAt = now.toISOString();
67
92
  const runId = options.runId ?? `dryrun-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
@@ -162,14 +187,714 @@ export async function runDryRun(options) {
162
187
  warnings
163
188
  };
164
189
  }
190
+ function isLocalCodexActor(value) {
191
+ return value === "codex-tui" || value === "codex-exec";
192
+ }
193
+ function resolveRequestedLocalCodexActor(actor) {
194
+ if (actor && isLocalCodexActor(actor)) {
195
+ return actor;
196
+ }
197
+ if (process.env.MIMETIC_ENABLE_LOCAL_CODEX_TUI === "1") {
198
+ return "codex-tui";
199
+ }
200
+ if (process.env.MIMETIC_ENABLE_LOCAL_CODEX_EXEC === "1") {
201
+ return "codex-exec";
202
+ }
203
+ return undefined;
204
+ }
205
+ async function runLocalCodexTui(options) {
206
+ const warnings = [];
207
+ if (options.simCount !== 1) {
208
+ return {
209
+ schema: "mimetic.run-result.v1",
210
+ ok: false,
211
+ cwd: options.cwd,
212
+ warnings,
213
+ error: {
214
+ code: "MIMETIC_ACTOR_FANOUT_UNIMPLEMENTED",
215
+ message: "Local Codex TUI actor support is intentionally limited to --sims 1 in this slice. Split 4x fanout after the 1x lifecycle is deterministic."
216
+ }
217
+ };
218
+ }
219
+ const timeoutMs = normalizeActorTimeout(options.timeoutMs ?? readEnvInteger("MIMETIC_CODEX_ACTOR_TIMEOUT_MS") ?? LOCAL_CODEX_TUI_DEFAULT_TIMEOUT_MS);
220
+ if (timeoutMs === null) {
221
+ return {
222
+ schema: "mimetic.run-result.v1",
223
+ ok: false,
224
+ cwd: options.cwd,
225
+ warnings,
226
+ error: {
227
+ code: "MIMETIC_INVALID_TIMEOUT",
228
+ message: `--timeout-ms must be an integer between 1 and ${LOCAL_CODEX_TUI_MAX_TIMEOUT_MS}.`
229
+ }
230
+ };
231
+ }
232
+ const now = new Date();
233
+ const createdAt = now.toISOString();
234
+ const runId = options.runId ?? `codex-tui-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
235
+ const artifactRoot = path.join(".mimetic", "runs", runId);
236
+ const absoluteArtifactRoot = path.join(options.cwd, artifactRoot);
237
+ const transcriptPath = path.join(absoluteArtifactRoot, "transcripts", "codex-tui-sanitized.txt");
238
+ const actorTracePath = path.join(absoluteArtifactRoot, "actor.json");
239
+ const eventsPath = path.join(absoluteArtifactRoot, "events.ndjson");
240
+ const packageName = await readPackageName(options.cwd);
241
+ const mimeticSource = await directoryExists(path.join(options.cwd, "mimetic")) ? "present" : "missing";
242
+ const selection = await loadDryRunSelection(options.cwd, mimeticSource);
243
+ if (mimeticSource === "missing") {
244
+ warnings.push("Committed mimetic/ source was not found; using built-in synthetic local actor defaults.");
245
+ }
246
+ warnings.push(...selection.warnings);
247
+ const verdictNonce = randomUUID().slice(0, 12);
248
+ const prompt = buildLocalCodexTuiPrompt(selection, verdictNonce);
249
+ const promptDigest = digestText(prompt);
250
+ const command = resolveLocalCodexTuiCommand(options.cwd, prompt, options.actorCommand);
251
+ const usesDefaultCodexCommand = options.actorCommand === undefined && process.env.MIMETIC_CODEX_ACTOR_COMMAND === undefined;
252
+ const simId = "sim-01";
253
+ const streamId = "sim-01-codex-tui";
254
+ const events = [];
255
+ const appendEvent = async (type, message, level = "info") => {
256
+ events.push({
257
+ id: `event-${String(events.length + 1).padStart(3, "0")}`,
258
+ at: new Date().toISOString(),
259
+ level,
260
+ type,
261
+ message,
262
+ simId,
263
+ streamId
264
+ });
265
+ await writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
266
+ };
267
+ await mkdir(path.dirname(transcriptPath), { recursive: true });
268
+ let actor;
269
+ const trustPreflight = usesDefaultCodexCommand ? await checkCodexWorkspaceTrust(options.cwd) : { ok: true };
270
+ if (!trustPreflight.ok) {
271
+ actor = {
272
+ durationMs: 0,
273
+ reason: trustPreflight.message,
274
+ status: "blocked",
275
+ transcript: `${trustPreflight.message}\nRecovery: ${trustPreflight.recoveryCommand}\n`,
276
+ transcriptBytes: Buffer.byteLength(trustPreflight.message)
277
+ };
278
+ await appendEvent("actor.preflight.blocked", trustPreflight.message, "warn");
279
+ }
280
+ else {
281
+ await appendEvent("actor.spawned", `Spawned local Codex TUI actor command ${command.name} in explicit opt-in mode.`);
282
+ await appendEvent("actor.prompt.submitted", `Submitted bounded public-safe dogfood prompt digest ${promptDigest}; raw prompt omitted from event log.`);
283
+ await appendEvent("actor.running", "Published local Codex TUI running snapshot for Observer polling.");
284
+ const runningAt = new Date().toISOString();
285
+ const runningBundle = {
286
+ schema: RUN_BUNDLE_SCHEMA,
287
+ runId,
288
+ mode: "live",
289
+ simCount: 1,
290
+ createdAt,
291
+ cwd: options.cwd,
292
+ artifactRoot,
293
+ source: {
294
+ packageName,
295
+ mimeticSource,
296
+ git: {
297
+ status: "not_captured",
298
+ note: "Source git state capture is planned for the core primitives slice."
299
+ }
300
+ },
301
+ persona: selection.persona,
302
+ scenario: selection.scenario,
303
+ lifecycle: [
304
+ {
305
+ at: createdAt,
306
+ event: "run.created",
307
+ message: "Live local Codex TUI run created with one explicit opt-in actor."
308
+ },
309
+ {
310
+ at: createdAt,
311
+ event: "actor.selected",
312
+ message: "Selected local codex-tui actor."
313
+ },
314
+ {
315
+ at: runningAt,
316
+ event: "actor.running",
317
+ message: "Local Codex TUI actor is running; Observer data will refresh with sanitized evidence after completion."
318
+ }
319
+ ],
320
+ simulations: [
321
+ {
322
+ id: simId,
323
+ index: 1,
324
+ personaId: selection.persona.id,
325
+ scenarioId: selection.scenario.id,
326
+ status: "running",
327
+ streamKind: "tui",
328
+ mode: "tui-sim",
329
+ progress: 35,
330
+ currentStep: "Local Codex TUI actor running",
331
+ summary: "Local Codex TUI actor is running.",
332
+ streamIds: [streamId],
333
+ startedAt: createdAt,
334
+ updatedAt: runningAt
335
+ }
336
+ ],
337
+ streams: [
338
+ {
339
+ id: streamId,
340
+ simId,
341
+ kind: "tui",
342
+ label: "Local Codex TUI actor",
343
+ status: "running",
344
+ transport: "pty",
345
+ updatedAt: runningAt,
346
+ embed: {
347
+ kind: "terminal",
348
+ title: "Local Codex TUI actor"
349
+ },
350
+ terminal: {
351
+ title: "Local Codex TUI actor",
352
+ format: "ansi",
353
+ stdin: "sent",
354
+ tail: "Codex TUI actor is running; sanitized transcript evidence will be linked after completion."
355
+ },
356
+ completion: {
357
+ checkedAt: runningAt,
358
+ reason: "actor process is still running",
359
+ status: "running"
360
+ },
361
+ artifacts: [
362
+ { label: "run bundle", path: "run.json", kind: "bundle" },
363
+ { label: "review", path: "review.md", kind: "review" },
364
+ { label: "event log", path: "events.ndjson", kind: "events" }
365
+ ]
366
+ }
367
+ ],
368
+ events,
369
+ redaction: {
370
+ status: "passed",
371
+ notes: "Running TUI bundle contains no raw transcript yet; final actor output will be redacted before persistence."
372
+ },
373
+ artifacts: {
374
+ run: "run.json",
375
+ reviewJson: "review.json",
376
+ reviewMarkdown: "review.md",
377
+ observerData: "observer/observer-data.json",
378
+ events: "events.ndjson"
379
+ },
380
+ review: createLocalActorRunningReviewSummary("Codex TUI"),
381
+ feedbackCandidates: []
382
+ };
383
+ await writeRunBundleArtifacts(absoluteArtifactRoot, runningBundle);
384
+ await writeJson(path.join(options.cwd, ".mimetic", "runs", "latest.json"), {
385
+ schema: "mimetic.latest-run.v1",
386
+ runId,
387
+ path: artifactRoot,
388
+ updatedAt: runningAt
389
+ });
390
+ actor = await executeLocalActorCommand(command, {
391
+ cwd: options.cwd,
392
+ timeoutMs,
393
+ verdictNonce
394
+ });
395
+ }
396
+ const completedAt = new Date().toISOString();
397
+ const redactedTranscript = redactSensitiveText(actor.transcript);
398
+ const tail = tailText(redactedTranscript, 6_000);
399
+ const status = actor.status;
400
+ const verdictReason = actor.reason;
401
+ await writeFile(transcriptPath, redactedTranscript.length > 0 ? redactedTranscript : "No transcript output captured.\n", "utf8");
402
+ await writeJson(actorTracePath, {
403
+ schema: "mimetic.local-codex-tui-actor.v1",
404
+ actor: "codex-tui",
405
+ commandName: command.name,
406
+ promptDigest,
407
+ verdictNonce,
408
+ startedAt: createdAt,
409
+ completedAt,
410
+ durationMs: actor.durationMs,
411
+ exitCode: actor.exitCode,
412
+ signal: actor.signal,
413
+ status,
414
+ timeoutMs,
415
+ transcriptBytes: actor.transcriptBytes,
416
+ transcriptPath: "transcripts/codex-tui-sanitized.txt",
417
+ redaction: "passed"
418
+ });
419
+ await appendEvent("actor.observation", `Captured ${actor.transcriptBytes} output byte${actor.transcriptBytes === 1 ? "" : "s"}; sanitized transcript tail recorded with redaction=passed.`);
420
+ await appendEvent("actor.artifact", "Wrote sanitized Codex TUI transcript and actor trace artifacts under the ignored run directory.");
421
+ await appendEvent("actor.verdict", `Local Codex TUI actor verdict ${status}: ${verdictReason}`, status === "passed" ? "info" : status === "timed_out" ? "warn" : "error");
422
+ await appendEvent(status === "timed_out" ? "actor.timeout" : status === "blocked" && !trustPreflight.ok ? "actor.blocked" : "actor.exited", status === "timed_out"
423
+ ? `Actor timed out after ${timeoutMs}ms; last safe observation retained.`
424
+ : status === "blocked" && !trustPreflight.ok
425
+ ? "Actor launch was blocked by preflight before spawn."
426
+ : `Actor exited with code ${actor.exitCode ?? "null"}${actor.signal ? ` and signal ${actor.signal}` : ""}.`, status === "passed" ? "info" : "warn");
427
+ const bundle = {
428
+ schema: RUN_BUNDLE_SCHEMA,
429
+ runId,
430
+ mode: "live",
431
+ simCount: 1,
432
+ createdAt,
433
+ cwd: options.cwd,
434
+ artifactRoot,
435
+ source: {
436
+ packageName,
437
+ mimeticSource,
438
+ git: {
439
+ status: "not_captured",
440
+ note: "Source git state capture is planned for the core primitives slice."
441
+ }
442
+ },
443
+ persona: selection.persona,
444
+ scenario: selection.scenario,
445
+ lifecycle: [
446
+ {
447
+ at: createdAt,
448
+ event: "run.created",
449
+ message: "Live local Codex TUI run created with one explicit opt-in actor."
450
+ },
451
+ {
452
+ at: createdAt,
453
+ event: "actor.selected",
454
+ message: "Selected local codex-tui actor."
455
+ },
456
+ {
457
+ at: completedAt,
458
+ event: "review.skeleton.created",
459
+ message: "Created review skeleton from sanitized actor lifecycle evidence."
460
+ }
461
+ ],
462
+ simulations: [
463
+ {
464
+ id: simId,
465
+ index: 1,
466
+ personaId: selection.persona.id,
467
+ scenarioId: selection.scenario.id,
468
+ status,
469
+ streamKind: "tui",
470
+ mode: "tui-sim",
471
+ progress: 100,
472
+ currentStep: status === "passed" ? "Local Codex TUI actor completed" : "Local Codex TUI actor needs review",
473
+ summary: `Local Codex TUI actor ${status}: ${verdictReason}`,
474
+ streamIds: [streamId],
475
+ startedAt: createdAt,
476
+ updatedAt: completedAt
477
+ }
478
+ ],
479
+ streams: [
480
+ {
481
+ id: streamId,
482
+ simId,
483
+ kind: "tui",
484
+ label: "Local Codex TUI actor",
485
+ status,
486
+ transport: "pty",
487
+ updatedAt: completedAt,
488
+ embed: {
489
+ kind: "terminal",
490
+ title: "Local Codex TUI actor"
491
+ },
492
+ terminal: {
493
+ title: "Local Codex TUI actor",
494
+ format: "ansi",
495
+ stdin: "sent",
496
+ tail
497
+ },
498
+ completion: {
499
+ checkedAt: completedAt,
500
+ ...(actor.exitCode === undefined ? {} : { exitCode: actor.exitCode }),
501
+ logTail: tail,
502
+ reason: verdictReason,
503
+ status
504
+ },
505
+ artifacts: [
506
+ { label: "run bundle", path: "run.json", kind: "bundle" },
507
+ { label: "review", path: "review.md", kind: "review" },
508
+ { label: "event log", path: "events.ndjson", kind: "events" },
509
+ { label: "sanitized transcript", path: "transcripts/codex-tui-sanitized.txt", kind: "log" },
510
+ { label: "actor trace", path: "actor.json", kind: "trace" }
511
+ ]
512
+ }
513
+ ],
514
+ events,
515
+ redaction: {
516
+ status: "passed",
517
+ notes: "Actor output was redacted before transcript and bundle persistence; raw prompt is omitted from event log."
518
+ },
519
+ artifacts: {
520
+ run: "run.json",
521
+ reviewJson: "review.json",
522
+ reviewMarkdown: "review.md",
523
+ observerData: "observer/observer-data.json",
524
+ events: "events.ndjson"
525
+ },
526
+ review: createLocalActorReviewSummary("Codex TUI", status, verdictReason),
527
+ feedbackCandidates: []
528
+ };
529
+ await writeRunBundleArtifacts(absoluteArtifactRoot, bundle);
530
+ await writeJson(path.join(options.cwd, ".mimetic", "runs", "latest.json"), {
531
+ schema: "mimetic.latest-run.v1",
532
+ runId,
533
+ path: artifactRoot,
534
+ updatedAt: completedAt
535
+ });
536
+ return {
537
+ schema: "mimetic.run-result.v1",
538
+ ok: status === "passed",
539
+ runId,
540
+ mode: "live",
541
+ simCount: 1,
542
+ cwd: options.cwd,
543
+ artifactRoot,
544
+ bundlePath: path.join(artifactRoot, "run.json"),
545
+ reviewPath: path.join(artifactRoot, "review.md"),
546
+ latestPath: path.join(".mimetic", "runs", "latest.json"),
547
+ warnings,
548
+ ...(status === "passed"
549
+ ? {}
550
+ : {
551
+ error: {
552
+ code: "MIMETIC_LOCAL_CODEX_TUI_FAILED",
553
+ message: `Local Codex TUI actor ${status}: ${verdictReason}`
554
+ }
555
+ })
556
+ };
557
+ }
558
+ function buildLocalCodexExecBundle(args) {
559
+ return {
560
+ schema: RUN_BUNDLE_SCHEMA,
561
+ runId: args.runId,
562
+ mode: "live",
563
+ simCount: args.simCount,
564
+ createdAt: args.createdAt,
565
+ cwd: args.cwd,
566
+ artifactRoot: args.artifactRoot,
567
+ source: {
568
+ packageName: args.packageName,
569
+ mimeticSource: args.mimeticSource,
570
+ git: {
571
+ status: "not_captured",
572
+ note: "Source git state capture is planned for the core primitives slice."
573
+ }
574
+ },
575
+ persona: args.persona,
576
+ scenario: args.scenario,
577
+ lifecycle: args.lifecycle,
578
+ simulations: args.lanes.map((lane, index) => ({
579
+ id: lane.simId,
580
+ index: index + 1,
581
+ personaId: `codex-exec-${lane.focus.id}`,
582
+ scenarioId: args.scenario.id,
583
+ status: lane.status,
584
+ streamKind: "terminal",
585
+ mode: "cli-sim",
586
+ progress: lane.progress,
587
+ currentStep: lane.currentStep,
588
+ summary: lane.summary,
589
+ streamIds: [lane.streamId],
590
+ startedAt: args.createdAt,
591
+ updatedAt: lane.updatedAt
592
+ })),
593
+ streams: args.lanes.map((lane) => {
594
+ const artifacts = [
595
+ { label: "run bundle", path: "run.json", kind: "bundle" },
596
+ { label: "review", path: "review.md", kind: "review" },
597
+ { label: "event log", path: "events.ndjson", kind: "events" },
598
+ ...(lane.transcriptPath ? [{ label: "sanitized transcript", path: lane.transcriptPath, kind: "log" }] : []),
599
+ ...(lane.tracePath ? [{ label: "actor trace", path: lane.tracePath, kind: "trace" }] : [])
600
+ ];
601
+ return {
602
+ id: lane.streamId,
603
+ simId: lane.simId,
604
+ kind: "terminal",
605
+ label: `Local Codex exec - ${lane.focus.label}`,
606
+ status: lane.status,
607
+ transport: "snapshot",
608
+ updatedAt: lane.updatedAt,
609
+ embed: {
610
+ kind: "terminal",
611
+ title: `Local Codex exec - ${lane.focus.label}`
612
+ },
613
+ terminal: {
614
+ title: `Local Codex exec - ${lane.focus.label}`,
615
+ format: "plain",
616
+ stdin: "sent",
617
+ tail: lane.terminalTail
618
+ },
619
+ ...(lane.completion ? { completion: lane.completion } : {}),
620
+ artifacts
621
+ };
622
+ }),
623
+ events: args.events,
624
+ redaction: {
625
+ status: "passed",
626
+ notes: "Actor output was redacted before transcript and bundle persistence; raw prompt is omitted from event log."
627
+ },
628
+ artifacts: {
629
+ run: "run.json",
630
+ reviewJson: "review.json",
631
+ reviewMarkdown: "review.md",
632
+ observerData: "observer/observer-data.json",
633
+ events: "events.ndjson"
634
+ },
635
+ review: args.review,
636
+ feedbackCandidates: []
637
+ };
638
+ }
639
+ async function runLocalCodexExec(options) {
640
+ const warnings = [];
641
+ if (options.simCount > 4) {
642
+ return {
643
+ schema: "mimetic.run-result.v1",
644
+ ok: false,
645
+ cwd: options.cwd,
646
+ warnings,
647
+ error: {
648
+ code: "MIMETIC_ACTOR_FANOUT_UNIMPLEMENTED",
649
+ message: "Local Codex exec actor fanout is intentionally limited to --sims 4 in this slice."
650
+ }
651
+ };
652
+ }
653
+ const timeoutMs = normalizeActorTimeout(options.timeoutMs ?? readEnvInteger("MIMETIC_CODEX_ACTOR_TIMEOUT_MS") ?? LOCAL_CODEX_TUI_DEFAULT_TIMEOUT_MS);
654
+ if (timeoutMs === null) {
655
+ return {
656
+ schema: "mimetic.run-result.v1",
657
+ ok: false,
658
+ cwd: options.cwd,
659
+ warnings,
660
+ error: {
661
+ code: "MIMETIC_INVALID_TIMEOUT",
662
+ message: `--timeout-ms must be an integer between 1 and ${LOCAL_CODEX_TUI_MAX_TIMEOUT_MS}.`
663
+ }
664
+ };
665
+ }
666
+ const now = new Date();
667
+ const createdAt = now.toISOString();
668
+ const runId = options.runId ?? `codex-exec-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
669
+ const artifactRoot = path.join(".mimetic", "runs", runId);
670
+ const absoluteArtifactRoot = path.join(options.cwd, artifactRoot);
671
+ const eventsPath = path.join(absoluteArtifactRoot, "events.ndjson");
672
+ const packageName = await readPackageName(options.cwd);
673
+ const mimeticSource = await directoryExists(path.join(options.cwd, "mimetic")) ? "present" : "missing";
674
+ const selection = await loadDryRunSelection(options.cwd, mimeticSource);
675
+ if (mimeticSource === "missing") {
676
+ warnings.push("Committed mimetic/ source was not found; using built-in synthetic local actor defaults.");
677
+ }
678
+ warnings.push(...selection.warnings);
679
+ const events = [];
680
+ const pushEvent = (type, message, level = "info", simId, streamId) => {
681
+ events.push({
682
+ id: `event-${String(events.length + 1).padStart(3, "0")}`,
683
+ at: new Date().toISOString(),
684
+ level,
685
+ type,
686
+ message,
687
+ ...(simId === undefined ? {} : { simId }),
688
+ ...(streamId === undefined ? {} : { streamId })
689
+ });
690
+ };
691
+ await mkdir(path.join(absoluteArtifactRoot, "transcripts"), { recursive: true });
692
+ await mkdir(path.join(absoluteArtifactRoot, "actors"), { recursive: true });
693
+ await mkdir(path.join(absoluteArtifactRoot, "observer"), { recursive: true });
694
+ await writeJson(path.join(options.cwd, ".mimetic", "runs", "latest.json"), {
695
+ schema: "mimetic.latest-run.v1",
696
+ runId,
697
+ path: artifactRoot,
698
+ updatedAt: createdAt
699
+ });
700
+ const lanes = Array.from({ length: options.simCount }, (_, index) => {
701
+ const focus = localCodexExecFocus(index);
702
+ const simId = `sim-${String(index + 1).padStart(2, "0")}`;
703
+ const streamId = `${simId}-codex-exec`;
704
+ const prompt = buildLocalCodexExecPrompt(selection, {
705
+ focus,
706
+ index: index + 1,
707
+ total: options.simCount
708
+ });
709
+ const promptDigest = digestText(prompt);
710
+ const command = resolveLocalCodexExecCommand(options.cwd, prompt, options.actorCommand);
711
+ pushEvent("actor.spawned", `Spawned local Codex exec actor lane ${index + 1}/${options.simCount} (${focus.label}) command ${command.name} in explicit opt-in mode.`, "info", simId, streamId);
712
+ pushEvent("actor.prompt.submitted", `Submitted bounded public-safe dogfood prompt digest ${promptDigest}; raw prompt omitted from event log.`, "info", simId, streamId);
713
+ return { command, focus, promptDigest, simId, streamId };
714
+ });
715
+ for (const lane of lanes) {
716
+ pushEvent("actor.running", "Published local Codex exec running snapshot for Observer polling.", "info", lane.simId, lane.streamId);
717
+ }
718
+ await writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
719
+ const baseLifecycle = [
720
+ {
721
+ at: createdAt,
722
+ event: "run.created",
723
+ message: `Live local Codex exec run created with ${options.simCount} explicit opt-in actor${options.simCount === 1 ? "" : "s"}.`
724
+ },
725
+ {
726
+ at: createdAt,
727
+ event: "actor.selected",
728
+ message: "Selected local codex-exec actor."
729
+ }
730
+ ];
731
+ const runningAt = new Date().toISOString();
732
+ const runningBundle = buildLocalCodexExecBundle({
733
+ runId,
734
+ simCount: options.simCount,
735
+ createdAt,
736
+ cwd: options.cwd,
737
+ artifactRoot,
738
+ packageName,
739
+ mimeticSource,
740
+ persona: selection.persona,
741
+ scenario: selection.scenario,
742
+ lifecycle: [
743
+ ...baseLifecycle,
744
+ {
745
+ at: runningAt,
746
+ event: "actor.running",
747
+ message: "Local Codex exec actor lanes are running; Observer data will refresh as sanitized evidence arrives."
748
+ }
749
+ ],
750
+ lanes: lanes.map((lane) => ({
751
+ focus: lane.focus,
752
+ simId: lane.simId,
753
+ streamId: lane.streamId,
754
+ status: "running",
755
+ progress: 35,
756
+ currentStep: "Local Codex exec actor running",
757
+ summary: `Local Codex exec actor ${lane.focus.label} is running.`,
758
+ terminalTail: "Codex exec actor is running; sanitized transcript evidence will be linked after completion.",
759
+ updatedAt: runningAt,
760
+ completion: {
761
+ checkedAt: runningAt,
762
+ reason: "actor process is still running",
763
+ status: "running"
764
+ }
765
+ })),
766
+ events,
767
+ review: createLocalActorRunningReviewSummary(options.simCount === 1 ? "Codex exec" : "Codex exec fanout")
768
+ });
769
+ await writeRunBundleArtifacts(absoluteArtifactRoot, runningBundle);
770
+ const laneResults = await Promise.all(lanes.map(async (lane) => {
771
+ const actor = await executeLocalActorCommand(lane.command, {
772
+ cwd: options.cwd,
773
+ timeoutMs
774
+ });
775
+ const redactedTranscript = redactSensitiveText(actor.transcript);
776
+ const tail = tailText(redactedTranscript, 6_000);
777
+ const transcriptPath = options.simCount === 1
778
+ ? "transcripts/codex-exec-sanitized.jsonl"
779
+ : `transcripts/${lane.streamId}-sanitized.jsonl`;
780
+ const tracePath = options.simCount === 1 ? "actor.json" : `actors/${lane.streamId}.json`;
781
+ await writeFile(path.join(absoluteArtifactRoot, transcriptPath), redactedTranscript.length > 0 ? redactedTranscript : "No transcript output captured.\n", "utf8");
782
+ await writeJson(path.join(absoluteArtifactRoot, tracePath), {
783
+ schema: "mimetic.local-codex-exec-actor.v1",
784
+ actor: "codex-exec",
785
+ commandName: lane.command.name,
786
+ focusId: lane.focus.id,
787
+ promptDigest: lane.promptDigest,
788
+ startedAt: createdAt,
789
+ completedAt: new Date().toISOString(),
790
+ durationMs: actor.durationMs,
791
+ exitCode: actor.exitCode,
792
+ signal: actor.signal,
793
+ status: actor.status,
794
+ timeoutMs,
795
+ transcriptBytes: actor.transcriptBytes,
796
+ transcriptPath,
797
+ redaction: "passed"
798
+ });
799
+ return {
800
+ actor,
801
+ command: lane.command,
802
+ focus: lane.focus,
803
+ promptDigest: lane.promptDigest,
804
+ simId: lane.simId,
805
+ streamId: lane.streamId,
806
+ tail,
807
+ tracePath,
808
+ transcriptPath
809
+ };
810
+ }));
811
+ const completedAt = new Date().toISOString();
812
+ const laneStatuses = laneResults.map((result) => result.actor.status);
813
+ const status = aggregateActorStatus(laneStatuses);
814
+ const verdictReason = options.simCount === 1
815
+ ? laneResults[0]?.actor.reason ?? "actor did not return a result"
816
+ : summarizeExecFanout(laneStatuses);
817
+ for (const result of laneResults) {
818
+ pushEvent("actor.observation", `Captured ${result.actor.transcriptBytes} output byte${result.actor.transcriptBytes === 1 ? "" : "s"}; sanitized transcript tail recorded with redaction=passed.`, "info", result.simId, result.streamId);
819
+ pushEvent("actor.artifact", "Wrote sanitized Codex exec transcript and actor trace artifacts under the ignored run directory.", "info", result.simId, result.streamId);
820
+ pushEvent("actor.verdict", `Local Codex exec actor lane ${result.focus.label} verdict ${result.actor.status}: ${result.actor.reason}`, result.actor.status === "passed" ? "info" : result.actor.status === "timed_out" ? "warn" : "error", result.simId, result.streamId);
821
+ pushEvent(result.actor.status === "timed_out" ? "actor.timeout" : "actor.exited", result.actor.status === "timed_out"
822
+ ? `Actor timed out after ${timeoutMs}ms; last safe observation retained.`
823
+ : `Actor exited with code ${result.actor.exitCode ?? "null"}${result.actor.signal ? ` and signal ${result.actor.signal}` : ""}.`, result.actor.status === "passed" ? "info" : "warn", result.simId, result.streamId);
824
+ }
825
+ await writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
826
+ const bundle = buildLocalCodexExecBundle({
827
+ runId,
828
+ simCount: options.simCount,
829
+ createdAt,
830
+ cwd: options.cwd,
831
+ artifactRoot,
832
+ packageName,
833
+ mimeticSource,
834
+ persona: selection.persona,
835
+ scenario: selection.scenario,
836
+ lifecycle: [
837
+ ...baseLifecycle,
838
+ {
839
+ at: completedAt,
840
+ event: "review.skeleton.created",
841
+ message: "Created review skeleton from sanitized actor lifecycle evidence."
842
+ }
843
+ ],
844
+ lanes: laneResults.map((result) => ({
845
+ focus: result.focus,
846
+ simId: result.simId,
847
+ streamId: result.streamId,
848
+ status: result.actor.status,
849
+ progress: 100,
850
+ currentStep: result.actor.status === "passed" ? "Local Codex exec actor completed" : "Local Codex exec actor needs review",
851
+ summary: `Local Codex exec actor ${result.focus.label} ${result.actor.status}: ${result.actor.reason}`,
852
+ terminalTail: result.tail,
853
+ updatedAt: completedAt,
854
+ transcriptPath: result.transcriptPath,
855
+ tracePath: result.tracePath,
856
+ completion: {
857
+ checkedAt: completedAt,
858
+ ...(result.actor.exitCode === undefined ? {} : { exitCode: result.actor.exitCode }),
859
+ logTail: result.tail,
860
+ reason: result.actor.reason,
861
+ status: result.actor.status
862
+ }
863
+ })),
864
+ events,
865
+ review: createLocalActorReviewSummary(options.simCount === 1 ? "Codex exec" : "Codex exec fanout", status, verdictReason)
866
+ });
867
+ await writeRunBundleArtifacts(absoluteArtifactRoot, bundle);
868
+ return {
869
+ schema: "mimetic.run-result.v1",
870
+ ok: status === "passed",
871
+ runId,
872
+ mode: "live",
873
+ simCount: options.simCount,
874
+ cwd: options.cwd,
875
+ artifactRoot,
876
+ bundlePath: path.join(artifactRoot, "run.json"),
877
+ reviewPath: path.join(artifactRoot, "review.md"),
878
+ latestPath: path.join(".mimetic", "runs", "latest.json"),
879
+ warnings,
880
+ ...(status === "passed"
881
+ ? {}
882
+ : {
883
+ error: {
884
+ code: "MIMETIC_LOCAL_CODEX_EXEC_FAILED",
885
+ message: `Local Codex exec actor ${status}: ${verdictReason}`
886
+ }
887
+ })
888
+ };
889
+ }
165
890
  function buildSyntheticObserverFixtures(args) {
166
891
  const templates = [
167
892
  {
168
893
  kind: "ui",
169
- mode: "ui-sim",
894
+ mode: "browser-sim",
170
895
  label: "UI journey",
171
896
  currentStep: "Route and viewport contract captured",
172
- summary: "UI sim lane reserved for browser/VNC playback, screenshots, route state, and interaction trace.",
897
+ summary: "Browser lane reserved for VNC playback, screenshots, route state, and interaction trace.",
173
898
  tail: "open target app\nresolve first-run route\ncapture viewport state\nrecord interaction trace",
174
899
  viewport: { width: 1440, height: 960, deviceScaleFactor: 1 }
175
900
  },
@@ -193,7 +918,7 @@ function buildSyntheticObserverFixtures(args) {
193
918
  },
194
919
  {
195
920
  kind: "codex-ui",
196
- mode: "codex-ui-sim",
921
+ mode: "codex-app-sim",
197
922
  label: "Codex UI",
198
923
  currentStep: "App-server embed contract captured",
199
924
  summary: "Codex UI lane reserved for app-server sessions that can be watched beside terminal evidence.",
@@ -307,6 +1032,481 @@ function streamTransport(kind) {
307
1032
  return "polling";
308
1033
  return "snapshot";
309
1034
  }
1035
+ function resolveLocalCodexTuiCommand(cwd, prompt, overrideCommand) {
1036
+ const envCommand = process.env.MIMETIC_CODEX_ACTOR_COMMAND;
1037
+ const commandParts = overrideCommand && overrideCommand.length > 0
1038
+ ? overrideCommand
1039
+ : envCommand
1040
+ ? parseCommandLine(envCommand)
1041
+ : defaultLocalCodexTuiCommand(cwd, prompt);
1042
+ const [command, ...args] = commandParts;
1043
+ if (!command) {
1044
+ const [fallbackCommand, ...fallbackArgs] = defaultLocalCodexTuiCommand(cwd, prompt);
1045
+ return {
1046
+ command: fallbackCommand ?? "codex",
1047
+ args: fallbackArgs,
1048
+ name: fallbackCommand ? path.basename(fallbackCommand) : "codex"
1049
+ };
1050
+ }
1051
+ return {
1052
+ command,
1053
+ args,
1054
+ name: path.basename(command)
1055
+ };
1056
+ }
1057
+ function defaultLocalCodexTuiCommand(cwd, prompt) {
1058
+ const codexParts = [
1059
+ "codex",
1060
+ "--no-alt-screen",
1061
+ "-C",
1062
+ cwd,
1063
+ "--sandbox",
1064
+ "read-only",
1065
+ "--ask-for-approval",
1066
+ "never",
1067
+ prompt
1068
+ ];
1069
+ if (process.platform === "linux") {
1070
+ return ["script", "-qfec", shellJoin(codexParts), "/dev/null"];
1071
+ }
1072
+ return codexParts;
1073
+ }
1074
+ function resolveLocalCodexExecCommand(cwd, prompt, overrideCommand) {
1075
+ const envCommand = process.env.MIMETIC_CODEX_ACTOR_COMMAND;
1076
+ const commandParts = overrideCommand && overrideCommand.length > 0
1077
+ ? overrideCommand
1078
+ : envCommand
1079
+ ? parseCommandLine(envCommand)
1080
+ : defaultLocalCodexExecCommand(cwd, prompt);
1081
+ const [command, ...args] = commandParts;
1082
+ if (!command) {
1083
+ const [fallbackCommand, ...fallbackArgs] = defaultLocalCodexExecCommand(cwd, prompt);
1084
+ return {
1085
+ command: fallbackCommand ?? "codex",
1086
+ args: fallbackArgs,
1087
+ name: fallbackCommand ? path.basename(fallbackCommand) : "codex"
1088
+ };
1089
+ }
1090
+ return {
1091
+ command,
1092
+ args,
1093
+ name: path.basename(command)
1094
+ };
1095
+ }
1096
+ function defaultLocalCodexExecCommand(cwd, prompt) {
1097
+ return [
1098
+ "codex",
1099
+ "exec",
1100
+ "--skip-git-repo-check",
1101
+ "--ignore-rules",
1102
+ "--ephemeral",
1103
+ "-C",
1104
+ cwd,
1105
+ "--sandbox",
1106
+ "read-only",
1107
+ "--json",
1108
+ prompt
1109
+ ];
1110
+ }
1111
+ function executeLocalActorCommand(command, options) {
1112
+ const startedAt = Date.now();
1113
+ let transcript = "";
1114
+ let transcriptBytes = 0;
1115
+ let terminalQueryBuffer = "";
1116
+ let observedMarkerStatus = null;
1117
+ let stoppingAfterMarker = false;
1118
+ let timedOut = false;
1119
+ let settled = false;
1120
+ let timer;
1121
+ let markerKillTimer;
1122
+ return new Promise((resolve) => {
1123
+ const finish = (result) => {
1124
+ if (settled) {
1125
+ return;
1126
+ }
1127
+ settled = true;
1128
+ clearTimeout(timer);
1129
+ clearTimeout(markerKillTimer);
1130
+ const normalizedTranscript = normalizeLocalActorTranscript(transcript);
1131
+ resolve({
1132
+ ...result,
1133
+ durationMs: Date.now() - startedAt,
1134
+ transcript: redactSensitiveText(normalizedTranscript),
1135
+ transcriptBytes
1136
+ });
1137
+ };
1138
+ const child = spawn(command.command, command.args, {
1139
+ cwd: options.cwd,
1140
+ env: {
1141
+ ...process.env,
1142
+ TERM: process.env.TERM ?? "xterm-256color",
1143
+ COLUMNS: process.env.COLUMNS ?? "120",
1144
+ LINES: process.env.LINES ?? "40",
1145
+ ...(options.verdictNonce ? { MIMETIC_ACTOR_VERDICT_NONCE: options.verdictNonce } : {})
1146
+ },
1147
+ stdio: ["pipe", "pipe", "pipe"]
1148
+ });
1149
+ timer = setTimeout(() => {
1150
+ timedOut = true;
1151
+ child.kill("SIGTERM");
1152
+ setTimeout(() => {
1153
+ if (timedOut) {
1154
+ child.kill("SIGKILL");
1155
+ }
1156
+ }, 2_000).unref();
1157
+ }, options.timeoutMs);
1158
+ timer.unref();
1159
+ const capture = (chunk) => {
1160
+ transcriptBytes += chunk.byteLength;
1161
+ transcript = limitTranscript(transcript + chunk.toString("utf8"));
1162
+ terminalQueryBuffer = respondToTerminalQueries(terminalQueryBuffer, chunk, child.stdin);
1163
+ const markerStatus = observedMarkerStatus ?? extractLocalActorVerdict(normalizeLocalActorTranscript(transcript), options.verdictNonce);
1164
+ if (markerStatus && !observedMarkerStatus) {
1165
+ observedMarkerStatus = markerStatus;
1166
+ stoppingAfterMarker = true;
1167
+ child.kill("SIGTERM");
1168
+ markerKillTimer = setTimeout(() => {
1169
+ child.kill("SIGKILL");
1170
+ }, 2_000);
1171
+ markerKillTimer.unref();
1172
+ }
1173
+ };
1174
+ child.stdout?.on("data", capture);
1175
+ child.stderr?.on("data", capture);
1176
+ child.once("error", (error) => {
1177
+ finish({
1178
+ status: "blocked",
1179
+ reason: `actor command could not start: ${error.message}`
1180
+ });
1181
+ });
1182
+ child.once("close", (code, signal) => {
1183
+ if (timedOut || (Date.now() - startedAt >= options.timeoutMs && code === null)) {
1184
+ timedOut = false;
1185
+ finish({
1186
+ status: "timed_out",
1187
+ reason: `actor exceeded ${options.timeoutMs}ms timeout`,
1188
+ ...(signal === null ? {} : { signal })
1189
+ });
1190
+ return;
1191
+ }
1192
+ const markerStatus = observedMarkerStatus ?? extractLocalActorVerdict(normalizeLocalActorTranscript(transcript), options.verdictNonce);
1193
+ const processFailed = code !== 0 && !(stoppingAfterMarker && markerStatus);
1194
+ const status = processFailed
1195
+ ? markerStatus === "blocked" ? "blocked" : "failed"
1196
+ : markerStatus ?? "passed";
1197
+ finish({
1198
+ status,
1199
+ reason: processFailed
1200
+ ? markerStatus === "blocked"
1201
+ ? `actor reported blocked verdict marker and exited with code ${code ?? "null"}`
1202
+ : `actor process exited with code ${code ?? "null"}`
1203
+ : markerStatus
1204
+ ? `actor reported ${markerStatus} verdict marker`
1205
+ : "actor process exited successfully",
1206
+ ...(code === null ? {} : { exitCode: code }),
1207
+ ...(signal === null ? {} : { signal })
1208
+ });
1209
+ });
1210
+ });
1211
+ }
1212
+ function respondToTerminalQueries(currentBuffer, chunk, stdin) {
1213
+ if (!stdin || !stdin.writable) {
1214
+ return "";
1215
+ }
1216
+ let buffer = `${currentBuffer}${chunk.toString("latin1")}`;
1217
+ while (true) {
1218
+ const cprIndex = buffer.indexOf("\x1b[6n");
1219
+ const oscMatch = /\x1b\](10|11|12);\?(?:\x07|\x1b\\)/.exec(buffer);
1220
+ const oscIndex = oscMatch?.index ?? -1;
1221
+ if (cprIndex === -1 && oscIndex === -1) {
1222
+ break;
1223
+ }
1224
+ if (cprIndex !== -1 && (oscIndex === -1 || cprIndex < oscIndex)) {
1225
+ stdin.write("\x1b[24;120R");
1226
+ buffer = buffer.slice(cprIndex + "\x1b[6n".length);
1227
+ continue;
1228
+ }
1229
+ const colorSlot = oscMatch?.[1] ?? "10";
1230
+ stdin.write(terminalColorResponse(colorSlot));
1231
+ buffer = buffer.slice((oscIndex === -1 ? 0 : oscIndex) + (oscMatch?.[0].length ?? 0));
1232
+ }
1233
+ return buffer.slice(-128);
1234
+ }
1235
+ function terminalColorResponse(slot) {
1236
+ if (slot === "11") {
1237
+ return "\x1b]11;rgb:0000/0000/0000\x07";
1238
+ }
1239
+ return `\x1b]${slot};rgb:ffff/ffff/ffff\x07`;
1240
+ }
1241
+ function normalizeLocalActorTranscript(transcript) {
1242
+ return transcript
1243
+ .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
1244
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
1245
+ .replace(/\x1b[78=>]/g, "")
1246
+ .replace(/\r\n/g, "\n")
1247
+ .replace(/\r/g, "\n")
1248
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
1249
+ }
1250
+ function extractLocalActorVerdict(transcript, verdictNonce) {
1251
+ const compactTranscript = transcript.replace(/\s+/g, "");
1252
+ const match = verdictNonce
1253
+ ? new RegExp(`MIMETIC_ACTOR_VERDICT=(passed|blocked|failed)MIMETIC_ACTOR_NONCE=${escapeRegExp(verdictNonce)}`, "i").exec(compactTranscript)
1254
+ : /MIMETIC_ACTOR_VERDICT=(passed|blocked|failed)/i.exec(compactTranscript);
1255
+ if (!match) {
1256
+ return null;
1257
+ }
1258
+ return match[1]?.toLowerCase();
1259
+ }
1260
+ async function checkCodexWorkspaceTrust(cwd) {
1261
+ if (process.env.MIMETIC_SKIP_CODEX_TRUST_PREFLIGHT === "1") {
1262
+ return { ok: true };
1263
+ }
1264
+ const trustRoot = await detectCodexTrustRoot(cwd);
1265
+ if (!trustRoot) {
1266
+ return { ok: true };
1267
+ }
1268
+ const configPath = path.join(process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"), "config.toml");
1269
+ const configText = await readTextIfExists(configPath);
1270
+ if (configText && codexConfigTrustsProject(configText, trustRoot)) {
1271
+ return { ok: true };
1272
+ }
1273
+ return {
1274
+ ok: false,
1275
+ trustRoot,
1276
+ message: `Codex workspace trust preflight blocked local TUI launch; trust root is not explicitly trusted as an exact Codex project root: ${trustRoot}`,
1277
+ recoveryCommand: `codex --no-alt-screen -C ${shellQuote(trustRoot)}`
1278
+ };
1279
+ }
1280
+ async function detectCodexTrustRoot(cwd) {
1281
+ const worktreeRoot = await findGitWorktreeRoot(cwd);
1282
+ if (!worktreeRoot) {
1283
+ return null;
1284
+ }
1285
+ const dotGitPath = path.join(worktreeRoot, ".git");
1286
+ if (await directoryExists(dotGitPath)) {
1287
+ return worktreeRoot;
1288
+ }
1289
+ const gitFile = await readTextIfExists(dotGitPath);
1290
+ if (!gitFile?.startsWith("gitdir:")) {
1291
+ return worktreeRoot;
1292
+ }
1293
+ const gitDir = gitFile.slice("gitdir:".length).trim();
1294
+ const absoluteGitDir = path.isAbsolute(gitDir) ? gitDir : path.resolve(worktreeRoot, gitDir);
1295
+ const commonDirText = await readTextIfExists(path.join(absoluteGitDir, "commondir"));
1296
+ if (!commonDirText) {
1297
+ return worktreeRoot;
1298
+ }
1299
+ const commonDir = commonDirText.trim();
1300
+ const absoluteCommonDir = path.resolve(absoluteGitDir, commonDir);
1301
+ return path.basename(absoluteCommonDir) === ".git" ? path.dirname(absoluteCommonDir) : worktreeRoot;
1302
+ }
1303
+ async function findGitWorktreeRoot(cwd) {
1304
+ let current = path.resolve(cwd);
1305
+ while (true) {
1306
+ if (await fileExists(path.join(current, ".git")) || await directoryExists(path.join(current, ".git"))) {
1307
+ return current;
1308
+ }
1309
+ const parent = path.dirname(current);
1310
+ if (parent === current) {
1311
+ return null;
1312
+ }
1313
+ current = parent;
1314
+ }
1315
+ }
1316
+ function codexConfigTrustsProject(configText, trustRoot) {
1317
+ const sectionPattern = /^\[projects\."((?:\\.|[^"\\])*)"\]\s*$/gm;
1318
+ let sectionMatch;
1319
+ while ((sectionMatch = sectionPattern.exec(configText)) !== null) {
1320
+ const projectPath = unescapeTomlString(sectionMatch[1] ?? "");
1321
+ const afterSection = configText.slice(sectionMatch.index + sectionMatch[0].length);
1322
+ const nextSectionIndex = afterSection.search(/^\[/m);
1323
+ const sectionBody = nextSectionIndex === -1 ? afterSection : afterSection.slice(0, nextSectionIndex);
1324
+ if (/^trust_level\s*=\s*"trusted"\s*$/m.test(sectionBody) && isSamePath(projectPath, trustRoot)) {
1325
+ return true;
1326
+ }
1327
+ }
1328
+ return false;
1329
+ }
1330
+ function unescapeTomlString(value) {
1331
+ return value.replace(/\\(["\\])/g, "$1");
1332
+ }
1333
+ function isSamePath(candidatePath, targetPath) {
1334
+ const candidate = path.resolve(candidatePath);
1335
+ const target = path.resolve(targetPath);
1336
+ return candidate === target;
1337
+ }
1338
+ function normalizeActorTimeout(value) {
1339
+ if (!Number.isInteger(value) || value === undefined || value < 1 || value > LOCAL_CODEX_TUI_MAX_TIMEOUT_MS) {
1340
+ return null;
1341
+ }
1342
+ return value;
1343
+ }
1344
+ function readEnvInteger(name) {
1345
+ const value = process.env[name];
1346
+ if (value === undefined || value.trim() === "") {
1347
+ return undefined;
1348
+ }
1349
+ return /^\d+$/.test(value) ? Number.parseInt(value, 10) : Number.NaN;
1350
+ }
1351
+ function buildLocalCodexTuiPrompt(selection, verdictNonce) {
1352
+ return [
1353
+ "You are a Mimetic local Codex TUI dogfood actor.",
1354
+ `Persona: ${selection.persona.name}.`,
1355
+ `Scenario: ${selection.scenario.title}.`,
1356
+ "Inspect this public repository's Mimetic setup, run evidence, and Observer affordances.",
1357
+ "Run at most two read-only inspection commands; prefer file reads, `node dist/cli.js --help`, or `pnpm typecheck` when available.",
1358
+ "Do not run commands that write runtime artifacts or temp config, including `pnpm mimetic`, `mimetic watch`, `mimetic feedback`, `mimetic init`, tests, builds, installs, or commands that write `.mimetic/`.",
1359
+ "If the strongest proof would require writes in this read-only sandbox, inspect existing artifacts instead and name the write-required proof as a follow-up.",
1360
+ "Use passed when read-only inspection confirms the committed harness and existing evidence contract; write-required follow-ups alone are not blockers.",
1361
+ "Do not print secrets, do not commit, do not push, do not open GitHub issues, and do not use private data.",
1362
+ "Finish by summarizing one public-safe harness improvement.",
1363
+ `Then print exactly one final machine-readable line in this format: MIMETIC_ACTOR_VERDICT=<status> MIMETIC_ACTOR_NONCE=${verdictNonce}.`,
1364
+ "Replace <status> with exactly one lowercase word: passed, blocked, or failed."
1365
+ ].join(" ");
1366
+ }
1367
+ function localCodexExecFocus(index) {
1368
+ const focuses = [
1369
+ {
1370
+ id: "install-readability",
1371
+ label: "install/readability",
1372
+ instruction: "audit whether a new user can understand the committed Mimetic dogfood setup quickly",
1373
+ suggestedCommands: [
1374
+ "test -r mimetic/README.md && sed -n '1,40p' mimetic/README.md",
1375
+ "test -r mimetic/config.ts && wc -l mimetic/config.ts"
1376
+ ]
1377
+ },
1378
+ {
1379
+ id: "public-safety-trust",
1380
+ label: "public-safety/trust",
1381
+ instruction: "check the local actor boundaries, public-safety claims, and trust-bootstrap language",
1382
+ suggestedCommands: [
1383
+ "test -r mimetic/coverage-map.md && sed -n '1,80p' mimetic/coverage-map.md",
1384
+ "test -r docs/architecture/local-codex-tui-actor.md && sed -n '1,80p' docs/architecture/local-codex-tui-actor.md"
1385
+ ]
1386
+ },
1387
+ {
1388
+ id: "observer-evidence",
1389
+ label: "Observer/evidence",
1390
+ instruction: "inspect whether run evidence and Observer expectations are easy to verify",
1391
+ suggestedCommands: [
1392
+ "test -r mimetic/coverage-matrix.md && sed -n '1,80p' mimetic/coverage-matrix.md",
1393
+ "test -r mimetic/scenarios/onboarding-regression.yaml && sed -n '1,120p' mimetic/scenarios/onboarding-regression.yaml"
1394
+ ]
1395
+ },
1396
+ {
1397
+ id: "verification-release",
1398
+ label: "verification/release",
1399
+ instruction: "inspect the verification and release-gate promises exposed to local dogfood actors",
1400
+ suggestedCommands: [
1401
+ "test -r package.json && node -e \"const p=require('./package.json'); console.log(p.scripts.check); console.log(p.scripts['public-surface:scan']);\"",
1402
+ "test -r mimetic/README.md && sed -n '1,80p' mimetic/README.md"
1403
+ ]
1404
+ }
1405
+ ];
1406
+ return focuses[index % focuses.length] ?? focuses[0];
1407
+ }
1408
+ function aggregateActorStatus(statuses) {
1409
+ if (statuses.includes("failed"))
1410
+ return "failed";
1411
+ if (statuses.includes("timed_out"))
1412
+ return "timed_out";
1413
+ if (statuses.includes("blocked"))
1414
+ return "blocked";
1415
+ return statuses.length > 0 && statuses.every((status) => status === "passed") ? "passed" : "failed";
1416
+ }
1417
+ function summarizeExecFanout(statuses) {
1418
+ if (statuses.length === 1) {
1419
+ return statuses[0] === "passed" ? "actor process exited successfully" : `actor lane ${statuses[0]}`;
1420
+ }
1421
+ const counts = statuses.reduce((current, status) => ({ ...current, [status]: current[status] + 1 }), { passed: 0, failed: 0, blocked: 0, timed_out: 0 });
1422
+ const summary = Object.keys(counts)
1423
+ .filter((status) => counts[status] > 0)
1424
+ .map((status) => `${counts[status]} ${status}`)
1425
+ .join(", ");
1426
+ return statuses.every((status) => status === "passed")
1427
+ ? `all ${statuses.length} Codex exec lanes passed`
1428
+ : `${statuses.length} Codex exec lanes completed with ${summary}`;
1429
+ }
1430
+ function buildLocalCodexExecPrompt(selection, lane) {
1431
+ const suggestedCommands = lane?.focus.suggestedCommands ?? [
1432
+ "test -r mimetic/config.ts && wc -l mimetic/config.ts",
1433
+ "test -r mimetic/README.md && sed -n '1,40p' mimetic/README.md"
1434
+ ];
1435
+ return [
1436
+ "You are a Mimetic local Codex exec dogfood actor running noninteractively.",
1437
+ `Persona: ${selection.persona.name}.`,
1438
+ `Scenario: ${selection.scenario.title}.`,
1439
+ ...(lane ? [`Fanout lane ${lane.index}/${lane.total}. Focus: ${lane.focus.instruction}.`] : []),
1440
+ "Run at most two read-only local inspection commands.",
1441
+ `Suggested commands: \`${suggestedCommands[0]}\` and \`${suggestedCommands[1]}\`.`,
1442
+ "Do not edit files, do not run network commands, do not commit, do not push, do not open GitHub issues, and do not print secrets.",
1443
+ "Do not inspect additional files unless one suggested command fails.",
1444
+ "Finish within three sentences with exactly one public-safe verdict line using passed, blocked, or failed."
1445
+ ].join(" ");
1446
+ }
1447
+ function parseCommandLine(input) {
1448
+ const tokens = [];
1449
+ let current = "";
1450
+ let quote = null;
1451
+ let escaping = false;
1452
+ for (const char of input.trim()) {
1453
+ if (escaping) {
1454
+ current += char;
1455
+ escaping = false;
1456
+ continue;
1457
+ }
1458
+ if (char === "\\" && quote !== "'") {
1459
+ escaping = true;
1460
+ continue;
1461
+ }
1462
+ if (quote) {
1463
+ if (char === quote) {
1464
+ quote = null;
1465
+ }
1466
+ else {
1467
+ current += char;
1468
+ }
1469
+ continue;
1470
+ }
1471
+ if (char === "\"" || char === "'") {
1472
+ quote = char;
1473
+ continue;
1474
+ }
1475
+ if (/\s/.test(char)) {
1476
+ if (current !== "") {
1477
+ tokens.push(current);
1478
+ current = "";
1479
+ }
1480
+ continue;
1481
+ }
1482
+ current += char;
1483
+ }
1484
+ if (current !== "") {
1485
+ tokens.push(current);
1486
+ }
1487
+ return quote ? [] : tokens;
1488
+ }
1489
+ function shellJoin(parts) {
1490
+ return parts.map(shellQuote).join(" ");
1491
+ }
1492
+ function shellQuote(value) {
1493
+ if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(value)) {
1494
+ return value;
1495
+ }
1496
+ return `'${value.replace(/'/g, "'\\''")}'`;
1497
+ }
1498
+ function limitTranscript(value) {
1499
+ if (value.length <= LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS) {
1500
+ return value;
1501
+ }
1502
+ return `[...sanitized transcript truncated to last ${LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS} characters...]\n${value.slice(-LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS)}`;
1503
+ }
1504
+ function tailText(value, maxChars) {
1505
+ if (value.length <= maxChars) {
1506
+ return value;
1507
+ }
1508
+ return value.slice(-maxChars);
1509
+ }
310
1510
  function normalizeSimCount(value) {
311
1511
  if (value === undefined) {
312
1512
  return 1;
@@ -490,6 +1690,41 @@ function createReviewSummary() {
490
1690
  ]
491
1691
  };
492
1692
  }
1693
+ function createLocalActorRunningReviewSummary(actorLabel) {
1694
+ return {
1695
+ schema: REVIEW_SCHEMA,
1696
+ verdict: "contract_proof_only",
1697
+ summary: `Live local ${actorLabel} actor is running. This is an in-progress Observer snapshot; final verdict will be written after sanitized actor evidence is captured.`,
1698
+ gaps: [
1699
+ "Final actor verdict and transcript artifacts are not available until the run completes.",
1700
+ "Live Observer follow depends on polling observer/observer-data.json while this run is active.",
1701
+ "No GitHub mutation, target OSS mutation, E2B substrate, or production data is used by this local actor contract."
1702
+ ]
1703
+ };
1704
+ }
1705
+ function createLocalActorReviewSummary(actorLabel, status, reason) {
1706
+ const verdict = status === "passed"
1707
+ ? "pass"
1708
+ : status === "timed_out"
1709
+ ? "timed_out"
1710
+ : status === "blocked"
1711
+ ? "blocked"
1712
+ : "fail";
1713
+ const isTui = actorLabel.toLowerCase().includes("tui");
1714
+ const isFanout = actorLabel.toLowerCase().includes("fanout");
1715
+ return {
1716
+ schema: REVIEW_SCHEMA,
1717
+ verdict,
1718
+ summary: `Live local ${actorLabel} actor ${status}: ${reason}. This proves the local actor lifecycle and sanitized evidence path${isFanout ? " across requested fanout lanes" : ""}, not target product behavior.`,
1719
+ gaps: [
1720
+ isTui
1721
+ ? "Only one local Codex TUI actor is supported in this slice."
1722
+ : "Codex TUI trust bootstrap, PTY rendering, and keyboard-focus proof remain separate from the noninteractive exec actor.",
1723
+ "Live follow uses polling Observer snapshots; raw interactive terminal streaming remains a follow-up hardening step.",
1724
+ "No GitHub mutation, target OSS mutation, E2B substrate, or production data was used by this local actor contract."
1725
+ ]
1726
+ };
1727
+ }
493
1728
  async function loadDryRunSelection(cwd, mimeticSource) {
494
1729
  const warnings = [];
495
1730
  if (mimeticSource === "missing") {
@@ -531,10 +1766,12 @@ async function loadDryRunSelection(cwd, mimeticSource) {
531
1766
  };
532
1767
  }
533
1768
  function renderReviewMarkdown(bundle) {
534
- return `# Mimetic Dry-Run Review
1769
+ return `# Mimetic Run Review
535
1770
 
536
1771
  Run: ${bundle.runId}
537
1772
 
1773
+ Mode: ${bundle.mode}
1774
+
538
1775
  Verdict: ${bundle.review.verdict}
539
1776
 
540
1777
  ${bundle.review.summary}
@@ -607,6 +1844,13 @@ async function readTextIfExists(filePath) {
607
1844
  async function writeJson(filePath, value) {
608
1845
  await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
609
1846
  }
1847
+ async function writeRunBundleArtifacts(absoluteArtifactRoot, bundle) {
1848
+ await writeJson(path.join(absoluteArtifactRoot, "run.json"), bundle);
1849
+ await writeJson(path.join(absoluteArtifactRoot, "review.json"), bundle.review);
1850
+ await writeFile(path.join(absoluteArtifactRoot, "review.md"), renderReviewMarkdown(bundle), "utf8");
1851
+ await mkdir(path.join(absoluteArtifactRoot, "observer"), { recursive: true });
1852
+ await writeJson(path.join(absoluteArtifactRoot, "observer", "observer-data.json"), buildObserverData(bundle));
1853
+ }
610
1854
  async function validateCwd(cwd) {
611
1855
  try {
612
1856
  const stats = await stat(cwd);
@@ -653,11 +1897,17 @@ async function fileExists(filePath) {
653
1897
  function containsSensitivePattern(text) {
654
1898
  return sensitivePatterns.some((pattern) => pattern.test(text));
655
1899
  }
1900
+ function redactSensitiveText(text) {
1901
+ return sensitivePatterns.reduce((current, pattern) => {
1902
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
1903
+ return current.replace(new RegExp(pattern.source, flags), "[REDACTED_SECRET]");
1904
+ }, text);
1905
+ }
656
1906
  function isRunBundle(value) {
657
1907
  return isRecord(value)
658
1908
  && value.schema === RUN_BUNDLE_SCHEMA
659
1909
  && typeof value.runId === "string"
660
- && value.mode === "dry-run"
1910
+ && (value.mode === "dry-run" || value.mode === "live")
661
1911
  && typeof value.createdAt === "string"
662
1912
  && isRecord(value.review)
663
1913
  && isReviewSummary(value.review)
@@ -667,7 +1917,11 @@ function isRunBundle(value) {
667
1917
  function isReviewSummary(value) {
668
1918
  return isRecord(value)
669
1919
  && value.schema === REVIEW_SCHEMA
670
- && value.verdict === "contract_proof_only"
1920
+ && (value.verdict === "contract_proof_only"
1921
+ || value.verdict === "pass"
1922
+ || value.verdict === "fail"
1923
+ || value.verdict === "blocked"
1924
+ || value.verdict === "timed_out")
671
1925
  && typeof value.summary === "string"
672
1926
  && Array.isArray(value.gaps)
673
1927
  && value.gaps.every((gap) => typeof gap === "string");