oh-my-opencode 5.1.54 → 5.1.56

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 (89) hide show
  1. package/dist/cli/index.js +29 -18
  2. package/dist/cli-node/index.js +29 -18
  3. package/dist/config/schema/agent-overrides.d.ts +64 -0
  4. package/dist/config/schema/categories.d.ts +8 -0
  5. package/dist/config/schema/oh-my-opencode-config.d.ts +64 -0
  6. package/dist/index.js +120 -48
  7. package/dist/oh-my-opencode.schema.json +112 -0
  8. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  9. package/dist/skills/programming/SKILL.md +2 -0
  10. package/dist/skills/programming/references/testing/README.md +2 -2
  11. package/dist/skills/programming/scripts/typescript/check-test-junk-rules.ts +140 -0
  12. package/dist/skills/programming/scripts/typescript/check-test-junk.test.ts +125 -0
  13. package/dist/skills/programming/scripts/typescript/check-test-junk.ts +148 -0
  14. package/dist/skills/programming/scripts/typescript/mutation-proof.test.ts +43 -0
  15. package/dist/skills/programming/scripts/typescript/mutation-proof.ts +179 -0
  16. package/dist/skills/remove-ai-slops/SKILL.md +32 -20
  17. package/dist/skills/ulw-research/scripts/source-commands.mjs +3 -1
  18. package/dist/skills/ulw-research/scripts/source-integrity.test.ts +39 -2
  19. package/dist/tools/delegate-task/models-distribution.d.ts +1 -0
  20. package/dist/tui.js +28 -17
  21. package/package.json +16 -14
  22. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  23. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -0
  24. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  29. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  33. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  34. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  36. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  38. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  40. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  42. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  44. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  45. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  46. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  68. package/packages/omo-codex/plugin/package-lock.json +12 -12
  69. package/packages/omo-codex/plugin/package.json +1 -1
  70. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  71. package/packages/omo-codex/plugin/skills/programming/SKILL.md +2 -0
  72. package/packages/omo-codex/plugin/skills/programming/references/testing/README.md +2 -2
  73. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-test-junk-rules.ts +140 -0
  74. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-test-junk.ts +148 -0
  75. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/mutation-proof.ts +179 -0
  76. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +32 -20
  77. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +3 -1
  78. package/packages/omo-codex/scripts/install-dist/install-local.mjs +4 -2
  79. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  80. package/packages/shared-skills/skills/programming/SKILL.md +2 -0
  81. package/packages/shared-skills/skills/programming/references/testing/README.md +2 -2
  82. package/packages/shared-skills/skills/programming/scripts/typescript/check-test-junk-rules.ts +140 -0
  83. package/packages/shared-skills/skills/programming/scripts/typescript/check-test-junk.test.ts +125 -0
  84. package/packages/shared-skills/skills/programming/scripts/typescript/check-test-junk.ts +148 -0
  85. package/packages/shared-skills/skills/programming/scripts/typescript/mutation-proof.test.ts +43 -0
  86. package/packages/shared-skills/skills/programming/scripts/typescript/mutation-proof.ts +179 -0
  87. package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +32 -20
  88. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +3 -1
  89. package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +39 -2
@@ -0,0 +1,43 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test"
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import { join } from "node:path"
5
+
6
+ import { MutationError, runMutation } from "./mutation-proof"
7
+
8
+ let root = ""
9
+ const OWNER = "export const add = (a: number, b: number): number => a + b\n"
10
+
11
+ describe("#given a production owner and a test that exercises it", () => {
12
+ beforeAll(() => {
13
+ root = mkdtempSync(join(process.env.RUN_TMP ?? tmpdir(), "mutation-proof-"))
14
+ writeFileSync(join(root, "owner.ts"), OWNER)
15
+ writeFileSync(
16
+ join(root, "owner.test.ts"),
17
+ 'import { expect, test } from "bun:test"\nimport { add } from "./owner"\ntest("adds", () => { expect(add(1, 2)).toBe(3) })\n',
18
+ )
19
+ })
20
+
21
+ afterAll(() => rmSync(root, { recursive: true, force: true }))
22
+
23
+ test("#when the owner is broken #then the test goes red and the owner is restored byte for byte", async () => {
24
+ const result = await runMutation({
25
+ cwd: root,
26
+ file: "owner.ts",
27
+ find: "a + b",
28
+ replace: "a - b",
29
+ tests: [{ label: "candidate", command: [process.execPath, "test", "owner.test.ts"] }],
30
+ })
31
+ expect(result.outcomes.map((outcome) => [outcome.label, outcome.verdict])).toEqual([["candidate", "red"]])
32
+ expect(result.restoredSha256).toBe(result.originalSha256)
33
+ expect(readFileSync(join(root, "owner.ts"), "utf8")).toBe(OWNER)
34
+ })
35
+
36
+ test("#when the find text is not unique or empty #then it refuses before touching the file", async () => {
37
+ for (const [find, kind] of [["a", "FIND_NOT_UNIQUE"], ["", "FIND_EMPTY"]] as const) {
38
+ const attempt = runMutation({ cwd: root, file: "owner.ts", find, replace: "x", tests: [] })
39
+ await expect(attempt).rejects.toMatchObject({ kind })
40
+ }
41
+ expect(readFileSync(join(root, "owner.ts"), "utf8")).toBe(OWNER)
42
+ })
43
+ })
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Prove that a test can fail: apply one exact-match edit to a production file, run each test
4
+ * command against the mutated owner, then restore the original bytes and verify their hash.
5
+ *
6
+ * Usage:
7
+ * bun run scripts/typescript/mutation-proof.ts --file <path> --find <text> --replace <text> \
8
+ * --test '<label>=<argv...>' [--test ...] [--cwd <dir>] [--json]
9
+ *
10
+ * --test takes "label=command words"; the command is split on spaces and quoting is not
11
+ * supported, so select a test by a pattern without spaces or run the whole file.
12
+ *
13
+ * Verdicts per test: red (exit != 0, the test caught the mutation) or green (exit 0, it did not).
14
+ * The owner is restored on success, on a thrown error, and on SIGINT/SIGTERM.
15
+ * Exit codes: 0 ran and restored, 1 the find text is empty or did not match exactly once or the
16
+ * restore hash differed (the file was still restored), 2 usage, 130/143 interrupted and restored.
17
+ * Read references/testing/junk-classes.md "Proof rule" for how to read a candidate/keeper pair.
18
+ */
19
+
20
+ import { createHash } from "node:crypto"
21
+ import { readFileSync, writeFileSync } from "node:fs"
22
+ import { resolve } from "node:path"
23
+ import process from "node:process"
24
+ import { parseArgs } from "node:util"
25
+
26
+ export type MutationTest = { readonly label: string; readonly command: readonly string[] }
27
+
28
+ export type MutationOutcome = {
29
+ readonly label: string
30
+ readonly command: readonly string[]
31
+ readonly exitCode: number
32
+ readonly verdict: "red" | "green"
33
+ readonly outputTail: string
34
+ }
35
+
36
+ export type MutationResult = {
37
+ readonly file: string
38
+ readonly find: string
39
+ readonly replace: string
40
+ readonly originalSha256: string
41
+ readonly restoredSha256: string
42
+ readonly outcomes: readonly MutationOutcome[]
43
+ }
44
+
45
+ export class MutationError extends Error {
46
+ constructor(
47
+ readonly kind: "FIND_EMPTY" | "FIND_NOT_UNIQUE" | "RESTORE_MISMATCH",
48
+ message: string,
49
+ ) {
50
+ super(message)
51
+ this.name = "MutationError"
52
+ }
53
+ }
54
+
55
+ const sha256 = (bytes: Uint8Array): string => createHash("sha256").update(bytes).digest("hex")
56
+
57
+ async function runCaptured(command: readonly string[], cwd: string): Promise<{ readonly exitCode: number; readonly outputTail: string }> {
58
+ const child = Bun.spawn([...command], { cwd, stdin: "ignore", stdout: "pipe", stderr: "pipe" })
59
+ const [stdout, stderr, exitCode] = await Promise.all([
60
+ new Response(child.stdout).text(),
61
+ new Response(child.stderr).text(),
62
+ child.exited,
63
+ ])
64
+ const lines = `${stdout}${stderr}`.trimEnd().split("\n")
65
+ return { exitCode, outputTail: lines.slice(-30).join("\n") }
66
+ }
67
+
68
+ export async function runMutation(options: {
69
+ readonly cwd: string
70
+ readonly file: string
71
+ readonly find: string
72
+ readonly replace: string
73
+ readonly tests: readonly MutationTest[]
74
+ }): Promise<MutationResult> {
75
+ if (options.find.length === 0) throw new MutationError("FIND_EMPTY", `${options.file}: find text is empty`)
76
+ const full = resolve(options.cwd, options.file)
77
+ const original = readFileSync(full)
78
+ const text = original.toString("utf8")
79
+ const occurrences = text.split(options.find).length - 1
80
+ if (occurrences !== 1) {
81
+ throw new MutationError("FIND_NOT_UNIQUE", `${options.file}: find matched ${occurrences} times (need exactly 1)`)
82
+ }
83
+ const originalSha256 = sha256(original)
84
+ const outcomes: MutationOutcome[] = []
85
+ const restoreOnSignal = (signal: NodeJS.Signals): void => {
86
+ writeFileSync(full, original)
87
+ process.exit(signal === "SIGINT" ? 130 : 143)
88
+ }
89
+ process.on("SIGINT", restoreOnSignal)
90
+ process.on("SIGTERM", restoreOnSignal)
91
+ try {
92
+ writeFileSync(full, text.replace(options.find, () => options.replace))
93
+ for (const test of options.tests) {
94
+ const run = await runCaptured(test.command, options.cwd)
95
+ outcomes.push({
96
+ label: test.label,
97
+ command: test.command,
98
+ exitCode: run.exitCode,
99
+ verdict: run.exitCode === 0 ? "green" : "red",
100
+ outputTail: run.outputTail,
101
+ })
102
+ }
103
+ } finally {
104
+ writeFileSync(full, original)
105
+ process.off("SIGINT", restoreOnSignal)
106
+ process.off("SIGTERM", restoreOnSignal)
107
+ }
108
+ const restoredSha256 = sha256(readFileSync(full))
109
+ if (restoredSha256 !== originalSha256) {
110
+ throw new MutationError("RESTORE_MISMATCH", `${options.file} not restored byte for byte`)
111
+ }
112
+ return { file: options.file, find: options.find, replace: options.replace, originalSha256, restoredSha256, outcomes }
113
+ }
114
+
115
+ function parseTest(spec: string): MutationTest | undefined {
116
+ const separator = spec.indexOf("=")
117
+ if (separator <= 0) return undefined
118
+ const label = spec.slice(0, separator)
119
+ const command = spec
120
+ .slice(separator + 1)
121
+ .split(" ")
122
+ .filter((word) => word.length > 0)
123
+ return command.length === 0 ? undefined : { label, command }
124
+ }
125
+
126
+ function usage(): number {
127
+ console.error(
128
+ "usage: mutation-proof.ts --file <path> --find <text> --replace <text> --test '<label>=<argv...>' [--test ...] [--cwd <dir>] [--json]",
129
+ )
130
+ return 2
131
+ }
132
+
133
+ async function main(argv: readonly string[]): Promise<number> {
134
+ const { values } = parseArgs({
135
+ args: [...argv],
136
+ options: {
137
+ file: { type: "string" },
138
+ find: { type: "string" },
139
+ replace: { type: "string" },
140
+ test: { type: "string", multiple: true },
141
+ cwd: { type: "string", default: process.cwd() },
142
+ json: { type: "boolean", default: false },
143
+ },
144
+ strict: true,
145
+ })
146
+ if (values.file === undefined || values.find === undefined || values.replace === undefined || values.test === undefined) {
147
+ return usage()
148
+ }
149
+ const tests: MutationTest[] = []
150
+ for (const spec of values.test) {
151
+ const parsed = parseTest(spec)
152
+ if (parsed === undefined) return usage()
153
+ tests.push(parsed)
154
+ }
155
+ try {
156
+ const result = await runMutation({
157
+ cwd: values.cwd ?? process.cwd(),
158
+ file: values.file,
159
+ find: values.find,
160
+ replace: values.replace,
161
+ tests,
162
+ })
163
+ if (values.json) {
164
+ console.log(JSON.stringify(result, null, 2))
165
+ } else {
166
+ for (const outcome of result.outcomes) console.log(`${outcome.label}: ${outcome.verdict} (exit ${outcome.exitCode})`)
167
+ console.log(`restored ${result.file} (sha256 ${result.restoredSha256.slice(0, 12)})`)
168
+ }
169
+ return 0
170
+ } catch (error) {
171
+ if (error instanceof MutationError) {
172
+ console.error(`mutation-proof: ${error.kind}: ${error.message}`)
173
+ return 1
174
+ }
175
+ throw error
176
+ }
177
+ }
178
+
179
+ if (import.meta.main) process.exit(await main(process.argv.slice(2)))
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: remove-ai-slops
3
- description: "Removes AI-generated code smells from branch changes or an explicit file list behind regression tests. Use when the user asks to clean up, deslop, or remove AI-slop patterns from recent changes."
3
+ description: "Removes AI-generated code smells from branch changes or an explicit file list behind regression tests, including junk tests (prose pins, source greps, copied inventories, mock echoes), each proven by its class's rule before a cut. Use when the user asks to clean up, deslop, remove AI-slop patterns, or prune tests from recent changes."
4
4
  argument-hint: "[file ...]"
5
5
  ---
6
6
 
@@ -21,7 +21,7 @@ The core safety invariant: **behavior is locked by green tests before a single l
21
21
 
22
22
  ## Categories (what counts as slop)
23
23
 
24
- The agent looks for these nine categories. The first three are stylistic, the next three are structural, the next two are about hidden cost, and the last is about behavior coverage.
24
+ The agent looks for these ten categories. The first three are stylistic, the next three are structural, the next two are about hidden cost, the ninth is tests, and the last is size.
25
25
 
26
26
  ### Stylistic
27
27
  1. **Obvious comments** — comments restating code, trivial docstrings, section dividers, commented-out code, vague TODOs/Notes.
@@ -62,10 +62,13 @@ The agent looks for these nine categories. The first three are stylistic, the ne
62
62
 
63
63
  **Hard rule**: only apply when behavior equivalence is obvious. Do NOT change algorithms with subtle correctness implications. Do NOT micro-optimize hot paths without a benchmark. If in doubt, SKIP.
64
64
 
65
- ### Behavior coverage
66
- 9. **Missing tests** — behavior present in changed files that is not locked by any regression test. The fix is not to remove code but to ADD the narrowest test that pins the behavior. EXCEPTION: a PROSE file (prompt, `SKILL.md`, rule, markdown) has no behavioral seam — do NOT add a text/word-count/phrase pin for it; that guards a diff, not behavior. Cover only a machine-consumed value (parsed field, sentinel a runtime greps, a doc JSON sample through its real validator) or leave it to review.
65
+ ### Tests
66
+ 9. **Tests: missing or junk** — tests are code in scope like any other file, judged by the one table in the `programming` skill, `references/testing/junk-classes.md` (junk classes, retention bar, R/F/C/D marks, proof rule). Two directions:
67
+ - **Missing**: a behavior in the changed files that no test guards, where the repository keeps tests for that kind of behavior and a regression would otherwise pass unnoticed. ADD the narrowest test that passes the four-question authoring gate (`references/testing/README.md`); name its owner contract. A PROSE file (prompt, `SKILL.md`, rule, markdown) has no behavioral seam: cover only a machine-consumed value (parsed field, sentinel a runtime greps, a doc JSON sample through its real validator) or leave it to review.
68
+ - **Junk**: mark every in-scope test declaration `R` / `F` / `C` / `D`. A `D` or `C` needs the proof the table names for its class BEFORE the cut: for a behavior class (cannot fail, mock echo, source grep, duplicate contract, layer replay, implementation mirror, fabricated postcondition, input transcript) a mutation of the production owner run against the candidate and its named keeper on the unmodified test tree; for a prose pin the assertion line plus the repo's prose rule; for a change certificate the revert that turns it red; for a seam keeper the `knip`/grep showing no production caller; for a copied inventory the generator or consumer that owns it; for a capability restatement the delivery test that owns the promise (none: `F`, not `D`); for near-duplicate cases the one table that keeps every old assertion (typecheck after). A misnamed test is re-marked by what its assertions check, never cut for its name. A mutation proves `D` only when the keeper goes red and the candidate stays green (cannot fail) or both go red (redundant); a candidate that is the ONLY red is `R`, or `C` into the keeper and re-run. Mutations run in Phase 4b, serially, by the orchestrator, never by a batch worker. A test-only production seam (export, flag, getter, reset hook) whose last test was deleted goes with it.
69
+ - KEEP: everything under the retention bar (public API, protocol, config, migration, storage, security, platform, shipped-copy equality, Node-compat suites, issue regressions, machine-consumed text). A test that is red on the unchanged base is a product bug to report, never a deletion. Static or slow is never a reason to delete.
67
70
 
68
- ### Structural
71
+ ### Size
69
72
  10. **Oversized modules** — any source file exceeding **250 pure LOC** (non-blank, non-comment lines). This is an architectural defect, not a style preference. Measure: `awk '!/^[[:space:]]*$/ && !/^[[:space:]]*(#|\/\/)/' <file> | wc -l`.
70
73
 
71
74
  **When found, do NOT just flag it. Execute a full modular refactoring:**
@@ -123,16 +126,17 @@ For each in-scope source file:
123
126
 
124
127
  1. Identify the public/observable behavior the file exposes (exported functions, HTTP handlers, CLI commands, classes used elsewhere).
125
128
  2. Check whether existing tests cover that behavior. Use `git grep` / project test conventions to find related test files.
126
- 3. **If behavior is uncovered or weakly covered, write the narrowest regression test that pins current behavior BEFORE editing the file.** Tests should pin observable outputs, not implementation details. A PROSE file (prompt/`SKILL.md`/rule/markdown) is exempt — its wording is not behavior; skip the test and rely on review, or assert only a machine-consumed value.
127
- 4. Run the test suite (or at minimum the relevant tests). They must be **green** before any cleanup begins.
129
+ 3. **If a behavior the cleanup could change is uncovered, write the narrowest test that passes the authoring gate (`programming/references/testing/README.md`) BEFORE editing the file**, and name the behavior it guards. It asserts observable outputs at the real boundary, never the implementation shape the cleanup is about to change, and never a value derived from the code under test. A PROSE file (prompt/`SKILL.md`/rule/markdown) is exempt: its wording is not behavior; skip the test and rely on review, or assert only a machine-consumed value.
130
+ 4. Run the test suite (or at minimum the relevant tests). A test that is red on the unchanged base is recorded as a bug (baseline-red list), excluded from every proof, and neither fixed in this pass nor deleted; everything else must be **green** before any cleanup begins.
131
+ 5. Run the `programming` skill's static detector over every in-scope test file, the ones written in step 3 included: `bun run <programming skill dir>/scripts/typescript/check-test-junk.ts <test files>` (exit 1 lists `file:line` per junk class it can see: source grep, prose pin, fixed sleep, `.only`/`.skip`, self-comparison, lower-bound cardinality). Its findings are category-9 candidates for the Tests Ledger (marked `R`/`F`/`C`/`D` by the table once its proof runs), never automatic cuts, and a test from step 3 that it flags is rewritten before the pass continues.
128
132
 
129
- If you cannot establish a green baseline (e.g., test runner is broken), STOP and report. Do not proceed with cleanup on unverified ground.
133
+ Only a broken runner stops the pass: if the suite cannot run at all, STOP and report. Do not proceed with cleanup on unverified ground.
130
134
 
131
135
  ### Phase 3: Cleanup plan — existence first, then smells
132
136
 
133
137
  The largest, safest deletion is code that should not have existed. **Before categorizing smells, run the deletion ladder on each changed unit:**
134
138
 
135
- - **Delete entirely** — the behavior is not needed (YAGNI, speculative, dead on arrival).
139
+ - **Delete entirely** — the behavior is not needed (YAGNI, speculative, dead on arrival); for a test, its contract is already owned by a keeper or it has none (category 9, with the proof).
136
140
  - **Reuse** — an existing helper or pattern in this repo already does it; replace the reimplementation with a call to it.
137
141
  - **Platform / stdlib / native / dependency** — the language stdlib, the runtime, or an already-installed dependency already does it (a hand-rolled date picker → `<input type="date">`, a custom query parser → `URLSearchParams`, a bespoke debounce → the util already imported).
138
142
  - **Simplify in place** — it must exist; make it smaller.
@@ -159,11 +163,11 @@ File: src/bar.py
159
163
 
160
164
  **Intentional shortcuts:** if the plan deliberately keeps a bounded simplification (a naive scan fine under N rows, a global lock, an O(n²) path), mark it in-code with a `debt:` comment naming the ceiling and the upgrade trigger (in omo, prefix with `// @allow` so the comment-checker treats it as intentional), and list it under "Remaining Risks / Deferred" in the report. That section is the debt ledger — a simplification with a known ceiling and no marker is indistinguishable from a bug.
161
165
 
162
- Order rule (safest → riskiest): comments → dead code → defensive → duplication → complexity → abstraction/boundary → performance → tests → oversized-modules. This minimizes blast radius of any one change.
166
+ Order rule (safest → riskiest): comments → dead code → defensive → duplication → complexity → abstraction/boundary → performance → tests (`F` rows; `D`/`C` wait for Phase 4b) → oversized-modules. This minimizes blast radius of any one change.
163
167
 
164
168
  ### Phase 4: Parallel slop removal via `deep-low` agents in batches of 5
165
169
 
166
- Files are processed by `deep-low` category agents with the `$omo:remove-ai-slops` skill loaded, **batched 5 at a time in parallel**. The executable skill name is `remove-ai-slops`. The `deep-low` category gives the agent enough thoroughness to correctly evaluate the 9 categories and respect the KEEP rules without slipping into surface fixes; the 5-wide batch is the sweet spot — more than 5 creates result-merging noise and context contention, fewer wastes parallelism.
170
+ Files are processed by `deep-low` category agents with the `$omo:remove-ai-slops` skill loaded, **batched 5 at a time in parallel**. The executable skill name is `remove-ai-slops`. The `deep-low` category gives the agent enough thoroughness to correctly evaluate the 10 categories and respect the KEEP rules without slipping into surface fixes; the 5-wide batch is the sweet spot — more than 5 creates result-merging noise and context contention, fewer wastes parallelism.
167
171
 
168
172
  **Batching protocol** (strict):
169
173
 
@@ -189,9 +193,9 @@ Remove AI slops from: {file_path}
189
193
 
190
194
  First run the deletion ladder from Phase 3 on this file (delete entirely / reuse existing repo code / platform-stdlib-native / simplify in place); only code that must exist proceeds to smell removal.
191
195
 
192
- Then evaluate EVERY category defined in this skill's "Categories (what counts as slop)" section, applying that section's KEEP and REFACTOR rules verbatim — the Categories section you have loaded is canonical, do not work from a restated subset.
196
+ Then evaluate EVERY category defined in this skill's "Categories (what counts as slop)" section, applying that section's KEEP and REFACTOR rules verbatim — the Categories section you have loaded is canonical, do not work from a restated subset. For a test file, do NOT cut yet: mark every declaration R/F/C/D against `programming/references/testing/junk-classes.md` and return one ledger row per D/C/F (`| test | mark | keeper or reason | proposed proof |`), where the proposed proof names the class's proof kind and, for a mutation, the owner file, the exact find/replace, and the keeper to run. This worker applies `F` rows in the test file it owns and reports them in the ledger as applied; `D`/`C` rows are returned uncut; it never mutates any file to prove a test - the orchestrator runs the proofs in Phase 4b.
193
197
 
194
- Apply changes in this order (safest → riskiest): comments → dead code → defensive → duplication → complexity → abstraction/boundary → performance → oversized-modules.
198
+ Apply changes in this order (safest → riskiest): comments → dead code → defensive → duplication → complexity → abstraction/boundary → performance → tests (`F` rows; `D`/`C` wait for Phase 4b) → oversized-modules.
195
199
 
196
200
  Hard constraints:
197
201
  - Behavior MUST be preserved. When equivalence is not obvious, SKIP.
@@ -201,11 +205,15 @@ Hard constraints:
201
205
  - Diff stays minimal and scoped to slop removal.
202
206
 
203
207
  Report changes grouped by category. For each change, give before/after, why-slop, why-safe.
204
- For each skipped issue, give reason.
208
+ For each skipped issue, give reason. For a test file, end with the ledger rows.
205
209
  """
206
210
  )
207
211
  ```
208
212
 
213
+ ### Phase 4b: Prove the test cuts, serially, then cut
214
+
215
+ After the last batch finishes and no worker is running, the orchestrator takes the collected ledger rows. For each row whose proof is a mutation, run the `programming` skill's `bun run <programming skill dir>/scripts/typescript/mutation-proof.ts --file <owner> --find <text> --replace <text> --test 'candidate=<argv>' --test 'keeper=<argv>'` (add `--json` for the ledger; each `--test` value is split on spaces with no quoting, so a filter that contains a space needs a wrapper script or a file-path target instead): it applies the find/replace to the production owner, runs the candidate and the keeper on the unmodified test tree, records both verdicts, and restores the owner byte for byte (sha256-checked) before the next row. One mutation at a time, never while another worker or test run is live, and never two on the same file; a mutation touches a file outside the diff only transiently and is never committed. Rows proven per the table become the cuts; a D or C whose proof fails or was not run is an R. Non-mutation proofs (prose pin, change certificate, seam keeper, copied inventory, capability restatement, near-duplicate cases) are checked here too, from the row's evidence.
216
+
209
217
  **Batch failure handling**: a `multi_agent_v1.wait_agent` timeout only means no new mailbox update arrived, not that a `deep-low` agent failed. For long passes, require each child to send `WORKING: <file> - <current phase>` and `BLOCKED: <reason>` only when it cannot progress. Treat a running child as alive. Mark a file for retry only when the child is completed without the deliverable, ack-only after followup, explicitly `BLOCKED:`, or no longer running. Do NOT block the remaining 4 in that batch; collect successful results and retry the failed file once later. If retry also fails, escalate that file under "Issues Found & Fixed" in the final report.
210
218
 
211
219
  ### Phase 5: Verify with quality gates + critical review
@@ -260,8 +268,8 @@ Files: [N files]
260
268
 
261
269
  Behavior Lock:
262
270
  - Existing coverage: [N files already covered]
263
- - Tests added: [M new regression tests at path/to/test_X.py]
264
- - Baseline status: GREEN
271
+ - Tests added: [M gate-passing tests at path/to/test_X.py, each naming its contract]
272
+ - Baseline status: GREEN apart from the baseline-red list (recorded as bugs, excluded from proofs, not deleted): [none] OR [list]
265
273
 
266
274
  Cleanup Plan:
267
275
  - path/to/file1.ts: [ladder: 1 delete (native) + simplify-in-place] → [dead code → complexity → performance]
@@ -279,6 +287,12 @@ Per-File Results (each cut shows what replaces it):
279
287
  - Obvious comments: 5 removed → nothing
280
288
  - Over-defensive: 1 simplified (redundant isinstance on typed param)
281
289
 
290
+ Tests Ledger (category 9; one row per test declaration marked D/C/F, proofs run in Phase 4b):
291
+ | Test | Mark | Keeper / reason | Proof |
292
+ |---|---|---|---|
293
+ | `foo.test.ts > reads the source for "export"` | D | source grep; `foo.test.ts > exports parse()` owns the contract | mutation parse.ts `return tokens` -> `return []`: candidate green, keeper red; restored sha-equal |
294
+ | `skill.test.ts > mentions the gate` | D | prose pin on SKILL.md | assertion line + repo prose rule (no mutation applies) |
295
+
282
296
  Quality Gates:
283
297
  - Regression tests: PASS (12 tests, 0 failed)
284
298
  - Lint: PASS
@@ -331,7 +345,5 @@ Final Status: CLEAN | ISSUES FIXED | REQUIRES ATTENTION
331
345
  ## Quality Assurance
332
346
 
333
347
  - NEVER remove code that serves a functional purpose.
334
- - ALWAYS verify changes compile/parse and pass type-check.
335
- - ALWAYS preserve test coverage; add tests rather than remove them.
336
- - If uncertain about a change, err on the side of keeping the original code.
337
- - The default action when in doubt is SKIP, not GUESS.
348
+ - Preserve every proven contract: a test leaves only with its proof receipt (category 9), and a missing contract gets a gate-passing test, never a change certificate.
349
+ - When in doubt, SKIP and keep the original; never GUESS.
@@ -143,7 +143,9 @@ function declareOverKeptRaw(sourcesDir, id, input) {
143
143
  const dir = join(sourcesDir, String(id))
144
144
  if (input.kind !== "converted-only" || !existsSync(join(dir, "raw.bin")) || !existsSync(join(dir, "meta.json"))) return
145
145
  const before = JSON.parse(readFileSync(join(dir, "meta.json"), "utf8"))
146
- Object.assign(input, { url: before.url, raw: readFileSync(join(dir, "raw.bin")), kind: before.kind, declared: true })
146
+ const raw = readFileSync(join(dir, "raw.bin"))
147
+ if (sha256Of(raw) !== before.sha256) throw new CliError(`snapshot_hash_mismatch: ${id} raw.bin no longer matches its pinned hash`)
148
+ Object.assign(input, { url: before.url, raw, kind: before.kind, observed_at: before.observed_at, declared: true })
147
149
  delete input.path
148
150
  }
149
151
 
@@ -1,11 +1,12 @@
1
1
  import { describe, expect, onTestFinished, test } from "bun:test"
2
- import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs"
2
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs"
3
3
  import { tmpdir } from "node:os"
4
4
  import { join } from "node:path"
5
5
 
6
- import { sha256Of } from "./cli-support.mjs"
6
+ import { CliError, sha256Of } from "./cli-support.mjs"
7
7
  import { addRow, readLedger, writeLedger } from "./fact-ledger.mjs"
8
8
  import { run } from "./report-tools.mjs"
9
+ import { writeSnapshot } from "./source-snapshot.mjs"
9
10
 
10
11
  const BRIEF = readFileSync(join(import.meta.dir, "tests/fixtures/fidelity-brief-locked.md"), "utf8")
11
12
  const PAGE = '<html><body><p>The main deck spans <span title="9 m">320</span> m in total.</p><table id="sizes"><tr><th>Name</th><th>Length</th></tr><tr><td>Deck</td><td>320 m</td></tr></table></body></html>'
@@ -127,6 +128,42 @@ describe("converted text is never a raw snapshot", () => {
127
128
  })
128
129
  })
129
130
 
131
+ describe("replacement over retained raw bytes", () => {
132
+ for (const [name, tampered, recaptured] of [
133
+ ["healthy retained bytes", false, false],
134
+ ["tampered retained bytes", true, false],
135
+ ["an explicit new capture after retained drift", true, true],
136
+ ] as const) {
137
+ test(`#given ${name} #when text replaces the extraction #then only validated retained bytes keep their original observation`, async () => {
138
+ const dir = session()
139
+ const observedAt = "2026-01-01T00:00:00.000Z"
140
+ const { dir: source } = writeSnapshot(join(dir, "sources"), "S1", {
141
+ url: "https://example.invalid/bridge", html: PLAIN, extractor: "model", observed_at: observedAt,
142
+ })
143
+ if (tampered) writeFileSync(join(source, "raw.bin"), PLAIN.replace("320", "330"))
144
+ const before = Object.fromEntries(readdirSync(source).map((name) => [name, readFileSync(join(source, name))]))
145
+ const raw = recaptured ? "A new capture says 340 m." : PLAIN
146
+ const text = recaptured ? raw : "The corrected extraction says 320 m."
147
+ const replaced = snapshot(dir, "S1", "--text", file(dir, "corrected.txt", text), "--replace",
148
+ ...(recaptured ? ["--raw", file(dir, "recaptured.txt", raw)] : []))
149
+
150
+ if (tampered && !recaptured) {
151
+ await expect(replaced).rejects.toBeInstanceOf(CliError)
152
+ expect(Object.fromEntries(readdirSync(source).map((name) => [name, readFileSync(join(source, name))]))).toEqual(before)
153
+ return
154
+ }
155
+
156
+ expect((await replaced).exitCode).toBe(0)
157
+ expect(readFileSync(join(source, "raw.bin"), "utf8")).toBe(raw)
158
+ expect(readFileSync(join(source, "text.txt"), "utf8")).toBe(text)
159
+ const meta = JSON.parse(readFileSync(join(source, "meta.json"), "utf8"))
160
+ expect(meta).toMatchObject({ sha256: sha256Of(raw), text_source: "declared", text_sha256: sha256Of(text) })
161
+ if (recaptured) expect(meta.observed_at).not.toBe(observedAt)
162
+ else expect(meta.observed_at).toBe(observedAt)
163
+ })
164
+ }
165
+ })
166
+
130
167
  // chmod 000 does not stop the owner reading a file on Windows (no POSIX mode bits), so there the same
131
168
  // fail-closed condition is produced the way an unreadable path occurs on win32: raw.bin is replaced by
132
169
  // a directory, and reading it fails (EISDIR). Both paths must make verify exit 1 with the cause.
@@ -0,0 +1 @@
1
+ export declare const distributeTaskModels: import("@oh-my-opencode/omo-config-core").ModelDistributor;
package/dist/tui.js CHANGED
@@ -70209,6 +70209,7 @@ var init_agent = __esm(() => {
70209
70209
  prompt: string2().optional(),
70210
70210
  model: string2().optional(),
70211
70211
  models: array(OmoAgentModelEntrySchema).optional(),
70212
+ models_distribution: _enum(["sequential", "round_robin"]).optional(),
70212
70213
  reasoning: OmoReasoningSchema.optional(),
70213
70214
  variant: string2().optional(),
70214
70215
  reasoningEffort: OmoReasoningEffortSchema.optional(),
@@ -70241,6 +70242,7 @@ var init_category = __esm(() => {
70241
70242
  description: string2().optional(),
70242
70243
  model: string2().optional(),
70243
70244
  models: array(union([string2(), OmoFallbackModelObjectSchema])).optional(),
70245
+ models_distribution: _enum(["sequential", "round_robin"]).optional(),
70244
70246
  reasoning: OmoReasoningSchema.optional(),
70245
70247
  temperature: number2().min(0).max(2).optional(),
70246
70248
  top_p: number2().min(0).max(1).optional(),
@@ -72075,9 +72077,13 @@ function resolveModelReferences(view) {
72075
72077
  }
72076
72078
  var init_model_reference_resolution = () => {};
72077
72079
 
72080
+ // packages/omo-config-core/src/models/models-distribution.ts
72081
+ var init_models_distribution = () => {};
72082
+
72078
72083
  // packages/omo-config-core/src/models/index.ts
72079
72084
  var init_models = __esm(() => {
72080
72085
  init_model_reference_resolution();
72086
+ init_models_distribution();
72081
72087
  });
72082
72088
 
72083
72089
  // packages/omo-config-core/src/writer/types.ts
@@ -72734,11 +72740,11 @@ function recordFields(value, fields) {
72734
72740
  }
72735
72741
  function modelInput(view) {
72736
72742
  const agents = isPlainRecord(view.agents) ? Object.fromEntries(Object.entries(view.agents).flatMap(([name, definition]) => {
72737
- const fields = recordFields(definition, ["description", "prompt", "model", "models", "variant", "reasoningEffort", "tools", "temperature", "disable"]);
72743
+ const fields = recordFields(definition, ["description", "prompt", "model", "models", "models_distribution", "variant", "reasoningEffort", "tools", "temperature", "disable"]);
72738
72744
  return fields === undefined ? [] : [[name, fields]];
72739
72745
  })) : undefined;
72740
72746
  const categories = isPlainRecord(view.categories) ? Object.fromEntries(Object.entries(view.categories).flatMap(([name, definition]) => {
72741
- const fields = recordFields(definition, ["description", "model", "fallback_models", "variant", "temperature", "top_p", "maxTokens", "thinking", "reasoningEffort", "textVerbosity", "tools", "prompt_append", "max_prompt_tokens", "is_unstable_agent", "disable"]);
72747
+ const fields = recordFields(definition, ["description", "model", "models", "models_distribution", "fallback_models", "variant", "temperature", "top_p", "maxTokens", "thinking", "reasoningEffort", "textVerbosity", "tools", "prompt_append", "max_prompt_tokens", "is_unstable_agent", "disable"]);
72742
72748
  return fields === undefined ? [] : [[name, fields]];
72743
72749
  })) : undefined;
72744
72750
  return {
@@ -73029,6 +73035,7 @@ var init_agent_overrides = __esm(() => {
73029
73035
  AgentOverrideConfigSchema = object({
73030
73036
  model: string2().optional(),
73031
73037
  models: array(union([string2(), FallbackModelObjectSchema])).optional(),
73038
+ models_distribution: _enum(["sequential", "round_robin"]).optional(),
73032
73039
  fallback_models: FallbackModelsSchema.optional(),
73033
73040
  reasoning: OmoReasoningSchema.optional(),
73034
73041
  variant: string2().optional(),
@@ -73146,6 +73153,7 @@ var init_categories = __esm(() => {
73146
73153
  description: string2().optional(),
73147
73154
  model: string2().optional(),
73148
73155
  models: array(union([string2(), FallbackModelObjectSchema])).optional(),
73156
+ models_distribution: _enum(["sequential", "round_robin"]).optional(),
73149
73157
  fallback_models: FallbackModelsSchema.optional(),
73150
73158
  reasoning: OmoReasoningSchema.optional(),
73151
73159
  variant: string2().optional(),
@@ -73889,7 +73897,8 @@ function materializeAgentModelChains(config) {
73889
73897
  return [name, {
73890
73898
  ...rest,
73891
73899
  ...primarySettings,
73892
- fallback_models: fallbacks
73900
+ fallback_models: fallbacks,
73901
+ ...agent.models_distribution === "round_robin" ? { models: agent.models } : {}
73893
73902
  }];
73894
73903
  }));
73895
73904
  return changed ? { ...config, agents } : config;
@@ -75706,7 +75715,7 @@ init_shared();
75706
75715
  // package.json
75707
75716
  var package_default = {
75708
75717
  name: "oh-my-opencode",
75709
- version: "5.1.54",
75718
+ version: "5.1.56",
75710
75719
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
75711
75720
  main: "./dist/index.js",
75712
75721
  types: "dist/index.d.ts",
@@ -75730,6 +75739,7 @@ var package_default = {
75730
75739
  "packages/openclaw-core",
75731
75740
  "packages/boulder-state",
75732
75741
  "packages/memory-core",
75742
+ "packages/device-service-core",
75733
75743
  "packages/isolation-core",
75734
75744
  "packages/telemetry-core",
75735
75745
  "packages/claude-code-compat-core",
@@ -75850,7 +75860,7 @@ var package_default = {
75850
75860
  "check:copy": "bun script/check-copy.ts",
75851
75861
  typecheck: "bun run check:copy && bun check --no-script --all -p tsconfig.json && bun run typecheck:script && bun run typecheck:packages && bun run typecheck:skill-scripts",
75852
75862
  "typecheck:skill-scripts": "bun check --no-script --all -p packages/shared-skills/skills/frontend/scripts/detect/tsconfig.json",
75853
- "typecheck:packages": "bun check --no-script --all -p packages/rules-engine/tsconfig.json && bun check --no-script --all -p packages/delegate-core/tsconfig.json && bun check --no-script --all -p packages/mcp-stdio-core/tsconfig.json && bun check --no-script --all -p packages/mcp-client-core/tsconfig.json && bun check --no-script --all -p packages/git-bash-mcp/tsconfig.json && bun check --no-script --all -p packages/ast-grep-mcp/tsconfig.json && bun check --no-script --all -p packages/lsp-core/tsconfig.json && bun check --no-script --all -p packages/utils/tsconfig.json && bun check --no-script --all -p packages/model-core/tsconfig.json && bun check --no-script --all -p packages/omo-config-core/tsconfig.json && bun check --no-script --all -p packages/prompts-core/tsconfig.json && bun check --no-script --all -p packages/comment-checker-core/tsconfig.json && bun check --no-script --all -p packages/hashline-core/tsconfig.json && bun check --no-script --all -p packages/tmux-core/tsconfig.json && bun check --no-script --all -p packages/team-core/tsconfig.json && bun check --no-script --all -p packages/openclaw-core/tsconfig.json && bun check --no-script --all -p packages/boulder-state/tsconfig.json && bun check --no-script --all -p packages/isolation-core/tsconfig.json && bun check --no-script --all -p packages/memory-core/tsconfig.json && bun check --no-script --all -p packages/telemetry-core/tsconfig.json && bun check --no-script --all -p packages/claude-code-compat-core/tsconfig.json && bun check --no-script --all -p packages/skills-loader-core/tsconfig.json && bun check --no-script --all -p packages/agents-md-core/tsconfig.json && bun check --no-script --all -p packages/omo-codex/plugin/shared/tsconfig.json && bun check --no-script --all -p packages/omo-codex/tsconfig.json && bun check --no-script --all -p packages/omo-senpi/tsconfig.json && bun check --no-script --all -p packages/senpi-task/tsconfig.json && bun check --no-script --all -p packages/omo-opencode/tsconfig.json && bun check --no-script --all -p packages/omo-native/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-protocol/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-engine/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-prelude/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-service/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-tool/tsconfig.json",
75863
+ "typecheck:packages": "bun check --no-script --all -p packages/rules-engine/tsconfig.json && bun check --no-script --all -p packages/delegate-core/tsconfig.json && bun check --no-script --all -p packages/mcp-stdio-core/tsconfig.json && bun check --no-script --all -p packages/mcp-client-core/tsconfig.json && bun check --no-script --all -p packages/git-bash-mcp/tsconfig.json && bun check --no-script --all -p packages/ast-grep-mcp/tsconfig.json && bun check --no-script --all -p packages/lsp-core/tsconfig.json && bun check --no-script --all -p packages/utils/tsconfig.json && bun check --no-script --all -p packages/device-service-core/tsconfig.json && bun check --no-script --all -p packages/model-core/tsconfig.json && bun check --no-script --all -p packages/omo-config-core/tsconfig.json && bun check --no-script --all -p packages/prompts-core/tsconfig.json && bun check --no-script --all -p packages/comment-checker-core/tsconfig.json && bun check --no-script --all -p packages/hashline-core/tsconfig.json && bun check --no-script --all -p packages/tmux-core/tsconfig.json && bun check --no-script --all -p packages/team-core/tsconfig.json && bun check --no-script --all -p packages/openclaw-core/tsconfig.json && bun check --no-script --all -p packages/boulder-state/tsconfig.json && bun check --no-script --all -p packages/isolation-core/tsconfig.json && bun check --no-script --all -p packages/memory-core/tsconfig.json && bun check --no-script --all -p packages/telemetry-core/tsconfig.json && bun check --no-script --all -p packages/claude-code-compat-core/tsconfig.json && bun check --no-script --all -p packages/skills-loader-core/tsconfig.json && bun check --no-script --all -p packages/agents-md-core/tsconfig.json && bun check --no-script --all -p packages/omo-codex/plugin/shared/tsconfig.json && bun check --no-script --all -p packages/omo-codex/tsconfig.json && bun check --no-script --all -p packages/omo-senpi/tsconfig.json && bun check --no-script --all -p packages/senpi-task/tsconfig.json && bun check --no-script --all -p packages/omo-opencode/tsconfig.json && bun check --no-script --all -p packages/omo-native/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-protocol/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-engine/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-prelude/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-service/tsconfig.json && bun check --no-script --all -p packages/senpi-desktop-tool/tsconfig.json",
75854
75864
  "typecheck:script": "bun check --no-script --all -p script/tsconfig.json",
75855
75865
  test: "bun test --timeout 20000",
75856
75866
  "changelog:section": "bun script/changelog-section.ts",
@@ -75897,6 +75907,7 @@ var package_default = {
75897
75907
  "@oh-my-opencode/agents-md-core": "workspace:*",
75898
75908
  "@oh-my-opencode/ast-grep-mcp": "workspace:*",
75899
75909
  "@oh-my-opencode/boulder-state": "workspace:*",
75910
+ "@oh-my-opencode/device-service-core": "workspace:*",
75900
75911
  "@oh-my-opencode/claude-code-compat-core": "workspace:*",
75901
75912
  "@oh-my-opencode/comment-checker-core": "workspace:*",
75902
75913
  "@oh-my-opencode/delegate-core": "workspace:*",
@@ -75956,18 +75967,18 @@ var package_default = {
75956
75967
  typescript: "^7.0.2"
75957
75968
  },
75958
75969
  optionalDependencies: {
75959
- "oh-my-opencode-darwin-arm64": "5.1.54",
75960
- "oh-my-opencode-darwin-x64": "5.1.54",
75961
- "oh-my-opencode-darwin-x64-baseline": "5.1.54",
75962
- "oh-my-opencode-linux-arm64": "5.1.54",
75963
- "oh-my-opencode-linux-arm64-musl": "5.1.54",
75964
- "oh-my-opencode-linux-x64": "5.1.54",
75965
- "oh-my-opencode-linux-x64-baseline": "5.1.54",
75966
- "oh-my-opencode-linux-x64-musl": "5.1.54",
75967
- "oh-my-opencode-linux-x64-musl-baseline": "5.1.54",
75968
- "oh-my-opencode-windows-arm64": "5.1.54",
75969
- "oh-my-opencode-windows-x64": "5.1.54",
75970
- "oh-my-opencode-windows-x64-baseline": "5.1.54"
75970
+ "oh-my-opencode-darwin-arm64": "5.1.56",
75971
+ "oh-my-opencode-darwin-x64": "5.1.56",
75972
+ "oh-my-opencode-darwin-x64-baseline": "5.1.56",
75973
+ "oh-my-opencode-linux-arm64": "5.1.56",
75974
+ "oh-my-opencode-linux-arm64-musl": "5.1.56",
75975
+ "oh-my-opencode-linux-x64": "5.1.56",
75976
+ "oh-my-opencode-linux-x64-baseline": "5.1.56",
75977
+ "oh-my-opencode-linux-x64-musl": "5.1.56",
75978
+ "oh-my-opencode-linux-x64-musl-baseline": "5.1.56",
75979
+ "oh-my-opencode-windows-arm64": "5.1.56",
75980
+ "oh-my-opencode-windows-x64": "5.1.56",
75981
+ "oh-my-opencode-windows-x64-baseline": "5.1.56"
75971
75982
  },
75972
75983
  overrides: {
75973
75984
  hono: "^4.13.8",