mimetic-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/argv.d.ts +1 -0
  4. package/dist/argv.js +8 -0
  5. package/dist/argv.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +5 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/feedback.d.ts +48 -0
  10. package/dist/feedback.js +243 -0
  11. package/dist/feedback.js.map +1 -0
  12. package/dist/index.d.ts +15 -0
  13. package/dist/index.js +9 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/init-templates.d.ts +12 -0
  16. package/dist/init-templates.js +251 -0
  17. package/dist/init-templates.js.map +1 -0
  18. package/dist/init.d.ts +26 -0
  19. package/dist/init.js +343 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/observer-assets.d.ts +2 -0
  22. package/dist/observer-assets.js +2322 -0
  23. package/dist/observer-assets.js.map +1 -0
  24. package/dist/observer-data.d.ts +53 -0
  25. package/dist/observer-data.js +123 -0
  26. package/dist/observer-data.js.map +1 -0
  27. package/dist/observer.d.ts +36 -0
  28. package/dist/observer.js +360 -0
  29. package/dist/observer.js.map +1 -0
  30. package/dist/oss-lab.d.ts +50 -0
  31. package/dist/oss-lab.js +298 -0
  32. package/dist/oss-lab.js.map +1 -0
  33. package/dist/oss-meta-lab.d.ts +43 -0
  34. package/dist/oss-meta-lab.js +901 -0
  35. package/dist/oss-meta-lab.js.map +1 -0
  36. package/dist/program.d.ts +36 -0
  37. package/dist/program.js +825 -0
  38. package/dist/program.js.map +1 -0
  39. package/dist/run.d.ts +206 -0
  40. package/dist/run.js +688 -0
  41. package/dist/run.js.map +1 -0
  42. package/package.json +78 -0
  43. package/skills/mimetic-cli/SKILL.md +92 -0
  44. package/skills/mimetic-cli/agents/openai.yaml +7 -0
package/dist/run.js ADDED
@@ -0,0 +1,688 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ export const RUN_BUNDLE_SCHEMA = "mimetic.run-bundle.v1";
5
+ export const REVIEW_SCHEMA = "mimetic.review.v1";
6
+ export const VERIFY_SCHEMA = "mimetic.verify-result.v1";
7
+ export const RUNS_SCHEMA = "mimetic.runs-result.v1";
8
+ export const DOCTOR_SCHEMA = "mimetic.doctor-result.v1";
9
+ const sensitivePatterns = [
10
+ /sk-[a-z0-9_-]{12,}/i,
11
+ /gho_[a-z0-9_]{12,}/i,
12
+ /BEGIN (RSA|OPENSSH|PRIVATE) KEY/i
13
+ ];
14
+ const builtinPersona = {
15
+ id: "builtin-synthetic-new-user",
16
+ name: "Built-in Synthetic New User",
17
+ source: "builtin:synthetic-new-user",
18
+ sourceDigest: "builtin"
19
+ };
20
+ const builtinScenario = {
21
+ id: "builtin-first-run-smoke",
22
+ title: "Built-in First-Run Smoke",
23
+ goal: "Create a public-safe dry-run contract bundle from built-in defaults.",
24
+ source: "builtin:first-run-smoke",
25
+ sourceDigest: "builtin"
26
+ };
27
+ export async function runDryRun(options) {
28
+ const cwd = path.resolve(options.cwd);
29
+ const cwdError = await validateCwd(cwd);
30
+ const warnings = [];
31
+ if (cwdError) {
32
+ return {
33
+ schema: "mimetic.run-result.v1",
34
+ ok: false,
35
+ cwd,
36
+ warnings,
37
+ error: cwdError
38
+ };
39
+ }
40
+ if (!options.dryRun) {
41
+ return {
42
+ schema: "mimetic.run-result.v1",
43
+ ok: false,
44
+ cwd,
45
+ warnings,
46
+ error: {
47
+ code: "MIMETIC_LIVE_RUN_UNIMPLEMENTED",
48
+ message: "Only run --dry-run is implemented in this open-source-safe slice."
49
+ }
50
+ };
51
+ }
52
+ const simCount = normalizeSimCount(options.simCount);
53
+ if (simCount === null) {
54
+ return {
55
+ schema: "mimetic.run-result.v1",
56
+ ok: false,
57
+ cwd,
58
+ warnings,
59
+ error: {
60
+ code: "MIMETIC_INVALID_SIM_COUNT",
61
+ message: "--sims must be an integer between 1 and 64."
62
+ }
63
+ };
64
+ }
65
+ const now = new Date();
66
+ const createdAt = now.toISOString();
67
+ const runId = options.runId ?? `dryrun-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
68
+ const artifactRoot = path.join(".mimetic", "runs", runId);
69
+ const absoluteArtifactRoot = path.join(cwd, artifactRoot);
70
+ const packageName = await readPackageName(cwd);
71
+ const mimeticSource = await directoryExists(path.join(cwd, "mimetic")) ? "present" : "missing";
72
+ const selection = await loadDryRunSelection(cwd, mimeticSource);
73
+ if (mimeticSource === "missing") {
74
+ warnings.push("Committed mimetic/ source was not found; using built-in synthetic dry-run defaults.");
75
+ }
76
+ warnings.push(...selection.warnings);
77
+ const observerFixtures = buildSyntheticObserverFixtures({
78
+ createdAt,
79
+ personaId: selection.persona.id,
80
+ scenarioId: selection.scenario.id,
81
+ simCount
82
+ });
83
+ const bundle = {
84
+ schema: RUN_BUNDLE_SCHEMA,
85
+ runId,
86
+ mode: "dry-run",
87
+ simCount,
88
+ createdAt,
89
+ cwd,
90
+ artifactRoot,
91
+ source: {
92
+ packageName,
93
+ mimeticSource,
94
+ git: {
95
+ status: "not_captured",
96
+ note: "Source git state capture is planned for the core primitives slice."
97
+ }
98
+ },
99
+ persona: selection.persona,
100
+ scenario: selection.scenario,
101
+ lifecycle: [
102
+ {
103
+ at: createdAt,
104
+ event: "run.created",
105
+ message: `Synthetic dry-run contract bundle created with ${simCount} sim${simCount === 1 ? "" : "s"}.`
106
+ },
107
+ {
108
+ at: createdAt,
109
+ event: "persona.selected",
110
+ message: "Selected public-safe synthetic persona."
111
+ },
112
+ {
113
+ at: createdAt,
114
+ event: "scenario.selected",
115
+ message: "Selected public-safe first-run scenario."
116
+ },
117
+ {
118
+ at: createdAt,
119
+ event: "review.skeleton.created",
120
+ message: "Created review skeleton without claiming product proof."
121
+ }
122
+ ],
123
+ simulations: observerFixtures.simulations,
124
+ streams: observerFixtures.streams,
125
+ events: observerFixtures.events,
126
+ redaction: {
127
+ status: "passed",
128
+ notes: "Dry-run bundle contains synthetic contract proof only."
129
+ },
130
+ artifacts: {
131
+ run: "run.json",
132
+ reviewJson: "review.json",
133
+ reviewMarkdown: "review.md",
134
+ observerData: "observer/observer-data.json",
135
+ events: "events.ndjson"
136
+ },
137
+ review: createReviewSummary(),
138
+ feedbackCandidates: []
139
+ };
140
+ await mkdir(absoluteArtifactRoot, { recursive: true });
141
+ await writeJson(path.join(absoluteArtifactRoot, "run.json"), bundle);
142
+ await writeJson(path.join(absoluteArtifactRoot, "review.json"), bundle.review);
143
+ await writeFile(path.join(absoluteArtifactRoot, "review.md"), renderReviewMarkdown(bundle), "utf8");
144
+ await writeFile(path.join(absoluteArtifactRoot, "events.ndjson"), `${bundle.events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
145
+ await writeJson(path.join(cwd, ".mimetic", "runs", "latest.json"), {
146
+ schema: "mimetic.latest-run.v1",
147
+ runId,
148
+ path: artifactRoot,
149
+ updatedAt: createdAt
150
+ });
151
+ return {
152
+ schema: "mimetic.run-result.v1",
153
+ ok: true,
154
+ runId,
155
+ mode: "dry-run",
156
+ simCount,
157
+ cwd,
158
+ artifactRoot,
159
+ bundlePath: path.join(artifactRoot, "run.json"),
160
+ reviewPath: path.join(artifactRoot, "review.md"),
161
+ latestPath: path.join(".mimetic", "runs", "latest.json"),
162
+ warnings
163
+ };
164
+ }
165
+ function buildSyntheticObserverFixtures(args) {
166
+ const templates = [
167
+ {
168
+ kind: "ui",
169
+ mode: "ui-sim",
170
+ label: "UI journey",
171
+ currentStep: "Route and viewport contract captured",
172
+ summary: "UI sim lane reserved for browser/VNC playback, screenshots, route state, and interaction trace.",
173
+ tail: "open target app\nresolve first-run route\ncapture viewport state\nrecord interaction trace",
174
+ viewport: { width: 1440, height: 960, deviceScaleFactor: 1 }
175
+ },
176
+ {
177
+ kind: "terminal",
178
+ mode: "cli-sim",
179
+ label: "CLI actor",
180
+ currentStep: "Command transcript contract captured",
181
+ summary: "CLI lane reserved for command-by-command persona runs with stdout/stderr and artifact links.",
182
+ tail: "$ mimetic doctor\nok target cwd\nok mimetic source\n$ mimetic run --scenario first-run-smoke\ncontract proof emitted",
183
+ viewport: undefined
184
+ },
185
+ {
186
+ kind: "tui",
187
+ mode: "tui-sim",
188
+ label: "TUI actor",
189
+ currentStep: "Terminal UI frame contract captured",
190
+ summary: "TUI lane reserved for PTY bytes, ANSI rendering, focus replay, and optional assisted attach.",
191
+ tail: "\u001b[2mMimetic TUI frame\u001b[0m\n> persona: skeptical-power-user\n> scenario: onboarding-regression\nstatus: awaiting live PTY transport",
192
+ viewport: undefined
193
+ },
194
+ {
195
+ kind: "codex-ui",
196
+ mode: "codex-ui-sim",
197
+ label: "Codex UI",
198
+ currentStep: "App-server embed contract captured",
199
+ summary: "Codex UI lane reserved for app-server sessions that can be watched beside terminal evidence.",
200
+ tail: "codex-app-server session contract\nstate: not_connected\nembed: pending provider URL\nreceipts: planned",
201
+ viewport: { width: 1280, height: 900, deviceScaleFactor: 1 }
202
+ }
203
+ ];
204
+ const simulations = [];
205
+ const streams = [];
206
+ const events = [
207
+ {
208
+ id: "event-000",
209
+ at: args.createdAt,
210
+ level: "info",
211
+ type: "observer.contract.created",
212
+ message: "Created public-safe observer stream contract."
213
+ }
214
+ ];
215
+ for (let index = 0; index < args.simCount; index += 1) {
216
+ const template = templates[index % templates.length];
217
+ if (!template) {
218
+ throw new Error("Synthetic observer template missing.");
219
+ }
220
+ const simId = `sim-${String(index + 1).padStart(2, "0")}`;
221
+ const streamId = `${simId}-${template.kind}`;
222
+ const status = "contract_proof_only";
223
+ simulations.push({
224
+ id: simId,
225
+ index: index + 1,
226
+ personaId: args.personaId,
227
+ scenarioId: args.scenarioId,
228
+ status,
229
+ streamKind: template.kind,
230
+ mode: template.mode,
231
+ progress: 100,
232
+ currentStep: template.currentStep,
233
+ summary: template.summary,
234
+ streamIds: [streamId],
235
+ startedAt: args.createdAt,
236
+ updatedAt: args.createdAt
237
+ });
238
+ streams.push({
239
+ id: streamId,
240
+ simId,
241
+ kind: template.kind,
242
+ label: template.label,
243
+ status,
244
+ transport: streamTransport(template.kind),
245
+ updatedAt: args.createdAt,
246
+ embed: {
247
+ kind: template.kind === "terminal" || template.kind === "tui" ? "terminal" : "placeholder",
248
+ title: template.label
249
+ },
250
+ ...(template.viewport ? { viewport: template.viewport } : {}),
251
+ terminal: {
252
+ title: template.label,
253
+ format: template.kind === "tui" ? "ansi" : "plain",
254
+ stdin: "disabled",
255
+ tail: template.tail
256
+ },
257
+ ...(template.kind === "ui" || template.kind === "codex-ui"
258
+ ? {
259
+ ui: {
260
+ route: template.kind === "ui" ? "/first-run" : "/codex/session",
261
+ intent: template.summary,
262
+ state: "contract-only"
263
+ }
264
+ }
265
+ : {}),
266
+ ...(template.kind === "codex-ui"
267
+ ? {
268
+ codex: {
269
+ provider: "codex-app-server",
270
+ state: "not_connected",
271
+ contract: "Observer accepts an app-server embed URL, session id, status feed, terminal receipt feed, and artifact links."
272
+ }
273
+ }
274
+ : {}),
275
+ artifacts: [
276
+ { label: "run bundle", path: "run.json", kind: "bundle" },
277
+ { label: "review", path: "review.md", kind: "review" },
278
+ { label: "event log", path: "events.ndjson", kind: "events" }
279
+ ]
280
+ });
281
+ events.push({
282
+ id: `event-${String(index + 1).padStart(3, "0")}-a`,
283
+ at: args.createdAt,
284
+ level: "info",
285
+ type: "sim.contract.ready",
286
+ message: `${template.label} stream contract ready.`,
287
+ simId,
288
+ streamId
289
+ }, {
290
+ id: `event-${String(index + 1).padStart(3, "0")}-b`,
291
+ at: args.createdAt,
292
+ level: "warn",
293
+ type: "sim.live-substrate.missing",
294
+ message: "No live actor launched in dry-run mode; observer lane is ready for real substrate evidence.",
295
+ simId,
296
+ streamId
297
+ });
298
+ }
299
+ return { events, simulations, streams };
300
+ }
301
+ function streamTransport(kind) {
302
+ if (kind === "tui")
303
+ return "pty";
304
+ if (kind === "codex-ui")
305
+ return "app-server";
306
+ if (kind === "ui" || kind === "browser")
307
+ return "polling";
308
+ return "snapshot";
309
+ }
310
+ function normalizeSimCount(value) {
311
+ if (value === undefined) {
312
+ return 1;
313
+ }
314
+ if (!Number.isInteger(value) || value < 1 || value > 64) {
315
+ return null;
316
+ }
317
+ return value;
318
+ }
319
+ export async function verifyRun(cwdInput, runInput) {
320
+ const cwd = path.resolve(cwdInput);
321
+ const checks = [];
322
+ const resolved = await resolveRunPath(cwd, runInput);
323
+ if (!resolved) {
324
+ return {
325
+ schema: VERIFY_SCHEMA,
326
+ ok: false,
327
+ cwd,
328
+ run: runInput,
329
+ checks,
330
+ error: {
331
+ code: "MIMETIC_RUN_NOT_FOUND",
332
+ message: `Run not found: ${runInput}`
333
+ }
334
+ };
335
+ }
336
+ const bundlePath = path.join(resolved, "run.json");
337
+ const bundle = await readJsonIfExists(bundlePath);
338
+ const reviewJson = await readJsonIfExists(path.join(resolved, "review.json"));
339
+ const reviewMarkdown = await readTextIfExists(path.join(resolved, "review.md"));
340
+ checks.push({
341
+ name: "run.json exists",
342
+ ok: bundle !== null,
343
+ message: bundle === null ? "run.json missing" : "run.json present"
344
+ });
345
+ checks.push({
346
+ name: "run schema",
347
+ ok: isRecord(bundle) && bundle.schema === RUN_BUNDLE_SCHEMA,
348
+ message: "run bundle schema is mimetic.run-bundle.v1"
349
+ });
350
+ checks.push({
351
+ name: "redaction passed",
352
+ ok: isRecord(bundle) && isRecord(bundle.redaction) && bundle.redaction.status === "passed",
353
+ message: "redaction status must be passed"
354
+ });
355
+ checks.push({
356
+ name: "review artifacts exist",
357
+ ok: reviewJson !== null && reviewMarkdown !== null,
358
+ message: "review.json and review.md must exist"
359
+ });
360
+ checks.push({
361
+ name: "public-safety scan",
362
+ ok: !containsSensitivePattern(JSON.stringify(bundle ?? {}) + (reviewMarkdown ?? "")),
363
+ message: "bundle and review text must not match known secret patterns"
364
+ });
365
+ const ok = checks.every((check) => check.ok);
366
+ return {
367
+ schema: VERIFY_SCHEMA,
368
+ ok,
369
+ cwd,
370
+ run: runInput,
371
+ bundlePath: path.relative(cwd, bundlePath),
372
+ checks,
373
+ ...(ok
374
+ ? {}
375
+ : {
376
+ error: {
377
+ code: "MIMETIC_INVALID_RUN_BUNDLE",
378
+ message: "Run bundle failed verification."
379
+ }
380
+ })
381
+ };
382
+ }
383
+ export async function loadRunBundle(cwdInput, runInput) {
384
+ const cwd = path.resolve(cwdInput);
385
+ const resolved = await resolveRunPath(cwd, runInput);
386
+ if (!resolved) {
387
+ return null;
388
+ }
389
+ const bundlePath = path.join(resolved, "run.json");
390
+ const bundle = await readJsonIfExists(bundlePath);
391
+ if (!isRunBundle(bundle)) {
392
+ return null;
393
+ }
394
+ return {
395
+ bundle,
396
+ bundlePath: path.relative(cwd, bundlePath),
397
+ runDir: resolved
398
+ };
399
+ }
400
+ export async function listRuns(cwdInput) {
401
+ const cwd = path.resolve(cwdInput);
402
+ const runsRoot = path.join(cwd, ".mimetic", "runs");
403
+ const entries = await readdir(runsRoot, { withFileTypes: true }).catch(() => []);
404
+ const runs = [];
405
+ const latest = await readLatest(cwd);
406
+ for (const entry of entries) {
407
+ if (!entry.isDirectory()) {
408
+ continue;
409
+ }
410
+ const bundle = await readJsonIfExists(path.join(runsRoot, entry.name, "run.json"));
411
+ runs.push({
412
+ runId: entry.name,
413
+ createdAt: isRecord(bundle) && typeof bundle.createdAt === "string" ? bundle.createdAt : null,
414
+ mode: isRecord(bundle) && typeof bundle.mode === "string" ? bundle.mode : null,
415
+ path: path.join(".mimetic", "runs", entry.name)
416
+ });
417
+ }
418
+ return {
419
+ schema: RUNS_SCHEMA,
420
+ ok: true,
421
+ cwd,
422
+ runs: runs.sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? "")),
423
+ latest: latest?.runId ?? null
424
+ };
425
+ }
426
+ export async function readReview(cwdInput, runInput) {
427
+ const verified = await verifyRun(cwdInput, runInput);
428
+ if (!verified.ok || !verified.bundlePath) {
429
+ return verified;
430
+ }
431
+ const cwd = path.resolve(cwdInput);
432
+ const runDir = path.dirname(path.join(cwd, verified.bundlePath));
433
+ const review = await readJsonIfExists(path.join(runDir, "review.json"));
434
+ if (!isReviewSummary(review)) {
435
+ return {
436
+ ...verified,
437
+ ok: false,
438
+ error: {
439
+ code: "MIMETIC_INVALID_RUN_BUNDLE",
440
+ message: "review.json is missing or invalid."
441
+ }
442
+ };
443
+ }
444
+ return {
445
+ ...review,
446
+ path: path.relative(cwd, path.join(runDir, "review.json")),
447
+ runId: path.basename(runDir)
448
+ };
449
+ }
450
+ export async function doctor(cwdInput) {
451
+ const cwd = path.resolve(cwdInput);
452
+ const checks = [
453
+ {
454
+ name: "target cwd",
455
+ ok: await directoryExists(cwd),
456
+ message: "target directory exists"
457
+ },
458
+ {
459
+ name: "package.json",
460
+ ok: await fileExists(path.join(cwd, "package.json")),
461
+ message: "package.json is present"
462
+ },
463
+ {
464
+ name: "mimetic source",
465
+ ok: await directoryExists(path.join(cwd, "mimetic")),
466
+ message: "committed mimetic/ source directory is present"
467
+ },
468
+ {
469
+ name: "runtime ignore",
470
+ ok: (await readTextIfExists(path.join(cwd, ".gitignore")))?.includes(".mimetic/") ?? false,
471
+ message: ".gitignore contains .mimetic/"
472
+ }
473
+ ];
474
+ return {
475
+ schema: DOCTOR_SCHEMA,
476
+ ok: checks.every((check) => check.ok),
477
+ cwd,
478
+ checks
479
+ };
480
+ }
481
+ function createReviewSummary() {
482
+ return {
483
+ schema: REVIEW_SCHEMA,
484
+ verdict: "contract_proof_only",
485
+ summary: "Synthetic dry-run bundle was generated. This proves Mimetic artifact plumbing, not product behavior.",
486
+ gaps: [
487
+ "No browser was launched.",
488
+ "No product state was verified.",
489
+ "No model, provider, or E2B substrate was used."
490
+ ]
491
+ };
492
+ }
493
+ async function loadDryRunSelection(cwd, mimeticSource) {
494
+ const warnings = [];
495
+ if (mimeticSource === "missing") {
496
+ return {
497
+ persona: builtinPersona,
498
+ scenario: builtinScenario,
499
+ warnings
500
+ };
501
+ }
502
+ const personaPath = "mimetic/personas/synthetic-new-user.yaml";
503
+ const scenarioPath = "mimetic/scenarios/first-run-smoke.yaml";
504
+ const personaText = await readTextIfExists(path.join(cwd, personaPath));
505
+ const scenarioText = await readTextIfExists(path.join(cwd, scenarioPath));
506
+ if (personaText === null) {
507
+ warnings.push(`${personaPath} was not found; using built-in persona defaults.`);
508
+ }
509
+ if (scenarioText === null) {
510
+ warnings.push(`${scenarioPath} was not found; using built-in scenario defaults.`);
511
+ }
512
+ return {
513
+ persona: personaText === null
514
+ ? builtinPersona
515
+ : {
516
+ id: readYamlScalar(personaText, "id") ?? "synthetic-new-user",
517
+ name: readYamlScalar(personaText, "name") ?? "Synthetic New User",
518
+ source: personaPath,
519
+ sourceDigest: digestText(personaText)
520
+ },
521
+ scenario: scenarioText === null
522
+ ? builtinScenario
523
+ : {
524
+ id: readYamlScalar(scenarioText, "id") ?? "first-run-smoke",
525
+ title: readYamlScalar(scenarioText, "title") ?? "First-run smoke",
526
+ goal: readYamlScalar(scenarioText, "goal") ?? "Run a public-safe first-run smoke scenario.",
527
+ source: scenarioPath,
528
+ sourceDigest: digestText(scenarioText)
529
+ },
530
+ warnings
531
+ };
532
+ }
533
+ function renderReviewMarkdown(bundle) {
534
+ return `# Mimetic Dry-Run Review
535
+
536
+ Run: ${bundle.runId}
537
+
538
+ Verdict: ${bundle.review.verdict}
539
+
540
+ ${bundle.review.summary}
541
+
542
+ ## Public-Safety
543
+
544
+ - Redaction: ${bundle.redaction.status}
545
+ - Notes: ${bundle.redaction.notes}
546
+
547
+ ## Gaps
548
+
549
+ ${bundle.review.gaps.map((gap) => `- ${gap}`).join("\n")}
550
+ `;
551
+ }
552
+ function readYamlScalar(text, key) {
553
+ const match = text.match(new RegExp(`^${escapeRegExp(key)}:\\s*(.+?)\\s*$`, "m"));
554
+ if (!match?.[1]) {
555
+ return null;
556
+ }
557
+ return match[1].replace(/^["']|["']$/g, "");
558
+ }
559
+ function digestText(text) {
560
+ return createHash("sha256").update(text).digest("hex").slice(0, 12);
561
+ }
562
+ function escapeRegExp(value) {
563
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
564
+ }
565
+ async function resolveRunPath(cwd, runInput) {
566
+ if (runInput === "latest") {
567
+ const latest = await readLatest(cwd);
568
+ return latest ? path.join(cwd, latest.path) : null;
569
+ }
570
+ const direct = path.join(cwd, ".mimetic", "runs", runInput);
571
+ return await directoryExists(direct) ? direct : null;
572
+ }
573
+ async function readLatest(cwd) {
574
+ const latest = await readJsonIfExists(path.join(cwd, ".mimetic", "runs", "latest.json"));
575
+ if (isRunPointer(latest)) {
576
+ return latest;
577
+ }
578
+ return null;
579
+ }
580
+ async function readPackageName(cwd) {
581
+ const packageJson = await readJsonIfExists(path.join(cwd, "package.json"));
582
+ return isRecord(packageJson) && typeof packageJson.name === "string" ? packageJson.name : null;
583
+ }
584
+ async function readJsonIfExists(filePath) {
585
+ const text = await readTextIfExists(filePath);
586
+ if (text === null) {
587
+ return null;
588
+ }
589
+ try {
590
+ return JSON.parse(text);
591
+ }
592
+ catch {
593
+ return null;
594
+ }
595
+ }
596
+ async function readTextIfExists(filePath) {
597
+ try {
598
+ return await readFile(filePath, "utf8");
599
+ }
600
+ catch (error) {
601
+ if (isNodeError(error) && error.code === "ENOENT") {
602
+ return null;
603
+ }
604
+ throw error;
605
+ }
606
+ }
607
+ async function writeJson(filePath, value) {
608
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
609
+ }
610
+ async function validateCwd(cwd) {
611
+ try {
612
+ const stats = await stat(cwd);
613
+ if (!stats.isDirectory()) {
614
+ return {
615
+ code: "MIMETIC_INVALID_CWD",
616
+ message: `Target cwd is not a directory: ${cwd}`
617
+ };
618
+ }
619
+ return null;
620
+ }
621
+ catch (error) {
622
+ if (isNodeError(error) && error.code === "ENOENT") {
623
+ return {
624
+ code: "MIMETIC_INVALID_CWD",
625
+ message: `Target cwd does not exist: ${cwd}`
626
+ };
627
+ }
628
+ throw error;
629
+ }
630
+ }
631
+ async function directoryExists(directoryPath) {
632
+ try {
633
+ return (await stat(directoryPath)).isDirectory();
634
+ }
635
+ catch (error) {
636
+ if (isNodeError(error) && error.code === "ENOENT") {
637
+ return false;
638
+ }
639
+ throw error;
640
+ }
641
+ }
642
+ async function fileExists(filePath) {
643
+ try {
644
+ return (await stat(filePath)).isFile();
645
+ }
646
+ catch (error) {
647
+ if (isNodeError(error) && error.code === "ENOENT") {
648
+ return false;
649
+ }
650
+ throw error;
651
+ }
652
+ }
653
+ function containsSensitivePattern(text) {
654
+ return sensitivePatterns.some((pattern) => pattern.test(text));
655
+ }
656
+ function isRunBundle(value) {
657
+ return isRecord(value)
658
+ && value.schema === RUN_BUNDLE_SCHEMA
659
+ && typeof value.runId === "string"
660
+ && value.mode === "dry-run"
661
+ && typeof value.createdAt === "string"
662
+ && isRecord(value.review)
663
+ && isReviewSummary(value.review)
664
+ && isRecord(value.redaction)
665
+ && value.redaction.status === "passed";
666
+ }
667
+ function isReviewSummary(value) {
668
+ return isRecord(value)
669
+ && value.schema === REVIEW_SCHEMA
670
+ && value.verdict === "contract_proof_only"
671
+ && typeof value.summary === "string"
672
+ && Array.isArray(value.gaps)
673
+ && value.gaps.every((gap) => typeof gap === "string");
674
+ }
675
+ function isRunPointer(value) {
676
+ return isRecord(value)
677
+ && value.schema === "mimetic.latest-run.v1"
678
+ && typeof value.runId === "string"
679
+ && typeof value.path === "string"
680
+ && typeof value.updatedAt === "string";
681
+ }
682
+ function isRecord(value) {
683
+ return typeof value === "object" && value !== null && !Array.isArray(value);
684
+ }
685
+ function isNodeError(error) {
686
+ return error instanceof Error && "code" in error;
687
+ }
688
+ //# sourceMappingURL=run.js.map