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,636 @@
1
+ import { cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"
2
+ import { existsSync } from "node:fs"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+ import { SKILL_PREFIX, validManagedMarkdown, validResourceID, validSkillID } from "./ids.mjs"
7
+ import { adaptAgentForOpenCode, detectOpenCodeMajor } from "./opencode-compat.mjs"
8
+ import { applyConfiguredModel, readModelPolicy } from "./model-config.mjs"
9
+
10
+ export { SKILL_PREFIX } from "./ids.mjs"
11
+
12
+ export const PACKAGE_NAME = "opencode-agent-skill"
13
+ export const LEGACY_PACKAGE_NAME = "@laivannha0202/opencode-agent-skill"
14
+ export const MANAGED_MARKER = "<!-- managed-by: @laivannha0202/opencode-agent-skill -->"
15
+ export const AGENTS_BEGIN = "<!-- BEGIN OCSKILL UNIVERSAL ENGINEERING SYSTEM -->"
16
+ export const AGENTS_END = "<!-- END OCSKILL UNIVERSAL ENGINEERING SYSTEM -->"
17
+
18
+ const PACKAGE_ROOT = fileURLToPath(new URL("../", import.meta.url))
19
+ const ROUTER_PLUGIN_STATE = "ues-router/index.js"
20
+
21
+ function validManagedPlugin(value) {
22
+ return value === ROUTER_PLUGIN_STATE
23
+ }
24
+
25
+ function isOwnedPackageName(value) {
26
+ return !value || value === PACKAGE_NAME || value === LEGACY_PACKAGE_NAME
27
+ }
28
+
29
+ export function getConfigDir() {
30
+ return process.env.OPENCODE_CONFIG_DIR
31
+ ? path.resolve(process.env.OPENCODE_CONFIG_DIR)
32
+ : path.join(os.homedir(), ".config", "opencode")
33
+ }
34
+
35
+ async function readText(file, fallback = "") {
36
+ try {
37
+ return await readFile(file, "utf8")
38
+ } catch (error) {
39
+ if (error && error.code === "ENOENT") return fallback
40
+ throw error
41
+ }
42
+ }
43
+
44
+ async function packageVersion() {
45
+ const json = JSON.parse(await readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8"))
46
+ return json.version
47
+ }
48
+
49
+ function installSkillName(source, name) {
50
+ const next = source.replace(/^name:\s*[^\r\n]+/m, `name: ${SKILL_PREFIX}${name}`)
51
+ return next.includes(MANAGED_MARKER)
52
+ ? next
53
+ : `${next.trimEnd()}\n\n${MANAGED_MARKER}\n`
54
+ }
55
+
56
+ function removeManagedBlock(source) {
57
+ const start = source.indexOf(AGENTS_BEGIN)
58
+ if (start < 0) return source
59
+
60
+ const end = source.indexOf(AGENTS_END, start)
61
+ if (end < 0) return source
62
+
63
+ const after = end + AGENTS_END.length
64
+ return (source.slice(0, start) + source.slice(after))
65
+ .replace(/\n{3,}/g, "\n\n")
66
+ .trim()
67
+ }
68
+
69
+ async function writeManagedFile(file, content, warnings) {
70
+ if (existsSync(file)) {
71
+ const current = await readText(file)
72
+ if (!current.includes(MANAGED_MARKER)) {
73
+ warnings.push(`Skipped existing unmanaged file: ${file}`)
74
+ return false
75
+ }
76
+ }
77
+
78
+ await mkdir(path.dirname(file), { recursive: true })
79
+ await writeFile(file, content, "utf8")
80
+ return true
81
+ }
82
+
83
+ async function readManagedState(file, warnings = []) {
84
+ const raw = await readText(file)
85
+ if (!raw) return { kind: "empty", state: { skills: [], commands: [], agents: [], plugins: [] } }
86
+
87
+ try {
88
+ const parsed = JSON.parse(raw)
89
+ if (parsed.package && !isOwnedPackageName(parsed.package)) {
90
+ warnings.push(`Ignored state owned by another package: ${parsed.package}`)
91
+ return {
92
+ kind: "foreign",
93
+ package: parsed.package,
94
+ state: { skills: [], commands: [], agents: [], plugins: [] },
95
+ }
96
+ }
97
+ return {
98
+ kind: "managed",
99
+ state: {
100
+ ...parsed,
101
+ skills: Array.isArray(parsed.skills) ? parsed.skills.filter(validSkillID) : [],
102
+ commands: Array.isArray(parsed.commands) ? parsed.commands.filter(validManagedMarkdown) : [],
103
+ agents: Array.isArray(parsed.agents) ? parsed.agents.filter(validManagedMarkdown) : [],
104
+ plugins: Array.isArray(parsed.plugins) ? parsed.plugins.filter(validManagedPlugin) : [],
105
+ },
106
+ }
107
+ } catch {
108
+ warnings.push(`Invalid UES state file; run 'ocskill install' to rebuild it: ${file}`)
109
+ return { kind: "invalid", state: { skills: [], commands: [], agents: [], plugins: [] } }
110
+ }
111
+ }
112
+
113
+ async function cleanupStaleManaged(configDir, previousState, installed, warnings) {
114
+ const currentSkills = new Set(installed.skills)
115
+ const currentCommands = new Set(installed.commands)
116
+ const currentAgents = new Set(installed.agents)
117
+ const currentPlugins = new Set(installed.plugins || [])
118
+
119
+ for (const id of previousState.skills || []) {
120
+ if (currentSkills.has(id) || !validSkillID(id)) continue
121
+ const dir = path.join(configDir, "skills", id)
122
+ const markerFile = path.join(dir, "SKILL.md")
123
+ const current = await readText(markerFile)
124
+ if (current.includes(MANAGED_MARKER)) {
125
+ await rm(dir, { recursive: true, force: true })
126
+ } else if (existsSync(dir)) {
127
+ warnings.push(`Preserved stale skill without UES marker: ${dir}`)
128
+ }
129
+ }
130
+
131
+ for (const name of previousState.commands || []) {
132
+ if (currentCommands.has(name) || !validManagedMarkdown(name)) continue
133
+ const file = path.join(configDir, "commands", name)
134
+ const current = await readText(file)
135
+ if (current.includes(MANAGED_MARKER)) {
136
+ await rm(file, { force: true })
137
+ } else if (existsSync(file)) {
138
+ warnings.push(`Preserved stale command without UES marker: ${file}`)
139
+ }
140
+ }
141
+
142
+ for (const name of previousState.agents || []) {
143
+ if (currentAgents.has(name) || !validManagedMarkdown(name)) continue
144
+ const file = path.join(configDir, "agents", name)
145
+ const current = await readText(file)
146
+ if (current.includes(MANAGED_MARKER)) {
147
+ await rm(file, { force: true })
148
+ } else if (existsSync(file)) {
149
+ warnings.push(`Preserved stale subagent without UES marker: ${file}`)
150
+ }
151
+ }
152
+
153
+ for (const name of previousState.plugins || []) {
154
+ if (currentPlugins.has(name) || !validManagedPlugin(name)) continue
155
+ const file = path.join(configDir, "plugins", name)
156
+ const current = await readText(file)
157
+ if (current.includes(MANAGED_MARKER)) {
158
+ await rm(path.dirname(file), { recursive: true, force: true })
159
+ } else if (existsSync(file)) {
160
+ warnings.push(`Preserved stale plugin without UES marker: ${file}`)
161
+ }
162
+ }
163
+ }
164
+
165
+ async function scanUntrackedManaged(configDir) {
166
+ const found = { skills: [], commands: [], agents: [], plugins: [] }
167
+
168
+ const skillsDir = path.join(configDir, "skills")
169
+ for (const entry of await readdir(skillsDir, { withFileTypes: true }).catch(() => [])) {
170
+ if (!entry.isDirectory() || !validSkillID(entry.name)) continue
171
+ const current = await readText(path.join(skillsDir, entry.name, "SKILL.md"))
172
+ if (current.includes(MANAGED_MARKER)) found.skills.push(entry.name)
173
+ }
174
+
175
+ for (const [targetDir, key] of [
176
+ [path.join(configDir, "commands"), "commands"],
177
+ [path.join(configDir, "agents"), "agents"],
178
+ ]) {
179
+ for (const entry of await readdir(targetDir, { withFileTypes: true }).catch(() => [])) {
180
+ if (!entry.isFile() || !validManagedMarkdown(entry.name)) continue
181
+ const current = await readText(path.join(targetDir, entry.name))
182
+ if (current.includes(MANAGED_MARKER)) found[key].push(entry.name)
183
+ }
184
+ }
185
+
186
+ const routerFile = path.join(configDir, "plugins", ROUTER_PLUGIN_STATE)
187
+ const routerCurrent = await readText(routerFile)
188
+ if (routerCurrent.includes(MANAGED_MARKER)) found.plugins.push(ROUTER_PLUGIN_STATE)
189
+
190
+ return found
191
+ }
192
+
193
+ async function warnUntrackedManaged(configDir, installed, warnings) {
194
+ const currentSkills = new Set(installed.skills)
195
+ const currentCommands = new Set(installed.commands)
196
+ const currentAgents = new Set(installed.agents)
197
+ const currentPlugins = new Set(installed.plugins || [])
198
+ const untracked = await scanUntrackedManaged(configDir)
199
+
200
+ for (const id of untracked.skills) {
201
+ if (!currentSkills.has(id)) {
202
+ warnings.push(`Untracked managed skill left over from an earlier install: ${id}`)
203
+ }
204
+ }
205
+ for (const name of untracked.commands) {
206
+ if (!currentCommands.has(name)) {
207
+ warnings.push(`Untracked managed command left over from an earlier install: ${name}`)
208
+ }
209
+ }
210
+ for (const name of untracked.agents) {
211
+ if (!currentAgents.has(name)) {
212
+ warnings.push(`Untracked managed subagent left over from an earlier install: ${name}`)
213
+ }
214
+ }
215
+ for (const name of untracked.plugins || []) {
216
+ if (!currentPlugins.has(name)) {
217
+ warnings.push(`Untracked managed plugin left over from an earlier install: ${name}`)
218
+ }
219
+ }
220
+ }
221
+
222
+ async function removeManagedAgentsBlock(configDir) {
223
+ const globalAgentsFile = path.join(configDir, "AGENTS.md")
224
+ const existingAgents = await readText(globalAgentsFile)
225
+ if (existingAgents.includes(AGENTS_BEGIN)) {
226
+ const clean = removeManagedBlock(existingAgents)
227
+ if (clean) await writeFile(globalAgentsFile, clean + "\n", "utf8")
228
+ else await rm(globalAgentsFile, { force: true })
229
+ }
230
+ }
231
+
232
+ async function installSkillDirectory(sourceDir, targetDir, sourceName, warnings) {
233
+ const targetSkill = path.join(targetDir, "SKILL.md")
234
+
235
+ if (existsSync(targetDir)) {
236
+ if (!existsSync(targetSkill)) {
237
+ warnings.push(`Skipped existing unmanaged skill directory: ${targetDir}`)
238
+ return false
239
+ }
240
+
241
+ const current = await readText(targetSkill)
242
+ if (!current.includes(MANAGED_MARKER)) {
243
+ warnings.push(`Skipped existing unmanaged skill: ${targetSkill}`)
244
+ return false
245
+ }
246
+
247
+ await rm(targetDir, { recursive: true, force: true })
248
+ }
249
+
250
+ await mkdir(path.dirname(targetDir), { recursive: true })
251
+ await cp(sourceDir, targetDir, { recursive: true })
252
+
253
+ const copiedSkill = await readFile(targetSkill, "utf8")
254
+ await writeFile(targetSkill, installSkillName(copiedSkill, sourceName), "utf8")
255
+ return true
256
+ }
257
+
258
+ async function installPluginDirectory(sourceDir, targetDir, warnings) {
259
+ const targetEntry = path.join(targetDir, "index.js")
260
+
261
+ if (existsSync(targetDir)) {
262
+ if (!existsSync(targetEntry)) {
263
+ warnings.push(`Skipped existing unmanaged plugin directory: ${targetDir}`)
264
+ return false
265
+ }
266
+
267
+ const current = await readText(targetEntry)
268
+ if (!current.includes(MANAGED_MARKER)) {
269
+ warnings.push(`Skipped existing unmanaged plugin: ${targetEntry}`)
270
+ return false
271
+ }
272
+
273
+ await rm(targetDir, { recursive: true, force: true })
274
+ }
275
+
276
+ await mkdir(path.dirname(targetDir), { recursive: true })
277
+ await cp(sourceDir, targetDir, { recursive: true })
278
+
279
+ const copiedEntry = await readFile(targetEntry, "utf8")
280
+ await writeFile(
281
+ targetEntry,
282
+ copiedEntry.includes(MANAGED_MARKER)
283
+ ? copiedEntry
284
+ : `${copiedEntry.trimEnd()}\n\n// ${MANAGED_MARKER}\n`,
285
+ "utf8",
286
+ )
287
+ return true
288
+ }
289
+
290
+ export async function installResources(options = {}) {
291
+ const sourceRoot = options.sourceRoot ?? PACKAGE_ROOT
292
+ const configDir = getConfigDir()
293
+ const skillsTarget = path.join(configDir, "skills")
294
+ const commandsTarget = path.join(configDir, "commands")
295
+ const agentsTarget = path.join(configDir, "agents")
296
+ const pluginsTarget = path.join(configDir, "plugins")
297
+ const stateDir = path.join(configDir, ".ues")
298
+ const stateFile = path.join(stateDir, "state.json")
299
+ const warnings = []
300
+ const openCodeMajor = options.openCodeMajor ?? detectOpenCodeMajor()
301
+ const installed = { skills: [], commands: [], agents: [], plugins: [] }
302
+ const previous = await readManagedState(stateFile, warnings)
303
+ const previousState = previous.state
304
+ const modelPolicy = await readModelPolicy(configDir)
305
+
306
+ if (previous.kind === "foreign") {
307
+ if (options.force) {
308
+ const backupFile = `${stateFile}.foreign-${Date.now()}`
309
+ warnings.push(
310
+ `Backed up state owned by ${previous.package} before re-owning: ${backupFile}`,
311
+ )
312
+ await cp(stateFile, backupFile)
313
+ } else {
314
+ warnings.push(`Skipped install: state is owned by ${previous.package}`)
315
+ return {
316
+ configDir,
317
+ version: await packageVersion(),
318
+ stateError: `State belongs to another package: ${previous.package}`,
319
+ skills: [],
320
+ commands: [],
321
+ agents: [],
322
+ plugins: [],
323
+ warnings,
324
+ }
325
+ }
326
+ }
327
+
328
+ if (previous.kind === "invalid") {
329
+ const backupFile = `${stateFile}.invalid-${Date.now()}`
330
+ warnings.push(`Backed up unreadable state file before rebuilding: ${backupFile}`)
331
+ await cp(stateFile, backupFile)
332
+ }
333
+
334
+ await mkdir(skillsTarget, { recursive: true })
335
+ await mkdir(commandsTarget, { recursive: true })
336
+ await mkdir(agentsTarget, { recursive: true })
337
+ await mkdir(pluginsTarget, { recursive: true })
338
+ await mkdir(stateDir, { recursive: true })
339
+
340
+ const sourceSkills = path.join(sourceRoot, "global-config", "skills")
341
+ const skillEntries = await readdir(sourceSkills, { withFileTypes: true })
342
+
343
+ for (const entry of skillEntries) {
344
+ if (!entry.isDirectory()) continue
345
+
346
+ const sourceDir = path.join(sourceSkills, entry.name)
347
+ const targetID = `${SKILL_PREFIX}${entry.name}`
348
+ const targetDir = path.join(skillsTarget, targetID)
349
+
350
+ if (await installSkillDirectory(sourceDir, targetDir, entry.name, warnings)) {
351
+ installed.skills.push(targetID)
352
+ }
353
+ }
354
+
355
+ const sourceCommands = path.join(sourceRoot, "global-config", "commands")
356
+ const commandEntries = await readdir(sourceCommands, { withFileTypes: true })
357
+
358
+ for (const entry of commandEntries) {
359
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue
360
+
361
+ const id = entry.name.slice(0, -3)
362
+ if (!validResourceID(id)) {
363
+ warnings.push(`Skipped command with invalid resource ID: ${id}`)
364
+ continue
365
+ }
366
+ const targetName = `${SKILL_PREFIX}${id}.md`
367
+ const targetFile = path.join(commandsTarget, targetName)
368
+ const source = await readFile(path.join(sourceCommands, entry.name), "utf8")
369
+ const content = source.includes(MANAGED_MARKER)
370
+ ? source
371
+ : `${source.trimEnd()}\n\n${MANAGED_MARKER}\n`
372
+
373
+ if (await writeManagedFile(targetFile, content, warnings)) {
374
+ installed.commands.push(targetName)
375
+ }
376
+ }
377
+
378
+ const sourceAgents = path.join(sourceRoot, "global-config", "agents")
379
+ const agentEntries = await readdir(sourceAgents, { withFileTypes: true })
380
+
381
+ for (const entry of agentEntries) {
382
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue
383
+
384
+ const id = entry.name.slice(0, -3)
385
+ if (!validResourceID(id)) {
386
+ warnings.push(`Skipped subagent with invalid resource ID: ${id}`)
387
+ continue
388
+ }
389
+ const targetName = `${SKILL_PREFIX}${id}.md`
390
+ const targetFile = path.join(agentsTarget, targetName)
391
+ const source = applyConfiguredModel(
392
+ adaptAgentForOpenCode(
393
+ await readFile(path.join(sourceAgents, entry.name), "utf8"),
394
+ openCodeMajor,
395
+ ),
396
+ id,
397
+ modelPolicy,
398
+ )
399
+ const content = source.includes(MANAGED_MARKER)
400
+ ? source
401
+ : `${source.trimEnd()}\n\n${MANAGED_MARKER}\n`
402
+
403
+ if (await writeManagedFile(targetFile, content, warnings)) {
404
+ installed.agents.push(targetName)
405
+ }
406
+ }
407
+
408
+ if (openCodeMajor >= 2) {
409
+ const sourcePluginDir = path.join(sourceRoot, "global-config", "plugins", "ues-router")
410
+ const targetPluginDir = path.join(pluginsTarget, "ues-router")
411
+ if (existsSync(path.join(sourcePluginDir, "index.js"))) {
412
+ if (await installPluginDirectory(sourcePluginDir, targetPluginDir, warnings)) {
413
+ installed.plugins.push(ROUTER_PLUGIN_STATE)
414
+ }
415
+ }
416
+
417
+ const routerConfigFile = path.join(stateDir, "router.json")
418
+ if (!existsSync(routerConfigFile)) {
419
+ await writeFile(
420
+ routerConfigFile,
421
+ JSON.stringify({ enabled: true, maxSkills: 4 }, null, 2) + "\n",
422
+ "utf8",
423
+ )
424
+ }
425
+ }
426
+
427
+ const globalAgentsFile = path.join(configDir, "AGENTS.md")
428
+ const existingAgents = await readText(globalAgentsFile)
429
+ const cleanAgents = removeManagedBlock(existingAgents)
430
+ const workflow = (await readFile(
431
+ path.join(sourceRoot, "global-config", "AGENTS.md"),
432
+ "utf8",
433
+ )).trim()
434
+
435
+ const managedBlock = `${AGENTS_BEGIN}\n${workflow}\n${AGENTS_END}`
436
+ const mergedAgents = [cleanAgents, managedBlock].filter(Boolean).join("\n\n") + "\n"
437
+ await writeFile(globalAgentsFile, mergedAgents, "utf8")
438
+
439
+ await cleanupStaleManaged(configDir, previousState, installed, warnings)
440
+
441
+ if (previous.kind === "invalid" || (previous.kind === "foreign" && options.force)) {
442
+ await warnUntrackedManaged(configDir, installed, warnings)
443
+ }
444
+
445
+ const state = {
446
+ schemaVersion: 2,
447
+ package: PACKAGE_NAME,
448
+ version: await packageVersion(),
449
+ configDir,
450
+ installedAt: new Date().toISOString(),
451
+ openCodeMajor,
452
+ skills: installed.skills.sort(),
453
+ commands: installed.commands.sort(),
454
+ agents: installed.agents.sort(),
455
+ plugins: installed.plugins.sort(),
456
+ }
457
+
458
+ await writeFile(stateFile, JSON.stringify(state, null, 2) + "\n", "utf8")
459
+ return { ...state, warnings }
460
+ }
461
+
462
+ export async function removeResources(options = {}) {
463
+ const configDir = getConfigDir()
464
+ const stateFile = path.join(configDir, ".ues", "state.json")
465
+ const warnings = []
466
+ const previous = await readManagedState(stateFile, warnings)
467
+
468
+ if (options.force && (previous.kind === "foreign" || previous.kind === "invalid")) {
469
+ const backupFile = path.join(
470
+ configDir,
471
+ `state.${previous.kind}-${Date.now()}.backup.json`,
472
+ )
473
+ warnings.push(`Backed up state before forced remove: ${backupFile}`)
474
+ await cp(stateFile, backupFile)
475
+
476
+ const untracked = await scanUntrackedManaged(configDir)
477
+ for (const id of untracked.skills) {
478
+ await rm(path.join(configDir, "skills", id), { recursive: true, force: true })
479
+ }
480
+ for (const [targetDir, key] of [
481
+ [path.join(configDir, "commands"), "commands"],
482
+ [path.join(configDir, "agents"), "agents"],
483
+ ]) {
484
+ for (const name of untracked[key]) {
485
+ await rm(path.join(targetDir, name), { force: true })
486
+ }
487
+ }
488
+ for (const name of untracked.plugins || []) {
489
+ if (!validManagedPlugin(name)) continue
490
+ await rm(path.join(configDir, "plugins", "ues-router"), { recursive: true, force: true })
491
+ }
492
+ const removed = {
493
+ skills: untracked.skills.length,
494
+ commands: untracked.commands.length,
495
+ agents: untracked.agents.length,
496
+ plugins: (untracked.plugins || []).length,
497
+ }
498
+
499
+ await removeManagedAgentsBlock(configDir)
500
+ await rm(path.join(configDir, ".ues"), { recursive: true, force: true })
501
+ return { configDir, ...removed, warnings }
502
+ }
503
+
504
+ const state = previous.state
505
+ const removed = { skills: 0, commands: 0, agents: 0, plugins: 0 }
506
+
507
+ if (previous.kind === "foreign") {
508
+ warnings.push(`Skipped remove: state is owned by ${previous.package}`)
509
+ return {
510
+ configDir,
511
+ stateError: `State belongs to another package: ${previous.package}`,
512
+ ...removed,
513
+ warnings,
514
+ }
515
+ }
516
+
517
+ if (previous.kind === "invalid") {
518
+ warnings.push("Skipped remove: state file is unreadable")
519
+ return {
520
+ configDir,
521
+ stateError: "State file is invalid; refusing to modify it.",
522
+ ...removed,
523
+ warnings,
524
+ }
525
+ }
526
+
527
+ for (const id of state.skills || []) {
528
+ if (!validSkillID(id)) continue
529
+ const dir = path.join(configDir, "skills", id)
530
+ const file = path.join(dir, "SKILL.md")
531
+ const current = await readText(file)
532
+ if (current.includes(MANAGED_MARKER)) {
533
+ await rm(dir, { recursive: true, force: true })
534
+ removed.skills += 1
535
+ }
536
+ }
537
+
538
+ for (const name of state.commands || []) {
539
+ if (!validManagedMarkdown(name)) continue
540
+ const file = path.join(configDir, "commands", name)
541
+ const current = await readText(file)
542
+ if (current.includes(MANAGED_MARKER)) {
543
+ await rm(file, { force: true })
544
+ removed.commands += 1
545
+ }
546
+ }
547
+
548
+ for (const name of state.agents || []) {
549
+ if (!validManagedMarkdown(name)) continue
550
+ const file = path.join(configDir, "agents", name)
551
+ const current = await readText(file)
552
+ if (current.includes(MANAGED_MARKER)) {
553
+ await rm(file, { force: true })
554
+ removed.agents += 1
555
+ }
556
+ }
557
+
558
+ for (const name of state.plugins || []) {
559
+ if (!validManagedPlugin(name)) continue
560
+ const file = path.join(configDir, "plugins", name)
561
+ const current = await readText(file)
562
+ if (current.includes(MANAGED_MARKER)) {
563
+ await rm(path.dirname(file), { recursive: true, force: true })
564
+ removed.plugins += 1
565
+ }
566
+ }
567
+
568
+ await removeManagedAgentsBlock(configDir)
569
+ await rm(path.join(configDir, ".ues"), { recursive: true, force: true })
570
+ return { configDir, ...removed, warnings }
571
+ }
572
+
573
+ export async function getStatus() {
574
+ const configDir = getConfigDir()
575
+ const stateFile = path.join(configDir, ".ues", "state.json")
576
+ const raw = await readText(stateFile)
577
+ if (!raw) return { installed: false, configDir }
578
+
579
+ let state
580
+ try {
581
+ state = JSON.parse(raw)
582
+ } catch {
583
+ return {
584
+ installed: false,
585
+ configDir,
586
+ stateError: "Invalid UES state file; run ocskill install to rebuild it.",
587
+ }
588
+ }
589
+
590
+ if (state.package && !isOwnedPackageName(state.package)) {
591
+ return {
592
+ installed: false,
593
+ configDir,
594
+ stateError: `State belongs to another package: ${state.package}`,
595
+ }
596
+ }
597
+
598
+ state.skills = Array.isArray(state.skills) ? state.skills.filter(validSkillID) : []
599
+ state.commands = Array.isArray(state.commands) ? state.commands.filter(validManagedMarkdown) : []
600
+ state.agents = Array.isArray(state.agents) ? state.agents.filter(validManagedMarkdown) : []
601
+ state.plugins = Array.isArray(state.plugins) ? state.plugins.filter(validManagedPlugin) : []
602
+
603
+ let skillsPresent = 0
604
+ let commandsPresent = 0
605
+ let agentsPresent = 0
606
+ let pluginsPresent = 0
607
+
608
+ for (const id of state.skills || []) {
609
+ if (existsSync(path.join(configDir, "skills", id, "SKILL.md"))) skillsPresent += 1
610
+ }
611
+ for (const name of state.commands || []) {
612
+ if (existsSync(path.join(configDir, "commands", name))) commandsPresent += 1
613
+ }
614
+ for (const name of state.agents || []) {
615
+ if (existsSync(path.join(configDir, "agents", name))) agentsPresent += 1
616
+ }
617
+ for (const name of state.plugins || []) {
618
+ if (existsSync(path.join(configDir, "plugins", name))) pluginsPresent += 1
619
+ }
620
+
621
+ const agents = await readText(path.join(configDir, "AGENTS.md"))
622
+
623
+ return {
624
+ installed: true,
625
+ ...state,
626
+ skillsPresent,
627
+ commandsPresent,
628
+ agentsPresent,
629
+ pluginsPresent,
630
+ workflowPresent: agents.includes(AGENTS_BEGIN),
631
+ }
632
+ }
633
+
634
+ export async function getPackageVersion() {
635
+ return packageVersion()
636
+ }