From 4c486e996c604f7419db3cd205d2891174f74872 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 27 Aug 2026 17:07:37 +0000 Subject: [PATCH 1/2] Make no-MCP Workbench agents use discover then confirm. CLI isolate no longer blocks on System.in when stdin is not a TTY, so Maven one-shot isolate cannot hang or start-then-stop a worker. Pointers and AGENT-GUIDE now show the Maven exec wrapper and reserve live isolate for an already-running Workbench. Co-authored-by: ds-code-t --- docs/agent/README.md | 2 +- docs/agent/feature-map.md | 4 +- docs/ai-run-configuration.md | 6 +- docs/consumer-agent-guide.md | 52 +++++++----- docs/consumer-project.md | 15 ++-- docs/pickleball-workbench.md | 13 +-- .../.github/copilot-instructions.md | 4 +- maven-consumer-project/AGENTS.md | 4 +- .../diagnostic/PickleballGuidanceChecks.java | 23 +++-- .../ParallelCountEstimatorChecks.java | 29 ++++++- pickleball-workbench/AGENTS.md | 2 +- .../workbench/WorkbenchApplication.java | 18 +++- .../workbench/WorkbenchApplicationTest.java | 41 +++++++++ scripts/verify_agent_contract.py | 85 ++++++++++++++++++- .../reporting/diagnostic/DiagnosticCli.java | 6 +- .../launcher/WorkbenchAgentCommands.java | 6 +- .../ParallelCountEstimator.java | 13 ++- .../pickleball/guidance/AGENT-GUIDE.md | 52 +++++++----- .../pickleball/guidance/docs/agent/README.md | 2 +- .../guidance/docs/agent/feature-map.md | 4 +- .../guidance/docs/ai-run-configuration.md | 6 +- .../guidance/docs/consumer-agent-guide.md | 52 +++++++----- .../guidance/docs/consumer-project.md | 15 ++-- .../guidance/docs/pickleball-workbench.md | 13 +-- .../launcher/WorkbenchAgentCommandsTest.java | 37 ++++++++ .../ParallelCountEstimatorTest.java | 20 ++++- 26 files changed, 394 insertions(+), 130 deletions(-) diff --git a/docs/agent/README.md b/docs/agent/README.md index 5ef38667..4ad5f259 100644 --- a/docs/agent/README.md +++ b/docs/agent/README.md @@ -18,7 +18,7 @@ This directory supports repository-native AI coding agents. It is not a runtime Agent adapters should remain small and point back to the canonical contract rather than copying the full project description. -The nested `/maven-consumer-project/AGENTS.md` is a dependency-owned Workbench bootstrap plus a short Discover/Isolate/Confirm pointer. It materializes version-matched guidance through `PickleballWorkbenchLauncher export-guidance`, directs the consumer agent to `.pickleball/AGENT-GUIDE.md`, and tells agents to use Workbench `hint` / `discover` / `isolate` / `confirm`. Do not start the GUI. Do not register IDE MCP. Refresh/version/manifest semantics, authoring rules, configuration, diagnostics, and troubleshooting belong in the exported dependency guidance. +The nested `/maven-consumer-project/AGENTS.md` is a dependency-owned Workbench bootstrap plus a short Discover/Confirm pointer. It materializes version-matched guidance through `PickleballWorkbenchLauncher export-guidance`, directs the consumer agent to `.pickleball/AGENT-GUIDE.md`, and tells agents to use Workbench `hint` / `discover` / `confirm`. Live isolate only if `workbench_*` tools are already in the session. Do not start the GUI. Do not register IDE MCP. Refresh/version/manifest semantics, authoring rules, configuration, diagnostics, and troubleshooting belong in the exported dependency guidance. The nested `/maven-consumer-project/.github/copilot-instructions.md` is identical to `AGENTS.md` so Copilot Chat sees the same Workbench pointer. diff --git a/docs/agent/feature-map.md b/docs/agent/feature-map.md index fa669e76..83449d59 100644 --- a/docs/agent/feature-map.md +++ b/docs/agent/feature-map.md @@ -8,7 +8,7 @@ This file maps consumer-visible capabilities to implementation anchors, executab | Neutral controller/worker wire protocol | `pickleball-control-protocol/src/main/java/tools/dscode/control/protocol`; `ControlProtocol`; `ControlBridgeRequests`; `ControlBridgeResponses`; immutable `ControlBridge*` records; `InvestigationHandoff` | `verifyProtocolIsolation`; `ControlBridgeClientTest`; `InvestigationHandoffTest`; consumer `@control-bridge`; protocol/version/capability assertions | `docs/pickleball-workbench.md`; `docs/diagnostic-reporting.md`; `pickleball-workbench/AGENTS.md` | | Pickleball Workbench synchronization / persistent live worker / MCP stdio / player-style Swing+WebView UI / watched-agent control lease / scenario name-tag filter / Text-Blocks editor toggle | `pickleball-workbench`; `WorkbenchApplication`; `WorkbenchRuntimeBoundary`; `WorkbenchServices`; `WorkbenchController`; `tools.dscode.workbench.lease`; `WorkbenchAttachServer`; `tools.dscode.workbench.sync`; `WorkbenchSyncPlanner`; `WorkbenchSyncInputs`; `WorkbenchWorkerManager`; `WorkbenchLiveSession`; `tools.dscode.workbench.bridge.ControlBridgeClient`; `tools.dscode.workbench.mcp`; `workbench_diagnostic_catalog`; `workbench_diagnostic_run`; `workbench_diagnostic_summary`; `workbench_investigation_emit`; `tools.dscode.workbench.player`; `LiveEditorView`; `tools.dscode.workbench.catalog`; `ScenarioFilter`; `ConsumerFeatureCatalog`; `tools.dscode.workbench.mapping`; `tools.dscode.workbench.terminal`; `tools.dscode.workbench.diagnostics`; `tools.dscode.workbench.ui`; `FeaturePickerPanel`; OpenJFX `WebView` / `JFXPanel`; protocol `ControlProtocol.WORKER_MAIN_CLASS` | `:pickleball-workbench:test`; `WorkbenchRuntimeBoundaryTest`; `ConsumerFeatureCatalogTest`; `ScenarioFilterTest`; `GherkinBlockDocumentTest`; `LivePlaybackCoordinatorTest`; `LiveFeatureSaveTest`; `LiveEditorViewTest`; `WorkbenchControlLeaseTest`; `WorkbenchControllerLeaseTest`; `WorkbenchAttachServerTest`; `MappingValueCodecTest`; `WorkerLogBufferTest`; `DiagnosticEvidenceNavigatorTest`; `InvestigationHandoffTest`; `WorkbenchSynchronizerTest`; `WorkbenchSyncPlannerTest`; `WorkbenchMcpServerTest`; `WorkbenchUiControllerTest`; `LiveScenarioPlayerTest`; packaged UI/MCP probes | `docs/pickleball-workbench.md`; `docs/pickleball-workbench-player.md`; `docs/consumer-agent-guide.md`; `pickleball-workbench/AGENTS.md` | | Consumer-side Control Bridge and live investigation | worker-side `pickleball-control-api/src/main/java/tools/dscode/control/bridge`; core `ControlRuntime`; protocol `tools.dscode.control.protocol`; controller client `tools.dscode.workbench.bridge.ControlBridgeClient`; endpoints `/v1/status`, `/v1/scenarios`, `/v1/events`, `/v1/pause`, `/v1/resume`, `/v1/steps/execute`, `/v1/mappings/*`, `/v1/browser/*`, `/v1/services/call`, `/v1/breakpoints*`, `/v1/step-overrides*` | controller-only fake-server `ControlBridgeClientTest`; `control-bridge.feature` tagged `@control-bridge`; `ControlBridgeTestSteps.java`; Workbench `worker-check` / `live-check` | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md`; `pickleball-workbench/AGENTS.md` | -| Consumer Workbench launcher/extractor | `tools.dscode.launcher.PickleballWorkbenchLauncher`; `WorkbenchAgentCommands`; `ControlProtocol.EMBEDDED_WORKBENCH_RESOURCE`; root `shadowJar`; `verifyEmbeddedWorkbench`; content-addressed `.pickleball/workbench/controller//` extraction; agent verbs `export-guidance`, `hint`, `discover`, `isolate`, `confirm` | `PickleballWorkbenchLauncherTest`; `WorkbenchAgentCommandsTest`; `verifyEmbeddedWorkbench`; `verifyWorkbenchEntrypoint` | `docs/getting-started.md`; `docs/consumer-project.md`; `docs/pickleball-workbench.md`; `docs/consumer-agent-guide.md` | +| Consumer Workbench launcher/extractor | `tools.dscode.launcher.PickleballWorkbenchLauncher`; `WorkbenchAgentCommands`; `ControlProtocol.EMBEDDED_WORKBENCH_RESOURCE`; root `shadowJar`; `verifyEmbeddedWorkbench`; content-addressed `.pickleball/workbench/controller//` extraction; agent verbs `export-guidance`, `hint`, `discover`, `confirm` (live `isolate` only with already-running Workbench / `workbench_*`) | `PickleballWorkbenchLauncherTest`; `WorkbenchAgentCommandsTest`; `verifyEmbeddedWorkbench`; `verifyWorkbenchEntrypoint` | `docs/getting-started.md`; `docs/consumer-project.md`; `docs/pickleball-workbench.md`; `docs/consumer-agent-guide.md` | | Step Override runtime and Workbench authoring | `src/main/java/tools/dscode/control/override`; `src/main/java/io/cucumber/core/runner/StepOverrideDispatcher.java`; `NPickleStepTestStepFactory`; `src/main/aspectj/tools/dscode/control/override/StepOverrideLifecycleAspect.aj`; bridge `/v1/step-overrides*`; `ControlBridgeClient`; `WorkbenchLiveSession`; MCP `workbench_step_override_*` | `StepOverrideCompilerTest`; `StepOverrideChecks`; `StepOverrideBridgeTestSteps`; `@step-override`; Workbench `live-check`; `WorkbenchMcpServerTest`; `WorkbenchUiControllerTest` | `docs/step-overrides.md`; `docs/pickleball-workbench.md`; `pickleball-workbench/AGENTS.md` | | Dynamic control API and semantic hooks | `pickleball-control-api/src/main/java/tools/dscode/control/api`; `src/main/java/tools/dscode/common/control`; `src/main/aspectj/tools/dscode/common/control/ControlRuntimeAspect.aj`; search `DynamicControl`, `MappingControl`, `ElementControl`, `ServiceCallControl`, `ControlHook` | `DynamicControlApiChecks.java`; `ControlRuntimeObserverChecks.java`; `internal-framework-java-checks.feature`; `control-bridge.feature` | `docs/dynamic-control-api.md` | | Pickleball-native element inspection | `ElementControl.java`; `ElementInspection.java`; `ElementEvidence.java`; `ExecutionDictionary.java`; `DefinitionContext.java`; `BrowserSteps.getCurrentDriverIfPresent`; bridge `/v1/browser/elements`; Workbench `workbench_element_inspect` | `@control-bridge`; custom element/category consumer scenarios | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md`; `docs/custom-element-definitions.md` | @@ -48,6 +48,6 @@ The canonical worker bootstrap environment is `PKB_CONTROL_BRIDGE_SESSION_DIR`, `WorkbenchLiveSession` binds operations to the controller-owned paused scenario and verifies worker PID, bridge runtime id, and scenario id stability. A `FAILED` `executeStep` result leaves that same paused worker available; it is not a worker-ending error. Step Override source is compiled and loaded worker-side. Normal live calls do not invoke Maven/Gradle, resynchronize, or restart the worker. -The lightweight non-Spring stdio MCP adapter and player-style Swing/WebView UI both delegate through `WorkbenchServices` / `WorkbenchController`. `LiveScenarioPlayer` is a Workbench-side headless presentation model for the editable session buffer, stable line identities, selection, click-to-seek playhead, and player state only; it does not parse or execute Pickleball steps, model ParsingMap/NodeMap semantics, or claim runtime rewind. `LiveEditorView` is the Text vs Blocks presentation choice for that same buffer; toggling it must not change document text, selection, or playhead id. While `RUNNING`, `WorkbenchController.executeStep` owns playhead follow once (`LivePlaybackCoordinator.followExecutedStep`); the Swing Play loop continues without remaking that mark, and leftover marks of an already-consumed step are no-ops. The controller also owns the watched-agent control lease (`HUMAN` / `AGENT`, banner `currentAction`, gated Save permission). Consumer AI agents use Workbench `discover` / `isolate` / `confirm` as the one front door. Live `workbench_*` MCP tools are an optional alias when already connected; agents must not self-register IDE MCP or start an IDE-owned stdio server. Isolate a known failure with Workbench CLI `isolate` or already-connected `workbench_*` tools. If isolate cannot obtain a live worker, tell the human that Workbench CLI isolate failed. UI mode may write a localhost attach endpoint to `.pickleball/workbench/attach.json` so a watcher can join a human GUI session instead of starting a second Workbench; that attach file is not the consumer-agent path for this release. The left-rail picker filters project-owned scenarios by name (starts with / contains / ends with / full match; default contains; case-insensitive) and Cucumber tags (include AND, exclude NOT, Feature/Rule/outline/Examples inheritance parsed from catalog `.feature` files without calling Cucumber). Feature-file selection is an optional collapsed secondary filter; with none selected, name/tag apply to every catalog scenario. The live editor is Gherkin text or a block WebView over that same player model. Global Play starts from the first executable step; Step Editor **Step** is isolated `executeStep`; **From Here** runs from the selected step; wait-at-end stays in play so Enter can append-and-continue. Mapping property types go through `mappingPut` / `mappingRestore`. Terminal tails existing worker log files. Diagnostic explorer reads retained `reports/diagnostic-runs` artifacts in the repository evidence order. The default buffer is a Workbench-owned browser demo against `URL.home`; **Save** is confirmation-gated and copies only a picker-loaded scenario back to its originating `.feature` file. OpenJFX is Workbench-only. MCP mode reserves stdout for newline-delimited MCP JSON-RPC and redirects ordinary output to stderr. The UI remains execution-oriented and does not recreate a project IDE, generic build/process UI, source navigator, or collaboration system. +The lightweight non-Spring stdio MCP adapter and player-style Swing/WebView UI both delegate through `WorkbenchServices` / `WorkbenchController`. `LiveScenarioPlayer` is a Workbench-side headless presentation model for the editable session buffer, stable line identities, selection, click-to-seek playhead, and player state only; it does not parse or execute Pickleball steps, model ParsingMap/NodeMap semantics, or claim runtime rewind. `LiveEditorView` is the Text vs Blocks presentation choice for that same buffer; toggling it must not change document text, selection, or playhead id. While `RUNNING`, `WorkbenchController.executeStep` owns playhead follow once (`LivePlaybackCoordinator.followExecutedStep`); the Swing Play loop continues without remaking that mark, and leftover marks of an already-consumed step are no-ops. The controller also owns the watched-agent control lease (`HUMAN` / `AGENT`, banner `currentAction`, gated Save permission). Consumer AI agents use Workbench `discover` / `confirm` as the one front door. Live isolate (paused worker + `execute_step`) only if `workbench_*` tools are already connected or a human/host already has a Workbench session; agents must not Maven-exec `isolate` as a one-shot, self-register IDE MCP, or start an IDE-owned stdio server. UI mode may write a localhost attach endpoint to `.pickleball/workbench/attach.json` so a watcher can join a human GUI session instead of starting a second Workbench; that attach file is not the consumer-agent path for this release. The left-rail picker filters project-owned scenarios by name (starts with / contains / ends with / full match; default contains; case-insensitive) and Cucumber tags (include AND, exclude NOT, Feature/Rule/outline/Examples inheritance parsed from catalog `.feature` files without calling Cucumber). Feature-file selection is an optional collapsed secondary filter; with none selected, name/tag apply to every catalog scenario. The live editor is Gherkin text or a block WebView over that same player model. Global Play starts from the first executable step; Step Editor **Step** is isolated `executeStep`; **From Here** runs from the selected step; wait-at-end stays in play so Enter can append-and-continue. Mapping property types go through `mappingPut` / `mappingRestore`. Terminal tails existing worker log files. Diagnostic explorer reads retained `reports/diagnostic-runs` artifacts in the repository evidence order. The default buffer is a Workbench-owned browser demo against `URL.home`; **Save** is confirmation-gated and copies only a picker-loaded scenario back to its originating `.feature` file. OpenJFX is Workbench-only. MCP mode reserves stdout for newline-delimited MCP JSON-RPC and redirects ordinary output to stderr. The UI remains execution-oriented and does not recreate a project IDE, generic build/process UI, source navigator, or collaboration system. For this boundary, scenario validation is limited to the affected `@control-bridge` and/or `@step-override-bridge` tags with `pkb_parallel=80` where practical. Do not run `@all` for Workbench isolation changes. diff --git a/docs/ai-run-configuration.md b/docs/ai-run-configuration.md index f38c6d3f..79c453fa 100644 --- a/docs/ai-run-configuration.md +++ b/docs/ai-run-configuration.md @@ -323,7 +323,7 @@ When operating in a consumer project: ## AI agents -The agent-facing entry is Pickleball Workbench (`hint`, `discover`, `isolate`, `confirm`). Set a **complete** Discover `pkb_runvars` rather than a partial overlay. Workbench `hint` prints the browser-ladder result and estimated integer parallel count. The browser ladder keeps a remote project `pkb_browser` (`SAUCE_*` / `GRID_*` / `REMOTE_*`); otherwise it prefers `CHROME_HEADLESS`. Unused Sauce/Grid yaml files are not auto-selected. +The agent-facing entry is Pickleball Workbench (`hint`, `discover`, `confirm`). Live isolate only if `workbench_*` tools are already present. Set a **complete** Discover `pkb_runvars` rather than a partial overlay. Workbench `hint` prints the browser-ladder result and estimated integer parallel count. The browser ladder keeps a remote project `pkb_browser` (`SAUCE_*` / `GRID_*` / `REMOTE_*`); otherwise it prefers `CHROME_HEADLESS`. Unused Sauce/Grid yaml files are not auto-selected. ```text pkb_browser= @@ -333,8 +333,8 @@ pkb_loglevel=warn pkb_reportretention=failed ``` -plus the narrowest useful `pkb_tags` / `pkb_name`. Multi-scenario Discover/Confirm use that high parallelism. Isolate / live Workbench stays one paused scenario. +plus the narrowest useful `pkb_tags` / `pkb_name`. Multi-scenario Discover/Confirm use that high parallelism. Live isolate stays one paused scenario and is only for an already-running Workbench. -After Discover, inspect `pkb_run_profile` from `run-catalog.json`, `run-index.json`, or `summary.json`. Isolate and confirm replay that retained profile through `pkb_runvars`. If there is no prior Discover snapshot, Workbench says so; it does not silently re-resolve from project defaults. +After Discover, inspect `pkb_run_profile` from `run-catalog.json`, `run-index.json`, or `summary.json`. Confirm (and live isolate, if workbench_* is already in session) replay that retained profile through `pkb_runvars`. If there is no prior Discover snapshot, Workbench says so; it does not silently re-resolve from project defaults. Never supply `pkb_run_profile` as input. Workbench MCP `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` return the same retained `runProfile` when present. The consumer worker resolves the same snapshot internally through `PickleballRunner`; it does not accept `pkb_run_profile` as input. diff --git a/docs/consumer-agent-guide.md b/docs/consumer-agent-guide.md index 25a8e3f5..15f4266d 100644 --- a/docs/consumer-agent-guide.md +++ b/docs/consumer-agent-guide.md @@ -8,28 +8,33 @@ A consumer project may contain only a short `AGENTS.md` bridge. That bridge uses Pickleball Workbench is the one front door. It is a Java/Maven program, not an IDE feature and not a GUI requirement. Do not start the GUI. Do not register IDE MCP. -If `workbench_*` tools are already present in this session, use them as an alias for the same Isolate loop. Otherwise keep using Workbench CLI verbs. Missing `workbench_*` tools is not a reason to skip Discover. +No-MCP agent path: Workbench `discover` then `confirm` (narrow `--tags` / `--name`). Live isolate (paused worker + `execute_step`) only if `workbench_*` tools are already present in this session, or a human/host already has a Workbench session. Missing `workbench_*` tools is not a reason to skip Discover, and is not a reason to Maven-exec `isolate`. -From the consumer project, with Pickleball on the test classpath (`classpathScope=test`): +From the consumer project, with Pickleball on the test classpath (`classpathScope=test`), use the same Maven exec wrapper as the project pointer; only change `-Dexec.args`: -1. **Discover** — `PickleballWorkbenchLauncher discover` (optional `--tags` / `--name`). Workbench applies complete AI `pkb_runvars`: browser ladder, high/auto parallel, diagnostic, warn, failed retention. It wraps consumer `mvn test`. Do not start a live worker to run the whole suite. Then read `run-catalog.json` and the retained `pkb_run_profile`. -2. **Isolate** — `PickleballWorkbenchLauncher isolate` with `--tags` / `--name` for a known scenario. Replays the last Discover `pkb_run_profile` as `pkb_runvars` (never supply `pkb_run_profile` as input). One paused scenario; do not parallelize isolate. If isolate cannot obtain a live worker, tell the human that Workbench CLI isolate failed. -3. **Confirm** — `PickleballWorkbenchLauncher confirm` with the same Discover snapshot and narrow tags/name. -4. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. +```text +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover" +``` + +Same launcher for `hint`, `discover`, and `confirm` — change `exec.args` only. -`hint` (alias `discover-hint`) prints the recommended Discover `pkb_runvars` and `NEXT: run discover`. `mcp` and `ui` are host/human commands. Agents must not use `ui`. Hosts may already wire `mcp .`; that is optional host wiring, mentioned once, not an agent setup step. +1. **Discover** — `-Dexec.args=discover` (optional `--tags` / `--name`). Workbench applies complete AI `pkb_runvars`: browser ladder, high/auto parallel, diagnostic, warn, failed retention. It wraps consumer `mvn test`. Do not start a live worker to run the whole suite. Then read `run-catalog.json` and the retained `pkb_run_profile`. +2. **Confirm** — `-Dexec.args=confirm --tags=... --name=...` with the same Discover snapshot and narrow tags/name. This is the no-MCP path after Discover. Never supply `pkb_run_profile` as input. +3. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. + +`hint` (alias `discover-hint`) is `-Dexec.args=hint` and prints the recommended Discover `pkb_runvars` and `NEXT: run discover`. `mcp` and `ui` are host/human commands. Agents must not use `ui`. Hosts may already wire `mcp .`; that is optional host wiring, mentioned once, not an agent setup step. Do not copy consumer features into `.pickleball` as a sandbox. ### Live isolation loop -This loop isolates a **known** failing scenario after Discover. Prefer Workbench CLI `isolate`. If `workbench_*` tools are already in this session, they are the same loop (`workbench_sync` / `workbench_worker_start` must use the Discover snapshot replayed as `pkb_runvars`). +This loop is **not** the no-MCP Maven path. Use it only when `workbench_*` tools are already in this session, or a human/host already has a Workbench session. Do not Maven-exec `isolate` as a one-shot; there is no CLI `execute_step`, and non-TTY isolate will not hold a worker. -1. Workbench `isolate` (CLI) or, when already connected, `workbench_sync` then `workbench_worker_start`. +1. Already-connected `workbench_sync` then `workbench_worker_start` (Discover snapshot replayed as `pkb_runvars`), or interactive TTY Workbench CLI `isolate`. 2. `workbench_request_control` when using the MCP alias. 3. Isolate with `workbench_execute_step` and/or `workbench_player_replace_document`. 4. Inspect with `workbench_browser_page`, `workbench_element_inspect`, and paged `workbench_events`. Prefer those over `workbench_browser_screenshot`. -5. Confirm with Workbench `confirm`. Read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` when those tools already exist. +5. Confirm with Workbench `confirm` (`-Dexec.args=confirm --tags=... --name=...`). Read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` when those tools already exist. 6. Emit the human handoff with `workbench_investigation_emit` or `DiagnosticCli emit-investigation`. In chat print only `.pickleball/investigations//report.html`. `workbench_execute_step` returns a structured `SUCCESS` / `FAILED` / `UNAVAILABLE` result. A FAILED Gherkin hypothesis does not end the worker, does not fail the paused scenario, and is not an MCP `isError`. Page `workbench_events` with `afterSequence` and a small `limit` (default 100, max 500). Live buffer edits do not require `workbench_sync` and do not write the original `.feature` until explicit Save (`workbench_request_save`). Worker restart without rebuild already exists (`workbench_worker_restart`). Step Overrides compile worker-side (`workbench_step_override_compile`). @@ -46,7 +51,7 @@ This loop isolates a **known** failing scenario after Discover. Prefer Workbench Keep first-read small. After a successful export: 1. Follow the consumer project's own instructions first; they remain authoritative for project-specific behavior. -2. Stay in this guide's tool chooser: Workbench `discover` when the failing scenario is unknown. Isolate a known failure with Workbench `isolate`, or with already-connected `workbench_*` tools as the same loop. Do not start the GUI. +2. Stay in this guide's tool chooser: Workbench `discover` when the failing scenario is unknown, then `confirm` with narrow tags/name. Live isolate only if `workbench_*` tools are already in this session. Do not start the GUI. 3. Inspect the **real** consumer `pom.xml`, Pickleball runner subclass, features, configuration, data, mappings, and test support before changing them. 4. Open a specific exported guide only when that topic is needed, for example `docs/dynamic-steps.md`, `docs/diagnostic-reporting.md`, `docs/configuration.md`, or `docs/ai-run-configuration.md`. 5. Do not assume the Pickleball core source repository is present. A normal consumer may only have the Maven dependency. @@ -150,13 +155,13 @@ pkb_reportretention=failed Use the narrowest `pkb_tags` / `pkb_name` that isolate the failure. Do not add the `pretty` plugin; it is console noise for agents. `pkb_reportretention=failed` keeps dense evidence for failing scenarios and does not retain it for passing ones. Workbench `hint` prints the estimated integer `pkb_parallel` and the selected browser for the current project/JVM. `pkb_parallel=auto` also resolves to that estimate at run start and stamps the integer into `pkb_run_profile`. -These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after isolation: +These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after Discover: ```text -PickleballWorkbenchLauncher confirm --tags=@the-failing-tag --name='The failing scenario' +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=confirm --tags=@the-failing-tag --name='The failing scenario'" ``` -After any diagnostic run, read `pkb_run_profile` from the pack. That is the complete resolved RunVar list, including inherited execution-context paths and the integer parallel count. Do not treat omitted `pkb_runvars` keys as equal to project `pickleball.properties`. Isolate and confirm replay that retained profile through `pkb_runvars`; they do not silently re-resolve from project defaults. +After any diagnostic run, read `pkb_run_profile` from the pack. That is the complete resolved RunVar list, including inherited execution-context paths and the integer parallel count. Do not treat omitted `pkb_runvars` keys as equal to project `pickleball.properties`. Confirm (and live isolate, if workbench_* is already in session) replay that retained profile through `pkb_runvars`; they do not silently re-resolve from project defaults. A selected profile or partial `pkb_runvars` input inherits only missing project execution-context RunVars: @@ -214,16 +219,17 @@ After isolation and the diagnostic rerun, emit a small human handoff. JSON is th ## Diagnostic utility commands -The agent-facing name is Workbench. From a Maven consumer where Pickleball is on the test classpath: +The agent-facing name is Workbench. From a Maven consumer where Pickleball is on the test classpath, use the same launcher as the project pointer and only change `-Dexec.args`: ```text -PickleballWorkbenchLauncher export-guidance .pickleball -PickleballWorkbenchLauncher hint -PickleballWorkbenchLauncher discover [--tags ] [--name ] -PickleballWorkbenchLauncher isolate [--tags ] [--name ] -PickleballWorkbenchLauncher confirm [--tags ] [--name ] +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=export-guidance .pickleball" +"-Dexec.args=hint" +"-Dexec.args=discover [--tags ] [--name ]" +"-Dexec.args=confirm [--tags ] [--name ]" ``` +Do not Maven-exec `isolate` unless stdin is an interactive TTY or `workbench_*` tools are already in this session. + `DiagnosticCli` remains the implementation behind export-guidance/hint and the comparison/rebuild utilities: ```text @@ -238,7 +244,7 @@ DiagnosticCli rebuild `DiagnosticCli help`, `--help`, and `-h` print that DiagnosticCli list and state that Workbench is the agent entry. -Use Workbench `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, `hint` for the complete Discover `pkb_runvars` (browser ladder, estimated `pkb_parallel`, diagnostic evidence controls), and `discover` / `isolate` / `confirm` for the turnkey loop. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. +Use Workbench `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, `hint` for the complete Discover `pkb_runvars` (browser ladder, estimated `pkb_parallel`, diagnostic evidence controls), and `discover` / `confirm` for the no-MCP turnkey loop. Live isolate only if `workbench_*` tools are already in this session. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. ## Controlled diagnostic reruns @@ -306,12 +312,12 @@ For a normal external consumer, do not assume those core files exist. This section is for Pickleball maintainers. It is not the product suite and is not first-read for consumer agents. -Pickleball's example Maven consumer includes an opt-in mixed pass/fail suite tagged only `@agent-pointer-eval` (`maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`). It is not part of `@all`, `@regression`, or the other Maven suite-profile tags. The failures are intentional canned fixtures for scoring whether a consumer AI agent follows the short `AGENTS.md` pointer into this guide and then uses Workbench discover-then-isolate. Do not treat those failures as product bugs, and do not "fix" the feature unless a human asked to change the harness. During an eval, still follow Discover / Isolate / Confirm; do not ignore canned fails. +Pickleball's example Maven consumer includes an opt-in mixed pass/fail suite tagged only `@agent-pointer-eval` (`maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`). It is not part of `@all`, `@regression`, or the other Maven suite-profile tags. The failures are intentional canned fixtures for scoring whether a consumer AI agent follows the short `AGENTS.md` pointer into this guide and then uses Workbench discover-then-confirm. Do not treat those failures as product bugs, and do not "fix" the feature unless a human asked to change the harness. During an eval, still follow Discover / Confirm; live isolate only if `workbench_*` tools are already in this session. Do not ignore canned fails. Run it explicitly: ```text -PickleballWorkbenchLauncher discover --tags=@agent-pointer-eval +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover --tags=@agent-pointer-eval" ``` or `-Dpkb_runvars="pkb_tags=@agent-pointer-eval, pkb_browser=CHROME_HEADLESS"`. Do not add this tag to consumer `AGENTS.md` or Copilot pointer files. diff --git a/docs/consumer-project.md b/docs/consumer-project.md index 48e16392..6c70a1a4 100644 --- a/docs/consumer-project.md +++ b/docs/consumer-project.md @@ -26,7 +26,7 @@ Rerun export before Pickleball work even when `.pickleball` already exists. A su Compatibility note: an older Pickleball release whose exporter predates the manifest lifecycle may leave newer files or a newer manifest behind after a downgrade. Those leftovers are not authoritative for the downgraded dependency; prefer the dependency actually resolved on the test classpath and the files freshly exported by that dependency. -AI agents should read `.pickleball/AGENT-GUIDE.md` first after a successful export. Workbench is the one front door: `discover`, `isolate`, and `confirm`. Do not start the GUI. Do not register IDE MCP. If `workbench_*` tools are already connected they are an Isolate alias. Do not treat `.pickleball/maven-consumer-project/` as the project under test, and do not dump `docs/README.md` or the whole snapshot into first-read context. Human readers can start with `.pickleball/docs/README.md`; links from those guides to `maven-consumer-project` resolve to the exported version-matched reference files. +AI agents should read `.pickleball/AGENT-GUIDE.md` first after a successful export. Workbench is the one front door: `discover` then `confirm`. Do not start the GUI. Do not register IDE MCP. Live isolate only if `workbench_*` tools are already connected. Do not treat `.pickleball/maven-consumer-project/` as the project under test, and do not dump `docs/README.md` or the whole snapshot into first-read context. Human readers can start with `.pickleball/docs/README.md`; links from those guides to `maven-consumer-project` resolve to the exported version-matched reference files. ## Version-matched reference snapshot @@ -77,20 +77,20 @@ or use the included wrappers: ## Launch the dependency-matched Workbench -The test-scoped Pickleball dependency already contains its controller-only Workbench payload. Hosts that already run Workbench MCP can launch it from the resolved test classpath: +The test-scoped Pickleball dependency already contains its controller-only Workbench payload. Agents use the same launcher as `AGENTS.md` and only change `-Dexec.args` (`hint`, `discover`, `confirm`). Hosts that already run Workbench MCP can launch it from the resolved test classpath: ```bash ./mvnw -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java \ -Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher \ -Dexec.classpathScope=test \ - "-Dexec.args=mcp ." + "-Dexec.args=discover" ``` ```powershell -.\mvnw.cmd -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=mcp ." +.\mvnw.cmd -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover" ``` -Humans who want the Swing player can pass `ui .` instead. Agents for this release should not use the GUI, `ui .`, or `attach.json` as their path. The launcher verifies and extracts the opaque payload beneath `.pickleball/workbench/controller//`, then creates a separate Workbench JVM. Workbench captures this project's compiled outputs and effective test runtime before creating a separate worker JVM. Only the worker loads the consumer-resolved Pickleball runtime; the Workbench artifact and process contain no core implementation. See `docs/pickleball-workbench.md` for commands, lifecycle, protocol compatibility, and isolation checks. The live-loop order lives in `.pickleball/AGENT-GUIDE.md`. +Optional host MCP uses `"-Dexec.args=mcp ."`. Humans who want the Swing player can pass `ui .` instead. Agents for this release should not use the GUI, `ui .`, or `attach.json` as their path. The launcher verifies and extracts the opaque payload beneath `.pickleball/workbench/controller//`, then creates a separate Workbench JVM. Workbench captures this project's compiled outputs and effective test runtime before creating a separate worker JVM. Only the worker loads the consumer-resolved Pickleball runtime; the Workbench artifact and process contain no core implementation. See `docs/pickleball-workbench.md` for commands, lifecycle, protocol compatibility, and isolation checks. The live-loop order lives in `.pickleball/AGENT-GUIDE.md`. Runner defaults include: @@ -235,8 +235,9 @@ Do not recursively ingest an entire run. ### Diagnostic CLI ```text -PickleballWorkbenchLauncher hint -PickleballWorkbenchLauncher discover +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=hint" +"-Dexec.args=discover" +"-Dexec.args=confirm --tags=@the-failing-tag" DiagnosticCli compare-runs [output-json] DiagnosticCli compare-fingerprints [output-json] DiagnosticCli emit-investigation diff --git a/docs/pickleball-workbench.md b/docs/pickleball-workbench.md index 459aba95..a9c20a55 100644 --- a/docs/pickleball-workbench.md +++ b/docs/pickleball-workbench.md @@ -45,7 +45,7 @@ META-INF/pickleball/workbench/pickleball-workbench.jar Run the small launcher from the consumer test classpath. For Maven consumers, this command requires no cache path, separate Workbench dependency, or separately selected version. -Workbench is the consumer AI-agent front door. Agents use `export-guidance`, `hint`, `discover`, `isolate`, and `confirm`. Do not start the GUI. Do not register IDE MCP. +Workbench is the consumer AI-agent front door. Agents use `export-guidance`, `hint`, `discover`, and `confirm`. Live isolate only if `workbench_*` tools are already in this session. Do not start the GUI. Do not register IDE MCP. ```bash mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java \ @@ -57,10 +57,11 @@ mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java \ ```bash "-Dexec.args=hint" "-Dexec.args=discover --tags=@smoke" -"-Dexec.args=isolate --name='The failing scenario'" -"-Dexec.args=confirm --tags=@smoke" +"-Dexec.args=confirm --tags=@smoke --name='The failing scenario'" ``` +Same launcher; only change `-Dexec.args`. Do not Maven-exec `isolate` as a one-shot. Live isolate needs an already-running Workbench (pre-attached `workbench_*` or an interactive TTY session). + `mcp` and `ui` remain host/human commands. Hosts that already run Workbench MCP can launch it from the consumer test classpath: ```bash @@ -82,7 +83,7 @@ Gradle consumers can expose the same dependency-owned launcher without resolving tasks.register('pickleballWorkbench', JavaExec) { classpath = sourceSets.test.runtimeClasspath mainClass = 'tools.dscode.launcher.PickleballWorkbenchLauncher' - args 'mcp', projectDir.absolutePath // optional host MCP; pass 'ui' for the Swing player + args 'hint' // no-MCP Discover helper; pass 'discover' / 'confirm' for the agent path. Optional host MCP: 'mcp', projectDir.absolutePath. Humans: 'ui'. } ``` @@ -325,7 +326,7 @@ A Copilot or other MCP-style client finds that file in the consumer project, the 4. Use the existing live tools (`workbench_execute_step`, Mapping, evidence, worker) while holding the lease, and `workbench_set_current_action` so the human can watch. 5. `POST {url}/tools/workbench_request_save` to ask to copy the live scenario into the original feature. The call blocks until the human clicks Allow or Deny, or Take control. -Headless `java -jar pickleball-workbench-.jar mcp ` stays stdio JSON-RPC only. That is the consumer-agent path. That client may hold the lease without a banner. Save is still a distinct explicit tool and never an implicit write. +Headless `java -jar pickleball-workbench-.jar mcp ` stays stdio JSON-RPC only. That is optional host wiring when `workbench_*` tools are already connected, not the consumer-agent path. That client may hold the lease without a banner. Save is still a distinct explicit tool and never an implicit write. A human-watched UI session is optional and separate. From `maven-consumer-project`, a person may start `ui .` and then a watcher can join `.pickleball/workbench/attach.json`. Do not launch a second `mcp` process against the same live UI session, and do not treat that attach file as the default agent path. @@ -337,7 +338,7 @@ Start the lightweight non-Spring MCP server for a consumer project. This is opti java -jar $workbenchJar mcp ".\maven-consumer-project" ``` -Or, from a Maven consumer test classpath, `"-Dexec.args=mcp ."`. That launcher is optional host wiring for environments that already run MCP. Agents use Workbench `discover` / `isolate` / `confirm`. They must not self-register IDE MCP or start an IDE-owned stdio server. Isolate a known failure with Workbench CLI `isolate` or already-connected `workbench_*` tools. Do not document or use the Swing GUI, `ui .`, or `.pickleball/workbench/attach.json` as the agent path. +Or, from a Maven consumer test classpath, `"-Dexec.args=mcp ."`. That launcher is optional host wiring for environments that already run MCP. Agents use Workbench `discover` / `confirm`. They must not self-register IDE MCP or start an IDE-owned stdio server. Live isolate only if `workbench_*` tools are already connected or stdin is an interactive TTY. Do not document or use the Swing GUI, `ui .`, or `.pickleball/workbench/attach.json` as the agent path. The server uses the official Java MCP SDK core and stdio transport with the Jackson 2 JSON adapter. MCP dependencies are Workbench-only and are shaded into the executable companion. Workbench deliberately does not use Spring Boot, Spring Framework, Spring AI, WebMVC, or Tomcat. diff --git a/maven-consumer-project/.github/copilot-instructions.md b/maven-consumer-project/.github/copilot-instructions.md index c3436469..588e4aaa 100644 --- a/maven-consumer-project/.github/copilot-instructions.md +++ b/maven-consumer-project/.github/copilot-instructions.md @@ -1,5 +1,5 @@ Before any Pickleball work, run `mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=export-guidance .pickleball"` and, after a successful export, follow `.pickleball/AGENT-GUIDE.md`. -Use Workbench `hint`, `discover`, `isolate`, and `confirm` for Discover / Isolate / Confirm. Do not start the GUI. Do not register IDE MCP. +Use Workbench `hint`, `discover`, and `confirm` (narrow `--tags` / `--name`). Same launcher; only change `-Dexec.args`. Do not start the GUI. Do not register IDE MCP. -If `workbench_*` tools are already in this session, they are an alias for the same Isolate loop. +Live isolate only if `workbench_*` tools are already in this session. Do not Maven-exec `isolate` as a one-shot. diff --git a/maven-consumer-project/AGENTS.md b/maven-consumer-project/AGENTS.md index c3436469..588e4aaa 100644 --- a/maven-consumer-project/AGENTS.md +++ b/maven-consumer-project/AGENTS.md @@ -1,5 +1,5 @@ Before any Pickleball work, run `mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=export-guidance .pickleball"` and, after a successful export, follow `.pickleball/AGENT-GUIDE.md`. -Use Workbench `hint`, `discover`, `isolate`, and `confirm` for Discover / Isolate / Confirm. Do not start the GUI. Do not register IDE MCP. +Use Workbench `hint`, `discover`, and `confirm` (narrow `--tags` / `--name`). Same launcher; only change `-Dexec.args`. Do not start the GUI. Do not register IDE MCP. -If `workbench_*` tools are already in this session, they are an alias for the same Isolate loop. +Live isolate only if `workbench_*` tools are already in this session. Do not Maven-exec `isolate` as a one-shot. diff --git a/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/PickleballGuidanceChecks.java b/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/PickleballGuidanceChecks.java index cbe2b072..ba1f4717 100644 --- a/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/PickleballGuidanceChecks.java +++ b/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/PickleballGuidanceChecks.java @@ -56,10 +56,9 @@ void dependencyPrintsCanonicalAgentGuide() { assertTrue(guide.contains("docs/pickleball-workbench.md")); assertTrue(guide.contains("DiagnosticCli help")); assertTrue(guide.contains("**Discover**")); - assertTrue(guide.contains("**Isolate**")); assertTrue(guide.contains("**Confirm**")); assertTrue(guide.contains("Do not register IDE MCP")); - assertTrue(guide.contains("tell the human")); + assertTrue(guide.contains("already in this session") || guide.contains("already present")); assertTrue(guide.contains("run-catalog.json")); assertTrue(guide.contains("PickleballWorkbenchLauncher")); assertTrue(guide.contains("does not auto-watch") || guide.contains("workbench_sync")); @@ -78,12 +77,18 @@ void dependencyPrintsCanonicalAgentGuide() { guide.indexOf("### Live isolation loop") ); assertTrue(chooserList.contains("discover")); - assertTrue(chooserList.contains("isolate")); assertTrue(chooserList.contains("confirm")); assertTrue(chooserList.contains("run-catalog.json")); assertTrue(chooserList.contains("Do not start a live worker to run the whole suite") || chooserList.contains("Do not start a live worker")); assertTrue(chooserList.contains("pkb_run_profile")); + assertTrue(chooserList.contains("-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher")); + assertTrue(chooserList.contains("-Dexec.args=discover")); + assertTrue(chooserList.contains("change") && chooserList.contains("exec.args")); + assertFalse(chooserList.contains("-Dexec.args=isolate")); + assertFalse(chooserList.contains("PickleballWorkbenchLauncher isolate")); + assertFalse(chooserList.contains("**Isolate**")); + assertTrue(chooserList.contains("already") && chooserList.contains("workbench_*")); assertFalse(chooserList.contains("Access probe")); assertFalse(chooserList.contains("CLI/Maven-primary")); assertTrue(guide.contains("Maintainer-only: pointer-eval harness")); @@ -94,8 +99,14 @@ void dependencyPrintsCanonicalAgentGuide() { ); assertTrue(liveLoop.contains("isolate")); assertTrue(liveLoop.contains("workbench_*") || liveLoop.contains("workbench_sync")); + assertTrue(liveLoop.contains("already")); assertTrue(liveLoop.contains("optional host wiring") || chooser.contains("optional host wiring")); - } + assertFalse(liveLoop.contains("Prefer Workbench CLI `isolate`")); + String maintainer = guide.substring(guide.indexOf("## Maintainer-only: pointer-eval harness")); + assertTrue(maintainer.contains("@agent-pointer-eval")); + assertTrue(guide.indexOf("## Maintainer-only: pointer-eval harness") + > guide.indexOf("## When the core Pickleball repository is also present")); + } @Test void diagnosticCliHelpListsCommandsAndUnknownCommandsFailClearly() { @@ -277,11 +288,13 @@ void dependencyExportsVersionMatchedGuidanceAndManifest() throws Exception { assertTrue(guide.contains("workbench_investigation_emit")); assertTrue(guide.contains("pkb_reportretention=failed")); assertTrue(guide.contains("Do not copy, modify, or execute files")); - assertTrue(guide.contains("tell the human")); + assertTrue(guide.contains("already in this session") || guide.contains("already present")); assertTrue(guide.contains("one front door")); assertTrue(guide.contains("PickleballWorkbenchLauncher")); assertTrue(guide.contains("Maintainer-only: pointer-eval harness")); assertTrue(guide.contains("@agent-pointer-eval")); + assertTrue(guide.contains("-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher")); + assertFalse(guide.contains("PickleballWorkbenchLauncher isolate")); String consumerProject = Files.readString(root.resolve("docs/consumer-project.md")); assertTrue(consumerProject.contains("keep console verbosity low")); diff --git a/maven-consumer-project/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorChecks.java b/maven-consumer-project/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorChecks.java index 1b9d4196..536812b5 100644 --- a/maven-consumer-project/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorChecks.java +++ b/maven-consumer-project/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorChecks.java @@ -2,6 +2,9 @@ import org.junit.jupiter.api.Test; +import java.nio.file.Files; +import java.nio.file.Path; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -48,10 +51,34 @@ void invalidParallelValuesFailClearly() { @Test void recommendedDiscoverRunVarsIncludeEstimatedParallel() { String runVars = ParallelCountEstimator.recommendedDiscoverRunVars(); - assertTrue(runVars.contains("pkb_browser=CHROME_HEADLESS")); + assertTrue(runVars.contains("pkb_browser=")); assertTrue(runVars.contains("pkb_parallel=" + ParallelCountEstimator.estimate())); assertTrue(runVars.contains("pkb_reportingmode=diagnostic")); assertFalse(runVars.contains("pkb_parallel=80")); assertFalse(runVars.contains("pkb_parallel=auto")); } + + @Test + void recommendedDiscoverRunVarsUseBrowserLadder() throws Exception { + Path project = Files.createTempDirectory("pkb-discover-runvars-"); + try { + Path resources = project.resolve("src/test/resources"); + Files.createDirectories(resources); + Files.writeString(resources.resolve("pickleball.properties"), "pkb_browser=GRID_EDGE\n"); + String runVars = ParallelCountEstimator.recommendedDiscoverRunVars(project); + assertTrue(runVars.contains("pkb_browser=GRID_EDGE")); + assertFalse(runVars.contains("pkb_browser=CHROME_HEADLESS")); + } finally { + deleteTree(project); + } + } + + private static void deleteTree(Path root) throws Exception { + if (root == null || !Files.exists(root)) return; + try (var paths = Files.walk(root)) { + for (Path path : paths.sorted((a, b) -> b.compareTo(a)).toList()) { + Files.deleteIfExists(path); + } + } + } } diff --git a/pickleball-workbench/AGENTS.md b/pickleball-workbench/AGENTS.md index 376d0402..56c7cfe9 100644 --- a/pickleball-workbench/AGENTS.md +++ b/pickleball-workbench/AGENTS.md @@ -103,7 +103,7 @@ Workbench provides: java -jar pickleball-workbench-.jar mcp ``` -The MCP adapter is `tools.dscode.workbench.mcp.WorkbenchMcpServer` plus `WorkbenchMcpTools`. It exposes project synchronization/status, interactive worker lifecycle, live Gherkin, Mapping operations, events/evidence, browser/service controls, semantic breakpoints, Step Override authoring, the watched-agent control lease, player-state inspection, gated Save, sparse diagnostic catalog/run/summary readers, and `workbench_investigation_emit` through `WorkbenchServices`. Consumer agents use this headless stdio server (`mcp .`), not the Swing GUI. +The MCP adapter is `tools.dscode.workbench.mcp.WorkbenchMcpServer` plus `WorkbenchMcpTools`. It exposes project synchronization/status, interactive worker lifecycle, live Gherkin, Mapping operations, events/evidence, browser/service controls, semantic breakpoints, Step Override authoring, the watched-agent control lease, player-state inspection, gated Save, sparse diagnostic catalog/run/summary readers, and `workbench_investigation_emit` through `WorkbenchServices`. Consumer agents use Workbench `discover` / `confirm` as the Java/Maven front door, not this stdio server. Hosts may already wire `mcp .` as optional alias when `workbench_*` tools are already in the session. Agents must not self-register IDE MCP or start the GUI. UI mode cannot share process stdout with stdio MCP. `ui` therefore starts a 127.0.0.1-only JSON attach facade (`WorkbenchAttachServer`) over the same tools and writes `.pickleball/workbench/attach.json` so a Copilot/MCP client can join the visible session. Bind localhost only. Do not launch a second `mcp` process against a running UI. diff --git a/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java b/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java index 708aeafd..fad69af6 100644 --- a/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java +++ b/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java @@ -196,6 +196,15 @@ private static int isolate(String[] args, PrintStream out, PrintStream err) { return 1; } + boolean once = Boolean.getBoolean("pickleball.workbench.isolate.once"); + if (!once && !stdinIsInteractiveTty()) { + err.println("Live isolate holds a paused worker for execute_step."); + err.println("That needs an already-running Workbench (pre-attached workbench_* tools or an interactive TTY session)."); + err.println("Maven one-shot isolate is not the no-MCP agent path. Do not register IDE MCP. Do not start the GUI."); + out.println("NEXT: confirm --tags/--name for the no-MCP path."); + return 2; + } + try { new WorkbenchSynchronizer().sync(parsed.project()); try (WorkbenchLiveSession live = new WorkbenchLiveSession(parsed.project(), workerProperties)) { @@ -205,7 +214,7 @@ private static int isolate(String[] args, PrintStream out, PrintStream err) { + " scenario=" + started.scenarioId()); out.println("Replayed pkb_runvars=" + workerProperties.get("pkb_runvars")); out.println("Isolate stays one paused scenario. Do not start the GUI."); - if (Boolean.getBoolean("pickleball.workbench.isolate.once")) { + if (once) { requireCleanStop(live.stop()); return 0; } @@ -224,6 +233,10 @@ private static int isolate(String[] args, PrintStream out, PrintStream err) { } } + static boolean stdinIsInteractiveTty() { + return System.console() != null; + } + private record IsolateArgs(Path project, String tags, String name) { } @@ -512,7 +525,8 @@ private static void printUsage(PrintStream out) { out.println("sync uses the selected project wrapper and materializes .pickleball/workbench."); out.println("worker-check starts, restarts, and gracefully stops direct consumer workers without rebuilding."); out.println("live-check exercises raw Gherkin, Step Override, and live runtime operations on one persistent worker."); - out.println("isolate starts/reuses a live worker from the last Discover snapshot replayed as pkb_runvars. Do not start ui for agents."); + out.println("isolate holds a paused worker from the last Discover snapshot when stdin is an interactive TTY, or when pickleball.workbench.isolate.once is set."); + out.println("Non-TTY Maven one-shot isolate does not hold a worker; NEXT is confirm --tags/--name. Live isolate needs an already-running Workbench (pre-attached workbench_* or interactive session). Do not start ui for agents."); out.println("mcp serves the same Workbench services over protocol-only stdio; optional host wiring, not an agent setup step."); out.println("ui opens the thin Swing Workbench over the same controller services and writes a localhost agent-attach endpoint to .pickleball/workbench/attach.json."); } diff --git a/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java b/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java index ded82106..101911ec 100644 --- a/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java +++ b/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java @@ -1,17 +1,24 @@ package tools.dscode.workbench; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; +import tools.dscode.control.protocol.ControlProtocol; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class WorkbenchApplicationTest { + @TempDir + Path tempDir; @Test void helpListsSynchronizationWorkerLiveMcpAndUiCommands() { @@ -95,6 +102,40 @@ void isolateRequiresAProject() { assertTrue(output.stderr().contains("Usage: pickleball-workbench isolate ")); } + @Test + @Timeout(value = 5, unit = TimeUnit.SECONDS) + void isolateWithoutInteractiveTtyDoesNotHangAndPointsToConfirm() throws Exception { + assertFalse(WorkbenchApplication.stdinIsInteractiveTty()); + Path snapshot = tempDir.resolve(ControlProtocol.LAST_DISCOVER_SNAPSHOT_RELATIVE); + Files.createDirectories(snapshot.getParent()); + Files.writeString(snapshot, """ + { + "schemaVersion": 1, + "source": "workbench-discover", + "runId": "run-1", + "runProfile": "pkb_browser=CHROME_HEADLESS, pkb_parallel=4, pkb_reportingmode=diagnostic", + "runVars": { + "pkb_browser": "CHROME_HEADLESS", + "pkb_parallel": "4", + "pkb_reportingmode": "diagnostic" + } + } + """); + + Output output = run("isolate", tempDir.toString(), "--tags=@failing"); + + assertEquals(2, output.exitCode()); + assertTrue(output.stderr().contains("already-running Workbench")); + assertTrue(output.stderr().contains("pre-attached workbench_*") + || output.stderr().contains("workbench_*")); + assertTrue(output.stderr().contains("confirm") || output.stdout().contains("confirm")); + assertTrue(output.stdout().contains("NEXT: confirm")); + assertFalse(output.stdout().contains("execute_step")); + assertFalse(output.stderr().contains("execute_step")); + assertFalse(output.stdout().contains("Workbench isolate worker:")); + assertFalse(output.stderr().toLowerCase().contains("intellij")); + } + @Test void unknownCommandsFailClearly() { Output output = run("unknown"); diff --git a/scripts/verify_agent_contract.py b/scripts/verify_agent_contract.py index fcd5897c..5ff63f50 100644 --- a/scripts/verify_agent_contract.py +++ b/scripts/verify_agent_contract.py @@ -324,9 +324,10 @@ def validate_consumer_bridge(errors: list[str]) -> None: "Consumer guidance bridge must say not to register IDE MCP: " + relative ) for required in ( + "hint", "discover", - "isolate", "confirm", + "isolate", "workbench_", ): if required not in lowered: @@ -334,6 +335,22 @@ def validate_consumer_bridge(errors: list[str]) -> None: f"Consumer guidance bridge must mention {required}: " + relative ) + if "already" not in lowered or "workbench_" not in lowered: + errors.append( + "Consumer guidance bridge must gate isolate on workbench_* already in session: " + + relative + ) + if "hint`, `discover`, `isolate`, and `confirm`" in text: + errors.append( + "Consumer guidance bridge must not list isolate as a required first-path verb: " + + relative + ) + if "-Dexec.args=isolate" in text: + errors.append( + "Consumer guidance bridge must not Maven-exec isolate as a first-path verb: " + + relative + ) + if "do not skip workbench" in lowered: errors.append( "Consumer guidance bridge must not say 'Do not skip Workbench': " + relative @@ -418,6 +435,8 @@ def validate_dependency_owned_guidance(errors: list[str]) -> None: "Generated Maven consumer reference", "maven-consumer-project/", "@agent-pointer-eval", + "Maintainer-only: pointer-eval harness", + "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher", ): if required not in text: errors.append( @@ -425,6 +444,70 @@ def validate_dependency_owned_guidance(errors: list[str]) -> None: "docs/consumer-agent-guide.md" ) + chooser_start = text.find("## Tool chooser") + live_start = text.find("### Live isolation loop") + first_read = text.find("## First-read") + maintainer = text.find("## Maintainer-only: pointer-eval harness") + if chooser_start < 0 or live_start < 0 or first_read < 0 or maintainer < 0: + errors.append( + "docs/consumer-agent-guide.md must keep Tool chooser, Live isolation loop, " + "First-read, and Maintainer-only pointer-eval sections." + ) + return + + chooser = text[chooser_start:live_start] + live = text[live_start:first_read] + if "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" not in chooser: + errors.append( + "AGENT-GUIDE tool chooser must show the same Maven exec wrapper as the pointer " + "(-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher)." + ) + if "-Dexec.args=discover" not in chooser: + errors.append( + "AGENT-GUIDE tool chooser must show -Dexec.args=discover, not a bare launcher verb." + ) + if "PickleballWorkbenchLauncher discover" in chooser: + errors.append( + "AGENT-GUIDE tool chooser must not show bare PickleballWorkbenchLauncher discover; " + "use the Maven exec wrapper and change exec.args." + ) + if "-Dexec.args=isolate" in chooser or "PickleballWorkbenchLauncher isolate" in chooser: + errors.append( + "AGENT-GUIDE tool chooser must not tell agents to Maven-exec isolate when " + "workbench_* tools are absent." + ) + if "**Isolate**" in chooser: + errors.append( + "AGENT-GUIDE tool chooser must not list Isolate as a required no-MCP numbered step." + ) + if "already" not in chooser.lower() or "workbench_*" not in chooser: + errors.append( + "AGENT-GUIDE tool chooser must say live isolate only if workbench_* is already present." + ) + if "already" not in live.lower() or "workbench_*" not in live: + errors.append( + "AGENT-GUIDE live isolation loop must require an already-running Workbench / workbench_*." + ) + if maintainer < text.find("## When the core Pickleball repository is also present"): + errors.append( + "@agent-pointer-eval guidance must stay maintainer-only at the bottom of " + "docs/consumer-agent-guide.md." + ) + + workbench = ROOT / "docs" / "pickleball-workbench.md" + if workbench.is_file(): + workbench_text = workbench.read_text(encoding="utf-8") + if "That is the consumer-agent path" in workbench_text: + errors.append( + "docs/pickleball-workbench.md must not call mcp the consumer-agent path." + ) + groovy = workbench_text + if "args 'mcp'" in groovy and "args 'hint'" not in groovy: + errors.append( + "docs/pickleball-workbench.md Gradle pickleballWorkbench example must not " + "default to mcp." + ) + def validate_consumer_reference_build_hook(errors: list[str]) -> None: settings = ROOT / "settings.gradle" diff --git a/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticCli.java b/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticCli.java index 49d8f7cd..896b5a87 100644 --- a/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticCli.java +++ b/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticCli.java @@ -140,8 +140,8 @@ private static int discoverHint(String[] args, PrintStream out) { out.println("pkb_runvars=" + plan.runVars()); out.println(); out.println("Browser: " + plan.browser().browser() + " (" + plan.browser().reason() + ")."); - out.println("Multi-scenario Discover/Confirm use this high pkb_parallel. Isolate stays one paused scenario — do not parallelize isolate."); - out.println("The agent-facing entry is Pickleball Workbench (`hint` / `discover` / `isolate` / `confirm`), not a separate DiagnosticCli story. After Discover, isolate and confirm replay the retained pkb_run_profile through pkb_runvars. Never supply pkb_run_profile as input."); + out.println("Multi-scenario Discover/Confirm use this high pkb_parallel. Live isolate stays one paused scenario and is only for an already-running Workbench."); + out.println("The agent-facing entry is Pickleball Workbench (`hint` / `discover` / `confirm`), not a separate DiagnosticCli story. After Discover, confirm (and live isolate, if workbench_* is already in session) replay the retained pkb_run_profile through pkb_runvars. Never supply pkb_run_profile as input."); out.println(); out.println("After Discover, read reports/diagnostic-runs/run-catalog.json, then only the relevant run-index.json / summary.json."); out.println("NEXT: run discover"); @@ -601,7 +601,7 @@ private static void usage(PrintStream out) { out.println(" DiagnosticCli compare-runs [output-json]"); out.println(" DiagnosticCli compare-fingerprints [output-json]"); out.println(" DiagnosticCli rebuild "); - out.println("Agents should run export-guidance, hint, discover, isolate, and confirm through PickleballWorkbenchLauncher."); + out.println("Agents should run export-guidance, hint, discover, and confirm through PickleballWorkbenchLauncher. Live isolate only if workbench_* tools are already in the session."); } private record GitLayout(Path repositoryRoot, Path gitDirectory, Path commonGitDirectory) { diff --git a/src/main/java/tools/dscode/launcher/WorkbenchAgentCommands.java b/src/main/java/tools/dscode/launcher/WorkbenchAgentCommands.java index 174cc50d..f42c0de3 100644 --- a/src/main/java/tools/dscode/launcher/WorkbenchAgentCommands.java +++ b/src/main/java/tools/dscode/launcher/WorkbenchAgentCommands.java @@ -55,8 +55,8 @@ private static int hint(WorkbenchCommandLine.Parsed parsed, PrintStream out) { out.println("pkb_runvars=" + plan.runVars()); out.println(); out.println("Browser: " + plan.browser().browser() + " (" + plan.browser().reason() + ")."); - out.println("Multi-scenario Discover/Confirm use this high pkb_parallel. Isolate stays one paused scenario — do not parallelize isolate."); - out.println("After Discover, isolate and confirm replay the retained pkb_run_profile through pkb_runvars. Never supply pkb_run_profile as input."); + out.println("Multi-scenario Discover/Confirm use this high pkb_parallel. Live isolate stays one paused scenario and is only for an already-running Workbench."); + out.println("After Discover, confirm (and live isolate, if workbench_* is already in session) replay the retained pkb_run_profile through pkb_runvars. Never supply pkb_run_profile as input."); out.println(); out.println("NEXT: run discover"); return 0; @@ -119,7 +119,7 @@ private static int printCatalog( out.println("run-catalog.json: " + latest.catalog()); out.println("retained pkb_run_profile: " + latest.runProfile()); if (writeSnapshot) { - out.println("NEXT: isolate a known failing scenario, then confirm."); + out.println("NEXT: confirm --tags/--name for the no-MCP path. Live isolate only if workbench_* tools are already in this session."); } return mavenExit; } catch (Exception failure) { diff --git a/src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java b/src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java index 4cdf96af..3fc41ef0 100644 --- a/src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java +++ b/src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java @@ -1,5 +1,9 @@ package tools.dscode.parallelutilities; +import tools.dscode.common.reporting.diagnostic.AgentBrowserLadder; + +import java.nio.file.Path; + /** * Conservative high parallel-count estimate from JVM-visible CPU and heap. * @@ -60,11 +64,16 @@ public static int resolve(String configured) { } public static String recommendedDiscoverRunVars() { - return recommendedDiscoverRunVars("CHROME_HEADLESS"); + return recommendedDiscoverRunVars(Path.of("").toAbsolutePath().normalize()); + } + + /** Browser comes from {@link AgentBrowserLadder}, not a hardcoded headless Chrome. */ + public static String recommendedDiscoverRunVars(Path projectRoot) { + return recommendedDiscoverRunVars(AgentBrowserLadder.select(projectRoot).browser()); } public static String recommendedDiscoverRunVars(String browser) { - String selected = browser == null || browser.isBlank() ? "CHROME_HEADLESS" : browser.trim(); + String selected = browser == null || browser.isBlank() ? AgentBrowserLadder.CHROME_HEADLESS : browser.trim(); return "pkb_browser=" + selected + ", pkb_parallel=" + estimate() + ", pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed"; } diff --git a/src/main/resources/META-INF/pickleball/guidance/AGENT-GUIDE.md b/src/main/resources/META-INF/pickleball/guidance/AGENT-GUIDE.md index 25a8e3f5..15f4266d 100644 --- a/src/main/resources/META-INF/pickleball/guidance/AGENT-GUIDE.md +++ b/src/main/resources/META-INF/pickleball/guidance/AGENT-GUIDE.md @@ -8,28 +8,33 @@ A consumer project may contain only a short `AGENTS.md` bridge. That bridge uses Pickleball Workbench is the one front door. It is a Java/Maven program, not an IDE feature and not a GUI requirement. Do not start the GUI. Do not register IDE MCP. -If `workbench_*` tools are already present in this session, use them as an alias for the same Isolate loop. Otherwise keep using Workbench CLI verbs. Missing `workbench_*` tools is not a reason to skip Discover. +No-MCP agent path: Workbench `discover` then `confirm` (narrow `--tags` / `--name`). Live isolate (paused worker + `execute_step`) only if `workbench_*` tools are already present in this session, or a human/host already has a Workbench session. Missing `workbench_*` tools is not a reason to skip Discover, and is not a reason to Maven-exec `isolate`. -From the consumer project, with Pickleball on the test classpath (`classpathScope=test`): +From the consumer project, with Pickleball on the test classpath (`classpathScope=test`), use the same Maven exec wrapper as the project pointer; only change `-Dexec.args`: -1. **Discover** — `PickleballWorkbenchLauncher discover` (optional `--tags` / `--name`). Workbench applies complete AI `pkb_runvars`: browser ladder, high/auto parallel, diagnostic, warn, failed retention. It wraps consumer `mvn test`. Do not start a live worker to run the whole suite. Then read `run-catalog.json` and the retained `pkb_run_profile`. -2. **Isolate** — `PickleballWorkbenchLauncher isolate` with `--tags` / `--name` for a known scenario. Replays the last Discover `pkb_run_profile` as `pkb_runvars` (never supply `pkb_run_profile` as input). One paused scenario; do not parallelize isolate. If isolate cannot obtain a live worker, tell the human that Workbench CLI isolate failed. -3. **Confirm** — `PickleballWorkbenchLauncher confirm` with the same Discover snapshot and narrow tags/name. -4. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. +```text +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover" +``` + +Same launcher for `hint`, `discover`, and `confirm` — change `exec.args` only. -`hint` (alias `discover-hint`) prints the recommended Discover `pkb_runvars` and `NEXT: run discover`. `mcp` and `ui` are host/human commands. Agents must not use `ui`. Hosts may already wire `mcp .`; that is optional host wiring, mentioned once, not an agent setup step. +1. **Discover** — `-Dexec.args=discover` (optional `--tags` / `--name`). Workbench applies complete AI `pkb_runvars`: browser ladder, high/auto parallel, diagnostic, warn, failed retention. It wraps consumer `mvn test`. Do not start a live worker to run the whole suite. Then read `run-catalog.json` and the retained `pkb_run_profile`. +2. **Confirm** — `-Dexec.args=confirm --tags=... --name=...` with the same Discover snapshot and narrow tags/name. This is the no-MCP path after Discover. Never supply `pkb_run_profile` as input. +3. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. + +`hint` (alias `discover-hint`) is `-Dexec.args=hint` and prints the recommended Discover `pkb_runvars` and `NEXT: run discover`. `mcp` and `ui` are host/human commands. Agents must not use `ui`. Hosts may already wire `mcp .`; that is optional host wiring, mentioned once, not an agent setup step. Do not copy consumer features into `.pickleball` as a sandbox. ### Live isolation loop -This loop isolates a **known** failing scenario after Discover. Prefer Workbench CLI `isolate`. If `workbench_*` tools are already in this session, they are the same loop (`workbench_sync` / `workbench_worker_start` must use the Discover snapshot replayed as `pkb_runvars`). +This loop is **not** the no-MCP Maven path. Use it only when `workbench_*` tools are already in this session, or a human/host already has a Workbench session. Do not Maven-exec `isolate` as a one-shot; there is no CLI `execute_step`, and non-TTY isolate will not hold a worker. -1. Workbench `isolate` (CLI) or, when already connected, `workbench_sync` then `workbench_worker_start`. +1. Already-connected `workbench_sync` then `workbench_worker_start` (Discover snapshot replayed as `pkb_runvars`), or interactive TTY Workbench CLI `isolate`. 2. `workbench_request_control` when using the MCP alias. 3. Isolate with `workbench_execute_step` and/or `workbench_player_replace_document`. 4. Inspect with `workbench_browser_page`, `workbench_element_inspect`, and paged `workbench_events`. Prefer those over `workbench_browser_screenshot`. -5. Confirm with Workbench `confirm`. Read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` when those tools already exist. +5. Confirm with Workbench `confirm` (`-Dexec.args=confirm --tags=... --name=...`). Read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` when those tools already exist. 6. Emit the human handoff with `workbench_investigation_emit` or `DiagnosticCli emit-investigation`. In chat print only `.pickleball/investigations//report.html`. `workbench_execute_step` returns a structured `SUCCESS` / `FAILED` / `UNAVAILABLE` result. A FAILED Gherkin hypothesis does not end the worker, does not fail the paused scenario, and is not an MCP `isError`. Page `workbench_events` with `afterSequence` and a small `limit` (default 100, max 500). Live buffer edits do not require `workbench_sync` and do not write the original `.feature` until explicit Save (`workbench_request_save`). Worker restart without rebuild already exists (`workbench_worker_restart`). Step Overrides compile worker-side (`workbench_step_override_compile`). @@ -46,7 +51,7 @@ This loop isolates a **known** failing scenario after Discover. Prefer Workbench Keep first-read small. After a successful export: 1. Follow the consumer project's own instructions first; they remain authoritative for project-specific behavior. -2. Stay in this guide's tool chooser: Workbench `discover` when the failing scenario is unknown. Isolate a known failure with Workbench `isolate`, or with already-connected `workbench_*` tools as the same loop. Do not start the GUI. +2. Stay in this guide's tool chooser: Workbench `discover` when the failing scenario is unknown, then `confirm` with narrow tags/name. Live isolate only if `workbench_*` tools are already in this session. Do not start the GUI. 3. Inspect the **real** consumer `pom.xml`, Pickleball runner subclass, features, configuration, data, mappings, and test support before changing them. 4. Open a specific exported guide only when that topic is needed, for example `docs/dynamic-steps.md`, `docs/diagnostic-reporting.md`, `docs/configuration.md`, or `docs/ai-run-configuration.md`. 5. Do not assume the Pickleball core source repository is present. A normal consumer may only have the Maven dependency. @@ -150,13 +155,13 @@ pkb_reportretention=failed Use the narrowest `pkb_tags` / `pkb_name` that isolate the failure. Do not add the `pretty` plugin; it is console noise for agents. `pkb_reportretention=failed` keeps dense evidence for failing scenarios and does not retain it for passing ones. Workbench `hint` prints the estimated integer `pkb_parallel` and the selected browser for the current project/JVM. `pkb_parallel=auto` also resolves to that estimate at run start and stamps the integer into `pkb_run_profile`. -These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after isolation: +These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after Discover: ```text -PickleballWorkbenchLauncher confirm --tags=@the-failing-tag --name='The failing scenario' +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=confirm --tags=@the-failing-tag --name='The failing scenario'" ``` -After any diagnostic run, read `pkb_run_profile` from the pack. That is the complete resolved RunVar list, including inherited execution-context paths and the integer parallel count. Do not treat omitted `pkb_runvars` keys as equal to project `pickleball.properties`. Isolate and confirm replay that retained profile through `pkb_runvars`; they do not silently re-resolve from project defaults. +After any diagnostic run, read `pkb_run_profile` from the pack. That is the complete resolved RunVar list, including inherited execution-context paths and the integer parallel count. Do not treat omitted `pkb_runvars` keys as equal to project `pickleball.properties`. Confirm (and live isolate, if workbench_* is already in session) replay that retained profile through `pkb_runvars`; they do not silently re-resolve from project defaults. A selected profile or partial `pkb_runvars` input inherits only missing project execution-context RunVars: @@ -214,16 +219,17 @@ After isolation and the diagnostic rerun, emit a small human handoff. JSON is th ## Diagnostic utility commands -The agent-facing name is Workbench. From a Maven consumer where Pickleball is on the test classpath: +The agent-facing name is Workbench. From a Maven consumer where Pickleball is on the test classpath, use the same launcher as the project pointer and only change `-Dexec.args`: ```text -PickleballWorkbenchLauncher export-guidance .pickleball -PickleballWorkbenchLauncher hint -PickleballWorkbenchLauncher discover [--tags ] [--name ] -PickleballWorkbenchLauncher isolate [--tags ] [--name ] -PickleballWorkbenchLauncher confirm [--tags ] [--name ] +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=export-guidance .pickleball" +"-Dexec.args=hint" +"-Dexec.args=discover [--tags ] [--name ]" +"-Dexec.args=confirm [--tags ] [--name ]" ``` +Do not Maven-exec `isolate` unless stdin is an interactive TTY or `workbench_*` tools are already in this session. + `DiagnosticCli` remains the implementation behind export-guidance/hint and the comparison/rebuild utilities: ```text @@ -238,7 +244,7 @@ DiagnosticCli rebuild `DiagnosticCli help`, `--help`, and `-h` print that DiagnosticCli list and state that Workbench is the agent entry. -Use Workbench `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, `hint` for the complete Discover `pkb_runvars` (browser ladder, estimated `pkb_parallel`, diagnostic evidence controls), and `discover` / `isolate` / `confirm` for the turnkey loop. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. +Use Workbench `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, `hint` for the complete Discover `pkb_runvars` (browser ladder, estimated `pkb_parallel`, diagnostic evidence controls), and `discover` / `confirm` for the no-MCP turnkey loop. Live isolate only if `workbench_*` tools are already in this session. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. ## Controlled diagnostic reruns @@ -306,12 +312,12 @@ For a normal external consumer, do not assume those core files exist. This section is for Pickleball maintainers. It is not the product suite and is not first-read for consumer agents. -Pickleball's example Maven consumer includes an opt-in mixed pass/fail suite tagged only `@agent-pointer-eval` (`maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`). It is not part of `@all`, `@regression`, or the other Maven suite-profile tags. The failures are intentional canned fixtures for scoring whether a consumer AI agent follows the short `AGENTS.md` pointer into this guide and then uses Workbench discover-then-isolate. Do not treat those failures as product bugs, and do not "fix" the feature unless a human asked to change the harness. During an eval, still follow Discover / Isolate / Confirm; do not ignore canned fails. +Pickleball's example Maven consumer includes an opt-in mixed pass/fail suite tagged only `@agent-pointer-eval` (`maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`). It is not part of `@all`, `@regression`, or the other Maven suite-profile tags. The failures are intentional canned fixtures for scoring whether a consumer AI agent follows the short `AGENTS.md` pointer into this guide and then uses Workbench discover-then-confirm. Do not treat those failures as product bugs, and do not "fix" the feature unless a human asked to change the harness. During an eval, still follow Discover / Confirm; live isolate only if `workbench_*` tools are already in this session. Do not ignore canned fails. Run it explicitly: ```text -PickleballWorkbenchLauncher discover --tags=@agent-pointer-eval +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover --tags=@agent-pointer-eval" ``` or `-Dpkb_runvars="pkb_tags=@agent-pointer-eval, pkb_browser=CHROME_HEADLESS"`. Do not add this tag to consumer `AGENTS.md` or Copilot pointer files. diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/agent/README.md b/src/main/resources/META-INF/pickleball/guidance/docs/agent/README.md index 5ef38667..4ad5f259 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/agent/README.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/agent/README.md @@ -18,7 +18,7 @@ This directory supports repository-native AI coding agents. It is not a runtime Agent adapters should remain small and point back to the canonical contract rather than copying the full project description. -The nested `/maven-consumer-project/AGENTS.md` is a dependency-owned Workbench bootstrap plus a short Discover/Isolate/Confirm pointer. It materializes version-matched guidance through `PickleballWorkbenchLauncher export-guidance`, directs the consumer agent to `.pickleball/AGENT-GUIDE.md`, and tells agents to use Workbench `hint` / `discover` / `isolate` / `confirm`. Do not start the GUI. Do not register IDE MCP. Refresh/version/manifest semantics, authoring rules, configuration, diagnostics, and troubleshooting belong in the exported dependency guidance. +The nested `/maven-consumer-project/AGENTS.md` is a dependency-owned Workbench bootstrap plus a short Discover/Confirm pointer. It materializes version-matched guidance through `PickleballWorkbenchLauncher export-guidance`, directs the consumer agent to `.pickleball/AGENT-GUIDE.md`, and tells agents to use Workbench `hint` / `discover` / `confirm`. Live isolate only if `workbench_*` tools are already in the session. Do not start the GUI. Do not register IDE MCP. Refresh/version/manifest semantics, authoring rules, configuration, diagnostics, and troubleshooting belong in the exported dependency guidance. The nested `/maven-consumer-project/.github/copilot-instructions.md` is identical to `AGENTS.md` so Copilot Chat sees the same Workbench pointer. diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/agent/feature-map.md b/src/main/resources/META-INF/pickleball/guidance/docs/agent/feature-map.md index fa669e76..83449d59 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/agent/feature-map.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/agent/feature-map.md @@ -8,7 +8,7 @@ This file maps consumer-visible capabilities to implementation anchors, executab | Neutral controller/worker wire protocol | `pickleball-control-protocol/src/main/java/tools/dscode/control/protocol`; `ControlProtocol`; `ControlBridgeRequests`; `ControlBridgeResponses`; immutable `ControlBridge*` records; `InvestigationHandoff` | `verifyProtocolIsolation`; `ControlBridgeClientTest`; `InvestigationHandoffTest`; consumer `@control-bridge`; protocol/version/capability assertions | `docs/pickleball-workbench.md`; `docs/diagnostic-reporting.md`; `pickleball-workbench/AGENTS.md` | | Pickleball Workbench synchronization / persistent live worker / MCP stdio / player-style Swing+WebView UI / watched-agent control lease / scenario name-tag filter / Text-Blocks editor toggle | `pickleball-workbench`; `WorkbenchApplication`; `WorkbenchRuntimeBoundary`; `WorkbenchServices`; `WorkbenchController`; `tools.dscode.workbench.lease`; `WorkbenchAttachServer`; `tools.dscode.workbench.sync`; `WorkbenchSyncPlanner`; `WorkbenchSyncInputs`; `WorkbenchWorkerManager`; `WorkbenchLiveSession`; `tools.dscode.workbench.bridge.ControlBridgeClient`; `tools.dscode.workbench.mcp`; `workbench_diagnostic_catalog`; `workbench_diagnostic_run`; `workbench_diagnostic_summary`; `workbench_investigation_emit`; `tools.dscode.workbench.player`; `LiveEditorView`; `tools.dscode.workbench.catalog`; `ScenarioFilter`; `ConsumerFeatureCatalog`; `tools.dscode.workbench.mapping`; `tools.dscode.workbench.terminal`; `tools.dscode.workbench.diagnostics`; `tools.dscode.workbench.ui`; `FeaturePickerPanel`; OpenJFX `WebView` / `JFXPanel`; protocol `ControlProtocol.WORKER_MAIN_CLASS` | `:pickleball-workbench:test`; `WorkbenchRuntimeBoundaryTest`; `ConsumerFeatureCatalogTest`; `ScenarioFilterTest`; `GherkinBlockDocumentTest`; `LivePlaybackCoordinatorTest`; `LiveFeatureSaveTest`; `LiveEditorViewTest`; `WorkbenchControlLeaseTest`; `WorkbenchControllerLeaseTest`; `WorkbenchAttachServerTest`; `MappingValueCodecTest`; `WorkerLogBufferTest`; `DiagnosticEvidenceNavigatorTest`; `InvestigationHandoffTest`; `WorkbenchSynchronizerTest`; `WorkbenchSyncPlannerTest`; `WorkbenchMcpServerTest`; `WorkbenchUiControllerTest`; `LiveScenarioPlayerTest`; packaged UI/MCP probes | `docs/pickleball-workbench.md`; `docs/pickleball-workbench-player.md`; `docs/consumer-agent-guide.md`; `pickleball-workbench/AGENTS.md` | | Consumer-side Control Bridge and live investigation | worker-side `pickleball-control-api/src/main/java/tools/dscode/control/bridge`; core `ControlRuntime`; protocol `tools.dscode.control.protocol`; controller client `tools.dscode.workbench.bridge.ControlBridgeClient`; endpoints `/v1/status`, `/v1/scenarios`, `/v1/events`, `/v1/pause`, `/v1/resume`, `/v1/steps/execute`, `/v1/mappings/*`, `/v1/browser/*`, `/v1/services/call`, `/v1/breakpoints*`, `/v1/step-overrides*` | controller-only fake-server `ControlBridgeClientTest`; `control-bridge.feature` tagged `@control-bridge`; `ControlBridgeTestSteps.java`; Workbench `worker-check` / `live-check` | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md`; `pickleball-workbench/AGENTS.md` | -| Consumer Workbench launcher/extractor | `tools.dscode.launcher.PickleballWorkbenchLauncher`; `WorkbenchAgentCommands`; `ControlProtocol.EMBEDDED_WORKBENCH_RESOURCE`; root `shadowJar`; `verifyEmbeddedWorkbench`; content-addressed `.pickleball/workbench/controller//` extraction; agent verbs `export-guidance`, `hint`, `discover`, `isolate`, `confirm` | `PickleballWorkbenchLauncherTest`; `WorkbenchAgentCommandsTest`; `verifyEmbeddedWorkbench`; `verifyWorkbenchEntrypoint` | `docs/getting-started.md`; `docs/consumer-project.md`; `docs/pickleball-workbench.md`; `docs/consumer-agent-guide.md` | +| Consumer Workbench launcher/extractor | `tools.dscode.launcher.PickleballWorkbenchLauncher`; `WorkbenchAgentCommands`; `ControlProtocol.EMBEDDED_WORKBENCH_RESOURCE`; root `shadowJar`; `verifyEmbeddedWorkbench`; content-addressed `.pickleball/workbench/controller//` extraction; agent verbs `export-guidance`, `hint`, `discover`, `confirm` (live `isolate` only with already-running Workbench / `workbench_*`) | `PickleballWorkbenchLauncherTest`; `WorkbenchAgentCommandsTest`; `verifyEmbeddedWorkbench`; `verifyWorkbenchEntrypoint` | `docs/getting-started.md`; `docs/consumer-project.md`; `docs/pickleball-workbench.md`; `docs/consumer-agent-guide.md` | | Step Override runtime and Workbench authoring | `src/main/java/tools/dscode/control/override`; `src/main/java/io/cucumber/core/runner/StepOverrideDispatcher.java`; `NPickleStepTestStepFactory`; `src/main/aspectj/tools/dscode/control/override/StepOverrideLifecycleAspect.aj`; bridge `/v1/step-overrides*`; `ControlBridgeClient`; `WorkbenchLiveSession`; MCP `workbench_step_override_*` | `StepOverrideCompilerTest`; `StepOverrideChecks`; `StepOverrideBridgeTestSteps`; `@step-override`; Workbench `live-check`; `WorkbenchMcpServerTest`; `WorkbenchUiControllerTest` | `docs/step-overrides.md`; `docs/pickleball-workbench.md`; `pickleball-workbench/AGENTS.md` | | Dynamic control API and semantic hooks | `pickleball-control-api/src/main/java/tools/dscode/control/api`; `src/main/java/tools/dscode/common/control`; `src/main/aspectj/tools/dscode/common/control/ControlRuntimeAspect.aj`; search `DynamicControl`, `MappingControl`, `ElementControl`, `ServiceCallControl`, `ControlHook` | `DynamicControlApiChecks.java`; `ControlRuntimeObserverChecks.java`; `internal-framework-java-checks.feature`; `control-bridge.feature` | `docs/dynamic-control-api.md` | | Pickleball-native element inspection | `ElementControl.java`; `ElementInspection.java`; `ElementEvidence.java`; `ExecutionDictionary.java`; `DefinitionContext.java`; `BrowserSteps.getCurrentDriverIfPresent`; bridge `/v1/browser/elements`; Workbench `workbench_element_inspect` | `@control-bridge`; custom element/category consumer scenarios | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md`; `docs/custom-element-definitions.md` | @@ -48,6 +48,6 @@ The canonical worker bootstrap environment is `PKB_CONTROL_BRIDGE_SESSION_DIR`, `WorkbenchLiveSession` binds operations to the controller-owned paused scenario and verifies worker PID, bridge runtime id, and scenario id stability. A `FAILED` `executeStep` result leaves that same paused worker available; it is not a worker-ending error. Step Override source is compiled and loaded worker-side. Normal live calls do not invoke Maven/Gradle, resynchronize, or restart the worker. -The lightweight non-Spring stdio MCP adapter and player-style Swing/WebView UI both delegate through `WorkbenchServices` / `WorkbenchController`. `LiveScenarioPlayer` is a Workbench-side headless presentation model for the editable session buffer, stable line identities, selection, click-to-seek playhead, and player state only; it does not parse or execute Pickleball steps, model ParsingMap/NodeMap semantics, or claim runtime rewind. `LiveEditorView` is the Text vs Blocks presentation choice for that same buffer; toggling it must not change document text, selection, or playhead id. While `RUNNING`, `WorkbenchController.executeStep` owns playhead follow once (`LivePlaybackCoordinator.followExecutedStep`); the Swing Play loop continues without remaking that mark, and leftover marks of an already-consumed step are no-ops. The controller also owns the watched-agent control lease (`HUMAN` / `AGENT`, banner `currentAction`, gated Save permission). Consumer AI agents use Workbench `discover` / `isolate` / `confirm` as the one front door. Live `workbench_*` MCP tools are an optional alias when already connected; agents must not self-register IDE MCP or start an IDE-owned stdio server. Isolate a known failure with Workbench CLI `isolate` or already-connected `workbench_*` tools. If isolate cannot obtain a live worker, tell the human that Workbench CLI isolate failed. UI mode may write a localhost attach endpoint to `.pickleball/workbench/attach.json` so a watcher can join a human GUI session instead of starting a second Workbench; that attach file is not the consumer-agent path for this release. The left-rail picker filters project-owned scenarios by name (starts with / contains / ends with / full match; default contains; case-insensitive) and Cucumber tags (include AND, exclude NOT, Feature/Rule/outline/Examples inheritance parsed from catalog `.feature` files without calling Cucumber). Feature-file selection is an optional collapsed secondary filter; with none selected, name/tag apply to every catalog scenario. The live editor is Gherkin text or a block WebView over that same player model. Global Play starts from the first executable step; Step Editor **Step** is isolated `executeStep`; **From Here** runs from the selected step; wait-at-end stays in play so Enter can append-and-continue. Mapping property types go through `mappingPut` / `mappingRestore`. Terminal tails existing worker log files. Diagnostic explorer reads retained `reports/diagnostic-runs` artifacts in the repository evidence order. The default buffer is a Workbench-owned browser demo against `URL.home`; **Save** is confirmation-gated and copies only a picker-loaded scenario back to its originating `.feature` file. OpenJFX is Workbench-only. MCP mode reserves stdout for newline-delimited MCP JSON-RPC and redirects ordinary output to stderr. The UI remains execution-oriented and does not recreate a project IDE, generic build/process UI, source navigator, or collaboration system. +The lightweight non-Spring stdio MCP adapter and player-style Swing/WebView UI both delegate through `WorkbenchServices` / `WorkbenchController`. `LiveScenarioPlayer` is a Workbench-side headless presentation model for the editable session buffer, stable line identities, selection, click-to-seek playhead, and player state only; it does not parse or execute Pickleball steps, model ParsingMap/NodeMap semantics, or claim runtime rewind. `LiveEditorView` is the Text vs Blocks presentation choice for that same buffer; toggling it must not change document text, selection, or playhead id. While `RUNNING`, `WorkbenchController.executeStep` owns playhead follow once (`LivePlaybackCoordinator.followExecutedStep`); the Swing Play loop continues without remaking that mark, and leftover marks of an already-consumed step are no-ops. The controller also owns the watched-agent control lease (`HUMAN` / `AGENT`, banner `currentAction`, gated Save permission). Consumer AI agents use Workbench `discover` / `confirm` as the one front door. Live isolate (paused worker + `execute_step`) only if `workbench_*` tools are already connected or a human/host already has a Workbench session; agents must not Maven-exec `isolate` as a one-shot, self-register IDE MCP, or start an IDE-owned stdio server. UI mode may write a localhost attach endpoint to `.pickleball/workbench/attach.json` so a watcher can join a human GUI session instead of starting a second Workbench; that attach file is not the consumer-agent path for this release. The left-rail picker filters project-owned scenarios by name (starts with / contains / ends with / full match; default contains; case-insensitive) and Cucumber tags (include AND, exclude NOT, Feature/Rule/outline/Examples inheritance parsed from catalog `.feature` files without calling Cucumber). Feature-file selection is an optional collapsed secondary filter; with none selected, name/tag apply to every catalog scenario. The live editor is Gherkin text or a block WebView over that same player model. Global Play starts from the first executable step; Step Editor **Step** is isolated `executeStep`; **From Here** runs from the selected step; wait-at-end stays in play so Enter can append-and-continue. Mapping property types go through `mappingPut` / `mappingRestore`. Terminal tails existing worker log files. Diagnostic explorer reads retained `reports/diagnostic-runs` artifacts in the repository evidence order. The default buffer is a Workbench-owned browser demo against `URL.home`; **Save** is confirmation-gated and copies only a picker-loaded scenario back to its originating `.feature` file. OpenJFX is Workbench-only. MCP mode reserves stdout for newline-delimited MCP JSON-RPC and redirects ordinary output to stderr. The UI remains execution-oriented and does not recreate a project IDE, generic build/process UI, source navigator, or collaboration system. For this boundary, scenario validation is limited to the affected `@control-bridge` and/or `@step-override-bridge` tags with `pkb_parallel=80` where practical. Do not run `@all` for Workbench isolation changes. diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/ai-run-configuration.md b/src/main/resources/META-INF/pickleball/guidance/docs/ai-run-configuration.md index f38c6d3f..79c453fa 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/ai-run-configuration.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/ai-run-configuration.md @@ -323,7 +323,7 @@ When operating in a consumer project: ## AI agents -The agent-facing entry is Pickleball Workbench (`hint`, `discover`, `isolate`, `confirm`). Set a **complete** Discover `pkb_runvars` rather than a partial overlay. Workbench `hint` prints the browser-ladder result and estimated integer parallel count. The browser ladder keeps a remote project `pkb_browser` (`SAUCE_*` / `GRID_*` / `REMOTE_*`); otherwise it prefers `CHROME_HEADLESS`. Unused Sauce/Grid yaml files are not auto-selected. +The agent-facing entry is Pickleball Workbench (`hint`, `discover`, `confirm`). Live isolate only if `workbench_*` tools are already present. Set a **complete** Discover `pkb_runvars` rather than a partial overlay. Workbench `hint` prints the browser-ladder result and estimated integer parallel count. The browser ladder keeps a remote project `pkb_browser` (`SAUCE_*` / `GRID_*` / `REMOTE_*`); otherwise it prefers `CHROME_HEADLESS`. Unused Sauce/Grid yaml files are not auto-selected. ```text pkb_browser= @@ -333,8 +333,8 @@ pkb_loglevel=warn pkb_reportretention=failed ``` -plus the narrowest useful `pkb_tags` / `pkb_name`. Multi-scenario Discover/Confirm use that high parallelism. Isolate / live Workbench stays one paused scenario. +plus the narrowest useful `pkb_tags` / `pkb_name`. Multi-scenario Discover/Confirm use that high parallelism. Live isolate stays one paused scenario and is only for an already-running Workbench. -After Discover, inspect `pkb_run_profile` from `run-catalog.json`, `run-index.json`, or `summary.json`. Isolate and confirm replay that retained profile through `pkb_runvars`. If there is no prior Discover snapshot, Workbench says so; it does not silently re-resolve from project defaults. +After Discover, inspect `pkb_run_profile` from `run-catalog.json`, `run-index.json`, or `summary.json`. Confirm (and live isolate, if workbench_* is already in session) replay that retained profile through `pkb_runvars`. If there is no prior Discover snapshot, Workbench says so; it does not silently re-resolve from project defaults. Never supply `pkb_run_profile` as input. Workbench MCP `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` return the same retained `runProfile` when present. The consumer worker resolves the same snapshot internally through `PickleballRunner`; it does not accept `pkb_run_profile` as input. diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/consumer-agent-guide.md b/src/main/resources/META-INF/pickleball/guidance/docs/consumer-agent-guide.md index 25a8e3f5..15f4266d 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/consumer-agent-guide.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/consumer-agent-guide.md @@ -8,28 +8,33 @@ A consumer project may contain only a short `AGENTS.md` bridge. That bridge uses Pickleball Workbench is the one front door. It is a Java/Maven program, not an IDE feature and not a GUI requirement. Do not start the GUI. Do not register IDE MCP. -If `workbench_*` tools are already present in this session, use them as an alias for the same Isolate loop. Otherwise keep using Workbench CLI verbs. Missing `workbench_*` tools is not a reason to skip Discover. +No-MCP agent path: Workbench `discover` then `confirm` (narrow `--tags` / `--name`). Live isolate (paused worker + `execute_step`) only if `workbench_*` tools are already present in this session, or a human/host already has a Workbench session. Missing `workbench_*` tools is not a reason to skip Discover, and is not a reason to Maven-exec `isolate`. -From the consumer project, with Pickleball on the test classpath (`classpathScope=test`): +From the consumer project, with Pickleball on the test classpath (`classpathScope=test`), use the same Maven exec wrapper as the project pointer; only change `-Dexec.args`: -1. **Discover** — `PickleballWorkbenchLauncher discover` (optional `--tags` / `--name`). Workbench applies complete AI `pkb_runvars`: browser ladder, high/auto parallel, diagnostic, warn, failed retention. It wraps consumer `mvn test`. Do not start a live worker to run the whole suite. Then read `run-catalog.json` and the retained `pkb_run_profile`. -2. **Isolate** — `PickleballWorkbenchLauncher isolate` with `--tags` / `--name` for a known scenario. Replays the last Discover `pkb_run_profile` as `pkb_runvars` (never supply `pkb_run_profile` as input). One paused scenario; do not parallelize isolate. If isolate cannot obtain a live worker, tell the human that Workbench CLI isolate failed. -3. **Confirm** — `PickleballWorkbenchLauncher confirm` with the same Discover snapshot and narrow tags/name. -4. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. +```text +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover" +``` + +Same launcher for `hint`, `discover`, and `confirm` — change `exec.args` only. -`hint` (alias `discover-hint`) prints the recommended Discover `pkb_runvars` and `NEXT: run discover`. `mcp` and `ui` are host/human commands. Agents must not use `ui`. Hosts may already wire `mcp .`; that is optional host wiring, mentioned once, not an agent setup step. +1. **Discover** — `-Dexec.args=discover` (optional `--tags` / `--name`). Workbench applies complete AI `pkb_runvars`: browser ladder, high/auto parallel, diagnostic, warn, failed retention. It wraps consumer `mvn test`. Do not start a live worker to run the whole suite. Then read `run-catalog.json` and the retained `pkb_run_profile`. +2. **Confirm** — `-Dexec.args=confirm --tags=... --name=...` with the same Discover snapshot and narrow tags/name. This is the no-MCP path after Discover. Never supply `pkb_run_profile` as input. +3. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. + +`hint` (alias `discover-hint`) is `-Dexec.args=hint` and prints the recommended Discover `pkb_runvars` and `NEXT: run discover`. `mcp` and `ui` are host/human commands. Agents must not use `ui`. Hosts may already wire `mcp .`; that is optional host wiring, mentioned once, not an agent setup step. Do not copy consumer features into `.pickleball` as a sandbox. ### Live isolation loop -This loop isolates a **known** failing scenario after Discover. Prefer Workbench CLI `isolate`. If `workbench_*` tools are already in this session, they are the same loop (`workbench_sync` / `workbench_worker_start` must use the Discover snapshot replayed as `pkb_runvars`). +This loop is **not** the no-MCP Maven path. Use it only when `workbench_*` tools are already in this session, or a human/host already has a Workbench session. Do not Maven-exec `isolate` as a one-shot; there is no CLI `execute_step`, and non-TTY isolate will not hold a worker. -1. Workbench `isolate` (CLI) or, when already connected, `workbench_sync` then `workbench_worker_start`. +1. Already-connected `workbench_sync` then `workbench_worker_start` (Discover snapshot replayed as `pkb_runvars`), or interactive TTY Workbench CLI `isolate`. 2. `workbench_request_control` when using the MCP alias. 3. Isolate with `workbench_execute_step` and/or `workbench_player_replace_document`. 4. Inspect with `workbench_browser_page`, `workbench_element_inspect`, and paged `workbench_events`. Prefer those over `workbench_browser_screenshot`. -5. Confirm with Workbench `confirm`. Read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` when those tools already exist. +5. Confirm with Workbench `confirm` (`-Dexec.args=confirm --tags=... --name=...`). Read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary` when those tools already exist. 6. Emit the human handoff with `workbench_investigation_emit` or `DiagnosticCli emit-investigation`. In chat print only `.pickleball/investigations//report.html`. `workbench_execute_step` returns a structured `SUCCESS` / `FAILED` / `UNAVAILABLE` result. A FAILED Gherkin hypothesis does not end the worker, does not fail the paused scenario, and is not an MCP `isError`. Page `workbench_events` with `afterSequence` and a small `limit` (default 100, max 500). Live buffer edits do not require `workbench_sync` and do not write the original `.feature` until explicit Save (`workbench_request_save`). Worker restart without rebuild already exists (`workbench_worker_restart`). Step Overrides compile worker-side (`workbench_step_override_compile`). @@ -46,7 +51,7 @@ This loop isolates a **known** failing scenario after Discover. Prefer Workbench Keep first-read small. After a successful export: 1. Follow the consumer project's own instructions first; they remain authoritative for project-specific behavior. -2. Stay in this guide's tool chooser: Workbench `discover` when the failing scenario is unknown. Isolate a known failure with Workbench `isolate`, or with already-connected `workbench_*` tools as the same loop. Do not start the GUI. +2. Stay in this guide's tool chooser: Workbench `discover` when the failing scenario is unknown, then `confirm` with narrow tags/name. Live isolate only if `workbench_*` tools are already in this session. Do not start the GUI. 3. Inspect the **real** consumer `pom.xml`, Pickleball runner subclass, features, configuration, data, mappings, and test support before changing them. 4. Open a specific exported guide only when that topic is needed, for example `docs/dynamic-steps.md`, `docs/diagnostic-reporting.md`, `docs/configuration.md`, or `docs/ai-run-configuration.md`. 5. Do not assume the Pickleball core source repository is present. A normal consumer may only have the Maven dependency. @@ -150,13 +155,13 @@ pkb_reportretention=failed Use the narrowest `pkb_tags` / `pkb_name` that isolate the failure. Do not add the `pretty` plugin; it is console noise for agents. `pkb_reportretention=failed` keeps dense evidence for failing scenarios and does not retain it for passing ones. Workbench `hint` prints the estimated integer `pkb_parallel` and the selected browser for the current project/JVM. `pkb_parallel=auto` also resolves to that estimate at run start and stamps the integer into `pkb_run_profile`. -These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after isolation: +These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after Discover: ```text -PickleballWorkbenchLauncher confirm --tags=@the-failing-tag --name='The failing scenario' +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=confirm --tags=@the-failing-tag --name='The failing scenario'" ``` -After any diagnostic run, read `pkb_run_profile` from the pack. That is the complete resolved RunVar list, including inherited execution-context paths and the integer parallel count. Do not treat omitted `pkb_runvars` keys as equal to project `pickleball.properties`. Isolate and confirm replay that retained profile through `pkb_runvars`; they do not silently re-resolve from project defaults. +After any diagnostic run, read `pkb_run_profile` from the pack. That is the complete resolved RunVar list, including inherited execution-context paths and the integer parallel count. Do not treat omitted `pkb_runvars` keys as equal to project `pickleball.properties`. Confirm (and live isolate, if workbench_* is already in session) replay that retained profile through `pkb_runvars`; they do not silently re-resolve from project defaults. A selected profile or partial `pkb_runvars` input inherits only missing project execution-context RunVars: @@ -214,16 +219,17 @@ After isolation and the diagnostic rerun, emit a small human handoff. JSON is th ## Diagnostic utility commands -The agent-facing name is Workbench. From a Maven consumer where Pickleball is on the test classpath: +The agent-facing name is Workbench. From a Maven consumer where Pickleball is on the test classpath, use the same launcher as the project pointer and only change `-Dexec.args`: ```text -PickleballWorkbenchLauncher export-guidance .pickleball -PickleballWorkbenchLauncher hint -PickleballWorkbenchLauncher discover [--tags ] [--name ] -PickleballWorkbenchLauncher isolate [--tags ] [--name ] -PickleballWorkbenchLauncher confirm [--tags ] [--name ] +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=export-guidance .pickleball" +"-Dexec.args=hint" +"-Dexec.args=discover [--tags ] [--name ]" +"-Dexec.args=confirm [--tags ] [--name ]" ``` +Do not Maven-exec `isolate` unless stdin is an interactive TTY or `workbench_*` tools are already in this session. + `DiagnosticCli` remains the implementation behind export-guidance/hint and the comparison/rebuild utilities: ```text @@ -238,7 +244,7 @@ DiagnosticCli rebuild `DiagnosticCli help`, `--help`, and `-h` print that DiagnosticCli list and state that Workbench is the agent entry. -Use Workbench `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, `hint` for the complete Discover `pkb_runvars` (browser ladder, estimated `pkb_parallel`, diagnostic evidence controls), and `discover` / `isolate` / `confirm` for the turnkey loop. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. +Use Workbench `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, `hint` for the complete Discover `pkb_runvars` (browser ladder, estimated `pkb_parallel`, diagnostic evidence controls), and `discover` / `confirm` for the no-MCP turnkey loop. Live isolate only if `workbench_*` tools are already in this session. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. ## Controlled diagnostic reruns @@ -306,12 +312,12 @@ For a normal external consumer, do not assume those core files exist. This section is for Pickleball maintainers. It is not the product suite and is not first-read for consumer agents. -Pickleball's example Maven consumer includes an opt-in mixed pass/fail suite tagged only `@agent-pointer-eval` (`maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`). It is not part of `@all`, `@regression`, or the other Maven suite-profile tags. The failures are intentional canned fixtures for scoring whether a consumer AI agent follows the short `AGENTS.md` pointer into this guide and then uses Workbench discover-then-isolate. Do not treat those failures as product bugs, and do not "fix" the feature unless a human asked to change the harness. During an eval, still follow Discover / Isolate / Confirm; do not ignore canned fails. +Pickleball's example Maven consumer includes an opt-in mixed pass/fail suite tagged only `@agent-pointer-eval` (`maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`). It is not part of `@all`, `@regression`, or the other Maven suite-profile tags. The failures are intentional canned fixtures for scoring whether a consumer AI agent follows the short `AGENTS.md` pointer into this guide and then uses Workbench discover-then-confirm. Do not treat those failures as product bugs, and do not "fix" the feature unless a human asked to change the harness. During an eval, still follow Discover / Confirm; live isolate only if `workbench_*` tools are already in this session. Do not ignore canned fails. Run it explicitly: ```text -PickleballWorkbenchLauncher discover --tags=@agent-pointer-eval +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover --tags=@agent-pointer-eval" ``` or `-Dpkb_runvars="pkb_tags=@agent-pointer-eval, pkb_browser=CHROME_HEADLESS"`. Do not add this tag to consumer `AGENTS.md` or Copilot pointer files. diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/consumer-project.md b/src/main/resources/META-INF/pickleball/guidance/docs/consumer-project.md index 48e16392..6c70a1a4 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/consumer-project.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/consumer-project.md @@ -26,7 +26,7 @@ Rerun export before Pickleball work even when `.pickleball` already exists. A su Compatibility note: an older Pickleball release whose exporter predates the manifest lifecycle may leave newer files or a newer manifest behind after a downgrade. Those leftovers are not authoritative for the downgraded dependency; prefer the dependency actually resolved on the test classpath and the files freshly exported by that dependency. -AI agents should read `.pickleball/AGENT-GUIDE.md` first after a successful export. Workbench is the one front door: `discover`, `isolate`, and `confirm`. Do not start the GUI. Do not register IDE MCP. If `workbench_*` tools are already connected they are an Isolate alias. Do not treat `.pickleball/maven-consumer-project/` as the project under test, and do not dump `docs/README.md` or the whole snapshot into first-read context. Human readers can start with `.pickleball/docs/README.md`; links from those guides to `maven-consumer-project` resolve to the exported version-matched reference files. +AI agents should read `.pickleball/AGENT-GUIDE.md` first after a successful export. Workbench is the one front door: `discover` then `confirm`. Do not start the GUI. Do not register IDE MCP. Live isolate only if `workbench_*` tools are already connected. Do not treat `.pickleball/maven-consumer-project/` as the project under test, and do not dump `docs/README.md` or the whole snapshot into first-read context. Human readers can start with `.pickleball/docs/README.md`; links from those guides to `maven-consumer-project` resolve to the exported version-matched reference files. ## Version-matched reference snapshot @@ -77,20 +77,20 @@ or use the included wrappers: ## Launch the dependency-matched Workbench -The test-scoped Pickleball dependency already contains its controller-only Workbench payload. Hosts that already run Workbench MCP can launch it from the resolved test classpath: +The test-scoped Pickleball dependency already contains its controller-only Workbench payload. Agents use the same launcher as `AGENTS.md` and only change `-Dexec.args` (`hint`, `discover`, `confirm`). Hosts that already run Workbench MCP can launch it from the resolved test classpath: ```bash ./mvnw -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java \ -Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher \ -Dexec.classpathScope=test \ - "-Dexec.args=mcp ." + "-Dexec.args=discover" ``` ```powershell -.\mvnw.cmd -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=mcp ." +.\mvnw.cmd -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=discover" ``` -Humans who want the Swing player can pass `ui .` instead. Agents for this release should not use the GUI, `ui .`, or `attach.json` as their path. The launcher verifies and extracts the opaque payload beneath `.pickleball/workbench/controller//`, then creates a separate Workbench JVM. Workbench captures this project's compiled outputs and effective test runtime before creating a separate worker JVM. Only the worker loads the consumer-resolved Pickleball runtime; the Workbench artifact and process contain no core implementation. See `docs/pickleball-workbench.md` for commands, lifecycle, protocol compatibility, and isolation checks. The live-loop order lives in `.pickleball/AGENT-GUIDE.md`. +Optional host MCP uses `"-Dexec.args=mcp ."`. Humans who want the Swing player can pass `ui .` instead. Agents for this release should not use the GUI, `ui .`, or `attach.json` as their path. The launcher verifies and extracts the opaque payload beneath `.pickleball/workbench/controller//`, then creates a separate Workbench JVM. Workbench captures this project's compiled outputs and effective test runtime before creating a separate worker JVM. Only the worker loads the consumer-resolved Pickleball runtime; the Workbench artifact and process contain no core implementation. See `docs/pickleball-workbench.md` for commands, lifecycle, protocol compatibility, and isolation checks. The live-loop order lives in `.pickleball/AGENT-GUIDE.md`. Runner defaults include: @@ -235,8 +235,9 @@ Do not recursively ingest an entire run. ### Diagnostic CLI ```text -PickleballWorkbenchLauncher hint -PickleballWorkbenchLauncher discover +mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java "-Dexec.mainClass=tools.dscode.launcher.PickleballWorkbenchLauncher" "-Dexec.classpathScope=test" "-Dexec.args=hint" +"-Dexec.args=discover" +"-Dexec.args=confirm --tags=@the-failing-tag" DiagnosticCli compare-runs [output-json] DiagnosticCli compare-fingerprints [output-json] DiagnosticCli emit-investigation diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/pickleball-workbench.md b/src/main/resources/META-INF/pickleball/guidance/docs/pickleball-workbench.md index 459aba95..a9c20a55 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/pickleball-workbench.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/pickleball-workbench.md @@ -45,7 +45,7 @@ META-INF/pickleball/workbench/pickleball-workbench.jar Run the small launcher from the consumer test classpath. For Maven consumers, this command requires no cache path, separate Workbench dependency, or separately selected version. -Workbench is the consumer AI-agent front door. Agents use `export-guidance`, `hint`, `discover`, `isolate`, and `confirm`. Do not start the GUI. Do not register IDE MCP. +Workbench is the consumer AI-agent front door. Agents use `export-guidance`, `hint`, `discover`, and `confirm`. Live isolate only if `workbench_*` tools are already in this session. Do not start the GUI. Do not register IDE MCP. ```bash mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java \ @@ -57,10 +57,11 @@ mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java \ ```bash "-Dexec.args=hint" "-Dexec.args=discover --tags=@smoke" -"-Dexec.args=isolate --name='The failing scenario'" -"-Dexec.args=confirm --tags=@smoke" +"-Dexec.args=confirm --tags=@smoke --name='The failing scenario'" ``` +Same launcher; only change `-Dexec.args`. Do not Maven-exec `isolate` as a one-shot. Live isolate needs an already-running Workbench (pre-attached `workbench_*` or an interactive TTY session). + `mcp` and `ui` remain host/human commands. Hosts that already run Workbench MCP can launch it from the consumer test classpath: ```bash @@ -82,7 +83,7 @@ Gradle consumers can expose the same dependency-owned launcher without resolving tasks.register('pickleballWorkbench', JavaExec) { classpath = sourceSets.test.runtimeClasspath mainClass = 'tools.dscode.launcher.PickleballWorkbenchLauncher' - args 'mcp', projectDir.absolutePath // optional host MCP; pass 'ui' for the Swing player + args 'hint' // no-MCP Discover helper; pass 'discover' / 'confirm' for the agent path. Optional host MCP: 'mcp', projectDir.absolutePath. Humans: 'ui'. } ``` @@ -325,7 +326,7 @@ A Copilot or other MCP-style client finds that file in the consumer project, the 4. Use the existing live tools (`workbench_execute_step`, Mapping, evidence, worker) while holding the lease, and `workbench_set_current_action` so the human can watch. 5. `POST {url}/tools/workbench_request_save` to ask to copy the live scenario into the original feature. The call blocks until the human clicks Allow or Deny, or Take control. -Headless `java -jar pickleball-workbench-.jar mcp ` stays stdio JSON-RPC only. That is the consumer-agent path. That client may hold the lease without a banner. Save is still a distinct explicit tool and never an implicit write. +Headless `java -jar pickleball-workbench-.jar mcp ` stays stdio JSON-RPC only. That is optional host wiring when `workbench_*` tools are already connected, not the consumer-agent path. That client may hold the lease without a banner. Save is still a distinct explicit tool and never an implicit write. A human-watched UI session is optional and separate. From `maven-consumer-project`, a person may start `ui .` and then a watcher can join `.pickleball/workbench/attach.json`. Do not launch a second `mcp` process against the same live UI session, and do not treat that attach file as the default agent path. @@ -337,7 +338,7 @@ Start the lightweight non-Spring MCP server for a consumer project. This is opti java -jar $workbenchJar mcp ".\maven-consumer-project" ``` -Or, from a Maven consumer test classpath, `"-Dexec.args=mcp ."`. That launcher is optional host wiring for environments that already run MCP. Agents use Workbench `discover` / `isolate` / `confirm`. They must not self-register IDE MCP or start an IDE-owned stdio server. Isolate a known failure with Workbench CLI `isolate` or already-connected `workbench_*` tools. Do not document or use the Swing GUI, `ui .`, or `.pickleball/workbench/attach.json` as the agent path. +Or, from a Maven consumer test classpath, `"-Dexec.args=mcp ."`. That launcher is optional host wiring for environments that already run MCP. Agents use Workbench `discover` / `confirm`. They must not self-register IDE MCP or start an IDE-owned stdio server. Live isolate only if `workbench_*` tools are already connected or stdin is an interactive TTY. Do not document or use the Swing GUI, `ui .`, or `.pickleball/workbench/attach.json` as the agent path. The server uses the official Java MCP SDK core and stdio transport with the Jackson 2 JSON adapter. MCP dependencies are Workbench-only and are shaded into the executable companion. Workbench deliberately does not use Spring Boot, Spring Framework, Spring AI, WebMVC, or Tomcat. diff --git a/src/test/java/tools/dscode/launcher/WorkbenchAgentCommandsTest.java b/src/test/java/tools/dscode/launcher/WorkbenchAgentCommandsTest.java index cac4f284..07b8c395 100644 --- a/src/test/java/tools/dscode/launcher/WorkbenchAgentCommandsTest.java +++ b/src/test/java/tools/dscode/launcher/WorkbenchAgentCommandsTest.java @@ -85,6 +85,43 @@ void discoverWrapsMavenAndRecordsSnapshot() throws Exception { assertTrue(snapshot.runProfile().contains("pkb_browser=CHROME_HEADLESS")); } + @Test + void discoverNextIsConfirmNotIsolateMavenExec() throws Exception { + Path catalogDir = tempDir.resolve("reports/diagnostic-runs"); + Files.createDirectories(catalogDir); + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + int exit = WorkbenchAgentCommands.run( + new String[]{"discover", tempDir.toString(), "--tags=@smoke"}, + new PrintStream(stdout, true, StandardCharsets.UTF_8), + System.err, + (project, command, out, err) -> { + try { + Files.writeString(catalogDir.resolve("run-catalog.json"), """ + { + "schemaVersion": 1, + "runs": [ + { + "runId": "run-next", + "runProfile": "pkb_browser=CHROME_HEADLESS, pkb_parallel=4, pkb_reportingmode=diagnostic, pkb_tags=@smoke", + "lineage": { "runPurpose": "workbench-discover" } + } + ] + } + """); + } catch (Exception failure) { + throw new RuntimeException(failure); + } + return 0; + } + ); + + assertEquals(0, exit); + String text = stdout.toString(StandardCharsets.UTF_8); + assertTrue(text.contains("NEXT: confirm")); + assertFalse(text.contains("NEXT: isolate")); + assertFalse(text.contains("execute_step")); + } + @Test void confirmRequiresDiscoverSnapshot() { ByteArrayOutputStream stderr = new ByteArrayOutputStream(); diff --git a/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java b/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java index 9fb907f3..e8aaf2d5 100644 --- a/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java +++ b/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java @@ -1,6 +1,10 @@ package tools.dscode.parallelutilities; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -8,6 +12,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class ParallelCountEstimatorTest { + @TempDir + Path tempDir; + @Test void tinyHeapYieldsMinimumWorkers() { assertEquals(2, ParallelCountEstimator.estimate(8, 256L * 1024 * 1024)); @@ -50,7 +57,7 @@ void invalidParallelValuesFailClearly() { @Test void recommendedDiscoverRunVarsIncludeEstimatedParallel() { String runVars = ParallelCountEstimator.recommendedDiscoverRunVars(); - assertTrue(runVars.contains("pkb_browser=CHROME_HEADLESS")); + assertTrue(runVars.contains("pkb_browser=")); assertTrue(runVars.contains("pkb_parallel=" + ParallelCountEstimator.estimate())); assertTrue(runVars.contains("pkb_reportingmode=diagnostic")); assertTrue(runVars.contains("pkb_loglevel=warn")); @@ -58,4 +65,15 @@ void recommendedDiscoverRunVarsIncludeEstimatedParallel() { assertFalse(runVars.contains("pkb_parallel=80")); assertFalse(runVars.contains("pkb_parallel=auto")); } + + @Test + void recommendedDiscoverRunVarsUseBrowserLadder() throws Exception { + Path resources = tempDir.resolve("src/test/resources"); + Files.createDirectories(resources); + Files.writeString(resources.resolve("pickleball.properties"), "pkb_browser=SAUCE_CHROME\n"); + + String runVars = ParallelCountEstimator.recommendedDiscoverRunVars(tempDir); + assertTrue(runVars.contains("pkb_browser=SAUCE_CHROME")); + assertFalse(runVars.contains("pkb_browser=CHROME_HEADLESS")); + } } From abd7145a9438e0df1d1f98320adb65c03844b95d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 27 Aug 2026 17:11:18 +0000 Subject: [PATCH 2/2] Detect non-TTY isolate without depending on System.console(). Gradle test JVMs can have a console even when Maven exec stdin is a pipe. Isolate now takes an explicit interactive-stdin flag, treats console-null or non-tty fd 0 as non-interactive, and the unit test drives that path directly. Co-authored-by: ds-code-t --- .../workbench/WorkbenchApplication.java | 25 ++++++++++++++++--- .../workbench/WorkbenchApplicationTest.java | 23 ++++++++++++++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java b/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java index fad69af6..f7d41b8b 100644 --- a/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java +++ b/pickleball-workbench/src/main/java/tools/dscode/workbench/WorkbenchApplication.java @@ -63,7 +63,7 @@ static int run(String[] args, PrintStream out, PrintStream err) { case "status" -> status(args, out); case "worker-check" -> workerCheck(args, out); case "live-check" -> liveCheck(args, out); - case "isolate" -> isolate(args, out, err); + case "isolate" -> isolate(args, out, err, stdinIsInteractiveTty()); case "ui" -> ui(args); case "mcp" -> throw new IllegalArgumentException( "MCP mode must be launched through the Workbench executable." @@ -183,7 +183,7 @@ private static int ui(String[] args) { return 0; } - private static int isolate(String[] args, PrintStream out, PrintStream err) { + static int isolate(String[] args, PrintStream out, PrintStream err, boolean interactiveStdin) { IsolateArgs parsed = isolateArgs(args); Map workerProperties; try { @@ -197,7 +197,7 @@ private static int isolate(String[] args, PrintStream out, PrintStream err) { } boolean once = Boolean.getBoolean("pickleball.workbench.isolate.once"); - if (!once && !stdinIsInteractiveTty()) { + if (!once && !interactiveStdin) { err.println("Live isolate holds a paused worker for execute_step."); err.println("That needs an already-running Workbench (pre-attached workbench_* tools or an interactive TTY session)."); err.println("Maven one-shot isolate is not the no-MCP agent path. Do not register IDE MCP. Do not start the GUI."); @@ -234,7 +234,24 @@ private static int isolate(String[] args, PrintStream out, PrintStream err) { } static boolean stdinIsInteractiveTty() { - return System.console() != null; + if (System.console() == null) return false; + try { + ProcessBuilder builder = new ProcessBuilder("sh", "-c", "test -t 0"); + builder.redirectInput(ProcessBuilder.Redirect.INHERIT); + builder.redirectOutput(ProcessBuilder.Redirect.DISCARD); + builder.redirectError(ProcessBuilder.Redirect.DISCARD); + Process process = builder.start(); + if (!process.waitFor(2, java.util.concurrent.TimeUnit.SECONDS)) { + process.destroyForcibly(); + return false; + } + return process.exitValue() == 0; + } catch (InterruptedException failure) { + Thread.currentThread().interrupt(); + return false; + } catch (Exception ignored) { + return true; + } } private record IsolateArgs(Path project, String tags, String name) { diff --git a/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java b/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java index 101911ec..34d658fc 100644 --- a/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java +++ b/pickleball-workbench/src/test/java/tools/dscode/workbench/WorkbenchApplicationTest.java @@ -105,7 +105,6 @@ void isolateRequiresAProject() { @Test @Timeout(value = 5, unit = TimeUnit.SECONDS) void isolateWithoutInteractiveTtyDoesNotHangAndPointsToConfirm() throws Exception { - assertFalse(WorkbenchApplication.stdinIsInteractiveTty()); Path snapshot = tempDir.resolve(ControlProtocol.LAST_DISCOVER_SNAPSHOT_RELATIVE); Files.createDirectories(snapshot.getParent()); Files.writeString(snapshot, """ @@ -122,7 +121,23 @@ void isolateWithoutInteractiveTtyDoesNotHangAndPointsToConfirm() throws Exceptio } """); - Output output = run("isolate", tempDir.toString(), "--tags=@failing"); + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + int exitCode; + try (PrintStream out = new PrintStream(stdout, true, StandardCharsets.UTF_8); + PrintStream err = new PrintStream(stderr, true, StandardCharsets.UTF_8)) { + exitCode = WorkbenchApplication.isolate( + new String[]{"isolate", tempDir.toString(), "--tags=@failing"}, + out, + err, + false + ); + } + Output output = new Output( + exitCode, + stdout.toString(StandardCharsets.UTF_8), + stderr.toString(StandardCharsets.UTF_8) + ); assertEquals(2, output.exitCode()); assertTrue(output.stderr().contains("already-running Workbench")); @@ -130,8 +145,8 @@ void isolateWithoutInteractiveTtyDoesNotHangAndPointsToConfirm() throws Exceptio || output.stderr().contains("workbench_*")); assertTrue(output.stderr().contains("confirm") || output.stdout().contains("confirm")); assertTrue(output.stdout().contains("NEXT: confirm")); - assertFalse(output.stdout().contains("execute_step")); - assertFalse(output.stderr().contains("execute_step")); + assertFalse(output.stdout().contains("NEXT: execute_step")); + assertFalse(output.stderr().contains("NEXT: execute_step")); assertFalse(output.stdout().contains("Workbench isolate worker:")); assertFalse(output.stderr().toLowerCase().contains("intellij")); }