opencode-codeops 2.2.0 → 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 +45 -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 +16 -3
- package/plugin/index.ts +45 -4
- package/plugin/tui.js +113 -0
- 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/scripts/build-tui.mjs +73 -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": {
|
|
@@ -11,14 +11,17 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./plugin/index.ts",
|
|
13
13
|
"./server": "./plugin/index.ts",
|
|
14
|
-
"./tui": "./plugin/tui.
|
|
14
|
+
"./tui": "./plugin/tui.js"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"opencode-codeops": "bin/index.mjs"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test",
|
|
21
|
-
"
|
|
21
|
+
"build:tui": "node scripts/build-tui.mjs",
|
|
22
|
+
"build:discovery": "node scripts/build-discovery-ui.mjs",
|
|
23
|
+
"prepack": "npm run build:discovery",
|
|
24
|
+
"typecheck": "tsc --noEmit && tsc -p discovery-ui --noEmit",
|
|
22
25
|
"check:version": "node scripts/check-version.mjs",
|
|
23
26
|
"verify": "npm run typecheck && npm run test && npm run check:version",
|
|
24
27
|
"prepublishOnly": "npm run verify"
|
|
@@ -39,6 +42,8 @@
|
|
|
39
42
|
"!bin/*.test.mjs",
|
|
40
43
|
"!bin/*.spec.test.mjs",
|
|
41
44
|
"references/",
|
|
45
|
+
"discovery-ui/dist/",
|
|
46
|
+
"!discovery-ui/src/",
|
|
42
47
|
"schemas/",
|
|
43
48
|
"install.sh",
|
|
44
49
|
"README.md",
|
|
@@ -71,9 +76,17 @@
|
|
|
71
76
|
"@opencode/plugin": "^2.0.24"
|
|
72
77
|
},
|
|
73
78
|
"devDependencies": {
|
|
79
|
+
"@fluentui/react-components": "^9.74.11",
|
|
80
|
+
"@fluentui/react-icons": "^2.0.344",
|
|
81
|
+
"@griffel/react": "^1.7.8",
|
|
74
82
|
"@opentui/core": "0.5.17",
|
|
75
83
|
"@opentui/solid": "0.5.17",
|
|
76
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",
|
|
77
90
|
"solid-js": "1.9.12",
|
|
78
91
|
"typescript": "^5.0.0"
|
|
79
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
|
},
|
package/plugin/tui.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Generated from plugin/tui.tsx — do not edit; run `npm run build:tui` to regenerate.
|
|
2
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
3
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
4
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
5
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
6
|
+
/**
|
|
7
|
+
* The CodeOps TUI entry: the live run-progress view in the session sidebar.
|
|
8
|
+
*
|
|
9
|
+
* The view is deliberately honest: it shows only reported values from the
|
|
10
|
+
* server's run state, always with an as-of time, and it renders nothing when
|
|
11
|
+
* there is no run, when the host lacks the events API, or when every call
|
|
12
|
+
* fails. There are no timers — the text recomputes whenever the component
|
|
13
|
+
* re-renders — and no file access; all display and merge logic lives in the
|
|
14
|
+
* shared helper module so it can be tested under plain Node.
|
|
15
|
+
*
|
|
16
|
+
* @module tui
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Plugin, usePlugin } from "@opencode/plugin/tui";
|
|
20
|
+
import { createEffect, createResource, createSignal, onCleanup, Show } from "solid-js";
|
|
21
|
+
import { acceptRunCleared, acceptRunUpdate, describeRun, mergeRunState } from "../bin/lib/codeops-progress.mjs";
|
|
22
|
+
import { CodeOpsRpc, requestProgress } from "../bin/lib/codeops-rpc.mjs";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The live run-progress view.
|
|
26
|
+
*
|
|
27
|
+
* Resolves the session's location once, subscribes to the `updated` and
|
|
28
|
+
* `cleared` events before requesting an initial snapshot, and merges both
|
|
29
|
+
* delivery paths monotonically. A clear records its server-stamped
|
|
30
|
+
* `clearedAt`; both merge paths ignore deliveries at or before it, so a late
|
|
31
|
+
* snapshot or update can never resurrect a cleared run. Every failure path is
|
|
32
|
+
* silent: a host without the events API still renders from the snapshot, and
|
|
33
|
+
* an invalid or missing snapshot renders nothing.
|
|
34
|
+
*
|
|
35
|
+
* @returns The three display lines, or nothing when no run is known.
|
|
36
|
+
*/
|
|
37
|
+
function CodeOpsProgress() {
|
|
38
|
+
const context = usePlugin();
|
|
39
|
+
const location = context.location ?? context.data.location.default();
|
|
40
|
+
const directory = location?.directory ?? "";
|
|
41
|
+
const [run, setRun] = createSignal(null);
|
|
42
|
+
const [clearedAt, setClearedAt] = createSignal(0);
|
|
43
|
+
|
|
44
|
+
// Subscribe BEFORE the snapshot: events are live-only, so a snapshot
|
|
45
|
+
// fetched first could miss an update arriving in between. Guarded: a host
|
|
46
|
+
// without the events API still renders from the snapshot below.
|
|
47
|
+
const stops = [];
|
|
48
|
+
try {
|
|
49
|
+
const events = context.client.rpc(CodeOpsRpc).events;
|
|
50
|
+
const stopUpdated = events.on("updated", event => {
|
|
51
|
+
const snapshot = acceptRunUpdate(event, directory);
|
|
52
|
+
if (snapshot && snapshot.updatedAt > clearedAt()) {
|
|
53
|
+
setRun(current => mergeRunState(current, snapshot));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
stops.push(stopUpdated);
|
|
57
|
+
const stopCleared = events.on("cleared", event => {
|
|
58
|
+
const cleared = acceptRunCleared(event, directory);
|
|
59
|
+
if (!cleared) return;
|
|
60
|
+
// Record the clear time for every accepted clear, before the identity
|
|
61
|
+
// gate: a clear that arrives before the initial snapshot must still
|
|
62
|
+
// suppress that snapshot, or a cleared run could reappear.
|
|
63
|
+
setClearedAt(cleared.clearedAt);
|
|
64
|
+
const current = run();
|
|
65
|
+
if (current && cleared.plan === current.plan && cleared.sessionID === current.sessionID) {
|
|
66
|
+
setRun(null);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
stops.push(stopCleared);
|
|
70
|
+
} catch {
|
|
71
|
+
// Events unavailable: the snapshot-only view below still renders.
|
|
72
|
+
}
|
|
73
|
+
onCleanup(() => stops.forEach(stop => stop()));
|
|
74
|
+
|
|
75
|
+
// Initial snapshot; null or a failure means "nothing known". The clear
|
|
76
|
+
// guard applies here too: a snapshot at or before the last clear is ignored.
|
|
77
|
+
const [initial] = createResource(async () => requestProgress(context.client, {
|
|
78
|
+
location
|
|
79
|
+
}));
|
|
80
|
+
createEffect(() => {
|
|
81
|
+
const snapshot = initial();
|
|
82
|
+
if (snapshot && snapshot.updatedAt > clearedAt()) {
|
|
83
|
+
setRun(current => mergeRunState(current, snapshot));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return _$createComponent(Show, {
|
|
87
|
+
get when() {
|
|
88
|
+
return run();
|
|
89
|
+
},
|
|
90
|
+
children: snapshot => {
|
|
91
|
+
const view = () => describeRun(snapshot(), Date.now());
|
|
92
|
+
return _$memo(() => view().lines.map(line => (() => {
|
|
93
|
+
var _el$ = _$createElement("text");
|
|
94
|
+
_$insert(_el$, line);
|
|
95
|
+
return _el$;
|
|
96
|
+
})()));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The opencode-codeops TUI plugin: claims the sidebar content slot with the
|
|
103
|
+
* live run view. The host disposes the claim when the plugin unloads.
|
|
104
|
+
*/
|
|
105
|
+
export default Plugin.define({
|
|
106
|
+
id: "opencode-codeops-tui",
|
|
107
|
+
setup(context) {
|
|
108
|
+
return context.ui.slot({
|
|
109
|
+
append: "sidebar.content",
|
|
110
|
+
render: () => _$createComponent(CodeOpsProgress, {})
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the packaged TUI entry.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode transpiles plugin TSX with its OpenTUI renderer runtime only for
|
|
5
|
+
* files outside `node_modules`. A published package always installs into
|
|
6
|
+
* `node_modules`, so a source `.tsx` entry falls back to the default React
|
|
7
|
+
* JSX transform and fails to load ("Cannot find package 'react'"). Compiling
|
|
8
|
+
* `plugin/tui.tsx` with `@opentui/solid`'s own transform — the same pipeline
|
|
9
|
+
* the host applies to project-local plugins — produces a plain-JavaScript
|
|
10
|
+
* entry whose runtime imports (`@opentui/solid`, `solid-js`) the host
|
|
11
|
+
* rewrites to its bundled renderer.
|
|
12
|
+
*
|
|
13
|
+
* The generated artifact `plugin/tui.js` is committed and shipped as the
|
|
14
|
+
* `./tui` export; `scripts/tui-entry-build.spec.test.mjs` fails when it is
|
|
15
|
+
* stale, so the release verification blocks a stale entry.
|
|
16
|
+
*
|
|
17
|
+
* @module build-tui
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync, writeFileSync } from "node:fs"
|
|
21
|
+
import { createRequire } from "node:module"
|
|
22
|
+
import { dirname, join } from "node:path"
|
|
23
|
+
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
24
|
+
|
|
25
|
+
/** Repository root: this file lives in `<root>/scripts/`. */
|
|
26
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
27
|
+
|
|
28
|
+
/** Source of the TUI entry, authored as TSX. */
|
|
29
|
+
const SOURCE = join(ROOT, "plugin", "tui.tsx")
|
|
30
|
+
|
|
31
|
+
/** Generated JavaScript entry shipped as `./tui`. */
|
|
32
|
+
const TARGET = join(ROOT, "plugin", "tui.js")
|
|
33
|
+
|
|
34
|
+
/** Header marking the artifact as generated. */
|
|
35
|
+
const HEADER =
|
|
36
|
+
"// Generated from plugin/tui.tsx — do not edit; run `npm run build:tui` to regenerate.\n"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load `@opentui/solid`'s Solid JSX transform.
|
|
40
|
+
*
|
|
41
|
+
* The transform module is not part of the package's public exports map, so it
|
|
42
|
+
* is resolved through the package root: resolve the package entry point, then
|
|
43
|
+
* import the transform beside it.
|
|
44
|
+
*
|
|
45
|
+
* @returns The `transformSolidSource(code, options)` function.
|
|
46
|
+
*/
|
|
47
|
+
async function loadTransform() {
|
|
48
|
+
const require = createRequire(join(ROOT, "scripts", "build-tui.mjs"))
|
|
49
|
+
const packageEntry = require.resolve("@opentui/solid")
|
|
50
|
+
const modulePath = join(dirname(packageEntry), "scripts", "solid-transform.js")
|
|
51
|
+
const module = await import(pathToFileURL(modulePath).href)
|
|
52
|
+
return module.transformSolidSource
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Compile the current `plugin/tui.tsx` into the shipped entry source.
|
|
57
|
+
*
|
|
58
|
+
* @returns The complete `plugin/tui.js` content, header included.
|
|
59
|
+
*/
|
|
60
|
+
export async function buildTuiSource() {
|
|
61
|
+
const transformSolidSource = await loadTransform()
|
|
62
|
+
const source = readFileSync(SOURCE, "utf8")
|
|
63
|
+
const transformed = await transformSolidSource(source, { filename: SOURCE })
|
|
64
|
+
const code = typeof transformed === "string" ? transformed : transformed.code
|
|
65
|
+
return HEADER + code
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// When run directly (`npm run build:tui`), regenerate the committed artifact.
|
|
69
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
70
|
+
const code = await buildTuiSource()
|
|
71
|
+
writeFileSync(TARGET, code)
|
|
72
|
+
console.log(`built plugin/tui.js (${code.length} chars)`)
|
|
73
|
+
}
|
|
@@ -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
|