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.
@@ -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.2.0",
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.tsx"
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
- "typecheck": "tsc --noEmit",
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
- // Plugin cleanup: stop listening for events. The runtime's scratch
442
- // directory is deliberately left in place — other sessions may still be
443
- // using it — and the startup sweep reclaims abandoned directories.
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,
@@ -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.