diff --git a/docs/agent/feature-map.md b/docs/agent/feature-map.md index 419166f9..8c1f7678 100644 --- a/docs/agent/feature-map.md +++ b/docs/agent/feature-map.md @@ -15,13 +15,13 @@ This file maps consumer-visible capabilities to implementation anchors, executab | Direct service-call control/evidence | `ServiceCallControl.java`; `ServiceCallEvidence.java`; `BoundedJsonEvidence.java`; `ServiceCallSteps.java`; `ModularScenarios.java`; `RestAssuredUtil.java`; bridge `/v1/services/call`; Workbench `workbench_service_call` | `@control-bridge`; existing service-call features | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md`; `docs/service-call-scenarios.md` | | Semantic breakpoints | `ControlBridgeCoordinator.java`; `ControlBridgeBreakpoint.java`; `ControlHook.java`; bridge `/v1/breakpoints*`; Workbench `workbench_breakpoint_*` | `@control-bridge`; Workbench UI/MCP tests and `live-check` | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md` | | Dynamic steps/expression execution | `DynamicSteps.java`; `DynamicExecution.java`; `StepExtension.java`; tree-parsing classes | `dynamic-steps.feature`; `forms-dynamic-steps.feature` | `docs/dynamic-steps.md` | -| Selenium navigation and interaction | `BrowserSteps.java`; `NavigationSteps.java`; `ElementWrapper.java`; `HumanInteractions.java`; `SeleniumUtils.java` | `navigation.feature`; `forms-dynamic-steps.feature`; `dialogs.feature`; browser test-site pages | `docs/dynamic-steps.md`; `docs/custom-element-definitions.md` | +| Selenium navigation and interaction | `BrowserSteps.java`; `NavigationSteps.java`; `ElementWrapper.java`; `HumanInteractions.java`; `SeleniumUtils.java`; `DriverConstruction.java`; bundled `META-INF/pickleball/configs/CHROME_HEADLESS.yaml`; `ParsingMap.initializeConfigs` fallback | `navigation.feature`; `forms-dynamic-steps.feature`; `dialogs.feature`; `ChromeHeadlessConfigChecks.java`; browser test-site pages | `docs/dynamic-steps.md`; `docs/custom-element-definitions.md`; `docs/configuration.md`; `docs/config-files-and-resource-mapping.md` | | Custom element definitions/catalog context | `ExecutionDictionary.java`; `ElementMatch.java`; consumer `PickleballTests.java`; search `category(`, `inheritsFrom` | `catalog-context.feature`; `forms-dynamic-steps.feature`; `site/catalog.html` | `docs/custom-element-definitions.md`; `docs/config-files-and-resource-mapping.md` | | Mapping, ParsingMap/NodeMap, templates/directives | `MappingSteps.java`; `FileAndDataParsing.java`; `MappingProcessor.java`; `QuoteParser.java`; `NodeMap.java`; `ParsingMap.java`; `ValueFormatting.java`; `common/dataelements` | `mapping-and-resources.feature`; `mapping-value-type-preservation.feature`; `scenario-data-references.feature`; Data Element features; `QuoteParserChecks.java`; internal Java checks | `docs/mapping-and-templating.md`; `docs/data-values-and-elements.md`; `docs/data-element-query-runtime.md`; `docs/config-files-and-resource-mapping.md` | -| Configuration/profiles/RunVars | `PKB_props.java`; `PickleballProfiles.java`; `PkbPropertyValueNormalizer.java`; runner/config classes; search `pkb_profile`, `pkb_runvars`, `pkb_run_profile`, `pkb_configpath` | `configuration-system-properties.feature`; `ProfileConfigurationChecks.java`; consumer properties/profile examples | `docs/configuration.md`; `docs/getting-started.md`; `docs/ai-run-configuration.md`; `docs/consumer-project.md` | +| Configuration/profiles/RunVars | `PKB_props.java`; `PickleballProfiles.java`; `PkbPropertyValueNormalizer.java`; `ParallelCountEstimator.java`; runner/config classes; search `pkb_profile`, `pkb_runvars`, `pkb_run_profile`, `pkb_configpath`, `pkb_parallel=auto` | `configuration-system-properties.feature`; `ProfileConfigurationChecks.java`; `ParallelCountEstimatorChecks.java`; consumer properties/profile examples | `docs/configuration.md`; `docs/getting-started.md`; `docs/ai-run-configuration.md`; `docs/consumer-project.md` | | Consumer guidance export/reference snapshot | `DiagnosticCli.java`; `gradle/consumer-guidance.gradle`; `scripts/sync_consumer_guidance.py`; `maven-consumer-project/AGENTS.md`; `maven-consumer-project/.github/copilot-instructions.md`; search `export-guidance`, `discover-hint`, `GUIDANCE-MANIFEST.json`, `.pickleball/investigations` | `PickleballGuidanceChecks.java`; consumer guidance contract checks | `docs/consumer-agent-guide.md`; `docs/consumer-project.md` | | Consumer AI-agent pointer-eval harness | `maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`; tag `@agent-pointer-eval` (optional `@agent-pointer-eval-pass` / `@agent-pointer-eval-fail`) | opt-in mixed pass/fail; must not carry `@all`, `@regression`, or other Maven suite-profile tags; `scripts/verify_agent_contract.py` | `docs/consumer-agent-guide.md` | -| Diagnostic reporting and controlled reruns | `src/main/java/tools/dscode/common/reporting/diagnostic`; `InvestigationHandoff`; diagnostic aspects; `DiagnosticCli.java`; `emit-investigation`; `VisualFingerprintComparator.java`; `DiagnosticRunComparator.java` | `DiagnosticReportingChecks.java`; `Diagnostic213CompletionChecks.java`; `InvestigationHandoffChecks.java`; diagnostic features | `docs/diagnostic-reporting.md`; `docs/ai-diagnostic-reporting-plan.md`; `docs/ai-run-configuration.md`; root `AGENTS.md` | +| Diagnostic reporting and controlled reruns | `src/main/java/tools/dscode/common/reporting/diagnostic`; `InvestigationHandoff`; diagnostic aspects; `DiagnosticCli.java`; `emit-investigation`; `VisualFingerprintComparator.java`; `DiagnosticRunComparator.java`; catalog/summary `runProfile` | `DiagnosticReportingChecks.java`; `Diagnostic213CompletionChecks.java`; `InvestigationHandoffChecks.java`; diagnostic features | `docs/diagnostic-reporting.md`; `docs/ai-diagnostic-reporting-plan.md`; `docs/ai-run-configuration.md`; root `AGENTS.md` | | Nested steps/block conditionals | search `Nested`, `Conditional`, `Block`, `Condition` in core implementation | `nested-and-block-conditionals.feature` | `docs/nested-steps.md`; `docs/block-conditionals.md` | | Component scenarios/reusable RUN/selectors/markers | `ModularScenarios.java`; `ScenarioStep.java`; `ScenarioStepData.java`; `StepBase.java`; `StepExtension.java`; `CurrentScenarioState.java`; `CucumberScanUtil.java`; search `finalizerSteps`, `RunSelection` | `component-scenarios.feature`; `reusable-scenario-selection.feature`; `run-step-parameter-variations.feature`; marker features | `docs/component-scenarios.md`; `docs/service-call-scenarios.md`; `docs/data-values-and-elements.md` | | Service-call definitions/execution | `ServiceCallSteps.java`; `ModularScenarios.java`; `StepExtension.java`; `CurrentScenarioState.java`; `RestAssuredUtil.java`; mapping classes; `maven-consumer-project/src/test/resources/calls` | `service-call-execution.feature`; `run-step-parameter-variations.feature`; reusable selection/parameter features; local server support | `docs/service-call-scenarios.md`; `docs/component-scenarios.md`; `docs/mapping-and-templating.md` | diff --git a/docs/agent/repository-index.md b/docs/agent/repository-index.md index 400252cc..7ff607f3 100644 --- a/docs/agent/repository-index.md +++ b/docs/agent/repository-index.md @@ -366,6 +366,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `src/main/java/tools/dscode/cucumberextended/utilities/StringUtilities.java` - `src/main/java/tools/dscode/launcher/PickleballWorkbenchLauncher.java` - `src/main/java/tools/dscode/misc/DummySteps.java` +- `src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java` - `src/main/java/tools/dscode/parallelutilities/Stagger.java` - `src/main/java/tools/dscode/pickleruntime/CucumberOptionResolver.java` - `src/main/java/tools/dscode/registry/GlobalRegistry.java` @@ -382,6 +383,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `src/main/java/tools/dscode/testengine/PkbPropertyValueNormalizer.java` - `src/main/java/tools/dscode/testengine/SensitiveConfiguration.java` - `src/main/java/tools/dscode/testengine/WorkbenchWorkerMain.java` +- `src/main/resources/META-INF/pickleball/configs/CHROME_HEADLESS.yaml` - `src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine` - `src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener` @@ -389,6 +391,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `src/test/java/tools/dscode/control/override/StepOverrideCompilerTest.java` - `src/test/java/tools/dscode/launcher/PickleballWorkbenchLauncherTest.java` +- `src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java` - `src/test/java/tools/dscode/testengine/DynamicSuiteBootstrapWorkbenchRootTest.java` ## Control API module @@ -589,6 +592,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `maven-consumer-project/src/test/java/tools/dscode/common/dataelements/DataElementPhaseOneChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/dataelements/DataElementPhaseThreeChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/dataelements/DataElementPhaseTwoChecks.java` +- `maven-consumer-project/src/test/java/tools/dscode/common/driver/ChromeHeadlessConfigChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/mappings/MappingDataRefactorChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/mappings/QuoteParserChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/Diagnostic213CompletionChecks.java` @@ -599,6 +603,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `maven-consumer-project/src/test/java/tools/dscode/common/util/datetime/BusinessTimePostModifierChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/coredefinitions/DataTableConversionChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/coredefinitions/ModularScenariosChecks.java` +- `maven-consumer-project/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/testengine/PkbPropertyValueNormalizerChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/testengine/ProfileConfigurationChecks.java` diff --git a/docs/ai-run-configuration.md b/docs/ai-run-configuration.md index 109a9fda..6201a14e 100644 --- a/docs/ai-run-configuration.md +++ b/docs/ai-run-configuration.md @@ -243,10 +243,10 @@ Example compact rerun: -Dpkb_changed_variables=pkb_browser ``` -For an agent's bounded confirmation `mvn test` (not `PickleballTests` human defaults of `pretty` / `@all`), include diagnostic evidence controls and keep selection narrow: +For an agent's bounded confirmation `mvn test` (not `PickleballTests` human defaults of `pretty` / `@all`), include diagnostic evidence controls, headless Chrome, and high parallelism when more than one scenario will run: ```text --Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" +-Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_parallel=auto, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" ``` Lineage metadata is not execution configuration: @@ -320,3 +320,21 @@ When operating in a consumer project: - never expose protected values; - keep diagnostic lineage outside the RunVar set; - prefer the retained run profile over manually reconstructing configuration from many source layers. + +## AI agents + +Set a **complete** Discover `pkb_runvars` rather than a partial overlay: + +```text +pkb_browser=CHROME_HEADLESS +pkb_parallel= +pkb_reportingmode=diagnostic +pkb_loglevel=warn +pkb_reportretention=failed +``` + +plus the narrowest useful `pkb_tags` / `pkb_name`. `DiagnosticCli discover-hint` prints the estimated integer parallel count for the current JVM. Multi-scenario Discover/Confirm must use headless Chrome and high parallelism. Isolate / live Workbench stays one paused scenario. + +After the run, inspect `pkb_run_profile` from `run-catalog.json`, `run-index.json`, or `summary.json`. That output is the complete resolved RunVar list after inheritance (glue/features/data/call/component/configpath) and after `pkb_parallel=auto` is stamped as an integer. Do not assume omitted `pkb_runvars` keys equal project `pickleball.properties` — optional keys such as headed Chrome, `pretty`, and `pkb_tags=@all` do not leak into a controlled run, which is why agents must set the Discover keys explicitly. + +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/config-files-and-resource-mapping.md b/docs/config-files-and-resource-mapping.md index 3478dd27..9b3be1b1 100644 --- a/docs/config-files-and-resource-mapping.md +++ b/docs/config-files-and-resource-mapping.md @@ -51,6 +51,10 @@ run: pkb_runvars=pkb_configpath=,pkb_browser=firefox The controlled run uses the default `configs` resource root because the blank value intentionally suppresses the project path. +## Bundled browser configs + +Named browser yaml files under the configured config path remain the local override, including headed `CHROME.yaml`. When `CHROME_HEADLESS` is absent from that mapping, Pickleball fills it from the JAR resource `META-INF/pickleball/configs/CHROME_HEADLESS.yaml` so agents can set `pkb_browser=CHROME_HEADLESS` without copying yaml. See [Execution Configuration](configuration.md). + ## Initialization order Run configuration is resolved before the final config source is bound: diff --git a/docs/configuration.md b/docs/configuration.md index f11b2e96..456c4a5c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -329,12 +329,12 @@ The existing path semantics for `pkb_features`, `pkb_datapath`, `pkb_callpath`, | `pkb_tags` | `@smoke and not @slow` | Cucumber tag expression | | `pkb_name` | `Checkout.*` | scenario-name expression | | `pkb_environment` | `QA` | project environment label | -| `pkb_browser` | `chrome` | browser configuration | +| `pkb_browser` | `chrome` | browser configuration name looked up under the `configs` mapping (`CHROME_HEADLESS` uses the consumer yaml when present, otherwise Pickleball's bundled headless Chrome) | | `pkb_profile` | `qa,browser_firefox` | selected named profile(s) | | `pkb_runvars` | `pkb_tags=@smoke, pkb_browser=chrome` | compact controlled RunVar input | | `pkb_runvars.` | `pkb_runvars.pkb_browser=chrome` | expanded controlled RunVar member | | `pkb_run_profile` | generated assignment string | canonical resolved RunVar output; external input rejected | -| `pkb_parallel` | `4` | parallel scenario count | +| `pkb_parallel` | `4`, `auto` | parallel scenario count; `auto` resolves at run start to a conservative JVM estimate and stamps the integer into `pkb_run_profile` | | `pkb_loglevel` | `debug` | console log level | | `pkb_reportingmode` | `diagnostic` | diagnostic evidence pipeline | | `pkb_reportretention` | `all`, `failed`, `none` | automatic evidence/report retention | @@ -344,6 +344,29 @@ The existing path semantics for `pkb_features`, `pkb_datapath`, `pkb_callpath`, Other existing `pkb_*` RunVars retain their previous behavior unless specifically documented otherwise. +## Conservative `pkb_parallel` + +`pkb_parallel` is an explicit positive integer unless the value is `auto`. + +`auto` is resolved at run start from JVM-visible resources only (`Runtime.availableProcessors()` and `Runtime.maxMemory()`). No OS-specific native calls. The conservative estimate is: + +```text +max(2, min(availableProcessors, floor(maxMemoryMB / 512), 24)) +``` + +Chrome workers are RAM-heavy, so a 32-core / 64GiB box does not blindly pick 32 workers; the hard cap is 24, and heap can cap lower. Tiny heaps resolve to 2. An explicit numeric `pkb_parallel` is never overwritten. Omitting `pkb_parallel` does not enable parallel execution. + +The resolved integer is stamped into the final RunVars and `pkb_run_profile`. `DiagnosticCli discover-hint` prints that estimated number in the recommended Discover `pkb_runvars` command. + +## Bundled `CHROME_HEADLESS` + +`pkb_browser` names a configuration object under the loaded `configs` mapping. Resolution for `CHROME_HEADLESS`: + +1. If the consumer `pkb_configpath` / configs mapping already contains `CHROME_HEADLESS` (or a case-insensitive named browser yaml such as `CHROME_HEADLESS.yaml`), that local override wins, including headed chrome.yaml-style configs. +2. Otherwise Pickleball injects a framework-bundled `CHROME_HEADLESS` resource from `META-INF/pickleball/configs/CHROME_HEADLESS.yaml` inside the Pickleball JAR. + +The bundled headless config uses `--headless=new`, a fixed `--window-size=1920,1080`, no `MAXIMIZE`, and `QUIT_LOCAL_DRIVER`. Consumer `CHROME`, `EDGE`, `GRID`, and `SAUCE` yaml files are unchanged. Agents can set `pkb_browser=CHROME_HEADLESS` without copying yaml into the project. + ## Cucumber aliases Pickleball synchronizes its main selection aliases with Cucumber properties, including: diff --git a/docs/consumer-agent-guide.md b/docs/consumer-agent-guide.md index ea29adbc..467090e8 100644 --- a/docs/consumer-agent-guide.md +++ b/docs/consumer-agent-guide.md @@ -14,9 +14,9 @@ If `workbench_*` tools are already present in this session, you may use them as Use this order. Consumer AI agents for this Pickleball release discover failures with diagnostic `mvn test`. Live Workbench MCP is an optional alias when `workbench_*` tools are already connected. Do not start the Workbench GUI. -1. **Discover** — when you do not yet know which scenarios fail, or you need many/parallel scenarios: run one diagnostic `mvn test` with `pkb_runvars` (`pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, the narrowest useful `pkb_tags`/`pkb_name`, and `pkb_parallel` when the project supports it). This is not a skip of Workbench; it is how you find failures. Do not start a worker just to run the whole suite. After the run, open `run-catalog.json`, then only the relevant `run-index.json` / `summary.json`. If `workbench_*` tools already exist, you may instead read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary`. Do not ingest `events.jsonl` or screenshots to find which scenarios failed. Do not `workbench_worker_start` to read the catalog. +1. **Discover** — when you do not yet know which scenarios fail, or you need many/parallel scenarios: run one diagnostic `mvn test` with a **complete** `pkb_runvars` that always includes `pkb_browser=CHROME_HEADLESS`, a high `pkb_parallel` (the conservative JVM estimate printed by `discover-hint`, or `pkb_parallel=auto`), `pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, plus the narrowest useful `pkb_tags`/`pkb_name`. Multi-scenario Discover and Confirm MUST use headless Chrome and high parallelism. Isolate / live Workbench stays one paused scenario; do not parallelize isolate. Use headed Chrome only when the scenario actually needs a window. Set those keys explicitly so project headed-chrome / pretty / `@all` defaults do not sneak in. Execution-context keys (`pkb_glue`, `pkb_features`, `pkb_datapath`, `pkb_callpath`, `pkb_componentpath`, `pkb_configpath`) may still inherit; after the run, read `pkb_run_profile` from `run-catalog.json` / `run-index.json` / `summary.json` as the complete resolved list, including inherited glue/features and the integer `pkb_parallel`. Do not assume omitted keys equal project pickleball.properties. This is not a skip of Workbench; it is how you find failures. Do not start a worker just to run the whole suite. After the run, open `run-catalog.json`, then only the relevant `run-index.json` / `summary.json`. If `workbench_*` tools already exist, you may instead read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary`. Do not ingest `events.jsonl` or screenshots to find which scenarios failed. Do not `workbench_worker_start` to read the catalog. 2. **Isolate / debug a known failing scenario** — after discovery has named the trouble spots, isolate those scenarios with live Workbench. If `workbench_*` tools are already present, use the live MCP loop below. If they are absent, do not invent IDE MCP and do not start an IDE-owned stdio server. Stop and tell the human that live isolate needs either pre-wired `workbench_*` MCP tools or a Workbench CLI session. Do not keep using `mvn test` for isolation/debug. -3. **Confirm after isolation** — one bounded diagnostic `mvn test` with the existing AI defaults (`pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, narrowest useful `pkb_tags`/`pkb_name`). +3. **Confirm after isolation** — one bounded diagnostic `mvn test` with the same complete AI `pkb_runvars` (`pkb_browser=CHROME_HEADLESS`, high `pkb_parallel` when more than one scenario, `pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, narrowest useful `pkb_tags`/`pkb_name`). 4. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. Change the project's own features/Java only after the live buffer is right. Explicit Save is what writes a `.feature` file. Discover must work with zero MCP. Missing `workbench_*` tools is not a reason to skip Discover, and it is not a reason to self-register IDE MCP. A multi-scenario diagnostic test is a reason to run `mvn test` first; do not start a worker to run the whole suite. @@ -146,22 +146,26 @@ Never supply `pkb_run_profile` or `pkb_run_profile.` as input. They are When you launch Pickleball tests and the intended execution settings are known, use `pkb_runvars` as the authoritative input. Put intentional tag/name selection, browser, evidence/logging controls, and other non-secret RunVar changes inside `pkb_runvars`; do not default to ambient optional project settings or separate JVM `-Dpkb_*` RunVars. Use `pkb_profile` or ordinary JVM RunVar overrides only when the task specifically tests those configuration semantics or the user asks for them. Keep protected secrets and diagnostic lineage outside `pkb_runvars`. -For an agent's bounded confirmation `mvn test` (not the human runner defaults), include diagnostic evidence controls and keep the selection narrow. Documented AI defaults: +For an agent's bounded confirmation `mvn test` (not the human runner defaults), include diagnostic evidence controls, headless Chrome, and high parallelism when more than one scenario will run. Documented AI Discover/Confirm `pkb_runvars`: ```text +pkb_browser=CHROME_HEADLESS +pkb_parallel= pkb_reportingmode=diagnostic pkb_loglevel=warn 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. +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. `DiagnosticCli discover-hint` prints the estimated integer `pkb_parallel` for the current 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`). Example confirmation after a live-loop isolation: +These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after a live-loop isolation: ```text -mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" +mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_parallel=auto, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" ``` +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`. + A selected profile or partial `pkb_runvars` input inherits only missing project execution-context RunVars: ```text @@ -191,9 +195,9 @@ For AI-controlled diagnostic runs, keep terminal logging minimal. Diagnostic mod Use this escalation order: -1. `run-catalog.json` to choose relevant runs. -2. Selected `run-index.json` and `clusters.json` for outcomes, scenario identity, failure grouping, capabilities, retention, step rollups, profile fingerprints, and representative visual references. -3. Selected scenario `summary.json` when additional sparse detail is needed. +1. `run-catalog.json` to choose relevant runs. Each catalog entry includes the retained `pkb_run_profile` when present. +2. Selected `run-index.json` and `clusters.json` for outcomes, scenario identity, failure grouping, capabilities, retention, step rollups, the complete `runProfile`, profile fingerprints, and representative visual references. +3. Selected scenario `summary.json` when additional sparse detail is needed, including the same `runProfile`. 4. Relevant `events.jsonl` only when exact step/lifecycle/order/INFO+ detail remains unanswered. 5. Existing `comparisonToPrevious` or Pickleball run/fingerprint comparison before opening screenshots. 6. A representative PNG only when semantic visual meaning must be understood. @@ -232,7 +236,7 @@ DiagnosticCli rebuild `DiagnosticCli help`, `--help`, and `-h` print this same command list. -Use `guidance` to print this guide, `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, and `discover-hint` for the diagnostic `mvn test` one-liner plus `run-catalog.json` next step. Prefer `DiagnosticCli` over constructing Maven classpaths and JShell scripts for routine diagnostic operations. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. +Use `guidance` to print this guide, `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, and `discover-hint` for the complete diagnostic `mvn test` `pkb_runvars` one-liner (headless Chrome, estimated `pkb_parallel`, diagnostic evidence controls) plus `run-catalog.json` / `pkb_run_profile` next step. Prefer `DiagnosticCli` over constructing Maven classpaths and JShell scripts for routine diagnostic operations. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. ## Controlled diagnostic reruns diff --git a/docs/consumer-project.md b/docs/consumer-project.md index 3648b4eb..befba07c 100644 --- a/docs/consumer-project.md +++ b/docs/consumer-project.md @@ -155,9 +155,11 @@ mvn test -Dpkb_tags="@workflow and @nested-steps and not @block-conditionals" Human `PickleballTests` defaults remain `pretty` and `@all`. Agents launching a bounded confirmation should not reuse those defaults. Use a separate `pkb_runvars` command, for example: ```bash -mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" +mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_parallel=auto, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" ``` +After the run, read `pkb_run_profile` from `run-catalog.json` / `run-index.json` / `summary.json`. That is the complete resolved RunVar list. Do not assume omitted `pkb_runvars` keys equal project `pickleball.properties`. + The consumer `pom.xml` also defines Maven profiles such as: ```bash diff --git a/docs/diagnostic-reporting.md b/docs/diagnostic-reporting.md index 85da2c10..449fd205 100644 --- a/docs/diagnostic-reporting.md +++ b/docs/diagnostic-reporting.md @@ -52,15 +52,15 @@ reports/diagnostic-runs/ An interrupted scenario may retain raw `trace.jsonl` instead of the gzip form. `pkb_diagnostic_output` may move the diagnostic-runs root. -`run-index.json` includes sparse scenario/outcome/comparison metadata plus the sanitized final `runProfile`, deterministic `runProfileFingerprint`, and compatibility field `directRunProfile`. The field name `directRunProfile` remains for diagnostic schema compatibility even though new direct controlled input is `pkb_runvars`. +`run-index.json` includes sparse scenario/outcome/comparison metadata plus the sanitized final `runProfile`, deterministic `runProfileFingerprint`, and compatibility field `directRunProfile`. The field name `directRunProfile` remains for diagnostic schema compatibility even though new direct controlled input is `pkb_runvars`. `run-catalog.json` copies each run's `runProfile` / `runProfileFingerprint` when present. Scenario `summary.json` also includes the same `runProfile` so agents can inspect the complete resolved RunVar snapshot without opening `configuration.json`. ## AI evidence access protocol Use the shallowest evidence layer that completely answers the question: -1. `run-catalog.json` — choose candidate runs. -2. Selected `run-index.json` / `clusters.json` — outcomes, identities, failure groups, capabilities, retention, profile fingerprints, representative visuals. -3. Selected scenario `summary.json` — additional sparse detail. +1. `run-catalog.json` — choose candidate runs. Catalog entries include the retained `runProfile` when present. +2. Selected `run-index.json` / `clusters.json` — outcomes, identities, failure groups, capabilities, retention, the complete `runProfile`, profile fingerprints, representative visuals. +3. Selected scenario `summary.json` — additional sparse detail, including the same `runProfile`. 4. Targeted `events.jsonl` — exact step/lifecycle/order/INFO+ detail only when needed. 5. Existing `comparisonToPrevious`, run comparison, or fingerprint comparison. 6. Representative PNG only when semantic visual meaning matters. diff --git a/docs/pickleball-workbench.md b/docs/pickleball-workbench.md index 1d979f06..128dea62 100644 --- a/docs/pickleball-workbench.md +++ b/docs/pickleball-workbench.md @@ -410,7 +410,7 @@ workbench_step_override_remove workbench_step_override_clear ``` -Sparse diagnostic readers (do not glob `reports/diagnostic-runs`; these return JSON only and do not dump events, traces, or PNG bytes): +Sparse diagnostic readers (do not glob `reports/diagnostic-runs`; these return JSON only and do not dump events, traces, or PNG bytes). Catalog, run-index, and summary payloads include the retained `pkb_run_profile` when present: ```text workbench_diagnostic_catalog diff --git a/maven-consumer-project/src/test/java/com/example/pickleball/InternalFrameworkTestSteps.java b/maven-consumer-project/src/test/java/com/example/pickleball/InternalFrameworkTestSteps.java index c3c30326..116fbc17 100644 --- a/maven-consumer-project/src/test/java/com/example/pickleball/InternalFrameworkTestSteps.java +++ b/maven-consumer-project/src/test/java/com/example/pickleball/InternalFrameworkTestSteps.java @@ -9,6 +9,7 @@ import tools.dscode.common.dataelements.DataElementPhaseOneChecks; import tools.dscode.common.dataelements.DataElementPhaseThreeChecks; import tools.dscode.common.dataelements.DataElementPhaseTwoChecks; +import tools.dscode.common.driver.ChromeHeadlessConfigChecks; import tools.dscode.common.mappings.MappingDataRefactorChecks; import tools.dscode.common.mappings.QuoteParserChecks; import tools.dscode.common.reporting.diagnostic.Diagnostic213CompletionChecks; @@ -19,6 +20,7 @@ import tools.dscode.common.util.datetime.BusinessTemporalDeltaChecks; import tools.dscode.common.util.datetime.BusinessTimePostModifierChecks; import tools.dscode.coredefinitions.ModularScenariosChecks; +import tools.dscode.parallelutilities.ParallelCountEstimatorChecks; import tools.dscode.testengine.PkbPropertyValueNormalizerChecks; import tools.dscode.testengine.ProfileConfigurationChecks; @@ -47,6 +49,8 @@ public static void runInternalPickleballJavaTests() { BusinessTimePostModifierChecks.class, PkbPropertyValueNormalizerChecks.class, ProfileConfigurationChecks.class, + ParallelCountEstimatorChecks.class, + ChromeHeadlessConfigChecks.class, DynamicControlApiChecks.class, ControlRuntimeObserverChecks.class ); diff --git a/maven-consumer-project/src/test/java/tools/dscode/common/driver/ChromeHeadlessConfigChecks.java b/maven-consumer-project/src/test/java/tools/dscode/common/driver/ChromeHeadlessConfigChecks.java new file mode 100644 index 00000000..2bc6d0bd --- /dev/null +++ b/maven-consumer-project/src/test/java/tools/dscode/common/driver/ChromeHeadlessConfigChecks.java @@ -0,0 +1,89 @@ +package tools.dscode.common.driver; + +import org.junit.jupiter.api.Test; +import tools.dscode.common.mappings.ParsingMap; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public final class ChromeHeadlessConfigChecks { + @Test + void bundledChromeHeadlessIsUsedWhenConsumerConfigOmitsIt() throws Exception { + Path configRoot = Files.createTempDirectory("pkb-bundled-chrome-headless-"); + Files.writeString(configRoot.resolve("URL.yaml"), "home: http://example.test\n"); + try { + ParsingMap.initializeConfigs(configRoot.toString()); + ParsingMap globals = ParsingMap.getGlobalsParsingmap(); + + assertEquals("CREATE_LOCAL_DRIVER", String.valueOf(globals.get("configs.CHROME_HEADLESS.constructor"))); + assertEquals("chrome", String.valueOf(globals.get("configs.CHROME_HEADLESS.browser"))); + Object args = globals.get("configs.CHROME_HEADLESS.driver.options.args"); + assertTrue(containsText(args, "--headless=new")); + assertTrue(containsText(args, "--window-size=1920,1080")); + assertFalse(containsText(globals.get("configs.CHROME_HEADLESS.postActions"), "MAXIMIZE")); + assertTrue(containsText(globals.get("configs.CHROME_HEADLESS.cleanup"), "QUIT_LOCAL_DRIVER")); + assertEquals("http://example.test", String.valueOf(globals.get("configs.URL.home"))); + } finally { + ParsingMap.initializeConfigs("configs"); + Files.deleteIfExists(configRoot.resolve("URL.yaml")); + Files.deleteIfExists(configRoot); + } + } + + @Test + void consumerChromeHeadlessYamlOverridesTheBundledDefault() throws Exception { + Path configRoot = Files.createTempDirectory("pkb-override-chrome-headless-"); + Files.writeString(configRoot.resolve("CHROME_HEADLESS.yaml"), """ + constructor: CREATE_LOCAL_DRIVER + browser: chrome + marker: consumer-override + """); + try { + ParsingMap.initializeConfigs(configRoot.toString()); + ParsingMap globals = ParsingMap.getGlobalsParsingmap(); + assertEquals("consumer-override", String.valueOf(globals.get("configs.CHROME_HEADLESS.marker"))); + assertEquals("null", String.valueOf(globals.get("configs.CHROME_HEADLESS.driver"))); + } finally { + ParsingMap.initializeConfigs("configs"); + Files.deleteIfExists(configRoot.resolve("CHROME_HEADLESS.yaml")); + Files.deleteIfExists(configRoot); + } + } + + @Test + void namedConsumerBrowserYamlStillLoadsAlongsideBundledHeadless() throws Exception { + Path configRoot = Files.createTempDirectory("pkb-chrome-and-headless-"); + Files.writeString(configRoot.resolve("CHROME.yaml"), """ + constructor: CREATE_LOCAL_DRIVER + browser: chrome + postActions: + - "MAXIMIZE" + """); + try { + ParsingMap.initializeConfigs(configRoot.toString()); + ParsingMap globals = ParsingMap.getGlobalsParsingmap(); + assertTrue(containsText(globals.get("configs.CHROME.postActions"), "MAXIMIZE")); + assertTrue(containsText(globals.get("configs.CHROME_HEADLESS.driver.options.args"), "--headless=new")); + assertFalse(containsText(globals.get("configs.CHROME_HEADLESS.postActions"), "MAXIMIZE")); + } finally { + ParsingMap.initializeConfigs("configs"); + Files.deleteIfExists(configRoot.resolve("CHROME.yaml")); + Files.deleteIfExists(configRoot); + } + } + + private static boolean containsText(Object value, String expected) { + if (value == null) { + return false; + } + if (value instanceof List list) { + return list.stream().map(String::valueOf).anyMatch(item -> item.contains(expected)); + } + return String.valueOf(value).contains(expected); + } +} diff --git a/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/Diagnostic213CompletionChecks.java b/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/Diagnostic213CompletionChecks.java index a9182126..63def272 100644 --- a/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/Diagnostic213CompletionChecks.java +++ b/maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/Diagnostic213CompletionChecks.java @@ -583,6 +583,15 @@ void runProfileMetadataIsSparseAndRebuildable() throws Exception { Map rebuiltComparison = (Map) rebuilt.get("comparisonMetadata"); assertEquals(fingerprint, rebuiltComparison.get("runProfileFingerprint")); assertEquals(Boolean.TRUE, rebuiltComparison.get("directRunProfile")); + + @SuppressWarnings("unchecked") + Map catalog = JSON.readValue( + runRoot.getParent().resolve("run-catalog.json").toFile(), LinkedHashMap.class); + @SuppressWarnings("unchecked") + List> catalogRuns = (List>) catalog.get("runs"); + assertFalse(catalogRuns.isEmpty()); + assertEquals(runProfile, catalogRuns.getFirst().get("runProfile")); + assertEquals(fingerprint, catalogRuns.getFirst().get("runProfileFingerprint")); } finally { deleteTree(root); } 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 345b86e9..74e282f7 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 @@ -66,6 +66,8 @@ void dependencyPrintsCanonicalAgentGuide() { assertTrue(guide.contains("PickleballWorkbenchLauncher")); assertTrue(guide.contains("does not auto-watch")); assertTrue(guide.contains("pkb_parallel")); + assertTrue(guide.contains("CHROME_HEADLESS")); + assertTrue(guide.contains("pkb_run_profile")); String chooser = guide.substring(0, guide.indexOf("Generated guidance lifecycle")); assertFalse(chooser.contains("attach.json")); assertFalse(chooser.contains("ui .")); @@ -79,6 +81,11 @@ void dependencyPrintsCanonicalAgentGuide() { assertTrue(chooserList.contains("run-catalog.json")); assertTrue(chooserList.contains("after discovery has named the trouble spots")); assertTrue(chooserList.contains("Do not start a worker just to run the whole suite")); + assertTrue(chooserList.contains("CHROME_HEADLESS")); + assertTrue(chooserList.contains("pkb_parallel")); + assertTrue(chooserList.contains("do not parallelize isolate") + || chooserList.contains("Do not parallelize isolate")); + assertTrue(chooserList.contains("pkb_run_profile")); assertTrue(guide.contains("Maintainer pointer-eval harness")); assertTrue(guide.contains("@agent-pointer-eval")); String liveLoop = guide.substring( @@ -161,8 +168,15 @@ void discoverHintPrintsDiagnosticMvnTestAndRunCatalogNext() { String text = output.toString(StandardCharsets.UTF_8); assertTrue(text.contains("mvn test")); assertTrue(text.contains("pkb_runvars")); + assertTrue(text.contains("pkb_browser=CHROME_HEADLESS")); + assertTrue(text.contains("pkb_parallel=" + tools.dscode.parallelutilities.ParallelCountEstimator.estimate())); assertTrue(text.contains("pkb_reportingmode=diagnostic")); - assertTrue(text.contains("pkb_parallel")); + assertTrue(text.contains("pkb_loglevel=warn")); + assertTrue(text.contains("pkb_reportretention=failed")); + assertTrue(text.contains("pkb_run_profile")); + assertFalse(text.contains("pkb_parallel=80")); + assertFalse(text.contains("when the project supports it")); + assertFalse(text.contains("when the project supports it")); assertTrue(text.contains("reports/diagnostic-runs/run-catalog.json")); assertTrue(text.contains("NEXT: run the diagnostic mvn test above, then open run-catalog.json")); assertFalse(text.toLowerCase().contains("mcp")); 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 new file mode 100644 index 00000000..1b9d4196 --- /dev/null +++ b/maven-consumer-project/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorChecks.java @@ -0,0 +1,57 @@ +package tools.dscode.parallelutilities; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public final class ParallelCountEstimatorChecks { + @Test + void tinyHeapYieldsMinimumWorkers() { + assertEquals(2, ParallelCountEstimator.estimate(4, 128L * 1024 * 1024)); + assertEquals(2, ParallelCountEstimator.estimate(8, 256L * 1024 * 1024)); + } + + @Test + void manyCoresAndLargeHeapAreCappedAtTwentyFour() { + assertEquals(24, ParallelCountEstimator.estimate(32, 64L * 1024 * 1024 * 1024)); + assertEquals(24, ParallelCountEstimator.estimate(128, 64L * 1024 * 1024 * 1024)); + } + + @Test + void memoryCanCapBelowCoreCountBeforeTheHardCap() { + assertEquals(4, ParallelCountEstimator.estimate(16, 2048L * 1024 * 1024)); + } + + @Test + void explicitNumericIsNotOverwritten() { + assertEquals(7, ParallelCountEstimator.resolve("7")); + assertEquals(1, ParallelCountEstimator.resolve("1")); + } + + @Test + void autoResolvesToTheLiveEstimate() { + assertEquals(ParallelCountEstimator.estimate(), ParallelCountEstimator.resolve("auto")); + assertEquals(ParallelCountEstimator.estimate(), ParallelCountEstimator.resolve(" AUTO ")); + assertTrue(ParallelCountEstimator.isAuto("auto")); + assertFalse(ParallelCountEstimator.isAuto("8")); + } + + @Test + void invalidParallelValuesFailClearly() { + assertThrows(IllegalArgumentException.class, () -> ParallelCountEstimator.resolve("nope")); + assertThrows(IllegalArgumentException.class, () -> ParallelCountEstimator.resolve("0")); + } + + @Test + void recommendedDiscoverRunVarsIncludeEstimatedParallel() { + String runVars = ParallelCountEstimator.recommendedDiscoverRunVars(); + assertTrue(runVars.contains("pkb_browser=CHROME_HEADLESS")); + 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")); + } +} diff --git a/maven-consumer-project/src/test/java/tools/dscode/testengine/ProfileConfigurationChecks.java b/maven-consumer-project/src/test/java/tools/dscode/testengine/ProfileConfigurationChecks.java index 2e258448..0e6367cb 100644 --- a/maven-consumer-project/src/test/java/tools/dscode/testengine/ProfileConfigurationChecks.java +++ b/maven-consumer-project/src/test/java/tools/dscode/testengine/ProfileConfigurationChecks.java @@ -563,6 +563,30 @@ void configPathCanRebindTheStableConfigsNamespace() throws Exception { } } + @Test + void autoParallelResolvesToEstimatedIntegerInFinalRunVars() { + LinkedHashMap values = baseValues(); + values.put(PKB_RUN_VARS, "pkb_tags=@direct, pkb_parallel=auto"); + + PickleballProfiles.apply(values); + + String resolved = values.get(PKB_PARALLEL); + assertEquals(Integer.toString(tools.dscode.parallelutilities.ParallelCountEstimator.estimate()), resolved); + assertFalse("auto".equalsIgnoreCase(resolved)); + assertTrue(PickleballProfiles.serializeRunProfile(values).contains("pkb_parallel=" + resolved)); + } + + @Test + void explicitNumericParallelIsNotOverwritten() { + LinkedHashMap values = baseValues(); + values.put(PKB_RUN_VARS, "pkb_tags=@direct, pkb_parallel=9"); + + PickleballProfiles.apply(values); + + assertEquals("9", values.get(PKB_PARALLEL)); + assertTrue(PickleballProfiles.serializeRunProfile(values).contains("pkb_parallel=9")); + } + private static LinkedHashMap baseValues() { LinkedHashMap values = new LinkedHashMap<>(); values.put(PKB_GLUE, "com.example.pickleball"); diff --git a/pickleball-workbench/src/main/java/tools/dscode/workbench/mcp/WorkbenchMcpTools.java b/pickleball-workbench/src/main/java/tools/dscode/workbench/mcp/WorkbenchMcpTools.java index cbbf03c5..29d00b23 100644 --- a/pickleball-workbench/src/main/java/tools/dscode/workbench/mcp/WorkbenchMcpTools.java +++ b/pickleball-workbench/src/main/java/tools/dscode/workbench/mcp/WorkbenchMcpTools.java @@ -195,15 +195,15 @@ private void register() { args -> Map.of("removed", services.clearStepOverrides())); add("workbench_diagnostic_catalog", - "Read reports/diagnostic-runs/run-catalog.json as sparse JSON. Do not glob the diagnostic tree.", + "Read reports/diagnostic-runs/run-catalog.json as sparse JSON, including each run's pkb_run_profile when retained. Do not glob the diagnostic tree.", schema(Map.of()), args -> services.diagnosticCatalog()); add("workbench_diagnostic_run", - "Read one run's run-index.json and clusters.json. Does not return events, traces, or screenshots.", + "Read one run's run-index.json and clusters.json, including the retained pkb_run_profile. Does not return events, traces, or screenshots.", schema(Map.of("runId", stringProperty("Diagnostic run directory name from the catalog.")), "runId"), args -> services.diagnosticRun(text(args, "runId"))); add("workbench_diagnostic_summary", - "Read one scenario summary.json. Does not return events.jsonl, traces, or PNG bytes.", + "Read one scenario summary.json, including the retained pkb_run_profile. Does not return events.jsonl, traces, or PNG bytes.", schema(Map.of( "runId", stringProperty("Diagnostic run directory name from the catalog."), "scenarioId", stringProperty("Scenario directory name under that run.") diff --git a/pickleball-workbench/src/test/java/tools/dscode/workbench/diagnostics/DiagnosticEvidenceNavigatorTest.java b/pickleball-workbench/src/test/java/tools/dscode/workbench/diagnostics/DiagnosticEvidenceNavigatorTest.java index 3cba86ff..a886270d 100644 --- a/pickleball-workbench/src/test/java/tools/dscode/workbench/diagnostics/DiagnosticEvidenceNavigatorTest.java +++ b/pickleball-workbench/src/test/java/tools/dscode/workbench/diagnostics/DiagnosticEvidenceNavigatorTest.java @@ -57,16 +57,16 @@ void sparseReadersReturnCatalogIndexClustersAndSummaryWithoutEventsOrScreenshots Path scenario = run.resolve("scenarios/scenario-1"); Files.createDirectories(scenario.resolve("screenshots")); Files.writeString(root.resolve("run-catalog.json"), """ - {"runs":[{"runId":"run-1","outcome":"FAILED"}]} + {"runs":[{"runId":"run-1","outcome":"FAILED","runProfile":"pkb_browser=CHROME_HEADLESS, pkb_parallel=4"}]} """); Files.writeString(run.resolve("run-index.json"), """ - {"runId":"run-1","outcome":"FAILED","scenarioCount":1} + {"runId":"run-1","outcome":"FAILED","scenarioCount":1,"runProfile":"pkb_browser=CHROME_HEADLESS, pkb_parallel=4"} """); Files.writeString(run.resolve("clusters.json"), """ {"clusters":[{"id":"c1","size":1}]} """); Files.writeString(scenario.resolve("summary.json"), """ - {"scenarioId":"scenario-1","outcome":"FAILED","lastStepText":"Then stay"} + {"scenarioId":"scenario-1","outcome":"FAILED","lastStepText":"Then stay","runProfile":"pkb_browser=CHROME_HEADLESS, pkb_parallel=4"} """); Files.writeString(scenario.resolve("events.jsonl"), "{\"stepText\":\"secret-event\"}\n"); Files.write(scenario.resolve("screenshots/frame-1.png"), new byte[]{9, 9, 9}); @@ -74,16 +74,19 @@ void sparseReadersReturnCatalogIndexClustersAndSummaryWithoutEventsOrScreenshots DiagnosticEvidenceNavigator navigator = new DiagnosticEvidenceNavigator(project); String catalog = navigator.catalogDocument().toString(); assertTrue(catalog.contains("run-1")); + assertTrue(catalog.contains("pkb_parallel=4")); assertFalse(catalog.contains("secret-event")); String runDocument = navigator.runDocument("run-1").toString(); assertTrue(runDocument.contains("FAILED")); + assertTrue(runDocument.contains("pkb_parallel=4")); assertTrue(runDocument.contains("\"clusters\"")); assertFalse(runDocument.contains("secret-event")); assertFalse(runDocument.contains("frame-1.png")); String summary = navigator.scenarioSummaryDocument("run-1", "scenario-1").toString(); assertTrue(summary.contains("Then stay")); + assertTrue(summary.contains("pkb_parallel=4")); assertFalse(summary.contains("secret-event")); assertFalse(summary.contains("frame-1.png")); } diff --git a/pickleball-workbench/src/test/java/tools/dscode/workbench/mcp/WorkbenchMcpServerTest.java b/pickleball-workbench/src/test/java/tools/dscode/workbench/mcp/WorkbenchMcpServerTest.java index c23b109b..18aef460 100644 --- a/pickleball-workbench/src/test/java/tools/dscode/workbench/mcp/WorkbenchMcpServerTest.java +++ b/pickleball-workbench/src/test/java/tools/dscode/workbench/mcp/WorkbenchMcpServerTest.java @@ -277,6 +277,12 @@ void diagnosticToolsDelegateToSharedServicesAndStaySparse() throws Exception { WorkbenchMcpTools tools = new WorkbenchMcpTools(services, JSON); assertTrue(tools.names().contains("workbench_diagnostic_catalog")); assertTrue(tools.names().contains("workbench_investigation_emit")); + tools.specifications().stream() + .filter(spec -> spec.tool().name().startsWith("workbench_diagnostic_")) + .forEach(spec -> assertTrue( + spec.tool().description().contains("pkb_run_profile"), + spec.tool().name() + " should surface pkb_run_profile" + )); Object catalog = tools.call("workbench_diagnostic_catalog", Map.of()); assertTrue(catalogCalled.get()); assertTrue(JSON.writeValueAsString(catalog).contains("run-1")); diff --git a/src/main/java/tools/dscode/common/mappings/ParsingMap.java b/src/main/java/tools/dscode/common/mappings/ParsingMap.java index 5c880b29..bb64ddbe 100644 --- a/src/main/java/tools/dscode/common/mappings/ParsingMap.java +++ b/src/main/java/tools/dscode/common/mappings/ParsingMap.java @@ -28,6 +28,7 @@ import tools.dscode.common.treeparsing.parsedComponents.DataElementMatch; import tools.dscode.common.treeparsing.parsedComponents.ElementMatch; +import java.io.InputStream; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; @@ -71,12 +72,64 @@ public class ParsingMap extends MappingProcessor { @Deprecated public static final String configsRoot = CONFIGS_MAP_ROOT; + public static final String BUNDLED_CHROME_HEADLESS_RESOURCE = + "META-INF/pickleball/configs/CHROME_HEADLESS.yaml"; + public static final String CHROME_HEADLESS_CONFIG_KEY = "CHROME_HEADLESS"; + public static synchronized void initializeConfigs(String configuredPath) { String path = configuredPath == null || configuredPath.isBlank() ? DEFAULT_CONFIG_PATH : configuredPath.trim(); JsonNode configsNode = loadConfigs(path); - GLOBALS.root.set(CONFIGS_MAP_ROOT, configsNode); + ObjectNode configs; + if (configsNode instanceof ObjectNode objectNode) { + configs = objectNode; + } else if (configsNode == null || configsNode.isNull()) { + configs = MAPPER.createObjectNode(); + } else { + GLOBALS.root.set(CONFIGS_MAP_ROOT, configsNode); + return; + } + applyBundledBrowserDefaults(configs); + GLOBALS.root.set(CONFIGS_MAP_ROOT, configs); + } + + static void applyBundledBrowserDefaults(ObjectNode configs) { + if (configs == null || hasConfigKeyIgnoreCase(configs, CHROME_HEADLESS_CONFIG_KEY)) { + return; + } + JsonNode bundled = loadBundledChromeHeadless(); + if (bundled != null && !bundled.isNull()) { + configs.set(CHROME_HEADLESS_CONFIG_KEY, bundled); + } + } + + private static boolean hasConfigKeyIgnoreCase(ObjectNode configs, String key) { + if (configs.has(key)) { + return true; + } + var names = configs.fieldNames(); + while (names.hasNext()) { + if (names.next().equalsIgnoreCase(key)) { + return true; + } + } + return false; + } + + private static JsonNode loadBundledChromeHeadless() { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) { + loader = ParsingMap.class.getClassLoader(); + } + try (InputStream in = loader.getResourceAsStream(BUNDLED_CHROME_HEADLESS_RESOURCE)) { + if (in == null) { + return null; + } + return FileAndDataParsing.YAML_MAPPER.readTree(in); + } catch (Exception ignored) { + return null; + } } private static JsonNode loadConfigs(String path) { 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 79a85edb..734bbc55 100644 --- a/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticCli.java +++ b/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticCli.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import tools.dscode.control.protocol.InvestigationHandoff; +import tools.dscode.parallelutilities.ParallelCountEstimator; import java.io.BufferedReader; import java.io.IOException; @@ -132,8 +133,12 @@ private static int exportGuidance(String[] args, PrintStream out, PrintStream er private static int discoverHint(String[] args, PrintStream out) { requireLength(args, 1, 1, "discover-hint"); - out.println("Recommended diagnostic Discover command (`pkb_runvars`; parallel is OK when the project supports it):"); - out.println("mvn test -Dpkb_runvars=\"pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed, pkb_parallel=80\""); + String runVars = ParallelCountEstimator.recommendedDiscoverRunVars(); + out.println("Recommended complete diagnostic Discover `pkb_runvars` (set these explicitly so headed Chrome / pretty / @all project defaults do not sneak in):"); + out.println("mvn test -Dpkb_runvars=\"" + runVars + "\""); + out.println(); + out.println("Multi-scenario Discover/Confirm must use CHROME_HEADLESS and this high pkb_parallel. Isolate / live Workbench stays one paused scenario — do not parallelize isolate. Headed Chrome only when the scenario actually needs a window."); + out.println("Execution-context keys (glue/features/data/call/component/configpath) may still inherit. After the run, read pkb_run_profile from run-catalog.json / run-index.json / summary.json for the complete resolved list, including inherited paths and the integer pkb_parallel. Do not assume omitted keys equal project pickleball.properties. Never supply pkb_run_profile as input."); out.println(); out.println("After the run, read reports/diagnostic-runs/run-catalog.json, then only the relevant run-index.json / summary.json."); out.println("NEXT: run the diagnostic mvn test above, then open run-catalog.json"); diff --git a/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticIndexRebuilder.java b/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticIndexRebuilder.java index 6dba889d..84e44d2d 100644 --- a/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticIndexRebuilder.java +++ b/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticIndexRebuilder.java @@ -96,7 +96,8 @@ public static void rebuildRunCatalog(Path runsRoot) throws IOException { Map summary = new LinkedHashMap<>(); for (String key : List.of( "runId", "outcome", "completion", "startedAt", "reportRetention", - "configurationHash", "comparisonMetadata", "counts", "lineage" + "configurationHash", "runProfile", "runProfileFingerprint", + "comparisonMetadata", "counts", "lineage" )) { summary.put(key, index.get(key)); } @@ -225,10 +226,36 @@ private static Map recoverScenarioSummary(Path scenarioRoot) thr } catch (Throwable ignored) { } } + copyRunProfileIfMissing(summary, scenarioRoot); writeAtomic(summaryPath, summary); return summary; } + private static void copyRunProfileIfMissing(Map summary, Path scenarioRoot) { + if (!text(summary.get("runProfile")).isBlank()) { + return; + } + Path scenariosRoot = scenarioRoot.getParent(); + Path runRoot = scenariosRoot == null ? null : scenariosRoot.getParent(); + if (runRoot == null) { + return; + } + for (String name : List.of("configuration.json", "run-index.json")) { + Path source = runRoot.resolve(name); + if (!Files.isRegularFile(source)) { + continue; + } + try { + Object runProfile = readMap(source).get("runProfile"); + if (runProfile != null && !text(runProfile).isBlank()) { + summary.put("runProfile", runProfile); + return; + } + } catch (IOException ignored) { + } + } + } + private static List> recoverRepresentatives(List> screenshots) { if (screenshots.isEmpty()) return List.of(); List> result = new ArrayList<>(); diff --git a/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticReporter.java b/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticReporter.java index b3e33a65..7fd81bb6 100644 --- a/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticReporter.java +++ b/src/main/java/tools/dscode/common/reporting/diagnostic/DiagnosticReporter.java @@ -681,7 +681,11 @@ private void writeRunCatalog() { @SuppressWarnings("unchecked") Map index = JSON.readValue(indexPath.toFile(), LinkedHashMap.class); Map summary = new LinkedHashMap<>(); - for (String key : List.of("runId", "outcome", "completion", "startedAt", "reportRetention", "configurationHash", "comparisonMetadata", "counts", "lineage")) { + for (String key : List.of( + "runId", "outcome", "completion", "startedAt", "reportRetention", + "configurationHash", "runProfile", "runProfileFingerprint", + "comparisonMetadata", "counts", "lineage" + )) { summary.put(key, index.get(key)); } summary.put("runIndex", path.getFileName() + "/run-index.json"); @@ -920,7 +924,11 @@ private void writeClusters() throws IOException { private void writeScenarioSummary(ScenarioSummary summary) { try { - writeJsonAtomic(summaryPath(summary.executionId), summary.fullMap()); + Map body = summary.fullMap(); + if (!runProfile.isBlank()) { + body.put("runProfile", runProfile); + } + writeJsonAtomic(summaryPath(summary.executionId), body); } catch (Throwable t) { failEvidence("write scenario summary", t); } diff --git a/src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java b/src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java new file mode 100644 index 00000000..3ec82c0c --- /dev/null +++ b/src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java @@ -0,0 +1,66 @@ +package tools.dscode.parallelutilities; + +/** + * Conservative high parallel-count estimate from JVM-visible CPU and heap. + * + *

Chrome workers are RAM-heavy, so this never blindly equals core count on a + * large box. Formula:

+ * + *
+ * max(2, min(availableProcessors, floor(maxMemoryMB / 512), 24))
+ * 
+ * + *

{@code pkb_parallel=auto} resolves to this estimate at run start. An + * explicit positive integer is left unchanged. Omitting {@code pkb_parallel} + * does not enable parallel execution.

+ */ +public final class ParallelCountEstimator { + public static final String AUTO_VALUE = "auto"; + public static final int MIN_WORKERS = 2; + public static final int MAX_WORKERS = 24; + public static final int MEMORY_MB_PER_WORKER = 512; + + private ParallelCountEstimator() { + } + + public static int estimate() { + Runtime runtime = Runtime.getRuntime(); + return estimate(runtime.availableProcessors(), runtime.maxMemory()); + } + + public static int estimate(int availableProcessors, long maxMemoryBytes) { + int cores = Math.max(1, availableProcessors); + long maxMemoryMb = Math.max(0L, maxMemoryBytes / (1024L * 1024L)); + int fromMemory = (int) Math.min(Integer.MAX_VALUE, maxMemoryMb / MEMORY_MB_PER_WORKER); + int capped = Math.min(cores, Math.min(fromMemory, MAX_WORKERS)); + return Math.max(MIN_WORKERS, capped); + } + + public static boolean isAuto(String value) { + return value != null && AUTO_VALUE.equalsIgnoreCase(value.trim()); + } + + /** Resolve {@code auto} or return the explicit positive integer. */ + public static int resolve(String configured) { + if (configured == null || configured.isBlank() || isAuto(configured)) { + return estimate(); + } + try { + int parsed = Integer.parseInt(configured.trim()); + if (parsed < 1) { + throw new IllegalArgumentException( + "pkb_parallel must be a positive integer or 'auto', but was: " + configured); + } + return parsed; + } catch (NumberFormatException exception) { + throw new IllegalArgumentException( + "pkb_parallel must be a positive integer or 'auto', but was: " + configured, + exception); + } + } + + public static String recommendedDiscoverRunVars() { + return "pkb_browser=CHROME_HEADLESS, pkb_parallel=" + estimate() + + ", pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed"; + } +} diff --git a/src/main/java/tools/dscode/testengine/PickleballProfiles.java b/src/main/java/tools/dscode/testengine/PickleballProfiles.java index 0cf05b8c..f322440f 100644 --- a/src/main/java/tools/dscode/testengine/PickleballProfiles.java +++ b/src/main/java/tools/dscode/testengine/PickleballProfiles.java @@ -7,6 +7,7 @@ import tools.dscode.common.mappings.MapConfigurations; import tools.dscode.common.mappings.MappingProcessor; import tools.dscode.common.mappings.NodeMap; +import tools.dscode.parallelutilities.ParallelCountEstimator; import java.io.InputStream; import java.net.URL; @@ -36,6 +37,7 @@ import static tools.dscode.testengine.PKB_props.PKB_FEATURES; import static tools.dscode.testengine.PKB_props.PKB_GLUE; import static tools.dscode.testengine.PKB_props.PKB_OPTIONS; +import static tools.dscode.testengine.PKB_props.PKB_PARALLEL; import static tools.dscode.testengine.PKB_props.PKB_PREFIX; import static tools.dscode.testengine.PKB_props.PKB_PROFILE; import static tools.dscode.testengine.PKB_props.PKB_RUN_PROFILE; @@ -132,6 +134,7 @@ static Resolution apply( restoreProtectedReferences(composed, defaultProfile); ObjectNode resolved = resolveProfile(composed, registry, directReferenceContext); + stampResolvedParallel(resolved); Map finalRunVars = toRunVarMap(resolved); clearManagedValues(values); @@ -544,6 +547,21 @@ private static String unresolvedPkbReference(String value) { return null; } + private static void stampResolvedParallel(ObjectNode resolved) { + if (resolved == null || !resolved.has(PKB_PARALLEL)) { + return; + } + JsonNode configured = resolved.get(PKB_PARALLEL); + if (configured == null || configured.isNull()) { + return; + } + String text = configured.asText(); + if (text == null || text.isBlank()) { + return; + } + resolved.put(PKB_PARALLEL, Integer.toString(ParallelCountEstimator.resolve(text))); + } + private static Map toRunVarMap(ObjectNode profile) { LinkedHashMap out = new LinkedHashMap<>(); profile.fields().forEachRemaining(entry -> { diff --git a/src/main/java/tools/dscode/testengine/PickleballRunner.java b/src/main/java/tools/dscode/testengine/PickleballRunner.java index 81513d4b..adc77cb1 100644 --- a/src/main/java/tools/dscode/testengine/PickleballRunner.java +++ b/src/main/java/tools/dscode/testengine/PickleballRunner.java @@ -4,6 +4,7 @@ import io.cucumber.core.runner.CurrentScenarioState; import tools.dscode.common.mappings.ParsingMap; import tools.dscode.common.reporting.logging.Level; +import tools.dscode.parallelutilities.ParallelCountEstimator; import java.io.InputStream; import java.net.URL; @@ -332,13 +333,12 @@ private void applyPkbAliases() { String parallel = get(PKB_PARALLEL); if (parallel != null && !parallel.isBlank()) { - String trimmed = parallel.trim(); - Integer.parseInt(trimmed); - values.put(PKB_PARALLEL, trimmed); + String resolved = Integer.toString(ParallelCountEstimator.resolve(parallel)); + values.put(PKB_PARALLEL, resolved); values.putIfAbsent(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true"); values.putIfAbsent(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed"); - values.putIfAbsent(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, trimmed); - values.putIfAbsent(PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, trimmed); + values.putIfAbsent(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, resolved); + values.putIfAbsent(PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, resolved); } } diff --git a/src/main/resources/META-INF/pickleball/configs/CHROME_HEADLESS.yaml b/src/main/resources/META-INF/pickleball/configs/CHROME_HEADLESS.yaml new file mode 100644 index 00000000..feb71030 --- /dev/null +++ b/src/main/resources/META-INF/pickleball/configs/CHROME_HEADLESS.yaml @@ -0,0 +1,28 @@ +constructor: CREATE_LOCAL_DRIVER + +browser: chrome + +driver: + capabilities: + browserName: chrome + acceptInsecureCerts: true + pageLoadStrategy: normal + options: + args: + - "--headless=new" + - "--window-size=1920,1080" + - "--disable-notifications" + - "--disable-popup-blocking" + - "--remote-allow-origins=*" + service: + port: 0 + verbose: false + +postActions: + - "CLEAR_COOKIES" + - "IMPLICIT_WAIT: 5000" + - "PAGE_LOAD_TIMEOUT: 30000" + - "SCRIPT_TIMEOUT: 30000" + +cleanup: + - "QUIT_LOCAL_DRIVER" 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 ea29adbc..467090e8 100644 --- a/src/main/resources/META-INF/pickleball/guidance/AGENT-GUIDE.md +++ b/src/main/resources/META-INF/pickleball/guidance/AGENT-GUIDE.md @@ -14,9 +14,9 @@ If `workbench_*` tools are already present in this session, you may use them as Use this order. Consumer AI agents for this Pickleball release discover failures with diagnostic `mvn test`. Live Workbench MCP is an optional alias when `workbench_*` tools are already connected. Do not start the Workbench GUI. -1. **Discover** — when you do not yet know which scenarios fail, or you need many/parallel scenarios: run one diagnostic `mvn test` with `pkb_runvars` (`pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, the narrowest useful `pkb_tags`/`pkb_name`, and `pkb_parallel` when the project supports it). This is not a skip of Workbench; it is how you find failures. Do not start a worker just to run the whole suite. After the run, open `run-catalog.json`, then only the relevant `run-index.json` / `summary.json`. If `workbench_*` tools already exist, you may instead read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary`. Do not ingest `events.jsonl` or screenshots to find which scenarios failed. Do not `workbench_worker_start` to read the catalog. +1. **Discover** — when you do not yet know which scenarios fail, or you need many/parallel scenarios: run one diagnostic `mvn test` with a **complete** `pkb_runvars` that always includes `pkb_browser=CHROME_HEADLESS`, a high `pkb_parallel` (the conservative JVM estimate printed by `discover-hint`, or `pkb_parallel=auto`), `pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, plus the narrowest useful `pkb_tags`/`pkb_name`. Multi-scenario Discover and Confirm MUST use headless Chrome and high parallelism. Isolate / live Workbench stays one paused scenario; do not parallelize isolate. Use headed Chrome only when the scenario actually needs a window. Set those keys explicitly so project headed-chrome / pretty / `@all` defaults do not sneak in. Execution-context keys (`pkb_glue`, `pkb_features`, `pkb_datapath`, `pkb_callpath`, `pkb_componentpath`, `pkb_configpath`) may still inherit; after the run, read `pkb_run_profile` from `run-catalog.json` / `run-index.json` / `summary.json` as the complete resolved list, including inherited glue/features and the integer `pkb_parallel`. Do not assume omitted keys equal project pickleball.properties. This is not a skip of Workbench; it is how you find failures. Do not start a worker just to run the whole suite. After the run, open `run-catalog.json`, then only the relevant `run-index.json` / `summary.json`. If `workbench_*` tools already exist, you may instead read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary`. Do not ingest `events.jsonl` or screenshots to find which scenarios failed. Do not `workbench_worker_start` to read the catalog. 2. **Isolate / debug a known failing scenario** — after discovery has named the trouble spots, isolate those scenarios with live Workbench. If `workbench_*` tools are already present, use the live MCP loop below. If they are absent, do not invent IDE MCP and do not start an IDE-owned stdio server. Stop and tell the human that live isolate needs either pre-wired `workbench_*` MCP tools or a Workbench CLI session. Do not keep using `mvn test` for isolation/debug. -3. **Confirm after isolation** — one bounded diagnostic `mvn test` with the existing AI defaults (`pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, narrowest useful `pkb_tags`/`pkb_name`). +3. **Confirm after isolation** — one bounded diagnostic `mvn test` with the same complete AI `pkb_runvars` (`pkb_browser=CHROME_HEADLESS`, high `pkb_parallel` when more than one scenario, `pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, narrowest useful `pkb_tags`/`pkb_name`). 4. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. Change the project's own features/Java only after the live buffer is right. Explicit Save is what writes a `.feature` file. Discover must work with zero MCP. Missing `workbench_*` tools is not a reason to skip Discover, and it is not a reason to self-register IDE MCP. A multi-scenario diagnostic test is a reason to run `mvn test` first; do not start a worker to run the whole suite. @@ -146,22 +146,26 @@ Never supply `pkb_run_profile` or `pkb_run_profile.` as input. They are When you launch Pickleball tests and the intended execution settings are known, use `pkb_runvars` as the authoritative input. Put intentional tag/name selection, browser, evidence/logging controls, and other non-secret RunVar changes inside `pkb_runvars`; do not default to ambient optional project settings or separate JVM `-Dpkb_*` RunVars. Use `pkb_profile` or ordinary JVM RunVar overrides only when the task specifically tests those configuration semantics or the user asks for them. Keep protected secrets and diagnostic lineage outside `pkb_runvars`. -For an agent's bounded confirmation `mvn test` (not the human runner defaults), include diagnostic evidence controls and keep the selection narrow. Documented AI defaults: +For an agent's bounded confirmation `mvn test` (not the human runner defaults), include diagnostic evidence controls, headless Chrome, and high parallelism when more than one scenario will run. Documented AI Discover/Confirm `pkb_runvars`: ```text +pkb_browser=CHROME_HEADLESS +pkb_parallel= pkb_reportingmode=diagnostic pkb_loglevel=warn 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. +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. `DiagnosticCli discover-hint` prints the estimated integer `pkb_parallel` for the current 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`). Example confirmation after a live-loop isolation: +These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after a live-loop isolation: ```text -mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" +mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_parallel=auto, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" ``` +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`. + A selected profile or partial `pkb_runvars` input inherits only missing project execution-context RunVars: ```text @@ -191,9 +195,9 @@ For AI-controlled diagnostic runs, keep terminal logging minimal. Diagnostic mod Use this escalation order: -1. `run-catalog.json` to choose relevant runs. -2. Selected `run-index.json` and `clusters.json` for outcomes, scenario identity, failure grouping, capabilities, retention, step rollups, profile fingerprints, and representative visual references. -3. Selected scenario `summary.json` when additional sparse detail is needed. +1. `run-catalog.json` to choose relevant runs. Each catalog entry includes the retained `pkb_run_profile` when present. +2. Selected `run-index.json` and `clusters.json` for outcomes, scenario identity, failure grouping, capabilities, retention, step rollups, the complete `runProfile`, profile fingerprints, and representative visual references. +3. Selected scenario `summary.json` when additional sparse detail is needed, including the same `runProfile`. 4. Relevant `events.jsonl` only when exact step/lifecycle/order/INFO+ detail remains unanswered. 5. Existing `comparisonToPrevious` or Pickleball run/fingerprint comparison before opening screenshots. 6. A representative PNG only when semantic visual meaning must be understood. @@ -232,7 +236,7 @@ DiagnosticCli rebuild `DiagnosticCli help`, `--help`, and `-h` print this same command list. -Use `guidance` to print this guide, `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, and `discover-hint` for the diagnostic `mvn test` one-liner plus `run-catalog.json` next step. Prefer `DiagnosticCli` over constructing Maven classpaths and JShell scripts for routine diagnostic operations. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. +Use `guidance` to print this guide, `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, and `discover-hint` for the complete diagnostic `mvn test` `pkb_runvars` one-liner (headless Chrome, estimated `pkb_parallel`, diagnostic evidence controls) plus `run-catalog.json` / `pkb_run_profile` next step. Prefer `DiagnosticCli` over constructing Maven classpaths and JShell scripts for routine diagnostic operations. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. ## Controlled diagnostic reruns 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 419166f9..8c1f7678 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 @@ -15,13 +15,13 @@ This file maps consumer-visible capabilities to implementation anchors, executab | Direct service-call control/evidence | `ServiceCallControl.java`; `ServiceCallEvidence.java`; `BoundedJsonEvidence.java`; `ServiceCallSteps.java`; `ModularScenarios.java`; `RestAssuredUtil.java`; bridge `/v1/services/call`; Workbench `workbench_service_call` | `@control-bridge`; existing service-call features | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md`; `docs/service-call-scenarios.md` | | Semantic breakpoints | `ControlBridgeCoordinator.java`; `ControlBridgeBreakpoint.java`; `ControlHook.java`; bridge `/v1/breakpoints*`; Workbench `workbench_breakpoint_*` | `@control-bridge`; Workbench UI/MCP tests and `live-check` | `docs/dynamic-control-api.md`; `docs/pickleball-workbench.md` | | Dynamic steps/expression execution | `DynamicSteps.java`; `DynamicExecution.java`; `StepExtension.java`; tree-parsing classes | `dynamic-steps.feature`; `forms-dynamic-steps.feature` | `docs/dynamic-steps.md` | -| Selenium navigation and interaction | `BrowserSteps.java`; `NavigationSteps.java`; `ElementWrapper.java`; `HumanInteractions.java`; `SeleniumUtils.java` | `navigation.feature`; `forms-dynamic-steps.feature`; `dialogs.feature`; browser test-site pages | `docs/dynamic-steps.md`; `docs/custom-element-definitions.md` | +| Selenium navigation and interaction | `BrowserSteps.java`; `NavigationSteps.java`; `ElementWrapper.java`; `HumanInteractions.java`; `SeleniumUtils.java`; `DriverConstruction.java`; bundled `META-INF/pickleball/configs/CHROME_HEADLESS.yaml`; `ParsingMap.initializeConfigs` fallback | `navigation.feature`; `forms-dynamic-steps.feature`; `dialogs.feature`; `ChromeHeadlessConfigChecks.java`; browser test-site pages | `docs/dynamic-steps.md`; `docs/custom-element-definitions.md`; `docs/configuration.md`; `docs/config-files-and-resource-mapping.md` | | Custom element definitions/catalog context | `ExecutionDictionary.java`; `ElementMatch.java`; consumer `PickleballTests.java`; search `category(`, `inheritsFrom` | `catalog-context.feature`; `forms-dynamic-steps.feature`; `site/catalog.html` | `docs/custom-element-definitions.md`; `docs/config-files-and-resource-mapping.md` | | Mapping, ParsingMap/NodeMap, templates/directives | `MappingSteps.java`; `FileAndDataParsing.java`; `MappingProcessor.java`; `QuoteParser.java`; `NodeMap.java`; `ParsingMap.java`; `ValueFormatting.java`; `common/dataelements` | `mapping-and-resources.feature`; `mapping-value-type-preservation.feature`; `scenario-data-references.feature`; Data Element features; `QuoteParserChecks.java`; internal Java checks | `docs/mapping-and-templating.md`; `docs/data-values-and-elements.md`; `docs/data-element-query-runtime.md`; `docs/config-files-and-resource-mapping.md` | -| Configuration/profiles/RunVars | `PKB_props.java`; `PickleballProfiles.java`; `PkbPropertyValueNormalizer.java`; runner/config classes; search `pkb_profile`, `pkb_runvars`, `pkb_run_profile`, `pkb_configpath` | `configuration-system-properties.feature`; `ProfileConfigurationChecks.java`; consumer properties/profile examples | `docs/configuration.md`; `docs/getting-started.md`; `docs/ai-run-configuration.md`; `docs/consumer-project.md` | +| Configuration/profiles/RunVars | `PKB_props.java`; `PickleballProfiles.java`; `PkbPropertyValueNormalizer.java`; `ParallelCountEstimator.java`; runner/config classes; search `pkb_profile`, `pkb_runvars`, `pkb_run_profile`, `pkb_configpath`, `pkb_parallel=auto` | `configuration-system-properties.feature`; `ProfileConfigurationChecks.java`; `ParallelCountEstimatorChecks.java`; consumer properties/profile examples | `docs/configuration.md`; `docs/getting-started.md`; `docs/ai-run-configuration.md`; `docs/consumer-project.md` | | Consumer guidance export/reference snapshot | `DiagnosticCli.java`; `gradle/consumer-guidance.gradle`; `scripts/sync_consumer_guidance.py`; `maven-consumer-project/AGENTS.md`; `maven-consumer-project/.github/copilot-instructions.md`; search `export-guidance`, `discover-hint`, `GUIDANCE-MANIFEST.json`, `.pickleball/investigations` | `PickleballGuidanceChecks.java`; consumer guidance contract checks | `docs/consumer-agent-guide.md`; `docs/consumer-project.md` | | Consumer AI-agent pointer-eval harness | `maven-consumer-project/src/test/resources/features/agent-pointer-eval.feature`; tag `@agent-pointer-eval` (optional `@agent-pointer-eval-pass` / `@agent-pointer-eval-fail`) | opt-in mixed pass/fail; must not carry `@all`, `@regression`, or other Maven suite-profile tags; `scripts/verify_agent_contract.py` | `docs/consumer-agent-guide.md` | -| Diagnostic reporting and controlled reruns | `src/main/java/tools/dscode/common/reporting/diagnostic`; `InvestigationHandoff`; diagnostic aspects; `DiagnosticCli.java`; `emit-investigation`; `VisualFingerprintComparator.java`; `DiagnosticRunComparator.java` | `DiagnosticReportingChecks.java`; `Diagnostic213CompletionChecks.java`; `InvestigationHandoffChecks.java`; diagnostic features | `docs/diagnostic-reporting.md`; `docs/ai-diagnostic-reporting-plan.md`; `docs/ai-run-configuration.md`; root `AGENTS.md` | +| Diagnostic reporting and controlled reruns | `src/main/java/tools/dscode/common/reporting/diagnostic`; `InvestigationHandoff`; diagnostic aspects; `DiagnosticCli.java`; `emit-investigation`; `VisualFingerprintComparator.java`; `DiagnosticRunComparator.java`; catalog/summary `runProfile` | `DiagnosticReportingChecks.java`; `Diagnostic213CompletionChecks.java`; `InvestigationHandoffChecks.java`; diagnostic features | `docs/diagnostic-reporting.md`; `docs/ai-diagnostic-reporting-plan.md`; `docs/ai-run-configuration.md`; root `AGENTS.md` | | Nested steps/block conditionals | search `Nested`, `Conditional`, `Block`, `Condition` in core implementation | `nested-and-block-conditionals.feature` | `docs/nested-steps.md`; `docs/block-conditionals.md` | | Component scenarios/reusable RUN/selectors/markers | `ModularScenarios.java`; `ScenarioStep.java`; `ScenarioStepData.java`; `StepBase.java`; `StepExtension.java`; `CurrentScenarioState.java`; `CucumberScanUtil.java`; search `finalizerSteps`, `RunSelection` | `component-scenarios.feature`; `reusable-scenario-selection.feature`; `run-step-parameter-variations.feature`; marker features | `docs/component-scenarios.md`; `docs/service-call-scenarios.md`; `docs/data-values-and-elements.md` | | Service-call definitions/execution | `ServiceCallSteps.java`; `ModularScenarios.java`; `StepExtension.java`; `CurrentScenarioState.java`; `RestAssuredUtil.java`; mapping classes; `maven-consumer-project/src/test/resources/calls` | `service-call-execution.feature`; `run-step-parameter-variations.feature`; reusable selection/parameter features; local server support | `docs/service-call-scenarios.md`; `docs/component-scenarios.md`; `docs/mapping-and-templating.md` | diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/agent/repository-index.md b/src/main/resources/META-INF/pickleball/guidance/docs/agent/repository-index.md index 400252cc..7ff607f3 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/agent/repository-index.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/agent/repository-index.md @@ -366,6 +366,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `src/main/java/tools/dscode/cucumberextended/utilities/StringUtilities.java` - `src/main/java/tools/dscode/launcher/PickleballWorkbenchLauncher.java` - `src/main/java/tools/dscode/misc/DummySteps.java` +- `src/main/java/tools/dscode/parallelutilities/ParallelCountEstimator.java` - `src/main/java/tools/dscode/parallelutilities/Stagger.java` - `src/main/java/tools/dscode/pickleruntime/CucumberOptionResolver.java` - `src/main/java/tools/dscode/registry/GlobalRegistry.java` @@ -382,6 +383,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `src/main/java/tools/dscode/testengine/PkbPropertyValueNormalizer.java` - `src/main/java/tools/dscode/testengine/SensitiveConfiguration.java` - `src/main/java/tools/dscode/testengine/WorkbenchWorkerMain.java` +- `src/main/resources/META-INF/pickleball/configs/CHROME_HEADLESS.yaml` - `src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine` - `src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener` @@ -389,6 +391,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `src/test/java/tools/dscode/control/override/StepOverrideCompilerTest.java` - `src/test/java/tools/dscode/launcher/PickleballWorkbenchLauncherTest.java` +- `src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java` - `src/test/java/tools/dscode/testengine/DynamicSuiteBootstrapWorkbenchRootTest.java` ## Control API module @@ -589,6 +592,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `maven-consumer-project/src/test/java/tools/dscode/common/dataelements/DataElementPhaseOneChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/dataelements/DataElementPhaseThreeChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/dataelements/DataElementPhaseTwoChecks.java` +- `maven-consumer-project/src/test/java/tools/dscode/common/driver/ChromeHeadlessConfigChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/mappings/MappingDataRefactorChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/mappings/QuoteParserChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/common/reporting/diagnostic/Diagnostic213CompletionChecks.java` @@ -599,6 +603,7 @@ This inventory helps coding agents discover relevant files. It does not replace - `maven-consumer-project/src/test/java/tools/dscode/common/util/datetime/BusinessTimePostModifierChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/coredefinitions/DataTableConversionChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/coredefinitions/ModularScenariosChecks.java` +- `maven-consumer-project/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/testengine/PkbPropertyValueNormalizerChecks.java` - `maven-consumer-project/src/test/java/tools/dscode/testengine/ProfileConfigurationChecks.java` 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 109a9fda..6201a14e 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 @@ -243,10 +243,10 @@ Example compact rerun: -Dpkb_changed_variables=pkb_browser ``` -For an agent's bounded confirmation `mvn test` (not `PickleballTests` human defaults of `pretty` / `@all`), include diagnostic evidence controls and keep selection narrow: +For an agent's bounded confirmation `mvn test` (not `PickleballTests` human defaults of `pretty` / `@all`), include diagnostic evidence controls, headless Chrome, and high parallelism when more than one scenario will run: ```text --Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" +-Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_parallel=auto, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" ``` Lineage metadata is not execution configuration: @@ -320,3 +320,21 @@ When operating in a consumer project: - never expose protected values; - keep diagnostic lineage outside the RunVar set; - prefer the retained run profile over manually reconstructing configuration from many source layers. + +## AI agents + +Set a **complete** Discover `pkb_runvars` rather than a partial overlay: + +```text +pkb_browser=CHROME_HEADLESS +pkb_parallel= +pkb_reportingmode=diagnostic +pkb_loglevel=warn +pkb_reportretention=failed +``` + +plus the narrowest useful `pkb_tags` / `pkb_name`. `DiagnosticCli discover-hint` prints the estimated integer parallel count for the current JVM. Multi-scenario Discover/Confirm must use headless Chrome and high parallelism. Isolate / live Workbench stays one paused scenario. + +After the run, inspect `pkb_run_profile` from `run-catalog.json`, `run-index.json`, or `summary.json`. That output is the complete resolved RunVar list after inheritance (glue/features/data/call/component/configpath) and after `pkb_parallel=auto` is stamped as an integer. Do not assume omitted `pkb_runvars` keys equal project `pickleball.properties` — optional keys such as headed Chrome, `pretty`, and `pkb_tags=@all` do not leak into a controlled run, which is why agents must set the Discover keys explicitly. + +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/config-files-and-resource-mapping.md b/src/main/resources/META-INF/pickleball/guidance/docs/config-files-and-resource-mapping.md index 3478dd27..9b3be1b1 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/config-files-and-resource-mapping.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/config-files-and-resource-mapping.md @@ -51,6 +51,10 @@ run: pkb_runvars=pkb_configpath=,pkb_browser=firefox The controlled run uses the default `configs` resource root because the blank value intentionally suppresses the project path. +## Bundled browser configs + +Named browser yaml files under the configured config path remain the local override, including headed `CHROME.yaml`. When `CHROME_HEADLESS` is absent from that mapping, Pickleball fills it from the JAR resource `META-INF/pickleball/configs/CHROME_HEADLESS.yaml` so agents can set `pkb_browser=CHROME_HEADLESS` without copying yaml. See [Execution Configuration](configuration.md). + ## Initialization order Run configuration is resolved before the final config source is bound: diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/configuration.md b/src/main/resources/META-INF/pickleball/guidance/docs/configuration.md index f11b2e96..456c4a5c 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/configuration.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/configuration.md @@ -329,12 +329,12 @@ The existing path semantics for `pkb_features`, `pkb_datapath`, `pkb_callpath`, | `pkb_tags` | `@smoke and not @slow` | Cucumber tag expression | | `pkb_name` | `Checkout.*` | scenario-name expression | | `pkb_environment` | `QA` | project environment label | -| `pkb_browser` | `chrome` | browser configuration | +| `pkb_browser` | `chrome` | browser configuration name looked up under the `configs` mapping (`CHROME_HEADLESS` uses the consumer yaml when present, otherwise Pickleball's bundled headless Chrome) | | `pkb_profile` | `qa,browser_firefox` | selected named profile(s) | | `pkb_runvars` | `pkb_tags=@smoke, pkb_browser=chrome` | compact controlled RunVar input | | `pkb_runvars.` | `pkb_runvars.pkb_browser=chrome` | expanded controlled RunVar member | | `pkb_run_profile` | generated assignment string | canonical resolved RunVar output; external input rejected | -| `pkb_parallel` | `4` | parallel scenario count | +| `pkb_parallel` | `4`, `auto` | parallel scenario count; `auto` resolves at run start to a conservative JVM estimate and stamps the integer into `pkb_run_profile` | | `pkb_loglevel` | `debug` | console log level | | `pkb_reportingmode` | `diagnostic` | diagnostic evidence pipeline | | `pkb_reportretention` | `all`, `failed`, `none` | automatic evidence/report retention | @@ -344,6 +344,29 @@ The existing path semantics for `pkb_features`, `pkb_datapath`, `pkb_callpath`, Other existing `pkb_*` RunVars retain their previous behavior unless specifically documented otherwise. +## Conservative `pkb_parallel` + +`pkb_parallel` is an explicit positive integer unless the value is `auto`. + +`auto` is resolved at run start from JVM-visible resources only (`Runtime.availableProcessors()` and `Runtime.maxMemory()`). No OS-specific native calls. The conservative estimate is: + +```text +max(2, min(availableProcessors, floor(maxMemoryMB / 512), 24)) +``` + +Chrome workers are RAM-heavy, so a 32-core / 64GiB box does not blindly pick 32 workers; the hard cap is 24, and heap can cap lower. Tiny heaps resolve to 2. An explicit numeric `pkb_parallel` is never overwritten. Omitting `pkb_parallel` does not enable parallel execution. + +The resolved integer is stamped into the final RunVars and `pkb_run_profile`. `DiagnosticCli discover-hint` prints that estimated number in the recommended Discover `pkb_runvars` command. + +## Bundled `CHROME_HEADLESS` + +`pkb_browser` names a configuration object under the loaded `configs` mapping. Resolution for `CHROME_HEADLESS`: + +1. If the consumer `pkb_configpath` / configs mapping already contains `CHROME_HEADLESS` (or a case-insensitive named browser yaml such as `CHROME_HEADLESS.yaml`), that local override wins, including headed chrome.yaml-style configs. +2. Otherwise Pickleball injects a framework-bundled `CHROME_HEADLESS` resource from `META-INF/pickleball/configs/CHROME_HEADLESS.yaml` inside the Pickleball JAR. + +The bundled headless config uses `--headless=new`, a fixed `--window-size=1920,1080`, no `MAXIMIZE`, and `QUIT_LOCAL_DRIVER`. Consumer `CHROME`, `EDGE`, `GRID`, and `SAUCE` yaml files are unchanged. Agents can set `pkb_browser=CHROME_HEADLESS` without copying yaml into the project. + ## Cucumber aliases Pickleball synchronizes its main selection aliases with Cucumber properties, including: 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 ea29adbc..467090e8 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 @@ -14,9 +14,9 @@ If `workbench_*` tools are already present in this session, you may use them as Use this order. Consumer AI agents for this Pickleball release discover failures with diagnostic `mvn test`. Live Workbench MCP is an optional alias when `workbench_*` tools are already connected. Do not start the Workbench GUI. -1. **Discover** — when you do not yet know which scenarios fail, or you need many/parallel scenarios: run one diagnostic `mvn test` with `pkb_runvars` (`pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, the narrowest useful `pkb_tags`/`pkb_name`, and `pkb_parallel` when the project supports it). This is not a skip of Workbench; it is how you find failures. Do not start a worker just to run the whole suite. After the run, open `run-catalog.json`, then only the relevant `run-index.json` / `summary.json`. If `workbench_*` tools already exist, you may instead read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary`. Do not ingest `events.jsonl` or screenshots to find which scenarios failed. Do not `workbench_worker_start` to read the catalog. +1. **Discover** — when you do not yet know which scenarios fail, or you need many/parallel scenarios: run one diagnostic `mvn test` with a **complete** `pkb_runvars` that always includes `pkb_browser=CHROME_HEADLESS`, a high `pkb_parallel` (the conservative JVM estimate printed by `discover-hint`, or `pkb_parallel=auto`), `pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, plus the narrowest useful `pkb_tags`/`pkb_name`. Multi-scenario Discover and Confirm MUST use headless Chrome and high parallelism. Isolate / live Workbench stays one paused scenario; do not parallelize isolate. Use headed Chrome only when the scenario actually needs a window. Set those keys explicitly so project headed-chrome / pretty / `@all` defaults do not sneak in. Execution-context keys (`pkb_glue`, `pkb_features`, `pkb_datapath`, `pkb_callpath`, `pkb_componentpath`, `pkb_configpath`) may still inherit; after the run, read `pkb_run_profile` from `run-catalog.json` / `run-index.json` / `summary.json` as the complete resolved list, including inherited glue/features and the integer `pkb_parallel`. Do not assume omitted keys equal project pickleball.properties. This is not a skip of Workbench; it is how you find failures. Do not start a worker just to run the whole suite. After the run, open `run-catalog.json`, then only the relevant `run-index.json` / `summary.json`. If `workbench_*` tools already exist, you may instead read the pack with `workbench_diagnostic_catalog`, `workbench_diagnostic_run`, and `workbench_diagnostic_summary`. Do not ingest `events.jsonl` or screenshots to find which scenarios failed. Do not `workbench_worker_start` to read the catalog. 2. **Isolate / debug a known failing scenario** — after discovery has named the trouble spots, isolate those scenarios with live Workbench. If `workbench_*` tools are already present, use the live MCP loop below. If they are absent, do not invent IDE MCP and do not start an IDE-owned stdio server. Stop and tell the human that live isolate needs either pre-wired `workbench_*` MCP tools or a Workbench CLI session. Do not keep using `mvn test` for isolation/debug. -3. **Confirm after isolation** — one bounded diagnostic `mvn test` with the existing AI defaults (`pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, narrowest useful `pkb_tags`/`pkb_name`). +3. **Confirm after isolation** — one bounded diagnostic `mvn test` with the same complete AI `pkb_runvars` (`pkb_browser=CHROME_HEADLESS`, high `pkb_parallel` when more than one scenario, `pkb_reportingmode=diagnostic`, `pkb_loglevel=warn`, `pkb_reportretention=failed`, narrowest useful `pkb_tags`/`pkb_name`). 4. **Emit the human handoff, then edit real consumer source** — write `.pickleball/investigations//` then in chat print only `.pickleball/investigations//report.html`. Change the project's own features/Java only after the live buffer is right. Explicit Save is what writes a `.feature` file. Discover must work with zero MCP. Missing `workbench_*` tools is not a reason to skip Discover, and it is not a reason to self-register IDE MCP. A multi-scenario diagnostic test is a reason to run `mvn test` first; do not start a worker to run the whole suite. @@ -146,22 +146,26 @@ Never supply `pkb_run_profile` or `pkb_run_profile.` as input. They are When you launch Pickleball tests and the intended execution settings are known, use `pkb_runvars` as the authoritative input. Put intentional tag/name selection, browser, evidence/logging controls, and other non-secret RunVar changes inside `pkb_runvars`; do not default to ambient optional project settings or separate JVM `-Dpkb_*` RunVars. Use `pkb_profile` or ordinary JVM RunVar overrides only when the task specifically tests those configuration semantics or the user asks for them. Keep protected secrets and diagnostic lineage outside `pkb_runvars`. -For an agent's bounded confirmation `mvn test` (not the human runner defaults), include diagnostic evidence controls and keep the selection narrow. Documented AI defaults: +For an agent's bounded confirmation `mvn test` (not the human runner defaults), include diagnostic evidence controls, headless Chrome, and high parallelism when more than one scenario will run. Documented AI Discover/Confirm `pkb_runvars`: ```text +pkb_browser=CHROME_HEADLESS +pkb_parallel= pkb_reportingmode=diagnostic pkb_loglevel=warn 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. +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. `DiagnosticCli discover-hint` prints the estimated integer `pkb_parallel` for the current 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`). Example confirmation after a live-loop isolation: +These are documented agent defaults, not `PickleballTests` human defaults (`pretty`, `@all`, often headed Chrome). Example confirmation after a live-loop isolation: ```text -mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" +mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_parallel=auto, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" ``` +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`. + A selected profile or partial `pkb_runvars` input inherits only missing project execution-context RunVars: ```text @@ -191,9 +195,9 @@ For AI-controlled diagnostic runs, keep terminal logging minimal. Diagnostic mod Use this escalation order: -1. `run-catalog.json` to choose relevant runs. -2. Selected `run-index.json` and `clusters.json` for outcomes, scenario identity, failure grouping, capabilities, retention, step rollups, profile fingerprints, and representative visual references. -3. Selected scenario `summary.json` when additional sparse detail is needed. +1. `run-catalog.json` to choose relevant runs. Each catalog entry includes the retained `pkb_run_profile` when present. +2. Selected `run-index.json` and `clusters.json` for outcomes, scenario identity, failure grouping, capabilities, retention, step rollups, the complete `runProfile`, profile fingerprints, and representative visual references. +3. Selected scenario `summary.json` when additional sparse detail is needed, including the same `runProfile`. 4. Relevant `events.jsonl` only when exact step/lifecycle/order/INFO+ detail remains unanswered. 5. Existing `comparisonToPrevious` or Pickleball run/fingerprint comparison before opening screenshots. 6. A representative PNG only when semantic visual meaning must be understood. @@ -232,7 +236,7 @@ DiagnosticCli rebuild `DiagnosticCli help`, `--help`, and `-h` print this same command list. -Use `guidance` to print this guide, `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, and `discover-hint` for the diagnostic `mvn test` one-liner plus `run-catalog.json` next step. Prefer `DiagnosticCli` over constructing Maven classpaths and JShell scripts for routine diagnostic operations. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. +Use `guidance` to print this guide, `export-guidance` to materialize the complete version-matched documentation plus curated Maven consumer reference, and `discover-hint` for the complete diagnostic `mvn test` `pkb_runvars` one-liner (headless Chrome, estimated `pkb_parallel`, diagnostic evidence controls) plus `run-catalog.json` / `pkb_run_profile` next step. Prefer `DiagnosticCli` over constructing Maven classpaths and JShell scripts for routine diagnostic operations. `emit-investigation` writes `.pickleball/investigations//investigation.json` and `report.html` and prints the relative HTML path. ## Controlled diagnostic reruns 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 3648b4eb..befba07c 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 @@ -155,9 +155,11 @@ mvn test -Dpkb_tags="@workflow and @nested-steps and not @block-conditionals" Human `PickleballTests` defaults remain `pretty` and `@all`. Agents launching a bounded confirmation should not reuse those defaults. Use a separate `pkb_runvars` command, for example: ```bash -mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" +mvn test -Dpkb_runvars="pkb_tags=@the-failing-tag, pkb_name=The failing scenario, pkb_browser=CHROME_HEADLESS, pkb_parallel=auto, pkb_reportingmode=diagnostic, pkb_loglevel=warn, pkb_reportretention=failed" ``` +After the run, read `pkb_run_profile` from `run-catalog.json` / `run-index.json` / `summary.json`. That is the complete resolved RunVar list. Do not assume omitted `pkb_runvars` keys equal project `pickleball.properties`. + The consumer `pom.xml` also defines Maven profiles such as: ```bash diff --git a/src/main/resources/META-INF/pickleball/guidance/docs/diagnostic-reporting.md b/src/main/resources/META-INF/pickleball/guidance/docs/diagnostic-reporting.md index 85da2c10..449fd205 100644 --- a/src/main/resources/META-INF/pickleball/guidance/docs/diagnostic-reporting.md +++ b/src/main/resources/META-INF/pickleball/guidance/docs/diagnostic-reporting.md @@ -52,15 +52,15 @@ reports/diagnostic-runs/ An interrupted scenario may retain raw `trace.jsonl` instead of the gzip form. `pkb_diagnostic_output` may move the diagnostic-runs root. -`run-index.json` includes sparse scenario/outcome/comparison metadata plus the sanitized final `runProfile`, deterministic `runProfileFingerprint`, and compatibility field `directRunProfile`. The field name `directRunProfile` remains for diagnostic schema compatibility even though new direct controlled input is `pkb_runvars`. +`run-index.json` includes sparse scenario/outcome/comparison metadata plus the sanitized final `runProfile`, deterministic `runProfileFingerprint`, and compatibility field `directRunProfile`. The field name `directRunProfile` remains for diagnostic schema compatibility even though new direct controlled input is `pkb_runvars`. `run-catalog.json` copies each run's `runProfile` / `runProfileFingerprint` when present. Scenario `summary.json` also includes the same `runProfile` so agents can inspect the complete resolved RunVar snapshot without opening `configuration.json`. ## AI evidence access protocol Use the shallowest evidence layer that completely answers the question: -1. `run-catalog.json` — choose candidate runs. -2. Selected `run-index.json` / `clusters.json` — outcomes, identities, failure groups, capabilities, retention, profile fingerprints, representative visuals. -3. Selected scenario `summary.json` — additional sparse detail. +1. `run-catalog.json` — choose candidate runs. Catalog entries include the retained `runProfile` when present. +2. Selected `run-index.json` / `clusters.json` — outcomes, identities, failure groups, capabilities, retention, the complete `runProfile`, profile fingerprints, representative visuals. +3. Selected scenario `summary.json` — additional sparse detail, including the same `runProfile`. 4. Targeted `events.jsonl` — exact step/lifecycle/order/INFO+ detail only when needed. 5. Existing `comparisonToPrevious`, run comparison, or fingerprint comparison. 6. Representative PNG only when semantic visual meaning matters. 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 1d979f06..128dea62 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 @@ -410,7 +410,7 @@ workbench_step_override_remove workbench_step_override_clear ``` -Sparse diagnostic readers (do not glob `reports/diagnostic-runs`; these return JSON only and do not dump events, traces, or PNG bytes): +Sparse diagnostic readers (do not glob `reports/diagnostic-runs`; these return JSON only and do not dump events, traces, or PNG bytes). Catalog, run-index, and summary payloads include the retained `pkb_run_profile` when present: ```text workbench_diagnostic_catalog diff --git a/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java b/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java new file mode 100644 index 00000000..9fb907f3 --- /dev/null +++ b/src/test/java/tools/dscode/parallelutilities/ParallelCountEstimatorTest.java @@ -0,0 +1,61 @@ +package tools.dscode.parallelutilities; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ParallelCountEstimatorTest { + @Test + void tinyHeapYieldsMinimumWorkers() { + assertEquals(2, ParallelCountEstimator.estimate(8, 256L * 1024 * 1024)); + assertEquals(2, ParallelCountEstimator.estimate(32, 512L * 1024 * 1024)); + } + + @Test + void manyCoresAndLargeHeapAreCapped() { + long sixtyFourGib = 64L * 1024 * 1024 * 1024; + assertEquals(24, ParallelCountEstimator.estimate(32, sixtyFourGib)); + assertEquals(24, ParallelCountEstimator.estimate(128, sixtyFourGib)); + } + + @Test + void memoryCanCapBelowCoreCountBeforeTheHardCap() { + assertEquals(4, ParallelCountEstimator.estimate(16, 2048L * 1024 * 1024)); + } + + @Test + void explicitNumericIsNotOverwritten() { + assertEquals(7, ParallelCountEstimator.resolve("7")); + assertEquals(1, ParallelCountEstimator.resolve("1")); + } + + @Test + void autoResolvesToTheLiveEstimate() { + assertEquals(ParallelCountEstimator.estimate(), ParallelCountEstimator.resolve("auto")); + assertEquals(ParallelCountEstimator.estimate(), ParallelCountEstimator.resolve("AUTO")); + assertTrue(ParallelCountEstimator.isAuto(" auto ")); + assertFalse(ParallelCountEstimator.isAuto("8")); + } + + @Test + void invalidParallelValuesFailClearly() { + assertThrows(IllegalArgumentException.class, () -> ParallelCountEstimator.resolve("nope")); + assertThrows(IllegalArgumentException.class, () -> ParallelCountEstimator.resolve("0")); + assertThrows(IllegalArgumentException.class, () -> ParallelCountEstimator.resolve("-3")); + } + + @Test + void recommendedDiscoverRunVarsIncludeEstimatedParallel() { + String runVars = ParallelCountEstimator.recommendedDiscoverRunVars(); + assertTrue(runVars.contains("pkb_browser=CHROME_HEADLESS")); + assertTrue(runVars.contains("pkb_parallel=" + ParallelCountEstimator.estimate())); + assertTrue(runVars.contains("pkb_reportingmode=diagnostic")); + assertTrue(runVars.contains("pkb_loglevel=warn")); + assertTrue(runVars.contains("pkb_reportretention=failed")); + assertFalse(runVars.contains("pkb_parallel=80")); + assertFalse(runVars.contains("pkb_parallel=auto")); + } +}