opencode-codeops 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +16 -0
- package/_shared/quality-profile.md +4 -0
- package/_shared/reasoning-effort.md +135 -0
- package/bin/lib/reasoning-effort.d.mts +85 -0
- package/bin/lib/reasoning-effort.mjs +331 -0
- package/package.json +1 -1
- package/plugin/index.ts +102 -0
- package/scripts/__pycache__/install_agents.cpython-312.pyc +0 -0
- package/scripts/codeops_effort.py +216 -0
- package/skills/exec-plan/SKILL.md +12 -0
- package/skills/exec-plan/execution-protocol.md +43 -3
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/make-plan/SKILL.md +17 -0
- package/skills/make-plan/templates.md +2 -0
- package/skills/make-requirements/SKILL.md +10 -0
- package/skills/preflight/SKILL.md +10 -0
- package/skills/retro-requirements/SKILL.md +10 -0
- package/skills/setup-routing/SKILL.md +2 -0
- package/skills/setup-routing/routing.md +16 -1
- package/skills/upgrade-plan/SKILL.md +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to CodeOps are recorded here.
|
|
4
4
|
|
|
5
|
+
## 1.10.0 — 2026-10-04
|
|
6
|
+
|
|
7
|
+
### Documentation
|
|
8
|
+
|
|
9
|
+
- agents: refresh managed project facts
|
|
10
|
+
- routing: clarify runtime variant validation
|
|
11
|
+
- plan: fix phase 3 review wording gaps
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- effort: tolerate malformed message parts in marker scan
|
|
16
|
+
- effort: correct phase 2 review findings
|
|
17
|
+
- effort: harden helper totality and contract wording
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- docs: add auto-effort flags and reasoning documentation
|
|
22
|
+
- plan: carry reasoning suggestions through plan skills
|
|
23
|
+
- effort: add session CLI and plugin runtime hooks
|
|
24
|
+
- effort: add reasoning-effort contract and helper
|
|
25
|
+
|
|
26
|
+
### Tests
|
|
27
|
+
|
|
28
|
+
- effort: add implementation tests for reasoning-effort helper
|
|
29
|
+
|
|
5
30
|
## 1.9.0 — 2026-10-04
|
|
6
31
|
|
|
7
32
|
### Features
|
package/README.md
CHANGED
|
@@ -153,6 +153,22 @@ To pin specific models per role, use the `setup-routing` skill or add overrides
|
|
|
153
153
|
}
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
### Adaptive reasoning effort
|
|
157
|
+
|
|
158
|
+
CodeOps can pick a reasoning level per dispatch instead of always inheriting the parent session's
|
|
159
|
+
variant. A dispatch packet may carry one standalone marker line:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
[codeops-effort: medium]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Reasoning-heavy skills also accept `--auto-effort` (use the skill's recommended level) or
|
|
166
|
+
`--auto-effort=high` (explicit level), announce it, and clear it before the final run summary.
|
|
167
|
+
Resolution order: dispatch marker, then session `--auto-effort`, then
|
|
168
|
+
`routing.roles.<agent>.reasoning` in `codeops/codeops.json`, then the inherited parent variant.
|
|
169
|
+
The levels are suggestions: no permission, verification step, or review gate ever reads them. The
|
|
170
|
+
full contract is in `_shared/reasoning-effort.md`.
|
|
171
|
+
|
|
156
172
|
## Project specialists
|
|
157
173
|
|
|
158
174
|
CodeOps can recommend project-specific specialist subagents when repository evidence shows a
|
|
@@ -147,6 +147,10 @@ Resolution order is:
|
|
|
147
147
|
3. project `[agents]` defaults in `opencode.json`;
|
|
148
148
|
4. the parent session's model and effort.
|
|
149
149
|
|
|
150
|
+
For the runtime reasoning-effort override (a dispatch marker, a session `--auto-effort` level, or
|
|
151
|
+
a routing role default), see [reasoning-effort.md](reasoning-effort.md); the static resolution
|
|
152
|
+
order above is unchanged.
|
|
153
|
+
|
|
150
154
|
Use `python3 "${CODEOPS_PLUGIN_ROOT}/scripts/install_agents.py" --project . --roles ...` to create optional project agents. Generated agent files carry a CodeOps marker. The installer owns only marked files and preserves every hand-authored file. Use `--check` to detect missing or stale generated agents and `--dry-run` to preview changes. Project specialists are generated with `--custom <role>` from `codeops/specialists/<role>.md` and indexed into `AGENTS.md` with `--sync-agents-md`; their routing policy may also set `reasoning`.
|
|
151
155
|
|
|
152
156
|
Dynamic packets are the correctness baseline. If a named agent is missing or a model pin is unavailable, spawn a generic subagent with the complete packet or run inline. Report the fallback and preserve required reviewer independence, sandbox intent, and every ambiguity/readiness/verification gate.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Reasoning effort (shared contract)
|
|
2
|
+
|
|
3
|
+
> **CodeOps Artifact Schema**: 1
|
|
4
|
+
|
|
5
|
+
CodeOps subagents normally inherit the parent session's model variant, so a top-tier parent
|
|
6
|
+
would pay top-tier reasoning cost for every child dispatch. Adaptive reasoning effort lets an
|
|
7
|
+
explicit source pick a level per dispatch or per skill run. This document is the shipped
|
|
8
|
+
contract consumed by the plugin and the skills. It is advisory by design and never a gate.
|
|
9
|
+
|
|
10
|
+
## Levels
|
|
11
|
+
|
|
12
|
+
| Level | Meaning | Typical work |
|
|
13
|
+
| ----- | ------- | ------------ |
|
|
14
|
+
| `low` | Mechanical, fully specified, deterministic verification | Docs and formatting edits, renames, config updates |
|
|
15
|
+
| `medium` | Ordinary bounded feature work with known patterns | Standard implementation phases, recon, single-file reviews |
|
|
16
|
+
| `high` | Correctness- or security-sensitive, cross-cutting, or planning/review work | Requirements, planning, correctness/security review, ambiguous implementation |
|
|
17
|
+
| `max` | Adversarial or high-risk analysis where a missed detail is costly | Thorough preflight, complex or sensitive phases |
|
|
18
|
+
|
|
19
|
+
The four levels are the complete suggestion vocabulary. A level is applied only when the
|
|
20
|
+
runtime model exposes a matching variant, or when the model reports reasoning support but has no
|
|
21
|
+
variant record (the level is then written directly as the provider reasoning option). A level
|
|
22
|
+
the model does not expose leaves the request unchanged and never raises a provider error.
|
|
23
|
+
Routing policy is project configuration, not a suggestion:
|
|
24
|
+
`routing.roles.<agent>.reasoning` may name any value from the provider enum
|
|
25
|
+
(`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`).
|
|
26
|
+
|
|
27
|
+
## Marker grammar
|
|
28
|
+
|
|
29
|
+
A dispatch marker is one standalone line in a dispatch message:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
[codeops-effort: <level>]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- The whole trimmed line must match `^\[codeops-effort:\s*(low|medium|high|max)\]\s*$`.
|
|
36
|
+
- The level is exactly lower-case. Unknown or malformed markers are ignored, never errors.
|
|
37
|
+
- Only user-role text parts of a message are scanned; tool output, diffs, and quoted documents
|
|
38
|
+
are never scanned.
|
|
39
|
+
- The first valid marker in message order wins; additional markers are ignored.
|
|
40
|
+
- The marker is deliberately distinct from the human-readable plan line
|
|
41
|
+
`> **Reasoning**: ...`, so a quoted plan line can never act as a machine directive.
|
|
42
|
+
|
|
43
|
+
For quality-agent packets the marker follows the dispatch header; for packet kinds without a
|
|
44
|
+
header it is the first line. `exec-plan` owns the composition rule.
|
|
45
|
+
|
|
46
|
+
## Precedence
|
|
47
|
+
|
|
48
|
+
The most specific source wins:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
dispatch marker > session auto-effort > routing.roles[<agent>].reasoning > inherit parent variant
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Source | Scope | Owner |
|
|
55
|
+
| ------ | ----- | ----- |
|
|
56
|
+
| Dispatch marker | One child dispatch | `exec-plan` composes it from the phase suggestion or the run's forced level |
|
|
57
|
+
| Session auto-effort | One skill run in the user's session | `--auto-effort` through `scripts/codeops_effort.py` |
|
|
58
|
+
| Routing role default | Project policy for one agent name | `codeops/codeops.json` → `routing.roles.<agent>.reasoning` |
|
|
59
|
+
| Inherit | Everything else | The child keeps the parent model and variant |
|
|
60
|
+
|
|
61
|
+
Routing lookup applies only when the dispatching agent's name has an explicit `routing.roles`
|
|
62
|
+
entry. There are no built-in catalog defaults, hand-authored agents are untouched, and
|
|
63
|
+
generated specialists keep their embedded value unless the project adds a routing entry.
|
|
64
|
+
|
|
65
|
+
## Skill recommendation table
|
|
66
|
+
|
|
67
|
+
Used for suggestions and for a bare `--auto-effort`:
|
|
68
|
+
|
|
69
|
+
| Skill | Recommended level | Notes |
|
|
70
|
+
| ----- | ----------------- | ----- |
|
|
71
|
+
| `make-requirements` | `high` | Structured discovery and gap expansion |
|
|
72
|
+
| `make-plan` | `high` | Decomposition plus the Zero-Ambiguity Gate |
|
|
73
|
+
| `preflight` | `high`; `max` with `--thorough` | Adversarial multi-dimension audit |
|
|
74
|
+
| `grill-me` | `high` | Branch-by-branch disambiguation |
|
|
75
|
+
| `exec-plan` | current phase's `Reasoning:` level | Bare flag follows the phase; `=<level>` forces a constant |
|
|
76
|
+
| `retro-requirements` | `high` | Nine-phase archaeology |
|
|
77
|
+
| `upgrade-plan` | `high` | Content gate plus structural migration |
|
|
78
|
+
| `setup-codeops`, `setup-routing` | `high` (reference only) | Structure and policy authoring |
|
|
79
|
+
| `techdocs`, `analyze-project`, `clean-comments`, `outcome-review` | `medium` (reference only) | Structured but bounded |
|
|
80
|
+
| `roadmap`, `git-commit`, `github-issues` | `low` (reference only) | Mechanical bookkeeping |
|
|
81
|
+
|
|
82
|
+
Skills that accept `--auto-effort` are `make-requirements`, `make-plan`, `preflight`,
|
|
83
|
+
`grill-me`, `exec-plan`, `retro-requirements`, and `upgrade-plan`. Other skills document their
|
|
84
|
+
recommended levels for reference only and do not implement the flag.
|
|
85
|
+
|
|
86
|
+
## Plan suggestion derivation
|
|
87
|
+
|
|
88
|
+
`make-plan` derives an advisory level for every phase and every task mini-plan from signals it
|
|
89
|
+
already records:
|
|
90
|
+
|
|
91
|
+
| Signal | Suggested level |
|
|
92
|
+
| ------ | --------------- |
|
|
93
|
+
| Phase carries a complexity escalation approval, or a complex/sensitive tag | `max` |
|
|
94
|
+
| Phase carries a security, financial-integrity, concurrency, performance-critical, compiler-semantics, or migration lens/risk tag | `high` |
|
|
95
|
+
| Docs/config/rename-only phase with deterministic verification | `low` |
|
|
96
|
+
| Any other non-trivial phase | `medium` |
|
|
97
|
+
|
|
98
|
+
The line is written as `> **Reasoning**: <level> — <one-line reason>`. It is a suggestion: the
|
|
99
|
+
user may edit or delete it, and `exec-plan` never blocks on it. A plan without the line keeps
|
|
100
|
+
the inherited behavior.
|
|
101
|
+
|
|
102
|
+
## Auto-effort option
|
|
103
|
+
|
|
104
|
+
Skills listed above accept the flag. Parsing follows the existing standalone-token pattern
|
|
105
|
+
(`--auto-design`, `--explore-scope`): exactly one occurrence before the first `--` sentinel,
|
|
106
|
+
removed before resolving targets, paths, or modes; zero means advise-only; more than one or an
|
|
107
|
+
invalid `=<level>` is an argument error.
|
|
108
|
+
|
|
109
|
+
| Flag | Behavior |
|
|
110
|
+
| ---- | -------- |
|
|
111
|
+
| *(absent)* | Print `Suggested reasoning: <level> — <reason>` once at start (`exec-plan`: before each phase); change nothing |
|
|
112
|
+
| `--auto-effort` | Use the skill's recommended level (`exec-plan`: the current phase level) |
|
|
113
|
+
| `--auto-effort=<level>` | Use the named level; reject values outside the four-level set |
|
|
114
|
+
|
|
115
|
+
Semantics:
|
|
116
|
+
|
|
117
|
+
1. Announce `Auto-effort active — reasoning <level> applied for this run`.
|
|
118
|
+
2. Record the level for the session run through `scripts/codeops_effort.py`.
|
|
119
|
+
3. Clear it at run completion, before the final summary.
|
|
120
|
+
4. Run-scoped: the level applies from the point it is set until cleared or the session ends.
|
|
121
|
+
5. Fail-open: when the session temp directory is unavailable or the helper fails, print an
|
|
122
|
+
advise-only note and continue; the skill never blocks.
|
|
123
|
+
6. Never a gate: effort affects cost and latency only. No readiness check, verification step,
|
|
124
|
+
reviewer requirement, or finding gate may read it.
|
|
125
|
+
|
|
126
|
+
For `exec-plan`, a bare flag follows each phase's suggestion, while `--auto-effort=<level>`
|
|
127
|
+
forces that level for the whole run, including every dispatch marker composed during it.
|
|
128
|
+
|
|
129
|
+
## Suggestion-only guarantee
|
|
130
|
+
|
|
131
|
+
| Allowed | Forbidden |
|
|
132
|
+
| ------- | --------- |
|
|
133
|
+
| Printing a suggested level and its reason | Blocking a run because a level is missing or unsupported |
|
|
134
|
+
| Applying a level when a marker, flag, or routing entry explicitly asks | Overriding an explicit user plan edit with a derived default |
|
|
135
|
+
| Reporting the applied level and its source | Treating effort as an acceptance, review, or security control |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for the CodeOps adaptive reasoning-effort helper.
|
|
3
|
+
*
|
|
4
|
+
* The helper is plain JavaScript so `node --test` can exercise it directly;
|
|
5
|
+
* these declarations give the TypeScript plugin entry point typed access.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A level from the four-level suggestion vocabulary. */
|
|
9
|
+
export type EffortLevel = "low" | "medium" | "high" | "max"
|
|
10
|
+
|
|
11
|
+
/** A reasoning value accepted by `routing.roles.<agent>.reasoning`. */
|
|
12
|
+
export type RoutingReasoning =
|
|
13
|
+
| "none"
|
|
14
|
+
| "minimal"
|
|
15
|
+
| "low"
|
|
16
|
+
| "medium"
|
|
17
|
+
| "high"
|
|
18
|
+
| "xhigh"
|
|
19
|
+
| "max"
|
|
20
|
+
|
|
21
|
+
/** The four levels a plan or skill may suggest. */
|
|
22
|
+
export declare const EFFORT_LEVELS: readonly EffortLevel[]
|
|
23
|
+
|
|
24
|
+
/** Check whether a value is one of the four suggestion levels. */
|
|
25
|
+
export declare function isEffortLevel(value: unknown): value is EffortLevel
|
|
26
|
+
|
|
27
|
+
/** The reasoning values accepted by routing role entries. */
|
|
28
|
+
export declare const ROUTING_REASONING_VALUES: readonly RoutingReasoning[]
|
|
29
|
+
|
|
30
|
+
/** Check whether a value is a valid routing reasoning entry. */
|
|
31
|
+
export declare function isRoutingReasoning(value: unknown): value is RoutingReasoning
|
|
32
|
+
|
|
33
|
+
/** Find the first valid dispatch marker across message text parts. */
|
|
34
|
+
export declare function findEffortMarker(texts: unknown): EffortLevel | undefined
|
|
35
|
+
|
|
36
|
+
/** Candidate sources for {@link resolveEffort}. */
|
|
37
|
+
export interface ResolveEffortInput {
|
|
38
|
+
marker?: unknown
|
|
39
|
+
session?: unknown
|
|
40
|
+
routing?: unknown
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Resolve the effective level from the explicit sources, most specific first. */
|
|
44
|
+
export declare function resolveEffort(
|
|
45
|
+
input?: ResolveEffortInput
|
|
46
|
+
): EffortLevel | RoutingReasoning | undefined
|
|
47
|
+
|
|
48
|
+
/** Compute the session state-file path inside the session temp directory. */
|
|
49
|
+
export declare function sessionEffortPath(sessionID?: string, base?: string): string
|
|
50
|
+
|
|
51
|
+
/** Parse session state-file text into a validated level. */
|
|
52
|
+
export declare function parseStateFile(text: string): EffortLevel | undefined
|
|
53
|
+
|
|
54
|
+
/** Read the session's reasoning-effort state file. */
|
|
55
|
+
export declare function readSessionEffort(
|
|
56
|
+
sessionID?: string,
|
|
57
|
+
base?: string
|
|
58
|
+
): EffortLevel | undefined
|
|
59
|
+
|
|
60
|
+
/** Read an agent's explicit reasoning entry from the project routing config. */
|
|
61
|
+
export declare function readRoutingReasoning(
|
|
62
|
+
config: unknown,
|
|
63
|
+
agent: unknown
|
|
64
|
+
): RoutingReasoning | undefined
|
|
65
|
+
|
|
66
|
+
/** Extract the model's runtime variant record, when the host exposes one. */
|
|
67
|
+
export declare function extractModelVariants(
|
|
68
|
+
model: unknown
|
|
69
|
+
): Record<string, unknown> | undefined
|
|
70
|
+
|
|
71
|
+
/** Check whether a model advertises reasoning support. */
|
|
72
|
+
export declare function modelSupportsReasoning(model: unknown): boolean
|
|
73
|
+
|
|
74
|
+
/** Merge the model's variant options for a level into the request options. */
|
|
75
|
+
export declare function applyEffort(
|
|
76
|
+
options: Record<string, unknown>,
|
|
77
|
+
level: unknown,
|
|
78
|
+
model: unknown
|
|
79
|
+
): Record<string, unknown>
|
|
80
|
+
|
|
81
|
+
/** Recursively merge plain objects into a new object. */
|
|
82
|
+
export declare function deepMergePlain(
|
|
83
|
+
target: unknown,
|
|
84
|
+
source: unknown
|
|
85
|
+
): Record<string, unknown>
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive reasoning-effort helper for CodeOps sessions.
|
|
4
|
+
*
|
|
5
|
+
* The plugin needs one place to answer a simple question per request: "which
|
|
6
|
+
* reasoning level should this request use, if any?" This module answers it
|
|
7
|
+
* without any framework dependency so `node --test` can exercise every edge:
|
|
8
|
+
*
|
|
9
|
+
* - marker scanning over message text parts;
|
|
10
|
+
* - source precedence (dispatch marker, session flag, routing default);
|
|
11
|
+
* - routing-config lookup with hostile-shape tolerance;
|
|
12
|
+
* - provider-option application through the model's own variant record; and
|
|
13
|
+
* - reading the per-session state file written by the skills.
|
|
14
|
+
*
|
|
15
|
+
* Every function is deliberately total: malformed input yields "no override",
|
|
16
|
+
* never a thrown error. The plugin hooks sit on the request path, where a
|
|
17
|
+
* crash would break the user's session, so safety beats strictness here.
|
|
18
|
+
*
|
|
19
|
+
* @module lib/reasoning-effort
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { lstatSync, readFileSync } from "node:fs"
|
|
23
|
+
import { join } from "node:path"
|
|
24
|
+
|
|
25
|
+
import { sessionTmpDir } from "./tmp-hygiene.mjs"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The four levels a plan or skill may suggest.
|
|
29
|
+
*
|
|
30
|
+
* These are the only values accepted in dispatch markers, session flags, and
|
|
31
|
+
* plan suggestions. Routing policy is project configuration and may name the
|
|
32
|
+
* wider provider enum instead.
|
|
33
|
+
*/
|
|
34
|
+
export const EFFORT_LEVELS = Object.freeze(["low", "medium", "high", "max"])
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The reasoning values accepted by `routing.roles.<agent>.reasoning` in the
|
|
38
|
+
* project configuration.
|
|
39
|
+
*
|
|
40
|
+
* The list mirrors the CodeOps config schema, which passes provider-native
|
|
41
|
+
* values through so a project can ask for `minimal` or `xhigh` even though the
|
|
42
|
+
* suggestion vocabulary only offers the four common levels.
|
|
43
|
+
*/
|
|
44
|
+
export const ROUTING_REASONING_VALUES = Object.freeze([
|
|
45
|
+
"none",
|
|
46
|
+
"minimal",
|
|
47
|
+
"low",
|
|
48
|
+
"medium",
|
|
49
|
+
"high",
|
|
50
|
+
"xhigh",
|
|
51
|
+
"max",
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Largest state file accepted by {@link readSessionEffort}.
|
|
56
|
+
*
|
|
57
|
+
* The real file is a few dozen bytes. The cap keeps a hostile or corrupt file
|
|
58
|
+
* from being slurped into memory if the temp directory is ever tampered with.
|
|
59
|
+
*/
|
|
60
|
+
const MAX_STATE_FILE_BYTES = 4096
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* One standalone marker line, anchored to the whole line.
|
|
64
|
+
*
|
|
65
|
+
* The marker is intentionally different from the human-readable plan line
|
|
66
|
+
* `> **Reasoning**: ...`, so quoting a plan can never act as a machine
|
|
67
|
+
* directive. Whitespace inside the brackets is tolerated; text before or after
|
|
68
|
+
* is not.
|
|
69
|
+
*/
|
|
70
|
+
const MARKER_PATTERN = /^\[codeops-effort:\s*(low|medium|high|max)\]\s*$/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check whether a value is one of the four suggestion levels.
|
|
74
|
+
*
|
|
75
|
+
* @param value - Value to inspect
|
|
76
|
+
* @returns True only for `"low"`, `"medium"`, `"high"`, or `"max"`
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* isEffortLevel("high") // true
|
|
80
|
+
* isEffortLevel("xhigh") // false
|
|
81
|
+
*/
|
|
82
|
+
export function isEffortLevel(value) {
|
|
83
|
+
return typeof value === "string" && EFFORT_LEVELS.includes(value)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check whether a value is a valid routing reasoning entry.
|
|
88
|
+
*
|
|
89
|
+
* @param value - Value to inspect
|
|
90
|
+
* @returns True for any value in the routing enum
|
|
91
|
+
*/
|
|
92
|
+
export function isRoutingReasoning(value) {
|
|
93
|
+
return typeof value === "string" && ROUTING_REASONING_VALUES.includes(value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Find the first valid dispatch marker across a list of message text parts.
|
|
98
|
+
*
|
|
99
|
+
* Only string entries are inspected; non-string entries (tool parts, images,
|
|
100
|
+
* malformed structures) are skipped so a hostile part can never break the
|
|
101
|
+
* request path. Lines are split on `\n`, `\r\n`, and `\r`, then trimmed before
|
|
102
|
+
* the anchored pattern is applied, so surrounding whitespace is tolerated.
|
|
103
|
+
*
|
|
104
|
+
* @param texts - Candidate text parts (usually the text of a message's parts)
|
|
105
|
+
* @returns The first valid level, or `undefined` when none is present
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* findEffortMarker(["run the task", "[codeops-effort: medium]"]) // "medium"
|
|
109
|
+
*/
|
|
110
|
+
export function findEffortMarker(texts) {
|
|
111
|
+
if (!Array.isArray(texts)) return undefined
|
|
112
|
+
for (const text of texts) {
|
|
113
|
+
if (typeof text !== "string") continue
|
|
114
|
+
for (const line of text.split(/\r\n|\r|\n/)) {
|
|
115
|
+
const match = MARKER_PATTERN.exec(line.trim())
|
|
116
|
+
if (match) return match[1]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Resolve the effective level from the three explicit sources.
|
|
124
|
+
*
|
|
125
|
+
* Precedence is "most specific wins": a dispatch marker beats a session
|
|
126
|
+
* flag, which beats a routing default. Each source is validated before it is
|
|
127
|
+
* accepted, so an invalid value is ignored rather than propagated. A
|
|
128
|
+
* non-object argument yields `undefined` instead of throwing.
|
|
129
|
+
*
|
|
130
|
+
* @param input - Candidate sources; all optional
|
|
131
|
+
* @returns The first valid value, or `undefined` when no source applies
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* resolveEffort({ session: "high", routing: "low" }) // "high"
|
|
135
|
+
*/
|
|
136
|
+
export function resolveEffort(input) {
|
|
137
|
+
if (!isPlainObject(input)) return undefined
|
|
138
|
+
const { marker, session, routing } = input
|
|
139
|
+
if (isEffortLevel(marker)) return marker
|
|
140
|
+
if (isEffortLevel(session)) return session
|
|
141
|
+
if (isRoutingReasoning(routing)) return routing
|
|
142
|
+
return undefined
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compute the session state-file path inside the session temp directory.
|
|
147
|
+
*
|
|
148
|
+
* @param sessionID - Session identifier
|
|
149
|
+
* @param base - Base temp directory (injectable for tests)
|
|
150
|
+
* @returns Absolute path to `reasoning-effort.json` for the session
|
|
151
|
+
*/
|
|
152
|
+
export function sessionEffortPath(sessionID, base) {
|
|
153
|
+
return join(sessionTmpDir(sessionID, base), "reasoning-effort.json")
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Parse session state-file text into a validated level.
|
|
158
|
+
*
|
|
159
|
+
* The expected payload is `{"schema":1,"reasoning":"<level>"}` with any
|
|
160
|
+
* unknown keys ignored. Anything else — malformed JSON, the wrong schema, a
|
|
161
|
+
* level outside the four-level allowlist — yields `undefined`.
|
|
162
|
+
*
|
|
163
|
+
* @param text - Raw file contents
|
|
164
|
+
* @returns The validated level, or `undefined`
|
|
165
|
+
*/
|
|
166
|
+
export function parseStateFile(text) {
|
|
167
|
+
try {
|
|
168
|
+
const value = JSON.parse(text)
|
|
169
|
+
if (!isPlainObject(value)) return undefined
|
|
170
|
+
if (value.schema !== 1) return undefined
|
|
171
|
+
return isEffortLevel(value.reasoning) ? value.reasoning : undefined
|
|
172
|
+
} catch {
|
|
173
|
+
return undefined
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Read the session's reasoning-effort state file.
|
|
179
|
+
*
|
|
180
|
+
* The file is written by `scripts/codeops_effort.py` for `--auto-effort`
|
|
181
|
+
* runs. Only a small regular file is accepted: a missing file, a non-regular
|
|
182
|
+
* file (symlinks are never followed), an oversized file, and any read or parse
|
|
183
|
+
* failure all mean "no session effort" and return `undefined`.
|
|
184
|
+
*
|
|
185
|
+
* @param sessionID - Session identifier
|
|
186
|
+
* @param base - Base temp directory (injectable for tests)
|
|
187
|
+
* @returns The session level, or `undefined`
|
|
188
|
+
*/
|
|
189
|
+
export function readSessionEffort(sessionID, base) {
|
|
190
|
+
try {
|
|
191
|
+
const path = sessionEffortPath(sessionID, base)
|
|
192
|
+
const info = lstatSync(path)
|
|
193
|
+
if (!info.isFile()) return undefined
|
|
194
|
+
if (info.size > MAX_STATE_FILE_BYTES) return undefined
|
|
195
|
+
return parseStateFile(readFileSync(path, "utf-8"))
|
|
196
|
+
} catch {
|
|
197
|
+
return undefined
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Read an agent's explicit reasoning entry from the project routing config.
|
|
203
|
+
*
|
|
204
|
+
* Only an own `routing.roles.<agent>.reasoning` property counts; unknown
|
|
205
|
+
* agents, missing sections, inherited properties, and malformed shapes all
|
|
206
|
+
* return `undefined` instead of throwing.
|
|
207
|
+
*
|
|
208
|
+
* @param config - Parsed `codeops/codeops.json` content (any shape)
|
|
209
|
+
* @param agent - Dispatching agent name
|
|
210
|
+
* @returns The configured routing value, or `undefined`
|
|
211
|
+
*/
|
|
212
|
+
export function readRoutingReasoning(config, agent) {
|
|
213
|
+
if (!isPlainObject(config)) return undefined
|
|
214
|
+
const routing = config.routing
|
|
215
|
+
if (!isPlainObject(routing)) return undefined
|
|
216
|
+
const roles = routing.roles
|
|
217
|
+
if (!isPlainObject(roles)) return undefined
|
|
218
|
+
if (typeof agent !== "string" || agent.length === 0) return undefined
|
|
219
|
+
if (!Object.prototype.hasOwnProperty.call(roles, agent)) return undefined
|
|
220
|
+
const entry = roles[agent]
|
|
221
|
+
if (!isPlainObject(entry)) return undefined
|
|
222
|
+
return isRoutingReasoning(entry.reasoning) ? entry.reasoning : undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Extract the model's runtime variant record, when the host exposes one.
|
|
227
|
+
*
|
|
228
|
+
* The installed SDK type does not declare `variants`, but the running host
|
|
229
|
+
* attaches it to every model. The `in` check keeps this graceful when the
|
|
230
|
+
* property is absent, and a cast is never used.
|
|
231
|
+
*
|
|
232
|
+
* @param model - Model object from the hook input
|
|
233
|
+
* @returns The variants record when it is a plain object, else `undefined`
|
|
234
|
+
*/
|
|
235
|
+
export function extractModelVariants(model) {
|
|
236
|
+
if (!isPlainObject(model)) return undefined
|
|
237
|
+
if (!("variants" in model)) return undefined
|
|
238
|
+
return isPlainObject(model.variants) ? model.variants : undefined
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check whether a model advertises reasoning support.
|
|
243
|
+
*
|
|
244
|
+
* @param model - Model object from the hook input
|
|
245
|
+
* @returns True only when `capabilities.reasoning` is exactly `true`
|
|
246
|
+
*/
|
|
247
|
+
export function modelSupportsReasoning(model) {
|
|
248
|
+
if (!isPlainObject(model)) return false
|
|
249
|
+
const capabilities = model.capabilities
|
|
250
|
+
if (!isPlainObject(capabilities)) return false
|
|
251
|
+
return capabilities.reasoning === true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Merge the model's variant options for a level into the request options.
|
|
256
|
+
*
|
|
257
|
+
* The runtime model carries a `variants` record whose entries are the exact
|
|
258
|
+
* provider options for each level (for example `reasoningEffort`, or a nested
|
|
259
|
+
* `reasoning.effort`). This function is the only place that mapping is
|
|
260
|
+
* consumed, so the plugin never hardcodes a provider key. The function is
|
|
261
|
+
* pure: it returns a new object when a change applies and the original object
|
|
262
|
+
* reference otherwise.
|
|
263
|
+
*
|
|
264
|
+
* @param options - Current provider options
|
|
265
|
+
* @param level - Candidate level from {@link resolveEffort}
|
|
266
|
+
* @param model - Model object from the hook input
|
|
267
|
+
* @returns The original options, or a new merged object when a change applies
|
|
268
|
+
*/
|
|
269
|
+
export function applyEffort(options, level, model) {
|
|
270
|
+
if (!isRoutingReasoning(level)) return options
|
|
271
|
+
if (!modelSupportsReasoning(model)) return options
|
|
272
|
+
|
|
273
|
+
const variants = extractModelVariants(model)
|
|
274
|
+
if (variants !== undefined) {
|
|
275
|
+
if (!Object.prototype.hasOwnProperty.call(variants, level)) return options
|
|
276
|
+
const variantOptions = variants[level]
|
|
277
|
+
if (!isPlainObject(variantOptions)) return options
|
|
278
|
+
return deepMergePlain(options, variantOptions)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return { ...options, reasoningEffort: level }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Recursively merge plain objects into a new object.
|
|
286
|
+
*
|
|
287
|
+
* Values that are not plain objects — arrays, class instances, primitives —
|
|
288
|
+
* are replaced, not merged. Neither input is mutated. The result is created
|
|
289
|
+
* with data properties, so a hostile `__proto__` key in a variant can never
|
|
290
|
+
* change the result's prototype chain.
|
|
291
|
+
*
|
|
292
|
+
* @param target - Base object
|
|
293
|
+
* @param source - Overrides to merge on top
|
|
294
|
+
* @returns A new merged plain object
|
|
295
|
+
*/
|
|
296
|
+
export function deepMergePlain(target, source) {
|
|
297
|
+
const result = isPlainObject(target) ? { ...target } : {}
|
|
298
|
+
if (!isPlainObject(source)) return result
|
|
299
|
+
|
|
300
|
+
for (const key of Object.keys(source)) {
|
|
301
|
+
const incoming = source[key]
|
|
302
|
+
const current = Object.prototype.hasOwnProperty.call(result, key)
|
|
303
|
+
? result[key]
|
|
304
|
+
: undefined
|
|
305
|
+
const merged =
|
|
306
|
+
isPlainObject(incoming) && isPlainObject(current)
|
|
307
|
+
? deepMergePlain(current, incoming)
|
|
308
|
+
: incoming
|
|
309
|
+
Object.defineProperty(result, key, {
|
|
310
|
+
value: merged,
|
|
311
|
+
writable: true,
|
|
312
|
+
enumerable: true,
|
|
313
|
+
configurable: true,
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return result
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Check whether a value is a plain object (not an array, class instance, or
|
|
322
|
+
* `null`).
|
|
323
|
+
*
|
|
324
|
+
* @param value - Value to inspect
|
|
325
|
+
* @returns True for `{}`-shaped objects and objects with a null prototype
|
|
326
|
+
*/
|
|
327
|
+
function isPlainObject(value) {
|
|
328
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
|
|
329
|
+
const prototype = Object.getPrototypeOf(value)
|
|
330
|
+
return prototype === Object.prototype || prototype === null
|
|
331
|
+
}
|
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -9,6 +9,14 @@ import {
|
|
|
9
9
|
ensureSessionTmpDir,
|
|
10
10
|
removeSessionTmpDir,
|
|
11
11
|
} from "../bin/lib/tmp-hygiene.mjs"
|
|
12
|
+
import {
|
|
13
|
+
applyEffort,
|
|
14
|
+
findEffortMarker,
|
|
15
|
+
readRoutingReasoning,
|
|
16
|
+
readSessionEffort,
|
|
17
|
+
resolveEffort,
|
|
18
|
+
} from "../bin/lib/reasoning-effort.mjs"
|
|
19
|
+
import type { EffortLevel } from "../bin/lib/reasoning-effort.mjs"
|
|
12
20
|
|
|
13
21
|
// ---------------------------------------------------------------------------
|
|
14
22
|
// Package root — resolved at module load time so it is always the plugin's
|
|
@@ -137,11 +145,44 @@ async function warnOnVersionSkew(
|
|
|
137
145
|
}
|
|
138
146
|
}
|
|
139
147
|
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Helper — log one content-free warning. Logging is best effort: a failed log
|
|
150
|
+
// must never break a request.
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
async function warnContentFree(
|
|
153
|
+
client: Parameters<Plugin>[0]["client"],
|
|
154
|
+
message: string
|
|
155
|
+
): Promise<void> {
|
|
156
|
+
try {
|
|
157
|
+
await client.app.log({ body: { service: "codeops", level: "warn", message } })
|
|
158
|
+
} catch {
|
|
159
|
+
// Best effort only.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Helper — read the project routing config fresh on every request, so an edit
|
|
165
|
+
// applies without restarting the session. Any failure means "no routing".
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
function readRoutingConfig(directory: string): unknown {
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(readFileSync(join(directory, "codeops", "codeops.json"), "utf8"))
|
|
170
|
+
} catch {
|
|
171
|
+
return {}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
140
175
|
// ---------------------------------------------------------------------------
|
|
141
176
|
// CodeOps plugin for OpenCode
|
|
142
177
|
// Replaces: hooks/hooks.json + hook_session_context.sh + hook_marker_guard.sh
|
|
143
178
|
// ---------------------------------------------------------------------------
|
|
144
179
|
export const CodeOpsPlugin: Plugin = async ({ client, directory }) => {
|
|
180
|
+
// Reasoning-effort state lives for the lifetime of this plugin instance:
|
|
181
|
+
// one entry per user message that carried a dispatch marker, plus a
|
|
182
|
+
// deduplication set for unsupported-level warnings.
|
|
183
|
+
const effortMarkers = new Map<string, { sessionID: string; level: EffortLevel }>()
|
|
184
|
+
const warnedEffortLevels = new Set<string>()
|
|
185
|
+
|
|
145
186
|
return {
|
|
146
187
|
// -----------------------------------------------------------------------
|
|
147
188
|
// Hook 1 & 2: inject standards on session.created and session.compacted.
|
|
@@ -160,6 +201,13 @@ export const CodeOpsPlugin: Plugin = async ({ client, directory }) => {
|
|
|
160
201
|
} else if (event.type === "session.deleted") {
|
|
161
202
|
const info = (event.properties as { info: { id: string } }).info
|
|
162
203
|
removeSessionTmpDir(info.id)
|
|
204
|
+
try {
|
|
205
|
+
for (const [messageID, entry] of effortMarkers) {
|
|
206
|
+
if (entry.sessionID === info.id) effortMarkers.delete(messageID)
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
await warnContentFree(client, "Could not clear captured reasoning-effort markers.")
|
|
210
|
+
}
|
|
163
211
|
} else if (event.type === "session.compacted") {
|
|
164
212
|
const sessionId: string = (event.properties as { sessionID: string }).sessionID
|
|
165
213
|
await injectStandards(client, sessionId)
|
|
@@ -218,5 +266,59 @@ export const CodeOpsPlugin: Plugin = async ({ client, directory }) => {
|
|
|
218
266
|
)
|
|
219
267
|
}
|
|
220
268
|
},
|
|
269
|
+
|
|
270
|
+
// -----------------------------------------------------------------------
|
|
271
|
+
// Hook 6: capture a dispatch marker from an incoming user message. The
|
|
272
|
+
// marker travels in the dispatch packet text; storing it by message id
|
|
273
|
+
// lets the later chat.params hook apply it to the same request.
|
|
274
|
+
// -----------------------------------------------------------------------
|
|
275
|
+
"chat.message": async (input, output) => {
|
|
276
|
+
try {
|
|
277
|
+
const texts = output.parts.map((part) =>
|
|
278
|
+
part?.type === "text" ? part.text : undefined
|
|
279
|
+
)
|
|
280
|
+
const level = findEffortMarker(texts)
|
|
281
|
+
if (level !== undefined) {
|
|
282
|
+
effortMarkers.set(output.message.id, { sessionID: input.sessionID, level })
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
await warnContentFree(client, "Could not scan a message for a reasoning-effort marker.")
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
// -----------------------------------------------------------------------
|
|
290
|
+
// Hook 7: resolve the request's reasoning level (dispatch marker, then
|
|
291
|
+
// session flag, then routing default) and merge the model's own variant
|
|
292
|
+
// options. Any failure leaves the request unchanged.
|
|
293
|
+
// -----------------------------------------------------------------------
|
|
294
|
+
"chat.params": async (input, output) => {
|
|
295
|
+
try {
|
|
296
|
+
const stored = effortMarkers.get(input.message.id)
|
|
297
|
+
const marker = stored && stored.sessionID === input.sessionID ? stored.level : undefined
|
|
298
|
+
const session = readSessionEffort(input.sessionID)
|
|
299
|
+
const routing = readRoutingReasoning(readRoutingConfig(directory), input.agent)
|
|
300
|
+
const level = resolveEffort({ marker, session, routing })
|
|
301
|
+
if (level === undefined) return
|
|
302
|
+
|
|
303
|
+
const applied = applyEffort(output.options, level, input.model)
|
|
304
|
+
if (applied === output.options) {
|
|
305
|
+
if (marker !== undefined) {
|
|
306
|
+
const warningKey = `${input.sessionID}:${marker}`
|
|
307
|
+
if (!warnedEffortLevels.has(warningKey)) {
|
|
308
|
+
warnedEffortLevels.add(warningKey)
|
|
309
|
+
await warnContentFree(
|
|
310
|
+
client,
|
|
311
|
+
`Reasoning effort ${marker} is not available for agent ${input.agent}; ` +
|
|
312
|
+
"request left unchanged."
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
output.options = applied
|
|
319
|
+
} catch {
|
|
320
|
+
await warnContentFree(client, "Could not apply a reasoning-effort level to a request.")
|
|
321
|
+
}
|
|
322
|
+
},
|
|
221
323
|
}
|
|
222
324
|
}
|
|
Binary file
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Record and clear the opt-in reasoning-effort level for a CodeOps session run.
|
|
3
|
+
|
|
4
|
+
The skills call this helper when a user passes `--auto-effort`. It writes one
|
|
5
|
+
small JSON file inside the session's CodeOps temp directory; the plugin reads
|
|
6
|
+
that file on every request and applies the level. The command fails closed:
|
|
7
|
+
an invalid level or a directory outside the CodeOps temp root exits with
|
|
8
|
+
status 2 and writes nothing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#: The only levels a session flag may record.
|
|
23
|
+
EFFORT_LEVELS = ("low", "medium", "high", "max")
|
|
24
|
+
|
|
25
|
+
#: Name of the state file the plugin reads.
|
|
26
|
+
STATE_FILE_NAME = "reasoning-effort.json"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def codeops_tmp_root() -> Path:
|
|
30
|
+
"""Return the CodeOps-owned temp root for the active temp directory.
|
|
31
|
+
|
|
32
|
+
The root mirrors the layout `bin/lib/tmp-hygiene.mjs` owns, so the helper
|
|
33
|
+
and the plugin resolve the same per-session directory.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Resolved path of the CodeOps temp root.
|
|
37
|
+
"""
|
|
38
|
+
return Path(tempfile.gettempdir()).resolve() / "opencode" / "codeops"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def resolve_session_dir(raw_dir: str) -> Path | None:
|
|
42
|
+
"""Validate a session directory argument against the CodeOps temp root.
|
|
43
|
+
|
|
44
|
+
A directory is accepted only when it exists, resolves strictly inside the
|
|
45
|
+
CodeOps temp root, and is not the root itself. Symlinks are resolved, so a
|
|
46
|
+
link that points outside the root is rejected.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
raw_dir: The `--dir` argument as provided by the caller.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The resolved session directory, or None when it is not acceptable.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
resolved = Path(raw_dir).resolve(strict=True)
|
|
56
|
+
except (OSError, RuntimeError):
|
|
57
|
+
# RuntimeError covers a symlink loop on Python 3.12, which the
|
|
58
|
+
# filesystem reports as ELOOP rather than a plain OSError.
|
|
59
|
+
return None
|
|
60
|
+
root = codeops_tmp_root()
|
|
61
|
+
if resolved == root or root not in resolved.parents:
|
|
62
|
+
return None
|
|
63
|
+
if not resolved.is_dir():
|
|
64
|
+
return None
|
|
65
|
+
return resolved
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def state_path(session_dir: Path) -> Path:
|
|
69
|
+
"""Return the state-file path inside a validated session directory.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
session_dir: A directory accepted by `resolve_session_dir`.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Path of the reasoning-effort state file.
|
|
76
|
+
"""
|
|
77
|
+
return session_dir / STATE_FILE_NAME
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def set_level(session_dir: Path, level: str) -> int:
|
|
81
|
+
"""Write the session level atomically and report it.
|
|
82
|
+
|
|
83
|
+
The payload is written to a temporary file in the same directory and then
|
|
84
|
+
moved over the final name with `os.replace`, so a reader never observes a
|
|
85
|
+
partially written file.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
session_dir: A validated session directory.
|
|
89
|
+
level: One of `EFFORT_LEVELS`.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Process exit code 0.
|
|
93
|
+
"""
|
|
94
|
+
payload = {
|
|
95
|
+
"schema": 1,
|
|
96
|
+
"reasoning": level,
|
|
97
|
+
"setAt": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
98
|
+
}
|
|
99
|
+
path = state_path(session_dir)
|
|
100
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
101
|
+
prefix=f"{STATE_FILE_NAME}.", suffix=".tmp", dir=session_dir
|
|
102
|
+
)
|
|
103
|
+
try:
|
|
104
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
105
|
+
json.dump(payload, handle, separators=(",", ":"), sort_keys=True)
|
|
106
|
+
handle.write("\n")
|
|
107
|
+
os.replace(temporary_name, path)
|
|
108
|
+
except OSError:
|
|
109
|
+
try:
|
|
110
|
+
os.unlink(temporary_name)
|
|
111
|
+
except OSError:
|
|
112
|
+
pass
|
|
113
|
+
print("Error: could not write the reasoning-effort state file.", file=sys.stderr)
|
|
114
|
+
return 2
|
|
115
|
+
print(f"Reasoning effort set: {level} for this session run.")
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def clear_level(session_dir: Path) -> int:
|
|
120
|
+
"""Remove the session level when present and report the cleanup.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
session_dir: A validated session directory.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Process exit code 0.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
state_path(session_dir).unlink()
|
|
130
|
+
except FileNotFoundError:
|
|
131
|
+
pass
|
|
132
|
+
except OSError:
|
|
133
|
+
print("Error: could not remove the reasoning-effort state file.", file=sys.stderr)
|
|
134
|
+
return 2
|
|
135
|
+
print("Reasoning effort cleared.")
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def show_status(session_dir: Path) -> int:
|
|
140
|
+
"""Print the stored session level, or the empty-state message.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
session_dir: A validated session directory.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Process exit code 0.
|
|
147
|
+
"""
|
|
148
|
+
level = None
|
|
149
|
+
try:
|
|
150
|
+
payload = json.loads(state_path(session_dir).read_text(encoding="utf-8"))
|
|
151
|
+
if (
|
|
152
|
+
isinstance(payload, dict)
|
|
153
|
+
and payload.get("schema") == 1
|
|
154
|
+
and payload.get("reasoning") in EFFORT_LEVELS
|
|
155
|
+
):
|
|
156
|
+
level = payload["reasoning"]
|
|
157
|
+
except (OSError, json.JSONDecodeError):
|
|
158
|
+
level = None
|
|
159
|
+
if level is None:
|
|
160
|
+
print("No session reasoning effort set.")
|
|
161
|
+
else:
|
|
162
|
+
print(f"Session reasoning effort: {level}")
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def parse_args() -> argparse.Namespace:
|
|
167
|
+
"""Parse the command-line arguments.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
The parsed argparse namespace.
|
|
171
|
+
"""
|
|
172
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
173
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
174
|
+
|
|
175
|
+
set_parser = sub.add_parser("set", help="record a session reasoning level")
|
|
176
|
+
set_parser.add_argument("--dir", required=True, help="session temp directory")
|
|
177
|
+
set_parser.add_argument("--reasoning", required=True, help="level to record")
|
|
178
|
+
|
|
179
|
+
clear_parser = sub.add_parser("clear", help="remove the session reasoning level")
|
|
180
|
+
clear_parser.add_argument("--dir", required=True, help="session temp directory")
|
|
181
|
+
|
|
182
|
+
status_parser = sub.add_parser("status", help="print the session reasoning level")
|
|
183
|
+
status_parser.add_argument("--dir", required=True, help="session temp directory")
|
|
184
|
+
|
|
185
|
+
return parser.parse_args()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main() -> int:
|
|
189
|
+
"""Run the requested command.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Process exit code: 0 on success, 2 on invalid input.
|
|
193
|
+
"""
|
|
194
|
+
args = parse_args()
|
|
195
|
+
session_dir = resolve_session_dir(args.dir)
|
|
196
|
+
if session_dir is None:
|
|
197
|
+
print(
|
|
198
|
+
"Error: --dir must be an existing session directory inside the CodeOps temp root.",
|
|
199
|
+
file=sys.stderr,
|
|
200
|
+
)
|
|
201
|
+
return 2
|
|
202
|
+
if args.command == "set":
|
|
203
|
+
if args.reasoning not in EFFORT_LEVELS:
|
|
204
|
+
print(
|
|
205
|
+
"Error: --reasoning must be one of: " + ", ".join(EFFORT_LEVELS) + ".",
|
|
206
|
+
file=sys.stderr,
|
|
207
|
+
)
|
|
208
|
+
return 2
|
|
209
|
+
return set_level(session_dir, args.reasoning)
|
|
210
|
+
if args.command == "clear":
|
|
211
|
+
return clear_level(session_dir)
|
|
212
|
+
return show_status(session_dir)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if __name__ == "__main__":
|
|
216
|
+
raise SystemExit(main())
|
|
@@ -36,6 +36,18 @@ do not report or implement optional additions raised during execution or review.
|
|
|
36
36
|
Exploration may create `SE-*` proposals, but only the user may choose `Keep` and authorize a plan
|
|
37
37
|
update.
|
|
38
38
|
|
|
39
|
+
## Auto-effort option
|
|
40
|
+
|
|
41
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--auto-effort` or `--auto-effort=<level>`
|
|
42
|
+
token before the first `--` sentinel, remove it before resolving targets, paths, or modes; zero
|
|
43
|
+
occurrences means each phase's `Reasoning:` level is printed as a suggestion only, more than one
|
|
44
|
+
or an invalid level is an argument error; announce
|
|
45
|
+
`Auto-effort active — reasoning <level> applied for this run`; then read and apply
|
|
46
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. A bare
|
|
47
|
+
flag follows each phase's suggestion; `--auto-effort=<level>` forces that level for the whole run,
|
|
48
|
+
including every dispatch marker composed during it. The run clears the level before its final
|
|
49
|
+
summary.
|
|
50
|
+
|
|
39
51
|
Execute the implementation plan at `plans/$ARGUMENTS/99-execution-plan.md`. The first
|
|
40
52
|
argument is the feature name; an optional flag selects the commit mode.
|
|
41
53
|
|
|
@@ -70,6 +70,10 @@ scope baseline; missing mode context fails closed to strict scope.
|
|
|
70
70
|
When opt-in outcome metrics are enabled, record only a content-free execution-stage event through
|
|
71
71
|
`codeops_outcomes.py`; metrics never gate execution.
|
|
72
72
|
|
|
73
|
+
When the phase header carries `> **Reasoning**: <level> — <reason>`, record it as the phase's
|
|
74
|
+
advisory level for dispatch markers, inline reporting, and applied-level reporting. It is a
|
|
75
|
+
suggestion: no gate reads it, and its absence means "inherit".
|
|
76
|
+
|
|
73
77
|
**Spec-author dispatch (profile-gated).** Tasks marked `[spec-author]` dispatch the
|
|
74
78
|
spec-test-author agent — packet per `_shared/quality-profile.md` — BEFORE any implementation
|
|
75
79
|
task of that phase, and the red phase is confirmed from its report. A spec test that cannot be
|
|
@@ -300,12 +304,46 @@ receives nothing else and must not need anything else:
|
|
|
300
304
|
- the scope mode (`strict` or `explore`) and confirmed product scope baseline; missing or invalid
|
|
301
305
|
scope context fails closed to strict mode. Missing or invalid original-goal or smallest-design
|
|
302
306
|
context blocks dispatch;
|
|
303
|
-
- the target file paths and the project's verify command
|
|
307
|
+
- the target file paths and the project's verify command;
|
|
308
|
+
- the phase's reasoning marker line (`[codeops-effort: <level>]`), resolved from the run's forced
|
|
309
|
+
`--auto-effort=<level>` level, else the phase's `Reasoning:` suggestion; omitted when neither
|
|
310
|
+
exists.
|
|
304
311
|
|
|
305
312
|
Excerpting owned content into a packet is the intended retrieval mechanism, not restatement. The
|
|
306
313
|
quoted AR/ST/spec content is context for the executor's *understanding* — it must not surface as a
|
|
307
314
|
citation in shipped code (the executor carries the same doc-standard ban and self-check).
|
|
308
315
|
|
|
316
|
+
**Reasoning marker.** Every dispatched unit — executor, reviewer, auditor, spec-test author,
|
|
317
|
+
specialist, or scout — receives one standalone marker line in its packet:
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
[codeops-effort: medium]
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Place it immediately after the `[codeops-dispatch …]` header for quality agents, or as the first
|
|
324
|
+
line for packets without a header. When neither the forced run level nor the phase suggestion
|
|
325
|
+
exists, add no marker: routing policy still applies, otherwise the child inherits the parent
|
|
326
|
+
variant. The marker is packet context; it never appears in shipped code comments. The
|
|
327
|
+
complexity-gate design challenger is excluded because its independence contract forbids extra
|
|
328
|
+
packet shaping.
|
|
329
|
+
|
|
330
|
+
**Applied-level reporting.** For every dispatch, report the level and its source in the dispatch
|
|
331
|
+
commentary, for example `Dispatch: executor — reasoning: medium (phase suggestion)`,
|
|
332
|
+
`— reasoning: high (routing default)`, or `— inherited`. Reporting is observational; it never
|
|
333
|
+
gates a dispatch.
|
|
334
|
+
|
|
335
|
+
**Inline phases.** When a phase runs inline (the default):
|
|
336
|
+
|
|
337
|
+
1. Print `Suggested reasoning: <level> — <reason>` before the phase's first task when the phase
|
|
338
|
+
header carries the line.
|
|
339
|
+
2. Without `--auto-effort`, change nothing else — the session keeps its own variant.
|
|
340
|
+
3. With `--auto-effort`, set the session level for the phase through
|
|
341
|
+
`python3 "${CODEOPS_PLUGIN_ROOT}/scripts/codeops_effort.py" set --dir "$CODEOPS_TMPDIR" --reasoning <level>`
|
|
342
|
+
and update it when the next phase's suggestion differs; with `--auto-effort=<level>`, set that
|
|
343
|
+
level once and keep it for the whole run. A phase without a `Reasoning:` line leaves the
|
|
344
|
+
session level unchanged.
|
|
345
|
+
4. When `$CODEOPS_TMPDIR` is empty or the helper fails, print an advise-only note and continue.
|
|
346
|
+
|
|
309
347
|
**Division of labor.** The PARENT — never the executor — updates `99-execution-plan.md`
|
|
310
348
|
(two-stage marks), the Progress header, and the roadmap. The executor implements task-by-task,
|
|
311
349
|
runs verify per the Verify-output capture rule, and reports per task. Mark `[~]` as the executor
|
|
@@ -404,8 +442,10 @@ otherwise still `[~]` — with the progress counter and Last Updated stamp curre
|
|
|
404
442
|
2. **🚨 First: update `99-execution-plan.md`** with ALL completed tasks (before anything else).
|
|
405
443
|
3. Run the verify command (output captured per the Verify-output capture rule).
|
|
406
444
|
4. Handle the commit per the active commit mode (see [commit-modes.md](commit-modes.md)).
|
|
407
|
-
5.
|
|
408
|
-
|
|
445
|
+
5. If this run set a session level through `--auto-effort`, clear it before the summary:
|
|
446
|
+
`python3 "${CODEOPS_PLUGIN_ROOT}/scripts/codeops_effort.py" clear --dir "$CODEOPS_TMPDIR"`.
|
|
447
|
+
6. Report the session summary (must include `Execution Plan Updated: ✅`).
|
|
448
|
+
7. **Cleanup:** delete every temporary artifact this session created — verify logs under
|
|
409
449
|
`$CODEOPS_TMPDIR`, scratch directories, temporary diffs — per
|
|
410
450
|
`_shared/workspace-hygiene.md`, and report `Cleanup: done` or name what was kept and why.
|
|
411
451
|
|
package/skills/grill-me/SKILL.md
CHANGED
|
@@ -21,6 +21,16 @@ implementation work begins.
|
|
|
21
21
|
|
|
22
22
|
> **CodeOps Artifact Schema**: 1
|
|
23
23
|
|
|
24
|
+
## Auto-effort option
|
|
25
|
+
|
|
26
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--auto-effort` or `--auto-effort=<level>`
|
|
27
|
+
token before the first `--` sentinel, remove it before resolving targets, paths, or modes; zero
|
|
28
|
+
occurrences means this skill's recommended level (`high`) is printed as a suggestion only, more
|
|
29
|
+
than one or an invalid level is an argument error; announce
|
|
30
|
+
`Auto-effort active — reasoning <level> applied for this run`; then read and apply
|
|
31
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
32
|
+
run clears the level before its final summary.
|
|
33
|
+
|
|
24
34
|
## Core Directive
|
|
25
35
|
|
|
26
36
|
> **Interview the user relentlessly about every aspect of the topic until you reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one.**
|
|
@@ -24,6 +24,16 @@ If `$ARGUMENTS` contains exactly one exact standalone `--explore-scope` token be
|
|
|
24
24
|
do not report or plan optional additions. Exploration may propose `SE-*` items but
|
|
25
25
|
never accepts them; only the user may choose `Keep`.
|
|
26
26
|
|
|
27
|
+
## Auto-effort option
|
|
28
|
+
|
|
29
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--auto-effort` or `--auto-effort=<level>`
|
|
30
|
+
token before the first `--` sentinel, remove it before resolving targets, paths, or modes; zero
|
|
31
|
+
occurrences means this skill's recommended level (`high`) is printed as a suggestion only, more
|
|
32
|
+
than one or an invalid level is an argument error; announce
|
|
33
|
+
`Auto-effort active — reasoning <level> applied for this run`; then read and apply
|
|
34
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
35
|
+
run clears the level before its final summary.
|
|
36
|
+
|
|
27
37
|
## Plan readiness proof
|
|
28
38
|
|
|
29
39
|
A plan is not ready merely because its documents exist. Before presenting it as executable,
|
|
@@ -106,6 +116,7 @@ Mini-plan shape:
|
|
|
106
116
|
|
|
107
117
|
> **Type**: Task (lightweight) · **Feature**: search · **CodeOps Artifact Schema**: 1
|
|
108
118
|
> **Progress**: 0/3 tasks (0%)
|
|
119
|
+
> **Reasoning**: medium — bounded UI change reusing existing patterns
|
|
109
120
|
|
|
110
121
|
## Objective
|
|
111
122
|
Debounce the search box to 300ms to cut redundant queries.
|
|
@@ -121,6 +132,12 @@ shared debounce subsystem.
|
|
|
121
132
|
**Verify**: [project verify command]
|
|
122
133
|
```
|
|
123
134
|
|
|
135
|
+
Every phase and every task mini-plan carries the same advisory
|
|
136
|
+
`> **Reasoning**: <level> — <reason>` line. Derive the level from the signals the plan already
|
|
137
|
+
records, using [../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Plan
|
|
138
|
+
suggestion derivation, and keep the reason a short plain-language phrase. The line is a
|
|
139
|
+
suggestion: no gate reads it, deleting it is valid, and its absence means "inherit".
|
|
140
|
+
|
|
124
141
|
Everything below is the **full feature** pipeline; skip it for tasks.
|
|
125
142
|
|
|
126
143
|
## Project configuration
|
|
@@ -458,6 +458,8 @@ task-size criteria in [quality-checklist.md](quality-checklist.md))
|
|
|
458
458
|
> committed, staged, unstaged, and untracked phase-start state)_
|
|
459
459
|
> **Lenses**: [add-on lenses — include this line only when the target repo carries a quality
|
|
460
460
|
> profile; informational: activation stays profile-driven]
|
|
461
|
+
> **Reasoning**: [advisory level — `low`, `medium`, `high`, or `max`, with a one-line reason;
|
|
462
|
+
> derivation in `../../_shared/reasoning-effort.md` §Plan suggestion derivation]
|
|
461
463
|
|
|
462
464
|
### Step 1.1: [Step Objective]
|
|
463
465
|
|
|
@@ -27,6 +27,16 @@ requirements decisions under that policy and propagate its downward-only context
|
|
|
27
27
|
invoked supported children; an unsupported child fails closed. This mode does not grant action permission or scope expansion. **Normal mode:** without the exact token, every material choice
|
|
28
28
|
still requires an explicit user decision; historical delegated records must not infer delegated authority.
|
|
29
29
|
|
|
30
|
+
## Auto-effort option
|
|
31
|
+
|
|
32
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--auto-effort` or `--auto-effort=<level>`
|
|
33
|
+
token before the first `--` sentinel, remove it before resolving targets, paths, or modes; zero
|
|
34
|
+
occurrences means this skill's recommended level (`high`) is printed as a suggestion only, more
|
|
35
|
+
than one or an invalid level is an argument error; announce
|
|
36
|
+
`Auto-effort active — reasoning <level> applied for this run`; then read and apply
|
|
37
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
38
|
+
run clears the level before its final summary.
|
|
39
|
+
|
|
30
40
|
Transform a rough project idea into a structured, complete set of formal
|
|
31
41
|
**requirement documents (RDs)**. This skill is upstream of, and independent
|
|
32
42
|
from, the make-plan skill — neither requires the other.
|
|
@@ -33,6 +33,16 @@ If `$ARGUMENTS` contains exactly one exact standalone `--explore-scope` token be
|
|
|
33
33
|
do not report optional additions as findings or suggestions. Exploration records them
|
|
34
34
|
as separate `SE-*` proposals; finding resolution never chooses `Keep`.
|
|
35
35
|
|
|
36
|
+
## Auto-effort option
|
|
37
|
+
|
|
38
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--auto-effort` or `--auto-effort=<level>`
|
|
39
|
+
token before the first `--` sentinel, remove it before resolving targets, paths, or modes; zero
|
|
40
|
+
occurrences means this skill's recommended level (`high`, or `max` with `--thorough`) is printed
|
|
41
|
+
as a suggestion only, more than one or an invalid level is an argument error; announce
|
|
42
|
+
`Auto-effort active — reasoning <level> applied for this run`; then read and apply
|
|
43
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
44
|
+
run clears the level before its final summary.
|
|
45
|
+
|
|
36
46
|
Run a rigorous quality audit of the artifact named in `$ARGUMENTS`, **grounded in the actual
|
|
37
47
|
codebase**. Find every issue, ambiguity, contradiction, gap, and risk; verify every claim and
|
|
38
48
|
assumption against the real code; present each finding with options + a recommendation; iterate
|
|
@@ -20,6 +20,16 @@ description: >-
|
|
|
20
20
|
|
|
21
21
|
> **CodeOps Artifact Schema**: 1
|
|
22
22
|
|
|
23
|
+
## Auto-effort option
|
|
24
|
+
|
|
25
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--auto-effort` or `--auto-effort=<level>`
|
|
26
|
+
token before the first `--` sentinel, remove it before resolving targets, paths, or modes; zero
|
|
27
|
+
occurrences means this skill's recommended level (`high`) is printed as a suggestion only, more
|
|
28
|
+
than one or an invalid level is an argument error; announce
|
|
29
|
+
`Auto-effort active — reasoning <level> applied for this run`; then read and apply
|
|
30
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
31
|
+
run clears the level before its final summary.
|
|
32
|
+
|
|
23
33
|
Analyze an existing codebase — any language, any framework — and produce a
|
|
24
34
|
structured **reconstruction brief** that can be fed to the make-requirements
|
|
25
35
|
skill to generate formal requirement documents capable of rebuilding the entire
|
|
@@ -50,6 +50,8 @@ Present:
|
|
|
50
50
|
|
|
51
51
|
- detected domains and concrete evidence;
|
|
52
52
|
- phase tag → capability/effort policy;
|
|
53
|
+
- reasoning-effort defaults per role when risk signals justify them (see
|
|
54
|
+
[routing.md](routing.md) §Reasoning effort policy);
|
|
53
55
|
- required specialist reviewers;
|
|
54
56
|
- proposed concurrency limit;
|
|
55
57
|
- whether custom TOML agents add value over dynamic packets; and
|
|
@@ -27,7 +27,7 @@ CodeOps routing lives under the optional `routing` and `quality` fields in `code
|
|
|
27
27
|
|
|
28
28
|
Allowed effort values follow the active OpenCode release. Prefer `medium` for bounded reconnaissance, `high` for correctness/security review, and higher supported levels only for genuinely demanding semantic or architectural work.
|
|
29
29
|
|
|
30
|
-
An optional per-role `reasoning` field sets the provider reasoning-effort passthrough (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). Generated project specialists default to `max` through their brief or the embedded `reasoningEffort`; a routing value wins over the brief.
|
|
30
|
+
An optional per-role `reasoning` field sets the provider reasoning-effort passthrough (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). Generated project specialists default to `max` through their brief or the embedded `reasoningEffort`; a routing value wins over the brief and is embedded without provider-capability detection. At runtime the plugin applies a level only when the active model exposes a matching variant; an unsupported value leaves the request unchanged (see the policy below).
|
|
31
31
|
|
|
32
32
|
```json
|
|
33
33
|
"roles": {
|
|
@@ -35,6 +35,21 @@ An optional per-role `reasoning` field sets the provider reasoning-effort passth
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Reasoning effort policy
|
|
39
|
+
|
|
40
|
+
A role's `reasoning` entry is a project default, not the only source. The runtime resolution
|
|
41
|
+
order is `dispatch marker > session auto-effort > routing role default > inherit parent variant`
|
|
42
|
+
(see [../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md)). A dispatch marker
|
|
43
|
+
comes from an execution plan's per-phase suggestion; a session level comes from an explicit
|
|
44
|
+
`--auto-effort` run. Both override the routing default for their scope, and routing applies only
|
|
45
|
+
when a role entry exists — with no entry the child inherits the parent variant.
|
|
46
|
+
|
|
47
|
+
The suggestion vocabulary is `low`, `medium`, `high`, and `max`, while the routing field accepts
|
|
48
|
+
the wider provider enum (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). The deepseek
|
|
49
|
+
flash model exposes `low`/`medium`/`high`/`max`; prefer `low` for mechanical work, `medium` for
|
|
50
|
+
bounded implementation, `high` for planning and review, and `max` only for adversarial analysis.
|
|
51
|
+
An unsupported value is skipped at runtime, so a routing entry never produces a provider error.
|
|
52
|
+
|
|
38
53
|
Model pins are optional per role. When omitted, OpenCode resolves the model from the explicit spawn, project defaults, and parent session. A missing pin must never block the workflow.
|
|
39
54
|
|
|
40
55
|
Reviewer selection is driven by risk tags:
|
|
@@ -7,6 +7,16 @@ description: Upgrade an existing CodeOps requirements set, specification, plan,
|
|
|
7
7
|
|
|
8
8
|
The current CodeOps artifact schema is `1`. Historical Claude CodeOps `3.x` stamps describe the producing skill release, not this schema. Treat them as legacy input requiring assessment, not as numeric predecessors of schema 1.
|
|
9
9
|
|
|
10
|
+
## Auto-effort option
|
|
11
|
+
|
|
12
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--auto-effort` or `--auto-effort=<level>`
|
|
13
|
+
token before the first `--` sentinel, remove it before resolving targets, paths, or modes; zero
|
|
14
|
+
occurrences means this skill's recommended level (`high`) is printed as a suggestion only, more
|
|
15
|
+
than one or an invalid level is an argument error; announce
|
|
16
|
+
`Auto-effort active — reasoning <level> applied for this run`; then read and apply
|
|
17
|
+
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
18
|
+
run clears the level before its final summary.
|
|
19
|
+
|
|
10
20
|
## Scope
|
|
11
21
|
|
|
12
22
|
Upgrade content and structure in place. Layout moves belong to `setup-codeops`. Never combine a layout migration and semantic/schema upgrade into one irreversible operation.
|