opencode-agent-skill 7.7.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 (206) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/LICENSE +9 -0
  3. package/README.md +581 -0
  4. package/bin/ocskill.mjs +975 -0
  5. package/docs/DETERMINISTIC-TOOLS.md +88 -0
  6. package/docs/ENGINEERING-DESIGN.md +176 -0
  7. package/docs/EVALS.md +136 -0
  8. package/docs/NPM-PUBLISH.md +102 -0
  9. package/docs/OPENCODE-COMPAT.md +109 -0
  10. package/docs/RESEARCH-SOURCES.md +37 -0
  11. package/docs/TRACE-SCHEMA.md +109 -0
  12. package/docs/V7-INTELLIGENCE-RUNTIME.md +166 -0
  13. package/evals/live/fixtures/engineering-bench/package.json +1 -0
  14. package/evals/live/fixtures/engineering-bench/src/api-errors.mjs +3 -0
  15. package/evals/live/fixtures/engineering-bench/src/authz.mjs +3 -0
  16. package/evals/live/fixtures/engineering-bench/src/cache-tags.mjs +3 -0
  17. package/evals/live/fixtures/engineering-bench/src/config.mjs +3 -0
  18. package/evals/live/fixtures/engineering-bench/src/contract-consumer.mjs +6 -0
  19. package/evals/live/fixtures/engineering-bench/src/contract-producer.mjs +3 -0
  20. package/evals/live/fixtures/engineering-bench/src/dedupe.mjs +3 -0
  21. package/evals/live/fixtures/engineering-bench/src/dependency.mjs +3 -0
  22. package/evals/live/fixtures/engineering-bench/src/discount.mjs +3 -0
  23. package/evals/live/fixtures/engineering-bench/src/inventory.mjs +3 -0
  24. package/evals/live/fixtures/engineering-bench/src/migration.mjs +3 -0
  25. package/evals/live/fixtures/engineering-bench/src/money.mjs +3 -0
  26. package/evals/live/fixtures/engineering-bench/src/pagination.mjs +5 -0
  27. package/evals/live/fixtures/engineering-bench/src/path-safe.mjs +5 -0
  28. package/evals/live/fixtures/engineering-bench/src/payment.mjs +5 -0
  29. package/evals/live/fixtures/engineering-bench/src/query-sort.mjs +3 -0
  30. package/evals/live/fixtures/engineering-bench/src/react-state.mjs +8 -0
  31. package/evals/live/fixtures/engineering-bench/src/retry.mjs +3 -0
  32. package/evals/live/fixtures/engineering-bench/src/rn-platform.mjs +3 -0
  33. package/evals/live/fixtures/engineering-bench/src/upload.mjs +3 -0
  34. package/evals/live/fixtures/engineering-bench/src/webhook.mjs +3 -0
  35. package/evals/live/graders/engineering-bench.mjs +239 -0
  36. package/evals/live/tasks.json +126 -0
  37. package/evals/long/fixtures/long-horizon/package.json +1 -0
  38. package/evals/long/fixtures/long-horizon/src/auth.mjs +5 -0
  39. package/evals/long/fixtures/long-horizon/src/checkout.mjs +9 -0
  40. package/evals/long/fixtures/long-horizon/src/inventory.mjs +5 -0
  41. package/evals/long/fixtures/long-horizon/src/money.mjs +3 -0
  42. package/evals/long/fixtures/long-horizon/src/payment.mjs +7 -0
  43. package/evals/long/fixtures/long-horizon/src/product-api.mjs +10 -0
  44. package/evals/long/fixtures/long-horizon/src/product-cache.mjs +3 -0
  45. package/evals/long/fixtures/long-horizon/src/product-service.mjs +6 -0
  46. package/evals/long/fixtures/long-horizon/src/product-state.mjs +8 -0
  47. package/evals/long/fixtures/long-horizon/src/project-api.mjs +9 -0
  48. package/evals/long/fixtures/long-horizon/src/project-service.mjs +7 -0
  49. package/evals/long/fixtures/long-horizon/src/user-consumer.mjs +3 -0
  50. package/evals/long/fixtures/long-horizon/src/user-migration.mjs +7 -0
  51. package/evals/long/fixtures/long-horizon/src/user-serializer.mjs +3 -0
  52. package/evals/long/fixtures/long-horizon/src/user-validation.mjs +3 -0
  53. package/evals/long/graders/long-horizon.mjs +209 -0
  54. package/evals/long/tasks.json +36 -0
  55. package/evals/router-triggers.json +1238 -0
  56. package/evals/routing.json +321 -0
  57. package/global-config/AGENTS.md +212 -0
  58. package/global-config/agents/architect.md +38 -0
  59. package/global-config/agents/codebase-mapper.md +41 -0
  60. package/global-config/agents/critic.md +37 -0
  61. package/global-config/agents/debugger.md +36 -0
  62. package/global-config/agents/executor.md +48 -0
  63. package/global-config/agents/integration-verifier.md +43 -0
  64. package/global-config/agents/plan-checker.md +43 -0
  65. package/global-config/agents/researcher.md +30 -0
  66. package/global-config/agents/reviewer.md +30 -0
  67. package/global-config/agents/verifier.md +33 -0
  68. package/global-config/commands/audit.md +8 -0
  69. package/global-config/commands/critique.md +8 -0
  70. package/global-config/commands/debug.md +8 -0
  71. package/global-config/commands/feature.md +8 -0
  72. package/global-config/commands/fix.md +8 -0
  73. package/global-config/commands/plan.md +8 -0
  74. package/global-config/commands/research.md +8 -0
  75. package/global-config/commands/resume.md +22 -0
  76. package/global-config/commands/review.md +8 -0
  77. package/global-config/commands/run.md +29 -0
  78. package/global-config/commands/verify.md +8 -0
  79. package/global-config/plugins/ues-router/capabilities.js +20 -0
  80. package/global-config/plugins/ues-router/index.js +277 -0
  81. package/global-config/plugins/ues-router/router.js +74 -0
  82. package/global-config/plugins/ues-router/safety.js +16 -0
  83. package/global-config/skills/accessibility/SKILL.md +10 -0
  84. package/global-config/skills/accessibility/references/workflow.md +17 -0
  85. package/global-config/skills/api-contract/SKILL.md +12 -0
  86. package/global-config/skills/api-contract/references/workflow.md +17 -0
  87. package/global-config/skills/auth-security/SKILL.md +12 -0
  88. package/global-config/skills/auth-security/references/workflow.md +15 -0
  89. package/global-config/skills/bug-diagnosis/SKILL.md +28 -0
  90. package/global-config/skills/change-impact-analysis/SKILL.md +23 -0
  91. package/global-config/skills/code-review/SKILL.md +19 -0
  92. package/global-config/skills/context-engineering/SKILL.md +18 -0
  93. package/global-config/skills/context-engineering/references/large-repo.md +16 -0
  94. package/global-config/skills/database-engineering/SKILL.md +12 -0
  95. package/global-config/skills/database-engineering/references/workflow.md +15 -0
  96. package/global-config/skills/dependency-management/SKILL.md +12 -0
  97. package/global-config/skills/dependency-management/references/workflow.md +14 -0
  98. package/global-config/skills/devops-engineering/SKILL.md +10 -0
  99. package/global-config/skills/devops-engineering/references/workflow.md +11 -0
  100. package/global-config/skills/django-engineering/SKILL.md +10 -0
  101. package/global-config/skills/django-engineering/references/workflow.md +11 -0
  102. package/global-config/skills/documentation-engineering/SKILL.md +10 -0
  103. package/global-config/skills/documentation-engineering/references/workflow.md +15 -0
  104. package/global-config/skills/dotnet-engineering/SKILL.md +10 -0
  105. package/global-config/skills/dotnet-engineering/references/workflow.md +11 -0
  106. package/global-config/skills/ecommerce-engineering/SKILL.md +10 -0
  107. package/global-config/skills/ecommerce-engineering/references/workflow.md +17 -0
  108. package/global-config/skills/engineering-orchestrator/SKILL.md +29 -0
  109. package/global-config/skills/engineering-orchestrator/references/delegation.md +22 -0
  110. package/global-config/skills/engineering-orchestrator/references/evaluator-loop.md +18 -0
  111. package/global-config/skills/engineering-orchestrator/references/long-horizon.md +57 -0
  112. package/global-config/skills/engineering-orchestrator/references/model-escalation.md +19 -0
  113. package/global-config/skills/engineering-orchestrator/references/retry-policy.md +12 -0
  114. package/global-config/skills/engineering-orchestrator/references/routing.md +39 -0
  115. package/global-config/skills/engineering-orchestrator/references/verification-matrix.md +18 -0
  116. package/global-config/skills/fastapi-engineering/SKILL.md +10 -0
  117. package/global-config/skills/fastapi-engineering/references/workflow.md +13 -0
  118. package/global-config/skills/file-upload-engineering/SKILL.md +10 -0
  119. package/global-config/skills/file-upload-engineering/references/workflow.md +17 -0
  120. package/global-config/skills/flutter-engineering/SKILL.md +10 -0
  121. package/global-config/skills/flutter-engineering/references/workflow.md +13 -0
  122. package/global-config/skills/git-safety/SKILL.md +10 -0
  123. package/global-config/skills/git-safety/references/workflow.md +15 -0
  124. package/global-config/skills/implementation-engineer/SKILL.md +10 -0
  125. package/global-config/skills/implementation-engineer/references/workflow.md +14 -0
  126. package/global-config/skills/java-spring-engineering/SKILL.md +10 -0
  127. package/global-config/skills/java-spring-engineering/references/workflow.md +13 -0
  128. package/global-config/skills/long-task-state/SKILL.md +28 -0
  129. package/global-config/skills/long-task-state/references/context-ledger.md +27 -0
  130. package/global-config/skills/long-task-state/templates/STATE.md +48 -0
  131. package/global-config/skills/nestjs-engineering/SKILL.md +10 -0
  132. package/global-config/skills/nestjs-engineering/references/workflow.md +11 -0
  133. package/global-config/skills/nextjs-engineering/SKILL.md +12 -0
  134. package/global-config/skills/nextjs-engineering/references/workflow.md +15 -0
  135. package/global-config/skills/nodejs-engineering/SKILL.md +12 -0
  136. package/global-config/skills/nodejs-engineering/references/workflow.md +11 -0
  137. package/global-config/skills/payment-engineering/SKILL.md +12 -0
  138. package/global-config/skills/payment-engineering/references/workflow.md +19 -0
  139. package/global-config/skills/performance-engineering/SKILL.md +10 -0
  140. package/global-config/skills/performance-engineering/references/workflow.md +17 -0
  141. package/global-config/skills/python-engineering/SKILL.md +10 -0
  142. package/global-config/skills/python-engineering/references/workflow.md +11 -0
  143. package/global-config/skills/react-engineering/SKILL.md +14 -0
  144. package/global-config/skills/react-engineering/references/workflow.md +16 -0
  145. package/global-config/skills/react-native-engineering/SKILL.md +14 -0
  146. package/global-config/skills/react-native-engineering/references/workflow.md +16 -0
  147. package/global-config/skills/repo-explorer/SKILL.md +17 -0
  148. package/global-config/skills/research-verification/SKILL.md +18 -0
  149. package/global-config/skills/research-verification/references/source-hierarchy.md +12 -0
  150. package/global-config/skills/rest-api-design/SKILL.md +10 -0
  151. package/global-config/skills/rest-api-design/references/workflow.md +18 -0
  152. package/global-config/skills/software-architect/SKILL.md +10 -0
  153. package/global-config/skills/software-architect/references/workflow.md +18 -0
  154. package/global-config/skills/task-planner/SKILL.md +21 -0
  155. package/global-config/skills/task-planner/references/plan-schema.md +56 -0
  156. package/global-config/skills/test-driven-development/SKILL.md +22 -0
  157. package/global-config/skills/test-driven-development/references/writing-good-tests.md +21 -0
  158. package/global-config/skills/test-verification/SKILL.md +20 -0
  159. package/global-config/skills/ui-ux-engineering/SKILL.md +10 -0
  160. package/global-config/skills/ui-ux-engineering/references/workflow.md +17 -0
  161. package/global-config/skills/web-security-review/SKILL.md +10 -0
  162. package/global-config/skills/web-security-review/references/workflow.md +22 -0
  163. package/lib/context-manifest.mjs +101 -0
  164. package/lib/control-center.mjs +148 -0
  165. package/lib/eval-auth.mjs +21 -0
  166. package/lib/eval-report.mjs +72 -0
  167. package/lib/eval-telemetry.mjs +155 -0
  168. package/lib/evidence-receipt.mjs +50 -0
  169. package/lib/hermes-bridge.mjs +28 -0
  170. package/lib/ids.mjs +21 -0
  171. package/lib/installer.mjs +636 -0
  172. package/lib/learning-engine.mjs +161 -0
  173. package/lib/model-config.mjs +88 -0
  174. package/lib/model-policy.mjs +71 -0
  175. package/lib/opencode-compat.mjs +86 -0
  176. package/lib/orchestrator-policy.mjs +35 -0
  177. package/lib/process-runner.mjs +117 -0
  178. package/lib/repo-graph.mjs +135 -0
  179. package/lib/repo-inspect.mjs +264 -0
  180. package/lib/review-scope.mjs +98 -0
  181. package/lib/router-config.mjs +40 -0
  182. package/lib/task-engine.mjs +777 -0
  183. package/lib/task-graph.mjs +262 -0
  184. package/lib/update-resolver.mjs +43 -0
  185. package/lib/verification-plan.mjs +52 -0
  186. package/lib/version.mjs +47 -0
  187. package/lib/workspace-snapshot.mjs +45 -0
  188. package/lib/worktree-sandbox.mjs +59 -0
  189. package/package.json +69 -0
  190. package/scripts/check-working-tree.mjs +2 -0
  191. package/scripts/collect-evidence.mjs +2 -0
  192. package/scripts/control-center.mjs +37 -0
  193. package/scripts/detect-stack.mjs +2 -0
  194. package/scripts/detect-test-commands.mjs +2 -0
  195. package/scripts/eval-live.mjs +437 -0
  196. package/scripts/eval-report.mjs +49 -0
  197. package/scripts/eval-router.mjs +45 -0
  198. package/scripts/eval-skills.mjs +71 -0
  199. package/scripts/impact-map.mjs +4 -0
  200. package/scripts/install.mjs +61 -0
  201. package/scripts/repo-map.mjs +2 -0
  202. package/scripts/smoke-packed-install.mjs +326 -0
  203. package/scripts/syntax-check.mjs +35 -0
  204. package/scripts/uninstall.mjs +21 -0
  205. package/scripts/validate-live-suite.mjs +65 -0
  206. package/scripts/validate.mjs +133 -0
@@ -0,0 +1,101 @@
1
+ import { existsSync } from "node:fs"
2
+ import { readFile, stat } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { buildRepoGraph } from "./repo-graph.mjs"
5
+ import { taskFiles } from "./task-graph.mjs"
6
+
7
+ const INSTRUCTION_FILES = ["AGENTS.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle", "build.gradle.kts"]
8
+
9
+ function rel(root, file) {
10
+ return path.relative(root, file).replaceAll("\\", "/")
11
+ }
12
+
13
+ function unique(values) {
14
+ return [...new Set(values.filter(Boolean))]
15
+ }
16
+
17
+ async function fileInfo(root, relative) {
18
+ const full = path.resolve(root, relative)
19
+ if (!full.startsWith(path.resolve(root) + path.sep) && full !== path.resolve(root)) return null
20
+ const info = await stat(full).catch(() => null)
21
+ if (!info?.isFile()) return null
22
+ return { path: rel(root, full), bytes: info.size }
23
+ }
24
+
25
+ async function excerpt(root, relative, limit) {
26
+ const full = path.resolve(root, relative)
27
+ const source = await readFile(full, "utf8").catch(() => "")
28
+ if (!source) return null
29
+ return {
30
+ path: rel(root, full),
31
+ text: source.length <= limit ? source : source.slice(0, limit) + "\n...[truncated]",
32
+ }
33
+ }
34
+
35
+ export async function buildContextManifest(root, task, options = {}) {
36
+ root = path.resolve(root)
37
+ const budget = Math.max(4_000, Number(options.budget ?? 24_000))
38
+ const declared = taskFiles(task)
39
+ const graph = await buildRepoGraph(root, { maxFiles: options.maxFiles ?? 2500 })
40
+ const nodeMap = new Map(graph.nodes.map((node) => [node.path, node]))
41
+ const incoming = new Map()
42
+ for (const edge of graph.edges) {
43
+ const list = incoming.get(edge.to) || []
44
+ list.push(edge.from)
45
+ incoming.set(edge.to, list)
46
+ }
47
+
48
+ const related = []
49
+ for (const file of declared) {
50
+ related.push(...(nodeMap.get(file)?.localImports || []))
51
+ related.push(...(incoming.get(file) || []))
52
+ }
53
+
54
+ const baseNames = declared.map((file) => path.basename(file, path.extname(file)).toLowerCase())
55
+ const tests = graph.nodes
56
+ .map((node) => node.path)
57
+ .filter((file) => /(^|\/)(test|tests|__tests__|spec)(\/|$)|\.(test|spec)\./i.test(file))
58
+ .filter((file) => baseNames.some((name) => name && file.toLowerCase().includes(name)))
59
+ .slice(0, 20)
60
+
61
+ const instructions = []
62
+ for (const name of INSTRUCTION_FILES) {
63
+ if (existsSync(path.join(root, name))) instructions.push(name)
64
+ }
65
+
66
+ const candidates = unique([...declared, ...related.slice(0, 30), ...tests, ...instructions])
67
+ const files = []
68
+ for (const file of candidates) {
69
+ const info = await fileInfo(root, file)
70
+ if (info) files.push(info)
71
+ }
72
+
73
+ let remaining = budget
74
+ const excerpts = []
75
+ for (const file of unique([...declared, ...tests, ...related.slice(0, 12), ...instructions])) {
76
+ if (remaining <= 0) break
77
+ const perFile = Math.min(4_000, remaining)
78
+ const item = await excerpt(root, file, perFile)
79
+ if (!item) continue
80
+ remaining -= item.text.length
81
+ excerpts.push(item)
82
+ }
83
+
84
+ return {
85
+ schemaVersion: 1,
86
+ task: task?.id || null,
87
+ declared,
88
+ related: unique(related).filter((file) => !declared.includes(file)).slice(0, 30),
89
+ tests,
90
+ instructions,
91
+ files,
92
+ excerpts,
93
+ graph: {
94
+ scannedFiles: graph.scannedFiles,
95
+ truncated: graph.truncated,
96
+ hotspots: graph.hotspots.slice(0, 12),
97
+ },
98
+ budget,
99
+ used: budget - remaining,
100
+ }
101
+ }
@@ -0,0 +1,148 @@
1
+ import { existsSync } from "node:fs"
2
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { readLearningState } from "./learning-engine.mjs"
5
+
6
+ function escapeHtml(value) {
7
+ return String(value ?? "")
8
+ .replaceAll("&", "&amp;")
9
+ .replaceAll("<", "&lt;")
10
+ .replaceAll(">", "&gt;")
11
+ .replaceAll('"', "&quot;")
12
+ }
13
+
14
+ async function readJson(file, fallback = null) {
15
+ try { return JSON.parse(await readFile(file, "utf8")) } catch { return fallback }
16
+ }
17
+
18
+ async function collectWork(root) {
19
+ const workRoot = path.join(root, ".ues-work")
20
+ const entries = await readdir(workRoot, { withFileTypes: true }).catch(() => [])
21
+ const items = []
22
+ for (const entry of entries) {
23
+ if (!entry.isDirectory()) continue
24
+ const dir = path.join(workRoot, entry.name)
25
+ const [state, plan, evidence] = await Promise.all([
26
+ readJson(path.join(dir, "STATE.json"), {}),
27
+ readJson(path.join(dir, "PLAN.json"), {}),
28
+ readJson(path.join(dir, "EVIDENCE.json"), {}),
29
+ ])
30
+ items.push({
31
+ slug: entry.name,
32
+ status: state.status || "unknown",
33
+ goal: state.goal || plan.goal || "",
34
+ nextAction: state.nextAction || "",
35
+ blockers: state.blockers || [],
36
+ tasks: (plan.tasks || []).map((task) => ({
37
+ id: task.id,
38
+ title: task.title,
39
+ risk: task.risk || "medium",
40
+ state: state.tasks?.[task.id] || {},
41
+ })),
42
+ receipts: (evidence.receipts || []).length,
43
+ updatedAt: state.updatedAt || null,
44
+ })
45
+ }
46
+ return items.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))
47
+ }
48
+
49
+ async function collectEvalSummary(root) {
50
+ const evalRoot = path.join(root, ".ues-evals")
51
+ const entries = await readdir(evalRoot, { withFileTypes: true }).catch(() => [])
52
+ const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort().slice(-20)
53
+ const rows = []
54
+ for (const name of files) {
55
+ const payload = await readJson(path.join(evalRoot, name), null)
56
+ if (!payload) continue
57
+ rows.push({ file: name, model: payload.model, suite: payload.suite, summary: payload.summary || {} })
58
+ }
59
+ return rows.reverse()
60
+ }
61
+
62
+ export async function collectControlCenterData(root = process.cwd()) {
63
+ root = path.resolve(root)
64
+ const [work, learning, evals] = await Promise.all([
65
+ collectWork(root),
66
+ readLearningState(root),
67
+ collectEvalSummary(root),
68
+ ])
69
+ return {
70
+ schemaVersion: 1,
71
+ generatedAt: new Date().toISOString(),
72
+ root,
73
+ work,
74
+ learning,
75
+ evals,
76
+ }
77
+ }
78
+
79
+ export function renderControlCenter(data) {
80
+ const payload = JSON.stringify(data).replaceAll("<", "\\u003c")
81
+ return `<!doctype html>
82
+ <html lang="en">
83
+ <head>
84
+ <meta charset="utf-8">
85
+ <meta name="viewport" content="width=device-width,initial-scale=1">
86
+ <title>UES Control Center</title>
87
+ <style>
88
+ :root{font-family:Inter,ui-sans-serif,system-ui,sans-serif;color-scheme:dark;background:#0b1020;color:#e7ecf5}
89
+ body{margin:0}.shell{max-width:1200px;margin:auto;padding:28px}.top{display:flex;justify-content:space-between;gap:20px;align-items:end}
90
+ h1{margin:0;font-size:30px}.muted{color:#9da9bd}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px;margin-top:20px}
91
+ .card{background:#121a2c;border:1px solid #26324a;border-radius:14px;padding:16px}.pill{display:inline-block;padding:3px 8px;border:1px solid #3b4966;border-radius:999px;font-size:12px}
92
+ .task{display:grid;grid-template-columns:1fr auto;gap:8px;border-top:1px solid #243049;padding:9px 0}.ok{color:#7ee787}.warn{color:#f2cc60}.bad{color:#ff7b72}
93
+ table{width:100%;border-collapse:collapse;margin-top:10px}th,td{text-align:left;padding:8px;border-bottom:1px solid #26324a;font-size:13px}
94
+ pre{white-space:pre-wrap;word-break:break-word;background:#0a0f1c;padding:10px;border-radius:10px}.section{margin-top:28px}
95
+ </style>
96
+ </head>
97
+ <body><div class="shell">
98
+ <div class="top"><div><h1>UES Control Center</h1><div class="muted" id="root"></div></div><div class="muted" id="generated"></div></div>
99
+ <div class="section"><h2>Long-horizon work</h2><div class="grid" id="work"></div></div>
100
+ <div class="section"><h2>Learning loop</h2><div class="grid" id="learning"></div></div>
101
+ <div class="section"><h2>Recent evaluations</h2><div class="card"><table><thead><tr><th>Suite</th><th>Model</th><th>Baseline</th><th>UES</th></tr></thead><tbody id="evals"></tbody></table></div></div>
102
+ </div>
103
+ <script>
104
+ let data=${payload};
105
+ const esc=(v)=>String(v??"").replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
106
+ function render(next){
107
+ data=next;
108
+ document.querySelector("#root").textContent=data.root;
109
+ document.querySelector("#generated").textContent="Generated "+data.generatedAt;
110
+ const work=document.querySelector("#work");
111
+ work.innerHTML="";
112
+ for(const item of data.work){
113
+ const tasks=item.tasks.map(t=>'<div class="task"><span>'+esc(t.id)+' · '+esc(t.title||'')+'</span><span class="pill">'+esc(t.state.status||'pending')+'</span></div>').join('');
114
+ work.insertAdjacentHTML("beforeend",'<div class="card"><div><span class="pill">'+esc(item.status)+'</span></div><h3>'+esc(item.slug)+'</h3><div class="muted">'+esc(item.goal)+'</div><p>'+esc(item.nextAction)+'</p>'+tasks+'<div class="muted">receipts: '+item.receipts+' · blockers: '+item.blockers.length+'</div></div>');
115
+ }
116
+ if(!data.work.length) work.innerHTML='<div class="card muted">No .ues-work items found.</div>';
117
+ const learning=document.querySelector("#learning");
118
+ learning.innerHTML='<div class="card"><h3>Accepted lessons</h3><strong>'+data.learning.accepted.length+'</strong></div><div class="card"><h3>Proposals</h3><strong>'+data.learning.proposals.length+'</strong></div>';
119
+ const tbody=document.querySelector("#evals");
120
+ tbody.innerHTML="";
121
+ for(const row of data.evals){
122
+ const fmt=(x)=>x?((x.passed||0)+'/'+(x.total||0)):'—';
123
+ tbody.insertAdjacentHTML("beforeend",'<tr><td>'+esc(row.suite)+'</td><td>'+esc(row.model)+'</td><td>'+fmt(row.summary.baseline)+'</td><td>'+fmt(row.summary.ues)+'</td></tr>');
124
+ }
125
+ }
126
+ render(data);
127
+ if(location.protocol==="http:" || location.protocol==="https:"){
128
+ setInterval(async()=>{
129
+ try{
130
+ const response=await fetch("/data.json",{cache:"no-store"});
131
+ if(response.ok) render(await response.json());
132
+ }catch{}
133
+ },3000);
134
+ }
135
+ </script></body></html>`
136
+ }
137
+
138
+ export async function writeControlCenter(root = process.cwd(), outputDir = null) {
139
+ root = path.resolve(root)
140
+ const dir = path.resolve(outputDir || path.join(root, ".ues-dashboard"))
141
+ await mkdir(dir, { recursive: true })
142
+ const data = await collectControlCenterData(root)
143
+ const html = renderControlCenter(data)
144
+ const file = path.join(dir, "index.html")
145
+ await writeFile(file, html, "utf8")
146
+ await writeFile(path.join(dir, "data.json"), JSON.stringify(data, null, 2) + "\n", "utf8")
147
+ return { dir, file, data }
148
+ }
@@ -0,0 +1,21 @@
1
+ import { existsSync } from "node:fs"
2
+ import { copyFile, mkdir } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+
6
+ export function currentOpenCodeAuthPath(home = os.homedir()) {
7
+ return path.join(home, ".local", "share", "opencode", "auth.json")
8
+ }
9
+
10
+ export async function copyCurrentOpenCodeAuth(dataRoot, options = {}) {
11
+ const source = options.source || currentOpenCodeAuthPath(options.home)
12
+ const target = path.join(dataRoot, "opencode", "auth.json")
13
+
14
+ if (!existsSync(source)) {
15
+ return { copied: false, source, target, reason: "not-found" }
16
+ }
17
+
18
+ await mkdir(path.dirname(target), { recursive: true })
19
+ await copyFile(source, target)
20
+ return { copied: true, source, target }
21
+ }
@@ -0,0 +1,72 @@
1
+ export function summarizeEvalResults(results) {
2
+ const modes = {}
3
+ for (const item of results) {
4
+ const mode = item.mode || "unknown"
5
+ const bucket = modes[mode] ??= {
6
+ passed: 0,
7
+ total: 0,
8
+ durationMs: 0,
9
+ toolCalls: 0,
10
+ toolSamples: 0,
11
+ tokens: 0,
12
+ tokenSamples: 0,
13
+ cost: 0,
14
+ costSamples: 0,
15
+ }
16
+ bucket.total += 1
17
+ if (item.passed) bucket.passed += 1
18
+ bucket.durationMs += Number(item.durationMs) || 0
19
+
20
+ if ((item.telemetry?.jsonLines || 0) > 0) {
21
+ bucket.toolCalls += Number(item.telemetry?.toolCalls) || 0
22
+ bucket.toolSamples += 1
23
+ }
24
+ if ((item.telemetry?.usageSamples || 0) > 0) {
25
+ bucket.tokens += Number(item.telemetry?.tokens?.total) || 0
26
+ bucket.tokenSamples += 1
27
+ }
28
+ if ((item.telemetry?.costSamples || 0) > 0) {
29
+ bucket.cost += Number(item.telemetry?.cost) || 0
30
+ bucket.costSamples += 1
31
+ }
32
+ }
33
+
34
+ for (const bucket of Object.values(modes)) {
35
+ bucket.passRate = bucket.total ? bucket.passed / bucket.total : 0
36
+ bucket.avgDurationMs = bucket.total ? bucket.durationMs / bucket.total : 0
37
+ bucket.avgToolCalls = bucket.toolSamples ? bucket.toolCalls / bucket.toolSamples : null
38
+ bucket.avgTokens = bucket.tokenSamples ? bucket.tokens / bucket.tokenSamples : null
39
+ bucket.avgCost = bucket.costSamples ? bucket.cost / bucket.costSamples : null
40
+ bucket.telemetryCoverage = {
41
+ tools: bucket.total ? bucket.toolSamples / bucket.total : 0,
42
+ tokens: bucket.total ? bucket.tokenSamples / bucket.total : 0,
43
+ cost: bucket.total ? bucket.costSamples / bucket.total : 0,
44
+ }
45
+ delete bucket.durationMs
46
+ delete bucket.toolCalls
47
+ delete bucket.toolSamples
48
+ delete bucket.tokens
49
+ delete bucket.tokenSamples
50
+ delete bucket.cost
51
+ delete bucket.costSamples
52
+ }
53
+
54
+ const byTaskMap = new Map()
55
+ for (const item of results) {
56
+ const task = byTaskMap.get(item.task) || {}
57
+ const bucket = task[item.mode] || { passed: 0, total: 0 }
58
+ bucket.total += 1
59
+ if (item.passed) bucket.passed += 1
60
+ bucket.passRate = bucket.passed / bucket.total
61
+ task[item.mode] = bucket
62
+ byTaskMap.set(item.task, task)
63
+ }
64
+
65
+ const baseline = modes.baseline?.passRate
66
+ const ues = modes.ues?.passRate
67
+ return {
68
+ modes,
69
+ passRateDelta: typeof baseline === "number" && typeof ues === "number" ? ues - baseline : null,
70
+ byTask: Object.fromEntries([...byTaskMap.entries()].sort(([a], [b]) => a.localeCompare(b))),
71
+ }
72
+ }
@@ -0,0 +1,155 @@
1
+ function asNumber(value) {
2
+ return typeof value === "number" && Number.isFinite(value) ? value : 0
3
+ }
4
+
5
+ function normalizedToolName(value) {
6
+ return typeof value === "string" ? value.trim().toLowerCase() : ""
7
+ }
8
+
9
+ function visit(value, callback, path = []) {
10
+ if (!value || typeof value !== "object") return
11
+ callback(value, path)
12
+ if (Array.isArray(value)) {
13
+ value.forEach((item, index) => visit(item, callback, [...path, index]))
14
+ return
15
+ }
16
+ for (const [key, child] of Object.entries(value)) {
17
+ if (child && typeof child === "object") visit(child, callback, [...path, key])
18
+ }
19
+ }
20
+
21
+ function usageFromObject(object) {
22
+ const usage = object?.usage
23
+ if (!usage || typeof usage !== "object") return null
24
+
25
+ const input =
26
+ asNumber(usage.inputTokens) ||
27
+ asNumber(usage.input_tokens) ||
28
+ asNumber(usage.promptTokens) ||
29
+ asNumber(usage.prompt_tokens) ||
30
+ asNumber(usage.input)
31
+
32
+ const output =
33
+ asNumber(usage.outputTokens) ||
34
+ asNumber(usage.output_tokens) ||
35
+ asNumber(usage.completionTokens) ||
36
+ asNumber(usage.completion_tokens) ||
37
+ asNumber(usage.output)
38
+
39
+ const total =
40
+ asNumber(usage.totalTokens) ||
41
+ asNumber(usage.total_tokens) ||
42
+ asNumber(usage.total) ||
43
+ input + output
44
+
45
+ if (input === 0 && output === 0 && total === 0) return null
46
+ return { input, output, total }
47
+ }
48
+
49
+ function toolCandidate(object) {
50
+ const type = String(object.type || object.kind || object.event || "").toLowerCase()
51
+ let name = normalizedToolName(object.tool || object.toolName || object.tool_name)
52
+
53
+ if (!name && type.includes("tool")) {
54
+ name = normalizedToolName(object.name)
55
+ }
56
+ if (!name) return null
57
+
58
+ const input =
59
+ object.input && typeof object.input === "object" ? object.input :
60
+ object.args && typeof object.args === "object" ? object.args :
61
+ object.arguments && typeof object.arguments === "object" ? object.arguments :
62
+ {}
63
+
64
+ const id =
65
+ object.callID || object.callId || object.toolCallID || object.toolCallId ||
66
+ object.id || null
67
+
68
+ return { name, input, id: id == null ? null : String(id) }
69
+ }
70
+
71
+ export function parseOpenCodeTelemetry(stdout) {
72
+ const lines = String(stdout || "").split(/\r?\n/).filter((line) => line.trim())
73
+ const parsed = []
74
+ let parseErrors = 0
75
+
76
+ for (const line of lines) {
77
+ try {
78
+ parsed.push(JSON.parse(line))
79
+ } catch {
80
+ parseErrors += 1
81
+ }
82
+ }
83
+
84
+ const toolKeys = new Set()
85
+ const tools = new Map()
86
+ const skills = new Set()
87
+ const subagents = new Set()
88
+ const tokens = { input: 0, output: 0, total: 0 }
89
+ let cost = 0
90
+ let usageSamples = 0
91
+ let costSamples = 0
92
+
93
+ parsed.forEach((event, eventIndex) => {
94
+ let eventUsage = null
95
+ let eventCost = null
96
+
97
+ visit(event, (object, objectPath) => {
98
+ const tool = toolCandidate(object)
99
+ if (tool) {
100
+ const fallbackKey = `${eventIndex}:${objectPath.join(".")}:${tool.name}`
101
+ const key = tool.id ? `${tool.name}:${tool.id}` : fallbackKey
102
+ if (!toolKeys.has(key)) {
103
+ toolKeys.add(key)
104
+ tools.set(tool.name, (tools.get(tool.name) || 0) + 1)
105
+
106
+ if (tool.name === "skill") {
107
+ const id = tool.input.id || tool.input.skill || tool.input.name
108
+ if (typeof id === "string" && id) skills.add(id)
109
+ }
110
+
111
+ if (tool.name === "task" || tool.name === "subagent") {
112
+ const id = tool.input.agent || tool.input.agentID || tool.input.agentId || tool.input.id
113
+ if (typeof id === "string" && id) subagents.add(id)
114
+ }
115
+ }
116
+ }
117
+
118
+ if (!eventUsage) eventUsage = usageFromObject(object)
119
+ if (eventCost === null) {
120
+ for (const key of ["cost", "totalCost", "total_cost"]) {
121
+ if (typeof object[key] === "number" && Number.isFinite(object[key])) {
122
+ eventCost = object[key]
123
+ break
124
+ }
125
+ }
126
+ }
127
+ })
128
+
129
+ if (eventUsage) {
130
+ usageSamples += 1
131
+ tokens.input += eventUsage.input
132
+ tokens.output += eventUsage.output
133
+ tokens.total += eventUsage.total
134
+ }
135
+ if (eventCost !== null) {
136
+ costSamples += 1
137
+ cost += eventCost
138
+ }
139
+ })
140
+
141
+ return {
142
+ schemaVersion: 1,
143
+ format: "best-effort-opencode-jsonl",
144
+ jsonLines: parsed.length,
145
+ parseErrors,
146
+ toolCalls: [...tools.values()].reduce((sum, value) => sum + value, 0),
147
+ tools: Object.fromEntries([...tools.entries()].sort(([a], [b]) => a.localeCompare(b))),
148
+ skillsLoaded: [...skills].sort(),
149
+ subagents: [...subagents].sort(),
150
+ tokens,
151
+ usageSamples,
152
+ cost,
153
+ costSamples,
154
+ }
155
+ }
@@ -0,0 +1,50 @@
1
+ import { createHash, randomUUID } from "node:crypto"
2
+
3
+ function digest(value) {
4
+ return createHash("sha256").update(String(value || "")).digest("hex")
5
+ }
6
+
7
+ export function createVerificationReceipt(input = {}) {
8
+ const command = String(input.command || "").trim()
9
+ if (!command) throw new Error("verification receipt command is required")
10
+
11
+ const exitCode = Number(input.exitCode)
12
+ if (!Number.isInteger(exitCode)) throw new Error("verification receipt exitCode must be an integer")
13
+
14
+ const startedAt = String(input.startedAt || new Date().toISOString())
15
+ const finishedAt = String(input.finishedAt || new Date().toISOString())
16
+
17
+ return {
18
+ schemaVersion: 1,
19
+ id: input.id || randomUUID(),
20
+ task: input.task || null,
21
+ runId: input.runId || null,
22
+ command,
23
+ args: Array.isArray(input.args) ? input.args.map(String) : [],
24
+ cwd: input.cwd || null,
25
+ exitCode,
26
+ passed: exitCode === 0,
27
+ startedAt,
28
+ finishedAt,
29
+ durationMs: Math.max(0, Number(input.durationMs) || 0),
30
+ stdoutSha256: digest(input.stdout),
31
+ stderrSha256: digest(input.stderr),
32
+ workspaceBefore: input.workspaceBefore || null,
33
+ workspaceAfter: input.workspaceAfter || null,
34
+ }
35
+ }
36
+
37
+ export function validateVerificationReceipt(receipt) {
38
+ const errors = []
39
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
40
+ return { valid: false, errors: ["receipt must be an object"] }
41
+ }
42
+ if (receipt.schemaVersion !== 1) errors.push("schemaVersion must be 1")
43
+ if (!String(receipt.id || "").trim()) errors.push("id is required")
44
+ if (!String(receipt.command || "").trim()) errors.push("command is required")
45
+ if (!Number.isInteger(receipt.exitCode)) errors.push("exitCode must be an integer")
46
+ if (typeof receipt.passed !== "boolean") errors.push("passed must be boolean")
47
+ if (!String(receipt.stdoutSha256 || "").match(/^[a-f0-9]{64}$/)) errors.push("stdoutSha256 is invalid")
48
+ if (!String(receipt.stderrSha256 || "").match(/^[a-f0-9]{64}$/)) errors.push("stderrSha256 is invalid")
49
+ return { valid: errors.length === 0, errors }
50
+ }
@@ -0,0 +1,28 @@
1
+ import { spawnSync } from "node:child_process"
2
+
3
+ export function hermesStatus() {
4
+ const result = spawnSync("hermes", ["--version"], { encoding: "utf8", shell: process.platform === "win32" })
5
+ return {
6
+ available: result.status === 0,
7
+ version: result.status === 0 ? String(result.stdout || result.stderr || "").trim() : null,
8
+ error: result.status === 0 ? null : String(result.stderr || result.stdout || "Hermes CLI not found").trim(),
9
+ }
10
+ }
11
+
12
+ export function buildHermesDelegationPrompt(contextPack) {
13
+ return [
14
+ "You are an optional external executor for a UES task.",
15
+ "Implement exactly the approved task described below.",
16
+ "Do not broaden scope, merge, push, publish, deploy, or alter durable UES state.",
17
+ "Run the declared verification and return a concise structured report.",
18
+ "",
19
+ JSON.stringify(contextPack, null, 2),
20
+ ].join("\n")
21
+ }
22
+
23
+
24
+ export function hermesOneShotArgs(prompt) {
25
+ const text = String(prompt || "").trim()
26
+ if (!text) throw new Error("Hermes one-shot prompt is required")
27
+ return ["chat", "-q", text]
28
+ }
package/lib/ids.mjs ADDED
@@ -0,0 +1,21 @@
1
+ export const SKILL_PREFIX = "ues-"
2
+
3
+ const RESOURCE_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
4
+ const MANAGED_SKILL_PATTERN = /^ues-[a-z0-9]+(?:-[a-z0-9]+)*$/
5
+ const MANAGED_MARKDOWN_PATTERN = /^ues-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/
6
+
7
+ function testPattern(pattern, value) {
8
+ return typeof value === "string" && pattern.test(value)
9
+ }
10
+
11
+ export function validResourceID(value) {
12
+ return testPattern(RESOURCE_ID_PATTERN, value)
13
+ }
14
+
15
+ export function validSkillID(value) {
16
+ return testPattern(MANAGED_SKILL_PATTERN, value)
17
+ }
18
+
19
+ export function validManagedMarkdown(value) {
20
+ return testPattern(MANAGED_MARKDOWN_PATTERN, value)
21
+ }