oh-my-workflow 0.2.0 → 0.4.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.
@@ -0,0 +1,540 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta
7
+ name="description"
8
+ content="oh-my-workflow adds a slash-command workflow mode to coding agents, backed by schema-gated coding-agent CLI nodes and a JSONL journal."
9
+ />
10
+ <title>oh-my-workflow docs</title>
11
+ <style>
12
+ :root {
13
+ --ink: #151515;
14
+ --muted: #5c625f;
15
+ --paper: #fbfaf7;
16
+ --line: #d9d5ca;
17
+ --trace: #113f3a;
18
+ --signal: #c4442e;
19
+ --code: #f1eee6;
20
+ --panel: #ffffff;
21
+ --focus: #1f6feb;
22
+ }
23
+
24
+ * {
25
+ box-sizing: border-box;
26
+ }
27
+
28
+ html {
29
+ scroll-behavior: smooth;
30
+ }
31
+
32
+ body {
33
+ margin: 0;
34
+ color: var(--ink);
35
+ background: var(--paper);
36
+ font-family:
37
+ ui-sans-serif,
38
+ -apple-system,
39
+ BlinkMacSystemFont,
40
+ "Segoe UI",
41
+ sans-serif;
42
+ line-height: 1.55;
43
+ }
44
+
45
+ a {
46
+ color: inherit;
47
+ text-decoration-color: var(--signal);
48
+ text-decoration-thickness: 2px;
49
+ text-underline-offset: 4px;
50
+ }
51
+
52
+ a:focus-visible,
53
+ button:focus-visible {
54
+ outline: 3px solid var(--focus);
55
+ outline-offset: 3px;
56
+ }
57
+
58
+ .shell {
59
+ max-width: 1160px;
60
+ margin: 0 auto;
61
+ padding: 0 24px;
62
+ }
63
+
64
+ header {
65
+ position: sticky;
66
+ top: 0;
67
+ z-index: 5;
68
+ background: color-mix(in srgb, var(--paper) 94%, transparent);
69
+ border-bottom: 1px solid var(--line);
70
+ backdrop-filter: blur(12px);
71
+ }
72
+
73
+ nav {
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: space-between;
77
+ min-height: 62px;
78
+ gap: 20px;
79
+ }
80
+
81
+ .brand {
82
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
83
+ font-weight: 700;
84
+ letter-spacing: 0;
85
+ }
86
+
87
+ .navlinks {
88
+ display: flex;
89
+ flex-wrap: wrap;
90
+ gap: 16px;
91
+ color: var(--muted);
92
+ font-size: 14px;
93
+ }
94
+
95
+ .hero {
96
+ display: grid;
97
+ grid-template-columns: minmax(0, 1fr) minmax(340px, 0.82fr);
98
+ gap: 44px;
99
+ align-items: center;
100
+ padding: 64px 0 56px;
101
+ }
102
+
103
+ .eyebrow {
104
+ margin: 0 0 18px;
105
+ color: var(--signal);
106
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
107
+ font-size: 13px;
108
+ font-weight: 700;
109
+ text-transform: uppercase;
110
+ }
111
+
112
+ h1,
113
+ h2,
114
+ h3 {
115
+ margin: 0;
116
+ line-height: 1.05;
117
+ letter-spacing: 0;
118
+ }
119
+
120
+ h1 {
121
+ max-width: 760px;
122
+ font-family: Georgia, "Times New Roman", serif;
123
+ font-size: clamp(52px, 6vw, 84px);
124
+ font-weight: 700;
125
+ }
126
+
127
+ h2 {
128
+ font-family: Georgia, "Times New Roman", serif;
129
+ font-size: clamp(34px, 5vw, 62px);
130
+ }
131
+
132
+ h3 {
133
+ font-size: 20px;
134
+ }
135
+
136
+ .lead {
137
+ max-width: 710px;
138
+ margin: 24px 0 0;
139
+ color: var(--muted);
140
+ font-size: 20px;
141
+ }
142
+
143
+ .actions {
144
+ display: flex;
145
+ flex-wrap: wrap;
146
+ gap: 12px;
147
+ margin-top: 28px;
148
+ }
149
+
150
+ .button {
151
+ display: inline-flex;
152
+ min-height: 44px;
153
+ align-items: center;
154
+ justify-content: center;
155
+ border: 1px solid var(--ink);
156
+ border-radius: 6px;
157
+ padding: 10px 16px;
158
+ background: var(--ink);
159
+ color: var(--paper);
160
+ font-weight: 700;
161
+ text-decoration: none;
162
+ }
163
+
164
+ .button.secondary {
165
+ background: transparent;
166
+ color: var(--ink);
167
+ }
168
+
169
+ .trace-card {
170
+ border: 1px solid var(--trace);
171
+ background: #f4f7f2;
172
+ box-shadow: 10px 10px 0 var(--trace);
173
+ }
174
+
175
+ .trace-head {
176
+ display: flex;
177
+ justify-content: space-between;
178
+ border-bottom: 1px solid var(--trace);
179
+ padding: 12px 14px;
180
+ color: var(--trace);
181
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
182
+ font-size: 13px;
183
+ font-weight: 700;
184
+ }
185
+
186
+ .trace-body {
187
+ padding: 18px;
188
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
189
+ font-size: 13px;
190
+ }
191
+
192
+ .trace-line {
193
+ display: grid;
194
+ grid-template-columns: 80px 1fr auto;
195
+ gap: 12px;
196
+ padding: 10px 0;
197
+ border-bottom: 1px solid rgba(17, 63, 58, 0.16);
198
+ }
199
+
200
+ .trace-line:last-child {
201
+ border-bottom: 0;
202
+ }
203
+
204
+ .ok {
205
+ color: #1f6f43;
206
+ font-weight: 700;
207
+ }
208
+
209
+ .fail {
210
+ color: var(--signal);
211
+ font-weight: 700;
212
+ }
213
+
214
+ section {
215
+ padding: 74px 0;
216
+ border-top: 1px solid var(--line);
217
+ }
218
+
219
+ .section-head {
220
+ display: grid;
221
+ grid-template-columns: minmax(0, 0.8fr) minmax(280px, 0.55fr);
222
+ gap: 40px;
223
+ align-items: end;
224
+ margin-bottom: 32px;
225
+ }
226
+
227
+ .section-head p {
228
+ margin: 0;
229
+ color: var(--muted);
230
+ font-size: 18px;
231
+ }
232
+
233
+ .grid {
234
+ display: grid;
235
+ grid-template-columns: repeat(3, minmax(0, 1fr));
236
+ gap: 16px;
237
+ }
238
+
239
+ .panel {
240
+ border: 1px solid var(--line);
241
+ border-radius: 8px;
242
+ background: var(--panel);
243
+ padding: 22px;
244
+ }
245
+
246
+ .panel p,
247
+ .panel li {
248
+ color: var(--muted);
249
+ }
250
+
251
+ pre {
252
+ margin: 0;
253
+ overflow-x: auto;
254
+ border: 1px solid var(--line);
255
+ border-radius: 8px;
256
+ background: var(--code);
257
+ padding: 18px;
258
+ color: #25231e;
259
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
260
+ font-size: 14px;
261
+ line-height: 1.5;
262
+ }
263
+
264
+ code {
265
+ font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
266
+ }
267
+
268
+ .wide {
269
+ display: grid;
270
+ grid-template-columns: minmax(0, 1fr) minmax(320px, 0.85fr);
271
+ gap: 20px;
272
+ align-items: start;
273
+ }
274
+
275
+ .api-list {
276
+ display: grid;
277
+ gap: 12px;
278
+ }
279
+
280
+ .api-row {
281
+ display: grid;
282
+ grid-template-columns: 140px 1fr;
283
+ gap: 18px;
284
+ padding: 14px 0;
285
+ border-bottom: 1px solid var(--line);
286
+ }
287
+
288
+ .api-row code {
289
+ color: var(--trace);
290
+ font-weight: 700;
291
+ }
292
+
293
+ .api-row p {
294
+ margin: 0;
295
+ color: var(--muted);
296
+ }
297
+
298
+ .proof {
299
+ display: grid;
300
+ grid-template-columns: repeat(4, minmax(0, 1fr));
301
+ gap: 12px;
302
+ }
303
+
304
+ .proof strong {
305
+ display: block;
306
+ font-size: 24px;
307
+ }
308
+
309
+ footer {
310
+ border-top: 1px solid var(--line);
311
+ padding: 32px 0;
312
+ color: var(--muted);
313
+ }
314
+
315
+ @media (max-width: 860px) {
316
+ .hero,
317
+ .section-head,
318
+ .wide {
319
+ grid-template-columns: 1fr;
320
+ }
321
+
322
+ h1 {
323
+ font-size: clamp(44px, 13vw, 64px);
324
+ }
325
+
326
+ .lead {
327
+ font-size: 18px;
328
+ }
329
+
330
+ .grid,
331
+ .proof {
332
+ grid-template-columns: 1fr;
333
+ }
334
+
335
+ .trace-line,
336
+ .api-row {
337
+ grid-template-columns: 1fr;
338
+ }
339
+ }
340
+
341
+ @media (max-width: 520px) {
342
+ .shell {
343
+ padding: 0 20px;
344
+ }
345
+
346
+ nav {
347
+ align-items: flex-start;
348
+ padding: 10px 20px;
349
+ }
350
+
351
+ .brand {
352
+ max-width: 96px;
353
+ line-height: 1.2;
354
+ }
355
+
356
+ .navlinks {
357
+ justify-content: flex-end;
358
+ gap: 8px 14px;
359
+ }
360
+
361
+ h1 {
362
+ font-size: clamp(40px, 12vw, 52px);
363
+ }
364
+
365
+ .trace-card {
366
+ display: none;
367
+ }
368
+
369
+ pre {
370
+ white-space: pre-wrap;
371
+ overflow-wrap: anywhere;
372
+ }
373
+ }
374
+ </style>
375
+ </head>
376
+ <body>
377
+ <header>
378
+ <nav class="shell" aria-label="Primary navigation">
379
+ <a class="brand" href="#">oh-my-workflow</a>
380
+ <div class="navlinks">
381
+ <a href="#quickstart">Quickstart</a>
382
+ <a href="#api">API</a>
383
+ <a href="#patterns">Patterns</a>
384
+ <a href="#deploy">Deploy</a>
385
+ <a href="https://github.com/domuk-k/oh-my-workflow">GitHub</a>
386
+ </div>
387
+ </nav>
388
+ </header>
389
+
390
+ <main>
391
+ <div class="shell hero">
392
+ <div>
393
+ <p class="eyebrow">Workflow mode for coding agents</p>
394
+ <h1>Give your coding agent a workflow mode.</h1>
395
+ <p class="lead">
396
+ Add <code>/omw</code>, describe a multi-step job, and let your agent write and run
397
+ the workflow. The runtime handles whole agent CLIs like <code>claude -p</code>,
398
+ <code>codex exec</code>, and <code>hermes</code> with bounded concurrency, schema
399
+ gates, automatic repair, resume, and a JSONL journal.
400
+ </p>
401
+ <div class="actions">
402
+ <a class="button" href="#quickstart">Add /omw to your agent</a>
403
+ <a class="button secondary" href="https://github.com/domuk-k/oh-my-workflow">Read the source</a>
404
+ </div>
405
+ </div>
406
+
407
+ <aside class="trace-card" aria-label="Example workflow trace">
408
+ <div class="trace-head">
409
+ <span>.omw/r-demo.jsonl</span>
410
+ <span>6 ok / 1 failed</span>
411
+ </div>
412
+ <div class="trace-body">
413
+ <div class="trace-line"><span>Scope</span><span>call#1</span><span class="ok">ok</span></div>
414
+ <div class="trace-line"><span>Search</span><span>search:a repaired schema</span><span class="ok">ok</span></div>
415
+ <div class="trace-line"><span>Search</span><span>search:b timed out</span><span class="fail">null</span></div>
416
+ <div class="trace-line"><span>Verify</span><span>pipeline survivors</span><span class="ok">ok</span></div>
417
+ <div class="trace-line"><span>Synth</span><span>single result JSON</span><span class="ok">ok</span></div>
418
+ </div>
419
+ </aside>
420
+ </div>
421
+
422
+ <section id="quickstart">
423
+ <div class="shell wide">
424
+ <div>
425
+ <p class="eyebrow">Quickstart</p>
426
+ <h2>Add one command to your coding agent.</h2>
427
+ <p class="lead">
428
+ Install the authoring skill, then ask your own agent to create and run a workflow.
429
+ omw defaults to <code>--agent auto</code>, so the skill can call the CLI without making
430
+ you pick Claude, Codex, or Hermes.
431
+ </p>
432
+ </div>
433
+ <pre><code>npx skills add domuk-k/oh-my-workflow --skill omw
434
+
435
+ /omw review this repo, fan out three bug-finding passes,
436
+ verify the strongest claims, and return the fixes with evidence.</code></pre>
437
+ </div>
438
+ </section>
439
+
440
+ <section>
441
+ <div class="shell">
442
+ <div class="section-head">
443
+ <div>
444
+ <p class="eyebrow">Why now</p>
445
+ <h2>Agent CLIs made workflow glue the bottleneck.</h2>
446
+ </div>
447
+ <p>
448
+ Coding agents are no longer only chat panes. They are CLIs you can launch, resume, bound,
449
+ and compose. Most teams still rebuild the same orchestration shell each time.
450
+ </p>
451
+ </div>
452
+ <div class="grid">
453
+ <article class="panel">
454
+ <h3>Closed workflow surfaces proved the pattern</h3>
455
+ <p>Native dynamic workflows are useful. omw makes the authoring shape portable across hosts.</p>
456
+ </article>
457
+ <article class="panel">
458
+ <h3>Schema gates turn prose into contracts</h3>
459
+ <p>Each node can self-repair against JSON Schema errors before your script sees the result.</p>
460
+ </article>
461
+ <article class="panel">
462
+ <h3>Journals make agent work debuggable</h3>
463
+ <p>Every phase, attempt, failure kind, cached hit, and final result is replayable as JSONL.</p>
464
+ </article>
465
+ </div>
466
+ </div>
467
+ </section>
468
+
469
+ <section id="api">
470
+ <div class="shell wide">
471
+ <div>
472
+ <p class="eyebrow">API</p>
473
+ <h2>The whole runtime is seven hooks.</h2>
474
+ <p class="lead">No DSL, no source transform, no ambient globals. Destructure only what you use.</p>
475
+ </div>
476
+ <div class="api-list panel">
477
+ <div class="api-row"><code>agent</code><p>Run one coding-agent CLI node. Returns validated output, raw text, or null.</p></div>
478
+ <div class="api-row"><code>parallel</code><p>Run independent thunks concurrently and keep failures as null.</p></div>
479
+ <div class="api-row"><code>pipeline</code><p>Move each item through stages independently without stage-wide barriers.</p></div>
480
+ <div class="api-row"><code>workflow</code><p>Run one nested workflow, sharing adapter, journal, and budget pool.</p></div>
481
+ <div class="api-row"><code>budget</code><p>Read total, spent, and remaining output-token budget.</p></div>
482
+ <div class="api-row"><code>phase</code><p>Group later calls under a journal and pretty-tree heading.</p></div>
483
+ <div class="api-row"><code>log</code><p>Write narration into the side-channel journal, never stdout.</p></div>
484
+ </div>
485
+ </div>
486
+ </section>
487
+
488
+ <section id="patterns">
489
+ <div class="shell">
490
+ <div class="section-head">
491
+ <div>
492
+ <p class="eyebrow">Patterns</p>
493
+ <h2>Use it when the graph matters.</h2>
494
+ </div>
495
+ <p>omw is for multi-step agent work: search, verify, vote, synthesize, loop, resume.</p>
496
+ </div>
497
+ <div class="grid">
498
+ <article class="panel">
499
+ <h3>Fan-out</h3>
500
+ <p>Launch N research or review nodes with bounded concurrency, then dedupe and rank survivors.</p>
501
+ </article>
502
+ <article class="panel">
503
+ <h3>Verify-vote</h3>
504
+ <p>Have independent agents judge a finding. Failed nodes abstain instead of voting no.</p>
505
+ </article>
506
+ <article class="panel">
507
+ <h3>Loop-until-dry</h3>
508
+ <p>Keep asking for one more issue until a round returns no new validated result.</p>
509
+ </article>
510
+ </div>
511
+ </div>
512
+ </section>
513
+
514
+ <section id="deploy">
515
+ <div class="shell wide">
516
+ <div>
517
+ <p class="eyebrow">Launch proof</p>
518
+ <h2>Small enough to audit, tested enough to trust.</h2>
519
+ <p class="lead">
520
+ The repo ships a conformance suite, live adapter smoke tests behind <code>OMW_LIVE=1</code>,
521
+ Vercel static docs configuration, and a Show HN launch note in <code>docs/launch/</code>.
522
+ </p>
523
+ </div>
524
+ <div class="proof">
525
+ <div class="panel"><strong>212</strong><span>unit and conformance tests</span></div>
526
+ <div class="panel"><strong>1</strong><span>runtime dependency: ajv</span></div>
527
+ <div class="panel"><strong>1</strong><span>result JSON on stdout</span></div>
528
+ <div class="panel"><strong>30s</strong><span>free demo path</span></div>
529
+ </div>
530
+ </div>
531
+ </section>
532
+ </main>
533
+
534
+ <footer>
535
+ <div class="shell">
536
+ <span>MIT licensed. Built for people composing real coding-agent CLIs.</span>
537
+ </div>
538
+ </footer>
539
+ </body>
540
+ </html>
@@ -0,0 +1,2 @@
1
+ User-agent: *
2
+ Allow: /
@@ -38,28 +38,28 @@ const synthSchema = {
38
38
  properties: { summary: { type: "string" }, count: { type: "number" } },
39
39
  };
40
40
 
41
- export default async function deepResearch(rt: Runtime, _args: unknown) {
42
- rt.phase("Scope");
43
- const scope = (await rt.agent("SCOPE the research question into topics", {
41
+ export default async function deepResearch({ agent, parallel, pipeline, phase }: Runtime, _args: unknown) {
42
+ phase("Scope");
43
+ const scope = (await agent("SCOPE the research question into topics", {
44
44
  schema: scopeSchema,
45
45
  })) as { topics: string[] } | null;
46
46
  if (!scope) return { error: "scoping failed", confirmed: 0 };
47
47
 
48
- rt.phase("Search");
49
- const searched = await rt.parallel(
50
- scope.topics.map((t) => () => rt.agent(`SEARCH ${t}`, { schema: searchSchema, label: `search:${t}` })),
48
+ phase("Search");
49
+ const searched = await parallel(
50
+ scope.topics.map((t) => () => agent(`SEARCH ${t}`, { schema: searchSchema, label: `search:${t}` })),
51
51
  );
52
52
  const found = searched.filter(Boolean); // a failed/timed-out node is dropped here
53
53
 
54
- rt.phase("Verify");
55
- const verified = await rt.pipeline(found, async (f) => {
56
- const v = await rt.agent(`VERIFY ${JSON.stringify(f)}`, { schema: verifySchema });
54
+ phase("Verify");
55
+ const verified = await pipeline(found, async (f) => {
56
+ const v = await agent(`VERIFY ${JSON.stringify(f)}`, { schema: verifySchema });
57
57
  return v ? { ...(f as object), ...(v as object) } : null;
58
58
  });
59
59
  const confirmed = verified.filter(Boolean);
60
60
 
61
- rt.phase("Synthesize");
62
- const summary = await rt.agent(`SYNTH over ${confirmed.length} findings`, { schema: synthSchema });
61
+ phase("Synthesize");
62
+ const summary = await agent(`SYNTH over ${confirmed.length} findings`, { schema: synthSchema });
63
63
 
64
64
  return { confirmed, summary };
65
65
  }
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "oh-my-workflow",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Run coding-agent CLIs (claude -p / codex exec) as nodes in a plain-JS workflow. The thin deterministic glue.",
5
5
  "type": "module",
6
- "author": "Dongwook Kim (https://github.com/domuk-k)",
6
+ "author": "Dongwook Kim <dannyworks102@gmail.com> (https://github.com/domuk-k)",
7
7
  "engines": {
8
8
  "bun": ">=1.0.0"
9
9
  },
10
10
  "files": [
11
11
  "src",
12
12
  "examples",
13
+ "conformance",
13
14
  "skill",
15
+ "scripts/build-docs.ts",
16
+ "scripts/check-docs.ts",
17
+ "docs/site",
18
+ "docs/launch",
19
+ "vercel.json",
14
20
  "README.md",
15
21
  "LICENSE"
16
22
  ],
@@ -32,7 +38,9 @@
32
38
  "scripts": {
33
39
  "test": "bun test",
34
40
  "typecheck": "tsc --noEmit",
35
- "prepublishOnly": "bun run typecheck && bun test"
41
+ "docs:build": "bun scripts/build-docs.ts",
42
+ "docs:check": "bun scripts/check-docs.ts",
43
+ "prepublishOnly": "bun run typecheck && bun test && bun run docs:build && bun run docs:check"
36
44
  },
37
45
  "keywords": [
38
46
  "workflow",
@@ -0,0 +1,10 @@
1
+ import { cpSync, mkdirSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ const out = join(process.cwd(), "dist", "docs");
5
+
6
+ rmSync(out, { recursive: true, force: true });
7
+ mkdirSync(out, { recursive: true });
8
+ cpSync(join(process.cwd(), "docs", "site"), out, { recursive: true });
9
+
10
+ console.log(`docs built -> ${out}`);
@@ -0,0 +1,58 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ type Check = { name: string; ok: boolean; detail?: string };
5
+
6
+ const root = process.cwd();
7
+ const site = join(root, "dist", "docs", "index.html");
8
+ const robots = join(root, "dist", "docs", "robots.txt");
9
+ const launch = join(root, "docs", "launch", "show-hn.md");
10
+ const skill = join(root, "skill", "SKILL.md");
11
+
12
+ const checks: Check[] = [];
13
+ const add = (name: string, ok: boolean, detail?: string) => checks.push({ name, ok, detail });
14
+
15
+ add("docs build output exists", existsSync(site), site);
16
+ add("robots.txt exists", existsSync(robots), robots);
17
+ add("launch note exists", existsSync(launch), launch);
18
+ add("skill exists", existsSync(skill), skill);
19
+
20
+ const html = existsSync(site) ? readFileSync(site, "utf8") : "";
21
+ const launchText = existsSync(launch) ? readFileSync(launch, "utf8") : "";
22
+ const skillText = existsSync(skill) ? readFileSync(skill, "utf8") : "";
23
+
24
+ for (const phrase of [
25
+ "Give your coding agent a workflow mode.",
26
+ "Add one command to your coding agent.",
27
+ "Why now",
28
+ "The whole runtime is seven hooks.",
29
+ "Launch proof",
30
+ "npx skills add domuk-k/oh-my-workflow --skill omw",
31
+ "--agent auto",
32
+ ]) {
33
+ add(`site contains: ${phrase}`, html.includes(phrase));
34
+ }
35
+
36
+ for (const id of ["quickstart", "api", "patterns", "deploy"]) {
37
+ add(`section id #${id}`, html.includes(`id="${id}"`));
38
+ add(`nav href #${id}`, html.includes(`href="#${id}"`));
39
+ }
40
+
41
+ add("site has meta description", /<meta\s+name="description"\s+content="[^"]{80,180}"/.test(html));
42
+ add("site has accessible nav label", html.includes('aria-label="Primary navigation"'));
43
+ add("site has no placeholder words", !/\b(TODO|TBD|lorem|placeholder)\b/i.test(html + "\n" + launchText));
44
+ add("launch note has title", /^Title:\n\n> Show HN:/m.test(launchText));
45
+ add("launch note has skill install command", launchText.includes("npx skills add domuk-k/oh-my-workflow --skill omw"));
46
+ add("launch note explains why now", launchText.includes("Why now:"));
47
+ add("skill frontmatter exposes /omw", /^name:\s*omw\s*$/m.test(skillText));
48
+ add("skill teaches auto adapter", skillText.includes("--agent auto"));
49
+
50
+ const failed = checks.filter((c) => !c.ok);
51
+ for (const c of checks) {
52
+ console.log(`${c.ok ? "ok" : "fail"} - ${c.name}${c.detail ? ` (${c.detail})` : ""}`);
53
+ }
54
+
55
+ if (failed.length > 0) {
56
+ console.error(`docs check failed: ${failed.length}/${checks.length}`);
57
+ process.exit(1);
58
+ }