@hecer/yoke 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +13 -13
- package/.codex-plugin/plugin.json +7 -7
- package/CHANGELOG.md +294 -288
- package/README.md +874 -874
- package/TODOS.md +5 -5
- package/agents/docs.toml +6 -6
- package/agents/implementer.toml +6 -6
- package/agents/reviewer.toml +6 -6
- package/agents/security.toml +6 -6
- package/bench/README.md +86 -86
- package/bench/RESULTS.md +35 -35
- package/bench/output-compaction.mjs +65 -65
- package/bench/result-schema.mjs +12 -12
- package/bench/results/claude-2026-07-27T18-03-26.json +50 -50
- package/bench/results/codex-unavailable-1785175418318.json +15 -15
- package/bench/results/gemini-2026-07-27T18-03-44.json +46 -46
- package/bench/run-matrix.mjs +26 -26
- package/bench/run.mjs +106 -106
- package/canon/AGENTS.md +30 -30
- package/canon/context/DECISIONS.md +4 -4
- package/canon/context/GLOSSARY.md +11 -11
- package/canon/context/KNOWLEDGE.md +4 -4
- package/canon/context/PROJECT.md +15 -15
- package/canon/loop/loop-spec.md +65 -65
- package/canon/loop/prd.schema.md +43 -43
- package/canon/manifest.yaml +59 -59
- package/canon/policy/gates.md +7 -7
- package/canon/policy/roles.md +9 -9
- package/canon/skills/ATTRIBUTION.md +99 -99
- package/canon/skills/authoring-prd/SKILL.md +58 -58
- package/canon/skills/brainstorming/SKILL.md +164 -164
- package/canon/skills/codebase-design/DEEPENING.md +15 -15
- package/canon/skills/codebase-design/DESIGN-IT-TWICE.md +12 -12
- package/canon/skills/codebase-design/SKILL.md +39 -39
- package/canon/skills/dispatching-parallel-agents/SKILL.md +182 -182
- package/canon/skills/document-release/SKILL.md +302 -302
- package/canon/skills/domain-modeling/ADR-FORMAT.md +19 -19
- package/canon/skills/domain-modeling/CONTEXT-FORMAT.md +39 -39
- package/canon/skills/domain-modeling/SKILL.md +35 -35
- package/canon/skills/executing-plans/SKILL.md +70 -70
- package/canon/skills/finishing-a-development-branch/SKILL.md +200 -200
- package/canon/skills/health/SKILL.md +177 -177
- package/canon/skills/maintaining-context/SKILL.md +34 -34
- package/canon/skills/minimal-code/SKILL.md +21 -21
- package/canon/skills/no-ai-slop/SKILL.md +103 -103
- package/canon/skills/no-ai-slop/eval.md +43 -43
- package/canon/skills/plan-ceo-review/SKILL.md +541 -541
- package/canon/skills/plan-eng-review/SKILL.md +362 -362
- package/canon/skills/receiving-code-review/SKILL.md +213 -213
- package/canon/skills/requesting-code-review/SKILL.md +105 -105
- package/canon/skills/resolving-merge-conflicts/SKILL.md +18 -18
- package/canon/skills/retro/SKILL.md +397 -397
- package/canon/skills/review/SKILL.md +246 -246
- package/canon/skills/ship/SKILL.md +691 -691
- package/canon/skills/subagent-driven-development/SKILL.md +277 -277
- package/canon/skills/systematic-debugging/SKILL.md +296 -296
- package/canon/skills/tdd/SKILL.md +371 -371
- package/canon/skills/unslop-ui/SKILL.md +34 -34
- package/canon/skills/using-git-worktrees/SKILL.md +218 -218
- package/canon/skills/verification-before-completion/SKILL.md +139 -139
- package/canon/skills/visual-verification/SKILL.md +54 -54
- package/canon/skills/workflow/SKILL.md +22 -22
- package/canon/skills/writing-for-agents/SKILL-MECHANICS.md +27 -27
- package/canon/skills/writing-for-agents/SKILL.md +42 -42
- package/canon/skills/writing-plans/SKILL.md +152 -152
- package/canon/skills/writing-skills/SKILL.md +655 -655
- package/canon/skills/yoke-retrofit/SKILL.md +26 -26
- package/canon/skills/yoke-workflow/SKILL.md +20 -20
- package/canon/tools/codex-rtk-hook.mjs +35 -35
- package/canon/tools/graphify.md +3 -3
- package/canon/tools/playwright-mcp.md +3 -3
- package/canon/tools/rtk.md +7 -7
- package/canon/tools/serena.md +6 -6
- package/dist/agents/process.js +3 -0
- package/dist/loop/watchdog.js +1 -1
- package/dist/prd/command.js +17 -17
- package/dist/retrofit/planners/claude.js +14 -14
- package/dist/retrofit/preserve.js +2 -2
- package/docs/MIGRATING-TO-1.0.md +33 -33
- package/docs/MIGRATING-TO-1.1.md +27 -27
- package/docs/MIGRATING-TO-1.4.md +70 -70
- package/docs/PUBLISHING.md +91 -91
- package/docs/superpowers/plans/2026-06-28-baustein-e-context-layer.md +981 -981
- package/docs/superpowers/plans/2026-06-29-baustein-f-routing.md +258 -258
- package/docs/superpowers/plans/2026-06-29-baustein-g-loop-observability.md +1006 -1006
- package/docs/superpowers/plans/2026-06-29-baustein-h-loop-robustness.md +374 -374
- package/docs/superpowers/plans/2026-06-30-baustein-i-visual-design-verification.md +450 -450
- package/docs/superpowers/plans/2026-07-02-baustein-k-zero-to-100-bootstrap.md +1024 -1024
- package/docs/superpowers/plans/2026-07-02-baustein-m-flow-smoke-proofs.md +574 -574
- package/docs/superpowers/plans/2026-08-13-gauntlet-quality-loop.md +537 -537
- package/docs/superpowers/plans/2026-08-16-artifact-backed-output-compaction.md +329 -329
- package/docs/superpowers/specs/2026-06-28-baustein-e-context-layer-design.md +146 -146
- package/docs/superpowers/specs/2026-06-29-baustein-f-routing-design.md +106 -106
- package/docs/superpowers/specs/2026-06-29-baustein-g-loop-observability-design.md +186 -186
- package/docs/superpowers/specs/2026-06-29-baustein-h-loop-robustness-design.md +113 -113
- package/docs/superpowers/specs/2026-06-30-baustein-i-visual-design-verification-design.md +98 -98
- package/docs/superpowers/specs/2026-07-02-baustein-k-zero-to-100-bootstrap-design.md +200 -200
- package/docs/superpowers/specs/2026-07-02-baustein-m-flow-smoke-proofs-design.md +155 -155
- package/docs/superpowers/specs/2026-08-13-gauntlet-quality-loop-design.md +422 -422
- package/docs/superpowers/specs/2026-08-16-artifact-backed-output-compaction-design.md +166 -166
- package/gemini-extension.json +6 -6
- package/hooks/hooks.json +19 -19
- package/package.json +87 -87
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
# Baustein M — `yoke flow-smoke`: built-in browser gate with screenshot/video proofs
|
|
2
|
-
|
|
3
|
-
Date: 2026-07-02
|
|
4
|
-
Status: approved (design delegated by user; proof concept approved in conversation: screenshots
|
|
5
|
-
always, video on failure, `.yoke/proof/` storage, loop links proofs to stories)
|
|
6
|
-
|
|
7
|
-
## Problem
|
|
8
|
-
|
|
9
|
-
The verify gate is code-only unless an agent hand-rolls a Playwright smoke (the
|
|
10
|
-
`visual-verification` skill teaches it, but nothing enforces or standardizes it). A story can
|
|
11
|
-
pass unit tests and design-scan yet ship a blank page, an unwired route, or a runtime console
|
|
12
|
-
error. And when a story IS done, there is no visual evidence — "done with a photo" is the answer
|
|
13
|
-
to the loudest agentic-coding pain point ("agent says done, but it isn't").
|
|
14
|
-
|
|
15
|
-
## Goal
|
|
16
|
-
|
|
17
|
-
A built-in, mechanical browser gate that any project can add to `verify.command`:
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
yoke flow-smoke [dir] [--url=<baseUrl>] [--label=<name>]
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
For every configured flow: load the route, optionally wait for a landmark selector, assert zero
|
|
24
|
-
console/page errors, and **always** save a screenshot to `.yoke/proof/<label>/<flow>.png`.
|
|
25
|
-
Record video per flow but **keep it only on failure** (`<flow>.webm`). Exit 0 = all flows green
|
|
26
|
-
(chainable in `verify.command`), 1 = failures, 2 = not runnable (no config / no playwright).
|
|
27
|
-
|
|
28
|
-
## Part 1: Config schema
|
|
29
|
-
|
|
30
|
-
Extend `YokeConfigSchema` (src/retrofit/config.ts) with an optional `smoke` section:
|
|
31
|
-
|
|
32
|
-
```yaml
|
|
33
|
-
smoke:
|
|
34
|
-
baseUrl: http://localhost:3000
|
|
35
|
-
flows:
|
|
36
|
-
- name: home
|
|
37
|
-
path: /
|
|
38
|
-
landmark: "main h1" # optional CSS selector
|
|
39
|
-
- name: login
|
|
40
|
-
path: /login
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Zod: `smoke: z.object({ baseUrl: z.string().min(1), flows: z.array(z.object({ name: z.string().min(1), path: z.string().min(1), landmark: z.string().optional() })).min(1) }).optional()` and the matching optional field on the `YokeConfig` interface. Existing configs (no `smoke`) stay valid.
|
|
44
|
-
|
|
45
|
-
## Part 2: `runFlowSmoke` (src/smoke/command.ts)
|
|
46
|
-
|
|
47
|
-
`export async function runFlowSmoke(targetDir: string, opts: FlowSmokeOptions = {}): Promise<number>`
|
|
48
|
-
|
|
49
|
-
Sequence:
|
|
50
|
-
1. Load config. No config file or no `smoke` section → print guidance (example YAML above) and
|
|
51
|
-
exit **2**. `--url` overrides `baseUrl`.
|
|
52
|
-
2. Resolve Playwright **from the target project** (never a Yoke dependency):
|
|
53
|
-
`createRequire(join(targetDir, 'package.json')).resolve('playwright')`, then dynamic
|
|
54
|
-
`import(pathToFileURL(resolved).href)`. Resolution failure → print
|
|
55
|
-
`Playwright not found in <dir>. Install it: npm i -D playwright && npx playwright install chromium`
|
|
56
|
-
and exit **2**.
|
|
57
|
-
3. Proof dir: `.yoke/proof/<label>/` where label = `opts.label` ?? `process.env.YOKE_STORY` ??
|
|
58
|
-
`'latest'`. Wipe the label dir before the run (`rmSync(recursive, force)` + mkdir) — each run
|
|
59
|
-
is fresh evidence, no stale screenshots.
|
|
60
|
-
4. Launch chromium headless once; per flow, create a **new context** with
|
|
61
|
-
`recordVideo: { dir: <proofDir>/.video-tmp }` and a page. Collect errors: `page.on('console')`
|
|
62
|
-
with `type() === 'error'`, and `page.on('pageerror')`.
|
|
63
|
-
5. Per flow, in order (fail-fast per flow, continue to the next flow):
|
|
64
|
-
- `page.goto(baseUrl + path, { waitUntil: 'load', timeout: 30_000 })`; a non-OK response
|
|
65
|
-
(`!response.ok()`) is a failure (`HTTP <status>`).
|
|
66
|
-
- if `landmark`: `page.waitForSelector(landmark, { timeout: 10_000 })`; timeout → failure
|
|
67
|
-
(`landmark "<sel>" not found`).
|
|
68
|
-
- collected errors non-empty → failure (`N console error(s): <first, truncated 200 chars>`).
|
|
69
|
-
- **always** `page.screenshot({ path: <proofDir>/<flow.name>.png, fullPage: true })` — also on
|
|
70
|
-
failure (the failure screenshot IS the evidence), inside try/catch (a crashed page must not
|
|
71
|
-
mask the original failure).
|
|
72
|
-
- close context; then: flow failed → move its video to `<proofDir>/<flow.name>.webm`
|
|
73
|
-
(`page.video().path()` after close); flow passed → delete the video file. Remove
|
|
74
|
-
`.video-tmp` at the end.
|
|
75
|
-
6. Report per flow: `✔ home (screenshot: .yoke/proof/latest/home.png)` /
|
|
76
|
-
`✘ login — 2 console error(s): ... (screenshot + video saved)`. Summary line
|
|
77
|
-
`Flow-smoke: N/M flows green — proof: .yoke/proof/<label>/`.
|
|
78
|
-
7. Close the browser in a `finally`. Exit 0 all green, 1 any failure.
|
|
79
|
-
|
|
80
|
-
Injectable seam for tests: `opts.browser?: () => Promise<SmokeBrowser>` — a minimal structural
|
|
81
|
-
interface defined in the module:
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
export interface SmokePage {
|
|
85
|
-
goto(url: string, opts?: object): Promise<{ ok(): boolean; status(): number } | null>
|
|
86
|
-
waitForSelector(sel: string, opts?: object): Promise<unknown>
|
|
87
|
-
screenshot(opts: { path: string; fullPage?: boolean }): Promise<unknown>
|
|
88
|
-
on(event: 'console' | 'pageerror', handler: (arg: any) => void): void
|
|
89
|
-
video(): { path(): Promise<string> } | null
|
|
90
|
-
}
|
|
91
|
-
export interface SmokeContext { newPage(): Promise<SmokePage>; close(): Promise<void> }
|
|
92
|
-
export interface SmokeBrowser {
|
|
93
|
-
newContext(opts?: object): Promise<SmokeContext>
|
|
94
|
-
close(): Promise<void>
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The real path adapts Playwright's chromium to this interface; tests inject fakes (no Playwright
|
|
99
|
-
in Yoke's devDependencies — an fs-level fake writes marker files for "screenshot"/"video").
|
|
100
|
-
|
|
101
|
-
## Part 3: Loop linkage — proofs per story
|
|
102
|
-
|
|
103
|
-
- In `src/loop/loop.ts`, both verify call sites set `process.env.YOKE_STORY = story.id` before
|
|
104
|
-
`opts.verify(...)` and delete it in a `finally`. A `yoke flow-smoke` inside `verify.command`
|
|
105
|
-
then writes to `.yoke/proof/<story-id>/` automatically — every completed story has visual
|
|
106
|
-
evidence, every blocked story has failure evidence (screenshot + video).
|
|
107
|
-
- `.yoke/proof/` joins `YOKE_IGNORE_LINES` (runtime artifact; must not break the clean-tree gate).
|
|
108
|
-
|
|
109
|
-
## Part 4: CLI + async main
|
|
110
|
-
|
|
111
|
-
- `main()` in src/cli.ts becomes `number | Promise<number>`-returning; the isMain block awaits it
|
|
112
|
-
(top-level await, ESM). Existing sync cases are untouched.
|
|
113
|
-
- `case 'flow-smoke'`: `[dir]`, `--url=`, `--label=` → `return runFlowSmoke(targetDir, { url, label })`.
|
|
114
|
-
- Usage line gains `flow-smoke [dir] [--url=<baseUrl>] [--label=<name>]`.
|
|
115
|
-
|
|
116
|
-
## Part 5: Canon skill update
|
|
117
|
-
|
|
118
|
-
`canon/skills/visual-verification/SKILL.md`: replace the hand-rolled flow-smoke instruction with
|
|
119
|
-
the built-in — configure `smoke:` in `.yoke/config.yaml`, chain
|
|
120
|
-
`... && yoke design-scan . && yoke flow-smoke .` in `verify.command`; keep the Playwright-MCP
|
|
121
|
-
guidance for *debugging* a failed flow (watch the saved video first). Mention proofs land in
|
|
122
|
-
`.yoke/proof/<story>/`. Skill count stays 27 (content update, no new skill).
|
|
123
|
-
|
|
124
|
-
## Testing
|
|
125
|
-
|
|
126
|
-
- `tests/retrofit/config.test.ts` (extend): smoke section round-trips; config without smoke stays
|
|
127
|
-
valid; invalid smoke (empty flows) rejected.
|
|
128
|
-
- `tests/smoke/command.test.ts` (fake browser):
|
|
129
|
-
- exit 2 when no smoke config (message contains example);
|
|
130
|
-
- exit 2 when playwright is unresolvable: call without `opts.browser` (the seam bypasses
|
|
131
|
-
resolution) against a temp dir that has a smoke config but no playwright install;
|
|
132
|
-
- green flow: screenshot file written under `.yoke/proof/latest/<name>.png`, video deleted, exit 0;
|
|
133
|
-
- landmark timeout → exit 1, screenshot still written, video kept as `<name>.webm`;
|
|
134
|
-
- console error collected → exit 1;
|
|
135
|
-
- non-OK response → exit 1;
|
|
136
|
-
- label resolution: `--label` beats `YOKE_STORY` env beats `latest`;
|
|
137
|
-
- proof dir wiped between runs (stale file gone);
|
|
138
|
-
- one failing flow does not stop later flows (both reported).
|
|
139
|
-
- `tests/loop/*`: one test that a fake verifier observes `process.env.YOKE_STORY === story.id`
|
|
140
|
-
during verify and that it is unset afterwards (both normal and `--isolate` paths if cheap;
|
|
141
|
-
normal path suffices).
|
|
142
|
-
- `tests/retrofit/gitignore.test.ts`: `.yoke/proof/` ensured.
|
|
143
|
-
|
|
144
|
-
## Non-goals
|
|
145
|
-
|
|
146
|
-
- No dev-server startup/readiness management (`start-server-and-test` and friends exist; the
|
|
147
|
-
skill documents chaining). A connection-refused goto is an ordinary flow failure.
|
|
148
|
-
- No visual diffing/pixel comparison, no multi-browser matrix, no video-always mode (token/disk
|
|
149
|
-
cost; Ebene C in the Baustein-I backlog stays opt-in future work).
|
|
150
|
-
- Playwright stays a target-project dependency, never Yoke's.
|
|
151
|
-
|
|
152
|
-
## Attribution
|
|
153
|
-
|
|
154
|
-
Browser-QA-as-gate idea: gstack `/qa` (MIT © Garry Tan), natively re-implemented cross-agent with
|
|
155
|
-
a proof-artifact contract; no code copied. Extend the existing ATTRIBUTION.md gstack entry.
|
|
1
|
+
# Baustein M — `yoke flow-smoke`: built-in browser gate with screenshot/video proofs
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-02
|
|
4
|
+
Status: approved (design delegated by user; proof concept approved in conversation: screenshots
|
|
5
|
+
always, video on failure, `.yoke/proof/` storage, loop links proofs to stories)
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
The verify gate is code-only unless an agent hand-rolls a Playwright smoke (the
|
|
10
|
+
`visual-verification` skill teaches it, but nothing enforces or standardizes it). A story can
|
|
11
|
+
pass unit tests and design-scan yet ship a blank page, an unwired route, or a runtime console
|
|
12
|
+
error. And when a story IS done, there is no visual evidence — "done with a photo" is the answer
|
|
13
|
+
to the loudest agentic-coding pain point ("agent says done, but it isn't").
|
|
14
|
+
|
|
15
|
+
## Goal
|
|
16
|
+
|
|
17
|
+
A built-in, mechanical browser gate that any project can add to `verify.command`:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
yoke flow-smoke [dir] [--url=<baseUrl>] [--label=<name>]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For every configured flow: load the route, optionally wait for a landmark selector, assert zero
|
|
24
|
+
console/page errors, and **always** save a screenshot to `.yoke/proof/<label>/<flow>.png`.
|
|
25
|
+
Record video per flow but **keep it only on failure** (`<flow>.webm`). Exit 0 = all flows green
|
|
26
|
+
(chainable in `verify.command`), 1 = failures, 2 = not runnable (no config / no playwright).
|
|
27
|
+
|
|
28
|
+
## Part 1: Config schema
|
|
29
|
+
|
|
30
|
+
Extend `YokeConfigSchema` (src/retrofit/config.ts) with an optional `smoke` section:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
smoke:
|
|
34
|
+
baseUrl: http://localhost:3000
|
|
35
|
+
flows:
|
|
36
|
+
- name: home
|
|
37
|
+
path: /
|
|
38
|
+
landmark: "main h1" # optional CSS selector
|
|
39
|
+
- name: login
|
|
40
|
+
path: /login
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Zod: `smoke: z.object({ baseUrl: z.string().min(1), flows: z.array(z.object({ name: z.string().min(1), path: z.string().min(1), landmark: z.string().optional() })).min(1) }).optional()` and the matching optional field on the `YokeConfig` interface. Existing configs (no `smoke`) stay valid.
|
|
44
|
+
|
|
45
|
+
## Part 2: `runFlowSmoke` (src/smoke/command.ts)
|
|
46
|
+
|
|
47
|
+
`export async function runFlowSmoke(targetDir: string, opts: FlowSmokeOptions = {}): Promise<number>`
|
|
48
|
+
|
|
49
|
+
Sequence:
|
|
50
|
+
1. Load config. No config file or no `smoke` section → print guidance (example YAML above) and
|
|
51
|
+
exit **2**. `--url` overrides `baseUrl`.
|
|
52
|
+
2. Resolve Playwright **from the target project** (never a Yoke dependency):
|
|
53
|
+
`createRequire(join(targetDir, 'package.json')).resolve('playwright')`, then dynamic
|
|
54
|
+
`import(pathToFileURL(resolved).href)`. Resolution failure → print
|
|
55
|
+
`Playwright not found in <dir>. Install it: npm i -D playwright && npx playwright install chromium`
|
|
56
|
+
and exit **2**.
|
|
57
|
+
3. Proof dir: `.yoke/proof/<label>/` where label = `opts.label` ?? `process.env.YOKE_STORY` ??
|
|
58
|
+
`'latest'`. Wipe the label dir before the run (`rmSync(recursive, force)` + mkdir) — each run
|
|
59
|
+
is fresh evidence, no stale screenshots.
|
|
60
|
+
4. Launch chromium headless once; per flow, create a **new context** with
|
|
61
|
+
`recordVideo: { dir: <proofDir>/.video-tmp }` and a page. Collect errors: `page.on('console')`
|
|
62
|
+
with `type() === 'error'`, and `page.on('pageerror')`.
|
|
63
|
+
5. Per flow, in order (fail-fast per flow, continue to the next flow):
|
|
64
|
+
- `page.goto(baseUrl + path, { waitUntil: 'load', timeout: 30_000 })`; a non-OK response
|
|
65
|
+
(`!response.ok()`) is a failure (`HTTP <status>`).
|
|
66
|
+
- if `landmark`: `page.waitForSelector(landmark, { timeout: 10_000 })`; timeout → failure
|
|
67
|
+
(`landmark "<sel>" not found`).
|
|
68
|
+
- collected errors non-empty → failure (`N console error(s): <first, truncated 200 chars>`).
|
|
69
|
+
- **always** `page.screenshot({ path: <proofDir>/<flow.name>.png, fullPage: true })` — also on
|
|
70
|
+
failure (the failure screenshot IS the evidence), inside try/catch (a crashed page must not
|
|
71
|
+
mask the original failure).
|
|
72
|
+
- close context; then: flow failed → move its video to `<proofDir>/<flow.name>.webm`
|
|
73
|
+
(`page.video().path()` after close); flow passed → delete the video file. Remove
|
|
74
|
+
`.video-tmp` at the end.
|
|
75
|
+
6. Report per flow: `✔ home (screenshot: .yoke/proof/latest/home.png)` /
|
|
76
|
+
`✘ login — 2 console error(s): ... (screenshot + video saved)`. Summary line
|
|
77
|
+
`Flow-smoke: N/M flows green — proof: .yoke/proof/<label>/`.
|
|
78
|
+
7. Close the browser in a `finally`. Exit 0 all green, 1 any failure.
|
|
79
|
+
|
|
80
|
+
Injectable seam for tests: `opts.browser?: () => Promise<SmokeBrowser>` — a minimal structural
|
|
81
|
+
interface defined in the module:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
export interface SmokePage {
|
|
85
|
+
goto(url: string, opts?: object): Promise<{ ok(): boolean; status(): number } | null>
|
|
86
|
+
waitForSelector(sel: string, opts?: object): Promise<unknown>
|
|
87
|
+
screenshot(opts: { path: string; fullPage?: boolean }): Promise<unknown>
|
|
88
|
+
on(event: 'console' | 'pageerror', handler: (arg: any) => void): void
|
|
89
|
+
video(): { path(): Promise<string> } | null
|
|
90
|
+
}
|
|
91
|
+
export interface SmokeContext { newPage(): Promise<SmokePage>; close(): Promise<void> }
|
|
92
|
+
export interface SmokeBrowser {
|
|
93
|
+
newContext(opts?: object): Promise<SmokeContext>
|
|
94
|
+
close(): Promise<void>
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The real path adapts Playwright's chromium to this interface; tests inject fakes (no Playwright
|
|
99
|
+
in Yoke's devDependencies — an fs-level fake writes marker files for "screenshot"/"video").
|
|
100
|
+
|
|
101
|
+
## Part 3: Loop linkage — proofs per story
|
|
102
|
+
|
|
103
|
+
- In `src/loop/loop.ts`, both verify call sites set `process.env.YOKE_STORY = story.id` before
|
|
104
|
+
`opts.verify(...)` and delete it in a `finally`. A `yoke flow-smoke` inside `verify.command`
|
|
105
|
+
then writes to `.yoke/proof/<story-id>/` automatically — every completed story has visual
|
|
106
|
+
evidence, every blocked story has failure evidence (screenshot + video).
|
|
107
|
+
- `.yoke/proof/` joins `YOKE_IGNORE_LINES` (runtime artifact; must not break the clean-tree gate).
|
|
108
|
+
|
|
109
|
+
## Part 4: CLI + async main
|
|
110
|
+
|
|
111
|
+
- `main()` in src/cli.ts becomes `number | Promise<number>`-returning; the isMain block awaits it
|
|
112
|
+
(top-level await, ESM). Existing sync cases are untouched.
|
|
113
|
+
- `case 'flow-smoke'`: `[dir]`, `--url=`, `--label=` → `return runFlowSmoke(targetDir, { url, label })`.
|
|
114
|
+
- Usage line gains `flow-smoke [dir] [--url=<baseUrl>] [--label=<name>]`.
|
|
115
|
+
|
|
116
|
+
## Part 5: Canon skill update
|
|
117
|
+
|
|
118
|
+
`canon/skills/visual-verification/SKILL.md`: replace the hand-rolled flow-smoke instruction with
|
|
119
|
+
the built-in — configure `smoke:` in `.yoke/config.yaml`, chain
|
|
120
|
+
`... && yoke design-scan . && yoke flow-smoke .` in `verify.command`; keep the Playwright-MCP
|
|
121
|
+
guidance for *debugging* a failed flow (watch the saved video first). Mention proofs land in
|
|
122
|
+
`.yoke/proof/<story>/`. Skill count stays 27 (content update, no new skill).
|
|
123
|
+
|
|
124
|
+
## Testing
|
|
125
|
+
|
|
126
|
+
- `tests/retrofit/config.test.ts` (extend): smoke section round-trips; config without smoke stays
|
|
127
|
+
valid; invalid smoke (empty flows) rejected.
|
|
128
|
+
- `tests/smoke/command.test.ts` (fake browser):
|
|
129
|
+
- exit 2 when no smoke config (message contains example);
|
|
130
|
+
- exit 2 when playwright is unresolvable: call without `opts.browser` (the seam bypasses
|
|
131
|
+
resolution) against a temp dir that has a smoke config but no playwright install;
|
|
132
|
+
- green flow: screenshot file written under `.yoke/proof/latest/<name>.png`, video deleted, exit 0;
|
|
133
|
+
- landmark timeout → exit 1, screenshot still written, video kept as `<name>.webm`;
|
|
134
|
+
- console error collected → exit 1;
|
|
135
|
+
- non-OK response → exit 1;
|
|
136
|
+
- label resolution: `--label` beats `YOKE_STORY` env beats `latest`;
|
|
137
|
+
- proof dir wiped between runs (stale file gone);
|
|
138
|
+
- one failing flow does not stop later flows (both reported).
|
|
139
|
+
- `tests/loop/*`: one test that a fake verifier observes `process.env.YOKE_STORY === story.id`
|
|
140
|
+
during verify and that it is unset afterwards (both normal and `--isolate` paths if cheap;
|
|
141
|
+
normal path suffices).
|
|
142
|
+
- `tests/retrofit/gitignore.test.ts`: `.yoke/proof/` ensured.
|
|
143
|
+
|
|
144
|
+
## Non-goals
|
|
145
|
+
|
|
146
|
+
- No dev-server startup/readiness management (`start-server-and-test` and friends exist; the
|
|
147
|
+
skill documents chaining). A connection-refused goto is an ordinary flow failure.
|
|
148
|
+
- No visual diffing/pixel comparison, no multi-browser matrix, no video-always mode (token/disk
|
|
149
|
+
cost; Ebene C in the Baustein-I backlog stays opt-in future work).
|
|
150
|
+
- Playwright stays a target-project dependency, never Yoke's.
|
|
151
|
+
|
|
152
|
+
## Attribution
|
|
153
|
+
|
|
154
|
+
Browser-QA-as-gate idea: gstack `/qa` (MIT © Garry Tan), natively re-implemented cross-agent with
|
|
155
|
+
a proof-artifact contract; no code copied. Extend the existing ATTRIBUTION.md gstack entry.
|