deepflow 0.1.78 → 0.1.80
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/README.md +14 -3
- package/bin/install.js +3 -2
- package/package.json +4 -1
- package/src/commands/df/auto-cycle.md +33 -19
- package/src/commands/df/execute.md +166 -473
- package/src/commands/df/plan.md +113 -163
- package/src/commands/df/verify.md +433 -3
- package/src/skills/browse-fetch/SKILL.md +258 -0
- package/src/skills/browse-verify/SKILL.md +264 -0
- package/templates/config-template.yaml +14 -0
- package/src/skills/context-hub/SKILL.md +0 -87
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browse-fetch
|
|
3
|
+
description: Fetches live web content using headless Chromium via Playwright. Use when you need to read documentation, articles, or any public URL that requires JavaScript rendering. Falls back to WebFetch for simple HTML pages.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Browse-Fetch
|
|
7
|
+
|
|
8
|
+
Retrieve live web content with a headless browser. Handles JavaScript-rendered pages, SPAs, and dynamic content that WebFetch cannot reach.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Reading documentation sites that require JavaScript to render (e.g., React-based docs, Vite, Next.js portals)
|
|
13
|
+
- Fetching the current content of a specific URL provided by the user
|
|
14
|
+
- Extracting article or reference content from a known page before implementing code against it
|
|
15
|
+
|
|
16
|
+
## Skip When
|
|
17
|
+
|
|
18
|
+
- The URL is a plain HTML page or GitHub raw file — use WebFetch instead (faster, no overhead)
|
|
19
|
+
- The target requires authentication (login wall) or CAPTCHA — browser cannot bypass; note the block and continue
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Browser Core Protocol
|
|
24
|
+
|
|
25
|
+
This protocol is the reusable foundation for all browser-based skills (browse-fetch, browse-verify, etc.).
|
|
26
|
+
|
|
27
|
+
### 1. Install Check
|
|
28
|
+
|
|
29
|
+
Before launching, verify Playwright is available:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Prefer bun if available, fall back to node
|
|
33
|
+
if which bun > /dev/null 2>&1; then RUNTIME=bun; else RUNTIME=node; fi
|
|
34
|
+
|
|
35
|
+
$RUNTIME -e "require('playwright')" 2>/dev/null \
|
|
36
|
+
|| npx --yes playwright install chromium --with-deps 2>&1 | tail -5
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If installation fails, fall back to WebFetch (see Fallback section below).
|
|
40
|
+
|
|
41
|
+
### 2. Launch Command
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Detect runtime
|
|
45
|
+
if which bun > /dev/null 2>&1; then RUNTIME=bun; else RUNTIME=node; fi
|
|
46
|
+
|
|
47
|
+
$RUNTIME -e "
|
|
48
|
+
const { chromium } = require('playwright');
|
|
49
|
+
(async () => {
|
|
50
|
+
const browser = await chromium.launch({ headless: true });
|
|
51
|
+
const context = await browser.newContext({
|
|
52
|
+
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'
|
|
53
|
+
});
|
|
54
|
+
const page = await context.newPage();
|
|
55
|
+
|
|
56
|
+
// --- navigation + extraction (see sections 3–4) ---
|
|
57
|
+
|
|
58
|
+
await browser.close();
|
|
59
|
+
})().catch(e => { console.error(e.message); process.exit(1); });
|
|
60
|
+
"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Navigation
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// Inside the async IIFE above
|
|
67
|
+
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
68
|
+
// Allow JS to settle
|
|
69
|
+
await page.waitForTimeout(1500);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- Use `waitUntil: 'domcontentloaded'` for speed; upgrade to `'networkidle'` only if content is missing.
|
|
73
|
+
- Set `timeout: 30000` (30 s). On timeout, treat as graceful failure (see section 5).
|
|
74
|
+
|
|
75
|
+
### 4. Content Extraction
|
|
76
|
+
|
|
77
|
+
Extract the main readable text, not raw HTML:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
// Primary: semantic content containers
|
|
81
|
+
let text = await page.innerText('main, article, [role="main"]').catch(() => '');
|
|
82
|
+
|
|
83
|
+
// Fallback: full body text
|
|
84
|
+
if (!text || text.trim().length < 100) {
|
|
85
|
+
text = await page.innerText('body').catch(() => '');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Truncate to ~4000 tokens (~16000 chars) to stay within context budget
|
|
89
|
+
const MAX_CHARS = 16000;
|
|
90
|
+
if (text.length > MAX_CHARS) {
|
|
91
|
+
text = text.slice(0, MAX_CHARS) + '\n\n[content truncated — use a more specific selector or paginate]';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(text);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For interactive element inspection (e.g., browse-verify), use `locator.ariaSnapshot()` instead of `innerText`.
|
|
98
|
+
|
|
99
|
+
### 5. Graceful Failure
|
|
100
|
+
|
|
101
|
+
Detect and handle blocks without crashing:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
const title = await page.title();
|
|
105
|
+
const url = page.url();
|
|
106
|
+
|
|
107
|
+
// Login wall
|
|
108
|
+
if (/sign.?in|log.?in|auth/i.test(title) || url.includes('/login')) {
|
|
109
|
+
console.log(`[browse-fetch] Blocked by login wall at ${url}. Skipping.`);
|
|
110
|
+
await browser.close();
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// CAPTCHA
|
|
115
|
+
const bodyText = await page.innerText('body').catch(() => '');
|
|
116
|
+
if (/captcha|robot|human verification/i.test(bodyText)) {
|
|
117
|
+
console.log(`[browse-fetch] CAPTCHA detected at ${url}. Skipping.`);
|
|
118
|
+
await browser.close();
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
On graceful failure: return the URL and a short explanation, then continue with the task using available context.
|
|
124
|
+
|
|
125
|
+
### 6. Cleanup
|
|
126
|
+
|
|
127
|
+
Always close the browser in a `finally` block or after use:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
await browser.close();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Fetch Workflow
|
|
136
|
+
|
|
137
|
+
**Goal:** retrieve and return the text content of a single URL.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Full inline script — adapt URL and selector per query
|
|
141
|
+
if which bun > /dev/null 2>&1; then RUNTIME=bun; else RUNTIME=node; fi
|
|
142
|
+
|
|
143
|
+
$RUNTIME -e "
|
|
144
|
+
const { chromium } = require('playwright');
|
|
145
|
+
(async () => {
|
|
146
|
+
const browser = await chromium.launch({ headless: true });
|
|
147
|
+
const context = await browser.newContext({
|
|
148
|
+
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'
|
|
149
|
+
});
|
|
150
|
+
const page = await context.newPage();
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await page.goto('https://example.com/docs/page', {
|
|
154
|
+
waitUntil: 'domcontentloaded',
|
|
155
|
+
timeout: 30000
|
|
156
|
+
});
|
|
157
|
+
await page.waitForTimeout(1500);
|
|
158
|
+
|
|
159
|
+
const title = await page.title();
|
|
160
|
+
const url = page.url();
|
|
161
|
+
|
|
162
|
+
if (/sign.?in|log.?in|auth/i.test(title) || url.includes('/login')) {
|
|
163
|
+
console.log('[browse-fetch] Blocked by login wall at ' + url);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let text = await page.innerText('main, article, [role=\"main\"]').catch(() => '');
|
|
168
|
+
if (!text || text.trim().length < 100) {
|
|
169
|
+
text = await page.innerText('body').catch(() => '');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const MAX_CHARS = 16000;
|
|
173
|
+
if (text.length > MAX_CHARS) {
|
|
174
|
+
text = text.slice(0, MAX_CHARS) + '\n\n[content truncated]';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('=== ' + title + ' ===\n' + text);
|
|
178
|
+
} finally {
|
|
179
|
+
await browser.close();
|
|
180
|
+
}
|
|
181
|
+
})().catch(e => { console.error(e.message); process.exit(1); });
|
|
182
|
+
"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Adapt the URL and selector per query. The agent inlines the full script via `node -e` or `bun -e` so no temp files are needed for extractions under ~4000 tokens.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Search + Navigation Protocol
|
|
190
|
+
|
|
191
|
+
**Time-box:** 60 seconds total. **Page cap:** 5 pages per query.
|
|
192
|
+
|
|
193
|
+
> Search engines (Google, DuckDuckGo) block headless browsers with CAPTCHAs. Do NOT use Playwright to search them.
|
|
194
|
+
|
|
195
|
+
Instead, use one of these strategies:
|
|
196
|
+
|
|
197
|
+
| Strategy | When to use |
|
|
198
|
+
|----------|-------------|
|
|
199
|
+
| Direct URL construction | You know the domain (e.g., `docs.stripe.com/api/charges`) |
|
|
200
|
+
| WebSearch tool | General keyword search before fetching pages |
|
|
201
|
+
| Site-specific search | Navigate to `site.com/search?q=term` if the site exposes it |
|
|
202
|
+
|
|
203
|
+
**Navigation loop** (up to 5 pages):
|
|
204
|
+
|
|
205
|
+
1. Construct or obtain the target URL.
|
|
206
|
+
2. Run the fetch workflow above.
|
|
207
|
+
3. If the page lacks the needed information, look for a next-page link or a more specific sub-URL.
|
|
208
|
+
4. Repeat up to 4 more times (5 total).
|
|
209
|
+
5. Stop and summarize what was found within the 60 s window.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Session Cache
|
|
214
|
+
|
|
215
|
+
The context window is the cache. Extracted content lives in the conversation until it is no longer needed.
|
|
216
|
+
|
|
217
|
+
For extractions larger than ~4000 tokens, write to a temp file and reference it:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# Write large extraction to temp file
|
|
221
|
+
TMPFILE=$(mktemp /tmp/browse-fetch-XXXXXX.txt)
|
|
222
|
+
$RUNTIME -e "...script..." > "$TMPFILE"
|
|
223
|
+
echo "Content saved to $TMPFILE"
|
|
224
|
+
# Read relevant sections with grep or head rather than loading all at once
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Fallback Without Playwright
|
|
230
|
+
|
|
231
|
+
When Playwright is unavailable or fails to install, fall back to the WebFetch tool for:
|
|
232
|
+
|
|
233
|
+
- Static HTML sites (GitHub README, raw docs, Wikipedia)
|
|
234
|
+
- Any URL the user provides where JavaScript rendering is not required
|
|
235
|
+
|
|
236
|
+
| Condition | Action |
|
|
237
|
+
|-----------|--------|
|
|
238
|
+
| `playwright` not installed, install fails | Use WebFetch |
|
|
239
|
+
| Page is a known static domain (github.com/raw, pastebin, etc.) | Use WebFetch directly — skip Playwright |
|
|
240
|
+
| Playwright times out twice | Use WebFetch as fallback attempt |
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
WebFetch: { url: "https://example.com/page", prompt: "Extract the main content" }
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
If WebFetch also fails, return the URL with an explanation and continue the task.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Rules
|
|
251
|
+
|
|
252
|
+
- Always run the install check before the first browser launch in a session.
|
|
253
|
+
- Detect runtime with `which bun` first; use `node` if bun is absent.
|
|
254
|
+
- Never navigate to Google or DuckDuckGo with Playwright — use WebSearch tool or direct URLs.
|
|
255
|
+
- Truncate output at ~4000 tokens (~16 000 chars) to protect context budget.
|
|
256
|
+
- On login wall or CAPTCHA, log the block, skip, and continue — never retry infinitely.
|
|
257
|
+
- Close the browser in every code path (use `finally`).
|
|
258
|
+
- Do not persist browser sessions across unrelated tasks.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browse-verify
|
|
3
|
+
description: Verifies UI acceptance criteria by launching a headless browser, extracting the accessibility tree, and evaluating structured assertions deterministically. Use when a spec has browser-based ACs that need automated verification after implementation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Browse-Verify
|
|
7
|
+
|
|
8
|
+
Headless browser verification using Playwright's accessibility tree. Evaluates structured assertions from PLAN.md without LLM calls — purely deterministic matching.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
After implementing a spec that contains browser-based acceptance criteria:
|
|
13
|
+
- Visual/layout checks (element presence, text content, roles)
|
|
14
|
+
- Interactive state checks (aria-checked, aria-expanded, aria-disabled)
|
|
15
|
+
- Structural checks (element within a container)
|
|
16
|
+
|
|
17
|
+
**Skip when:** The spec has no browser-facing ACs, or the implementation is backend-only.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Node.js (preferred) or Bun
|
|
22
|
+
- Playwright 1.x (`npm install playwright` or `npx playwright install`)
|
|
23
|
+
- Chromium browser (auto-installed if missing)
|
|
24
|
+
|
|
25
|
+
## Runtime Detection
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Prefer Node.js; fall back to Bun
|
|
29
|
+
if which node > /dev/null 2>&1; then
|
|
30
|
+
RUNTIME=node
|
|
31
|
+
elif which bun > /dev/null 2>&1; then
|
|
32
|
+
RUNTIME=bun
|
|
33
|
+
else
|
|
34
|
+
echo "Error: neither node nor bun found" && exit 1
|
|
35
|
+
fi
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Browser Auto-Install
|
|
39
|
+
|
|
40
|
+
Before running, ensure Chromium is available:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx playwright install chromium
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Run this once per environment. If it fails due to permissions, instruct the user to run it manually.
|
|
47
|
+
|
|
48
|
+
## Protocol
|
|
49
|
+
|
|
50
|
+
### 1. Read Assertions from PLAN.md
|
|
51
|
+
|
|
52
|
+
Assertions are written into PLAN.md by the `plan` skill during planning. Format:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
assertions:
|
|
56
|
+
- role: button
|
|
57
|
+
name: "Submit"
|
|
58
|
+
check: visible
|
|
59
|
+
- role: checkbox
|
|
60
|
+
name: "Accept terms"
|
|
61
|
+
check: state
|
|
62
|
+
value: checked
|
|
63
|
+
- role: heading
|
|
64
|
+
name: "Dashboard"
|
|
65
|
+
check: visible
|
|
66
|
+
within: main
|
|
67
|
+
- role: textbox
|
|
68
|
+
name: "Email"
|
|
69
|
+
check: value
|
|
70
|
+
value: "user@example.com"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Assertion schema:
|
|
74
|
+
|
|
75
|
+
| Field | Required | Description |
|
|
76
|
+
|----------|----------|-------------|
|
|
77
|
+
| `role` | yes | ARIA role (button, checkbox, heading, textbox, link, etc.) |
|
|
78
|
+
| `name` | yes | Accessible name (exact or partial match) |
|
|
79
|
+
| `check` | yes | One of: `visible`, `absent`, `state`, `value`, `count` |
|
|
80
|
+
| `value` | no | Expected value for `state` or `value` checks |
|
|
81
|
+
| `within` | no | Ancestor role or selector to scope the search |
|
|
82
|
+
|
|
83
|
+
### 2. Launch Browser and Navigate
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
const { chromium } = require('playwright');
|
|
87
|
+
|
|
88
|
+
const browser = await chromium.launch({ headless: true });
|
|
89
|
+
const page = await browser.newPage();
|
|
90
|
+
|
|
91
|
+
await page.goto(TARGET_URL, { waitUntil: 'networkidle' });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`TARGET_URL` is read from the spec's metadata or passed as an argument.
|
|
95
|
+
|
|
96
|
+
### 3. Extract Accessibility Tree
|
|
97
|
+
|
|
98
|
+
Use `locator.ariaSnapshot()` — **NOT** `page.accessibility.snapshot()` (removed in Playwright 1.x):
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Full-page aria snapshot (YAML-like role tree)
|
|
102
|
+
const snapshot = await page.locator('body').ariaSnapshot();
|
|
103
|
+
|
|
104
|
+
// Scoped snapshot within a container
|
|
105
|
+
const containerSnapshot = await page.locator('main').ariaSnapshot();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`ariaSnapshot()` returns a YAML-like string such as:
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
- heading "Dashboard" [level=1]
|
|
112
|
+
- button "Submit" [disabled]
|
|
113
|
+
- checkbox "Accept terms" [checked]
|
|
114
|
+
- textbox "Email": user@example.com
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 4. Capture Bounding Boxes (optional)
|
|
118
|
+
|
|
119
|
+
For spatial/layout assertions or debugging:
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
const element = page.getByRole(role, { name: assertionName });
|
|
123
|
+
const box = await element.boundingBox();
|
|
124
|
+
// box: { x, y, width, height } or null if not visible
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 5. Evaluate Assertions Deterministically
|
|
128
|
+
|
|
129
|
+
Parse the aria snapshot and evaluate each assertion. No LLM calls during this phase.
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
function evaluateAssertion(snapshot, assertion) {
|
|
133
|
+
const { role, name, check, value, within } = assertion;
|
|
134
|
+
|
|
135
|
+
// Optionally scope to a sub-tree
|
|
136
|
+
const tree = within
|
|
137
|
+
? extractSubtree(snapshot, within)
|
|
138
|
+
: snapshot;
|
|
139
|
+
|
|
140
|
+
switch (check) {
|
|
141
|
+
case 'visible':
|
|
142
|
+
return treeContains(tree, role, name);
|
|
143
|
+
|
|
144
|
+
case 'absent':
|
|
145
|
+
return !treeContains(tree, role, name);
|
|
146
|
+
|
|
147
|
+
case 'state':
|
|
148
|
+
// e.g., value: "checked", "disabled", "expanded"
|
|
149
|
+
return treeContainsWithState(tree, role, name, value);
|
|
150
|
+
|
|
151
|
+
case 'value':
|
|
152
|
+
// Matches textbox/combobox displayed value
|
|
153
|
+
return treeContainsWithValue(tree, role, name, value);
|
|
154
|
+
|
|
155
|
+
case 'count':
|
|
156
|
+
return countMatches(tree, role, name) === parseInt(value, 10);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Matching rules:
|
|
162
|
+
- Role matching is case-insensitive
|
|
163
|
+
- Name matching is case-insensitive substring match (unless wrapped in quotes for exact match)
|
|
164
|
+
- State tokens (`[checked]`, `[disabled]`, `[expanded]`) are parsed from the snapshot line
|
|
165
|
+
|
|
166
|
+
### 6. Capture Screenshot
|
|
167
|
+
|
|
168
|
+
After evaluation, capture a screenshot for the audit trail:
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
const screenshotDir = `.deepflow/screenshots/${specName}`;
|
|
172
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
173
|
+
const screenshotPath = `${screenshotDir}/${timestamp}.png`;
|
|
174
|
+
|
|
175
|
+
await fs.mkdir(screenshotDir, { recursive: true });
|
|
176
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Screenshot path convention: `.deepflow/screenshots/{spec-name}/{timestamp}.png`
|
|
180
|
+
|
|
181
|
+
### 7. Report Results
|
|
182
|
+
|
|
183
|
+
Emit a structured result for each assertion:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
[PASS] button "Submit" — visible ✓
|
|
187
|
+
[PASS] checkbox "Accept terms" — state: checked ✓
|
|
188
|
+
[FAIL] heading "Dashboard" — expected visible, not found in snapshot
|
|
189
|
+
[PASS] textbox "Email" — value: user@example.com ✓
|
|
190
|
+
|
|
191
|
+
Results: 3 passed, 1 failed
|
|
192
|
+
Screenshot: .deepflow/screenshots/login-form/2026-03-14T12-00-00-000Z.png
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Exit with code 0 if all assertions pass, 1 if any fail.
|
|
196
|
+
|
|
197
|
+
### 8. Tear Down
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
await browser.close();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Always close the browser, even on error (use try/finally).
|
|
204
|
+
|
|
205
|
+
## Full Script Template
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
#!/usr/bin/env node
|
|
209
|
+
const { chromium } = require('playwright');
|
|
210
|
+
const fs = require('fs/promises');
|
|
211
|
+
const path = require('path');
|
|
212
|
+
|
|
213
|
+
async function main({ targetUrl, specName, assertions }) {
|
|
214
|
+
// Auto-install chromium if needed
|
|
215
|
+
// (handled by: npx playwright install chromium)
|
|
216
|
+
|
|
217
|
+
const browser = await chromium.launch({ headless: true });
|
|
218
|
+
const page = await browser.newPage();
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
await page.goto(targetUrl, { waitUntil: 'networkidle' });
|
|
222
|
+
|
|
223
|
+
const snapshot = await page.locator('body').ariaSnapshot();
|
|
224
|
+
|
|
225
|
+
const results = assertions.map(assertion => ({
|
|
226
|
+
assertion,
|
|
227
|
+
passed: evaluateAssertion(snapshot, assertion),
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
// Screenshot
|
|
231
|
+
const screenshotDir = path.join('.deepflow', 'screenshots', specName);
|
|
232
|
+
await fs.mkdir(screenshotDir, { recursive: true });
|
|
233
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
234
|
+
const screenshotPath = path.join(screenshotDir, `${timestamp}.png`);
|
|
235
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
236
|
+
|
|
237
|
+
// Report
|
|
238
|
+
const passed = results.filter(r => r.passed).length;
|
|
239
|
+
const failed = results.filter(r => !r.passed).length;
|
|
240
|
+
|
|
241
|
+
for (const { assertion, passed: ok } of results) {
|
|
242
|
+
const status = ok ? '[PASS]' : '[FAIL]';
|
|
243
|
+
console.log(`${status} ${assertion.role} "${assertion.name}" — ${assertion.check}${assertion.value ? ': ' + assertion.value : ''}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
247
|
+
console.log(`Screenshot: ${screenshotPath}`);
|
|
248
|
+
|
|
249
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
250
|
+
} finally {
|
|
251
|
+
await browser.close();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Rules
|
|
257
|
+
|
|
258
|
+
- Never call an LLM during the verify phase — all assertion evaluation is deterministic
|
|
259
|
+
- Always use `locator.ariaSnapshot()`, never `page.accessibility.snapshot()` (removed)
|
|
260
|
+
- Always close the browser in a `finally` block
|
|
261
|
+
- Screenshot every run regardless of pass/fail outcome
|
|
262
|
+
- If Playwright is not installed, emit a clear error and instructions — don't silently skip
|
|
263
|
+
- Partial name matching is the default; use exact matching only when the assertion specifies it
|
|
264
|
+
- Report results to stdout in the structured format above for downstream parsing
|
|
@@ -75,3 +75,17 @@ quality:
|
|
|
75
75
|
|
|
76
76
|
# Retry flaky tests once before failing (default: true)
|
|
77
77
|
test_retry_on_fail: true
|
|
78
|
+
|
|
79
|
+
# Enable L5 browser verification after tests pass (default: false)
|
|
80
|
+
# When true, deepflow will start the dev server and run visual checks
|
|
81
|
+
browser_verify: false
|
|
82
|
+
|
|
83
|
+
# Override the dev server start command for browser verification
|
|
84
|
+
# If empty, deepflow will attempt to auto-detect (e.g., "npm run dev", "yarn dev")
|
|
85
|
+
dev_command: ""
|
|
86
|
+
|
|
87
|
+
# Port that the dev server listens on (default: 3000)
|
|
88
|
+
dev_port: 3000
|
|
89
|
+
|
|
90
|
+
# Timeout in seconds to wait for the dev server to become ready (default: 30)
|
|
91
|
+
browser_timeout: 30
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: context-hub
|
|
3
|
-
description: Fetches curated API docs for external libraries before coding. Use when implementing code that uses external APIs/SDKs (Stripe, OpenAI, MongoDB, etc.) to avoid hallucinating APIs and reduce token usage.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Context Hub
|
|
7
|
-
|
|
8
|
-
Fetch curated, versioned docs for external libraries instead of guessing APIs.
|
|
9
|
-
|
|
10
|
-
## When to Use
|
|
11
|
-
|
|
12
|
-
Before writing code that calls an external API or SDK:
|
|
13
|
-
- New library integration (e.g., Stripe payments, AWS S3)
|
|
14
|
-
- Unfamiliar API version or method
|
|
15
|
-
- Complex API with many options (e.g., MongoDB aggregation)
|
|
16
|
-
|
|
17
|
-
**Skip when:** Working with internal code (use LSP instead) or well-known stdlib APIs.
|
|
18
|
-
|
|
19
|
-
## Prerequisites
|
|
20
|
-
|
|
21
|
-
Requires `chub` CLI: `npm install -g @aisuite/chub`
|
|
22
|
-
|
|
23
|
-
If `chub` is not installed, tell the user and skip — don't block implementation.
|
|
24
|
-
|
|
25
|
-
## Workflow
|
|
26
|
-
|
|
27
|
-
### 1. Search for docs
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
chub search "<library or API>" --json
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Example:
|
|
34
|
-
```bash
|
|
35
|
-
chub search "stripe payments" --json
|
|
36
|
-
chub search "mongodb aggregation" --json
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 2. Fetch relevant docs
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
chub get <id> --lang <py|js|ts>
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Use `--lang` matching the project language. Use `--full` only if the summary lacks what you need.
|
|
46
|
-
|
|
47
|
-
### 3. Write code using fetched docs
|
|
48
|
-
|
|
49
|
-
Use the retrieved documentation as ground truth for API signatures, parameter names, and patterns.
|
|
50
|
-
|
|
51
|
-
### 4. Annotate discoveries
|
|
52
|
-
|
|
53
|
-
When you find something the docs missed or got wrong:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
chub annotate <id> "Note: method X requires param Y since v2.0"
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
This persists locally and appears on future `chub get` calls — the agent learns across sessions.
|
|
60
|
-
|
|
61
|
-
### 5. Rate docs (optional)
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
chub feedback <id> up --label accurate
|
|
65
|
-
chub feedback <id> down --label outdated
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Labels: `accurate`, `outdated`, `incomplete`, `wrong-version`, `helpful`
|
|
69
|
-
|
|
70
|
-
## Integration with LSP
|
|
71
|
-
|
|
72
|
-
| Need | Tool |
|
|
73
|
-
|------|------|
|
|
74
|
-
| Internal code navigation | LSP (`goToDefinition`, `findReferences`) |
|
|
75
|
-
| External API signatures | Context Hub (`chub get`) |
|
|
76
|
-
| Symbol search in project | LSP (`workspaceSymbol`) |
|
|
77
|
-
| Library usage patterns | Context Hub (`chub search`) |
|
|
78
|
-
|
|
79
|
-
**Combined approach:** Use LSP to understand how the project currently uses a library, then use Context Hub to verify correct API usage and discover better patterns.
|
|
80
|
-
|
|
81
|
-
## Rules
|
|
82
|
-
|
|
83
|
-
- Always search before implementing external API calls
|
|
84
|
-
- Trust chub docs over training data for API specifics
|
|
85
|
-
- Annotate gaps so future sessions benefit
|
|
86
|
-
- Don't block on chub failures — fall back to best knowledge
|
|
87
|
-
- Prefer `--json` flag for programmatic parsing in automated workflows
|