@invisibleloop/pulse 0.2.10 → 0.2.12
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/commands/verify.md +24 -4
- package/docs/src/pages/getting-started.js +18 -1
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/src/agent/commands/verify.md +72 -0
- package/src/agent/coverage-check.js +63 -2
- package/src/cli/dev.js +12 -6
- package/src/cli/index.js +13 -7
- package/src/cli/scaffold.js +5 -0
|
@@ -32,21 +32,41 @@ Run `mcp__chrome-devtools__lighthouse_audit` on the same route with `{ "strategy
|
|
|
32
32
|
|
|
33
33
|
**Same pass bar: all four categories must be 100.** Report the actual scores. If any score is below 100, fix and restart from step 2.
|
|
34
34
|
|
|
35
|
-
### 7.
|
|
35
|
+
### 7. Performance
|
|
36
|
+
|
|
37
|
+
Navigate to the page route, then run `mcp__chrome-devtools__performance_start_trace` with `reload: true` and `autoStop: true`. Report LCP and CLS. Flag any LCP insight that suggests a fixable problem (render-blocking resources, large image load delay, etc.).
|
|
38
|
+
|
|
39
|
+
### 8. Console errors
|
|
36
40
|
|
|
37
41
|
Use `mcp__chrome-devtools__list_console_messages` — report any errors or unexpected warnings.
|
|
38
42
|
|
|
39
|
-
###
|
|
43
|
+
### 9. Code review
|
|
44
|
+
|
|
45
|
+
Check the spec against the checklist in `.claude/pulse-checklist.md`. Work through every item. Fix anything that fails before proceeding to the next step.
|
|
46
|
+
|
|
47
|
+
### 10. Close the browser
|
|
40
48
|
|
|
41
49
|
Use `mcp__chrome-devtools__list_pages` to get all open pages, then call `mcp__chrome-devtools__close_page` for every page ID returned. This closes the entire browser, not just the tab. `pageId` must be a number, not a string.
|
|
42
50
|
|
|
43
|
-
###
|
|
51
|
+
### 11. Write verification stamp
|
|
52
|
+
|
|
53
|
+
Run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
date +%s > .pulse-verified
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This records that verify completed successfully. The stop hook checks this stamp — do not skip this step.
|
|
60
|
+
|
|
61
|
+
### 12. Report
|
|
44
62
|
|
|
45
63
|
Summarise:
|
|
46
64
|
- Validation: pass or fail (with errors if any)
|
|
47
65
|
- Visual: what was visible in the screenshot
|
|
48
66
|
- Lighthouse desktop: scores for all four categories
|
|
49
67
|
- Lighthouse mobile: scores for all four categories
|
|
68
|
+
- Performance: LCP and CLS values, any flagged insights
|
|
50
69
|
- Console: any errors
|
|
70
|
+
- Review: pass or issues found and fixed
|
|
51
71
|
|
|
52
|
-
Only confirm the page is good when validation passes, both Lighthouse runs are 100/100/100/100, and there are no console errors. Otherwise, fix and run `/verify` again.
|
|
72
|
+
Only confirm the page is good when validation passes, both Lighthouse runs are 100/100/100/100, CLS is 0.00, and there are no console errors. Otherwise, fix and run `/verify` again.
|
|
@@ -78,7 +78,12 @@ Validate the email format before submitting."`, 'bash'))}
|
|
|
78
78
|
├── .claude/
|
|
79
79
|
│ ├── CLAUDE.md ← session instructions Claude reads on startup
|
|
80
80
|
│ ├── settings.json ← hooks: syntax checks, colour guards, package blocklist
|
|
81
|
-
│
|
|
81
|
+
│ ├── pulse-checklist.md ← spec review checklist, kept in sync by Pulse
|
|
82
|
+
│ └── commands/ ← slash commands available inside Claude
|
|
83
|
+
│ ├── pulse-dev.md
|
|
84
|
+
│ ├── pulse-stop.md
|
|
85
|
+
│ ├── pulse-build.md
|
|
86
|
+
│ └── pulse-start.md
|
|
82
87
|
├── package.json
|
|
83
88
|
└── pulse.config.js ← port and project settings`, 'bash'))}
|
|
84
89
|
<p><code>src/pages/home.js</code> is a complete working spec — a counter with increment and decrement buttons. Open <a href="http://localhost:3000">localhost:3000</a> after running <code>/pulse-dev</code> to see it. Every new page you create goes into <code>src/pages/</code> and is discovered automatically.</p>
|
|
@@ -93,6 +98,18 @@ Validate the email format before submitting."`, 'bash'))}
|
|
|
93
98
|
/pulse-report # Lighthouse audit + performance report`, 'bash'))}
|
|
94
99
|
<p>You can also skip the commands entirely — just describe what you want and the agent handles the rest, including starting the dev server when needed.</p>
|
|
95
100
|
|
|
101
|
+
${section('update', 'Keeping up to date')}
|
|
102
|
+
<p>When a new version of Pulse is released, update the package and then run <code>pulse update</code> from your project root:</p>
|
|
103
|
+
${codeBlock(highlight(`npm update @invisibleloop/pulse
|
|
104
|
+
pulse update`, 'bash'))}
|
|
105
|
+
<p><code>pulse update</code> copies the latest UI assets and agent files into your project:</p>
|
|
106
|
+
<ul>
|
|
107
|
+
<li><code>public/pulse-ui.css</code> and <code>public/pulse-ui.js</code> — component library styles and behaviour</li>
|
|
108
|
+
<li><code>.claude/pulse-checklist.md</code> — spec review checklist</li>
|
|
109
|
+
<li><code>.claude/commands/</code> — slash commands</li>
|
|
110
|
+
</ul>
|
|
111
|
+
<p>Your own source files, <code>CLAUDE.md</code>, and <code>pulse.config.js</code> are never touched.</p>
|
|
112
|
+
|
|
96
113
|
${section('next-steps', 'Next steps')}
|
|
97
114
|
<ul>
|
|
98
115
|
<li><a href="/how-it-works">How It Works</a> — the MCP server, what the agent knows, and the full build cycle</li>
|
package/package.json
CHANGED
package/public/.pulse-ui-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.12
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Verify
|
|
2
|
+
|
|
3
|
+
Run the full verification loop on the current page or the page specified in $ARGUMENTS.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
### 1. Identify the target
|
|
8
|
+
|
|
9
|
+
If $ARGUMENTS is provided, use it as the file path or route. Otherwise, identify the most recently edited spec file from context.
|
|
10
|
+
|
|
11
|
+
### 2. Validate the spec
|
|
12
|
+
|
|
13
|
+
Use `pulse_validate` with the spec file content. If validation fails, report every error clearly and stop — do not proceed to screenshot until the spec is valid.
|
|
14
|
+
|
|
15
|
+
### 3. Check the dev server
|
|
16
|
+
|
|
17
|
+
Use `pulse_fetch_page` to confirm the server is responding for the route. If it errors, use `pulse_restart_server` and retry once.
|
|
18
|
+
|
|
19
|
+
### 4. Screenshot
|
|
20
|
+
|
|
21
|
+
Use `mcp__chrome-devtools__navigate_page` to load the page route, then `mcp__chrome-devtools__take_screenshot` to capture the result. Describe what you see — layout, content, any obvious rendering issues.
|
|
22
|
+
|
|
23
|
+
### 5. Lighthouse — desktop
|
|
24
|
+
|
|
25
|
+
Run `mcp__chrome-devtools__lighthouse_audit` on the route with `{ "strategy": "desktop" }`.
|
|
26
|
+
|
|
27
|
+
**Pass bar: Performance, Accessibility, Best Practices, and SEO must all be 100.** Report the actual scores. If any score is below 100, identify the failing audit(s), fix the issue, and restart from step 2.
|
|
28
|
+
|
|
29
|
+
### 6. Lighthouse — mobile
|
|
30
|
+
|
|
31
|
+
Run `mcp__chrome-devtools__lighthouse_audit` on the same route with `{ "strategy": "mobile" }`.
|
|
32
|
+
|
|
33
|
+
**Same pass bar: all four categories must be 100.** Report the actual scores. If any score is below 100, fix and restart from step 2.
|
|
34
|
+
|
|
35
|
+
### 7. Performance
|
|
36
|
+
|
|
37
|
+
Navigate to the page route, then run `mcp__chrome-devtools__performance_start_trace` with `reload: true` and `autoStop: true`. Report LCP and CLS. Flag any LCP insight that suggests a fixable problem (render-blocking resources, large image load delay, etc.).
|
|
38
|
+
|
|
39
|
+
### 8. Console errors
|
|
40
|
+
|
|
41
|
+
Use `mcp__chrome-devtools__list_console_messages` — report any errors or unexpected warnings.
|
|
42
|
+
|
|
43
|
+
### 9. Code review
|
|
44
|
+
|
|
45
|
+
Check the spec against the checklist in `.claude/pulse-checklist.md`. Work through every item. Fix anything that fails before proceeding to the next step.
|
|
46
|
+
|
|
47
|
+
### 10. Close the browser
|
|
48
|
+
|
|
49
|
+
Use `mcp__chrome-devtools__list_pages` to get all open pages, then call `mcp__chrome-devtools__close_page` for every page ID returned. This closes the entire browser, not just the tab. `pageId` must be a number, not a string.
|
|
50
|
+
|
|
51
|
+
### 11. Write verification stamp
|
|
52
|
+
|
|
53
|
+
Run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
date +%s > .pulse-verified
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This records that verify completed successfully. The stop hook checks this stamp — do not skip this step.
|
|
60
|
+
|
|
61
|
+
### 12. Report
|
|
62
|
+
|
|
63
|
+
Summarise:
|
|
64
|
+
- Validation: pass or fail (with errors if any)
|
|
65
|
+
- Visual: what was visible in the screenshot
|
|
66
|
+
- Lighthouse desktop: scores for all four categories
|
|
67
|
+
- Lighthouse mobile: scores for all four categories
|
|
68
|
+
- Performance: LCP and CLS values, any flagged insights
|
|
69
|
+
- Console: any errors
|
|
70
|
+
- Review: pass or issues found and fixed
|
|
71
|
+
|
|
72
|
+
Only confirm the page is good when validation passes, both Lighthouse runs are 100/100/100/100, CLS is 0.00, and there are no console errors. Otherwise, fix and run `/verify` again.
|
|
@@ -35,7 +35,7 @@ try {
|
|
|
35
35
|
// Parse the coverage report — look for lines with uncovered line numbers
|
|
36
36
|
const lines = out.split('\n')
|
|
37
37
|
let inReport = false
|
|
38
|
-
const
|
|
38
|
+
const rawGaps = []
|
|
39
39
|
|
|
40
40
|
for (const line of lines) {
|
|
41
41
|
if (line.includes('start of coverage report')) { inReport = true; continue }
|
|
@@ -46,11 +46,72 @@ for (const line of lines) {
|
|
|
46
46
|
if (m) {
|
|
47
47
|
const uncov = m[2].trim()
|
|
48
48
|
if (uncov && uncov !== 'uncovered lines') {
|
|
49
|
-
|
|
49
|
+
rawGaps.push({ file: m[1].trim(), uncovered: uncov })
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Returns true if all uncovered line numbers in the given file fall inside
|
|
56
|
+
* a `server:` property block or an action `run:` function — both of which
|
|
57
|
+
* call real external APIs and are explicitly exempt from coverage requirements.
|
|
58
|
+
*/
|
|
59
|
+
function allLinesExempt(filePath, uncoveredSpec) {
|
|
60
|
+
let src
|
|
61
|
+
try { src = fs.readFileSync(filePath, 'utf8') } catch { return false }
|
|
62
|
+
const srcLines = src.split('\n')
|
|
63
|
+
|
|
64
|
+
// Expand "30-36 50" → [30, 31, 32, 33, 34, 35, 36, 50]
|
|
65
|
+
const lineNums = []
|
|
66
|
+
for (const part of uncoveredSpec.split(/\s+/)) {
|
|
67
|
+
const range = part.match(/^(\d+)-(\d+)$/)
|
|
68
|
+
if (range) {
|
|
69
|
+
for (let i = parseInt(range[1]); i <= parseInt(range[2]); i++) lineNums.push(i)
|
|
70
|
+
} else if (/^\d+$/.test(part)) {
|
|
71
|
+
lineNums.push(parseInt(part))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build a set of exempt line numbers: lines inside `server:` blocks or action `run:` functions.
|
|
76
|
+
// Strategy: scan the source tracking brace depth relative to the start of each exempt block.
|
|
77
|
+
const exemptLines = new Set()
|
|
78
|
+
let i = 0
|
|
79
|
+
while (i < srcLines.length) {
|
|
80
|
+
const trimmed = srcLines[i].trimStart()
|
|
81
|
+
// Detect start of `server:` property or action `run:` function
|
|
82
|
+
const isExemptStart = /^server\s*:/.test(trimmed) || /^run\s*:/.test(trimmed) || /^run\s*\(/.test(trimmed)
|
|
83
|
+
if (isExemptStart) {
|
|
84
|
+
// Mark lines from here until the block closes (depth reaches 0 after opening)
|
|
85
|
+
let depth = 0
|
|
86
|
+
let started = false
|
|
87
|
+
const startLine = i
|
|
88
|
+
while (i < srcLines.length) {
|
|
89
|
+
exemptLines.add(i + 1) // 1-based
|
|
90
|
+
for (const ch of srcLines[i]) {
|
|
91
|
+
if (ch === '{' || ch === '(') { depth++; started = true }
|
|
92
|
+
if (ch === '}' || ch === ')') { depth-- }
|
|
93
|
+
}
|
|
94
|
+
if (started && depth <= 0) break
|
|
95
|
+
i++
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
i++
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return lineNums.every(n => exemptLines.has(n))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const gaps = []
|
|
105
|
+
for (const { file, uncovered } of rawGaps) {
|
|
106
|
+
// Try to resolve relative to cwd (coverage reports use relative paths)
|
|
107
|
+
const candidates = [file, `src/pages/${file}`, file.replace(/^.*src\/pages\//, 'src/pages/')]
|
|
108
|
+
const resolved = candidates.find(p => fs.existsSync(p))
|
|
109
|
+
|
|
110
|
+
if (resolved && allLinesExempt(resolved, uncovered)) continue // auto-exempt
|
|
111
|
+
|
|
112
|
+
gaps.push(` ${file} — uncovered lines: ${uncovered}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
54
115
|
if (gaps.length) {
|
|
55
116
|
process.stdout.write(JSON.stringify({
|
|
56
117
|
decision: 'block',
|
package/src/cli/dev.js
CHANGED
|
@@ -63,12 +63,18 @@ const PUBLIC_DIR = path.join(ROOT, 'public')
|
|
|
63
63
|
}
|
|
64
64
|
fs.writeFileSync(stampPath, pkgVersion, 'utf8')
|
|
65
65
|
|
|
66
|
-
// Sync agent
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
// Sync agent files into .claude/
|
|
67
|
+
const agentFiles = [
|
|
68
|
+
['../agent/checklist.md', 'pulse-checklist.md'],
|
|
69
|
+
['../agent/coverage-check.js', 'coverage-check.js'],
|
|
70
|
+
]
|
|
71
|
+
for (const [rel, dst] of agentFiles) {
|
|
72
|
+
const src = new URL(rel, import.meta.url).pathname
|
|
73
|
+
if (fs.existsSync(src)) {
|
|
74
|
+
const dstPath = path.join(ROOT, '.claude', dst)
|
|
75
|
+
fs.mkdirSync(path.dirname(dstPath), { recursive: true })
|
|
76
|
+
fs.copyFileSync(src, dstPath)
|
|
77
|
+
}
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
// Sync slash commands into .claude/commands/ so /pulse-dev etc. always exist
|
package/src/cli/index.js
CHANGED
|
@@ -292,13 +292,19 @@ async function runUpdate(root) {
|
|
|
292
292
|
updated.push(`public/${asset}`)
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
// Sync agent
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
295
|
+
// Sync agent files into .claude/
|
|
296
|
+
const agentFiles = [
|
|
297
|
+
['../agent/checklist.md', 'pulse-checklist.md'],
|
|
298
|
+
['../agent/coverage-check.js', 'coverage-check.js'],
|
|
299
|
+
]
|
|
300
|
+
for (const [rel, dst] of agentFiles) {
|
|
301
|
+
const src = new URL(rel, import.meta.url).pathname
|
|
302
|
+
if (fs.existsSync(src)) {
|
|
303
|
+
const dstPath = path.join(root, '.claude', dst)
|
|
304
|
+
fs.mkdirSync(path.dirname(dstPath), { recursive: true })
|
|
305
|
+
fs.copyFileSync(src, dstPath)
|
|
306
|
+
updated.push(`.claude/${dst}`)
|
|
307
|
+
}
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
// Sync slash commands into .claude/commands/
|
package/src/cli/scaffold.js
CHANGED
|
@@ -168,6 +168,11 @@ ${port !== 3000 ? ` port: ${port},\n` : ''}}
|
|
|
168
168
|
type: 'command',
|
|
169
169
|
command: `node .claude/coverage-check.js`,
|
|
170
170
|
statusMessage: 'Checking test coverage...',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
type: 'command',
|
|
174
|
+
command: `node -e "const fs=require('fs');const{execSync}=require('child_process');let specs=[];try{const out=execSync('git status --porcelain',{encoding:'utf8'});out.split('\\n').forEach(l=>{const f=l.slice(3).trim().split(' -> ').pop();if(f&&f.match(/^src\\/pages\\/.+\\.js$/)&&!f.endsWith('.test.js'))specs.push(f);});}catch{if(fs.existsSync('src/pages'))for(const f of fs.readdirSync('src/pages')){if(f.endsWith('.js')&&!f.endsWith('.test.js'))specs.push('src/pages/'+f);}}if(!specs.length)process.exit(0);const stamp='.pulse-verified';let stampMtime=0;try{stampMtime=fs.statSync(stamp).mtimeMs;}catch{}const stale=specs.filter(f=>{try{return fs.statSync(f).mtimeMs>stampMtime;}catch{return true;}});if(stale.length){process.stdout.write(JSON.stringify({decision:'block',reason:'VERIFY REQUIRED: These pages have not been verified since last edit:\\n'+stale.map(f=>' '+f).join('\\n')+'\\n\\nRun /verify for each changed page. /verify runs Lighthouse (desktop + mobile), performance trace, console check, and code review — then writes the stamp that clears this check.'}));}"`,
|
|
175
|
+
statusMessage: 'Checking verification stamp...',
|
|
171
176
|
}
|
|
172
177
|
]
|
|
173
178
|
}
|