opencode-agent-skill 10.0.0 → 12.0.0-beta.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 (69) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +60 -8
  3. package/bin/ocskill.mjs +354 -6
  4. package/docs/DETERMINISTIC-TOOLS.md +1 -1
  5. package/docs/ENGINEERING-DESIGN.md +4 -4
  6. package/docs/EVALS.md +3 -3
  7. package/docs/GITHUB-RULESET.md +50 -0
  8. package/docs/NPM-PUBLISH.md +4 -4
  9. package/docs/OPENCODE-COMPAT.md +3 -3
  10. package/docs/TRACE-SCHEMA.md +1 -1
  11. package/docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md +75 -0
  12. package/docs/V11-PERCEPTION-ADAPTIVE.md +220 -0
  13. package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +27 -0
  14. package/evals/repo-scale/tasks.json +62 -0
  15. package/evals/router-triggers.json +82 -0
  16. package/evals/routing.json +76 -0
  17. package/evals/v11/tasks.json +122 -0
  18. package/global-config/agents/merge-arbiter.md +12 -0
  19. package/global-config/agents/visual-verifier.md +12 -0
  20. package/global-config/plugins/ues-router/index.js +272 -2
  21. package/global-config/plugins/ues-router/router.js +27 -3
  22. package/global-config/skills/browser-qa/SKILL.md +14 -0
  23. package/global-config/skills/browser-qa/references/workflow.md +11 -0
  24. package/global-config/skills/browser-security/SKILL.md +12 -0
  25. package/global-config/skills/component-visual-testing/SKILL.md +10 -0
  26. package/global-config/skills/design-source/SKILL.md +10 -0
  27. package/global-config/skills/design-source/references/workflow.md +12 -0
  28. package/global-config/skills/dynamic-workflow/SKILL.md +18 -0
  29. package/global-config/skills/dynamic-workflow/references/workflow.md +19 -0
  30. package/global-config/skills/responsive-verification/SKILL.md +10 -0
  31. package/global-config/skills/skill-authoring/SKILL.md +12 -0
  32. package/global-config/skills/skill-evaluation/SKILL.md +17 -0
  33. package/global-config/skills/visual-fidelity/SKILL.md +14 -0
  34. package/global-config/skills/visual-fidelity/references/workflow.md +14 -0
  35. package/lib/browser-adapter.mjs +82 -0
  36. package/lib/browser-runtime.mjs +193 -0
  37. package/lib/capability-registry.mjs +109 -0
  38. package/lib/context-engine-v11.mjs +150 -0
  39. package/lib/context-manifest.mjs +16 -3
  40. package/lib/context-quality.mjs +59 -0
  41. package/lib/control-center.mjs +12 -2
  42. package/lib/decision-policy.mjs +23 -0
  43. package/lib/dynamic-workflow.mjs +179 -0
  44. package/lib/eval-ablation.mjs +43 -1
  45. package/lib/eval-report.mjs +72 -0
  46. package/lib/eval-telemetry.mjs +61 -0
  47. package/lib/evidence-budget.mjs +84 -0
  48. package/lib/evidence-store.mjs +178 -0
  49. package/lib/hermes-bridge.mjs +45 -1
  50. package/lib/model-config.mjs +21 -1
  51. package/lib/model-performance.mjs +113 -0
  52. package/lib/model-policy.mjs +59 -1
  53. package/lib/orchestrator-policy.mjs +1 -1
  54. package/lib/png-diff.mjs +229 -0
  55. package/lib/prompt-cache.mjs +60 -0
  56. package/lib/repo-scale-fixture.mjs +45 -0
  57. package/lib/skill-quality.mjs +72 -0
  58. package/lib/task-engine.mjs +95 -7
  59. package/lib/ui-inspector.mjs +152 -0
  60. package/lib/v11-metrics.mjs +64 -0
  61. package/lib/visual-spec.mjs +159 -0
  62. package/lib/work-plan-scope.mjs +49 -0
  63. package/package.json +13 -5
  64. package/scripts/check-release-consistency.mjs +228 -0
  65. package/scripts/eval-ablation.mjs +4 -1
  66. package/scripts/validate-repo-scale-suite.mjs +27 -0
  67. package/scripts/validate-v11-suite.mjs +58 -0
  68. package/scripts/validate-v12-foundation.mjs +24 -0
  69. package/scripts/validate.mjs +16 -4
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readdirSync, readFileSync } from "node:fs"
4
+ import path from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ const DEFAULT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
8
+
9
+ function readJson(root, relative) {
10
+ const file = path.join(root, relative)
11
+ if (!existsSync(file)) {
12
+ return { ok: false, error: `${relative}: file not found`, value: null }
13
+ }
14
+ try {
15
+ return { ok: true, error: null, value: JSON.parse(readFileSync(file, "utf8")) }
16
+ } catch (e) {
17
+ return { ok: false, error: `${relative}: invalid JSON (${e.message})`, value: null }
18
+ }
19
+ }
20
+
21
+ function readText(root, relative) {
22
+ const file = path.join(root, relative)
23
+ if (!existsSync(file)) {
24
+ return { ok: false, error: `${relative}: file not found`, value: null }
25
+ }
26
+ return { ok: true, error: null, value: readFileSync(file, "utf8") }
27
+ }
28
+
29
+ function countSkillDirs(root) {
30
+ const fullPath = path.join(root, "global-config", "skills")
31
+ if (!existsSync(fullPath)) return { ok: false, error: "global-config/skills: directory not found", value: 0 }
32
+ const entries = readdirSync(fullPath, { withFileTypes: true })
33
+ const count = entries.filter((entry) => entry.isDirectory() && existsSync(path.join(fullPath, entry.name, "SKILL.md"))).length
34
+ return { ok: true, error: null, value: count }
35
+ }
36
+
37
+ function countDir(root, dirPath, filter) {
38
+ const fullPath = path.join(root, dirPath)
39
+ if (!existsSync(fullPath)) {
40
+ return { ok: false, error: `${dirPath}: directory not found`, value: 0 }
41
+ }
42
+ const entries = readdirSync(fullPath, { withFileTypes: true })
43
+ const count = filter ? entries.filter(filter).length : entries.length
44
+ return { ok: true, error: null, value: count }
45
+ }
46
+
47
+ export function checkReleaseConsistency(root) {
48
+ root = root || DEFAULT_ROOT
49
+ const errors = []
50
+ const warnings = []
51
+
52
+ function rJson(relative) {
53
+ return readJson(root, relative)
54
+ }
55
+ function rText(relative) {
56
+ return readText(root, relative)
57
+ }
58
+ function rCount(dirPath, filter) {
59
+ return countDir(root, dirPath, filter)
60
+ }
61
+
62
+ // 1. package.json version == package-lock.json version
63
+ const pkg = rJson("package.json")
64
+ const lock = rJson("package-lock.json")
65
+ if (pkg.ok && lock.ok) {
66
+ const pkgVersion = pkg.value.version
67
+ const lockVersion = lock.value.version
68
+ if (pkgVersion !== lockVersion) {
69
+ errors.push(`package.json version (${pkgVersion}) != package-lock.json version (${lockVersion})`)
70
+ }
71
+ } else {
72
+ if (!pkg.ok) errors.push(pkg.error)
73
+ if (!lock.ok) errors.push(lock.error)
74
+ }
75
+
76
+ // 2. Count skills, commands, subagents
77
+ const skillRes = countSkillDirs(root)
78
+ const commandRes = rCount("global-config/commands", (e) => e.isFile() && e.name.endsWith(".md"))
79
+ const agentRes = rCount("global-config/agents", (e) => e.isFile() && e.name.endsWith(".md"))
80
+ const skillCount = skillRes.ok ? skillRes.value : 0
81
+ const commandCount = commandRes.ok ? commandRes.value : 0
82
+ const agentCount = agentRes.ok ? agentRes.value : 0
83
+ if (!skillRes.ok) errors.push(skillRes.error)
84
+ if (!commandRes.ok) errors.push(commandRes.error)
85
+ if (!agentRes.ok) errors.push(agentRes.error)
86
+
87
+ const expectedVersion = pkg.ok ? pkg.value.version : "unknown"
88
+ const routing = rJson("evals/routing.json")
89
+ const routerTriggers = rJson("evals/router-triggers.json")
90
+ const staticScenarioCount = routing.ok && Array.isArray(routing.value.scenarios) ? routing.value.scenarios.length : 0
91
+ const routerCaseCount = routerTriggers.ok && Array.isArray(routerTriggers.value.cases) ? routerTriggers.value.cases.length : 0
92
+ if (!routing.ok) errors.push(routing.error)
93
+ if (!routerTriggers.ok) errors.push(routerTriggers.error)
94
+
95
+ // 3. Check README current version block
96
+ const readme = rText("README.md")
97
+ if (readme.ok) {
98
+ const versionMatch = readme.value.match(/Phiên bản hiện tại:\s*\n```text\n(\S+)/)
99
+ if (versionMatch && versionMatch[1] !== expectedVersion) {
100
+ errors.push(`README.md: current version says ${versionMatch[1]}, expected ${expectedVersion}`)
101
+ } else if (!versionMatch) {
102
+ errors.push("README.md: could not find current version block")
103
+ }
104
+ if (staticScenarioCount && !readme.value.includes("**" + staticScenarioCount + " static skill-routing scenarios**")) {
105
+ errors.push("README.md: static routing scenario count drift (actual: " + staticScenarioCount + ")")
106
+ }
107
+ if (routerCaseCount && !readme.value.includes("**" + routerCaseCount + " V2 router cases**")) {
108
+ errors.push("README.md: router case count drift (actual: " + routerCaseCount + ")")
109
+ }
110
+ } else {
111
+ errors.push(readme.error)
112
+ }
113
+
114
+ // 4. Check V11 docs for stale status markers
115
+ const v11Docs = ["docs/V11-PERCEPTION-ADAPTIVE.md", "docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md"]
116
+ for (const doc of v11Docs) {
117
+ const content = rText(doc)
118
+ if (!content.ok) {
119
+ errors.push(content.error)
120
+ continue
121
+ }
122
+ if (content.value.includes("Status: development")) {
123
+ errors.push(`${doc}: still marked as development`)
124
+ }
125
+ if (content.value.includes("11.0.0-dev.")) {
126
+ errors.push(`${doc}: still references dev version 11.0.0-dev.*`)
127
+ }
128
+ if (content.value.includes("npm `latest` remains V10")) {
129
+ errors.push(`${doc}: still says V10 remains npm latest`)
130
+ }
131
+ }
132
+
133
+ // 5. Verify docs reflect actual counts (skip historical sections)
134
+ const docsToCheck = ["README.md", "docs/ENGINEERING-DESIGN.md", "docs/OPENCODE-COMPAT.md"]
135
+ for (const doc of docsToCheck) {
136
+ const content = rText(doc)
137
+ if (!content.ok) {
138
+ errors.push(content.error)
139
+ continue
140
+ }
141
+ const sections = content.value.split(/\n(?=#{1,6}\s)/)
142
+ for (const section of sections) {
143
+ const isHistorical = /^\s*#{1,6}\s+.*(V\d+\.|UES\s+\d+\.|historical|Historical)/im.test(section)
144
+ if (isHistorical) continue
145
+ if (section.includes("39 skills") || section.includes("39 namespaced skills")) {
146
+ errors.push(`${doc}: current section still references 39 skills (actual: ${skillCount})`)
147
+ }
148
+ if (section.includes("10 namespaced subagents") || section.includes("10 subagents")) {
149
+ errors.push(`${doc}: current section still references 10 subagents (actual: ${agentCount})`)
150
+ }
151
+ if (section.includes("34 static") && section.includes("scenarios")) {
152
+ errors.push(`${doc}: current section still references 34 static scenarios (actual: ${staticScenarioCount})`)
153
+ }
154
+ }
155
+ }
156
+
157
+ // 6. Check CI workflow
158
+ const ciYaml = rText(".github/workflows/ci.yml")
159
+ if (ciYaml.ok) {
160
+ if (!ciYaml.value.includes("evals:v11:validate")) {
161
+ errors.push(".github/workflows/ci.yml: missing evals:v11:validate job")
162
+ }
163
+ if (!/^\s*name:\s*CI Gate\s*$/m.test(ciYaml.value)) errors.push(".github/workflows/ci.yml: missing CI Gate aggregate job name")
164
+ if (!/needs:\s*\[\s*static\s*,\s*unit\s*,\s*package\s*\]/m.test(ciYaml.value)) errors.push(".github/workflows/ci.yml: CI Gate must depend on static, unit and package")
165
+ if (!/if:\s*always\(\)/m.test(ciYaml.value)) errors.push(".github/workflows/ci.yml: aggregate gate must use if: always()")
166
+ if (!ciYaml.value.includes("docs:check")) errors.push(".github/workflows/ci.yml: missing docs:check job")
167
+ for (const check of ["evals:v12:validate","evals:repo-scale:validate"]) {
168
+ if (!ciYaml.value.includes(check)) errors.push(".github/workflows/ci.yml: missing " + check + " job")
169
+ }
170
+ } else {
171
+ errors.push(ciYaml.error)
172
+ }
173
+
174
+ // 7. Check Security workflow
175
+ const securityYaml = rText(".github/workflows/security.yml")
176
+ if (securityYaml.ok) {
177
+ if (!/^\s*name:\s*Security Gate\s*$/m.test(securityYaml.value)) errors.push(".github/workflows/security.yml: missing Security Gate aggregate job name")
178
+ if (!/needs:\s*\[\s*codeql\s*,\s*dependency-review\s*\]/m.test(securityYaml.value)) errors.push(".github/workflows/security.yml: Security Gate must depend on codeql and dependency-review")
179
+ } else {
180
+ errors.push(securityYaml.error)
181
+ }
182
+
183
+ // 8. Check publish workflow idempotency
184
+ const publishYaml = rText(".github/workflows/publish.yml")
185
+ if (publishYaml.ok) {
186
+ if (!publishYaml.value.includes("already published")) {
187
+ errors.push(".github/workflows/publish.yml: missing idempotency check for existing versions")
188
+ }
189
+ } else {
190
+ errors.push(publishYaml.error)
191
+ }
192
+
193
+ // 9. Check npm script docs:check exists
194
+ if (pkg.ok) {
195
+ const pkgScripts = pkg.value.scripts || {}
196
+ if (!pkgScripts["docs:check"]) {
197
+ warnings.push("package.json: missing docs:check npm script")
198
+ }
199
+ }
200
+
201
+ return {
202
+ pass: errors.length === 0,
203
+ errors,
204
+ warnings,
205
+ version: expectedVersion,
206
+ skillCount,
207
+ commandCount,
208
+ subagentCount: agentCount,
209
+ staticScenarioCount,
210
+ routerCaseCount,
211
+ }
212
+ }
213
+
214
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
215
+ const result = checkReleaseConsistency(process.env.UES_BUNDLE_ROOT || DEFAULT_ROOT)
216
+ if (result.warnings.length) {
217
+ console.warn("Warnings:")
218
+ for (const w of result.warnings) console.warn(` WARN: ${w}`)
219
+ }
220
+ if (result.errors.length) {
221
+ console.error("Release consistency check FAILED:")
222
+ for (const e of result.errors) console.error(` - ${e}`)
223
+ process.exit(1)
224
+ }
225
+ console.log(
226
+ `Release consistency check PASS: package=${result.version}, skills=${result.skillCount}, commands=${result.commandCount}, subagents=${result.subagentCount}`,
227
+ )
228
+ }
@@ -14,7 +14,7 @@ function positional() {
14
14
  const values = []
15
15
  for (let index = 0; index < args.length; index += 1) {
16
16
  if (args[index].startsWith("--")) {
17
- if (["--pass-rate-tolerance", "--min-initial-reduction", "--max-token-ratio", "--max-duration-ratio"].includes(args[index])) index += 1
17
+ if (["--pass-rate-tolerance", "--min-initial-reduction", "--max-token-ratio", "--max-duration-ratio", "--min-cacheable-ratio", "--min-evidence-reuse-ratio", "--max-repeated-stable-ratio"].includes(args[index])) index += 1
18
18
  continue
19
19
  }
20
20
  values.push(args[index])
@@ -38,6 +38,9 @@ const report = compareEvalSummaries(reference, candidate, {
38
38
  minInitialInputReduction: Number(option("--min-initial-reduction", "0.10")),
39
39
  maxTotalTokenRatio: Number(option("--max-token-ratio", "1.05")),
40
40
  maxDurationRatio: Number(option("--max-duration-ratio", "1.10")),
41
+ minCacheableRatio: option("--min-cacheable-ratio", null) == null ? null : Number(option("--min-cacheable-ratio", null)),
42
+ minEvidenceReuseRatio: option("--min-evidence-reuse-ratio", null) == null ? null : Number(option("--min-evidence-reuse-ratio", null)),
43
+ maxRepeatedStableRatio: option("--max-repeated-stable-ratio", null) == null ? null : Number(option("--max-repeated-stable-ratio", null)),
41
44
  })
42
45
 
43
46
  console.log(JSON.stringify(report, null, 2))
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { mkdtemp, readFile, rm } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+ import { generateRepoScaleFixture } from "../lib/repo-scale-fixture.mjs"
7
+
8
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
9
+ const suite = JSON.parse(await readFile(path.join(root, "evals", "repo-scale", "tasks.json"), "utf8"))
10
+ const errors = []
11
+ if (!Array.isArray(suite.tasks) || suite.tasks.length < 4) errors.push("repo-scale suite must define at least 4 tasks")
12
+ for (const task of suite.tasks || []) {
13
+ if (!task.id || !task.objective || task.objective.length < 40) errors.push((task.id || "<missing>") + ": objective too weak")
14
+ if (!Array.isArray(task.requiredFiles) || task.requiredFiles.length < 2) errors.push((task.id || "<missing>") + ": requiredFiles must contain at least 2 paths")
15
+ if (!Array.isArray(task.acceptance) || task.acceptance.length < 2) errors.push((task.id || "<missing>") + ": acceptance must contain at least 2 checks")
16
+ }
17
+ const tmp = await mkdtemp(path.join(os.tmpdir(), "ues-repo-scale-"))
18
+ try {
19
+ const generated = await generateRepoScaleFixture(tmp)
20
+ if (generated.generatedModules < Number(suite.minimumGeneratedModules || 300)) errors.push("generated repo too small: " + generated.generatedModules + " modules")
21
+ } finally { await rm(tmp, { recursive: true, force: true }) }
22
+ if (errors.length) {
23
+ console.error("Repo-scale suite validation failed:")
24
+ for (const error of errors) console.error("- " + error)
25
+ process.exit(1)
26
+ }
27
+ console.log("Validated repo-scale suite: " + suite.tasks.length + " tasks, >=" + suite.minimumGeneratedModules + " generated modules.")
@@ -0,0 +1,58 @@
1
+ import { existsSync } from "node:fs"
2
+ import { readFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
7
+ const file = path.join(root, "evals", "v11", "tasks.json")
8
+ const suite = JSON.parse(await readFile(file, "utf8"))
9
+ const errors = []
10
+
11
+ const REQUIRED_V11_FILES = [
12
+ "lib/context-engine-v11.mjs",
13
+ "lib/evidence-store.mjs",
14
+ "lib/evidence-budget.mjs",
15
+ "lib/prompt-cache.mjs",
16
+ "lib/capability-registry.mjs",
17
+ "lib/browser-adapter.mjs",
18
+ "lib/browser-runtime.mjs",
19
+ "lib/png-diff.mjs",
20
+ "lib/visual-spec.mjs",
21
+ "lib/dynamic-workflow.mjs",
22
+ "lib/skill-quality.mjs",
23
+ "lib/ui-inspector.mjs",
24
+ "lib/v11-metrics.mjs",
25
+ "global-config/agents/visual-verifier.md",
26
+ "global-config/agents/merge-arbiter.md",
27
+ ]
28
+ for (const relative of REQUIRED_V11_FILES) {
29
+ if (!existsSync(path.join(root, relative))) errors.push("missing V11 runtime file: " + relative)
30
+ }
31
+ const ids = new Set()
32
+ const categories = new Set()
33
+
34
+ if (!Array.isArray(suite.tasks) || suite.tasks.length < 10) errors.push("V11 suite must contain at least 10 tasks")
35
+
36
+ for (const task of suite.tasks || []) {
37
+ if (!task.id || ids.has(task.id)) errors.push("missing/duplicate id: " + (task.id || "<missing>"))
38
+ ids.add(task.id)
39
+ if (!task.category) errors.push((task.id || "<missing>") + ": missing category")
40
+ categories.add(task.category)
41
+ if (!task.objective || task.objective.length < 30) errors.push((task.id || "<missing>") + ": objective is too weak")
42
+ if (!Array.isArray(task.requiredFiles) || !task.requiredFiles.length) errors.push((task.id || "<missing>") + ": requiredFiles missing")
43
+ for (const relative of task.requiredFiles || []) {
44
+ if (!existsSync(path.join(root, relative))) errors.push((task.id || "<missing>") + ": missing required file " + relative)
45
+ }
46
+ }
47
+
48
+ for (const category of ["context","routing","visual","browser","workflow","sidecar"]) {
49
+ if (!categories.has(category)) errors.push("V11 suite missing category: " + category)
50
+ }
51
+
52
+ if (errors.length) {
53
+ console.error("V11 suite validation failed:")
54
+ for (const error of errors) console.error("- " + error)
55
+ process.exit(1)
56
+ }
57
+
58
+ console.log("Validated " + suite.tasks.length + " V11 contract tasks across " + categories.size + " categories and " + REQUIRED_V11_FILES.length + " required runtime files.")
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs"
3
+ import { readFile } from "node:fs/promises"
4
+ import path from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
8
+ const errors = []
9
+ const required = [
10
+ "lib/model-performance.mjs","lib/context-quality.mjs","lib/work-plan-scope.mjs",
11
+ "lib/decision-policy.mjs","lib/repo-scale-fixture.mjs","evals/repo-scale/tasks.json",
12
+ "scripts/validate-repo-scale-suite.mjs",
13
+ ]
14
+ for (const relative of required) if (!existsSync(path.join(root, relative))) errors.push("missing V12 foundation file: " + relative)
15
+ const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"))
16
+ for (const script of ["evals:v12:validate","evals:repo-scale:validate"]) if (!pkg.scripts?.[script]) errors.push("package.json missing script " + script)
17
+ const tasks = JSON.parse(await readFile(path.join(root, "evals", "repo-scale", "tasks.json"), "utf8"))
18
+ if ((tasks.tasks || []).length < 4) errors.push("V12 repo-scale suite needs at least 4 contract tasks")
19
+ if (errors.length) {
20
+ console.error("V12 foundation validation failed:")
21
+ for (const error of errors) console.error("- " + error)
22
+ process.exit(1)
23
+ }
24
+ console.log("Validated V12 weak-model intelligence foundation files and repo-scale contracts.")
@@ -87,9 +87,12 @@ for (const entry of await readdir(agentsRoot, { withFileTypes: true })) {
87
87
  if (!/mode:\s*subagent/.test(source)) errors.push(`${entry.name}: agent must use mode: subagent`)
88
88
  }
89
89
 
90
- if (ids.size < 39) errors.push(`expected at least 39 skills, found ${ids.size}`)
90
+ const V11_REQUIRED_SKILLS = ["visual-fidelity","browser-qa","design-source","responsive-verification","component-visual-testing","browser-security","skill-authoring","skill-evaluation","dynamic-workflow"]
91
+ for (const id of V11_REQUIRED_SKILLS) if (!ids.has(id)) errors.push(`missing V11 skill ${id}`)
92
+
93
+ if (ids.size < 48) errors.push(`expected at least 48 skills for V11, found ${ids.size}`)
91
94
  if (commands < 11) errors.push(`expected at least 11 commands, found ${commands}`)
92
- if (agents < 10) errors.push(`expected at least 10 subagents, found ${agents}`)
95
+ if (agents < 12) errors.push(`expected at least 12 subagents for V11, found ${agents}`)
93
96
 
94
97
  const routerIndex = path.join(pluginsRoot, "ues-router", "index.js")
95
98
  const routerCore = path.join(pluginsRoot, "ues-router", "router.js")
@@ -115,6 +118,15 @@ if (existsSync(routerIndex)) {
115
118
  if (!source.includes('name: "task_policy"')) errors.push("v8 router plugin must expose adaptive task policy")
116
119
  if (!source.includes('name: "cancel_task"')) errors.push("v8 router plugin must expose executor cancellation")
117
120
  if (!source.includes('name: "recover_task"')) errors.push("v8 router plugin must expose task-scoped recovery")
121
+ if (!source.includes('name: "capability_requirements"')) errors.push("v11 router plugin must expose capability requirements")
122
+ if (!source.includes('name: "evidence_get"')) errors.push("v11 router plugin must expose bounded evidence retrieval")
123
+ if (!source.includes('name: "browser_plan"')) errors.push("v11 router plugin must expose browser verification planning")
124
+ if (!source.includes('name: "browser_inspect"')) errors.push("v11 router plugin must expose bounded browser inspection")
125
+ if (!source.includes('name: "visual_geometry"')) errors.push("v11 router plugin must expose visual geometry receipts")
126
+ if (!source.includes('name: "visual_compare"')) errors.push("v11 router plugin must expose deterministic PNG comparison")
127
+ if (!source.includes('name: "workflow_plan"')) errors.push("v11 router plugin must expose dynamic workflow planning")
128
+ if (!source.includes('name: "ui_tokens"')) errors.push("v11 router plugin must expose compact design-token extraction")
129
+ if (!source.includes('name: "ui_layout"')) errors.push("v11 router plugin must expose responsive UI layout verification")
118
130
  if (!source.includes("ctx.session.interrupt")) errors.push("v8 task dispatch must interrupt timed-out executors")
119
131
  if (!source.includes('"work", "heartbeat"')) errors.push("v8 task dispatch must refresh task leases")
120
132
  }
@@ -138,11 +150,11 @@ if (existsSync(plainInstallSmoke)) {
138
150
  }
139
151
  }
140
152
 
141
- for (const name of ["process-runner.mjs","evidence-receipt.mjs","gate-receipt.mjs","runtime-events.mjs","context-manifest.mjs","orchestrator-policy.mjs","worktree-sandbox.mjs","learning-engine.mjs","hermes-bridge.mjs","control-center.mjs"]) {
153
+ for (const name of ["process-runner.mjs","evidence-receipt.mjs","gate-receipt.mjs","runtime-events.mjs","context-manifest.mjs","orchestrator-policy.mjs","worktree-sandbox.mjs","learning-engine.mjs","hermes-bridge.mjs","control-center.mjs","evidence-store.mjs","evidence-budget.mjs","prompt-cache.mjs","capability-registry.mjs","visual-spec.mjs","png-diff.mjs","browser-adapter.mjs","browser-runtime.mjs","dynamic-workflow.mjs","skill-quality.mjs","ui-inspector.mjs","context-engine-v11.mjs","v11-metrics.mjs"]) {
142
154
  if (!existsSync(path.join(root, "lib", name))) errors.push(`missing V8 core module ${name}`)
143
155
  }
144
156
 
145
- for (const name of ["codebase-mapper.md","plan-checker.md","executor.md","integration-verifier.md"]) {
157
+ for (const name of ["codebase-mapper.md","plan-checker.md","executor.md","integration-verifier.md","visual-verifier.md","merge-arbiter.md"]) {
146
158
  if (!existsSync(path.join(agentsRoot, name))) errors.push(`missing V6 subagent ${name}`)
147
159
  }
148
160