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
package/bin/ocskill.mjs
ADDED
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
3
|
+
import { spawnSync } from "node:child_process"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
import os from "node:os"
|
|
7
|
+
import {
|
|
8
|
+
PACKAGE_NAME,
|
|
9
|
+
getConfigDir,
|
|
10
|
+
getPackageVersion,
|
|
11
|
+
getStatus,
|
|
12
|
+
installResources,
|
|
13
|
+
removeResources,
|
|
14
|
+
} from "../lib/installer.mjs"
|
|
15
|
+
import { compareVersions } from "../lib/version.mjs"
|
|
16
|
+
import { resolveLatestPublishedVersion } from "../lib/update-resolver.mjs"
|
|
17
|
+
import { readRouterConfig, writeRouterConfig } from "../lib/router-config.mjs"
|
|
18
|
+
import {
|
|
19
|
+
detectStack,
|
|
20
|
+
detectTestCommands,
|
|
21
|
+
repoMap,
|
|
22
|
+
impactMap,
|
|
23
|
+
collectEvidence,
|
|
24
|
+
checkWorkingTree,
|
|
25
|
+
} from "../lib/repo-inspect.mjs"
|
|
26
|
+
import { buildRepoGraph } from "../lib/repo-graph.mjs"
|
|
27
|
+
import { analyzePlan } from "../lib/task-graph.mjs"
|
|
28
|
+
import {
|
|
29
|
+
addBlocker,
|
|
30
|
+
addDecision,
|
|
31
|
+
approvePlan,
|
|
32
|
+
completeTask,
|
|
33
|
+
contextPack,
|
|
34
|
+
failTask,
|
|
35
|
+
finalizeWork,
|
|
36
|
+
importPlan,
|
|
37
|
+
initWork,
|
|
38
|
+
recordIntegrationVerification,
|
|
39
|
+
resolveBlocker,
|
|
40
|
+
resumeWork,
|
|
41
|
+
startTask,
|
|
42
|
+
workStatus,
|
|
43
|
+
heartbeatTask,
|
|
44
|
+
recoverStaleTasks,
|
|
45
|
+
recordVerificationReceipt,
|
|
46
|
+
workspaceFingerprint,
|
|
47
|
+
} from "../lib/task-engine.mjs"
|
|
48
|
+
import { reviewScope } from "../lib/review-scope.mjs"
|
|
49
|
+
import { buildVerificationPlan } from "../lib/verification-plan.mjs"
|
|
50
|
+
import { resolveAdaptiveModel, resolveModel } from "../lib/model-policy.mjs"
|
|
51
|
+
import { createVerificationReceipt } from "../lib/evidence-receipt.mjs"
|
|
52
|
+
import { classifyEngineeringTask } from "../lib/orchestrator-policy.mjs"
|
|
53
|
+
import { createTaskSandbox, listTaskSandboxes, removeTaskSandbox } from "../lib/worktree-sandbox.mjs"
|
|
54
|
+
import { analyzeEvalTraces, saveLearningAnalysis, readLearningState, acceptLearning } from "../lib/learning-engine.mjs"
|
|
55
|
+
import { hermesStatus, buildHermesDelegationPrompt, hermesOneShotArgs } from "../lib/hermes-bridge.mjs"
|
|
56
|
+
import { readModelPolicy, validateModelID, writeModelPolicy } from "../lib/model-config.mjs"
|
|
57
|
+
|
|
58
|
+
const args = process.argv.slice(2)
|
|
59
|
+
const command = args[0] || "help"
|
|
60
|
+
const force = args.includes("--force")
|
|
61
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
62
|
+
|
|
63
|
+
function printHelp() {
|
|
64
|
+
console.log(`
|
|
65
|
+
OpenCode Universal Engineering System
|
|
66
|
+
|
|
67
|
+
Usage:
|
|
68
|
+
ocskill install [--force] Install or re-sync bundled OpenCode resources
|
|
69
|
+
ocskill status Show package/resource synchronization status
|
|
70
|
+
ocskill doctor Check Node, npm, OpenCode and installed resources
|
|
71
|
+
ocskill eval Validate the bundled static skill-routing suite
|
|
72
|
+
ocskill eval-live [options] Run baseline-vs-UES live behavioral evals
|
|
73
|
+
ocskill eval-report [paths] Aggregate live eval pass-rate/cost/tool telemetry
|
|
74
|
+
ocskill inspect [dir] Deterministic repository/stack/test-command map
|
|
75
|
+
ocskill impact <query> [dir] Search likely impact paths and matching lines
|
|
76
|
+
ocskill evidence [dir] Collect stack, verification and Git evidence
|
|
77
|
+
ocskill working-tree [dir] Report Git branch/HEAD/dirty state
|
|
78
|
+
ocskill repo-graph [dir] Build a bounded source import/dependency graph
|
|
79
|
+
ocskill review-scope [base] [dir]
|
|
80
|
+
Enumerate changed files, review coverage and risk
|
|
81
|
+
ocskill verification-plan [dir]
|
|
82
|
+
Build project-native verification recommendations
|
|
83
|
+
ocskill task-graph <plan> Validate PLAN.json and compute dependency-safe waves
|
|
84
|
+
ocskill context-pack <slug> <task> [dir]
|
|
85
|
+
Emit bounded task context for a fresh executor
|
|
86
|
+
ocskill work <action> ... Manage persistent .ues-work long-task state
|
|
87
|
+
ocskill model-policy <role> [--attempt N]
|
|
88
|
+
Resolve light/standard/heavy escalation tier
|
|
89
|
+
ocskill task-policy <text> Classify task mode/risk/context/model tier
|
|
90
|
+
ocskill sandbox <action> ... Manage isolated Git worktree task sandboxes
|
|
91
|
+
ocskill learn <action> ... Analyze eval traces and promote accepted lessons
|
|
92
|
+
ocskill hermes <action> ... Optional Hermes adapter/status
|
|
93
|
+
ocskill dashboard [dir] [--serve] [--port N]
|
|
94
|
+
Generate/serve the local UES Control Center
|
|
95
|
+
ocskill models <status|on|off|set|role> ...
|
|
96
|
+
Configure role/tier model routing, then re-sync
|
|
97
|
+
ocskill router [status|on|off] [--max N]
|
|
98
|
+
Configure the OpenCode v2 automatic skill router
|
|
99
|
+
ocskill update Update the global npm package and re-sync resources
|
|
100
|
+
ocskill remove [--force] Remove managed resources and uninstall the npm package
|
|
101
|
+
ocskill version Show package version
|
|
102
|
+
ocskill help Show this help
|
|
103
|
+
|
|
104
|
+
Long-task actions:
|
|
105
|
+
ocskill work init <slug> [dir] --goal <text>
|
|
106
|
+
ocskill work plan <slug> <plan.json> [dir]
|
|
107
|
+
ocskill work status|resume <slug> [dir]
|
|
108
|
+
ocskill work approve-plan <slug> [dir] --evidence <plan-checker-evidence>
|
|
109
|
+
ocskill work start <slug> <task-id> [dir] [--lease-ms N]
|
|
110
|
+
ocskill work heartbeat <slug> <task-id> [dir] [--run-id <id>]
|
|
111
|
+
ocskill work recover <slug> [dir] [--force]
|
|
112
|
+
ocskill work verify-command <slug> <task-id> [dir] [--run-id <id>] -- <command> [args...]
|
|
113
|
+
ocskill work complete <slug> <task-id> [dir] --evidence <text> [--report-file <file>] [--run-id <id>]
|
|
114
|
+
ocskill work fail <slug> <task-id> [dir] --reason <text> [--run-id <id>]
|
|
115
|
+
ocskill work decision <slug> [dir] --text <decision>
|
|
116
|
+
ocskill work block|unblock <slug> [dir] --text <blocker>
|
|
117
|
+
ocskill work verify-integration <slug> [dir] --verdict PASS|FAIL|PARTIAL --evidence <text> [--report-file <file>]
|
|
118
|
+
ocskill work finalize <slug> [dir] --evidence <integration-evidence>
|
|
119
|
+
|
|
120
|
+
Model routing:
|
|
121
|
+
ocskill models status
|
|
122
|
+
ocskill models on|off
|
|
123
|
+
ocskill models set <light|standard|heavy> <provider/model[#variant]>
|
|
124
|
+
ocskill models role <role> <light|standard|heavy>
|
|
125
|
+
|
|
126
|
+
--force backs up and replaces/removes state owned by another package.
|
|
127
|
+
`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findWindowsCommand(name) {
|
|
131
|
+
if (path.isAbsolute(name) && existsSync(name)) return name
|
|
132
|
+
|
|
133
|
+
const result = spawnSync("where", [name], { encoding: "utf8" })
|
|
134
|
+
if (result.status !== 0 || !result.stdout) return null
|
|
135
|
+
|
|
136
|
+
const matches = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
137
|
+
return matches.find((item) => /\.(cmd|bat)$/i.test(item)) || matches[0] || null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function quoteCmd(value) {
|
|
141
|
+
if (/^[A-Za-z0-9_@%+=:,./\\-]+$/.test(value)) return value
|
|
142
|
+
return `"${value.replaceAll('"', '""')}"`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function findNodeShimEntry(cmdPath) {
|
|
146
|
+
const dir = path.dirname(cmdPath)
|
|
147
|
+
|
|
148
|
+
if (/^npm(?:\.cmd|\.exe)?$/i.test(path.basename(cmdPath))) {
|
|
149
|
+
const entry = path.join(dir, "node_modules", "npm", "bin", "npm-cli.js")
|
|
150
|
+
if (existsSync(entry)) return entry
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let shim = ""
|
|
154
|
+
try {
|
|
155
|
+
shim = readFileSync(cmdPath, "utf8")
|
|
156
|
+
} catch {}
|
|
157
|
+
const match = shim.match(/node_modules[\\/][^\s"]+?\.(?:js|mjs)/gi)?.at(-1)
|
|
158
|
+
if (!match) return null
|
|
159
|
+
const entry = path.resolve(dir, match)
|
|
160
|
+
return existsSync(entry) ? entry : null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function run(executable, commandArgs, options = {}) {
|
|
164
|
+
const common = { stdio: "inherit", ...options }
|
|
165
|
+
|
|
166
|
+
if (process.platform !== "win32") {
|
|
167
|
+
const result = spawnSync(executable, commandArgs, common)
|
|
168
|
+
return result.status ?? 1
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const resolved = findWindowsCommand(executable)
|
|
172
|
+
if (!resolved) return 127
|
|
173
|
+
|
|
174
|
+
if (/\.(cmd|bat)$/i.test(resolved)) {
|
|
175
|
+
const entry = findNodeShimEntry(resolved)
|
|
176
|
+
if (entry) {
|
|
177
|
+
const result = spawnSync(process.execPath, [entry, ...commandArgs], common)
|
|
178
|
+
return result.status ?? 1
|
|
179
|
+
}
|
|
180
|
+
const line = [resolved, ...commandArgs].map(quoteCmd).join(" ")
|
|
181
|
+
const result = spawnSync(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", line], common)
|
|
182
|
+
return result.status ?? 1
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const result = spawnSync(resolved, commandArgs, common)
|
|
186
|
+
return result.status ?? 1
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function hasCommand(name) {
|
|
190
|
+
if (process.platform === "win32") return findWindowsCommand(name) !== null
|
|
191
|
+
return spawnSync("which", [name], { stdio: "ignore" }).status === 0
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function runCapture(executable, commandArgs, options = {}) {
|
|
195
|
+
const common = { encoding: "utf8", ...options }
|
|
196
|
+
|
|
197
|
+
if (process.platform !== "win32") {
|
|
198
|
+
return spawnSync(executable, commandArgs, common)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const resolved = findWindowsCommand(executable)
|
|
202
|
+
if (!resolved) return { status: 127, stdout: "", stderr: `Command not found: ${executable}` }
|
|
203
|
+
|
|
204
|
+
if (/\.(cmd|bat)$/i.test(resolved)) {
|
|
205
|
+
const entry = findNodeShimEntry(resolved)
|
|
206
|
+
if (entry) return spawnSync(process.execPath, [entry, ...commandArgs], common)
|
|
207
|
+
|
|
208
|
+
const line = [resolved, ...commandArgs].map(quoteCmd).join(" ")
|
|
209
|
+
return spawnSync(
|
|
210
|
+
process.env.ComSpec || "cmd.exe",
|
|
211
|
+
["/d", "/s", "/c", line],
|
|
212
|
+
common,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return spawnSync(resolved, commandArgs, common)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async function install() {
|
|
221
|
+
const result = await installResources({ force })
|
|
222
|
+
if (result.stateError) {
|
|
223
|
+
for (const warning of result.warnings) console.warn(`[ocskill] WARNING: ${warning}`)
|
|
224
|
+
console.error(`[ocskill] ERROR: ${result.stateError}`)
|
|
225
|
+
process.exitCode = 1
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
console.log(`[ocskill] Installed resources for v${result.version}`)
|
|
229
|
+
console.log(`[ocskill] OpenCode config: ${result.configDir}`)
|
|
230
|
+
console.log(`[ocskill] Skills: ${result.skills.length}`)
|
|
231
|
+
console.log(`[ocskill] Commands: ${result.commands.length}`)
|
|
232
|
+
console.log(`[ocskill] Subagents: ${result.agents.length}`)
|
|
233
|
+
console.log(`[ocskill] OpenCode major: ${result.openCodeMajor}`)
|
|
234
|
+
if ((result.plugins || []).length) console.log(`[ocskill] Router plugins: ${result.plugins.length}`)
|
|
235
|
+
for (const warning of result.warnings) console.warn(`[ocskill] WARNING: ${warning}`)
|
|
236
|
+
console.log("[ocskill] Start a new OpenCode session to pick up workflow changes.")
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function status() {
|
|
240
|
+
const currentVersion = await getPackageVersion()
|
|
241
|
+
const result = await getStatus()
|
|
242
|
+
|
|
243
|
+
if (!result.installed) {
|
|
244
|
+
console.log(`[ocskill] Package version: ${currentVersion}`)
|
|
245
|
+
console.log("[ocskill] OpenCode resources are not installed or state is invalid.")
|
|
246
|
+
console.log(`[ocskill] Expected config: ${result.configDir}`)
|
|
247
|
+
if (result.stateError) console.log(`[ocskill] State: ${result.stateError}`)
|
|
248
|
+
process.exitCode = 1
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const synced = result.version === currentVersion
|
|
253
|
+
console.log(`[ocskill] Package version: ${currentVersion}`)
|
|
254
|
+
console.log(`[ocskill] Resource version: ${result.version}`)
|
|
255
|
+
console.log(`[ocskill] Sync: ${synced ? "OK" : "OUTDATED - run ocskill install"}`)
|
|
256
|
+
console.log(`[ocskill] Config: ${result.configDir}`)
|
|
257
|
+
console.log(`[ocskill] Skills: ${result.skillsPresent}/${result.skills.length}`)
|
|
258
|
+
console.log(`[ocskill] Commands: ${result.commandsPresent}/${result.commands.length}`)
|
|
259
|
+
console.log(`[ocskill] Subagents: ${result.agentsPresent}/${(result.agents || []).length}`)
|
|
260
|
+
console.log(`[ocskill] OpenCode major: ${result.openCodeMajor ?? "legacy/unknown"}`)
|
|
261
|
+
if ((result.plugins || []).length) console.log(`[ocskill] Router plugins: ${result.pluginsPresent}/${result.plugins.length}`)
|
|
262
|
+
console.log(`[ocskill] Workflow: ${result.workflowPresent ? "OK" : "MISSING"}`)
|
|
263
|
+
|
|
264
|
+
if (!synced ||
|
|
265
|
+
result.skillsPresent !== result.skills.length ||
|
|
266
|
+
result.commandsPresent !== result.commands.length ||
|
|
267
|
+
result.agentsPresent !== (result.agents || []).length ||
|
|
268
|
+
result.pluginsPresent !== (result.plugins || []).length ||
|
|
269
|
+
!result.workflowPresent) {
|
|
270
|
+
process.exitCode = 1
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function doctor() {
|
|
275
|
+
console.log("OpenCode Universal Engineering System - doctor")
|
|
276
|
+
console.log(`Package: ${PACKAGE_NAME}`)
|
|
277
|
+
console.log(`Version: ${await getPackageVersion()}`)
|
|
278
|
+
console.log(`Node: ${process.version}`)
|
|
279
|
+
console.log(`Config: ${getConfigDir()}`)
|
|
280
|
+
console.log(`npm: ${hasCommand("npm") ? "OK" : "MISSING"}`)
|
|
281
|
+
console.log(`OpenCode: ${hasCommand("opencode") ? "OK" : "MISSING"}`)
|
|
282
|
+
if (hasCommand("opencode")) run("opencode", ["--version"])
|
|
283
|
+
await status()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function evaluate() {
|
|
287
|
+
const code = run(process.execPath, [path.join(packageRoot, "scripts", "eval-skills.mjs")])
|
|
288
|
+
if (code !== 0) process.exitCode = code
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function evaluateLive() {
|
|
292
|
+
const code = run(
|
|
293
|
+
process.execPath,
|
|
294
|
+
[path.join(packageRoot, "scripts", "eval-live.mjs"), ...args.slice(1)],
|
|
295
|
+
)
|
|
296
|
+
if (code !== 0) process.exitCode = code
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function printJson(value) {
|
|
300
|
+
console.log(JSON.stringify(value, null, 2))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function evaluateReport() {
|
|
304
|
+
const code = run(
|
|
305
|
+
process.execPath,
|
|
306
|
+
[path.join(packageRoot, "scripts", "eval-report.mjs"), ...args.slice(1)],
|
|
307
|
+
)
|
|
308
|
+
if (code !== 0) process.exitCode = code
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function inspectRepository() {
|
|
312
|
+
printJson(await repoMap(args[1] || process.cwd()))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function inspectImpact() {
|
|
316
|
+
const query = args[1]
|
|
317
|
+
if (!query) {
|
|
318
|
+
console.error("Usage: ocskill impact <query> [dir]")
|
|
319
|
+
process.exitCode = 2
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
printJson(await impactMap(args[2] || process.cwd(), query))
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function inspectEvidence() {
|
|
326
|
+
printJson(await collectEvidence(args[1] || process.cwd()))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function inspectWorkingTree() {
|
|
330
|
+
printJson(await checkWorkingTree(args[1] || process.cwd()))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function inspectStack() {
|
|
334
|
+
printJson(await detectStack(args[1] || process.cwd()))
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function inspectTests() {
|
|
338
|
+
printJson(await detectTestCommands(args[1] || process.cwd()))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function optionValue(name) {
|
|
342
|
+
const index = args.indexOf(name)
|
|
343
|
+
return index >= 0 ? args[index + 1] : null
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function inspectRepoGraph() {
|
|
347
|
+
printJson(await buildRepoGraph(args[1] || process.cwd()))
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function inspectReviewScope() {
|
|
351
|
+
const base = args[1] && !args[1].startsWith("--") ? args[1] : null
|
|
352
|
+
const root = args[2] && !args[2].startsWith("--") ? args[2] : process.cwd()
|
|
353
|
+
printJson(reviewScope(root, base))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function inspectVerificationPlan() {
|
|
357
|
+
printJson(await buildVerificationPlan(args[1] || process.cwd(), optionValue("--base")))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function inspectTaskGraph() {
|
|
361
|
+
const file = args[1]
|
|
362
|
+
if (!file) {
|
|
363
|
+
console.error("Usage: ocskill task-graph <plan.json>")
|
|
364
|
+
process.exitCode = 2
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const plan = JSON.parse(readFileSync(path.resolve(file), "utf8"))
|
|
369
|
+
const analysis = analyzePlan(plan)
|
|
370
|
+
printJson(analysis)
|
|
371
|
+
if (!analysis.valid) process.exitCode = 1
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error(error instanceof Error ? error.message : error)
|
|
374
|
+
process.exitCode = 1
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function inspectContextPack() {
|
|
379
|
+
const slug = args[1]
|
|
380
|
+
const taskID = args[2]
|
|
381
|
+
if (!slug || !taskID) {
|
|
382
|
+
console.error("Usage: ocskill context-pack <slug> <task-id> [dir]")
|
|
383
|
+
process.exitCode = 2
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
printJson(await contextPack(args[3] || process.cwd(), slug, taskID))
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function workControl() {
|
|
390
|
+
const action = args[1]
|
|
391
|
+
const slug = args[2]
|
|
392
|
+
if (!action || !slug) {
|
|
393
|
+
console.error("Usage: ocskill work <init|plan|status|resume|approve-plan|start|heartbeat|recover|verify-command|complete|fail|decision|block|unblock|verify-integration|finalize> <slug> ...")
|
|
394
|
+
process.exitCode = 2
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
if (action === "init") {
|
|
400
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
401
|
+
printJson(await initWork(root, slug, optionValue("--goal")))
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
if (action === "plan") {
|
|
405
|
+
const planFile = args[3]
|
|
406
|
+
const root = args[4] && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
407
|
+
if (!planFile) throw new Error("Usage: ocskill work plan <slug> <plan.json> [dir]")
|
|
408
|
+
const result = await importPlan(root, slug, planFile)
|
|
409
|
+
printJson({ state: result.state, analysis: result.analysis, dir: result.paths.dir })
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
if (action === "status") {
|
|
413
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
414
|
+
printJson(await workStatus(root, slug))
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
if (action === "resume") {
|
|
418
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
419
|
+
printJson(await resumeWork(root, slug))
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
if (action === "approve-plan") {
|
|
423
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
424
|
+
printJson(await approvePlan(root, slug, optionValue("--evidence")))
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
if (action === "start") {
|
|
428
|
+
const taskID = args[3]
|
|
429
|
+
const root = args[4] && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
430
|
+
if (!taskID) throw new Error("Usage: ocskill work start <slug> <task-id> [dir]")
|
|
431
|
+
printJson(await startTask(root, slug, taskID, {
|
|
432
|
+
leaseMs: Number.parseInt(optionValue("--lease-ms") || "0", 10) || undefined,
|
|
433
|
+
}))
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
if (action === "heartbeat") {
|
|
437
|
+
const taskID = args[3]
|
|
438
|
+
const root = args[4] && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
439
|
+
if (!taskID) throw new Error("Usage: ocskill work heartbeat <slug> <task-id> [dir] [--run-id <id>]")
|
|
440
|
+
printJson(await heartbeatTask(root, slug, taskID, optionValue("--run-id"), {
|
|
441
|
+
leaseMs: Number.parseInt(optionValue("--lease-ms") || "0", 10) || undefined,
|
|
442
|
+
}))
|
|
443
|
+
return
|
|
444
|
+
}
|
|
445
|
+
if (action === "recover") {
|
|
446
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
447
|
+
printJson(await recoverStaleTasks(root, slug, { force: args.includes("--force") }))
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
if (action === "verify-command") {
|
|
451
|
+
const taskID = args[3]
|
|
452
|
+
const root = args[4] && args[4] !== "--" && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
453
|
+
const separator = args.indexOf("--")
|
|
454
|
+
const executable = separator >= 0 ? args[separator + 1] : null
|
|
455
|
+
const commandArgs = separator >= 0 ? args.slice(separator + 2) : []
|
|
456
|
+
if (!taskID || !executable) {
|
|
457
|
+
throw new Error("Usage: ocskill work verify-command <slug> <task-id> [dir] [--run-id <id>] -- <command> [args...]")
|
|
458
|
+
}
|
|
459
|
+
const startedAt = new Date().toISOString()
|
|
460
|
+
const before = workspaceFingerprint(root)
|
|
461
|
+
const startedMs = Date.now()
|
|
462
|
+
const result = runCapture(executable, commandArgs, {
|
|
463
|
+
cwd: path.resolve(root),
|
|
464
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
465
|
+
})
|
|
466
|
+
const receipt = createVerificationReceipt({
|
|
467
|
+
task: taskID,
|
|
468
|
+
runId: optionValue("--run-id"),
|
|
469
|
+
command: executable,
|
|
470
|
+
args: commandArgs,
|
|
471
|
+
cwd: path.resolve(root),
|
|
472
|
+
exitCode: result.status ?? 1,
|
|
473
|
+
startedAt,
|
|
474
|
+
finishedAt: new Date().toISOString(),
|
|
475
|
+
durationMs: Date.now() - startedMs,
|
|
476
|
+
stdout: result.stdout,
|
|
477
|
+
stderr: result.stderr,
|
|
478
|
+
workspaceBefore: before,
|
|
479
|
+
workspaceAfter: workspaceFingerprint(root),
|
|
480
|
+
})
|
|
481
|
+
const recorded = await recordVerificationReceipt(root, slug, taskID, receipt)
|
|
482
|
+
const clip = (value) => {
|
|
483
|
+
const text = String(value || "")
|
|
484
|
+
return text.length <= 8000 ? text : "...[truncated]\n" + text.slice(-8000)
|
|
485
|
+
}
|
|
486
|
+
printJson({
|
|
487
|
+
receipt: recorded,
|
|
488
|
+
output: {
|
|
489
|
+
stdout: clip(result.stdout),
|
|
490
|
+
stderr: clip(result.stderr),
|
|
491
|
+
},
|
|
492
|
+
})
|
|
493
|
+
if ((result.status ?? 1) !== 0) process.exitCode = result.status ?? 1
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
if (action === "complete") {
|
|
497
|
+
const taskID = args[3]
|
|
498
|
+
const root = args[4] && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
499
|
+
if (!taskID) throw new Error("Usage: ocskill work complete <slug> <task-id> [dir] --evidence <text>")
|
|
500
|
+
const reportFile = optionValue("--report-file")
|
|
501
|
+
const report = reportFile ? readFileSync(path.resolve(reportFile), "utf8") : null
|
|
502
|
+
printJson(await completeTask(root, slug, taskID, {
|
|
503
|
+
evidence: optionValue("--evidence"),
|
|
504
|
+
report,
|
|
505
|
+
runId: optionValue("--run-id"),
|
|
506
|
+
}))
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
if (action === "fail") {
|
|
510
|
+
const taskID = args[3]
|
|
511
|
+
const root = args[4] && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
512
|
+
if (!taskID) throw new Error("Usage: ocskill work fail <slug> <task-id> [dir] --reason <text>")
|
|
513
|
+
printJson(await failTask(root, slug, taskID, optionValue("--reason"), { runId: optionValue("--run-id") }))
|
|
514
|
+
return
|
|
515
|
+
}
|
|
516
|
+
if (action === "decision") {
|
|
517
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
518
|
+
printJson(await addDecision(root, slug, optionValue("--text")))
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
if (action === "block") {
|
|
522
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
523
|
+
printJson(await addBlocker(root, slug, optionValue("--text")))
|
|
524
|
+
return
|
|
525
|
+
}
|
|
526
|
+
if (action === "unblock") {
|
|
527
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
528
|
+
printJson(await resolveBlocker(root, slug, optionValue("--text")))
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
if (action === "verify-integration") {
|
|
532
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
533
|
+
const reportFile = optionValue("--report-file")
|
|
534
|
+
const report = reportFile ? readFileSync(path.resolve(reportFile), "utf8") : null
|
|
535
|
+
printJson(await recordIntegrationVerification(
|
|
536
|
+
root,
|
|
537
|
+
slug,
|
|
538
|
+
optionValue("--verdict"),
|
|
539
|
+
optionValue("--evidence"),
|
|
540
|
+
report,
|
|
541
|
+
))
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
if (action === "finalize") {
|
|
545
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
546
|
+
printJson(await finalizeWork(root, slug, optionValue("--evidence")))
|
|
547
|
+
return
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
throw new Error("Unknown work action: " + action)
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.error(error instanceof Error ? error.message : error)
|
|
553
|
+
if (error?.validation) printJson(error.validation)
|
|
554
|
+
process.exitCode = 1
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async function modelPolicy() {
|
|
559
|
+
const role = args[1]
|
|
560
|
+
if (!role) {
|
|
561
|
+
console.error("Usage: ocskill model-policy <role> [--attempt N]")
|
|
562
|
+
process.exitCode = 2
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
const attempt = Number.parseInt(optionValue("--attempt") || "1", 10)
|
|
566
|
+
const policy = await readModelPolicy(getConfigDir())
|
|
567
|
+
const normalizedAttempt = Number.isInteger(attempt) && attempt > 0 ? attempt : 1
|
|
568
|
+
const taskText = optionValue("--text")
|
|
569
|
+
if (taskText) {
|
|
570
|
+
printJson(resolveAdaptiveModel(role, normalizedAttempt, classifyEngineeringTask(taskText), policy))
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
printJson(resolveModel(role, normalizedAttempt, policy))
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
async function modelsControl() {
|
|
578
|
+
const action = args[1] || "status"
|
|
579
|
+
let policy = await readModelPolicy(getConfigDir())
|
|
580
|
+
|
|
581
|
+
if (action === "status") {
|
|
582
|
+
printJson(policy)
|
|
583
|
+
return
|
|
584
|
+
}
|
|
585
|
+
if (action === "on" || action === "off") {
|
|
586
|
+
policy = await writeModelPolicy(getConfigDir(), { enabled: action === "on" })
|
|
587
|
+
printJson(policy)
|
|
588
|
+
console.log("[ocskill] Run 'ocskill install' to rewrite managed agent frontmatter.")
|
|
589
|
+
return
|
|
590
|
+
}
|
|
591
|
+
if (action === "set") {
|
|
592
|
+
const tier = args[2]
|
|
593
|
+
const model = args[3]
|
|
594
|
+
if (!["light", "standard", "heavy"].includes(tier) || !validateModelID(model)) {
|
|
595
|
+
console.error("Usage: ocskill models set <light|standard|heavy> <provider/model[#variant]>")
|
|
596
|
+
process.exitCode = 2
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
policy = await writeModelPolicy(getConfigDir(), {
|
|
600
|
+
enabled: true,
|
|
601
|
+
tiers: { [tier]: model },
|
|
602
|
+
})
|
|
603
|
+
printJson(policy)
|
|
604
|
+
console.log("[ocskill] Run 'ocskill install' to apply model mappings to managed agents.")
|
|
605
|
+
return
|
|
606
|
+
}
|
|
607
|
+
if (action === "role") {
|
|
608
|
+
const role = args[2]
|
|
609
|
+
const tier = args[3]
|
|
610
|
+
if (!role || !["light", "standard", "heavy"].includes(tier)) {
|
|
611
|
+
console.error("Usage: ocskill models role <role> <light|standard|heavy>")
|
|
612
|
+
process.exitCode = 2
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
policy = await writeModelPolicy(getConfigDir(), {
|
|
616
|
+
roleTiers: { [role]: tier },
|
|
617
|
+
})
|
|
618
|
+
printJson(policy)
|
|
619
|
+
console.log("[ocskill] Run 'ocskill install' to apply role-tier changes.")
|
|
620
|
+
return
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
console.error("Usage: ocskill models <status|on|off|set|role> ...")
|
|
624
|
+
process.exitCode = 2
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async function routerControl() {
|
|
628
|
+
const action = args[1] || "status"
|
|
629
|
+
const maxIndex = args.indexOf("--max")
|
|
630
|
+
const maxValue = maxIndex >= 0 ? Number.parseInt(args[maxIndex + 1] || "", 10) : null
|
|
631
|
+
|
|
632
|
+
if (!["status", "on", "off"].includes(action)) {
|
|
633
|
+
console.error("Usage: ocskill router [status|on|off] [--max 1..6]")
|
|
634
|
+
process.exitCode = 2
|
|
635
|
+
return
|
|
636
|
+
}
|
|
637
|
+
if (maxIndex >= 0 && (!Number.isInteger(maxValue) || maxValue < 1 || maxValue > 6)) {
|
|
638
|
+
console.error("--max must be an integer from 1 through 6")
|
|
639
|
+
process.exitCode = 2
|
|
640
|
+
return
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
let config = await readRouterConfig(getConfigDir())
|
|
644
|
+
if (action !== "status" || maxValue !== null) {
|
|
645
|
+
config = await writeRouterConfig(getConfigDir(), {
|
|
646
|
+
enabled: action === "on" ? true : action === "off" ? false : config.enabled,
|
|
647
|
+
...(maxValue !== null ? { maxSkills: maxValue } : {}),
|
|
648
|
+
})
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const installed = await getStatus()
|
|
652
|
+
const runtimeAvailable =
|
|
653
|
+
installed.installed &&
|
|
654
|
+
Number(installed.openCodeMajor) >= 2 &&
|
|
655
|
+
(installed.plugins || []).includes("ues-router/index.js") &&
|
|
656
|
+
installed.pluginsPresent === installed.plugins.length
|
|
657
|
+
|
|
658
|
+
console.log(`[ocskill] Router preference: ${config.enabled ? "ON" : "OFF"}`)
|
|
659
|
+
console.log(`[ocskill] Router runtime: ${runtimeAvailable ? "AVAILABLE" : "UNAVAILABLE"}`)
|
|
660
|
+
if (!runtimeAvailable) {
|
|
661
|
+
console.log("[ocskill] Runtime routing requires OpenCode 2.x followed by 'ocskill install'.")
|
|
662
|
+
}
|
|
663
|
+
console.log(`[ocskill] Max automatic skills: ${config.maxSkills}`)
|
|
664
|
+
console.log(`[ocskill] Config: ${config.file}`)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
async function taskPolicyControl() {
|
|
669
|
+
const text = args.slice(1).join(" ").trim()
|
|
670
|
+
if (!text) {
|
|
671
|
+
console.error("Usage: ocskill task-policy <text>")
|
|
672
|
+
process.exitCode = 2
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
printJson(classifyEngineeringTask(text))
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function sandboxControl() {
|
|
679
|
+
const action = args[1] || "list"
|
|
680
|
+
try {
|
|
681
|
+
if (action === "list") {
|
|
682
|
+
printJson(listTaskSandboxes(args[2] || process.cwd()))
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
if (action === "create") {
|
|
686
|
+
const slug = args[2]
|
|
687
|
+
const taskID = args[3]
|
|
688
|
+
const root = args[4] && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
689
|
+
if (!slug || !taskID) throw new Error("Usage: ocskill sandbox create <slug> <task-id> [dir]")
|
|
690
|
+
printJson(await createTaskSandbox(root, slug, taskID))
|
|
691
|
+
return
|
|
692
|
+
}
|
|
693
|
+
if (action === "remove") {
|
|
694
|
+
const dir = args[2]
|
|
695
|
+
const root = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
696
|
+
if (!dir) throw new Error("Usage: ocskill sandbox remove <worktree-path> [dir] [--force]")
|
|
697
|
+
printJson(await removeTaskSandbox(root, dir, { force: args.includes("--force") }))
|
|
698
|
+
return
|
|
699
|
+
}
|
|
700
|
+
throw new Error("Usage: ocskill sandbox <list|create|remove> ...")
|
|
701
|
+
} catch (error) {
|
|
702
|
+
console.error(error instanceof Error ? error.message : error)
|
|
703
|
+
process.exitCode = 1
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async function learningControl() {
|
|
708
|
+
const action = args[1] || "status"
|
|
709
|
+
const root = args[2] && !args[2].startsWith("--") ? args[2] : process.cwd()
|
|
710
|
+
try {
|
|
711
|
+
if (action === "status") {
|
|
712
|
+
printJson(await readLearningState(root))
|
|
713
|
+
return
|
|
714
|
+
}
|
|
715
|
+
if (action === "analyze") {
|
|
716
|
+
const evalDir = optionValue("--eval-dir") || path.join(path.resolve(root), ".ues-evals")
|
|
717
|
+
const analysis = await analyzeEvalTraces(evalDir)
|
|
718
|
+
const state = await saveLearningAnalysis(root, analysis)
|
|
719
|
+
printJson({ analysis, state })
|
|
720
|
+
return
|
|
721
|
+
}
|
|
722
|
+
if (action === "accept") {
|
|
723
|
+
const id = args[2]
|
|
724
|
+
const acceptRoot = args[3] && !args[3].startsWith("--") ? args[3] : process.cwd()
|
|
725
|
+
if (!id) throw new Error("Usage: ocskill learn accept <proposal-id> [dir]")
|
|
726
|
+
printJson(await acceptLearning(acceptRoot, id))
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
throw new Error("Usage: ocskill learn <status|analyze|accept> ...")
|
|
730
|
+
} catch (error) {
|
|
731
|
+
console.error(error instanceof Error ? error.message : error)
|
|
732
|
+
process.exitCode = 1
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
async function hermesControl() {
|
|
737
|
+
const action = args[1] || "status"
|
|
738
|
+
if (action === "status") {
|
|
739
|
+
printJson(hermesStatus())
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
if (action === "prompt" || action === "exec") {
|
|
743
|
+
const slug = args[2]
|
|
744
|
+
const taskID = args[3]
|
|
745
|
+
const root = args[4] && !args[4].startsWith("--") ? args[4] : process.cwd()
|
|
746
|
+
if (!slug || !taskID) {
|
|
747
|
+
console.error("Usage: ocskill hermes <prompt|exec> <slug> <task-id> [dir]")
|
|
748
|
+
process.exitCode = 2
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
const prompt = buildHermesDelegationPrompt(await contextPack(root, slug, taskID))
|
|
752
|
+
if (action === "prompt") {
|
|
753
|
+
console.log(prompt)
|
|
754
|
+
return
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const status = hermesStatus()
|
|
758
|
+
if (!status.available) {
|
|
759
|
+
console.error(status.error || "Hermes CLI is unavailable")
|
|
760
|
+
process.exitCode = 1
|
|
761
|
+
return
|
|
762
|
+
}
|
|
763
|
+
const result = runCapture("hermes", hermesOneShotArgs(prompt), {
|
|
764
|
+
cwd: path.resolve(root),
|
|
765
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
766
|
+
})
|
|
767
|
+
if (result.stdout) process.stdout.write(result.stdout)
|
|
768
|
+
if (result.stderr) process.stderr.write(result.stderr)
|
|
769
|
+
if ((result.status ?? 1) !== 0) process.exitCode = result.status ?? 1
|
|
770
|
+
return
|
|
771
|
+
}
|
|
772
|
+
console.error("Usage: ocskill hermes <status|prompt|exec> ...")
|
|
773
|
+
process.exitCode = 2
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
async function dashboardControl() {
|
|
777
|
+
const forwarded = args.slice(1)
|
|
778
|
+
const code = run(process.execPath, [path.join(packageRoot, "scripts", "control-center.mjs"), ...forwarded])
|
|
779
|
+
if (code !== 0) process.exitCode = code
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
async function update() {
|
|
783
|
+
if (!hasCommand("npm")) {
|
|
784
|
+
console.error("[ocskill] npm is required to update this global package.")
|
|
785
|
+
process.exitCode = 1
|
|
786
|
+
return
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const currentVersion = await getPackageVersion()
|
|
790
|
+
let resolved
|
|
791
|
+
try {
|
|
792
|
+
resolved = resolveLatestPublishedVersion({
|
|
793
|
+
runCapture,
|
|
794
|
+
packageName: PACKAGE_NAME,
|
|
795
|
+
cwd: os.homedir(),
|
|
796
|
+
})
|
|
797
|
+
} catch (error) {
|
|
798
|
+
console.error("[ocskill] Could not determine the latest published npm version; refusing an unsafe self-update.")
|
|
799
|
+
console.error(error instanceof Error ? error.message : error)
|
|
800
|
+
process.exitCode = error?.exitCode || 1
|
|
801
|
+
return
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const latestVersion = resolved.version
|
|
805
|
+
|
|
806
|
+
let compared
|
|
807
|
+
try {
|
|
808
|
+
compared = compareVersions(latestVersion, currentVersion)
|
|
809
|
+
} catch (error) {
|
|
810
|
+
console.error("[ocskill] npm returned a version that could not be compared safely; refusing update.")
|
|
811
|
+
console.error(error instanceof Error ? error.message : error)
|
|
812
|
+
process.exitCode = 1
|
|
813
|
+
return
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (compared < 0) {
|
|
817
|
+
console.error(
|
|
818
|
+
`[ocskill] Registry latest is v${latestVersion}, older than installed v${currentVersion}; refusing downgrade.`,
|
|
819
|
+
)
|
|
820
|
+
process.exitCode = 1
|
|
821
|
+
return
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (compared === 0) {
|
|
825
|
+
console.log(`[ocskill] Already on latest npm version v${currentVersion}; re-syncing current resources.`)
|
|
826
|
+
await install()
|
|
827
|
+
return
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
console.log(`[ocskill] Updating ${PACKAGE_NAME} from v${currentVersion} to v${latestVersion}...`)
|
|
831
|
+
const code = run(
|
|
832
|
+
"npm",
|
|
833
|
+
["install", "-g", `${PACKAGE_NAME}@${latestVersion}`, "--ignore-scripts"],
|
|
834
|
+
{ cwd: os.homedir() },
|
|
835
|
+
)
|
|
836
|
+
if (code !== 0) {
|
|
837
|
+
process.exitCode = code
|
|
838
|
+
return
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
console.log("[ocskill] Re-syncing resources after update...")
|
|
842
|
+
const syncCode = run("ocskill", ["install"])
|
|
843
|
+
if (syncCode !== 0) process.exitCode = syncCode
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async function remove() {
|
|
847
|
+
const removed = await removeResources({ force })
|
|
848
|
+
if (removed.stateError) {
|
|
849
|
+
for (const warning of removed.warnings || []) {
|
|
850
|
+
console.warn(`[ocskill] WARNING: ${warning}`)
|
|
851
|
+
}
|
|
852
|
+
console.error(`[ocskill] ERROR: ${removed.stateError}`)
|
|
853
|
+
process.exitCode = 1
|
|
854
|
+
return
|
|
855
|
+
}
|
|
856
|
+
console.log(
|
|
857
|
+
`[ocskill] Removed ${removed.skills} skills, ${removed.commands} commands, ${removed.agents} subagents and ${removed.plugins || 0} plugins.`,
|
|
858
|
+
)
|
|
859
|
+
for (const warning of removed.warnings || []) {
|
|
860
|
+
console.warn(`[ocskill] WARNING: ${warning}`)
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (!hasCommand("npm")) return
|
|
864
|
+
|
|
865
|
+
const code = run(
|
|
866
|
+
"npm",
|
|
867
|
+
["uninstall", "-g", PACKAGE_NAME, "--ignore-scripts"],
|
|
868
|
+
{ cwd: os.homedir() },
|
|
869
|
+
)
|
|
870
|
+
if (code !== 0) process.exitCode = code
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
switch (command) {
|
|
874
|
+
case "install":
|
|
875
|
+
case "sync":
|
|
876
|
+
await install()
|
|
877
|
+
break
|
|
878
|
+
case "status":
|
|
879
|
+
await status()
|
|
880
|
+
break
|
|
881
|
+
case "doctor":
|
|
882
|
+
await doctor()
|
|
883
|
+
break
|
|
884
|
+
case "eval":
|
|
885
|
+
case "evals":
|
|
886
|
+
await evaluate()
|
|
887
|
+
break
|
|
888
|
+
case "eval-live":
|
|
889
|
+
await evaluateLive()
|
|
890
|
+
break
|
|
891
|
+
case "eval-report":
|
|
892
|
+
await evaluateReport()
|
|
893
|
+
break
|
|
894
|
+
case "inspect":
|
|
895
|
+
await inspectRepository()
|
|
896
|
+
break
|
|
897
|
+
case "impact":
|
|
898
|
+
await inspectImpact()
|
|
899
|
+
break
|
|
900
|
+
case "evidence":
|
|
901
|
+
await inspectEvidence()
|
|
902
|
+
break
|
|
903
|
+
case "working-tree":
|
|
904
|
+
await inspectWorkingTree()
|
|
905
|
+
break
|
|
906
|
+
case "repo-graph":
|
|
907
|
+
await inspectRepoGraph()
|
|
908
|
+
break
|
|
909
|
+
case "review-scope":
|
|
910
|
+
await inspectReviewScope()
|
|
911
|
+
break
|
|
912
|
+
case "verification-plan":
|
|
913
|
+
await inspectVerificationPlan()
|
|
914
|
+
break
|
|
915
|
+
case "task-graph":
|
|
916
|
+
await inspectTaskGraph()
|
|
917
|
+
break
|
|
918
|
+
case "context-pack":
|
|
919
|
+
await inspectContextPack()
|
|
920
|
+
break
|
|
921
|
+
case "work":
|
|
922
|
+
await workControl()
|
|
923
|
+
break
|
|
924
|
+
case "model-policy":
|
|
925
|
+
await modelPolicy()
|
|
926
|
+
break
|
|
927
|
+
case "task-policy":
|
|
928
|
+
await taskPolicyControl()
|
|
929
|
+
break
|
|
930
|
+
case "sandbox":
|
|
931
|
+
await sandboxControl()
|
|
932
|
+
break
|
|
933
|
+
case "learn":
|
|
934
|
+
await learningControl()
|
|
935
|
+
break
|
|
936
|
+
case "hermes":
|
|
937
|
+
await hermesControl()
|
|
938
|
+
break
|
|
939
|
+
case "dashboard":
|
|
940
|
+
await dashboardControl()
|
|
941
|
+
break
|
|
942
|
+
case "models":
|
|
943
|
+
await modelsControl()
|
|
944
|
+
break
|
|
945
|
+
case "detect-stack":
|
|
946
|
+
await inspectStack()
|
|
947
|
+
break
|
|
948
|
+
case "detect-tests":
|
|
949
|
+
await inspectTests()
|
|
950
|
+
break
|
|
951
|
+
case "router":
|
|
952
|
+
await routerControl()
|
|
953
|
+
break
|
|
954
|
+
case "update":
|
|
955
|
+
await update()
|
|
956
|
+
break
|
|
957
|
+
case "remove":
|
|
958
|
+
case "uninstall":
|
|
959
|
+
await remove()
|
|
960
|
+
break
|
|
961
|
+
case "version":
|
|
962
|
+
case "--version":
|
|
963
|
+
case "-v":
|
|
964
|
+
console.log(await getPackageVersion())
|
|
965
|
+
break
|
|
966
|
+
case "help":
|
|
967
|
+
case "--help":
|
|
968
|
+
case "-h":
|
|
969
|
+
printHelp()
|
|
970
|
+
break
|
|
971
|
+
default:
|
|
972
|
+
console.error(`Unknown command: ${command}`)
|
|
973
|
+
printHelp()
|
|
974
|
+
process.exitCode = 1
|
|
975
|
+
}
|