opencode-agent-skill 11.0.0 → 13.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +119 -14
  3. package/bin/ocskill.mjs +416 -83
  4. package/docs/DETERMINISTIC-TOOLS.md +1 -1
  5. package/docs/ENGINEERING-DESIGN.md +4 -4
  6. package/docs/EVALS.md +3 -3
  7. package/docs/GITHUB-RULESET.md +50 -0
  8. package/docs/NPM-PUBLISH.md +22 -8
  9. package/docs/OPENCODE-COMPAT.md +14 -4
  10. package/docs/TRACE-SCHEMA.md +1 -1
  11. package/docs/V11-PERCEPTION-ADAPTIVE.md +2 -2
  12. package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +27 -0
  13. package/docs/V13-PARALLEL-WEAK-MODEL-RUNTIME.md +75 -0
  14. package/evals/repo-scale/tasks.json +62 -0
  15. package/global-config/AGENTS.md +4 -0
  16. package/global-config/commands/resume.md +4 -1
  17. package/global-config/commands/run.md +9 -7
  18. package/global-config/plugins/ues-router/capabilities.js +1 -0
  19. package/global-config/plugins/ues-router/command-runtime.js +79 -0
  20. package/global-config/plugins/ues-router/index.js +364 -43
  21. package/global-config/plugins/ues-router/parallel-runtime.js +271 -0
  22. package/global-config/plugins/ues-router/text-runtime.js +115 -0
  23. package/global-config/plugins/ues-router/verifier-runtime.js +33 -0
  24. package/global-config/skills/dynamic-workflow/SKILL.md +7 -6
  25. package/global-config/skills/dynamic-workflow/references/workflow.md +8 -6
  26. package/global-config/skills/engineering-orchestrator/references/long-horizon.md +7 -5
  27. package/lib/cli-utils.mjs +17 -4
  28. package/lib/context-engine-v11.mjs +4 -0
  29. package/lib/context-quality.mjs +59 -0
  30. package/lib/decision-policy.mjs +23 -0
  31. package/lib/installer.mjs +49 -16
  32. package/lib/model-config.mjs +13 -1
  33. package/lib/model-performance.mjs +113 -0
  34. package/lib/model-policy.mjs +9 -2
  35. package/lib/repo-scale-fixture.mjs +45 -0
  36. package/lib/task-engine.mjs +36 -12
  37. package/lib/task-graph.mjs +32 -0
  38. package/lib/text-encoding.mjs +74 -0
  39. package/lib/work-plan-scope.mjs +49 -0
  40. package/lib/worktree-sandbox.mjs +141 -12
  41. package/package.json +8 -4
  42. package/scripts/check-release-consistency.mjs +260 -0
  43. package/scripts/smoke-packed-install.mjs +39 -3
  44. package/scripts/smoke-plain-install.mjs +27 -7
  45. package/scripts/validate-repo-scale-suite.mjs +27 -0
  46. package/scripts/validate-v12-foundation.mjs +24 -0
  47. package/scripts/validate.mjs +18 -1
package/bin/ocskill.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { writeFileSync } from "node:fs"
2
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs"
3
3
  import { spawnSync } from "node:child_process"
4
4
  import { fileURLToPath } from "node:url"
5
5
  import path from "node:path"
@@ -62,12 +62,13 @@ import { buildVerificationPlan } from "../lib/verification-plan.mjs"
62
62
  import { resolveAdaptiveModel, resolveCapabilityModel, resolveModel } from "../lib/model-policy.mjs"
63
63
  import { createVerificationReceipt } from "../lib/evidence-receipt.mjs"
64
64
  import { classifyEngineeringTask } from "../lib/orchestrator-policy.mjs"
65
- import { createTaskSandbox, integrateTaskSandbox, listTaskSandboxes, removeTaskSandbox } from "../lib/worktree-sandbox.mjs"
65
+ import { createTaskSandbox, integrateTaskSandbox, listTaskSandboxes, removeTaskSandbox, rollbackTaskSandbox } from "../lib/worktree-sandbox.mjs"
66
66
  import { analyzeEvalTraces, saveLearningAnalysis, readLearningState, acceptLearning, promoteLearning } from "../lib/learning-engine.mjs"
67
67
  import { hermesStatus, buildHermesDelegationPrompt, buildHermesWorkflowPrompt, hermesOneShotArgs, hermesSidecarPlan } from "../lib/hermes-bridge.mjs"
68
- import { readModelPolicy, validateModelID, writeModelPolicy } from "../lib/model-config.mjs"
68
+ import { readModelPolicy, recordModelPerformance, validateModelID, writeModelPolicy } from "../lib/model-config.mjs"
69
69
  import { evidenceStoreStatus, gcEvidenceStore, getEvidence, putEvidence } from "../lib/evidence-store.mjs"
70
70
  import { inferTaskCapabilities } from "../lib/capability-registry.mjs"
71
+ import { MODEL_TASK_CLASSES } from "../lib/model-performance.mjs"
71
72
  import { browserCapability, buildBrowserVerificationPlan } from "../lib/browser-adapter.mjs"
72
73
  import { inspectBrowserPage, summarizeBrowserInspection } from "../lib/browser-runtime.mjs"
73
74
  import { comparePngFiles, cropPngFile } from "../lib/png-diff.mjs"
@@ -76,6 +77,7 @@ import { planDynamicWorkflow } from "../lib/dynamic-workflow.mjs"
76
77
  import { lintSkillCatalog } from "../lib/skill-quality.mjs"
77
78
  import { designTokenEvidence, extractDesignTokens, inspectResponsiveLayout } from "../lib/ui-inspector.mjs"
78
79
  import {
80
+ cliErrorPayload,
79
81
  clipOutput,
80
82
  errorMessage,
81
83
  optionInt,
@@ -84,10 +86,22 @@ import {
84
86
  positionalArg,
85
87
  readJsonFile,
86
88
  readTextFile,
89
+ usageError,
87
90
  } from "../lib/cli-utils.mjs"
88
91
 
89
- const args = process.argv.slice(2)
92
+ const rawArgs = process.argv.slice(2)
93
+ const rawSeparator = rawArgs.indexOf("--")
94
+ const jsonOutput = rawArgs.some((value, index) =>
95
+ value === "--json" && (rawSeparator < 0 || index < rawSeparator),
96
+ )
97
+ const args = rawArgs.filter((value, index) =>
98
+ !(value === "--json" && (rawSeparator < 0 || index < rawSeparator)),
99
+ )
90
100
  const command = args[0] || "help"
101
+ const argsSeparator = args.indexOf("--")
102
+ const helpRequested = args.some((value, index) =>
103
+ (value === "--help" || value === "-h") && (argsSeparator < 0 || index < argsSeparator),
104
+ )
91
105
  const force = args.includes("--force")
92
106
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
93
107
 
@@ -106,7 +120,11 @@ Usage:
106
120
  ocskill impact <query> [dir] Search likely impact paths and matching lines
107
121
  ocskill evidence [dir] Collect stack, verification and Git evidence
108
122
  ocskill working-tree [dir] Report Git branch/HEAD/dirty state
109
- ocskill repo-graph [dir] Build a bounded source import/dependency graph
123
+ ocskill diff [dir] [--out file] Emit Git diff safely as UTF-8 (Windows-safe)
124
+ ocskill text-read <file> [--start N] [--max N] Read UTF-8/UTF-16 text safely with bounded output
125
+ ocskill normalize-text <file> Convert UTF-8/UTF-16 text files to UTF-8
126
+ ocskill repo-graph [dir] [--compact] [--max-files N]
127
+ Build a bounded source import/dependency graph; compact omits full node/edge payloads
110
128
  ocskill index <status|build|rebuild> [dir]
111
129
  Build/reuse the persistent incremental semantic index
112
130
  ocskill aci <search|refs|view|text> ...
@@ -123,7 +141,7 @@ Usage:
123
141
  ocskill model-policy <role> [--attempt N]
124
142
  Resolve light/standard/heavy escalation tier
125
143
  ocskill task-policy <text> Classify task mode/risk/context/model tier
126
- ocskill sandbox <action> ... Create, integrate and clean isolated Git worktree sandboxes
144
+ ocskill sandbox <action> ... Create, integrate, rollback and clean isolated Git worktree sandboxes
127
145
  Also supports capability/exec for fail-closed container verification
128
146
  ocskill learn <action> ... Analyze eval traces and promote benchmark-validated lessons
129
147
  ocskill hermes <action> ... Optional Hermes sidecar/status/task/workflow planning
@@ -150,12 +168,15 @@ Usage:
150
168
  ocskill work plan <slug> <plan.json> [dir]
151
169
  ocskill work status|resume <slug> [dir]
152
170
  ocskill work gate-receipt <slug> <plan|integration> [dir] --evidence <text> [--verdict PASS|FAIL|PARTIAL] [--verifier <role>] [--session-id <id>] [--report-file <file>] [--out <file>]
171
+ ocskill work agent-receipt <slug> <task-id> [dir] --run-id <id> --verdict PASS|FAIL --evidence <text>
153
172
  ocskill work approve-plan <slug> [dir] --evidence <plan-checker-evidence> [--receipt-file <file>]
154
173
  ocskill work start <slug> <task-id> [dir] [--lease-ms N]
155
174
  ocskill work attach-session <slug> <task-id> [dir] --run-id <id> --session-id <id> [--execution-dir <dir>] [--sandbox-dir <dir>]
156
175
  ocskill work heartbeat <slug> <task-id> [dir] --run-id <id>
157
176
  ocskill work recover <slug> [dir] [--force]
158
177
  ocskill work recover-task <slug> <task-id> [dir] [--force] [--reason <text>]
178
+ ocskill work checkpoint <slug> <task-id> [dir] --run-id <id> [--reason <text>]
179
+ ocskill work checkpoint-resumed <slug> <task-id> [dir] --run-id <id> [--reason <text>]
159
180
  ocskill work events <slug> [dir] [--limit N]
160
181
  ocskill work verify-command <slug> <task-id> [dir] --run-id <id> -- <command> [args...]
161
182
  ocskill work complete <slug> <task-id> [dir] --run-id <id> --evidence <text> [--report-file <file>]
@@ -171,11 +192,101 @@ Usage:
171
192
  ocskill models set <light|standard|heavy> <provider/model[#variant]>
172
193
  ocskill models role <role> <light|standard|heavy>
173
194
  ocskill models capability <provider/model> [--vision on|off] [--browser on|off] [--reasoning on|off] [--long-context on|off] [--cost low|medium|high] [--latency fast|medium|slow] [--quality 0..1]
195
+ ocskill models observe <provider/model> --task-class <class> --passed on|off [--retries N] [--tokens N] [--latency-ms N]
174
196
 
175
197
  --force backs up and replaces/removes state owned by another package.
176
198
  `)
177
199
  }
178
200
 
201
+ function printCommandHelp(commandName, subcommand) {
202
+ const workUsage = {
203
+ init: "ocskill work init <slug> [dir] --goal <text>",
204
+ plan: "ocskill work plan <slug> <plan.json> [dir]",
205
+ status: "ocskill work status [slug|dir]",
206
+ resume: "ocskill work resume <slug> [dir]",
207
+ "gate-receipt": "ocskill work gate-receipt <slug> <plan|integration> [dir] --evidence <text>",
208
+ "agent-receipt": "ocskill work agent-receipt <slug> <task-id> [dir] --run-id <id> --verdict PASS|FAIL --evidence <text>",
209
+ "approve-plan": "ocskill work approve-plan <slug> [dir] --evidence <text>",
210
+ start: "ocskill work start <slug> <task-id> [dir]",
211
+ "attach-session": "ocskill work attach-session <slug> <task-id> [dir] --run-id <id> --session-id <id>",
212
+ heartbeat: "ocskill work heartbeat <slug> <task-id> [dir] --run-id <id>",
213
+ recover: "ocskill work recover <slug> [dir]",
214
+ "recover-task": "ocskill work recover-task <slug> <task-id> [dir]",
215
+ checkpoint: "ocskill work checkpoint <slug> <task-id> [dir] --run-id <id> [--reason <text>]",
216
+ "checkpoint-resumed": "ocskill work checkpoint-resumed <slug> <task-id> [dir] --run-id <id> [--reason <text>]",
217
+ events: "ocskill work events <slug> [dir]",
218
+ "verify-command": "ocskill work verify-command <slug> <task-id> [dir] --run-id <id> -- <command> [args...]",
219
+ complete: "ocskill work complete <slug> <task-id> [dir] --run-id <id> --evidence <text>",
220
+ fail: "ocskill work fail <slug> <task-id> [dir] --run-id <id> --reason <text>",
221
+ decision: "ocskill work decision <slug> [dir] --text <decision>",
222
+ block: "ocskill work block <slug> [dir] --text <blocker>",
223
+ unblock: "ocskill work unblock <slug> [dir] --text <blocker>",
224
+ "verify-integration": "ocskill work verify-integration <slug> [dir] --verdict PASS|FAIL|PARTIAL --evidence <text>",
225
+ finalize: "ocskill work finalize <slug> [dir] --evidence <text>",
226
+ }
227
+ if (commandName === "work") {
228
+ if (subcommand && workUsage[subcommand]) console.log("Usage: " + workUsage[subcommand] + "\n")
229
+ else console.log("Usage: ocskill work <action> ...\n\nActions:\n " + Object.keys(workUsage).join("\n "))
230
+ return
231
+ }
232
+ if (commandName === "sandbox") {
233
+ console.log("Usage: ocskill sandbox <capability|exec|list|create|integrate|rollback|remove> ...\n")
234
+ return
235
+ }
236
+ if (commandName === "diff") {
237
+ console.log("Usage: ocskill diff [dir] [--base <ref>] [--out <utf8-file>]\n")
238
+ return
239
+ }
240
+ if (commandName === "text-read") {
241
+ console.log("Usage: ocskill text-read <file> [--start N] [--max N] [--json]\n")
242
+ return
243
+ }
244
+ if (commandName === "normalize-text") {
245
+ console.log("Usage: ocskill normalize-text <file> [--out <utf8-file>]\n")
246
+ return
247
+ }
248
+ printHelp()
249
+ }
250
+
251
+ function printCliError(error, options = {}) {
252
+ const payload = cliErrorPayload(error, {
253
+ command: options.command || args.filter((value) => value !== "--help" && value !== "-h").join(" "),
254
+ usage: options.usage,
255
+ hint: options.hint,
256
+ recoverable: options.recoverable,
257
+ })
258
+ if (jsonOutput) console.error(JSON.stringify(payload))
259
+ else {
260
+ console.error("[ocskill] " + payload.error.code + ": " + payload.error.message)
261
+ if (payload.error.hint) console.error("[ocskill] Hint: " + payload.error.hint)
262
+ }
263
+ process.exitCode = payload.exitCode
264
+ }
265
+
266
+ function pathLikeWorkRoot(value) {
267
+ return !value || value === "." || value === ".." || value.includes("/") || value.includes("\\")
268
+ }
269
+
270
+ function listWorkspaces(root) {
271
+ root = path.resolve(root)
272
+ const base = path.join(root, ".ues-work")
273
+ if (!existsSync(base)) return []
274
+ const result = []
275
+ for (const entry of readdirSync(base, { withFileTypes: true })) {
276
+ if (!entry.isDirectory()) continue
277
+ try {
278
+ const state = JSON.parse(readTextFile(path.join(base, entry.name, "STATE.json")))
279
+ result.push({
280
+ slug: entry.name,
281
+ status: state.status || null,
282
+ goal: state.goal || null,
283
+ updatedAt: state.updatedAt || null,
284
+ nextAction: state.nextAction || null,
285
+ })
286
+ } catch {}
287
+ }
288
+ return result.sort((a, b) => a.slug.localeCompare(b.slug))
289
+ }
179
290
 
180
291
  function run(executable, commandArgs, options = {}) {
181
292
  const common = { stdio: "inherit", ...options }
@@ -239,9 +350,18 @@ async function install() {
239
350
  console.log(`[ocskill] Installed resources for v${result.version}`)
240
351
  console.log(`[ocskill] OpenCode config: ${result.configDir}`)
241
352
  console.log(`[ocskill] Skills: ${result.skills.length}`)
242
- console.log(`[ocskill] Commands: ${result.commands.length}`)
353
+ if (Number(result.openCodeMajor) >= 2) {
354
+ console.log(`[ocskill] Prompt aliases: ${(result.promptAliases || []).length} (session.prompt compatibility path)`)
355
+ } else {
356
+ console.log(`[ocskill] Commands: ${result.commands.length}`)
357
+ }
243
358
  console.log(`[ocskill] Subagents: ${result.agents.length}`)
244
359
  console.log(`[ocskill] OpenCode major: ${result.openCodeMajor}`)
360
+ console.log(
361
+ Number(result.openCodeMajor) >= 2
362
+ ? "[ocskill] V13 parallel: runtime capability-gated; open a new OpenCode session and check ues.capabilities."
363
+ : "[ocskill] V13 parallel: unavailable on OpenCode 1.x; CLI/durable workflow remains available.",
364
+ )
245
365
  if ((result.plugins || []).length) console.log(`[ocskill] Router plugins: ${result.plugins.length}`)
246
366
  for (const warning of result.warnings) console.warn(`[ocskill] WARNING: ${warning}`)
247
367
  console.log("[ocskill] Start a new OpenCode session to pick up workflow changes.")
@@ -266,15 +386,25 @@ async function status() {
266
386
  console.log(`[ocskill] Sync: ${synced ? "OK" : "OUTDATED - run ocskill install"}`)
267
387
  console.log(`[ocskill] Config: ${result.configDir}`)
268
388
  console.log(`[ocskill] Skills: ${result.skillsPresent}/${result.skills.length}`)
269
- console.log(`[ocskill] Commands: ${result.commandsPresent}/${result.commands.length}`)
389
+ if (Number(result.openCodeMajor) >= 2) {
390
+ console.log(`[ocskill] Prompt aliases: ${result.promptAliasesPresent || 0}/${(result.promptAliases || []).length}`)
391
+ } else {
392
+ console.log(`[ocskill] Commands: ${result.commandsPresent}/${result.commands.length}`)
393
+ }
270
394
  console.log(`[ocskill] Subagents: ${result.agentsPresent}/${(result.agents || []).length}`)
271
395
  console.log(`[ocskill] OpenCode major: ${result.openCodeMajor ?? "legacy/unknown"}`)
396
+ console.log(
397
+ Number(result.openCodeMajor) >= 2
398
+ ? "[ocskill] V13 parallel: runtime capability-gated; verify freshDispatch with ues.capabilities in a new session."
399
+ : "[ocskill] V13 parallel: unavailable on OpenCode 1.x; use serial durable execution or upgrade the runtime.",
400
+ )
272
401
  if ((result.plugins || []).length) console.log(`[ocskill] Router plugins: ${result.pluginsPresent}/${result.plugins.length}`)
273
402
  console.log(`[ocskill] Workflow: ${result.workflowPresent ? "OK" : "MISSING"}`)
274
403
 
275
404
  if (!synced ||
276
405
  result.skillsPresent !== result.skills.length ||
277
406
  result.commandsPresent !== result.commands.length ||
407
+ (result.promptAliasesPresent || 0) !== (result.promptAliases || []).length ||
278
408
  result.agentsPresent !== (result.agents || []).length ||
279
409
  result.pluginsPresent !== (result.plugins || []).length ||
280
410
  !result.workflowPresent) {
@@ -330,8 +460,7 @@ async function inspectRepository() {
330
460
  async function inspectImpact() {
331
461
  const query = args[1]
332
462
  if (!query) {
333
- console.error("Usage: ocskill impact <query> [dir]")
334
- process.exitCode = 2
463
+ printCliError(Object.assign(new Error("Usage: ocskill impact <query> [dir]"), { code: "UES_USAGE", exitCode: 2 }))
335
464
  return
336
465
  }
337
466
  printJson(await impactMap(positionalArg(args, 2) || process.cwd(), query))
@@ -354,7 +483,26 @@ async function inspectTests() {
354
483
  }
355
484
 
356
485
  async function inspectRepoGraph() {
357
- printJson(await buildRepoGraph(positionalArg(args, 1) || process.cwd()))
486
+ const root = positionalArg(args, 1) || process.cwd()
487
+ const parsedMaxFiles = optionInt(args, "--max-files", 2500)
488
+ const maxFiles = Number.isFinite(parsedMaxFiles)
489
+ ? Math.max(100, Math.min(parsedMaxFiles, 10000))
490
+ : 2500
491
+ const graph = await buildRepoGraph(root, { maxFiles })
492
+ if (args.includes("--compact")) {
493
+ printJson({
494
+ schemaVersion: graph.schemaVersion,
495
+ root: graph.root,
496
+ scannedFiles: graph.scannedFiles,
497
+ truncated: graph.truncated,
498
+ nodeCount: graph.nodes.length,
499
+ edgeCount: graph.edges.length,
500
+ hotspots: graph.hotspots,
501
+ externalImports: graph.externalImports,
502
+ })
503
+ return
504
+ }
505
+ printJson(graph)
358
506
  }
359
507
 
360
508
  async function semanticIndexControl() {
@@ -372,8 +520,7 @@ async function semanticIndexControl() {
372
520
  }
373
521
  throw new Error("Usage: ocskill index <status|build|rebuild> [dir]")
374
522
  } catch (error) {
375
- console.error(errorMessage(error))
376
- process.exitCode = 1
523
+ printCliError(error)
377
524
  }
378
525
  }
379
526
 
@@ -414,8 +561,7 @@ async function aciControl() {
414
561
  }
415
562
  throw new Error("Usage: ocskill aci <search|refs|view|text> ...")
416
563
  } catch (error) {
417
- console.error(errorMessage(error))
418
- process.exitCode = 1
564
+ printCliError(error)
419
565
  }
420
566
  }
421
567
 
@@ -444,8 +590,7 @@ async function traceControl() {
444
590
  }
445
591
  throw new Error("Usage: ocskill trace <show|append> ...")
446
592
  } catch (error) {
447
- console.error(errorMessage(error))
448
- process.exitCode = 1
593
+ printCliError(error)
449
594
  }
450
595
  }
451
596
 
@@ -462,8 +607,7 @@ async function inspectVerificationPlan() {
462
607
  async function inspectTaskGraph() {
463
608
  const file = args[1]
464
609
  if (!file) {
465
- console.error("Usage: ocskill task-graph <plan.json>")
466
- process.exitCode = 2
610
+ printCliError(Object.assign(new Error("Usage: ocskill task-graph <plan.json>"), { code: "UES_USAGE", exitCode: 2 }))
467
611
  return
468
612
  }
469
613
  try {
@@ -472,8 +616,7 @@ async function inspectTaskGraph() {
472
616
  printJson(analysis)
473
617
  if (!analysis.valid) process.exitCode = 1
474
618
  } catch (error) {
475
- console.error(errorMessage(error))
476
- process.exitCode = 1
619
+ printCliError(error)
477
620
  }
478
621
  }
479
622
 
@@ -481,8 +624,7 @@ async function inspectContextPack() {
481
624
  const slug = args[1]
482
625
  const taskID = args[2]
483
626
  if (!slug || !taskID) {
484
- console.error("Usage: ocskill context-pack <slug> <task-id> [dir]")
485
- process.exitCode = 2
627
+ printCliError(Object.assign(new Error("Usage: ocskill context-pack <slug> <task-id> [dir]"), { code: "UES_USAGE", exitCode: 2 }))
486
628
  return
487
629
  }
488
630
  printJson(await contextPack(positionalArg(args, 3) || process.cwd(), slug, taskID))
@@ -491,9 +633,56 @@ async function inspectContextPack() {
491
633
  async function workControl() {
492
634
  const action = args[1]
493
635
  const slug = args[2]
494
- if (!action || !slug) {
495
- console.error("Usage: ocskill work <init|plan|status|resume|gate-receipt|approve-plan|start|attach-session|heartbeat|recover|recover-task|events|verify-command|complete|fail|decision|block|unblock|verify-integration|finalize> <slug> ...")
496
- process.exitCode = 2
636
+ if (!action) {
637
+ const error = new Error("Usage: ocskill work <action> ...")
638
+ error.code = "UES_USAGE"
639
+ error.exitCode = 2
640
+ printCliError(error)
641
+ return
642
+ }
643
+
644
+ if (action === "status" && pathLikeWorkRoot(slug)) {
645
+ const root = path.resolve(slug || process.cwd())
646
+ const workspaces = listWorkspaces(root)
647
+ const active = workspaces.filter((item) => item.status !== "completed")
648
+ const candidate = active.length === 1
649
+ ? active[0]
650
+ : active.length === 0 && workspaces.length === 1
651
+ ? workspaces[0]
652
+ : null
653
+ if (candidate) {
654
+ const status = await workStatus(root, candidate.slug)
655
+ printJson({
656
+ ...status,
657
+ autoResolved: true,
658
+ autoResolvedSlug: candidate.slug,
659
+ requestedRoot: root,
660
+ })
661
+ } else {
662
+ printJson({
663
+ schemaVersion: 1,
664
+ root,
665
+ autoResolved: false,
666
+ workspaces,
667
+ hint: workspaces.length > 1 ? "Pass an explicit work slug because multiple workspaces exist." : null,
668
+ })
669
+ }
670
+ return
671
+ }
672
+
673
+ if (!slug) {
674
+ const error = usageError("Usage: ocskill work " + action + " <slug> ...")
675
+ printCliError(error)
676
+ return
677
+ }
678
+
679
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(String(slug))) {
680
+ printCliError(usageError(
681
+ "Invalid work slug '" + String(slug) + "'. Use lowercase letters, numbers and dashes only.",
682
+ ), {
683
+ usage: "Usage: ocskill work " + action + " <slug> ...",
684
+ hint: "Example slug: recovery-2-foundation",
685
+ })
497
686
  return
498
687
  }
499
688
 
@@ -550,6 +739,44 @@ async function workControl() {
550
739
  }
551
740
  return
552
741
  }
742
+ if (action === "agent-receipt") {
743
+ const taskID = args[3]
744
+ const root = positionalArg(args, 4) || process.cwd()
745
+ const runId = optionValue(args, "--run-id")
746
+ const verdict = String(optionValue(args, "--verdict") || "").toUpperCase()
747
+ const evidence = String(optionValue(args, "--evidence") || "").trim()
748
+ const verifier = String(optionValue(args, "--verifier") || "ues-verifier")
749
+ const sessionID = optionValue(args, "--session-id")
750
+ if (!taskID || !runId || !["PASS", "FAIL"].includes(verdict) || !evidence) {
751
+ const error = new Error("Usage: ocskill work agent-receipt <slug> <task-id> [dir] --run-id <id> --verdict PASS|FAIL --evidence <text> [--verifier <role>] [--session-id <id>]")
752
+ error.code = "UES_USAGE"
753
+ error.exitCode = 2
754
+ throw error
755
+ }
756
+ const fingerprint = workspaceFingerprint(root)
757
+ const timestamp = new Date().toISOString()
758
+ const receipt = createVerificationReceipt({
759
+ task: taskID,
760
+ runId,
761
+ command: "ues-agent-verifier",
762
+ args: [verifier, ...(sessionID ? [sessionID] : [])],
763
+ cwd: path.resolve(root),
764
+ exitCode: verdict === "PASS" ? 0 : 1,
765
+ startedAt: timestamp,
766
+ finishedAt: timestamp,
767
+ durationMs: 0,
768
+ stdout: evidence,
769
+ stderr: verdict === "PASS" ? "" : evidence,
770
+ workspaceBefore: fingerprint,
771
+ workspaceAfter: fingerprint,
772
+ })
773
+ receipt.kind = "agent-verifier"
774
+ receipt.verifier = verifier
775
+ receipt.sessionID = sessionID || null
776
+ receipt.evidence = evidence
777
+ printJson(await recordVerificationReceipt(root, slug, taskID, receipt))
778
+ return
779
+ }
553
780
  if (action === "approve-plan") {
554
781
  const root = positionalArg(args, 3) || process.cwd()
555
782
  const receiptFile = optionValue(args, "--receipt-file")
@@ -738,19 +965,20 @@ async function workControl() {
738
965
  return
739
966
  }
740
967
 
741
- throw new Error("Unknown work action: " + action)
968
+ throw usageError("Unknown work action: " + action)
742
969
  } catch (error) {
743
- console.error(errorMessage(error))
744
- if (error?.validation) printJson(error.validation)
745
- process.exitCode = 1
970
+ if (error?.validation && !jsonOutput) printJson(error.validation)
971
+ printCliError(error, {
972
+ command: "work " + String(action || ""),
973
+ recoverable: error?.code === "UES_USAGE",
974
+ })
746
975
  }
747
976
  }
748
977
 
749
978
  async function modelPolicy() {
750
979
  const role = args[1]
751
980
  if (!role) {
752
- console.error("Usage: ocskill model-policy <role> [--attempt N]")
753
- process.exitCode = 2
981
+ printCliError(Object.assign(new Error("Usage: ocskill model-policy <role> [--attempt N]"), { code: "UES_USAGE", exitCode: 2 }))
754
982
  return
755
983
  }
756
984
  const attempt = optionInt(args, "--attempt", 1)
@@ -784,8 +1012,7 @@ async function modelsControl() {
784
1012
  const tier = args[2]
785
1013
  const model = args[3]
786
1014
  if (!["light", "standard", "heavy"].includes(tier) || !validateModelID(model)) {
787
- console.error("Usage: ocskill models set <light|standard|heavy> <provider/model[#variant]>")
788
- process.exitCode = 2
1015
+ printCliError(Object.assign(new Error("Usage: ocskill models set <light|standard|heavy> <provider/model[#variant]>"), { code: "UES_USAGE", exitCode: 2 }))
789
1016
  return
790
1017
  }
791
1018
  policy = await writeModelPolicy(getConfigDir(), {
@@ -800,8 +1027,7 @@ async function modelsControl() {
800
1027
  const role = args[2]
801
1028
  const tier = args[3]
802
1029
  if (!role || !["light", "standard", "heavy"].includes(tier)) {
803
- console.error("Usage: ocskill models role <role> <light|standard|heavy>")
804
- process.exitCode = 2
1030
+ printCliError(Object.assign(new Error("Usage: ocskill models role <role> <light|standard|heavy>"), { code: "UES_USAGE", exitCode: 2 }))
805
1031
  return
806
1032
  }
807
1033
  policy = await writeModelPolicy(getConfigDir(), {
@@ -812,11 +1038,30 @@ async function modelsControl() {
812
1038
  return
813
1039
  }
814
1040
 
1041
+ if (action === "observe") {
1042
+ const model = args[2]
1043
+ if (!validateModelID(model)) {
1044
+ printCliError(Object.assign(new Error("Usage: ocskill models observe <provider/model> --task-class <class> --passed on|off"), { code: "UES_USAGE", exitCode: 2 }))
1045
+ return
1046
+ }
1047
+ const taskClass = optionValue(args, "--task-class") || "general"
1048
+ if (!MODEL_TASK_CLASSES.includes(taskClass)) throw new Error("--task-class must be one of: " + MODEL_TASK_CLASSES.join(", "))
1049
+ const passedRaw = String(optionValue(args, "--passed") || "").toLowerCase()
1050
+ if (!["on","off","true","false","pass","fail"].includes(passedRaw)) throw new Error("--passed must be on/off, true/false, or pass/fail")
1051
+ policy = await recordModelPerformance(getConfigDir(), {
1052
+ model, taskClass, passed: ["on","true","pass"].includes(passedRaw),
1053
+ retries: optionInt(args, "--retries", 0) || 0,
1054
+ tokens: optionInt(args, "--tokens", 0) || 0,
1055
+ latencyMs: optionInt(args, "--latency-ms", 0) || 0,
1056
+ })
1057
+ printJson({ model, taskClass, recorded: true, performance: policy.performance?.[model]?.[taskClass] || null })
1058
+ return
1059
+ }
1060
+
815
1061
  if (action === "capability") {
816
1062
  const model = args[2]
817
1063
  if (!validateModelID(model)) {
818
- console.error("Usage: ocskill models capability <provider/model> [capability flags]")
819
- process.exitCode = 2
1064
+ printCliError(Object.assign(new Error("Usage: ocskill models capability <provider/model> [capability flags]"), { code: "UES_USAGE", exitCode: 2 }))
820
1065
  return
821
1066
  }
822
1067
  const current = policy.capabilities?.[model] || {}
@@ -854,8 +1099,7 @@ async function modelsControl() {
854
1099
  return
855
1100
  }
856
1101
 
857
- console.error("Usage: ocskill models <status|on|off|set|role> ...")
858
- process.exitCode = 2
1102
+ printCliError(Object.assign(new Error("Usage: ocskill models <status|on|off|set|role> ..."), { code: "UES_USAGE", exitCode: 2 }))
859
1103
  }
860
1104
 
861
1105
  async function routerControl() {
@@ -864,13 +1108,14 @@ async function routerControl() {
864
1108
  const maxValue = maxIndex >= 0 ? Number.parseInt(optionValue(args, "--max") ?? "", 10) : null
865
1109
 
866
1110
  if (!["status", "on", "off"].includes(action)) {
867
- console.error("Usage: ocskill router [status|on|off] [--max 1..6]")
868
- process.exitCode = 2
1111
+ printCliError(Object.assign(new Error("Usage: ocskill router [status|on|off] [--max 1..6]"), { code: "UES_USAGE", exitCode: 2 }))
869
1112
  return
870
1113
  }
871
1114
  if (maxIndex >= 0 && (!Number.isInteger(maxValue) || maxValue < 1 || maxValue > 6)) {
872
- console.error("--max must be an integer from 1 through 6")
873
- process.exitCode = 2
1115
+ printCliError(Object.assign(new Error("--max must be an integer from 1 through 6"), {
1116
+ code: "UES_USAGE",
1117
+ exitCode: 2,
1118
+ }))
874
1119
  return
875
1120
  }
876
1121
 
@@ -902,8 +1147,7 @@ async function routerControl() {
902
1147
  async function taskPolicyControl() {
903
1148
  const text = args.slice(1).join(" ").trim()
904
1149
  if (!text) {
905
- console.error("Usage: ocskill task-policy <text>")
906
- process.exitCode = 2
1150
+ printCliError(Object.assign(new Error("Usage: ocskill task-policy <text>"), { code: "UES_USAGE", exitCode: 2 }))
907
1151
  return
908
1152
  }
909
1153
  printJson(classifyEngineeringTask(text))
@@ -945,7 +1189,11 @@ async function sandboxControl() {
945
1189
  const taskID = args[3]
946
1190
  const root = positionalArg(args, 4) || process.cwd()
947
1191
  if (!slug || !taskID) throw new Error("Usage: ocskill sandbox create <slug> <task-id> [dir]")
948
- printJson(await createTaskSandbox(root, slug, taskID))
1192
+ printJson(await createTaskSandbox(root, slug, taskID, {
1193
+ inheritDirtyRoot: args.includes("--inherit-dirty-root"),
1194
+ allowDirtyRoot: args.includes("--allow-dirty-root"),
1195
+ startPoint: optionValue(args, "--start-point") || undefined,
1196
+ }))
949
1197
  return
950
1198
  }
951
1199
  if (action === "integrate") {
@@ -955,6 +1203,13 @@ async function sandboxControl() {
955
1203
  printJson(await integrateTaskSandbox(root, dir, { keep: args.includes("--keep") }))
956
1204
  return
957
1205
  }
1206
+ if (action === "rollback") {
1207
+ const dir = args[2]
1208
+ const root = positionalArg(args, 3) || process.cwd()
1209
+ if (!dir) throw new Error("Usage: ocskill sandbox rollback <worktree-path> [dir] [--keep]")
1210
+ printJson(await rollbackTaskSandbox(root, dir, { keep: args.includes("--keep") }))
1211
+ return
1212
+ }
958
1213
  if (action === "remove") {
959
1214
  const dir = args[2]
960
1215
  const root = positionalArg(args, 3) || process.cwd()
@@ -965,10 +1220,9 @@ async function sandboxControl() {
965
1220
  }))
966
1221
  return
967
1222
  }
968
- throw new Error("Usage: ocskill sandbox <capability|exec|list|create|integrate|remove> ...")
1223
+ throw new Error("Usage: ocskill sandbox <capability|exec|list|create|integrate|rollback|remove> ...")
969
1224
  } catch (error) {
970
- console.error(errorMessage(error))
971
- process.exitCode = 1
1225
+ printCliError(error)
972
1226
  }
973
1227
  }
974
1228
 
@@ -1005,8 +1259,7 @@ async function learningControl() {
1005
1259
  }
1006
1260
  throw new Error("Usage: ocskill learn <status|analyze|accept|promote> ...")
1007
1261
  } catch (error) {
1008
- console.error(errorMessage(error))
1009
- process.exitCode = 1
1262
+ printCliError(error)
1010
1263
  }
1011
1264
  }
1012
1265
 
@@ -1020,8 +1273,7 @@ async function hermesControl() {
1020
1273
  const slug = args[2]
1021
1274
  const root = positionalArg(args, 3) || process.cwd()
1022
1275
  if (!slug) {
1023
- console.error("Usage: ocskill hermes <workflow|exec-workflow> <slug> [dir] [--max-concurrent N]")
1024
- process.exitCode = 2
1276
+ printCliError(Object.assign(new Error("Usage: ocskill hermes <workflow|exec-workflow> <slug> [dir] [--max-concurrent N]"), { code: "UES_USAGE", exitCode: 2 }))
1025
1277
  return
1026
1278
  }
1027
1279
  const planFile = path.join(path.resolve(root), ".ues-work", slug, "PLAN.json")
@@ -1055,8 +1307,7 @@ async function hermesControl() {
1055
1307
  const taskID = args[3]
1056
1308
  const root = positionalArg(args, 4) || process.cwd()
1057
1309
  if (!slug || !taskID) {
1058
- console.error("Usage: ocskill hermes <prompt|exec> <slug> <task-id> [dir]")
1059
- process.exitCode = 2
1310
+ printCliError(Object.assign(new Error("Usage: ocskill hermes <prompt|exec> <slug> <task-id> [dir]"), { code: "UES_USAGE", exitCode: 2 }))
1060
1311
  return
1061
1312
  }
1062
1313
  const prompt = buildHermesDelegationPrompt(await contextPack(root, slug, taskID))
@@ -1080,8 +1331,7 @@ async function hermesControl() {
1080
1331
  if ((result.status ?? 1) !== 0) process.exitCode = result.status ?? 1
1081
1332
  return
1082
1333
  }
1083
- console.error("Usage: ocskill hermes <status|prompt|exec|workflow|exec-workflow> ...")
1084
- process.exitCode = 2
1334
+ printCliError(Object.assign(new Error("Usage: ocskill hermes <status|prompt|exec|workflow|exec-workflow> ..."), { code: "UES_USAGE", exitCode: 2 }))
1085
1335
  }
1086
1336
 
1087
1337
  async function evidenceStoreControl() {
@@ -1123,16 +1373,14 @@ async function evidenceStoreControl() {
1123
1373
  }
1124
1374
  throw new Error("Usage: ocskill store <status|put|get|gc> ...")
1125
1375
  } catch (error) {
1126
- console.error(errorMessage(error))
1127
- process.exitCode = 1
1376
+ printCliError(error)
1128
1377
  }
1129
1378
  }
1130
1379
 
1131
1380
  async function capabilityControl() {
1132
1381
  const text = args.slice(1).join(" ").trim()
1133
1382
  if (!text) {
1134
- console.error("Usage: ocskill capabilities <task text>")
1135
- process.exitCode = 2
1383
+ printCliError(Object.assign(new Error("Usage: ocskill capabilities <task text>"), { code: "UES_USAGE", exitCode: 2 }))
1136
1384
  return
1137
1385
  }
1138
1386
  printJson(inferTaskCapabilities(text))
@@ -1181,8 +1429,7 @@ async function visualControl() {
1181
1429
  }
1182
1430
  throw new Error("Usage: ocskill visual <spec|geometry|compare|crop|viewports> ...")
1183
1431
  } catch (error) {
1184
- console.error(errorMessage(error))
1185
- process.exitCode = 1
1432
+ printCliError(error)
1186
1433
  }
1187
1434
  }
1188
1435
 
@@ -1220,8 +1467,7 @@ async function browserControl() {
1220
1467
  }
1221
1468
  throw new Error("Usage: ocskill browser <capability|plan|inspect> ...")
1222
1469
  } catch (error) {
1223
- console.error(errorMessage(error))
1224
- process.exitCode = 1
1470
+ printCliError(error)
1225
1471
  }
1226
1472
  }
1227
1473
 
@@ -1251,16 +1497,83 @@ async function uiControl() {
1251
1497
  }
1252
1498
  throw new Error("Usage: ocskill ui <tokens|layout> ...")
1253
1499
  } catch (error) {
1254
- console.error(errorMessage(error))
1255
- process.exitCode = 1
1500
+ printCliError(error)
1256
1501
  }
1257
1502
  }
1258
1503
 
1504
+ async function diffControl() {
1505
+ const root = path.resolve(positionalArg(args, 1) || process.cwd())
1506
+ const base = optionValue(args, "--base")
1507
+ const outputFile = optionValue(args, "--out")
1508
+ const commandArgs = ["diff", "--binary", "--no-ext-diff", ...(base ? [base] : []), "--"]
1509
+ const result = runCapture("git", commandArgs, { cwd: root, maxBuffer: 16 * 1024 * 1024 })
1510
+ if ((result.status ?? 1) !== 0) {
1511
+ const error = new Error((result.stderr || result.stdout || "git diff failed").trim())
1512
+ error.code = "UES_GIT_DIFF"
1513
+ throw error
1514
+ }
1515
+ if (outputFile) {
1516
+ const resolved = path.resolve(outputFile)
1517
+ writeFileSync(resolved, result.stdout || "", "utf8")
1518
+ printJson({ schemaVersion: 1, root, base: base || null, file: resolved, encoding: "utf8", bytes: Buffer.byteLength(result.stdout || "") })
1519
+ return
1520
+ }
1521
+ if (jsonOutput) {
1522
+ printJson({ schemaVersion: 1, root, base: base || null, encoding: "utf8", diff: result.stdout || "" })
1523
+ return
1524
+ }
1525
+ process.stdout.write(result.stdout || "")
1526
+ }
1527
+
1528
+ async function textReadControl() {
1529
+ const file = positionalArg(args, 1)
1530
+ if (!file) {
1531
+ const error = new Error("Usage: ocskill text-read <file> [--start N] [--max N] [--json]")
1532
+ error.code = "UES_USAGE"
1533
+ error.exitCode = 2
1534
+ throw error
1535
+ }
1536
+ const text = readTextFile(file)
1537
+ const parsedStart = optionInt(args, "--start", 0)
1538
+ const parsedMax = optionInt(args, "--max", 12000)
1539
+ const start = Number.isFinite(parsedStart) ? Math.max(0, parsedStart) : 0
1540
+ const maxChars = Number.isFinite(parsedMax) ? Math.max(256, Math.min(parsedMax, 100000)) : 12000
1541
+ const slice = text.slice(start, start + maxChars)
1542
+ const truncated = start + slice.length < text.length
1543
+ if (jsonOutput) {
1544
+ printJson({
1545
+ schemaVersion: 1,
1546
+ file: path.resolve(file),
1547
+ chars: text.length,
1548
+ start,
1549
+ returnedChars: slice.length,
1550
+ truncated,
1551
+ text: slice,
1552
+ })
1553
+ return
1554
+ }
1555
+ process.stdout.write(slice)
1556
+ if (truncated) process.stdout.write("\n...[UES text-read truncated]...\n")
1557
+ }
1558
+
1559
+ async function normalizeTextControl() {
1560
+ const file = args[1]
1561
+ if (!file) {
1562
+ const error = new Error("Usage: ocskill normalize-text <file> [--out <utf8-file>]")
1563
+ error.code = "UES_USAGE"
1564
+ error.exitCode = 2
1565
+ throw error
1566
+ }
1567
+ const text = readTextFile(file)
1568
+ const output = path.resolve(optionValue(args, "--out") || file)
1569
+ writeFileSync(output, text, "utf8")
1570
+ printJson({ schemaVersion: 1, input: path.resolve(file), output, encoding: "utf8", bytes: Buffer.byteLength(text) })
1571
+ }
1572
+
1259
1573
  async function workflowPlanControl() {
1260
1574
  const file = args[1]
1261
1575
  if (!file) {
1262
- console.error("Usage: ocskill workflow-plan <PLAN.json> [--max-concurrent N]")
1263
- process.exitCode = 2
1576
+ printCliError(Object.assign(new Error("Usage: ocskill workflow-plan <PLAN.json> [--max-concurrent N]"), { code: "UES_USAGE", exitCode: 2 }))
1264
1577
  return
1265
1578
  }
1266
1579
  try {
@@ -1274,23 +1587,20 @@ async function workflowPlanControl() {
1274
1587
  minVisionAgentCost: optionInt(args, "--min-vision-agent-cost", 4),
1275
1588
  }))
1276
1589
  } catch (error) {
1277
- console.error(errorMessage(error))
1278
- process.exitCode = 1
1590
+ printCliError(error)
1279
1591
  }
1280
1592
  }
1281
1593
 
1282
1594
  async function skillsControl() {
1283
1595
  const action = args[1] || "lint"
1284
1596
  if (action !== "lint") {
1285
- console.error("Usage: ocskill skills lint [dir]")
1286
- process.exitCode = 2
1597
+ printCliError(Object.assign(new Error("Usage: ocskill skills lint [dir]"), { code: "UES_USAGE", exitCode: 2 }))
1287
1598
  return
1288
1599
  }
1289
1600
  try {
1290
1601
  printJson(await lintSkillCatalog(positionalArg(args, 2) || packageRoot))
1291
1602
  } catch (error) {
1292
- console.error(errorMessage(error))
1293
- process.exitCode = 1
1603
+ printCliError(error)
1294
1604
  }
1295
1605
  }
1296
1606
 
@@ -1329,8 +1639,7 @@ async function update() {
1329
1639
  compared = compareVersions(latestVersion, currentVersion)
1330
1640
  } catch (error) {
1331
1641
  console.error("[ocskill] npm returned a version that could not be compared safely; refusing update.")
1332
- console.error(errorMessage(error))
1333
- process.exitCode = 1
1642
+ printCliError(error)
1334
1643
  return
1335
1644
  }
1336
1645
 
@@ -1391,7 +1700,13 @@ async function remove() {
1391
1700
  if (code !== 0) process.exitCode = code
1392
1701
  }
1393
1702
 
1394
- switch (command) {
1703
+ async function main() {
1704
+ if (helpRequested) {
1705
+ printCommandHelp(command, args[1])
1706
+ return
1707
+ }
1708
+
1709
+ switch (command) {
1395
1710
  case "install":
1396
1711
  case "sync":
1397
1712
  await install()
@@ -1481,6 +1796,15 @@ switch (command) {
1481
1796
  case "workflow-plan":
1482
1797
  await workflowPlanControl()
1483
1798
  break
1799
+ case "diff":
1800
+ await diffControl().catch((error) => printCliError(error))
1801
+ break
1802
+ case "text-read":
1803
+ await textReadControl().catch((error) => printCliError(error))
1804
+ break
1805
+ case "normalize-text":
1806
+ await normalizeTextControl().catch((error) => printCliError(error))
1807
+ break
1484
1808
  case "ui":
1485
1809
  await uiControl()
1486
1810
  break
@@ -1519,8 +1843,17 @@ switch (command) {
1519
1843
  case "-h":
1520
1844
  printHelp()
1521
1845
  break
1522
- default:
1523
- console.error(`Unknown command: ${command}`)
1524
- printHelp()
1525
- process.exitCode = 1
1846
+ default: {
1847
+ const error = new Error("Unknown command: " + command)
1848
+ error.code = "UES_USAGE"
1849
+ error.exitCode = 2
1850
+ throw error
1851
+ }
1852
+ }
1853
+ }
1854
+
1855
+ try {
1856
+ await main()
1857
+ } catch (error) {
1858
+ printCliError(error)
1526
1859
  }