opencode-codeops 2.2.1 → 2.4.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 CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  All notable changes to CodeOps are recorded here.
4
4
 
5
+ ## 2.4.0 — 2026-10-29
6
+
7
+ ### Features
8
+
9
+ - setup-codeops: enable Discovery by default for new projects
10
+
11
+ ## 2.3.0 — 2026-10-29
12
+
13
+ ### Fixes
14
+
15
+ - discovery: close the browser acceptance findings
16
+ - discovery: guard the locked-group selection rule
17
+
18
+ ### Documentation
19
+
20
+ - discovery: mark browser acceptance as the remaining gate
21
+ - discovery: document the redesigned plan workspace
22
+ - discovery-ui-redesign: add the preflighted implementation plan
23
+ - discovery: add the agent protocol and skill adoption
24
+ - discovery: record the host spike outcomes
25
+ - discovery: add the plan set with preflight fixes applied
26
+
27
+ ### Features
28
+
29
+ - discovery: redesign the plan workspace with group navigation, a decision summary, honest save feedback, and System/Light/Dark themes
30
+ - discovery: complete the Fluent UI workspace
31
+ - discovery: add guarded editing and the workspace shell
32
+ - discovery: add workspace destinations and theme preference
33
+ - discovery: finish the docs, live evidence, and UI fixes
34
+ - discovery: add the Fluent UI app and packaging
35
+ - discovery: wire the opt-in tools and waking
36
+ - discovery: add the listener and HTTP API
37
+ - discovery: add the store
38
+ - discovery: add the rules and model module
39
+
40
+ ### Tests
41
+
42
+ - discovery: add navigation and theme specification tests
43
+ - discovery: isolate the fallback-port spec test
44
+
45
+ ### Chores
46
+
47
+ - codeops: refresh the specialist check
48
+ - project: refresh managed guidance
49
+
5
50
  ## 2.2.1 — 2026-10-10
6
51
 
7
52
  ### Fixes
package/README.md CHANGED
@@ -162,6 +162,59 @@ when no run is active, when the host lacks the plugin APIs, or when every call f
162
162
  Markdown execution plan remains the single progress source of truth; the live view never gates
163
163
  execution.
164
164
 
165
+ ## Discovery
166
+
167
+ Discovery is a local web page where the agent publishes a CodeOps question round
168
+ instead of asking every question in chat. Questions, options, recommendations, and dependency
169
+ rules render in the browser; answers are saved there, and Submit wakes the OpenCode session that
170
+ published them so it can continue.
171
+
172
+ ### Plan workspace
173
+
174
+ The plan workspace uses a persistent group navigation column and shows one selected group at a
175
+ time. Groups carry ready, pending, and completed cues, and a selected group stays selected while
176
+ you inspect a locked or completed group. The plan URL, group links
177
+ (`#/p/<project>/<plan>/groups/<key>`), and the summary link
178
+ (`#/p/<project>/<plan>/summary`) are stable and work with browser back and forward.
179
+
180
+ The action bar always shows **Submit group** for the selected group. The **Decision summary**
181
+ lists current answers, their statuses, and links back to each group; **Submit all ready groups**
182
+ sends every ready group from one place. Readiness and completion are labelled separately.
183
+
184
+ Save state is server-backed. **Saved** appears only after the server acknowledges the write;
185
+ **Unsaved changes** and **Saving…** show pending work, and a failed save keeps the draft with
186
+ **Retry**. Edits survive group changes and live refreshes. A draft that can no longer be saved —
187
+ because the question became read-only, locked, retired, hidden, or its group was removed — stays
188
+ visible under **Unsent drafts** and can be explicitly discarded or left after confirmation.
189
+
190
+ The shared header carries a real breadcrumb and a **System / Light / Dark** theme control. System
191
+ follows the operating-system setting; an explicit choice is remembered in the browser. If storage
192
+ is unavailable, the choice still applies for the current tab.
193
+
194
+ New projects created with `setup-codeops` start with Discovery enabled. For an existing project,
195
+ run `analyze-project --with-discovery`, or add the setting to `codeops/codeops.json`:
196
+
197
+ ```json
198
+ {
199
+ "discovery": { "enabled": true, "chatMaxQuestions": 3 }
200
+ }
201
+ ```
202
+
203
+ Rounds with at most `chatMaxQuestions` (default 3) questions stay in chat. The page is served
204
+ from the plugin at `http://localhost:4747/`; set `CODEOPS_DISCOVERY_PORT` to listen on a
205
+ different port. Discovery data lives in `$CODEOPS_DATA_DIR/discovery`, which defaults to
206
+ `~/.local/share/opencode/codeops/discovery`.
207
+
208
+ The listener binds to `127.0.0.1` only and has no login. It assumes a single-user machine: any
209
+ local user can read the page and the stored answers. Browsers limit concurrent
210
+ connections per host, so keep only a few plan tabs open at once. The agent continues only while
211
+ OpenCode is running in that project; submissions that arrive while it is not are delivered when
212
+ it starts again.
213
+
214
+ Pass `--no-discovery` to a skill to skip the page for one run and use chat. Building from a
215
+ checkout with `npm run build:discovery` refreshes the page assets without restarting OpenCode.
216
+ Remote and SSH sessions are not supported.
217
+
165
218
  ## Agent model configuration
166
219
 
167
220
  All CodeOps subagents inherit the model of the primary agent that invoked them. No provider-specific configuration is required out of the box.
@@ -0,0 +1,92 @@
1
+ # Discovery (optional answer surface)
2
+
3
+ > **CodeOps Artifact Schema**: 1
4
+
5
+ Discovery moves large question rounds out of the chat scroll and into a local
6
+ browser page. New projects created with `setup-codeops` enable it by default;
7
+ existing projects opt in. It never replaces the Ambiguity Register. Use it only
8
+ when this file's conditions hold; otherwise ask in chat exactly as before.
9
+
10
+ ## When it applies
11
+
12
+ All three must hold:
13
+
14
+ 1. `codeops/codeops.json` sets `discovery.enabled: true`.
15
+ 2. The `codeops_discovery_publish`, `codeops_discovery_answers`, and
16
+ `codeops_discovery_resolve` tools are available.
17
+ 3. The run does not carry the `--no-discovery` token.
18
+
19
+ If any condition fails, ask in chat. Never mention a page the user cannot use.
20
+
21
+ ## Round size
22
+
23
+ A round with at most `discovery.chatMaxQuestions` questions (default 3) stays
24
+ in chat. Larger rounds are published. The user can always ask to answer in
25
+ chat instead; honor that for the rest of the run.
26
+
27
+ ## What never goes to the page
28
+
29
+ The **Complexity Escalation Gate** always stays in chat: its visible STOP
30
+ packet, the challenger verdict, and the approval decision. The page has no
31
+ "accept all" control, so a question that would waive a gate must not be
32
+ published.
33
+
34
+ ## Publishing
35
+
36
+ - One Discovery plan per artifact: the `kind` (`plan`, `requirements`, or
37
+ `preflight`) plus the repository-relative `artifact` path. Republishing the
38
+ same pair creates the next revision.
39
+ - Groups follow dependency order; a foundational group is listed in `dependsOn`
40
+ of the groups it unlocks.
41
+ - Every question carries `why`, `evidence` (`file:line` where possible),
42
+ options with their trade-offs, and a `recommendation`. The recommendation is
43
+ shown and never pre-selected.
44
+ - Use `showIf` and `implies` when one answer settles another question in the
45
+ same plan. No chains: an implied answer never implies a third.
46
+ - Keys are stable: `DQ-<n>` for discovery, `PF-NNN` for preflight findings.
47
+ Never reuse a key for a different decision.
48
+ - A question whose answer is already recorded may be republished unchanged.
49
+ Omitting an **open** question retires it; recorded, submitted, and
50
+ already-retired questions are kept (with a warning when the content changed).
51
+ Retire a submitted question through `codeops_discovery_resolve`. A follow-up
52
+ publish therefore resends every earlier question plus the new ones.
53
+
54
+ ## After publishing
55
+
56
+ Send one chat line with the link (and the fallback-port notice when present).
57
+ Then **end the turn** and **do not poll**. The user answers in the browser; the
58
+ wake message continues the work. Under the no-waker fallback, call
59
+ `codeops_discovery_answers` with `wait: true` after publishing.
60
+
61
+ ## Ownership
62
+
63
+ On an `owned` result, ask the user in chat whether to take over. Publish with
64
+ `takeover: true` only after an explicit yes. Never take over silently.
65
+
66
+ ## Reading and recording
67
+
68
+ On the wake message, or when the user says they answered, call
69
+ `codeops_discovery_answers`. Then:
70
+
71
+ - Record each answer as exact text with its question key and its source.
72
+ - Record an implied answer as "**implied by** <key>, shown and submitted".
73
+ Note when the user overrode an implication.
74
+ - Record a Defer as a named deferral: decision · owner · revisit.
75
+ - Leave "needs discussion" questions open; they are not decisions.
76
+ - Call `codeops_discovery_resolve` with the register ids for the recorded
77
+ answers. The **Ambiguity Register stays the authoritative record**; the page
78
+ is only the collection surface.
79
+ - Answers the user gives in chat for published questions are recorded and
80
+ resolved exactly the same way.
81
+
82
+ ## Follow-ups
83
+
84
+ Republish the same artifact with **every earlier question plus the new ones**.
85
+ A publish is the plan's full question set: omitting an open question retires
86
+ it, while submitted and recorded questions are kept until they are recorded. Use `proposal` for
87
+ settlements the agent infers; the user confirms or reopens them on the page.
88
+
89
+ ## Unavailable
90
+
91
+ On `disabled` or `unavailable`, say so in one line and ask in chat. **Never
92
+ skip or weaken a gate** because the page is unavailable.
@@ -221,6 +221,10 @@ named larger machinery.
221
221
  save the register with header `✅ GATE PASSED — 0 ambiguities identified (systematic review
222
222
  completed)`. This proves the gate was executed.
223
223
 
224
+ ## Discovery (optional answer surface)
225
+
226
+ When a workflow runs with Discovery enabled, the Ambiguity Register stays the **authoritative record** ([discovery.md](discovery.md)). Answers transcribed from the page are exact text with their question key; an implied answer counts as a user decision only when the page showed it and the user submitted it. Deferrals still need decision, owner, and revisit. Complexity escalations stay in **chat**: the STOP packet, the challenger verdict, and the approval are never published to the page.
227
+
224
228
  ## Deferral rules (named deferral only)
225
229
 
226
230
  A decision may be **explicitly deferred** — the status the whole pipeline shares (grill-me
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Type declarations for the CodeOps Discovery rules and model helper.
3
+ *
4
+ * The helper is plain JavaScript so the browser bundle and `node --test` can
5
+ * import the same file. These declarations give the TypeScript plugin entry
6
+ * point and the UI build typed access to it.
7
+ */
8
+
9
+ /** Schema version written into every Discovery data file. */
10
+ export declare const DISCOVERY_SCHEMA_VERSION: 1
11
+
12
+ /** A question type. */
13
+ export type QuestionType = "single" | "multiple" | "text" | "single-other"
14
+
15
+ /** Every supported question type. */
16
+ export declare const QUESTION_TYPES: readonly QuestionType[]
17
+
18
+ /** Question types that carry options. */
19
+ export declare const CHOICE_TYPES: readonly QuestionType[]
20
+
21
+ /** A plan kind. */
22
+ export type PlanKind = "plan" | "requirements" | "preflight"
23
+
24
+ /** Every supported plan kind. */
25
+ export declare const PLAN_KINDS: readonly PlanKind[]
26
+
27
+ /** Pattern for question keys, group keys, and option ids. */
28
+ export declare const KEY_PATTERN: RegExp
29
+
30
+ /** Pattern for URL and file-name slugs. */
31
+ export declare const SLUG_PATTERN: RegExp
32
+
33
+ /** Maximum length of a title, group title, or option label. */
34
+ export declare const TITLE_MAX: 200
35
+
36
+ /** Maximum length of every other free-text value. */
37
+ export declare const TEXT_MAX: 4000
38
+
39
+ /** Maximum length of an artifact path. */
40
+ export declare const ARTIFACT_MAX: 300
41
+
42
+ /** Structural caps for one publish. */
43
+ export declare const LIMITS: {
44
+ readonly groups: 50
45
+ readonly questions: 200
46
+ readonly optionsMin: 2
47
+ readonly optionsMax: 20
48
+ readonly evidence: 20
49
+ }
50
+
51
+ /** Question fields covered by the fingerprint. */
52
+ export declare const FINGERPRINT_FIELDS: readonly string[]
53
+
54
+ /** A stored answer; exactly one field is set. */
55
+ export interface Answer {
56
+ choice?: string
57
+ choices?: string[]
58
+ text?: string
59
+ other?: string
60
+ }
61
+
62
+ /** A deferral recorded by the user. */
63
+ export interface Defer {
64
+ decision: string
65
+ owner: string
66
+ revisit: string
67
+ }
68
+
69
+ /** A compact answer: an option id, option ids, or text. */
70
+ export type CompactAnswer = string | string[]
71
+
72
+ /** An implication attached to one option. */
73
+ export interface Implication {
74
+ question: string
75
+ answer: CompactAnswer
76
+ reason: string
77
+ }
78
+
79
+ /** One question option. */
80
+ export interface QuestionOption {
81
+ id: string
82
+ label: string
83
+ description?: string
84
+ implies?: Implication[]
85
+ }
86
+
87
+ /** One visibility condition. */
88
+ export interface ShowIfCondition {
89
+ question: string
90
+ anyOf: string[]
91
+ }
92
+
93
+ /** An agent-proposed settlement. */
94
+ export interface Proposal {
95
+ answer: CompactAnswer
96
+ reason: string
97
+ }
98
+
99
+ /** One normalized published question. */
100
+ export interface Question {
101
+ key: string
102
+ group: string
103
+ type: QuestionType
104
+ title: string
105
+ why?: string
106
+ evidence?: string[]
107
+ options?: QuestionOption[]
108
+ recommendation?: string | string[]
109
+ showIf?: ShowIfCondition[]
110
+ proposal?: Proposal
111
+ }
112
+
113
+ /** One normalized published group. */
114
+ export interface Group {
115
+ key: string
116
+ title: string
117
+ description?: string
118
+ dependsOn: string[]
119
+ }
120
+
121
+ /** A normalized published plan. */
122
+ export interface PublishedPlan {
123
+ artifact: string
124
+ kind: PlanKind
125
+ title: string
126
+ groups: Group[]
127
+ questions: Question[]
128
+ }
129
+
130
+ /** The stored lifecycle of one question. */
131
+ export type QuestionStatus = "open" | "submitted" | "recorded" | "retired"
132
+
133
+ /** The mutable state of one question. */
134
+ export interface QuestionState {
135
+ answer: Answer | null
136
+ notes: string
137
+ defer: Defer | null
138
+ needsDiscussion: boolean
139
+ proposalDismissed: boolean
140
+ status: QuestionStatus
141
+ changed: boolean
142
+ revisedAt: number
143
+ recordedAs: string | null
144
+ }
145
+
146
+ /** The mutable state of one plan. */
147
+ export interface PlanState {
148
+ questions: Record<string, QuestionState>
149
+ submittedGroups: string[]
150
+ }
151
+
152
+ /** One evaluated question. */
153
+ export interface EvaluationEntry {
154
+ visible: boolean
155
+ effective: Answer | null
156
+ source: "user" | "implied" | null
157
+ impliedBy: string[]
158
+ impliedReasons: string[]
159
+ overridesImplied: boolean
160
+ conflict: { from: string; answer: Answer }[]
161
+ }
162
+
163
+ /** The result of evaluating a plan. */
164
+ export interface Evaluation {
165
+ questions: Map<string, EvaluationEntry>
166
+ lockedGroups: Set<string>
167
+ }
168
+
169
+ /** One reason a group is not ready. */
170
+ export interface Missing {
171
+ key: string
172
+ reason: "unanswered" | "changed" | "conflict" | "proposal" | "nothing-to-submit"
173
+ }
174
+
175
+ /** Per-bucket question counts for list views. */
176
+ export interface PlanCounts {
177
+ open: number
178
+ answered: number
179
+ deferred: number
180
+ discussion: number
181
+ submitted: number
182
+ recorded: number
183
+ }
184
+
185
+ /** The JSON Schema of the publish tool input. */
186
+ export declare const DiscoveryPublishSchema: Record<string, unknown>
187
+
188
+ /** Validate and normalize a publish payload; never throws. */
189
+ export declare function validatePublish(
190
+ input: unknown
191
+ ):
192
+ | { ok: true; plan: PublishedPlan }
193
+ | { ok: false; errors: string[] }
194
+
195
+ /** Validate one stored answer for its question type. */
196
+ export declare function validateAnswer(
197
+ question: Question,
198
+ answer: unknown
199
+ ): { ok: true } | { ok: false; error: string }
200
+
201
+ /** Validate a deferral. */
202
+ export declare function validateDefer(
203
+ defer: unknown
204
+ ): { ok: true } | { ok: false; error: string }
205
+
206
+ /** Convert a compact answer into the stored shape. */
207
+ export declare function toAnswer(question: Question, compact: CompactAnswer): Answer
208
+
209
+ /** Canonical content string used to detect a changed question. */
210
+ export declare function questionFingerprint(question: Question): string
211
+
212
+ /** Evaluate visibility, implications, conflicts, and group locks. */
213
+ export declare function evaluatePlan(plan: PublishedPlan, state: PlanState): Evaluation
214
+
215
+ /** Whether a group can be submitted, and what is missing when it cannot. */
216
+ export declare function groupReadiness(
217
+ plan: PublishedPlan,
218
+ state: PlanState,
219
+ evaluation: Evaluation,
220
+ groupKey: string
221
+ ): { ready: boolean; locked: boolean; missing: Missing[] }
222
+
223
+ /** Counts used by list views. */
224
+ export declare function planCounts(
225
+ plan: PublishedPlan,
226
+ state: PlanState,
227
+ evaluation: Evaluation
228
+ ): PlanCounts