pagespeed-quest 0.9.1 → 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.
- package/CHANGELOG.md +11 -0
- package/README.ja.md +42 -0
- package/README.md +43 -0
- package/build/cli.d.ts +8 -0
- package/build/cli.js +264 -0
- package/build/command.js +3 -209
- package/build/llm.d.ts +20 -0
- package/build/llm.js +42 -0
- package/build/recording.d.ts +2 -0
- package/build/recording.js +125 -8
- package/llmdocs/00-guide.md +124 -0
- package/llmdocs/10-inventory.md +133 -0
- package/llmdocs/20-experiments.md +194 -0
- package/llmdocs/30-measuring.md +117 -0
- package/llmdocs/90-commands.md +181 -0
- package/package.json +14 -8
- package/scripts/build-llm-docs.js +70 -0
- package/scripts/validate-plugin-skills.js +117 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Validate the Agent Skills bundle under plugins/pagespeed-quest/.
|
|
4
|
+
*
|
|
5
|
+
* The distributed SKILL.md files are read by Claude Code, `gh skill` and other
|
|
6
|
+
* agents, so they are held to the ideamans LLM CLI standard:
|
|
7
|
+
*
|
|
8
|
+
* - exactly the five portable frontmatter fields, no Claude-specific ones
|
|
9
|
+
* - `name` matching the directory
|
|
10
|
+
* - a `## Failure modes` table at the end
|
|
11
|
+
* - an install skill in the bundle
|
|
12
|
+
* - plugin.json version identical to package.json version
|
|
13
|
+
*
|
|
14
|
+
* Run via `yarn test` (as test:plugins) or `yarn validate-plugin-skills`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, readdirSync, existsSync } from 'fs'
|
|
18
|
+
import { dirname, join } from 'path'
|
|
19
|
+
import { fileURLToPath } from 'url'
|
|
20
|
+
|
|
21
|
+
const projectRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
22
|
+
const pluginDir = join(projectRoot, 'plugins', 'pagespeed-quest')
|
|
23
|
+
const skillsDir = join(pluginDir, 'skills')
|
|
24
|
+
|
|
25
|
+
const ALLOWED_FIELDS = ['name', 'description', 'license', 'compatibility', 'allowed-tools']
|
|
26
|
+
const REQUIRED_FIELDS = ['name', 'description']
|
|
27
|
+
|
|
28
|
+
const errors = []
|
|
29
|
+
|
|
30
|
+
function fail(message) {
|
|
31
|
+
errors.push(message)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseFrontmatter(text, where) {
|
|
35
|
+
if (!text.startsWith('---\n')) {
|
|
36
|
+
fail(`${where}: missing YAML frontmatter`)
|
|
37
|
+
return {}
|
|
38
|
+
}
|
|
39
|
+
const end = text.indexOf('\n---\n', 3)
|
|
40
|
+
if (end < 0) {
|
|
41
|
+
fail(`${where}: unterminated frontmatter`)
|
|
42
|
+
return {}
|
|
43
|
+
}
|
|
44
|
+
const fields = {}
|
|
45
|
+
for (const line of text.slice(4, end).split('\n')) {
|
|
46
|
+
const match = line.match(/^([A-Za-z-]+):\s*(.*)$/)
|
|
47
|
+
if (match) fields[match[1]] = match[2]
|
|
48
|
+
}
|
|
49
|
+
return fields
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const packageJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'))
|
|
53
|
+
const pluginJson = JSON.parse(readFileSync(join(pluginDir, '.claude-plugin', 'plugin.json'), 'utf8'))
|
|
54
|
+
|
|
55
|
+
if (pluginJson.version !== packageJson.version) {
|
|
56
|
+
fail(`plugin.json version ${pluginJson.version} does not match package.json version ${packageJson.version}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!existsSync(skillsDir)) {
|
|
60
|
+
fail('plugins/pagespeed-quest/skills does not exist')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const skillNames = existsSync(skillsDir)
|
|
64
|
+
? readdirSync(skillsDir, { withFileTypes: true })
|
|
65
|
+
.filter((entry) => entry.isDirectory())
|
|
66
|
+
.map((entry) => entry.name)
|
|
67
|
+
: []
|
|
68
|
+
|
|
69
|
+
if (skillNames.length < 2) {
|
|
70
|
+
fail('a distributed plugin needs at least an install skill and a usage skill')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!skillNames.some((name) => name.endsWith('-install'))) {
|
|
74
|
+
fail('no install skill found — every distributed plugin ships one')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!skillNames.some((name) => name.endsWith('-usage'))) {
|
|
78
|
+
fail('no usage skill found — every distributed plugin ships one')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const skillName of skillNames) {
|
|
82
|
+
const where = `skills/${skillName}/SKILL.md`
|
|
83
|
+
const path = join(skillsDir, skillName, 'SKILL.md')
|
|
84
|
+
|
|
85
|
+
if (!existsSync(path)) {
|
|
86
|
+
fail(`${where}: missing`)
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const text = readFileSync(path, 'utf8')
|
|
91
|
+
const fields = parseFrontmatter(text, where)
|
|
92
|
+
|
|
93
|
+
for (const field of Object.keys(fields)) {
|
|
94
|
+
if (!ALLOWED_FIELDS.includes(field)) {
|
|
95
|
+
fail(`${where}: frontmatter field "${field}" is not portable across agents — remove it`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const field of REQUIRED_FIELDS) {
|
|
99
|
+
if (!fields[field]) fail(`${where}: frontmatter field "${field}" is required`)
|
|
100
|
+
}
|
|
101
|
+
if (fields.name && fields.name !== skillName) {
|
|
102
|
+
fail(`${where}: name "${fields.name}" does not match its directory "${skillName}"`)
|
|
103
|
+
}
|
|
104
|
+
if (fields.description && fields.description.length > 1024) {
|
|
105
|
+
fail(`${where}: description is ${fields.description.length} characters, the limit is 1024`)
|
|
106
|
+
}
|
|
107
|
+
if (!text.includes('\n## Failure modes\n')) {
|
|
108
|
+
fail(`${where}: no "## Failure modes" section — agents need the failure table`)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (errors.length > 0) {
|
|
113
|
+
process.stderr.write(`plugin skill validation failed:\n${errors.map((e) => ` - ${e}`).join('\n')}\n`)
|
|
114
|
+
process.exit(1)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
process.stdout.write(`plugin skills OK (${skillNames.sort().join(', ')}) at version ${pluginJson.version}\n`)
|