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,264 @@
1
+ import { existsSync } from "node:fs"
2
+ import { readFile, readdir, stat } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { spawnSync } from "node:child_process"
5
+
6
+ const SKIP_DIRS = new Set([
7
+ ".git", "node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
8
+ "Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "bin", "obj",
9
+ ])
10
+
11
+ const TEXT_EXTENSIONS = new Set([
12
+ ".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".json", ".jsonc", ".md", ".py",
13
+ ".java", ".kt", ".kts", ".cs", ".go", ".rs", ".rb", ".php", ".vue", ".svelte",
14
+ ".yml", ".yaml", ".toml", ".xml", ".gradle", ".properties", ".sql", ".sh", ".ps1",
15
+ ])
16
+
17
+ function runGit(cwd, args) {
18
+ return spawnSync("git", args, { cwd, encoding: "utf8" })
19
+ }
20
+
21
+ async function readJson(file) {
22
+ try {
23
+ return JSON.parse(await readFile(file, "utf8"))
24
+ } catch {
25
+ return null
26
+ }
27
+ }
28
+
29
+ async function listTopLevel(root) {
30
+ const entries = await readdir(root, { withFileTypes: true }).catch(() => [])
31
+ return entries
32
+ .filter((entry) => !SKIP_DIRS.has(entry.name))
33
+ .map((entry) => ({ name: entry.name, type: entry.isDirectory() ? "directory" : "file" }))
34
+ .sort((a, b) => a.name.localeCompare(b.name))
35
+ }
36
+
37
+ export async function detectStack(root = process.cwd()) {
38
+ root = path.resolve(root)
39
+ const packageJson = await readJson(path.join(root, "package.json"))
40
+ const stacks = []
41
+ const evidence = []
42
+
43
+ if (packageJson) {
44
+ const deps = { ...(packageJson.dependencies || {}), ...(packageJson.devDependencies || {}) }
45
+ stacks.push("node")
46
+ evidence.push("package.json")
47
+ if (deps.react) stacks.push("react")
48
+ if (deps["react-native"]) stacks.push("react-native")
49
+ if (deps.next) stacks.push("nextjs")
50
+ if (deps["@nestjs/core"]) stacks.push("nestjs")
51
+ if (deps.express) stacks.push("express")
52
+ if (deps.fastify) stacks.push("fastify")
53
+ if (deps.typescript) stacks.push("typescript")
54
+ }
55
+
56
+ const markers = [
57
+ ["pyproject.toml", "python"], ["requirements.txt", "python"], ["manage.py", "django"],
58
+ ["pom.xml", "java-maven"], ["build.gradle", "java-gradle"], ["build.gradle.kts", "kotlin-gradle"],
59
+ ["*.csproj", "dotnet"], ["pubspec.yaml", "flutter"], ["go.mod", "go"], ["Cargo.toml", "rust"],
60
+ ]
61
+
62
+ for (const [marker, stack] of markers) {
63
+ if (marker.startsWith("*.")) {
64
+ const suffix = marker.slice(1)
65
+ const entries = await readdir(root).catch(() => [])
66
+ if (entries.some((name) => name.endsWith(suffix))) {
67
+ stacks.push(stack)
68
+ evidence.push(marker)
69
+ }
70
+ } else if (existsSync(path.join(root, marker))) {
71
+ stacks.push(stack)
72
+ evidence.push(marker)
73
+ }
74
+ }
75
+
76
+ let packageManager = null
77
+ for (const [file, manager] of [
78
+ ["pnpm-lock.yaml", "pnpm"], ["yarn.lock", "yarn"], ["bun.lockb", "bun"],
79
+ ["bun.lock", "bun"], ["package-lock.json", "npm"],
80
+ ]) {
81
+ if (existsSync(path.join(root, file))) {
82
+ packageManager = manager
83
+ evidence.push(file)
84
+ break
85
+ }
86
+ }
87
+
88
+ return {
89
+ root,
90
+ packageManager,
91
+ stacks: [...new Set(stacks)],
92
+ evidence: [...new Set(evidence)],
93
+ node: packageJson ? {
94
+ name: packageJson.name || null,
95
+ version: packageJson.version || null,
96
+ type: packageJson.type || null,
97
+ engines: packageJson.engines || null,
98
+ } : null,
99
+ }
100
+ }
101
+
102
+ export async function detectTestCommands(root = process.cwd()) {
103
+ root = path.resolve(root)
104
+ const commands = []
105
+ const packageJson = await readJson(path.join(root, "package.json"))
106
+
107
+ if (packageJson?.scripts) {
108
+ const manager = existsSync(path.join(root, "pnpm-lock.yaml")) ? "pnpm"
109
+ : existsSync(path.join(root, "yarn.lock")) ? "yarn"
110
+ : existsSync(path.join(root, "bun.lock")) || existsSync(path.join(root, "bun.lockb")) ? "bun"
111
+ : "npm"
112
+
113
+ for (const name of ["test", "typecheck", "check", "lint", "build", "ci"]) {
114
+ if (!packageJson.scripts[name]) continue
115
+ const command = `${manager} run ${name}`
116
+ commands.push({ kind: name, command, source: "package.json" })
117
+ }
118
+ }
119
+
120
+ if (existsSync(path.join(root, "pyproject.toml")) || existsSync(path.join(root, "pytest.ini"))) {
121
+ commands.push({ kind: "test", command: "python -m pytest", source: "python project markers" })
122
+ }
123
+ if (existsSync(path.join(root, "pom.xml"))) {
124
+ commands.push({ kind: "test", command: "mvn test", source: "pom.xml" })
125
+ }
126
+ if (existsSync(path.join(root, "gradlew")) || existsSync(path.join(root, "gradlew.bat"))) {
127
+ commands.push({ kind: "test", command: process.platform === "win32" ? "gradlew.bat test" : "./gradlew test", source: "Gradle wrapper" })
128
+ }
129
+ const entries = await readdir(root).catch(() => [])
130
+ if (entries.some((name) => name.endsWith(".sln") || name.endsWith(".csproj"))) {
131
+ commands.push({ kind: "test", command: "dotnet test", source: ".NET project" })
132
+ }
133
+ if (existsSync(path.join(root, "pubspec.yaml"))) {
134
+ commands.push({ kind: "test", command: "flutter test", source: "pubspec.yaml" })
135
+ commands.push({ kind: "analyze", command: "flutter analyze", source: "pubspec.yaml" })
136
+ }
137
+
138
+ const seen = new Set()
139
+ return commands.filter((item) => {
140
+ if (seen.has(item.command)) return false
141
+ seen.add(item.command)
142
+ return true
143
+ })
144
+ }
145
+
146
+ export async function repoMap(root = process.cwd()) {
147
+ root = path.resolve(root)
148
+ const topLevel = await listTopLevel(root)
149
+ const packageJson = await readJson(path.join(root, "package.json"))
150
+ const workspaces = Array.isArray(packageJson?.workspaces)
151
+ ? packageJson.workspaces
152
+ : Array.isArray(packageJson?.workspaces?.packages)
153
+ ? packageJson.workspaces.packages
154
+ : []
155
+
156
+ const important = [
157
+ "README.md", "AGENTS.md", "CONTRIBUTING.md", "package.json", "pyproject.toml",
158
+ "pom.xml", "build.gradle", "build.gradle.kts", "pubspec.yaml", "go.mod", "Cargo.toml",
159
+ "Dockerfile", "docker-compose.yml", "docker-compose.yaml",
160
+ ].filter((name) => existsSync(path.join(root, name)))
161
+
162
+ return {
163
+ root,
164
+ topLevel,
165
+ important,
166
+ workspaces,
167
+ stack: await detectStack(root),
168
+ verification: await detectTestCommands(root),
169
+ }
170
+ }
171
+
172
+ async function walk(root, options = {}) {
173
+ const maxFiles = options.maxFiles ?? 2000
174
+ const maxDepth = options.maxDepth ?? 6
175
+ const files = []
176
+
177
+ async function visit(dir, depth) {
178
+ if (files.length >= maxFiles || depth > maxDepth) return
179
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
180
+ for (const entry of entries) {
181
+ if (files.length >= maxFiles) break
182
+ if (SKIP_DIRS.has(entry.name)) continue
183
+ const full = path.join(dir, entry.name)
184
+ if (entry.isDirectory()) await visit(full, depth + 1)
185
+ else if (entry.isFile()) files.push(full)
186
+ }
187
+ }
188
+
189
+ await visit(root, 0)
190
+ return files
191
+ }
192
+
193
+ export async function impactMap(root = process.cwd(), query = "") {
194
+ root = path.resolve(root)
195
+ query = String(query || "").trim()
196
+ if (!query) throw new Error("impactMap requires a non-empty query")
197
+
198
+ const needle = query.toLowerCase()
199
+ const files = await walk(root)
200
+ const matches = []
201
+
202
+ for (const file of files) {
203
+ const relative = path.relative(root, file)
204
+ if (relative.toLowerCase().includes(needle)) {
205
+ matches.push({ path: relative, kind: "path", lines: [] })
206
+ continue
207
+ }
208
+
209
+ const ext = path.extname(file).toLowerCase()
210
+ if (!TEXT_EXTENSIONS.has(ext) && !["Dockerfile", "Makefile"].includes(path.basename(file))) continue
211
+
212
+ const info = await stat(file).catch(() => null)
213
+ if (!info || info.size > 512 * 1024) continue
214
+
215
+ const source = await readFile(file, "utf8").catch(() => "")
216
+ const lines = source.split(/\r?\n/)
217
+ const hitLines = []
218
+ for (let index = 0; index < lines.length; index += 1) {
219
+ if (lines[index].toLowerCase().includes(needle)) {
220
+ hitLines.push({ line: index + 1, text: lines[index].trim().slice(0, 240) })
221
+ if (hitLines.length >= 5) break
222
+ }
223
+ }
224
+ if (hitLines.length) matches.push({ path: relative, kind: "content", lines: hitLines })
225
+ if (matches.length >= 100) break
226
+ }
227
+
228
+ return { root, query, matches, truncated: matches.length >= 100 }
229
+ }
230
+
231
+ export async function checkWorkingTree(root = process.cwd()) {
232
+ root = path.resolve(root)
233
+ const status = runGit(root, ["status", "--porcelain=v1", "--branch"])
234
+ if (status.status !== 0) {
235
+ return { root, git: false, error: (status.stderr || status.stdout || "").trim() }
236
+ }
237
+
238
+ const lines = status.stdout.split(/\r?\n/).filter(Boolean)
239
+ const branch = lines[0]?.replace(/^##\s*/, "") || null
240
+ const changes = lines.slice(1)
241
+ const head = runGit(root, ["rev-parse", "HEAD"])
242
+ return {
243
+ root,
244
+ git: true,
245
+ branch,
246
+ head: head.status === 0 ? head.stdout.trim() : null,
247
+ clean: changes.length === 0,
248
+ changes,
249
+ }
250
+ }
251
+
252
+ export async function collectEvidence(root = process.cwd()) {
253
+ root = path.resolve(root)
254
+ const [map, workingTree] = await Promise.all([repoMap(root), checkWorkingTree(root)])
255
+ return {
256
+ collectedAt: new Date().toISOString(),
257
+ root,
258
+ stack: map.stack,
259
+ verification: map.verification,
260
+ important: map.important,
261
+ workspaces: map.workspaces,
262
+ workingTree,
263
+ }
264
+ }
@@ -0,0 +1,98 @@
1
+ import path from "node:path"
2
+ import { spawnSync } from "node:child_process"
3
+
4
+ function git(root, args) {
5
+ return spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 })
6
+ }
7
+
8
+ function normalize(value) {
9
+ return String(value || "").trim().replaceAll("\\", "/")
10
+ }
11
+
12
+ function riskForPath(file) {
13
+ const value = file.toLowerCase()
14
+ const reasons = []
15
+ let score = 0
16
+
17
+ const add = (points, reason) => {
18
+ score += points
19
+ reasons.push(reason)
20
+ }
21
+
22
+ if (/(migration|schema|prisma|database|sql)/.test(value)) add(3, "persistence/schema")
23
+ if (/(auth|permission|security|token|session|credential|secret)/.test(value)) add(3, "auth/security")
24
+ if (/(payment|billing|checkout|webhook|refund)/.test(value)) add(3, "payment")
25
+ if (/(package\.json|package-lock\.json|pnpm-lock|yarn\.lock|pom\.xml|build\.gradle|\.csproj$)/.test(value)) add(2, "dependency/build")
26
+ if (/(docker|deploy|workflow|\.github\/|terraform|k8s|kubernetes)/.test(value)) add(2, "delivery/infrastructure")
27
+ if (/\.(env|pem|key)$/.test(value)) add(4, "sensitive-file")
28
+ if (/(api|controller|route|handler|dto|contract|openapi)/.test(value)) add(1, "public/interface")
29
+ if (/(test|spec|__tests__)/.test(value)) score = Math.max(0, score - 1)
30
+
31
+ const level = score >= 5 ? "critical" : score >= 3 ? "high" : score >= 1 ? "medium" : "low"
32
+ return { level, score, reasons }
33
+ }
34
+
35
+ export function classifyReviewFiles(files) {
36
+ return files.map((file) => ({ path: normalize(file), risk: riskForPath(file) }))
37
+ }
38
+
39
+ function parseNameStatus(output) {
40
+ return output
41
+ .split(/\r?\n/)
42
+ .filter(Boolean)
43
+ .map((line) => {
44
+ const parts = line.split("\t")
45
+ const status = parts[0]
46
+ const file = parts[parts.length - 1]
47
+ return { status, path: normalize(file) }
48
+ })
49
+ }
50
+
51
+ export function reviewScope(root = process.cwd(), base = null) {
52
+ root = path.resolve(root)
53
+ const inside = git(root, ["rev-parse", "--is-inside-work-tree"])
54
+ if (inside.status !== 0) {
55
+ return { schemaVersion: 1, root, git: false, error: (inside.stderr || inside.stdout || "").trim() }
56
+ }
57
+
58
+ let committed = []
59
+ let mergeBase = null
60
+ if (base) {
61
+ const merge = git(root, ["merge-base", base, "HEAD"])
62
+ if (merge.status === 0) {
63
+ mergeBase = merge.stdout.trim()
64
+ const diff = git(root, ["diff", "--name-status", mergeBase + "..HEAD"])
65
+ if (diff.status === 0) committed = parseNameStatus(diff.stdout)
66
+ }
67
+ }
68
+
69
+ const work = git(root, ["status", "--porcelain=v1"])
70
+ const workspace = work.status === 0
71
+ ? work.stdout.split(/\r?\n/).filter(Boolean).map((line) => ({
72
+ status: line.slice(0, 2).trim() || "??",
73
+ path: normalize(line.slice(3).replace(/^.* -> /, "")),
74
+ }))
75
+ : []
76
+
77
+ const byPath = new Map()
78
+ for (const item of [...committed, ...workspace]) byPath.set(item.path, item)
79
+ const files = [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path))
80
+ const classified = classifyReviewFiles(files.map((item) => item.path))
81
+ const riskMap = new Map(classified.map((item) => [item.path, item.risk]))
82
+
83
+ const scoped = files.map((item) => ({ ...item, risk: riskMap.get(item.path) }))
84
+ const maxScore = Math.max(0, ...scoped.map((item) => item.risk.score))
85
+ const overallRisk = maxScore >= 5 ? "critical" : maxScore >= 3 ? "high" : maxScore >= 1 ? "medium" : "low"
86
+
87
+ return {
88
+ schemaVersion: 1,
89
+ root,
90
+ git: true,
91
+ base,
92
+ mergeBase,
93
+ overallRisk,
94
+ totalFiles: scoped.length,
95
+ files: scoped,
96
+ coverageRequired: scoped.map((item) => item.path),
97
+ }
98
+ }
@@ -0,0 +1,40 @@
1
+ import { existsSync } from "node:fs"
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+
5
+ export const DEFAULT_ROUTER_CONFIG = Object.freeze({ enabled: true, maxSkills: 4 })
6
+
7
+ export async function readRouterConfig(configDir) {
8
+ const file = path.join(configDir, ".ues", "router.json")
9
+ if (!existsSync(file)) return { ...DEFAULT_ROUTER_CONFIG, file, exists: false }
10
+
11
+ try {
12
+ const parsed = JSON.parse(await readFile(file, "utf8"))
13
+ return {
14
+ enabled: parsed.enabled !== false,
15
+ maxSkills: Number.isInteger(parsed.maxSkills)
16
+ ? Math.max(1, Math.min(parsed.maxSkills, 6))
17
+ : DEFAULT_ROUTER_CONFIG.maxSkills,
18
+ file,
19
+ exists: true,
20
+ }
21
+ } catch {
22
+ return { ...DEFAULT_ROUTER_CONFIG, file, exists: true, invalid: true }
23
+ }
24
+ }
25
+
26
+ export async function writeRouterConfig(configDir, next) {
27
+ const current = await readRouterConfig(configDir)
28
+ const value = {
29
+ enabled: next.enabled ?? current.enabled,
30
+ maxSkills: next.maxSkills ?? current.maxSkills,
31
+ }
32
+ if (!Number.isInteger(value.maxSkills) || value.maxSkills < 1 || value.maxSkills > 6) {
33
+ throw new RangeError("router maxSkills must be an integer from 1 through 6")
34
+ }
35
+
36
+ const file = path.join(configDir, ".ues", "router.json")
37
+ await mkdir(path.dirname(file), { recursive: true })
38
+ await writeFile(file, JSON.stringify(value, null, 2) + "\n", "utf8")
39
+ return { ...value, file, exists: true }
40
+ }