deepflow 0.1.87 → 0.1.89

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.
@@ -7,229 +7,35 @@ allowed-tools: [Bash, WebFetch, WebSearch, Read]
7
7
 
8
8
  # Browse-Fetch
9
9
 
10
- Retrieve live web content with a headless browser. Handles JavaScript-rendered pages, SPAs, and dynamic content that WebFetch cannot reach.
10
+ Retrieve live web content with a headless browser. Handles JS-rendered pages, SPAs, and dynamic content that WebFetch cannot reach.
11
11
 
12
- ## When to Use
13
-
14
- - Reading documentation sites that require JavaScript to render (e.g., React-based docs, Vite, Next.js portals)
15
- - Fetching the current content of a specific URL provided by the user
16
- - Extracting article or reference content from a known page before implementing code against it
17
-
18
- ## Skip When
19
-
20
- - The URL is a plain HTML page or GitHub raw file — use WebFetch instead (faster, no overhead)
21
- - The target requires authentication (login wall) or CAPTCHA — browser cannot bypass; note the block and continue
12
+ **Use when:** URL requires JavaScript rendering (React-based docs, SPAs, portals).
13
+ **Skip when:** Plain HTML / GitHub raw file (use WebFetch) or auth-walled / CAPTCHA-blocked page.
22
14
 
23
15
  ---
24
16
 
25
17
  ## Browser Core Protocol
26
18
 
27
- This protocol is the reusable foundation for all browser-based skills (browse-fetch, browse-verify, etc.).
19
+ The fetch script below implements all steps. Summary of what each phase does:
28
20
 
29
- ### 1. Install Check
21
+ 1. **Runtime detection** — prefer `node`, fall back to `bun`.
22
+ 2. **Install check** — `require('playwright')` or auto-install chromium via npx.
23
+ 3. **Launch** — headless Chromium with desktop Chrome user-agent.
24
+ 4. **Navigate** — `goto` with `domcontentloaded` (upgrade to `networkidle` if content missing), 30s timeout, 1.5s settle delay.
25
+ 5. **Block detection** — check title/URL for login walls, body for CAPTCHA. On detection: log, skip, continue task.
26
+ 6. **Extract** — `page.evaluate()` converts DOM → Markdown in-browser (zero deps). Strips noise elements (nav/footer/header/aside/scripts/cookies), picks `main|article|[role=main]|body`, recursive `md()` handles headings, lists, tables, code blocks, links, images, blockquotes, definition lists. Falls back to `page.innerText('body')` if extraction < 100 chars.
27
+ 7. **Truncate** — cap at 16000 chars (~4000 tokens). Append `[content truncated]` marker.
28
+ 8. **Cleanup** — always `browser.close()` in `finally`.
30
29
 
31
- Before launching, verify Playwright is available:
30
+ ## Fetch Script
31
+
32
+ Inline via `$RUNTIME -e`. Adapt URL per query.
32
33
 
33
34
  ```bash
34
- # Prefer Node.js; fall back to Bun
35
35
  if which node > /dev/null 2>&1; then RUNTIME=node; elif which bun > /dev/null 2>&1; then RUNTIME=bun; else echo "Error: neither node nor bun found" && exit 1; fi
36
36
 
37
37
  $RUNTIME -e "require('playwright')" 2>/dev/null \
38
38
  || npx --yes playwright install chromium --with-deps 2>&1 | tail -5
39
- ```
40
-
41
- If installation fails, fall back to WebFetch (see Fallback section below).
42
-
43
- ### 2. Launch Command
44
-
45
- ```bash
46
- # Detect runtime — prefer Node.js per decision
47
- if which node > /dev/null 2>&1; then RUNTIME=node; elif which bun > /dev/null 2>&1; then RUNTIME=bun; else echo "Error: neither node nor bun found" && exit 1; fi
48
-
49
- $RUNTIME -e "
50
- const { chromium } = require('playwright');
51
- (async () => {
52
- const browser = await chromium.launch({ headless: true });
53
- const context = await browser.newContext({
54
- userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
55
- });
56
- const page = await context.newPage();
57
-
58
- // --- navigation + extraction (see sections 3–4) ---
59
-
60
- await browser.close();
61
- })().catch(e => { console.error(e.message); process.exit(1); });
62
- "
63
- ```
64
-
65
- ### 3. Navigation
66
-
67
- ```js
68
- // Inside the async IIFE above
69
- await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
70
- // Allow JS to settle
71
- await page.waitForTimeout(1500);
72
- ```
73
-
74
- - Use `waitUntil: 'domcontentloaded'` for speed; upgrade to `'networkidle'` only if content is missing.
75
- - Set `timeout: 30000` (30 s). On timeout, treat as graceful failure (see section 5).
76
-
77
- ### 4. Content Extraction
78
-
79
- Extract content as **structured Markdown** optimized for LLM consumption (not raw HTML or flat text).
80
-
81
- ```js
82
- // Convert DOM to Markdown inside the browser context — zero dependencies
83
- let text = await page.evaluate(() => {
84
- // Remove noise elements
85
- const noise = 'nav, footer, header, aside, script, style, noscript, svg, [role="navigation"], [role="banner"], [role="contentinfo"], .cookie-banner, #cookie-consent';
86
- document.querySelectorAll(noise).forEach(el => el.remove());
87
-
88
- // Pick main content container
89
- const root = document.querySelector('main, article, [role="main"]') || document.body;
90
-
91
- function md(node, listDepth = 0) {
92
- if (node.nodeType === 3) return node.textContent;
93
- if (node.nodeType !== 1) return '';
94
- const tag = node.tagName.toLowerCase();
95
- const children = () => Array.from(node.childNodes).map(c => md(c, listDepth)).join('');
96
-
97
- // Skip hidden elements
98
- if (node.getAttribute('aria-hidden') === 'true' || node.hidden) return '';
99
-
100
- switch (tag) {
101
- case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': {
102
- const level = '#'.repeat(parseInt(tag[1]));
103
- const text = node.textContent.trim();
104
- return text ? '\n\n' + level + ' ' + text + '\n\n' : '';
105
- }
106
- case 'p': return '\n\n' + children().trim() + '\n\n';
107
- case 'br': return '\n';
108
- case 'hr': return '\n\n---\n\n';
109
- case 'strong': case 'b': { const t = children().trim(); return t ? '**' + t + '**' : ''; }
110
- case 'em': case 'i': { const t = children().trim(); return t ? '*' + t + '*' : ''; }
111
- case 'code': {
112
- const t = node.textContent;
113
- return node.parentElement && node.parentElement.tagName.toLowerCase() === 'pre' ? t : '`' + t + '`';
114
- }
115
- case 'pre': {
116
- const code = node.querySelector('code');
117
- const lang = code ? (code.className.match(/language-(\w+)/)||[])[1] || '' : '';
118
- const t = (code || node).textContent.trim();
119
- return '\n\n```' + lang + '\n' + t + '\n```\n\n';
120
- }
121
- case 'a': {
122
- const href = node.getAttribute('href');
123
- const t = children().trim();
124
- return (href && t && !href.startsWith('#')) ? '[' + t + '](' + href + ')' : t;
125
- }
126
- case 'img': {
127
- const alt = node.getAttribute('alt') || '';
128
- return alt ? '[image: ' + alt + ']' : '';
129
- }
130
- case 'ul': case 'ol': return '\n\n' + children() + '\n';
131
- case 'li': {
132
- const indent = ' '.repeat(listDepth);
133
- const bullet = node.parentElement && node.parentElement.tagName.toLowerCase() === 'ol'
134
- ? (Array.from(node.parentElement.children).indexOf(node) + 1) + '. '
135
- : '- ';
136
- const content = Array.from(node.childNodes).map(c => {
137
- const t = c.tagName && (c.tagName.toLowerCase() === 'ul' || c.tagName.toLowerCase() === 'ol')
138
- ? md(c, listDepth + 1) : md(c, listDepth);
139
- return t;
140
- }).join('').trim();
141
- return indent + bullet + content + '\n';
142
- }
143
- case 'table': {
144
- const rows = Array.from(node.querySelectorAll('tr'));
145
- if (!rows.length) return '';
146
- const matrix = rows.map(r => Array.from(r.querySelectorAll('th, td')).map(c => c.textContent.trim()));
147
- const cols = Math.max(...matrix.map(r => r.length));
148
- const widths = Array.from({length: cols}, (_, i) => Math.max(...matrix.map(r => (r[i]||'').length), 3));
149
- let out = '\n\n';
150
- matrix.forEach((row, ri) => {
151
- out += '| ' + Array.from({length: cols}, (_, i) => (row[i]||'').padEnd(widths[i])).join(' | ') + ' |\n';
152
- if (ri === 0) out += '| ' + widths.map(w => '-'.repeat(w)).join(' | ') + ' |\n';
153
- });
154
- return out + '\n';
155
- }
156
- case 'blockquote': return '\n\n> ' + children().trim().replace(/\n/g, '\n> ') + '\n\n';
157
- case 'dl': return '\n\n' + children() + '\n';
158
- case 'dt': return '**' + children().trim() + '**\n';
159
- case 'dd': return ': ' + children().trim() + '\n';
160
- case 'div': case 'section': case 'span': case 'figure': case 'figcaption':
161
- return children();
162
- default: return children();
163
- }
164
- }
165
-
166
- let result = md(root);
167
- // Collapse excessive whitespace
168
- result = result.replace(/\n{3,}/g, '\n\n').trim();
169
- return result;
170
- });
171
-
172
- // Fallback if extraction is too short
173
- if (!text || text.trim().length < 100) {
174
- text = await page.innerText('body').catch(() => '');
175
- }
176
-
177
- // Truncate to ~4000 tokens (~16000 chars) to stay within context budget
178
- const MAX_CHARS = 16000;
179
- if (text.length > MAX_CHARS) {
180
- text = text.slice(0, MAX_CHARS) + '\n\n[content truncated — use a more specific selector or paginate]';
181
- }
182
-
183
- console.log(text);
184
- ```
185
-
186
- For interactive element inspection (e.g., browse-verify), use `locator.ariaSnapshot()` instead of `innerText`.
187
-
188
- ### 5. Graceful Failure
189
-
190
- Detect and handle blocks without crashing:
191
-
192
- ```js
193
- const title = await page.title();
194
- const url = page.url();
195
-
196
- // Login wall
197
- if (/sign.?in|log.?in|auth/i.test(title) || url.includes('/login')) {
198
- console.log(`[browse-fetch] Blocked by login wall at ${url}. Skipping.`);
199
- await browser.close();
200
- process.exit(0);
201
- }
202
-
203
- // CAPTCHA
204
- const bodyText = await page.innerText('body').catch(() => '');
205
- if (/captcha|robot|human verification/i.test(bodyText)) {
206
- console.log(`[browse-fetch] CAPTCHA detected at ${url}. Skipping.`);
207
- await browser.close();
208
- process.exit(0);
209
- }
210
- ```
211
-
212
- On graceful failure: return the URL and a short explanation, then continue with the task using available context.
213
-
214
- ### 6. Cleanup
215
-
216
- Always close the browser in a `finally` block or after use:
217
-
218
- ```js
219
- await browser.close();
220
- ```
221
-
222
- ---
223
-
224
- ## Fetch Workflow
225
-
226
- **Goal:** retrieve and return structured Markdown content of a single URL.
227
-
228
- The full inline script uses `page.evaluate()` to convert DOM → Markdown inside the browser (zero Node dependencies). Adapt the URL per query.
229
-
230
- ```bash
231
- # Full inline script — adapt URL per query
232
- if which node > /dev/null 2>&1; then RUNTIME=node; elif which bun > /dev/null 2>&1; then RUNTIME=bun; else echo "Error: neither node nor bun found" && exit 1; fi
233
39
 
234
40
  $RUNTIME -e "
235
41
  const { chromium } = require('playwright');
@@ -342,77 +148,46 @@ const { chromium } = require('playwright');
342
148
  "
343
149
  ```
344
150
 
345
- The agent inlines the full script via `node -e` or `bun -e` so no temp files are needed for extractions under ~4000 tokens.
151
+ For interactive element inspection (e.g., browse-verify), use `locator.ariaSnapshot()` instead of DOM extraction.
346
152
 
347
153
  ---
348
154
 
349
155
  ## Search + Navigation Protocol
350
156
 
351
- **Time-box:** 60 seconds total. **Page cap:** 5 pages per query.
352
-
353
- > Search engines (Google, DuckDuckGo) block headless browsers with CAPTCHAs. Do NOT use Playwright to search them.
354
-
355
- Instead, use one of these strategies:
157
+ **Time-box:** 60s. **Page cap:** 5 pages per query.
356
158
 
357
- | Strategy | When to use |
358
- |----------|-------------|
359
- | Direct URL construction | You know the domain (e.g., `docs.stripe.com/api/charges`) |
360
- | WebSearch tool | General keyword search before fetching pages |
361
- | Site-specific search | Navigate to `site.com/search?q=term` if the site exposes it |
159
+ Do NOT use Playwright for Google/DuckDuckGo (CAPTCHA). Instead:
362
160
 
363
- **Navigation loop** (up to 5 pages):
161
+ | Strategy | When |
162
+ |----------|------|
163
+ | Direct URL construction | Domain is known (e.g., `docs.stripe.com/api/charges`) |
164
+ | WebSearch tool | General keyword search before fetching |
165
+ | Site-specific search | Site exposes `/search?q=term` |
364
166
 
365
- 1. Construct or obtain the target URL.
366
- 2. Run the fetch workflow above.
367
- 3. If the page lacks the needed information, look for a next-page link or a more specific sub-URL.
368
- 4. Repeat up to 4 more times (5 total).
369
- 5. Stop and summarize what was found within the 60 s window.
167
+ Navigation loop: construct URL → fetch → if info missing, find next-page or sub-URL repeat (max 5 pages) → summarize within 60s.
370
168
 
371
169
  ---
372
170
 
373
171
  ## Session Cache
374
172
 
375
- The context window is the cache. Extracted content lives in the conversation until it is no longer needed.
376
-
377
- For extractions larger than ~4000 tokens, write to a temp file and reference it:
378
-
379
- ```bash
380
- # Write large extraction to temp file
381
- TMPFILE=$(mktemp /tmp/browse-fetch-XXXXXX.txt)
382
- $RUNTIME -e "...script..." > "$TMPFILE"
383
- echo "Content saved to $TMPFILE"
384
- # Read relevant sections with grep or head rather than loading all at once
385
- ```
173
+ Context window is the cache. For extractions > ~4000 tokens, write to temp file (`mktemp /tmp/browse-fetch-XXXXXX.txt`) and read relevant sections with grep/head.
386
174
 
387
175
  ---
388
176
 
389
177
  ## Fallback Without Playwright
390
178
 
391
- When Playwright is unavailable or fails to install, fall back to the WebFetch tool for:
392
-
393
- - Static HTML sites (GitHub README, raw docs, Wikipedia)
394
- - Any URL the user provides where JavaScript rendering is not required
395
-
396
179
  | Condition | Action |
397
180
  |-----------|--------|
398
- | `playwright` not installed, install fails | Use WebFetch |
399
- | Page is a known static domain (github.com/raw, pastebin, etc.) | Use WebFetch directly skip Playwright |
400
- | Playwright times out twice | Use WebFetch as fallback attempt |
401
-
402
- ```
403
- WebFetch: { url: "https://example.com/page", prompt: "Extract the main content" }
404
- ```
405
-
406
- If WebFetch also fails, return the URL with an explanation and continue the task.
181
+ | Playwright install fails | Use WebFetch |
182
+ | Known static domain (github raw, pastebin, wikipedia) | Use WebFetch directly, skip Playwright |
183
+ | Playwright times out twice | Use WebFetch as fallback |
184
+ | WebFetch also fails | Return URL with explanation, continue task |
407
185
 
408
186
  ---
409
187
 
410
188
  ## Rules
411
189
 
412
- - Always run the install check before the first browser launch in a session.
413
- - Detect runtime with `which node` first; fall back to `bun` if node is absent.
414
- - Never navigate to Google or DuckDuckGo with Playwright — use WebSearch tool or direct URLs.
415
- - Truncate output at ~4000 tokens (~16 000 chars) to protect context budget.
416
- - On login wall or CAPTCHA, log the block, skip, and continue — never retry infinitely.
417
- - Close the browser in every code path (use `finally`).
190
+ - Never navigate to Google/DuckDuckGo with Playwright use WebSearch or direct URLs.
191
+ - On login wall or CAPTCHA: log, skip, continue. Never retry infinitely.
192
+ - Close browser in every code path (`finally` block).
418
193
  - Do not persist browser sessions across unrelated tasks.
@@ -8,49 +8,18 @@ context: fork
8
8
 
9
9
  Headless browser verification using Playwright's accessibility tree. Evaluates structured assertions from PLAN.md without LLM calls — purely deterministic matching.
10
10
 
11
- ## When to Use
11
+ **Use when:** Spec has browser-based ACs (element presence, text content, roles, interactive states, structure).
12
+ **Skip when:** No browser-facing ACs or backend-only implementation.
12
13
 
13
- After implementing a spec that contains browser-based acceptance criteria:
14
- - Visual/layout checks (element presence, text content, roles)
15
- - Interactive state checks (aria-checked, aria-expanded, aria-disabled)
16
- - Structural checks (element within a container)
14
+ **Prerequisites:** Node.js or Bun + Playwright 1.x. Runtime detection and browser auto-install follow the same protocol as browse-fetch (`which node || which bun`; `npx playwright install chromium`).
17
15
 
18
- **Skip when:** The spec has no browser-facing ACs, or the implementation is backend-only.
19
-
20
- ## Prerequisites
21
-
22
- - Node.js (preferred) or Bun
23
- - Playwright 1.x (`npm install playwright` or `npx playwright install`)
24
- - Chromium browser (auto-installed if missing)
25
-
26
- ## Runtime Detection
27
-
28
- ```bash
29
- # Prefer Node.js; fall back to Bun
30
- if which node > /dev/null 2>&1; then
31
- RUNTIME=node
32
- elif which bun > /dev/null 2>&1; then
33
- RUNTIME=bun
34
- else
35
- echo "Error: neither node nor bun found" && exit 1
36
- fi
37
- ```
38
-
39
- ## Browser Auto-Install
40
-
41
- Before running, ensure Chromium is available:
42
-
43
- ```bash
44
- npx playwright install chromium
45
- ```
46
-
47
- Run this once per environment. If it fails due to permissions, instruct the user to run it manually.
16
+ ---
48
17
 
49
18
  ## Protocol
50
19
 
51
20
  ### 1. Read Assertions from PLAN.md
52
21
 
53
- Assertions are written into PLAN.md by the `plan` skill during planning. Format:
22
+ Assertions are written by the `plan` skill. Format:
54
23
 
55
24
  ```yaml
56
25
  assertions:
@@ -65,10 +34,6 @@ assertions:
65
34
  name: "Dashboard"
66
35
  check: visible
67
36
  within: main
68
- - role: textbox
69
- name: "Email"
70
- check: value
71
- value: "user@example.com"
72
37
  ```
73
38
 
74
39
  Assertion schema:
@@ -78,36 +43,29 @@ Assertion schema:
78
43
  | `role` | yes | ARIA role (button, checkbox, heading, textbox, link, etc.) |
79
44
  | `name` | yes | Accessible name (exact or partial match) |
80
45
  | `check` | yes | One of: `visible`, `absent`, `state`, `value`, `count` |
81
- | `value` | no | Expected value for `state` or `value` checks |
46
+ | `value` | no | Expected value for `state`, `value`, or `count` checks |
82
47
  | `within` | no | Ancestor role or selector to scope the search |
83
48
 
84
49
  ### 2. Launch Browser and Navigate
85
50
 
86
51
  ```javascript
87
- const { chromium } = require('playwright');
88
-
89
52
  const browser = await chromium.launch({ headless: true });
90
53
  const page = await browser.newPage();
91
-
92
54
  await page.goto(TARGET_URL, { waitUntil: 'networkidle' });
93
55
  ```
94
56
 
95
- `TARGET_URL` is read from the spec's metadata or passed as an argument.
57
+ `TARGET_URL` from spec metadata or passed as argument.
96
58
 
97
59
  ### 3. Extract Accessibility Tree
98
60
 
99
61
  Use `locator.ariaSnapshot()` — **NOT** `page.accessibility.snapshot()` (removed in Playwright 1.x):
100
62
 
101
63
  ```javascript
102
- // Full-page aria snapshot (YAML-like role tree)
103
64
  const snapshot = await page.locator('body').ariaSnapshot();
104
-
105
- // Scoped snapshot within a container
106
- const containerSnapshot = await page.locator('main').ariaSnapshot();
65
+ // Scoped: await page.locator('main').ariaSnapshot();
107
66
  ```
108
67
 
109
- `ariaSnapshot()` returns a YAML-like string such as:
110
-
68
+ Returns YAML-like role tree:
111
69
  ```yaml
112
70
  - heading "Dashboard" [level=1]
113
71
  - button "Submit" [disabled]
@@ -115,151 +73,59 @@ const containerSnapshot = await page.locator('main').ariaSnapshot();
115
73
  - textbox "Email": user@example.com
116
74
  ```
117
75
 
118
- ### 4. Capture Bounding Boxes (optional)
76
+ ### 4. Bounding Boxes (optional)
119
77
 
120
- For spatial/layout assertions or debugging:
78
+ For spatial/layout assertions: `await page.getByRole(role, { name }).boundingBox()` returns `{ x, y, width, height }` or null.
121
79
 
122
- ```javascript
123
- const element = page.getByRole(role, { name: assertionName });
124
- const box = await element.boundingBox();
125
- // box: { x, y, width, height } or null if not visible
126
- ```
127
-
128
- ### 5. Evaluate Assertions Deterministically
80
+ ### 5. Evaluate Assertions
129
81
 
130
- Parse the aria snapshot and evaluate each assertion. No LLM calls during this phase.
131
-
132
- ```javascript
133
- function evaluateAssertion(snapshot, assertion) {
134
- const { role, name, check, value, within } = assertion;
82
+ Parse the aria snapshot and evaluate each assertion deterministically (no LLM calls).
135
83
 
136
- // Optionally scope to a sub-tree
137
- const tree = within
138
- ? extractSubtree(snapshot, within)
139
- : snapshot;
140
-
141
- switch (check) {
142
- case 'visible':
143
- return treeContains(tree, role, name);
144
-
145
- case 'absent':
146
- return !treeContains(tree, role, name);
147
-
148
- case 'state':
149
- // e.g., value: "checked", "disabled", "expanded"
150
- return treeContainsWithState(tree, role, name, value);
151
-
152
- case 'value':
153
- // Matches textbox/combobox displayed value
154
- return treeContainsWithValue(tree, role, name, value);
155
-
156
- case 'count':
157
- return countMatches(tree, role, name) === parseInt(value, 10);
158
- }
159
- }
160
- ```
84
+ | Check | Logic |
85
+ |-------|-------|
86
+ | `visible` | Role+name found in tree |
87
+ | `absent` | Role+name NOT found in tree |
88
+ | `state` | Role+name found with state token (e.g., `[checked]`, `[disabled]`, `[expanded]`) |
89
+ | `value` | Role+name found with matching displayed value (textbox/combobox) |
90
+ | `count` | Number of role+name matches equals `parseInt(value)` |
161
91
 
162
92
  Matching rules:
163
- - Role matching is case-insensitive
164
- - Name matching is case-insensitive substring match (unless wrapped in quotes for exact match)
165
- - State tokens (`[checked]`, `[disabled]`, `[expanded]`) are parsed from the snapshot line
166
-
167
- ### 6. Capture Screenshot
93
+ - Role matching: case-insensitive
94
+ - Name matching: case-insensitive substring (exact match only when assertion wraps name in quotes)
95
+ - If `within` specified, scope to that ancestor's subtree first
168
96
 
169
- After evaluation, capture a screenshot for the audit trail:
97
+ ### 6. Screenshot
170
98
 
171
- ```javascript
172
- const screenshotDir = `.deepflow/screenshots/${specName}`;
173
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
174
- const screenshotPath = `${screenshotDir}/${timestamp}.png`;
175
-
176
- await fs.mkdir(screenshotDir, { recursive: true });
177
- await page.screenshot({ path: screenshotPath, fullPage: true });
99
+ Capture after every run regardless of pass/fail:
178
100
  ```
179
-
180
- Screenshot path convention: `.deepflow/screenshots/{spec-name}/{timestamp}.png`
101
+ .deepflow/screenshots/{spec-name}/{ISO-timestamp}.png
102
+ ```
103
+ Use `page.screenshot({ path, fullPage: true })`.
181
104
 
182
105
  ### 7. Report Results
183
106
 
184
- Emit a structured result for each assertion:
185
-
186
107
  ```
187
- [PASS] button "Submit" — visible
188
- [PASS] checkbox "Accept terms" — state: checked ✓
108
+ [PASS] button "Submit" — visible
189
109
  [FAIL] heading "Dashboard" — expected visible, not found in snapshot
190
- [PASS] textbox "Email" — value: user@example.com ✓
191
110
 
192
- Results: 3 passed, 1 failed
111
+ Results: 1 passed, 1 failed
193
112
  Screenshot: .deepflow/screenshots/login-form/2026-03-14T12-00-00-000Z.png
194
113
  ```
195
114
 
196
- Exit with code 0 if all assertions pass, 1 if any fail.
115
+ Exit code 0 if all pass, 1 if any fail.
197
116
 
198
117
  ### 8. Tear Down
199
118
 
200
- ```javascript
201
- await browser.close();
202
- ```
203
-
204
- Always close the browser, even on error (use try/finally).
119
+ Always `browser.close()` in a `finally` block.
205
120
 
206
- ## Full Script Template
207
-
208
- ```javascript
209
- #!/usr/bin/env node
210
- const { chromium } = require('playwright');
211
- const fs = require('fs/promises');
212
- const path = require('path');
213
-
214
- async function main({ targetUrl, specName, assertions }) {
215
- // Auto-install chromium if needed
216
- // (handled by: npx playwright install chromium)
217
-
218
- const browser = await chromium.launch({ headless: true });
219
- const page = await browser.newPage();
220
-
221
- try {
222
- await page.goto(targetUrl, { waitUntil: 'networkidle' });
223
-
224
- const snapshot = await page.locator('body').ariaSnapshot();
225
-
226
- const results = assertions.map(assertion => ({
227
- assertion,
228
- passed: evaluateAssertion(snapshot, assertion),
229
- }));
230
-
231
- // Screenshot
232
- const screenshotDir = path.join('.deepflow', 'screenshots', specName);
233
- await fs.mkdir(screenshotDir, { recursive: true });
234
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
235
- const screenshotPath = path.join(screenshotDir, `${timestamp}.png`);
236
- await page.screenshot({ path: screenshotPath, fullPage: true });
237
-
238
- // Report
239
- const passed = results.filter(r => r.passed).length;
240
- const failed = results.filter(r => !r.passed).length;
241
-
242
- for (const { assertion, passed: ok } of results) {
243
- const status = ok ? '[PASS]' : '[FAIL]';
244
- console.log(`${status} ${assertion.role} "${assertion.name}" — ${assertion.check}${assertion.value ? ': ' + assertion.value : ''}`);
245
- }
246
-
247
- console.log(`\nResults: ${passed} passed, ${failed} failed`);
248
- console.log(`Screenshot: ${screenshotPath}`);
249
-
250
- process.exit(failed > 0 ? 1 : 0);
251
- } finally {
252
- await browser.close();
253
- }
254
- }
255
- ```
121
+ ---
256
122
 
257
123
  ## Rules
258
124
 
259
- - Never call an LLM during the verify phase — all assertion evaluation is deterministic
260
- - Always use `locator.ariaSnapshot()`, never `page.accessibility.snapshot()` (removed)
261
- - Always close the browser in a `finally` block
262
- - Screenshot every run regardless of pass/fail outcome
263
- - If Playwright is not installed, emit a clear error and instructions — don't silently skip
264
- - Partial name matching is the default; use exact matching only when the assertion specifies it
265
- - Report results to stdout in the structured format above for downstream parsing
125
+ - Never call an LLM during the verify phase — all evaluation is deterministic.
126
+ - Always use `locator.ariaSnapshot()`, never `page.accessibility.snapshot()` (removed).
127
+ - Always close browser in `finally`.
128
+ - Screenshot every run regardless of outcome.
129
+ - If Playwright not installed, emit clear error with instructions — don't silently skip.
130
+ - Partial name matching is default; exact only when assertion specifies it.
131
+ - Report results in structured format above for downstream parsing.
@@ -4,9 +4,7 @@ description: Finds incomplete code in codebase. Use when analyzing for TODOs, st
4
4
  allowed-tools: [Read, Grep, Glob]
5
5
  ---
6
6
 
7
- # Code Completeness
8
-
9
- Find incomplete work in codebase.
7
+ # Code Completeness — Find Incomplete Work
10
8
 
11
9
  ## Explicit Markers
12
10
 
@@ -30,8 +28,7 @@ Find incomplete work in codebase.
30
28
 
31
29
  | Pattern | Language |
32
30
  |---------|----------|
33
- | `it.skip`, `test.skip` | JavaScript |
34
- | `describe.skip` | JavaScript |
31
+ | `it.skip`, `test.skip`, `describe.skip` | JavaScript |
35
32
  | `@pytest.mark.skip` | Python |
36
33
  | `t.Skip()` | Go |
37
34
 
@@ -51,10 +48,6 @@ REQ-1: {requirement}
51
48
  Status: PARTIAL
52
49
  Found: src/api/upload.ts:45 — `// TODO: validation`
53
50
  Action: Complete validation
54
-
55
- REQ-2: {requirement}
56
- Status: MISSING
57
- Action: Create src/services/storage.ts
58
51
  ```
59
52
 
60
53
  ## Rules