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.
- package/CHANGELOG.md +163 -0
- package/LICENSE +9 -0
- package/README.md +581 -0
- package/bin/ocskill.mjs +975 -0
- package/docs/DETERMINISTIC-TOOLS.md +88 -0
- package/docs/ENGINEERING-DESIGN.md +176 -0
- package/docs/EVALS.md +136 -0
- package/docs/NPM-PUBLISH.md +102 -0
- package/docs/OPENCODE-COMPAT.md +109 -0
- package/docs/RESEARCH-SOURCES.md +37 -0
- package/docs/TRACE-SCHEMA.md +109 -0
- package/docs/V7-INTELLIGENCE-RUNTIME.md +166 -0
- package/evals/live/fixtures/engineering-bench/package.json +1 -0
- package/evals/live/fixtures/engineering-bench/src/api-errors.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/authz.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/cache-tags.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/config.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/contract-consumer.mjs +6 -0
- package/evals/live/fixtures/engineering-bench/src/contract-producer.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/dedupe.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/dependency.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/discount.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/inventory.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/migration.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/money.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/pagination.mjs +5 -0
- package/evals/live/fixtures/engineering-bench/src/path-safe.mjs +5 -0
- package/evals/live/fixtures/engineering-bench/src/payment.mjs +5 -0
- package/evals/live/fixtures/engineering-bench/src/query-sort.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/react-state.mjs +8 -0
- package/evals/live/fixtures/engineering-bench/src/retry.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/rn-platform.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/upload.mjs +3 -0
- package/evals/live/fixtures/engineering-bench/src/webhook.mjs +3 -0
- package/evals/live/graders/engineering-bench.mjs +239 -0
- package/evals/live/tasks.json +126 -0
- package/evals/long/fixtures/long-horizon/package.json +1 -0
- package/evals/long/fixtures/long-horizon/src/auth.mjs +5 -0
- package/evals/long/fixtures/long-horizon/src/checkout.mjs +9 -0
- package/evals/long/fixtures/long-horizon/src/inventory.mjs +5 -0
- package/evals/long/fixtures/long-horizon/src/money.mjs +3 -0
- package/evals/long/fixtures/long-horizon/src/payment.mjs +7 -0
- package/evals/long/fixtures/long-horizon/src/product-api.mjs +10 -0
- package/evals/long/fixtures/long-horizon/src/product-cache.mjs +3 -0
- package/evals/long/fixtures/long-horizon/src/product-service.mjs +6 -0
- package/evals/long/fixtures/long-horizon/src/product-state.mjs +8 -0
- package/evals/long/fixtures/long-horizon/src/project-api.mjs +9 -0
- package/evals/long/fixtures/long-horizon/src/project-service.mjs +7 -0
- package/evals/long/fixtures/long-horizon/src/user-consumer.mjs +3 -0
- package/evals/long/fixtures/long-horizon/src/user-migration.mjs +7 -0
- package/evals/long/fixtures/long-horizon/src/user-serializer.mjs +3 -0
- package/evals/long/fixtures/long-horizon/src/user-validation.mjs +3 -0
- package/evals/long/graders/long-horizon.mjs +209 -0
- package/evals/long/tasks.json +36 -0
- package/evals/router-triggers.json +1238 -0
- package/evals/routing.json +321 -0
- package/global-config/AGENTS.md +212 -0
- package/global-config/agents/architect.md +38 -0
- package/global-config/agents/codebase-mapper.md +41 -0
- package/global-config/agents/critic.md +37 -0
- package/global-config/agents/debugger.md +36 -0
- package/global-config/agents/executor.md +48 -0
- package/global-config/agents/integration-verifier.md +43 -0
- package/global-config/agents/plan-checker.md +43 -0
- package/global-config/agents/researcher.md +30 -0
- package/global-config/agents/reviewer.md +30 -0
- package/global-config/agents/verifier.md +33 -0
- package/global-config/commands/audit.md +8 -0
- package/global-config/commands/critique.md +8 -0
- package/global-config/commands/debug.md +8 -0
- package/global-config/commands/feature.md +8 -0
- package/global-config/commands/fix.md +8 -0
- package/global-config/commands/plan.md +8 -0
- package/global-config/commands/research.md +8 -0
- package/global-config/commands/resume.md +22 -0
- package/global-config/commands/review.md +8 -0
- package/global-config/commands/run.md +29 -0
- package/global-config/commands/verify.md +8 -0
- package/global-config/plugins/ues-router/capabilities.js +20 -0
- package/global-config/plugins/ues-router/index.js +277 -0
- package/global-config/plugins/ues-router/router.js +74 -0
- package/global-config/plugins/ues-router/safety.js +16 -0
- package/global-config/skills/accessibility/SKILL.md +10 -0
- package/global-config/skills/accessibility/references/workflow.md +17 -0
- package/global-config/skills/api-contract/SKILL.md +12 -0
- package/global-config/skills/api-contract/references/workflow.md +17 -0
- package/global-config/skills/auth-security/SKILL.md +12 -0
- package/global-config/skills/auth-security/references/workflow.md +15 -0
- package/global-config/skills/bug-diagnosis/SKILL.md +28 -0
- package/global-config/skills/change-impact-analysis/SKILL.md +23 -0
- package/global-config/skills/code-review/SKILL.md +19 -0
- package/global-config/skills/context-engineering/SKILL.md +18 -0
- package/global-config/skills/context-engineering/references/large-repo.md +16 -0
- package/global-config/skills/database-engineering/SKILL.md +12 -0
- package/global-config/skills/database-engineering/references/workflow.md +15 -0
- package/global-config/skills/dependency-management/SKILL.md +12 -0
- package/global-config/skills/dependency-management/references/workflow.md +14 -0
- package/global-config/skills/devops-engineering/SKILL.md +10 -0
- package/global-config/skills/devops-engineering/references/workflow.md +11 -0
- package/global-config/skills/django-engineering/SKILL.md +10 -0
- package/global-config/skills/django-engineering/references/workflow.md +11 -0
- package/global-config/skills/documentation-engineering/SKILL.md +10 -0
- package/global-config/skills/documentation-engineering/references/workflow.md +15 -0
- package/global-config/skills/dotnet-engineering/SKILL.md +10 -0
- package/global-config/skills/dotnet-engineering/references/workflow.md +11 -0
- package/global-config/skills/ecommerce-engineering/SKILL.md +10 -0
- package/global-config/skills/ecommerce-engineering/references/workflow.md +17 -0
- package/global-config/skills/engineering-orchestrator/SKILL.md +29 -0
- package/global-config/skills/engineering-orchestrator/references/delegation.md +22 -0
- package/global-config/skills/engineering-orchestrator/references/evaluator-loop.md +18 -0
- package/global-config/skills/engineering-orchestrator/references/long-horizon.md +57 -0
- package/global-config/skills/engineering-orchestrator/references/model-escalation.md +19 -0
- package/global-config/skills/engineering-orchestrator/references/retry-policy.md +12 -0
- package/global-config/skills/engineering-orchestrator/references/routing.md +39 -0
- package/global-config/skills/engineering-orchestrator/references/verification-matrix.md +18 -0
- package/global-config/skills/fastapi-engineering/SKILL.md +10 -0
- package/global-config/skills/fastapi-engineering/references/workflow.md +13 -0
- package/global-config/skills/file-upload-engineering/SKILL.md +10 -0
- package/global-config/skills/file-upload-engineering/references/workflow.md +17 -0
- package/global-config/skills/flutter-engineering/SKILL.md +10 -0
- package/global-config/skills/flutter-engineering/references/workflow.md +13 -0
- package/global-config/skills/git-safety/SKILL.md +10 -0
- package/global-config/skills/git-safety/references/workflow.md +15 -0
- package/global-config/skills/implementation-engineer/SKILL.md +10 -0
- package/global-config/skills/implementation-engineer/references/workflow.md +14 -0
- package/global-config/skills/java-spring-engineering/SKILL.md +10 -0
- package/global-config/skills/java-spring-engineering/references/workflow.md +13 -0
- package/global-config/skills/long-task-state/SKILL.md +28 -0
- package/global-config/skills/long-task-state/references/context-ledger.md +27 -0
- package/global-config/skills/long-task-state/templates/STATE.md +48 -0
- package/global-config/skills/nestjs-engineering/SKILL.md +10 -0
- package/global-config/skills/nestjs-engineering/references/workflow.md +11 -0
- package/global-config/skills/nextjs-engineering/SKILL.md +12 -0
- package/global-config/skills/nextjs-engineering/references/workflow.md +15 -0
- package/global-config/skills/nodejs-engineering/SKILL.md +12 -0
- package/global-config/skills/nodejs-engineering/references/workflow.md +11 -0
- package/global-config/skills/payment-engineering/SKILL.md +12 -0
- package/global-config/skills/payment-engineering/references/workflow.md +19 -0
- package/global-config/skills/performance-engineering/SKILL.md +10 -0
- package/global-config/skills/performance-engineering/references/workflow.md +17 -0
- package/global-config/skills/python-engineering/SKILL.md +10 -0
- package/global-config/skills/python-engineering/references/workflow.md +11 -0
- package/global-config/skills/react-engineering/SKILL.md +14 -0
- package/global-config/skills/react-engineering/references/workflow.md +16 -0
- package/global-config/skills/react-native-engineering/SKILL.md +14 -0
- package/global-config/skills/react-native-engineering/references/workflow.md +16 -0
- package/global-config/skills/repo-explorer/SKILL.md +17 -0
- package/global-config/skills/research-verification/SKILL.md +18 -0
- package/global-config/skills/research-verification/references/source-hierarchy.md +12 -0
- package/global-config/skills/rest-api-design/SKILL.md +10 -0
- package/global-config/skills/rest-api-design/references/workflow.md +18 -0
- package/global-config/skills/software-architect/SKILL.md +10 -0
- package/global-config/skills/software-architect/references/workflow.md +18 -0
- package/global-config/skills/task-planner/SKILL.md +21 -0
- package/global-config/skills/task-planner/references/plan-schema.md +56 -0
- package/global-config/skills/test-driven-development/SKILL.md +22 -0
- package/global-config/skills/test-driven-development/references/writing-good-tests.md +21 -0
- package/global-config/skills/test-verification/SKILL.md +20 -0
- package/global-config/skills/ui-ux-engineering/SKILL.md +10 -0
- package/global-config/skills/ui-ux-engineering/references/workflow.md +17 -0
- package/global-config/skills/web-security-review/SKILL.md +10 -0
- package/global-config/skills/web-security-review/references/workflow.md +22 -0
- package/lib/context-manifest.mjs +101 -0
- package/lib/control-center.mjs +148 -0
- package/lib/eval-auth.mjs +21 -0
- package/lib/eval-report.mjs +72 -0
- package/lib/eval-telemetry.mjs +155 -0
- package/lib/evidence-receipt.mjs +50 -0
- package/lib/hermes-bridge.mjs +28 -0
- package/lib/ids.mjs +21 -0
- package/lib/installer.mjs +636 -0
- package/lib/learning-engine.mjs +161 -0
- package/lib/model-config.mjs +88 -0
- package/lib/model-policy.mjs +71 -0
- package/lib/opencode-compat.mjs +86 -0
- package/lib/orchestrator-policy.mjs +35 -0
- package/lib/process-runner.mjs +117 -0
- package/lib/repo-graph.mjs +135 -0
- package/lib/repo-inspect.mjs +264 -0
- package/lib/review-scope.mjs +98 -0
- package/lib/router-config.mjs +40 -0
- package/lib/task-engine.mjs +777 -0
- package/lib/task-graph.mjs +262 -0
- package/lib/update-resolver.mjs +43 -0
- package/lib/verification-plan.mjs +52 -0
- package/lib/version.mjs +47 -0
- package/lib/workspace-snapshot.mjs +45 -0
- package/lib/worktree-sandbox.mjs +59 -0
- package/package.json +69 -0
- package/scripts/check-working-tree.mjs +2 -0
- package/scripts/collect-evidence.mjs +2 -0
- package/scripts/control-center.mjs +37 -0
- package/scripts/detect-stack.mjs +2 -0
- package/scripts/detect-test-commands.mjs +2 -0
- package/scripts/eval-live.mjs +437 -0
- package/scripts/eval-report.mjs +49 -0
- package/scripts/eval-router.mjs +45 -0
- package/scripts/eval-skills.mjs +71 -0
- package/scripts/impact-map.mjs +4 -0
- package/scripts/install.mjs +61 -0
- package/scripts/repo-map.mjs +2 -0
- package/scripts/smoke-packed-install.mjs +326 -0
- package/scripts/syntax-check.mjs +35 -0
- package/scripts/uninstall.mjs +21 -0
- package/scripts/validate-live-suite.mjs +65 -0
- package/scripts/validate.mjs +133 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
|
|
3
|
+
const VALID_RISKS = new Set(["low", "medium", "high", "critical"])
|
|
4
|
+
|
|
5
|
+
function unsafeRepoPath(value) {
|
|
6
|
+
const raw = String(value || "").replaceAll("\\", "/")
|
|
7
|
+
if (!raw) return false
|
|
8
|
+
if (path.posix.isAbsolute(raw) || /^[A-Za-z]:\//.test(raw)) return true
|
|
9
|
+
const normalized = path.posix.normalize(raw).replace(/^\.\//, "")
|
|
10
|
+
return normalized === ".." || normalized.startsWith("../")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeFile(value) {
|
|
14
|
+
if (unsafeRepoPath(value)) return ""
|
|
15
|
+
const normalized = path.posix
|
|
16
|
+
.normalize(String(value || "").replaceAll("\\", "/"))
|
|
17
|
+
.replace(/^\.\//, "")
|
|
18
|
+
return normalized === "." ? "" : normalized
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function taskFiles(task) {
|
|
22
|
+
const files = task?.files
|
|
23
|
+
if (Array.isArray(files)) return [...new Set(files.map(normalizeFile).filter(Boolean))].sort()
|
|
24
|
+
if (!files || typeof files !== "object") return []
|
|
25
|
+
|
|
26
|
+
const values = []
|
|
27
|
+
for (const key of ["create", "modify", "test", "delete", "read"]) {
|
|
28
|
+
if (!Array.isArray(files[key])) continue
|
|
29
|
+
values.push(...files[key])
|
|
30
|
+
}
|
|
31
|
+
return [...new Set(values.map(normalizeFile).filter(Boolean))].sort()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function taskWriteFiles(task) {
|
|
35
|
+
const files = task?.files
|
|
36
|
+
if (Array.isArray(files)) return taskFiles(task)
|
|
37
|
+
if (!files || typeof files !== "object") return []
|
|
38
|
+
const values = []
|
|
39
|
+
for (const key of ["create", "modify", "test", "delete"]) {
|
|
40
|
+
if (Array.isArray(files[key])) values.push(...files[key])
|
|
41
|
+
}
|
|
42
|
+
return [...new Set(values.map(normalizeFile).filter(Boolean))].sort()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function taskReadFiles(task) {
|
|
46
|
+
const files = task?.files
|
|
47
|
+
if (!files || Array.isArray(files) || typeof files !== "object") return []
|
|
48
|
+
return [...new Set((files.read || []).map(normalizeFile).filter(Boolean))].sort()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function taskMap(plan) {
|
|
52
|
+
return new Map((plan?.tasks || []).map((task) => [task.id, task]))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function validatePlan(plan) {
|
|
56
|
+
const errors = []
|
|
57
|
+
const warnings = []
|
|
58
|
+
|
|
59
|
+
if (!plan || typeof plan !== "object" || Array.isArray(plan)) {
|
|
60
|
+
return { valid: false, errors: ["plan must be a JSON object"], warnings }
|
|
61
|
+
}
|
|
62
|
+
if (plan.schemaVersion !== 1) errors.push("schemaVersion must be 1")
|
|
63
|
+
if (!String(plan.goal || "").trim()) errors.push("goal must be a non-empty string")
|
|
64
|
+
if (!Array.isArray(plan.tasks) || plan.tasks.length === 0) {
|
|
65
|
+
errors.push("tasks must be a non-empty array")
|
|
66
|
+
return { valid: false, errors, warnings }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ids = new Set()
|
|
70
|
+
for (const [index, task] of plan.tasks.entries()) {
|
|
71
|
+
const prefix = `tasks[${index}]`
|
|
72
|
+
if (!task || typeof task !== "object" || Array.isArray(task)) {
|
|
73
|
+
errors.push(`${prefix} must be an object`)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const id = String(task.id || "").trim()
|
|
78
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(id)) {
|
|
79
|
+
errors.push(`${prefix}.id must use letters, numbers, dot, underscore or dash`)
|
|
80
|
+
} else if (ids.has(id)) {
|
|
81
|
+
errors.push(`duplicate task id: ${id}`)
|
|
82
|
+
} else {
|
|
83
|
+
ids.add(id)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!String(task.title || "").trim()) errors.push(`${prefix}.title is required`)
|
|
87
|
+
if (!String(task.summary || "").trim()) warnings.push(`${id || prefix}: summary is empty`)
|
|
88
|
+
|
|
89
|
+
const deps = task.dependsOn ?? []
|
|
90
|
+
if (!Array.isArray(deps)) errors.push(`${id || prefix}.dependsOn must be an array`)
|
|
91
|
+
else if (deps.includes(id)) errors.push(`${id}: task cannot depend on itself`)
|
|
92
|
+
|
|
93
|
+
if (!Array.isArray(task.acceptance) || task.acceptance.length === 0) {
|
|
94
|
+
errors.push(`${id || prefix}: acceptance must contain at least one observable criterion`)
|
|
95
|
+
}
|
|
96
|
+
if (!Array.isArray(task.verification) || task.verification.length === 0) {
|
|
97
|
+
errors.push(`${id || prefix}: verification must contain at least one concrete check`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const risk = task.risk ?? "medium"
|
|
101
|
+
if (!VALID_RISKS.has(risk)) errors.push(`${id || prefix}: invalid risk '${risk}'`)
|
|
102
|
+
|
|
103
|
+
const rawFiles = task.files
|
|
104
|
+
const rawValues = Array.isArray(rawFiles)
|
|
105
|
+
? rawFiles
|
|
106
|
+
: rawFiles && typeof rawFiles === "object"
|
|
107
|
+
? ["create", "modify", "test", "delete", "read"].flatMap((key) => Array.isArray(rawFiles[key]) ? rawFiles[key] : [])
|
|
108
|
+
: []
|
|
109
|
+
for (const file of rawValues) {
|
|
110
|
+
if (unsafeRepoPath(file)) errors.push(`${id || prefix}: file scope must stay inside the repository: ${file}`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const files = taskFiles(task)
|
|
114
|
+
if (files.length === 0) warnings.push(`${id || prefix}: no files declared; parallel scheduling will be conservative`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const map = taskMap(plan)
|
|
118
|
+
for (const task of plan.tasks) {
|
|
119
|
+
if (!task?.id || !Array.isArray(task.dependsOn)) continue
|
|
120
|
+
for (const dep of task.dependsOn) {
|
|
121
|
+
if (!map.has(dep)) errors.push(`${task.id}: unknown dependency '${dep}'`)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const cycle = findCycle(plan)
|
|
126
|
+
if (cycle.length) errors.push(`dependency cycle: ${cycle.join(" -> ")}`)
|
|
127
|
+
|
|
128
|
+
return { valid: errors.length === 0, errors, warnings }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function findCycle(plan) {
|
|
132
|
+
const map = taskMap(plan)
|
|
133
|
+
const visiting = new Set()
|
|
134
|
+
const visited = new Set()
|
|
135
|
+
const stack = []
|
|
136
|
+
|
|
137
|
+
function visit(id) {
|
|
138
|
+
if (visiting.has(id)) {
|
|
139
|
+
const start = stack.indexOf(id)
|
|
140
|
+
return [...stack.slice(start), id]
|
|
141
|
+
}
|
|
142
|
+
if (visited.has(id)) return []
|
|
143
|
+
|
|
144
|
+
visiting.add(id)
|
|
145
|
+
stack.push(id)
|
|
146
|
+
for (const dep of map.get(id)?.dependsOn || []) {
|
|
147
|
+
const cycle = visit(dep)
|
|
148
|
+
if (cycle.length) return cycle
|
|
149
|
+
}
|
|
150
|
+
stack.pop()
|
|
151
|
+
visiting.delete(id)
|
|
152
|
+
visited.add(id)
|
|
153
|
+
return []
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const id of map.keys()) {
|
|
157
|
+
const cycle = visit(id)
|
|
158
|
+
if (cycle.length) return cycle
|
|
159
|
+
}
|
|
160
|
+
return []
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function overlaps(taskA, taskB) {
|
|
164
|
+
const allA = taskFiles(taskA)
|
|
165
|
+
const allB = taskFiles(taskB)
|
|
166
|
+
if (allA.length === 0 || allB.length === 0) return true
|
|
167
|
+
|
|
168
|
+
const writeA = new Set(taskWriteFiles(taskA))
|
|
169
|
+
const writeB = new Set(taskWriteFiles(taskB))
|
|
170
|
+
const readA = new Set(taskReadFiles(taskA))
|
|
171
|
+
const readB = new Set(taskReadFiles(taskB))
|
|
172
|
+
|
|
173
|
+
if ([...writeA].some((file) => writeB.has(file) || readB.has(file))) return true
|
|
174
|
+
if ([...writeB].some((file) => readA.has(file))) return true
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function computeTopologicalWaves(plan) {
|
|
179
|
+
const validation = validatePlan(plan)
|
|
180
|
+
if (!validation.valid) {
|
|
181
|
+
const error = new Error("invalid task plan")
|
|
182
|
+
error.validation = validation
|
|
183
|
+
throw error
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const map = taskMap(plan)
|
|
187
|
+
const complete = new Set()
|
|
188
|
+
const remaining = new Set(map.keys())
|
|
189
|
+
const waves = []
|
|
190
|
+
|
|
191
|
+
while (remaining.size) {
|
|
192
|
+
const ready = [...remaining]
|
|
193
|
+
.filter((id) => (map.get(id).dependsOn || []).every((dep) => complete.has(dep)))
|
|
194
|
+
.sort()
|
|
195
|
+
|
|
196
|
+
if (ready.length === 0) throw new Error("task graph cannot make progress")
|
|
197
|
+
waves.push(ready)
|
|
198
|
+
for (const id of ready) {
|
|
199
|
+
remaining.delete(id)
|
|
200
|
+
complete.add(id)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return waves
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function computeSafeWaves(plan) {
|
|
207
|
+
const validation = validatePlan(plan)
|
|
208
|
+
if (!validation.valid) {
|
|
209
|
+
const error = new Error("invalid task plan")
|
|
210
|
+
error.validation = validation
|
|
211
|
+
throw error
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const map = taskMap(plan)
|
|
215
|
+
const complete = new Set()
|
|
216
|
+
const remaining = new Set(map.keys())
|
|
217
|
+
const waves = []
|
|
218
|
+
const serialized = []
|
|
219
|
+
|
|
220
|
+
while (remaining.size) {
|
|
221
|
+
const ready = [...remaining]
|
|
222
|
+
.filter((id) => (map.get(id).dependsOn || []).every((dep) => complete.has(dep)))
|
|
223
|
+
.sort()
|
|
224
|
+
|
|
225
|
+
if (ready.length === 0) throw new Error("task graph cannot make progress")
|
|
226
|
+
|
|
227
|
+
const selected = []
|
|
228
|
+
for (const id of ready) {
|
|
229
|
+
const task = map.get(id)
|
|
230
|
+
const conflict = selected.find((otherID) => overlaps(task, map.get(otherID)))
|
|
231
|
+
if (conflict) {
|
|
232
|
+
serialized.push({ task: id, conflictsWith: conflict, reason: "declared write/read conflict or unknown file scope" })
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
selected.push(id)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (selected.length === 0) selected.push(ready[0])
|
|
239
|
+
waves.push(selected)
|
|
240
|
+
for (const id of selected) {
|
|
241
|
+
remaining.delete(id)
|
|
242
|
+
complete.add(id)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { waves, serialized }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function analyzePlan(plan) {
|
|
250
|
+
const validation = validatePlan(plan)
|
|
251
|
+
if (!validation.valid) return { ...validation, topologicalWaves: [], safeWaves: [], serialized: [] }
|
|
252
|
+
|
|
253
|
+
const topologicalWaves = computeTopologicalWaves(plan)
|
|
254
|
+
const safe = computeSafeWaves(plan)
|
|
255
|
+
return {
|
|
256
|
+
...validation,
|
|
257
|
+
topologicalWaves,
|
|
258
|
+
safeWaves: safe.waves,
|
|
259
|
+
serialized: safe.serialized,
|
|
260
|
+
taskCount: plan.tasks.length,
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function parseJsonString(stdout) {
|
|
2
|
+
try {
|
|
3
|
+
const value = JSON.parse(String(stdout || "").trim())
|
|
4
|
+
return typeof value === "string" && value ? value : null
|
|
5
|
+
} catch {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function parseLatestTag(stdout) {
|
|
11
|
+
for (const line of String(stdout || "").split(/\r?\n/)) {
|
|
12
|
+
const match = line.trim().match(/^latest:\s*(\S+)$/)
|
|
13
|
+
if (match) return match[1]
|
|
14
|
+
}
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveLatestPublishedVersion({ runCapture, packageName, cwd }) {
|
|
19
|
+
const direct = runCapture(
|
|
20
|
+
"npm",
|
|
21
|
+
["view", `${packageName}@latest`, "version", "--json"],
|
|
22
|
+
{ cwd },
|
|
23
|
+
)
|
|
24
|
+
if (direct.status === 0) {
|
|
25
|
+
const version = parseJsonString(direct.stdout)
|
|
26
|
+
if (version) return { version, source: "view@latest" }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tags = runCapture("npm", ["dist-tag", "ls", packageName], { cwd })
|
|
30
|
+
if (tags.status === 0) {
|
|
31
|
+
const version = parseLatestTag(tags.stdout)
|
|
32
|
+
if (version) return { version, source: "dist-tag" }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const detail = [direct.stderr, direct.stdout, tags.stderr, tags.stdout]
|
|
36
|
+
.map((value) => String(value || "").trim())
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join("\n")
|
|
39
|
+
|
|
40
|
+
const error = new Error("Could not resolve npm latest dist-tag safely." + (detail ? "\n" + detail : ""))
|
|
41
|
+
error.exitCode = direct.status || tags.status || 1
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { detectStack, detectTestCommands, checkWorkingTree } from "./repo-inspect.mjs"
|
|
3
|
+
import { reviewScope } from "./review-scope.mjs"
|
|
4
|
+
|
|
5
|
+
export async function buildVerificationPlan(root = process.cwd(), base = null) {
|
|
6
|
+
root = path.resolve(root)
|
|
7
|
+
const [stack, commands, workingTree] = await Promise.all([
|
|
8
|
+
detectStack(root),
|
|
9
|
+
detectTestCommands(root),
|
|
10
|
+
checkWorkingTree(root),
|
|
11
|
+
])
|
|
12
|
+
const scope = reviewScope(root, base)
|
|
13
|
+
|
|
14
|
+
const recommended = []
|
|
15
|
+
const add = (kind, command, reason) => {
|
|
16
|
+
if (!command || recommended.some((item) => item.command === command)) return
|
|
17
|
+
recommended.push({ kind, command, reason })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const item of commands) {
|
|
21
|
+
if (["test", "typecheck", "check", "lint", "build", "analyze", "ci"].includes(item.kind)) {
|
|
22
|
+
add(item.kind, item.command, "project-native " + item.kind + " command from " + item.source)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (scope.git && ["high", "critical"].includes(scope.overallRisk)) {
|
|
27
|
+
add("diff-review", "git diff --check", "high-risk changes require whitespace/conflict-marker sanity")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const acceptancePrompts = []
|
|
31
|
+
if (scope.git) {
|
|
32
|
+
for (const file of scope.files) {
|
|
33
|
+
for (const reason of file.risk.reasons) {
|
|
34
|
+
if (reason === "auth/security") acceptancePrompts.push("Verify denied/unauthorized cases, not only the happy path.")
|
|
35
|
+
if (reason === "payment") acceptancePrompts.push("Verify idempotency/retry/state-transition behavior with negative cases.")
|
|
36
|
+
if (reason === "persistence/schema") acceptancePrompts.push("Verify migration compatibility, existing rows, rollback/roll-forward behavior.")
|
|
37
|
+
if (reason === "public/interface") acceptancePrompts.push("Verify producers and consumers of changed contracts.")
|
|
38
|
+
if (reason === "delivery/infrastructure") acceptancePrompts.push("Verify configuration/deployment behavior in a representative environment.")
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
schemaVersion: 1,
|
|
45
|
+
root,
|
|
46
|
+
stack,
|
|
47
|
+
workingTree,
|
|
48
|
+
scope,
|
|
49
|
+
recommended,
|
|
50
|
+
acceptancePrompts: [...new Set(acceptancePrompts)],
|
|
51
|
+
}
|
|
52
|
+
}
|
package/lib/version.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function parse(value) {
|
|
2
|
+
const match = String(value).trim().match(
|
|
3
|
+
/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/,
|
|
4
|
+
)
|
|
5
|
+
if (!match) throw new Error(`Invalid semantic version: ${value}`)
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
core: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
9
|
+
prerelease: match[4] ? match[4].split(".") : [],
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function compareIdentifier(left, right) {
|
|
14
|
+
const leftNumber = /^\d+$/.test(left) ? Number(left) : null
|
|
15
|
+
const rightNumber = /^\d+$/.test(right) ? Number(right) : null
|
|
16
|
+
|
|
17
|
+
if (leftNumber !== null && rightNumber !== null) return Math.sign(leftNumber - rightNumber)
|
|
18
|
+
if (leftNumber !== null) return -1
|
|
19
|
+
if (rightNumber !== null) return 1
|
|
20
|
+
return left === right ? 0 : left < right ? -1 : 1
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function compareVersions(left, right) {
|
|
24
|
+
const a = parse(left)
|
|
25
|
+
const b = parse(right)
|
|
26
|
+
|
|
27
|
+
for (let index = 0; index < 3; index += 1) {
|
|
28
|
+
if (a.core[index] !== b.core[index]) {
|
|
29
|
+
return Math.sign(a.core[index] - b.core[index])
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (a.prerelease.length === 0 && b.prerelease.length === 0) return 0
|
|
34
|
+
if (a.prerelease.length === 0) return 1
|
|
35
|
+
if (b.prerelease.length === 0) return -1
|
|
36
|
+
|
|
37
|
+
const length = Math.max(a.prerelease.length, b.prerelease.length)
|
|
38
|
+
for (let index = 0; index < length; index += 1) {
|
|
39
|
+
if (a.prerelease[index] === undefined) return -1
|
|
40
|
+
if (b.prerelease[index] === undefined) return 1
|
|
41
|
+
|
|
42
|
+
const compared = compareIdentifier(a.prerelease[index], b.prerelease[index])
|
|
43
|
+
if (compared !== 0) return compared
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return 0
|
|
47
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { readFile, readdir, stat } from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
const SKIP = new Set([".git", "node_modules", ".next", "dist", "build", "coverage"])
|
|
6
|
+
|
|
7
|
+
async function walk(root) {
|
|
8
|
+
const files = []
|
|
9
|
+
async function visit(dir) {
|
|
10
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
if (SKIP.has(entry.name)) continue
|
|
13
|
+
const full = path.join(dir, entry.name)
|
|
14
|
+
if (entry.isDirectory()) await visit(full)
|
|
15
|
+
else if (entry.isFile()) files.push(full)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
await visit(root)
|
|
19
|
+
return files
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function snapshotWorkspace(root) {
|
|
23
|
+
const snapshot = {}
|
|
24
|
+
for (const file of await walk(root)) {
|
|
25
|
+
const info = await stat(file).catch(() => null)
|
|
26
|
+
if (!info || info.size > 2 * 1024 * 1024) continue
|
|
27
|
+
const bytes = await readFile(file)
|
|
28
|
+
snapshot[path.relative(root, file)] = {
|
|
29
|
+
size: bytes.length,
|
|
30
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return snapshot
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function diffWorkspaceSnapshots(before, after) {
|
|
37
|
+
const paths = new Set([...Object.keys(before), ...Object.keys(after)])
|
|
38
|
+
const changed = []
|
|
39
|
+
for (const file of [...paths].sort()) {
|
|
40
|
+
if (!before[file]) changed.push({ path: file, change: "added" })
|
|
41
|
+
else if (!after[file]) changed.push({ path: file, change: "removed" })
|
|
42
|
+
else if (before[file].sha256 !== after[file].sha256) changed.push({ path: file, change: "modified" })
|
|
43
|
+
}
|
|
44
|
+
return changed
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises"
|
|
3
|
+
import { spawnSync } from "node:child_process"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
|
|
6
|
+
function git(root, args) {
|
|
7
|
+
return spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 })
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function safeName(value) {
|
|
11
|
+
return String(value || "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function createTaskSandbox(root, slug, taskID, options = {}) {
|
|
15
|
+
root = path.resolve(root)
|
|
16
|
+
const base = options.baseDir
|
|
17
|
+
? path.resolve(options.baseDir)
|
|
18
|
+
: path.join(path.dirname(root), "." + path.basename(root) + ".ues-sandboxes")
|
|
19
|
+
const name = safeName(slug + "-" + taskID)
|
|
20
|
+
if (!name) throw new Error("sandbox name is empty")
|
|
21
|
+
const dir = path.join(base, name)
|
|
22
|
+
if (existsSync(dir)) throw new Error("sandbox already exists: " + dir)
|
|
23
|
+
|
|
24
|
+
await mkdir(base, { recursive: true })
|
|
25
|
+
const branch = options.branch || "ues/" + name
|
|
26
|
+
const startPoint = options.startPoint || "HEAD"
|
|
27
|
+
const result = git(root, ["worktree", "add", "-b", branch, dir, startPoint])
|
|
28
|
+
if (result.status !== 0) throw new Error((result.stderr || result.stdout || "git worktree add failed").trim())
|
|
29
|
+
|
|
30
|
+
return { schemaVersion: 1, root, dir, branch, slug, taskID, startPoint }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function removeTaskSandbox(root, dir, options = {}) {
|
|
34
|
+
root = path.resolve(root)
|
|
35
|
+
dir = path.resolve(dir)
|
|
36
|
+
const result = git(root, ["worktree", "remove", ...(options.force ? ["--force"] : []), dir])
|
|
37
|
+
if (result.status !== 0 && existsSync(dir)) {
|
|
38
|
+
if (options.force) await rm(dir, { recursive: true, force: true })
|
|
39
|
+
else throw new Error((result.stderr || result.stdout || "git worktree remove failed").trim())
|
|
40
|
+
}
|
|
41
|
+
git(root, ["worktree", "prune"])
|
|
42
|
+
return { removed: !existsSync(dir), dir }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function listTaskSandboxes(root) {
|
|
46
|
+
const result = git(path.resolve(root), ["worktree", "list", "--porcelain"])
|
|
47
|
+
if (result.status !== 0) throw new Error((result.stderr || result.stdout || "git worktree list failed").trim())
|
|
48
|
+
const items = []
|
|
49
|
+
let current = null
|
|
50
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
51
|
+
if (line.startsWith("worktree ")) {
|
|
52
|
+
if (current) items.push(current)
|
|
53
|
+
current = { path: line.slice(9) }
|
|
54
|
+
} else if (current && line.startsWith("HEAD ")) current.head = line.slice(5)
|
|
55
|
+
else if (current && line.startsWith("branch ")) current.branch = line.slice(7)
|
|
56
|
+
}
|
|
57
|
+
if (current) items.push(current)
|
|
58
|
+
return items
|
|
59
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-agent-skill",
|
|
3
|
+
"version": "7.7.0",
|
|
4
|
+
"description": "Adaptive crash-safe engineering runtime, durable orchestration, structured evidence, learning loop and control center for OpenCode",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ocskill": "bin/ocskill.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/ocskill.mjs",
|
|
11
|
+
"lib/",
|
|
12
|
+
"scripts/",
|
|
13
|
+
"global-config/",
|
|
14
|
+
"evals/",
|
|
15
|
+
"docs/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"syntax": "node scripts/syntax-check.mjs",
|
|
22
|
+
"validate": "node scripts/validate.mjs",
|
|
23
|
+
"evals": "node scripts/eval-skills.mjs",
|
|
24
|
+
"evals:live:validate": "node scripts/validate-live-suite.mjs",
|
|
25
|
+
"test": "node --test test/*.test.mjs",
|
|
26
|
+
"ci": "npm run syntax && npm run validate && npm run evals && npm run evals:router && npm run evals:live:validate && npm run evals:long:validate && npm test && npm pack --dry-run && npm run smoke:pack",
|
|
27
|
+
"postinstall": "node scripts/install.mjs",
|
|
28
|
+
"preuninstall": "node scripts/uninstall.mjs",
|
|
29
|
+
"prepublishOnly": "npm run ci",
|
|
30
|
+
"evals:live": "node scripts/eval-live.mjs",
|
|
31
|
+
"smoke:pack": "node scripts/smoke-packed-install.mjs",
|
|
32
|
+
"inspect:stack": "node scripts/detect-stack.mjs",
|
|
33
|
+
"inspect:tests": "node scripts/detect-test-commands.mjs",
|
|
34
|
+
"inspect:repo": "node scripts/repo-map.mjs",
|
|
35
|
+
"inspect:impact": "node scripts/impact-map.mjs",
|
|
36
|
+
"inspect:evidence": "node scripts/collect-evidence.mjs",
|
|
37
|
+
"inspect:git": "node scripts/check-working-tree.mjs",
|
|
38
|
+
"evals:report": "node scripts/eval-report.mjs",
|
|
39
|
+
"evals:router": "node scripts/eval-router.mjs",
|
|
40
|
+
"evals:long:validate": "node scripts/validate-live-suite.mjs --suite long",
|
|
41
|
+
"evals:long": "node scripts/eval-live.mjs --suite long",
|
|
42
|
+
"dashboard": "node scripts/control-center.mjs"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"opencode",
|
|
46
|
+
"agent-skills",
|
|
47
|
+
"coding-agent",
|
|
48
|
+
"developer-tools",
|
|
49
|
+
"engineering-workflow",
|
|
50
|
+
"code-review",
|
|
51
|
+
"debugging",
|
|
52
|
+
"verification"
|
|
53
|
+
],
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/laivannha0202/opencode-agent-skill-.git"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/laivannha0202/opencode-agent-skill-#readme",
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/laivannha0202/opencode-agent-skill-/issues"
|
|
67
|
+
},
|
|
68
|
+
"license": "MIT"
|
|
69
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from "node:http"
|
|
3
|
+
import { readFile } from "node:fs/promises"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { collectControlCenterData, writeControlCenter } from "../lib/control-center.mjs"
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
const root = path.resolve(args[0] && !args[0].startsWith("--") ? args[0] : process.cwd())
|
|
9
|
+
const outputIndex = args.indexOf("--output")
|
|
10
|
+
const output = outputIndex >= 0 ? args[outputIndex + 1] : null
|
|
11
|
+
const portIndex = args.indexOf("--port")
|
|
12
|
+
const port = portIndex >= 0 ? Number.parseInt(args[portIndex + 1] || "4177", 10) : 4177
|
|
13
|
+
const serve = args.includes("--serve")
|
|
14
|
+
|
|
15
|
+
const result = await writeControlCenter(root, output)
|
|
16
|
+
console.log("[ues] Control Center: " + result.file)
|
|
17
|
+
|
|
18
|
+
if (serve) {
|
|
19
|
+
const server = createServer(async (req, res) => {
|
|
20
|
+
if (req.url === "/data.json") {
|
|
21
|
+
const current = await collectControlCenterData(root)
|
|
22
|
+
const body = JSON.stringify(current, null, 2) + "\n"
|
|
23
|
+
res.writeHead(200, {
|
|
24
|
+
"content-type": "application/json; charset=utf-8",
|
|
25
|
+
"cache-control": "no-store",
|
|
26
|
+
})
|
|
27
|
+
res.end(body)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
const body = await readFile(result.file)
|
|
31
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" })
|
|
32
|
+
res.end(body)
|
|
33
|
+
})
|
|
34
|
+
server.listen(port, "127.0.0.1", () => {
|
|
35
|
+
console.log("[ues] Serving http://127.0.0.1:" + port)
|
|
36
|
+
})
|
|
37
|
+
}
|