opencode-codeops 2.2.1 → 2.3.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 +38 -0
- package/README.md +53 -0
- package/_shared/discovery.md +92 -0
- package/_shared/zero-ambiguity-gate.md +4 -0
- package/bin/lib/discovery-rules.d.mts +228 -0
- package/bin/lib/discovery-rules.mjs +1213 -0
- package/bin/lib/discovery-server.d.mts +62 -0
- package/bin/lib/discovery-server.mjs +755 -0
- package/bin/lib/discovery-store.d.mts +224 -0
- package/bin/lib/discovery-store.mjs +1189 -0
- package/bin/lib/discovery-tools.d.mts +66 -0
- package/bin/lib/discovery-tools.mjs +540 -0
- package/discovery-ui/dist/assets/app.js +107 -0
- package/discovery-ui/dist/index.html +17 -0
- package/package.json +14 -2
- package/plugin/index.ts +45 -4
- package/schemas/codeops-config.schema.json +8 -0
- package/scripts/__pycache__/codeops_plan.cpython-314.pyc +0 -0
- package/scripts/__pycache__/install_agents.cpython-314.pyc +0 -0
- package/scripts/build-discovery-ui.mjs +59 -0
- package/skills/analyze-project/SKILL.md +19 -0
- package/skills/make-plan/SKILL.md +7 -1
- package/skills/preflight/SKILL.md +4 -0
- package/skills/preflight/report-format.md +4 -0
- package/scripts/__pycache__/codeops_plan.cpython-312.pyc +0 -0
- package/scripts/__pycache__/install_agents.cpython-312.pyc +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="csp-nonce" content="__CSP_NONCE__" />
|
|
7
|
+
<link
|
|
8
|
+
rel="icon"
|
|
9
|
+
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
|
|
10
|
+
/>
|
|
11
|
+
<title>Discovery</title>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" nonce="__CSP_NONCE__" src="/assets/app.js"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codeops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Specification-first engineering for complex systems — CodeOps plugin for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test",
|
|
21
21
|
"build:tui": "node scripts/build-tui.mjs",
|
|
22
|
-
"
|
|
22
|
+
"build:discovery": "node scripts/build-discovery-ui.mjs",
|
|
23
|
+
"prepack": "npm run build:discovery",
|
|
24
|
+
"typecheck": "tsc --noEmit && tsc -p discovery-ui --noEmit",
|
|
23
25
|
"check:version": "node scripts/check-version.mjs",
|
|
24
26
|
"verify": "npm run typecheck && npm run test && npm run check:version",
|
|
25
27
|
"prepublishOnly": "npm run verify"
|
|
@@ -40,6 +42,8 @@
|
|
|
40
42
|
"!bin/*.test.mjs",
|
|
41
43
|
"!bin/*.spec.test.mjs",
|
|
42
44
|
"references/",
|
|
45
|
+
"discovery-ui/dist/",
|
|
46
|
+
"!discovery-ui/src/",
|
|
43
47
|
"schemas/",
|
|
44
48
|
"install.sh",
|
|
45
49
|
"README.md",
|
|
@@ -72,9 +76,17 @@
|
|
|
72
76
|
"@opencode/plugin": "^2.0.24"
|
|
73
77
|
},
|
|
74
78
|
"devDependencies": {
|
|
79
|
+
"@fluentui/react-components": "^9.74.11",
|
|
80
|
+
"@fluentui/react-icons": "^2.0.344",
|
|
81
|
+
"@griffel/react": "^1.7.8",
|
|
75
82
|
"@opentui/core": "0.5.17",
|
|
76
83
|
"@opentui/solid": "0.5.17",
|
|
77
84
|
"@types/node": "^22.20.2",
|
|
85
|
+
"@types/react": "^19.3.0",
|
|
86
|
+
"@types/react-dom": "^19.3.0",
|
|
87
|
+
"esbuild": "^0.28.2",
|
|
88
|
+
"react": "^19.3.1",
|
|
89
|
+
"react-dom": "^19.3.1",
|
|
78
90
|
"solid-js": "1.9.12",
|
|
79
91
|
"typescript": "^5.0.0"
|
|
80
92
|
},
|
package/plugin/index.ts
CHANGED
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
registerCodeOpsProgressTool,
|
|
15
15
|
} from "../bin/lib/codeops-progress.mjs"
|
|
16
16
|
import { registerCodeOpsRpc } from "../bin/lib/codeops-rpc.mjs"
|
|
17
|
+
import {
|
|
18
|
+
createDiscoveryController,
|
|
19
|
+
registerDiscoveryTools,
|
|
20
|
+
} from "../bin/lib/discovery-tools.mjs"
|
|
21
|
+
import { resolveDataDir } from "../bin/lib/discovery-store.mjs"
|
|
17
22
|
import {
|
|
18
23
|
appendEffortTrace,
|
|
19
24
|
applyEffort,
|
|
@@ -221,6 +226,13 @@ export default Plugin.define({
|
|
|
221
226
|
async setup(ctx) {
|
|
222
227
|
const directory = ctx.location.directory
|
|
223
228
|
|
|
229
|
+
// Discovery is opt-in; the controller is declared here so the
|
|
230
|
+
// session.deleted branch below can reach it.
|
|
231
|
+
let discovery: ReturnType<typeof createDiscoveryController> | null = null
|
|
232
|
+
// Read through a function so the later assignment is visible to the
|
|
233
|
+
// long-lived event loop without a cast.
|
|
234
|
+
const currentDiscovery = () => discovery
|
|
235
|
+
|
|
224
236
|
// Reasoning-effort state lives for the lifetime of this plugin instance:
|
|
225
237
|
// the latest dispatch-marker level per session, plus a deduplication set
|
|
226
238
|
// for unsupported-level warnings.
|
|
@@ -255,6 +267,7 @@ export default Plugin.define({
|
|
|
255
267
|
if (event.type === "session.deleted") {
|
|
256
268
|
effortMarkers.delete(event.data.sessionID)
|
|
257
269
|
progress.clearSession(event.data.sessionID)
|
|
270
|
+
await currentDiscovery()?.onSessionDeleted(event.data.sessionID)
|
|
258
271
|
}
|
|
259
272
|
}
|
|
260
273
|
} catch {
|
|
@@ -438,11 +451,39 @@ export default Plugin.define({
|
|
|
438
451
|
}
|
|
439
452
|
|
|
440
453
|
// ---------------------------------------------------------------------
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
454
|
+
// Discovery: opt-in browser-based question rounds. The controller owns the
|
|
455
|
+
// process-wide listener and its store; a disabled project creates nothing,
|
|
456
|
+
// so the plugin behaves exactly as before.
|
|
457
|
+
// ---------------------------------------------------------------------
|
|
458
|
+
try {
|
|
459
|
+
const controllerForDiscovery = createDiscoveryController(ctx, {
|
|
460
|
+
directory,
|
|
461
|
+
dataDir: resolveDataDir(process.env),
|
|
462
|
+
assetsDir: join(PACKAGE_ROOT, "discovery-ui", "dist"),
|
|
463
|
+
version: packageVersion,
|
|
464
|
+
warn: warnContentFree,
|
|
465
|
+
})
|
|
466
|
+
discovery = controllerForDiscovery
|
|
467
|
+
await controllerForDiscovery.sync()
|
|
468
|
+
await ctx.session.hook("context", async () => {
|
|
469
|
+
await discovery?.sync()
|
|
470
|
+
})
|
|
471
|
+
const registered = await registerDiscoveryTools(ctx, controllerForDiscovery)
|
|
472
|
+
if (!registered) {
|
|
473
|
+
warnContentFree("The Discovery tools are unavailable in this OpenCode build.")
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
warnContentFree("Could not set up Discovery.")
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ---------------------------------------------------------------------
|
|
480
|
+
// Plugin cleanup: unregister Discovery, then stop listening for events.
|
|
481
|
+
// The runtime's scratch directory is deliberately left in place — other
|
|
482
|
+
// sessions may still be using it — and the startup sweep reclaims
|
|
483
|
+
// abandoned directories.
|
|
444
484
|
// ---------------------------------------------------------------------
|
|
445
|
-
return () => {
|
|
485
|
+
return async () => {
|
|
486
|
+
await discovery?.dispose()
|
|
446
487
|
controller.abort()
|
|
447
488
|
}
|
|
448
489
|
},
|
|
@@ -46,6 +46,14 @@
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
|
+
"discovery": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": false,
|
|
52
|
+
"properties": {
|
|
53
|
+
"enabled": {"type": "boolean"},
|
|
54
|
+
"chatMaxQuestions": {"type": "integer", "minimum": 0}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
49
57
|
"metrics": {
|
|
50
58
|
"type": "object",
|
|
51
59
|
"additionalProperties": false,
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the Discovery web app into a self-contained bundle.
|
|
3
|
+
*
|
|
4
|
+
* esbuild bundles `discovery-ui/src/main.tsx` into `<outdir>/assets/app.js`
|
|
5
|
+
* and copies the HTML template beside it. The listener serves that directory;
|
|
6
|
+
* no remote asset is referenced, so the page works offline and under the
|
|
7
|
+
* nonce-based CSP.
|
|
8
|
+
*
|
|
9
|
+
* @module build-discovery-ui
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { cpSync, mkdirSync, rmSync } from "node:fs"
|
|
13
|
+
import { dirname, join, resolve } from "node:path"
|
|
14
|
+
import { fileURLToPath } from "node:url"
|
|
15
|
+
|
|
16
|
+
import { build } from "esbuild"
|
|
17
|
+
|
|
18
|
+
/** The repository root. */
|
|
19
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build the app.
|
|
23
|
+
*
|
|
24
|
+
* @param {{ outdir?: string, metafile?: boolean }} [options] - Output directory
|
|
25
|
+
* (defaults to `discovery-ui/dist`) and whether to return the metafile.
|
|
26
|
+
* @returns {Promise<{ metafile?: object }>} The esbuild metafile when requested.
|
|
27
|
+
*/
|
|
28
|
+
export async function buildDiscoveryUi(options = {}) {
|
|
29
|
+
const outdir = options.outdir ?? join(ROOT, "discovery-ui", "dist")
|
|
30
|
+
const metafile = options.metafile !== false
|
|
31
|
+
rmSync(outdir, { recursive: true, force: true })
|
|
32
|
+
mkdirSync(join(outdir, "assets"), { recursive: true })
|
|
33
|
+
|
|
34
|
+
const result = await build({
|
|
35
|
+
absWorkingDir: ROOT,
|
|
36
|
+
entryPoints: [join(ROOT, "discovery-ui", "src", "main.tsx")],
|
|
37
|
+
outfile: join(outdir, "assets", "app.js"),
|
|
38
|
+
bundle: true,
|
|
39
|
+
format: "esm",
|
|
40
|
+
minify: true,
|
|
41
|
+
target: "es2022",
|
|
42
|
+
jsx: "automatic",
|
|
43
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
44
|
+
metafile,
|
|
45
|
+
logLevel: "silent",
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
cpSync(join(ROOT, "discovery-ui", "index.html"), join(outdir, "index.html"))
|
|
49
|
+
return { metafile: result.metafile }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const invokedDirectly =
|
|
53
|
+
process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
54
|
+
|
|
55
|
+
if (invokedDirectly) {
|
|
56
|
+
const { metafile } = await buildDiscoveryUi()
|
|
57
|
+
const outputs = Object.keys(metafile?.outputs ?? {})
|
|
58
|
+
console.error(`Discovery UI built: ${outputs.join(", ")}`)
|
|
59
|
+
}
|
|
@@ -26,6 +26,25 @@ description: Analyze the current repository and create or refresh concise CodeOp
|
|
|
26
26
|
9. Validate every recorded command or mark it explicitly unverified.
|
|
27
27
|
10. Specialist coverage: read `codeops/specialist-check.json` when present and include a `Specialist coverage:` line in the managed section — `up to date (<YYYY-MM-DD>)` (from `checkedAt`) when the recorded plan set matches the current one; `check due — run analyze-agents` otherwise; when the state is absent or corrupt, render `check due — run analyze-agents (never checked)`. The `analyze-agents` skill runs the check (criteria: [../../_shared/specialist-agents.md](../../_shared/specialist-agents.md)); when a specialization signal is strong and no specialist covers it, report the candidate with evidence and recommend the `setup-routing` creation flow; never write agent files here. Preserve the `<!-- CODEOPS-SPECIALISTS:START -->` / `<!-- CODEOPS-SPECIALISTS:END -->` block byte-for-byte when refreshing the managed section.
|
|
28
28
|
|
|
29
|
+
## Discovery switch
|
|
30
|
+
|
|
31
|
+
`analyze-project` accepts `--with-discovery` and `--without-discovery`; passing both flags is a usage error. Both flags validate `codeops/codeops.json` against `schemas/codeops-config.schema.json` before changing anything: an invalid file stops the run with its schema errors and writes nothing (never discard malformed quality, routing, or metrics policy). Refuse to write when `codeops/` or `codeops/codeops.json` is a symlink or not a regular file; never write through a link.
|
|
32
|
+
|
|
33
|
+
- `--with-discovery`: set `discovery.enabled: true`, keeping every other key. When no file exists, create the minimal AR-35 file with the layout resolved from `_shared/layout-convention.md` (`nested` → `codeops`, `flat` → `.`):
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"schema": 1,
|
|
37
|
+
"mode": "strict",
|
|
38
|
+
"artifacts": { "layout": "flat", "root": "." },
|
|
39
|
+
"discovery": { "enabled": true }
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
A newly created file always contains the `discovery` object. Preview the config change, then write it.
|
|
43
|
+
- `--without-discovery`: set `discovery.enabled: false`, keeping `chatMaxQuestions` and every other key. When no config exists, change no config; the managed line is simply removed.
|
|
44
|
+
- **Managed block (derived state).** On every run, including runs with neither flag, render this line immediately after `Specialist coverage:` when `discovery.enabled` is true:
|
|
45
|
+
`- Discovery: enabled — question rounds open in the local Discovery page`
|
|
46
|
+
Keep exactly one copy and replace it in place. On `--without-discovery` remove it by full-line match, including the em dash. Write with the file's dominant line ending. If the `CODEOPS-PROJECT` markers are missing, duplicated, or reversed, stop without writing and name the manual fix. The `CODEOPS-SPECIALISTS` block stays byte-for-byte.
|
|
47
|
+
|
|
29
48
|
Keep `AGENTS.md` small. Operational routing belongs in `codeops/codeops.json` or `opencode.json`, not prose.
|
|
30
49
|
|
|
31
50
|
---
|
|
@@ -34,6 +34,10 @@ than one or an invalid level is an argument error; announce
|
|
|
34
34
|
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
35
35
|
run clears the level before its final summary.
|
|
36
36
|
|
|
37
|
+
## Discovery option
|
|
38
|
+
|
|
39
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--no-discovery` token before the first `--` sentinel, remove it before resolving targets, paths, or modes; more than one occurrence is invalid; when present, every question round stays in chat for this run. Read [../../_shared/discovery.md](../../_shared/discovery.md) when Discovery applies.
|
|
40
|
+
|
|
37
41
|
## Plan readiness proof
|
|
38
42
|
|
|
39
43
|
A plan is not ready merely because its documents exist. Before presenting it as executable,
|
|
@@ -165,6 +169,8 @@ When a `requirements/` directory exists with `RD-XX-*.md` files (produced by the
|
|
|
165
169
|
|
|
166
170
|
Cover at minimum: **Feature scope** (what it does / does NOT do, boundaries), **Technical context** (affected code, existing patterns, constraints), **Dependencies** (prerequisites, external deps), and **Success criteria** (definition of done, required tests, required docs).
|
|
167
171
|
|
|
172
|
+
When Discovery applies, publish the interview per [../../_shared/discovery.md](../../_shared/discovery.md): groups by category with Scope first, and every question with `why`, `evidence`, and a recommendation. A round within `chatMaxQuestions` stays in chat.
|
|
173
|
+
|
|
168
174
|
### 1.2 Analyze current implementation
|
|
169
175
|
|
|
170
176
|
Read relevant source files; identify affected components; find similar/reference patterns; note technical debt; review project docs and AGENTS.md. If `docs/index.md` exists with `techdocs: true` frontmatter, read the relevant architecture sections first (see the techdocs skill).
|
|
@@ -194,7 +200,7 @@ Register is created. No exceptions, no overrides, no "good enough."** Plans buil
|
|
|
194
200
|
produce code the user did not ask for. Every item in every plan document must trace to an explicit,
|
|
195
201
|
user-confirmed decision.
|
|
196
202
|
|
|
197
|
-
The mechanism is the **Ambiguity Register** (`00-ambiguity-register.md`): a numbered inventory of every gap, ambiguity, unstated assumption, and open question, hunted systematically across all 12 categories. The full category checklist, register template, gate-enforcement rules, named-deferral policy, traceability requirement, surface-during-authoring rule, and interactions with the grill-me / upgrade-plan skills are in **[zero-ambiguity-gate.md](zero-ambiguity-gate.md)** — **read it now, before Phase 2.**
|
|
203
|
+
The mechanism is the **Ambiguity Register** (`00-ambiguity-register.md`): a numbered inventory of every gap, ambiguity, unstated assumption, and open question, hunted systematically across all 12 categories. When Discovery applies, publish the register review per [../../_shared/discovery.md](../../_shared/discovery.md): one group per register category with Scope first; the register remains the authoritative record. Questions that surface while writing documents stay in chat when the round stays within `chatMaxQuestions`; otherwise add them to a new group on the same plan and republish the full question set. The full category checklist, register template, gate-enforcement rules, named-deferral policy, traceability requirement, surface-during-authoring rule, and interactions with the grill-me / upgrade-plan skills are in **[zero-ambiguity-gate.md](zero-ambiguity-gate.md)** — **read it now, before Phase 2.**
|
|
198
204
|
|
|
199
205
|
That shared protocol also owns the always-active **Complexity Escalation Gate**. A new layer,
|
|
200
206
|
dependency, harness, framework, infrastructure surface, cross-cutting refactor, or other material
|
|
@@ -43,6 +43,10 @@ as a suggestion only, more than one or an invalid level is an argument error; an
|
|
|
43
43
|
[../../_shared/reasoning-effort.md](../../_shared/reasoning-effort.md) §Auto-effort option. The
|
|
44
44
|
run clears the level before its final summary.
|
|
45
45
|
|
|
46
|
+
## Discovery option
|
|
47
|
+
|
|
48
|
+
If `$ARGUMENTS` contains exactly one exact standalone `--no-discovery` token before the first `--` sentinel, remove it before resolving targets, paths, or modes; more than one occurrence is invalid; when present, every finding stays in chat for this run. Read [../../_shared/discovery.md](../../_shared/discovery.md) when Discovery applies.
|
|
49
|
+
|
|
46
50
|
Run a rigorous quality audit of the artifact named in `$ARGUMENTS`, **grounded in the actual
|
|
47
51
|
codebase**. Find every issue, ambiguity, contradiction, gap, and risk; verify every claim and
|
|
48
52
|
assumption against the real code; present each finding with options + a recommendation; iterate
|
|
@@ -121,6 +121,10 @@ Use the severity icons from SKILL.md (red/orange/yellow/blue circles) in the act
|
|
|
121
121
|
proposal and collect a distinct `Keep`, `Defer`, or `Discard` ruling. In strict scope, optional
|
|
122
122
|
remediations are not reported.
|
|
123
123
|
|
|
124
|
+
### Discovery path
|
|
125
|
+
|
|
126
|
+
When Discovery applies, after the challenger has run, publish all findings as a `preflight` plan grouped **CRITICAL**, **MAJOR**, **MINOR**, and **OBSERVATION**. Each finding is `PF-NNN` of type `single-other`: its options plus "Other" for a custom decision or a dismissal with reasoning. A Defer is recorded as **Accepted Risk** with decision, owner, and revisit. Answers come back as `**User Decision:**` lines. The batch pacing rules below apply only to chat presentation; the previous-decisions rule is unchanged.
|
|
127
|
+
|
|
124
128
|
### Batch presentation rules
|
|
125
129
|
|
|
126
130
|
- **<= 5 findings** — present all at once, let the user respond to each.
|
|
Binary file
|
|
Binary file
|