pagespeed-quest 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.ja.md +42 -0
  3. package/README.md +43 -0
  4. package/build/cli.d.ts +8 -0
  5. package/build/cli.js +264 -0
  6. package/build/command.js +3 -229
  7. package/build/llm.d.ts +20 -0
  8. package/build/llm.js +42 -0
  9. package/llmdocs/00-guide.md +124 -0
  10. package/llmdocs/10-inventory.md +133 -0
  11. package/llmdocs/20-experiments.md +194 -0
  12. package/llmdocs/30-measuring.md +117 -0
  13. package/llmdocs/90-commands.md +181 -0
  14. package/package.json +12 -6
  15. package/scripts/build-llm-docs.js +70 -0
  16. package/scripts/validate-plugin-skills.js +117 -0
  17. package/build/docs/assets/css/main.css +0 -1
  18. package/build/docs/assets/images/icons.png +0 -0
  19. package/build/docs/assets/images/icons@2x.png +0 -0
  20. package/build/docs/assets/images/widgets.png +0 -0
  21. package/build/docs/assets/images/widgets@2x.png +0 -0
  22. package/build/docs/assets/js/main.js +0 -51
  23. package/build/docs/classes/inventoryrepository.html +0 -305
  24. package/build/docs/classes/playbackproxy.html +0 -524
  25. package/build/docs/classes/proxy.html +0 -474
  26. package/build/docs/classes/recordingproxy.html +0 -532
  27. package/build/docs/classes/testproxy.html +0 -497
  28. package/build/docs/classes/throttle.html +0 -425
  29. package/build/docs/classes/throttlinglog.html +0 -174
  30. package/build/docs/classes/throttlingtransform.html +0 -6297
  31. package/build/docs/globals.html +0 -1131
  32. package/build/docs/index.html +0 -348
  33. package/build/docs/interfaces/beautifyresult.html +0 -174
  34. package/build/docs/interfaces/contentencodingpair.html +0 -174
  35. package/build/docs/interfaces/inventory.html +0 -174
  36. package/build/docs/interfaces/lighthouseoptions.html +0 -230
  37. package/build/docs/interfaces/playbacktransaction.html +0 -272
  38. package/build/docs/interfaces/proxyoptions.html +0 -423
  39. package/build/docs/interfaces/recordingsession.html +0 -174
  40. package/build/docs/interfaces/recordingtransaction.html +0 -286
  41. package/build/docs/interfaces/resource.html +0 -314
  42. package/build/docs/interfaces/transaction.html +0 -258
  43. package/build/docs/interfaces/withproxyoptions.html +0 -418
  44. package/build/logger.d.ts +0 -2
  45. package/build/logger.js +0 -15
  46. package/build/proxy.d.ts +0 -35
  47. package/build/proxy.js +0 -96
  48. package/build/throttling.d.ts +0 -33
  49. package/build/throttling.js +0 -89
@@ -0,0 +1,194 @@
1
+ # Experiments — which edit models which optimization
2
+
3
+ Each recipe is: the hypothesis, the exact edit, what should move, and what to
4
+ watch out for. Make one edit, measure, record the delta, revert or keep. The
5
+ metric names are the Lighthouse ones reported in `artifacts/summary.md`
6
+ (LCP ×0.25, CLS ×0.25, TBT ×0.30, FCP ×0.10, SI ×0.10).
7
+
8
+ Paths below are relative to the inventory directory.
9
+
10
+ ## Images
11
+
12
+ ### Compress or re-encode an image
13
+
14
+ **Hypothesis** "the hero image is far heavier than it needs to be."
15
+
16
+ Replace the file in place, keeping the same path:
17
+
18
+ ```sh
19
+ cwebp -q 75 contents/GET/https/example.com/img/hero.jpg \
20
+ -o contents/GET/https/example.com/img/hero.jpg
21
+ ```
22
+
23
+ Then, in `index.json`, set the served type to match the new bytes:
24
+
25
+ ```json
26
+ { "contentTypeMime": "image/webp", "rawHeaders": { "content-type": "image/webp" } }
27
+ ```
28
+
29
+ **Moves** LCP, SI, and total transfer. Transfer time falls in proportion to the
30
+ byte count at the recorded `mbps`.
31
+
32
+ **Watch out** The URL still ends in `.jpg`; that is fine, browsers go by
33
+ `Content-Type`. Do not touch `content-length`. If the format is not supported by
34
+ the recorded device profile the image simply fails to decode — check
35
+ `psq capture`.
36
+
37
+ ### Serve a correctly sized image
38
+
39
+ Same edit, but resize rather than re-encode. This is usually the larger win on
40
+ mobile recordings, because the recorded page often ships a desktop-sized asset.
41
+ Compare `summary.md`'s image traffic line before and after.
42
+
43
+ ### Lazy-load below-the-fold images
44
+
45
+ Edit the document: add `loading="lazy"` to `<img>` elements below the fold.
46
+
47
+ **Moves** FCP and SI, sometimes LCP indirectly (bandwidth freed for the hero).
48
+ Nothing needs to change in `index.json` — the lazily-loaded requests simply do
49
+ not happen during the measured window.
50
+
51
+ ### Preload the LCP image
52
+
53
+ ```html
54
+ <link rel="preload" as="image" href="/img/hero.webp" fetchpriority="high">
55
+ ```
56
+
57
+ in `<head>`. **Moves** LCP. **Watch out** the recorded `ttfbMs` of that resource
58
+ still applies as a fixed delay, so the win is bounded by it.
59
+
60
+ ## Scripts
61
+
62
+ ### Defer or async a render-blocking script
63
+
64
+ Edit the document, add `defer` (order preserved) or `async` (order not).
65
+
66
+ **Moves** FCP, LCP, SI. TBT often moves too, because the work lands after the
67
+ first paint.
68
+
69
+ ### Remove a third-party tag
70
+
71
+ Delete the `<script>` from the document. Optionally also delete the resource
72
+ from `index.json` — but only after the reference is gone, or the browser will
73
+ record a failed request (see the 404 rule).
74
+
75
+ **Moves** TBT most visibly, plus whatever the tag was blocking. This is the
76
+ single most informative experiment on most commercial pages: it puts a number on
77
+ "what is the tag manager costing us".
78
+
79
+ **Watch out** Some tags inject further requests. Removing the parent removes
80
+ those too, which is realistic — but it makes the delta larger than the tag's own
81
+ weight, so say so when reporting.
82
+
83
+ ### Shrink the bundle
84
+
85
+ Replace the file under `contents/` with a smaller build (drop a polyfill, remove
86
+ a library, run a real minifier). Because the browser actually executes what you
87
+ serve, **TBT and main-thread time respond for real** — this is not a simulated
88
+ number.
89
+
90
+ ## CSS and fonts
91
+
92
+ ### Inline critical CSS, defer the rest
93
+
94
+ In the document, add a `<style>` block with the above-the-fold rules and switch
95
+ the stylesheet link to a deferred load:
96
+
97
+ ```html
98
+ <link rel="preload" href="/assets/app.css" as="style" onload="this.rel='stylesheet'">
99
+ ```
100
+
101
+ **Moves** FCP, LCP, SI. **Watch out** CLS often gets *worse* if the inlined
102
+ subset is incomplete — check `psq capture --compare` as well as the score.
103
+
104
+ ### Remove unused CSS
105
+
106
+ Edit the stylesheet under `contents/`. Remember `minify: true` re-minifies, so
107
+ the win shows up as fewer bytes, not as tidier formatting.
108
+
109
+ ### `font-display: swap` and font preloading
110
+
111
+ Edit the `@font-face` block in the stylesheet, and/or add
112
+ `<link rel="preload" as="font" crossorigin>` to the document.
113
+
114
+ **Moves** FCP and LCP when text was the largest element; CLS if the swap causes
115
+ reflow. Subsetting the font file itself is the transfer-size half of the same
116
+ experiment.
117
+
118
+ ## Network and server
119
+
120
+ These are the levers that do not exist in a normal development environment, and
121
+ they are the reason for the `index.json` fields.
122
+
123
+ ### "What if the server responded faster?"
124
+
125
+ Lower `ttfbMs` on the document resource (often 200–800 ms on a real recording):
126
+
127
+ ```json
128
+ { "url": "https://example.com/", "ttfbMs": 80 }
129
+ ```
130
+
131
+ **Moves** everything downstream, roughly one-for-one. Run this early — it tells
132
+ you how much of the budget is back-end and therefore *not* fixable in the
133
+ front-end.
134
+
135
+ ### "What if we put it on a CDN / on a faster link?"
136
+
137
+ Raise `mbps` on the resources served from the slow host. To model a slower
138
+ network instead, lower it. Apply it to a whole host, not a single file, or the
139
+ result will not resemble anything real.
140
+
141
+ ### "What if we finally enabled compression?"
142
+
143
+ Set `contentEncoding` to `br` (or `gzip`) on the text resources that had
144
+ `identity`. The proxy re-compresses and the transfer time follows.
145
+
146
+ The reverse — setting `identity` on everything — quantifies what compression is
147
+ already buying you, which is a useful number when someone proposes turning it
148
+ off for CPU reasons.
149
+
150
+ ### "What if there were fewer requests?"
151
+
152
+ Concatenate two files into one under `contents/`, update the document to
153
+ reference the survivor, and delete the other entry from `index.json`. The win
154
+ here is mostly the removed `ttfbMs`, so it is only worth it where that value is
155
+ large.
156
+
157
+ ## Layout stability
158
+
159
+ ### Reserve space for images and embeds
160
+
161
+ Add `width` / `height` (or an `aspect-ratio` rule) in the document or the
162
+ stylesheet. **Moves** CLS, which carries the same weight as LCP — a cheap edit
163
+ with a disproportionate score effect.
164
+
165
+ ## What this cannot model
166
+
167
+ State these plainly when reporting results.
168
+
169
+ - **Caching across page views.** Every playback is a cold load.
170
+ - **HTTP/2 prioritisation and connection contention.** Each host is served by
171
+ its own local server; the real contention on a shared connection is not
172
+ reproduced.
173
+ - **Server-side changes that alter the HTML.** You can hand-edit the HTML to
174
+ what the server *would* emit, but the edit is your hypothesis, not a
175
+ measurement of it.
176
+ - **Anything after the load.** Interaction, INP, scrolling — a recording is one
177
+ page load.
178
+ - **Variable content.** Personalised or A/B-tested pages are frozen at the
179
+ moment of recording, which is a feature for comparison and a limitation for
180
+ representativeness.
181
+
182
+ ## Reporting a result
183
+
184
+ A useful report answers three questions: what was changed, what moved, and
185
+ whether the page still looks right.
186
+
187
+ ```
188
+ Hypothesis : defer the tag manager
189
+ Edit : removed <script src="gtm.js"> from contents/GET/https/example.com/index.html
190
+ Before : LCP 3.41s TBT 890ms Overall 42 (artifacts-baseline/summary.md)
191
+ After : LCP 2.78s TBT 210ms Overall 61 (artifacts/summary.md)
192
+ Visual : no diff (artifacts/capture-diff.txt)
193
+ Caveat : removing the parent tag also removed 4 requests it injected
194
+ ```
@@ -0,0 +1,117 @@
1
+ # Measuring — scores, video, screenshots
2
+
3
+ Three measurement surfaces, all driven through the same playback proxy so they
4
+ see the same page. Choose by the question being asked.
5
+
6
+ | Question | Command | Read |
7
+ | --- | --- | --- |
8
+ | Did the score move? | `psq lighthouse playback` | `artifacts/summary.md` |
9
+ | What does the difference look like? | `psq loadshow playback` | `artifacts/loadshow.mp4` |
10
+ | Did I break the page? | `psq capture --compare <png>` | `artifacts/capture-diff.png` |
11
+
12
+ Keep the two sides in separate artifact directories. `-a` is an option of the
13
+ `lighthouse` / `loadshow` group, so it goes before the sub-subcommand.
14
+
15
+ ## Lighthouse before / after
16
+
17
+ ```sh
18
+ psq -i ./baseline lighthouse -a ./artifacts-baseline playback
19
+ psq -i ./inventory lighthouse -a ./artifacts-after playback
20
+ diff artifacts-baseline/summary.md artifacts-after/summary.md
21
+ ```
22
+
23
+ Each run writes:
24
+
25
+ | File | Contents |
26
+ | --- | --- |
27
+ | `summary.md` | metrics, per-metric scores with weights, overall score, traffic and resource size by type |
28
+ | `lighthouse.report.html` | the full report (open it when a number needs explaining) |
29
+ | `lighthouse.report.json` | the same, machine-readable |
30
+ | `lighthouse.digest.png` | score gauge and metrics as one image — the thing to paste into a report |
31
+ | `lighthouse-0.trace.json`, `*.devtoolslog.json` | saved assets (`--save-assets`) |
32
+
33
+ `summary.md` is the file to diff. It is deliberately short and stable, so the
34
+ diff shows the delta and nothing else.
35
+
36
+ Options on the `lighthouse` group: `-a/--artifacts` (default `./artifacts`),
37
+ `-t/--timeout` (ms, default 30000), `-l/--laud` — show the browser window and
38
+ open the report at the end. `--laud` is for watching a run, not for measuring;
39
+ a visible browser is not the same environment as the headless one.
40
+
41
+ **Repeat before concluding.** Lighthouse noise on a fixed recording is a few
42
+ points. Three runs per side is usually enough to see whether a delta is real.
43
+
44
+ ## Video of the load, before and after
45
+
46
+ ```sh
47
+ psq -i ./baseline loadshow -a ./artifacts-baseline playback
48
+ psq -i ./inventory loadshow -a ./artifacts-after playback
49
+ ```
50
+
51
+ Each writes `<artifacts>/loadshow.mp4` — a filmstrip of the load, laid out in
52
+ 3 columns for mobile recordings and 2 for desktop, plus
53
+ `<artifacts>/loadshow/summary.md` and the intermediate frames in
54
+ `<artifacts>/loadshow/`.
55
+
56
+ Use `-c/--credit` to stamp a label into the video, which is what makes a pair of
57
+ videos legible side by side:
58
+
59
+ ```sh
60
+ psq -i ./baseline loadshow -a ./artifacts-baseline -c "Before" playback
61
+ psq -i ./inventory loadshow -a ./artifacts-after -c "After — deferred GTM" playback
62
+ ```
63
+
64
+ To place them side by side, stack the two MP4s with ffmpeg; `psq` does not
65
+ compose videos:
66
+
67
+ ```sh
68
+ ffmpeg -i artifacts-baseline/loadshow.mp4 -i artifacts-after/loadshow.mp4 \
69
+ -filter_complex hstack -c:v libx264 before-after.mp4
70
+ ```
71
+
72
+ ### Recording *with* loadshow rather than Lighthouse
73
+
74
+ ```sh
75
+ psq loadshow recording https://example.com/
76
+ ```
77
+
78
+ Lighthouse and loadshow drive the browser slightly differently. If the goal of
79
+ the quest is the video rather than the score, record with `loadshow recording`
80
+ so that the recording and the playback are driven the same way.
81
+
82
+ ## Screenshot and visual diff
83
+
84
+ ```sh
85
+ psq -i ./baseline capture -a ./artifacts-baseline # baseline.png
86
+ psq capture --compare ./artifacts-baseline/capture.png \
87
+ --baseline-label "Before" --current-label "After"
88
+ ```
89
+
90
+ `capture` runs the proxy in **full-throttle** mode — no TTFB, no bandwidth
91
+ simulation — because the question is what the page looks like when fully
92
+ loaded, not when. Never quote a timing number from a `capture` run.
93
+
94
+ Output: `capture.png` (rendered at 400×1600, resized to 200×800),
95
+ `capture-diff.png`, and `capture-diff.txt` with a text digest of the difference.
96
+
97
+ Run this after **every** structural edit. A deleted script that also removed a
98
+ banner will make every metric better and the page wrong.
99
+
100
+ ## Reading `summary.md`
101
+
102
+ ```
103
+ ## Metrics
104
+ - LCP 2780 ms
105
+ - CLS 0.02
106
+ ...
107
+ ## Scores
108
+ - LCP 0.71 x0.25
109
+ - TBT 0.88 x0.30
110
+ - **Overall** 0.61
111
+ ## Traffic: Type / Count / Size / Bytes
112
+ - script / 14 / 512.3 KB / 524595
113
+ ```
114
+
115
+ *Traffic* is what crossed the wire (after compression); *Resource* is the
116
+ decompressed size. A change that moves resource size but not traffic size means
117
+ you edited something that compresses away — expect little timing effect.
@@ -0,0 +1,181 @@
1
+ # Command catalog
2
+
3
+ <!-- Generated by scripts/build-llm-docs.js. Do not edit by hand. -->
4
+
5
+ Options belong to the command they are declared on, so they must appear
6
+ before the next subcommand: `psq -i <dir> lighthouse -a <dir> playback`.
7
+
8
+ ## psq
9
+
10
+ Record a web page through a proxy, edit the recording, and replay it to measure the difference
11
+
12
+ ```
13
+ Usage: psq [options] [command]
14
+
15
+ Record a web page through a proxy, edit the recording, and replay it to measure the difference
16
+
17
+ Options:
18
+ -v, --version Output the version number
19
+ -i, --inventory <dir> Inventory directory (default: "./inventory")
20
+ -h, --help display help for command
21
+
22
+ Commands:
23
+ lighthouse [options] Run Lighthouse (performance category) via a proxy
24
+ loadshow [options] Run loadshow via a proxy
25
+ proxy [options] Run the playback (or recording) proxy standalone
26
+ capture [options] Capture a screenshot via playback proxy and optionally compare with a baseline
27
+ llm [options] Print the embedded reference for AI agents
28
+ help [command] display help for command
29
+ ```
30
+
31
+ ### psq lighthouse
32
+
33
+ Run Lighthouse (performance category) via a proxy
34
+
35
+ ```
36
+ Usage: psq lighthouse [options] [command]
37
+
38
+ Run Lighthouse (performance category) via a proxy
39
+
40
+ Options:
41
+ -a, --artifacts <dir> Artifacts directory (default: "./artifacts")
42
+ -l, --laud Show browser and open the report after completion (default: false)
43
+ -t, --timeout <ms> Timeout milliseconds (default: "30000")
44
+ -h, --help display help for command
45
+
46
+ Commands:
47
+ recording [options] <url> Record contents by lighthouse
48
+ playback Playback contents for lighthouse
49
+ help [command] display help for command
50
+ ```
51
+
52
+ #### psq lighthouse recording
53
+
54
+ Record contents by lighthouse
55
+
56
+ ```
57
+ Usage: psq lighthouse recording [options] <url>
58
+
59
+ Record contents by lighthouse
60
+
61
+ Arguments:
62
+ url Url to measure performance
63
+
64
+ Options:
65
+ -d, --device <mobile|desktop> Device type (default: "mobile")
66
+ -x, --exclude <pattern> Regex patterns to exclude URLs from recording (repeatable) (default: [])
67
+ -h, --help display help for command
68
+ ```
69
+
70
+ #### psq lighthouse playback
71
+
72
+ Playback contents for lighthouse
73
+
74
+ ```
75
+ Usage: psq lighthouse playback [options]
76
+
77
+ Playback contents for lighthouse
78
+
79
+ Options:
80
+ -h, --help display help for command
81
+ ```
82
+
83
+ ### psq loadshow
84
+
85
+ Run loadshow via a proxy
86
+
87
+ ```
88
+ Usage: psq loadshow [options] [command]
89
+
90
+ Run loadshow via a proxy
91
+
92
+ Options:
93
+ -a, --artifacts <dir> Artifacts directory (default: "./artifacts")
94
+ -c, --credit <string> Credit string
95
+ -t, --timeout <ms> Timeout milliseconds (default: "30000")
96
+ -h, --help display help for command
97
+
98
+ Commands:
99
+ recording [options] <url> Record contents by loadshow
100
+ playback Playback contents for loadshow
101
+ help [command] display help for command
102
+ ```
103
+
104
+ #### psq loadshow recording
105
+
106
+ Record contents by loadshow
107
+
108
+ ```
109
+ Usage: psq loadshow recording [options] <url>
110
+
111
+ Record contents by loadshow
112
+
113
+ Arguments:
114
+ url Url to measure performance
115
+
116
+ Options:
117
+ -d, --device <mobile|desktop> Device type (default: "mobile")
118
+ -x, --exclude <pattern> Regex patterns to exclude URLs from recording (repeatable) (default: [])
119
+ -h, --help display help for command
120
+ ```
121
+
122
+ #### psq loadshow playback
123
+
124
+ Playback contents for loadshow
125
+
126
+ ```
127
+ Usage: psq loadshow playback [options]
128
+
129
+ Playback contents for loadshow
130
+
131
+ Options:
132
+ -h, --help display help for command
133
+ ```
134
+
135
+ ### psq proxy
136
+
137
+ Run the playback (or recording) proxy standalone
138
+
139
+ ```
140
+ Usage: psq proxy [options]
141
+
142
+ Run the playback (or recording) proxy standalone
143
+
144
+ Options:
145
+ -p, --port <number> Proxy port (default: "8080")
146
+ -r, --record <url> Recording URL to start the proxy as recording mode (default: "")
147
+ -x, --exclude <pattern> Regex patterns to exclude URLs from recording (repeatable) (default: [])
148
+ -h, --help display help for command
149
+ ```
150
+
151
+ ### psq capture
152
+
153
+ Capture a screenshot via playback proxy and optionally compare with a baseline
154
+
155
+ ```
156
+ Usage: psq capture [options]
157
+
158
+ Capture a screenshot via playback proxy and optionally compare with a baseline
159
+
160
+ Options:
161
+ -a, --artifacts <dir> Artifacts directory (default: "./artifacts")
162
+ -t, --timeout <ms> Timeout milliseconds (default: "30000")
163
+ --compare <file> Baseline PNG file to compare against
164
+ --baseline-label <label> Label for the baseline image (default: "前回")
165
+ --current-label <label> Label for the current image (default: "今回")
166
+ -h, --help display help for command
167
+ ```
168
+
169
+ ### psq llm
170
+
171
+ Print the embedded reference for AI agents
172
+
173
+ ```
174
+ Usage: psq llm [options]
175
+
176
+ Print the embedded reference for AI agents
177
+
178
+ Options:
179
+ -f, --format <markdown|json> Output format (default: "markdown")
180
+ -h, --help display help for command
181
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pagespeed-quest",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "A framework for efficient web front-end speed improvement",
6
6
  "main": "build/index.js",
@@ -17,16 +17,20 @@
17
17
  "psq": "build/command.js"
18
18
  },
19
19
  "scripts": {
20
- "build": "run-p build:*",
20
+ "build": "run-s build:module build:llmdocs",
21
21
  "build:module": "tsc -p tsconfig.json",
22
+ "build:llmdocs": "node scripts/build-llm-docs.js",
23
+ "validate-plugin-skills": "node scripts/validate-plugin-skills.js",
22
24
  "fix": "run-s fix:*",
23
25
  "fix:prettier": "prettier \"src/**/*.ts\" --write",
24
26
  "fix:lint": "eslint src --ext .ts --fix",
25
- "test": "run-s build test:*",
27
+ "test": "run-s build:module test:*",
26
28
  "test:lint": "eslint src --ext .ts",
27
29
  "test:prettier": "prettier \"src/**/*.ts\" --list-different",
28
30
  "test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
29
31
  "test:unit": "nyc --silent ava",
32
+ "test:plugins": "node scripts/validate-plugin-skills.js",
33
+ "test:llmdocs": "node scripts/build-llm-docs.js --check",
30
34
  "check-cli": "run-s test diff-integration-tests check-integration-tests",
31
35
  "check-integration-tests": "run-s check-integration-test:*",
32
36
  "diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'",
@@ -47,10 +51,11 @@
47
51
  "adhoc": "yarn build && node build/adhoc.js",
48
52
  "command": "yarn build && node build/command.js",
49
53
  "prepublishOnly": "yarn build",
50
- "postinstall": "node scripts/postinstall.js"
54
+ "postinstall": "node scripts/postinstall.js",
55
+ "ai:regen": "run-s build:module build:llmdocs validate-plugin-skills"
51
56
  },
52
57
  "engines": {
53
- "node": ">=22"
58
+ "node": ">=22.19"
54
59
  },
55
60
  "dependencies": {
56
61
  "commander": "^11.1.0",
@@ -68,7 +73,7 @@
68
73
  "tmp-promise": "^3.0.3"
69
74
  },
70
75
  "devDependencies": {
71
- "@ava/typescript": "^5.0.0",
76
+ "@ava/typescript": "^7.0.0",
72
77
  "@istanbuljs/nyc-config-typescript": "^1.0.1",
73
78
  "@types/node": "^20.8.9",
74
79
  "@typescript-eslint/eslint-plugin": "^4.0.1",
@@ -94,6 +99,7 @@
94
99
  "files": [
95
100
  "build",
96
101
  "scripts",
102
+ "llmdocs",
97
103
  "!**/*.spec.*",
98
104
  "!**/*.json",
99
105
  "CHANGELOG.md",
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate llmdocs/90-commands.md from the actual `psq` command tree.
4
+ *
5
+ * Run through `yarn build` (and `yarn ai:regen`) after `tsc`, because it walks
6
+ * the compiled command definitions rather than a hand-written list. Never edit
7
+ * llmdocs/90-commands.md by hand — change the command definitions in
8
+ * src/cli.ts and run this again.
9
+ */
10
+
11
+ import { readFile, writeFile } from 'fs/promises'
12
+ import { dirname, join } from 'path'
13
+ import { fileURLToPath } from 'url'
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url))
16
+ const projectRoot = join(__dirname, '..')
17
+ const outputPath = join(projectRoot, 'llmdocs', '90-commands.md')
18
+
19
+ const { createProgram } = await import(join(projectRoot, 'build', 'cli.js'))
20
+
21
+ function walk(command, path, depth, lines) {
22
+ const heading = '#'.repeat(Math.min(depth + 1, 6))
23
+ const title = [...path, command.name()].join(' ')
24
+
25
+ lines.push(`${heading} ${title}`)
26
+ lines.push('')
27
+ const description = command.description()
28
+ if (description) {
29
+ lines.push(description)
30
+ lines.push('')
31
+ }
32
+ lines.push('```')
33
+ lines.push(command.helpInformation().trimEnd())
34
+ lines.push('```')
35
+ lines.push('')
36
+
37
+ for (const child of command.commands) {
38
+ walk(child, [...path, command.name()], depth + 1, lines)
39
+ }
40
+ }
41
+
42
+ const program = createProgram()
43
+
44
+ const lines = [
45
+ '# Command catalog',
46
+ '',
47
+ '<!-- Generated by scripts/build-llm-docs.js. Do not edit by hand. -->',
48
+ '',
49
+ 'Options belong to the command they are declared on, so they must appear',
50
+ 'before the next subcommand: `psq -i <dir> lighthouse -a <dir> playback`.',
51
+ '',
52
+ ]
53
+
54
+ walk(program, [], 1, lines)
55
+
56
+ const generated = `${lines.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd()}\n`
57
+
58
+ if (process.argv.includes('--check')) {
59
+ const current = await readFile(outputPath, 'utf8').catch(() => '')
60
+ if (current !== generated) {
61
+ process.stderr.write(
62
+ `llmdocs/90-commands.md is out of date with the command definitions.\nRun \`yarn ai:regen\` and commit the result — do not edit it by hand.\n`
63
+ )
64
+ process.exit(1)
65
+ }
66
+ process.stdout.write('llmdocs/90-commands.md is up to date\n')
67
+ } else {
68
+ await writeFile(outputPath, generated)
69
+ process.stdout.write(`Wrote ${outputPath}\n`)
70
+ }